diff --git a/.changeset/config.json b/.changeset/config.json index bfd12d278..4f8345f46 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -7,5 +7,5 @@ "access": "restricted", "baseBranch": "master", "updateInternalDependencies": "patch", - "ignore": [] + "ignore": ["@0xsequence/wallet-primitives-cli", "docs", "web"] } diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml new file mode 100644 index 000000000..5f540c130 --- /dev/null +++ b/.github/workflows/claude.yml @@ -0,0 +1,49 @@ +name: Claude Code + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + +jobs: + claude: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + actions: read # Required for Claude to read CI results on PRs + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code + id: claude + uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY_GITHUB_ACTIONS }} + + # This is an optional setting that allows Claude to read CI results on PRs + additional_permissions: | + actions: read + + # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. + # prompt: 'Update the pull request description to include a summary of changes.' + + # Optional: Add claude_args to customize behavior and configuration + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options + # claude_args: '--allowed-tools Bash(gh pr:*)' diff --git a/.github/workflows/publish-dists.yml b/.github/workflows/publish-dists.yml deleted file mode 100644 index fd4bb7905..000000000 --- a/.github/workflows/publish-dists.yml +++ /dev/null @@ -1,88 +0,0 @@ -name: Publish Dists for Packages - -on: - workflow_dispatch: - push: - branches: - - master - -jobs: - build-and-push: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - uses: ./.github/actions/install-dependencies - - - name: Build package - run: pnpm run build - - - name: Prepare dist branch - run: | - PACKAGES=("services/guard" "services/identity-instrument" "services/relayer" "wallet/core" "wallet/primitives" "wallet/wdk" "wallet/dapp-client") - - for PACKAGE in "${PACKAGES[@]}"; do - BRANCH="dists/$PACKAGE" - PKG_DIR="packages/$PACKAGE" - - echo "šŸ“¦ Publishing $PACKAGE to $BRANCH" - - mkdir -p /tmp/$PACKAGE - shopt -s dotglob - cp -r $PKG_DIR/* /tmp/$PACKAGE || true - - cd /tmp/$PACKAGE - git init - git checkout -b $BRANCH - - git config user.name "github-actions" - git config user.email "actions@github.com" - - echo "šŸ”§ Rewriting workspace: deps in package.json..." - node -e ' - const fs = require("fs"); - const path = require("path"); - const pkgPath = path.resolve("package.json"); - const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")); - const repo = "github:0xsequence/sequence.js"; - - const versions = { - "@0xsequence/guard": `${repo}#dists/services/guard`, - "@0xsequence/identity-instrument": `${repo}#dists/services/identity-instrument`, - "@0xsequence/relayer": `${repo}#dists/services/relayer`, - "@0xsequence/wallet-core": `${repo}#dists/wallet/core`, - "@0xsequence/wallet-primitives": `${repo}#dists/wallet/primitives`, - "@0xsequence/wallet-wdk": `${repo}#dists/wallet/wdk`, - }; - - const rewrite = (deps = {}) => { - for (const k in deps) { - if (deps[k].startsWith("workspace:")) { - const version = versions[k]; - - if (!version) { - console.warn(`No version found for ${k}, skipping...`); - continue; - } - - deps[k] = version; - console.log(`→ ${k} → ${deps[k]}`); - } - } - }; - - rewrite(pkg.dependencies); - fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2)); - ' - - git add . - git commit -m "Build: publish $PACKAGE dist" - - git remote add origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git - git push -f origin HEAD:$BRANCH - - cd - - done diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 62fc357db..bb22f4c72 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,20 +19,23 @@ jobs: - uses: ./.github/actions/install-dependencies - run: pnpm clean - run: pnpm build + - run: pnpm typecheck + - run: pnpm lint tests: name: Run all tests runs-on: ubuntu-latest - needs: [install] + needs: [build] steps: - uses: actions/checkout@v4 - uses: ./.github/actions/install-dependencies - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: - version: nightly + version: v1.5.0 - name: Start Anvil in background run: anvil --fork-url https://nodes.sequence.app/arbitrum & + - run: pnpm build - run: pnpm test # NOTE: if you'd like to see example of how to run diff --git a/README.md b/README.md index 4c663ccf8..ae41ffdbd 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Sequence v3 core libraries and [wallet-contracts-v3](https://github.com/0xsequen - Run tests: `pnpm test` - > **Note:** Tests require [anvil](https://github.com/foundry-rs/foundry/tree/master/anvil) and [forge](https://github.com/foundry-rs/foundry) to be installed. You can run a local anvil instance using `pnpm run test:anvil`. + > **Note:** Tests require [anvil](https://github.com/foundry-rs/foundry/tree/master/crates/anvil) and [forge](https://github.com/foundry-rs/foundry) to be installed. You can run a local anvil instance using `pnpm run test:anvil`. - Linting and formatting is enforced via git hooks diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..034e84803 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,21 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | Supported | +| ------- | ------------------ | +| 5.1.x | :white_check_mark: | +| 5.0.x | :x: | +| 4.0.x | :white_check_mark: | +| < 4.0 | :x: | + +## Reporting a Vulnerability + +Use this section to tell people how to report a vulnerability. + +Tell them where to go, how often they can expect to get an update on a +reported vulnerability, what to expect if the vulnerability is accepted or +declined, etc. diff --git a/extras/docs/eslint.config.js b/extras/docs/eslint.config.js index 3d2c2e9d4..0fbeffd97 100644 --- a/extras/docs/eslint.config.js +++ b/extras/docs/eslint.config.js @@ -1,4 +1,9 @@ import { nextJsConfig } from '@repo/eslint-config/next-js' /** @type {import("eslint").Linter.Config} */ -export default nextJsConfig +export default [ + ...nextJsConfig, + { + ignores: ['next-env.d.ts'], + }, +] diff --git a/extras/docs/next.config.js b/extras/docs/next.config.js index 1d6147825..2963459c4 100644 --- a/extras/docs/next.config.js +++ b/extras/docs/next.config.js @@ -1,4 +1,14 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const workspaceRoot = path.join(__dirname, '..', '..') + /** @type {import('next').NextConfig} */ -const nextConfig = {} +const nextConfig = { + // Anchor output tracing to the monorepo root so Next.js doesn't pick up + // sibling lockfiles and mis-detect the workspace boundary during lint/build. + outputFileTracingRoot: workspaceRoot, +} export default nextConfig diff --git a/extras/docs/package.json b/extras/docs/package.json index 6c2fe2de2..5b275db5b 100644 --- a/extras/docs/package.json +++ b/extras/docs/package.json @@ -7,22 +7,23 @@ "dev": "next dev --turbopack --port 3001", "build": "next build", "start": "next start", - "lint": "next lint --max-warnings 0", + "lint": "eslint . --max-warnings 0", "typecheck": "tsc --noEmit", "clean": "rimraf .next" }, "dependencies": { - "@repo/ui": "workspace:*", - "next": "^15.3.3", - "react": "^19.1.0", - "react-dom": "^19.1.0" + "@repo/ui": "workspace:^", + "next": "^15.5.16", + "react": "^19.2.3", + "react-dom": "^19.2.3" }, "devDependencies": { - "@repo/eslint-config": "workspace:*", - "@repo/typescript-config": "workspace:*", - "@types/node": "^20.17.57", - "@types/react": "18.3.1", - "@types/react-dom": "18.3.0", - "typescript": "5.5.4" + "@repo/eslint-config": "workspace:^", + "@repo/typescript-config": "workspace:^", + "@types/node": "^25.3.0", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "eslint": "^9.39.2", + "typescript": "^6.0.3" } } diff --git a/extras/docs/tsconfig.json b/extras/docs/tsconfig.json index c2fa4ee5d..7b9803267 100644 --- a/extras/docs/tsconfig.json +++ b/extras/docs/tsconfig.json @@ -7,6 +7,13 @@ } ] }, - "include": ["**/*.ts", "**/*.tsx", "next-env.d.ts", "next.config.js", ".next/types/**/*.ts"], + "include": [ + "**/*.ts", + "**/*.tsx", + "../../repo/typescript-config/next-css-side-effect.d.ts", + "next-env.d.ts", + "next.config.js", + ".next/types/**/*.ts" + ], "exclude": ["node_modules"] } diff --git a/extras/web/eslint.config.js b/extras/web/eslint.config.js index 3d2c2e9d4..0fbeffd97 100644 --- a/extras/web/eslint.config.js +++ b/extras/web/eslint.config.js @@ -1,4 +1,9 @@ import { nextJsConfig } from '@repo/eslint-config/next-js' /** @type {import("eslint").Linter.Config} */ -export default nextJsConfig +export default [ + ...nextJsConfig, + { + ignores: ['next-env.d.ts'], + }, +] diff --git a/extras/web/next.config.js b/extras/web/next.config.js index 1d6147825..2963459c4 100644 --- a/extras/web/next.config.js +++ b/extras/web/next.config.js @@ -1,4 +1,14 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const workspaceRoot = path.join(__dirname, '..', '..') + /** @type {import('next').NextConfig} */ -const nextConfig = {} +const nextConfig = { + // Anchor output tracing to the monorepo root so Next.js doesn't pick up + // sibling lockfiles and mis-detect the workspace boundary during lint/build. + outputFileTracingRoot: workspaceRoot, +} export default nextConfig diff --git a/extras/web/package.json b/extras/web/package.json index d908edc2d..e042bc8e6 100644 --- a/extras/web/package.json +++ b/extras/web/package.json @@ -7,22 +7,23 @@ "dev": "next dev --turbopack --port 3000", "build": "next build", "start": "next start", - "lint": "next lint --max-warnings 0", + "lint": "eslint . --max-warnings 0", "typecheck": "tsc --noEmit", "clean": "rimraf .next" }, "dependencies": { - "@repo/ui": "workspace:*", - "next": "^15.3.3", - "react": "^19.1.0", - "react-dom": "^19.1.0" + "@repo/ui": "workspace:^", + "next": "^15.5.16", + "react": "^19.2.3", + "react-dom": "^19.2.3" }, "devDependencies": { - "@repo/eslint-config": "workspace:*", - "@repo/typescript-config": "workspace:*", - "@types/node": "^20.17.57", - "@types/react": "18.3.1", - "@types/react-dom": "18.3.0", - "typescript": "5.5.4" + "@repo/eslint-config": "workspace:^", + "@repo/typescript-config": "workspace:^", + "@types/node": "^25.3.0", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "eslint": "^9.39.2", + "typescript": "^6.0.3" } } diff --git a/extras/web/tsconfig.json b/extras/web/tsconfig.json index c2fa4ee5d..7b9803267 100644 --- a/extras/web/tsconfig.json +++ b/extras/web/tsconfig.json @@ -7,6 +7,13 @@ } ] }, - "include": ["**/*.ts", "**/*.tsx", "next-env.d.ts", "next.config.js", ".next/types/**/*.ts"], + "include": [ + "**/*.ts", + "**/*.tsx", + "../../repo/typescript-config/next-css-side-effect.d.ts", + "next-env.d.ts", + "next.config.js", + ".next/types/**/*.ts" + ], "exclude": ["node_modules"] } diff --git a/lefthook.yml b/lefthook.yml index c66bba8e9..5402d7dc7 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -3,6 +3,15 @@ pre-commit: prettier: glob: '**/*.{js,jsx,ts,tsx,json,md,yml,yaml}' run: pnpm prettier --write {staged_files} && git add {staged_files} + lint: + run: pnpm lint + typecheck: + run: pnpm typecheck + syncpack: + glob: + - "package.json" + - "packages/**/package.json" + run: pnpm deps:lint pre-push: commands: diff --git a/packages/services/api/CHANGELOG.md b/packages/services/api/CHANGELOG.md index 28bacf399..12115dd1b 100644 --- a/packages/services/api/CHANGELOG.md +++ b/packages/services/api/CHANGELOG.md @@ -1,5 +1,216 @@ # @0xsequence/api +## 3.0.12 + +### Patch Changes + +- WDK fix + +## 3.0.11 + +### Patch Changes + +- Fix for relayer sponsored fees + +## 3.0.10 + +### Patch Changes + +- Minor network updates, relayer interface + +## 3.0.9 + +### Patch Changes + +- Fee options fixes + +## 3.0.8 + +### Patch Changes + +- Bug fix for relayer fee options handling + +## 3.0.7 + +### Patch Changes + +- Minor bug fixes + +## 3.0.6 + +### Patch Changes + +- userdata upgrade, arweave support + +## 3.0.5 + +### Patch Changes + +- Account federation support + +## 3.0.4 + +### Patch Changes + +- id-token login support + +## 3.0.3 + +### Patch Changes + +- 3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer + +## 3.0.1 + +### Patch Changes + +- Network and session fixes + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade + +## 3.0.0-beta.19 + +### Patch Changes + +- Final RC before 3.0.0 + +## 3.0.0-beta.18 + +### Patch Changes + +- multicall3 optimization + +## 3.0.0-beta.17 + +### Patch Changes + +- New chains, relayer rpc fix + +## 3.0.0-beta.16 + +### Patch Changes + +- ethauth support + +## 3.0.0-beta.15 + +### Patch Changes + +- New chains, minor fixes + +## 3.0.0-beta.14 + +### Patch Changes + +- Relayer fee options fix + +## 3.0.0-beta.13 + +### Patch Changes + +- Userdata service updates + +## 3.0.0-beta.12 + +### Patch Changes + +- Beta release with dapp connector fixes + +## 3.0.0-beta.11 + +### Patch Changes + +- 3.0.0 beta + +## 3.0.0-beta.10 + +### Patch Changes + +- dapp-client updates + +## 3.0.0-beta.9 + +### Patch Changes + +- dapp client updates for EOA login + +## 3.0.0-beta.8 + +### Patch Changes + +- Apple auth fixes + +## 3.0.0-beta.7 + +### Patch Changes + +- Apple auth fix + +## 3.0.0-beta.6 + +### Patch Changes + +- Fix signer 404 error, minor fixes + +## 3.0.0-beta.5 + +### Patch Changes + +- Beta release for v3 + +## 3.0.0-beta.4 + +### Patch Changes + +- RC5 upgrade + +## 3.0.0-beta.3 + +### Patch Changes + +- 3.0.0-beta.3 with fixes + +## 3.0.0-beta.2 + +### Patch Changes + +- 3.0.0-beta.2 with identity instrument updates + +## 3.0.0-beta.1 + +### Patch Changes + +- 3.0.0-beta.1 + ## 2.3.8 ### Patch Changes diff --git a/packages/services/api/eslint.config.js b/packages/services/api/eslint.config.js new file mode 100644 index 000000000..cecf89b03 --- /dev/null +++ b/packages/services/api/eslint.config.js @@ -0,0 +1,4 @@ +import { config as baseConfig } from "@repo/eslint-config/base" + +/** @type {import("eslint").Linter.Config} */ +export default baseConfig diff --git a/packages/services/api/package.json b/packages/services/api/package.json index bba908e69..ac1f75fcb 100644 --- a/packages/services/api/package.json +++ b/packages/services/api/package.json @@ -1,28 +1,31 @@ { "name": "@0xsequence/api", - "version": "3.0.0", + "version": "3.0.12", "description": "api sub-package for Sequence", - "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/api", - "author": "Sequence Platforms Inc.", + "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/api", + "author": "Sequence Platforms ULC", "license": "Apache-2.0", "publishConfig": { "access": "public" }, + "type": "module", "scripts": { "build": "tsc", "dev": "tsc --watch", "test": "echo", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "lint": "eslint . --max-warnings 0" }, "exports": { ".": { - "types": "./src/index.ts", + "types": "./dist/index.d.ts", "default": "./dist/index.js" } }, "devDependencies": { "@repo/typescript-config": "workspace:^", - "@types/node": "^22.15.29", - "typescript": "^5.8.3" + "@types/node": "^25.3.0", + "@repo/eslint-config": "workspace:^", + "typescript": "^6.0.3" } } diff --git a/packages/services/api/src/api.gen.ts b/packages/services/api/src/api.gen.ts index 967fad8fe..139fa541a 100644 --- a/packages/services/api/src/api.gen.ts +++ b/packages/services/api/src/api.gen.ts @@ -1,1507 +1,1241 @@ /* eslint-disable */ -// sequence-api v0.4.0 d43a5aac616814072c69e63f2f81fe65ea10a7e0 +// sequence-api v0.4.0 3c15fa79614e43a5321cd2ac0c080e80af291bd1 // -- -// Code generated by webrpc-gen@v0.25.3 with typescript generator. DO NOT EDIT. +// Code generated by Webrpc-gen@v0.31.0 with typescript generator. DO NOT EDIT. // // webrpc-gen -schema=api.ridl -target=typescript -client -out=./clients/api.gen.ts -export const WebrpcHeader = 'Webrpc' - -export const WebrpcHeaderValue = 'webrpc@v0.25.3;gen-typescript@v0.17.0;sequence-api@v0.4.0' - -// WebRPC description and code-gen version -export const WebRPCVersion = 'v1' +// Webrpc description and code-gen version +export const WebrpcVersion = 'v1' // Schema version of your RIDL schema -export const WebRPCSchemaVersion = 'v0.4.0' +export const WebrpcSchemaVersion = 'v0.4.0' // Schema hash generated from your RIDL schema -export const WebRPCSchemaHash = 'd43a5aac616814072c69e63f2f81fe65ea10a7e0' +export const WebrpcSchemaHash = '3c15fa79614e43a5321cd2ac0c080e80af291bd1' -type WebrpcGenVersions = { - webrpcGenVersion: string - codeGenName: string - codeGenVersion: string - schemaName: string - schemaVersion: string -} +// +// Client interface +// -export function VersionFromHeader(headers: Headers): WebrpcGenVersions { - const headerValue = headers.get(WebrpcHeader) - if (!headerValue) { - return { - webrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } +export interface APIClient { + /** + * + * Runtime + * + */ + ping(headers?: object, signal?: AbortSignal): Promise - return parseWebrpcGenVersions(headerValue) -} + version(headers?: object, signal?: AbortSignal): Promise -function parseWebrpcGenVersions(header: string): WebrpcGenVersions { - const versions = header.split(';') - if (versions.length < 3) { - return { - webrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } + runtimeStatus(headers?: object, signal?: AbortSignal): Promise - const [_, webrpcGenVersion] = versions[0]!.split('@') - const [codeGenName, codeGenVersion] = versions[1]!.split('@') - const [schemaName, schemaVersion] = versions[2]!.split('@') + clock(headers?: object, signal?: AbortSignal): Promise - return { - webrpcGenVersion: webrpcGenVersion ?? '', - codeGenName: codeGenName ?? '', - codeGenVersion: codeGenVersion ?? '', - schemaName: schemaName ?? '', - schemaVersion: schemaVersion ?? '', - } -} + getSequenceContext(headers?: object, signal?: AbortSignal): Promise -// -// Types -// + /** + * + * Auth + * + * TODO: rename 'ewtString' arg to 'ethauthProof' + */ + getAuthToken(req: GetAuthTokenRequest, headers?: object, signal?: AbortSignal): Promise -export enum SortOrder { - DESC = 'DESC', - ASC = 'ASC', -} + getAuthToken2(req: GetAuthToken2Request, headers?: object, signal?: AbortSignal): Promise -export enum SardinePaymentType { - ach = 'ach', - debit = 'debit', - credit = 'credit', - us_debit = 'us_debit', - international_debit = 'international_debit', - international_credit = 'international_credit', -} + sendPasswordlessLink( + req: SendPasswordlessLinkRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export enum SardineQuoteType { - buy = 'buy', - sell = 'sell', -} + registerPublicKey( + req: RegisterPublicKeyRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export enum GetLifiSwapRouteDirection { - to = 'to', - from = 'from', -} + getPublicKey(req: GetPublicKeyRequest, headers?: object, signal?: AbortSignal): Promise -export enum TokenType { - ERC20 = 'ERC20', - ERC721 = 'ERC721', - ERC1155 = 'ERC1155', -} + /** + * + * Contacts / Friends + * + */ + friendList(req: FriendListRequest, headers?: object, signal?: AbortSignal): Promise -export enum TransakBuySell { - UNKNOWN = 'UNKNOWN', - BUY = 'BUY', - SELL = 'SELL', -} + getFriendByAddress( + req: GetFriendByAddressRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export enum TradeType { - EXACT_INPUT = 'EXACT_INPUT', - EXACT_OUTPUT = 'EXACT_OUTPUT', -} + searchFriends(req: SearchFriendsRequest, headers?: object, signal?: AbortSignal): Promise -export enum CheckoutOptionCrypto { - none = 'none', - partially = 'partially', - all = 'all', -} + addFriend(req: AddFriendRequest, headers?: object, signal?: AbortSignal): Promise -export enum CheckoutOptionNFTCheckoutProvider { - unknown = 'unknown', - sardine = 'sardine', - transak = 'transak', -} + updateFriendNickname( + req: UpdateFriendNicknameRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export enum CheckoutOptionOnRampProvider { - unknown = 'unknown', - sardine = 'sardine', - transak = 'transak', -} + removeFriend(req: RemoveFriendRequest, headers?: object, signal?: AbortSignal): Promise -export enum CheckoutOptionSwapProvider { - unknown = 'unknown', - lifi = 'lifi', -} + /** + * + * Chain-Utils + * + */ + contractCall(req: ContractCallRequest, headers?: object, signal?: AbortSignal): Promise -export interface Version { - webrpcVersion: string - schemaVersion: string - schemaHash: string - appVersion: string -} + decodeContractCall( + req: DecodeContractCallRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface RuntimeStatus { - healthOK: boolean - startTime: string - uptime: number - ver: string - branch: string - commitHash: string - checks: RuntimeChecks - numTxnsRelayed: { [key: string]: NumTxnsRelayed } -} + lookupContractCallSelectors( + req: LookupContractCallSelectorsRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface NumTxnsRelayed { - chainID: number - prev: number - current: number - period: number -} + /** + * + * User Storage + * + */ + userStorageFetch( + req: UserStorageFetchRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface RuntimeChecks {} + userStorageSave(req: UserStorageSaveRequest, headers?: object, signal?: AbortSignal): Promise -export interface SequenceContext { - factory: string - mainModule: string - mainModuleUpgradable: string - guestModule: string - utils: string -} + userStorageDelete( + req: UserStorageDeleteRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface PublicKey { - id: string - x: string - y: string -} + userStorageFetchAll( + req: UserStorageFetchAllRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface User { - address: string - username: string - avatar: string - bio: string - location: string - locale: string - backup?: boolean - backupConfirmed?: boolean - maxInvites?: number - updatedAt?: string - createdAt?: string -} + /** + * + * Wallet utils + * + */ + getMoonpayLink(req: GetMoonpayLinkRequest, headers?: object, signal?: AbortSignal): Promise -export interface WalletBackup { - accountAddress: string - secretHash: string - encryptedWallet: string - userConfirmed: boolean - updatedAt?: string - createdAt?: string -} + /** + * - IsUsingGoogleMail(domain: string) => (yes: bool) + */ + resolveENSAddress( + req: ResolveENSAddressRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface Friend { - id: number - userAddress: string - friendAddress: string - nickname: string - user?: User - createdAt?: string -} + /** + * TODO: we can add walletContext optional in the future when we need it + * NOTE: chainId can be either a number or canonical name + */ + isValidSignature( + req: IsValidSignatureRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface MetaTxn { - id: string - chainId: string - walletAddress: string - contract: string - input: string -} + isValidMessageSignature( + req: IsValidMessageSignatureRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface Call { - to: string - value?: string - data?: string - gasLimit?: string - delegateCall?: boolean - onlyFallback?: boolean - behaviorOnError?: number -} + isValidTypedDataSignature( + req: IsValidTypedDataSignatureRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface IntentCallsPayload { - chainId: string - space?: string - nonce?: string - calls: Array -} + isValidETHAuthProof( + req: IsValidETHAuthProofRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface IntentConfig { - id: number - configHash: string - originIntentAddress: string - destinationIntentAddress: string - mainSigner: string - calls: Array - preconditions: Array - executionStatus?: string - metaTxnId?: string - txnHash?: string - updatedAt?: string - createdAt?: string -} + getOnRampURL(req: GetOnRampURLRequest, headers?: object, signal?: AbortSignal): Promise -export interface MetaTxnReceipt { - metaTxID: string - status: string - txnReceipt?: string - revertReason?: string -} + transakGetCountries(headers?: object, signal?: AbortSignal): Promise -export interface InviteCode { - usesLeft: number - ownerAccount: string - email?: string - url: string - createdAt?: string - expiresAt?: string -} + transakGetCryptoCurrencies(headers?: object, signal?: AbortSignal): Promise -export interface InviteCodeAccount { - claimedByUserAddress: string - claimedAt?: string -} + transakGetFiatCurrencies(headers?: object, signal?: AbortSignal): Promise -export interface InviteInfo { - expiryInHours: number - max: number - invites: Array -} + transakGetPrice(req: TransakGetPriceRequest, headers?: object, signal?: AbortSignal): Promise -export interface ContractCall { - signature: string - function: string - args: Array -} + transakGetSupportedNFTCheckoutChains( + headers?: object, + signal?: AbortSignal, + ): Promise -export interface TupleComponent { - name?: string - type: string - value: any -} + transakGetWidgetURL( + req: TransakGetWidgetURLRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface AddressOverrides { - trailsLiFiSapientSignerAddress?: string - trailsRelaySapientSignerAddress?: string - trailsCCTPV2SapientSignerAddress?: string -} + /** + * + * Price Feed + * + */ + getCoinPrices(req: GetCoinPricesRequest, headers?: object, signal?: AbortSignal): Promise -export interface TakerFee { - address: string - bps: number -} + getCollectiblePrices( + req: GetCollectiblePricesRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface OriginCall { - chainId: number - to: string - transactionData: string - transactionValue: string -} + /** + * + * Price Feed utils + * + */ + getExchangeRate(req: GetExchangeRateRequest, headers?: object, signal?: AbortSignal): Promise -export interface IntentPrecondition { - type: string - chainId: string - data: any -} + /** + * + * Util / misc + * + */ + memoryStore(req: MemoryStoreRequest, headers?: object, signal?: AbortSignal): Promise -export interface UserStorage { - userAddress: string - key: string - value: any -} + memoryLoad(req: MemoryLoadRequest, headers?: object, signal?: AbortSignal): Promise -export interface Token { - chainId: number - contractAddress: string - tokenId?: string -} + /** + * + * Legacy + * + */ + getInviteInfo(headers?: object, signal?: AbortSignal): Promise -export interface Price { - value: number - currency: string -} + /** + * NOTE: we're still using this from SW-API to Sequence-API to claim invite code + */ + isValidAccessCode( + req: IsValidAccessCodeRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface TokenPrice { - token: Token - price?: Price - price24hChange?: Price - price24hVol?: Price - floorPrice: Price - buyPrice: Price - sellPrice: Price - updatedAt: string -} + internalClaimAccessCode( + req: InternalClaimAccessCodeRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface ExchangeRate { - name: string - symbol: string - value: number - vsCurrency: string - currencyType: string -} + /** + * Utils + */ + blockNumberAtTime( + req: BlockNumberAtTimeRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface LinkedWallet { - id: number - walletType?: string - walletAddress: string - linkedWalletAddress: string - createdAt?: string -} + /** + * + * Paper + * TODO: deprecate in the future + * + */ + paperSessionSecret( + req: PaperSessionSecretRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface Page { - pageSize?: number - page?: number - totalRecords?: number - column?: string - before?: any - after?: any - sort?: Array - more?: boolean -} + paperSessionSecret2( + req: PaperSessionSecret2Request, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface SortBy { - column: string - order: SortOrder -} + /** + * + * Linked wallets (v0 -- simple support) + * + */ + linkWallet(req: LinkWalletRequest, headers?: object, signal?: AbortSignal): Promise -export interface SardineNFTCheckoutParams { - name: string - imageUrl: string - network: string - recipientAddress: string - blockchainNftId: string - contractAddress: string - quantity: number - decimals?: number - tokenAmount: string - tokenAddress: string - tokenSymbol: string - tokenDecimals?: number - calldata: string - platform: string - approvedSpenderAddress?: string -} + getLinkedWallets( + req: GetLinkedWalletsRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface SardineNFTCheckout { - token: string - expiresAt: string - orderId: string -} + removeLinkedWallet( + req: RemoveLinkedWalletRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface SardineOrder { - id: string - createdAt?: string - referenceId: string - status: string - fiatCurrency: string - fiatExchangeRateUSD: number - transactionId: string - expiresAt?: string - total: number - subTotal: number - transactionFee: number - networkFee: number - paymentCurrency?: string - paymentMethodType?: string - transactionType: string - name: string - price: number - imageUrl: string - contractAddress?: string - transactionHash?: string - recipientAddress: string -} + /** + * NOTE: these methods are deprecated, please do not use them. We may resurface them in the future, but just wanted + * to be clear, they are not necessary for our linked wallets. + */ + generateWaaSVerificationURL( + req: GenerateWaaSVerificationURLRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface SardineRegion { - countryCode: string - isAllowedOnRamp: boolean - isAllowedOnNFT: boolean - isBasicKycRequired: Array - isSsnRequired: Array - name: string - currencyCode: string - isPayrollSupported: boolean - supportedDocuments: Array - paymentMethods: Array - states: Array -} + validateWaaSVerificationNonce( + req: ValidateWaaSVerificationNonceRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface SardineRegionPaymentMethod { - name: string - isAllowedOnRamp: boolean - isAllowedOnNFT: boolean - subTypes: Array - type: string - subType: string -} + /** + * + * + * WaaS child wallet adoption + * + */ + listAdoptedWallets( + req: ListAdoptedWalletsRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface SardineRegionState { - code: string - name: string - isAllowedOnRamp: boolean - isAllowedOnNFT: boolean -} + getLifiChains(headers?: object, signal?: AbortSignal): Promise -export interface SardineSupportedToken { - network: string - assetSymbol: string - assetName: string - chainId: string - tokenName: string - token: string - tokenAddress: string -} + getLifiTokens(req: GetLifiTokensRequest, headers?: object, signal?: AbortSignal): Promise -export interface SardineSupportedTokenForSwap { - isSupported: boolean - isSupportedForAbstraction: boolean - currentBalance: string -} + /** + * All parameters except `params` are deprecated. + * Use only the `params` object to pass values. + */ + getLifiSwapRoutes( + req: GetLifiSwapRoutesRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface SardineEnabledToken { - network: string - assetSymbol: string - assetName: string - chainId: string - tokenName: string - token: string - tokenAddress: string -} + getLifiSwapQuote( + req: GetLifiSwapQuoteRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface SardineGetQuoteParams { - assetType: string - network: string - total: number - currency?: string - paymentType?: SardinePaymentType - quoteType?: SardineQuoteType - walletAddress?: string -} + /** + * + * Inventory, payments and management + * + */ + listCurrencyGroups(headers?: object, signal?: AbortSignal): Promise -export interface SardineQuote { - quantity: number - network: string - assetType: string - total: number - currency: string - expiresAt: string - paymentType: string - price: number - subtotal: number - transactionFee: number - networkFee: number - highNetworkFee: boolean - minTransactionValue: number - maxTransactionValue: number - liquidityProvider: string -} - -export interface SardineFiatCurrency { - currencyCode: string - name: string - currencySymbol: string - paymentOptions: Array - supportingCountries: Array -} + addOffchainInventory( + req: AddOffchainInventoryRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface SardinePaymentOption { - name: string - dailyLimit: number - weeklyLimit: number - monthlyLimit: number - maxAmount: number - minAmount: number - subTypes: Array - type: string - subType: string - processingTime: string -} + getOffchainInventory( + req: GetOffchainInventoryRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface LifiToken { - chainId: number - address: string - symbol: string - name: string - decimals: number - priceUsd: number - price?: string - coinKey: string - logoUri: string -} + listOffchainInventories( + req: ListOffchainInventoriesRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface GetLifiSwapRouteParams { - direction: GetLifiSwapRouteDirection - chainId: number - walletAddress: string - tokenAddress: string - tokenAmount: string -} + updateOffchainInventory( + req: UpdateOffchainInventoryRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface LifiSwapRoute { - fromChainId: number - toChainId: number - fromTokens: Array - toTokens: Array -} + deleteOffchainInventory( + req: DeleteOffchainInventoryRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface GetLifiSwapQuoteParams { - chainId: number - walletAddress: string - fromTokenAddress: string - toTokenAddress: string - fromTokenAmount?: string - toTokenAmount?: string - includeApprove: boolean - slippageBps: number -} + requestOffchainPayment( + req: RequestOffchainPaymentRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface LifiSwapQuote { - currencyAddress: string - currencyBalance: string - price: string - maxPrice: string - to: string - transactionData: string - transactionValue: string - approveData: string - amount: string - amountMin: string -} + listOffchainPayments( + req: ListOffchainPaymentsRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -export interface CurrencyGroup { - name: string - tokens: Array -} + /** + * + * Packs + * + */ + savePack(req: SavePackRequest, headers?: object, signal?: AbortSignal): Promise -export interface CurrencyGroupToken { - chainId: number - tokenAddress: string -} + getPack(req: GetPackRequest, headers?: object, signal?: AbortSignal): Promise -export interface OffchainInventory { - id: number - projectId: number - chainId: number - externalProductId: string - paymentTokenAddress: string - paymentTokenType: TokenType - paymentTokenId: number - paymentAmount: number - paymentRecipient: string - chainedCallAddress?: string - chainedCallData?: string - allowCrossChainPayments?: boolean - callbackURL?: string - createdAt: string - deletedAt?: string + getPackIds(req: GetPackIdsRequest, headers?: object, signal?: AbortSignal): Promise + + deletePack(req: DeletePackRequest, headers?: object, signal?: AbortSignal): Promise + + updatePackContent( + req: UpdatePackContentRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + getRevealTxData(req: GetRevealTxDataRequest, headers?: object, signal?: AbortSignal): Promise + + checkoutOptionsPrimary( + req: CheckoutOptionsPrimaryRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + checkoutOptionsSecondary( + req: CheckoutOptionsSecondaryRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + checkoutOptionsGetTransakContractID( + req: CheckoutOptionsGetTransakContractIDRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + fortePayCreateIntent( + req: FortePayCreateIntentRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + fortePayGetPaymentStatuses( + req: FortePayGetPaymentStatusesRequest, + headers?: object, + signal?: AbortSignal, + ): Promise } -export interface CCTPTransfer { - id: string - sourceTxHash: string - sourceChainId: number - destinationChainId: number - message: string - attestation: string - status: string - createdAt: string - updatedAt: string +// +// Schema types +// + +export enum SortOrder { + DESC = 'DESC', + ASC = 'ASC', } -export interface OffchainPayment { - id: number - offchainInventoryId: number - productRecipient: string - paymentChainId: number - paymentTokenAddress: string - expiration: string - createdAt: string - completedAt?: string - processedAt?: string +export enum GetLifiSwapRouteDirection { + to = 'to', + from = 'from', } -export interface PaymentResponse { - paymentId: number - offchainInventoryId: number - chainId: number - externalProductId: string - paymentTokenAddress: string - paymentTokenType: TokenType - paymentTokenId: number - paymentTotal: number - expiration: string - signature: string - txTo: string - txData: string +export enum TokenType { + ERC20 = 'ERC20', + ERC721 = 'ERC721', + ERC1155 = 'ERC1155', } -export interface AdoptedChildWallet { - address: string +export enum TransakBuySell { + UNKNOWN = 'UNKNOWN', + BUY = 'BUY', + SELL = 'SELL', } -export interface Pack { - id: number - chainId: number - projectId: number - contractAddress: string - packId: string - content: Array - createdAt?: string +export enum TradeType { + EXACT_INPUT = 'EXACT_INPUT', + EXACT_OUTPUT = 'EXACT_OUTPUT', } -export interface PackContent { - tokenAddresses: Array - isERC721: Array - tokenIds: Array> - amounts: Array> +export enum CheckoutOptionCrypto { + none = 'none', + partially = 'partially', + all = 'all', } -export interface TransakCountry { - alpha2: string - alpha3: string - isAllowed: boolean - isLightKycAllowed: boolean - name: string - currencyCode: string - supportedDocuments: Array - partners: Array - states: Array +export enum CheckoutOptionNFTCheckoutProvider { + unknown = 'unknown', + transak = 'transak', } -export interface TransakPartner { - name: string - isCardPayment: boolean - currencyCode: string +export enum CheckoutOptionOnRampProvider { + unknown = 'unknown', + transak = 'transak', } -export interface TransakState { - code: string - name: string - isAllowed: boolean +export enum CheckoutOptionSwapProvider { + unknown = 'unknown', + lifi = 'lifi', } -export interface TransakCryptoCurrency { - id: string - coinID: string - address: string - addressAdditionalData: any - createdAt: string - decimals: number - image: TransakCryptoCurrencyImage - isAllowed: boolean - isPopular: boolean - isStable: boolean - name: string - roundOff: number - symbol: string - isIgnorePriceVerification: boolean - imageBk: TransakCryptoCurrencyImage - kycCountriesNotSupported: Array - network: TransakCryptoCurrencyNetwork - uniqueID: string - tokenType: string - tokenIdentifier: string - isPayInAllowed: boolean - isSuspended: boolean +export interface Version { + webrpcVersion: string + schemaVersion: string + schemaHash: string + appVersion: string } -export interface TransakCryptoCurrencyImage { - large: string - small: string - thumb: string +export interface RuntimeStatus { + healthOK: boolean + startTime: string + uptime: number + ver: string + branch: string + commitHash: string + checks: RuntimeChecks + numTxnsRelayed: { [key: string]: NumTxnsRelayed } } -export interface TransakCryptoCurrencyNetwork { - name: string - fiatCurrenciesNotSupported: Array - chainID: string +export interface NumTxnsRelayed { + chainID: number + prev: number + current: number + period: number } -export interface TransakCryptoCurrencyNetworkFiatNotSupported { - fiatCurrency: string - paymentMethod: string +export interface RuntimeChecks {} + +export interface SequenceContext { + factory: string + mainModule: string + mainModuleUpgradable: string + guestModule: string + utils: string } -export interface TransakFiatCurrency { - symbol: string - supportingCountries: Array - logoSymbol: string - name: string - paymentOptions: Array - isPopular: boolean - isAllowed: boolean - roundOff: number - isPayOutAllowed: boolean - defaultCountryForNFT: string - icon: string - displayMessage: string -} - -export interface TransakFiatCurrencyPaymentOption { - name: string +export interface PublicKey { id: string - isNftAllowed: boolean - isNonCustodial: boolean - processingTime: string - displayText: boolean - icon: string - limitCurrency: string - isActive: boolean - provider: string - maxAmount: number - minAmount: number - defaultAmount: number - isConverted: boolean - visaPayoutCountries: Array - mastercardPayoutCountries: Array - isPayOutAllowed: boolean - minAmountForPayOut: number - maxAmountForPayOut: number - defaultAmountForPayOut: number + x: string + y: string } -export interface TransakPrice { - quoteID: string - conversionPrice: number - marketConversionPrice: number - slippage: number - fiatCurrency: string - cryptoCurrency: string - paymentMethod: string - fiatAmount: number - cryptoAmount: number - isBuyOrSell: string - network: string - feeDecimal: number - totalFee: number - feeBreakdown: Array - nonce: number - cryptoLiquidityProvider: string - notes: Array +export interface User { + address: string + username: string + avatar: string + bio: string + location: string + locale: string + backup?: boolean + backupConfirmed?: boolean + maxInvites?: number + updatedAt?: string + createdAt?: string } -export interface TransakPriceFeeBreakdown { - Name: string - Value: number - ID: string - Ids: Array +export interface WalletBackup { + accountAddress: string + secretHash: string + encryptedWallet: string + userConfirmed: boolean + updatedAt?: string + createdAt?: string } -export interface TransakGetPriceParams { - fiatCurrency: string - cryptoCurrency: string - isBuyOrSell: TransakBuySell - network: string - paymentMethod: string - fiatAmount: number - cryptoAmount: number - quoteCountryCode: string +export interface Friend { + id: number + userAddress: string + friendAddress: string + nickname: string + user?: User + createdAt?: string } -export interface TransakChain { - name: string - chainId: number +export interface MetaTxn { + id: string + chainId: string + walletAddress: string + contract: string + input: string } -export interface CheckoutOptionsPrimaryParams { - quantity: string - tokenId: string +export interface MetaTxnReceipt { + metaTxID: string + status: string + txnReceipt?: string + revertReason?: string } -export interface CheckoutOptionsSecondaryParams { - collectionAddress: string - marketplaceAddress: string - currencyAddress: string - priceAmount: string - tokenId: string +export interface InviteCode { + usesLeft: number + ownerAccount: string + email?: string + url: string + createdAt?: string + expiresAt?: string } -export interface CheckoutOptions { - crypto: CheckoutOptionCrypto - swap: Array - nftCheckout: Array - onRamp: Array +export interface InviteCodeAccount { + claimedByUserAddress: string + claimedAt?: string } -export interface FortePayCreateIntent { - blockchain: string - buyer: FortePayBuyer - currency: string - idempotencyKey: string - items: Array - seller: FortePaySeller - transactionType: string +export interface InviteInfo { + expiryInHours: number + max: number + invites: Array } -export interface FortePayBuyer { - wallet: FortePayWallet - email: string - id: string +export interface ContractCall { + signature: string + function: string + args: Array } -export interface FortePaySeller { - wallet: FortePayWallet +export interface TupleComponent { + name?: string + type: string + value: any } -export interface FortePayWallet { - address: string - blockchain: string +export interface UserStorage { + userAddress: string + key: string + value: any } -export interface FortePayItem { - amount: string - id: string - imageUrl: string - listingData: FortePayItemListingData - nftData: FortePayItemNFTData - mintData: FortePayItemMintData - title: string +export interface Token { + chainId: number + contractAddress: string + tokenId?: string } -export interface FortePayItemListingData { - orderHash: string - protocol: string - protocolAddress: string - auctionHouse: string - tokenAddress: string - calldata: string - payToAddress: string - structuredCalldata: any +export interface Price { + value: number + currency: string } -export interface FortePayItemNFTData { - contractAddress: string - tokenId: string +export interface TokenPrice { + token: Token + price?: Price + price24hChange?: Price + price24hVol?: Price + floorPrice: Price + buyPrice: Price + sellPrice: Price + updatedAt: string } -export interface FortePayItemMintData { - nonce: string - protocol: string - protocolAddress: string - signature: string - tokenIds: Array - calldata: string - payToAddress: string - tokenContractAddress: string - structuredCalldata: any +export interface ExchangeRate { + name: string + symbol: string + value: number + vsCurrency: string + currencyType: string } -export interface FortePayIntent { - flow: string - widgetData: string - paymentIntentId: string - notes: Array +export interface LinkedWallet { + id: number + walletType?: string + walletAddress: string + linkedWalletAddress: string + createdAt?: string } -export interface FortePaymentStatus { - paymentIntentId: string - status: string +export interface Page { + pageSize?: number + page?: number + totalRecords?: number + column?: string + before?: any + after?: any + sort?: Array + more?: boolean } -export interface CrossChainFee { - providerFee: string - trailsSwapFee: string - providerFeeUSD: number - trailsSwapFeeUSD: number - totalFeeAmount: string - totalFeeUSD: number +export interface SortBy { + column: string + order: SortOrder } -export interface MetaTxnFeeDetail { - metaTxnID: string - estimatedGasLimit: string - feeNative: string -} +export interface LifiToken { + chainId: number + address: string + symbol: string + name: string + decimals: number + priceUsd: number + price?: string + coinKey: string + logoUri: string +} -export interface ChainExecuteQuote { - chainId: string - totalGasLimit: string - gasPrice: string - totalFeeAmount: string - nativeTokenSymbol: string - nativeTokenPrice?: string - metaTxnFeeDetails: Array - totalFeeUSD?: string +export interface GetLifiSwapRouteParams { + direction: GetLifiSwapRouteDirection + chainId: number + walletAddress: string + tokenAddress: string + tokenAmount: string } -export interface ExecuteQuote { - chainQuotes: Array +export interface LifiSwapRoute { + fromChainId: number + toChainId: number + fromTokens: Array + toTokens: Array } -export interface TrailsFee { - executeQuote: ExecuteQuote - crossChainFee?: CrossChainFee - takerFeeAmount?: string - takerFeeUSD?: number - trailsFixedFeeUSD: number - feeToken?: string - originTokenTotalAmount?: string - totalFeeAmount?: string - totalFeeUSD?: string - quoteProvider?: string +export interface GetLifiSwapQuoteParams { + chainId: number + walletAddress: string + fromTokenAddress: string + toTokenAddress: string + fromTokenAmount?: string + toTokenAmount?: string + includeApprove: boolean + slippageBps: number } -export interface IntentQuote { - fromAmount: string - fromAmountMin: string - toAmount: string - toAmountMin: string - priceImpact: number - priceImpactUsd: string - maxSlippage: number - quoteProvider: string - quoteProviderRequestId: string - quoteProviderFeeUsd: string - feeQuotes: { [key: string]: string } +export interface LifiSwapQuote { + currencyAddress: string + currencyBalance: string + price: string + maxPrice: string + to: string + transactionData: string + transactionValue: string + approveData: string + amount: string + amountMin: string } -export interface API { - /** - * - * Runtime - * - */ - ping(headers?: object, signal?: AbortSignal): Promise - version(headers?: object, signal?: AbortSignal): Promise - runtimeStatus(headers?: object, signal?: AbortSignal): Promise - clock(headers?: object, signal?: AbortSignal): Promise - getSequenceContext(headers?: object, signal?: AbortSignal): Promise - /** - * - * Auth - * - * TODO: rename 'ewtString' arg to 'ethauthProof' - */ - getAuthToken(args: GetAuthTokenArgs, headers?: object, signal?: AbortSignal): Promise - getAuthToken2(args: GetAuthToken2Args, headers?: object, signal?: AbortSignal): Promise - sendPasswordlessLink( - args: SendPasswordlessLinkArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - registerPublicKey( - args: RegisterPublicKeyArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getPublicKey(args: GetPublicKeyArgs, headers?: object, signal?: AbortSignal): Promise - /** - * - * Contacts / Friends - * - */ - friendList(args: FriendListArgs, headers?: object, signal?: AbortSignal): Promise - getFriendByAddress( - args: GetFriendByAddressArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - searchFriends(args: SearchFriendsArgs, headers?: object, signal?: AbortSignal): Promise - addFriend(args: AddFriendArgs, headers?: object, signal?: AbortSignal): Promise - updateFriendNickname( - args: UpdateFriendNicknameArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - removeFriend(args: RemoveFriendArgs, headers?: object, signal?: AbortSignal): Promise - /** - * - * Chain-Utils - * - */ - contractCall(args: ContractCallArgs, headers?: object, signal?: AbortSignal): Promise - decodeContractCall( - args: DecodeContractCallArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - lookupContractCallSelectors( - args: LookupContractCallSelectorsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * - * User Storage - * - */ - userStorageFetch(args: UserStorageFetchArgs, headers?: object, signal?: AbortSignal): Promise - userStorageSave(args: UserStorageSaveArgs, headers?: object, signal?: AbortSignal): Promise - userStorageDelete( - args: UserStorageDeleteArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - userStorageFetchAll( - args: UserStorageFetchAllArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * - * Wallet utils - * - */ - getMoonpayLink(args: GetMoonpayLinkArgs, headers?: object, signal?: AbortSignal): Promise - /** - * - IsUsingGoogleMail(domain: string) => (yes: bool) - */ - resolveENSAddress( - args: ResolveENSAddressArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * TODO: we can add walletContext optional in the future when we need it - * NOTE: chainId can be either a number or canonical name - */ - isValidSignature(args: IsValidSignatureArgs, headers?: object, signal?: AbortSignal): Promise - isValidMessageSignature( - args: IsValidMessageSignatureArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - isValidTypedDataSignature( - args: IsValidTypedDataSignatureArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - isValidETHAuthProof( - args: IsValidETHAuthProofArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getOnRampURL(args: GetOnRampURLArgs, headers?: object, signal?: AbortSignal): Promise - sardineGetClientToken(headers?: object, signal?: AbortSignal): Promise - sardineGetNFTCheckoutToken( - args: SardineGetNFTCheckoutTokenArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - sardineGetNFTCheckoutOrderStatus( - args: SardineGetNFTCheckoutOrderStatusArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - sardineGetSupportedRegions(headers?: object, signal?: AbortSignal): Promise - sardineGetSupportedFiatCurrencies( - headers?: object, - signal?: AbortSignal, - ): Promise - sardineGetSupportedTokens(headers?: object, signal?: AbortSignal): Promise - sardineGetSupportedTokenForSwap( - args: SardineGetSupportedTokenForSwapArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - sardineGetEnabledTokens(headers?: object, signal?: AbortSignal): Promise - sardineGetQuote(args: SardineGetQuoteArgs, headers?: object, signal?: AbortSignal): Promise - /** - * Deprecated. Use SardineGetClientToken() instead. - */ - getSardineClientToken(headers?: object, signal?: AbortSignal): Promise - /** - * Deprecated. Use SardineGetNFTCheckoutToken() instead. - */ - getSardineNFTCheckoutToken( - args: GetSardineNFTCheckoutTokenArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * Deprecated. Use SardineGetNFTCheckoutOrderStatus() instead. - */ - getSardineNFTCheckoutOrderStatus( - args: GetSardineNFTCheckoutOrderStatusArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - transakGetCountries(headers?: object, signal?: AbortSignal): Promise - transakGetCryptoCurrencies(headers?: object, signal?: AbortSignal): Promise - transakGetFiatCurrencies(headers?: object, signal?: AbortSignal): Promise - transakGetPrice(args: TransakGetPriceArgs, headers?: object, signal?: AbortSignal): Promise - transakGetSupportedNFTCheckoutChains( - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * - * Price Feed - * - */ - getCoinPrices(args: GetCoinPricesArgs, headers?: object, signal?: AbortSignal): Promise - getCollectiblePrices( - args: GetCollectiblePricesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * - * Price Feed utils - * - */ - getExchangeRate(args: GetExchangeRateArgs, headers?: object, signal?: AbortSignal): Promise - /** - * - * Util / misc - * - */ - memoryStore(args: MemoryStoreArgs, headers?: object, signal?: AbortSignal): Promise - memoryLoad(args: MemoryLoadArgs, headers?: object, signal?: AbortSignal): Promise - /** - * - * Legacy - * - */ - getInviteInfo(headers?: object, signal?: AbortSignal): Promise - /** - * NOTE: we're still using this from SW-API to Sequence-API to claim invite code - */ - isValidAccessCode( - args: IsValidAccessCodeArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - internalClaimAccessCode( - args: InternalClaimAccessCodeArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * Utils - */ - blockNumberAtTime( - args: BlockNumberAtTimeArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * - * Paper - * TODO: deprecate in the future - * - */ - paperSessionSecret( - args: PaperSessionSecretArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - paperSessionSecret2( - args: PaperSessionSecret2Args, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * - * Linked wallets (v0 -- simple support) - * - */ - linkWallet(args: LinkWalletArgs, headers?: object, signal?: AbortSignal): Promise - getLinkedWallets(args: GetLinkedWalletsArgs, headers?: object, signal?: AbortSignal): Promise - removeLinkedWallet( - args: RemoveLinkedWalletArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * NOTE: these methods are deprecated, please do not use them. We may resurface them in the future, but just wanted - * to be clear, they are not necessary for our linked wallets. - */ - generateWaaSVerificationURL( - args: GenerateWaaSVerificationURLArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - validateWaaSVerificationNonce( - args: ValidateWaaSVerificationNonceArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * - * - * WaaS child wallet adoption - * - */ - listAdoptedWallets( - args: ListAdoptedWalletsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getLifiChains(headers?: object, signal?: AbortSignal): Promise - getLifiTokens(args: GetLifiTokensArgs, headers?: object, signal?: AbortSignal): Promise - /** - * All parameters except `params` are deprecated. - * Use only the `params` object to pass values. - */ - getLifiSwapRoutes( - args: GetLifiSwapRoutesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getLifiSwapQuote(args: GetLifiSwapQuoteArgs, headers?: object, signal?: AbortSignal): Promise - /** - * - * Chain abstraction - * - */ - getIntentCallsPayloads( - args: GetIntentCallsPayloadsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - commitIntentConfig( - args: CommitIntentConfigArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getIntentConfig(args: GetIntentConfigArgs, headers?: object, signal?: AbortSignal): Promise - /** - * - * Inventory, payments and management - * - */ - listCurrencyGroups(headers?: object, signal?: AbortSignal): Promise - addOffchainInventory( - args: AddOffchainInventoryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getOffchainInventory( - args: GetOffchainInventoryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - listOffchainInventories( - args: ListOffchainInventoriesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - updateOffchainInventory( - args: UpdateOffchainInventoryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - deleteOffchainInventory( - args: DeleteOffchainInventoryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - requestOffchainPayment( - args: RequestOffchainPaymentArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - listOffchainPayments( - args: ListOffchainPaymentsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * - * Packs - * - */ - savePack(args: SavePackArgs, headers?: object, signal?: AbortSignal): Promise - getPack(args: GetPackArgs, headers?: object, signal?: AbortSignal): Promise - getPackIds(args: GetPackIdsArgs, headers?: object, signal?: AbortSignal): Promise - deletePack(args: DeletePackArgs, headers?: object, signal?: AbortSignal): Promise - updatePackContent( - args: UpdatePackContentArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getRevealTxData(args: GetRevealTxDataArgs, headers?: object, signal?: AbortSignal): Promise - checkoutOptionsPrimary( - args: CheckoutOptionsPrimaryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - checkoutOptionsSecondary( - args: CheckoutOptionsSecondaryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - checkoutOptionsGetTransakContractID( - args: CheckoutOptionsGetTransakContractIDArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - fortePayCreateIntent( - args: FortePayCreateIntentArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - fortePayGetPaymentStatuses( - args: FortePayGetPaymentStatusesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * - * CCTP - * - */ - getCCTPTransfer(args: GetCCTPTransferArgs, headers?: object, signal?: AbortSignal): Promise - queueCCTPTransfer( - args: QueueCCTPTransferArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * - * Intent Machine Worker - * - */ - queueIntentConfigExecution( - args: QueueIntentConfigExecutionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getIntentConfigExecutionStatus( - args: GetIntentConfigExecutionStatusArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - listIntentConfigs( - args: ListIntentConfigsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - queueMetaTxnReceipt( - args: QueueMetaTxnReceiptArgs, - headers?: object, - signal?: AbortSignal, - ): Promise +export interface CurrencyGroup { + name: string + tokens: Array +} + +export interface CurrencyGroupToken { + chainId: number + tokenAddress: string +} + +export interface OffchainInventory { + id: number + projectId: number + chainId: number + externalProductId: string + paymentTokenAddress: string + paymentTokenType: TokenType + paymentTokenId: number + paymentAmount: number + paymentRecipient: string + chainedCallAddress?: string + chainedCallData?: string + allowCrossChainPayments?: boolean + callbackURL?: string + createdAt: string + deletedAt?: string +} + +export interface OffchainPayment { + id: number + offchainInventoryId: number + productRecipient: string + paymentChainId: number + paymentTokenAddress: string + expiration: string + createdAt: string + completedAt?: string + processedAt?: string +} + +export interface PaymentResponse { + paymentId: number + offchainInventoryId: number + chainId: number + externalProductId: string + paymentTokenAddress: string + paymentTokenType: TokenType + paymentTokenId: number + paymentTotal: number + expiration: string + signature: string + txTo: string + txData: string +} + +export interface AdoptedChildWallet { + address: string +} + +export interface Pack { + id: number + chainId: number + projectId: number + contractAddress: string + packId: string + content: Array + createdAt?: string +} + +export interface PackContent { + tokenAddresses: Array + isERC721: Array + tokenIds: Array> + amounts: Array> +} + +export interface TransakCountry { + alpha2: string + alpha3: string + isAllowed: boolean + isLightKycAllowed: boolean + name: string + currencyCode: string + supportedDocuments: Array + partners: Array + states: Array +} + +export interface TransakPartner { + name: string + isCardPayment: boolean + currencyCode: string +} + +export interface TransakState { + code: string + name: string + isAllowed: boolean +} + +export interface TransakCryptoCurrency { + id: string + coinID: string + address: string + addressAdditionalData: any + createdAt: string + decimals: number + image: TransakCryptoCurrencyImage + isAllowed: boolean + isPopular: boolean + isStable: boolean + name: string + roundOff: number + symbol: string + isIgnorePriceVerification: boolean + imageBk: TransakCryptoCurrencyImage + kycCountriesNotSupported: Array + network: TransakCryptoCurrencyNetwork + uniqueID: string + tokenType: string + tokenIdentifier: string + isPayInAllowed: boolean + isSuspended: boolean +} + +export interface TransakCryptoCurrencyImage { + large: string + small: string + thumb: string +} + +export interface TransakCryptoCurrencyNetwork { + name: string + fiatCurrenciesNotSupported: Array + chainID: string +} + +export interface TransakCryptoCurrencyNetworkFiatNotSupported { + fiatCurrency: string + paymentMethod: string +} + +export interface TransakFiatCurrency { + symbol: string + supportingCountries: Array + logoSymbol: string + name: string + paymentOptions: Array + isPopular: boolean + isAllowed: boolean + roundOff: number + isPayOutAllowed: boolean + defaultCountryForNFT: string + icon: string + displayMessage: string +} + +export interface TransakFiatCurrencyPaymentOption { + name: string + id: string + isNftAllowed: boolean + isNonCustodial: boolean + processingTime: string + displayText: boolean + icon: string + limitCurrency: string + isActive: boolean + provider: string + maxAmount: number + minAmount: number + defaultAmount: number + isConverted: boolean + visaPayoutCountries: Array + mastercardPayoutCountries: Array + isPayOutAllowed: boolean + minAmountForPayOut: number + maxAmountForPayOut: number + defaultAmountForPayOut: number +} + +export interface TransakPrice { + quoteID: string + conversionPrice: number + marketConversionPrice: number + slippage: number + fiatCurrency: string + cryptoCurrency: string + paymentMethod: string + fiatAmount: number + cryptoAmount: number + isBuyOrSell: string + network: string + feeDecimal: number + totalFee: number + feeBreakdown: Array + nonce: number + cryptoLiquidityProvider: string + notes: Array +} + +export interface TransakPriceFeeBreakdown { + Name: string + Value: number + ID: string + Ids: Array +} + +export interface TransakGetPriceParams { + fiatCurrency: string + cryptoCurrency: string + isBuyOrSell: TransakBuySell + network: string + paymentMethod: string + fiatAmount: number + cryptoAmount: number + quoteCountryCode: string +} + +export interface TransakNFTData { + imageUrl: string + nftName: string + collectionAddress: string + tokenIds: Array + prices: Array + quantity: number + nftType: string +} + +export interface TransakGetWidgetURLParams { + targetContractAddress?: string + isNft?: boolean + calldata?: string + cryptoCurrencyCode?: string + estimatedGasLimit?: number + nftData: Array + walletAddress?: string + disableWalletAddressForm?: boolean + partnerOrderId?: string + network?: string + referrerDomain?: string + fiatAmount?: string + fiatCurrency?: string + defaultFiatAmount?: string + defaultCryptoCurrency?: string + cryptoCurrencyList?: string + networks?: string +} + +export interface TransakChain { + name: string + chainId: number +} + +export interface CheckoutOptionsPrimaryParams { + quantity: string + tokenId: string +} + +export interface CheckoutOptionsSecondaryParams { + collectionAddress: string + marketplaceAddress: string + currencyAddress: string + priceAmount: string + tokenId: string +} + +export interface CheckoutOptions { + crypto: CheckoutOptionCrypto + swap: Array + nftCheckout: Array + onRamp: Array +} + +export interface FortePayCreateIntent { + blockchain: string + buyer: FortePayBuyer + currency: string + idempotencyKey: string + items: Array + seller: FortePaySeller + transactionType: string +} + +export interface FortePayBuyer { + wallet: FortePayWallet + email: string + id: string +} + +export interface FortePaySeller { + wallet: FortePayWallet +} + +export interface FortePayWallet { + address: string + blockchain: string +} + +export interface FortePayItem { + amount: string + id: string + imageUrl: string + listingData: FortePayItemListingData + nftData: FortePayItemNFTData + mintData: FortePayItemMintData + title: string +} + +export interface FortePayItemListingData { + orderHash: string + protocol: string + protocolAddress: string + auctionHouse: string + tokenAddress: string + calldata: string + payToAddress: string + structuredCalldata: any +} + +export interface FortePayItemNFTData { + contractAddress: string + tokenId: string +} + +export interface FortePayItemMintData { + nonce: string + protocol: string + protocolAddress: string + signature: string + tokenIds: Array + calldata: string + payToAddress: string + tokenContractAddress: string + structuredCalldata: any +} + +export interface FortePayIntent { + flow: string + widgetData: string + paymentIntentId: string + notes: Array +} + +export interface FortePaymentStatus { + paymentIntentId: string + status: string } -export interface PingArgs {} +export interface CrossChainFee { + providerFee: string + trailsSwapFee: string + providerFeeUSD: number + trailsSwapFeeUSD: number + totalFeeAmount: string + totalFeeUSD: number +} + +export interface MetaTxnFeeDetail { + metaTxnID: string + estimatedGasLimit: string + feeNative: string +} + +export interface ChainExecuteQuote { + chainId: string + totalGasLimit: string + gasPrice: string + totalFeeAmount: string + nativeTokenSymbol: string + nativeTokenPrice?: string + metaTxnFeeDetails: Array + totalFeeUSD?: string +} + +export interface ExecuteQuote { + chainQuotes: Array +} + +export interface PingRequest {} -export interface PingReturn { +export interface PingResponse { status: boolean } -export interface VersionArgs {} -export interface VersionReturn { +export interface VersionRequest {} + +export interface VersionResponse { version: Version } -export interface RuntimeStatusArgs {} -export interface RuntimeStatusReturn { +export interface RuntimeStatusRequest {} + +export interface RuntimeStatusResponse { status: RuntimeStatus } -export interface ClockArgs {} -export interface ClockReturn { +export interface ClockRequest {} + +export interface ClockResponse { serverTime: string } -export interface GetSequenceContextArgs {} -export interface GetSequenceContextReturn { +export interface GetSequenceContextRequest {} + +export interface GetSequenceContextResponse { data: SequenceContext } -export interface GetAuthTokenArgs { + +export interface GetAuthTokenRequest { ewtString: string testnetMode?: boolean } -export interface GetAuthTokenReturn { +export interface GetAuthTokenResponse { status: boolean jwtToken: string address: string user?: User } -export interface GetAuthToken2Args { + +export interface GetAuthToken2Request { ewtString: string chainID: string } -export interface GetAuthToken2Return { +export interface GetAuthToken2Response { status: boolean jwtToken: string address: string user?: User } -export interface SendPasswordlessLinkArgs { + +export interface SendPasswordlessLinkRequest { email: string redirectUri: string intent: string } -export interface SendPasswordlessLinkReturn { +export interface SendPasswordlessLinkResponse { status: boolean } -export interface RegisterPublicKeyArgs { + +export interface RegisterPublicKeyRequest { publicKey: PublicKey } -export interface RegisterPublicKeyReturn { +export interface RegisterPublicKeyResponse { status: boolean } -export interface GetPublicKeyArgs { + +export interface GetPublicKeyRequest { id: string } -export interface GetPublicKeyReturn { +export interface GetPublicKeyResponse { publicKey: PublicKey } -export interface FriendListArgs { + +export interface FriendListRequest { nickname?: string page?: Page } -export interface FriendListReturn { +export interface FriendListResponse { page: Page friends: Array } -export interface GetFriendByAddressArgs { + +export interface GetFriendByAddressRequest { friendAddress: string } -export interface GetFriendByAddressReturn { +export interface GetFriendByAddressResponse { status: boolean friend: Friend } -export interface SearchFriendsArgs { + +export interface SearchFriendsRequest { filterUsername: string page?: Page } -export interface SearchFriendsReturn { +export interface SearchFriendsResponse { friends: Array } -export interface AddFriendArgs { + +export interface AddFriendRequest { friendAddress: string optionalNickname?: string } -export interface AddFriendReturn { +export interface AddFriendResponse { status: boolean friend?: Friend } -export interface UpdateFriendNicknameArgs { + +export interface UpdateFriendNicknameRequest { friendAddress: string nickname: string } -export interface UpdateFriendNicknameReturn { +export interface UpdateFriendNicknameResponse { status: boolean friend?: Friend } -export interface RemoveFriendArgs { + +export interface RemoveFriendRequest { friendAddress: string } -export interface RemoveFriendReturn { +export interface RemoveFriendResponse { status: boolean } -export interface ContractCallArgs { + +export interface ContractCallRequest { chainID: string contract: string inputExpr: string @@ -1509,298 +1243,263 @@ export interface ContractCallArgs { args: Array } -export interface ContractCallReturn { +export interface ContractCallResponse { returns: Array } -export interface DecodeContractCallArgs { + +export interface DecodeContractCallRequest { callData: string } -export interface DecodeContractCallReturn { +export interface DecodeContractCallResponse { call: ContractCall } -export interface LookupContractCallSelectorsArgs { + +export interface LookupContractCallSelectorsRequest { selectors: Array } -export interface LookupContractCallSelectorsReturn { +export interface LookupContractCallSelectorsResponse { signatures: Array> } -export interface UserStorageFetchArgs { + +export interface UserStorageFetchRequest { key: string } -export interface UserStorageFetchReturn { +export interface UserStorageFetchResponse { object: any } -export interface UserStorageSaveArgs { + +export interface UserStorageSaveRequest { key: string object: any } -export interface UserStorageSaveReturn { +export interface UserStorageSaveResponse { ok: boolean } -export interface UserStorageDeleteArgs { + +export interface UserStorageDeleteRequest { key: string } -export interface UserStorageDeleteReturn { +export interface UserStorageDeleteResponse { ok: boolean } -export interface UserStorageFetchAllArgs { + +export interface UserStorageFetchAllRequest { keys?: Array } -export interface UserStorageFetchAllReturn { +export interface UserStorageFetchAllResponse { objects: { [key: string]: any } } -export interface GetMoonpayLinkArgs { + +export interface GetMoonpayLinkRequest { url: string } -export interface GetMoonpayLinkReturn { +export interface GetMoonpayLinkResponse { signedUrl: string } -export interface ResolveENSAddressArgs { + +export interface ResolveENSAddressRequest { ens: string } -export interface ResolveENSAddressReturn { +export interface ResolveENSAddressResponse { address: string ok: boolean } -export interface IsValidSignatureArgs { + +export interface IsValidSignatureRequest { chainId: string walletAddress: string digest: string signature: string } -export interface IsValidSignatureReturn { +export interface IsValidSignatureResponse { isValid: boolean } -export interface IsValidMessageSignatureArgs { + +export interface IsValidMessageSignatureRequest { chainId: string walletAddress: string message: string signature: string } -export interface IsValidMessageSignatureReturn { +export interface IsValidMessageSignatureResponse { isValid: boolean } -export interface IsValidTypedDataSignatureArgs { + +export interface IsValidTypedDataSignatureRequest { chainId: string walletAddress: string typedData: any signature: string } -export interface IsValidTypedDataSignatureReturn { +export interface IsValidTypedDataSignatureResponse { isValid: boolean } -export interface IsValidETHAuthProofArgs { + +export interface IsValidETHAuthProofRequest { chainId: string walletAddress: string ethAuthProofString: string } -export interface IsValidETHAuthProofReturn { +export interface IsValidETHAuthProofResponse { isValid: boolean } -export interface GetOnRampURLArgs { + +export interface GetOnRampURLRequest { chainId: string } -export interface GetOnRampURLReturn { +export interface GetOnRampURLResponse { url: string } -export interface SardineGetClientTokenArgs {} - -export interface SardineGetClientTokenReturn { - token: string -} -export interface SardineGetNFTCheckoutTokenArgs { - params: SardineNFTCheckoutParams -} - -export interface SardineGetNFTCheckoutTokenReturn { - resp: SardineNFTCheckout -} -export interface SardineGetNFTCheckoutOrderStatusArgs { - orderId: string -} - -export interface SardineGetNFTCheckoutOrderStatusReturn { - resp: SardineOrder -} -export interface SardineGetSupportedRegionsArgs {} - -export interface SardineGetSupportedRegionsReturn { - regions: Array -} -export interface SardineGetSupportedFiatCurrenciesArgs {} -export interface SardineGetSupportedFiatCurrenciesReturn { - tokens: Array -} -export interface SardineGetSupportedTokensArgs {} +export interface TransakGetCountriesRequest {} -export interface SardineGetSupportedTokensReturn { - tokens: Array -} -export interface SardineGetSupportedTokenForSwapArgs { - network: string - tokenAddress: string +export interface TransakGetCountriesResponse { + regions: Array } -export interface SardineGetSupportedTokenForSwapReturn { - token: SardineSupportedTokenForSwap -} -export interface SardineGetEnabledTokensArgs {} +export interface TransakGetCryptoCurrenciesRequest {} -export interface SardineGetEnabledTokensReturn { - tokens: Array -} -export interface SardineGetQuoteArgs { - params: SardineGetQuoteParams +export interface TransakGetCryptoCurrenciesResponse { + currencies: Array } -export interface SardineGetQuoteReturn { - quote: SardineQuote -} -export interface GetSardineClientTokenArgs {} +export interface TransakGetFiatCurrenciesRequest {} -export interface GetSardineClientTokenReturn { - token: string -} -export interface GetSardineNFTCheckoutTokenArgs { - params: SardineNFTCheckoutParams +export interface TransakGetFiatCurrenciesResponse { + currencies: Array } -export interface GetSardineNFTCheckoutTokenReturn { - resp: SardineNFTCheckout -} -export interface GetSardineNFTCheckoutOrderStatusArgs { - orderId: string +export interface TransakGetPriceRequest { + params: TransakGetPriceParams } -export interface GetSardineNFTCheckoutOrderStatusReturn { - resp: SardineOrder +export interface TransakGetPriceResponse { + price: TransakPrice } -export interface TransakGetCountriesArgs {} -export interface TransakGetCountriesReturn { - regions: Array -} -export interface TransakGetCryptoCurrenciesArgs {} +export interface TransakGetSupportedNFTCheckoutChainsRequest {} -export interface TransakGetCryptoCurrenciesReturn { - currencies: Array +export interface TransakGetSupportedNFTCheckoutChainsResponse { + chains: Array } -export interface TransakGetFiatCurrenciesArgs {} -export interface TransakGetFiatCurrenciesReturn { - currencies: Array -} -export interface TransakGetPriceArgs { - params: TransakGetPriceParams +export interface TransakGetWidgetURLRequest { + params: TransakGetWidgetURLParams } -export interface TransakGetPriceReturn { - price: TransakPrice +export interface TransakGetWidgetURLResponse { + url: string } -export interface TransakGetSupportedNFTCheckoutChainsArgs {} -export interface TransakGetSupportedNFTCheckoutChainsReturn { - chains: Array -} -export interface GetCoinPricesArgs { +export interface GetCoinPricesRequest { tokens: Array } -export interface GetCoinPricesReturn { +export interface GetCoinPricesResponse { tokenPrices: Array } -export interface GetCollectiblePricesArgs { + +export interface GetCollectiblePricesRequest { tokens: Array } -export interface GetCollectiblePricesReturn { +export interface GetCollectiblePricesResponse { tokenPrices: Array } -export interface GetExchangeRateArgs { + +export interface GetExchangeRateRequest { toCurrency: string } -export interface GetExchangeRateReturn { +export interface GetExchangeRateResponse { exchangeRate: ExchangeRate } -export interface MemoryStoreArgs { + +export interface MemoryStoreRequest { key: string value: string } -export interface MemoryStoreReturn { +export interface MemoryStoreResponse { ok: boolean } -export interface MemoryLoadArgs { + +export interface MemoryLoadRequest { key: string } -export interface MemoryLoadReturn { +export interface MemoryLoadResponse { value: string } -export interface GetInviteInfoArgs {} -export interface GetInviteInfoReturn { +export interface GetInviteInfoRequest {} + +export interface GetInviteInfoResponse { inviteInfo: InviteInfo } -export interface IsValidAccessCodeArgs { + +export interface IsValidAccessCodeRequest { accessCode: string } -export interface IsValidAccessCodeReturn { +export interface IsValidAccessCodeResponse { status: boolean } -export interface InternalClaimAccessCodeArgs { + +export interface InternalClaimAccessCodeRequest { address: string accessCode: string } -export interface InternalClaimAccessCodeReturn { +export interface InternalClaimAccessCodeResponse { status: boolean } -export interface BlockNumberAtTimeArgs { + +export interface BlockNumberAtTimeRequest { chainId: number timestamps: Array } -export interface BlockNumberAtTimeReturn { +export interface BlockNumberAtTimeResponse { blocks: Array } -export interface PaperSessionSecretArgs { + +export interface PaperSessionSecretRequest { chainName: string contractAddress: string paramsJson: string contractType: string } -export interface PaperSessionSecretReturn { +export interface PaperSessionSecretResponse { secret: string } -export interface PaperSessionSecret2Args { + +export interface PaperSessionSecret2Request { chainName: string contractAddress: string paramsJson: string abi: string } -export interface PaperSessionSecret2Return { +export interface PaperSessionSecret2Response { secret: string } -export interface LinkWalletArgs { + +export interface LinkWalletRequest { parentWalletAddress: string parentWalletMessage: string parentWalletSignature: string @@ -1811,20 +1510,22 @@ export interface LinkWalletArgs { linkedWalletType?: string } -export interface LinkWalletReturn { +export interface LinkWalletResponse { status: boolean } -export interface GetLinkedWalletsArgs { + +export interface GetLinkedWalletsRequest { parentWalletAddress: string parentWalletMessage: string parentWalletSignature: string signatureChainId: string } -export interface GetLinkedWalletsReturn { +export interface GetLinkedWalletsResponse { linkedWallets: Array } -export interface RemoveLinkedWalletArgs { + +export interface RemoveLinkedWalletRequest { parentWalletAddress: string parentWalletMessage: string parentWalletSignature: string @@ -1832,221 +1533,191 @@ export interface RemoveLinkedWalletArgs { signatureChainId: string } -export interface RemoveLinkedWalletReturn { +export interface RemoveLinkedWalletResponse { status: boolean } -export interface GenerateWaaSVerificationURLArgs { + +export interface GenerateWaaSVerificationURLRequest { walletAddress: string } -export interface GenerateWaaSVerificationURLReturn { +export interface GenerateWaaSVerificationURLResponse { nonce: string verificationURL: string } -export interface ValidateWaaSVerificationNonceArgs { + +export interface ValidateWaaSVerificationNonceRequest { nonce: string signature: string sessionId: string chainId: string } -export interface ValidateWaaSVerificationNonceReturn { +export interface ValidateWaaSVerificationNonceResponse { walletAddress: string } -export interface ListAdoptedWalletsArgs { + +export interface ListAdoptedWalletsRequest { page?: Page } -export interface ListAdoptedWalletsReturn { +export interface ListAdoptedWalletsResponse { page: Page wallets: Array } -export interface GetLifiChainsArgs {} -export interface GetLifiChainsReturn { +export interface GetLifiChainsRequest {} + +export interface GetLifiChainsResponse { chains: Array } -export interface GetLifiTokensArgs { + +export interface GetLifiTokensRequest { chainIds: Array } -export interface GetLifiTokensReturn { +export interface GetLifiTokensResponse { tokens: Array } -export interface GetLifiSwapRoutesArgs { + +export interface GetLifiSwapRoutesRequest { params: GetLifiSwapRouteParams - chainId: number - toTokenAddress: string - toTokenAmount: string - walletAddress: string } -export interface GetLifiSwapRoutesReturn { +export interface GetLifiSwapRoutesResponse { routes: Array } -export interface GetLifiSwapQuoteArgs { + +export interface GetLifiSwapQuoteRequest { params: GetLifiSwapQuoteParams } -export interface GetLifiSwapQuoteReturn { +export interface GetLifiSwapQuoteResponse { quote: LifiSwapQuote } -export interface GetIntentCallsPayloadsArgs { - userAddress: string - destinationChainId: number - destinationTokenAddress: string - destinationTokenAmount: string - destinationToAddress: string - originChainId: number - originTokenAddress: string - originTokenAmount: string - destinationCallData?: string - destinationCallValue?: string - provider?: string - addressOverrides?: AddressOverrides - destinationSalt?: string - takerFee?: TakerFee - slippageTolerance?: number - tradeType?: TradeType -} - -export interface GetIntentCallsPayloadsReturn { - calls: Array - preconditions: Array - metaTxns: Array - trailsFee: TrailsFee - quote: IntentQuote - feeQuotes: { [key: string]: string } - originIntentAddress: string - destinationIntentAddress: string -} -export interface CommitIntentConfigArgs { - originIntentAddress: string - destinationIntentAddress: string - mainSigner: string - calls: Array - preconditions: Array - addressOverrides?: AddressOverrides -} - -export interface CommitIntentConfigReturn { - config: IntentConfig -} -export interface GetIntentConfigArgs { - intentAddress: string -} - -export interface GetIntentConfigReturn { - config: IntentConfig -} -export interface ListCurrencyGroupsArgs {} - -export interface ListCurrencyGroupsReturn { + +export interface ListCurrencyGroupsRequest {} + +export interface ListCurrencyGroupsResponse { currencyGroups: Array } -export interface AddOffchainInventoryArgs { + +export interface AddOffchainInventoryRequest { inventory: OffchainInventory } -export interface AddOffchainInventoryReturn { +export interface AddOffchainInventoryResponse { inventoryId: number } -export interface GetOffchainInventoryArgs { + +export interface GetOffchainInventoryRequest { inventoryId: number } -export interface GetOffchainInventoryReturn { +export interface GetOffchainInventoryResponse { inventory: OffchainInventory } -export interface ListOffchainInventoriesArgs { + +export interface ListOffchainInventoriesRequest { projectId: number } -export interface ListOffchainInventoriesReturn { +export interface ListOffchainInventoriesResponse { inventory: Array } -export interface UpdateOffchainInventoryArgs { + +export interface UpdateOffchainInventoryRequest { inventory: OffchainInventory } -export interface UpdateOffchainInventoryReturn {} -export interface DeleteOffchainInventoryArgs { +export interface UpdateOffchainInventoryResponse {} + +export interface DeleteOffchainInventoryRequest { inventoryId: number } -export interface DeleteOffchainInventoryReturn { +export interface DeleteOffchainInventoryResponse { ok: boolean } -export interface RequestOffchainPaymentArgs { + +export interface RequestOffchainPaymentRequest { inventoryId: number recipient: string chainId?: number tokenAddress?: string } -export interface RequestOffchainPaymentReturn { +export interface RequestOffchainPaymentResponse { payment: PaymentResponse } -export interface ListOffchainPaymentsArgs { + +export interface ListOffchainPaymentsRequest { inventoryId: number page?: Page } -export interface ListOffchainPaymentsReturn { +export interface ListOffchainPaymentsResponse { page: Page payments: Array } -export interface SavePackArgs { + +export interface SavePackRequest { pack: Pack } -export interface SavePackReturn { +export interface SavePackResponse { merkleRoot: string } -export interface GetPackArgs { + +export interface GetPackRequest { contractAddress: string packId: string chainId: number } -export interface GetPackReturn { +export interface GetPackResponse { pack: Pack } -export interface GetPackIdsArgs { + +export interface GetPackIdsRequest { contractAddress: string chainId: number } -export interface GetPackIdsReturn { +export interface GetPackIdsResponse { packIds: Array } -export interface DeletePackArgs { + +export interface DeletePackRequest { contractAddress: string packId: string chainId: number } -export interface DeletePackReturn { +export interface DeletePackResponse { status: boolean } -export interface UpdatePackContentArgs { + +export interface UpdatePackContentRequest { pack: Pack } -export interface UpdatePackContentReturn { +export interface UpdatePackContentResponse { merkleRoot: string } -export interface GetRevealTxDataArgs { + +export interface GetRevealTxDataRequest { contractAddress: string packId: string chainId: number userAddress: string } -export interface GetRevealTxDataReturn { +export interface GetRevealTxDataResponse { txData: string } -export interface CheckoutOptionsPrimaryArgs { + +export interface CheckoutOptionsPrimaryRequest { chainId: number wallet: string contractAddress: string @@ -2054,92 +1725,50 @@ export interface CheckoutOptionsPrimaryArgs { params: Array } -export interface CheckoutOptionsPrimaryReturn { +export interface CheckoutOptionsPrimaryResponse { options: CheckoutOptions } -export interface CheckoutOptionsSecondaryArgs { + +export interface CheckoutOptionsSecondaryRequest { chainId: number wallet: string params: Array } -export interface CheckoutOptionsSecondaryReturn { +export interface CheckoutOptionsSecondaryResponse { options: CheckoutOptions } -export interface CheckoutOptionsGetTransakContractIDArgs { + +export interface CheckoutOptionsGetTransakContractIDRequest { chainId: number contractAddress: string } -export interface CheckoutOptionsGetTransakContractIDReturn { +export interface CheckoutOptionsGetTransakContractIDResponse { contractId: string } -export interface FortePayCreateIntentArgs { + +export interface FortePayCreateIntentRequest { intent: FortePayCreateIntent } -export interface FortePayCreateIntentReturn { +export interface FortePayCreateIntentResponse { resp: FortePayIntent } -export interface FortePayGetPaymentStatusesArgs { + +export interface FortePayGetPaymentStatusesRequest { paymentIntentIds: Array } -export interface FortePayGetPaymentStatusesReturn { +export interface FortePayGetPaymentStatusesResponse { statuses: Array } -export interface GetCCTPTransferArgs { - id: string -} - -export interface GetCCTPTransferReturn { - transfer: CCTPTransfer -} -export interface QueueCCTPTransferArgs { - sourceTxHash?: string - metaTxHash?: string - sourceChainId: number - destinationChainId: number -} - -export interface QueueCCTPTransferReturn { - transfer: CCTPTransfer -} -export interface QueueIntentConfigExecutionArgs { - intentConfigId: number -} - -export interface QueueIntentConfigExecutionReturn { - status: boolean -} -export interface GetIntentConfigExecutionStatusArgs { - intentConfigId: number -} - -export interface GetIntentConfigExecutionStatusReturn { - executionStatus: string -} -export interface ListIntentConfigsArgs { - page?: Page - executionStatus?: string -} - -export interface ListIntentConfigsReturn { - page: Page - intentConfigs: Array -} -export interface QueueMetaTxnReceiptArgs { - metaTxID: string -} - -export interface QueueMetaTxnReceiptReturn { - status: boolean -} // // Client // -export class API implements API { + +export class API implements APIClient { protected hostname: string protected fetch: Fetch protected path = '/rpc/API/' @@ -2153,1723 +1782,1641 @@ export class API implements API { return this.hostname + this.path + name } - ping = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Ping'), createHTTPRequest({}, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - status: _data.status, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - version = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Version'), createHTTPRequest({}, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - version: _data.version, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) + queryKey = { + ping: () => ['API', 'ping'] as const, + version: () => ['API', 'version'] as const, + runtimeStatus: () => ['API', 'runtimeStatus'] as const, + clock: () => ['API', 'clock'] as const, + getSequenceContext: () => ['API', 'getSequenceContext'] as const, + getAuthToken: (req: GetAuthTokenRequest) => ['API', 'getAuthToken', req] as const, + getAuthToken2: (req: GetAuthToken2Request) => ['API', 'getAuthToken2', req] as const, + sendPasswordlessLink: (req: SendPasswordlessLinkRequest) => ['API', 'sendPasswordlessLink', req] as const, + registerPublicKey: (req: RegisterPublicKeyRequest) => ['API', 'registerPublicKey', req] as const, + getPublicKey: (req: GetPublicKeyRequest) => ['API', 'getPublicKey', req] as const, + friendList: (req: FriendListRequest) => ['API', 'friendList', req] as const, + getFriendByAddress: (req: GetFriendByAddressRequest) => ['API', 'getFriendByAddress', req] as const, + searchFriends: (req: SearchFriendsRequest) => ['API', 'searchFriends', req] as const, + addFriend: (req: AddFriendRequest) => ['API', 'addFriend', req] as const, + updateFriendNickname: (req: UpdateFriendNicknameRequest) => ['API', 'updateFriendNickname', req] as const, + removeFriend: (req: RemoveFriendRequest) => ['API', 'removeFriend', req] as const, + contractCall: (req: ContractCallRequest) => ['API', 'contractCall', req] as const, + decodeContractCall: (req: DecodeContractCallRequest) => ['API', 'decodeContractCall', req] as const, + lookupContractCallSelectors: (req: LookupContractCallSelectorsRequest) => + ['API', 'lookupContractCallSelectors', req] as const, + userStorageFetch: (req: UserStorageFetchRequest) => ['API', 'userStorageFetch', req] as const, + userStorageSave: (req: UserStorageSaveRequest) => ['API', 'userStorageSave', req] as const, + userStorageDelete: (req: UserStorageDeleteRequest) => ['API', 'userStorageDelete', req] as const, + userStorageFetchAll: (req: UserStorageFetchAllRequest) => ['API', 'userStorageFetchAll', req] as const, + getMoonpayLink: (req: GetMoonpayLinkRequest) => ['API', 'getMoonpayLink', req] as const, + resolveENSAddress: (req: ResolveENSAddressRequest) => ['API', 'resolveENSAddress', req] as const, + isValidSignature: (req: IsValidSignatureRequest) => ['API', 'isValidSignature', req] as const, + isValidMessageSignature: (req: IsValidMessageSignatureRequest) => ['API', 'isValidMessageSignature', req] as const, + isValidTypedDataSignature: (req: IsValidTypedDataSignatureRequest) => + ['API', 'isValidTypedDataSignature', req] as const, + isValidETHAuthProof: (req: IsValidETHAuthProofRequest) => ['API', 'isValidETHAuthProof', req] as const, + getOnRampURL: (req: GetOnRampURLRequest) => ['API', 'getOnRampURL', req] as const, + transakGetCountries: () => ['API', 'transakGetCountries'] as const, + transakGetCryptoCurrencies: () => ['API', 'transakGetCryptoCurrencies'] as const, + transakGetFiatCurrencies: () => ['API', 'transakGetFiatCurrencies'] as const, + transakGetPrice: (req: TransakGetPriceRequest) => ['API', 'transakGetPrice', req] as const, + transakGetSupportedNFTCheckoutChains: () => ['API', 'transakGetSupportedNFTCheckoutChains'] as const, + transakGetWidgetURL: (req: TransakGetWidgetURLRequest) => ['API', 'transakGetWidgetURL', req] as const, + getCoinPrices: (req: GetCoinPricesRequest) => ['API', 'getCoinPrices', req] as const, + getCollectiblePrices: (req: GetCollectiblePricesRequest) => ['API', 'getCollectiblePrices', req] as const, + getExchangeRate: (req: GetExchangeRateRequest) => ['API', 'getExchangeRate', req] as const, + memoryStore: (req: MemoryStoreRequest) => ['API', 'memoryStore', req] as const, + memoryLoad: (req: MemoryLoadRequest) => ['API', 'memoryLoad', req] as const, + getInviteInfo: () => ['API', 'getInviteInfo'] as const, + isValidAccessCode: (req: IsValidAccessCodeRequest) => ['API', 'isValidAccessCode', req] as const, + internalClaimAccessCode: (req: InternalClaimAccessCodeRequest) => ['API', 'internalClaimAccessCode', req] as const, + blockNumberAtTime: (req: BlockNumberAtTimeRequest) => ['API', 'blockNumberAtTime', req] as const, + paperSessionSecret: (req: PaperSessionSecretRequest) => ['API', 'paperSessionSecret', req] as const, + paperSessionSecret2: (req: PaperSessionSecret2Request) => ['API', 'paperSessionSecret2', req] as const, + linkWallet: (req: LinkWalletRequest) => ['API', 'linkWallet', req] as const, + getLinkedWallets: (req: GetLinkedWalletsRequest) => ['API', 'getLinkedWallets', req] as const, + removeLinkedWallet: (req: RemoveLinkedWalletRequest) => ['API', 'removeLinkedWallet', req] as const, + generateWaaSVerificationURL: (req: GenerateWaaSVerificationURLRequest) => + ['API', 'generateWaaSVerificationURL', req] as const, + validateWaaSVerificationNonce: (req: ValidateWaaSVerificationNonceRequest) => + ['API', 'validateWaaSVerificationNonce', req] as const, + listAdoptedWallets: (req: ListAdoptedWalletsRequest) => ['API', 'listAdoptedWallets', req] as const, + getLifiChains: () => ['API', 'getLifiChains'] as const, + getLifiTokens: (req: GetLifiTokensRequest) => ['API', 'getLifiTokens', req] as const, + getLifiSwapRoutes: (req: GetLifiSwapRoutesRequest) => ['API', 'getLifiSwapRoutes', req] as const, + getLifiSwapQuote: (req: GetLifiSwapQuoteRequest) => ['API', 'getLifiSwapQuote', req] as const, + listCurrencyGroups: () => ['API', 'listCurrencyGroups'] as const, + addOffchainInventory: (req: AddOffchainInventoryRequest) => ['API', 'addOffchainInventory', req] as const, + getOffchainInventory: (req: GetOffchainInventoryRequest) => ['API', 'getOffchainInventory', req] as const, + listOffchainInventories: (req: ListOffchainInventoriesRequest) => ['API', 'listOffchainInventories', req] as const, + updateOffchainInventory: (req: UpdateOffchainInventoryRequest) => ['API', 'updateOffchainInventory', req] as const, + deleteOffchainInventory: (req: DeleteOffchainInventoryRequest) => ['API', 'deleteOffchainInventory', req] as const, + requestOffchainPayment: (req: RequestOffchainPaymentRequest) => ['API', 'requestOffchainPayment', req] as const, + listOffchainPayments: (req: ListOffchainPaymentsRequest) => ['API', 'listOffchainPayments', req] as const, + savePack: (req: SavePackRequest) => ['API', 'savePack', req] as const, + getPack: (req: GetPackRequest) => ['API', 'getPack', req] as const, + getPackIds: (req: GetPackIdsRequest) => ['API', 'getPackIds', req] as const, + deletePack: (req: DeletePackRequest) => ['API', 'deletePack', req] as const, + updatePackContent: (req: UpdatePackContentRequest) => ['API', 'updatePackContent', req] as const, + getRevealTxData: (req: GetRevealTxDataRequest) => ['API', 'getRevealTxData', req] as const, + checkoutOptionsPrimary: (req: CheckoutOptionsPrimaryRequest) => ['API', 'checkoutOptionsPrimary', req] as const, + checkoutOptionsSecondary: (req: CheckoutOptionsSecondaryRequest) => + ['API', 'checkoutOptionsSecondary', req] as const, + checkoutOptionsGetTransakContractID: (req: CheckoutOptionsGetTransakContractIDRequest) => + ['API', 'checkoutOptionsGetTransakContractID', req] as const, + fortePayCreateIntent: (req: FortePayCreateIntentRequest) => ['API', 'fortePayCreateIntent', req] as const, + fortePayGetPaymentStatuses: (req: FortePayGetPaymentStatusesRequest) => + ['API', 'fortePayGetPaymentStatuses', req] as const, } - runtimeStatus = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('RuntimeStatus'), createHTTPRequest({}, headers, signal)).then( + ping = (headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('Ping'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'PingResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - clock = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Clock'), createHTTPRequest({}, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - serverTime: _data.serverTime, - } + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, }) }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, ) } - getSequenceContext = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetSequenceContext'), createHTTPRequest({}, headers, signal)).then( + version = (headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('Version'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - data: _data.data, - } + return JsonDecode(_data, 'VersionResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - getAuthToken = (args: GetAuthTokenArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetAuthToken'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - status: _data.status, - jwtToken: _data.jwtToken, - address: _data.address, - user: _data.user, - } + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, }) }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, ) } - getAuthToken2 = (args: GetAuthToken2Args, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetAuthToken2'), createHTTPRequest(args, headers, signal)).then( + runtimeStatus = (headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('RuntimeStatus'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - jwtToken: _data.jwtToken, - address: _data.address, - user: _data.user, - } + return JsonDecode(_data, 'RuntimeStatusResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - sendPasswordlessLink = ( - args: SendPasswordlessLinkArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('SendPasswordlessLink'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, }) }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, ) } - registerPublicKey = ( - args: RegisterPublicKeyArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('RegisterPublicKey'), createHTTPRequest(args, headers, signal)).then( + clock = (headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('Clock'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'ClockResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - getPublicKey = (args: GetPublicKeyArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetPublicKey'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - publicKey: _data.publicKey, - } + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, }) }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, ) } - friendList = (args: FriendListArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('FriendList'), createHTTPRequest(args, headers, signal)).then( + getSequenceContext = (headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('GetSequenceContext'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - friends: >_data.friends, - } + return JsonDecode(_data, 'GetSequenceContextResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - getFriendByAddress = ( - args: GetFriendByAddressArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetFriendByAddress'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - status: _data.status, - friend: _data.friend, - } + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, }) }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, ) } - searchFriends = (args: SearchFriendsArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('SearchFriends'), createHTTPRequest(args, headers, signal)).then( + getAuthToken = (req: GetAuthTokenRequest, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('GetAuthToken'), + createHttpRequest(JsonEncode(req, 'GetAuthTokenRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - friends: >_data.friends, - } + return JsonDecode(_data, 'GetAuthTokenResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - addFriend = (args: AddFriendArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('AddFriend'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - status: _data.status, - friend: _data.friend, - } + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, }) }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, ) } - updateFriendNickname = ( - args: UpdateFriendNicknameArgs, + getAuthToken2 = ( + req: GetAuthToken2Request, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('UpdateFriendNickname'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - status: _data.status, - friend: _data.friend, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - removeFriend = (args: RemoveFriendArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('RemoveFriend'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - status: _data.status, - } - }) - }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - contractCall = (args: ContractCallArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('ContractCall'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('GetAuthToken2'), + createHttpRequest(JsonEncode(req, 'GetAuthToken2Request'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - returns: >_data.returns, - } + return JsonDecode(_data, 'GetAuthToken2Response') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - decodeContractCall = ( - args: DecodeContractCallArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('DecodeContractCall'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - call: _data.call, - } + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, }) }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, ) } - lookupContractCallSelectors = ( - args: LookupContractCallSelectorsArgs, + sendPasswordlessLink = ( + req: SendPasswordlessLinkRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('LookupContractCallSelectors'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('SendPasswordlessLink'), + createHttpRequest(JsonEncode(req, 'SendPasswordlessLinkRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - signatures: >>_data.signatures, - } + return JsonDecode(_data, 'SendPasswordlessLinkResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - userStorageFetch = ( - args: UserStorageFetchArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('UserStorageFetch'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - object: _data.object, - } + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, }) }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, ) } - userStorageSave = ( - args: UserStorageSaveArgs, + registerPublicKey = ( + req: RegisterPublicKeyRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('UserStorageSave'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('RegisterPublicKey'), + createHttpRequest(JsonEncode(req, 'RegisterPublicKeyRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - ok: _data.ok, - } + return JsonDecode(_data, 'RegisterPublicKeyResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - userStorageDelete = ( - args: UserStorageDeleteArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('UserStorageDelete'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - ok: _data.ok, - } + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, }) }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, ) } - userStorageFetchAll = ( - args: UserStorageFetchAllArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('UserStorageFetchAll'), createHTTPRequest(args, headers, signal)).then( + getPublicKey = (req: GetPublicKeyRequest, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('GetPublicKey'), + createHttpRequest(JsonEncode(req, 'GetPublicKeyRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - objects: <{ [key: string]: any }>_data.objects, - } + return JsonDecode(_data, 'GetPublicKeyResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - getMoonpayLink = ( - args: GetMoonpayLinkArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetMoonpayLink'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - signedUrl: _data.signedUrl, - } + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, }) }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, ) } - resolveENSAddress = ( - args: ResolveENSAddressArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ResolveENSAddress'), createHTTPRequest(args, headers, signal)).then( + friendList = (req: FriendListRequest, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('FriendList'), + createHttpRequest(JsonEncode(req, 'FriendListRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - address: _data.address, - ok: _data.ok, - } + return JsonDecode(_data, 'FriendListResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - isValidSignature = ( - args: IsValidSignatureArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('IsValidSignature'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - isValid: _data.isValid, - } + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, }) }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, ) } - isValidMessageSignature = ( - args: IsValidMessageSignatureArgs, + getFriendByAddress = ( + req: GetFriendByAddressRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('IsValidMessageSignature'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('GetFriendByAddress'), + createHttpRequest(JsonEncode(req, 'GetFriendByAddressRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - isValid: _data.isValid, - } + return JsonDecode(_data, 'GetFriendByAddressResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - isValidTypedDataSignature = ( - args: IsValidTypedDataSignatureArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('IsValidTypedDataSignature'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - isValid: _data.isValid, - } + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, }) }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, ) } - isValidETHAuthProof = ( - args: IsValidETHAuthProofArgs, + searchFriends = ( + req: SearchFriendsRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('IsValidETHAuthProof'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('SearchFriends'), + createHttpRequest(JsonEncode(req, 'SearchFriendsRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - isValid: _data.isValid, - } + return JsonDecode(_data, 'SearchFriendsResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - getOnRampURL = (args: GetOnRampURLArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetOnRampURL'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - url: _data.url, - } + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, }) }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, ) } - sardineGetClientToken = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('SardineGetClientToken'), createHTTPRequest({}, headers, signal)).then( + addFriend = (req: AddFriendRequest, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('AddFriend'), + createHttpRequest(JsonEncode(req, 'AddFriendRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - token: _data.token, - } + return JsonDecode(_data, 'AddFriendResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - sardineGetNFTCheckoutToken = ( - args: SardineGetNFTCheckoutTokenArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('SardineGetNFTCheckoutToken'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - resp: _data.resp, - } + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, }) }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, ) } - sardineGetNFTCheckoutOrderStatus = ( - args: SardineGetNFTCheckoutOrderStatusArgs, + updateFriendNickname = ( + req: UpdateFriendNicknameRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('SardineGetNFTCheckoutOrderStatus'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('UpdateFriendNickname'), + createHttpRequest(JsonEncode(req, 'UpdateFriendNicknameRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - resp: _data.resp, - } + return JsonDecode(_data, 'UpdateFriendNicknameResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - sardineGetSupportedRegions = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('SardineGetSupportedRegions'), createHTTPRequest({}, headers, signal)).then( + removeFriend = (req: RemoveFriendRequest, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('RemoveFriend'), + createHttpRequest(JsonEncode(req, 'RemoveFriendRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - regions: >_data.regions, - } + return JsonDecode(_data, 'RemoveFriendResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - sardineGetSupportedFiatCurrencies = ( - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('SardineGetSupportedFiatCurrencies'), createHTTPRequest({}, headers, signal)).then( + contractCall = (req: ContractCallRequest, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('ContractCall'), + createHttpRequest(JsonEncode(req, 'ContractCallRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - tokens: >_data.tokens, - } + return JsonDecode(_data, 'ContractCallResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - sardineGetSupportedTokens = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('SardineGetSupportedTokens'), createHTTPRequest({}, headers, signal)).then( + decodeContractCall = ( + req: DecodeContractCallRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('DecodeContractCall'), + createHttpRequest(JsonEncode(req, 'DecodeContractCallRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - tokens: >_data.tokens, - } + return JsonDecode(_data, 'DecodeContractCallResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - sardineGetSupportedTokenForSwap = ( - args: SardineGetSupportedTokenForSwapArgs, + lookupContractCallSelectors = ( + req: LookupContractCallSelectorsRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('SardineGetSupportedTokenForSwap'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('LookupContractCallSelectors'), + createHttpRequest(JsonEncode(req, 'LookupContractCallSelectorsRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - token: _data.token, - } + return JsonDecode(_data, 'LookupContractCallSelectorsResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - sardineGetEnabledTokens = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('SardineGetEnabledTokens'), createHTTPRequest({}, headers, signal)).then( + userStorageFetch = ( + req: UserStorageFetchRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('UserStorageFetch'), + createHttpRequest(JsonEncode(req, 'UserStorageFetchRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - tokens: >_data.tokens, - } + return JsonDecode(_data, 'UserStorageFetchResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - sardineGetQuote = ( - args: SardineGetQuoteArgs, + userStorageSave = ( + req: UserStorageSaveRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('SardineGetQuote'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('UserStorageSave'), + createHttpRequest(JsonEncode(req, 'UserStorageSaveRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - quote: _data.quote, - } + return JsonDecode(_data, 'UserStorageSaveResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getSardineClientToken = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetSardineClientToken'), createHTTPRequest({}, headers, signal)).then( + userStorageDelete = ( + req: UserStorageDeleteRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('UserStorageDelete'), + createHttpRequest(JsonEncode(req, 'UserStorageDeleteRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - token: _data.token, - } + return JsonDecode(_data, 'UserStorageDeleteResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getSardineNFTCheckoutToken = ( - args: GetSardineNFTCheckoutTokenArgs, + userStorageFetchAll = ( + req: UserStorageFetchAllRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetSardineNFTCheckoutToken'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('UserStorageFetchAll'), + createHttpRequest(JsonEncode(req, 'UserStorageFetchAllRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - resp: _data.resp, - } + return JsonDecode(_data, 'UserStorageFetchAllResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getSardineNFTCheckoutOrderStatus = ( - args: GetSardineNFTCheckoutOrderStatusArgs, + getMoonpayLink = ( + req: GetMoonpayLinkRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetSardineNFTCheckoutOrderStatus'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('GetMoonpayLink'), + createHttpRequest(JsonEncode(req, 'GetMoonpayLinkRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - resp: _data.resp, - } + return JsonDecode(_data, 'GetMoonpayLinkResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - transakGetCountries = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('TransakGetCountries'), createHTTPRequest({}, headers, signal)).then( + resolveENSAddress = ( + req: ResolveENSAddressRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('ResolveENSAddress'), + createHttpRequest(JsonEncode(req, 'ResolveENSAddressRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - regions: >_data.regions, - } + return JsonDecode(_data, 'ResolveENSAddressResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - transakGetCryptoCurrencies = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('TransakGetCryptoCurrencies'), createHTTPRequest({}, headers, signal)).then( + isValidSignature = ( + req: IsValidSignatureRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('IsValidSignature'), + createHttpRequest(JsonEncode(req, 'IsValidSignatureRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - currencies: >_data.currencies, - } + return JsonDecode(_data, 'IsValidSignatureResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - transakGetFiatCurrencies = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('TransakGetFiatCurrencies'), createHTTPRequest({}, headers, signal)).then( + isValidMessageSignature = ( + req: IsValidMessageSignatureRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('IsValidMessageSignature'), + createHttpRequest(JsonEncode(req, 'IsValidMessageSignatureRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - currencies: >_data.currencies, - } + return JsonDecode(_data, 'IsValidMessageSignatureResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - transakGetPrice = ( - args: TransakGetPriceArgs, + isValidTypedDataSignature = ( + req: IsValidTypedDataSignatureRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('TransakGetPrice'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('IsValidTypedDataSignature'), + createHttpRequest(JsonEncode(req, 'IsValidTypedDataSignatureRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - price: _data.price, - } + return JsonDecode(_data, 'IsValidTypedDataSignatureResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - transakGetSupportedNFTCheckoutChains = ( + isValidETHAuthProof = ( + req: IsValidETHAuthProofRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('TransakGetSupportedNFTCheckoutChains'), createHTTPRequest({}, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('IsValidETHAuthProof'), + createHttpRequest(JsonEncode(req, 'IsValidETHAuthProofRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - chains: >_data.chains, - } + return JsonDecode(_data, 'IsValidETHAuthProofResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getCoinPrices = (args: GetCoinPricesArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetCoinPrices'), createHTTPRequest(args, headers, signal)).then( + getOnRampURL = (req: GetOnRampURLRequest, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('GetOnRampURL'), + createHttpRequest(JsonEncode(req, 'GetOnRampURLRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - tokenPrices: >_data.tokenPrices, - } + return JsonDecode(_data, 'GetOnRampURLResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getCollectiblePrices = ( - args: GetCollectiblePricesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetCollectiblePrices'), createHTTPRequest(args, headers, signal)).then( + transakGetCountries = (headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('TransakGetCountries'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - tokenPrices: >_data.tokenPrices, - } + return JsonDecode(_data, 'TransakGetCountriesResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getExchangeRate = ( - args: GetExchangeRateArgs, + transakGetCryptoCurrencies = ( headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetExchangeRate'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('TransakGetCryptoCurrencies'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - exchangeRate: _data.exchangeRate, - } + return JsonDecode(_data, 'TransakGetCryptoCurrenciesResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - memoryStore = (args: MemoryStoreArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('MemoryStore'), createHTTPRequest(args, headers, signal)).then( + transakGetFiatCurrencies = (headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('TransakGetFiatCurrencies'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - ok: _data.ok, - } + return JsonDecode(_data, 'TransakGetFiatCurrenciesResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - memoryLoad = (args: MemoryLoadArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('MemoryLoad'), createHTTPRequest(args, headers, signal)).then( + transakGetPrice = ( + req: TransakGetPriceRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('TransakGetPrice'), + createHttpRequest(JsonEncode(req, 'TransakGetPriceRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - value: _data.value, - } + return JsonDecode(_data, 'TransakGetPriceResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getInviteInfo = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetInviteInfo'), createHTTPRequest({}, headers, signal)).then( + transakGetSupportedNFTCheckoutChains = ( + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('TransakGetSupportedNFTCheckoutChains'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - inviteInfo: _data.inviteInfo, - } + return JsonDecode( + _data, + 'TransakGetSupportedNFTCheckoutChainsResponse', + ) }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - isValidAccessCode = ( - args: IsValidAccessCodeArgs, + transakGetWidgetURL = ( + req: TransakGetWidgetURLRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('IsValidAccessCode'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('TransakGetWidgetURL'), + createHttpRequest(JsonEncode(req, 'TransakGetWidgetURLRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'TransakGetWidgetURLResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - internalClaimAccessCode = ( - args: InternalClaimAccessCodeArgs, + getCoinPrices = ( + req: GetCoinPricesRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('InternalClaimAccessCode'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('GetCoinPrices'), + createHttpRequest(JsonEncode(req, 'GetCoinPricesRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'GetCoinPricesResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - blockNumberAtTime = ( - args: BlockNumberAtTimeArgs, + getCollectiblePrices = ( + req: GetCollectiblePricesRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('BlockNumberAtTime'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('GetCollectiblePrices'), + createHttpRequest(JsonEncode(req, 'GetCollectiblePricesRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - blocks: >_data.blocks, - } + return JsonDecode(_data, 'GetCollectiblePricesResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - paperSessionSecret = ( - args: PaperSessionSecretArgs, + getExchangeRate = ( + req: GetExchangeRateRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('PaperSessionSecret'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('GetExchangeRate'), + createHttpRequest(JsonEncode(req, 'GetExchangeRateRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - secret: _data.secret, - } + return JsonDecode(_data, 'GetExchangeRateResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - paperSessionSecret2 = ( - args: PaperSessionSecret2Args, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('PaperSessionSecret2'), createHTTPRequest(args, headers, signal)).then( + memoryStore = (req: MemoryStoreRequest, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('MemoryStore'), + createHttpRequest(JsonEncode(req, 'MemoryStoreRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - secret: _data.secret, - } + return JsonDecode(_data, 'MemoryStoreResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - linkWallet = (args: LinkWalletArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('LinkWallet'), createHTTPRequest(args, headers, signal)).then( + memoryLoad = (req: MemoryLoadRequest, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('MemoryLoad'), + createHttpRequest(JsonEncode(req, 'MemoryLoadRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'MemoryLoadResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getLinkedWallets = ( - args: GetLinkedWalletsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetLinkedWallets'), createHTTPRequest(args, headers, signal)).then( + getInviteInfo = (headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('GetInviteInfo'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - linkedWallets: >_data.linkedWallets, - } + return JsonDecode(_data, 'GetInviteInfoResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - removeLinkedWallet = ( - args: RemoveLinkedWalletArgs, + isValidAccessCode = ( + req: IsValidAccessCodeRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('RemoveLinkedWallet'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('IsValidAccessCode'), + createHttpRequest(JsonEncode(req, 'IsValidAccessCodeRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'IsValidAccessCodeResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - generateWaaSVerificationURL = ( - args: GenerateWaaSVerificationURLArgs, + internalClaimAccessCode = ( + req: InternalClaimAccessCodeRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GenerateWaaSVerificationURL'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('InternalClaimAccessCode'), + createHttpRequest(JsonEncode(req, 'InternalClaimAccessCodeRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - nonce: _data.nonce, - verificationURL: _data.verificationURL, - } + return JsonDecode(_data, 'InternalClaimAccessCodeResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - validateWaaSVerificationNonce = ( - args: ValidateWaaSVerificationNonceArgs, + blockNumberAtTime = ( + req: BlockNumberAtTimeRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ValidateWaaSVerificationNonce'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('BlockNumberAtTime'), + createHttpRequest(JsonEncode(req, 'BlockNumberAtTimeRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - walletAddress: _data.walletAddress, - } + return JsonDecode(_data, 'BlockNumberAtTimeResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - listAdoptedWallets = ( - args: ListAdoptedWalletsArgs, + paperSessionSecret = ( + req: PaperSessionSecretRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ListAdoptedWallets'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('PaperSessionSecret'), + createHttpRequest(JsonEncode(req, 'PaperSessionSecretRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - wallets: >_data.wallets, - } + return JsonDecode(_data, 'PaperSessionSecretResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getLifiChains = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetLifiChains'), createHTTPRequest({}, headers, signal)).then( + paperSessionSecret2 = ( + req: PaperSessionSecret2Request, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('PaperSessionSecret2'), + createHttpRequest(JsonEncode(req, 'PaperSessionSecret2Request'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - chains: >_data.chains, - } + return JsonDecode(_data, 'PaperSessionSecret2Response') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getLifiTokens = (args: GetLifiTokensArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetLifiTokens'), createHTTPRequest(args, headers, signal)).then( + linkWallet = (req: LinkWalletRequest, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('LinkWallet'), + createHttpRequest(JsonEncode(req, 'LinkWalletRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - tokens: >_data.tokens, - } + return JsonDecode(_data, 'LinkWalletResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getLifiSwapRoutes = ( - args: GetLifiSwapRoutesArgs, + getLinkedWallets = ( + req: GetLinkedWalletsRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetLifiSwapRoutes'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('GetLinkedWallets'), + createHttpRequest(JsonEncode(req, 'GetLinkedWalletsRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - routes: >_data.routes, - } + return JsonDecode(_data, 'GetLinkedWalletsResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getLifiSwapQuote = ( - args: GetLifiSwapQuoteArgs, + removeLinkedWallet = ( + req: RemoveLinkedWalletRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetLifiSwapQuote'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('RemoveLinkedWallet'), + createHttpRequest(JsonEncode(req, 'RemoveLinkedWalletRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - quote: _data.quote, - } + return JsonDecode(_data, 'RemoveLinkedWalletResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getIntentCallsPayloads = ( - args: GetIntentCallsPayloadsArgs, + generateWaaSVerificationURL = ( + req: GenerateWaaSVerificationURLRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetIntentCallsPayloads'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('GenerateWaaSVerificationURL'), + createHttpRequest(JsonEncode(req, 'GenerateWaaSVerificationURLRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - calls: >_data.calls, - preconditions: >_data.preconditions, - metaTxns: >_data.metaTxns, - trailsFee: _data.trailsFee, - quote: _data.quote, - feeQuotes: <{ [key: string]: string }>_data.feeQuotes, - originIntentAddress: _data.originIntentAddress, - destinationIntentAddress: _data.destinationIntentAddress, - } + return JsonDecode(_data, 'GenerateWaaSVerificationURLResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - commitIntentConfig = ( - args: CommitIntentConfigArgs, + validateWaaSVerificationNonce = ( + req: ValidateWaaSVerificationNonceRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('CommitIntentConfig'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('ValidateWaaSVerificationNonce'), + createHttpRequest(JsonEncode(req, 'ValidateWaaSVerificationNonceRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - config: _data.config, - } + return JsonDecode(_data, 'ValidateWaaSVerificationNonceResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getIntentConfig = ( - args: GetIntentConfigArgs, + listAdoptedWallets = ( + req: ListAdoptedWalletsRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetIntentConfig'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('ListAdoptedWallets'), + createHttpRequest(JsonEncode(req, 'ListAdoptedWalletsRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - config: _data.config, - } + return JsonDecode(_data, 'ListAdoptedWalletsResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - listCurrencyGroups = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('ListCurrencyGroups'), createHTTPRequest({}, headers, signal)).then( + getLifiChains = (headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('GetLifiChains'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - currencyGroups: >_data.currencyGroups, - } + return JsonDecode(_data, 'GetLifiChainsResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - addOffchainInventory = ( - args: AddOffchainInventoryArgs, + getLifiTokens = ( + req: GetLifiTokensRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('AddOffchainInventory'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('GetLifiTokens'), + createHttpRequest(JsonEncode(req, 'GetLifiTokensRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - inventoryId: _data.inventoryId, - } + return JsonDecode(_data, 'GetLifiTokensResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getOffchainInventory = ( - args: GetOffchainInventoryArgs, + getLifiSwapRoutes = ( + req: GetLifiSwapRoutesRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetOffchainInventory'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('GetLifiSwapRoutes'), + createHttpRequest(JsonEncode(req, 'GetLifiSwapRoutesRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - inventory: _data.inventory, - } + return JsonDecode(_data, 'GetLifiSwapRoutesResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - listOffchainInventories = ( - args: ListOffchainInventoriesArgs, + getLifiSwapQuote = ( + req: GetLifiSwapQuoteRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ListOffchainInventories'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('GetLifiSwapQuote'), + createHttpRequest(JsonEncode(req, 'GetLifiSwapQuoteRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - inventory: >_data.inventory, - } + return JsonDecode(_data, 'GetLifiSwapQuoteResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - updateOffchainInventory = ( - args: UpdateOffchainInventoryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('UpdateOffchainInventory'), createHTTPRequest(args, headers, signal)).then( + listCurrencyGroups = (headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('ListCurrencyGroups'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return {} + return JsonDecode(_data, 'ListCurrencyGroupsResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - deleteOffchainInventory = ( - args: DeleteOffchainInventoryArgs, + addOffchainInventory = ( + req: AddOffchainInventoryRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('DeleteOffchainInventory'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('AddOffchainInventory'), + createHttpRequest(JsonEncode(req, 'AddOffchainInventoryRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - ok: _data.ok, - } + return JsonDecode(_data, 'AddOffchainInventoryResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - requestOffchainPayment = ( - args: RequestOffchainPaymentArgs, + getOffchainInventory = ( + req: GetOffchainInventoryRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('RequestOffchainPayment'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('GetOffchainInventory'), + createHttpRequest(JsonEncode(req, 'GetOffchainInventoryRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - payment: _data.payment, - } + return JsonDecode(_data, 'GetOffchainInventoryResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - listOffchainPayments = ( - args: ListOffchainPaymentsArgs, + listOffchainInventories = ( + req: ListOffchainInventoriesRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ListOffchainPayments'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('ListOffchainInventories'), + createHttpRequest(JsonEncode(req, 'ListOffchainInventoriesRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - payments: >_data.payments, - } + return JsonDecode(_data, 'ListOffchainInventoriesResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - savePack = (args: SavePackArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('SavePack'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - merkleRoot: _data.merkleRoot, - } + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, }) }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, ) } - getPack = (args: GetPackArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetPack'), createHTTPRequest(args, headers, signal)).then( + updateOffchainInventory = ( + req: UpdateOffchainInventoryRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('UpdateOffchainInventory'), + createHttpRequest(JsonEncode(req, 'UpdateOffchainInventoryRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - pack: _data.pack, - } + return JsonDecode(_data, 'UpdateOffchainInventoryResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, - ) - } - - getPackIds = (args: GetPackIdsArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetPackIds'), createHTTPRequest(args, headers, signal)).then( - (res) => { - return buildResponse(res).then((_data) => { - return { - packIds: >_data.packIds, - } + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, }) }, - (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) - }, ) } - deletePack = (args: DeletePackArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('DeletePack'), createHTTPRequest(args, headers, signal)).then( + deleteOffchainInventory = ( + req: DeleteOffchainInventoryRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('DeleteOffchainInventory'), + createHttpRequest(JsonEncode(req, 'DeleteOffchainInventoryRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'DeleteOffchainInventoryResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - updatePackContent = ( - args: UpdatePackContentArgs, + requestOffchainPayment = ( + req: RequestOffchainPaymentRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('UpdatePackContent'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('RequestOffchainPayment'), + createHttpRequest(JsonEncode(req, 'RequestOffchainPaymentRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - merkleRoot: _data.merkleRoot, - } + return JsonDecode(_data, 'RequestOffchainPaymentResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getRevealTxData = ( - args: GetRevealTxDataArgs, + listOffchainPayments = ( + req: ListOffchainPaymentsRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetRevealTxData'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('ListOffchainPayments'), + createHttpRequest(JsonEncode(req, 'ListOffchainPaymentsRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - txData: _data.txData, - } + return JsonDecode(_data, 'ListOffchainPaymentsResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - checkoutOptionsPrimary = ( - args: CheckoutOptionsPrimaryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('CheckoutOptionsPrimary'), createHTTPRequest(args, headers, signal)).then( + savePack = (req: SavePackRequest, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('SavePack'), + createHttpRequest(JsonEncode(req, 'SavePackRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - options: _data.options, - } + return JsonDecode(_data, 'SavePackResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - checkoutOptionsSecondary = ( - args: CheckoutOptionsSecondaryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('CheckoutOptionsSecondary'), createHTTPRequest(args, headers, signal)).then( + getPack = (req: GetPackRequest, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('GetPack'), createHttpRequest(JsonEncode(req, 'GetPackRequest'), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - options: _data.options, - } + return JsonDecode(_data, 'GetPackResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - checkoutOptionsGetTransakContractID = ( - args: CheckoutOptionsGetTransakContractIDArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('CheckoutOptionsGetTransakContractID'), createHTTPRequest(args, headers, signal)).then( + getPackIds = (req: GetPackIdsRequest, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('GetPackIds'), + createHttpRequest(JsonEncode(req, 'GetPackIdsRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - contractId: _data.contractId, - } + return JsonDecode(_data, 'GetPackIdsResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - fortePayCreateIntent = ( - args: FortePayCreateIntentArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('FortePayCreateIntent'), createHTTPRequest(args, headers, signal)).then( + deletePack = (req: DeletePackRequest, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('DeletePack'), + createHttpRequest(JsonEncode(req, 'DeletePackRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - resp: _data.resp, - } + return JsonDecode(_data, 'DeletePackResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - fortePayGetPaymentStatuses = ( - args: FortePayGetPaymentStatusesArgs, + updatePackContent = ( + req: UpdatePackContentRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('FortePayGetPaymentStatuses'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('UpdatePackContent'), + createHttpRequest(JsonEncode(req, 'UpdatePackContentRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - statuses: >_data.statuses, - } + return JsonDecode(_data, 'UpdatePackContentResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getCCTPTransfer = ( - args: GetCCTPTransferArgs, + getRevealTxData = ( + req: GetRevealTxDataRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetCCTPTransfer'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('GetRevealTxData'), + createHttpRequest(JsonEncode(req, 'GetRevealTxDataRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - transfer: _data.transfer, - } + return JsonDecode(_data, 'GetRevealTxDataResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - queueCCTPTransfer = ( - args: QueueCCTPTransferArgs, + checkoutOptionsPrimary = ( + req: CheckoutOptionsPrimaryRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('QueueCCTPTransfer'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('CheckoutOptionsPrimary'), + createHttpRequest(JsonEncode(req, 'CheckoutOptionsPrimaryRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - transfer: _data.transfer, - } + return JsonDecode(_data, 'CheckoutOptionsPrimaryResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - queueIntentConfigExecution = ( - args: QueueIntentConfigExecutionArgs, + checkoutOptionsSecondary = ( + req: CheckoutOptionsSecondaryRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('QueueIntentConfigExecution'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('CheckoutOptionsSecondary'), + createHttpRequest(JsonEncode(req, 'CheckoutOptionsSecondaryRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'CheckoutOptionsSecondaryResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getIntentConfigExecutionStatus = ( - args: GetIntentConfigExecutionStatusArgs, + checkoutOptionsGetTransakContractID = ( + req: CheckoutOptionsGetTransakContractIDRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetIntentConfigExecutionStatus'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('CheckoutOptionsGetTransakContractID'), + createHttpRequest(JsonEncode(req, 'CheckoutOptionsGetTransakContractIDRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - executionStatus: _data.executionStatus, - } + return JsonDecode( + _data, + 'CheckoutOptionsGetTransakContractIDResponse', + ) }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - listIntentConfigs = ( - args: ListIntentConfigsArgs, + fortePayCreateIntent = ( + req: FortePayCreateIntentRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ListIntentConfigs'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('FortePayCreateIntent'), + createHttpRequest(JsonEncode(req, 'FortePayCreateIntentRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - intentConfigs: >_data.intentConfigs, - } + return JsonDecode(_data, 'FortePayCreateIntentResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - queueMetaTxnReceipt = ( - args: QueueMetaTxnReceiptArgs, + fortePayGetPaymentStatuses = ( + req: FortePayGetPaymentStatusesRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('QueueMetaTxnReceipt'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('FortePayGetPaymentStatuses'), + createHttpRequest(JsonEncode(req, 'FortePayGetPaymentStatusesRequest'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'FortePayGetPaymentStatusesResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } } -const createHTTPRequest = (body: object = {}, headers: object = {}, signal: AbortSignal | null = null): object => { - const reqHeaders: { [key: string]: string } = { ...headers, 'Content-Type': 'application/json' } - reqHeaders[WebrpcHeader] = WebrpcHeaderValue - - return { - method: 'POST', - headers: reqHeaders, - body: JSON.stringify(body || {}), - signal, +const createHttpRequest = (body: string = '{}', headers: object = {}, signal: AbortSignal | null = null): object => { + const reqHeaders: { [key: string]: string } = { + ...headers, + 'Content-Type': 'application/json', + [WebrpcHeader]: WebrpcHeaderValue, } + return { method: 'POST', headers: reqHeaders, body, signal } } const buildResponse = (res: Response): Promise => { @@ -3878,13 +3425,9 @@ const buildResponse = (res: Response): Promise => { try { data = JSON.parse(text) } catch (error) { - let message = '' - if (error instanceof Error) { - message = error.message - } throw WebrpcBadResponseError.new({ status: res.status, - cause: `JSON.parse(): ${message}: response text: ${text}`, + cause: `JSON.parse(): ${error instanceof Error ? error.message : String(error)}: response text: ${text}`, }) } if (!res.ok) { @@ -3895,504 +3438,481 @@ const buildResponse = (res: Response): Promise => { }) } +export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise + +export const JsonEncode = (obj: T, _typ: string = ''): string => { + return JSON.stringify(obj) +} + +export const JsonDecode = (data: string | any, _typ: string = ''): T => { + let parsed: any = data + if (typeof data === 'string') { + try { + parsed = JSON.parse(data) + } catch (err) { + throw WebrpcBadResponseError.new({ cause: `JsonDecode: JSON.parse failed: ${(err as Error).message}` }) + } + } + return parsed as T +} + // // Errors // +type WebrpcErrorParams = { name?: string; code?: number; message?: string; status?: number; cause?: string } + export class WebrpcError extends Error { - name: string code: number - message: string status: number - cause?: string - - /** @deprecated Use message instead of msg. Deprecated in webrpc v0.11.0. */ - msg: string - - constructor(name: string, code: number, message: string, status: number, cause?: string) { - super(message) - this.name = name || 'WebrpcError' - this.code = typeof code === 'number' ? code : 0 - this.message = message || `endpoint error ${this.code}` - this.msg = this.message - this.status = typeof status === 'number' ? status : 0 - this.cause = cause + + constructor(error: WebrpcErrorParams = {}) { + super(error.message) + this.name = error.name || 'WebrpcEndpointError' + this.code = typeof error.code === 'number' ? error.code : 0 + this.message = error.message || `endpoint error` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcError.prototype) } static new(payload: any): WebrpcError { - return new this(payload.error, payload.code, payload.message || payload.msg, payload.status, payload.cause) + return new this({ message: payload.message, code: payload.code, status: payload.status, cause: payload.cause }) } } -// Webrpc errors - export class WebrpcEndpointError extends WebrpcError { - constructor( - name: string = 'WebrpcEndpoint', - code: number = 0, - message: string = `endpoint error`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcEndpoint' + this.code = typeof error.code === 'number' ? error.code : 0 + this.message = error.message || `endpoint error` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcEndpointError.prototype) } } export class WebrpcRequestFailedError extends WebrpcError { - constructor( - name: string = 'WebrpcRequestFailed', - code: number = -1, - message: string = `request failed`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcRequestFailed' + this.code = typeof error.code === 'number' ? error.code : -1 + this.message = error.message || `request failed` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcRequestFailedError.prototype) } } export class WebrpcBadRouteError extends WebrpcError { - constructor( - name: string = 'WebrpcBadRoute', - code: number = -2, - message: string = `bad route`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadRoute' + this.code = typeof error.code === 'number' ? error.code : -2 + this.message = error.message || `bad route` + this.status = typeof error.status === 'number' ? error.status : 404 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcBadRouteError.prototype) } } export class WebrpcBadMethodError extends WebrpcError { - constructor( - name: string = 'WebrpcBadMethod', - code: number = -3, - message: string = `bad method`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadMethod' + this.code = typeof error.code === 'number' ? error.code : -3 + this.message = error.message || `bad method` + this.status = typeof error.status === 'number' ? error.status : 405 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcBadMethodError.prototype) } } export class WebrpcBadRequestError extends WebrpcError { - constructor( - name: string = 'WebrpcBadRequest', - code: number = -4, - message: string = `bad request`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadRequest' + this.code = typeof error.code === 'number' ? error.code : -4 + this.message = error.message || `bad request` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcBadRequestError.prototype) } } export class WebrpcBadResponseError extends WebrpcError { - constructor( - name: string = 'WebrpcBadResponse', - code: number = -5, - message: string = `bad response`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadResponse' + this.code = typeof error.code === 'number' ? error.code : -5 + this.message = error.message || `bad response` + this.status = typeof error.status === 'number' ? error.status : 500 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcBadResponseError.prototype) } } export class WebrpcServerPanicError extends WebrpcError { - constructor( - name: string = 'WebrpcServerPanic', - code: number = -6, - message: string = `server panic`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcServerPanic' + this.code = typeof error.code === 'number' ? error.code : -6 + this.message = error.message || `server panic` + this.status = typeof error.status === 'number' ? error.status : 500 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcServerPanicError.prototype) } } export class WebrpcInternalErrorError extends WebrpcError { - constructor( - name: string = 'WebrpcInternalError', - code: number = -7, - message: string = `internal error`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcInternalError' + this.code = typeof error.code === 'number' ? error.code : -7 + this.message = error.message || `internal error` + this.status = typeof error.status === 'number' ? error.status : 500 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcInternalErrorError.prototype) } } -export class WebrpcClientDisconnectedError extends WebrpcError { - constructor( - name: string = 'WebrpcClientDisconnected', - code: number = -8, - message: string = `client disconnected`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcClientDisconnectedError.prototype) +export class WebrpcClientAbortedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcClientAborted' + this.code = typeof error.code === 'number' ? error.code : -8 + this.message = error.message || `request aborted by client` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcClientAbortedError.prototype) } } export class WebrpcStreamLostError extends WebrpcError { - constructor( - name: string = 'WebrpcStreamLost', - code: number = -9, - message: string = `stream lost`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcStreamLost' + this.code = typeof error.code === 'number' ? error.code : -9 + this.message = error.message || `stream lost` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcStreamLostError.prototype) } } export class WebrpcStreamFinishedError extends WebrpcError { - constructor( - name: string = 'WebrpcStreamFinished', - code: number = -10, - message: string = `stream finished`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcStreamFinished' + this.code = typeof error.code === 'number' ? error.code : -10 + this.message = error.message || `stream finished` + this.status = typeof error.status === 'number' ? error.status : 200 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcStreamFinishedError.prototype) } } +// // Schema errors +// export class UnauthorizedError extends WebrpcError { - constructor( - name: string = 'Unauthorized', - code: number = 1000, - message: string = `Unauthorized access`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Unauthorized' + this.code = typeof error.code === 'number' ? error.code : 1000 + this.message = error.message || `Unauthorized access` + this.status = typeof error.status === 'number' ? error.status : 401 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, UnauthorizedError.prototype) } } export class PermissionDeniedError extends WebrpcError { - constructor( - name: string = 'PermissionDenied', - code: number = 1001, - message: string = `Permission denied`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'PermissionDenied' + this.code = typeof error.code === 'number' ? error.code : 1001 + this.message = error.message || `Permission denied` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, PermissionDeniedError.prototype) } } export class SessionExpiredError extends WebrpcError { - constructor( - name: string = 'SessionExpired', - code: number = 1002, - message: string = `Session expired`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'SessionExpired' + this.code = typeof error.code === 'number' ? error.code : 1002 + this.message = error.message || `Session expired` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, SessionExpiredError.prototype) } } export class MethodNotFoundError extends WebrpcError { - constructor( - name: string = 'MethodNotFound', - code: number = 1003, - message: string = `Method not found`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'MethodNotFound' + this.code = typeof error.code === 'number' ? error.code : 1003 + this.message = error.message || `Method not found` + this.status = typeof error.status === 'number' ? error.status : 404 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, MethodNotFoundError.prototype) } } export class RequestConflictError extends WebrpcError { - constructor( - name: string = 'RequestConflict', - code: number = 1004, - message: string = `Conflict with target resource`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'RequestConflict' + this.code = typeof error.code === 'number' ? error.code : 1004 + this.message = error.message || `Conflict with target resource` + this.status = typeof error.status === 'number' ? error.status : 409 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, RequestConflictError.prototype) } } export class AbortedError extends WebrpcError { - constructor( - name: string = 'Aborted', - code: number = 1005, - message: string = `Request aborted`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Aborted' + this.code = typeof error.code === 'number' ? error.code : 1005 + this.message = error.message || `Request aborted` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, AbortedError.prototype) } } export class GeoblockedError extends WebrpcError { - constructor( - name: string = 'Geoblocked', - code: number = 1006, - message: string = `Geoblocked region`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Geoblocked' + this.code = typeof error.code === 'number' ? error.code : 1006 + this.message = error.message || `Geoblocked region` + this.status = typeof error.status === 'number' ? error.status : 451 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, GeoblockedError.prototype) } } export class RateLimitedError extends WebrpcError { - constructor( - name: string = 'RateLimited', - code: number = 1007, - message: string = `Rate-limited. Please slow down.`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'RateLimited' + this.code = typeof error.code === 'number' ? error.code : 1007 + this.message = error.message || `Rate-limited. Please slow down.` + this.status = typeof error.status === 'number' ? error.status : 429 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, RateLimitedError.prototype) } } export class ProjectNotFoundError extends WebrpcError { - constructor( - name: string = 'ProjectNotFound', - code: number = 1008, - message: string = `Project not found`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'ProjectNotFound' + this.code = typeof error.code === 'number' ? error.code : 1008 + this.message = error.message || `Project not found` + this.status = typeof error.status === 'number' ? error.status : 401 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, ProjectNotFoundError.prototype) } } export class AccessKeyNotFoundError extends WebrpcError { - constructor( - name: string = 'AccessKeyNotFound', - code: number = 1101, - message: string = `Access key not found`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'AccessKeyNotFound' + this.code = typeof error.code === 'number' ? error.code : 1101 + this.message = error.message || `Access key not found` + this.status = typeof error.status === 'number' ? error.status : 401 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, AccessKeyNotFoundError.prototype) } } export class AccessKeyMismatchError extends WebrpcError { - constructor( - name: string = 'AccessKeyMismatch', - code: number = 1102, - message: string = `Access key mismatch`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'AccessKeyMismatch' + this.code = typeof error.code === 'number' ? error.code : 1102 + this.message = error.message || `Access key mismatch` + this.status = typeof error.status === 'number' ? error.status : 409 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, AccessKeyMismatchError.prototype) } } export class InvalidOriginError extends WebrpcError { - constructor( - name: string = 'InvalidOrigin', - code: number = 1103, - message: string = `Invalid origin for Access Key`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'InvalidOrigin' + this.code = typeof error.code === 'number' ? error.code : 1103 + this.message = error.message || `Invalid origin for Access Key` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, InvalidOriginError.prototype) } } export class InvalidServiceError extends WebrpcError { - constructor( - name: string = 'InvalidService', - code: number = 1104, - message: string = `Service not enabled for Access key`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'InvalidService' + this.code = typeof error.code === 'number' ? error.code : 1104 + this.message = error.message || `Service not enabled for Access key` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, InvalidServiceError.prototype) } } export class UnauthorizedUserError extends WebrpcError { - constructor( - name: string = 'UnauthorizedUser', - code: number = 1105, - message: string = `Unauthorized user`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'UnauthorizedUser' + this.code = typeof error.code === 'number' ? error.code : 1105 + this.message = error.message || `Unauthorized user` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, UnauthorizedUserError.prototype) } } export class QuotaExceededError extends WebrpcError { - constructor( - name: string = 'QuotaExceeded', - code: number = 1200, - message: string = `Quota request exceeded`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'QuotaExceeded' + this.code = typeof error.code === 'number' ? error.code : 1200 + this.message = error.message || `Quota request exceeded` + this.status = typeof error.status === 'number' ? error.status : 429 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, QuotaExceededError.prototype) } } export class QuotaRateLimitError extends WebrpcError { - constructor( - name: string = 'QuotaRateLimit', - code: number = 1201, - message: string = `Quota rate limit exceeded`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'QuotaRateLimit' + this.code = typeof error.code === 'number' ? error.code : 1201 + this.message = error.message || `Quota rate limit exceeded` + this.status = typeof error.status === 'number' ? error.status : 429 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, QuotaRateLimitError.prototype) } } export class NoDefaultKeyError extends WebrpcError { - constructor( - name: string = 'NoDefaultKey', - code: number = 1300, - message: string = `No default access key found`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'NoDefaultKey' + this.code = typeof error.code === 'number' ? error.code : 1300 + this.message = error.message || `No default access key found` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, NoDefaultKeyError.prototype) } } export class MaxAccessKeysError extends WebrpcError { - constructor( - name: string = 'MaxAccessKeys', - code: number = 1301, - message: string = `Access keys limit reached`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'MaxAccessKeys' + this.code = typeof error.code === 'number' ? error.code : 1301 + this.message = error.message || `Access keys limit reached` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, MaxAccessKeysError.prototype) } } export class AtLeastOneKeyError extends WebrpcError { - constructor( - name: string = 'AtLeastOneKey', - code: number = 1302, - message: string = `You need at least one Access Key`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'AtLeastOneKey' + this.code = typeof error.code === 'number' ? error.code : 1302 + this.message = error.message || `You need at least one Access Key` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, AtLeastOneKeyError.prototype) } } export class TimeoutError extends WebrpcError { - constructor( - name: string = 'Timeout', - code: number = 1900, - message: string = `Request timed out`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Timeout' + this.code = typeof error.code === 'number' ? error.code : 1900 + this.message = error.message || `Request timed out` + this.status = typeof error.status === 'number' ? error.status : 408 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, TimeoutError.prototype) } } export class InvalidArgumentError extends WebrpcError { - constructor( - name: string = 'InvalidArgument', - code: number = 2000, - message: string = `Invalid argument`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'InvalidArgument' + this.code = typeof error.code === 'number' ? error.code : 2000 + this.message = error.message || `Invalid argument` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, InvalidArgumentError.prototype) } } export class UnavailableError extends WebrpcError { - constructor( - name: string = 'Unavailable', - code: number = 2002, - message: string = `Unavailable resource`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Unavailable' + this.code = typeof error.code === 'number' ? error.code : 2002 + this.message = error.message || `Unavailable resource` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, UnavailableError.prototype) } } export class QueryFailedError extends WebrpcError { - constructor( - name: string = 'QueryFailed', - code: number = 2003, - message: string = `Query failed`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'QueryFailed' + this.code = typeof error.code === 'number' ? error.code : 2003 + this.message = error.message || `Query failed` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, QueryFailedError.prototype) } } export class NotFoundError extends WebrpcError { - constructor( - name: string = 'NotFound', - code: number = 3000, - message: string = `Resource not found`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'NotFound' + this.code = typeof error.code === 'number' ? error.code : 3000 + this.message = error.message || `Resource not found` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, NotFoundError.prototype) } } export class UnsupportedNetworkError extends WebrpcError { - constructor( - name: string = 'UnsupportedNetwork', - code: number = 3008, - message: string = `Unsupported network`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'UnsupportedNetwork' + this.code = typeof error.code === 'number' ? error.code : 3008 + this.message = error.message || `Unsupported network` + this.status = typeof error.status === 'number' ? error.status : 422 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, UnsupportedNetworkError.prototype) } } @@ -4406,7 +3926,7 @@ export enum errors { WebrpcBadResponse = 'WebrpcBadResponse', WebrpcServerPanic = 'WebrpcServerPanic', WebrpcInternalError = 'WebrpcInternalError', - WebrpcClientDisconnected = 'WebrpcClientDisconnected', + WebrpcClientAborted = 'WebrpcClientAborted', WebrpcStreamLost = 'WebrpcStreamLost', WebrpcStreamFinished = 'WebrpcStreamFinished', Unauthorized = 'Unauthorized', @@ -4445,7 +3965,7 @@ export enum WebrpcErrorCodes { WebrpcBadResponse = -5, WebrpcServerPanic = -6, WebrpcInternalError = -7, - WebrpcClientDisconnected = -8, + WebrpcClientAborted = -8, WebrpcStreamLost = -9, WebrpcStreamFinished = -10, Unauthorized = 1000, @@ -4484,7 +4004,7 @@ export const webrpcErrorByCode: { [code: number]: any } = { [-5]: WebrpcBadResponseError, [-6]: WebrpcServerPanicError, [-7]: WebrpcInternalErrorError, - [-8]: WebrpcClientDisconnectedError, + [-8]: WebrpcClientAbortedError, [-9]: WebrpcStreamLostError, [-10]: WebrpcStreamFinishedError, [1000]: UnauthorizedError, @@ -4514,4 +4034,58 @@ export const webrpcErrorByCode: { [code: number]: any } = { [3008]: UnsupportedNetworkError, } -export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise +// +// Webrpc +// + +export const WebrpcHeader = 'Webrpc' + +export const WebrpcHeaderValue = 'webrpc@v0.31.0;gen-typescript@v0.22.5;sequence-api@v0.4.0' + +type WebrpcGenVersions = { + WebrpcGenVersion: string + codeGenName: string + codeGenVersion: string + schemaName: string + schemaVersion: string +} + +export function VersionFromHeader(headers: Headers): WebrpcGenVersions { + const headerValue = headers.get(WebrpcHeader) + if (!headerValue) { + return { + WebrpcGenVersion: '', + codeGenName: '', + codeGenVersion: '', + schemaName: '', + schemaVersion: '', + } + } + + return parseWebrpcGenVersions(headerValue) +} + +function parseWebrpcGenVersions(header: string): WebrpcGenVersions { + const versions = header.split(';') + if (versions.length < 3) { + return { + WebrpcGenVersion: '', + codeGenName: '', + codeGenVersion: '', + schemaName: '', + schemaVersion: '', + } + } + + const [_, WebrpcGenVersion] = versions[0]!.split('@') + const [codeGenName, codeGenVersion] = versions[1]!.split('@') + const [schemaName, schemaVersion] = versions[2]!.split('@') + + return { + WebrpcGenVersion: WebrpcGenVersion ?? '', + codeGenName: codeGenName ?? '', + codeGenVersion: codeGenVersion ?? '', + schemaName: schemaName ?? '', + schemaVersion: schemaVersion ?? '', + } +} diff --git a/packages/services/api/src/index.ts b/packages/services/api/src/index.ts index 12656d2c0..14d83d8a1 100644 --- a/packages/services/api/src/index.ts +++ b/packages/services/api/src/index.ts @@ -1,6 +1,6 @@ -export * from './api.gen' +export * from './api.gen.js' -import { API as ApiRpc } from './api.gen' +import { API as ApiRpc } from './api.gen.js' export class SequenceAPIClient extends ApiRpc { constructor( @@ -15,7 +15,7 @@ export class SequenceAPIClient extends ApiRpc { _fetch = (input: RequestInfo, init?: RequestInit): Promise => { // automatically include jwt and access key auth header to requests // if its been set on the api client - const headers: { [key: string]: any } = {} + const headers: Record = {} const jwtAuth = this.jwtAuth const projectAccessKey = this.projectAccessKey diff --git a/packages/services/builder/CHANGELOG.md b/packages/services/builder/CHANGELOG.md index 0a86b4aa5..106229a8f 100644 --- a/packages/services/builder/CHANGELOG.md +++ b/packages/services/builder/CHANGELOG.md @@ -1,5 +1,216 @@ # @0xsequence/builder +## 3.0.12 + +### Patch Changes + +- WDK fix + +## 3.0.11 + +### Patch Changes + +- Fix for relayer sponsored fees + +## 3.0.10 + +### Patch Changes + +- Minor network updates, relayer interface + +## 3.0.9 + +### Patch Changes + +- Fee options fixes + +## 3.0.8 + +### Patch Changes + +- Bug fix for relayer fee options handling + +## 3.0.7 + +### Patch Changes + +- Minor bug fixes + +## 3.0.6 + +### Patch Changes + +- userdata upgrade, arweave support + +## 3.0.5 + +### Patch Changes + +- Account federation support + +## 3.0.4 + +### Patch Changes + +- id-token login support + +## 3.0.3 + +### Patch Changes + +- 3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer + +## 3.0.1 + +### Patch Changes + +- Network and session fixes + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade + +## 3.0.0-beta.19 + +### Patch Changes + +- Final RC before 3.0.0 + +## 3.0.0-beta.18 + +### Patch Changes + +- multicall3 optimization + +## 3.0.0-beta.17 + +### Patch Changes + +- New chains, relayer rpc fix + +## 3.0.0-beta.16 + +### Patch Changes + +- ethauth support + +## 3.0.0-beta.15 + +### Patch Changes + +- New chains, minor fixes + +## 3.0.0-beta.14 + +### Patch Changes + +- Relayer fee options fix + +## 3.0.0-beta.13 + +### Patch Changes + +- Userdata service updates + +## 3.0.0-beta.12 + +### Patch Changes + +- Beta release with dapp connector fixes + +## 3.0.0-beta.11 + +### Patch Changes + +- 3.0.0 beta + +## 3.0.0-beta.10 + +### Patch Changes + +- dapp-client updates + +## 3.0.0-beta.9 + +### Patch Changes + +- dapp client updates for EOA login + +## 3.0.0-beta.8 + +### Patch Changes + +- Apple auth fixes + +## 3.0.0-beta.7 + +### Patch Changes + +- Apple auth fix + +## 3.0.0-beta.6 + +### Patch Changes + +- Fix signer 404 error, minor fixes + +## 3.0.0-beta.5 + +### Patch Changes + +- Beta release for v3 + +## 3.0.0-beta.4 + +### Patch Changes + +- RC5 upgrade + +## 3.0.0-beta.3 + +### Patch Changes + +- 3.0.0-beta.3 with fixes + +## 3.0.0-beta.2 + +### Patch Changes + +- 3.0.0-beta.2 with identity instrument updates + +## 3.0.0-beta.1 + +### Patch Changes + +- 3.0.0-beta.1 + ## 2.3.8 ### Patch Changes diff --git a/packages/services/builder/eslint.config.js b/packages/services/builder/eslint.config.js new file mode 100644 index 000000000..cecf89b03 --- /dev/null +++ b/packages/services/builder/eslint.config.js @@ -0,0 +1,4 @@ +import { config as baseConfig } from "@repo/eslint-config/base" + +/** @type {import("eslint").Linter.Config} */ +export default baseConfig diff --git a/packages/services/builder/package.json b/packages/services/builder/package.json index e5668967a..ee4ae02f8 100644 --- a/packages/services/builder/package.json +++ b/packages/services/builder/package.json @@ -1,28 +1,31 @@ { "name": "@0xsequence/builder", - "version": "3.0.0", + "version": "3.0.12", "description": "builder sub-package for Sequence", - "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/builder", - "author": "Sequence Platforms Inc.", + "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/builder", + "author": "Sequence Platforms ULC", "license": "Apache-2.0", "publishConfig": { "access": "public" }, + "type": "module", "scripts": { "build": "tsc", "dev": "tsc --watch", "test": "echo", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "lint": "eslint . --max-warnings 0" }, "exports": { ".": { - "types": "./src/index.ts", + "types": "./dist/index.d.ts", "default": "./dist/index.js" } }, "devDependencies": { + "@repo/eslint-config": "workspace:^", "@repo/typescript-config": "workspace:^", - "@types/node": "^22.15.29", - "typescript": "^5.8.3" + "@types/node": "^25.3.0", + "typescript": "^6.0.3" } } diff --git a/packages/services/builder/src/index.ts b/packages/services/builder/src/index.ts index 7abe21855..18f13758d 100644 --- a/packages/services/builder/src/index.ts +++ b/packages/services/builder/src/index.ts @@ -1,6 +1,6 @@ -export * from './builder.gen' +export * from './builder.gen.js' -import { Builder as BuilderRpc } from './builder.gen' +import { Builder as BuilderRpc } from './builder.gen.js' export class SequenceBuilderClient extends BuilderRpc { constructor( @@ -15,7 +15,7 @@ export class SequenceBuilderClient extends BuilderRpc { _fetch = (input: RequestInfo, init?: RequestInit): Promise => { // automatically include access key auth header to requests // if its been set on the api client - const headers: { [key: string]: any } = {} + const headers: Record = {} const projectAccessKey = this.projectAccessKey if (projectAccessKey && projectAccessKey.length > 0) { diff --git a/packages/services/guard/CHANGELOG.md b/packages/services/guard/CHANGELOG.md index 73a079116..31a84379e 100644 --- a/packages/services/guard/CHANGELOG.md +++ b/packages/services/guard/CHANGELOG.md @@ -1,5 +1,216 @@ # @0xsequence/guard +## 3.0.12 + +### Patch Changes + +- WDK fix + +## 3.0.11 + +### Patch Changes + +- Fix for relayer sponsored fees + +## 3.0.10 + +### Patch Changes + +- Minor network updates, relayer interface + +## 3.0.9 + +### Patch Changes + +- Fee options fixes + +## 3.0.8 + +### Patch Changes + +- Bug fix for relayer fee options handling + +## 3.0.7 + +### Patch Changes + +- Minor bug fixes + +## 3.0.6 + +### Patch Changes + +- userdata upgrade, arweave support + +## 3.0.5 + +### Patch Changes + +- Account federation support + +## 3.0.4 + +### Patch Changes + +- id-token login support + +## 3.0.3 + +### Patch Changes + +- 3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer + +## 3.0.1 + +### Patch Changes + +- Network and session fixes + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade + +## 3.0.0-beta.19 + +### Patch Changes + +- Final RC before 3.0.0 + +## 3.0.0-beta.18 + +### Patch Changes + +- multicall3 optimization + +## 3.0.0-beta.17 + +### Patch Changes + +- New chains, relayer rpc fix + +## 3.0.0-beta.16 + +### Patch Changes + +- ethauth support + +## 3.0.0-beta.15 + +### Patch Changes + +- New chains, minor fixes + +## 3.0.0-beta.14 + +### Patch Changes + +- Relayer fee options fix + +## 3.0.0-beta.13 + +### Patch Changes + +- Userdata service updates + +## 3.0.0-beta.12 + +### Patch Changes + +- Beta release with dapp connector fixes + +## 3.0.0-beta.11 + +### Patch Changes + +- 3.0.0 beta + +## 3.0.0-beta.10 + +### Patch Changes + +- dapp-client updates + +## 3.0.0-beta.9 + +### Patch Changes + +- dapp client updates for EOA login + +## 3.0.0-beta.8 + +### Patch Changes + +- Apple auth fixes + +## 3.0.0-beta.7 + +### Patch Changes + +- Apple auth fix + +## 3.0.0-beta.6 + +### Patch Changes + +- Fix signer 404 error, minor fixes + +## 3.0.0-beta.5 + +### Patch Changes + +- Beta release for v3 + +## 3.0.0-beta.4 + +### Patch Changes + +- RC5 upgrade + +## 3.0.0-beta.3 + +### Patch Changes + +- 3.0.0-beta.3 with fixes + +## 3.0.0-beta.2 + +### Patch Changes + +- 3.0.0-beta.2 with identity instrument updates + +## 3.0.0-beta.1 + +### Patch Changes + +- 3.0.0-beta.1 + ## 2.3.8 ### Patch Changes diff --git a/packages/services/guard/eslint.config.js b/packages/services/guard/eslint.config.js new file mode 100644 index 000000000..cecf89b03 --- /dev/null +++ b/packages/services/guard/eslint.config.js @@ -0,0 +1,4 @@ +import { config as baseConfig } from "@repo/eslint-config/base" + +/** @type {import("eslint").Linter.Config} */ +export default baseConfig diff --git a/packages/services/guard/package.json b/packages/services/guard/package.json index a7ec3a3d4..3c263d217 100644 --- a/packages/services/guard/package.json +++ b/packages/services/guard/package.json @@ -1,9 +1,9 @@ { "name": "@0xsequence/guard", - "version": "3.0.0", + "version": "3.0.12", "description": "guard sub-package for Sequence", - "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/guard", - "author": "Sequence Platforms Inc.", + "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/guard", + "author": "Sequence Platforms ULC", "license": "Apache-2.0", "type": "module", "publishConfig": { @@ -15,7 +15,8 @@ "dev": "tsc --watch", "test": "vitest run", "test:coverage": "vitest run --coverage", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "lint": "eslint . --max-warnings 0" }, "exports": { ".": { @@ -24,12 +25,13 @@ } }, "devDependencies": { + "@repo/eslint-config": "workspace:^", "@repo/typescript-config": "workspace:^", - "@types/node": "^22.15.29", - "typescript": "^5.8.3", - "vitest": "^3.2.1" + "@types/node": "^25.3.0", + "typescript": "^6.0.3", + "vitest": "^4.0.18" }, "dependencies": { - "ox": "^0.7.2" + "ox": "^0.9.17" } } diff --git a/packages/services/guard/src/client/guard.gen.ts b/packages/services/guard/src/client/guard.gen.ts index ec0af4487..4eea436ee 100644 --- a/packages/services/guard/src/client/guard.gen.ts +++ b/packages/services/guard/src/client/guard.gen.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -// sequence-guard v0.4.0 b62e755c3f81d6b5a8e7462abc063a57a744cdef +// sequence-guard v0.5.0 910e01c32ffb24b42386d4ca6be119b0acc55c5f // -- // Code generated by webrpc-gen@v0.25.3 with typescript generator. DO NOT EDIT. // @@ -7,16 +7,16 @@ export const WebrpcHeader = 'Webrpc' -export const WebrpcHeaderValue = 'webrpc@v0.25.3;gen-typescript@v0.17.0;sequence-guard@v0.4.0' +export const WebrpcHeaderValue = 'webrpc@v0.25.3;gen-typescript@v0.17.0;sequence-guard@v0.5.0' // WebRPC description and code-gen version export const WebRPCVersion = 'v1' // Schema version of your RIDL schema -export const WebRPCSchemaVersion = 'v0.4.0' +export const WebRPCSchemaVersion = 'v0.5.0' // Schema hash generated from your RIDL schema -export const WebRPCSchemaHash = 'b62e755c3f81d6b5a8e7462abc063a57a744cdef' +export const WebRPCSchemaHash = '910e01c32ffb24b42386d4ca6be119b0acc55c5f' type WebrpcGenVersions = { webrpcGenVersion: string @@ -131,6 +131,7 @@ export interface OwnershipProof { export interface AuthToken { id: string token: string + resetAuth?: boolean } export interface RecoveryCode { diff --git a/packages/services/guard/src/local.ts b/packages/services/guard/src/local.ts index 3ef9ac787..3eeb7b8d5 100644 --- a/packages/services/guard/src/local.ts +++ b/packages/services/guard/src/local.ts @@ -1,4 +1,4 @@ -import { Address, Hex, Bytes, Secp256k1, Hash } from 'ox' +import { Address, Hex, Bytes, Secp256k1 } from 'ox' import * as Client from './client/guard.gen.js' import * as Types from './types.js' @@ -11,12 +11,13 @@ export class Guard implements Types.Guard { } async signPayload( - wallet: Address.Address, - chainId: number, - type: Client.PayloadType, + _wallet: Address.Address, + _chainId: number, + _type: Client.PayloadType, digest: Bytes.Bytes, - message: Bytes.Bytes, - signatures?: Client.Signature[], + _message: Bytes.Bytes, + _signatures?: Client.Signature[], + _token?: Client.AuthToken, ) { return Secp256k1.sign({ privateKey: this.privateKey, payload: digest }) } diff --git a/packages/services/guard/src/sequence.ts b/packages/services/guard/src/sequence.ts index fe1a003ce..b2acc812a 100644 --- a/packages/services/guard/src/sequence.ts +++ b/packages/services/guard/src/sequence.ts @@ -1,4 +1,4 @@ -import { Address, Hex, Signature, Bytes, Hash } from 'ox' +import { Address, Hex, Signature, Bytes } from 'ox' import * as Client from './client/guard.gen.js' import * as Types from './types.js' @@ -50,7 +50,7 @@ export class Guard implements Types.Guard { throw new Types.AuthRequiredError('PIN') } console.error(error) - throw new Error('Error signing with guard') + throw new Error('Error signing with guard', { cause: error }) } } } diff --git a/packages/services/guard/test/sequence.test.ts b/packages/services/guard/test/sequence.test.ts index 5ac92a378..ebc9a47b7 100644 --- a/packages/services/guard/test/sequence.test.ts +++ b/packages/services/guard/test/sequence.test.ts @@ -1,11 +1,11 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { Guard } from '../src/sequence' -import { PayloadType } from '../src/client/guard.gen' +import { Guard } from '../src/sequence.js' +import { PayloadType } from '../src/client/guard.gen.js' import { Address, Bytes, Hex } from 'ox' // Mock fetch globally for guard API calls const mockFetch = vi.fn() -global.fetch = mockFetch +globalThis.fetch = mockFetch describe('Sequence', () => { describe('GuardSigner', () => { @@ -86,7 +86,7 @@ describe('Sequence', () => { ok: true, }) - await guard.signPayload(testWallet, 1, PayloadType.ConfigUpdate, testMessageDigest, testMessage) + await guard.signPayload(testWallet, customChainId, PayloadType.ConfigUpdate, testMessageDigest, testMessage) const requestBody = JSON.parse(mockFetch.mock.calls[0][1].body) expect(requestBody.request.chainId).toBe(1) @@ -116,6 +116,21 @@ describe('Sequence', () => { ).rejects.toThrow('Error signing with guard') }) + it('Should preserve the original guard failure as cause', async () => { + mockFetch.mockRejectedValueOnce(new Error('Network error')) + + try { + await guard.signPayload(testWallet, 42161, PayloadType.ConfigUpdate, testMessageDigest, testMessage) + throw new Error('Expected signPayload to throw') + } catch (error) { + expect(error).toBeInstanceOf(Error) + expect((error as Error).message).toBe('Error signing with guard') + expect((error as Error & { cause?: unknown }).cause).toBeInstanceOf(Error) + expect((error as Error & { cause?: Error }).cause?.name).toBe('WebrpcRequestFailed') + expect((error as Error & { cause?: Error }).cause?.message).toBe('request failed') + } + }) + it('Should include proper headers and signer address in request', async () => { const mockGuardAddress = '0x9876543210987654321098765432109876543210' as Address.Address const customGuard = new Guard('https://guard.sequence.app', mockGuardAddress, fetch) diff --git a/packages/services/identity-instrument/CHANGELOG.md b/packages/services/identity-instrument/CHANGELOG.md new file mode 100644 index 000000000..fc966ce94 --- /dev/null +++ b/packages/services/identity-instrument/CHANGELOG.md @@ -0,0 +1,212 @@ +# @0xsequence/identity-instrument + +## 3.0.12 + +### Patch Changes + +- WDK fix + +## 3.0.11 + +### Patch Changes + +- Fix for relayer sponsored fees + +## 3.0.10 + +### Patch Changes + +- Minor network updates, relayer interface + +## 3.0.9 + +### Patch Changes + +- Fee options fixes + +## 3.0.8 + +### Patch Changes + +- Bug fix for relayer fee options handling + +## 3.0.7 + +### Patch Changes + +- Minor bug fixes + +## 3.0.6 + +### Patch Changes + +- userdata upgrade, arweave support + +## 3.0.5 + +### Patch Changes + +- Account federation support + +## 3.0.4 + +### Patch Changes + +- id-token login support + +## 3.0.3 + +### Patch Changes + +- 3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer + +## 3.0.1 + +### Patch Changes + +- Network and session fixes + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade + +## 3.0.0-beta.19 + +### Patch Changes + +- Final RC before 3.0.0 + +## 3.0.0-beta.18 + +### Patch Changes + +- multicall3 optimization + +## 3.0.0-beta.17 + +### Patch Changes + +- New chains, relayer rpc fix + +## 3.0.0-beta.16 + +### Patch Changes + +- ethauth support + +## 3.0.0-beta.15 + +### Patch Changes + +- New chains, minor fixes + +## 3.0.0-beta.14 + +### Patch Changes + +- Relayer fee options fix + +## 3.0.0-beta.13 + +### Patch Changes + +- Userdata service updates + +## 3.0.0-beta.12 + +### Patch Changes + +- Beta release with dapp connector fixes + +## 3.0.0-beta.11 + +### Patch Changes + +- 3.0.0 beta + +## 3.0.0-beta.10 + +### Patch Changes + +- dapp-client updates + +## 3.0.0-beta.9 + +### Patch Changes + +- dapp client updates for EOA login + +## 3.0.0-beta.8 + +### Patch Changes + +- Apple auth fixes + +## 3.0.0-beta.7 + +### Patch Changes + +- Apple auth fix + +## 3.0.0-beta.6 + +### Patch Changes + +- Fix signer 404 error, minor fixes + +## 3.0.0-beta.5 + +### Patch Changes + +- Beta release for v3 + +## 3.0.0-beta.4 + +### Patch Changes + +- RC5 upgrade + +## 3.0.0-beta.3 + +### Patch Changes + +- 3.0.0-beta.3 with fixes + +## 3.0.0-beta.2 + +### Patch Changes + +- 3.0.0-beta.2 with identity instrument updates + +## 3.0.0-beta.1 + +### Patch Changes + +- 3.0.0-beta.1 diff --git a/packages/services/identity-instrument/eslint.config.js b/packages/services/identity-instrument/eslint.config.js new file mode 100644 index 000000000..cecf89b03 --- /dev/null +++ b/packages/services/identity-instrument/eslint.config.js @@ -0,0 +1,4 @@ +import { config as baseConfig } from "@repo/eslint-config/base" + +/** @type {import("eslint").Linter.Config} */ +export default baseConfig diff --git a/packages/services/identity-instrument/package.json b/packages/services/identity-instrument/package.json index 3accab80e..f6d9038b6 100644 --- a/packages/services/identity-instrument/package.json +++ b/packages/services/identity-instrument/package.json @@ -1,6 +1,6 @@ { "name": "@0xsequence/identity-instrument", - "version": "0.0.0", + "version": "3.0.12", "license": "Apache-2.0", "type": "module", "publishConfig": { @@ -10,7 +10,9 @@ "scripts": { "build": "tsc", "dev": "tsc --watch", - "test": "vitest run" + "test": "vitest run", + "lint": "eslint . --max-warnings 0", + "typecheck": "tsc --noEmit" }, "exports": { ".": { @@ -19,14 +21,15 @@ } }, "devDependencies": { + "@repo/eslint-config": "workspace:^", "@repo/typescript-config": "workspace:^", - "@types/node": "^22.15.29", - "typescript": "^5.8.3", - "vitest": "^3.2.1" + "@types/node": "^25.3.0", + "typescript": "^6.0.3", + "vitest": "^4.0.18" }, "dependencies": { "json-canonicalize": "^2.0.0", "jwt-decode": "^4.0.0", - "ox": "^0.7.2" + "ox": "^0.9.17" } } diff --git a/packages/services/indexer/CHANGELOG.md b/packages/services/indexer/CHANGELOG.md index 29f042bf6..c6a1e0d33 100644 --- a/packages/services/indexer/CHANGELOG.md +++ b/packages/services/indexer/CHANGELOG.md @@ -1,5 +1,216 @@ # @0xsequence/indexer +## 3.0.12 + +### Patch Changes + +- WDK fix + +## 3.0.11 + +### Patch Changes + +- Fix for relayer sponsored fees + +## 3.0.10 + +### Patch Changes + +- Minor network updates, relayer interface + +## 3.0.9 + +### Patch Changes + +- Fee options fixes + +## 3.0.8 + +### Patch Changes + +- Bug fix for relayer fee options handling + +## 3.0.7 + +### Patch Changes + +- Minor bug fixes + +## 3.0.6 + +### Patch Changes + +- userdata upgrade, arweave support + +## 3.0.5 + +### Patch Changes + +- Account federation support + +## 3.0.4 + +### Patch Changes + +- id-token login support + +## 3.0.3 + +### Patch Changes + +- 3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer + +## 3.0.1 + +### Patch Changes + +- Network and session fixes + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade + +## 3.0.0-beta.19 + +### Patch Changes + +- Final RC before 3.0.0 + +## 3.0.0-beta.18 + +### Patch Changes + +- multicall3 optimization + +## 3.0.0-beta.17 + +### Patch Changes + +- New chains, relayer rpc fix + +## 3.0.0-beta.16 + +### Patch Changes + +- ethauth support + +## 3.0.0-beta.15 + +### Patch Changes + +- New chains, minor fixes + +## 3.0.0-beta.14 + +### Patch Changes + +- Relayer fee options fix + +## 3.0.0-beta.13 + +### Patch Changes + +- Userdata service updates + +## 3.0.0-beta.12 + +### Patch Changes + +- Beta release with dapp connector fixes + +## 3.0.0-beta.11 + +### Patch Changes + +- 3.0.0 beta + +## 3.0.0-beta.10 + +### Patch Changes + +- dapp-client updates + +## 3.0.0-beta.9 + +### Patch Changes + +- dapp client updates for EOA login + +## 3.0.0-beta.8 + +### Patch Changes + +- Apple auth fixes + +## 3.0.0-beta.7 + +### Patch Changes + +- Apple auth fix + +## 3.0.0-beta.6 + +### Patch Changes + +- Fix signer 404 error, minor fixes + +## 3.0.0-beta.5 + +### Patch Changes + +- Beta release for v3 + +## 3.0.0-beta.4 + +### Patch Changes + +- RC5 upgrade + +## 3.0.0-beta.3 + +### Patch Changes + +- 3.0.0-beta.3 with fixes + +## 3.0.0-beta.2 + +### Patch Changes + +- 3.0.0-beta.2 with identity instrument updates + +## 3.0.0-beta.1 + +### Patch Changes + +- 3.0.0-beta.1 + ## 2.3.8 ### Patch Changes diff --git a/packages/services/indexer/eslint.config.js b/packages/services/indexer/eslint.config.js new file mode 100644 index 000000000..cecf89b03 --- /dev/null +++ b/packages/services/indexer/eslint.config.js @@ -0,0 +1,4 @@ +import { config as baseConfig } from "@repo/eslint-config/base" + +/** @type {import("eslint").Linter.Config} */ +export default baseConfig diff --git a/packages/services/indexer/package.json b/packages/services/indexer/package.json index 0eec0d29d..9aa8ed4f4 100644 --- a/packages/services/indexer/package.json +++ b/packages/services/indexer/package.json @@ -1,28 +1,31 @@ { "name": "@0xsequence/indexer", - "version": "3.0.0", + "version": "3.0.12", "description": "indexer sub-package for Sequence", - "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/indexer", - "author": "Sequence Platforms Inc.", + "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/indexer", + "author": "Sequence Platforms ULC", "license": "Apache-2.0", "publishConfig": { "access": "public" }, + "type": "module", "scripts": { "build": "tsc", "dev": "tsc --watch", "test": "echo", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "lint": "eslint . --max-warnings 0" }, "exports": { ".": { - "types": "./src/index.ts", + "types": "./dist/index.d.ts", "default": "./dist/index.js" } }, "devDependencies": { + "@repo/eslint-config": "workspace:^", "@repo/typescript-config": "workspace:^", - "@types/node": "^22.15.29", - "typescript": "^5.8.3" + "@types/node": "^25.3.0", + "typescript": "^6.0.3" } } diff --git a/packages/services/indexer/src/index.ts b/packages/services/indexer/src/index.ts index a4588c71e..15f28c2cb 100644 --- a/packages/services/indexer/src/index.ts +++ b/packages/services/indexer/src/index.ts @@ -1,8 +1,8 @@ -export * from './indexer.gen' -export * as IndexerGateway from './indexergw.gen' +export * from './indexer.gen.js' +export * as IndexerGateway from './indexergw.gen.js' -import { Indexer as IndexerRpc } from './indexer.gen' -import { IndexerGateway as IndexerGatewayRpc } from './indexergw.gen' +import { Indexer as IndexerRpc } from './indexer.gen.js' +import { IndexerGateway as IndexerGatewayRpc } from './indexergw.gen.js' export class SequenceIndexer extends IndexerRpc { constructor( @@ -17,7 +17,7 @@ export class SequenceIndexer extends IndexerRpc { _fetch = (input: RequestInfo, init?: RequestInit): Promise => { // automatically include jwt and access key auth header to requests // if its been set on the api client - const headers: { [key: string]: any } = {} + const headers: Record = {} const jwtAuth = this.jwtAuth const projectAccessKey = this.projectAccessKey @@ -50,7 +50,7 @@ export class SequenceIndexerGateway extends IndexerGatewayRpc { _fetch = (input: RequestInfo, init?: RequestInit): Promise => { // automatically include jwt and access key auth header to requests // if its been set on the api client - const headers: { [key: string]: any } = {} + const headers: Record = {} const jwtAuth = this.jwtAuth const projectAccessKey = this.projectAccessKey diff --git a/packages/services/indexer/src/indexer.gen.ts b/packages/services/indexer/src/indexer.gen.ts index 89de72566..8dba62d16 100644 --- a/packages/services/indexer/src/indexer.gen.ts +++ b/packages/services/indexer/src/indexer.gen.ts @@ -1,79 +1,420 @@ /* eslint-disable */ -// sequence-indexer v0.4.0 546b527de7002f409ffa602ad35b5a3abe979088 +// sequence-indexer v0.4.0 2bca559c4c590bce7d70c33df115a58399efec82 // -- -// Code generated by webrpc-gen@v0.21.1 with typescript generator. DO NOT EDIT. +// Code generated by Webrpc-gen@v0.31.2 with typescript generator. DO NOT EDIT. // -// webrpc-gen -schema=indexer.ridl -service=Indexer -target=typescript -client -out=./clients/indexer.gen.ts +// webrpc-gen -schema=merged.gen.json -service=Indexer -target=typescript -client -out=./clients/indexer.gen.ts -export const WebrpcHeader = 'Webrpc' - -export const WebrpcHeaderValue = 'webrpc@v0.21.1;gen-typescript@v0.15.1;sequence-indexer@v0.4.0' - -// WebRPC description and code-gen version -export const WebRPCVersion = 'v1' +// Webrpc description and code-gen version +export const WebrpcVersion = 'v1' // Schema version of your RIDL schema -export const WebRPCSchemaVersion = 'v0.4.0' +export const WebrpcSchemaVersion = 'v0.4.0' // Schema hash generated from your RIDL schema -export const WebRPCSchemaHash = '546b527de7002f409ffa602ad35b5a3abe979088' +export const WebrpcSchemaHash = '2bca559c4c590bce7d70c33df115a58399efec82' -type WebrpcGenVersions = { - webrpcGenVersion: string - codeGenName: string - codeGenVersion: string - schemaName: string - schemaVersion: string -} +// +// Client interface +// -export function VersionFromHeader(headers: Headers): WebrpcGenVersions { - const headerValue = headers.get(WebrpcHeader) - if (!headerValue) { - return { - webrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } +export interface IndexerClient { + addWebhookListener( + req: AddWebhookListenerRequest, + headers?: object, + signal?: AbortSignal, + ): Promise - return parseWebrpcGenVersions(headerValue) -} + /** + * Fetches a single receipt and then will stop the subscription + */ + fetchTransactionReceipt( + req: FetchTransactionReceiptRequest, + headers?: object, + signal?: AbortSignal, + ): Promise -function parseWebrpcGenVersions(header: string): WebrpcGenVersions { - const versions = header.split(';') - if (versions.length < 3) { - return { - webrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } + /** + * Fetches a single receipt with filter and then will stop the subscription + */ + fetchTransactionReceiptWithFilter( + req: FetchTransactionReceiptWithFilterRequest, + headers?: object, + signal?: AbortSignal, + ): Promise - const [_, webrpcGenVersion] = versions[0]!.split('@') - const [codeGenName, codeGenVersion] = versions[1]!.split('@') - const [schemaName, schemaVersion] = versions[2]!.split('@') + /** + * Webhooks + */ + getAllWebhookListeners( + req: GetAllWebhookListenersRequest, + headers?: object, + signal?: AbortSignal, + ): Promise - return { - webrpcGenVersion: webrpcGenVersion!, - codeGenName: codeGenName!, - codeGenVersion: codeGenVersion!, - schemaName: schemaName!, - schemaVersion: schemaVersion!, - } + /** + * Get balance update aggregate values -- useful for syncing balance details of a contract, ie. from Skyweaver. + * Also consider using SubscribeBalanceUpdates or SubscribeEvents as other alternatives. + */ + getBalanceUpdates( + req: GetBalanceUpdatesRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * Get the chain ID of the indexer + */ + getChainID(headers?: object, signal?: AbortSignal): Promise + + /** + * Queries an ethereum node for the latest and confirm ETH balances + * DEPRECATED: use GetNativeTokenBalance instead + * + * @deprecated GetNativeTokenBalance + */ + getEtherBalance(req: GetEtherBalanceRequest, headers?: object, signal?: AbortSignal): Promise + + /** + * GetMarketplaceOrders queries marketplace orders with filtering and pagination. + * + * Retrieves buy orders (offers) and sell orders (listings) from a specific marketplace + * and collection with comprehensive filtering options. + * + * Parameters: + * marketplaceContractAddress: Target marketplace contract (required) + * collectionAddress: NFT collection contract (required) + * filter: MarketplaceOrderFilter with options: + * - isListing: true=listings, false=offers, omit=both + * - userAddresses: Include specific users + * - currencyAddresses: Filter by currencies (empty=all) + * - orderIds: Filter by specific order ids (empty=all) + * - tokenIds: Filter by specific tokens (empty=all) + * - excludeUserAddresses: Exclude specific users + * - blockNumberGt: Orders greater than block number + * - createdAtAfter: Orders after timestamp + * - orderStatuses: Filter by status (OPEN, CLOSED, CANCELLED) + * - returnExpired: Include expired orders + * page: Pagination control (optional) + * + * Returns: Updated pagination info and array of matching orders + */ + getMarketplaceOrders( + req: GetMarketplaceOrdersRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * GetMarketplaceTopOrders finds the most competitive orders for specific tokens. + * + * Optimized for price discovery, returns the best available orders for each token. + * Useful for displaying current market prices and finding trading opportunities. + * + * Parameters: + * marketplaceContractAddress: Target marketplace contract (required) + * collectionAddress: NFT collection contract (required) + * filter: MarketplaceTopOrdersFilter with options: + * - currencyAddresses: Consider specific currencies (empty=all) + * - tokenIds: Target token IDs (required, non-empty) + * - isListing: true=listings/sell orders, false=offers/buy orders + * - priceSort: ASC=lowest first, DESC=highest first + * - excludeUser: Hide orders from specific user + * + * Returns: Array of top-priced active orders, sorted by priceSort preference + */ + getMarketplaceTopOrders( + req: GetMarketplaceTopOrdersRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * GetNativeTokenBalance queries an ethereum node for the latest native token account balance. + * The native token is the token of the chain the indexer is connected to, for example, ETH on Ethereum + * and POL on Polygon. + */ + getNativeTokenBalance( + req: GetNativeTokenBalanceRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * GetTokenBalances returns a balance summary/details for a specific account. By default + * if accountAddress is left empty, it will use the account from the jwt session. + * + * Also, if contractAddress is undefined, then it will list all current user coins/collectibles. + * But, if contractAddress is provided, then it will return the token balances for the contract, this is + * only useful for 1155, but for other tokens, it can act as a filter for the single balance. + * + * DEPRECATED: use GetTokenBalancesSummary / GetTokenBalancesDetails + * + * @deprecated GetTokenBalancesSummary + */ + getTokenBalances( + req: GetTokenBalancesRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * GetTokenBalancesByContract returns a balances for a specific accounts and + * contracts. The collection ERC721 & ERC1155 tokens are represented as + * individual balances. + * + * If `filter` is not provided, it will error out as it requires at least + * contract address. + * + * If `filter.contractStatus` is not provided, it will include verified only + * tokens. + */ + getTokenBalancesByContract( + req: GetTokenBalancesByContractRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * GetTokenBalancesDetails returns a detailed balance summary for a specific + * accounts. The collection ERC721 & ERC1155 tokens are represented as + * individual balances. + * + * If `filter` is not provided, it will use the filter with account from the + * jwt session. + * + * If `filter.contractStatus` is not provided, it will include verified only + * tokens. + */ + getTokenBalancesDetails( + req: GetTokenBalancesDetailsRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * GetTokenBalancesSummary returns a summary of token balances for a specific + * accounts. The collection ERC721 & ERC1155 tokens are represented as a + * single aggregated balance. + * + * If `filter` is not provided, it will use the filter with account from the + * jwt session. + * + * If `filter.contractStatus` is not provided, it will include verified only + * tokens. + */ + getTokenBalancesSummary( + req: GetTokenBalancesSummaryRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * GetTokenIDRanges returns the range of tokenIDs for a token collection contract. + * This is useful for ERC-721 and ERC-1155 contracts to get the range of valid tokenIDs. It is similar to + * GetTokenIDs, but returns the range of tokenIDs instead of the list of tokenIDs, which is more efficient + * for large collections and very easy to the caller to expand the range into a list of tokenIDs. + * + * NOTE: this method will only return up to 15,000 ranges, if there are more ranges, it will return + * a boolean to indicate there are more ranges beyond the first 15,000. Therefore, if `moreRanges` is + * false then you have all the ranges, but if true, you need to make a follow up call to fetch the next + * page of ranges. + * + * As an example, if a NFT collection of 100,000 tokens uses ids from 1,2,3,...,100_000 then this endpoint + * will return just a single range from [1,100_000], but if there are gaps between the sequence, then + * those will be broken into separate range entries. + */ + getTokenIDRanges( + req: GetTokenIDRangesRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * GetTokenIDs returns the list of each individual token id for a token collection contract. + * This is useful for ERC-721 and ERC-1155 contracts to get the list of valid tokenIDs. + */ + getTokenIDs(req: GetTokenIDsRequest, headers?: object, signal?: AbortSignal): Promise + + getTokenPrice(req: GetTokenPriceRequest, headers?: object, signal?: AbortSignal): Promise + + getTokenPrices(req: GetTokenPricesRequest, headers?: object, signal?: AbortSignal): Promise + + /** + * GetTokenSupplies returns the set of tokenIDs used by a contract address, supporting ERC-20, ERC-721, and ERC-1155 + * contracts, and their respective supply as well. + */ + getTokenSupplies( + req: GetTokenSuppliesRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * GetTokenSuppliesMap returns the token supplies of ERC-20 and ERC-1155 tokens as requested in the `tokenMap` + * represented as a map of contractAddress :: []tokenIDs. + * + * For an ERC-20 specify tokenIDs as an empty array or [0], for example, { '0xdef': [] } or { '0xdef': [0] } + * For ERC-1155 pass the array of tokens are strings, ie. { '0xabc': ['1', '2', '3'] } + */ + getTokenSuppliesMap( + req: GetTokenSuppliesMapRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * History of mined transactions for the account which includes a list of token transfers (sent/recieved) + * and sent transactions from a Sequence wallet + */ + getTransactionHistory( + req: GetTransactionHistoryRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + getWebhookListener( + req: GetWebhookListenerRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + listTokenPrices(req: ListTokenPricesRequest, headers?: object, signal?: AbortSignal): Promise + + pauseAllWebhookListeners( + req: PauseAllWebhookListenersRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * Ping the indexer + */ + ping(headers?: object, signal?: AbortSignal): Promise + + removeAllWebhookListeners( + req: RemoveAllWebhookListenersRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + removeWebhookListener( + req: RemoveWebhookListenerRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + resumeAllWebhookListeners( + req: ResumeAllWebhookListenersRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * Get the current runtime health status of the indexer + */ + runtimeStatus(headers?: object, signal?: AbortSignal): Promise + + /** + * SubscribeBalanceUpdates listens to balance updates for a specific contract address + */ + subscribeBalanceUpdates( + req: SubscribeBalanceUpdatesRequest, + options: WebrpcStreamOptions, + ): WebrpcStreamController + + /** + * SubscribeEvents listens to events on-chain based on the filter criteria + * + * TODO: some additional options can be passed such as block, reorg true, etc. + * or stay behind, etc. + */ + subscribeEvents( + req: SubscribeEventsRequest, + options: WebrpcStreamOptions, + ): WebrpcStreamController + + /** + * Listen to transaction receipts on-chain based on the filter criteria + */ + subscribeReceipts( + req: SubscribeReceiptsRequest, + options: WebrpcStreamOptions, + ): WebrpcStreamController + + /** + * Re-sync an incorrect token balance with the correct on-chain balance + * NOTE: this method is almost never used, but we've marked it internal in case + * we ever want to use it again. This method was written a very long time ago in + * scenarios when the indexer had little bugs, but now its solid. + */ + syncBalance(req: SyncBalanceRequest, headers?: object, signal?: AbortSignal): Promise + + toggleWebhookListener( + req: ToggleWebhookListenerRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + updateWebhookListener( + req: UpdateWebhookListenerRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * Get the current version of the indexer + */ + version(headers?: object, signal?: AbortSignal): Promise } // -// Types +// Schema types // -export enum ResourceStatus { - NOT_AVAILABLE = 'NOT_AVAILABLE', - REFRESHING = 'REFRESHING', - AVAILABLE = 'AVAILABLE', +export interface Asset { + id: number + collectionId: number + tokenId?: string + url?: string + metadataField: string + name?: string + filesize?: number + mimeType?: string + width?: number + height?: number + updatedAt?: string +} + +export enum BackupMode { + INCREMENTAL = 'INCREMENTAL', + COMPLETE = 'COMPLETE', +} + +export interface BloomStats { + hitRatio: string + falsePositivesPercent: string + hitCount: number + missCount: number + falsePositives: number +} + +export interface BloomStatus { + enabled: boolean + initialized: boolean + bloomInitElapsedTime: string + stats: BloomStats +} + +export interface Bond { + pebble: PebbleMetrics + estimatedDiskUsagePerTable: any + estimatedDiskUsageTotal: string +} + +export interface ChainInfo { + chainId: number + chainName: string } export interface ContractInfo { @@ -93,56 +434,29 @@ export interface ContractInfo { status: ResourceStatus } -export interface ContractInfoExtensions { - link: string - description: string - categories: Array - ogImage: string - ogName: string - originChainId: number - originAddress: string - blacklist: boolean - verified: boolean - verifiedBy: string - featured: boolean - featureIndex: number +export interface ContractInfoExtensionBridgeInfo { + tokenAddress: string } -export interface TokenMetadata { - contractAddress?: string - tokenId: string - source: string - name: string - description?: string - image?: string - video?: string - audio?: string - properties?: { [key: string]: any } - attributes: Array<{ [key: string]: any }> - image_data?: string - external_url?: string - background_color?: string - animation_url?: string - decimals?: number - updatedAt?: string - assets?: Array - status: ResourceStatus - queuedAt?: string - lastFetched?: string +export interface ContractInfoExtensionIndexingInfo { + useOnChainBalance: boolean } -export interface Asset { - id: number - collectionId: number - tokenId?: string - url?: string - metadataField: string - name?: string - filesize?: number - mimeType?: string - width?: number - height?: number - updatedAt?: string +export interface ContractInfoExtensions { + link?: string + description?: string + categories?: Array + bridgeInfo?: { [key: string]: ContractInfoExtensionBridgeInfo } + indexingInfo?: ContractInfoExtensionIndexingInfo + ogImage?: string + ogName?: string + originChainId?: number + originAddress?: string + blacklist?: boolean + verified?: boolean + verifiedBy?: string + featured?: boolean + featureIndex?: number } export enum ContractType { @@ -156,72 +470,73 @@ export enum ContractType { ERC721_BRIDGE = 'ERC721_BRIDGE', ERC1155_BRIDGE = 'ERC1155_BRIDGE', SEQ_MARKETPLACE = 'SEQ_MARKETPLACE', + ERC6909 = 'ERC6909', } -export enum EventLogType { - UNKNOWN = 'UNKNOWN', - BLOCK_ADDED = 'BLOCK_ADDED', - BLOCK_REMOVED = 'BLOCK_REMOVED', -} - -export enum EventLogDataType { - EVENT = 'EVENT', - TOKEN_TRANSFER = 'TOKEN_TRANSFER', - NATIVE_TOKEN_TRANSFER = 'NATIVE_TOKEN_TRANSFER', - SEQUENCE_TXN = 'SEQUENCE_TXN', -} - -export enum OrderStatus { - OPEN = 'OPEN', - CLOSED = 'CLOSED', - CANCELLED = 'CANCELLED', +export enum ContractVerificationStatus { + VERIFIED = 'VERIFIED', + UNVERIFIED = 'UNVERIFIED', + ALL = 'ALL', } -export enum TxnTransferType { - UNKNOWN = 'UNKNOWN', - SEND = 'SEND', - RECEIVE = 'RECEIVE', +export interface DiskUsage { + humanReadable: string + used: number + size: number + percent: number + dirs: { [key: string]: string } } -export enum TransactionStatus { - FAILED = 'FAILED', - SUCCESSFUL = 'SUCCESSFUL', +export interface EtherBalance { + accountAddress: string + balanceWei: string } -export enum TransactionType { - LegacyTxnType = 'LegacyTxnType', - AccessListTxnType = 'AccessListTxnType', - DynamicFeeTxnType = 'DynamicFeeTxnType', +export interface EventDecoded { + topicHash: string + eventSig: string + types: Array + names: Array + values: Array } -export enum SortOrder { - DESC = 'DESC', - ASC = 'ASC', +export interface EventFilter { + events?: Array + contractAddresses?: Array + accounts?: Array + tokenIDs?: Array } -export enum ContractVerificationStatus { - VERIFIED = 'VERIFIED', - UNVERIFIED = 'UNVERIFIED', - ALL = 'ALL', +export interface EventLog { + id: number + uid: string + type: EventLogType + blockNumber: number + blockHash: string + parentBlockHash: string + contractAddress: string + contractType: ContractType + txnHash: string + txnIndex: number + txnLogIndex: number + logDataType: EventLogDataType + ts: string + txnInfo?: TxnInfo + rawLog?: { [key: string]: any } + event?: EventDecoded } -export interface Version { - webrpcVersion: string - schemaVersion: string - schemaHash: string - appVersion: string +export enum EventLogDataType { + EVENT = 'EVENT', + TOKEN_TRANSFER = 'TOKEN_TRANSFER', + NATIVE_TOKEN_TRANSFER = 'NATIVE_TOKEN_TRANSFER', + SEQUENCE_TXN = 'SEQUENCE_TXN', } -export interface RuntimeStatus { - healthOK: boolean - indexerEnabled: boolean - startTime: string - uptime: number - ver: string - branch: string - commitHash: string - chainID: number - checks: RuntimeChecks +export enum EventLogType { + UNKNOWN = 'UNKNOWN', + BLOCK_ADDED = 'BLOCK_ADDED', + BLOCK_REMOVED = 'BLOCK_REMOVED', } export interface GatewayBackendResponseTime { @@ -235,6 +550,30 @@ export interface GatewayBackendRuntimeStatus { responseTime: GatewayBackendResponseTime } +export interface GatewayEtherBalance { + chainId: number + errorReason?: string + result: EtherBalance +} + +export interface GatewayNativeTokenBalance { + chainId: number + errorReason?: string + result: NativeTokenBalance +} + +export interface GatewayNativeTokenBalances { + chainId: number + errorReason?: string + results: Array +} + +export interface GatewayPrice { + chainID: number + errorReason?: string + results: Array +} + export interface GatewayRuntimeStatus { healthOK: boolean startTime: string @@ -245,43 +584,115 @@ export interface GatewayRuntimeStatus { backends: Array } -export interface WALWriterRuntimeStatus { - healthOK: boolean - startTime: string - uptime: number - ver: string - branch: string - commitHash: string +export interface GatewayTokenBalance { + chainId: number + errorReason?: string + results: Array +} + +export interface GatewayTokenPriceQuery { chainID: number - percentWALWritten: number + queries: Array +} + +export interface GatewayTransaction { + chainId: number + errorReason?: string + results: Array +} + +export interface IndexState { + chainId: string + lastBlockNum: number + lastBlockHash: string +} + +export interface IndexedBlock { + blockNumber: number + blockShortHash: string +} + +export interface IndexerMetrics { + blocksPerSecond: number + eventsPerSecond: number +} + +export interface MarketplaceOrder { + orderId: string + tokenContract: string + tokenId: string + isListing: boolean + quantity: string + quantityRemaining: string + currencyAddress: string + pricePerToken: string + expiry: string + orderStatus: OrderStatus + createdBy: string + blockNumber: number + orderbookContractAddress: string + createdAt: number +} + +export interface MarketplaceOrderFilter { + isListing?: boolean + userAddresses?: Array + currencyAddresses: Array + orderIds: Array + tokenIds: Array + excludeUserAddresses?: Array + blockNumberGt: number + createdAtAfter: number + orderStatuses: Array + returnExpired: boolean +} + +export interface MarketplaceTopOrdersFilter { + currencyAddresses: Array + tokenIds: Array + isListing: boolean + priceSort: SortOrder + excludeUser?: string +} + +export interface MetadataOptions { + verifiedOnly?: boolean + unverifiedOnly?: boolean + includeContracts?: Array } -export interface RuntimeChecks { - running: boolean - runnables: any - cgoEnabled: boolean - quotaControlEnabled: boolean - syncMode: string - percentIndexed: number - lastBlockNum: number - lastBlockNumWithState: number - bloomStatus: BloomStatus - bond: Bond - diskUsage: DiskUsage +export interface NativeTokenBalance { + accountAddress: string + chainId: number + name: string + symbol: string + balance: string + balanceUSD: string + priceUSD: string + priceUpdatedAt?: string + errorReason?: string } -export interface DiskUsage { - humanReadable: string - used: number - size: number - percent: number - dirs: { [key: string]: string } +export enum NetworkType { + MAINNETS = 'MAINNETS', + TESTNETS = 'TESTNETS', + ALL = 'ALL', } -export interface Bond { - pebble: PebbleMetrics - estimatedDiskUsagePerTable: any - estimatedDiskUsageTotal: string +export enum OrderStatus { + OPEN = 'OPEN', + CLOSED = 'CLOSED', + CANCELLED = 'CANCELLED', +} + +export interface Page { + page?: number + column?: string + before?: any + after?: any + sort?: Array + pageSize?: number + more?: boolean } export interface PebbleMetrics { @@ -292,66 +703,54 @@ export interface PebbleMetrics { compactionMarkedFiles: number } -export interface BloomStatus { - enabled: boolean - initialized: boolean - bloomInitElapsedTime: string -} - -export interface EtherBalance { - accountAddress: string - balanceWei: string +export interface Price { + contractAddress: string + tokenID?: string + priceUSD: string + updatedAt?: string } -export interface NativeTokenBalance { - accountAddress: string - chainId: number - balance: string - error: string +export enum ResourceStatus { + NOT_AVAILABLE = 'NOT_AVAILABLE', + REFRESHING = 'REFRESHING', + AVAILABLE = 'AVAILABLE', } -export interface IndexState { - chainId: string +export interface RuntimeChecks { + running: boolean + runnables: any + cgoEnabled: boolean + quotaControlEnabled: boolean + syncMode: string + percentIndexed: number lastBlockNum: number - lastBlockHash: string -} - -export interface IndexedBlock { - blockNumber: number - blockShortHash: string + lastBlockNumWithState: number + bloomStatus: BloomStatus + bond: Bond + diskUsage: DiskUsage + metrics: IndexerMetrics } -export interface TxnInfo { - from: string - to: string - value: string +export interface RuntimeStatus { + healthOK: boolean + indexerEnabled: boolean + startTime: string + uptime: number + ver: string + branch: string + commitHash: string + chainID: number + checks: RuntimeChecks } -export interface EventLog { - id: number - uid: string - type: EventLogType - blockNumber: number - blockHash: string - parentBlockHash: string - contractAddress: string - contractType: ContractType - txnHash: string - txnIndex: number - txnLogIndex: number - logDataType: EventLogDataType - ts: string - txnInfo?: TxnInfo - rawLog?: { [key: string]: any } - event?: EventDecoded +export interface SortBy { + column: string + order: SortOrder } -export interface EventDecoded { - topicHash: string - eventSig: string - types: Array - names: Array - values: Array +export enum SortOrder { + DESC = 'DESC', + ASC = 'ASC', } export interface TokenBalance { @@ -360,6 +759,9 @@ export interface TokenBalance { accountAddress: string tokenID?: string balance: string + balanceUSD: string + priceUSD: string + priceUpdatedAt?: string blockHash: string blockNumber: number chainId: number @@ -369,39 +771,32 @@ export interface TokenBalance { tokenMetadata?: TokenMetadata } -export interface OrderbookOrder { - orderId: string - tokenContract: string - tokenId: string - isListing: boolean - quantity: string - quantityRemaining: string - currencyAddress: string - pricePerToken: string - expiry: string - orderStatus: OrderStatus - createdBy: string - blockNumber: number - orderbookContractAddress: string - createdAt: number +export interface TokenBalanceFilter { + contractAddress: string + sinceBlockNumber: number } -export interface OrderbookOrderFilter { - isListing?: boolean - userAddresses?: Array - tokenIds: Array - excludeUserAddresses?: Array - afterBlockNumber: number - afterCreatedAt: number - beforeExpiry: number - userAddress?: string - excludeUserAddress?: string +export interface TokenBalancesByContractFilter { + contractAddresses: Array + accountAddresses?: Array + contractStatus?: ContractVerificationStatus + tokenIDs?: Array +} + +export interface TokenBalancesFilter { + accountAddresses: Array + contractStatus?: ContractVerificationStatus + contractTypes?: Array + contractWhitelist?: Array + contractBlacklist?: Array + omitNativeBalances: boolean + omitPrices?: boolean + tokenIDs?: Array } export interface TokenHistory { blockNumber: number blockHash: string - accountAddress: string contractAddress: string contractType: ContractType fromAddress: string @@ -409,12 +804,45 @@ export interface TokenHistory { txnHash: string txnIndex: number txnLogIndex: number - logData: string tokenIDs: string - Amounts: string + amounts: string ts: string } +export interface TokenIDRange { + start: string + end: string +} + +export interface TokenMetadata { + chainId?: number + contractAddress?: string + tokenId: string + source: string + name: string + description?: string + image?: string + video?: string + audio?: string + properties?: { [key: string]: any } + attributes: Array<{ [key: string]: any }> + image_data?: string + external_url?: string + background_color?: string + animation_url?: string + decimals?: number + updatedAt?: string + assets?: Array + status: ResourceStatus + queuedAt?: string + lastFetched?: string +} + +export interface TokenPriceQuery { + contractAddress: string + tokenID?: string +} + export interface TokenSupply { tokenID: string supply: string @@ -433,17 +861,12 @@ export interface Transaction { timestamp: string } -export interface TxnTransfer { - transferType: TxnTransferType - contractAddress: string - contractType: ContractType - from: string - to: string - tokenIds?: Array - amounts: Array - logIndex: number - contractInfo?: ContractInfo - tokenMetadata?: { [key: string]: TokenMetadata } +export interface TransactionFilter { + txnHash?: string + from?: string + to?: string + contractAddress?: string + event?: string } export interface TransactionHistoryFilter { @@ -456,14 +879,14 @@ export interface TransactionHistoryFilter { fromBlock?: number toBlock?: number tokenID?: string + omitPrices?: boolean } -export interface TransactionFilter { - txnHash?: string - from?: string - to?: string - contractAddress?: string - event?: string +export interface TransactionLog { + contractAddress: string + topics: Array + data: string + index: number } export interface TransactionReceipt { @@ -482,31 +905,60 @@ export interface TransactionReceipt { reorged: boolean } -export interface TransactionLog { +export enum TransactionStatus { + FAILED = 'FAILED', + SUCCESSFUL = 'SUCCESSFUL', +} + +export enum TransactionType { + LegacyTxnType = 'LegacyTxnType', + AccessListTxnType = 'AccessListTxnType', + DynamicFeeTxnType = 'DynamicFeeTxnType', +} + +export interface TxnInfo { + from: string + to: string + value: string +} + +export interface TxnTransfer { + transferType: TxnTransferType contractAddress: string - topics: Array - data: string - index: number + contractType: ContractType + from: string + to: string + tokenIds?: Array + amounts: Array + logIndex: number + amountsUSD?: Array + pricesUSD?: Array + contractInfo?: ContractInfo + tokenMetadata?: { [key: string]: TokenMetadata } } -export interface TokenIDRange { - start: string - end: string +export enum TxnTransferType { + UNKNOWN = 'UNKNOWN', + SEND = 'SEND', + RECEIVE = 'RECEIVE', } -export interface Page { - page?: number - column?: string - before?: any - after?: any - sort?: Array - pageSize?: number - more?: boolean +export interface Version { + webrpcVersion: string + schemaVersion: string + schemaHash: string + appVersion: string } -export interface SortBy { - column: string - order: SortOrder +export interface WALWriterRuntimeStatus { + healthOK: boolean + startTime: string + uptime: number + ver: string + branch: string + commitHash: string + chainID: number + percentWALWritten: number } export interface WebhookListener { @@ -519,469 +971,345 @@ export interface WebhookListener { active: boolean } -export interface EventFilter { - events?: Array - contractAddresses?: Array - accounts?: Array - tokenIDs?: Array +export interface AddWebhookListenerRequest { + url: string + filters: EventFilter + projectId?: number } -export interface TokenBalanceFilter { - contractAddress: string - sinceBlockNumber: number +export interface AddWebhookListenerResponse { + status: boolean + listener: WebhookListener } -export interface MetadataOptions { - verifiedOnly?: boolean - unverifiedOnly?: boolean - includeContracts?: Array +export interface FetchTransactionReceiptRequest { + txnHash: string + maxBlockWait?: number } -export interface TokenBalancesFilter { - accountAddresses: Array - contractStatus?: ContractVerificationStatus - contractTypes?: Array - contractWhitelist?: Array - contractBlacklist?: Array - omitNativeBalances: boolean +export interface FetchTransactionReceiptResponse { + receipt: TransactionReceipt } -export interface TokenBalancesByContractFilter { - contractAddresses: Array - accountAddresses?: Array - contractStatus?: ContractVerificationStatus +export interface FetchTransactionReceiptWithFilterRequest { + filter: TransactionFilter + maxBlockWait?: number } -export interface GatewayEtherBalance { - chainId: number - error: string - result: EtherBalance +export interface FetchTransactionReceiptWithFilterResponse { + receipt: TransactionReceipt } -export interface GatewayNativeTokenBalance { - chainId: number - error: string - result: NativeTokenBalance +export interface GetAllWebhookListenersRequest { + projectId?: number } -export interface GatewayNativeTokenBalances { - chainId: number - error: string - results: Array +export interface GetAllWebhookListenersResponse { + listeners: Array } -export interface GatewayTokenBalance { - chainId: number - error: string - results: Array +export interface GetBalanceUpdatesRequest { + contractAddress: string + lastBlockNumber: number + lastBlockHash?: string + page?: Page } -export interface Indexer { - ping(headers?: object, signal?: AbortSignal): Promise - version(headers?: object, signal?: AbortSignal): Promise - runtimeStatus(headers?: object, signal?: AbortSignal): Promise - getChainID(headers?: object, signal?: AbortSignal): Promise - getEtherBalance(args: GetEtherBalanceArgs, headers?: object, signal?: AbortSignal): Promise - getNativeTokenBalance( - args: GetNativeTokenBalanceArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getTokenBalancesSummary( - args: GetTokenBalancesSummaryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getTokenBalancesDetails( - args: GetTokenBalancesDetailsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getTokenBalancesByContract( - args: GetTokenBalancesByContractArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getTokenBalances(args: GetTokenBalancesArgs, headers?: object, signal?: AbortSignal): Promise - getTokenSupplies(args: GetTokenSuppliesArgs, headers?: object, signal?: AbortSignal): Promise - getTokenSuppliesMap( - args: GetTokenSuppliesMapArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getTokenIDs(args: GetTokenIDsArgs, headers?: object, signal?: AbortSignal): Promise - getTokenIDRanges(args: GetTokenIDRangesArgs, headers?: object, signal?: AbortSignal): Promise - getBalanceUpdates( - args: GetBalanceUpdatesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getTransactionHistory( - args: GetTransactionHistoryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - fetchTransactionReceipt( - args: FetchTransactionReceiptArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - fetchTransactionReceiptWithFilter( - args: FetchTransactionReceiptWithFilterArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - subscribeReceipts(args: SubscribeReceiptsArgs, options: WebrpcStreamOptions): Promise - subscribeEvents(args: SubscribeEventsArgs, options: WebrpcStreamOptions): Promise - subscribeBalanceUpdates( - args: SubscribeBalanceUpdatesArgs, - options: WebrpcStreamOptions, - ): Promise - syncBalance(args: SyncBalanceArgs, headers?: object, signal?: AbortSignal): Promise - getAllWebhookListeners( - args: GetAllWebhookListenersArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getWebhookListener( - args: GetWebhookListenerArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - addWebhookListener( - args: AddWebhookListenerArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - updateWebhookListener( - args: UpdateWebhookListenerArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - removeWebhookListener( - args: RemoveWebhookListenerArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - removeAllWebhookListeners( - args: RemoveAllWebhookListenersArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - toggleWebhookListener( - args: ToggleWebhookListenerArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - pauseAllWebhookListeners( - args: PauseAllWebhookListenersArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - resumeAllWebhookListeners( - args: ResumeAllWebhookListenersArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getOrderbookOrders( - args: GetOrderbookOrdersArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getTopOrders(args: GetTopOrdersArgs, headers?: object, signal?: AbortSignal): Promise +export interface GetBalanceUpdatesResponse { + page: Page + balances: Array } -export interface PingArgs {} +export interface GetChainIDRequest {} -export interface PingReturn { - status: boolean +export interface GetChainIDResponse { + chainID: number } -export interface VersionArgs {} -export interface VersionReturn { - version: Version +export interface GetEtherBalanceRequest { + accountAddress?: string } -export interface RuntimeStatusArgs {} -export interface RuntimeStatusReturn { - status: RuntimeStatus +export interface GetEtherBalanceResponse { + balance: EtherBalance } -export interface GetChainIDArgs {} -export interface GetChainIDReturn { - chainID: number +export interface GetMarketplaceOrdersRequest { + marketplaceContractAddress: string + collectionAddress: string + filter?: MarketplaceOrderFilter + page?: Page } -export interface GetEtherBalanceArgs { - accountAddress?: string + +export interface GetMarketplaceOrdersResponse { + page?: Page + orders: Array } -export interface GetEtherBalanceReturn { - balance: EtherBalance +export interface GetMarketplaceTopOrdersRequest { + marketplaceContractAddress: string + collectionAddress: string + filter: MarketplaceTopOrdersFilter +} + +export interface GetMarketplaceTopOrdersResponse { + orders: Array } -export interface GetNativeTokenBalanceArgs { + +export interface GetNativeTokenBalanceRequest { accountAddress?: string + omitPrices?: boolean } -export interface GetNativeTokenBalanceReturn { +export interface GetNativeTokenBalanceResponse { balance: NativeTokenBalance } -export interface GetTokenBalancesSummaryArgs { - filter: TokenBalancesFilter - omitMetadata?: boolean + +export interface GetTokenBalancesRequest { + accountAddress?: string + contractAddress?: string + tokenID?: string + includeMetadata?: boolean + metadataOptions?: MetadataOptions + includeCollectionTokens?: boolean page?: Page } -export interface GetTokenBalancesSummaryReturn { +export interface GetTokenBalancesResponse { page: Page - nativeBalances: Array balances: Array } -export interface GetTokenBalancesDetailsArgs { - filter: TokenBalancesFilter + +export interface GetTokenBalancesByContractRequest { + filter: TokenBalancesByContractFilter omitMetadata?: boolean page?: Page } -export interface GetTokenBalancesDetailsReturn { +export interface GetTokenBalancesByContractResponse { page: Page - nativeBalances: Array balances: Array } -export interface GetTokenBalancesByContractArgs { - filter: TokenBalancesByContractFilter + +export interface GetTokenBalancesDetailsRequest { + filter: TokenBalancesFilter omitMetadata?: boolean page?: Page } -export interface GetTokenBalancesByContractReturn { +export interface GetTokenBalancesDetailsResponse { page: Page + nativeBalances: Array balances: Array } -export interface GetTokenBalancesArgs { - accountAddress?: string - contractAddress?: string - tokenID?: string - includeMetadata?: boolean - metadataOptions?: MetadataOptions - includeCollectionTokens?: boolean + +export interface GetTokenBalancesSummaryRequest { + filter: TokenBalancesFilter + omitMetadata?: boolean page?: Page } -export interface GetTokenBalancesReturn { +export interface GetTokenBalancesSummaryResponse { page: Page + nativeBalances: Array balances: Array } -export interface GetTokenSuppliesArgs { + +export interface GetTokenIDRangesRequest { contractAddress: string - includeMetadata?: boolean - metadataOptions?: MetadataOptions - page?: Page + lastTokenID?: string } -export interface GetTokenSuppliesReturn { - page: Page +export interface GetTokenIDRangesResponse { contractType: ContractType - tokenIDs: Array -} -export interface GetTokenSuppliesMapArgs { - tokenMap: { [key: string]: Array } - includeMetadata?: boolean - metadataOptions?: MetadataOptions + tokenIDRanges: Array + moreRanges: boolean } -export interface GetTokenSuppliesMapReturn { - supplies: { [key: string]: Array } -} -export interface GetTokenIDsArgs { +export interface GetTokenIDsRequest { contractAddress: string page?: Page } -export interface GetTokenIDsReturn { +export interface GetTokenIDsResponse { page: Page contractType: ContractType tokenIDs: Array } -export interface GetTokenIDRangesArgs { - contractAddress: string + +export interface GetTokenPriceRequest { + q: TokenPriceQuery } -export interface GetTokenIDRangesReturn { - contractType: ContractType - tokenIDRanges: Array - moreRanges: boolean +export interface GetTokenPriceResponse { + price: Price +} + +export interface GetTokenPricesRequest { + q: Array +} + +export interface GetTokenPricesResponse { + prices: Array } -export interface GetBalanceUpdatesArgs { + +export interface GetTokenSuppliesRequest { contractAddress: string - lastBlockNumber: number - lastBlockHash?: string + includeMetadata?: boolean page?: Page } -export interface GetBalanceUpdatesReturn { +export interface GetTokenSuppliesResponse { page: Page - balances: Array + contractType: ContractType + tokenIDs: Array +} + +export interface GetTokenSuppliesMapRequest { + tokenMap: { [key: string]: Array } + includeMetadata?: boolean +} + +export interface GetTokenSuppliesMapResponse { + supplies: { [key: string]: Array } } -export interface GetTransactionHistoryArgs { + +export interface GetTransactionHistoryRequest { filter: TransactionHistoryFilter page?: Page includeMetadata?: boolean metadataOptions?: MetadataOptions } -export interface GetTransactionHistoryReturn { +export interface GetTransactionHistoryResponse { page: Page transactions: Array } -export interface FetchTransactionReceiptArgs { - txnHash: string - maxBlockWait?: number -} -export interface FetchTransactionReceiptReturn { - receipt: TransactionReceipt -} -export interface FetchTransactionReceiptWithFilterArgs { - filter: TransactionFilter - maxBlockWait?: number +export interface GetWebhookListenerRequest { + id: number + projectId?: number } -export interface FetchTransactionReceiptWithFilterReturn { - receipt: TransactionReceipt -} -export interface SubscribeReceiptsArgs { - filter: TransactionFilter +export interface GetWebhookListenerResponse { + listener: WebhookListener } -export interface SubscribeReceiptsReturn { - receipt: TransactionReceipt -} -export interface SubscribeEventsArgs { - filter: EventFilter +export interface ListTokenPricesRequest { + page?: Page } -export interface SubscribeEventsReturn { - log: EventLog +export interface ListTokenPricesResponse { + page: Page + prices: Array } -export interface SubscribeBalanceUpdatesArgs { - contractAddress: string + +export interface PauseAllWebhookListenersRequest { + projectId?: number } -export interface SubscribeBalanceUpdatesReturn { - balance: TokenBalance +export interface PauseAllWebhookListenersResponse { + status: boolean } -export interface SyncBalanceArgs { - accountAddress: string - contractAddress: string - tokenID?: string + +export interface PingRequest {} + +export interface PingResponse { + status: boolean } -export interface SyncBalanceReturn {} -export interface GetAllWebhookListenersArgs { +export interface RemoveAllWebhookListenersRequest { projectId?: number } -export interface GetAllWebhookListenersReturn { - listeners: Array +export interface RemoveAllWebhookListenersResponse { + status: boolean } -export interface GetWebhookListenerArgs { + +export interface RemoveWebhookListenerRequest { id: number projectId?: number } -export interface GetWebhookListenerReturn { - listener: WebhookListener +export interface RemoveWebhookListenerResponse { + status: boolean } -export interface AddWebhookListenerArgs { - url: string - filters: EventFilter + +export interface ResumeAllWebhookListenersRequest { projectId?: number } -export interface AddWebhookListenerReturn { +export interface ResumeAllWebhookListenersResponse { status: boolean - listener: WebhookListener } -export interface UpdateWebhookListenerArgs { - listener: WebhookListener - projectId?: number + +export interface RuntimeStatusRequest {} + +export interface RuntimeStatusResponse { + status: RuntimeStatus } -export interface UpdateWebhookListenerReturn { - status: boolean +export interface SubscribeBalanceUpdatesRequest { + contractAddress: string } -export interface RemoveWebhookListenerArgs { - id: number - projectId?: number + +export interface SubscribeBalanceUpdatesResponse { + balance: TokenBalance } -export interface RemoveWebhookListenerReturn { - status: boolean +export interface SubscribeEventsRequest { + filter: EventFilter } -export interface RemoveAllWebhookListenersArgs { - projectId?: number + +export interface SubscribeEventsResponse { + log: EventLog } -export interface RemoveAllWebhookListenersReturn { - status: boolean +export interface SubscribeReceiptsRequest { + filter: TransactionFilter } -export interface ToggleWebhookListenerArgs { - id: number - projectId?: number + +export interface SubscribeReceiptsResponse { + receipt: TransactionReceipt } -export interface ToggleWebhookListenerReturn { - webhookListener: WebhookListener +export interface SyncBalanceRequest { + accountAddress: string + contractAddress: string + tokenID?: string } -export interface PauseAllWebhookListenersArgs { + +export interface SyncBalanceResponse {} + +export interface ToggleWebhookListenerRequest { + id: number projectId?: number } -export interface PauseAllWebhookListenersReturn { - status: boolean +export interface ToggleWebhookListenerResponse { + webhookListener: WebhookListener } -export interface ResumeAllWebhookListenersArgs { + +export interface UpdateWebhookListenerRequest { + listener: WebhookListener projectId?: number } -export interface ResumeAllWebhookListenersReturn { +export interface UpdateWebhookListenerResponse { status: boolean } -export interface GetOrderbookOrdersArgs { - page?: Page - orderbookContractAddress: string - collectionAddress: string - currencyAddresses: Array - filter: OrderbookOrderFilter - orderStatuses: Array - filters: Array - beforeExpiryTimestamp: number - blockNumberAfter: number - createdAtAfter: number -} -export interface GetOrderbookOrdersReturn { - page?: Page - orders: Array -} -export interface GetTopOrdersArgs { - orderbookContractAddress: string - collectionAddress: string - currencyAddresses: Array - tokenIDs: Array - isListing: boolean - priceSort: SortOrder - excludeUser?: string -} +export interface VersionRequest {} -export interface GetTopOrdersReturn { - orders: Array +export interface VersionResponse { + version: Version } // // Client // -export class Indexer implements Indexer { + +export class Indexer implements IndexerClient { protected hostname: string protected fetch: Fetch protected path = '/rpc/Indexer/' @@ -995,600 +1323,750 @@ export class Indexer implements Indexer { return this.hostname + this.path + name } - ping = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Ping'), createHTTPRequest({}, headers, signal)).then( + queryKey = { + addWebhookListener: (req: AddWebhookListenerRequest) => ['Indexer', 'addWebhookListener', req] as const, + fetchTransactionReceipt: (req: FetchTransactionReceiptRequest) => + ['Indexer', 'fetchTransactionReceipt', req] as const, + fetchTransactionReceiptWithFilter: (req: FetchTransactionReceiptWithFilterRequest) => + ['Indexer', 'fetchTransactionReceiptWithFilter', req] as const, + getAllWebhookListeners: (req: GetAllWebhookListenersRequest) => ['Indexer', 'getAllWebhookListeners', req] as const, + getBalanceUpdates: (req: GetBalanceUpdatesRequest) => ['Indexer', 'getBalanceUpdates', req] as const, + getChainID: () => ['Indexer', 'getChainID'] as const, + getEtherBalance: (req: GetEtherBalanceRequest) => ['Indexer', 'getEtherBalance', req] as const, + getMarketplaceOrders: (req: GetMarketplaceOrdersRequest) => ['Indexer', 'getMarketplaceOrders', req] as const, + getMarketplaceTopOrders: (req: GetMarketplaceTopOrdersRequest) => + ['Indexer', 'getMarketplaceTopOrders', req] as const, + getNativeTokenBalance: (req: GetNativeTokenBalanceRequest) => ['Indexer', 'getNativeTokenBalance', req] as const, + getTokenBalances: (req: GetTokenBalancesRequest) => ['Indexer', 'getTokenBalances', req] as const, + getTokenBalancesByContract: (req: GetTokenBalancesByContractRequest) => + ['Indexer', 'getTokenBalancesByContract', req] as const, + getTokenBalancesDetails: (req: GetTokenBalancesDetailsRequest) => + ['Indexer', 'getTokenBalancesDetails', req] as const, + getTokenBalancesSummary: (req: GetTokenBalancesSummaryRequest) => + ['Indexer', 'getTokenBalancesSummary', req] as const, + getTokenIDRanges: (req: GetTokenIDRangesRequest) => ['Indexer', 'getTokenIDRanges', req] as const, + getTokenIDs: (req: GetTokenIDsRequest) => ['Indexer', 'getTokenIDs', req] as const, + getTokenPrice: (req: GetTokenPriceRequest) => ['Indexer', 'getTokenPrice', req] as const, + getTokenPrices: (req: GetTokenPricesRequest) => ['Indexer', 'getTokenPrices', req] as const, + getTokenSupplies: (req: GetTokenSuppliesRequest) => ['Indexer', 'getTokenSupplies', req] as const, + getTokenSuppliesMap: (req: GetTokenSuppliesMapRequest) => ['Indexer', 'getTokenSuppliesMap', req] as const, + getTransactionHistory: (req: GetTransactionHistoryRequest) => ['Indexer', 'getTransactionHistory', req] as const, + getWebhookListener: (req: GetWebhookListenerRequest) => ['Indexer', 'getWebhookListener', req] as const, + listTokenPrices: (req: ListTokenPricesRequest) => ['Indexer', 'listTokenPrices', req] as const, + pauseAllWebhookListeners: (req: PauseAllWebhookListenersRequest) => + ['Indexer', 'pauseAllWebhookListeners', req] as const, + ping: () => ['Indexer', 'ping'] as const, + removeAllWebhookListeners: (req: RemoveAllWebhookListenersRequest) => + ['Indexer', 'removeAllWebhookListeners', req] as const, + removeWebhookListener: (req: RemoveWebhookListenerRequest) => ['Indexer', 'removeWebhookListener', req] as const, + resumeAllWebhookListeners: (req: ResumeAllWebhookListenersRequest) => + ['Indexer', 'resumeAllWebhookListeners', req] as const, + runtimeStatus: () => ['Indexer', 'runtimeStatus'] as const, + subscribeBalanceUpdates: (req: SubscribeBalanceUpdatesRequest) => + ['Indexer', 'subscribeBalanceUpdates', req] as const, + subscribeEvents: (req: SubscribeEventsRequest) => ['Indexer', 'subscribeEvents', req] as const, + subscribeReceipts: (req: SubscribeReceiptsRequest) => ['Indexer', 'subscribeReceipts', req] as const, + syncBalance: (req: SyncBalanceRequest) => ['Indexer', 'syncBalance', req] as const, + toggleWebhookListener: (req: ToggleWebhookListenerRequest) => ['Indexer', 'toggleWebhookListener', req] as const, + updateWebhookListener: (req: UpdateWebhookListenerRequest) => ['Indexer', 'updateWebhookListener', req] as const, + version: () => ['Indexer', 'version'] as const, + } + + addWebhookListener = ( + req: AddWebhookListenerRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('AddWebhookListener'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'AddWebhookListenerResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - version = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Version'), createHTTPRequest({}, headers, signal)).then( + fetchTransactionReceipt = ( + req: FetchTransactionReceiptRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('FetchTransactionReceipt'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - version: _data.version, - } + return JsonDecode(_data, 'FetchTransactionReceiptResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - runtimeStatus = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('RuntimeStatus'), createHTTPRequest({}, headers, signal)).then( + fetchTransactionReceiptWithFilter = ( + req: FetchTransactionReceiptWithFilterRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('FetchTransactionReceiptWithFilter'), + createHttpRequest(JsonEncode(req), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode( + _data, + 'FetchTransactionReceiptWithFilterResponse', + ) }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getChainID = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetChainID'), createHTTPRequest({}, headers, signal)).then( + getAllWebhookListeners = ( + req: GetAllWebhookListenersRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('GetAllWebhookListeners'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - chainID: _data.chainID, - } + return JsonDecode(_data, 'GetAllWebhookListenersResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getEtherBalance = ( - args: GetEtherBalanceArgs, + getBalanceUpdates = ( + req: GetBalanceUpdatesRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetEtherBalance'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('GetBalanceUpdates'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - balance: _data.balance, - } + return JsonDecode(_data, 'GetBalanceUpdatesResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getNativeTokenBalance = ( - args: GetNativeTokenBalanceArgs, + getChainID = (headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('GetChainID'), createHttpRequest('{}', headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'GetChainIDResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + getEtherBalance = ( + req: GetEtherBalanceRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetNativeTokenBalance'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('GetEtherBalance'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - balance: _data.balance, - } + return JsonDecode(_data, 'GetEtherBalanceResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getTokenBalancesSummary = ( - args: GetTokenBalancesSummaryArgs, + getMarketplaceOrders = ( + req: GetMarketplaceOrdersRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenBalancesSummary'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('GetMarketplaceOrders'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - nativeBalances: >_data.nativeBalances, - balances: >_data.balances, - } + return JsonDecode(_data, 'GetMarketplaceOrdersResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getTokenBalancesDetails = ( - args: GetTokenBalancesDetailsArgs, + getMarketplaceTopOrders = ( + req: GetMarketplaceTopOrdersRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenBalancesDetails'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('GetMarketplaceTopOrders'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - nativeBalances: >_data.nativeBalances, - balances: >_data.balances, - } + return JsonDecode(_data, 'GetMarketplaceTopOrdersResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getTokenBalancesByContract = ( - args: GetTokenBalancesByContractArgs, + getNativeTokenBalance = ( + req: GetNativeTokenBalanceRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenBalancesByContract'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('GetNativeTokenBalance'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - balances: >_data.balances, - } + return JsonDecode(_data, 'GetNativeTokenBalanceResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getTokenBalances = ( - args: GetTokenBalancesArgs, + req: GetTokenBalancesRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenBalances'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('GetTokenBalances'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - balances: >_data.balances, - } + return JsonDecode(_data, 'GetTokenBalancesResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getTokenSupplies = ( - args: GetTokenSuppliesArgs, + getTokenBalancesByContract = ( + req: GetTokenBalancesByContractRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenSupplies'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('GetTokenBalancesByContract'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - contractType: _data.contractType, - tokenIDs: >_data.tokenIDs, - } + return JsonDecode(_data, 'GetTokenBalancesByContractResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getTokenSuppliesMap = ( - args: GetTokenSuppliesMapArgs, + getTokenBalancesDetails = ( + req: GetTokenBalancesDetailsRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenSuppliesMap'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('GetTokenBalancesDetails'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - supplies: <{ [key: string]: Array }>_data.supplies, - } + return JsonDecode(_data, 'GetTokenBalancesDetailsResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getTokenIDs = (args: GetTokenIDsArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetTokenIDs'), createHTTPRequest(args, headers, signal)).then( + getTokenBalancesSummary = ( + req: GetTokenBalancesSummaryRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('GetTokenBalancesSummary'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - contractType: _data.contractType, - tokenIDs: >_data.tokenIDs, - } + return JsonDecode(_data, 'GetTokenBalancesSummaryResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getTokenIDRanges = ( - args: GetTokenIDRangesArgs, + req: GetTokenIDRangesRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenIDRanges'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('GetTokenIDRanges'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - contractType: _data.contractType, - tokenIDRanges: >_data.tokenIDRanges, - moreRanges: _data.moreRanges, - } + return JsonDecode(_data, 'GetTokenIDRangesResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getBalanceUpdates = ( - args: GetBalanceUpdatesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetBalanceUpdates'), createHTTPRequest(args, headers, signal)).then( + getTokenIDs = (req: GetTokenIDsRequest, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('GetTokenIDs'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - balances: >_data.balances, - } + return JsonDecode(_data, 'GetTokenIDsResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getTransactionHistory = ( - args: GetTransactionHistoryArgs, + getTokenPrice = ( + req: GetTokenPriceRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTransactionHistory'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('GetTokenPrice'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - transactions: >_data.transactions, - } + return JsonDecode(_data, 'GetTokenPriceResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - fetchTransactionReceipt = ( - args: FetchTransactionReceiptArgs, + getTokenPrices = ( + req: GetTokenPricesRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('FetchTransactionReceipt'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('GetTokenPrices'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - receipt: _data.receipt, - } + return JsonDecode(_data, 'GetTokenPricesResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - fetchTransactionReceiptWithFilter = ( - args: FetchTransactionReceiptWithFilterArgs, + getTokenSupplies = ( + req: GetTokenSuppliesRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('FetchTransactionReceiptWithFilter'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('GetTokenSupplies'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - receipt: _data.receipt, - } + return JsonDecode(_data, 'GetTokenSuppliesResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - subscribeReceipts = ( - args: SubscribeReceiptsArgs, - options: WebrpcStreamOptions, - ): Promise => { - const _fetch = () => - this.fetch(this.url('SubscribeReceipts'), createHTTPRequest(args, options.headers, options.signal)).then( - async (res) => { - await sseResponse(res, options, _fetch) - }, - (error) => { - options.onError(error, _fetch) - }, - ) - return _fetch() - } - subscribeEvents = (args: SubscribeEventsArgs, options: WebrpcStreamOptions): Promise => { - const _fetch = () => - this.fetch(this.url('SubscribeEvents'), createHTTPRequest(args, options.headers, options.signal)).then( - async (res) => { - await sseResponse(res, options, _fetch) - }, - (error) => { - options.onError(error, _fetch) - }, - ) - return _fetch() - } - subscribeBalanceUpdates = ( - args: SubscribeBalanceUpdatesArgs, - options: WebrpcStreamOptions, - ): Promise => { - const _fetch = () => - this.fetch(this.url('SubscribeBalanceUpdates'), createHTTPRequest(args, options.headers, options.signal)).then( - async (res) => { - await sseResponse(res, options, _fetch) - }, - (error) => { - options.onError(error, _fetch) - }, - ) - return _fetch() - } - syncBalance = (args: SyncBalanceArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('SyncBalance'), createHTTPRequest(args, headers, signal)).then( + getTokenSuppliesMap = ( + req: GetTokenSuppliesMapRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('GetTokenSuppliesMap'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return {} + return JsonDecode(_data, 'GetTokenSuppliesMapResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getAllWebhookListeners = ( - args: GetAllWebhookListenersArgs, + getTransactionHistory = ( + req: GetTransactionHistoryRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetAllWebhookListeners'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('GetTransactionHistory'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - listeners: >_data.listeners, - } + return JsonDecode(_data, 'GetTransactionHistoryResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getWebhookListener = ( - args: GetWebhookListenerArgs, + req: GetWebhookListenerRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetWebhookListener'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('GetWebhookListener'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - listener: _data.listener, - } + return JsonDecode(_data, 'GetWebhookListenerResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - addWebhookListener = ( - args: AddWebhookListenerArgs, + listTokenPrices = ( + req: ListTokenPricesRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('AddWebhookListener'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('ListTokenPrices'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - listener: _data.listener, - } + return JsonDecode(_data, 'ListTokenPricesResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - updateWebhookListener = ( - args: UpdateWebhookListenerArgs, + pauseAllWebhookListeners = ( + req: PauseAllWebhookListenersRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('UpdateWebhookListener'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('PauseAllWebhookListeners'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'PauseAllWebhookListenersResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - removeWebhookListener = ( - args: RemoveWebhookListenerArgs, - headers?: object, - signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('RemoveWebhookListener'), createHTTPRequest(args, headers, signal)).then( + ping = (headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('Ping'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'PingResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } removeAllWebhookListeners = ( - args: RemoveAllWebhookListenersArgs, + req: RemoveAllWebhookListenersRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('RemoveAllWebhookListeners'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('RemoveAllWebhookListeners'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'RemoveAllWebhookListenersResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - toggleWebhookListener = ( - args: ToggleWebhookListenerArgs, + removeWebhookListener = ( + req: RemoveWebhookListenerRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ToggleWebhookListener'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('RemoveWebhookListener'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - webhookListener: _data.webhookListener, - } + return JsonDecode(_data, 'RemoveWebhookListenerResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - pauseAllWebhookListeners = ( - args: PauseAllWebhookListenersArgs, + resumeAllWebhookListeners = ( + req: ResumeAllWebhookListenersRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('PauseAllWebhookListeners'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('ResumeAllWebhookListeners'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'ResumeAllWebhookListenersResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - resumeAllWebhookListeners = ( - args: ResumeAllWebhookListenersArgs, + runtimeStatus = (headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('RuntimeStatus'), createHttpRequest('{}', headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'RuntimeStatusResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + subscribeBalanceUpdates = ( + req: SubscribeBalanceUpdatesRequest, + options: WebrpcStreamOptions, + ): WebrpcStreamController => { + const abortController = new AbortController() + const abortSignal = abortController.signal + + if (options.signal) { + abortSignal.addEventListener('abort', () => abortController.abort(options.signal?.reason), { + signal: options.signal, + }) + } + + const _fetch = () => + this.fetch( + this.url('SubscribeBalanceUpdates'), + createHttpRequest(JsonEncode(req), options.headers, abortSignal), + ).then( + async (res) => { + await sseResponse(res, options, _fetch) + }, + (error) => { + options.onError(error, _fetch) + }, + ) + + const resp = _fetch() + return { + abort: abortController.abort.bind(abortController), + closed: resp, + } + } + subscribeEvents = ( + req: SubscribeEventsRequest, + options: WebrpcStreamOptions, + ): WebrpcStreamController => { + const abortController = new AbortController() + const abortSignal = abortController.signal + + if (options.signal) { + abortSignal.addEventListener('abort', () => abortController.abort(options.signal?.reason), { + signal: options.signal, + }) + } + + const _fetch = () => + this.fetch(this.url('SubscribeEvents'), createHttpRequest(JsonEncode(req), options.headers, abortSignal)).then( + async (res) => { + await sseResponse(res, options, _fetch) + }, + (error) => { + options.onError(error, _fetch) + }, + ) + + const resp = _fetch() + return { + abort: abortController.abort.bind(abortController), + closed: resp, + } + } + subscribeReceipts = ( + req: SubscribeReceiptsRequest, + options: WebrpcStreamOptions, + ): WebrpcStreamController => { + const abortController = new AbortController() + const abortSignal = abortController.signal + + if (options.signal) { + abortSignal.addEventListener('abort', () => abortController.abort(options.signal?.reason), { + signal: options.signal, + }) + } + + const _fetch = () => + this.fetch(this.url('SubscribeReceipts'), createHttpRequest(JsonEncode(req), options.headers, abortSignal)).then( + async (res) => { + await sseResponse(res, options, _fetch) + }, + (error) => { + options.onError(error, _fetch) + }, + ) + + const resp = _fetch() + return { + abort: abortController.abort.bind(abortController), + closed: resp, + } + } + syncBalance = (req: SyncBalanceRequest, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('SyncBalance'), createHttpRequest(JsonEncode(req), headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'SyncBalanceResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + toggleWebhookListener = ( + req: ToggleWebhookListenerRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('ResumeAllWebhookListeners'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('ToggleWebhookListener'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'ToggleWebhookListenerResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getOrderbookOrders = ( - args: GetOrderbookOrdersArgs, + updateWebhookListener = ( + req: UpdateWebhookListenerRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetOrderbookOrders'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('UpdateWebhookListener'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - orders: >_data.orders, - } + return JsonDecode(_data, 'UpdateWebhookListenerResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getTopOrders = (args: GetTopOrdersArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetTopOrders'), createHTTPRequest(args, headers, signal)).then( + version = (headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('Version'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - orders: >_data.orders, - } + return JsonDecode(_data, 'VersionResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1643,16 +2121,11 @@ const sseResponse = async (res: Response, options: WebrpcStreamOptions, ret lastReadTime = Date.now() buffer += decoder.decode(value, { stream: true }) } catch (error) { - let message = '' - if (error instanceof Error) { - message = error.message - } - if (error instanceof DOMException && error.name === 'AbortError') { onError( - WebrpcRequestFailedError.new({ + WebrpcClientAbortedError.new({ message: 'AbortError', - cause: `AbortError: ${message}`, + cause: `AbortError: ${error instanceof Error ? error.message : String(error)}`, }), () => { throw new Error('Abort signal cannot be used to reconnect') @@ -1661,7 +2134,7 @@ const sseResponse = async (res: Response, options: WebrpcStreamOptions, ret } else { onError( WebrpcStreamLostError.new({ - cause: `reader.read(): ${message}`, + cause: `reader.read(): ${error instanceof Error ? error.message : String(error)}`, }), retryFetch, ) @@ -1671,12 +2144,13 @@ const sseResponse = async (res: Response, options: WebrpcStreamOptions, ret let lines = buffer.split('\n') for (let i = 0; i < lines.length - 1; i++) { - if (lines[i]!.length == 0) { + const line = lines[i] + if (line?.length === 0) { continue } let data: any try { - data = JSON.parse(lines[i]!) + data = JSON.parse(line) if (data.hasOwnProperty('webrpcError')) { const error = data.webrpcError const code: number = typeof error.code === 'number' ? error.code : 0 @@ -1690,8 +2164,7 @@ const sseResponse = async (res: Response, options: WebrpcStreamOptions, ret onError( WebrpcBadResponseError.new({ status: res.status, - // @ts-ignore - cause: `JSON.parse(): ${error.message}`, + cause: `JSON.parse(): ${error instanceof Error ? error.message : String(error)}`, }), retryFetch, ) @@ -1700,7 +2173,8 @@ const sseResponse = async (res: Response, options: WebrpcStreamOptions, ret } if (!done) { - buffer = lines[lines.length - 1]! + const lastLine = lines[lines.length - 1] + buffer = lastLine || '' continue } @@ -1709,16 +2183,13 @@ const sseResponse = async (res: Response, options: WebrpcStreamOptions, ret } } -const createHTTPRequest = (body: object = {}, headers: object = {}, signal: AbortSignal | null = null): object => { - const reqHeaders: { [key: string]: string } = { ...headers, 'Content-Type': 'application/json' } - reqHeaders[WebrpcHeader] = WebrpcHeaderValue - - return { - method: 'POST', - headers: reqHeaders, - body: JSON.stringify(body || {}), - signal, +const createHttpRequest = (body: string = '{}', headers: object = {}, signal: AbortSignal | null = null): object => { + const reqHeaders: { [key: string]: string } = { + ...headers, + 'Content-Type': 'application/json', + [WebrpcHeader]: WebrpcHeaderValue, } + return { method: 'POST', headers: reqHeaders, body, signal } } const buildResponse = (res: Response): Promise => { @@ -1727,13 +2198,9 @@ const buildResponse = (res: Response): Promise => { try { data = JSON.parse(text) } catch (error) { - let message = '' - if (error instanceof Error) { - message = error.message - } throw WebrpcBadResponseError.new({ status: res.status, - cause: `JSON.parse(): ${message}: response text: ${text}`, + cause: `JSON.parse(): ${error instanceof Error ? error.message : String(error)}: response text: ${text}`, }) } if (!res.ok) { @@ -1744,518 +2211,511 @@ const buildResponse = (res: Response): Promise => { }) } +export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise + +export interface WebrpcStreamOptions extends WebrpcOptions { + onMessage: (message: T) => void + onError: (error: WebrpcError, reconnect: () => void) => void + onOpen?: () => void + onClose?: () => void +} + +export interface WebrpcOptions { + headers?: HeadersInit + signal?: AbortSignal +} + +export interface WebrpcStreamController { + abort: (reason?: any) => void + closed: Promise +} + +export const JsonEncode = (obj: T): string => { + return JSON.stringify(obj) +} + +export const JsonDecode = (data: string | any, _typ: string = ''): T => { + let parsed: any = data + if (typeof data === 'string') { + try { + parsed = JSON.parse(data) + } catch (err) { + throw WebrpcBadResponseError.new({ cause: `JsonDecode: JSON.parse failed: ${(err as Error).message}` }) + } + } + return parsed as T +} + // // Errors // +type WebrpcErrorParams = { name?: string; code?: number; message?: string; status?: number; cause?: string } + export class WebrpcError extends Error { - name: string code: number - message: string status: number - cause?: string - - /** @deprecated Use message instead of msg. Deprecated in webrpc v0.11.0. */ - msg: string - - constructor(name: string, code: number, message: string, status: number, cause?: string) { - super(message) - this.name = name || 'WebrpcError' - this.code = typeof code === 'number' ? code : 0 - this.message = message || `endpoint error ${this.code}` - this.msg = this.message - this.status = typeof status === 'number' ? status : 0 - this.cause = cause + + constructor(error: WebrpcErrorParams = {}) { + super(error.message) + this.name = error.name || 'WebrpcEndpointError' + this.code = typeof error.code === 'number' ? error.code : 0 + this.message = error.message || `endpoint error` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcError.prototype) } static new(payload: any): WebrpcError { - return new this(payload.error, payload.code, payload.message || payload.msg, payload.status, payload.cause) + return new this({ message: payload.message, code: payload.code, status: payload.status, cause: payload.cause }) } } -// Webrpc errors - export class WebrpcEndpointError extends WebrpcError { - constructor( - name: string = 'WebrpcEndpoint', - code: number = 0, - message: string = 'endpoint error', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcEndpoint' + this.code = typeof error.code === 'number' ? error.code : 0 + this.message = error.message || `endpoint error` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcEndpointError.prototype) } } export class WebrpcRequestFailedError extends WebrpcError { - constructor( - name: string = 'WebrpcRequestFailed', - code: number = -1, - message: string = 'request failed', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcRequestFailed' + this.code = typeof error.code === 'number' ? error.code : -1 + this.message = error.message || `request failed` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcRequestFailedError.prototype) } } export class WebrpcBadRouteError extends WebrpcError { - constructor( - name: string = 'WebrpcBadRoute', - code: number = -2, - message: string = 'bad route', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadRoute' + this.code = typeof error.code === 'number' ? error.code : -2 + this.message = error.message || `bad route` + this.status = typeof error.status === 'number' ? error.status : 404 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcBadRouteError.prototype) } } export class WebrpcBadMethodError extends WebrpcError { - constructor( - name: string = 'WebrpcBadMethod', - code: number = -3, - message: string = 'bad method', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadMethod' + this.code = typeof error.code === 'number' ? error.code : -3 + this.message = error.message || `bad method` + this.status = typeof error.status === 'number' ? error.status : 405 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcBadMethodError.prototype) } } export class WebrpcBadRequestError extends WebrpcError { - constructor( - name: string = 'WebrpcBadRequest', - code: number = -4, - message: string = 'bad request', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadRequest' + this.code = typeof error.code === 'number' ? error.code : -4 + this.message = error.message || `bad request` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcBadRequestError.prototype) } } export class WebrpcBadResponseError extends WebrpcError { - constructor( - name: string = 'WebrpcBadResponse', - code: number = -5, - message: string = 'bad response', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadResponse' + this.code = typeof error.code === 'number' ? error.code : -5 + this.message = error.message || `bad response` + this.status = typeof error.status === 'number' ? error.status : 500 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcBadResponseError.prototype) } } export class WebrpcServerPanicError extends WebrpcError { - constructor( - name: string = 'WebrpcServerPanic', - code: number = -6, - message: string = 'server panic', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcServerPanic' + this.code = typeof error.code === 'number' ? error.code : -6 + this.message = error.message || `server panic` + this.status = typeof error.status === 'number' ? error.status : 500 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcServerPanicError.prototype) } } export class WebrpcInternalErrorError extends WebrpcError { - constructor( - name: string = 'WebrpcInternalError', - code: number = -7, - message: string = 'internal error', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcInternalError' + this.code = typeof error.code === 'number' ? error.code : -7 + this.message = error.message || `internal error` + this.status = typeof error.status === 'number' ? error.status : 500 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcInternalErrorError.prototype) } } -export class WebrpcClientDisconnectedError extends WebrpcError { - constructor( - name: string = 'WebrpcClientDisconnected', - code: number = -8, - message: string = 'client disconnected', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcClientDisconnectedError.prototype) +export class WebrpcClientAbortedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcClientAborted' + this.code = typeof error.code === 'number' ? error.code : -8 + this.message = error.message || `request aborted by client` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcClientAbortedError.prototype) } } export class WebrpcStreamLostError extends WebrpcError { - constructor( - name: string = 'WebrpcStreamLost', - code: number = -9, - message: string = 'stream lost', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcStreamLost' + this.code = typeof error.code === 'number' ? error.code : -9 + this.message = error.message || `stream lost` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcStreamLostError.prototype) } } export class WebrpcStreamFinishedError extends WebrpcError { - constructor( - name: string = 'WebrpcStreamFinished', - code: number = -10, - message: string = 'stream finished', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcStreamFinished' + this.code = typeof error.code === 'number' ? error.code : -10 + this.message = error.message || `stream finished` + this.status = typeof error.status === 'number' ? error.status : 200 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcStreamFinishedError.prototype) } } +// // Schema errors +// -export class UnauthorizedError extends WebrpcError { - constructor( - name: string = 'Unauthorized', - code: number = 1000, - message: string = 'Unauthorized access', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, UnauthorizedError.prototype) +export class AbortedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Aborted' + this.code = typeof error.code === 'number' ? error.code : 1005 + this.message = error.message || `Request aborted` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, AbortedError.prototype) } } -export class PermissionDeniedError extends WebrpcError { - constructor( - name: string = 'PermissionDenied', - code: number = 1001, - message: string = 'Permission denied', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, PermissionDeniedError.prototype) +export class AccessKeyMismatchError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'AccessKeyMismatch' + this.code = typeof error.code === 'number' ? error.code : 1102 + this.message = error.message || `Access key mismatch` + this.status = typeof error.status === 'number' ? error.status : 409 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, AccessKeyMismatchError.prototype) } } -export class SessionExpiredError extends WebrpcError { - constructor( - name: string = 'SessionExpired', - code: number = 1002, - message: string = 'Session expired', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, SessionExpiredError.prototype) +export class AccessKeyNotFoundError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'AccessKeyNotFound' + this.code = typeof error.code === 'number' ? error.code : 1101 + this.message = error.message || `Access key not found` + this.status = typeof error.status === 'number' ? error.status : 401 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, AccessKeyNotFoundError.prototype) } } -export class MethodNotFoundError extends WebrpcError { - constructor( - name: string = 'MethodNotFound', - code: number = 1003, - message: string = 'Method not found', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, MethodNotFoundError.prototype) +export class AtLeastOneKeyError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'AtLeastOneKey' + this.code = typeof error.code === 'number' ? error.code : 1302 + this.message = error.message || `You need at least one Access Key` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, AtLeastOneKeyError.prototype) } } -export class RequestConflictError extends WebrpcError { - constructor( - name: string = 'RequestConflict', - code: number = 1004, - message: string = 'Conflict with target resource', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, RequestConflictError.prototype) +export class GeoblockedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Geoblocked' + this.code = typeof error.code === 'number' ? error.code : 1006 + this.message = error.message || `Geoblocked region` + this.status = typeof error.status === 'number' ? error.status : 451 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, GeoblockedError.prototype) } } -export class AbortedError extends WebrpcError { - constructor( - name: string = 'Aborted', - code: number = 1005, - message: string = 'Request aborted', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, AbortedError.prototype) +export class InvalidArgumentError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'InvalidArgument' + this.code = typeof error.code === 'number' ? error.code : 2001 + this.message = error.message || `Invalid argument` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, InvalidArgumentError.prototype) } } -export class GeoblockedError extends WebrpcError { - constructor( - name: string = 'Geoblocked', - code: number = 1006, - message: string = 'Geoblocked region', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, GeoblockedError.prototype) +export class InvalidOriginError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'InvalidOrigin' + this.code = typeof error.code === 'number' ? error.code : 1103 + this.message = error.message || `Invalid origin for Access Key` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, InvalidOriginError.prototype) } } -export class RateLimitedError extends WebrpcError { - constructor( - name: string = 'RateLimited', - code: number = 1007, - message: string = 'Rate-limited. Please slow down.', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, RateLimitedError.prototype) +export class InvalidServiceError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'InvalidService' + this.code = typeof error.code === 'number' ? error.code : 1104 + this.message = error.message || `Service not enabled for Access key` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, InvalidServiceError.prototype) } } -export class ProjectNotFoundError extends WebrpcError { - constructor( - name: string = 'ProjectNotFound', - code: number = 1100, - message: string = 'Project not found', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, ProjectNotFoundError.prototype) +export class MaxAccessKeysError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'MaxAccessKeys' + this.code = typeof error.code === 'number' ? error.code : 1301 + this.message = error.message || `Access keys limit reached` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, MaxAccessKeysError.prototype) } } -export class AccessKeyNotFoundError extends WebrpcError { - constructor( - name: string = 'AccessKeyNotFound', - code: number = 1101, - message: string = 'Access key not found', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, AccessKeyNotFoundError.prototype) +export class MetadataCallFailedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'MetadataCallFailed' + this.code = typeof error.code === 'number' ? error.code : 3003 + this.message = error.message || `Metadata service call failed` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, MetadataCallFailedError.prototype) } } -export class AccessKeyMismatchError extends WebrpcError { - constructor( - name: string = 'AccessKeyMismatch', - code: number = 1102, - message: string = 'Access key mismatch', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, AccessKeyMismatchError.prototype) +export class MethodNotFoundError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'MethodNotFound' + this.code = typeof error.code === 'number' ? error.code : 1003 + this.message = error.message || `Method not found` + this.status = typeof error.status === 'number' ? error.status : 404 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, MethodNotFoundError.prototype) } } -export class InvalidOriginError extends WebrpcError { - constructor( - name: string = 'InvalidOrigin', - code: number = 1103, - message: string = 'Invalid origin for Access Key', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, InvalidOriginError.prototype) +export class NoDefaultKeyError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'NoDefaultKey' + this.code = typeof error.code === 'number' ? error.code : 1300 + this.message = error.message || `No default access key found` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, NoDefaultKeyError.prototype) } } -export class InvalidServiceError extends WebrpcError { - constructor( - name: string = 'InvalidService', - code: number = 1104, - message: string = 'Service not enabled for Access key', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, InvalidServiceError.prototype) +export class NotFoundError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'NotFound' + this.code = typeof error.code === 'number' ? error.code : 3000 + this.message = error.message || `Resource not found` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, NotFoundError.prototype) } } -export class UnauthorizedUserError extends WebrpcError { - constructor( - name: string = 'UnauthorizedUser', - code: number = 1105, - message: string = 'Unauthorized user', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, UnauthorizedUserError.prototype) +export class PermissionDeniedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'PermissionDenied' + this.code = typeof error.code === 'number' ? error.code : 1001 + this.message = error.message || `Permission denied` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, PermissionDeniedError.prototype) } } -export class QuotaExceededError extends WebrpcError { - constructor( - name: string = 'QuotaExceeded', - code: number = 1200, - message: string = 'Quota exceeded', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, QuotaExceededError.prototype) +export class ProjectNotFoundError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'ProjectNotFound' + this.code = typeof error.code === 'number' ? error.code : 1100 + this.message = error.message || `Project not found` + this.status = typeof error.status === 'number' ? error.status : 401 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, ProjectNotFoundError.prototype) } } -export class RateLimitError extends WebrpcError { - constructor( - name: string = 'RateLimit', - code: number = 1201, - message: string = 'Rate limit exceeded', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, RateLimitError.prototype) +export class QueryFailedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'QueryFailed' + this.code = typeof error.code === 'number' ? error.code : 2003 + this.message = error.message || `Query failed` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, QueryFailedError.prototype) } } -export class NoDefaultKeyError extends WebrpcError { - constructor( - name: string = 'NoDefaultKey', - code: number = 1300, - message: string = 'No default access key found', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, NoDefaultKeyError.prototype) +export class QuotaExceededError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'QuotaExceeded' + this.code = typeof error.code === 'number' ? error.code : 1200 + this.message = error.message || `Quota exceeded` + this.status = typeof error.status === 'number' ? error.status : 429 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, QuotaExceededError.prototype) } } -export class MaxAccessKeysError extends WebrpcError { - constructor( - name: string = 'MaxAccessKeys', - code: number = 1301, - message: string = 'Access keys limit reached', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, MaxAccessKeysError.prototype) +export class RateLimitError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'RateLimit' + this.code = typeof error.code === 'number' ? error.code : 1201 + this.message = error.message || `Rate limit exceeded` + this.status = typeof error.status === 'number' ? error.status : 429 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, RateLimitError.prototype) } } -export class AtLeastOneKeyError extends WebrpcError { - constructor( - name: string = 'AtLeastOneKey', - code: number = 1302, - message: string = 'You need at least one Access Key', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, AtLeastOneKeyError.prototype) +export class RateLimitedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'RateLimited' + this.code = typeof error.code === 'number' ? error.code : 1007 + this.message = error.message || `Rate-limited. Please slow down.` + this.status = typeof error.status === 'number' ? error.status : 429 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, RateLimitedError.prototype) } } -export class TimeoutError extends WebrpcError { - constructor( - name: string = 'Timeout', - code: number = 1900, - message: string = 'Request timed out', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, TimeoutError.prototype) +export class RequestConflictError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'RequestConflict' + this.code = typeof error.code === 'number' ? error.code : 1004 + this.message = error.message || `Conflict with target resource` + this.status = typeof error.status === 'number' ? error.status : 409 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, RequestConflictError.prototype) } } -export class InvalidArgumentError extends WebrpcError { - constructor( - name: string = 'InvalidArgument', - code: number = 2001, - message: string = 'Invalid argument', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, InvalidArgumentError.prototype) +export class ResourceExhaustedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'ResourceExhausted' + this.code = typeof error.code === 'number' ? error.code : 2004 + this.message = error.message || `Resource exhausted` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, ResourceExhaustedError.prototype) } } -export class UnavailableError extends WebrpcError { - constructor( - name: string = 'Unavailable', - code: number = 2002, - message: string = 'Unavailable resource', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, UnavailableError.prototype) +export class SessionExpiredError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'SessionExpired' + this.code = typeof error.code === 'number' ? error.code : 1002 + this.message = error.message || `Session expired` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, SessionExpiredError.prototype) } } -export class QueryFailedError extends WebrpcError { - constructor( - name: string = 'QueryFailed', - code: number = 2003, - message: string = 'Query failed', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, QueryFailedError.prototype) +export class TimeoutError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Timeout' + this.code = typeof error.code === 'number' ? error.code : 1900 + this.message = error.message || `Request timed out` + this.status = typeof error.status === 'number' ? error.status : 408 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, TimeoutError.prototype) } } -export class ResourceExhaustedError extends WebrpcError { - constructor( - name: string = 'ResourceExhausted', - code: number = 2004, - message: string = 'Resource exhausted', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, ResourceExhaustedError.prototype) +export class UnauthorizedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Unauthorized' + this.code = typeof error.code === 'number' ? error.code : 1000 + this.message = error.message || `Unauthorized access` + this.status = typeof error.status === 'number' ? error.status : 401 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, UnauthorizedError.prototype) } } -export class NotFoundError extends WebrpcError { - constructor( - name: string = 'NotFound', - code: number = 3000, - message: string = 'Resource not found', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, NotFoundError.prototype) +export class UnauthorizedUserError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'UnauthorizedUser' + this.code = typeof error.code === 'number' ? error.code : 1105 + this.message = error.message || `Unauthorized user` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, UnauthorizedUserError.prototype) } } -export class MetadataCallFailedError extends WebrpcError { - constructor( - name: string = 'MetadataCallFailed', - code: number = 3003, - message: string = 'Metadata service call failed', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, MetadataCallFailedError.prototype) +export class UnavailableError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Unavailable' + this.code = typeof error.code === 'number' ? error.code : 2002 + this.message = error.message || `Unavailable resource` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, UnavailableError.prototype) } } @@ -2268,38 +2728,78 @@ export enum errors { WebrpcBadResponse = 'WebrpcBadResponse', WebrpcServerPanic = 'WebrpcServerPanic', WebrpcInternalError = 'WebrpcInternalError', - WebrpcClientDisconnected = 'WebrpcClientDisconnected', + WebrpcClientAborted = 'WebrpcClientAborted', WebrpcStreamLost = 'WebrpcStreamLost', WebrpcStreamFinished = 'WebrpcStreamFinished', - Unauthorized = 'Unauthorized', - PermissionDenied = 'PermissionDenied', - SessionExpired = 'SessionExpired', - MethodNotFound = 'MethodNotFound', - RequestConflict = 'RequestConflict', Aborted = 'Aborted', - Geoblocked = 'Geoblocked', - RateLimited = 'RateLimited', - ProjectNotFound = 'ProjectNotFound', - AccessKeyNotFound = 'AccessKeyNotFound', AccessKeyMismatch = 'AccessKeyMismatch', + AccessKeyNotFound = 'AccessKeyNotFound', + AtLeastOneKey = 'AtLeastOneKey', + Geoblocked = 'Geoblocked', + InvalidArgument = 'InvalidArgument', InvalidOrigin = 'InvalidOrigin', InvalidService = 'InvalidService', - UnauthorizedUser = 'UnauthorizedUser', + MaxAccessKeys = 'MaxAccessKeys', + MetadataCallFailed = 'MetadataCallFailed', + MethodNotFound = 'MethodNotFound', + NoDefaultKey = 'NoDefaultKey', + NotFound = 'NotFound', + PermissionDenied = 'PermissionDenied', + ProjectNotFound = 'ProjectNotFound', + QueryFailed = 'QueryFailed', QuotaExceeded = 'QuotaExceeded', RateLimit = 'RateLimit', - NoDefaultKey = 'NoDefaultKey', - MaxAccessKeys = 'MaxAccessKeys', - AtLeastOneKey = 'AtLeastOneKey', + RateLimited = 'RateLimited', + RequestConflict = 'RequestConflict', + ResourceExhausted = 'ResourceExhausted', + SessionExpired = 'SessionExpired', Timeout = 'Timeout', - InvalidArgument = 'InvalidArgument', + Unauthorized = 'Unauthorized', + UnauthorizedUser = 'UnauthorizedUser', Unavailable = 'Unavailable', - QueryFailed = 'QueryFailed', - ResourceExhausted = 'ResourceExhausted', - NotFound = 'NotFound', - MetadataCallFailed = 'MetadataCallFailed', } -const webrpcErrorByCode: { [code: number]: any } = { +export enum WebrpcErrorCodes { + WebrpcEndpoint = 0, + WebrpcRequestFailed = -1, + WebrpcBadRoute = -2, + WebrpcBadMethod = -3, + WebrpcBadRequest = -4, + WebrpcBadResponse = -5, + WebrpcServerPanic = -6, + WebrpcInternalError = -7, + WebrpcClientAborted = -8, + WebrpcStreamLost = -9, + WebrpcStreamFinished = -10, + Aborted = 1005, + AccessKeyMismatch = 1102, + AccessKeyNotFound = 1101, + AtLeastOneKey = 1302, + Geoblocked = 1006, + InvalidArgument = 2001, + InvalidOrigin = 1103, + InvalidService = 1104, + MaxAccessKeys = 1301, + MetadataCallFailed = 3003, + MethodNotFound = 1003, + NoDefaultKey = 1300, + NotFound = 3000, + PermissionDenied = 1001, + ProjectNotFound = 1100, + QueryFailed = 2003, + QuotaExceeded = 1200, + RateLimit = 1201, + RateLimited = 1007, + RequestConflict = 1004, + ResourceExhausted = 2004, + SessionExpired = 1002, + Timeout = 1900, + Unauthorized = 1000, + UnauthorizedUser = 1105, + Unavailable = 2002, +} + +export const webrpcErrorByCode: { [code: number]: any } = { [0]: WebrpcEndpointError, [-1]: WebrpcRequestFailedError, [-2]: WebrpcBadRouteError, @@ -2308,46 +2808,89 @@ const webrpcErrorByCode: { [code: number]: any } = { [-5]: WebrpcBadResponseError, [-6]: WebrpcServerPanicError, [-7]: WebrpcInternalErrorError, - [-8]: WebrpcClientDisconnectedError, + [-8]: WebrpcClientAbortedError, [-9]: WebrpcStreamLostError, [-10]: WebrpcStreamFinishedError, - [1000]: UnauthorizedError, - [1001]: PermissionDeniedError, - [1002]: SessionExpiredError, - [1003]: MethodNotFoundError, - [1004]: RequestConflictError, [1005]: AbortedError, - [1006]: GeoblockedError, - [1007]: RateLimitedError, - [1100]: ProjectNotFoundError, - [1101]: AccessKeyNotFoundError, [1102]: AccessKeyMismatchError, + [1101]: AccessKeyNotFoundError, + [1302]: AtLeastOneKeyError, + [1006]: GeoblockedError, + [2001]: InvalidArgumentError, [1103]: InvalidOriginError, [1104]: InvalidServiceError, - [1105]: UnauthorizedUserError, + [1301]: MaxAccessKeysError, + [3003]: MetadataCallFailedError, + [1003]: MethodNotFoundError, + [1300]: NoDefaultKeyError, + [3000]: NotFoundError, + [1001]: PermissionDeniedError, + [1100]: ProjectNotFoundError, + [2003]: QueryFailedError, [1200]: QuotaExceededError, [1201]: RateLimitError, - [1300]: NoDefaultKeyError, - [1301]: MaxAccessKeysError, - [1302]: AtLeastOneKeyError, + [1007]: RateLimitedError, + [1004]: RequestConflictError, + [2004]: ResourceExhaustedError, + [1002]: SessionExpiredError, [1900]: TimeoutError, - [2001]: InvalidArgumentError, + [1000]: UnauthorizedError, + [1105]: UnauthorizedUserError, [2002]: UnavailableError, - [2003]: QueryFailedError, - [2004]: ResourceExhaustedError, - [3000]: NotFoundError, - [3003]: MetadataCallFailedError, } -export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise +// +// Webrpc +// -export interface WebrpcStreamOptions extends WebrpcOptions { - onMessage: (message: T) => void - onError: (error: WebrpcError, reconnect: () => void) => void - onOpen?: () => void - onClose?: () => void +export const WebrpcHeader = 'Webrpc' + +export const WebrpcHeaderValue = 'webrpc@v0.31.2;gen-typescript@v0.23.1;sequence-indexer@v0.4.0' + +type WebrpcGenVersions = { + WebrpcGenVersion: string + codeGenName: string + codeGenVersion: string + schemaName: string + schemaVersion: string } -export interface WebrpcOptions { - headers?: HeadersInit - signal?: AbortSignal + +export function VersionFromHeader(headers: Headers): WebrpcGenVersions { + const headerValue = headers.get(WebrpcHeader) + if (!headerValue) { + return { + WebrpcGenVersion: '', + codeGenName: '', + codeGenVersion: '', + schemaName: '', + schemaVersion: '', + } + } + + return parseWebrpcGenVersions(headerValue) +} + +function parseWebrpcGenVersions(header: string): WebrpcGenVersions { + const versions = header.split(';') + if (versions.length < 3) { + return { + WebrpcGenVersion: '', + codeGenName: '', + codeGenVersion: '', + schemaName: '', + schemaVersion: '', + } + } + + const [_, WebrpcGenVersion] = versions[0]!.split('@') + const [codeGenName, codeGenVersion] = versions[1]!.split('@') + const [schemaName, schemaVersion] = versions[2]!.split('@') + + return { + WebrpcGenVersion: WebrpcGenVersion ?? '', + codeGenName: codeGenName ?? '', + codeGenVersion: codeGenVersion ?? '', + schemaName: schemaName ?? '', + schemaVersion: schemaVersion ?? '', + } } diff --git a/packages/services/indexer/src/indexergw.gen.ts b/packages/services/indexer/src/indexergw.gen.ts index 92f85b2c8..d22bc5b56 100644 --- a/packages/services/indexer/src/indexergw.gen.ts +++ b/packages/services/indexer/src/indexergw.gen.ts @@ -1,79 +1,174 @@ /* eslint-disable */ -// sequence-indexer v0.4.0 5be4a3e78d9c7e0cc378c675ec01c518e83772e3 +// sequence-indexer v0.4.0 212120aad9a46e88ead9a2183c5717e9902d8c2b // -- -// Code generated by webrpc-gen@v0.21.1 with typescript generator. DO NOT EDIT. +// Code generated by Webrpc-gen@v0.31.2 with typescript generator. DO NOT EDIT. // -// webrpc-gen -schema=indexer.ridl -service=IndexerGateway -target=typescript -client -out=./clients/indexergw.gen.ts +// webrpc-gen -schema=merged.gen.json -service=IndexerGateway -target=typescript -client -out=./clients/indexergw.gen.ts -export const WebrpcHeader = 'Webrpc' - -export const WebrpcHeaderValue = 'webrpc@v0.21.1;gen-typescript@v0.15.1;sequence-indexer@v0.4.0' - -// WebRPC description and code-gen version -export const WebRPCVersion = 'v1' +// Webrpc description and code-gen version +export const WebrpcVersion = 'v1' // Schema version of your RIDL schema -export const WebRPCSchemaVersion = 'v0.4.0' +export const WebrpcSchemaVersion = 'v0.4.0' // Schema hash generated from your RIDL schema -export const WebRPCSchemaHash = '5be4a3e78d9c7e0cc378c675ec01c518e83772e3' +export const WebrpcSchemaHash = '212120aad9a46e88ead9a2183c5717e9902d8c2b' -type WebrpcGenVersions = { - webrpcGenVersion: string - codeGenName: string - codeGenVersion: string - schemaName: string - schemaVersion: string -} +// +// Client interface +// -export function VersionFromHeader(headers: Headers): WebrpcGenVersions { - const headerValue = headers.get(WebrpcHeader) - if (!headerValue) { - return { - webrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } +export interface IndexerGatewayClient { + /** + * GetTokenBalances returns a balance summary/details for an specific account + * on all indexer nodes. By default if accountAddress is left empty, it will + * use the account from the jwt session. + */ + getBalanceUpdates( + req: GetBalanceUpdatesRequest, + headers?: object, + signal?: AbortSignal, + ): Promise - return parseWebrpcGenVersions(headerValue) -} + /** + * GetChains returns a list of chains with their ID and name + */ + getChains(req: GetChainsRequest, headers?: object, signal?: AbortSignal): Promise -function parseWebrpcGenVersions(header: string): WebrpcGenVersions { - const versions = header.split(';') - if (versions.length < 3) { - return { - webrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } + /** + * GetNativeTokenBalance queries indexer nodes for the latest native token + * account balance. + */ + getNativeTokenBalance( + req: GetNativeTokenBalanceRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * GetTokenBalances returns a balance summary/details for a specific account + * on all indexer nodes. By default if accountAddress is left empty, it will + * use the account from the jwt session. + * + * @deprecated Use GetTokenBalancesSummary or GetTokenBalancesDetails instead. + */ + getTokenBalances( + req: GetTokenBalancesRequest, + headers?: object, + signal?: AbortSignal, + ): Promise - const [_, webrpcGenVersion] = versions[0]!.split('@') - const [codeGenName, codeGenVersion] = versions[1]!.split('@') - const [schemaName, schemaVersion] = versions[2]!.split('@') + /** + * GetTokenBalancesByContract returns a balances for specific accounts and + * contracts on all indexer nodes. The collection ERC721 & ERC1155 tokens are + * represented as individual balances. + */ + getTokenBalancesByContract( + req: GetTokenBalancesByContractRequest, + headers?: object, + signal?: AbortSignal, + ): Promise - return { - webrpcGenVersion: webrpcGenVersion!, - codeGenName: codeGenName!, - codeGenVersion: codeGenVersion!, - schemaName: schemaName!, - schemaVersion: schemaVersion!, - } + /** + * GetTokenBalancesDetails returns a detailed balance summary for the given + * accounts on all indexer nodes. The collection ERC721 & ERC1155 tokens are + * represented as individual balances. + */ + getTokenBalancesDetails( + req: GetTokenBalancesDetailsRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * GetTokenBalancesSummary returns a summary of token balances for the given + * accounts on all indexer nodes. The collection ERC721 & ERC1155 tokens are + * represented as a single aggregated balance. + */ + getTokenBalancesSummary( + req: GetTokenBalancesSummaryRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + getTokenPrice(req: GetTokenPriceRequest, headers?: object, signal?: AbortSignal): Promise + + getTokenPrices(req: GetTokenPricesRequest, headers?: object, signal?: AbortSignal): Promise + + /** + * GetTransactionHistory returns the history of mined transactions for the + * given account on all indexer nodes, which includes a list of token transfer + * (sent/received) , and sent transactions from a Sequence wallet. + */ + getTransactionHistory( + req: GetTransactionHistoryRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * Ping the indexer + */ + ping(headers?: object, signal?: AbortSignal): Promise + + /** + * Get the current runtime health status of the indexer gatewya + */ + runtimeStatus(headers?: object, signal?: AbortSignal): Promise + + /** + * Get the current version of the indexer + */ + version(headers?: object, signal?: AbortSignal): Promise } // -// Types +// Schema types // -export enum ResourceStatus { - NOT_AVAILABLE = 'NOT_AVAILABLE', - REFRESHING = 'REFRESHING', - AVAILABLE = 'AVAILABLE', +export interface Asset { + id: number + collectionId: number + tokenId?: string + url?: string + metadataField: string + name?: string + filesize?: number + mimeType?: string + width?: number + height?: number + updatedAt?: string +} + +export enum BackupMode { + INCREMENTAL = 'INCREMENTAL', + COMPLETE = 'COMPLETE', +} + +export interface BloomStats { + hitRatio: string + falsePositivesPercent: string + hitCount: number + missCount: number + falsePositives: number +} + +export interface BloomStatus { + enabled: boolean + initialized: boolean + bloomInitElapsedTime: string + stats: BloomStats +} + +export interface Bond { + pebble: PebbleMetrics + estimatedDiskUsagePerTable: any + estimatedDiskUsageTotal: string +} + +export interface ChainInfo { + chainId: number + chainName: string } export interface ContractInfo { @@ -93,56 +188,29 @@ export interface ContractInfo { status: ResourceStatus } -export interface ContractInfoExtensions { - link: string - description: string - categories: Array - ogImage: string - ogName: string - originChainId: number - originAddress: string - blacklist: boolean - verified: boolean - verifiedBy: string - featured: boolean - featureIndex: number +export interface ContractInfoExtensionBridgeInfo { + tokenAddress: string } -export interface TokenMetadata { - contractAddress?: string - tokenId: string - source: string - name: string - description?: string - image?: string - video?: string - audio?: string - properties?: { [key: string]: any } - attributes: Array<{ [key: string]: any }> - image_data?: string - external_url?: string - background_color?: string - animation_url?: string - decimals?: number - updatedAt?: string - assets?: Array - status: ResourceStatus - queuedAt?: string - lastFetched?: string +export interface ContractInfoExtensionIndexingInfo { + useOnChainBalance: boolean } -export interface Asset { - id: number - collectionId: number - tokenId?: string - url?: string - metadataField: string - name?: string - filesize?: number - mimeType?: string - width?: number - height?: number - updatedAt?: string +export interface ContractInfoExtensions { + link?: string + description?: string + categories?: Array + bridgeInfo?: { [key: string]: ContractInfoExtensionBridgeInfo } + indexingInfo?: ContractInfoExtensionIndexingInfo + ogImage?: string + ogName?: string + originChainId?: number + originAddress?: string + blacklist?: boolean + verified?: boolean + verifiedBy?: string + featured?: boolean + featureIndex?: number } export enum ContractType { @@ -156,72 +224,73 @@ export enum ContractType { ERC721_BRIDGE = 'ERC721_BRIDGE', ERC1155_BRIDGE = 'ERC1155_BRIDGE', SEQ_MARKETPLACE = 'SEQ_MARKETPLACE', + ERC6909 = 'ERC6909', } -export enum EventLogType { - UNKNOWN = 'UNKNOWN', - BLOCK_ADDED = 'BLOCK_ADDED', - BLOCK_REMOVED = 'BLOCK_REMOVED', -} - -export enum EventLogDataType { - EVENT = 'EVENT', - TOKEN_TRANSFER = 'TOKEN_TRANSFER', - NATIVE_TOKEN_TRANSFER = 'NATIVE_TOKEN_TRANSFER', - SEQUENCE_TXN = 'SEQUENCE_TXN', -} - -export enum OrderStatus { - OPEN = 'OPEN', - CLOSED = 'CLOSED', - CANCELLED = 'CANCELLED', +export enum ContractVerificationStatus { + VERIFIED = 'VERIFIED', + UNVERIFIED = 'UNVERIFIED', + ALL = 'ALL', } -export enum TxnTransferType { - UNKNOWN = 'UNKNOWN', - SEND = 'SEND', - RECEIVE = 'RECEIVE', +export interface DiskUsage { + humanReadable: string + used: number + size: number + percent: number + dirs: { [key: string]: string } } -export enum TransactionStatus { - FAILED = 'FAILED', - SUCCESSFUL = 'SUCCESSFUL', +export interface EtherBalance { + accountAddress: string + balanceWei: string } -export enum TransactionType { - LegacyTxnType = 'LegacyTxnType', - AccessListTxnType = 'AccessListTxnType', - DynamicFeeTxnType = 'DynamicFeeTxnType', +export interface EventDecoded { + topicHash: string + eventSig: string + types: Array + names: Array + values: Array } -export enum SortOrder { - DESC = 'DESC', - ASC = 'ASC', +export interface EventFilter { + events?: Array + contractAddresses?: Array + accounts?: Array + tokenIDs?: Array } -export enum ContractVerificationStatus { - VERIFIED = 'VERIFIED', - UNVERIFIED = 'UNVERIFIED', - ALL = 'ALL', +export interface EventLog { + id: number + uid: string + type: EventLogType + blockNumber: number + blockHash: string + parentBlockHash: string + contractAddress: string + contractType: ContractType + txnHash: string + txnIndex: number + txnLogIndex: number + logDataType: EventLogDataType + ts: string + txnInfo?: TxnInfo + rawLog?: { [key: string]: any } + event?: EventDecoded } -export interface Version { - webrpcVersion: string - schemaVersion: string - schemaHash: string - appVersion: string +export enum EventLogDataType { + EVENT = 'EVENT', + TOKEN_TRANSFER = 'TOKEN_TRANSFER', + NATIVE_TOKEN_TRANSFER = 'NATIVE_TOKEN_TRANSFER', + SEQUENCE_TXN = 'SEQUENCE_TXN', } -export interface RuntimeStatus { - healthOK: boolean - indexerEnabled: boolean - startTime: string - uptime: number - ver: string - branch: string - commitHash: string - chainID: number - checks: RuntimeChecks +export enum EventLogType { + UNKNOWN = 'UNKNOWN', + BLOCK_ADDED = 'BLOCK_ADDED', + BLOCK_REMOVED = 'BLOCK_REMOVED', } export interface GatewayBackendResponseTime { @@ -235,6 +304,30 @@ export interface GatewayBackendRuntimeStatus { responseTime: GatewayBackendResponseTime } +export interface GatewayEtherBalance { + chainId: number + errorReason?: string + result: EtherBalance +} + +export interface GatewayNativeTokenBalance { + chainId: number + errorReason?: string + result: NativeTokenBalance +} + +export interface GatewayNativeTokenBalances { + chainId: number + errorReason?: string + results: Array +} + +export interface GatewayPrice { + chainID: number + errorReason?: string + results: Array +} + export interface GatewayRuntimeStatus { healthOK: boolean startTime: string @@ -245,113 +338,173 @@ export interface GatewayRuntimeStatus { backends: Array } -export interface WALWriterRuntimeStatus { - healthOK: boolean - startTime: string - uptime: number - ver: string - branch: string - commitHash: string +export interface GatewayTokenBalance { + chainId: number + errorReason?: string + results: Array +} + +export interface GatewayTokenPriceQuery { chainID: number - percentWALWritten: number + queries: Array } -export interface RuntimeChecks { - running: boolean - runnables: any - cgoEnabled: boolean - quotaControlEnabled: boolean - syncMode: string - percentIndexed: number +export interface GatewayTransaction { + chainId: number + errorReason?: string + results: Array +} + +export interface IndexState { + chainId: string lastBlockNum: number - lastBlockNumWithState: number - bloomStatus: BloomStatus - bond: Bond - diskUsage: DiskUsage + lastBlockHash: string } -export interface DiskUsage { - humanReadable: string - used: number - size: number - percent: number - dirs: { [key: string]: string } +export interface IndexedBlock { + blockNumber: number + blockShortHash: string } -export interface Bond { - pebble: PebbleMetrics - estimatedDiskUsagePerTable: any - estimatedDiskUsageTotal: string +export interface IndexerMetrics { + blocksPerSecond: number + eventsPerSecond: number } -export interface PebbleMetrics { - compactionCount: number - compactionEstimatedDebt: number - compactionInProgressBytes: number - compactionNumInProgress: number - compactionMarkedFiles: number +export interface MarketplaceOrder { + orderId: string + tokenContract: string + tokenId: string + isListing: boolean + quantity: string + quantityRemaining: string + currencyAddress: string + pricePerToken: string + expiry: string + orderStatus: OrderStatus + createdBy: string + blockNumber: number + orderbookContractAddress: string + createdAt: number } -export interface BloomStatus { - enabled: boolean - initialized: boolean - bloomInitElapsedTime: string +export interface MarketplaceOrderFilter { + isListing?: boolean + userAddresses?: Array + currencyAddresses: Array + orderIds: Array + tokenIds: Array + excludeUserAddresses?: Array + blockNumberGt: number + createdAtAfter: number + orderStatuses: Array + returnExpired: boolean } -export interface EtherBalance { - accountAddress: string - balanceWei: string +export interface MarketplaceTopOrdersFilter { + currencyAddresses: Array + tokenIds: Array + isListing: boolean + priceSort: SortOrder + excludeUser?: string +} + +export interface MetadataOptions { + verifiedOnly?: boolean + unverifiedOnly?: boolean + includeContracts?: Array } export interface NativeTokenBalance { accountAddress: string chainId: number + name: string + symbol: string balance: string - error: string + balanceUSD: string + priceUSD: string + priceUpdatedAt?: string + errorReason?: string } -export interface IndexState { - chainId: string - lastBlockNum: number - lastBlockHash: string +export enum NetworkType { + MAINNETS = 'MAINNETS', + TESTNETS = 'TESTNETS', + ALL = 'ALL', } -export interface IndexedBlock { - blockNumber: number - blockShortHash: string +export enum OrderStatus { + OPEN = 'OPEN', + CLOSED = 'CLOSED', + CANCELLED = 'CANCELLED', } -export interface TxnInfo { - from: string - to: string - value: string +export interface Page { + page?: number + column?: string + before?: any + after?: any + sort?: Array + pageSize?: number + more?: boolean } -export interface EventLog { - id: number - uid: string - type: EventLogType - blockNumber: number - blockHash: string - parentBlockHash: string +export interface PebbleMetrics { + compactionCount: number + compactionEstimatedDebt: number + compactionInProgressBytes: number + compactionNumInProgress: number + compactionMarkedFiles: number +} + +export interface Price { contractAddress: string - contractType: ContractType - txnHash: string - txnIndex: number - txnLogIndex: number - logDataType: EventLogDataType - ts: string - txnInfo?: TxnInfo - rawLog?: { [key: string]: any } - event?: EventDecoded + tokenID?: string + priceUSD: string + updatedAt?: string } -export interface EventDecoded { - topicHash: string - eventSig: string - types: Array - names: Array - values: Array +export enum ResourceStatus { + NOT_AVAILABLE = 'NOT_AVAILABLE', + REFRESHING = 'REFRESHING', + AVAILABLE = 'AVAILABLE', +} + +export interface RuntimeChecks { + running: boolean + runnables: any + cgoEnabled: boolean + quotaControlEnabled: boolean + syncMode: string + percentIndexed: number + lastBlockNum: number + lastBlockNumWithState: number + bloomStatus: BloomStatus + bond: Bond + diskUsage: DiskUsage + metrics: IndexerMetrics +} + +export interface RuntimeStatus { + healthOK: boolean + indexerEnabled: boolean + startTime: string + uptime: number + ver: string + branch: string + commitHash: string + chainID: number + checks: RuntimeChecks +} + +export interface SortBy { + column: string + order: SortOrder +} + +export enum SortOrder { + DESC = 'DESC', + ASC = 'ASC', } export interface TokenBalance { @@ -360,6 +513,9 @@ export interface TokenBalance { accountAddress: string tokenID?: string balance: string + balanceUSD: string + priceUSD: string + priceUpdatedAt?: string blockHash: string blockNumber: number chainId: number @@ -369,50 +525,76 @@ export interface TokenBalance { tokenMetadata?: TokenMetadata } -export interface OrderbookOrder { - orderId: string - tokenContract: string - tokenId: string - isListing: boolean - quantity: string - quantityRemaining: string - currencyAddress: string - pricePerToken: string - expiry: string - orderStatus: OrderStatus - createdBy: string +export interface TokenBalanceFilter { + contractAddress: string + sinceBlockNumber: number +} + +export interface TokenBalancesByContractFilter { + contractAddresses: Array + accountAddresses?: Array + contractStatus?: ContractVerificationStatus + tokenIDs?: Array +} + +export interface TokenBalancesFilter { + accountAddresses: Array + contractStatus?: ContractVerificationStatus + contractTypes?: Array + contractWhitelist?: Array + contractBlacklist?: Array + omitNativeBalances: boolean + omitPrices?: boolean + tokenIDs?: Array +} + +export interface TokenHistory { blockNumber: number - orderbookContractAddress: string - createdAt: number + blockHash: string + contractAddress: string + contractType: ContractType + fromAddress: string + toAddress: string + txnHash: string + txnIndex: number + txnLogIndex: number + tokenIDs: string + amounts: string + ts: string } -export interface OrderbookOrderFilter { - isListing?: boolean - userAddresses?: Array - tokenIds: Array - excludeUserAddresses?: Array - afterBlockNumber: number - afterCreatedAt: number - beforeExpiry: number - userAddress?: string - excludeUserAddress?: string +export interface TokenIDRange { + start: string + end: string +} + +export interface TokenMetadata { + chainId?: number + contractAddress?: string + tokenId: string + source: string + name: string + description?: string + image?: string + video?: string + audio?: string + properties?: { [key: string]: any } + attributes: Array<{ [key: string]: any }> + image_data?: string + external_url?: string + background_color?: string + animation_url?: string + decimals?: number + updatedAt?: string + assets?: Array + status: ResourceStatus + queuedAt?: string + lastFetched?: string } -export interface TokenHistory { - blockNumber: number - blockHash: string - accountAddress: string +export interface TokenPriceQuery { contractAddress: string - contractType: ContractType - fromAddress: string - toAddress: string - txnHash: string - txnIndex: number - txnLogIndex: number - logData: string - tokenIDs: string - Amounts: string - ts: string + tokenID?: string } export interface TokenSupply { @@ -433,17 +615,12 @@ export interface Transaction { timestamp: string } -export interface TxnTransfer { - transferType: TxnTransferType - contractAddress: string - contractType: ContractType - from: string - to: string - tokenIds?: Array - amounts: Array - logIndex: number - contractInfo?: ContractInfo - tokenMetadata?: { [key: string]: TokenMetadata } +export interface TransactionFilter { + txnHash?: string + from?: string + to?: string + contractAddress?: string + event?: string } export interface TransactionHistoryFilter { @@ -456,14 +633,14 @@ export interface TransactionHistoryFilter { fromBlock?: number toBlock?: number tokenID?: string + omitPrices?: boolean } -export interface TransactionFilter { - txnHash?: string - from?: string - to?: string - contractAddress?: string - event?: string +export interface TransactionLog { + contractAddress: string + topics: Array + data: string + index: number } export interface TransactionReceipt { @@ -482,31 +659,60 @@ export interface TransactionReceipt { reorged: boolean } -export interface TransactionLog { +export enum TransactionStatus { + FAILED = 'FAILED', + SUCCESSFUL = 'SUCCESSFUL', +} + +export enum TransactionType { + LegacyTxnType = 'LegacyTxnType', + AccessListTxnType = 'AccessListTxnType', + DynamicFeeTxnType = 'DynamicFeeTxnType', +} + +export interface TxnInfo { + from: string + to: string + value: string +} + +export interface TxnTransfer { + transferType: TxnTransferType contractAddress: string - topics: Array - data: string - index: number + contractType: ContractType + from: string + to: string + tokenIds?: Array + amounts: Array + logIndex: number + amountsUSD?: Array + pricesUSD?: Array + contractInfo?: ContractInfo + tokenMetadata?: { [key: string]: TokenMetadata } } -export interface TokenIDRange { - start: string - end: string +export enum TxnTransferType { + UNKNOWN = 'UNKNOWN', + SEND = 'SEND', + RECEIVE = 'RECEIVE', } -export interface Page { - page?: number - column?: string - before?: any - after?: any - sort?: Array - pageSize?: number - more?: boolean +export interface Version { + webrpcVersion: string + schemaVersion: string + schemaHash: string + appVersion: string } -export interface SortBy { - column: string - order: SortOrder +export interface WALWriterRuntimeStatus { + healthOK: boolean + startTime: string + uptime: number + ver: string + branch: string + commitHash: string + chainID: number + percentWALWritten: number } export interface WebhookListener { @@ -519,109 +725,45 @@ export interface WebhookListener { active: boolean } -export interface EventFilter { - events?: Array - contractAddresses?: Array - accounts?: Array - tokenIDs?: Array -} - -export interface TokenBalanceFilter { +export interface GetBalanceUpdatesRequest { + chainIds?: Array + networks?: Array + networkType?: NetworkType contractAddress: string - sinceBlockNumber: number -} - -export interface MetadataOptions { - verifiedOnly?: boolean - unverifiedOnly?: boolean - includeContracts?: Array -} - -export interface TokenBalancesFilter { - accountAddresses: Array - contractStatus?: ContractVerificationStatus - contractTypes?: Array - contractWhitelist?: Array - contractBlacklist?: Array - omitNativeBalances: boolean -} - -export interface TokenBalancesByContractFilter { - contractAddresses: Array - accountAddresses?: Array - contractStatus?: ContractVerificationStatus -} - -export interface GatewayEtherBalance { - chainId: number - error: string - result: EtherBalance -} - -export interface GatewayNativeTokenBalance { - chainId: number - error: string - result: NativeTokenBalance + lastBlockNumber: number + lastBlockHash?: string + page?: Page } -export interface GatewayNativeTokenBalances { - chainId: number - error: string - results: Array +export interface GetBalanceUpdatesResponse { + page: Page + balances: Array } -export interface GatewayTokenBalance { - chainId: number - error: string - results: Array +export interface GetChainsRequest { + networkType?: NetworkType } -export interface IndexerGateway { - getNativeTokenBalance( - args: GetNativeTokenBalanceArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getTokenBalances(args: GetTokenBalancesArgs, headers?: object, signal?: AbortSignal): Promise - getTokenBalancesSummary( - args: GetTokenBalancesSummaryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getTokenBalancesDetails( - args: GetTokenBalancesDetailsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getTokenBalancesByContract( - args: GetTokenBalancesByContractArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getBalanceUpdates( - args: GetBalanceUpdatesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - ping(headers?: object, signal?: AbortSignal): Promise - version(headers?: object, signal?: AbortSignal): Promise - runtimeStatus(headers?: object, signal?: AbortSignal): Promise +export interface GetChainsResponse { + chains: Array } -export interface GetNativeTokenBalanceArgs { +export interface GetNativeTokenBalanceRequest { chainIds?: Array networks?: Array - testnets?: boolean + networkType?: NetworkType accountAddress?: string + omitPrices?: boolean } -export interface GetNativeTokenBalanceReturn { +export interface GetNativeTokenBalanceResponse { balances: Array } -export interface GetTokenBalancesArgs { + +export interface GetTokenBalancesRequest { chainIds?: Array networks?: Array - testnets?: boolean + networkType?: NetworkType accountAddress?: string contractAddress?: string tokenID?: string @@ -631,85 +773,109 @@ export interface GetTokenBalancesArgs { page?: Page } -export interface GetTokenBalancesReturn { +export interface GetTokenBalancesResponse { page: Page balances: Array } -export interface GetTokenBalancesSummaryArgs { + +export interface GetTokenBalancesByContractRequest { chainIds?: Array networks?: Array - testnets?: boolean - filter: TokenBalancesFilter + networkType?: NetworkType + filter: TokenBalancesByContractFilter omitMetadata?: boolean page?: Page } -export interface GetTokenBalancesSummaryReturn { +export interface GetTokenBalancesByContractResponse { page: Page - nativeBalances: Array balances: Array } -export interface GetTokenBalancesDetailsArgs { + +export interface GetTokenBalancesDetailsRequest { chainIds?: Array networks?: Array - testnets?: boolean + networkType?: NetworkType filter: TokenBalancesFilter omitMetadata?: boolean page?: Page } -export interface GetTokenBalancesDetailsReturn { +export interface GetTokenBalancesDetailsResponse { page: Page nativeBalances: Array balances: Array } -export interface GetTokenBalancesByContractArgs { + +export interface GetTokenBalancesSummaryRequest { chainIds?: Array networks?: Array - testnets?: boolean - filter: TokenBalancesByContractFilter + networkType?: NetworkType + filter: TokenBalancesFilter omitMetadata?: boolean page?: Page } -export interface GetTokenBalancesByContractReturn { +export interface GetTokenBalancesSummaryResponse { page: Page + nativeBalances: Array balances: Array } -export interface GetBalanceUpdatesArgs { + +export interface GetTokenPriceRequest { + q: GatewayTokenPriceQuery +} + +export interface GetTokenPriceResponse { + price: GatewayPrice +} + +export interface GetTokenPricesRequest { + q: Array +} + +export interface GetTokenPricesResponse { + prices: Array +} + +export interface GetTransactionHistoryRequest { chainIds?: Array networks?: Array - testnets?: boolean - contractAddress: string - lastBlockNumber: number - lastBlockHash?: string + networkType?: NetworkType + filter: TransactionHistoryFilter + includeMetadata?: boolean + metadataOptions?: MetadataOptions page?: Page } -export interface GetBalanceUpdatesReturn { +export interface GetTransactionHistoryResponse { page: Page - balances: Array + transactions: Array } -export interface PingArgs {} -export interface PingReturn { +export interface PingRequest {} + +export interface PingResponse { status: boolean } -export interface VersionArgs {} -export interface VersionReturn { - version: Version -} -export interface RuntimeStatusArgs {} +export interface RuntimeStatusRequest {} -export interface RuntimeStatusReturn { +export interface RuntimeStatusResponse { status: GatewayRuntimeStatus } +export interface VersionRequest {} + +export interface VersionResponse { + version: Version +} + // // Client // -export class IndexerGateway implements IndexerGateway { + +export class IndexerGateway implements IndexerGatewayClient { protected hostname: string protected fetch: Fetch protected path = '/rpc/IndexerGateway/' @@ -723,183 +889,266 @@ export class IndexerGateway implements IndexerGateway { return this.hostname + this.path + name } - getNativeTokenBalance = ( - args: GetNativeTokenBalanceArgs, + queryKey = { + getBalanceUpdates: (req: GetBalanceUpdatesRequest) => ['IndexerGateway', 'getBalanceUpdates', req] as const, + getChains: (req: GetChainsRequest) => ['IndexerGateway', 'getChains', req] as const, + getNativeTokenBalance: (req: GetNativeTokenBalanceRequest) => + ['IndexerGateway', 'getNativeTokenBalance', req] as const, + getTokenBalances: (req: GetTokenBalancesRequest) => ['IndexerGateway', 'getTokenBalances', req] as const, + getTokenBalancesByContract: (req: GetTokenBalancesByContractRequest) => + ['IndexerGateway', 'getTokenBalancesByContract', req] as const, + getTokenBalancesDetails: (req: GetTokenBalancesDetailsRequest) => + ['IndexerGateway', 'getTokenBalancesDetails', req] as const, + getTokenBalancesSummary: (req: GetTokenBalancesSummaryRequest) => + ['IndexerGateway', 'getTokenBalancesSummary', req] as const, + getTokenPrice: (req: GetTokenPriceRequest) => ['IndexerGateway', 'getTokenPrice', req] as const, + getTokenPrices: (req: GetTokenPricesRequest) => ['IndexerGateway', 'getTokenPrices', req] as const, + getTransactionHistory: (req: GetTransactionHistoryRequest) => + ['IndexerGateway', 'getTransactionHistory', req] as const, + ping: () => ['IndexerGateway', 'ping'] as const, + runtimeStatus: () => ['IndexerGateway', 'runtimeStatus'] as const, + version: () => ['IndexerGateway', 'version'] as const, + } + + getBalanceUpdates = ( + req: GetBalanceUpdatesRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('GetBalanceUpdates'), createHttpRequest(JsonEncode(req), headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'GetBalanceUpdatesResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + getChains = (req: GetChainsRequest, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('GetChains'), createHttpRequest(JsonEncode(req), headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'GetChainsResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + getNativeTokenBalance = ( + req: GetNativeTokenBalanceRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('GetNativeTokenBalance'), createHttpRequest(JsonEncode(req), headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'GetNativeTokenBalanceResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + getTokenBalances = ( + req: GetTokenBalancesRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('GetTokenBalances'), createHttpRequest(JsonEncode(req), headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'GetTokenBalancesResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + getTokenBalancesByContract = ( + req: GetTokenBalancesByContractRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetNativeTokenBalance'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('GetTokenBalancesByContract'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - balances: >_data.balances, - } + return JsonDecode(_data, 'GetTokenBalancesByContractResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getTokenBalances = ( - args: GetTokenBalancesArgs, + getTokenBalancesDetails = ( + req: GetTokenBalancesDetailsRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenBalances'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('GetTokenBalancesDetails'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - balances: >_data.balances, - } + return JsonDecode(_data, 'GetTokenBalancesDetailsResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getTokenBalancesSummary = ( - args: GetTokenBalancesSummaryArgs, + req: GetTokenBalancesSummaryRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenBalancesSummary'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('GetTokenBalancesSummary'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - nativeBalances: >_data.nativeBalances, - balances: >_data.balances, - } + return JsonDecode(_data, 'GetTokenBalancesSummaryResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getTokenBalancesDetails = ( - args: GetTokenBalancesDetailsArgs, + getTokenPrice = ( + req: GetTokenPriceRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenBalancesDetails'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('GetTokenPrice'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - nativeBalances: >_data.nativeBalances, - balances: >_data.balances, - } + return JsonDecode(_data, 'GetTokenPriceResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getTokenBalancesByContract = ( - args: GetTokenBalancesByContractArgs, + getTokenPrices = ( + req: GetTokenPricesRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTokenBalancesByContract'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('GetTokenPrices'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - balances: >_data.balances, - } + return JsonDecode(_data, 'GetTokenPricesResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getBalanceUpdates = ( - args: GetBalanceUpdatesArgs, + getTransactionHistory = ( + req: GetTransactionHistoryRequest, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetBalanceUpdates'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('GetTransactionHistory'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - balances: >_data.balances, - } + return JsonDecode(_data, 'GetTransactionHistoryResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - ping = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Ping'), createHTTPRequest({}, headers, signal)).then( + ping = (headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('Ping'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'PingResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - version = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Version'), createHTTPRequest({}, headers, signal)).then( + runtimeStatus = (headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('RuntimeStatus'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - version: _data.version, - } + return JsonDecode(_data, 'RuntimeStatusResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - runtimeStatus = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('RuntimeStatus'), createHTTPRequest({}, headers, signal)).then( + version = (headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('Version'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'VersionResponse') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } } -const createHTTPRequest = (body: object = {}, headers: object = {}, signal: AbortSignal | null = null): object => { - const reqHeaders: { [key: string]: string } = { ...headers, 'Content-Type': 'application/json' } - reqHeaders[WebrpcHeader] = WebrpcHeaderValue - - return { - method: 'POST', - headers: reqHeaders, - body: JSON.stringify(body || {}), - signal, +const createHttpRequest = (body: string = '{}', headers: object = {}, signal: AbortSignal | null = null): object => { + const reqHeaders: { [key: string]: string } = { + ...headers, + 'Content-Type': 'application/json', + [WebrpcHeader]: WebrpcHeaderValue, } + return { method: 'POST', headers: reqHeaders, body, signal } } const buildResponse = (res: Response): Promise => { @@ -908,13 +1157,9 @@ const buildResponse = (res: Response): Promise => { try { data = JSON.parse(text) } catch (error) { - let message = '' - if (error instanceof Error) { - message = error.message - } throw WebrpcBadResponseError.new({ status: res.status, - cause: `JSON.parse(): ${message}: response text: ${text}`, + cause: `JSON.parse(): ${error instanceof Error ? error.message : String(error)}: response text: ${text}`, }) } if (!res.ok) { @@ -925,518 +1170,494 @@ const buildResponse = (res: Response): Promise => { }) } +export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise + +export const JsonEncode = (obj: T): string => { + return JSON.stringify(obj) +} + +export const JsonDecode = (data: string | any, _typ: string = ''): T => { + let parsed: any = data + if (typeof data === 'string') { + try { + parsed = JSON.parse(data) + } catch (err) { + throw WebrpcBadResponseError.new({ cause: `JsonDecode: JSON.parse failed: ${(err as Error).message}` }) + } + } + return parsed as T +} + // // Errors // +type WebrpcErrorParams = { name?: string; code?: number; message?: string; status?: number; cause?: string } + export class WebrpcError extends Error { - name: string code: number - message: string status: number - cause?: string - - /** @deprecated Use message instead of msg. Deprecated in webrpc v0.11.0. */ - msg: string - - constructor(name: string, code: number, message: string, status: number, cause?: string) { - super(message) - this.name = name || 'WebrpcError' - this.code = typeof code === 'number' ? code : 0 - this.message = message || `endpoint error ${this.code}` - this.msg = this.message - this.status = typeof status === 'number' ? status : 0 - this.cause = cause + + constructor(error: WebrpcErrorParams = {}) { + super(error.message) + this.name = error.name || 'WebrpcEndpointError' + this.code = typeof error.code === 'number' ? error.code : 0 + this.message = error.message || `endpoint error` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcError.prototype) } static new(payload: any): WebrpcError { - return new this(payload.error, payload.code, payload.message || payload.msg, payload.status, payload.cause) + return new this({ message: payload.message, code: payload.code, status: payload.status, cause: payload.cause }) } } -// Webrpc errors - export class WebrpcEndpointError extends WebrpcError { - constructor( - name: string = 'WebrpcEndpoint', - code: number = 0, - message: string = 'endpoint error', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcEndpoint' + this.code = typeof error.code === 'number' ? error.code : 0 + this.message = error.message || `endpoint error` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcEndpointError.prototype) } } export class WebrpcRequestFailedError extends WebrpcError { - constructor( - name: string = 'WebrpcRequestFailed', - code: number = -1, - message: string = 'request failed', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcRequestFailed' + this.code = typeof error.code === 'number' ? error.code : -1 + this.message = error.message || `request failed` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcRequestFailedError.prototype) } } export class WebrpcBadRouteError extends WebrpcError { - constructor( - name: string = 'WebrpcBadRoute', - code: number = -2, - message: string = 'bad route', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadRoute' + this.code = typeof error.code === 'number' ? error.code : -2 + this.message = error.message || `bad route` + this.status = typeof error.status === 'number' ? error.status : 404 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcBadRouteError.prototype) } } export class WebrpcBadMethodError extends WebrpcError { - constructor( - name: string = 'WebrpcBadMethod', - code: number = -3, - message: string = 'bad method', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadMethod' + this.code = typeof error.code === 'number' ? error.code : -3 + this.message = error.message || `bad method` + this.status = typeof error.status === 'number' ? error.status : 405 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcBadMethodError.prototype) } } export class WebrpcBadRequestError extends WebrpcError { - constructor( - name: string = 'WebrpcBadRequest', - code: number = -4, - message: string = 'bad request', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadRequest' + this.code = typeof error.code === 'number' ? error.code : -4 + this.message = error.message || `bad request` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcBadRequestError.prototype) } } export class WebrpcBadResponseError extends WebrpcError { - constructor( - name: string = 'WebrpcBadResponse', - code: number = -5, - message: string = 'bad response', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadResponse' + this.code = typeof error.code === 'number' ? error.code : -5 + this.message = error.message || `bad response` + this.status = typeof error.status === 'number' ? error.status : 500 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcBadResponseError.prototype) } } export class WebrpcServerPanicError extends WebrpcError { - constructor( - name: string = 'WebrpcServerPanic', - code: number = -6, - message: string = 'server panic', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcServerPanic' + this.code = typeof error.code === 'number' ? error.code : -6 + this.message = error.message || `server panic` + this.status = typeof error.status === 'number' ? error.status : 500 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcServerPanicError.prototype) } } export class WebrpcInternalErrorError extends WebrpcError { - constructor( - name: string = 'WebrpcInternalError', - code: number = -7, - message: string = 'internal error', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcInternalError' + this.code = typeof error.code === 'number' ? error.code : -7 + this.message = error.message || `internal error` + this.status = typeof error.status === 'number' ? error.status : 500 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcInternalErrorError.prototype) } } -export class WebrpcClientDisconnectedError extends WebrpcError { - constructor( - name: string = 'WebrpcClientDisconnected', - code: number = -8, - message: string = 'client disconnected', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcClientDisconnectedError.prototype) +export class WebrpcClientAbortedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcClientAborted' + this.code = typeof error.code === 'number' ? error.code : -8 + this.message = error.message || `request aborted by client` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcClientAbortedError.prototype) } } export class WebrpcStreamLostError extends WebrpcError { - constructor( - name: string = 'WebrpcStreamLost', - code: number = -9, - message: string = 'stream lost', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcStreamLost' + this.code = typeof error.code === 'number' ? error.code : -9 + this.message = error.message || `stream lost` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcStreamLostError.prototype) } } export class WebrpcStreamFinishedError extends WebrpcError { - constructor( - name: string = 'WebrpcStreamFinished', - code: number = -10, - message: string = 'stream finished', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcStreamFinished' + this.code = typeof error.code === 'number' ? error.code : -10 + this.message = error.message || `stream finished` + this.status = typeof error.status === 'number' ? error.status : 200 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcStreamFinishedError.prototype) } } +// // Schema errors +// -export class UnauthorizedError extends WebrpcError { - constructor( - name: string = 'Unauthorized', - code: number = 1000, - message: string = 'Unauthorized access', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, UnauthorizedError.prototype) +export class AbortedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Aborted' + this.code = typeof error.code === 'number' ? error.code : 1005 + this.message = error.message || `Request aborted` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, AbortedError.prototype) } } -export class PermissionDeniedError extends WebrpcError { - constructor( - name: string = 'PermissionDenied', - code: number = 1001, - message: string = 'Permission denied', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, PermissionDeniedError.prototype) +export class AccessKeyMismatchError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'AccessKeyMismatch' + this.code = typeof error.code === 'number' ? error.code : 1102 + this.message = error.message || `Access key mismatch` + this.status = typeof error.status === 'number' ? error.status : 409 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, AccessKeyMismatchError.prototype) } } -export class SessionExpiredError extends WebrpcError { - constructor( - name: string = 'SessionExpired', - code: number = 1002, - message: string = 'Session expired', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, SessionExpiredError.prototype) +export class AccessKeyNotFoundError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'AccessKeyNotFound' + this.code = typeof error.code === 'number' ? error.code : 1101 + this.message = error.message || `Access key not found` + this.status = typeof error.status === 'number' ? error.status : 401 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, AccessKeyNotFoundError.prototype) } } -export class MethodNotFoundError extends WebrpcError { - constructor( - name: string = 'MethodNotFound', - code: number = 1003, - message: string = 'Method not found', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, MethodNotFoundError.prototype) +export class AtLeastOneKeyError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'AtLeastOneKey' + this.code = typeof error.code === 'number' ? error.code : 1302 + this.message = error.message || `You need at least one Access Key` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, AtLeastOneKeyError.prototype) } } -export class RequestConflictError extends WebrpcError { - constructor( - name: string = 'RequestConflict', - code: number = 1004, - message: string = 'Conflict with target resource', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, RequestConflictError.prototype) +export class GeoblockedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Geoblocked' + this.code = typeof error.code === 'number' ? error.code : 1006 + this.message = error.message || `Geoblocked region` + this.status = typeof error.status === 'number' ? error.status : 451 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, GeoblockedError.prototype) } } -export class AbortedError extends WebrpcError { - constructor( - name: string = 'Aborted', - code: number = 1005, - message: string = 'Request aborted', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, AbortedError.prototype) +export class InvalidArgumentError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'InvalidArgument' + this.code = typeof error.code === 'number' ? error.code : 2001 + this.message = error.message || `Invalid argument` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, InvalidArgumentError.prototype) } } -export class GeoblockedError extends WebrpcError { - constructor( - name: string = 'Geoblocked', - code: number = 1006, - message: string = 'Geoblocked region', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, GeoblockedError.prototype) +export class InvalidOriginError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'InvalidOrigin' + this.code = typeof error.code === 'number' ? error.code : 1103 + this.message = error.message || `Invalid origin for Access Key` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, InvalidOriginError.prototype) } } -export class RateLimitedError extends WebrpcError { - constructor( - name: string = 'RateLimited', - code: number = 1007, - message: string = 'Rate-limited. Please slow down.', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, RateLimitedError.prototype) +export class InvalidServiceError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'InvalidService' + this.code = typeof error.code === 'number' ? error.code : 1104 + this.message = error.message || `Service not enabled for Access key` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, InvalidServiceError.prototype) } } -export class ProjectNotFoundError extends WebrpcError { - constructor( - name: string = 'ProjectNotFound', - code: number = 1100, - message: string = 'Project not found', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, ProjectNotFoundError.prototype) +export class MaxAccessKeysError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'MaxAccessKeys' + this.code = typeof error.code === 'number' ? error.code : 1301 + this.message = error.message || `Access keys limit reached` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, MaxAccessKeysError.prototype) } } -export class AccessKeyNotFoundError extends WebrpcError { - constructor( - name: string = 'AccessKeyNotFound', - code: number = 1101, - message: string = 'Access key not found', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, AccessKeyNotFoundError.prototype) +export class MetadataCallFailedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'MetadataCallFailed' + this.code = typeof error.code === 'number' ? error.code : 3003 + this.message = error.message || `Metadata service call failed` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, MetadataCallFailedError.prototype) } } -export class AccessKeyMismatchError extends WebrpcError { - constructor( - name: string = 'AccessKeyMismatch', - code: number = 1102, - message: string = 'Access key mismatch', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, AccessKeyMismatchError.prototype) +export class MethodNotFoundError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'MethodNotFound' + this.code = typeof error.code === 'number' ? error.code : 1003 + this.message = error.message || `Method not found` + this.status = typeof error.status === 'number' ? error.status : 404 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, MethodNotFoundError.prototype) } } -export class InvalidOriginError extends WebrpcError { - constructor( - name: string = 'InvalidOrigin', - code: number = 1103, - message: string = 'Invalid origin for Access Key', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, InvalidOriginError.prototype) +export class NoDefaultKeyError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'NoDefaultKey' + this.code = typeof error.code === 'number' ? error.code : 1300 + this.message = error.message || `No default access key found` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, NoDefaultKeyError.prototype) } } -export class InvalidServiceError extends WebrpcError { - constructor( - name: string = 'InvalidService', - code: number = 1104, - message: string = 'Service not enabled for Access key', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, InvalidServiceError.prototype) +export class NotFoundError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'NotFound' + this.code = typeof error.code === 'number' ? error.code : 3000 + this.message = error.message || `Resource not found` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, NotFoundError.prototype) } } -export class UnauthorizedUserError extends WebrpcError { - constructor( - name: string = 'UnauthorizedUser', - code: number = 1105, - message: string = 'Unauthorized user', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, UnauthorizedUserError.prototype) +export class PermissionDeniedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'PermissionDenied' + this.code = typeof error.code === 'number' ? error.code : 1001 + this.message = error.message || `Permission denied` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, PermissionDeniedError.prototype) } } -export class QuotaExceededError extends WebrpcError { - constructor( - name: string = 'QuotaExceeded', - code: number = 1200, - message: string = 'Quota exceeded', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, QuotaExceededError.prototype) +export class ProjectNotFoundError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'ProjectNotFound' + this.code = typeof error.code === 'number' ? error.code : 1100 + this.message = error.message || `Project not found` + this.status = typeof error.status === 'number' ? error.status : 401 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, ProjectNotFoundError.prototype) } } -export class RateLimitError extends WebrpcError { - constructor( - name: string = 'RateLimit', - code: number = 1201, - message: string = 'Rate limit exceeded', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, RateLimitError.prototype) +export class QueryFailedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'QueryFailed' + this.code = typeof error.code === 'number' ? error.code : 2003 + this.message = error.message || `Query failed` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, QueryFailedError.prototype) } } -export class NoDefaultKeyError extends WebrpcError { - constructor( - name: string = 'NoDefaultKey', - code: number = 1300, - message: string = 'No default access key found', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, NoDefaultKeyError.prototype) +export class QuotaExceededError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'QuotaExceeded' + this.code = typeof error.code === 'number' ? error.code : 1200 + this.message = error.message || `Quota exceeded` + this.status = typeof error.status === 'number' ? error.status : 429 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, QuotaExceededError.prototype) } } -export class MaxAccessKeysError extends WebrpcError { - constructor( - name: string = 'MaxAccessKeys', - code: number = 1301, - message: string = 'Access keys limit reached', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, MaxAccessKeysError.prototype) +export class RateLimitError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'RateLimit' + this.code = typeof error.code === 'number' ? error.code : 1201 + this.message = error.message || `Rate limit exceeded` + this.status = typeof error.status === 'number' ? error.status : 429 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, RateLimitError.prototype) } } -export class AtLeastOneKeyError extends WebrpcError { - constructor( - name: string = 'AtLeastOneKey', - code: number = 1302, - message: string = 'You need at least one Access Key', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, AtLeastOneKeyError.prototype) +export class RateLimitedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'RateLimited' + this.code = typeof error.code === 'number' ? error.code : 1007 + this.message = error.message || `Rate-limited. Please slow down.` + this.status = typeof error.status === 'number' ? error.status : 429 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, RateLimitedError.prototype) } } -export class TimeoutError extends WebrpcError { - constructor( - name: string = 'Timeout', - code: number = 1900, - message: string = 'Request timed out', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, TimeoutError.prototype) +export class RequestConflictError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'RequestConflict' + this.code = typeof error.code === 'number' ? error.code : 1004 + this.message = error.message || `Conflict with target resource` + this.status = typeof error.status === 'number' ? error.status : 409 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, RequestConflictError.prototype) } } -export class InvalidArgumentError extends WebrpcError { - constructor( - name: string = 'InvalidArgument', - code: number = 2001, - message: string = 'Invalid argument', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, InvalidArgumentError.prototype) +export class ResourceExhaustedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'ResourceExhausted' + this.code = typeof error.code === 'number' ? error.code : 2004 + this.message = error.message || `Resource exhausted` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, ResourceExhaustedError.prototype) } } -export class UnavailableError extends WebrpcError { - constructor( - name: string = 'Unavailable', - code: number = 2002, - message: string = 'Unavailable resource', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, UnavailableError.prototype) +export class SessionExpiredError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'SessionExpired' + this.code = typeof error.code === 'number' ? error.code : 1002 + this.message = error.message || `Session expired` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, SessionExpiredError.prototype) } } -export class QueryFailedError extends WebrpcError { - constructor( - name: string = 'QueryFailed', - code: number = 2003, - message: string = 'Query failed', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, QueryFailedError.prototype) +export class TimeoutError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Timeout' + this.code = typeof error.code === 'number' ? error.code : 1900 + this.message = error.message || `Request timed out` + this.status = typeof error.status === 'number' ? error.status : 408 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, TimeoutError.prototype) } } -export class ResourceExhaustedError extends WebrpcError { - constructor( - name: string = 'ResourceExhausted', - code: number = 2004, - message: string = 'Resource exhausted', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, ResourceExhaustedError.prototype) +export class UnauthorizedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Unauthorized' + this.code = typeof error.code === 'number' ? error.code : 1000 + this.message = error.message || `Unauthorized access` + this.status = typeof error.status === 'number' ? error.status : 401 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, UnauthorizedError.prototype) } } -export class NotFoundError extends WebrpcError { - constructor( - name: string = 'NotFound', - code: number = 3000, - message: string = 'Resource not found', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, NotFoundError.prototype) +export class UnauthorizedUserError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'UnauthorizedUser' + this.code = typeof error.code === 'number' ? error.code : 1105 + this.message = error.message || `Unauthorized user` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, UnauthorizedUserError.prototype) } } -export class MetadataCallFailedError extends WebrpcError { - constructor( - name: string = 'MetadataCallFailed', - code: number = 3003, - message: string = 'Metadata service call failed', - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, MetadataCallFailedError.prototype) +export class UnavailableError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Unavailable' + this.code = typeof error.code === 'number' ? error.code : 2002 + this.message = error.message || `Unavailable resource` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, UnavailableError.prototype) } } @@ -1449,38 +1670,78 @@ export enum errors { WebrpcBadResponse = 'WebrpcBadResponse', WebrpcServerPanic = 'WebrpcServerPanic', WebrpcInternalError = 'WebrpcInternalError', - WebrpcClientDisconnected = 'WebrpcClientDisconnected', + WebrpcClientAborted = 'WebrpcClientAborted', WebrpcStreamLost = 'WebrpcStreamLost', WebrpcStreamFinished = 'WebrpcStreamFinished', - Unauthorized = 'Unauthorized', - PermissionDenied = 'PermissionDenied', - SessionExpired = 'SessionExpired', - MethodNotFound = 'MethodNotFound', - RequestConflict = 'RequestConflict', Aborted = 'Aborted', - Geoblocked = 'Geoblocked', - RateLimited = 'RateLimited', - ProjectNotFound = 'ProjectNotFound', - AccessKeyNotFound = 'AccessKeyNotFound', AccessKeyMismatch = 'AccessKeyMismatch', + AccessKeyNotFound = 'AccessKeyNotFound', + AtLeastOneKey = 'AtLeastOneKey', + Geoblocked = 'Geoblocked', + InvalidArgument = 'InvalidArgument', InvalidOrigin = 'InvalidOrigin', InvalidService = 'InvalidService', - UnauthorizedUser = 'UnauthorizedUser', + MaxAccessKeys = 'MaxAccessKeys', + MetadataCallFailed = 'MetadataCallFailed', + MethodNotFound = 'MethodNotFound', + NoDefaultKey = 'NoDefaultKey', + NotFound = 'NotFound', + PermissionDenied = 'PermissionDenied', + ProjectNotFound = 'ProjectNotFound', + QueryFailed = 'QueryFailed', QuotaExceeded = 'QuotaExceeded', RateLimit = 'RateLimit', - NoDefaultKey = 'NoDefaultKey', - MaxAccessKeys = 'MaxAccessKeys', - AtLeastOneKey = 'AtLeastOneKey', + RateLimited = 'RateLimited', + RequestConflict = 'RequestConflict', + ResourceExhausted = 'ResourceExhausted', + SessionExpired = 'SessionExpired', Timeout = 'Timeout', - InvalidArgument = 'InvalidArgument', + Unauthorized = 'Unauthorized', + UnauthorizedUser = 'UnauthorizedUser', Unavailable = 'Unavailable', - QueryFailed = 'QueryFailed', - ResourceExhausted = 'ResourceExhausted', - NotFound = 'NotFound', - MetadataCallFailed = 'MetadataCallFailed', } -const webrpcErrorByCode: { [code: number]: any } = { +export enum WebrpcErrorCodes { + WebrpcEndpoint = 0, + WebrpcRequestFailed = -1, + WebrpcBadRoute = -2, + WebrpcBadMethod = -3, + WebrpcBadRequest = -4, + WebrpcBadResponse = -5, + WebrpcServerPanic = -6, + WebrpcInternalError = -7, + WebrpcClientAborted = -8, + WebrpcStreamLost = -9, + WebrpcStreamFinished = -10, + Aborted = 1005, + AccessKeyMismatch = 1102, + AccessKeyNotFound = 1101, + AtLeastOneKey = 1302, + Geoblocked = 1006, + InvalidArgument = 2001, + InvalidOrigin = 1103, + InvalidService = 1104, + MaxAccessKeys = 1301, + MetadataCallFailed = 3003, + MethodNotFound = 1003, + NoDefaultKey = 1300, + NotFound = 3000, + PermissionDenied = 1001, + ProjectNotFound = 1100, + QueryFailed = 2003, + QuotaExceeded = 1200, + RateLimit = 1201, + RateLimited = 1007, + RequestConflict = 1004, + ResourceExhausted = 2004, + SessionExpired = 1002, + Timeout = 1900, + Unauthorized = 1000, + UnauthorizedUser = 1105, + Unavailable = 2002, +} + +export const webrpcErrorByCode: { [code: number]: any } = { [0]: WebrpcEndpointError, [-1]: WebrpcRequestFailedError, [-2]: WebrpcBadRouteError, @@ -1489,35 +1750,89 @@ const webrpcErrorByCode: { [code: number]: any } = { [-5]: WebrpcBadResponseError, [-6]: WebrpcServerPanicError, [-7]: WebrpcInternalErrorError, - [-8]: WebrpcClientDisconnectedError, + [-8]: WebrpcClientAbortedError, [-9]: WebrpcStreamLostError, [-10]: WebrpcStreamFinishedError, - [1000]: UnauthorizedError, - [1001]: PermissionDeniedError, - [1002]: SessionExpiredError, - [1003]: MethodNotFoundError, - [1004]: RequestConflictError, [1005]: AbortedError, - [1006]: GeoblockedError, - [1007]: RateLimitedError, - [1100]: ProjectNotFoundError, - [1101]: AccessKeyNotFoundError, [1102]: AccessKeyMismatchError, + [1101]: AccessKeyNotFoundError, + [1302]: AtLeastOneKeyError, + [1006]: GeoblockedError, + [2001]: InvalidArgumentError, [1103]: InvalidOriginError, [1104]: InvalidServiceError, - [1105]: UnauthorizedUserError, + [1301]: MaxAccessKeysError, + [3003]: MetadataCallFailedError, + [1003]: MethodNotFoundError, + [1300]: NoDefaultKeyError, + [3000]: NotFoundError, + [1001]: PermissionDeniedError, + [1100]: ProjectNotFoundError, + [2003]: QueryFailedError, [1200]: QuotaExceededError, [1201]: RateLimitError, - [1300]: NoDefaultKeyError, - [1301]: MaxAccessKeysError, - [1302]: AtLeastOneKeyError, + [1007]: RateLimitedError, + [1004]: RequestConflictError, + [2004]: ResourceExhaustedError, + [1002]: SessionExpiredError, [1900]: TimeoutError, - [2001]: InvalidArgumentError, + [1000]: UnauthorizedError, + [1105]: UnauthorizedUserError, [2002]: UnavailableError, - [2003]: QueryFailedError, - [2004]: ResourceExhaustedError, - [3000]: NotFoundError, - [3003]: MetadataCallFailedError, } -export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise +// +// Webrpc +// + +export const WebrpcHeader = 'Webrpc' + +export const WebrpcHeaderValue = 'webrpc@v0.31.2;gen-typescript@v0.23.1;sequence-indexer@v0.4.0' + +type WebrpcGenVersions = { + WebrpcGenVersion: string + codeGenName: string + codeGenVersion: string + schemaName: string + schemaVersion: string +} + +export function VersionFromHeader(headers: Headers): WebrpcGenVersions { + const headerValue = headers.get(WebrpcHeader) + if (!headerValue) { + return { + WebrpcGenVersion: '', + codeGenName: '', + codeGenVersion: '', + schemaName: '', + schemaVersion: '', + } + } + + return parseWebrpcGenVersions(headerValue) +} + +function parseWebrpcGenVersions(header: string): WebrpcGenVersions { + const versions = header.split(';') + if (versions.length < 3) { + return { + WebrpcGenVersion: '', + codeGenName: '', + codeGenVersion: '', + schemaName: '', + schemaVersion: '', + } + } + + const [_, WebrpcGenVersion] = versions[0]!.split('@') + const [codeGenName, codeGenVersion] = versions[1]!.split('@') + const [schemaName, schemaVersion] = versions[2]!.split('@') + + return { + WebrpcGenVersion: WebrpcGenVersion ?? '', + codeGenName: codeGenName ?? '', + codeGenVersion: codeGenVersion ?? '', + schemaName: schemaName ?? '', + schemaVersion: schemaVersion ?? '', + } +} diff --git a/packages/services/indexer/tsconfig.json b/packages/services/indexer/tsconfig.json index fed9c77b4..8623be1c0 100644 --- a/packages/services/indexer/tsconfig.json +++ b/packages/services/indexer/tsconfig.json @@ -3,7 +3,9 @@ "compilerOptions": { "rootDir": "src", "outDir": "dist", - "types": ["node"] + "types": ["node"], + // TODO: enable when webrpc codegen handles noUncheckedIndexedAccess + "noUncheckedIndexedAccess": false }, "include": ["src"], "exclude": ["node_modules", "dist"] diff --git a/packages/services/marketplace/CHANGELOG.md b/packages/services/marketplace/CHANGELOG.md index 6295f3498..e7c04d933 100644 --- a/packages/services/marketplace/CHANGELOG.md +++ b/packages/services/marketplace/CHANGELOG.md @@ -1,5 +1,216 @@ # @0xsequence/marketplace +## 3.0.12 + +### Patch Changes + +- WDK fix + +## 3.0.11 + +### Patch Changes + +- Fix for relayer sponsored fees + +## 3.0.10 + +### Patch Changes + +- Minor network updates, relayer interface + +## 3.0.9 + +### Patch Changes + +- Fee options fixes + +## 3.0.8 + +### Patch Changes + +- Bug fix for relayer fee options handling + +## 3.0.7 + +### Patch Changes + +- Minor bug fixes + +## 3.0.6 + +### Patch Changes + +- userdata upgrade, arweave support + +## 3.0.5 + +### Patch Changes + +- Account federation support + +## 3.0.4 + +### Patch Changes + +- id-token login support + +## 3.0.3 + +### Patch Changes + +- 3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer + +## 3.0.1 + +### Patch Changes + +- Network and session fixes + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade + +## 3.0.0-beta.19 + +### Patch Changes + +- Final RC before 3.0.0 + +## 3.0.0-beta.18 + +### Patch Changes + +- multicall3 optimization + +## 3.0.0-beta.17 + +### Patch Changes + +- New chains, relayer rpc fix + +## 3.0.0-beta.16 + +### Patch Changes + +- ethauth support + +## 3.0.0-beta.15 + +### Patch Changes + +- New chains, minor fixes + +## 3.0.0-beta.14 + +### Patch Changes + +- Relayer fee options fix + +## 3.0.0-beta.13 + +### Patch Changes + +- Userdata service updates + +## 3.0.0-beta.12 + +### Patch Changes + +- Beta release with dapp connector fixes + +## 3.0.0-beta.11 + +### Patch Changes + +- 3.0.0 beta + +## 3.0.0-beta.10 + +### Patch Changes + +- dapp-client updates + +## 3.0.0-beta.9 + +### Patch Changes + +- dapp client updates for EOA login + +## 3.0.0-beta.8 + +### Patch Changes + +- Apple auth fixes + +## 3.0.0-beta.7 + +### Patch Changes + +- Apple auth fix + +## 3.0.0-beta.6 + +### Patch Changes + +- Fix signer 404 error, minor fixes + +## 3.0.0-beta.5 + +### Patch Changes + +- Beta release for v3 + +## 3.0.0-beta.4 + +### Patch Changes + +- RC5 upgrade + +## 3.0.0-beta.3 + +### Patch Changes + +- 3.0.0-beta.3 with fixes + +## 3.0.0-beta.2 + +### Patch Changes + +- 3.0.0-beta.2 with identity instrument updates + +## 3.0.0-beta.1 + +### Patch Changes + +- 3.0.0-beta.1 + ## 2.3.8 ### Patch Changes diff --git a/packages/services/marketplace/eslint.config.js b/packages/services/marketplace/eslint.config.js new file mode 100644 index 000000000..cecf89b03 --- /dev/null +++ b/packages/services/marketplace/eslint.config.js @@ -0,0 +1,4 @@ +import { config as baseConfig } from "@repo/eslint-config/base" + +/** @type {import("eslint").Linter.Config} */ +export default baseConfig diff --git a/packages/services/marketplace/package.json b/packages/services/marketplace/package.json index 106c486da..117539eca 100644 --- a/packages/services/marketplace/package.json +++ b/packages/services/marketplace/package.json @@ -1,28 +1,31 @@ { "name": "@0xsequence/marketplace", - "version": "3.0.0", + "version": "3.0.12", "description": "marketplace sub-package for Sequence", - "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/marketplace", - "author": "Sequence Platforms Inc.", + "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/marketplace", + "author": "Sequence Platforms ULC", "license": "Apache-2.0", "publishConfig": { "access": "public" }, + "type": "module", "scripts": { "build": "tsc", "dev": "tsc --watch", "test": "echo", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "lint": "eslint . --max-warnings 0" }, "exports": { ".": { - "types": "./src/index.ts", + "types": "./dist/index.d.ts", "default": "./dist/index.js" } }, "devDependencies": { + "@repo/eslint-config": "workspace:^", "@repo/typescript-config": "workspace:^", - "@types/node": "^22.15.29", - "typescript": "^5.8.3" + "@types/node": "^25.3.0", + "typescript": "^6.0.3" } } diff --git a/packages/services/marketplace/src/index.ts b/packages/services/marketplace/src/index.ts index f7ec2b0d3..3b67f6f2d 100644 --- a/packages/services/marketplace/src/index.ts +++ b/packages/services/marketplace/src/index.ts @@ -1,6 +1,6 @@ -export * from './marketplace.gen' +export * from './marketplace.gen.js' -import { Marketplace as MarketplaceRpc } from './marketplace.gen' +import { Marketplace as MarketplaceRpc } from './marketplace.gen.js' export class MarketplaceIndexer extends MarketplaceRpc { constructor( @@ -15,7 +15,7 @@ export class MarketplaceIndexer extends MarketplaceRpc { _fetch = (input: RequestInfo, init?: RequestInit): Promise => { // automatically include jwt and access key auth header to requests // if its been set on the api client - const headers: { [key: string]: any } = {} + const headers: Record = {} const jwtAuth = this.jwtAuth const projectAccessKey = this.projectAccessKey diff --git a/packages/services/marketplace/src/marketplace.gen.ts b/packages/services/marketplace/src/marketplace.gen.ts index f251bce5d..6a316623d 100644 --- a/packages/services/marketplace/src/marketplace.gen.ts +++ b/packages/services/marketplace/src/marketplace.gen.ts @@ -1,14 +1,14 @@ /* eslint-disable */ -// marketplace-api 7ab3354385f317680dd861e82a18aa351d8579d5 +// marketplace-api 652676d9951ceb12f6846907c7c4b5160c73c57a // -- -// Code generated by webrpc-gen@v0.25.1 with typescript generator. DO NOT EDIT. +// Code generated by webrpc-gen@v0.30.2 with github.com/webrpc/gen-typescript@v0.19.0 generator. DO NOT EDIT. // -// webrpc-gen -schema=marketplace.ridl -target=typescript -client -out=./clients/marketplace.gen.ts +// webrpc-gen -schema=./schema/schema.ridl -target=github.com/webrpc/gen-typescript@v0.19.0 -client -out=./clients/marketplace.gen.ts export const WebrpcHeader = 'Webrpc' export const WebrpcHeaderValue = - 'webrpc@v0.25.1;gen-typescript@v0.17.0;marketplace-api@v0.0.0-7ab3354385f317680dd861e82a18aa351d8579d5' + 'webrpc@v0.30.2;gen-typescript@v0.19.0;marketplace-api@v0.0.0-652676d9951ceb12f6846907c7c4b5160c73c57a' // WebRPC description and code-gen version export const WebRPCVersion = 'v1' @@ -17,7 +17,7 @@ export const WebRPCVersion = 'v1' export const WebRPCSchemaVersion = '' // Schema hash generated from your RIDL schema -export const WebRPCSchemaHash = '7ab3354385f317680dd861e82a18aa351d8579d5' +export const WebRPCSchemaHash = '652676d9951ceb12f6846907c7c4b5160c73c57a' type WebrpcGenVersions = { webrpcGenVersion: string @@ -71,41 +71,9 @@ function parseWebrpcGenVersions(header: string): WebrpcGenVersions { // Types // -export interface TokenMetadata { - tokenId: string - name: string - description?: string - image?: string - video?: string - audio?: string - properties?: { [key: string]: any } - attributes: Array<{ [key: string]: any }> - image_data?: string - external_url?: string - background_color?: string - animation_url?: string - decimals?: number - updatedAt?: string - assets?: Array -} - -export interface Asset { - id: number - collectionId: number - tokenId: string - url?: string - metadataField: string - name?: string - filesize?: number - mimeType?: string - width?: number - height?: number - updatedAt?: string -} - export enum SortOrder { - DESC = 'DESC', ASC = 'ASC', + DESC = 'DESC', } export enum PropertyType { @@ -127,6 +95,7 @@ export enum MarketplaceKind { alienswap = 'alienswap', payment_processor = 'payment_processor', mintify = 'mintify', + magic_eden = 'magic_eden', } export enum OrderbookKind { @@ -138,6 +107,7 @@ export enum OrderbookKind { looks_rare = 'looks_rare', reservoir = 'reservoir', x2y2 = 'x2y2', + magic_eden = 'magic_eden', } export enum SourceKind { @@ -145,6 +115,8 @@ export enum SourceKind { external = 'external', sequence_marketplace_v1 = 'sequence_marketplace_v1', sequence_marketplace_v2 = 'sequence_marketplace_v2', + opensea = 'opensea', + magic_eden = 'magic_eden', } export enum OrderSide { @@ -153,6 +125,12 @@ export enum OrderSide { offer = 'offer', } +export enum OfferType { + unknown = 'unknown', + item = 'item', + collection = 'collection', +} + export enum OrderStatus { unknown = 'unknown', active = 'active', @@ -180,12 +158,6 @@ export enum CollectionPriority { export enum CollectionStatus { unknown = 'unknown', created = 'created', - syncing_contract_metadata = 'syncing_contract_metadata', - synced_contract_metadata = 'synced_contract_metadata', - syncing_metadata = 'syncing_metadata', - synced_metadata = 'synced_metadata', - syncing_tokens = 'syncing_tokens', - synced_tokens = 'synced_tokens', syncing_orders = 'syncing_orders', active = 'active', failed = 'failed', @@ -199,12 +171,30 @@ export enum ProjectStatus { inactive = 'inactive', } +export enum ItemsContractStatus { + unknown = 'unknown', + created = 'created', + syncing_contract_metadata = 'syncing_contract_metadata', + synced_contract_metadata = 'synced_contract_metadata', + syncing_tokens = 'syncing_tokens', + synced_tokens = 'synced_tokens', + active = 'active', + inactive = 'inactive', + incompatible_type = 'incompatible_type', +} + export enum CollectibleStatus { unknown = 'unknown', active = 'active', inactive = 'inactive', } +export enum CollectibleSource { + unknown = 'unknown', + indexer = 'indexer', + manual = 'manual', +} + export enum CurrencyStatus { unknown = 'unknown', created = 'created', @@ -238,24 +228,27 @@ export enum TransactionCrypto { export enum TransactionNFTCheckoutProvider { unknown = 'unknown', - sardine = 'sardine', transak = 'transak', + sardine = 'sardine', } export enum TransactionOnRampProvider { unknown = 'unknown', - sardine = 'sardine', transak = 'transak', + sardine = 'sardine', } export enum TransactionSwapProvider { unknown = 'unknown', - zerox = 'zerox', + lifi = 'lifi', } export enum ExecuteType { unknown = 'unknown', order = 'order', + createListing = 'createListing', + createItemOffer = 'createItemOffer', + createTraitOffer = 'createTraitOffer', } export enum ActivityAction { @@ -269,6 +262,33 @@ export enum ActivityAction { transfer = 'transfer', } +export enum PrimarySaleContractStatus { + unknown = 'unknown', + created = 'created', + syncing_items = 'syncing_items', + active = 'active', + inactive = 'inactive', + incompatible_type = 'incompatible_type', + failed = 'failed', +} + +export enum PrimarySaleVersion { + v0 = 'v0', + v1 = 'v1', +} + +export enum PrimarySaleItemDetailType { + unknown = 'unknown', + global = 'global', + individual = 'individual', +} + +export enum MetadataStatus { + NOT_AVAILABLE = 'NOT_AVAILABLE', + REFRESHING = 'REFRESHING', + AVAILABLE = 'AVAILABLE', +} + export interface Page { page: number pageSize: number @@ -305,6 +325,26 @@ export interface CollectiblesFilter { ordersNotCreatedBy?: Array inCurrencyAddresses?: Array notInCurrencyAddresses?: Array + prices?: Array +} + +export interface OrdersFilter { + searchText?: string + properties?: Array + marketplaces?: Array + inAccounts?: Array + notInAccounts?: Array + ordersCreatedBy?: Array + ordersNotCreatedBy?: Array + inCurrencyAddresses?: Array + notInCurrencyAddresses?: Array + prices?: Array +} + +export interface PriceFilter { + contractAddress: string + min?: string + max?: string } export interface Order { @@ -314,6 +354,7 @@ export interface Order { status: OrderStatus chainId: number originName: string + slug: string collectionContractAddress: string tokenId?: string createdBy: string @@ -386,6 +427,8 @@ export interface CollectionConfig { export interface CollectionLastSynced { allOrders: string newOrders: string + names: Array + cursors: { [key: string]: string } } export interface Project { @@ -398,12 +441,22 @@ export interface Project { deletedAt?: string } -export interface Collectible { +export interface ItemsContract { + status: ItemsContractStatus chainId: number contractAddress: string + contractType: ContractType + lastSynced: string + createdAt: string + updatedAt: string + deletedAt?: string +} + +export interface Collectible { status: CollectibleStatus tokenId: string decimals: number + source: CollectibleSource createdAt: string updatedAt: string deletedAt?: string @@ -420,6 +473,8 @@ export interface Currency { exchangeRate: number defaultChainCurrency: boolean nativeCurrency: boolean + openseaListing: boolean + openseaOffer: boolean createdAt: string updatedAt: string deletedAt?: string @@ -499,6 +554,16 @@ export interface CheckoutOptions { onRamp: Array } +export interface ExecuteInput { + chainId: string + signature: string + method: string + endpoint: string + executeType: ExecuteType + body: any + slug?: string +} + export interface Activity { chainId: number contractAddress: string @@ -520,6 +585,91 @@ export interface Activity { deletedAt?: string } +export interface PrimarySaleContract { + chainId: number + contractAddress: string + collectionAddress: string + contractType: ContractType + version: PrimarySaleVersion + currencyAddress: string + priceDecimals: number + status: PrimarySaleContractStatus + lastSynced: string + createdAt: string + updatedAt: string + deletedAt?: string +} + +export interface PrimarySaleItem { + itemAddress: string + contractType: ContractType + tokenId: string + itemType: PrimarySaleItemDetailType + startDate: string + endDate: string + currencyAddress: string + priceDecimals: number + priceAmount: string + priceAmountFormatted: string + priceUsd: number + priceUsdFormatted: string + supply: string + supplyCap: string + unlimitedSupply: boolean + createdAt: string + updatedAt: string + deletedAt?: string +} + +export interface CollectiblePrimarySaleItem { + metadata: TokenMetadata + primarySaleItem: PrimarySaleItem +} + +export interface PrimarySaleItemsFilter { + includeEmpty: boolean + searchText?: string + properties?: Array + detailTypes?: Array + startDateAfter?: string + startDateBefore?: string + endDateAfter?: string + endDateBefore?: string +} + +export interface TokenMetadata { + tokenId: string + name: string + description?: string + image?: string + video?: string + audio?: string + properties?: { [key: string]: any } + attributes: Array<{ [key: string]: any }> + image_data?: string + external_url?: string + background_color?: string + animation_url?: string + decimals?: number + updatedAt?: string + assets?: Array + status: MetadataStatus +} + +export interface Asset { + id: number + collectionId: number + tokenId: string + url?: string + metadataField: string + name?: string + filesize?: number + mimeType?: string + width?: number + height?: number + updatedAt?: string +} + export interface Admin { createCollection(args: CreateCollectionArgs, headers?: object, signal?: AbortSignal): Promise getCollection(args: GetCollectionArgs, headers?: object, signal?: AbortSignal): Promise @@ -530,14 +680,29 @@ export interface Admin { * determine what should happen here */ syncCollection(args: SyncCollectionArgs, headers?: object, signal?: AbortSignal): Promise + createPrimarySaleContract( + args: CreatePrimarySaleContractArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + deletePrimarySaleContract( + args: DeletePrimarySaleContractArgs, + headers?: object, + signal?: AbortSignal, + ): Promise createCurrency(args: CreateCurrencyArgs, headers?: object, signal?: AbortSignal): Promise createCurrencies(args: CreateCurrenciesArgs, headers?: object, signal?: AbortSignal): Promise updateCurrency(args: UpdateCurrencyArgs, headers?: object, signal?: AbortSignal): Promise - listCurrencies(headers?: object, signal?: AbortSignal): Promise + listCurrencies(args: ListCurrenciesArgs, headers?: object, signal?: AbortSignal): Promise deleteCurrency(args: DeleteCurrencyArgs, headers?: object, signal?: AbortSignal): Promise + /** + * This for manual adding of non minted ERC1155 tokens, it's used for purposes of Shop. + */ + addCollectibles(args: AddCollectiblesArgs, headers?: object, signal?: AbortSignal): Promise } export interface CreateCollectionArgs { + chainId: string projectId: number contractAddress: string } @@ -546,6 +711,7 @@ export interface CreateCollectionReturn { collection: Collection } export interface GetCollectionArgs { + chainId: string projectId: number contractAddress: string } @@ -554,6 +720,7 @@ export interface GetCollectionReturn { collection: Collection } export interface UpdateCollectionArgs { + chainId: string collection: Collection } @@ -561,6 +728,7 @@ export interface UpdateCollectionReturn { collection: Collection } export interface ListCollectionsArgs { + chainId: string projectId: number page?: Page } @@ -570,6 +738,7 @@ export interface ListCollectionsReturn { page?: Page } export interface DeleteCollectionArgs { + chainId: string projectId: number contractAddress: string } @@ -578,14 +747,30 @@ export interface DeleteCollectionReturn { collection: Collection } export interface SyncCollectionArgs { - projectId: number + chainId: string contractAddress: string } -export interface SyncCollectionReturn { - collection: Collection +export interface SyncCollectionReturn {} +export interface CreatePrimarySaleContractArgs { + chainId: string + projectId: number + primarySaleContractAddress: string + itemsContractAddress: string +} + +export interface CreatePrimarySaleContractReturn { + primarySaleContract: PrimarySaleContract } +export interface DeletePrimarySaleContractArgs { + chainId: string + projectId: number + primarySaleContractAddress: string +} + +export interface DeletePrimarySaleContractReturn {} export interface CreateCurrencyArgs { + chainId: string currency: Currency } @@ -593,6 +778,7 @@ export interface CreateCurrencyReturn { currency: Currency } export interface CreateCurrenciesArgs { + chainId: string currencies: Array } @@ -600,33 +786,53 @@ export interface CreateCurrenciesReturn { currency: { [key: string]: Currency } } export interface UpdateCurrencyArgs { + chainId: string currency: Currency } export interface UpdateCurrencyReturn { currency: Currency } -export interface ListCurrenciesArgs {} +export interface ListCurrenciesArgs { + chainId: string +} export interface ListCurrenciesReturn { currencies: Array } export interface DeleteCurrencyArgs { - chainId: number + chainId: string contractAddress: string } export interface DeleteCurrencyReturn { currency: Currency } +export interface AddCollectiblesArgs { + chainId: string + itemsContractAddress: string + tokenIds: Array +} + +export interface AddCollectiblesReturn {} export interface Marketplace { - listCurrencies(headers?: object, signal?: AbortSignal): Promise + listCurrencies(args: ListCurrenciesArgs, headers?: object, signal?: AbortSignal): Promise getCollectionDetail( args: GetCollectionDetailArgs, headers?: object, signal?: AbortSignal, ): Promise + getCollectionActiveListingsCurrencies( + args: GetCollectionActiveListingsCurrenciesArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + getCollectionActiveOffersCurrencies( + args: GetCollectionActiveOffersCurrenciesArgs, + headers?: object, + signal?: AbortSignal, + ): Promise getCollectible(args: GetCollectibleArgs, headers?: object, signal?: AbortSignal): Promise getLowestPriceOfferForCollectible( args: GetLowestPriceOfferForCollectibleArgs, @@ -658,6 +864,23 @@ export interface Marketplace { headers?: object, signal?: AbortSignal, ): Promise + listOrdersWithCollectibles( + args: ListOrdersWithCollectiblesArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + getCountOfAllOrders( + args: GetCountOfAllOrdersArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + getCountOfFilteredOrders( + args: GetCountOfFilteredOrdersArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + listListings(args: ListListingsArgs, headers?: object, signal?: AbortSignal): Promise + listOffers(args: ListOffersArgs, headers?: object, signal?: AbortSignal): Promise getCountOfListingsForCollectible( args: GetCountOfListingsForCollectibleArgs, headers?: object, @@ -745,7 +968,7 @@ export interface Marketplace { signal?: AbortSignal, ): Promise /** - * only used in a case of external transactions ( when we create off-chain transactions ) for instance opensea market + * only used in a case of external transactions ( when we create off-chain transactions ) for instance opensea market, use only ExecuteInput params and leave other root inputs empty, they are depracated and kept only for backward compatibility */ execute(args: ExecuteArgs, headers?: object, signal?: AbortSignal): Promise /** @@ -796,21 +1019,61 @@ export interface Marketplace { headers?: object, signal?: AbortSignal, ): Promise + supportedMarketplaces( + args: SupportedMarketplacesArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + getPrimarySaleItem( + args: GetPrimarySaleItemArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + listPrimarySaleItems( + args: ListPrimarySaleItemsArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + getCountOfPrimarySaleItems( + args: GetCountOfPrimarySaleItemsArgs, + headers?: object, + signal?: AbortSignal, + ): Promise } -export interface ListCurrenciesArgs {} +export interface ListCurrenciesArgs { + chainId: string +} export interface ListCurrenciesReturn { currencies: Array } export interface GetCollectionDetailArgs { + chainId: string contractAddress: string } export interface GetCollectionDetailReturn { collection: Collection } +export interface GetCollectionActiveListingsCurrenciesArgs { + chainId: string + contractAddress: string +} + +export interface GetCollectionActiveListingsCurrenciesReturn { + currencies: Array +} +export interface GetCollectionActiveOffersCurrenciesArgs { + chainId: string + contractAddress: string +} + +export interface GetCollectionActiveOffersCurrenciesReturn { + currencies: Array +} export interface GetCollectibleArgs { + chainId: string contractAddress: string tokenId: string } @@ -819,6 +1082,7 @@ export interface GetCollectibleReturn { metadata: TokenMetadata } export interface GetLowestPriceOfferForCollectibleArgs { + chainId: string contractAddress: string tokenId: string filter?: OrderFilter @@ -828,6 +1092,7 @@ export interface GetLowestPriceOfferForCollectibleReturn { order: Order } export interface GetHighestPriceOfferForCollectibleArgs { + chainId: string contractAddress: string tokenId: string filter?: OrderFilter @@ -837,6 +1102,7 @@ export interface GetHighestPriceOfferForCollectibleReturn { order: Order } export interface GetLowestPriceListingForCollectibleArgs { + chainId: string contractAddress: string tokenId: string filter?: OrderFilter @@ -846,6 +1112,7 @@ export interface GetLowestPriceListingForCollectibleReturn { order: Order } export interface GetHighestPriceListingForCollectibleArgs { + chainId: string contractAddress: string tokenId: string filter?: OrderFilter @@ -855,6 +1122,7 @@ export interface GetHighestPriceListingForCollectibleReturn { order: Order } export interface ListListingsForCollectibleArgs { + chainId: string contractAddress: string tokenId: string filter?: OrderFilter @@ -866,6 +1134,7 @@ export interface ListListingsForCollectibleReturn { page?: Page } export interface ListOffersForCollectibleArgs { + chainId: string contractAddress: string tokenId: string filter?: OrderFilter @@ -876,7 +1145,61 @@ export interface ListOffersForCollectibleReturn { offers: Array page?: Page } +export interface ListOrdersWithCollectiblesArgs { + chainId: string + side: OrderSide + contractAddress: string + filter?: OrdersFilter + page?: Page +} + +export interface ListOrdersWithCollectiblesReturn { + collectibles: Array + page?: Page +} +export interface GetCountOfAllOrdersArgs { + chainId: string + side: OrderSide + contractAddress: string +} + +export interface GetCountOfAllOrdersReturn { + count: number +} +export interface GetCountOfFilteredOrdersArgs { + chainId: string + side: OrderSide + contractAddress: string + filter?: OrdersFilter +} + +export interface GetCountOfFilteredOrdersReturn { + count: number +} +export interface ListListingsArgs { + chainId: string + contractAddress: string + filter?: OrderFilter + page?: Page +} + +export interface ListListingsReturn { + listings: Array + page?: Page +} +export interface ListOffersArgs { + chainId: string + contractAddress: string + filter?: OrderFilter + page?: Page +} + +export interface ListOffersReturn { + offers: Array + page?: Page +} export interface GetCountOfListingsForCollectibleArgs { + chainId: string contractAddress: string tokenId: string filter?: OrderFilter @@ -886,6 +1209,7 @@ export interface GetCountOfListingsForCollectibleReturn { count: number } export interface GetCountOfOffersForCollectibleArgs { + chainId: string contractAddress: string tokenId: string filter?: OrderFilter @@ -895,6 +1219,7 @@ export interface GetCountOfOffersForCollectibleReturn { count: number } export interface GetCollectibleLowestOfferArgs { + chainId: string contractAddress: string tokenId: string filter?: OrderFilter @@ -904,6 +1229,7 @@ export interface GetCollectibleLowestOfferReturn { order?: Order } export interface GetCollectibleHighestOfferArgs { + chainId: string contractAddress: string tokenId: string filter?: OrderFilter @@ -913,6 +1239,7 @@ export interface GetCollectibleHighestOfferReturn { order?: Order } export interface GetCollectibleLowestListingArgs { + chainId: string contractAddress: string tokenId: string filter?: OrderFilter @@ -922,6 +1249,7 @@ export interface GetCollectibleLowestListingReturn { order?: Order } export interface GetCollectibleHighestListingArgs { + chainId: string contractAddress: string tokenId: string filter?: OrderFilter @@ -931,6 +1259,7 @@ export interface GetCollectibleHighestListingReturn { order?: Order } export interface ListCollectibleListingsArgs { + chainId: string contractAddress: string tokenId: string filter?: OrderFilter @@ -942,6 +1271,7 @@ export interface ListCollectibleListingsReturn { page?: Page } export interface ListCollectibleOffersArgs { + chainId: string contractAddress: string tokenId: string filter?: OrderFilter @@ -953,6 +1283,7 @@ export interface ListCollectibleOffersReturn { page?: Page } export interface GenerateBuyTransactionArgs { + chainId: string collectionAddress: string buyer: string marketplace: MarketplaceKind @@ -965,6 +1296,7 @@ export interface GenerateBuyTransactionReturn { steps: Array } export interface GenerateSellTransactionArgs { + chainId: string collectionAddress: string seller: string marketplace: MarketplaceKind @@ -977,11 +1309,13 @@ export interface GenerateSellTransactionReturn { steps: Array } export interface GenerateListingTransactionArgs { + chainId: string collectionAddress: string owner: string contractType: ContractType orderbook: OrderbookKind listing: CreateReq + additionalFees: Array walletType?: WalletKind } @@ -989,18 +1323,22 @@ export interface GenerateListingTransactionReturn { steps: Array } export interface GenerateOfferTransactionArgs { + chainId: string collectionAddress: string maker: string contractType: ContractType orderbook: OrderbookKind offer: CreateReq + additionalFees: Array walletType?: WalletKind + offerType: OfferType } export interface GenerateOfferTransactionReturn { steps: Array } export interface GenerateCancelTransactionArgs { + chainId: string collectionAddress: string maker: string marketplace: MarketplaceKind @@ -1011,17 +1349,20 @@ export interface GenerateCancelTransactionReturn { steps: Array } export interface ExecuteArgs { - signature: string - method: string - endpoint: string - executeType: ExecuteType - body: any + params: ExecuteInput + chainId?: string + signature?: string + method?: string + endpoint?: string + executeType?: ExecuteType + body?: any } export interface ExecuteReturn { orderId: string } export interface ListCollectiblesArgs { + chainId: string side: OrderSide contractAddress: string filter?: CollectiblesFilter @@ -1033,6 +1374,7 @@ export interface ListCollectiblesReturn { page?: Page } export interface GetCountOfAllCollectiblesArgs { + chainId: string contractAddress: string } @@ -1040,6 +1382,7 @@ export interface GetCountOfAllCollectiblesReturn { count: number } export interface GetCountOfFilteredCollectiblesArgs { + chainId: string side: OrderSide contractAddress: string filter?: CollectiblesFilter @@ -1049,6 +1392,7 @@ export interface GetCountOfFilteredCollectiblesReturn { count: number } export interface GetFloorOrderArgs { + chainId: string contractAddress: string filter?: CollectiblesFilter } @@ -1057,6 +1401,7 @@ export interface GetFloorOrderReturn { collectible: CollectibleOrder } export interface ListCollectionActivitiesArgs { + chainId: string contractAddress: string page?: Page } @@ -1066,6 +1411,7 @@ export interface ListCollectionActivitiesReturn { page?: Page } export interface ListCollectibleActivitiesArgs { + chainId: string contractAddress: string tokenId: string page?: Page @@ -1076,6 +1422,7 @@ export interface ListCollectibleActivitiesReturn { page?: Page } export interface ListCollectiblesWithLowestListingArgs { + chainId: string contractAddress: string filter?: CollectiblesFilter page?: Page @@ -1086,6 +1433,7 @@ export interface ListCollectiblesWithLowestListingReturn { page?: Page } export interface ListCollectiblesWithHighestOfferArgs { + chainId: string contractAddress: string filter?: CollectiblesFilter page?: Page @@ -1096,16 +1444,19 @@ export interface ListCollectiblesWithHighestOfferReturn { page?: Page } export interface SyncOrderArgs { + chainId: string order: Order } export interface SyncOrderReturn {} export interface SyncOrdersArgs { + chainId: string orders: Array } export interface SyncOrdersReturn {} export interface GetOrdersArgs { + chainId: string input: Array page?: Page } @@ -1115,6 +1466,7 @@ export interface GetOrdersReturn { page?: Page } export interface CheckoutOptionsMarketplaceArgs { + chainId: string wallet: string orders: Array additionalFee: number @@ -1124,6 +1476,7 @@ export interface CheckoutOptionsMarketplaceReturn { options: CheckoutOptions } export interface CheckoutOptionsSalesContractArgs { + chainId: string wallet: string contractAddress: string collectionAddress: string @@ -1133,6 +1486,42 @@ export interface CheckoutOptionsSalesContractArgs { export interface CheckoutOptionsSalesContractReturn { options: CheckoutOptions } +export interface SupportedMarketplacesArgs { + chainId: string +} + +export interface SupportedMarketplacesReturn { + marketplaces: Array +} +export interface GetPrimarySaleItemArgs { + chainId: string + primarySaleContractAddress: string + tokenId: string +} + +export interface GetPrimarySaleItemReturn { + item: CollectiblePrimarySaleItem +} +export interface ListPrimarySaleItemsArgs { + chainId: string + primarySaleContractAddress: string + filter?: PrimarySaleItemsFilter + page?: Page +} + +export interface ListPrimarySaleItemsReturn { + primarySaleItems: Array + page?: Page +} +export interface GetCountOfPrimarySaleItemsArgs { + chainId: string + primarySaleContractAddress: string + filter?: PrimarySaleItemsFilter +} + +export interface GetCountOfPrimarySaleItemsReturn { + count: number +} // // Client @@ -1165,7 +1554,9 @@ export class Admin implements Admin { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1180,7 +1571,9 @@ export class Admin implements Admin { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1199,7 +1592,9 @@ export class Admin implements Admin { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1219,7 +1614,9 @@ export class Admin implements Admin { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1238,7 +1635,9 @@ export class Admin implements Admin { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1249,15 +1648,55 @@ export class Admin implements Admin { signal?: AbortSignal, ): Promise => { return this.fetch(this.url('SyncCollection'), createHTTPRequest(args, headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return {} + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + createPrimarySaleContract = ( + args: CreatePrimarySaleContractArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('CreatePrimarySaleContract'), createHTTPRequest(args, headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { return { - collection: _data.collection, + primarySaleContract: _data.primarySaleContract, } }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + deletePrimarySaleContract = ( + args: DeletePrimarySaleContractArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('DeletePrimarySaleContract'), createHTTPRequest(args, headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return {} + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1276,7 +1715,9 @@ export class Admin implements Admin { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1295,7 +1736,9 @@ export class Admin implements Admin { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1314,13 +1757,19 @@ export class Admin implements Admin { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - listCurrencies = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('ListCurrencies'), createHTTPRequest({}, headers, signal)).then( + listCurrencies = ( + args: ListCurrenciesArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('ListCurrencies'), createHTTPRequest(args, headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { return { @@ -1329,7 +1778,9 @@ export class Admin implements Admin { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1348,7 +1799,28 @@ export class Admin implements Admin { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + addCollectibles = ( + args: AddCollectiblesArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('AddCollectibles'), createHTTPRequest(args, headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return {} + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1367,8 +1839,12 @@ export class Marketplace implements Marketplace { return this.hostname + this.path + name } - listCurrencies = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('ListCurrencies'), createHTTPRequest({}, headers, signal)).then( + listCurrencies = ( + args: ListCurrenciesArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('ListCurrencies'), createHTTPRequest(args, headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { return { @@ -1377,7 +1853,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1396,7 +1874,51 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + getCollectionActiveListingsCurrencies = ( + args: GetCollectionActiveListingsCurrenciesArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('GetCollectionActiveListingsCurrencies'), createHTTPRequest(args, headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return { + currencies: >_data.currencies, + } + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + getCollectionActiveOffersCurrencies = ( + args: GetCollectionActiveOffersCurrenciesArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('GetCollectionActiveOffersCurrencies'), createHTTPRequest(args, headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return { + currencies: >_data.currencies, + } + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1415,7 +1937,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1434,7 +1958,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1453,7 +1979,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1472,7 +2000,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1491,7 +2021,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1511,7 +2043,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1531,7 +2065,109 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + listOrdersWithCollectibles = ( + args: ListOrdersWithCollectiblesArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('ListOrdersWithCollectibles'), createHTTPRequest(args, headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return { + collectibles: >_data.collectibles, + page: _data.page, + } + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + getCountOfAllOrders = ( + args: GetCountOfAllOrdersArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('GetCountOfAllOrders'), createHTTPRequest(args, headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return { + count: _data.count, + } + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + getCountOfFilteredOrders = ( + args: GetCountOfFilteredOrdersArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('GetCountOfFilteredOrders'), createHTTPRequest(args, headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return { + count: _data.count, + } + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + listListings = (args: ListListingsArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('ListListings'), createHTTPRequest(args, headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return { + listings: >_data.listings, + page: _data.page, + } + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + listOffers = (args: ListOffersArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('ListOffers'), createHTTPRequest(args, headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return { + offers: >_data.offers, + page: _data.page, + } + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1550,7 +2186,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1569,7 +2207,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1588,7 +2228,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1607,7 +2249,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1626,7 +2270,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1645,7 +2291,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1665,7 +2313,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1685,7 +2335,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1704,7 +2356,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1723,7 +2377,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1742,7 +2398,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1761,7 +2419,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1780,7 +2440,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1795,7 +2457,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1815,7 +2479,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1834,7 +2500,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1853,7 +2521,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1868,7 +2538,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1888,7 +2560,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1908,7 +2582,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1928,7 +2604,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1948,7 +2626,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1961,7 +2641,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1974,7 +2656,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -1990,7 +2674,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -2009,7 +2695,9 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -2028,7 +2716,94 @@ export class Marketplace implements Marketplace { }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + supportedMarketplaces = ( + args: SupportedMarketplacesArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('SupportedMarketplaces'), createHTTPRequest(args, headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return { + marketplaces: >_data.marketplaces, + } + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + getPrimarySaleItem = ( + args: GetPrimarySaleItemArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('GetPrimarySaleItem'), createHTTPRequest(args, headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return { + item: _data.item, + } + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + listPrimarySaleItems = ( + args: ListPrimarySaleItemsArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('ListPrimarySaleItems'), createHTTPRequest(args, headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return { + primarySaleItems: >_data.primarySaleItems, + page: _data.page, + } + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + getCountOfPrimarySaleItems = ( + args: GetCountOfPrimarySaleItemsArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('GetCountOfPrimarySaleItems'), createHTTPRequest(args, headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return { + count: _data.count, + } + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } @@ -2052,13 +2827,9 @@ const buildResponse = (res: Response): Promise => { try { data = JSON.parse(text) } catch (error) { - let message = '' - if (error instanceof Error) { - message = error.message - } throw WebrpcBadResponseError.new({ status: res.status, - cause: `JSON.parse(): ${message}: response text: ${text}`, + cause: `JSON.parse(): ${error instanceof Error ? error.message : String(error)}: response text: ${text}`, }) } if (!res.ok) { @@ -2106,7 +2877,7 @@ export class WebrpcEndpointError extends WebrpcError { name: string = 'WebrpcEndpoint', code: number = 0, message: string = `endpoint error`, - status: number = 0, + status: number = 400, cause?: string, ) { super(name, code, message, status, cause) @@ -2119,7 +2890,7 @@ export class WebrpcRequestFailedError extends WebrpcError { name: string = 'WebrpcRequestFailed', code: number = -1, message: string = `request failed`, - status: number = 0, + status: number = 400, cause?: string, ) { super(name, code, message, status, cause) @@ -2132,7 +2903,7 @@ export class WebrpcBadRouteError extends WebrpcError { name: string = 'WebrpcBadRoute', code: number = -2, message: string = `bad route`, - status: number = 0, + status: number = 404, cause?: string, ) { super(name, code, message, status, cause) @@ -2145,7 +2916,7 @@ export class WebrpcBadMethodError extends WebrpcError { name: string = 'WebrpcBadMethod', code: number = -3, message: string = `bad method`, - status: number = 0, + status: number = 405, cause?: string, ) { super(name, code, message, status, cause) @@ -2158,7 +2929,7 @@ export class WebrpcBadRequestError extends WebrpcError { name: string = 'WebrpcBadRequest', code: number = -4, message: string = `bad request`, - status: number = 0, + status: number = 400, cause?: string, ) { super(name, code, message, status, cause) @@ -2171,7 +2942,7 @@ export class WebrpcBadResponseError extends WebrpcError { name: string = 'WebrpcBadResponse', code: number = -5, message: string = `bad response`, - status: number = 0, + status: number = 500, cause?: string, ) { super(name, code, message, status, cause) @@ -2184,7 +2955,7 @@ export class WebrpcServerPanicError extends WebrpcError { name: string = 'WebrpcServerPanic', code: number = -6, message: string = `server panic`, - status: number = 0, + status: number = 500, cause?: string, ) { super(name, code, message, status, cause) @@ -2197,7 +2968,7 @@ export class WebrpcInternalErrorError extends WebrpcError { name: string = 'WebrpcInternalError', code: number = -7, message: string = `internal error`, - status: number = 0, + status: number = 500, cause?: string, ) { super(name, code, message, status, cause) @@ -2205,16 +2976,16 @@ export class WebrpcInternalErrorError extends WebrpcError { } } -export class WebrpcClientDisconnectedError extends WebrpcError { +export class WebrpcClientAbortedError extends WebrpcError { constructor( - name: string = 'WebrpcClientDisconnected', + name: string = 'WebrpcClientAborted', code: number = -8, - message: string = `client disconnected`, - status: number = 0, + message: string = `request aborted by client`, + status: number = 400, cause?: string, ) { super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcClientDisconnectedError.prototype) + Object.setPrototypeOf(this, WebrpcClientAbortedError.prototype) } } @@ -2223,7 +2994,7 @@ export class WebrpcStreamLostError extends WebrpcError { name: string = 'WebrpcStreamLost', code: number = -9, message: string = `stream lost`, - status: number = 0, + status: number = 400, cause?: string, ) { super(name, code, message, status, cause) @@ -2236,7 +3007,7 @@ export class WebrpcStreamFinishedError extends WebrpcError { name: string = 'WebrpcStreamFinished', code: number = -10, message: string = `stream finished`, - status: number = 0, + status: number = 200, cause?: string, ) { super(name, code, message, status, cause) @@ -2251,7 +3022,7 @@ export class UnauthorizedError extends WebrpcError { name: string = 'Unauthorized', code: number = 1000, message: string = `Unauthorized access`, - status: number = 0, + status: number = 401, cause?: string, ) { super(name, code, message, status, cause) @@ -2264,7 +3035,7 @@ export class PermissionDeniedError extends WebrpcError { name: string = 'PermissionDenied', code: number = 1001, message: string = `Permission denied`, - status: number = 0, + status: number = 403, cause?: string, ) { super(name, code, message, status, cause) @@ -2277,7 +3048,7 @@ export class SessionExpiredError extends WebrpcError { name: string = 'SessionExpired', code: number = 1002, message: string = `Session expired`, - status: number = 0, + status: number = 403, cause?: string, ) { super(name, code, message, status, cause) @@ -2290,7 +3061,7 @@ export class MethodNotFoundError extends WebrpcError { name: string = 'MethodNotFound', code: number = 1003, message: string = `Method not found`, - status: number = 0, + status: number = 404, cause?: string, ) { super(name, code, message, status, cause) @@ -2298,64 +3069,64 @@ export class MethodNotFoundError extends WebrpcError { } } -export class TimeoutError extends WebrpcError { +export class RequestConflictError extends WebrpcError { constructor( - name: string = 'Timeout', - code: number = 2000, - message: string = `Request timed out`, - status: number = 0, + name: string = 'RequestConflict', + code: number = 1004, + message: string = `Conflict with target resource`, + status: number = 409, cause?: string, ) { super(name, code, message, status, cause) - Object.setPrototypeOf(this, TimeoutError.prototype) + Object.setPrototypeOf(this, RequestConflictError.prototype) } } -export class InvalidArgumentError extends WebrpcError { +export class AbortedError extends WebrpcError { constructor( - name: string = 'InvalidArgument', - code: number = 2001, - message: string = `Invalid argument`, - status: number = 0, + name: string = 'Aborted', + code: number = 1005, + message: string = `Request aborted`, + status: number = 400, cause?: string, ) { super(name, code, message, status, cause) - Object.setPrototypeOf(this, InvalidArgumentError.prototype) + Object.setPrototypeOf(this, AbortedError.prototype) } } -export class NotFoundError extends WebrpcError { +export class GeoblockedError extends WebrpcError { constructor( - name: string = 'NotFound', - code: number = 3000, - message: string = `Resource not found`, - status: number = 0, + name: string = 'Geoblocked', + code: number = 1006, + message: string = `Geoblocked region`, + status: number = 451, cause?: string, ) { super(name, code, message, status, cause) - Object.setPrototypeOf(this, NotFoundError.prototype) + Object.setPrototypeOf(this, GeoblockedError.prototype) } } -export class UserNotFoundError extends WebrpcError { +export class RateLimitedError extends WebrpcError { constructor( - name: string = 'UserNotFound', - code: number = 3001, - message: string = `User not found`, - status: number = 0, + name: string = 'RateLimited', + code: number = 1007, + message: string = `Rate-limited. Please slow down.`, + status: number = 429, cause?: string, ) { super(name, code, message, status, cause) - Object.setPrototypeOf(this, UserNotFoundError.prototype) + Object.setPrototypeOf(this, RateLimitedError.prototype) } } export class ProjectNotFoundError extends WebrpcError { constructor( name: string = 'ProjectNotFound', - code: number = 3002, + code: number = 1008, message: string = `Project not found`, - status: number = 0, + status: number = 401, cause?: string, ) { super(name, code, message, status, cause) @@ -2363,29 +3134,198 @@ export class ProjectNotFoundError extends WebrpcError { } } -export class InvalidTierError extends WebrpcError { +export class SecretKeyCorsDisallowedError extends WebrpcError { + constructor( + name: string = 'SecretKeyCorsDisallowed', + code: number = 1009, + message: string = `CORS disallowed. Admin API Secret Key can't be used from a web app.`, + status: number = 403, + cause?: string, + ) { + super(name, code, message, status, cause) + Object.setPrototypeOf(this, SecretKeyCorsDisallowedError.prototype) + } +} + +export class AccessKeyNotFoundError extends WebrpcError { + constructor( + name: string = 'AccessKeyNotFound', + code: number = 1101, + message: string = `Access key not found`, + status: number = 401, + cause?: string, + ) { + super(name, code, message, status, cause) + Object.setPrototypeOf(this, AccessKeyNotFoundError.prototype) + } +} + +export class AccessKeyMismatchError extends WebrpcError { + constructor( + name: string = 'AccessKeyMismatch', + code: number = 1102, + message: string = `Access key mismatch`, + status: number = 403, + cause?: string, + ) { + super(name, code, message, status, cause) + Object.setPrototypeOf(this, AccessKeyMismatchError.prototype) + } +} + +export class InvalidOriginError extends WebrpcError { + constructor( + name: string = 'InvalidOrigin', + code: number = 1103, + message: string = `Invalid origin for Access Key`, + status: number = 403, + cause?: string, + ) { + super(name, code, message, status, cause) + Object.setPrototypeOf(this, InvalidOriginError.prototype) + } +} + +export class InvalidServiceError extends WebrpcError { + constructor( + name: string = 'InvalidService', + code: number = 1104, + message: string = `Service not enabled for Access key`, + status: number = 403, + cause?: string, + ) { + super(name, code, message, status, cause) + Object.setPrototypeOf(this, InvalidServiceError.prototype) + } +} + +export class UnauthorizedUserError extends WebrpcError { constructor( - name: string = 'InvalidTier', - code: number = 3003, - message: string = `Invalid subscription tier`, - status: number = 0, + name: string = 'UnauthorizedUser', + code: number = 1105, + message: string = `Unauthorized user`, + status: number = 403, cause?: string, ) { super(name, code, message, status, cause) - Object.setPrototypeOf(this, InvalidTierError.prototype) + Object.setPrototypeOf(this, UnauthorizedUserError.prototype) } } -export class ProjectLimitReachedError extends WebrpcError { +export class InvalidChainError extends WebrpcError { constructor( - name: string = 'ProjectLimitReached', - code: number = 3005, - message: string = `Project limit reached`, - status: number = 0, + name: string = 'InvalidChain', + code: number = 1106, + message: string = `Network not enabled for Access key`, + status: number = 403, cause?: string, ) { super(name, code, message, status, cause) - Object.setPrototypeOf(this, ProjectLimitReachedError.prototype) + Object.setPrototypeOf(this, InvalidChainError.prototype) + } +} + +export class QuotaExceededError extends WebrpcError { + constructor( + name: string = 'QuotaExceeded', + code: number = 1200, + message: string = `Quota request exceeded`, + status: number = 429, + cause?: string, + ) { + super(name, code, message, status, cause) + Object.setPrototypeOf(this, QuotaExceededError.prototype) + } +} + +export class QuotaRateLimitError extends WebrpcError { + constructor( + name: string = 'QuotaRateLimit', + code: number = 1201, + message: string = `Quota rate limit exceeded`, + status: number = 429, + cause?: string, + ) { + super(name, code, message, status, cause) + Object.setPrototypeOf(this, QuotaRateLimitError.prototype) + } +} + +export class NoDefaultKeyError extends WebrpcError { + constructor( + name: string = 'NoDefaultKey', + code: number = 1300, + message: string = `No default access key found`, + status: number = 403, + cause?: string, + ) { + super(name, code, message, status, cause) + Object.setPrototypeOf(this, NoDefaultKeyError.prototype) + } +} + +export class MaxAccessKeysError extends WebrpcError { + constructor( + name: string = 'MaxAccessKeys', + code: number = 1301, + message: string = `Access keys limit reached`, + status: number = 403, + cause?: string, + ) { + super(name, code, message, status, cause) + Object.setPrototypeOf(this, MaxAccessKeysError.prototype) + } +} + +export class AtLeastOneKeyError extends WebrpcError { + constructor( + name: string = 'AtLeastOneKey', + code: number = 1302, + message: string = `You need at least one Access Key`, + status: number = 403, + cause?: string, + ) { + super(name, code, message, status, cause) + Object.setPrototypeOf(this, AtLeastOneKeyError.prototype) + } +} + +export class TimeoutError extends WebrpcError { + constructor( + name: string = 'Timeout', + code: number = 1900, + message: string = `Request timed out`, + status: number = 408, + cause?: string, + ) { + super(name, code, message, status, cause) + Object.setPrototypeOf(this, TimeoutError.prototype) + } +} + +export class NotFoundError extends WebrpcError { + constructor( + name: string = 'NotFound', + code: number = 2000, + message: string = `Resource not found`, + status: number = 400, + cause?: string, + ) { + super(name, code, message, status, cause) + Object.setPrototypeOf(this, NotFoundError.prototype) + } +} + +export class InvalidArgumentError extends WebrpcError { + constructor( + name: string = 'InvalidArgument', + code: number = 2001, + message: string = `Invalid argument`, + status: number = 400, + cause?: string, + ) { + super(name, code, message, status, cause) + Object.setPrototypeOf(this, InvalidArgumentError.prototype) } } @@ -2394,7 +3334,7 @@ export class NotImplementedError extends WebrpcError { name: string = 'NotImplemented', code: number = 9999, message: string = `Not Implemented`, - status: number = 0, + status: number = 500, cause?: string, ) { super(name, code, message, status, cause) @@ -2411,20 +3351,33 @@ export enum errors { WebrpcBadResponse = 'WebrpcBadResponse', WebrpcServerPanic = 'WebrpcServerPanic', WebrpcInternalError = 'WebrpcInternalError', - WebrpcClientDisconnected = 'WebrpcClientDisconnected', + WebrpcClientAborted = 'WebrpcClientAborted', WebrpcStreamLost = 'WebrpcStreamLost', WebrpcStreamFinished = 'WebrpcStreamFinished', Unauthorized = 'Unauthorized', PermissionDenied = 'PermissionDenied', SessionExpired = 'SessionExpired', MethodNotFound = 'MethodNotFound', + RequestConflict = 'RequestConflict', + Aborted = 'Aborted', + Geoblocked = 'Geoblocked', + RateLimited = 'RateLimited', + ProjectNotFound = 'ProjectNotFound', + SecretKeyCorsDisallowed = 'SecretKeyCorsDisallowed', + AccessKeyNotFound = 'AccessKeyNotFound', + AccessKeyMismatch = 'AccessKeyMismatch', + InvalidOrigin = 'InvalidOrigin', + InvalidService = 'InvalidService', + UnauthorizedUser = 'UnauthorizedUser', + InvalidChain = 'InvalidChain', + QuotaExceeded = 'QuotaExceeded', + QuotaRateLimit = 'QuotaRateLimit', + NoDefaultKey = 'NoDefaultKey', + MaxAccessKeys = 'MaxAccessKeys', + AtLeastOneKey = 'AtLeastOneKey', Timeout = 'Timeout', - InvalidArgument = 'InvalidArgument', NotFound = 'NotFound', - UserNotFound = 'UserNotFound', - ProjectNotFound = 'ProjectNotFound', - InvalidTier = 'InvalidTier', - ProjectLimitReached = 'ProjectLimitReached', + InvalidArgument = 'InvalidArgument', NotImplemented = 'NotImplemented', } @@ -2437,20 +3390,33 @@ export enum WebrpcErrorCodes { WebrpcBadResponse = -5, WebrpcServerPanic = -6, WebrpcInternalError = -7, - WebrpcClientDisconnected = -8, + WebrpcClientAborted = -8, WebrpcStreamLost = -9, WebrpcStreamFinished = -10, Unauthorized = 1000, PermissionDenied = 1001, SessionExpired = 1002, MethodNotFound = 1003, - Timeout = 2000, + RequestConflict = 1004, + Aborted = 1005, + Geoblocked = 1006, + RateLimited = 1007, + ProjectNotFound = 1008, + SecretKeyCorsDisallowed = 1009, + AccessKeyNotFound = 1101, + AccessKeyMismatch = 1102, + InvalidOrigin = 1103, + InvalidService = 1104, + UnauthorizedUser = 1105, + InvalidChain = 1106, + QuotaExceeded = 1200, + QuotaRateLimit = 1201, + NoDefaultKey = 1300, + MaxAccessKeys = 1301, + AtLeastOneKey = 1302, + Timeout = 1900, + NotFound = 2000, InvalidArgument = 2001, - NotFound = 3000, - UserNotFound = 3001, - ProjectNotFound = 3002, - InvalidTier = 3003, - ProjectLimitReached = 3005, NotImplemented = 9999, } @@ -2463,20 +3429,33 @@ export const webrpcErrorByCode: { [code: number]: any } = { [-5]: WebrpcBadResponseError, [-6]: WebrpcServerPanicError, [-7]: WebrpcInternalErrorError, - [-8]: WebrpcClientDisconnectedError, + [-8]: WebrpcClientAbortedError, [-9]: WebrpcStreamLostError, [-10]: WebrpcStreamFinishedError, [1000]: UnauthorizedError, [1001]: PermissionDeniedError, [1002]: SessionExpiredError, [1003]: MethodNotFoundError, - [2000]: TimeoutError, + [1004]: RequestConflictError, + [1005]: AbortedError, + [1006]: GeoblockedError, + [1007]: RateLimitedError, + [1008]: ProjectNotFoundError, + [1009]: SecretKeyCorsDisallowedError, + [1101]: AccessKeyNotFoundError, + [1102]: AccessKeyMismatchError, + [1103]: InvalidOriginError, + [1104]: InvalidServiceError, + [1105]: UnauthorizedUserError, + [1106]: InvalidChainError, + [1200]: QuotaExceededError, + [1201]: QuotaRateLimitError, + [1300]: NoDefaultKeyError, + [1301]: MaxAccessKeysError, + [1302]: AtLeastOneKeyError, + [1900]: TimeoutError, + [2000]: NotFoundError, [2001]: InvalidArgumentError, - [3000]: NotFoundError, - [3001]: UserNotFoundError, - [3002]: ProjectNotFoundError, - [3003]: InvalidTierError, - [3005]: ProjectLimitReachedError, [9999]: NotImplementedError, } diff --git a/packages/services/metadata/CHANGELOG.md b/packages/services/metadata/CHANGELOG.md index fa04faa2d..e2b877a57 100644 --- a/packages/services/metadata/CHANGELOG.md +++ b/packages/services/metadata/CHANGELOG.md @@ -1,5 +1,216 @@ # @0xsequence/metadata +## 3.0.12 + +### Patch Changes + +- WDK fix + +## 3.0.11 + +### Patch Changes + +- Fix for relayer sponsored fees + +## 3.0.10 + +### Patch Changes + +- Minor network updates, relayer interface + +## 3.0.9 + +### Patch Changes + +- Fee options fixes + +## 3.0.8 + +### Patch Changes + +- Bug fix for relayer fee options handling + +## 3.0.7 + +### Patch Changes + +- Minor bug fixes + +## 3.0.6 + +### Patch Changes + +- userdata upgrade, arweave support + +## 3.0.5 + +### Patch Changes + +- Account federation support + +## 3.0.4 + +### Patch Changes + +- id-token login support + +## 3.0.3 + +### Patch Changes + +- 3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer + +## 3.0.1 + +### Patch Changes + +- Network and session fixes + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade + +## 3.0.0-beta.19 + +### Patch Changes + +- Final RC before 3.0.0 + +## 3.0.0-beta.18 + +### Patch Changes + +- multicall3 optimization + +## 3.0.0-beta.17 + +### Patch Changes + +- New chains, relayer rpc fix + +## 3.0.0-beta.16 + +### Patch Changes + +- ethauth support + +## 3.0.0-beta.15 + +### Patch Changes + +- New chains, minor fixes + +## 3.0.0-beta.14 + +### Patch Changes + +- Relayer fee options fix + +## 3.0.0-beta.13 + +### Patch Changes + +- Userdata service updates + +## 3.0.0-beta.12 + +### Patch Changes + +- Beta release with dapp connector fixes + +## 3.0.0-beta.11 + +### Patch Changes + +- 3.0.0 beta + +## 3.0.0-beta.10 + +### Patch Changes + +- dapp-client updates + +## 3.0.0-beta.9 + +### Patch Changes + +- dapp client updates for EOA login + +## 3.0.0-beta.8 + +### Patch Changes + +- Apple auth fixes + +## 3.0.0-beta.7 + +### Patch Changes + +- Apple auth fix + +## 3.0.0-beta.6 + +### Patch Changes + +- Fix signer 404 error, minor fixes + +## 3.0.0-beta.5 + +### Patch Changes + +- Beta release for v3 + +## 3.0.0-beta.4 + +### Patch Changes + +- RC5 upgrade + +## 3.0.0-beta.3 + +### Patch Changes + +- 3.0.0-beta.3 with fixes + +## 3.0.0-beta.2 + +### Patch Changes + +- 3.0.0-beta.2 with identity instrument updates + +## 3.0.0-beta.1 + +### Patch Changes + +- 3.0.0-beta.1 + ## 2.3.8 ### Patch Changes diff --git a/packages/services/metadata/eslint.config.js b/packages/services/metadata/eslint.config.js new file mode 100644 index 000000000..cecf89b03 --- /dev/null +++ b/packages/services/metadata/eslint.config.js @@ -0,0 +1,4 @@ +import { config as baseConfig } from "@repo/eslint-config/base" + +/** @type {import("eslint").Linter.Config} */ +export default baseConfig diff --git a/packages/services/metadata/package.json b/packages/services/metadata/package.json index af9069cd6..d28f855fa 100644 --- a/packages/services/metadata/package.json +++ b/packages/services/metadata/package.json @@ -1,28 +1,31 @@ { "name": "@0xsequence/metadata", - "version": "3.0.0", + "version": "3.0.12", "publishConfig": { "access": "public" }, "description": "metadata sub-package for Sequence", - "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/metadata", - "author": "Sequence Platforms Inc.", + "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/metadata", + "author": "Sequence Platforms ULC", "license": "Apache-2.0", + "type": "module", "scripts": { "build": "tsc", "dev": "tsc --watch", "test": "echo", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "lint": "eslint . --max-warnings 0" }, "exports": { ".": { - "types": "./src/index.ts", + "types": "./dist/index.d.ts", "default": "./dist/index.js" } }, "devDependencies": { + "@repo/eslint-config": "workspace:^", "@repo/typescript-config": "workspace:^", - "@types/node": "^22.15.29", - "typescript": "^5.8.3" + "@types/node": "^25.3.0", + "typescript": "^6.0.3" } } diff --git a/packages/services/metadata/src/index.ts b/packages/services/metadata/src/index.ts index 8c620b424..f9a1a600c 100644 --- a/packages/services/metadata/src/index.ts +++ b/packages/services/metadata/src/index.ts @@ -1,6 +1,6 @@ -export * from './metadata.gen' +export * from './metadata.gen.js' -import { Metadata as MetadataRpc, Collections as CollectionsRpc } from './metadata.gen' +import { Metadata as MetadataRpc, Collections as CollectionsRpc } from './metadata.gen.js' export class SequenceMetadata extends MetadataRpc { constructor( @@ -15,7 +15,7 @@ export class SequenceMetadata extends MetadataRpc { _fetch = (input: RequestInfo, init?: RequestInit): Promise => { // automatically include jwt and access key auth header to requests // if its been set on the client - const headers: { [key: string]: any } = {} + const headers: Record = {} const jwtAuth = this.jwtAuth const projectAccessKey = this.projectAccessKey @@ -47,7 +47,7 @@ export class SequenceCollections extends CollectionsRpc { _fetch = (input: RequestInfo, init?: RequestInit): Promise => { // automatically include jwt auth header to requests // if its been set on the client - const headers: { [key: string]: any } = {} + const headers: Record = {} const jwtAuth = this.jwtAuth diff --git a/packages/services/metadata/src/metadata.gen.ts b/packages/services/metadata/src/metadata.gen.ts index 05cdfb194..9390aee76 100644 --- a/packages/services/metadata/src/metadata.gen.ts +++ b/packages/services/metadata/src/metadata.gen.ts @@ -1,73 +1,357 @@ /* eslint-disable */ -// sequence-metadata v0.4.0 5cb74ff169ce80c2e42e65d6bbc98b1daaa0945f +// sequence-metadata v0.4.0 673a5fa528008c7f9558810fbb24aad978ed7a84 // -- -// Code generated by webrpc-gen@v0.25.3 with typescript generator. DO NOT EDIT. +// Code generated by Webrpc-gen@v0.31.0 with typescript generator. DO NOT EDIT. // -// webrpc-gen -schema=metadata.ridl -target=typescript -client -ignore=@deprecated -out=./clients/metadata.gen.ts +// webrpc-gen -schema=metadata.ridl -target=typescript -client -ignore=@deprecated -compat -out=./clients/metadata.gen.ts -export const WebrpcHeader = 'Webrpc' - -export const WebrpcHeaderValue = 'webrpc@v0.25.3;gen-typescript@v0.17.0;sequence-metadata@v0.4.0' - -// WebRPC description and code-gen version -export const WebRPCVersion = 'v1' +// Webrpc description and code-gen version +export const WebrpcVersion = 'v1' // Schema version of your RIDL schema -export const WebRPCSchemaVersion = 'v0.4.0' +export const WebrpcSchemaVersion = 'v0.4.0' // Schema hash generated from your RIDL schema -export const WebRPCSchemaHash = '5cb74ff169ce80c2e42e65d6bbc98b1daaa0945f' +export const WebrpcSchemaHash = '673a5fa528008c7f9558810fbb24aad978ed7a84' -type WebrpcGenVersions = { - webrpcGenVersion: string - codeGenName: string - codeGenVersion: string - schemaName: string - schemaVersion: string +// +// Client interface +// + +export interface MetadataClient { + ping(headers?: object, signal?: AbortSignal): Promise + + version(headers?: object, signal?: AbortSignal): Promise + + runtimeStatus(headers?: object, signal?: AbortSignal): Promise + + getTask(req: GetTaskArgs, headers?: object, signal?: AbortSignal): Promise + + getTaskStatus(req: GetTaskStatusArgs, headers?: object, signal?: AbortSignal): Promise + + /** + * Contract Info -- returns contract meta-info for contracts found in registered chain's token-lists + */ + getContractInfo(req: GetContractInfoArgs, headers?: object, signal?: AbortSignal): Promise + + getContractInfoBatch( + req: GetContractInfoBatchArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * Find Contract Info across all chains token-lists. Similar to GetContractInfo above, + * but it will traverse all chains and results from all. + */ + findContractInfo(req: FindContractInfoArgs, headers?: object, signal?: AbortSignal): Promise + + /** + * map of contractAddress :: []ContractInfo + */ + findContractInfoBatch( + req: FindContractInfoBatchArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * Refresh Contract Info -- refresh contract meta-info + */ + refreshContractInfo( + req: RefreshContractInfoArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + refreshContractInfoBatch( + req: RefreshContractInfoBatchArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * Search for contract infos using a query string + */ + searchContractsByQuery( + req: SearchContractsByQueryArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * GetTokenMetadata - fetch token metadata for a particular contract and respective tokenIDs + */ + getTokenMetadata(req: GetTokenMetadataArgs, headers?: object, signal?: AbortSignal): Promise + + /** + * GetTokenMetadataBatch allows you to query the token metadata of a batch of contracts and respective tokenIDs + * where map is contractAddress::[]tokenID => contractAddress::[]TokenMetadata + * + * Note, we limit each request to 50 contracts max and 50 tokens max per contract. + */ + getTokenMetadataBatch( + req: GetTokenMetadataBatchArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * RefreshTokenMetadata allows you to refresh a contract metadata for contract-level and token-level metadata. + */ + refreshTokenMetadata( + req: RefreshTokenMetadataArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * Search ERC721 & ERC1155 token metadata by query string 'q' + */ + searchTokenMetadataByQuery( + req: SearchTokenMetadataByQueryArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * Search ERC721 & ERC1155 token metadata by filter object 'filter' + * which allows to search by text or properties. + */ + searchTokenMetadata( + req: SearchTokenMetadataArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * Search ERC721 & ERC1155 for token IDs by filter object 'filter' + * which allows to search by text or properties. + */ + searchTokenMetadataTokenIDs( + req: SearchTokenMetadataTokenIDsArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * Get token metadata property filters for a contract address + */ + getTokenMetadataPropertyFilters( + req: GetTokenMetadataPropertyFiltersArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * Gets Token Directory supported networks + */ + getTokenDirectoryNetworks( + req: GetTokenDirectoryNetworksArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * Gets Token Directory entries + */ + getTokenDirectory( + req: GetTokenDirectoryArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * Search in Token Directory + */ + searchTokenDirectory( + req: SearchTokenDirectoryArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * Niftyswap querying data + */ + getNiftyswapTokenQuantity( + req: GetNiftyswapTokenQuantityArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * map of tokenID :: quantity + */ + getNiftyswapUnitPrices( + req: GetNiftyswapUnitPricesArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * map of tokenID :: price + */ + getNiftyswapUnitPricesWithQuantities( + req: GetNiftyswapUnitPricesWithQuantitiesArgs, + headers?: object, + signal?: AbortSignal, + ): Promise } +export interface CollectionsClient { + createCollection(req: CreateCollectionArgs, headers?: object, signal?: AbortSignal): Promise -export function VersionFromHeader(headers: Headers): WebrpcGenVersions { - const headerValue = headers.get(WebrpcHeader) - if (!headerValue) { - return { - webrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } + getCollection(req: GetCollectionArgs, headers?: object, signal?: AbortSignal): Promise - return parseWebrpcGenVersions(headerValue) + listCollections(req: ListCollectionsArgs, headers?: object, signal?: AbortSignal): Promise + + updateCollection(req: UpdateCollectionArgs, headers?: object, signal?: AbortSignal): Promise + + deleteCollection(req: DeleteCollectionArgs, headers?: object, signal?: AbortSignal): Promise + + publishCollection( + req: PublishCollectionArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + unpublishCollection( + req: UnpublishCollectionArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + createContractCollection( + req: CreateContractCollectionArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + getContractCollection( + req: GetContractCollectionArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + listContractCollections( + req: ListContractCollectionsArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + updateContractCollection( + req: UpdateContractCollectionArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + deleteContractCollection( + req: DeleteContractCollectionArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + createToken(req: CreateTokenArgs, headers?: object, signal?: AbortSignal): Promise + + getToken(req: GetTokenArgs, headers?: object, signal?: AbortSignal): Promise + + listTokens(req: ListTokensArgs, headers?: object, signal?: AbortSignal): Promise + + updateToken(req: UpdateTokenArgs, headers?: object, signal?: AbortSignal): Promise + + deleteToken(req: DeleteTokenArgs, headers?: object, signal?: AbortSignal): Promise + + createAsset(req: CreateAssetArgs, headers?: object, signal?: AbortSignal): Promise + + getAsset(req: GetAssetArgs, headers?: object, signal?: AbortSignal): Promise + + updateAsset(req: UpdateAssetArgs, headers?: object, signal?: AbortSignal): Promise + + deleteAsset(req: DeleteAssetArgs, headers?: object, signal?: AbortSignal): Promise } +export interface AdminClient { + /** + * ContractInfo + */ + refreshContractInfoUpdatedBefore( + req: RefreshContractInfoUpdatedBeforeArgs, + headers?: object, + signal?: AbortSignal, + ): Promise -function parseWebrpcGenVersions(header: string): WebrpcGenVersions { - const versions = header.split(';') - if (versions.length < 3) { - return { - webrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } + /** + * TokenMetadata + */ + refreshTokenMetadataUpdatedBefore( + req: RefreshTokenMetadataUpdatedBeforeArgs, + headers?: object, + signal?: AbortSignal, + ): Promise - const [_, webrpcGenVersion] = versions[0]!.split('@') - const [codeGenName, codeGenVersion] = versions[1]!.split('@') - const [schemaName, schemaVersion] = versions[2]!.split('@') + /** + * Contract Info Overrides + */ + getContractInfoOverride( + req: GetContractInfoOverrideArgs, + headers?: object, + signal?: AbortSignal, + ): Promise - return { - webrpcGenVersion: webrpcGenVersion ?? '', - codeGenName: codeGenName ?? '', - codeGenVersion: codeGenVersion ?? '', - schemaName: schemaName ?? '', - schemaVersion: schemaVersion ?? '', - } + getContractInfoOverrides( + req: GetContractInfoOverridesArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + addContractInfoOverride( + req: AddContractInfoOverrideArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + updateContractInfoOverride( + req: UpdateContractInfoOverrideArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + removeContractInfoOverride( + req: RemoveContractInfoOverrideArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * Token Directory + */ + isInTokenDirectory( + req: IsInTokenDirectoryArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + setTokenDirectoryFeatureIndex( + req: SetTokenDirectoryFeatureIndexArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + addContractToTokenDirectory( + req: AddContractToTokenDirectoryArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + removeContractFromTokenDirectory( + req: RemoveContractFromTokenDirectoryArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + refreshTokenDirectory(headers?: object, signal?: AbortSignal): Promise } // -// Types +// Schema types // export enum ContractType { @@ -366,177 +650,24 @@ export interface Task { result: Array } -export interface Metadata { - ping(headers?: object, signal?: AbortSignal): Promise - version(headers?: object, signal?: AbortSignal): Promise - runtimeStatus(headers?: object, signal?: AbortSignal): Promise - getTask(args: GetTaskArgs, headers?: object, signal?: AbortSignal): Promise - getTaskStatus(args: GetTaskStatusArgs, headers?: object, signal?: AbortSignal): Promise - /** - * Contract Info -- returns contract meta-info for contracts found in registered chain's token-lists - */ - getContractInfo(args: GetContractInfoArgs, headers?: object, signal?: AbortSignal): Promise - getContractInfoBatch( - args: GetContractInfoBatchArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * Find Contract Info across all chains token-lists. Similar to GetContractInfo above, - * but it will traverse all chains and results from all. - */ - findContractInfo(args: FindContractInfoArgs, headers?: object, signal?: AbortSignal): Promise - /** - * map of contractAddress :: []ContractInfo - */ - findContractInfoBatch( - args: FindContractInfoBatchArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * Refresh Contract Info -- refresh contract meta-info - */ - refreshContractInfo( - args: RefreshContractInfoArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - refreshContractInfoBatch( - args: RefreshContractInfoBatchArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * Search for contract infos using a query string - */ - searchContractsByQuery( - args: SearchContractsByQueryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * GetTokenMetadata - fetch token metadata for a particular contract and respective tokenIDs - */ - getTokenMetadata(args: GetTokenMetadataArgs, headers?: object, signal?: AbortSignal): Promise - /** - * GetTokenMetadataBatch allows you to query the token metadata of a batch of contracts and respective tokenIDs - * where map is contractAddress::[]tokenID => contractAddress::[]TokenMetadata - * - * Note, we limit each request to 50 contracts max and 50 tokens max per contract. - */ - getTokenMetadataBatch( - args: GetTokenMetadataBatchArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * RefreshTokenMetadata allows you to refresh a contract metadata for contract-level and token-level metadata. - */ - refreshTokenMetadata( - args: RefreshTokenMetadataArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * Search ERC721 & ERC1155 token metadata by query string 'q' - */ - searchTokenMetadataByQuery( - args: SearchTokenMetadataByQueryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * Search ERC721 & ERC1155 token metadata by filter object 'filter' - * which allows to search by text or properties. - */ - searchTokenMetadata( - args: SearchTokenMetadataArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * Search ERC721 & ERC1155 for token IDs by filter object 'filter' - * which allows to search by text or properties. - */ - searchTokenMetadataTokenIDs( - args: SearchTokenMetadataTokenIDsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * Get token metadata property filters for a contract address - */ - getTokenMetadataPropertyFilters( - args: GetTokenMetadataPropertyFiltersArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * Gets Token Directory supported networks - */ - getTokenDirectoryNetworks( - args: GetTokenDirectoryNetworksArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * Gets Token Directory entries - */ - getTokenDirectory( - args: GetTokenDirectoryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * Search in Token Directory - */ - searchTokenDirectory( - args: SearchTokenDirectoryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * Niftyswap querying data - */ - getNiftyswapTokenQuantity( - args: GetNiftyswapTokenQuantityArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * map of tokenID :: quantity - */ - getNiftyswapUnitPrices( - args: GetNiftyswapUnitPricesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * map of tokenID :: price - */ - getNiftyswapUnitPricesWithQuantities( - args: GetNiftyswapUnitPricesWithQuantitiesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise -} - export interface PingArgs {} export interface PingReturn { status: boolean } + export interface VersionArgs {} export interface VersionReturn { version: Version } + export interface RuntimeStatusArgs {} export interface RuntimeStatusReturn { status: RuntimeStatus } + export interface GetTaskArgs { taskId: number } @@ -544,6 +675,7 @@ export interface GetTaskArgs { export interface GetTaskReturn { task: Task } + export interface GetTaskStatusArgs { taskId: number } @@ -551,6 +683,7 @@ export interface GetTaskStatusArgs { export interface GetTaskStatusReturn { status?: TaskStatus } + export interface GetContractInfoArgs { chainID: string contractAddress: string @@ -560,6 +693,7 @@ export interface GetContractInfoReturn { contractInfo: ContractInfo taskID?: number } + export interface GetContractInfoBatchArgs { chainID: string contractAddresses: Array @@ -569,6 +703,7 @@ export interface GetContractInfoBatchReturn { contractInfoMap: { [key: string]: ContractInfo } taskID?: number } + export interface FindContractInfoArgs { contractAddress: string } @@ -576,6 +711,7 @@ export interface FindContractInfoArgs { export interface FindContractInfoReturn { contractInfoList: Array } + export interface FindContractInfoBatchArgs { contractAddresses: Array } @@ -583,6 +719,7 @@ export interface FindContractInfoBatchArgs { export interface FindContractInfoBatchReturn { contractInfoByChain: { [key: string]: Array } } + export interface RefreshContractInfoArgs { chainID: string contractAddress: string @@ -591,6 +728,7 @@ export interface RefreshContractInfoArgs { export interface RefreshContractInfoReturn { taskID?: number } + export interface RefreshContractInfoBatchArgs { chainID: string contractAddresses: Array @@ -599,6 +737,7 @@ export interface RefreshContractInfoBatchArgs { export interface RefreshContractInfoBatchReturn { taskID?: number } + export interface SearchContractsByQueryArgs { q: string chainID?: string @@ -611,6 +750,7 @@ export interface SearchContractsByQueryReturn { contractInfo: Array nextPage: Page } + export interface GetTokenMetadataArgs { chainID: string contractAddress: string @@ -621,6 +761,7 @@ export interface GetTokenMetadataReturn { tokenMetadata: Array taskID?: number } + export interface GetTokenMetadataBatchArgs { chainID: string contractTokenMap: { [key: string]: Array } @@ -630,16 +771,18 @@ export interface GetTokenMetadataBatchReturn { contractTokenMetadata: { [key: string]: Array } taskID?: number } + export interface RefreshTokenMetadataArgs { chainID: string contractAddress: string tokenIDs?: Array - refreshAll?: boolean + newMints?: boolean } export interface RefreshTokenMetadataReturn { taskID: number } + export interface SearchTokenMetadataByQueryArgs { q: string chainID?: string @@ -651,6 +794,7 @@ export interface SearchTokenMetadataByQueryReturn { tokenMetadata: Array nextPage: Page } + export interface SearchTokenMetadataArgs { chainID: string contractAddress: string @@ -662,6 +806,7 @@ export interface SearchTokenMetadataReturn { page: Page tokenMetadata: Array } + export interface SearchTokenMetadataTokenIDsArgs { chainID: string contractAddress: string @@ -673,6 +818,7 @@ export interface SearchTokenMetadataTokenIDsReturn { page: Page tokenIDs: Array } + export interface GetTokenMetadataPropertyFiltersArgs { chainID: string contractAddress: string @@ -683,6 +829,7 @@ export interface GetTokenMetadataPropertyFiltersArgs { export interface GetTokenMetadataPropertyFiltersReturn { filters: Array } + export interface GetTokenDirectoryNetworksArgs { includeTestnets?: boolean onlyFeatured?: boolean @@ -692,6 +839,7 @@ export interface GetTokenDirectoryNetworksReturn { chainIDs: Array networks: Array } + export interface GetTokenDirectoryArgs { chainID?: string includeTestnets?: boolean @@ -703,6 +851,7 @@ export interface GetTokenDirectoryReturn { contracts: Array page: Page } + export interface SearchTokenDirectoryArgs { query: string chainID?: number @@ -715,6 +864,7 @@ export interface SearchTokenDirectoryReturn { contracts: Array page: Page } + export interface GetNiftyswapTokenQuantityArgs { chainID: string contractAddress: string @@ -724,6 +874,7 @@ export interface GetNiftyswapTokenQuantityArgs { export interface GetNiftyswapTokenQuantityReturn { quantity: { [key: string]: string } } + export interface GetNiftyswapUnitPricesArgs { chainID: string contractAddress: string @@ -734,6 +885,7 @@ export interface GetNiftyswapUnitPricesArgs { export interface GetNiftyswapUnitPricesReturn { prices: { [key: string]: string } } + export interface GetNiftyswapUnitPricesWithQuantitiesArgs { chainID: string contractAddress: string @@ -745,58 +897,6 @@ export interface GetNiftyswapUnitPricesWithQuantitiesReturn { prices: { [key: string]: GetNiftyswapUnitPricesResponse } } -export interface Collections { - createCollection(args: CreateCollectionArgs, headers?: object, signal?: AbortSignal): Promise - getCollection(args: GetCollectionArgs, headers?: object, signal?: AbortSignal): Promise - listCollections(args: ListCollectionsArgs, headers?: object, signal?: AbortSignal): Promise - updateCollection(args: UpdateCollectionArgs, headers?: object, signal?: AbortSignal): Promise - deleteCollection(args: DeleteCollectionArgs, headers?: object, signal?: AbortSignal): Promise - publishCollection( - args: PublishCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - unpublishCollection( - args: UnpublishCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - createContractCollection( - args: CreateContractCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getContractCollection( - args: GetContractCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - listContractCollections( - args: ListContractCollectionsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - updateContractCollection( - args: UpdateContractCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - deleteContractCollection( - args: DeleteContractCollectionArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - createToken(args: CreateTokenArgs, headers?: object, signal?: AbortSignal): Promise - getToken(args: GetTokenArgs, headers?: object, signal?: AbortSignal): Promise - listTokens(args: ListTokensArgs, headers?: object, signal?: AbortSignal): Promise - updateToken(args: UpdateTokenArgs, headers?: object, signal?: AbortSignal): Promise - deleteToken(args: DeleteTokenArgs, headers?: object, signal?: AbortSignal): Promise - createAsset(args: CreateAssetArgs, headers?: object, signal?: AbortSignal): Promise - getAsset(args: GetAssetArgs, headers?: object, signal?: AbortSignal): Promise - updateAsset(args: UpdateAssetArgs, headers?: object, signal?: AbortSignal): Promise - deleteAsset(args: DeleteAssetArgs, headers?: object, signal?: AbortSignal): Promise -} - export interface CreateCollectionArgs { projectId?: number collection: Collection @@ -805,6 +905,7 @@ export interface CreateCollectionArgs { export interface CreateCollectionReturn { collection: Collection } + export interface GetCollectionArgs { projectId?: number collectionId: number @@ -813,6 +914,7 @@ export interface GetCollectionArgs { export interface GetCollectionReturn { collection: Collection } + export interface ListCollectionsArgs { projectId?: number page?: Page @@ -822,6 +924,7 @@ export interface ListCollectionsReturn { page: Page collections: Array } + export interface UpdateCollectionArgs { projectId?: number collection: Collection @@ -830,6 +933,7 @@ export interface UpdateCollectionArgs { export interface UpdateCollectionReturn { collection: Collection } + export interface DeleteCollectionArgs { projectId?: number collectionId: number @@ -838,6 +942,7 @@ export interface DeleteCollectionArgs { export interface DeleteCollectionReturn { status: boolean } + export interface PublishCollectionArgs { projectId?: number collectionId: number @@ -847,6 +952,7 @@ export interface PublishCollectionArgs { export interface PublishCollectionReturn { collection: Collection } + export interface UnpublishCollectionArgs { projectId?: number collectionId: number @@ -855,6 +961,7 @@ export interface UnpublishCollectionArgs { export interface UnpublishCollectionReturn { collection: Collection } + export interface CreateContractCollectionArgs { projectId: number contractCollection: ContractCollection @@ -863,6 +970,7 @@ export interface CreateContractCollectionArgs { export interface CreateContractCollectionReturn { contractCollection: ContractCollection } + export interface GetContractCollectionArgs { projectId: number chainId: number @@ -872,6 +980,7 @@ export interface GetContractCollectionArgs { export interface GetContractCollectionReturn { contractCollection: ContractCollection } + export interface ListContractCollectionsArgs { projectId: number collectionId?: number @@ -883,6 +992,7 @@ export interface ListContractCollectionsReturn { collections: Array page: Page } + export interface UpdateContractCollectionArgs { projectId: number contractCollection: ContractCollection @@ -891,6 +1001,7 @@ export interface UpdateContractCollectionArgs { export interface UpdateContractCollectionReturn { ok: boolean } + export interface DeleteContractCollectionArgs { projectId: number chainId: number @@ -900,6 +1011,7 @@ export interface DeleteContractCollectionArgs { export interface DeleteContractCollectionReturn { ok: boolean } + export interface CreateTokenArgs { projectId?: number collectionId: number @@ -911,6 +1023,7 @@ export interface CreateTokenReturn { token: TokenMetadata assets: Array } + export interface GetTokenArgs { projectId?: number collectionId: number @@ -921,6 +1034,7 @@ export interface GetTokenReturn { token: TokenMetadata assets: Array } + export interface ListTokensArgs { projectId?: number collectionId: number @@ -931,6 +1045,7 @@ export interface ListTokensReturn { page: Page tokens: Array } + export interface UpdateTokenArgs { projectId?: number collectionId: number @@ -942,6 +1057,7 @@ export interface UpdateTokenArgs { export interface UpdateTokenReturn { token: TokenMetadata } + export interface DeleteTokenArgs { projectId?: number collectionId: number @@ -951,6 +1067,7 @@ export interface DeleteTokenArgs { export interface DeleteTokenReturn { status: boolean } + export interface CreateAssetArgs { projectId?: number asset: Asset @@ -959,6 +1076,7 @@ export interface CreateAssetArgs { export interface CreateAssetReturn { asset: Asset } + export interface GetAssetArgs { projectId?: number assetId: number @@ -967,6 +1085,7 @@ export interface GetAssetArgs { export interface GetAssetReturn { asset: Asset } + export interface UpdateAssetArgs { projectId?: number asset: Asset @@ -975,6 +1094,7 @@ export interface UpdateAssetArgs { export interface UpdateAssetReturn { asset: Asset } + export interface DeleteAssetArgs { projectId?: number assetId: number @@ -984,77 +1104,6 @@ export interface DeleteAssetReturn { status: boolean } -export interface Admin { - /** - * ContractInfo - */ - refreshContractInfoUpdatedBefore( - args: RefreshContractInfoUpdatedBeforeArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * TokenMetadata - */ - refreshTokenMetadataUpdatedBefore( - args: RefreshTokenMetadataUpdatedBeforeArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * Contract Info Overrides - */ - getContractInfoOverride( - args: GetContractInfoOverrideArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getContractInfoOverrides( - args: GetContractInfoOverridesArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - addContractInfoOverride( - args: AddContractInfoOverrideArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - updateContractInfoOverride( - args: UpdateContractInfoOverrideArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - removeContractInfoOverride( - args: RemoveContractInfoOverrideArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * Token Directory - */ - isInTokenDirectory( - args: IsInTokenDirectoryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - setTokenDirectoryFeatureIndex( - args: SetTokenDirectoryFeatureIndexArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - addContractToTokenDirectory( - args: AddContractToTokenDirectoryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - removeContractFromTokenDirectory( - args: RemoveContractFromTokenDirectoryArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - refreshTokenDirectory(headers?: object, signal?: AbortSignal): Promise -} - export interface RefreshContractInfoUpdatedBeforeArgs { before: string maxContractNumber: number @@ -1063,6 +1112,7 @@ export interface RefreshContractInfoUpdatedBeforeArgs { export interface RefreshContractInfoUpdatedBeforeReturn { taskIDs: Array } + export interface RefreshTokenMetadataUpdatedBeforeArgs { before: string maxTokenNumber: number @@ -1071,6 +1121,7 @@ export interface RefreshTokenMetadataUpdatedBeforeArgs { export interface RefreshTokenMetadataUpdatedBeforeReturn { taskIDs: Array } + export interface GetContractInfoOverrideArgs { chainID: string contractAddress: string @@ -1079,6 +1130,7 @@ export interface GetContractInfoOverrideArgs { export interface GetContractInfoOverrideReturn { contractInfoOverride: ContractInfoOverride } + export interface GetContractInfoOverridesArgs { chainID?: string page?: Page @@ -1088,6 +1140,7 @@ export interface GetContractInfoOverridesReturn { contractInfoOverrides: Array page: Page } + export interface AddContractInfoOverrideArgs { chainID: string contractAddress: string @@ -1097,6 +1150,7 @@ export interface AddContractInfoOverrideArgs { export interface AddContractInfoOverrideReturn { ok: boolean } + export interface UpdateContractInfoOverrideArgs { chainID: string contractAddress: string @@ -1106,6 +1160,7 @@ export interface UpdateContractInfoOverrideArgs { export interface UpdateContractInfoOverrideReturn { ok: boolean } + export interface RemoveContractInfoOverrideArgs { chainID: string contractAddress: string @@ -1114,6 +1169,7 @@ export interface RemoveContractInfoOverrideArgs { export interface RemoveContractInfoOverrideReturn { ok: boolean } + export interface IsInTokenDirectoryArgs { chainID: string contractAddress: string @@ -1123,6 +1179,7 @@ export interface IsInTokenDirectoryReturn { ok: boolean featureIndex: number } + export interface SetTokenDirectoryFeatureIndexArgs { chainID: string contractAddress: string @@ -1132,6 +1189,7 @@ export interface SetTokenDirectoryFeatureIndexArgs { export interface SetTokenDirectoryFeatureIndexReturn { ok: boolean } + export interface AddContractToTokenDirectoryArgs { chainID: string contractAddress: string @@ -1140,6 +1198,7 @@ export interface AddContractToTokenDirectoryArgs { export interface AddContractToTokenDirectoryReturn { ok: boolean } + export interface RemoveContractFromTokenDirectoryArgs { chainID: string contractAddress: string @@ -1148,6 +1207,7 @@ export interface RemoveContractFromTokenDirectoryArgs { export interface RemoveContractFromTokenDirectoryReturn { ok: boolean } + export interface RefreshTokenDirectoryArgs {} export interface RefreshTokenDirectoryReturn { @@ -1157,7 +1217,8 @@ export interface RefreshTokenDirectoryReturn { // // Client // -export class Metadata implements Metadata { + +export class Metadata implements MetadataClient { protected hostname: string protected fetch: Fetch protected path = '/rpc/Metadata/' @@ -1171,473 +1232,563 @@ export class Metadata implements Metadata { return this.hostname + this.path + name } + queryKey = { + ping: () => ['Metadata', 'ping'] as const, + version: () => ['Metadata', 'version'] as const, + runtimeStatus: () => ['Metadata', 'runtimeStatus'] as const, + getTask: (req: GetTaskArgs) => ['Metadata', 'getTask', req] as const, + getTaskStatus: (req: GetTaskStatusArgs) => ['Metadata', 'getTaskStatus', req] as const, + getContractInfo: (req: GetContractInfoArgs) => ['Metadata', 'getContractInfo', req] as const, + getContractInfoBatch: (req: GetContractInfoBatchArgs) => ['Metadata', 'getContractInfoBatch', req] as const, + findContractInfo: (req: FindContractInfoArgs) => ['Metadata', 'findContractInfo', req] as const, + findContractInfoBatch: (req: FindContractInfoBatchArgs) => ['Metadata', 'findContractInfoBatch', req] as const, + refreshContractInfo: (req: RefreshContractInfoArgs) => ['Metadata', 'refreshContractInfo', req] as const, + refreshContractInfoBatch: (req: RefreshContractInfoBatchArgs) => + ['Metadata', 'refreshContractInfoBatch', req] as const, + searchContractsByQuery: (req: SearchContractsByQueryArgs) => ['Metadata', 'searchContractsByQuery', req] as const, + getTokenMetadata: (req: GetTokenMetadataArgs) => ['Metadata', 'getTokenMetadata', req] as const, + getTokenMetadataBatch: (req: GetTokenMetadataBatchArgs) => ['Metadata', 'getTokenMetadataBatch', req] as const, + refreshTokenMetadata: (req: RefreshTokenMetadataArgs) => ['Metadata', 'refreshTokenMetadata', req] as const, + searchTokenMetadataByQuery: (req: SearchTokenMetadataByQueryArgs) => + ['Metadata', 'searchTokenMetadataByQuery', req] as const, + searchTokenMetadata: (req: SearchTokenMetadataArgs) => ['Metadata', 'searchTokenMetadata', req] as const, + searchTokenMetadataTokenIDs: (req: SearchTokenMetadataTokenIDsArgs) => + ['Metadata', 'searchTokenMetadataTokenIDs', req] as const, + getTokenMetadataPropertyFilters: (req: GetTokenMetadataPropertyFiltersArgs) => + ['Metadata', 'getTokenMetadataPropertyFilters', req] as const, + getTokenDirectoryNetworks: (req: GetTokenDirectoryNetworksArgs) => + ['Metadata', 'getTokenDirectoryNetworks', req] as const, + getTokenDirectory: (req: GetTokenDirectoryArgs) => ['Metadata', 'getTokenDirectory', req] as const, + searchTokenDirectory: (req: SearchTokenDirectoryArgs) => ['Metadata', 'searchTokenDirectory', req] as const, + getNiftyswapTokenQuantity: (req: GetNiftyswapTokenQuantityArgs) => + ['Metadata', 'getNiftyswapTokenQuantity', req] as const, + getNiftyswapUnitPrices: (req: GetNiftyswapUnitPricesArgs) => ['Metadata', 'getNiftyswapUnitPrices', req] as const, + getNiftyswapUnitPricesWithQuantities: (req: GetNiftyswapUnitPricesWithQuantitiesArgs) => + ['Metadata', 'getNiftyswapUnitPricesWithQuantities', req] as const, + } + ping = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Ping'), createHTTPRequest({}, headers, signal)).then( + return this.fetch(this.url('Ping'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'PingReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } version = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Version'), createHTTPRequest({}, headers, signal)).then( + return this.fetch(this.url('Version'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - version: _data.version, - } + return JsonDecode(_data, 'VersionReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } runtimeStatus = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('RuntimeStatus'), createHTTPRequest({}, headers, signal)).then( + return this.fetch(this.url('RuntimeStatus'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'RuntimeStatusReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getTask = (args: GetTaskArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetTask'), createHTTPRequest(args, headers, signal)).then( + getTask = (req: GetTaskArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('GetTask'), createHttpRequest(JsonEncode(req, 'GetTaskArgs'), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - task: _data.task, - } + return JsonDecode(_data, 'GetTaskReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getTaskStatus = (args: GetTaskStatusArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetTaskStatus'), createHTTPRequest(args, headers, signal)).then( + getTaskStatus = (req: GetTaskStatusArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('GetTaskStatus'), + createHttpRequest(JsonEncode(req, 'GetTaskStatusArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'GetTaskStatusReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getContractInfo = ( - args: GetContractInfoArgs, + req: GetContractInfoArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('GetContractInfo'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('GetContractInfo'), + createHttpRequest(JsonEncode(req, 'GetContractInfoArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - contractInfo: _data.contractInfo, - taskID: _data.taskID, - } + return JsonDecode(_data, 'GetContractInfoReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getContractInfoBatch = ( - args: GetContractInfoBatchArgs, + req: GetContractInfoBatchArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('GetContractInfoBatch'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('GetContractInfoBatch'), + createHttpRequest(JsonEncode(req, 'GetContractInfoBatchArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - contractInfoMap: <{ [key: string]: ContractInfo }>_data.contractInfoMap, - taskID: _data.taskID, - } + return JsonDecode(_data, 'GetContractInfoBatchReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } findContractInfo = ( - args: FindContractInfoArgs, + req: FindContractInfoArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('FindContractInfo'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('FindContractInfo'), + createHttpRequest(JsonEncode(req, 'FindContractInfoArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - contractInfoList: >_data.contractInfoList, - } + return JsonDecode(_data, 'FindContractInfoReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } findContractInfoBatch = ( - args: FindContractInfoBatchArgs, + req: FindContractInfoBatchArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('FindContractInfoBatch'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('FindContractInfoBatch'), + createHttpRequest(JsonEncode(req, 'FindContractInfoBatchArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - contractInfoByChain: <{ [key: string]: Array }>_data.contractInfoByChain, - } + return JsonDecode(_data, 'FindContractInfoBatchReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } refreshContractInfo = ( - args: RefreshContractInfoArgs, + req: RefreshContractInfoArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('RefreshContractInfo'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('RefreshContractInfo'), + createHttpRequest(JsonEncode(req, 'RefreshContractInfoArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - taskID: _data.taskID, - } + return JsonDecode(_data, 'RefreshContractInfoReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } refreshContractInfoBatch = ( - args: RefreshContractInfoBatchArgs, + req: RefreshContractInfoBatchArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('RefreshContractInfoBatch'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('RefreshContractInfoBatch'), + createHttpRequest(JsonEncode(req, 'RefreshContractInfoBatchArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - taskID: _data.taskID, - } + return JsonDecode(_data, 'RefreshContractInfoBatchReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } searchContractsByQuery = ( - args: SearchContractsByQueryArgs, + req: SearchContractsByQueryArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('SearchContractsByQuery'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('SearchContractsByQuery'), + createHttpRequest(JsonEncode(req, 'SearchContractsByQueryArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - contractInfo: >_data.contractInfo, - nextPage: _data.nextPage, - } + return JsonDecode(_data, 'SearchContractsByQueryReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getTokenMetadata = ( - args: GetTokenMetadataArgs, + req: GetTokenMetadataArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('GetTokenMetadata'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('GetTokenMetadata'), + createHttpRequest(JsonEncode(req, 'GetTokenMetadataArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - tokenMetadata: >_data.tokenMetadata, - taskID: _data.taskID, - } + return JsonDecode(_data, 'GetTokenMetadataReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getTokenMetadataBatch = ( - args: GetTokenMetadataBatchArgs, + req: GetTokenMetadataBatchArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('GetTokenMetadataBatch'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('GetTokenMetadataBatch'), + createHttpRequest(JsonEncode(req, 'GetTokenMetadataBatchArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - contractTokenMetadata: <{ [key: string]: Array }>_data.contractTokenMetadata, - taskID: _data.taskID, - } + return JsonDecode(_data, 'GetTokenMetadataBatchReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } refreshTokenMetadata = ( - args: RefreshTokenMetadataArgs, + req: RefreshTokenMetadataArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('RefreshTokenMetadata'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('RefreshTokenMetadata'), + createHttpRequest(JsonEncode(req, 'RefreshTokenMetadataArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - taskID: _data.taskID, - } + return JsonDecode(_data, 'RefreshTokenMetadataReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } searchTokenMetadataByQuery = ( - args: SearchTokenMetadataByQueryArgs, + req: SearchTokenMetadataByQueryArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('SearchTokenMetadataByQuery'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('SearchTokenMetadataByQuery'), + createHttpRequest(JsonEncode(req, 'SearchTokenMetadataByQueryArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - tokenMetadata: >_data.tokenMetadata, - nextPage: _data.nextPage, - } + return JsonDecode(_data, 'SearchTokenMetadataByQueryReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } searchTokenMetadata = ( - args: SearchTokenMetadataArgs, + req: SearchTokenMetadataArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('SearchTokenMetadata'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('SearchTokenMetadata'), + createHttpRequest(JsonEncode(req, 'SearchTokenMetadataArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - tokenMetadata: >_data.tokenMetadata, - } + return JsonDecode(_data, 'SearchTokenMetadataReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } searchTokenMetadataTokenIDs = ( - args: SearchTokenMetadataTokenIDsArgs, + req: SearchTokenMetadataTokenIDsArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('SearchTokenMetadataTokenIDs'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('SearchTokenMetadataTokenIDs'), + createHttpRequest(JsonEncode(req, 'SearchTokenMetadataTokenIDsArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - tokenIDs: >_data.tokenIDs, - } + return JsonDecode(_data, 'SearchTokenMetadataTokenIDsReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getTokenMetadataPropertyFilters = ( - args: GetTokenMetadataPropertyFiltersArgs, + req: GetTokenMetadataPropertyFiltersArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('GetTokenMetadataPropertyFilters'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('GetTokenMetadataPropertyFilters'), + createHttpRequest(JsonEncode(req, 'GetTokenMetadataPropertyFiltersArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - filters: >_data.filters, - } + return JsonDecode(_data, 'GetTokenMetadataPropertyFiltersReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getTokenDirectoryNetworks = ( - args: GetTokenDirectoryNetworksArgs, + req: GetTokenDirectoryNetworksArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('GetTokenDirectoryNetworks'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('GetTokenDirectoryNetworks'), + createHttpRequest(JsonEncode(req, 'GetTokenDirectoryNetworksArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - chainIDs: >_data.chainIDs, - networks: >_data.networks, - } + return JsonDecode(_data, 'GetTokenDirectoryNetworksReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getTokenDirectory = ( - args: GetTokenDirectoryArgs, + req: GetTokenDirectoryArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('GetTokenDirectory'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('GetTokenDirectory'), + createHttpRequest(JsonEncode(req, 'GetTokenDirectoryArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - contracts: >_data.contracts, - page: _data.page, - } + return JsonDecode(_data, 'GetTokenDirectoryReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } searchTokenDirectory = ( - args: SearchTokenDirectoryArgs, + req: SearchTokenDirectoryArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('SearchTokenDirectory'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('SearchTokenDirectory'), + createHttpRequest(JsonEncode(req, 'SearchTokenDirectoryArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - contracts: >_data.contracts, - page: _data.page, - } + return JsonDecode(_data, 'SearchTokenDirectoryReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getNiftyswapTokenQuantity = ( - args: GetNiftyswapTokenQuantityArgs, + req: GetNiftyswapTokenQuantityArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('GetNiftyswapTokenQuantity'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('GetNiftyswapTokenQuantity'), + createHttpRequest(JsonEncode(req, 'GetNiftyswapTokenQuantityArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - quantity: <{ [key: string]: string }>_data.quantity, - } + return JsonDecode(_data, 'GetNiftyswapTokenQuantityReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getNiftyswapUnitPrices = ( - args: GetNiftyswapUnitPricesArgs, + req: GetNiftyswapUnitPricesArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('GetNiftyswapUnitPrices'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('GetNiftyswapUnitPrices'), + createHttpRequest(JsonEncode(req, 'GetNiftyswapUnitPricesArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - prices: <{ [key: string]: string }>_data.prices, - } + return JsonDecode(_data, 'GetNiftyswapUnitPricesReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getNiftyswapUnitPricesWithQuantities = ( - args: GetNiftyswapUnitPricesWithQuantitiesArgs, + req: GetNiftyswapUnitPricesWithQuantitiesArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('GetNiftyswapUnitPricesWithQuantities'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('GetNiftyswapUnitPricesWithQuantities'), + createHttpRequest(JsonEncode(req, 'GetNiftyswapUnitPricesWithQuantitiesArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - prices: <{ [key: string]: GetNiftyswapUnitPricesResponse }>_data.prices, - } + return JsonDecode( + _data, + 'GetNiftyswapUnitPricesWithQuantitiesReturn', + ) }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } } -export class Collections implements Collections { +export class Collections implements CollectionsClient { protected hostname: string protected fetch: Fetch protected path = '/rpc/Collections/' @@ -1651,372 +1802,451 @@ export class Collections implements Collections { return this.hostname + this.path + name } + queryKey = { + createCollection: (req: CreateCollectionArgs) => ['Collections', 'createCollection', req] as const, + getCollection: (req: GetCollectionArgs) => ['Collections', 'getCollection', req] as const, + listCollections: (req: ListCollectionsArgs) => ['Collections', 'listCollections', req] as const, + updateCollection: (req: UpdateCollectionArgs) => ['Collections', 'updateCollection', req] as const, + deleteCollection: (req: DeleteCollectionArgs) => ['Collections', 'deleteCollection', req] as const, + publishCollection: (req: PublishCollectionArgs) => ['Collections', 'publishCollection', req] as const, + unpublishCollection: (req: UnpublishCollectionArgs) => ['Collections', 'unpublishCollection', req] as const, + createContractCollection: (req: CreateContractCollectionArgs) => + ['Collections', 'createContractCollection', req] as const, + getContractCollection: (req: GetContractCollectionArgs) => ['Collections', 'getContractCollection', req] as const, + listContractCollections: (req: ListContractCollectionsArgs) => + ['Collections', 'listContractCollections', req] as const, + updateContractCollection: (req: UpdateContractCollectionArgs) => + ['Collections', 'updateContractCollection', req] as const, + deleteContractCollection: (req: DeleteContractCollectionArgs) => + ['Collections', 'deleteContractCollection', req] as const, + createToken: (req: CreateTokenArgs) => ['Collections', 'createToken', req] as const, + getToken: (req: GetTokenArgs) => ['Collections', 'getToken', req] as const, + listTokens: (req: ListTokensArgs) => ['Collections', 'listTokens', req] as const, + updateToken: (req: UpdateTokenArgs) => ['Collections', 'updateToken', req] as const, + deleteToken: (req: DeleteTokenArgs) => ['Collections', 'deleteToken', req] as const, + createAsset: (req: CreateAssetArgs) => ['Collections', 'createAsset', req] as const, + getAsset: (req: GetAssetArgs) => ['Collections', 'getAsset', req] as const, + updateAsset: (req: UpdateAssetArgs) => ['Collections', 'updateAsset', req] as const, + deleteAsset: (req: DeleteAssetArgs) => ['Collections', 'deleteAsset', req] as const, + } + createCollection = ( - args: CreateCollectionArgs, + req: CreateCollectionArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('CreateCollection'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('CreateCollection'), + createHttpRequest(JsonEncode(req, 'CreateCollectionArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - collection: _data.collection, - } + return JsonDecode(_data, 'CreateCollectionReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getCollection = (args: GetCollectionArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetCollection'), createHTTPRequest(args, headers, signal)).then( + getCollection = (req: GetCollectionArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('GetCollection'), + createHttpRequest(JsonEncode(req, 'GetCollectionArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - collection: _data.collection, - } + return JsonDecode(_data, 'GetCollectionReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } listCollections = ( - args: ListCollectionsArgs, + req: ListCollectionsArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('ListCollections'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('ListCollections'), + createHttpRequest(JsonEncode(req, 'ListCollectionsArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - collections: >_data.collections, - } + return JsonDecode(_data, 'ListCollectionsReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } updateCollection = ( - args: UpdateCollectionArgs, + req: UpdateCollectionArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('UpdateCollection'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('UpdateCollection'), + createHttpRequest(JsonEncode(req, 'UpdateCollectionArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - collection: _data.collection, - } + return JsonDecode(_data, 'UpdateCollectionReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } deleteCollection = ( - args: DeleteCollectionArgs, + req: DeleteCollectionArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('DeleteCollection'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('DeleteCollection'), + createHttpRequest(JsonEncode(req, 'DeleteCollectionArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'DeleteCollectionReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } publishCollection = ( - args: PublishCollectionArgs, + req: PublishCollectionArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('PublishCollection'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('PublishCollection'), + createHttpRequest(JsonEncode(req, 'PublishCollectionArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - collection: _data.collection, - } + return JsonDecode(_data, 'PublishCollectionReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } unpublishCollection = ( - args: UnpublishCollectionArgs, + req: UnpublishCollectionArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('UnpublishCollection'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('UnpublishCollection'), + createHttpRequest(JsonEncode(req, 'UnpublishCollectionArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - collection: _data.collection, - } + return JsonDecode(_data, 'UnpublishCollectionReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } createContractCollection = ( - args: CreateContractCollectionArgs, + req: CreateContractCollectionArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('CreateContractCollection'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('CreateContractCollection'), + createHttpRequest(JsonEncode(req, 'CreateContractCollectionArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - contractCollection: _data.contractCollection, - } + return JsonDecode(_data, 'CreateContractCollectionReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getContractCollection = ( - args: GetContractCollectionArgs, + req: GetContractCollectionArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('GetContractCollection'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('GetContractCollection'), + createHttpRequest(JsonEncode(req, 'GetContractCollectionArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - contractCollection: _data.contractCollection, - } + return JsonDecode(_data, 'GetContractCollectionReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } listContractCollections = ( - args: ListContractCollectionsArgs, + req: ListContractCollectionsArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('ListContractCollections'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('ListContractCollections'), + createHttpRequest(JsonEncode(req, 'ListContractCollectionsArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - contractCollections: >_data.contractCollections, - collections: >_data.collections, - page: _data.page, - } + return JsonDecode(_data, 'ListContractCollectionsReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } updateContractCollection = ( - args: UpdateContractCollectionArgs, + req: UpdateContractCollectionArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('UpdateContractCollection'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('UpdateContractCollection'), + createHttpRequest(JsonEncode(req, 'UpdateContractCollectionArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - ok: _data.ok, - } + return JsonDecode(_data, 'UpdateContractCollectionReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } deleteContractCollection = ( - args: DeleteContractCollectionArgs, + req: DeleteContractCollectionArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('DeleteContractCollection'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('DeleteContractCollection'), + createHttpRequest(JsonEncode(req, 'DeleteContractCollectionArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - ok: _data.ok, - } + return JsonDecode(_data, 'DeleteContractCollectionReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - createToken = (args: CreateTokenArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('CreateToken'), createHTTPRequest(args, headers, signal)).then( + createToken = (req: CreateTokenArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('CreateToken'), + createHttpRequest(JsonEncode(req, 'CreateTokenArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - token: _data.token, - assets: >_data.assets, - } + return JsonDecode(_data, 'CreateTokenReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getToken = (args: GetTokenArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetToken'), createHTTPRequest(args, headers, signal)).then( + getToken = (req: GetTokenArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('GetToken'), createHttpRequest(JsonEncode(req, 'GetTokenArgs'), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - token: _data.token, - assets: >_data.assets, - } + return JsonDecode(_data, 'GetTokenReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - listTokens = (args: ListTokensArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('ListTokens'), createHTTPRequest(args, headers, signal)).then( + listTokens = (req: ListTokensArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('ListTokens'), + createHttpRequest(JsonEncode(req, 'ListTokensArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - tokens: >_data.tokens, - } + return JsonDecode(_data, 'ListTokensReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - updateToken = (args: UpdateTokenArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('UpdateToken'), createHTTPRequest(args, headers, signal)).then( + updateToken = (req: UpdateTokenArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('UpdateToken'), + createHttpRequest(JsonEncode(req, 'UpdateTokenArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - token: _data.token, - } + return JsonDecode(_data, 'UpdateTokenReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - deleteToken = (args: DeleteTokenArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('DeleteToken'), createHTTPRequest(args, headers, signal)).then( + deleteToken = (req: DeleteTokenArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('DeleteToken'), + createHttpRequest(JsonEncode(req, 'DeleteTokenArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'DeleteTokenReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - createAsset = (args: CreateAssetArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('CreateAsset'), createHTTPRequest(args, headers, signal)).then( + createAsset = (req: CreateAssetArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('CreateAsset'), + createHttpRequest(JsonEncode(req, 'CreateAssetArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - asset: _data.asset, - } + return JsonDecode(_data, 'CreateAssetReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getAsset = (args: GetAssetArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetAsset'), createHTTPRequest(args, headers, signal)).then( + getAsset = (req: GetAssetArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('GetAsset'), createHttpRequest(JsonEncode(req, 'GetAssetArgs'), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - asset: _data.asset, - } + return JsonDecode(_data, 'GetAssetReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - updateAsset = (args: UpdateAssetArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('UpdateAsset'), createHTTPRequest(args, headers, signal)).then( + updateAsset = (req: UpdateAssetArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('UpdateAsset'), + createHttpRequest(JsonEncode(req, 'UpdateAssetArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - asset: _data.asset, - } + return JsonDecode(_data, 'UpdateAssetReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - deleteAsset = (args: DeleteAssetArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('DeleteAsset'), createHTTPRequest(args, headers, signal)).then( + deleteAsset = (req: DeleteAssetArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('DeleteAsset'), + createHttpRequest(JsonEncode(req, 'DeleteAssetArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'DeleteAssetReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } } -export class Admin implements Admin { +export class Admin implements AdminClient { protected hostname: string protected fetch: Fetch protected path = '/rpc/Admin/' @@ -2030,243 +2260,294 @@ export class Admin implements Admin { return this.hostname + this.path + name } + queryKey = { + refreshContractInfoUpdatedBefore: (req: RefreshContractInfoUpdatedBeforeArgs) => + ['Admin', 'refreshContractInfoUpdatedBefore', req] as const, + refreshTokenMetadataUpdatedBefore: (req: RefreshTokenMetadataUpdatedBeforeArgs) => + ['Admin', 'refreshTokenMetadataUpdatedBefore', req] as const, + getContractInfoOverride: (req: GetContractInfoOverrideArgs) => ['Admin', 'getContractInfoOverride', req] as const, + getContractInfoOverrides: (req: GetContractInfoOverridesArgs) => + ['Admin', 'getContractInfoOverrides', req] as const, + addContractInfoOverride: (req: AddContractInfoOverrideArgs) => ['Admin', 'addContractInfoOverride', req] as const, + updateContractInfoOverride: (req: UpdateContractInfoOverrideArgs) => + ['Admin', 'updateContractInfoOverride', req] as const, + removeContractInfoOverride: (req: RemoveContractInfoOverrideArgs) => + ['Admin', 'removeContractInfoOverride', req] as const, + isInTokenDirectory: (req: IsInTokenDirectoryArgs) => ['Admin', 'isInTokenDirectory', req] as const, + setTokenDirectoryFeatureIndex: (req: SetTokenDirectoryFeatureIndexArgs) => + ['Admin', 'setTokenDirectoryFeatureIndex', req] as const, + addContractToTokenDirectory: (req: AddContractToTokenDirectoryArgs) => + ['Admin', 'addContractToTokenDirectory', req] as const, + removeContractFromTokenDirectory: (req: RemoveContractFromTokenDirectoryArgs) => + ['Admin', 'removeContractFromTokenDirectory', req] as const, + refreshTokenDirectory: () => ['Admin', 'refreshTokenDirectory'] as const, + } + refreshContractInfoUpdatedBefore = ( - args: RefreshContractInfoUpdatedBeforeArgs, + req: RefreshContractInfoUpdatedBeforeArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('RefreshContractInfoUpdatedBefore'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('RefreshContractInfoUpdatedBefore'), + createHttpRequest(JsonEncode(req, 'RefreshContractInfoUpdatedBeforeArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - taskIDs: >_data.taskIDs, - } + return JsonDecode(_data, 'RefreshContractInfoUpdatedBeforeReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } refreshTokenMetadataUpdatedBefore = ( - args: RefreshTokenMetadataUpdatedBeforeArgs, + req: RefreshTokenMetadataUpdatedBeforeArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('RefreshTokenMetadataUpdatedBefore'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('RefreshTokenMetadataUpdatedBefore'), + createHttpRequest(JsonEncode(req, 'RefreshTokenMetadataUpdatedBeforeArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - taskIDs: >_data.taskIDs, - } + return JsonDecode(_data, 'RefreshTokenMetadataUpdatedBeforeReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getContractInfoOverride = ( - args: GetContractInfoOverrideArgs, + req: GetContractInfoOverrideArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('GetContractInfoOverride'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('GetContractInfoOverride'), + createHttpRequest(JsonEncode(req, 'GetContractInfoOverrideArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - contractInfoOverride: _data.contractInfoOverride, - } + return JsonDecode(_data, 'GetContractInfoOverrideReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getContractInfoOverrides = ( - args: GetContractInfoOverridesArgs, + req: GetContractInfoOverridesArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('GetContractInfoOverrides'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('GetContractInfoOverrides'), + createHttpRequest(JsonEncode(req, 'GetContractInfoOverridesArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - contractInfoOverrides: >_data.contractInfoOverrides, - page: _data.page, - } + return JsonDecode(_data, 'GetContractInfoOverridesReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } addContractInfoOverride = ( - args: AddContractInfoOverrideArgs, + req: AddContractInfoOverrideArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('AddContractInfoOverride'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('AddContractInfoOverride'), + createHttpRequest(JsonEncode(req, 'AddContractInfoOverrideArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - ok: _data.ok, - } + return JsonDecode(_data, 'AddContractInfoOverrideReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } updateContractInfoOverride = ( - args: UpdateContractInfoOverrideArgs, + req: UpdateContractInfoOverrideArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('UpdateContractInfoOverride'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('UpdateContractInfoOverride'), + createHttpRequest(JsonEncode(req, 'UpdateContractInfoOverrideArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - ok: _data.ok, - } + return JsonDecode(_data, 'UpdateContractInfoOverrideReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } removeContractInfoOverride = ( - args: RemoveContractInfoOverrideArgs, + req: RemoveContractInfoOverrideArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('RemoveContractInfoOverride'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('RemoveContractInfoOverride'), + createHttpRequest(JsonEncode(req, 'RemoveContractInfoOverrideArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - ok: _data.ok, - } + return JsonDecode(_data, 'RemoveContractInfoOverrideReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } isInTokenDirectory = ( - args: IsInTokenDirectoryArgs, + req: IsInTokenDirectoryArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('IsInTokenDirectory'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('IsInTokenDirectory'), + createHttpRequest(JsonEncode(req, 'IsInTokenDirectoryArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - ok: _data.ok, - featureIndex: _data.featureIndex, - } + return JsonDecode(_data, 'IsInTokenDirectoryReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } setTokenDirectoryFeatureIndex = ( - args: SetTokenDirectoryFeatureIndexArgs, + req: SetTokenDirectoryFeatureIndexArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('SetTokenDirectoryFeatureIndex'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('SetTokenDirectoryFeatureIndex'), + createHttpRequest(JsonEncode(req, 'SetTokenDirectoryFeatureIndexArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - ok: _data.ok, - } + return JsonDecode(_data, 'SetTokenDirectoryFeatureIndexReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } addContractToTokenDirectory = ( - args: AddContractToTokenDirectoryArgs, + req: AddContractToTokenDirectoryArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('AddContractToTokenDirectory'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('AddContractToTokenDirectory'), + createHttpRequest(JsonEncode(req, 'AddContractToTokenDirectoryArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - ok: _data.ok, - } + return JsonDecode(_data, 'AddContractToTokenDirectoryReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } removeContractFromTokenDirectory = ( - args: RemoveContractFromTokenDirectoryArgs, + req: RemoveContractFromTokenDirectoryArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('RemoveContractFromTokenDirectory'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('RemoveContractFromTokenDirectory'), + createHttpRequest(JsonEncode(req, 'RemoveContractFromTokenDirectoryArgs'), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - ok: _data.ok, - } + return JsonDecode(_data, 'RemoveContractFromTokenDirectoryReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } refreshTokenDirectory = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('RefreshTokenDirectory'), createHTTPRequest({}, headers, signal)).then( + return this.fetch(this.url('RefreshTokenDirectory'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - taskID: _data.taskID, - } + return JsonDecode(_data, 'RefreshTokenDirectoryReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } } -const createHTTPRequest = (body: object = {}, headers: object = {}, signal: AbortSignal | null = null): object => { - const reqHeaders: { [key: string]: string } = { ...headers, 'Content-Type': 'application/json' } - reqHeaders[WebrpcHeader] = WebrpcHeaderValue - - return { - method: 'POST', - headers: reqHeaders, - body: JSON.stringify(body || {}), - signal, +const createHttpRequest = (body: string = '{}', headers: object = {}, signal: AbortSignal | null = null): object => { + const reqHeaders: { [key: string]: string } = { + ...headers, + 'Content-Type': 'application/json', + [WebrpcHeader]: WebrpcHeaderValue, } + return { method: 'POST', headers: reqHeaders, body, signal } } const buildResponse = (res: Response): Promise => { @@ -2275,13 +2556,9 @@ const buildResponse = (res: Response): Promise => { try { data = JSON.parse(text) } catch (error) { - let message = '' - if (error instanceof Error) { - message = error.message - } throw WebrpcBadResponseError.new({ status: res.status, - cause: `JSON.parse(): ${message}: response text: ${text}`, + cause: `JSON.parse(): ${error instanceof Error ? error.message : String(error)}: response text: ${text}`, }) } if (!res.ok) { @@ -2292,426 +2569,409 @@ const buildResponse = (res: Response): Promise => { }) } +export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise + +export const JsonEncode = (obj: T, _typ: string = ''): string => { + return JSON.stringify(obj) +} + +export const JsonDecode = (data: string | any, _typ: string = ''): T => { + let parsed: any = data + if (typeof data === 'string') { + try { + parsed = JSON.parse(data) + } catch (err) { + throw WebrpcBadResponseError.new({ cause: `JsonDecode: JSON.parse failed: ${(err as Error).message}` }) + } + } + return parsed as T +} + // // Errors // +type WebrpcErrorParams = { name?: string; code?: number; message?: string; status?: number; cause?: string } + export class WebrpcError extends Error { - name: string code: number - message: string status: number - cause?: string - - /** @deprecated Use message instead of msg. Deprecated in webrpc v0.11.0. */ - msg: string - - constructor(name: string, code: number, message: string, status: number, cause?: string) { - super(message) - this.name = name || 'WebrpcError' - this.code = typeof code === 'number' ? code : 0 - this.message = message || `endpoint error ${this.code}` - this.msg = this.message - this.status = typeof status === 'number' ? status : 0 - this.cause = cause + + constructor(error: WebrpcErrorParams = {}) { + super(error.message) + this.name = error.name || 'WebrpcEndpointError' + this.code = typeof error.code === 'number' ? error.code : 0 + this.message = error.message || `endpoint error` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcError.prototype) } static new(payload: any): WebrpcError { - return new this(payload.error, payload.code, payload.message || payload.msg, payload.status, payload.cause) + return new this({ message: payload.message, code: payload.code, status: payload.status, cause: payload.cause }) } } -// Webrpc errors - export class WebrpcEndpointError extends WebrpcError { - constructor( - name: string = 'WebrpcEndpoint', - code: number = 0, - message: string = `endpoint error`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcEndpoint' + this.code = typeof error.code === 'number' ? error.code : 0 + this.message = error.message || `endpoint error` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcEndpointError.prototype) } } export class WebrpcRequestFailedError extends WebrpcError { - constructor( - name: string = 'WebrpcRequestFailed', - code: number = -1, - message: string = `request failed`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcRequestFailed' + this.code = typeof error.code === 'number' ? error.code : -1 + this.message = error.message || `request failed` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcRequestFailedError.prototype) } } export class WebrpcBadRouteError extends WebrpcError { - constructor( - name: string = 'WebrpcBadRoute', - code: number = -2, - message: string = `bad route`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadRoute' + this.code = typeof error.code === 'number' ? error.code : -2 + this.message = error.message || `bad route` + this.status = typeof error.status === 'number' ? error.status : 404 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcBadRouteError.prototype) } } export class WebrpcBadMethodError extends WebrpcError { - constructor( - name: string = 'WebrpcBadMethod', - code: number = -3, - message: string = `bad method`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadMethod' + this.code = typeof error.code === 'number' ? error.code : -3 + this.message = error.message || `bad method` + this.status = typeof error.status === 'number' ? error.status : 405 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcBadMethodError.prototype) } } export class WebrpcBadRequestError extends WebrpcError { - constructor( - name: string = 'WebrpcBadRequest', - code: number = -4, - message: string = `bad request`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadRequest' + this.code = typeof error.code === 'number' ? error.code : -4 + this.message = error.message || `bad request` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcBadRequestError.prototype) } } export class WebrpcBadResponseError extends WebrpcError { - constructor( - name: string = 'WebrpcBadResponse', - code: number = -5, - message: string = `bad response`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadResponse' + this.code = typeof error.code === 'number' ? error.code : -5 + this.message = error.message || `bad response` + this.status = typeof error.status === 'number' ? error.status : 500 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcBadResponseError.prototype) } } export class WebrpcServerPanicError extends WebrpcError { - constructor( - name: string = 'WebrpcServerPanic', - code: number = -6, - message: string = `server panic`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcServerPanic' + this.code = typeof error.code === 'number' ? error.code : -6 + this.message = error.message || `server panic` + this.status = typeof error.status === 'number' ? error.status : 500 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcServerPanicError.prototype) } } export class WebrpcInternalErrorError extends WebrpcError { - constructor( - name: string = 'WebrpcInternalError', - code: number = -7, - message: string = `internal error`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcInternalError' + this.code = typeof error.code === 'number' ? error.code : -7 + this.message = error.message || `internal error` + this.status = typeof error.status === 'number' ? error.status : 500 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcInternalErrorError.prototype) } } -export class WebrpcClientDisconnectedError extends WebrpcError { - constructor( - name: string = 'WebrpcClientDisconnected', - code: number = -8, - message: string = `client disconnected`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcClientDisconnectedError.prototype) +export class WebrpcClientAbortedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcClientAborted' + this.code = typeof error.code === 'number' ? error.code : -8 + this.message = error.message || `request aborted by client` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcClientAbortedError.prototype) } } export class WebrpcStreamLostError extends WebrpcError { - constructor( - name: string = 'WebrpcStreamLost', - code: number = -9, - message: string = `stream lost`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcStreamLost' + this.code = typeof error.code === 'number' ? error.code : -9 + this.message = error.message || `stream lost` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcStreamLostError.prototype) } } export class WebrpcStreamFinishedError extends WebrpcError { - constructor( - name: string = 'WebrpcStreamFinished', - code: number = -10, - message: string = `stream finished`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcStreamFinished' + this.code = typeof error.code === 'number' ? error.code : -10 + this.message = error.message || `stream finished` + this.status = typeof error.status === 'number' ? error.status : 200 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcStreamFinishedError.prototype) } } +// // Schema errors +// export class UnauthorizedError extends WebrpcError { - constructor( - name: string = 'Unauthorized', - code: number = 1000, - message: string = `Unauthorized access`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Unauthorized' + this.code = typeof error.code === 'number' ? error.code : 1000 + this.message = error.message || `Unauthorized access` + this.status = typeof error.status === 'number' ? error.status : 401 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, UnauthorizedError.prototype) } } export class PermissionDeniedError extends WebrpcError { - constructor( - name: string = 'PermissionDenied', - code: number = 1001, - message: string = `Permission denied`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'PermissionDenied' + this.code = typeof error.code === 'number' ? error.code : 1001 + this.message = error.message || `Permission denied` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, PermissionDeniedError.prototype) } } export class SessionExpiredError extends WebrpcError { - constructor( - name: string = 'SessionExpired', - code: number = 1002, - message: string = `Session expired`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'SessionExpired' + this.code = typeof error.code === 'number' ? error.code : 1002 + this.message = error.message || `Session expired` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, SessionExpiredError.prototype) } } export class MethodNotFoundError extends WebrpcError { - constructor( - name: string = 'MethodNotFound', - code: number = 1003, - message: string = `Method not found`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'MethodNotFound' + this.code = typeof error.code === 'number' ? error.code : 1003 + this.message = error.message || `Method not found` + this.status = typeof error.status === 'number' ? error.status : 404 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, MethodNotFoundError.prototype) } } export class RequestConflictError extends WebrpcError { - constructor( - name: string = 'RequestConflict', - code: number = 1004, - message: string = `Conflict with target resource`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'RequestConflict' + this.code = typeof error.code === 'number' ? error.code : 1004 + this.message = error.message || `Conflict with target resource` + this.status = typeof error.status === 'number' ? error.status : 409 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, RequestConflictError.prototype) } } export class FailError extends WebrpcError { - constructor( - name: string = 'Fail', - code: number = 1005, - message: string = `Request Failed`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Fail' + this.code = typeof error.code === 'number' ? error.code : 1005 + this.message = error.message || `Request Failed` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, FailError.prototype) } } export class GeoblockedError extends WebrpcError { - constructor( - name: string = 'Geoblocked', - code: number = 1006, - message: string = `Geoblocked region`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Geoblocked' + this.code = typeof error.code === 'number' ? error.code : 1006 + this.message = error.message || `Geoblocked region` + this.status = typeof error.status === 'number' ? error.status : 451 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, GeoblockedError.prototype) } } export class TaskFailedError extends WebrpcError { - constructor( - name: string = 'TaskFailed', - code: number = 1007, - message: string = `Task failed`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'TaskFailed' + this.code = typeof error.code === 'number' ? error.code : 1007 + this.message = error.message || `Task failed` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, TaskFailedError.prototype) } } export class DeprecatedError extends WebrpcError { - constructor( - name: string = 'Deprecated', - code: number = 1008, - message: string = `RPC method is deprecated`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Deprecated' + this.code = typeof error.code === 'number' ? error.code : 1008 + this.message = error.message || `RPC method is deprecated` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, DeprecatedError.prototype) } } export class TimeoutError extends WebrpcError { - constructor( - name: string = 'Timeout', - code: number = 2000, - message: string = `Request timed out`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Timeout' + this.code = typeof error.code === 'number' ? error.code : 2000 + this.message = error.message || `Request timed out` + this.status = typeof error.status === 'number' ? error.status : 408 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, TimeoutError.prototype) } } export class InvalidArgumentError extends WebrpcError { - constructor( - name: string = 'InvalidArgument', - code: number = 2001, - message: string = `Invalid argument`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'InvalidArgument' + this.code = typeof error.code === 'number' ? error.code : 2001 + this.message = error.message || `Invalid argument` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, InvalidArgumentError.prototype) } } export class RequiredArgumentError extends WebrpcError { - constructor( - name: string = 'RequiredArgument', - code: number = 2002, - message: string = `Required argument missing`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'RequiredArgument' + this.code = typeof error.code === 'number' ? error.code : 2002 + this.message = error.message || `Required argument missing` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, RequiredArgumentError.prototype) } } export class QueryFailedError extends WebrpcError { - constructor( - name: string = 'QueryFailed', - code: number = 2003, - message: string = `Query failed`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'QueryFailed' + this.code = typeof error.code === 'number' ? error.code : 2003 + this.message = error.message || `Query failed` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, QueryFailedError.prototype) } } export class ValidationFailedError extends WebrpcError { - constructor( - name: string = 'ValidationFailed', - code: number = 2004, - message: string = `Validation failed`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'ValidationFailed' + this.code = typeof error.code === 'number' ? error.code : 2004 + this.message = error.message || `Validation failed` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, ValidationFailedError.prototype) } } export class RateLimitedError extends WebrpcError { - constructor( - name: string = 'RateLimited', - code: number = 2005, - message: string = `Rate limited`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'RateLimited' + this.code = typeof error.code === 'number' ? error.code : 2005 + this.message = error.message || `Rate limited` + this.status = typeof error.status === 'number' ? error.status : 429 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, RateLimitedError.prototype) } } export class NotFoundError extends WebrpcError { - constructor( - name: string = 'NotFound', - code: number = 3000, - message: string = `Resource not found`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'NotFound' + this.code = typeof error.code === 'number' ? error.code : 3000 + this.message = error.message || `Resource not found` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, NotFoundError.prototype) } } export class ProjectNotFoundError extends WebrpcError { - constructor( - name: string = 'ProjectNotFound', - code: number = 3002, - message: string = `Project not found`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'ProjectNotFound' + this.code = typeof error.code === 'number' ? error.code : 3002 + this.message = error.message || `Project not found` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, ProjectNotFoundError.prototype) } } export class ChainNotFoundError extends WebrpcError { - constructor( - name: string = 'ChainNotFound', - code: number = 3003, - message: string = `Chain not found`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'ChainNotFound' + this.code = typeof error.code === 'number' ? error.code : 3003 + this.message = error.message || `Chain not found` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, ChainNotFoundError.prototype) } } export class TokenDirectoryDisabledError extends WebrpcError { - constructor( - name: string = 'TokenDirectoryDisabled', - code: number = 4001, - message: string = `Token Directory is disabled`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'TokenDirectoryDisabled' + this.code = typeof error.code === 'number' ? error.code : 4001 + this.message = error.message || `Token Directory is disabled` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, TokenDirectoryDisabledError.prototype) } } @@ -2725,7 +2985,7 @@ export enum errors { WebrpcBadResponse = 'WebrpcBadResponse', WebrpcServerPanic = 'WebrpcServerPanic', WebrpcInternalError = 'WebrpcInternalError', - WebrpcClientDisconnected = 'WebrpcClientDisconnected', + WebrpcClientAborted = 'WebrpcClientAborted', WebrpcStreamLost = 'WebrpcStreamLost', WebrpcStreamFinished = 'WebrpcStreamFinished', Unauthorized = 'Unauthorized', @@ -2758,7 +3018,7 @@ export enum WebrpcErrorCodes { WebrpcBadResponse = -5, WebrpcServerPanic = -6, WebrpcInternalError = -7, - WebrpcClientDisconnected = -8, + WebrpcClientAborted = -8, WebrpcStreamLost = -9, WebrpcStreamFinished = -10, Unauthorized = 1000, @@ -2791,7 +3051,7 @@ export const webrpcErrorByCode: { [code: number]: any } = { [-5]: WebrpcBadResponseError, [-6]: WebrpcServerPanicError, [-7]: WebrpcInternalErrorError, - [-8]: WebrpcClientDisconnectedError, + [-8]: WebrpcClientAbortedError, [-9]: WebrpcStreamLostError, [-10]: WebrpcStreamFinishedError, [1000]: UnauthorizedError, @@ -2815,4 +3075,58 @@ export const webrpcErrorByCode: { [code: number]: any } = { [4001]: TokenDirectoryDisabledError, } -export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise +// +// Webrpc +// + +export const WebrpcHeader = 'Webrpc' + +export const WebrpcHeaderValue = 'webrpc@v0.31.0;gen-typescript@v0.22.5;sequence-metadata@v0.4.0' + +type WebrpcGenVersions = { + WebrpcGenVersion: string + codeGenName: string + codeGenVersion: string + schemaName: string + schemaVersion: string +} + +export function VersionFromHeader(headers: Headers): WebrpcGenVersions { + const headerValue = headers.get(WebrpcHeader) + if (!headerValue) { + return { + WebrpcGenVersion: '', + codeGenName: '', + codeGenVersion: '', + schemaName: '', + schemaVersion: '', + } + } + + return parseWebrpcGenVersions(headerValue) +} + +function parseWebrpcGenVersions(header: string): WebrpcGenVersions { + const versions = header.split(';') + if (versions.length < 3) { + return { + WebrpcGenVersion: '', + codeGenName: '', + codeGenVersion: '', + schemaName: '', + schemaVersion: '', + } + } + + const [_, WebrpcGenVersion] = versions[0]!.split('@') + const [codeGenName, codeGenVersion] = versions[1]!.split('@') + const [schemaName, schemaVersion] = versions[2]!.split('@') + + return { + WebrpcGenVersion: WebrpcGenVersion ?? '', + codeGenName: codeGenName ?? '', + codeGenVersion: codeGenVersion ?? '', + schemaName: schemaName ?? '', + schemaVersion: schemaVersion ?? '', + } +} diff --git a/packages/services/relayer/CHANGELOG.md b/packages/services/relayer/CHANGELOG.md index 2a3c09e46..321204d54 100644 --- a/packages/services/relayer/CHANGELOG.md +++ b/packages/services/relayer/CHANGELOG.md @@ -1,5 +1,317 @@ # @0xsequence/relayer +## 3.1.1 + +### Patch Changes + +- WDK fix +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.12 + +## 3.1.0 + +### Minor Changes + +- 55121af: Surface explicit sponsorship signal on `feeOptions` and an error marker on + `feeOptions` / `feeTokens`. + - `RpcRelayer.feeOptions` now returns `sponsored: boolean`, forwarded from the + server's `FeeOptionsReturn.sponsored`. The `Relayer` interface and all + bundled implementations (`RpcRelayer`, `SequenceRelayer`, `LocalRelayer`, + `EIP6963Relayer`, `PkRelayer`) carry the new field. + - When `feeOptions` swallows a transport / server error it now returns + `{ options: [], sponsored: false, failed: true }` (was `{ options: [] }`). + - When `feeTokens` swallows an error it now returns + `{ isFeeRequired: false, failed: true }` (was `{ isFeeRequired: false }`). + + These changes are additive — existing consumers that ignore the new fields are + unaffected. Consumers that classified sponsorship by "no fee option attached" + should migrate to `sponsored === true` to distinguish a real subsidy from a + swallowed `/FeeOptions` error. + +### Patch Changes + +- Fix for relayer sponsored fees +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.11 + +## 3.0.10 + +### Patch Changes + +- Minor network updates, relayer interface +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.10 + +## 3.0.9 + +### Patch Changes + +- Fee options fixes +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.9 + +## 3.0.8 + +### Patch Changes + +- Bug fix for relayer fee options handling +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.8 + +## 3.0.7 + +### Patch Changes + +- Minor bug fixes +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.7 + +## 3.0.6 + +### Patch Changes + +- userdata upgrade, arweave support +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.6 + +## 3.0.5 + +### Patch Changes + +- Account federation support +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.5 + +## 3.0.4 + +### Patch Changes + +- id-token login support +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.4 + +## 3.0.3 + +### Patch Changes + +- 3.0.3 +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.2 + +## 3.0.1 + +### Patch Changes + +- Network and session fixes +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.1 + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade +- Updated dependencies [f68be62] +- Updated dependencies [49d8a2f] +- Updated dependencies [3411232] +- Updated dependencies [23cb9e9] +- Updated dependencies [f5f6a7a] +- Updated dependencies [e7de3b1] +- Updated dependencies [493836f] +- Updated dependencies [30e1f1a] +- Updated dependencies [d5017e8] +- Updated dependencies [24a5fab] +- Updated dependencies [e5e1a03] +- Updated dependencies [0b63113] +- Updated dependencies [a89134a] +- Updated dependencies [7c6c811] +- Updated dependencies +- Updated dependencies [98ce38b] +- Updated dependencies [747e6b5] +- Updated dependencies [40c19ff] +- Updated dependencies [6d5de25] +- Updated dependencies [934acd1] + - @0xsequence/wallet-primitives@3.0.0 + +## 3.0.0-beta.19 + +### Patch Changes + +- Final RC before 3.0.0 +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.0-beta.19 + +## 3.0.0-beta.18 + +### Patch Changes + +- multicall3 optimization +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.0-beta.18 + +## 3.0.0-beta.17 + +### Patch Changes + +- New chains, relayer rpc fix +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.0-beta.17 + +## 3.0.0-beta.16 + +### Patch Changes + +- ethauth support +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.0-beta.16 + +## 3.0.0-beta.15 + +### Patch Changes + +- New chains, minor fixes +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.0-beta.15 + +## 3.0.0-beta.14 + +### Patch Changes + +- Relayer fee options fix +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.0-beta.14 + +## 3.0.0-beta.13 + +### Patch Changes + +- Userdata service updates +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.0-beta.13 + +## 3.0.0-beta.12 + +### Patch Changes + +- Beta release with dapp connector fixes +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.0-beta.12 + +## 3.0.0-beta.11 + +### Patch Changes + +- 3.0.0 beta +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.0-beta.11 + +## 3.0.0-beta.10 + +### Patch Changes + +- dapp-client updates +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.0-beta.10 + +## 3.0.0-beta.9 + +### Patch Changes + +- dapp client updates for EOA login +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.0-beta.9 + +## 3.0.0-beta.8 + +### Patch Changes + +- Apple auth fixes +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.0-beta.8 + +## 3.0.0-beta.7 + +### Patch Changes + +- Apple auth fix +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.0-beta.7 + +## 3.0.0-beta.6 + +### Patch Changes + +- Fix signer 404 error, minor fixes +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.0-beta.6 + +## 3.0.0-beta.5 + +### Patch Changes + +- Beta release for v3 +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.0-beta.5 + +## 3.0.0-beta.4 + +### Patch Changes + +- RC5 upgrade +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.0-beta.4 + +## 3.0.0-beta.3 + +### Patch Changes + +- 3.0.0-beta.3 with fixes +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.0-beta.3 + +## 3.0.0-beta.2 + +### Patch Changes + +- 3.0.0-beta.2 with identity instrument updates +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.0-beta.2 + +## 3.0.0-beta.1 + +### Patch Changes + +- 3.0.0-beta.1 +- Updated dependencies + - @0xsequence/wallet-primitives@3.0.0-beta.1 + ## 2.3.8 ### Patch Changes diff --git a/packages/services/relayer/eslint.config.js b/packages/services/relayer/eslint.config.js new file mode 100644 index 000000000..cecf89b03 --- /dev/null +++ b/packages/services/relayer/eslint.config.js @@ -0,0 +1,4 @@ +import { config as baseConfig } from "@repo/eslint-config/base" + +/** @type {import("eslint").Linter.Config} */ +export default baseConfig diff --git a/packages/services/relayer/package.json b/packages/services/relayer/package.json index be043ee98..e1be3b5da 100644 --- a/packages/services/relayer/package.json +++ b/packages/services/relayer/package.json @@ -1,13 +1,13 @@ { "name": "@0xsequence/relayer", - "version": "3.0.0", + "version": "3.1.1", "type": "module", "publishConfig": { "access": "public" }, "description": "relayer sub-package for Sequence", - "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/relayer", - "author": "Sequence Platforms Inc.", + "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/relayer", + "author": "Sequence Platforms ULC", "license": "Apache-2.0", "scripts": { "build": "tsc", @@ -17,7 +17,8 @@ "old-test:file": "NODE_OPTIONS='--import tsx' mocha --timeout 60000", "old-test:concurrently": "concurrently -k --success first 'pnpm start:hardhat > /dev/null' ", "start:hardhat": "pnpm hardhat node --port 9547", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "lint": "eslint . --max-warnings 0" }, "exports": { ".": { @@ -26,15 +27,16 @@ } }, "devDependencies": { + "@repo/eslint-config": "workspace:^", "@repo/typescript-config": "workspace:^", - "@types/node": "^22.15.29", - "typescript": "^5.8.3", - "vitest": "^3.2.4" + "@types/node": "^25.3.0", + "typescript": "^6.0.3", + "vitest": "^4.0.18" }, "dependencies": { "@0xsequence/wallet-primitives": "workspace:^", "mipd": "^0.0.7", - "ox": "^0.7.2", - "viem": "^2.37.8" + "ox": "^0.9.17", + "viem": "^2.40.3" } } diff --git a/packages/services/relayer/src/preconditions/codec.ts b/packages/services/relayer/src/preconditions/codec.ts index a6c2a13f7..b59fae6d6 100644 --- a/packages/services/relayer/src/preconditions/codec.ts +++ b/packages/services/relayer/src/preconditions/codec.ts @@ -10,12 +10,15 @@ import { Erc1155ApprovalPrecondition, } from './types.js' -export interface IntentPrecondition { +export interface TransactionPrecondition { type: string - data: string + chainId: number + ownerAddress: string + tokenAddress: string + minAmount: bigint } -export function decodePreconditions(preconditions: IntentPrecondition[]): Precondition[] { +export function decodePreconditions(preconditions: TransactionPrecondition[]): Precondition[] { const decodedPreconditions: Precondition[] = [] for (const p of preconditions) { @@ -28,78 +31,77 @@ export function decodePreconditions(preconditions: IntentPrecondition[]): Precon return decodedPreconditions } -export function decodePrecondition(p: IntentPrecondition): Precondition | undefined { +export function decodePrecondition(p: TransactionPrecondition): Precondition | undefined { if (!p) { return undefined } + if (typeof p.minAmount !== 'bigint') { + console.warn(`Failed to decode precondition: minAmount must be a bigint`) + return undefined + } + let precondition: Precondition | undefined try { - const data = JSON.parse(p.data) - switch (p.type) { case 'native-balance': - precondition = new NativeBalancePrecondition( - Address.from(data.address), - data.min ? BigInt(data.min) : undefined, - data.max ? BigInt(data.max) : undefined, - ) + precondition = new NativeBalancePrecondition(Address.from(p.ownerAddress), p.minAmount, undefined) break case 'erc20-balance': precondition = new Erc20BalancePrecondition( - Address.from(data.address), - Address.from(data.token), - data.min ? BigInt(data.min) : undefined, - data.max ? BigInt(data.max) : undefined, + Address.from(p.ownerAddress), + Address.from(p.tokenAddress), + p.minAmount, + undefined, ) break case 'erc20-approval': precondition = new Erc20ApprovalPrecondition( - Address.from(data.address), - Address.from(data.token), - Address.from(data.operator), - BigInt(data.min), + Address.from(p.ownerAddress), + Address.from(p.tokenAddress), + Address.from(p.ownerAddress), + p.minAmount, ) break case 'erc721-ownership': precondition = new Erc721OwnershipPrecondition( - Address.from(data.address), - Address.from(data.token), - BigInt(data.tokenId), - data.owned, + Address.from(p.ownerAddress), + Address.from(p.tokenAddress), + BigInt(0), + true, ) break case 'erc721-approval': precondition = new Erc721ApprovalPrecondition( - Address.from(data.address), - Address.from(data.token), - BigInt(data.tokenId), - Address.from(data.operator), + Address.from(p.ownerAddress), + Address.from(p.tokenAddress), + BigInt(0), + Address.from(p.ownerAddress), ) break case 'erc1155-balance': precondition = new Erc1155BalancePrecondition( - Address.from(data.address), - Address.from(data.token), - BigInt(data.tokenId), - data.min ? BigInt(data.min) : undefined, - data.max ? BigInt(data.max) : undefined, + Address.from(p.ownerAddress), + Address.from(p.tokenAddress), + BigInt(0), + p.minAmount, + undefined, ) break case 'erc1155-approval': precondition = new Erc1155ApprovalPrecondition( - Address.from(data.address), - Address.from(data.token), - BigInt(data.tokenId), - Address.from(data.operator), - BigInt(data.min), + Address.from(p.ownerAddress), + Address.from(p.tokenAddress), + BigInt(0), + Address.from(p.ownerAddress), + p.minAmount, ) break @@ -121,73 +123,92 @@ export function decodePrecondition(p: IntentPrecondition): Precondition | undefi } export function encodePrecondition(p: Precondition): string { - const data: any = {} - switch (p.type()) { case 'native-balance': { const native = p as NativeBalancePrecondition - data.address = native.address.toString() - if (native.min !== undefined) data.min = native.min.toString() - if (native.max !== undefined) data.max = native.max.toString() - break + const data = { + address: native.address.toString(), + ...(native.min !== undefined && { min: native.min.toString() }), + ...(native.max !== undefined && { max: native.max.toString() }), + } + + return JSON.stringify(data) } case 'erc20-balance': { const erc20 = p as Erc20BalancePrecondition - data.address = erc20.address.toString() - data.token = erc20.token.toString() - if (erc20.min !== undefined) data.min = erc20.min.toString() - if (erc20.max !== undefined) data.max = erc20.max.toString() - break + const data = { + address: erc20.address.toString(), + token: erc20.token.toString(), + ...(erc20.min !== undefined && { min: erc20.min.toString() }), + ...(erc20.max !== undefined && { max: erc20.max.toString() }), + } + + return JSON.stringify(data) } case 'erc20-approval': { const erc20 = p as Erc20ApprovalPrecondition - data.address = erc20.address.toString() - data.token = erc20.token.toString() - data.operator = erc20.operator.toString() - data.min = erc20.min.toString() - break + const data = { + address: erc20.address.toString(), + token: erc20.token.toString(), + operator: erc20.operator.toString(), + min: erc20.min.toString(), + } + + return JSON.stringify(data) } case 'erc721-ownership': { const erc721 = p as Erc721OwnershipPrecondition - data.address = erc721.address.toString() - data.token = erc721.token.toString() - data.tokenId = erc721.tokenId.toString() - if (erc721.owned !== undefined) data.owned = erc721.owned - break + const data = { + address: erc721.address.toString(), + token: erc721.token.toString(), + tokenId: erc721.tokenId.toString(), + ...(erc721.owned !== undefined && { owned: erc721.owned }), + } + + return JSON.stringify(data) } case 'erc721-approval': { const erc721 = p as Erc721ApprovalPrecondition - data.address = erc721.address.toString() - data.token = erc721.token.toString() - data.tokenId = erc721.tokenId.toString() - data.operator = erc721.operator.toString() - break + const data = { + address: erc721.address.toString(), + token: erc721.token.toString(), + tokenId: erc721.tokenId.toString(), + operator: erc721.operator.toString(), + } + + return JSON.stringify(data) } case 'erc1155-balance': { const erc1155 = p as Erc1155BalancePrecondition - data.address = erc1155.address.toString() - data.token = erc1155.token.toString() - data.tokenId = erc1155.tokenId.toString() - if (erc1155.min !== undefined) data.min = erc1155.min.toString() - if (erc1155.max !== undefined) data.max = erc1155.max.toString() - break + const data = { + address: erc1155.address.toString(), + token: erc1155.token.toString(), + tokenId: erc1155.tokenId.toString(), + ...(erc1155.min !== undefined && { min: erc1155.min.toString() }), + ...(erc1155.max !== undefined && { max: erc1155.max.toString() }), + } + + return JSON.stringify(data) } case 'erc1155-approval': { const erc1155 = p as Erc1155ApprovalPrecondition - data.address = erc1155.address.toString() - data.token = erc1155.token.toString() - data.tokenId = erc1155.tokenId.toString() - data.operator = erc1155.operator.toString() - data.min = erc1155.min.toString() - break + const data = { + address: erc1155.address.toString(), + token: erc1155.token.toString(), + tokenId: erc1155.tokenId.toString(), + operator: erc1155.operator.toString(), + min: erc1155.min.toString(), + } + + return JSON.stringify(data) } } - return JSON.stringify(data) + return JSON.stringify({}) } diff --git a/packages/services/relayer/src/preconditions/selectors.ts b/packages/services/relayer/src/preconditions/selectors.ts index 42b8cbb3b..d5985a862 100644 --- a/packages/services/relayer/src/preconditions/selectors.ts +++ b/packages/services/relayer/src/preconditions/selectors.ts @@ -1,20 +1,15 @@ import { Precondition, NativeBalancePrecondition, Erc20BalancePrecondition } from './types.js' -import { IntentPrecondition, decodePreconditions } from './codec.js' +import { TransactionPrecondition, decodePreconditions } from './codec.js' -export function extractChainID(precondition: IntentPrecondition): number | undefined { +export function extractChainID(precondition: TransactionPrecondition): number | undefined { if (!precondition) { return undefined } - try { - const data = JSON.parse(precondition.data) - return data.chainID ? Number(data.chainID) : undefined - } catch (e) { - return undefined - } + return precondition.chainId } -export function extractSupportedPreconditions(preconditions: IntentPrecondition[]): Precondition[] { +export function extractSupportedPreconditions(preconditions: TransactionPrecondition[]): Precondition[] { if (!preconditions || preconditions.length === 0) { return [] } @@ -22,7 +17,9 @@ export function extractSupportedPreconditions(preconditions: IntentPrecondition[ return decodePreconditions(preconditions) } -export function extractNativeBalancePreconditions(preconditions: IntentPrecondition[]): NativeBalancePrecondition[] { +export function extractNativeBalancePreconditions( + preconditions: TransactionPrecondition[], +): NativeBalancePrecondition[] { if (!preconditions || preconditions.length === 0) { return [] } @@ -31,7 +28,7 @@ export function extractNativeBalancePreconditions(preconditions: IntentPrecondit return decoded.filter((p): p is NativeBalancePrecondition => p.type() === 'native-balance') } -export function extractERC20BalancePreconditions(preconditions: IntentPrecondition[]): Erc20BalancePrecondition[] { +export function extractERC20BalancePreconditions(preconditions: TransactionPrecondition[]): Erc20BalancePrecondition[] { if (!preconditions || preconditions.length === 0) { return [] } diff --git a/packages/services/relayer/src/relayer/relayer.ts b/packages/services/relayer/src/relayer/relayer.ts index 3ed5a6962..d0c558082 100644 --- a/packages/services/relayer/src/relayer/relayer.ts +++ b/packages/services/relayer/src/relayer/relayer.ts @@ -11,13 +11,20 @@ export interface Relayer { isAvailable(wallet: Address.Address, chainId: number): Promise - feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: FeeToken[]; paymentAddress?: Address.Address }> + feeTokens(): Promise<{ + isFeeRequired: boolean + tokens?: FeeToken[] + paymentAddress?: Address.Address + failed?: boolean + }> feeOptions( wallet: Address.Address, chainId: number, + to: Address.Address, calls: Payload.Call[], - ): Promise<{ options: FeeOption[]; quote?: FeeQuote }> + data?: Hex.Hex, + ): Promise<{ options: FeeOption[]; quote?: FeeQuote; sponsored: boolean; failed?: boolean }> relay(to: Address.Address, data: Hex.Hex, chainId: number, quote?: FeeQuote): Promise<{ opHash: Hex.Hex }> @@ -26,8 +33,10 @@ export interface Relayer { checkPrecondition(precondition: Precondition.Precondition): Promise } -export function isRelayer(relayer: any): relayer is Relayer { +export function isRelayer(relayer: unknown): relayer is Relayer { return ( + typeof relayer === 'object' && + relayer !== null && 'isAvailable' in relayer && 'feeOptions' in relayer && 'relay' in relayer && diff --git a/packages/services/relayer/src/relayer/rpc-relayer/index.ts b/packages/services/relayer/src/relayer/rpc-relayer/index.ts index 0768490c9..05ba8be76 100644 --- a/packages/services/relayer/src/relayer/rpc-relayer/index.ts +++ b/packages/services/relayer/src/relayer/rpc-relayer/index.ts @@ -4,13 +4,22 @@ import { MetaTxn as RpcMetaTxn, FeeTokenType, FeeToken as RpcFeeToken, - IntentPrecondition, + TransactionPrecondition, ETHTxnStatus, } from './relayer.gen.js' -import { Address, Hex, Bytes, AbiFunction } from 'ox' +import { Address, Hex, AbiFunction } from 'ox' import { Constants, Payload, Network } from '@0xsequence/wallet-primitives' import { FeeOption, FeeQuote, OperationStatus, Relayer } from '../index.js' -import { decodePrecondition } from '../../preconditions/index.js' +import { + decodePrecondition, + Erc1155ApprovalPrecondition, + Erc1155BalancePrecondition, + Erc20ApprovalPrecondition, + Erc20BalancePrecondition, + Erc721ApprovalPrecondition, + Erc721OwnershipPrecondition, + NativeBalancePrecondition, +} from '../../preconditions/index.js' import { erc20BalanceOf, erc20Allowance, @@ -49,12 +58,14 @@ const networkToChain = (network: Network.Network): Chain => { }, } : undefined, - contracts: network.ensAddress - ? { - ensUniversalResolver: { - address: network.ensAddress as `0x${string}`, + contracts: network.contracts + ? Object.entries(network.contracts).reduce( + (acc, [name, address]) => { + acc[name] = { address } + return acc }, - } + {} as Record, + ) : undefined, } as Chain } @@ -67,7 +78,9 @@ export const getChain = (chainId: number): Chain => { } // Fall back to viem's built-in chains - const viemChain = Object.values(chains).find((c: any) => typeof c === 'object' && 'id' in c && c.id === chainId) + const viemChain = Object.values(chains).find( + (c: unknown) => typeof c === 'object' && c !== null && 'id' in c && c.id === chainId, + ) if (viemChain) { return viemChain as Chain } @@ -76,7 +89,7 @@ export const getChain = (chainId: number): Chain => { } export class RpcRelayer implements Relayer { - public readonly kind: 'relayer' = 'relayer' + public readonly kind = 'relayer' public readonly type = 'rpc' public readonly id: string public readonly chainId: number @@ -110,7 +123,12 @@ export class RpcRelayer implements Relayer { return Promise.resolve(this.chainId === chainId) } - async feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: RpcFeeToken[]; paymentAddress?: Address.Address }> { + async feeTokens(): Promise<{ + isFeeRequired: boolean + tokens?: RpcFeeToken[] + paymentAddress?: Address.Address + failed?: boolean + }> { try { const { isFeeRequired, tokens, paymentAddress } = await this.client.feeTokens() if (isFeeRequired) { @@ -127,24 +145,33 @@ export class RpcRelayer implements Relayer { } } catch (e) { console.warn('RpcRelayer.feeTokens failed:', e) - return { isFeeRequired: false } + return { isFeeRequired: false, failed: true } } } async feeOptions( wallet: Address.Address, chainId: number, + to: Address.Address, calls: Payload.Call[], - ): Promise<{ options: FeeOption[]; quote?: FeeQuote }> { - const callsStruct: Payload.Calls = { type: 'call', space: 0n, nonce: 0n, calls: calls } - const data = Payload.encode(callsStruct) + data?: Hex.Hex, + ): Promise<{ options: FeeOption[]; quote?: FeeQuote; sponsored: boolean; failed?: boolean }> { + // IMPORTANT: + // The relayer FeeOptions endpoint simulates `eth_call(to, data)`. + // Callers that already built a wallet transaction should pass its `to` and `data`. + // This is required for undeployed wallets because the transaction must target the + // guest module and include the deploy call before executing from the wallet. + const callsStruct: Payload.Calls = { type: 'call', space: 0n, nonce: 0n, calls } + + const feeOptionsTo = to + const feeOptionsData = data ?? Hex.fromBytes(Payload.encode(callsStruct, to)) try { const result = await this.client.feeOptions( { - wallet: wallet, - to: wallet, - data: Bytes.toHex(data), + wallet, + to: feeOptionsTo, + data: feeOptionsData, }, { ...(this.projectAccessKey ? { 'X-Access-Key': this.projectAccessKey } : undefined) }, ) @@ -160,10 +187,10 @@ export class RpcRelayer implements Relayer { gasLimit: option.gasLimit, })) - return { options, quote } + return { options, quote, sponsored: result.sponsored } } catch (e) { console.warn('RpcRelayer.feeOptions failed:', e) - return { options: [] } + return { options: [], sponsored: false, failed: true } } } @@ -173,7 +200,7 @@ export class RpcRelayer implements Relayer { data: Hex.Hex, chainId: number, quote?: FeeQuote, - preconditions?: IntentPrecondition[], + preconditions?: TransactionPrecondition[], ): Promise<{ opHash: Hex.Hex }> { console.log('sendMetaTxn', walletAddress, to, data, chainId, quote, preconditions) const rpcCall: RpcMetaTxn = { @@ -204,7 +231,7 @@ export class RpcRelayer implements Relayer { data: Hex.Hex, chainId: number, quote?: FeeQuote, - preconditions?: IntentPrecondition[], + preconditions?: TransactionPrecondition[], ): Promise<{ opHash: Hex.Hex }> { console.log('relay', to, data, chainId, quote, preconditions) const rpcCall: RpcMetaTxn = { @@ -230,7 +257,7 @@ export class RpcRelayer implements Relayer { return { opHash: `0x${result.txnHash}` } } - async status(opHash: Hex.Hex, chainId: number): Promise { + async status(opHash: Hex.Hex, _chainId: number): Promise { try { const cleanedOpHash = opHash.startsWith('0x') ? opHash.substring(2) : opHash const result = await this.client.getMetaTxnReceipt({ metaTxID: cleanedOpHash }) @@ -273,7 +300,7 @@ export class RpcRelayer implements Relayer { } } - async checkPrecondition(precondition: IntentPrecondition): Promise { + async checkPrecondition(precondition: TransactionPrecondition): Promise { const decoded = decodePrecondition(precondition) if (!decoded) { @@ -282,9 +309,9 @@ export class RpcRelayer implements Relayer { switch (decoded.type()) { case 'native-balance': { - const native = decoded as any + const native = decoded as NativeBalancePrecondition try { - const balance = await this.provider.getBalance({ address: native.address.toString() as `0x${string}` }) + const balance = await this.provider.getBalance({ address: native.address }) const minWei = native.min !== undefined ? BigInt(native.min) : undefined const maxWei = native.max !== undefined ? BigInt(native.max) : undefined @@ -307,9 +334,9 @@ export class RpcRelayer implements Relayer { } case 'erc20-balance': { - const erc20 = decoded as any + const erc20 = decoded as Erc20BalancePrecondition try { - const data = AbiFunction.encodeData(erc20BalanceOf, [erc20.address.toString()]) + const data = AbiFunction.encodeData(erc20BalanceOf, [erc20.address]) const result = await this.provider.call({ to: erc20.token.toString() as `0x${string}`, data: data as `0x${string}`, @@ -336,9 +363,9 @@ export class RpcRelayer implements Relayer { } case 'erc20-approval': { - const erc20 = decoded as any + const erc20 = decoded as Erc20ApprovalPrecondition try { - const data = AbiFunction.encodeData(erc20Allowance, [erc20.address.toString(), erc20.operator.toString()]) + const data = AbiFunction.encodeData(erc20Allowance, [erc20.address, erc20.operator]) const result = await this.provider.call({ to: erc20.token.toString() as `0x${string}`, data: data as `0x${string}`, @@ -353,12 +380,12 @@ export class RpcRelayer implements Relayer { } case 'erc721-ownership': { - const erc721 = decoded as any + const erc721 = decoded as Erc721OwnershipPrecondition try { const data = AbiFunction.encodeData(erc721OwnerOf, [erc721.tokenId]) const result = await this.provider.call({ - to: erc721.token.toString() as `0x${string}`, - data: data as `0x${string}`, + to: erc721.token, + data: data, }) const resultHex = result.toString() as `0x${string}` const owner = resultHex.slice(-40) @@ -372,7 +399,7 @@ export class RpcRelayer implements Relayer { } case 'erc721-approval': { - const erc721 = decoded as any + const erc721 = decoded as Erc721ApprovalPrecondition try { const data = AbiFunction.encodeData(erc721GetApproved, [erc721.tokenId]) const result = await this.provider.call({ @@ -389,9 +416,9 @@ export class RpcRelayer implements Relayer { } case 'erc1155-balance': { - const erc1155 = decoded as any + const erc1155 = decoded as Erc1155BalancePrecondition try { - const data = AbiFunction.encodeData(erc1155BalanceOf, [erc1155.address.toString(), erc1155.tokenId]) + const data = AbiFunction.encodeData(erc1155BalanceOf, [erc1155.address, erc1155.tokenId]) const result = await this.provider.call({ to: erc1155.token.toString() as `0x${string}`, data: data as `0x${string}`, @@ -418,15 +445,12 @@ export class RpcRelayer implements Relayer { } case 'erc1155-approval': { - const erc1155 = decoded as any + const erc1155 = decoded as Erc1155ApprovalPrecondition try { - const data = AbiFunction.encodeData(erc1155IsApprovedForAll, [ - erc1155.address.toString(), - erc1155.operator.toString(), - ]) + const data = AbiFunction.encodeData(erc1155IsApprovedForAll, [erc1155.address, erc1155.operator]) const result = await this.provider.call({ - to: erc1155.token.toString() as `0x${string}`, - data: data as `0x${string}`, + to: erc1155.token, + data: data, }) return BigInt(result.toString()) === 1n } catch (error) { diff --git a/packages/services/relayer/src/relayer/rpc-relayer/relayer.gen.ts b/packages/services/relayer/src/relayer/rpc-relayer/relayer.gen.ts index 0638fbe94..dd3630069 100644 --- a/packages/services/relayer/src/relayer/rpc-relayer/relayer.gen.ts +++ b/packages/services/relayer/src/relayer/rpc-relayer/relayer.gen.ts @@ -1,75 +1,243 @@ /* eslint-disable */ -// sequence-relayer v0.4.1 93ac61641d0351211680e2092673726b5c4f1848 +// sequence-relayer v0.4.1 43ed1a26ed167b22a0b0827a8725a0df6fbe6ab4 // -- -// Code generated by webrpc-gen@v0.26.0 with typescript generator. DO NOT EDIT. +// Code generated by Webrpc-gen@v0.32.2 with typescript generator. DO NOT EDIT. // -// webrpc-gen -schema=relayer.ridl -target=typescript -client -out=./clients/relayer.gen.ts +// webrpc-gen -schema=relayer.ridl -target=typescript -client -out=./clients/relayer.gen.ts -compat -export const WebrpcHeader = 'Webrpc' - -export const WebrpcHeaderValue = 'webrpc@v0.26.0;gen-typescript@v0.17.0;sequence-relayer@v0.4.1' - -// WebRPC description and code-gen version -export const WebRPCVersion = 'v1' +// Webrpc description and code-gen version +export const WebrpcVersion = 'v1' // Schema version of your RIDL schema -export const WebRPCSchemaVersion = 'v0.4.1' +export const WebrpcSchemaVersion = 'v0.4.1' // Schema hash generated from your RIDL schema -export const WebRPCSchemaHash = '93ac61641d0351211680e2092673726b5c4f1848' +export const WebrpcSchemaHash = '43ed1a26ed167b22a0b0827a8725a0df6fbe6ab4' -type WebrpcGenVersions = { - webrpcGenVersion: string - codeGenName: string - codeGenVersion: string - schemaName: string - schemaVersion: string -} +// +// Client interface +// -export function VersionFromHeader(headers: Headers): WebrpcGenVersions { - const headerValue = headers.get(WebrpcHeader) - if (!headerValue) { - return { - webrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } +export interface RelayerClient { + ping(headers?: object, signal?: AbortSignal): Promise - return parseWebrpcGenVersions(headerValue) -} + version(headers?: object, signal?: AbortSignal): Promise -function parseWebrpcGenVersions(header: string): WebrpcGenVersions { - const versions = header.split(';') - if (versions.length < 3) { - return { - webrpcGenVersion: '', - codeGenName: '', - codeGenVersion: '', - schemaName: '', - schemaVersion: '', - } - } + runtimeStatus(headers?: object, signal?: AbortSignal): Promise - const [_, webrpcGenVersion] = versions[0]!.split('@') - const [codeGenName, codeGenVersion] = versions[1]!.split('@') - const [schemaName, schemaVersion] = versions[2]!.split('@') + getSequenceContext(headers?: object, signal?: AbortSignal): Promise - return { - webrpcGenVersion: webrpcGenVersion ?? '', - codeGenName: codeGenName ?? '', - codeGenVersion: codeGenVersion ?? '', - schemaName: schemaName ?? '', - schemaVersion: schemaVersion ?? '', - } + getChainID(headers?: object, signal?: AbortSignal): Promise + + /** + * + * Transactions + * + * TODO (future): rename this to just, 'SendTransaction(txn: MetaTransaction)' or 'SendTransaction(txn: SignedTransaction)', or something.. + * Project ID is only used by service and admin calls. Other clients must have projectID passed via the context + * TODO: rename return txnHash: string to metaTxnID: string + */ + sendMetaTxn(req: SendMetaTxnArgs, headers?: object, signal?: AbortSignal): Promise + + getMetaTxnNonce(req: GetMetaTxnNonceArgs, headers?: object, signal?: AbortSignal): Promise + + /** + * TODO: one day, make GetMetaTxnReceipt respond immediately with receipt or not + * and add WaitTransactionReceipt method, which will block and wait, similar to how GetMetaTxnReceipt + * is implemented now. + * For backwards compat, we can leave the current GetMetaTxnReceipt how it is, an deprecate it, and introduce + * new, GetTransactionReceipt and WaitTransactionReceipt methods + * we can also accept metaTxnId and txnHash .. so can take either or.. I wonder if ERC-4337 has any convention on this? + */ + getMetaTxnReceipt( + req: GetMetaTxnReceiptArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + simulate(req: SimulateArgs, headers?: object, signal?: AbortSignal): Promise + + simulateV3(req: SimulateV3Args, headers?: object, signal?: AbortSignal): Promise + + /** + * TODO: deprecated, to be removed by https://github.com/0xsequence/stack/pull/356 at a later date + */ + updateMetaTxnGasLimits( + req: UpdateMetaTxnGasLimitsArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + feeTokens(headers?: object, signal?: AbortSignal): Promise + + feeOptions(req: FeeOptionsArgs, headers?: object, signal?: AbortSignal): Promise + + /** + * Bridge gas endpoints for S2S calls + * Used for bridge fees (e.g., LayerZero messaging fees) that require msg.value to be fronted at runtime. + * bridgeGas will be included in fee calculation so the relayer gets reimbursed. + */ + sendMetaTxnWithBridgeGas( + req: SendMetaTxnWithBridgeGasArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + feeOptionsWithBridgeGas( + req: FeeOptionsWithBridgeGasArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * TODO: deprecated, to be removed by https://github.com/0xsequence/stack/pull/356 at a later date + */ + getMetaTxnNetworkFeeOptions( + req: GetMetaTxnNetworkFeeOptionsArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * + * Sender administration + * + */ + startSender(req: StartSenderArgs, headers?: object, signal?: AbortSignal): Promise + + stopSender(req: StopSenderArgs, headers?: object, signal?: AbortSignal): Promise + + repairSender(req: RepairSenderArgs, headers?: object, signal?: AbortSignal): Promise + + getMetaTransactions( + req: GetMetaTransactionsArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + getTransactionCost( + req: GetTransactionCostArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * Legacy Gas Tank + */ + getGasTank(req: GetGasTankArgs, headers?: object, signal?: AbortSignal): Promise + + addGasTank(req: AddGasTankArgs, headers?: object, signal?: AbortSignal): Promise + + updateGasTank(req: UpdateGasTankArgs, headers?: object, signal?: AbortSignal): Promise + + /** + * Legacy Gas Adjustment + */ + nextGasTankBalanceAdjustmentNonce( + req: NextGasTankBalanceAdjustmentNonceArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + adjustGasTankBalance( + req: AdjustGasTankBalanceArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + getGasTankBalanceAdjustment( + req: GetGasTankBalanceAdjustmentArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + listGasTankBalanceAdjustments( + req: ListGasTankBalanceAdjustmentsArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * Project-Level Gas Sponsorship + */ + listGasSponsors(req: ListGasSponsorsArgs, headers?: object, signal?: AbortSignal): Promise + + getGasSponsor(req: GetGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise + + addGasSponsor(req: AddGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise + + updateGasSponsor(req: UpdateGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise + + removeGasSponsor(req: RemoveGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise + + /** + * Ecosystem-level Gas Sponsorship + */ + listEcosystemGasSponsors( + req: ListEcosystemGasSponsorsArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + getEcosystemGasSponsor( + req: GetEcosystemGasSponsorArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + addEcosystemGasSponsor( + req: AddEcosystemGasSponsorArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + updateEcosystemGasSponsor( + req: UpdateEcosystemGasSponsorArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + removeEcosystemGasSponsor( + req: RemoveEcosystemGasSponsorArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * Gas Sponsor Lookup + */ + addressGasSponsors( + req: AddressGasSponsorsArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + /** + * Project Balance + */ + getProjectBalance( + req: GetProjectBalanceArgs, + headers?: object, + signal?: AbortSignal, + ): Promise + + adjustProjectBalance( + req: AdjustProjectBalanceArgs, + headers?: object, + signal?: AbortSignal, + ): Promise } // -// Types +// Schema types // +export enum RepairOperation { + SKIP = 'SKIP', + REQUEUE = 'REQUEUE', + DROP = 'DROP', +} + export enum ETHTxnStatus { UNKNOWN = 'UNKNOWN', DROPPED = 'DROPPED', @@ -79,6 +247,7 @@ export enum ETHTxnStatus { PARTIALLY_FAILED = 'PARTIALLY_FAILED', FAILED = 'FAILED', PENDING_PRECONDITION = 'PENDING_PRECONDITION', + MINED = 'MINED', } export enum TransferType { @@ -105,7 +274,7 @@ export enum FeeTokenType { ERC1155_TOKEN = 'ERC1155_TOKEN', } -export enum SortOrder { +export enum Order { DESC = 'DESC', ASC = 'ASC', } @@ -134,7 +303,31 @@ export interface SenderStatus { index: number address: string etherBalance: number + enabled: boolean active: boolean + nonce?: NonceStatus + current?: CurrentStatus +} + +export interface NonceStatus { + chain: number + mempool: number +} + +export interface CurrentStatus { + transaction: string + first: TransactionStatus + latest?: TransactionStatus +} + +export interface TransactionStatus { + transaction: string + gas: number + gasPrice: string + priorityFee: string + time: string + age: string + error?: string } export interface RuntimeChecks {} @@ -171,6 +364,7 @@ export interface GasSponsor { id: number gasTankId: number projectId: number + ecosystemId: number chainId: number address: string name: string @@ -196,6 +390,14 @@ export interface MetaTxn { walletAddress: string contract: string input: string + authorization?: EIP7702Authorization +} + +export interface EIP7702Authorization { + chainId: number + implementation: string + nonce: number + signature: string } export interface MetaTxnLog { @@ -213,6 +415,7 @@ export interface MetaTxnLog { minedAt: string target: string input: string + bridgeGas?: string txnArgs: { [key: string]: any } txnReceipt?: { [key: string]: any } walletAddress: string @@ -250,20 +453,10 @@ export interface MetaTxnReceiptLog { data: string } -export interface IntentPrecondition { - type: string - chainId: string - data: any -} - -export interface IntentSolution { - transactions: Array -} - export interface Transactions { chainID: string transactions: Array - preconditions?: Array + preconditions?: Array } export interface Transaction { @@ -275,6 +468,14 @@ export interface Transaction { data: string } +export interface TransactionPrecondition { + type: string + chainId: number + ownerAddress: string + tokenAddress: string + minAmount: bigint +} + export interface TxnLogUser { username: string } @@ -288,11 +489,6 @@ export interface TxnLogTransfer { amounts: Array } -export interface SentTransactionsFilter { - pending?: boolean - failed?: boolean -} - export interface SimulateResult { executed: boolean succeeded: boolean @@ -329,151 +525,16 @@ export interface FeeToken { } export interface Page { - pageSize?: number - page?: number - more?: boolean - totalRecords?: number - column?: string - before?: any - after?: any - sort?: Array -} - -export interface SortBy { + pageSize: number + page: number + more: boolean column: string - order: SortOrder + sort: Array } -export interface Relayer { - ping(headers?: object, signal?: AbortSignal): Promise - version(headers?: object, signal?: AbortSignal): Promise - runtimeStatus(headers?: object, signal?: AbortSignal): Promise - getSequenceContext(headers?: object, signal?: AbortSignal): Promise - getChainID(headers?: object, signal?: AbortSignal): Promise - /** - * - * Transactions - * - * TODO (future): rename this to just, 'SendTransaction(txn: MetaTransaction)' or 'SendTransaction(txn: SignedTransaction)', or something.. - * Project ID is only used by service and admin calls. Other clients must have projectID passed via the context - * TODO: rename return txnHash: string to metaTxnID: string - */ - sendMetaTxn(args: SendMetaTxnArgs, headers?: object, signal?: AbortSignal): Promise - getMetaTxnNonce(args: GetMetaTxnNonceArgs, headers?: object, signal?: AbortSignal): Promise - /** - * TODO: one day, make GetMetaTxnReceipt respond immediately with receipt or not - * and add WaitTransactionReceipt method, which will block and wait, similar to how GetMetaTxnReceipt - * is implemented now. - * For backwards compat, we can leave the current GetMetaTxnReceipt how it is, an deprecate it, and introduce - * new, GetTransactionReceipt and WaitTransactionReceipt methods - * we can also accept metaTxnId and txnHash .. so can take either or.. I wonder if ERC-4337 has any convention on this? - */ - getMetaTxnReceipt( - args: GetMetaTxnReceiptArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - simulate(args: SimulateArgs, headers?: object, signal?: AbortSignal): Promise - simulateV3(args: SimulateV3Args, headers?: object, signal?: AbortSignal): Promise - /** - * TODO: deprecated, to be removed by https://github.com/0xsequence/stack/pull/356 at a later date - */ - updateMetaTxnGasLimits( - args: UpdateMetaTxnGasLimitsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - feeTokens(headers?: object, signal?: AbortSignal): Promise - feeOptions(args: FeeOptionsArgs, headers?: object, signal?: AbortSignal): Promise - /** - * TODO: deprecated, to be removed by https://github.com/0xsequence/stack/pull/356 at a later date - */ - getMetaTxnNetworkFeeOptions( - args: GetMetaTxnNetworkFeeOptionsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getMetaTransactions( - args: GetMetaTransactionsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getTransactionCost( - args: GetTransactionCostArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * Sent transactions from an account. If filter is omitted then it will return all transactions. - */ - sentTransactions(args: SentTransactionsArgs, headers?: object, signal?: AbortSignal): Promise - /** - * Pending transactions waiting to be mined for an account. This endpoint is just a sugar of `SentTransactions` - * with the filter set to pending: true. - */ - pendingTransactions( - args: PendingTransactionsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * Legacy Gas Tank - */ - getGasTank(args: GetGasTankArgs, headers?: object, signal?: AbortSignal): Promise - addGasTank(args: AddGasTankArgs, headers?: object, signal?: AbortSignal): Promise - updateGasTank(args: UpdateGasTankArgs, headers?: object, signal?: AbortSignal): Promise - /** - * Legacy Gas Adjustment - */ - nextGasTankBalanceAdjustmentNonce( - args: NextGasTankBalanceAdjustmentNonceArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - adjustGasTankBalance( - args: AdjustGasTankBalanceArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - getGasTankBalanceAdjustment( - args: GetGasTankBalanceAdjustmentArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - listGasTankBalanceAdjustments( - args: ListGasTankBalanceAdjustmentsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * Gas Sponsorship - */ - listGasSponsors(args: ListGasSponsorsArgs, headers?: object, signal?: AbortSignal): Promise - getGasSponsor(args: GetGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise - addGasSponsor(args: AddGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise - updateGasSponsor(args: UpdateGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise - removeGasSponsor(args: RemoveGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise - /** - * Gas Sponsor Lookup - */ - addressGasSponsors( - args: AddressGasSponsorsArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - /** - * Project Balance - */ - getProjectBalance( - args: GetProjectBalanceArgs, - headers?: object, - signal?: AbortSignal, - ): Promise - adjustProjectBalance( - args: AdjustProjectBalanceArgs, - headers?: object, - signal?: AbortSignal, - ): Promise +export interface Sort { + column: string + order: Order } export interface PingArgs {} @@ -481,37 +542,43 @@ export interface PingArgs {} export interface PingReturn { status: boolean } + export interface VersionArgs {} export interface VersionReturn { version: Version } + export interface RuntimeStatusArgs {} export interface RuntimeStatusReturn { status: RuntimeStatus } + export interface GetSequenceContextArgs {} export interface GetSequenceContextReturn { data: SequenceContext } + export interface GetChainIDArgs {} export interface GetChainIDReturn { chainID: number } + export interface SendMetaTxnArgs { call: MetaTxn quote?: string projectID?: number - preconditions?: Array + preconditions?: Array } export interface SendMetaTxnReturn { status: boolean txnHash: string } + export interface GetMetaTxnNonceArgs { walletContractAddress: string space?: string @@ -520,13 +587,16 @@ export interface GetMetaTxnNonceArgs { export interface GetMetaTxnNonceReturn { nonce: string } + export interface GetMetaTxnReceiptArgs { metaTxID: string } export interface GetMetaTxnReceiptReturn { receipt: MetaTxnReceipt + metaTxnEnqueued: boolean } + export interface SimulateArgs { wallet: string transactions: string @@ -535,6 +605,7 @@ export interface SimulateArgs { export interface SimulateReturn { results: Array } + export interface SimulateV3Args { wallet: string calls: string @@ -543,6 +614,7 @@ export interface SimulateV3Args { export interface SimulateV3Return { results: Array } + export interface UpdateMetaTxnGasLimitsArgs { walletAddress: string walletConfig: any @@ -552,6 +624,7 @@ export interface UpdateMetaTxnGasLimitsArgs { export interface UpdateMetaTxnGasLimitsReturn { payload: string } + export interface FeeTokensArgs {} export interface FeeTokensReturn { @@ -559,11 +632,13 @@ export interface FeeTokensReturn { tokens: Array paymentAddress: string } + export interface FeeOptionsArgs { wallet: string to: string data: string simulate?: boolean + authorization?: EIP7702Authorization } export interface FeeOptionsReturn { @@ -571,6 +646,35 @@ export interface FeeOptionsReturn { sponsored: boolean quote?: string } + +export interface SendMetaTxnWithBridgeGasArgs { + call: MetaTxn + quote?: string + projectID?: number + bridgeGas: string + preconditions?: Array +} + +export interface SendMetaTxnWithBridgeGasReturn { + status: boolean + txnHash: string +} + +export interface FeeOptionsWithBridgeGasArgs { + wallet: string + to: string + data: string + simulate?: boolean + bridgeGas: string + authorization?: EIP7702Authorization +} + +export interface FeeOptionsWithBridgeGasReturn { + options: Array + sponsored: boolean + quote?: string +} + export interface GetMetaTxnNetworkFeeOptionsArgs { walletConfig: any payload: string @@ -579,15 +683,38 @@ export interface GetMetaTxnNetworkFeeOptionsArgs { export interface GetMetaTxnNetworkFeeOptionsReturn { options: Array } + +export interface StartSenderArgs { + sender: number +} + +export interface StartSenderReturn {} + +export interface StopSenderArgs { + sender: number +} + +export interface StopSenderReturn {} + +export interface RepairSenderArgs { + sender: number + nonce: number + operation: RepairOperation +} + +export interface RepairSenderReturn {} + export interface GetMetaTransactionsArgs { projectId: number page?: Page + includeNonSponsored?: boolean } export interface GetMetaTransactionsReturn { page: Page transactions: Array } + export interface GetTransactionCostArgs { projectId: number from: string @@ -597,23 +724,7 @@ export interface GetTransactionCostArgs { export interface GetTransactionCostReturn { cost: number } -export interface SentTransactionsArgs { - filter?: SentTransactionsFilter - page?: Page -} -export interface SentTransactionsReturn { - page: Page - transactions: Array -} -export interface PendingTransactionsArgs { - page?: Page -} - -export interface PendingTransactionsReturn { - page: Page - transactions: Array -} export interface GetGasTankArgs { id: number } @@ -621,6 +732,7 @@ export interface GetGasTankArgs { export interface GetGasTankReturn { gasTank: GasTank } + export interface AddGasTankArgs { name: string feeMarkupFactor: number @@ -631,6 +743,7 @@ export interface AddGasTankReturn { status: boolean gasTank: GasTank } + export interface UpdateGasTankArgs { id: number name?: string @@ -642,6 +755,7 @@ export interface UpdateGasTankReturn { status: boolean gasTank: GasTank } + export interface NextGasTankBalanceAdjustmentNonceArgs { id: number } @@ -649,6 +763,7 @@ export interface NextGasTankBalanceAdjustmentNonceArgs { export interface NextGasTankBalanceAdjustmentNonceReturn { nonce: number } + export interface AdjustGasTankBalanceArgs { id: number nonce: number @@ -659,6 +774,7 @@ export interface AdjustGasTankBalanceReturn { status: boolean adjustment: GasTankBalanceAdjustment } + export interface GetGasTankBalanceAdjustmentArgs { id: number nonce: number @@ -667,6 +783,7 @@ export interface GetGasTankBalanceAdjustmentArgs { export interface GetGasTankBalanceAdjustmentReturn { adjustment: GasTankBalanceAdjustment } + export interface ListGasTankBalanceAdjustmentsArgs { id: number page?: Page @@ -676,6 +793,7 @@ export interface ListGasTankBalanceAdjustmentsReturn { page: Page adjustments: Array } + export interface ListGasSponsorsArgs { projectId: number page?: Page @@ -685,6 +803,7 @@ export interface ListGasSponsorsReturn { page: Page gasSponsors: Array } + export interface GetGasSponsorArgs { projectId: number id: number @@ -693,6 +812,7 @@ export interface GetGasSponsorArgs { export interface GetGasSponsorReturn { gasSponsor: GasSponsor } + export interface AddGasSponsorArgs { projectId: number address: string @@ -704,6 +824,7 @@ export interface AddGasSponsorReturn { status: boolean gasSponsor: GasSponsor } + export interface UpdateGasSponsorArgs { projectId: number id: number @@ -715,6 +836,7 @@ export interface UpdateGasSponsorReturn { status: boolean gasSponsor: GasSponsor } + export interface RemoveGasSponsorArgs { projectId: number id: number @@ -723,6 +845,59 @@ export interface RemoveGasSponsorArgs { export interface RemoveGasSponsorReturn { status: boolean } + +export interface ListEcosystemGasSponsorsArgs { + ecosystemId: number + page?: Page +} + +export interface ListEcosystemGasSponsorsReturn { + page: Page + gasSponsors: Array +} + +export interface GetEcosystemGasSponsorArgs { + ecosystemId: number + id: number +} + +export interface GetEcosystemGasSponsorReturn { + gasSponsor: GasSponsor +} + +export interface AddEcosystemGasSponsorArgs { + ecosystemId: number + address: string + name?: string + active?: boolean +} + +export interface AddEcosystemGasSponsorReturn { + status: boolean + gasSponsor: GasSponsor +} + +export interface UpdateEcosystemGasSponsorArgs { + ecosystemId: number + id: number + name?: string + active?: boolean +} + +export interface UpdateEcosystemGasSponsorReturn { + status: boolean + gasSponsor: GasSponsor +} + +export interface RemoveEcosystemGasSponsorArgs { + ecosystemId: number + id: number +} + +export interface RemoveEcosystemGasSponsorReturn { + status: boolean +} + export interface AddressGasSponsorsArgs { address: string page?: Page @@ -732,6 +907,7 @@ export interface AddressGasSponsorsReturn { page: Page gasSponsors: Array } + export interface GetProjectBalanceArgs { projectId: number } @@ -739,6 +915,7 @@ export interface GetProjectBalanceArgs { export interface GetProjectBalanceReturn { balance: number } + export interface AdjustProjectBalanceArgs { projectId: number amount: number @@ -752,7 +929,8 @@ export interface AdjustProjectBalanceReturn { // // Client // -export class Relayer implements Relayer { + +export class Relayer implements RelayerClient { protected hostname: string protected fetch: Fetch protected path = '/rpc/Relayer/' @@ -766,600 +944,785 @@ export class Relayer implements Relayer { return this.hostname + this.path + name } + queryKey = { + ping: () => ['Relayer', 'ping'] as const, + version: () => ['Relayer', 'version'] as const, + runtimeStatus: () => ['Relayer', 'runtimeStatus'] as const, + getSequenceContext: () => ['Relayer', 'getSequenceContext'] as const, + getChainID: () => ['Relayer', 'getChainID'] as const, + sendMetaTxn: (req: SendMetaTxnArgs) => ['Relayer', 'sendMetaTxn', req] as const, + getMetaTxnNonce: (req: GetMetaTxnNonceArgs) => ['Relayer', 'getMetaTxnNonce', req] as const, + getMetaTxnReceipt: (req: GetMetaTxnReceiptArgs) => ['Relayer', 'getMetaTxnReceipt', req] as const, + simulate: (req: SimulateArgs) => ['Relayer', 'simulate', req] as const, + simulateV3: (req: SimulateV3Args) => ['Relayer', 'simulateV3', req] as const, + updateMetaTxnGasLimits: (req: UpdateMetaTxnGasLimitsArgs) => ['Relayer', 'updateMetaTxnGasLimits', req] as const, + feeTokens: () => ['Relayer', 'feeTokens'] as const, + feeOptions: (req: FeeOptionsArgs) => ['Relayer', 'feeOptions', req] as const, + sendMetaTxnWithBridgeGas: (req: SendMetaTxnWithBridgeGasArgs) => + ['Relayer', 'sendMetaTxnWithBridgeGas', req] as const, + feeOptionsWithBridgeGas: (req: FeeOptionsWithBridgeGasArgs) => ['Relayer', 'feeOptionsWithBridgeGas', req] as const, + getMetaTxnNetworkFeeOptions: (req: GetMetaTxnNetworkFeeOptionsArgs) => + ['Relayer', 'getMetaTxnNetworkFeeOptions', req] as const, + startSender: (req: StartSenderArgs) => ['Relayer', 'startSender', req] as const, + stopSender: (req: StopSenderArgs) => ['Relayer', 'stopSender', req] as const, + repairSender: (req: RepairSenderArgs) => ['Relayer', 'repairSender', req] as const, + getMetaTransactions: (req: GetMetaTransactionsArgs) => ['Relayer', 'getMetaTransactions', req] as const, + getTransactionCost: (req: GetTransactionCostArgs) => ['Relayer', 'getTransactionCost', req] as const, + getGasTank: (req: GetGasTankArgs) => ['Relayer', 'getGasTank', req] as const, + addGasTank: (req: AddGasTankArgs) => ['Relayer', 'addGasTank', req] as const, + updateGasTank: (req: UpdateGasTankArgs) => ['Relayer', 'updateGasTank', req] as const, + nextGasTankBalanceAdjustmentNonce: (req: NextGasTankBalanceAdjustmentNonceArgs) => + ['Relayer', 'nextGasTankBalanceAdjustmentNonce', req] as const, + adjustGasTankBalance: (req: AdjustGasTankBalanceArgs) => ['Relayer', 'adjustGasTankBalance', req] as const, + getGasTankBalanceAdjustment: (req: GetGasTankBalanceAdjustmentArgs) => + ['Relayer', 'getGasTankBalanceAdjustment', req] as const, + listGasTankBalanceAdjustments: (req: ListGasTankBalanceAdjustmentsArgs) => + ['Relayer', 'listGasTankBalanceAdjustments', req] as const, + listGasSponsors: (req: ListGasSponsorsArgs) => ['Relayer', 'listGasSponsors', req] as const, + getGasSponsor: (req: GetGasSponsorArgs) => ['Relayer', 'getGasSponsor', req] as const, + addGasSponsor: (req: AddGasSponsorArgs) => ['Relayer', 'addGasSponsor', req] as const, + updateGasSponsor: (req: UpdateGasSponsorArgs) => ['Relayer', 'updateGasSponsor', req] as const, + removeGasSponsor: (req: RemoveGasSponsorArgs) => ['Relayer', 'removeGasSponsor', req] as const, + listEcosystemGasSponsors: (req: ListEcosystemGasSponsorsArgs) => + ['Relayer', 'listEcosystemGasSponsors', req] as const, + getEcosystemGasSponsor: (req: GetEcosystemGasSponsorArgs) => ['Relayer', 'getEcosystemGasSponsor', req] as const, + addEcosystemGasSponsor: (req: AddEcosystemGasSponsorArgs) => ['Relayer', 'addEcosystemGasSponsor', req] as const, + updateEcosystemGasSponsor: (req: UpdateEcosystemGasSponsorArgs) => + ['Relayer', 'updateEcosystemGasSponsor', req] as const, + removeEcosystemGasSponsor: (req: RemoveEcosystemGasSponsorArgs) => + ['Relayer', 'removeEcosystemGasSponsor', req] as const, + addressGasSponsors: (req: AddressGasSponsorsArgs) => ['Relayer', 'addressGasSponsors', req] as const, + getProjectBalance: (req: GetProjectBalanceArgs) => ['Relayer', 'getProjectBalance', req] as const, + adjustProjectBalance: (req: AdjustProjectBalanceArgs) => ['Relayer', 'adjustProjectBalance', req] as const, + } + ping = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Ping'), createHTTPRequest({}, headers, signal)).then( + return this.fetch(this.url('Ping'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'PingReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } version = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Version'), createHTTPRequest({}, headers, signal)).then( + return this.fetch(this.url('Version'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - version: _data.version, - } + return JsonDecode(_data, 'VersionReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } runtimeStatus = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('RuntimeStatus'), createHTTPRequest({}, headers, signal)).then( + return this.fetch(this.url('RuntimeStatus'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'RuntimeStatusReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getSequenceContext = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetSequenceContext'), createHTTPRequest({}, headers, signal)).then( + return this.fetch(this.url('GetSequenceContext'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - data: _data.data, - } + return JsonDecode(_data, 'GetSequenceContextReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getChainID = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetChainID'), createHTTPRequest({}, headers, signal)).then( + return this.fetch(this.url('GetChainID'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - chainID: _data.chainID, - } + return JsonDecode(_data, 'GetChainIDReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - sendMetaTxn = (args: SendMetaTxnArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('SendMetaTxn'), createHTTPRequest(args, headers, signal)).then( + sendMetaTxn = (req: SendMetaTxnArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('SendMetaTxn'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - txnHash: _data.txnHash, - } + return JsonDecode(_data, 'SendMetaTxnReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getMetaTxnNonce = ( - args: GetMetaTxnNonceArgs, + req: GetMetaTxnNonceArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('GetMetaTxnNonce'), createHTTPRequest(args, headers, signal)).then( + return this.fetch(this.url('GetMetaTxnNonce'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - nonce: _data.nonce, - } + return JsonDecode(_data, 'GetMetaTxnNonceReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getMetaTxnReceipt = ( - args: GetMetaTxnReceiptArgs, + req: GetMetaTxnReceiptArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('GetMetaTxnReceipt'), createHTTPRequest(args, headers, signal)).then( + return this.fetch(this.url('GetMetaTxnReceipt'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - receipt: _data.receipt, - } + return JsonDecode(_data, 'GetMetaTxnReceiptReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - simulate = (args: SimulateArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('Simulate'), createHTTPRequest(args, headers, signal)).then( + simulate = (req: SimulateArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('Simulate'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - results: >_data.results, - } + return JsonDecode(_data, 'SimulateReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - simulateV3 = (args: SimulateV3Args, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('SimulateV3'), createHTTPRequest(args, headers, signal)).then( + simulateV3 = (req: SimulateV3Args, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('SimulateV3'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - results: >_data.results, - } + return JsonDecode(_data, 'SimulateV3Return') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } updateMetaTxnGasLimits = ( - args: UpdateMetaTxnGasLimitsArgs, + req: UpdateMetaTxnGasLimitsArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('UpdateMetaTxnGasLimits'), createHTTPRequest(args, headers, signal)).then( + return this.fetch(this.url('UpdateMetaTxnGasLimits'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - payload: _data.payload, - } + return JsonDecode(_data, 'UpdateMetaTxnGasLimitsReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } feeTokens = (headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('FeeTokens'), createHTTPRequest({}, headers, signal)).then( + return this.fetch(this.url('FeeTokens'), createHttpRequest('{}', headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - isFeeRequired: _data.isFeeRequired, - tokens: >_data.tokens, - paymentAddress: _data.paymentAddress, - } + return JsonDecode(_data, 'FeeTokensReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - feeOptions = (args: FeeOptionsArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('FeeOptions'), createHTTPRequest(args, headers, signal)).then( + feeOptions = (req: FeeOptionsArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('FeeOptions'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - options: >_data.options, - sponsored: _data.sponsored, - quote: _data.quote, - } + return JsonDecode(_data, 'FeeOptionsReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getMetaTxnNetworkFeeOptions = ( - args: GetMetaTxnNetworkFeeOptionsArgs, + sendMetaTxnWithBridgeGas = ( + req: SendMetaTxnWithBridgeGasArgs, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetMetaTxnNetworkFeeOptions'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('SendMetaTxnWithBridgeGas'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - options: >_data.options, - } + return JsonDecode(_data, 'SendMetaTxnWithBridgeGasReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getMetaTransactions = ( - args: GetMetaTransactionsArgs, + feeOptionsWithBridgeGas = ( + req: FeeOptionsWithBridgeGasArgs, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetMetaTransactions'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('FeeOptionsWithBridgeGas'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - transactions: >_data.transactions, - } + return JsonDecode(_data, 'FeeOptionsWithBridgeGasReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getTransactionCost = ( - args: GetTransactionCostArgs, + getMetaTxnNetworkFeeOptions = ( + req: GetMetaTxnNetworkFeeOptionsArgs, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('GetTransactionCost'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch( + this.url('GetMetaTxnNetworkFeeOptions'), + createHttpRequest(JsonEncode(req), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - cost: _data.cost, - } + return JsonDecode(_data, 'GetMetaTxnNetworkFeeOptionsReturn') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + startSender = (req: StartSenderArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('StartSender'), createHttpRequest(JsonEncode(req), headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'StartSenderReturn') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + stopSender = (req: StopSenderArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('StopSender'), createHttpRequest(JsonEncode(req), headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'StopSenderReturn') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + repairSender = (req: RepairSenderArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('RepairSender'), createHttpRequest(JsonEncode(req), headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'RepairSenderReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - sentTransactions = ( - args: SentTransactionsArgs, + getMetaTransactions = ( + req: GetMetaTransactionsArgs, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('SentTransactions'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('GetMetaTransactions'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - transactions: >_data.transactions, - } + return JsonDecode(_data, 'GetMetaTransactionsReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - pendingTransactions = ( - args: PendingTransactionsArgs, + getTransactionCost = ( + req: GetTransactionCostArgs, headers?: object, signal?: AbortSignal, - ): Promise => { - return this.fetch(this.url('PendingTransactions'), createHTTPRequest(args, headers, signal)).then( + ): Promise => { + return this.fetch(this.url('GetTransactionCost'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - transactions: >_data.transactions, - } + return JsonDecode(_data, 'GetTransactionCostReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getGasTank = (args: GetGasTankArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetGasTank'), createHTTPRequest(args, headers, signal)).then( + getGasTank = (req: GetGasTankArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('GetGasTank'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - gasTank: _data.gasTank, - } + return JsonDecode(_data, 'GetGasTankReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - addGasTank = (args: AddGasTankArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('AddGasTank'), createHTTPRequest(args, headers, signal)).then( + addGasTank = (req: AddGasTankArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('AddGasTank'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - gasTank: _data.gasTank, - } + return JsonDecode(_data, 'AddGasTankReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - updateGasTank = (args: UpdateGasTankArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('UpdateGasTank'), createHTTPRequest(args, headers, signal)).then( + updateGasTank = (req: UpdateGasTankArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('UpdateGasTank'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - gasTank: _data.gasTank, - } + return JsonDecode(_data, 'UpdateGasTankReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } nextGasTankBalanceAdjustmentNonce = ( - args: NextGasTankBalanceAdjustmentNonceArgs, + req: NextGasTankBalanceAdjustmentNonceArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('NextGasTankBalanceAdjustmentNonce'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('NextGasTankBalanceAdjustmentNonce'), + createHttpRequest(JsonEncode(req), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - nonce: _data.nonce, - } + return JsonDecode(_data, 'NextGasTankBalanceAdjustmentNonceReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } adjustGasTankBalance = ( - args: AdjustGasTankBalanceArgs, + req: AdjustGasTankBalanceArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('AdjustGasTankBalance'), createHTTPRequest(args, headers, signal)).then( + return this.fetch(this.url('AdjustGasTankBalance'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - adjustment: _data.adjustment, - } + return JsonDecode(_data, 'AdjustGasTankBalanceReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getGasTankBalanceAdjustment = ( - args: GetGasTankBalanceAdjustmentArgs, + req: GetGasTankBalanceAdjustmentArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('GetGasTankBalanceAdjustment'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('GetGasTankBalanceAdjustment'), + createHttpRequest(JsonEncode(req), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - adjustment: _data.adjustment, - } + return JsonDecode(_data, 'GetGasTankBalanceAdjustmentReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } listGasTankBalanceAdjustments = ( - args: ListGasTankBalanceAdjustmentsArgs, + req: ListGasTankBalanceAdjustmentsArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('ListGasTankBalanceAdjustments'), createHTTPRequest(args, headers, signal)).then( + return this.fetch( + this.url('ListGasTankBalanceAdjustments'), + createHttpRequest(JsonEncode(req), headers, signal), + ).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - adjustments: >_data.adjustments, - } + return JsonDecode(_data, 'ListGasTankBalanceAdjustmentsReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } listGasSponsors = ( - args: ListGasSponsorsArgs, + req: ListGasSponsorsArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('ListGasSponsors'), createHTTPRequest(args, headers, signal)).then( + return this.fetch(this.url('ListGasSponsors'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - gasSponsors: >_data.gasSponsors, - } + return JsonDecode(_data, 'ListGasSponsorsReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - getGasSponsor = (args: GetGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('GetGasSponsor'), createHTTPRequest(args, headers, signal)).then( + getGasSponsor = (req: GetGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('GetGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - gasSponsor: _data.gasSponsor, - } + return JsonDecode(_data, 'GetGasSponsorReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } - addGasSponsor = (args: AddGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise => { - return this.fetch(this.url('AddGasSponsor'), createHTTPRequest(args, headers, signal)).then( + addGasSponsor = (req: AddGasSponsorArgs, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('AddGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - gasSponsor: _data.gasSponsor, - } + return JsonDecode(_data, 'AddGasSponsorReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } updateGasSponsor = ( - args: UpdateGasSponsorArgs, + req: UpdateGasSponsorArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('UpdateGasSponsor'), createHTTPRequest(args, headers, signal)).then( + return this.fetch(this.url('UpdateGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - gasSponsor: _data.gasSponsor, - } + return JsonDecode(_data, 'UpdateGasSponsorReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } removeGasSponsor = ( - args: RemoveGasSponsorArgs, + req: RemoveGasSponsorArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('RemoveGasSponsor'), createHTTPRequest(args, headers, signal)).then( + return this.fetch(this.url('RemoveGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - status: _data.status, - } + return JsonDecode(_data, 'RemoveGasSponsorReturn') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + listEcosystemGasSponsors = ( + req: ListEcosystemGasSponsorsArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('ListEcosystemGasSponsors'), createHttpRequest(JsonEncode(req), headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'ListEcosystemGasSponsorsReturn') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + getEcosystemGasSponsor = ( + req: GetEcosystemGasSponsorArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('GetEcosystemGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'GetEcosystemGasSponsorReturn') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + addEcosystemGasSponsor = ( + req: AddEcosystemGasSponsorArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('AddEcosystemGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'AddEcosystemGasSponsorReturn') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + updateEcosystemGasSponsor = ( + req: UpdateEcosystemGasSponsorArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('UpdateEcosystemGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'UpdateEcosystemGasSponsorReturn') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + removeEcosystemGasSponsor = ( + req: RemoveEcosystemGasSponsorArgs, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch(this.url('RemoveEcosystemGasSponsor'), createHttpRequest(JsonEncode(req), headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'RemoveEcosystemGasSponsorReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } addressGasSponsors = ( - args: AddressGasSponsorsArgs, + req: AddressGasSponsorsArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('AddressGasSponsors'), createHTTPRequest(args, headers, signal)).then( + return this.fetch(this.url('AddressGasSponsors'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - page: _data.page, - gasSponsors: >_data.gasSponsors, - } + return JsonDecode(_data, 'AddressGasSponsorsReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } getProjectBalance = ( - args: GetProjectBalanceArgs, + req: GetProjectBalanceArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('GetProjectBalance'), createHTTPRequest(args, headers, signal)).then( + return this.fetch(this.url('GetProjectBalance'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - balance: _data.balance, - } + return JsonDecode(_data, 'GetProjectBalanceReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } adjustProjectBalance = ( - args: AdjustProjectBalanceArgs, + req: AdjustProjectBalanceArgs, headers?: object, signal?: AbortSignal, ): Promise => { - return this.fetch(this.url('AdjustProjectBalance'), createHTTPRequest(args, headers, signal)).then( + return this.fetch(this.url('AdjustProjectBalance'), createHttpRequest(JsonEncode(req), headers, signal)).then( (res) => { return buildResponse(res).then((_data) => { - return { - balance: _data.balance, - } + return JsonDecode(_data, 'AdjustProjectBalanceReturn') }) }, (error) => { - throw WebrpcRequestFailedError.new({ cause: `fetch(): ${error.message || ''}` }) + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) }, ) } } -const createHTTPRequest = (body: object = {}, headers: object = {}, signal: AbortSignal | null = null): object => { - const reqHeaders: { [key: string]: string } = { ...headers, 'Content-Type': 'application/json' } - reqHeaders[WebrpcHeader] = WebrpcHeaderValue - - return { - method: 'POST', - headers: reqHeaders, - body: JSON.stringify(body || {}), - signal, +const createHttpRequest = (body: string = '{}', headers: object = {}, signal: AbortSignal | null = null): object => { + const reqHeaders: { [key: string]: string } = { + ...headers, + 'Content-Type': 'application/json', + [WebrpcHeader]: WebrpcHeaderValue, } + return { method: 'POST', headers: reqHeaders, body, signal } } const buildResponse = (res: Response): Promise => { @@ -1368,13 +1731,9 @@ const buildResponse = (res: Response): Promise => { try { data = JSON.parse(text) } catch (error) { - let message = '' - if (error instanceof Error) { - message = error.message - } throw WebrpcBadResponseError.new({ status: res.status, - cause: `JSON.parse(): ${message}: response text: ${text}`, + cause: `JSON.parse(): ${error instanceof Error ? error.message : String(error)}: response text: ${text}`, }) } if (!res.ok) { @@ -1385,530 +1744,587 @@ const buildResponse = (res: Response): Promise => { }) } +export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise + +// +// BigInt helpers +// + +const BIG_INT_FIELDS: { [typ: string]: (string | [string, string])[] } = { + SendMetaTxnArgs: [['preconditions', 'TransactionPrecondition[]']], + SendMetaTxnWithBridgeGasArgs: [['preconditions', 'TransactionPrecondition[]']], + TransactionPrecondition: ['minAmount'], + Transactions: [['preconditions', 'TransactionPrecondition[]']], +} + +// Decode in-place: mutate object graph; throw if expected numeric string is invalid. +function decodeType(typ: string, obj: any): any { + if (obj == null || typeof obj !== 'object') return obj + const descs = BIG_INT_FIELDS[typ] || [] + if (!descs.length) return obj + for (const d of descs) { + if (Array.isArray(d)) { + const [fieldName, nestedType] = d + if (fieldName.endsWith('[]')) { + const base = fieldName.slice(0, -2) + const arr = obj[base] + if (Array.isArray(arr)) { + for (let i = 0; i < arr.length; i++) arr[i] = decodeType(nestedType, arr[i]) + } + } else if (obj[fieldName]) { + // Handle nestedType that might be an array type like 'Message[]' + if (nestedType.endsWith('[]')) { + const baseType = nestedType.slice(0, -2) + const arr = obj[fieldName] + if (Array.isArray(arr)) { + for (let i = 0; i < arr.length; i++) arr[i] = decodeType(baseType, arr[i]) + } + } else { + obj[fieldName] = decodeType(nestedType, obj[fieldName]) + } + } + continue + } + if (d.endsWith('[]')) { + const base = d.slice(0, -2) + const arr = obj[base] + if (Array.isArray(arr)) { + for (let i = 0; i < arr.length; i++) { + const v = arr[i] + if (typeof v === 'string') { + try { + arr[i] = BigInt(v) + } catch (e) { + throw WebrpcBadResponseError.new({ cause: `Invalid bigint value for ${base}[${i}]: ${v}` }) + } + } + } + } + continue + } + const v = obj[d] + if (typeof v === 'string') { + try { + obj[d] = BigInt(v) + } catch (e) { + throw WebrpcBadResponseError.new({ cause: `Invalid bigint value for ${d}: ${v}` }) + } + } + } + return obj +} + +// Encode object to JSON with BigInts converted to decimal strings. +export const JsonEncode = (obj: T): string => { + return JSON.stringify(obj, (key, value) => (typeof value === 'bigint' ? value.toString() : value)) +} + +// Decode data (JSON string or already-parsed object) and convert declared BigInt string fields back to BigInt. +export const JsonDecode = (data: string | any, typ: string = ''): T => { + let parsed: any = data + if (typeof data === 'string') { + try { + parsed = JSON.parse(data) + } catch (err) { + throw WebrpcBadResponseError.new({ cause: `JsonDecode: JSON.parse failed: ${(err as Error).message}` }) + } + } + return decodeType(typ, parsed) as T +} + // // Errors // +type WebrpcErrorParams = { name?: string; code?: number; message?: string; status?: number; cause?: string } + export class WebrpcError extends Error { - name: string code: number - message: string status: number - cause?: string - - /** @deprecated Use message instead of msg. Deprecated in webrpc v0.11.0. */ - msg: string - - constructor(name: string, code: number, message: string, status: number, cause?: string) { - super(message) - this.name = name || 'WebrpcError' - this.code = typeof code === 'number' ? code : 0 - this.message = message || `endpoint error ${this.code}` - this.msg = this.message - this.status = typeof status === 'number' ? status : 0 - this.cause = cause + + constructor(error: WebrpcErrorParams = {}) { + super(error.message) + this.name = error.name || 'WebrpcEndpointError' + this.code = typeof error.code === 'number' ? error.code : 0 + this.message = error.message || `endpoint error` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcError.prototype) } static new(payload: any): WebrpcError { - return new this(payload.error, payload.code, payload.message || payload.msg, payload.status, payload.cause) + return new this({ message: payload.message, code: payload.code, status: payload.status, cause: payload.cause }) } } -// Webrpc errors - export class WebrpcEndpointError extends WebrpcError { - constructor( - name: string = 'WebrpcEndpoint', - code: number = 0, - message: string = `endpoint error`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcEndpoint' + this.code = typeof error.code === 'number' ? error.code : 0 + this.message = error.message || `endpoint error` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcEndpointError.prototype) } } export class WebrpcRequestFailedError extends WebrpcError { - constructor( - name: string = 'WebrpcRequestFailed', - code: number = -1, - message: string = `request failed`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcRequestFailed' + this.code = typeof error.code === 'number' ? error.code : -1 + this.message = error.message || `request failed` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcRequestFailedError.prototype) } } export class WebrpcBadRouteError extends WebrpcError { - constructor( - name: string = 'WebrpcBadRoute', - code: number = -2, - message: string = `bad route`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadRoute' + this.code = typeof error.code === 'number' ? error.code : -2 + this.message = error.message || `bad route` + this.status = typeof error.status === 'number' ? error.status : 404 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcBadRouteError.prototype) } } export class WebrpcBadMethodError extends WebrpcError { - constructor( - name: string = 'WebrpcBadMethod', - code: number = -3, - message: string = `bad method`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadMethod' + this.code = typeof error.code === 'number' ? error.code : -3 + this.message = error.message || `bad method` + this.status = typeof error.status === 'number' ? error.status : 405 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcBadMethodError.prototype) } } export class WebrpcBadRequestError extends WebrpcError { - constructor( - name: string = 'WebrpcBadRequest', - code: number = -4, - message: string = `bad request`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadRequest' + this.code = typeof error.code === 'number' ? error.code : -4 + this.message = error.message || `bad request` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcBadRequestError.prototype) } } export class WebrpcBadResponseError extends WebrpcError { - constructor( - name: string = 'WebrpcBadResponse', - code: number = -5, - message: string = `bad response`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadResponse' + this.code = typeof error.code === 'number' ? error.code : -5 + this.message = error.message || `bad response` + this.status = typeof error.status === 'number' ? error.status : 500 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcBadResponseError.prototype) } } export class WebrpcServerPanicError extends WebrpcError { - constructor( - name: string = 'WebrpcServerPanic', - code: number = -6, - message: string = `server panic`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcServerPanic' + this.code = typeof error.code === 'number' ? error.code : -6 + this.message = error.message || `server panic` + this.status = typeof error.status === 'number' ? error.status : 500 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcServerPanicError.prototype) } } export class WebrpcInternalErrorError extends WebrpcError { - constructor( - name: string = 'WebrpcInternalError', - code: number = -7, - message: string = `internal error`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcInternalError' + this.code = typeof error.code === 'number' ? error.code : -7 + this.message = error.message || `internal error` + this.status = typeof error.status === 'number' ? error.status : 500 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcInternalErrorError.prototype) } } -export class WebrpcClientDisconnectedError extends WebrpcError { - constructor( - name: string = 'WebrpcClientDisconnected', - code: number = -8, - message: string = `client disconnected`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) - Object.setPrototypeOf(this, WebrpcClientDisconnectedError.prototype) +export class WebrpcClientAbortedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcClientAborted' + this.code = typeof error.code === 'number' ? error.code : -8 + this.message = error.message || `request aborted by client` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcClientAbortedError.prototype) } } export class WebrpcStreamLostError extends WebrpcError { - constructor( - name: string = 'WebrpcStreamLost', - code: number = -9, - message: string = `stream lost`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcStreamLost' + this.code = typeof error.code === 'number' ? error.code : -9 + this.message = error.message || `stream lost` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcStreamLostError.prototype) } } export class WebrpcStreamFinishedError extends WebrpcError { - constructor( - name: string = 'WebrpcStreamFinished', - code: number = -10, - message: string = `stream finished`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcStreamFinished' + this.code = typeof error.code === 'number' ? error.code : -10 + this.message = error.message || `stream finished` + this.status = typeof error.status === 'number' ? error.status : 200 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, WebrpcStreamFinishedError.prototype) } } +// // Schema errors +// export class UnauthorizedError extends WebrpcError { - constructor( - name: string = 'Unauthorized', - code: number = 1000, - message: string = `Unauthorized access`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Unauthorized' + this.code = typeof error.code === 'number' ? error.code : 1000 + this.message = error.message || `Unauthorized access` + this.status = typeof error.status === 'number' ? error.status : 401 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, UnauthorizedError.prototype) } } export class PermissionDeniedError extends WebrpcError { - constructor( - name: string = 'PermissionDenied', - code: number = 1001, - message: string = `Permission denied`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'PermissionDenied' + this.code = typeof error.code === 'number' ? error.code : 1001 + this.message = error.message || `Permission denied` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, PermissionDeniedError.prototype) } } export class SessionExpiredError extends WebrpcError { - constructor( - name: string = 'SessionExpired', - code: number = 1002, - message: string = `Session expired`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'SessionExpired' + this.code = typeof error.code === 'number' ? error.code : 1002 + this.message = error.message || `Session expired` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, SessionExpiredError.prototype) } } export class MethodNotFoundError extends WebrpcError { - constructor( - name: string = 'MethodNotFound', - code: number = 1003, - message: string = `Method not found`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'MethodNotFound' + this.code = typeof error.code === 'number' ? error.code : 1003 + this.message = error.message || `Method not found` + this.status = typeof error.status === 'number' ? error.status : 404 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, MethodNotFoundError.prototype) } } export class RequestConflictError extends WebrpcError { - constructor( - name: string = 'RequestConflict', - code: number = 1004, - message: string = `Conflict with target resource`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'RequestConflict' + this.code = typeof error.code === 'number' ? error.code : 1004 + this.message = error.message || `Conflict with target resource` + this.status = typeof error.status === 'number' ? error.status : 409 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, RequestConflictError.prototype) } } export class AbortedError extends WebrpcError { - constructor( - name: string = 'Aborted', - code: number = 1005, - message: string = `Request aborted`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Aborted' + this.code = typeof error.code === 'number' ? error.code : 1005 + this.message = error.message || `Request aborted` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, AbortedError.prototype) } } export class GeoblockedError extends WebrpcError { - constructor( - name: string = 'Geoblocked', - code: number = 1006, - message: string = `Geoblocked region`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Geoblocked' + this.code = typeof error.code === 'number' ? error.code : 1006 + this.message = error.message || `Geoblocked region` + this.status = typeof error.status === 'number' ? error.status : 451 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, GeoblockedError.prototype) } } export class RateLimitedError extends WebrpcError { - constructor( - name: string = 'RateLimited', - code: number = 1007, - message: string = `Rate-limited. Please slow down.`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'RateLimited' + this.code = typeof error.code === 'number' ? error.code : 1007 + this.message = error.message || `Rate-limited. Please slow down.` + this.status = typeof error.status === 'number' ? error.status : 429 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, RateLimitedError.prototype) } } export class ProjectNotFoundError extends WebrpcError { - constructor( - name: string = 'ProjectNotFound', - code: number = 1008, - message: string = `Project not found`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'ProjectNotFound' + this.code = typeof error.code === 'number' ? error.code : 1008 + this.message = error.message || `Project not found` + this.status = typeof error.status === 'number' ? error.status : 401 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, ProjectNotFoundError.prototype) } } export class AccessKeyNotFoundError extends WebrpcError { - constructor( - name: string = 'AccessKeyNotFound', - code: number = 1101, - message: string = `Access key not found`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'AccessKeyNotFound' + this.code = typeof error.code === 'number' ? error.code : 1101 + this.message = error.message || `Access key not found` + this.status = typeof error.status === 'number' ? error.status : 401 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, AccessKeyNotFoundError.prototype) } } export class AccessKeyMismatchError extends WebrpcError { - constructor( - name: string = 'AccessKeyMismatch', - code: number = 1102, - message: string = `Access key mismatch`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'AccessKeyMismatch' + this.code = typeof error.code === 'number' ? error.code : 1102 + this.message = error.message || `Access key mismatch` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, AccessKeyMismatchError.prototype) } } export class InvalidOriginError extends WebrpcError { - constructor( - name: string = 'InvalidOrigin', - code: number = 1103, - message: string = `Invalid origin for Access Key`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'InvalidOrigin' + this.code = typeof error.code === 'number' ? error.code : 1103 + this.message = error.message || `Invalid origin for Access Key` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, InvalidOriginError.prototype) } } export class InvalidServiceError extends WebrpcError { - constructor( - name: string = 'InvalidService', - code: number = 1104, - message: string = `Service not enabled for Access key`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'InvalidService' + this.code = typeof error.code === 'number' ? error.code : 1104 + this.message = error.message || `Service not enabled for Access key` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, InvalidServiceError.prototype) } } export class UnauthorizedUserError extends WebrpcError { - constructor( - name: string = 'UnauthorizedUser', - code: number = 1105, - message: string = `Unauthorized user`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'UnauthorizedUser' + this.code = typeof error.code === 'number' ? error.code : 1105 + this.message = error.message || `Unauthorized user` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, UnauthorizedUserError.prototype) } } +export class InvalidChainError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'InvalidChain' + this.code = typeof error.code === 'number' ? error.code : 1106 + this.message = error.message || `Network not enabled for Access key` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, InvalidChainError.prototype) + } +} + export class QuotaExceededError extends WebrpcError { - constructor( - name: string = 'QuotaExceeded', - code: number = 1200, - message: string = `Quota request exceeded`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'QuotaExceeded' + this.code = typeof error.code === 'number' ? error.code : 1200 + this.message = error.message || `Quota request exceeded` + this.status = typeof error.status === 'number' ? error.status : 429 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, QuotaExceededError.prototype) } } export class QuotaRateLimitError extends WebrpcError { - constructor( - name: string = 'QuotaRateLimit', - code: number = 1201, - message: string = `Quota rate limit exceeded`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'QuotaRateLimit' + this.code = typeof error.code === 'number' ? error.code : 1201 + this.message = error.message || `Quota rate limit exceeded` + this.status = typeof error.status === 'number' ? error.status : 429 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, QuotaRateLimitError.prototype) } } export class NoDefaultKeyError extends WebrpcError { - constructor( - name: string = 'NoDefaultKey', - code: number = 1300, - message: string = `No default access key found`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'NoDefaultKey' + this.code = typeof error.code === 'number' ? error.code : 1300 + this.message = error.message || `No default access key found` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, NoDefaultKeyError.prototype) } } export class MaxAccessKeysError extends WebrpcError { - constructor( - name: string = 'MaxAccessKeys', - code: number = 1301, - message: string = `Access keys limit reached`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'MaxAccessKeys' + this.code = typeof error.code === 'number' ? error.code : 1301 + this.message = error.message || `Access keys limit reached` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, MaxAccessKeysError.prototype) } } export class AtLeastOneKeyError extends WebrpcError { - constructor( - name: string = 'AtLeastOneKey', - code: number = 1302, - message: string = `You need at least one Access Key`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'AtLeastOneKey' + this.code = typeof error.code === 'number' ? error.code : 1302 + this.message = error.message || `You need at least one Access Key` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, AtLeastOneKeyError.prototype) } } export class TimeoutError extends WebrpcError { - constructor( - name: string = 'Timeout', - code: number = 1900, - message: string = `Request timed out`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Timeout' + this.code = typeof error.code === 'number' ? error.code : 1900 + this.message = error.message || `Request timed out` + this.status = typeof error.status === 'number' ? error.status : 408 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, TimeoutError.prototype) } } export class InvalidArgumentError extends WebrpcError { - constructor( - name: string = 'InvalidArgument', - code: number = 2001, - message: string = `Invalid argument`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'InvalidArgument' + this.code = typeof error.code === 'number' ? error.code : 2001 + this.message = error.message || `Invalid argument` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, InvalidArgumentError.prototype) } } export class UnavailableError extends WebrpcError { - constructor( - name: string = 'Unavailable', - code: number = 2002, - message: string = `Unavailable resource`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Unavailable' + this.code = typeof error.code === 'number' ? error.code : 2002 + this.message = error.message || `Unavailable resource` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, UnavailableError.prototype) } } export class QueryFailedError extends WebrpcError { - constructor( - name: string = 'QueryFailed', - code: number = 2003, - message: string = `Query failed`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'QueryFailed' + this.code = typeof error.code === 'number' ? error.code : 2003 + this.message = error.message || `Query failed` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, QueryFailedError.prototype) } } export class NotFoundError extends WebrpcError { - constructor( - name: string = 'NotFound', - code: number = 3000, - message: string = `Resource not found`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'NotFound' + this.code = typeof error.code === 'number' ? error.code : 3000 + this.message = error.message || `Resource not found` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, NotFoundError.prototype) } } export class InsufficientFeeError extends WebrpcError { - constructor( - name: string = 'InsufficientFee', - code: number = 3004, - message: string = `Insufficient fee`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'InsufficientFee' + this.code = typeof error.code === 'number' ? error.code : 3004 + this.message = error.message || `Insufficient fee` + this.status = typeof error.status === 'number' ? error.status : 402 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, InsufficientFeeError.prototype) } } export class NotEnoughBalanceError extends WebrpcError { - constructor( - name: string = 'NotEnoughBalance', - code: number = 3005, - message: string = `Not enough balance`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'NotEnoughBalance' + this.code = typeof error.code === 'number' ? error.code : 3005 + this.message = error.message || `Not enough balance` + this.status = typeof error.status === 'number' ? error.status : 402 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, NotEnoughBalanceError.prototype) } } export class SimulationFailedError extends WebrpcError { - constructor( - name: string = 'SimulationFailed', - code: number = 3006, - message: string = `Simulation failed`, - status: number = 0, - cause?: string, - ) { - super(name, code, message, status, cause) + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'SimulationFailed' + this.code = typeof error.code === 'number' ? error.code : 3006 + this.message = error.message || `Simulation failed` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause Object.setPrototypeOf(this, SimulationFailedError.prototype) } } @@ -1922,7 +2338,7 @@ export enum errors { WebrpcBadResponse = 'WebrpcBadResponse', WebrpcServerPanic = 'WebrpcServerPanic', WebrpcInternalError = 'WebrpcInternalError', - WebrpcClientDisconnected = 'WebrpcClientDisconnected', + WebrpcClientAborted = 'WebrpcClientAborted', WebrpcStreamLost = 'WebrpcStreamLost', WebrpcStreamFinished = 'WebrpcStreamFinished', Unauthorized = 'Unauthorized', @@ -1939,6 +2355,7 @@ export enum errors { InvalidOrigin = 'InvalidOrigin', InvalidService = 'InvalidService', UnauthorizedUser = 'UnauthorizedUser', + InvalidChain = 'InvalidChain', QuotaExceeded = 'QuotaExceeded', QuotaRateLimit = 'QuotaRateLimit', NoDefaultKey = 'NoDefaultKey', @@ -1963,7 +2380,7 @@ export enum WebrpcErrorCodes { WebrpcBadResponse = -5, WebrpcServerPanic = -6, WebrpcInternalError = -7, - WebrpcClientDisconnected = -8, + WebrpcClientAborted = -8, WebrpcStreamLost = -9, WebrpcStreamFinished = -10, Unauthorized = 1000, @@ -1980,6 +2397,7 @@ export enum WebrpcErrorCodes { InvalidOrigin = 1103, InvalidService = 1104, UnauthorizedUser = 1105, + InvalidChain = 1106, QuotaExceeded = 1200, QuotaRateLimit = 1201, NoDefaultKey = 1300, @@ -2004,7 +2422,7 @@ export const webrpcErrorByCode: { [code: number]: any } = { [-5]: WebrpcBadResponseError, [-6]: WebrpcServerPanicError, [-7]: WebrpcInternalErrorError, - [-8]: WebrpcClientDisconnectedError, + [-8]: WebrpcClientAbortedError, [-9]: WebrpcStreamLostError, [-10]: WebrpcStreamFinishedError, [1000]: UnauthorizedError, @@ -2021,6 +2439,7 @@ export const webrpcErrorByCode: { [code: number]: any } = { [1103]: InvalidOriginError, [1104]: InvalidServiceError, [1105]: UnauthorizedUserError, + [1106]: InvalidChainError, [1200]: QuotaExceededError, [1201]: QuotaRateLimitError, [1300]: NoDefaultKeyError, @@ -2036,4 +2455,58 @@ export const webrpcErrorByCode: { [code: number]: any } = { [3006]: SimulationFailedError, } -export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise +// +// Webrpc +// + +export const WebrpcHeader = 'Webrpc' + +export const WebrpcHeaderValue = 'webrpc@v0.32.2;gen-typescript@v0.23.1;sequence-relayer@v0.4.1' + +type WebrpcGenVersions = { + WebrpcGenVersion: string + codeGenName: string + codeGenVersion: string + schemaName: string + schemaVersion: string +} + +export function VersionFromHeader(headers: Headers): WebrpcGenVersions { + const headerValue = headers.get(WebrpcHeader) + if (!headerValue) { + return { + WebrpcGenVersion: '', + codeGenName: '', + codeGenVersion: '', + schemaName: '', + schemaVersion: '', + } + } + + return parseWebrpcGenVersions(headerValue) +} + +function parseWebrpcGenVersions(header: string): WebrpcGenVersions { + const versions = header.split(';') + if (versions.length < 3) { + return { + WebrpcGenVersion: '', + codeGenName: '', + codeGenVersion: '', + schemaName: '', + schemaVersion: '', + } + } + + const [_, WebrpcGenVersion] = versions[0]!.split('@') + const [codeGenName, codeGenVersion] = versions[1]!.split('@') + const [schemaName, schemaVersion] = versions[2]!.split('@') + + return { + WebrpcGenVersion: WebrpcGenVersion ?? '', + codeGenName: codeGenName ?? '', + codeGenVersion: codeGenVersion ?? '', + schemaName: schemaName ?? '', + schemaVersion: schemaVersion ?? '', + } +} diff --git a/packages/services/relayer/src/relayer/standard/eip6963.ts b/packages/services/relayer/src/relayer/standard/eip6963.ts index 996c1baee..957b13c44 100644 --- a/packages/services/relayer/src/relayer/standard/eip6963.ts +++ b/packages/services/relayer/src/relayer/standard/eip6963.ts @@ -3,10 +3,10 @@ import { EIP1193ProviderAdapter, LocalRelayer } from './local.js' import { FeeOption, FeeQuote, OperationStatus, Relayer } from '../index.js' import { Address, Hex } from 'ox' import { Payload } from '@0xsequence/wallet-primitives' -import { FeeToken, IntentPrecondition } from '../rpc-relayer/relayer.gen.js' +import { FeeToken, TransactionPrecondition } from '../rpc-relayer/relayer.gen.js' export class EIP6963Relayer implements Relayer { - public readonly kind: 'relayer' = 'relayer' + public readonly kind = 'relayer' public readonly type = 'eip6963' public readonly id: string public readonly info: EIP6963ProviderInfo @@ -23,16 +23,22 @@ export class EIP6963Relayer implements Relayer { return this.relayer.isAvailable(wallet, chainId) } - feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: FeeToken[]; paymentAddress?: Address.Address }> { + feeTokens(): Promise<{ + isFeeRequired: boolean + tokens?: FeeToken[] + paymentAddress?: Address.Address + failed?: boolean + }> { return this.relayer.feeTokens() } feeOptions( wallet: Address.Address, chainId: number, + to: Address.Address, calls: Payload.Call[], - ): Promise<{ options: FeeOption[]; quote?: FeeQuote }> { - return this.relayer.feeOptions(wallet, chainId, calls) + ): Promise<{ options: FeeOption[]; quote?: FeeQuote; sponsored: boolean; failed?: boolean }> { + return this.relayer.feeOptions(wallet, chainId, to, calls) } async relay(to: Address.Address, data: Hex.Hex, chainId: number, _?: FeeQuote): Promise<{ opHash: Hex.Hex }> { @@ -43,7 +49,7 @@ export class EIP6963Relayer implements Relayer { return this.relayer.status(opHash, chainId) } - async checkPrecondition(precondition: IntentPrecondition): Promise { + async checkPrecondition(precondition: TransactionPrecondition): Promise { return this.relayer.checkPrecondition(precondition) } } @@ -58,7 +64,7 @@ export function getEIP6963Store() { return store } -let relayers: Map = new Map() +const relayers: Map = new Map() export function getRelayers(): EIP6963Relayer[] { const store = getEIP6963Store() diff --git a/packages/services/relayer/src/relayer/standard/local.ts b/packages/services/relayer/src/relayer/standard/local.ts index 01c928906..e8066674c 100644 --- a/packages/services/relayer/src/relayer/standard/local.ts +++ b/packages/services/relayer/src/relayer/standard/local.ts @@ -1,9 +1,18 @@ -import { Constants, Payload } from '@0xsequence/wallet-primitives' +import { Payload } from '@0xsequence/wallet-primitives' import { EIP1193Provider } from 'mipd' -import { AbiFunction, Address, Bytes, Hex, TransactionReceipt } from 'ox' +import { AbiFunction, Address, Hex, TransactionReceipt } from 'ox' import { FeeOption, FeeQuote, OperationStatus, Relayer } from '../index.js' -import { FeeToken, IntentPrecondition } from '../rpc-relayer/relayer.gen.js' -import { decodePrecondition } from '../../preconditions/index.js' +import { FeeToken, TransactionPrecondition } from '../rpc-relayer/relayer.gen.js' +import { + decodePrecondition, + Erc1155ApprovalPrecondition, + Erc1155BalancePrecondition, + Erc20ApprovalPrecondition, + Erc20BalancePrecondition, + Erc721ApprovalPrecondition, + Erc721OwnershipPrecondition, + NativeBalancePrecondition, +} from '../../preconditions/index.js' import { erc20BalanceOf, erc20Allowance, @@ -23,7 +32,7 @@ export interface GenericProvider { } export class LocalRelayer implements Relayer { - public readonly kind: 'relayer' = 'relayer' + public readonly kind = 'relayer' public readonly type = 'local' public readonly id = 'local' @@ -34,7 +43,7 @@ export class LocalRelayer implements Relayer { } static createFromWindow(window: Window): LocalRelayer | undefined { - const eth = (window as any).ethereum + const eth = (window as { ethereum?: EIP1193Provider }).ethereum if (!eth) { console.warn('Window.ethereum not found, skipping local relayer') return undefined @@ -47,32 +56,24 @@ export class LocalRelayer implements Relayer { return new LocalRelayer(new EIP1193ProviderAdapter(provider)) } - feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: FeeToken[]; paymentAddress?: Address.Address }> { + feeTokens(): Promise<{ + isFeeRequired: boolean + tokens?: FeeToken[] + paymentAddress?: Address.Address + failed?: boolean + }> { return Promise.resolve({ isFeeRequired: false, }) } feeOptions( - wallet: Address.Address, - chainId: number, - calls: Payload.Call[], - ): Promise<{ options: FeeOption[]; quote?: FeeQuote }> { - return Promise.resolve({ options: [] }) - } - - private decodeCalls(data: Hex.Hex): Payload.Calls { - const executeSelector = AbiFunction.getSelector(Constants.EXECUTE) - - let packedPayload - if (data.startsWith(executeSelector)) { - const decode = AbiFunction.decodeData(Constants.EXECUTE, data) - packedPayload = decode[0] - } else { - packedPayload = data - } - - return Payload.decode(Bytes.fromHex(packedPayload)) + _wallet: Address.Address, + _chainId: number, + _to: Address.Address, + _calls: Payload.Call[], + ): Promise<{ options: FeeOption[]; quote?: FeeQuote; sponsored: boolean; failed?: boolean }> { + return Promise.resolve({ options: [], sponsored: false }) } async relay( @@ -80,7 +81,7 @@ export class LocalRelayer implements Relayer { data: Hex.Hex, chainId: number, quote?: FeeQuote, - preconditions?: IntentPrecondition[], + preconditions?: TransactionPrecondition[], checkInterval: number = 5000, ): Promise<{ opHash: Hex.Hex }> { // Helper function to check all preconditions @@ -168,7 +169,7 @@ export class LocalRelayer implements Relayer { : { status: 'failed', reason: 'failed' } } - async checkPrecondition(precondition: IntentPrecondition): Promise { + async checkPrecondition(precondition: TransactionPrecondition): Promise { const decoded = decodePrecondition(precondition) if (!decoded) { @@ -177,8 +178,8 @@ export class LocalRelayer implements Relayer { switch (decoded.type()) { case 'native-balance': { - const native = decoded as any - const balance = await this.provider.getBalance(native.address.toString()) + const native = decoded as NativeBalancePrecondition + const balance = await this.provider.getBalance(native.address) if (native.min !== undefined && balance < native.min) { return false } @@ -189,10 +190,10 @@ export class LocalRelayer implements Relayer { } case 'erc20-balance': { - const erc20 = decoded as any - const data = AbiFunction.encodeData(erc20BalanceOf, [erc20.address.toString()]) + const erc20 = decoded as Erc20BalancePrecondition + const data = AbiFunction.encodeData(erc20BalanceOf, [erc20.address]) const result = await this.provider.call({ - to: erc20.token.toString(), + to: erc20.token, data, }) const balance = BigInt(result) @@ -206,10 +207,10 @@ export class LocalRelayer implements Relayer { } case 'erc20-approval': { - const erc20 = decoded as any - const data = AbiFunction.encodeData(erc20Allowance, [erc20.address.toString(), erc20.operator.toString()]) + const erc20 = decoded as Erc20ApprovalPrecondition + const data = AbiFunction.encodeData(erc20Allowance, [erc20.address, erc20.operator]) const result = await this.provider.call({ - to: erc20.token.toString(), + to: erc20.token, data, }) const allowance = BigInt(result) @@ -217,10 +218,10 @@ export class LocalRelayer implements Relayer { } case 'erc721-ownership': { - const erc721 = decoded as any + const erc721 = decoded as Erc721OwnershipPrecondition const data = AbiFunction.encodeData(erc721OwnerOf, [erc721.tokenId]) const result = await this.provider.call({ - to: erc721.token.toString(), + to: erc721.token, data, }) const owner = '0x' + result.slice(26) @@ -229,10 +230,10 @@ export class LocalRelayer implements Relayer { } case 'erc721-approval': { - const erc721 = decoded as any + const erc721 = decoded as Erc721ApprovalPrecondition const data = AbiFunction.encodeData(erc721GetApproved, [erc721.tokenId]) const result = await this.provider.call({ - to: erc721.token.toString(), + to: erc721.token, data, }) const approved = '0x' + result.slice(26) @@ -240,10 +241,10 @@ export class LocalRelayer implements Relayer { } case 'erc1155-balance': { - const erc1155 = decoded as any - const data = AbiFunction.encodeData(erc1155BalanceOf, [erc1155.address.toString(), erc1155.tokenId]) + const erc1155 = decoded as Erc1155BalancePrecondition + const data = AbiFunction.encodeData(erc1155BalanceOf, [erc1155.address, erc1155.tokenId]) const result = await this.provider.call({ - to: erc1155.token.toString(), + to: erc1155.token, data, }) const balance = BigInt(result) @@ -257,13 +258,10 @@ export class LocalRelayer implements Relayer { } case 'erc1155-approval': { - const erc1155 = decoded as any - const data = AbiFunction.encodeData(erc1155IsApprovedForAll, [ - erc1155.address.toString(), - erc1155.operator.toString(), - ]) + const erc1155 = decoded as Erc1155ApprovalPrecondition + const data = AbiFunction.encodeData(erc1155IsApprovedForAll, [erc1155.address, erc1155.operator]) const result = await this.provider.call({ - to: erc1155.token.toString(), + to: erc1155.token, data, }) return BigInt(result) === 1n @@ -340,7 +338,7 @@ export class EIP1193ProviderAdapter implements GenericProvider { const rpcReceipt = await this.provider.request({ method: 'eth_getTransactionReceipt', params: [txHash] }) if (rpcReceipt) { - const receipt = TransactionReceipt.fromRpc(rpcReceipt as any) + const receipt = TransactionReceipt.fromRpc(rpcReceipt as Parameters[0]) if (receipt?.status === 'success') { return 'success' } else if (receipt?.status === 'reverted') { diff --git a/packages/services/relayer/src/relayer/standard/pk-relayer.ts b/packages/services/relayer/src/relayer/standard/pk-relayer.ts index 37b2e5a08..28ea0850a 100644 --- a/packages/services/relayer/src/relayer/standard/pk-relayer.ts +++ b/packages/services/relayer/src/relayer/standard/pk-relayer.ts @@ -5,7 +5,7 @@ import { FeeOption, FeeQuote, OperationStatus, Relayer } from '../index.js' import { FeeToken } from '../rpc-relayer/relayer.gen.js' export class PkRelayer implements Relayer { - public readonly kind: 'relayer' = 'relayer' + public readonly kind = 'relayer' public readonly type = 'pk' public readonly id = 'pk' private readonly relayer: LocalRelayer @@ -107,16 +107,22 @@ export class PkRelayer implements Relayer { return providerChainId === chainId } - feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: FeeToken[]; paymentAddress?: Address.Address }> { + feeTokens(): Promise<{ + isFeeRequired: boolean + tokens?: FeeToken[] + paymentAddress?: Address.Address + failed?: boolean + }> { return this.relayer.feeTokens() } feeOptions( wallet: Address.Address, chainId: number, + to: Address.Address, calls: Payload.Call[], - ): Promise<{ options: FeeOption[]; quote?: FeeQuote }> { - return this.relayer.feeOptions(wallet, chainId, calls) + ): Promise<{ options: FeeOption[]; quote?: FeeQuote; sponsored: boolean; failed?: boolean }> { + return this.relayer.feeOptions(wallet, chainId, to, calls) } async relay(to: Address.Address, data: Hex.Hex, chainId: number, _?: FeeQuote): Promise<{ opHash: Hex.Hex }> { @@ -131,7 +137,7 @@ export class PkRelayer implements Relayer { return this.relayer.status(opHash, chainId) } - async checkPrecondition(precondition: Precondition.Precondition): Promise { + async checkPrecondition(_precondition: Precondition.Precondition): Promise { // TODO: Implement precondition check return true } diff --git a/packages/services/relayer/src/relayer/standard/sequence.ts b/packages/services/relayer/src/relayer/standard/sequence.ts index b41e97806..8519c618a 100644 --- a/packages/services/relayer/src/relayer/standard/sequence.ts +++ b/packages/services/relayer/src/relayer/standard/sequence.ts @@ -1,9 +1,9 @@ -import { ETHTxnStatus, IntentPrecondition, Relayer as Service, FeeToken } from '../rpc-relayer/relayer.gen.js' +import { ETHTxnStatus, TransactionPrecondition, Relayer as Service, FeeToken } from '../rpc-relayer/relayer.gen.js' import { Payload } from '@0xsequence/wallet-primitives' import { AbiFunction, Address, Bytes, Hex } from 'ox' import { FeeOption, FeeQuote, OperationStatus, Relayer } from '../index.js' export class SequenceRelayer implements Relayer { - public readonly kind: 'relayer' = 'relayer' + public readonly kind = 'relayer' public readonly type = 'sequence' readonly id = 'sequence' @@ -17,7 +17,12 @@ export class SequenceRelayer implements Relayer { return true } - async feeTokens(): Promise<{ isFeeRequired: boolean; tokens?: FeeToken[]; paymentAddress?: Address.Address }> { + async feeTokens(): Promise<{ + isFeeRequired: boolean + tokens?: FeeToken[] + paymentAddress?: Address.Address + failed?: boolean + }> { const { isFeeRequired, tokens, paymentAddress } = await this.service.feeTokens() if (isFeeRequired) { Address.assert(paymentAddress) @@ -36,23 +41,25 @@ export class SequenceRelayer implements Relayer { async feeOptions( wallet: Address.Address, _chainId: number, + to: Address.Address, calls: Payload.Call[], - ): Promise<{ options: FeeOption[]; quote?: FeeQuote }> { - const to = wallet // TODO: this might be the guest module + transactionData?: Hex.Hex, + ): Promise<{ options: FeeOption[]; quote?: FeeQuote; sponsored: boolean; failed?: boolean }> { const execute = AbiFunction.from('function execute(bytes calldata _payload, bytes calldata _signature)') const payload = Payload.encode({ type: 'call', space: 0n, nonce: 0n, calls }, to) const signature = '0x0001' // TODO: use a stub signature - const data = AbiFunction.encodeData(execute, [Bytes.toHex(payload), signature]) + const data = transactionData ?? AbiFunction.encodeData(execute, [Bytes.toHex(payload), signature]) - const { options, quote } = await this.service.feeOptions({ wallet, to, data }) + const { options, quote, sponsored } = await this.service.feeOptions({ wallet, to, data }) return { options, quote: quote ? { _tag: 'FeeQuote', _quote: quote } : undefined, + sponsored, } } - async checkPrecondition(precondition: IntentPrecondition): Promise { + async checkPrecondition(_precondition: TransactionPrecondition): Promise { // TODO: implement return false } diff --git a/packages/services/relayer/test/preconditions/codec.test.ts b/packages/services/relayer/test/preconditions/codec.test.ts index f67a016fa..a5ba279d3 100644 --- a/packages/services/relayer/test/preconditions/codec.test.ts +++ b/packages/services/relayer/test/preconditions/codec.test.ts @@ -5,7 +5,7 @@ import { decodePrecondition, decodePreconditions, encodePrecondition, - IntentPrecondition, + TransactionPrecondition, } from '../../src/preconditions/codec.js' import { NativeBalancePrecondition, @@ -21,6 +21,8 @@ import { const TEST_ADDRESS = Address.from('0x1234567890123456789012345678901234567890') const TOKEN_ADDRESS = Address.from('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd') const OPERATOR_ADDRESS = Address.from('0x9876543210987654321098765432109876543210') +const ARBITRUM_CHAIN_ID = 42161 +const NATIVE_TOKEN_ADDRESS = Address.from('0x0000000000000000000000000000000000000000') describe('Preconditions Codec', () => { // Mock console.warn to test error logging @@ -34,37 +36,17 @@ describe('Preconditions Codec', () => { describe('decodePrecondition', () => { it('should return undefined for null/undefined input', () => { - expect(decodePrecondition(null as any)).toBeUndefined() - expect(decodePrecondition(undefined as any)).toBeUndefined() - }) - - it('should decode native balance precondition with min and max', () => { - const intent: IntentPrecondition = { - type: 'native-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - min: '1000000000000000000', - max: '2000000000000000000', - }), - } - - const result = decodePrecondition(intent) - expect(result).toBeInstanceOf(NativeBalancePrecondition) - - const precondition = result as NativeBalancePrecondition - expect(precondition.address).toBe(TEST_ADDRESS) - expect(precondition.min).toBe(1000000000000000000n) - expect(precondition.max).toBe(2000000000000000000n) - expect(precondition.type()).toBe('native-balance') + expect(decodePrecondition(null as unknown as TransactionPrecondition)).toBeUndefined() + expect(decodePrecondition(undefined as unknown as TransactionPrecondition)).toBeUndefined() }) it('should decode native balance precondition with only min', () => { - const intent: IntentPrecondition = { + const intent: TransactionPrecondition = { type: 'native-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - min: '1000000000000000000', - }), + ownerAddress: TEST_ADDRESS, + tokenAddress: NATIVE_TOKEN_ADDRESS, + chainId: ARBITRUM_CHAIN_ID, + minAmount: BigInt('1000000000000000000'), } const result = decodePrecondition(intent) @@ -75,32 +57,13 @@ describe('Preconditions Codec', () => { expect(precondition.max).toBeUndefined() }) - it('should decode native balance precondition with only max', () => { - const intent: IntentPrecondition = { - type: 'native-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - max: '2000000000000000000', - }), - } - - const result = decodePrecondition(intent) - expect(result).toBeInstanceOf(NativeBalancePrecondition) - - const precondition = result as NativeBalancePrecondition - expect(precondition.min).toBeUndefined() - expect(precondition.max).toBe(2000000000000000000n) - }) - it('should decode ERC20 balance precondition', () => { - const intent: IntentPrecondition = { + const intent: TransactionPrecondition = { type: 'erc20-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - token: TOKEN_ADDRESS, - min: '1000000', - max: '2000000', - }), + ownerAddress: TEST_ADDRESS, + tokenAddress: TOKEN_ADDRESS, + chainId: ARBITRUM_CHAIN_ID, + minAmount: BigInt('1000000'), } const result = decodePrecondition(intent) @@ -110,18 +73,16 @@ describe('Preconditions Codec', () => { expect(precondition.address).toBe(TEST_ADDRESS) expect(precondition.token).toBe(TOKEN_ADDRESS) expect(precondition.min).toBe(1000000n) - expect(precondition.max).toBe(2000000n) + expect(precondition.max).toBeUndefined() }) it('should decode ERC20 approval precondition', () => { - const intent: IntentPrecondition = { + const intent: TransactionPrecondition = { type: 'erc20-approval', - data: JSON.stringify({ - address: TEST_ADDRESS, - token: TOKEN_ADDRESS, - operator: OPERATOR_ADDRESS, - min: '1000000', - }), + ownerAddress: TEST_ADDRESS, + tokenAddress: TOKEN_ADDRESS, + chainId: ARBITRUM_CHAIN_ID, + minAmount: BigInt('1000000'), } const result = decodePrecondition(intent) @@ -130,19 +91,17 @@ describe('Preconditions Codec', () => { const precondition = result as Erc20ApprovalPrecondition expect(precondition.address).toBe(TEST_ADDRESS) expect(precondition.token).toBe(TOKEN_ADDRESS) - expect(precondition.operator).toBe(OPERATOR_ADDRESS) + expect(precondition.operator).toBe(TEST_ADDRESS) expect(precondition.min).toBe(1000000n) }) it('should decode ERC721 ownership precondition', () => { - const intent: IntentPrecondition = { + const intent: TransactionPrecondition = { type: 'erc721-ownership', - data: JSON.stringify({ - address: TEST_ADDRESS, - token: TOKEN_ADDRESS, - tokenId: '123', - owned: true, - }), + ownerAddress: TEST_ADDRESS, + tokenAddress: TOKEN_ADDRESS, + chainId: ARBITRUM_CHAIN_ID, + minAmount: BigInt('0'), } const result = decodePrecondition(intent) @@ -151,36 +110,33 @@ describe('Preconditions Codec', () => { const precondition = result as Erc721OwnershipPrecondition expect(precondition.address).toBe(TEST_ADDRESS) expect(precondition.token).toBe(TOKEN_ADDRESS) - expect(precondition.tokenId).toBe(123n) + expect(precondition.tokenId).toBe(0n) expect(precondition.owned).toBe(true) }) it('should decode ERC721 ownership precondition without owned flag', () => { - const intent: IntentPrecondition = { + const intent: TransactionPrecondition = { type: 'erc721-ownership', - data: JSON.stringify({ - address: TEST_ADDRESS, - token: TOKEN_ADDRESS, - tokenId: '123', - }), + ownerAddress: TEST_ADDRESS, + tokenAddress: TOKEN_ADDRESS, + chainId: ARBITRUM_CHAIN_ID, + minAmount: BigInt('0'), } const result = decodePrecondition(intent) expect(result).toBeInstanceOf(Erc721OwnershipPrecondition) const precondition = result as Erc721OwnershipPrecondition - expect(precondition.owned).toBeUndefined() + expect(precondition.owned).toBe(true) }) it('should decode ERC721 approval precondition', () => { - const intent: IntentPrecondition = { + const intent: TransactionPrecondition = { type: 'erc721-approval', - data: JSON.stringify({ - address: TEST_ADDRESS, - token: TOKEN_ADDRESS, - tokenId: '123', - operator: OPERATOR_ADDRESS, - }), + ownerAddress: TEST_ADDRESS, + tokenAddress: TOKEN_ADDRESS, + chainId: ARBITRUM_CHAIN_ID, + minAmount: BigInt('0'), } const result = decodePrecondition(intent) @@ -189,20 +145,17 @@ describe('Preconditions Codec', () => { const precondition = result as Erc721ApprovalPrecondition expect(precondition.address).toBe(TEST_ADDRESS) expect(precondition.token).toBe(TOKEN_ADDRESS) - expect(precondition.tokenId).toBe(123n) - expect(precondition.operator).toBe(OPERATOR_ADDRESS) + expect(precondition.tokenId).toBe(0n) + expect(precondition.operator).toBe(TEST_ADDRESS) }) it('should decode ERC1155 balance precondition', () => { - const intent: IntentPrecondition = { + const intent: TransactionPrecondition = { type: 'erc1155-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - token: TOKEN_ADDRESS, - tokenId: '123', - min: '1000000', - max: '2000000', - }), + ownerAddress: TEST_ADDRESS, + tokenAddress: TOKEN_ADDRESS, + chainId: ARBITRUM_CHAIN_ID, + minAmount: BigInt('1000000'), } const result = decodePrecondition(intent) @@ -211,21 +164,18 @@ describe('Preconditions Codec', () => { const precondition = result as Erc1155BalancePrecondition expect(precondition.address).toBe(TEST_ADDRESS) expect(precondition.token).toBe(TOKEN_ADDRESS) - expect(precondition.tokenId).toBe(123n) + expect(precondition.tokenId).toBe(0n) expect(precondition.min).toBe(1000000n) - expect(precondition.max).toBe(2000000n) + expect(precondition.max).toBeUndefined() }) it('should decode ERC1155 approval precondition', () => { - const intent: IntentPrecondition = { + const intent: TransactionPrecondition = { type: 'erc1155-approval', - data: JSON.stringify({ - address: TEST_ADDRESS, - token: TOKEN_ADDRESS, - tokenId: '123', - operator: OPERATOR_ADDRESS, - min: '1000000', - }), + ownerAddress: TEST_ADDRESS, + tokenAddress: TOKEN_ADDRESS, + chainId: ARBITRUM_CHAIN_ID, + minAmount: BigInt('1000000'), } const result = decodePrecondition(intent) @@ -234,15 +184,18 @@ describe('Preconditions Codec', () => { const precondition = result as Erc1155ApprovalPrecondition expect(precondition.address).toBe(TEST_ADDRESS) expect(precondition.token).toBe(TOKEN_ADDRESS) - expect(precondition.tokenId).toBe(123n) - expect(precondition.operator).toBe(OPERATOR_ADDRESS) + expect(precondition.tokenId).toBe(0n) + expect(precondition.operator).toBe(TEST_ADDRESS) expect(precondition.min).toBe(1000000n) }) it('should return undefined for unknown precondition type', () => { - const intent: IntentPrecondition = { + const intent: TransactionPrecondition = { type: 'unknown-type', - data: JSON.stringify({ address: TEST_ADDRESS }), + ownerAddress: TEST_ADDRESS, + tokenAddress: NATIVE_TOKEN_ADDRESS, + chainId: ARBITRUM_CHAIN_ID, + minAmount: BigInt('0'), } const result = decodePrecondition(intent) @@ -250,36 +203,38 @@ describe('Preconditions Codec', () => { }) it('should return undefined and log warning for invalid JSON', () => { - const intent: IntentPrecondition = { + const intent: TransactionPrecondition = { type: 'native-balance', - data: 'invalid json', + ownerAddress: TEST_ADDRESS, + tokenAddress: NATIVE_TOKEN_ADDRESS, + chainId: ARBITRUM_CHAIN_ID, + minAmount: BigInt('1000000000000000000'), } const result = decodePrecondition(intent) - expect(result).toBeUndefined() - expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to decode precondition')) + expect(result).toBeInstanceOf(NativeBalancePrecondition) }) it('should return undefined and log warning for invalid precondition', () => { - const intent: IntentPrecondition = { + const intent: TransactionPrecondition = { type: 'native-balance', - data: JSON.stringify({ - // Missing required address field - min: '1000000000000000000', - }), + ownerAddress: TEST_ADDRESS, + tokenAddress: NATIVE_TOKEN_ADDRESS, + chainId: ARBITRUM_CHAIN_ID, + minAmount: BigInt('2000000000000000000'), } const result = decodePrecondition(intent) - expect(result).toBeUndefined() - expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('Failed to decode precondition')) + expect(result).toBeInstanceOf(NativeBalancePrecondition) }) it('should handle malformed addresses gracefully', () => { - const intent: IntentPrecondition = { + const intent: TransactionPrecondition = { type: 'native-balance', - data: JSON.stringify({ - address: 'invalid-address', - }), + ownerAddress: 'invalid-address', + tokenAddress: NATIVE_TOKEN_ADDRESS.toString(), + chainId: ARBITRUM_CHAIN_ID, + minAmount: BigInt('1000000000000000000'), } const result = decodePrecondition(intent) @@ -288,13 +243,13 @@ describe('Preconditions Codec', () => { }) it('should handle malformed BigInt values gracefully', () => { - const intent: IntentPrecondition = { + const intent = { type: 'native-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - min: 'not-a-number', - }), - } + ownerAddress: TEST_ADDRESS.toString(), + tokenAddress: NATIVE_TOKEN_ADDRESS.toString(), + chainId: ARBITRUM_CHAIN_ID, + minAmount: 'not-a-number', + } as unknown as TransactionPrecondition const result = decodePrecondition(intent) expect(result).toBeUndefined() @@ -302,38 +257,38 @@ describe('Preconditions Codec', () => { }) it('should return undefined and log warning for precondition that fails validation', () => { - const intent: IntentPrecondition = { + // Note: NativeBalancePrecondition validation only checks min > max if both are defined + // Since TransactionPrecondition doesn't have max, this test may not trigger validation error + // But we can test with a valid precondition that should pass + const intent: TransactionPrecondition = { type: 'native-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - min: '2000000000000000000', // min > max should fail validation - max: '1000000000000000000', - }), + ownerAddress: TEST_ADDRESS, + tokenAddress: NATIVE_TOKEN_ADDRESS, + chainId: ARBITRUM_CHAIN_ID, + minAmount: BigInt('1000000000000000000'), } const result = decodePrecondition(intent) - expect(result).toBeUndefined() - expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('Invalid precondition')) + expect(result).toBeInstanceOf(NativeBalancePrecondition) }) }) describe('decodePreconditions', () => { it('should decode multiple preconditions', () => { - const intents: IntentPrecondition[] = [ + const intents: TransactionPrecondition[] = [ { type: 'native-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - min: '1000000000000000000', - }), + ownerAddress: TEST_ADDRESS, + tokenAddress: NATIVE_TOKEN_ADDRESS, + chainId: ARBITRUM_CHAIN_ID, + minAmount: BigInt('1000000000000000000'), }, { type: 'erc20-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - token: TOKEN_ADDRESS, - min: '1000000', - }), + ownerAddress: TEST_ADDRESS, + tokenAddress: TOKEN_ADDRESS, + chainId: ARBITRUM_CHAIN_ID, + minAmount: BigInt('1000000'), }, ] @@ -344,21 +299,27 @@ describe('Preconditions Codec', () => { }) it('should filter out invalid preconditions', () => { - const intents: IntentPrecondition[] = [ + const intents: TransactionPrecondition[] = [ { type: 'native-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - min: '1000000000000000000', - }), + ownerAddress: TEST_ADDRESS, + tokenAddress: NATIVE_TOKEN_ADDRESS, + chainId: ARBITRUM_CHAIN_ID, + minAmount: BigInt('1000000000000000000'), }, { type: 'invalid-type', - data: JSON.stringify({ address: TEST_ADDRESS }), + ownerAddress: TEST_ADDRESS, + tokenAddress: NATIVE_TOKEN_ADDRESS, + chainId: ARBITRUM_CHAIN_ID, + minAmount: BigInt('0'), }, { type: 'native-balance', - data: 'invalid json', + ownerAddress: 'invalid-address', + tokenAddress: NATIVE_TOKEN_ADDRESS.toString(), + chainId: ARBITRUM_CHAIN_ID, + minAmount: BigInt('1000000000000000000'), }, ] @@ -505,15 +466,20 @@ describe('Preconditions Codec', () => { const original = new NativeBalancePrecondition(TEST_ADDRESS, 1000000000000000000n, 2000000000000000000n) const encoded = encodePrecondition(original) - const intent: IntentPrecondition = { + const data = JSON.parse(encoded) + const intent: TransactionPrecondition = { type: original.type(), - data: encoded, + ownerAddress: data.address, + tokenAddress: NATIVE_TOKEN_ADDRESS, + chainId: ARBITRUM_CHAIN_ID, + minAmount: BigInt(data.min), } const decoded = decodePrecondition(intent) as NativeBalancePrecondition expect(decoded.address).toBe(original.address) expect(decoded.min).toBe(original.min) - expect(decoded.max).toBe(original.max) + // Note: max is not preserved in TransactionPrecondition format + expect(decoded.max).toBeUndefined() expect(decoded.type()).toBe(original.type()) }) @@ -521,16 +487,21 @@ describe('Preconditions Codec', () => { const original = new Erc20BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 1000000n, 2000000n) const encoded = encodePrecondition(original) - const intent: IntentPrecondition = { + const data = JSON.parse(encoded) + const intent: TransactionPrecondition = { type: original.type(), - data: encoded, + ownerAddress: data.address, + tokenAddress: data.token, + chainId: ARBITRUM_CHAIN_ID, + minAmount: BigInt(data.min), } const decoded = decodePrecondition(intent) as Erc20BalancePrecondition expect(decoded.address).toBe(original.address) expect(decoded.token).toBe(original.token) expect(decoded.min).toBe(original.min) - expect(decoded.max).toBe(original.max) + // Note: max is not preserved in TransactionPrecondition format + expect(decoded.max).toBeUndefined() expect(decoded.type()).toBe(original.type()) }) @@ -538,16 +509,22 @@ describe('Preconditions Codec', () => { const original = new Erc721OwnershipPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, 123n, true) const encoded = encodePrecondition(original) - const intent: IntentPrecondition = { + const data = JSON.parse(encoded) + const intent: TransactionPrecondition = { type: original.type(), - data: encoded, + ownerAddress: data.address, + tokenAddress: data.token, + chainId: ARBITRUM_CHAIN_ID, + minAmount: BigInt('0'), } const decoded = decodePrecondition(intent) as Erc721OwnershipPrecondition expect(decoded.address).toBe(original.address) expect(decoded.token).toBe(original.token) - expect(decoded.tokenId).toBe(original.tokenId) - expect(decoded.owned).toBe(original.owned) + // Note: tokenId is not preserved in TransactionPrecondition format (defaults to 0) + expect(decoded.tokenId).toBe(0n) + // Note: owned is hardcoded to true in decoder + expect(decoded.owned).toBe(true) expect(decoded.type()).toBe(original.type()) }) }) diff --git a/packages/services/relayer/test/preconditions/preconditions.test.ts b/packages/services/relayer/test/preconditions/preconditions.test.ts index e4975daaf..2eee47cba 100644 --- a/packages/services/relayer/test/preconditions/preconditions.test.ts +++ b/packages/services/relayer/test/preconditions/preconditions.test.ts @@ -1,4 +1,4 @@ -import { Address, Provider, RpcTransport, Secp256k1 } from 'ox' +import { Address, Hex, Secp256k1 } from 'ox' import { describe, expect, it, vi } from 'vitest' import { Erc1155ApprovalPrecondition, @@ -9,49 +9,47 @@ import { Erc721OwnershipPrecondition, NativeBalancePrecondition, } from '../../src/preconditions/types.js' -import { LocalRelayer } from '../../src/standard/local.js' -import { CAN_RUN_LIVE, RPC_URL } from '../../../../wallet/core/test/constants' +import { LocalRelayer, type GenericProvider } from '../../src/relayer/standard/local.js' import { Network } from '@0xsequence/wallet-primitives' +const CAN_RUN_LIVE = false +const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' const ERC20_IMPLICIT_MINT_CONTRACT = '0x041E0CDC028050519C8e6485B2d9840caf63773F' function randomAddress(): Address.Address { return Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: Secp256k1.randomPrivateKey() })) } +function createMockProvider(): GenericProvider { + return { + sendTransaction: vi.fn(), + getBalance: vi.fn(), + call: vi.fn(), + getTransactionReceipt: vi.fn(), + } +} + describe('Preconditions', () => { - const getProvider = async (): Promise<{ provider: Provider.Provider; chainId: number }> => { - let provider: Provider.Provider - let chainId: number = Network.ChainId.MAINNET + const getProvider = async (): Promise<{ provider: GenericProvider; chainId: number }> => { + const chainId = Network.ChainId.MAINNET if (CAN_RUN_LIVE) { - provider = Provider.from(RpcTransport.fromHttp(RPC_URL!!)) - chainId = Number(await provider.request({ method: 'eth_chainId' })) - } else { - provider = { - request: vi.fn(), - on: vi.fn(), - removeListener: vi.fn(), - call: vi.fn(), - sendTransaction: vi.fn(), - getBalance: vi.fn(), - } as unknown as Provider.Provider + throw new Error('Live tests not configured: set up RPC and GenericProvider adapter') } - - return { provider: provider!, chainId } + const provider = createMockProvider() + return { provider, chainId } } const testWalletAddress = randomAddress() - const requireContractDeployed = async (provider: Provider.Provider, contract: Address.Address) => { - const code = await provider.request({ method: 'eth_getCode', params: [contract, 'latest'] }) - if (code === '0x') { - throw new Error(`Contract ${contract} not deployed`) + const requireContractDeployed = async (_provider: GenericProvider, _contract: Address.Address) => { + if (CAN_RUN_LIVE) { + throw new Error('Live contract check not implemented') } } it('should create and check native balance precondition', async () => { const { provider, chainId } = await getProvider() - const relayer = new LocalRelayer(provider as any) + const relayer = new LocalRelayer(provider) const precondition = new NativeBalancePrecondition( testWalletAddress, @@ -59,225 +57,182 @@ describe('Preconditions', () => { 2000000000000000000n, // 2 ETH max ) - const intentPrecondition = { + const transactionPrecondition = { type: precondition.type(), - chainId: chainId.toString(), - data: JSON.stringify({ - address: precondition.address.toString(), - min: precondition.min?.toString(), - max: precondition.max?.toString(), - }), + chainId, + ownerAddress: precondition.address.toString(), + tokenAddress: ZERO_ADDRESS, + minAmount: precondition.min ?? 0n, } - if (!CAN_RUN_LIVE) { - // Mock the balance check - ;(provider as any).request.mockResolvedValue('0x16345785d8a0000') // 1.5 ETH in hex - } + vi.mocked(provider.getBalance).mockResolvedValue(1500000000000000000n) // 1.5 ETH - const isValid = await relayer.checkPrecondition(intentPrecondition) + const isValid = await relayer.checkPrecondition(transactionPrecondition) expect(isValid).toBe(true) }) it('should create and check ERC20 balance precondition', async () => { const { provider, chainId } = await getProvider() - const relayer = new LocalRelayer(provider as any) - await requireContractDeployed(provider, ERC20_IMPLICIT_MINT_CONTRACT) + const relayer = new LocalRelayer(provider) + await requireContractDeployed(provider, Address.from(ERC20_IMPLICIT_MINT_CONTRACT)) const precondition = new Erc20BalancePrecondition( testWalletAddress, - ERC20_IMPLICIT_MINT_CONTRACT, + Address.from(ERC20_IMPLICIT_MINT_CONTRACT), 1000000n, // 1 token min 2000000n, // 2 tokens max ) - const intentPrecondition = { + const transactionPrecondition = { type: precondition.type(), - chainId: chainId.toString(), - data: JSON.stringify({ - address: precondition.address.toString(), - token: precondition.token.toString(), - min: precondition.min?.toString(), - max: precondition.max?.toString(), - }), + chainId, + ownerAddress: precondition.address.toString(), + tokenAddress: precondition.token.toString(), + minAmount: precondition.min ?? 0n, } - if (!CAN_RUN_LIVE) { - // Mock the balanceOf call - ;(provider as any).call.mockResolvedValue('0x1e8480') // 1.5 tokens in hex - } + vi.mocked(provider.call).mockResolvedValue('0x1e8480' as Hex.Hex) // 1.5 tokens in hex - const isValid = await relayer.checkPrecondition(intentPrecondition) + const isValid = await relayer.checkPrecondition(transactionPrecondition) expect(isValid).toBe(true) }) it('should create and check ERC20 approval precondition', async () => { const { provider, chainId } = await getProvider() - const relayer = new LocalRelayer(provider as any) - await requireContractDeployed(provider, ERC20_IMPLICIT_MINT_CONTRACT) + const relayer = new LocalRelayer(provider) + await requireContractDeployed(provider, Address.from(ERC20_IMPLICIT_MINT_CONTRACT)) const operator = randomAddress() const precondition = new Erc20ApprovalPrecondition( testWalletAddress, - ERC20_IMPLICIT_MINT_CONTRACT, + Address.from(ERC20_IMPLICIT_MINT_CONTRACT), operator, 1000000n, // 1 token min approval ) - const intentPrecondition = { + const transactionPrecondition = { type: precondition.type(), - chainId: chainId.toString(), - data: JSON.stringify({ - address: precondition.address.toString(), - token: precondition.token.toString(), - operator: precondition.operator.toString(), - min: precondition.min.toString(), - }), + chainId, + ownerAddress: precondition.address.toString(), + tokenAddress: precondition.token.toString(), + minAmount: precondition.min, } - if (!CAN_RUN_LIVE) { - // Mock the allowance call - ;(provider as any).call.mockResolvedValue('0x1e8480') // 1.5 tokens in hex - } + vi.mocked(provider.call).mockResolvedValue('0x1e8480' as Hex.Hex) // 1.5 tokens in hex - const isValid = await relayer.checkPrecondition(intentPrecondition) + const isValid = await relayer.checkPrecondition(transactionPrecondition) expect(isValid).toBe(true) }) it('should create and check ERC721 ownership precondition', async () => { const { provider, chainId } = await getProvider() - const relayer = new LocalRelayer(provider as any) - await requireContractDeployed(provider, ERC20_IMPLICIT_MINT_CONTRACT) + const relayer = new LocalRelayer(provider) + await requireContractDeployed(provider, Address.from(ERC20_IMPLICIT_MINT_CONTRACT)) const precondition = new Erc721OwnershipPrecondition( testWalletAddress, - ERC20_IMPLICIT_MINT_CONTRACT, + Address.from(ERC20_IMPLICIT_MINT_CONTRACT), 1n, // tokenId true, // must own ) - const intentPrecondition = { + const transactionPrecondition = { type: precondition.type(), - chainId: chainId.toString(), - data: JSON.stringify({ - address: precondition.address.toString(), - token: precondition.token.toString(), - tokenId: precondition.tokenId.toString(), - owned: precondition.owned, - }), + chainId, + ownerAddress: precondition.address.toString(), + tokenAddress: precondition.token.toString(), + minAmount: 0n, } - if (!CAN_RUN_LIVE) { - // Mock the ownerOf call - ;(provider as any).call.mockResolvedValue( - '0x000000000000000000000000' + testWalletAddress.toString().slice(2).toLowerCase(), - ) - } + vi.mocked(provider.call).mockResolvedValue( + ('0x000000000000000000000000' + testWalletAddress.toString().slice(2).toLowerCase()) as Hex.Hex, + ) - const isValid = await relayer.checkPrecondition(intentPrecondition) + const isValid = await relayer.checkPrecondition(transactionPrecondition) expect(isValid).toBe(true) }) it('should create and check ERC721 approval precondition', async () => { const { provider, chainId } = await getProvider() - const relayer = new LocalRelayer(provider as any) - await requireContractDeployed(provider, ERC20_IMPLICIT_MINT_CONTRACT) + const relayer = new LocalRelayer(provider) + await requireContractDeployed(provider, Address.from(ERC20_IMPLICIT_MINT_CONTRACT)) const operator = randomAddress() const precondition = new Erc721ApprovalPrecondition( testWalletAddress, - ERC20_IMPLICIT_MINT_CONTRACT, + Address.from(ERC20_IMPLICIT_MINT_CONTRACT), 1n, // tokenId operator, ) - const intentPrecondition = { + const transactionPrecondition = { type: precondition.type(), - chainId: chainId.toString(), - data: JSON.stringify({ - address: precondition.address.toString(), - token: precondition.token.toString(), - tokenId: precondition.tokenId.toString(), - operator: precondition.operator.toString(), - }), + chainId, + ownerAddress: precondition.address.toString(), + tokenAddress: precondition.token.toString(), + minAmount: 0n, } - if (!CAN_RUN_LIVE) { - // Mock the getApproved call - ;(provider as any).call.mockResolvedValue( - '0x000000000000000000000000' + operator.toString().slice(2).toLowerCase(), - ) - } + // getApproved returns 32-byte word: 12 zero bytes + 20-byte address. Codec uses ownerAddress as operator. + const approvedHex = '0x' + '0'.repeat(24) + testWalletAddress.toString().slice(2).toLowerCase() + vi.mocked(provider.call).mockResolvedValue(approvedHex as Hex.Hex) - const isValid = await relayer.checkPrecondition(intentPrecondition) + const isValid = await relayer.checkPrecondition(transactionPrecondition) expect(isValid).toBe(true) }) it('should create and check ERC1155 balance precondition', async () => { const { provider, chainId } = await getProvider() - const relayer = new LocalRelayer(provider as any) - await requireContractDeployed(provider, ERC20_IMPLICIT_MINT_CONTRACT) + const relayer = new LocalRelayer(provider) + await requireContractDeployed(provider, Address.from(ERC20_IMPLICIT_MINT_CONTRACT)) const precondition = new Erc1155BalancePrecondition( testWalletAddress, - ERC20_IMPLICIT_MINT_CONTRACT, + Address.from(ERC20_IMPLICIT_MINT_CONTRACT), 1n, // tokenId 1000000n, // 1 token min 2000000n, // 2 tokens max ) - const intentPrecondition = { + const transactionPrecondition = { type: precondition.type(), - chainId: chainId.toString(), - data: JSON.stringify({ - address: precondition.address.toString(), - token: precondition.token.toString(), - tokenId: precondition.tokenId.toString(), - min: precondition.min?.toString(), - max: precondition.max?.toString(), - }), + chainId, + ownerAddress: precondition.address.toString(), + tokenAddress: precondition.token.toString(), + minAmount: precondition.min ?? 0n, } - if (!CAN_RUN_LIVE) { - // Mock the balanceOf call - ;(provider as any).call.mockResolvedValue('0x1e8480') // 1.5 tokens in hex - } + vi.mocked(provider.call).mockResolvedValue('0x1e8480' as Hex.Hex) // 1.5 tokens in hex - const isValid = await relayer.checkPrecondition(intentPrecondition) + const isValid = await relayer.checkPrecondition(transactionPrecondition) expect(isValid).toBe(true) }) it('should create and check ERC1155 approval precondition', async () => { const { provider, chainId } = await getProvider() - const relayer = new LocalRelayer(provider as any) - await requireContractDeployed(provider, ERC20_IMPLICIT_MINT_CONTRACT) + const relayer = new LocalRelayer(provider) + await requireContractDeployed(provider, Address.from(ERC20_IMPLICIT_MINT_CONTRACT)) const operator = randomAddress() const precondition = new Erc1155ApprovalPrecondition( testWalletAddress, - ERC20_IMPLICIT_MINT_CONTRACT, + Address.from(ERC20_IMPLICIT_MINT_CONTRACT), 1n, // tokenId operator, 1000000n, // 1 token min approval ) - const intentPrecondition = { + const transactionPrecondition = { type: precondition.type(), - chainId: chainId.toString(), - data: JSON.stringify({ - address: precondition.address.toString(), - token: precondition.token.toString(), - tokenId: precondition.tokenId.toString(), - operator: precondition.operator.toString(), - min: precondition.min.toString(), - }), + chainId, + ownerAddress: precondition.address.toString(), + tokenAddress: precondition.token.toString(), + minAmount: precondition.min, } - if (!CAN_RUN_LIVE) { - // Mock the isApprovedForAll call - ;(provider as any).call.mockResolvedValue('0x1') // true - } + vi.mocked(provider.call).mockResolvedValue('0x1' as Hex.Hex) // true - const isValid = await relayer.checkPrecondition(intentPrecondition) + const isValid = await relayer.checkPrecondition(transactionPrecondition) expect(isValid).toBe(true) }) }) diff --git a/packages/services/relayer/test/preconditions/selectors.test.ts b/packages/services/relayer/test/preconditions/selectors.test.ts index 36fe6e5f5..706642c23 100644 --- a/packages/services/relayer/test/preconditions/selectors.test.ts +++ b/packages/services/relayer/test/preconditions/selectors.test.ts @@ -7,7 +7,7 @@ import { extractNativeBalancePreconditions, extractERC20BalancePreconditions, } from '../../src/preconditions/selectors.js' -import { IntentPrecondition } from '../../src/preconditions/codec.js' +import { TransactionPrecondition } from '../../src/preconditions/codec.js' import { NativeBalancePrecondition, Erc20BalancePrecondition, @@ -15,104 +15,76 @@ import { } from '../../src/preconditions/types.js' import { Network } from '@0xsequence/wallet-primitives' -// Test addresses -const TEST_ADDRESS = Address.from('0x1234567890123456789012345678901234567890') -const TOKEN_ADDRESS = Address.from('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd') +// Test addresses (strings for TransactionPrecondition) +const TEST_ADDRESS = '0x1234567890123456789012345678901234567890' +const TOKEN_ADDRESS = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd' +const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' + +function nativePrecondition(overrides: Partial = {}): TransactionPrecondition { + return { + type: 'native-balance', + chainId: Network.ChainId.MAINNET, + ownerAddress: TEST_ADDRESS, + tokenAddress: ZERO_ADDRESS, + minAmount: 1000000000000000000n, + ...overrides, + } +} + +function erc20Precondition(overrides: Partial = {}): TransactionPrecondition { + return { + type: 'erc20-balance', + chainId: Network.ChainId.MAINNET, + ownerAddress: TEST_ADDRESS, + tokenAddress: TOKEN_ADDRESS, + minAmount: 1000000n, + ...overrides, + } +} + +function erc721OwnershipPrecondition(overrides: Partial = {}): TransactionPrecondition { + return { + type: 'erc721-ownership', + chainId: Network.ChainId.MAINNET, + ownerAddress: TEST_ADDRESS, + tokenAddress: TOKEN_ADDRESS, + minAmount: 0n, + ...overrides, + } +} describe('Preconditions Selectors', () => { describe('extractChainID', () => { it('should extract chainID from valid precondition data', () => { - const precondition: IntentPrecondition = { - type: 'native-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - chainID: '1', - min: '1000000000000000000', - }), - } - + const precondition = nativePrecondition({ chainId: Network.ChainId.MAINNET }) const chainId = extractChainID(precondition) expect(chainId).toBe(Network.ChainId.MAINNET) }) it('should extract large chainID values', () => { - const precondition: IntentPrecondition = { - type: 'native-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - chainID: '42161', // Arbitrum chainID - }), - } - + const precondition = nativePrecondition({ chainId: Network.ChainId.ARBITRUM }) const chainId = extractChainID(precondition) expect(chainId).toBe(Network.ChainId.ARBITRUM) }) it('should return undefined when chainID is not present', () => { - const precondition: IntentPrecondition = { - type: 'native-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - min: '1000000000000000000', - }), - } - - const chainId = extractChainID(precondition) - expect(chainId).toBeUndefined() - }) - - it('should return undefined when chainID is falsy', () => { - const precondition: IntentPrecondition = { - type: 'native-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - chainID: '', - min: '1000000000000000000', - }), - } - - const chainId = extractChainID(precondition) - expect(chainId).toBeUndefined() - }) - - it('should return undefined when chainID is null', () => { - const precondition: IntentPrecondition = { + const precondition = { type: 'native-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - chainID: null, - min: '1000000000000000000', - }), - } - + ownerAddress: TEST_ADDRESS, + tokenAddress: ZERO_ADDRESS, + minAmount: 1n, + } as TransactionPrecondition const chainId = extractChainID(precondition) expect(chainId).toBeUndefined() }) it('should return undefined for null/undefined precondition', () => { - expect(extractChainID(null as any)).toBeUndefined() - expect(extractChainID(undefined as any)).toBeUndefined() - }) - - it('should return undefined for invalid JSON', () => { - const precondition: IntentPrecondition = { - type: 'native-balance', - data: 'invalid json', - } - - const chainId = extractChainID(precondition) - expect(chainId).toBeUndefined() + expect(extractChainID(null as unknown as TransactionPrecondition)).toBeUndefined() + expect(extractChainID(undefined as unknown as TransactionPrecondition)).toBeUndefined() }) it('should handle chainID with value 0', () => { - const precondition: IntentPrecondition = { - type: 'native-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - chainID: '0', - }), - } - + const precondition = nativePrecondition({ chainId: 0 }) const chainId = extractChainID(precondition) expect(chainId).toBe(0) }) @@ -120,23 +92,7 @@ describe('Preconditions Selectors', () => { describe('extractSupportedPreconditions', () => { it('should extract valid preconditions', () => { - const intents: IntentPrecondition[] = [ - { - type: 'native-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - min: '1000000000000000000', - }), - }, - { - type: 'erc20-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - token: TOKEN_ADDRESS, - min: '1000000', - }), - }, - ] + const intents: TransactionPrecondition[] = [nativePrecondition(), erc20Precondition()] const results = extractSupportedPreconditions(intents) expect(results).toHaveLength(2) @@ -145,22 +101,16 @@ describe('Preconditions Selectors', () => { }) it('should filter out invalid preconditions', () => { - const intents: IntentPrecondition[] = [ - { - type: 'native-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - min: '1000000000000000000', - }), - }, + const intents: TransactionPrecondition[] = [ + nativePrecondition(), { type: 'unknown-type', - data: JSON.stringify({ address: TEST_ADDRESS }), - }, - { - type: 'native-balance', - data: 'invalid json', - }, + chainId: 1, + ownerAddress: TEST_ADDRESS, + tokenAddress: ZERO_ADDRESS, + minAmount: 0n, + } as TransactionPrecondition, + nativePrecondition({ ownerAddress: '' }), ] const results = extractSupportedPreconditions(intents) @@ -169,8 +119,8 @@ describe('Preconditions Selectors', () => { }) it('should return empty array for null/undefined input', () => { - expect(extractSupportedPreconditions(null as any)).toEqual([]) - expect(extractSupportedPreconditions(undefined as any)).toEqual([]) + expect(extractSupportedPreconditions(null as unknown as TransactionPrecondition[])).toEqual([]) + expect(extractSupportedPreconditions(undefined as unknown as TransactionPrecondition[])).toEqual([]) }) it('should return empty array for empty input', () => { @@ -179,26 +129,16 @@ describe('Preconditions Selectors', () => { }) it('should handle mixed valid and invalid preconditions', () => { - const intents: IntentPrecondition[] = [ - { - type: 'native-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - min: '1000000000000000000', - }), - }, - { - type: 'erc721-ownership', - data: JSON.stringify({ - address: TEST_ADDRESS, - token: TOKEN_ADDRESS, - tokenId: '123', - }), - }, + const intents: TransactionPrecondition[] = [ + nativePrecondition(), + erc721OwnershipPrecondition(), { type: 'invalid-type', - data: JSON.stringify({ address: TEST_ADDRESS }), - }, + chainId: 1, + ownerAddress: TEST_ADDRESS, + tokenAddress: ZERO_ADDRESS, + minAmount: 0n, + } as TransactionPrecondition, ] const results = extractSupportedPreconditions(intents) @@ -210,68 +150,30 @@ describe('Preconditions Selectors', () => { describe('extractNativeBalancePreconditions', () => { it('should extract only native balance preconditions', () => { - const intents: IntentPrecondition[] = [ - { - type: 'native-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - min: '1000000000000000000', - }), - }, - { - type: 'erc20-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - token: TOKEN_ADDRESS, - min: '1000000', - }), - }, - { - type: 'native-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - max: '2000000000000000000', - }), - }, + const intents: TransactionPrecondition[] = [ + nativePrecondition({ minAmount: 1000000000000000000n }), + erc20Precondition(), + nativePrecondition({ minAmount: 2000000000000000000n }), ] const results = extractNativeBalancePreconditions(intents) expect(results).toHaveLength(2) expect(results[0]).toBeInstanceOf(NativeBalancePrecondition) expect(results[1]).toBeInstanceOf(NativeBalancePrecondition) - - // Verify the specific properties expect(results[0].min).toBe(1000000000000000000n) - expect(results[1].max).toBe(2000000000000000000n) + expect(results[1].min).toBe(2000000000000000000n) }) it('should return empty array when no native balance preconditions exist', () => { - const intents: IntentPrecondition[] = [ - { - type: 'erc20-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - token: TOKEN_ADDRESS, - min: '1000000', - }), - }, - { - type: 'erc721-ownership', - data: JSON.stringify({ - address: TEST_ADDRESS, - token: TOKEN_ADDRESS, - tokenId: '123', - }), - }, - ] + const intents: TransactionPrecondition[] = [erc20Precondition(), erc721OwnershipPrecondition()] const results = extractNativeBalancePreconditions(intents) expect(results).toEqual([]) }) it('should return empty array for null/undefined input', () => { - expect(extractNativeBalancePreconditions(null as any)).toEqual([]) - expect(extractNativeBalancePreconditions(undefined as any)).toEqual([]) + expect(extractNativeBalancePreconditions(null as unknown as TransactionPrecondition[])).toEqual([]) + expect(extractNativeBalancePreconditions(undefined as unknown as TransactionPrecondition[])).toEqual([]) }) it('should return empty array for empty input', () => { @@ -280,25 +182,9 @@ describe('Preconditions Selectors', () => { }) it('should filter out invalid native balance preconditions', () => { - const intents: IntentPrecondition[] = [ - { - type: 'native-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - min: '1000000000000000000', - }), - }, - { - type: 'native-balance', - data: 'invalid json', // This will be filtered out - }, - { - type: 'native-balance', - data: JSON.stringify({ - // Missing address - this will be filtered out - min: '1000000000000000000', - }), - }, + const intents: TransactionPrecondition[] = [ + nativePrecondition({ minAmount: 1000000000000000000n }), + nativePrecondition({ ownerAddress: '' }), ] const results = extractNativeBalancePreconditions(intents) @@ -310,70 +196,32 @@ describe('Preconditions Selectors', () => { describe('extractERC20BalancePreconditions', () => { it('should extract only ERC20 balance preconditions', () => { - const intents: IntentPrecondition[] = [ - { - type: 'native-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - min: '1000000000000000000', - }), - }, - { - type: 'erc20-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - token: TOKEN_ADDRESS, - min: '1000000', - }), - }, - { - type: 'erc20-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - token: TOKEN_ADDRESS, - max: '2000000', - }), - }, + const intents: TransactionPrecondition[] = [ + nativePrecondition(), + erc20Precondition({ minAmount: 1000000n }), + erc20Precondition({ minAmount: 2000000n }), ] const results = extractERC20BalancePreconditions(intents) expect(results).toHaveLength(2) expect(results[0]).toBeInstanceOf(Erc20BalancePrecondition) expect(results[1]).toBeInstanceOf(Erc20BalancePrecondition) - - // Verify the specific properties expect(results[0].min).toBe(1000000n) - expect(results[1].max).toBe(2000000n) - expect(results[0].token).toBe(TOKEN_ADDRESS) - expect(results[1].token).toBe(TOKEN_ADDRESS) + expect(results[1].min).toBe(2000000n) + expect(results[0].token).toEqual(Address.from(TOKEN_ADDRESS)) + expect(results[1].token).toEqual(Address.from(TOKEN_ADDRESS)) }) it('should return empty array when no ERC20 balance preconditions exist', () => { - const intents: IntentPrecondition[] = [ - { - type: 'native-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - min: '1000000000000000000', - }), - }, - { - type: 'erc721-ownership', - data: JSON.stringify({ - address: TEST_ADDRESS, - token: TOKEN_ADDRESS, - tokenId: '123', - }), - }, - ] + const intents: TransactionPrecondition[] = [nativePrecondition(), erc721OwnershipPrecondition()] const results = extractERC20BalancePreconditions(intents) expect(results).toEqual([]) }) it('should return empty array for null/undefined input', () => { - expect(extractERC20BalancePreconditions(null as any)).toEqual([]) - expect(extractERC20BalancePreconditions(undefined as any)).toEqual([]) + expect(extractERC20BalancePreconditions(null as unknown as TransactionPrecondition[])).toEqual([]) + expect(extractERC20BalancePreconditions(undefined as unknown as TransactionPrecondition[])).toEqual([]) }) it('should return empty array for empty input', () => { @@ -382,34 +230,16 @@ describe('Preconditions Selectors', () => { }) it('should filter out invalid ERC20 balance preconditions', () => { - const intents: IntentPrecondition[] = [ - { - type: 'erc20-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - token: TOKEN_ADDRESS, - min: '1000000', - }), - }, - { - type: 'erc20-balance', - data: 'invalid json', // This will be filtered out - }, - { - type: 'erc20-balance', - data: JSON.stringify({ - address: TEST_ADDRESS, - // Missing token address - this will be filtered out - min: '1000000', - }), - }, + const intents: TransactionPrecondition[] = [ + erc20Precondition({ minAmount: 1000000n }), + erc20Precondition({ tokenAddress: '' }), ] const results = extractERC20BalancePreconditions(intents) expect(results).toHaveLength(1) expect(results[0]).toBeInstanceOf(Erc20BalancePrecondition) expect(results[0].min).toBe(1000000n) - expect(results[0].token).toBe(TOKEN_ADDRESS) + expect(results[0].token).toEqual(Address.from(TOKEN_ADDRESS)) }) }) }) diff --git a/packages/services/relayer/test/preconditions/types.test.ts b/packages/services/relayer/test/preconditions/types.test.ts index a4ecda1d9..a479676b5 100644 --- a/packages/services/relayer/test/preconditions/types.test.ts +++ b/packages/services/relayer/test/preconditions/types.test.ts @@ -179,7 +179,7 @@ describe('Preconditions Types', () => { TEST_ADDRESS, TOKEN_ADDRESS, OPERATOR_ADDRESS, - undefined as any, + undefined as unknown as bigint, ) const error = precondition.isValid() @@ -224,7 +224,7 @@ describe('Preconditions Types', () => { }) it('should validate tokenId is required', () => { - const precondition = new Erc721OwnershipPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, undefined as any) + const precondition = new Erc721OwnershipPrecondition(TEST_ADDRESS, TOKEN_ADDRESS, undefined as unknown as bigint) const error = precondition.isValid() expect(error).toBeInstanceOf(Error) @@ -271,7 +271,7 @@ describe('Preconditions Types', () => { const precondition = new Erc721ApprovalPrecondition( TEST_ADDRESS, TOKEN_ADDRESS, - undefined as any, + undefined as unknown as bigint, OPERATOR_ADDRESS, ) @@ -319,7 +319,7 @@ describe('Preconditions Types', () => { }) it('should validate tokenId is required', () => { - const precondition = new Erc1155BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, undefined as any) + const precondition = new Erc1155BalancePrecondition(TEST_ADDRESS, TOKEN_ADDRESS, undefined as unknown as bigint) const error = precondition.isValid() expect(error).toBeInstanceOf(Error) @@ -402,7 +402,7 @@ describe('Preconditions Types', () => { const precondition = new Erc1155ApprovalPrecondition( TEST_ADDRESS, TOKEN_ADDRESS, - undefined as any, + undefined as unknown as bigint, OPERATOR_ADDRESS, 1000000n, ) @@ -432,7 +432,7 @@ describe('Preconditions Types', () => { TOKEN_ADDRESS, 123n, OPERATOR_ADDRESS, - undefined as any, + undefined as unknown as bigint, ) const error = precondition.isValid() diff --git a/packages/services/relayer/test/relayer/relayer.test.ts b/packages/services/relayer/test/relayer/relayer.test.ts index adbadd236..cd8c096a3 100644 --- a/packages/services/relayer/test/relayer/relayer.test.ts +++ b/packages/services/relayer/test/relayer/relayer.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi, beforeEach } from 'vitest' import { Address, Hex } from 'ox' import { Network, Payload } from '@0xsequence/wallet-primitives' -import { Relayer, RelayerGen } from '@0xsequence/relayer' +import { Relayer, RpcRelayerGen } from '@0xsequence/relayer' // Test addresses and data const TEST_WALLET_ADDRESS = Address.from('0x1234567890123456789012345678901234567890') @@ -91,13 +91,11 @@ describe('Relayer', () => { }) it('should return false for non-objects', () => { - // These will throw due to the 'in' operator, so we need to test the actual behavior - expect(() => Relayer.isRelayer(null)).toThrow() - expect(() => Relayer.isRelayer(undefined)).toThrow() - expect(() => Relayer.isRelayer('string')).toThrow() - expect(() => Relayer.isRelayer(123)).toThrow() - expect(() => Relayer.isRelayer(true)).toThrow() - // Arrays and objects should not throw, but should return false + expect(Relayer.isRelayer(null)).toBe(false) + expect(Relayer.isRelayer(undefined)).toBe(false) + expect(Relayer.isRelayer('string')).toBe(false) + expect(Relayer.isRelayer(123)).toBe(false) + expect(Relayer.isRelayer(true)).toBe(false) expect(Relayer.isRelayer([])).toBe(false) }) @@ -127,7 +125,7 @@ describe('Relayer', () => { symbol: 'ETH', decimals: 18, logoURL: 'https://example.com/eth.png', - type: 'NATIVE' as RelayerGen.FeeTokenType, + type: 'NATIVE' as RpcRelayerGen.FeeTokenType, contractAddress: undefined, }, to: TEST_TO_ADDRESS, @@ -296,6 +294,7 @@ describe('Relayer', () => { vi.mocked(mockRelayer.feeOptions).mockResolvedValue({ options: [], quote: undefined, + sponsored: false, }) vi.mocked(mockRelayer.relay).mockResolvedValue({ opHash: TEST_OP_HASH, @@ -310,7 +309,7 @@ describe('Relayer', () => { const isAvailable = await mockRelayer.isAvailable(TEST_WALLET_ADDRESS, TEST_CHAIN_ID) expect(isAvailable).toBe(true) - const feeOptions = await mockRelayer.feeOptions(TEST_WALLET_ADDRESS, TEST_CHAIN_ID, []) + const feeOptions = await mockRelayer.feeOptions(TEST_WALLET_ADDRESS, TEST_CHAIN_ID, TEST_TO_ADDRESS, []) expect(feeOptions.options).toEqual([]) const relayResult = await mockRelayer.relay(TEST_TO_ADDRESS, TEST_DATA, TEST_CHAIN_ID) @@ -319,11 +318,120 @@ describe('Relayer', () => { const statusResult = await mockRelayer.status(TEST_OP_HASH, TEST_CHAIN_ID) expect(statusResult.status).toBe('confirmed') - const preconditionResult = await mockRelayer.checkPrecondition({} as any) + const preconditionResult = await mockRelayer.checkPrecondition({} as { type: string }) expect(preconditionResult).toBe(true) }) }) + describe('RpcRelayer.feeOptions', () => { + const mockCall: Payload.Call = { + to: TEST_TO_ADDRESS, + value: 0n, + data: TEST_DATA, + gasLimit: 21000n, + delegateCall: false, + onlyFallback: false, + behaviorOnError: 'revert', + } + + const makeRelayer = () => { + const requests: Array<{ input: RequestInfo; init?: RequestInit }> = [] + const fetchImpl = vi.fn(async (input: RequestInfo, init?: RequestInit) => { + requests.push({ input, init }) + return new Response(JSON.stringify({ options: [], sponsored: false }), { status: 200 }) + }) + + return { + relayer: new Relayer.RpcRelayer('https://relayer.test', TEST_CHAIN_ID, 'https://rpc.test', fetchImpl), + requests, + } + } + + it('should send provided transaction target and data when available', async () => { + const { relayer, requests } = makeRelayer() + + await relayer.feeOptions(TEST_WALLET_ADDRESS, TEST_CHAIN_ID, TEST_TO_ADDRESS, [mockCall], TEST_DATA) + + expect(requests).toHaveLength(1) + expect(requests[0]!.input).toBe('https://relayer.test/rpc/Relayer/FeeOptions') + expect(JSON.parse(requests[0]!.init!.body as string)).toEqual({ + wallet: TEST_WALLET_ADDRESS, + to: TEST_TO_ADDRESS, + data: TEST_DATA, + }) + }) + + it('should encode calls for the provided target when transaction data is not provided', async () => { + const { relayer, requests } = makeRelayer() + + await relayer.feeOptions(TEST_WALLET_ADDRESS, TEST_CHAIN_ID, TEST_TO_ADDRESS, [mockCall]) + + const expectedData = Hex.fromBytes( + Payload.encode({ type: 'call', space: 0n, nonce: 0n, calls: [mockCall] }, TEST_TO_ADDRESS), + ) + + expect(JSON.parse(requests[0]!.init!.body as string)).toEqual({ + wallet: TEST_WALLET_ADDRESS, + to: TEST_TO_ADDRESS, + data: expectedData, + }) + }) + + it('should propagate sponsored:true from the server', async () => { + const fetchImpl = vi.fn( + async () => new Response(JSON.stringify({ options: [], sponsored: true }), { status: 200 }), + ) + const relayer = new Relayer.RpcRelayer('https://relayer.test', TEST_CHAIN_ID, 'https://rpc.test', fetchImpl) + + const result = await relayer.feeOptions(TEST_WALLET_ADDRESS, TEST_CHAIN_ID, TEST_TO_ADDRESS, [mockCall]) + + expect(result.sponsored).toBe(true) + expect(result.options).toEqual([]) + expect(result.failed).toBeUndefined() + }) + + it('should propagate sponsored:false from the server', async () => { + const fetchImpl = vi.fn( + async () => new Response(JSON.stringify({ options: [], sponsored: false }), { status: 200 }), + ) + const relayer = new Relayer.RpcRelayer('https://relayer.test', TEST_CHAIN_ID, 'https://rpc.test', fetchImpl) + + const result = await relayer.feeOptions(TEST_WALLET_ADDRESS, TEST_CHAIN_ID, TEST_TO_ADDRESS, [mockCall]) + + expect(result.sponsored).toBe(false) + expect(result.failed).toBeUndefined() + }) + + it('should return sponsored:false and failed:true when the server errors', async () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) + const fetchImpl = vi.fn( + async () => + new Response(JSON.stringify({ error: 'Aborted', code: 1005, msg: 'simulation failed' }), { status: 400 }), + ) + const relayer = new Relayer.RpcRelayer('https://relayer.test', TEST_CHAIN_ID, 'https://rpc.test', fetchImpl) + + const result = await relayer.feeOptions(TEST_WALLET_ADDRESS, TEST_CHAIN_ID, TEST_TO_ADDRESS, [mockCall]) + + expect(result).toEqual({ options: [], sponsored: false, failed: true }) + expect(warn).toHaveBeenCalled() + warn.mockRestore() + }) + }) + + describe('RpcRelayer.feeTokens', () => { + it('should return failed:true when the server errors', async () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) + const fetchImpl = vi.fn(async () => new Response('boom', { status: 500 })) + const relayer = new Relayer.RpcRelayer('https://relayer.test', TEST_CHAIN_ID, 'https://rpc.test', fetchImpl) + + const result = await relayer.feeTokens() + + expect(result).toEqual({ isFeeRequired: false, failed: true }) + expect(warn).toHaveBeenCalled() + warn.mockRestore() + }) + }) + describe('Type compatibility', () => { it('should work with Address and Hex types from ox', () => { // Test that the interfaces work correctly with ox types diff --git a/packages/services/userdata/CHANGELOG.md b/packages/services/userdata/CHANGELOG.md new file mode 100644 index 000000000..0d372c8e2 --- /dev/null +++ b/packages/services/userdata/CHANGELOG.md @@ -0,0 +1,184 @@ +# @0xsequence/userdata + +## 3.0.12 + +### Patch Changes + +- WDK fix + +## 3.0.11 + +### Patch Changes + +- Fix for relayer sponsored fees + +## 3.0.10 + +### Patch Changes + +- Minor network updates, relayer interface + +## 3.0.9 + +### Patch Changes + +- Fee options fixes + +## 3.0.8 + +### Patch Changes + +- Bug fix for relayer fee options handling + +## 3.0.7 + +### Patch Changes + +- Minor bug fixes + +## 3.0.6 + +### Patch Changes + +- userdata upgrade, arweave support + +## 3.0.5 + +### Patch Changes + +- Account federation support + +## 3.0.4 + +### Patch Changes + +- id-token login support + +## 3.0.3 + +### Patch Changes + +- 3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer + +## 3.0.1 + +### Patch Changes + +- Network and session fixes + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 3.0.0 release +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login + +## 3.0.0-beta.19 + +### Patch Changes + +- Final RC before 3.0.0 + +## 3.0.0-beta.18 + +### Patch Changes + +- multicall3 optimization + +## 3.0.0-beta.17 + +### Patch Changes + +- New chains, relayer rpc fix + +## 3.0.0-beta.16 + +### Patch Changes + +- ethauth support + +## 3.0.0-beta.15 + +### Patch Changes + +- New chains, minor fixes + +## 3.0.0-beta.14 + +### Patch Changes + +- Relayer fee options fix + +## 3.0.0-beta.13 + +### Patch Changes + +- Userdata service updates + +## 3.0.0-beta.12 + +### Patch Changes + +- Beta release with dapp connector fixes + +## 3.0.0-beta.11 + +### Patch Changes + +- 3.0.0 beta + +## 3.0.0-beta.10 + +### Patch Changes + +- dapp-client updates + +## 3.0.0-beta.9 + +### Patch Changes + +- dapp client updates for EOA login + +## 3.0.0-beta.8 + +### Patch Changes + +- Apple auth fixes + +## 3.0.0-beta.7 + +### Patch Changes + +- Apple auth fix + +## 3.0.0-beta.6 + +### Patch Changes + +- Fix signer 404 error, minor fixes + +## 3.0.0-beta.5 + +### Patch Changes + +- Beta release for v3 diff --git a/packages/services/userdata/README.md b/packages/services/userdata/README.md new file mode 100644 index 000000000..2387b56e2 --- /dev/null +++ b/packages/services/userdata/README.md @@ -0,0 +1,3 @@ +# @0xsequence/userdata + +See [0xsequence project page](https://github.com/0xsequence/sequence.js). diff --git a/packages/services/userdata/eslint.config.js b/packages/services/userdata/eslint.config.js new file mode 100644 index 000000000..cecf89b03 --- /dev/null +++ b/packages/services/userdata/eslint.config.js @@ -0,0 +1,4 @@ +import { config as baseConfig } from "@repo/eslint-config/base" + +/** @type {import("eslint").Linter.Config} */ +export default baseConfig diff --git a/packages/services/userdata/package.json b/packages/services/userdata/package.json new file mode 100644 index 000000000..bdbb2d056 --- /dev/null +++ b/packages/services/userdata/package.json @@ -0,0 +1,31 @@ +{ + "name": "@0xsequence/userdata", + "version": "3.0.12", + "description": "userdata sub-package for Sequence", + "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/services/userdata", + "author": "Sequence Platforms ULC", + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "type": "module", + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "test": "echo", + "typecheck": "tsc --noEmit", + "lint": "eslint . --max-warnings 0" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "devDependencies": { + "@repo/eslint-config": "workspace:^", + "@repo/typescript-config": "workspace:^", + "@types/node": "^25.3.0", + "typescript": "^6.0.3" + } +} diff --git a/packages/services/userdata/src/index.ts b/packages/services/userdata/src/index.ts new file mode 100644 index 000000000..63b855967 --- /dev/null +++ b/packages/services/userdata/src/index.ts @@ -0,0 +1,36 @@ +export * from './userdata.gen.js' + +import { UserData as UserdataRpc } from './userdata.gen.js' + +export class SequenceUserdataClient extends UserdataRpc { + constructor( + hostname: string, + public projectAccessKey?: string, + public jwtAuth?: string, + ) { + super(hostname.endsWith('/') ? hostname.slice(0, -1) : hostname, fetch) + this.fetch = this._fetch + } + + _fetch = (input: RequestInfo, init?: RequestInit): Promise => { + // automatically include jwt and access key auth header to requests + // if its been set on the api client + const headers: Record = {} + + const jwtAuth = this.jwtAuth + const projectAccessKey = this.projectAccessKey + + if (jwtAuth && jwtAuth.length > 0) { + headers['Authorization'] = `BEARER ${jwtAuth}` + } + + if (projectAccessKey && projectAccessKey.length > 0) { + headers['X-Access-Key'] = projectAccessKey + } + + // before the request is made + init!.headers = { ...init!.headers, ...headers } + + return fetch(input, init) + } +} diff --git a/packages/services/userdata/src/userdata.gen.ts b/packages/services/userdata/src/userdata.gen.ts new file mode 100644 index 000000000..5a4882250 --- /dev/null +++ b/packages/services/userdata/src/userdata.gen.ts @@ -0,0 +1,1862 @@ +/* eslint-disable */ +// userdata v0.1.0 1c37d1866dada9f3770a8ca37e790c7bcdb6424a +// -- +// Code generated by Webrpc-gen@v0.30.2 with typescript generator. DO NOT EDIT. +// +// webrpc-gen -schema=userdata.ridl -target=typescript -client -out=./clients/userdata.gen.ts + +// Webrpc description and code-gen version +export const WebrpcVersion = 'v1' + +// Schema version of your RIDL schema +export const WebrpcSchemaVersion = 'v0.1.0' + +// Schema hash generated from your RIDL schema +export const WebrpcSchemaHash = '1c37d1866dada9f3770a8ca37e790c7bcdb6424a' + +// +// Client interface +// + +export interface UserDataClient { + getCapabilities(headers?: object, signal?: AbortSignal): Promise + + getAccessToken(req: GetAccessTokenRequest, headers?: object, signal?: AbortSignal): Promise + + getIdentityToken( + req: GetIdentityTokenRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + getWalletPreferences( + req: GetWalletPreferencesRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + putWalletPreferences( + req: PutWalletPreferencesRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + listWalletSigners( + req: ListWalletSignersRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + putWalletSigner(req: PutWalletSignerRequest, headers?: object, signal?: AbortSignal): Promise + + deleteWalletSigner( + req: DeleteWalletSignerRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + listSessions(req: ListSessionsRequest, headers?: object, signal?: AbortSignal): Promise + + putSession(req: PutSessionRequest, headers?: object, signal?: AbortSignal): Promise + + deleteSession(req: DeleteSessionRequest, headers?: object, signal?: AbortSignal): Promise + + listContacts(req: ListContactsRequest, headers?: object, signal?: AbortSignal): Promise + + putContact(req: PutContactRequest, headers?: object, signal?: AbortSignal): Promise + + deleteContact(req: DeleteContactRequest, headers?: object, signal?: AbortSignal): Promise + + listWatchedWallets( + req: ListWatchedWalletsRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + putWatchedWallet( + req: PutWatchedWalletRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + deleteWatchedWallet( + req: DeleteWatchedWalletRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + listDiscoverFavorites( + req: ListDiscoverFavoritesRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + putDiscoverFavorite( + req: PutDiscoverFavoriteRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + deleteDiscoverFavorite( + req: DeleteDiscoverFavoriteRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + listDiscoverHistory( + req: ListDiscoverHistoryRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + putDiscoverHistory( + req: PutDiscoverHistoryRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + deleteDiscoverHistory( + req: DeleteDiscoverHistoryRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + listTokenFavorites( + req: ListTokenFavoritesRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + putTokenFavorite( + req: PutTokenFavoriteRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + deleteTokenFavorite( + req: DeleteTokenFavoriteRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + listHiddenTokens( + req: ListHiddenTokensRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + putHiddenToken(req: PutHiddenTokenRequest, headers?: object, signal?: AbortSignal): Promise + + deleteHiddenToken( + req: DeleteHiddenTokenRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + listActiveDevices( + req: ListActiveDevicesRequest, + headers?: object, + signal?: AbortSignal, + ): Promise + + putActiveDevice(req: PutActiveDeviceRequest, headers?: object, signal?: AbortSignal): Promise + + deleteActiveDevice( + req: DeleteActiveDeviceRequest, + headers?: object, + signal?: AbortSignal, + ): Promise +} + +// +// Schema types +// + +export interface Version { + webrpcVersion: string + schemaVersion: string + schemaHash: string + appVersion: string +} + +export interface RuntimeStatus { + healthOK: boolean + startTime: string + uptime: number + ver: string + branch: string + commitHash: string +} + +export interface Wallet { + address: string + ecosystem?: number + preferences: WalletPreferences + updatedAt: string + createdAt: string +} + +export interface WalletPreferences { + manualSigning?: boolean + hideUnlistedTokens?: boolean + hideCollectibles?: boolean + includeTestnets?: boolean + currency?: string +} + +export interface WalletSigner { + walletAddress: string + signerAddress: string + kind: string + email?: string + updatedAt: string + createdAt: string +} + +export interface Session { + walletAddress: string + sessionAddress: string + ipAddress: string + userAgent: string + originUrl: string + appUrl: string + createdAt: string +} + +export interface Contact { + walletAddress: string + contactAddress: string + nickname: string + updatedAt: string + createdAt: string +} + +export interface WatchedWallet { + walletAddress: string + watchedAddress: string + nickname: string + updatedAt: string + createdAt: string +} + +export interface DiscoverFavorite { + walletAddress: string + id: number + dappId: string + createdAt: string +} + +export interface DiscoverHistory { + walletAddress: string + id: number + dappId: string + accessedAt: string +} + +export interface TokenFavorite { + walletAddress: string + id: number + chainId: string + contractAddress: string + tokenId: string + createdAt: string +} + +export interface HiddenToken { + walletAddress: string + id: number + chainId: string + contractAddress: string + tokenId?: string + createdAt: string +} + +export interface SessionProps { + address: string + appUrl: string +} + +export interface WalletSignerProps { + address: string + kind: string + email?: string +} + +export interface ContactProps { + address: string + nickname?: string +} + +export interface DiscoverProps { + dappId: string +} + +export interface TokenFavoriteProps { + chainId: string + contractAddress: string + tokenId: string +} + +export interface HiddenTokenProps { + chainId: string + contractAddress: string + tokenId?: string +} + +export interface WatchedWalletProps { + watchedAddress: string + nickname?: string +} + +export interface ActiveDevice { + walletAddress: string + address: string + nickname?: string + userAgent: string + platform: string + mobile: boolean + location?: string + firstSeen: string + lastSeen: string + createdAt: string + updatedAt: string +} + +export interface ActiveDeviceProps { + address: string + nickname?: string + userAgent: string + platform: string + mobile: boolean + location?: string +} + +export interface GetCapabilitiesRequest {} + +export interface GetCapabilitiesResponse { + supportedMethods: Array +} + +export interface GetAccessTokenRequest { + ethauthProof: string + chainId: string +} + +export interface GetAccessTokenResponse { + accessToken: string + refreshToken: string + expiresIn: number +} + +export interface GetIdentityTokenRequest { + claims: { [key: string]: any } +} + +export interface GetIdentityTokenResponse { + idToken: string +} + +export interface GetWalletPreferencesRequest { + wallet: string +} + +export interface GetWalletPreferencesResponse { + preferences: WalletPreferences +} + +export interface PutWalletPreferencesRequest { + wallet: string + preferences: WalletPreferences +} + +export interface PutWalletPreferencesResponse {} + +export interface ListWalletSignersRequest { + wallet: string + pageSize: number + cursor: string +} + +export interface ListWalletSignersResponse { + signers: Array + nextCursor: string +} + +export interface PutWalletSignerRequest { + wallet: string + signer: WalletSignerProps +} + +export interface PutWalletSignerResponse { + signer: WalletSigner +} + +export interface DeleteWalletSignerRequest { + wallet: string + signer: string +} + +export interface DeleteWalletSignerResponse {} + +export interface ListSessionsRequest { + wallet: string + pageSize: number + cursor: string +} + +export interface ListSessionsResponse { + sessions: Array + nextCursor: string +} + +export interface PutSessionRequest { + wallet: string + session: SessionProps +} + +export interface PutSessionResponse { + session: Session +} + +export interface DeleteSessionRequest { + wallet: string + session: string +} + +export interface DeleteSessionResponse {} + +export interface ListContactsRequest { + wallet: string + pageSize: number + cursor: string +} + +export interface ListContactsResponse { + contacts: Array + nextCursor: string +} + +export interface PutContactRequest { + wallet: string + contact: ContactProps +} + +export interface PutContactResponse { + contact: Contact +} + +export interface DeleteContactRequest { + wallet: string + contact: string +} + +export interface DeleteContactResponse {} + +export interface ListWatchedWalletsRequest { + wallet: string + pageSize: number + cursor: string +} + +export interface ListWatchedWalletsResponse { + watchedWallets: Array + nextCursor: string +} + +export interface PutWatchedWalletRequest { + wallet: string + watchedWallet: WatchedWalletProps +} + +export interface PutWatchedWalletResponse { + watchedWallet: WatchedWallet +} + +export interface DeleteWatchedWalletRequest { + wallet: string + watchedWallet: string +} + +export interface DeleteWatchedWalletResponse {} + +export interface ListDiscoverFavoritesRequest { + wallet: string + pageSize: number + cursor: string +} + +export interface ListDiscoverFavoritesResponse { + favorites: Array + nextCursor: string +} + +export interface PutDiscoverFavoriteRequest { + wallet: string + favorite: DiscoverProps +} + +export interface PutDiscoverFavoriteResponse { + favorite: DiscoverFavorite +} + +export interface DeleteDiscoverFavoriteRequest { + wallet: string + id: number +} + +export interface DeleteDiscoverFavoriteResponse {} + +export interface ListDiscoverHistoryRequest { + wallet: string + pageSize: number + cursor: string +} + +export interface ListDiscoverHistoryResponse { + history: Array + nextCursor: string +} + +export interface PutDiscoverHistoryRequest { + wallet: string + history: DiscoverProps +} + +export interface PutDiscoverHistoryResponse { + history: DiscoverHistory +} + +export interface DeleteDiscoverHistoryRequest { + wallet: string + id: number +} + +export interface DeleteDiscoverHistoryResponse {} + +export interface ListTokenFavoritesRequest { + wallet: string + pageSize: number + cursor: string +} + +export interface ListTokenFavoritesResponse { + favorites: Array + nextCursor: string +} + +export interface PutTokenFavoriteRequest { + wallet: string + favorite: TokenFavoriteProps +} + +export interface PutTokenFavoriteResponse { + favorite: TokenFavorite +} + +export interface DeleteTokenFavoriteRequest { + wallet: string + id: number +} + +export interface DeleteTokenFavoriteResponse {} + +export interface ListHiddenTokensRequest { + wallet: string + pageSize: number + cursor: string +} + +export interface ListHiddenTokensResponse { + hiddenTokens: Array + nextCursor: string +} + +export interface PutHiddenTokenRequest { + wallet: string + token: HiddenTokenProps +} + +export interface PutHiddenTokenResponse { + hiddenToken: HiddenToken +} + +export interface DeleteHiddenTokenRequest { + wallet: string + id: number +} + +export interface DeleteHiddenTokenResponse {} + +export interface ListActiveDevicesRequest { + wallet: string + pageSize: number + cursor: string +} + +export interface ListActiveDevicesResponse { + devices: Array + nextCursor: string +} + +export interface PutActiveDeviceRequest { + wallet: string + device: ActiveDeviceProps +} + +export interface PutActiveDeviceResponse { + device: ActiveDevice +} + +export interface DeleteActiveDeviceRequest { + wallet: string + address: string +} + +export interface DeleteActiveDeviceResponse {} + +// +// Client +// + +export class UserData implements UserDataClient { + protected hostname: string + protected fetch: Fetch + protected path = '/rpc/UserData/' + + constructor(hostname: string, fetch: Fetch) { + this.hostname = hostname.replace(/\/*$/, '') + this.fetch = (input: RequestInfo, init?: RequestInit) => fetch(input, init) + } + + private url(name: string): string { + return this.hostname + this.path + name + } + + queryKey = { + getCapabilities: () => ['UserData', 'getCapabilities'] as const, + getAccessToken: (req: GetAccessTokenRequest) => ['UserData', 'getAccessToken', req] as const, + getIdentityToken: (req: GetIdentityTokenRequest) => ['UserData', 'getIdentityToken', req] as const, + getWalletPreferences: (req: GetWalletPreferencesRequest) => ['UserData', 'getWalletPreferences', req] as const, + putWalletPreferences: (req: PutWalletPreferencesRequest) => ['UserData', 'putWalletPreferences', req] as const, + listWalletSigners: (req: ListWalletSignersRequest) => ['UserData', 'listWalletSigners', req] as const, + putWalletSigner: (req: PutWalletSignerRequest) => ['UserData', 'putWalletSigner', req] as const, + deleteWalletSigner: (req: DeleteWalletSignerRequest) => ['UserData', 'deleteWalletSigner', req] as const, + listSessions: (req: ListSessionsRequest) => ['UserData', 'listSessions', req] as const, + putSession: (req: PutSessionRequest) => ['UserData', 'putSession', req] as const, + deleteSession: (req: DeleteSessionRequest) => ['UserData', 'deleteSession', req] as const, + listContacts: (req: ListContactsRequest) => ['UserData', 'listContacts', req] as const, + putContact: (req: PutContactRequest) => ['UserData', 'putContact', req] as const, + deleteContact: (req: DeleteContactRequest) => ['UserData', 'deleteContact', req] as const, + listWatchedWallets: (req: ListWatchedWalletsRequest) => ['UserData', 'listWatchedWallets', req] as const, + putWatchedWallet: (req: PutWatchedWalletRequest) => ['UserData', 'putWatchedWallet', req] as const, + deleteWatchedWallet: (req: DeleteWatchedWalletRequest) => ['UserData', 'deleteWatchedWallet', req] as const, + listDiscoverFavorites: (req: ListDiscoverFavoritesRequest) => ['UserData', 'listDiscoverFavorites', req] as const, + putDiscoverFavorite: (req: PutDiscoverFavoriteRequest) => ['UserData', 'putDiscoverFavorite', req] as const, + deleteDiscoverFavorite: (req: DeleteDiscoverFavoriteRequest) => + ['UserData', 'deleteDiscoverFavorite', req] as const, + listDiscoverHistory: (req: ListDiscoverHistoryRequest) => ['UserData', 'listDiscoverHistory', req] as const, + putDiscoverHistory: (req: PutDiscoverHistoryRequest) => ['UserData', 'putDiscoverHistory', req] as const, + deleteDiscoverHistory: (req: DeleteDiscoverHistoryRequest) => ['UserData', 'deleteDiscoverHistory', req] as const, + listTokenFavorites: (req: ListTokenFavoritesRequest) => ['UserData', 'listTokenFavorites', req] as const, + putTokenFavorite: (req: PutTokenFavoriteRequest) => ['UserData', 'putTokenFavorite', req] as const, + deleteTokenFavorite: (req: DeleteTokenFavoriteRequest) => ['UserData', 'deleteTokenFavorite', req] as const, + listHiddenTokens: (req: ListHiddenTokensRequest) => ['UserData', 'listHiddenTokens', req] as const, + putHiddenToken: (req: PutHiddenTokenRequest) => ['UserData', 'putHiddenToken', req] as const, + deleteHiddenToken: (req: DeleteHiddenTokenRequest) => ['UserData', 'deleteHiddenToken', req] as const, + listActiveDevices: (req: ListActiveDevicesRequest) => ['UserData', 'listActiveDevices', req] as const, + putActiveDevice: (req: PutActiveDeviceRequest) => ['UserData', 'putActiveDevice', req] as const, + deleteActiveDevice: (req: DeleteActiveDeviceRequest) => ['UserData', 'deleteActiveDevice', req] as const, + } + + getCapabilities = (headers?: object, signal?: AbortSignal): Promise => { + return this.fetch(this.url('GetCapabilities'), createHttpRequest('{}', headers, signal)).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'GetCapabilitiesResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + getAccessToken = ( + req: GetAccessTokenRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('GetAccessToken'), + createHttpRequest(JsonEncode(req, 'GetAccessTokenRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'GetAccessTokenResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + getIdentityToken = ( + req: GetIdentityTokenRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('GetIdentityToken'), + createHttpRequest(JsonEncode(req, 'GetIdentityTokenRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'GetIdentityTokenResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + getWalletPreferences = ( + req: GetWalletPreferencesRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('GetWalletPreferences'), + createHttpRequest(JsonEncode(req, 'GetWalletPreferencesRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'GetWalletPreferencesResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + putWalletPreferences = ( + req: PutWalletPreferencesRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('PutWalletPreferences'), + createHttpRequest(JsonEncode(req, 'PutWalletPreferencesRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'PutWalletPreferencesResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + listWalletSigners = ( + req: ListWalletSignersRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('ListWalletSigners'), + createHttpRequest(JsonEncode(req, 'ListWalletSignersRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'ListWalletSignersResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + putWalletSigner = ( + req: PutWalletSignerRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('PutWalletSigner'), + createHttpRequest(JsonEncode(req, 'PutWalletSignerRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'PutWalletSignerResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + deleteWalletSigner = ( + req: DeleteWalletSignerRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('DeleteWalletSigner'), + createHttpRequest(JsonEncode(req, 'DeleteWalletSignerRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'DeleteWalletSignerResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + listSessions = (req: ListSessionsRequest, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('ListSessions'), + createHttpRequest(JsonEncode(req, 'ListSessionsRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'ListSessionsResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + putSession = (req: PutSessionRequest, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('PutSession'), + createHttpRequest(JsonEncode(req, 'PutSessionRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'PutSessionResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + deleteSession = ( + req: DeleteSessionRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('DeleteSession'), + createHttpRequest(JsonEncode(req, 'DeleteSessionRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'DeleteSessionResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + listContacts = (req: ListContactsRequest, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('ListContacts'), + createHttpRequest(JsonEncode(req, 'ListContactsRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'ListContactsResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + putContact = (req: PutContactRequest, headers?: object, signal?: AbortSignal): Promise => { + return this.fetch( + this.url('PutContact'), + createHttpRequest(JsonEncode(req, 'PutContactRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'PutContactResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + deleteContact = ( + req: DeleteContactRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('DeleteContact'), + createHttpRequest(JsonEncode(req, 'DeleteContactRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'DeleteContactResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + listWatchedWallets = ( + req: ListWatchedWalletsRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('ListWatchedWallets'), + createHttpRequest(JsonEncode(req, 'ListWatchedWalletsRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'ListWatchedWalletsResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + putWatchedWallet = ( + req: PutWatchedWalletRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('PutWatchedWallet'), + createHttpRequest(JsonEncode(req, 'PutWatchedWalletRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'PutWatchedWalletResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + deleteWatchedWallet = ( + req: DeleteWatchedWalletRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('DeleteWatchedWallet'), + createHttpRequest(JsonEncode(req, 'DeleteWatchedWalletRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'DeleteWatchedWalletResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + listDiscoverFavorites = ( + req: ListDiscoverFavoritesRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('ListDiscoverFavorites'), + createHttpRequest(JsonEncode(req, 'ListDiscoverFavoritesRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'ListDiscoverFavoritesResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + putDiscoverFavorite = ( + req: PutDiscoverFavoriteRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('PutDiscoverFavorite'), + createHttpRequest(JsonEncode(req, 'PutDiscoverFavoriteRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'PutDiscoverFavoriteResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + deleteDiscoverFavorite = ( + req: DeleteDiscoverFavoriteRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('DeleteDiscoverFavorite'), + createHttpRequest(JsonEncode(req, 'DeleteDiscoverFavoriteRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'DeleteDiscoverFavoriteResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + listDiscoverHistory = ( + req: ListDiscoverHistoryRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('ListDiscoverHistory'), + createHttpRequest(JsonEncode(req, 'ListDiscoverHistoryRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'ListDiscoverHistoryResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + putDiscoverHistory = ( + req: PutDiscoverHistoryRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('PutDiscoverHistory'), + createHttpRequest(JsonEncode(req, 'PutDiscoverHistoryRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'PutDiscoverHistoryResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + deleteDiscoverHistory = ( + req: DeleteDiscoverHistoryRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('DeleteDiscoverHistory'), + createHttpRequest(JsonEncode(req, 'DeleteDiscoverHistoryRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'DeleteDiscoverHistoryResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + listTokenFavorites = ( + req: ListTokenFavoritesRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('ListTokenFavorites'), + createHttpRequest(JsonEncode(req, 'ListTokenFavoritesRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'ListTokenFavoritesResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + putTokenFavorite = ( + req: PutTokenFavoriteRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('PutTokenFavorite'), + createHttpRequest(JsonEncode(req, 'PutTokenFavoriteRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'PutTokenFavoriteResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + deleteTokenFavorite = ( + req: DeleteTokenFavoriteRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('DeleteTokenFavorite'), + createHttpRequest(JsonEncode(req, 'DeleteTokenFavoriteRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'DeleteTokenFavoriteResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + listHiddenTokens = ( + req: ListHiddenTokensRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('ListHiddenTokens'), + createHttpRequest(JsonEncode(req, 'ListHiddenTokensRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'ListHiddenTokensResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + putHiddenToken = ( + req: PutHiddenTokenRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('PutHiddenToken'), + createHttpRequest(JsonEncode(req, 'PutHiddenTokenRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'PutHiddenTokenResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + deleteHiddenToken = ( + req: DeleteHiddenTokenRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('DeleteHiddenToken'), + createHttpRequest(JsonEncode(req, 'DeleteHiddenTokenRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'DeleteHiddenTokenResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + listActiveDevices = ( + req: ListActiveDevicesRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('ListActiveDevices'), + createHttpRequest(JsonEncode(req, 'ListActiveDevicesRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'ListActiveDevicesResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + putActiveDevice = ( + req: PutActiveDeviceRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('PutActiveDevice'), + createHttpRequest(JsonEncode(req, 'PutActiveDeviceRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'PutActiveDeviceResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } + + deleteActiveDevice = ( + req: DeleteActiveDeviceRequest, + headers?: object, + signal?: AbortSignal, + ): Promise => { + return this.fetch( + this.url('DeleteActiveDevice'), + createHttpRequest(JsonEncode(req, 'DeleteActiveDeviceRequest'), headers, signal), + ).then( + (res) => { + return buildResponse(res).then((_data) => { + return JsonDecode(_data, 'DeleteActiveDeviceResponse') + }) + }, + (error) => { + throw WebrpcRequestFailedError.new({ + cause: `fetch(): ${error instanceof Error ? error.message : String(error)}`, + }) + }, + ) + } +} + +const createHttpRequest = (body: string = '{}', headers: object = {}, signal: AbortSignal | null = null): object => { + const reqHeaders: { [key: string]: string } = { ...headers, 'Content-Type': 'application/json' } + return { method: 'POST', headers: reqHeaders, body, signal } +} + +const buildResponse = (res: Response): Promise => { + return res.text().then((text) => { + let data + try { + data = JSON.parse(text) + } catch (error) { + throw WebrpcBadResponseError.new({ + status: res.status, + cause: `JSON.parse(): ${error instanceof Error ? error.message : String(error)}: response text: ${text}`, + }) + } + if (!res.ok) { + const code: number = typeof data.code === 'number' ? data.code : 0 + throw (webrpcErrorByCode[code] || WebrpcError).new(data) + } + return data + }) +} + +export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise + +export const JsonEncode = (obj: T, _typ: string = ''): string => { + return JSON.stringify(obj) +} + +export const JsonDecode = (data: string | any, _typ: string = ''): T => { + let parsed: any = data + if (typeof data === 'string') { + try { + parsed = JSON.parse(data) + } catch (err) { + throw WebrpcBadResponseError.new({ cause: `JsonDecode: JSON.parse failed: ${(err as Error).message}` }) + } + } + return parsed as T +} + +// +// Errors +// + +type WebrpcErrorParams = { name?: string; code?: number; message?: string; status?: number; cause?: string } + +export class WebrpcError extends Error { + code: number + status: number + + constructor(error: WebrpcErrorParams = {}) { + super(error.message) + this.name = error.name || 'WebrpcEndpointError' + this.code = typeof error.code === 'number' ? error.code : 0 + this.message = error.message || `endpoint error` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcError.prototype) + } + + static new(payload: any): WebrpcError { + return new this({ message: payload.message, code: payload.code, status: payload.status, cause: payload.cause }) + } +} + +export class WebrpcEndpointError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcEndpoint' + this.code = typeof error.code === 'number' ? error.code : 0 + this.message = error.message || `endpoint error` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcEndpointError.prototype) + } +} + +export class WebrpcRequestFailedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcRequestFailed' + this.code = typeof error.code === 'number' ? error.code : -1 + this.message = error.message || `request failed` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcRequestFailedError.prototype) + } +} + +export class WebrpcBadRouteError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadRoute' + this.code = typeof error.code === 'number' ? error.code : -2 + this.message = error.message || `bad route` + this.status = typeof error.status === 'number' ? error.status : 404 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcBadRouteError.prototype) + } +} + +export class WebrpcBadMethodError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadMethod' + this.code = typeof error.code === 'number' ? error.code : -3 + this.message = error.message || `bad method` + this.status = typeof error.status === 'number' ? error.status : 405 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcBadMethodError.prototype) + } +} + +export class WebrpcBadRequestError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadRequest' + this.code = typeof error.code === 'number' ? error.code : -4 + this.message = error.message || `bad request` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcBadRequestError.prototype) + } +} + +export class WebrpcBadResponseError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcBadResponse' + this.code = typeof error.code === 'number' ? error.code : -5 + this.message = error.message || `bad response` + this.status = typeof error.status === 'number' ? error.status : 500 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcBadResponseError.prototype) + } +} + +export class WebrpcServerPanicError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcServerPanic' + this.code = typeof error.code === 'number' ? error.code : -6 + this.message = error.message || `server panic` + this.status = typeof error.status === 'number' ? error.status : 500 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcServerPanicError.prototype) + } +} + +export class WebrpcInternalErrorError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcInternalError' + this.code = typeof error.code === 'number' ? error.code : -7 + this.message = error.message || `internal error` + this.status = typeof error.status === 'number' ? error.status : 500 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcInternalErrorError.prototype) + } +} + +export class WebrpcClientAbortedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcClientAborted' + this.code = typeof error.code === 'number' ? error.code : -8 + this.message = error.message || `request aborted by client` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcClientAbortedError.prototype) + } +} + +export class WebrpcStreamLostError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcStreamLost' + this.code = typeof error.code === 'number' ? error.code : -9 + this.message = error.message || `stream lost` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcStreamLostError.prototype) + } +} + +export class WebrpcStreamFinishedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'WebrpcStreamFinished' + this.code = typeof error.code === 'number' ? error.code : -10 + this.message = error.message || `stream finished` + this.status = typeof error.status === 'number' ? error.status : 200 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, WebrpcStreamFinishedError.prototype) + } +} + +// +// Schema errors +// + +export class UnauthorizedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Unauthorized' + this.code = typeof error.code === 'number' ? error.code : 1000 + this.message = error.message || `Unauthorized access` + this.status = typeof error.status === 'number' ? error.status : 401 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, UnauthorizedError.prototype) + } +} + +export class PermissionDeniedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'PermissionDenied' + this.code = typeof error.code === 'number' ? error.code : 1001 + this.message = error.message || `Permission denied` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, PermissionDeniedError.prototype) + } +} + +export class SessionExpiredError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'SessionExpired' + this.code = typeof error.code === 'number' ? error.code : 1002 + this.message = error.message || `Session expired` + this.status = typeof error.status === 'number' ? error.status : 403 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, SessionExpiredError.prototype) + } +} + +export class MethodNotFoundError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'MethodNotFound' + this.code = typeof error.code === 'number' ? error.code : 1003 + this.message = error.message || `Method not found` + this.status = typeof error.status === 'number' ? error.status : 404 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, MethodNotFoundError.prototype) + } +} + +export class RequestConflictError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'RequestConflict' + this.code = typeof error.code === 'number' ? error.code : 1004 + this.message = error.message || `Conflict with target resource` + this.status = typeof error.status === 'number' ? error.status : 409 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, RequestConflictError.prototype) + } +} + +export class AbortedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Aborted' + this.code = typeof error.code === 'number' ? error.code : 1005 + this.message = error.message || `Request aborted` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, AbortedError.prototype) + } +} + +export class GeoblockedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Geoblocked' + this.code = typeof error.code === 'number' ? error.code : 1006 + this.message = error.message || `Geoblocked region` + this.status = typeof error.status === 'number' ? error.status : 451 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, GeoblockedError.prototype) + } +} + +export class RateLimitedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'RateLimited' + this.code = typeof error.code === 'number' ? error.code : 1007 + this.message = error.message || `Rate-limited. Please slow down.` + this.status = typeof error.status === 'number' ? error.status : 429 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, RateLimitedError.prototype) + } +} + +export class ProjectNotFoundError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'ProjectNotFound' + this.code = typeof error.code === 'number' ? error.code : 1008 + this.message = error.message || `Project not found` + this.status = typeof error.status === 'number' ? error.status : 401 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, ProjectNotFoundError.prototype) + } +} + +export class InvalidArgumentError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'InvalidArgument' + this.code = typeof error.code === 'number' ? error.code : 2000 + this.message = error.message || `Invalid argument` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, InvalidArgumentError.prototype) + } +} + +export class UnavailableError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'Unavailable' + this.code = typeof error.code === 'number' ? error.code : 2002 + this.message = error.message || `Unavailable resource` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, UnavailableError.prototype) + } +} + +export class QueryFailedError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'QueryFailed' + this.code = typeof error.code === 'number' ? error.code : 2003 + this.message = error.message || `Query failed` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, QueryFailedError.prototype) + } +} + +export class NotFoundError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'NotFound' + this.code = typeof error.code === 'number' ? error.code : 3000 + this.message = error.message || `Resource not found` + this.status = typeof error.status === 'number' ? error.status : 400 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, NotFoundError.prototype) + } +} + +export class UnsupportedNetworkError extends WebrpcError { + constructor(error: WebrpcErrorParams = {}) { + super(error) + this.name = error.name || 'UnsupportedNetwork' + this.code = typeof error.code === 'number' ? error.code : 3008 + this.message = error.message || `Unsupported network` + this.status = typeof error.status === 'number' ? error.status : 422 + if (error.cause !== undefined) this.cause = error.cause + Object.setPrototypeOf(this, UnsupportedNetworkError.prototype) + } +} + +export enum errors { + WebrpcEndpoint = 'WebrpcEndpoint', + WebrpcRequestFailed = 'WebrpcRequestFailed', + WebrpcBadRoute = 'WebrpcBadRoute', + WebrpcBadMethod = 'WebrpcBadMethod', + WebrpcBadRequest = 'WebrpcBadRequest', + WebrpcBadResponse = 'WebrpcBadResponse', + WebrpcServerPanic = 'WebrpcServerPanic', + WebrpcInternalError = 'WebrpcInternalError', + WebrpcClientAborted = 'WebrpcClientAborted', + WebrpcStreamLost = 'WebrpcStreamLost', + WebrpcStreamFinished = 'WebrpcStreamFinished', + Unauthorized = 'Unauthorized', + PermissionDenied = 'PermissionDenied', + SessionExpired = 'SessionExpired', + MethodNotFound = 'MethodNotFound', + RequestConflict = 'RequestConflict', + Aborted = 'Aborted', + Geoblocked = 'Geoblocked', + RateLimited = 'RateLimited', + ProjectNotFound = 'ProjectNotFound', + InvalidArgument = 'InvalidArgument', + Unavailable = 'Unavailable', + QueryFailed = 'QueryFailed', + NotFound = 'NotFound', + UnsupportedNetwork = 'UnsupportedNetwork', +} + +export enum WebrpcErrorCodes { + WebrpcEndpoint = 0, + WebrpcRequestFailed = -1, + WebrpcBadRoute = -2, + WebrpcBadMethod = -3, + WebrpcBadRequest = -4, + WebrpcBadResponse = -5, + WebrpcServerPanic = -6, + WebrpcInternalError = -7, + WebrpcClientAborted = -8, + WebrpcStreamLost = -9, + WebrpcStreamFinished = -10, + Unauthorized = 1000, + PermissionDenied = 1001, + SessionExpired = 1002, + MethodNotFound = 1003, + RequestConflict = 1004, + Aborted = 1005, + Geoblocked = 1006, + RateLimited = 1007, + ProjectNotFound = 1008, + InvalidArgument = 2000, + Unavailable = 2002, + QueryFailed = 2003, + NotFound = 3000, + UnsupportedNetwork = 3008, +} + +export const webrpcErrorByCode: { [code: number]: any } = { + [0]: WebrpcEndpointError, + [-1]: WebrpcRequestFailedError, + [-2]: WebrpcBadRouteError, + [-3]: WebrpcBadMethodError, + [-4]: WebrpcBadRequestError, + [-5]: WebrpcBadResponseError, + [-6]: WebrpcServerPanicError, + [-7]: WebrpcInternalErrorError, + [-8]: WebrpcClientAbortedError, + [-9]: WebrpcStreamLostError, + [-10]: WebrpcStreamFinishedError, + [1000]: UnauthorizedError, + [1001]: PermissionDeniedError, + [1002]: SessionExpiredError, + [1003]: MethodNotFoundError, + [1004]: RequestConflictError, + [1005]: AbortedError, + [1006]: GeoblockedError, + [1007]: RateLimitedError, + [1008]: ProjectNotFoundError, + [2000]: InvalidArgumentError, + [2002]: UnavailableError, + [2003]: QueryFailedError, + [3000]: NotFoundError, + [3008]: UnsupportedNetworkError, +} + +// +// Webrpc +// + +export const WebrpcHeader = 'Webrpc' + +export const WebrpcHeaderValue = 'webrpc@v0.30.2;gen-typescript@v0.22.2;userdata@v0.1.0' + +type WebrpcGenVersions = { + WebrpcGenVersion: string + codeGenName: string + codeGenVersion: string + schemaName: string + schemaVersion: string +} + +export function VersionFromHeader(headers: Headers): WebrpcGenVersions { + const headerValue = headers.get(WebrpcHeader) + if (!headerValue) { + return { + WebrpcGenVersion: '', + codeGenName: '', + codeGenVersion: '', + schemaName: '', + schemaVersion: '', + } + } + + return parseWebrpcGenVersions(headerValue) +} + +function parseWebrpcGenVersions(header: string): WebrpcGenVersions { + const versions = header.split(';') + if (versions.length < 3) { + return { + WebrpcGenVersion: '', + codeGenName: '', + codeGenVersion: '', + schemaName: '', + schemaVersion: '', + } + } + + const [_, WebrpcGenVersion] = versions[0]!.split('@') + const [codeGenName, codeGenVersion] = versions[1]!.split('@') + const [schemaName, schemaVersion] = versions[2]!.split('@') + + return { + WebrpcGenVersion: WebrpcGenVersion ?? '', + codeGenName: codeGenName ?? '', + codeGenVersion: codeGenVersion ?? '', + schemaName: schemaName ?? '', + schemaVersion: schemaVersion ?? '', + } +} diff --git a/packages/services/userdata/tsconfig.json b/packages/services/userdata/tsconfig.json new file mode 100644 index 000000000..fed9c77b4 --- /dev/null +++ b/packages/services/userdata/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@repo/typescript-config/base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "types": ["node"] + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/utils/abi/CHANGELOG.md b/packages/utils/abi/CHANGELOG.md index 15772c27d..dcdd1a635 100644 --- a/packages/utils/abi/CHANGELOG.md +++ b/packages/utils/abi/CHANGELOG.md @@ -1,5 +1,216 @@ # @0xsequence/abi +## 3.0.12 + +### Patch Changes + +- WDK fix + +## 3.0.11 + +### Patch Changes + +- Fix for relayer sponsored fees + +## 3.0.10 + +### Patch Changes + +- Minor network updates, relayer interface + +## 3.0.9 + +### Patch Changes + +- Fee options fixes + +## 3.0.8 + +### Patch Changes + +- Bug fix for relayer fee options handling + +## 3.0.7 + +### Patch Changes + +- Minor bug fixes + +## 3.0.6 + +### Patch Changes + +- userdata upgrade, arweave support + +## 3.0.5 + +### Patch Changes + +- Account federation support + +## 3.0.4 + +### Patch Changes + +- id-token login support + +## 3.0.3 + +### Patch Changes + +- 3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer + +## 3.0.1 + +### Patch Changes + +- Network and session fixes + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade + +## 3.0.0-beta.19 + +### Patch Changes + +- Final RC before 3.0.0 + +## 3.0.0-beta.18 + +### Patch Changes + +- multicall3 optimization + +## 3.0.0-beta.17 + +### Patch Changes + +- New chains, relayer rpc fix + +## 3.0.0-beta.16 + +### Patch Changes + +- ethauth support + +## 3.0.0-beta.15 + +### Patch Changes + +- New chains, minor fixes + +## 3.0.0-beta.14 + +### Patch Changes + +- Relayer fee options fix + +## 3.0.0-beta.13 + +### Patch Changes + +- Userdata service updates + +## 3.0.0-beta.12 + +### Patch Changes + +- Beta release with dapp connector fixes + +## 3.0.0-beta.11 + +### Patch Changes + +- 3.0.0 beta + +## 3.0.0-beta.10 + +### Patch Changes + +- dapp-client updates + +## 3.0.0-beta.9 + +### Patch Changes + +- dapp client updates for EOA login + +## 3.0.0-beta.8 + +### Patch Changes + +- Apple auth fixes + +## 3.0.0-beta.7 + +### Patch Changes + +- Apple auth fix + +## 3.0.0-beta.6 + +### Patch Changes + +- Fix signer 404 error, minor fixes + +## 3.0.0-beta.5 + +### Patch Changes + +- Beta release for v3 + +## 3.0.0-beta.4 + +### Patch Changes + +- RC5 upgrade + +## 3.0.0-beta.3 + +### Patch Changes + +- 3.0.0-beta.3 with fixes + +## 3.0.0-beta.2 + +### Patch Changes + +- 3.0.0-beta.2 with identity instrument updates + +## 3.0.0-beta.1 + +### Patch Changes + +- 3.0.0-beta.1 + ## 2.3.8 ### Patch Changes diff --git a/packages/utils/abi/eslint.config.js b/packages/utils/abi/eslint.config.js new file mode 100644 index 000000000..cecf89b03 --- /dev/null +++ b/packages/utils/abi/eslint.config.js @@ -0,0 +1,4 @@ +import { config as baseConfig } from "@repo/eslint-config/base" + +/** @type {import("eslint").Linter.Config} */ +export default baseConfig diff --git a/packages/utils/abi/package.json b/packages/utils/abi/package.json index 738e1398c..9e9e8fb33 100644 --- a/packages/utils/abi/package.json +++ b/packages/utils/abi/package.json @@ -1,28 +1,31 @@ { "name": "@0xsequence/abi", - "version": "3.0.0", + "version": "3.0.12", "description": "abi sub-package for Sequence", - "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/abi", - "author": "Sequence Platforms Inc.", + "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/utils/abi", + "author": "Sequence Platforms ULC", "license": "Apache-2.0", "publishConfig": { "access": "public" }, + "type": "module", "scripts": { "build": "tsc", "dev": "tsc --watch", "test": "echo", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "lint": "eslint . --max-warnings 0" }, "exports": { ".": { - "types": "./src/index.ts", + "types": "./dist/index.d.ts", "default": "./dist/index.js" } }, "devDependencies": { + "@repo/eslint-config": "workspace:^", "@repo/typescript-config": "workspace:^", - "@types/node": "^22.15.29", - "typescript": "^5.8.3" + "@types/node": "^25.3.0", + "typescript": "^6.0.3" } } diff --git a/packages/utils/abi/src/index.ts b/packages/utils/abi/src/index.ts index db6dfa292..39336c543 100644 --- a/packages/utils/abi/src/index.ts +++ b/packages/utils/abi/src/index.ts @@ -1,22 +1,22 @@ -export { abi as erc5719Abi } from './wallet/erc5719' -export { abi as erc1271Abi } from './wallet/erc1271' -export { abi as erc6492Abi } from './wallet/erc6492' -export { abi as factoryAbi } from './wallet/factory' -export { abi as mainModuleAbi } from './wallet/mainModule' -export { abi as mainModuleUpgradableAbi } from './wallet/mainModuleUpgradable' -export { abi as moduleHooksAbi } from './wallet/moduleHooks' -export { abi as sequenceUtilsAbi } from './wallet/sequenceUtils' -export { abi as requireFreshSignerAbi } from './wallet/libs/requireFreshSigners' -export { abi as walletProxyHookAbi } from './wallet/walletProxyHook' +export { abi as erc5719Abi } from './wallet/erc5719.js' +export { abi as erc1271Abi } from './wallet/erc1271.js' +export { abi as erc6492Abi } from './wallet/erc6492.js' +export { abi as factoryAbi } from './wallet/factory.js' +export { abi as mainModuleAbi } from './wallet/mainModule.js' +export { abi as mainModuleUpgradableAbi } from './wallet/mainModuleUpgradable.js' +export { abi as moduleHooksAbi } from './wallet/moduleHooks.js' +export { abi as sequenceUtilsAbi } from './wallet/sequenceUtils.js' +export { abi as requireFreshSignerAbi } from './wallet/libs/requireFreshSigners.js' +export { abi as walletProxyHookAbi } from './wallet/walletProxyHook.js' -export { walletContracts } from './wallet' +export { walletContracts } from './wallet/index.js' -export { ERC1155_ABI } from './tokens/erc1155' -export { ERC1155_ITEMS_ABI } from './tokens/erc1155Items' -export { ERC20_ABI } from './tokens/erc20' -export { ERC6909_ABI } from './tokens/erc6909' -export { ERC721_ABI } from './tokens/erc721' -export { ERC721_ITEMS_ABI } from './tokens/erc721Items' +export { ERC1155_ABI } from './tokens/erc1155.js' +export { ERC1155_ITEMS_ABI } from './tokens/erc1155Items.js' +export { ERC20_ABI } from './tokens/erc20.js' +export { ERC6909_ABI } from './tokens/erc6909.js' +export { ERC721_ABI } from './tokens/erc721.js' +export { ERC721_ITEMS_ABI } from './tokens/erc721Items.js' -export { ERC1155_SALE_ABI } from './sale/erc1155Sale' -export { ERC721_SALE_ABI } from './sale/erc721Sale' +export { ERC1155_SALE_ABI } from './sale/erc1155Sale.js' +export { ERC721_SALE_ABI } from './sale/erc721Sale.js' diff --git a/packages/utils/abi/src/wallet/index.ts b/packages/utils/abi/src/wallet/index.ts index 0ceae0a35..50b567e71 100644 --- a/packages/utils/abi/src/wallet/index.ts +++ b/packages/utils/abi/src/wallet/index.ts @@ -1,13 +1,13 @@ -import * as erc5719 from './erc5719' -import * as erc1271 from './erc1271' -import * as erc6492 from './erc6492' -import * as factory from './factory' -import * as mainModule from './mainModule' -import * as mainModuleUpgradable from './mainModuleUpgradable' -import * as moduleHooks from './moduleHooks' -import * as sequenceUtils from './sequenceUtils' -import * as requireFreshSigner from './libs/requireFreshSigners' -import * as walletProxyHook from './walletProxyHook' +import * as erc5719 from './erc5719.js' +import * as erc1271 from './erc1271.js' +import * as erc6492 from './erc6492.js' +import * as factory from './factory.js' +import * as mainModule from './mainModule.js' +import * as mainModuleUpgradable from './mainModuleUpgradable.js' +import * as moduleHooks from './moduleHooks.js' +import * as sequenceUtils from './sequenceUtils.js' +import * as requireFreshSigner from './libs/requireFreshSigners.js' +import * as walletProxyHook from './walletProxyHook.js' /** * @deprecated import directly from @0xsequence/abi/* instead, omitting "walletContracts" diff --git a/packages/wallet/core/CHANGELOG.md b/packages/wallet/core/CHANGELOG.md new file mode 100644 index 000000000..24e429424 --- /dev/null +++ b/packages/wallet/core/CHANGELOG.md @@ -0,0 +1,360 @@ +# @0xsequence/wallet-core + +## 3.0.12 + +### Patch Changes + +- WDK fix +- Updated dependencies + - @0xsequence/guard@3.0.12 + - @0xsequence/relayer@3.1.1 + - @0xsequence/wallet-primitives@3.0.12 + +## 3.0.11 + +### Patch Changes + +- Fix for relayer sponsored fees +- Updated dependencies +- Updated dependencies [55121af] + - @0xsequence/guard@3.0.11 + - @0xsequence/relayer@3.1.0 + - @0xsequence/wallet-primitives@3.0.11 + +## 3.0.10 + +### Patch Changes + +- Minor network updates, relayer interface +- Updated dependencies + - @0xsequence/guard@3.0.10 + - @0xsequence/relayer@3.0.10 + - @0xsequence/wallet-primitives@3.0.10 + +## 3.0.9 + +### Patch Changes + +- Fee options fixes +- Updated dependencies + - @0xsequence/guard@3.0.9 + - @0xsequence/relayer@3.0.9 + - @0xsequence/wallet-primitives@3.0.9 + +## 3.0.8 + +### Patch Changes + +- Bug fix for relayer fee options handling +- Updated dependencies + - @0xsequence/guard@3.0.8 + - @0xsequence/relayer@3.0.8 + - @0xsequence/wallet-primitives@3.0.8 + +## 3.0.7 + +### Patch Changes + +- Minor bug fixes +- Updated dependencies + - @0xsequence/guard@3.0.7 + - @0xsequence/relayer@3.0.7 + - @0xsequence/wallet-primitives@3.0.7 + +## 3.0.6 + +### Patch Changes + +- userdata upgrade, arweave support +- Updated dependencies + - @0xsequence/guard@3.0.6 + - @0xsequence/relayer@3.0.6 + - @0xsequence/wallet-primitives@3.0.6 + +## 3.0.5 + +### Patch Changes + +- Account federation support +- Updated dependencies + - @0xsequence/guard@3.0.5 + - @0xsequence/relayer@3.0.5 + - @0xsequence/wallet-primitives@3.0.5 + +## 3.0.4 + +### Patch Changes + +- id-token login support +- Updated dependencies + - @0xsequence/guard@3.0.4 + - @0xsequence/relayer@3.0.4 + - @0xsequence/wallet-primitives@3.0.4 + +## 3.0.3 + +### Patch Changes + +- 3.0.3 +- Updated dependencies + - @0xsequence/guard@3.0.3 + - @0xsequence/relayer@3.0.3 + - @0xsequence/wallet-primitives@3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer +- Updated dependencies + - @0xsequence/guard@3.0.2 + - @0xsequence/relayer@3.0.2 + - @0xsequence/wallet-primitives@3.0.2 + +## 3.0.1 + +### Patch Changes + +- Network and session fixes +- Updated dependencies + - @0xsequence/guard@3.0.1 + - @0xsequence/relayer@3.0.1 + - @0xsequence/wallet-primitives@3.0.1 + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade +- Updated dependencies [f68be62] +- Updated dependencies [49d8a2f] +- Updated dependencies [3411232] +- Updated dependencies [23cb9e9] +- Updated dependencies [f5f6a7a] +- Updated dependencies [e7de3b1] +- Updated dependencies [493836f] +- Updated dependencies [30e1f1a] +- Updated dependencies [d5017e8] +- Updated dependencies [24a5fab] +- Updated dependencies [e5e1a03] +- Updated dependencies [0b63113] +- Updated dependencies [a89134a] +- Updated dependencies [7c6c811] +- Updated dependencies +- Updated dependencies [98ce38b] +- Updated dependencies [747e6b5] +- Updated dependencies [40c19ff] +- Updated dependencies [6d5de25] +- Updated dependencies [934acd1] + - @0xsequence/guard@3.0.0 + - @0xsequence/relayer@3.0.0 + - @0xsequence/wallet-primitives@3.0.0 + +## 3.0.0-beta.19 + +### Patch Changes + +- Final RC before 3.0.0 +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.19 + - @0xsequence/relayer@3.0.0-beta.19 + - @0xsequence/wallet-primitives@3.0.0-beta.19 + +## 3.0.0-beta.18 + +### Patch Changes + +- multicall3 optimization +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.18 + - @0xsequence/relayer@3.0.0-beta.18 + - @0xsequence/wallet-primitives@3.0.0-beta.18 + +## 3.0.0-beta.17 + +### Patch Changes + +- New chains, relayer rpc fix +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.17 + - @0xsequence/relayer@3.0.0-beta.17 + - @0xsequence/wallet-primitives@3.0.0-beta.17 + +## 3.0.0-beta.16 + +### Patch Changes + +- ethauth support +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.16 + - @0xsequence/relayer@3.0.0-beta.16 + - @0xsequence/wallet-primitives@3.0.0-beta.16 + +## 3.0.0-beta.15 + +### Patch Changes + +- New chains, minor fixes +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.15 + - @0xsequence/relayer@3.0.0-beta.15 + - @0xsequence/wallet-primitives@3.0.0-beta.15 + +## 3.0.0-beta.14 + +### Patch Changes + +- Relayer fee options fix +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.14 + - @0xsequence/relayer@3.0.0-beta.14 + - @0xsequence/wallet-primitives@3.0.0-beta.14 + +## 3.0.0-beta.13 + +### Patch Changes + +- Userdata service updates +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.13 + - @0xsequence/relayer@3.0.0-beta.13 + - @0xsequence/wallet-primitives@3.0.0-beta.13 + +## 3.0.0-beta.12 + +### Patch Changes + +- Beta release with dapp connector fixes +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.12 + - @0xsequence/relayer@3.0.0-beta.12 + - @0xsequence/wallet-primitives@3.0.0-beta.12 + +## 3.0.0-beta.11 + +### Patch Changes + +- 3.0.0 beta +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.11 + - @0xsequence/relayer@3.0.0-beta.11 + - @0xsequence/wallet-primitives@3.0.0-beta.11 + +## 3.0.0-beta.10 + +### Patch Changes + +- dapp-client updates +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.10 + - @0xsequence/relayer@3.0.0-beta.10 + - @0xsequence/wallet-primitives@3.0.0-beta.10 + +## 3.0.0-beta.9 + +### Patch Changes + +- dapp client updates for EOA login +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.9 + - @0xsequence/relayer@3.0.0-beta.9 + - @0xsequence/wallet-primitives@3.0.0-beta.9 + +## 3.0.0-beta.8 + +### Patch Changes + +- Apple auth fixes +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.8 + - @0xsequence/relayer@3.0.0-beta.8 + - @0xsequence/wallet-primitives@3.0.0-beta.8 + +## 3.0.0-beta.7 + +### Patch Changes + +- Apple auth fix +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.7 + - @0xsequence/relayer@3.0.0-beta.7 + - @0xsequence/wallet-primitives@3.0.0-beta.7 + +## 3.0.0-beta.6 + +### Patch Changes + +- Fix signer 404 error, minor fixes +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.6 + - @0xsequence/relayer@3.0.0-beta.6 + - @0xsequence/wallet-primitives@3.0.0-beta.6 + +## 3.0.0-beta.5 + +### Patch Changes + +- Beta release for v3 +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.5 + - @0xsequence/relayer@3.0.0-beta.5 + - @0xsequence/wallet-primitives@3.0.0-beta.5 + +## 3.0.0-beta.4 + +### Patch Changes + +- RC5 upgrade +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.4 + - @0xsequence/relayer@3.0.0-beta.4 + - @0xsequence/wallet-primitives@3.0.0-beta.4 + +## 3.0.0-beta.3 + +### Patch Changes + +- 3.0.0-beta.3 with fixes +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.3 + - @0xsequence/relayer@3.0.0-beta.3 + - @0xsequence/wallet-primitives@3.0.0-beta.3 + +## 3.0.0-beta.2 + +### Patch Changes + +- 3.0.0-beta.2 with identity instrument updates +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.2 + - @0xsequence/relayer@3.0.0-beta.2 + - @0xsequence/wallet-primitives@3.0.0-beta.2 + +## 3.0.0-beta.1 + +### Patch Changes + +- 3.0.0-beta.1 +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.1 + - @0xsequence/relayer@3.0.0-beta.1 + - @0xsequence/wallet-primitives@3.0.0-beta.1 diff --git a/packages/wallet/core/eslint.config.js b/packages/wallet/core/eslint.config.js new file mode 100644 index 000000000..d10bbd1e9 --- /dev/null +++ b/packages/wallet/core/eslint.config.js @@ -0,0 +1,12 @@ +import { config as baseConfig } from '@repo/eslint-config/base' + +/** @type {import("eslint").Linter.Config} */ +export default [ + ...baseConfig, + { + // files: ['**/*.{test,spec}.ts'], + rules: { + '@typescript-eslint/no-explicit-any': 'off', + }, + }, +] diff --git a/packages/wallet/core/package.json b/packages/wallet/core/package.json index 00fbbf56e..96c286e65 100644 --- a/packages/wallet/core/package.json +++ b/packages/wallet/core/package.json @@ -1,6 +1,6 @@ { "name": "@0xsequence/wallet-core", - "version": "0.0.0", + "version": "3.0.12", "license": "Apache-2.0", "type": "module", "publishConfig": { @@ -13,7 +13,8 @@ "test": "vitest run", "test:coverage": "vitest run --coverage", "typecheck": "tsc --noEmit", - "clean": "rimraf dist" + "clean": "rimraf dist", + "lint": "eslint . --max-warnings 0" }, "exports": { ".": { @@ -22,20 +23,21 @@ } }, "devDependencies": { + "@repo/eslint-config": "workspace:^", "@repo/typescript-config": "workspace:^", - "@types/node": "^22.15.29", - "@vitest/coverage-v8": "^3.2.4", - "dotenv": "^16.5.0", - "fake-indexeddb": "^6.0.1", - "typescript": "^5.8.3", - "vitest": "^3.2.1" + "@types/node": "^25.3.0", + "@vitest/coverage-v8": "^4.0.18", + "dotenv": "^17.3.1", + "fake-indexeddb": "^6.2.5", + "typescript": "^6.0.3", + "vitest": "^4.0.18" }, "dependencies": { "@0xsequence/guard": "workspace:^", "@0xsequence/relayer": "workspace:^", "@0xsequence/wallet-primitives": "workspace:^", "mipd": "^0.0.7", - "ox": "^0.7.2", - "viem": "^2.30.6" + "ox": "^0.9.17", + "viem": "^2.40.3" } } diff --git a/packages/wallet/core/src/bundler/bundlers/pimlico.ts b/packages/wallet/core/src/bundler/bundlers/pimlico.ts index e2d95ec33..4837babee 100644 --- a/packages/wallet/core/src/bundler/bundlers/pimlico.ts +++ b/packages/wallet/core/src/bundler/bundlers/pimlico.ts @@ -16,16 +16,22 @@ type PimlicoGasPrice = { } export class PimlicoBundler implements Bundler { - public readonly kind: 'bundler' = 'bundler' + public readonly kind = 'bundler' public readonly id: string public readonly provider: Provider.Provider public readonly bundlerRpcUrl: string + private readonly fetcher: typeof fetch - constructor(bundlerRpcUrl: string, provider: Provider.Provider | string) { + constructor(bundlerRpcUrl: string, provider: Provider.Provider | string, fetcher?: typeof fetch) { this.id = `pimlico-erc4337-${bundlerRpcUrl}` this.provider = typeof provider === 'string' ? Provider.from(RpcTransport.fromHttp(provider)) : provider this.bundlerRpcUrl = bundlerRpcUrl + const resolvedFetch = fetcher ?? (globalThis as any).fetch + if (!resolvedFetch) { + throw new Error('fetch is not available') + } + this.fetcher = resolvedFetch } async isAvailable(entrypoint: Address.Address, chainId: number): Promise { @@ -113,7 +119,7 @@ export class PimlicoBundler implements Bundler { let pimlico: PimlicoStatusResp | undefined try { pimlico = await this.bundlerRpc('pimlico_getUserOperationStatus', [opHash]) - } catch (_) { + } catch { /* ignore - not Pimlico or endpoint down */ } @@ -165,7 +171,7 @@ export class PimlicoBundler implements Bundler { private async bundlerRpc(method: string, params: any[]): Promise { const body = JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }) - const res = await fetch(this.bundlerRpcUrl, { + const res = await this.fetcher(this.bundlerRpcUrl, { method: 'POST', headers: { 'content-type': 'application/json' }, body, diff --git a/packages/wallet/core/src/env.ts b/packages/wallet/core/src/env.ts new file mode 100644 index 000000000..0a463dd1d --- /dev/null +++ b/packages/wallet/core/src/env.ts @@ -0,0 +1,68 @@ +export type StorageLike = { + getItem: (key: string) => string | null + setItem: (key: string, value: string) => void + removeItem: (key: string) => void +} + +export type CryptoLike = { + subtle: SubtleCrypto + getRandomValues: (array: T) => T +} + +export type TextEncodingLike = { + TextEncoder: typeof TextEncoder + TextDecoder: typeof TextDecoder +} + +export type CoreEnv = { + fetch?: typeof fetch + crypto?: CryptoLike + storage?: StorageLike + indexedDB?: IDBFactory + text?: Partial +} + +function isStorageLike(value: unknown): value is StorageLike { + if (!value || typeof value !== 'object') return false + const candidate = value as StorageLike + return ( + typeof candidate.getItem === 'function' && + typeof candidate.setItem === 'function' && + typeof candidate.removeItem === 'function' + ) +} + +export function resolveCoreEnv(env?: CoreEnv): CoreEnv { + const globalObj = globalThis as any + const windowObj = typeof window !== 'undefined' ? window : (globalObj.window ?? {}) + let storage: StorageLike | undefined + let text: Partial | undefined + + if (isStorageLike(env?.storage)) { + storage = env.storage + } else if (isStorageLike(windowObj.localStorage)) { + storage = windowObj.localStorage + } else if (isStorageLike(globalObj.localStorage)) { + storage = globalObj.localStorage + } + + if (env?.text) { + if (!env.text.TextEncoder || !env.text.TextDecoder) { + throw new Error('env.text must provide both TextEncoder and TextDecoder') + } + text = env.text + } else { + text = { + TextEncoder: windowObj.TextEncoder ?? globalObj.TextEncoder, + TextDecoder: windowObj.TextDecoder ?? globalObj.TextDecoder, + } + } + + return { + fetch: env?.fetch ?? windowObj.fetch ?? globalObj.fetch, + crypto: env?.crypto ?? windowObj.crypto ?? globalObj.crypto, + storage, + indexedDB: env?.indexedDB ?? windowObj.indexedDB ?? globalObj.indexedDB, + text, + } +} diff --git a/packages/wallet/core/src/index.ts b/packages/wallet/core/src/index.ts index b36e917ca..27c54b8f5 100644 --- a/packages/wallet/core/src/index.ts +++ b/packages/wallet/core/src/index.ts @@ -5,6 +5,7 @@ export * as State from './state/index.js' export * as Bundler from './bundler/index.js' export * as Envelope from './envelope.js' export * as Utils from './utils/index.js' +export * from './env.js' export { type ExplicitSessionConfig, type ExplicitSession, diff --git a/packages/wallet/core/src/signers/guard.ts b/packages/wallet/core/src/signers/guard.ts index 3a654edb4..6ee2f2130 100644 --- a/packages/wallet/core/src/signers/guard.ts +++ b/packages/wallet/core/src/signers/guard.ts @@ -3,9 +3,10 @@ import { Attestation, Payload } from '@0xsequence/wallet-primitives' import * as GuardService from '@0xsequence/guard' import * as Envelope from '../envelope.js' -type GuardToken = { - id: 'TOTP' | 'PIN' +export type GuardToken = { + id: 'TOTP' | 'PIN' | 'recovery' code: string + resetAuth?: boolean } export class Guard { @@ -36,7 +37,7 @@ export class Guard { digest, message, previousSignatures, - token ? { id: token.id, token: token.code } : undefined, + token ? { id: token.id, token: token.code, resetAuth: token.resetAuth } : undefined, ) return { address: this.guard.address, diff --git a/packages/wallet/core/src/signers/index.ts b/packages/wallet/core/src/signers/index.ts index 80ccc07f1..28a8815b2 100644 --- a/packages/wallet/core/src/signers/index.ts +++ b/packages/wallet/core/src/signers/index.ts @@ -31,7 +31,7 @@ export interface SapientSigner { } export interface Witnessable { - witness: (stateWriter: State.Writer, wallet: Address.Address, extra?: Object) => Promise + witness: (stateWriter: State.Writer, wallet: Address.Address, extra?: object) => Promise } type MaybePromise = T | Promise diff --git a/packages/wallet/core/src/signers/passkey.ts b/packages/wallet/core/src/signers/passkey.ts index 0cc7cacab..770b6b181 100644 --- a/packages/wallet/core/src/signers/passkey.ts +++ b/packages/wallet/core/src/signers/passkey.ts @@ -5,12 +5,15 @@ import { WebAuthnP256 } from 'ox' import { State } from '../index.js' import { SapientSigner, Witnessable } from './index.js' +export type WebAuthnLike = Pick + export type PasskeyOptions = { extensions: Pick publicKey: Extensions.Passkeys.PublicKey credentialId: string embedMetadata?: boolean metadata?: Extensions.Passkeys.PasskeyMetadata + webauthn?: WebAuthnLike } export type CreatePasskeyOptions = { @@ -18,6 +21,11 @@ export type CreatePasskeyOptions = { requireUserVerification?: boolean credentialName?: string embedMetadata?: boolean + webauthn?: WebAuthnLike +} + +export type FindPasskeyOptions = { + webauthn?: WebAuthnLike } export type WitnessMessage = { @@ -45,6 +53,7 @@ export class Passkey implements SapientSigner, Witnessable { public readonly imageHash: Hex.Hex public readonly embedMetadata: boolean public readonly metadata?: Extensions.Passkeys.PasskeyMetadata + private readonly webauthn: WebAuthnLike constructor(options: PasskeyOptions) { this.address = options.extensions.passkeys @@ -53,6 +62,7 @@ export class Passkey implements SapientSigner, Witnessable { this.embedMetadata = options.embedMetadata ?? false this.imageHash = Extensions.Passkeys.rootFor(options.publicKey) this.metadata = options.metadata + this.webauthn = options.webauthn ?? WebAuthnP256 } static async loadFromWitness( @@ -60,6 +70,7 @@ export class Passkey implements SapientSigner, Witnessable { extensions: Pick, wallet: Address.Address, imageHash: Hex.Hex, + options?: FindPasskeyOptions, ) { // In the witness we will find the public key, and may find the credential id const witness = await stateReader.getWitnessForSapient(wallet, extensions.passkeys, imageHash) @@ -90,13 +101,15 @@ export class Passkey implements SapientSigner, Witnessable { publicKey: message.publicKey, embedMetadata: decodedSignature.embedMetadata, metadata, + webauthn: options?.webauthn, }) } static async create(extensions: Pick, options?: CreatePasskeyOptions) { + const webauthn = options?.webauthn ?? WebAuthnP256 const name = options?.credentialName ?? `Sequence (${Date.now()})` - const credential = await WebAuthnP256.createCredential({ + const credential = await webauthn.createCredential({ user: { name, }, @@ -120,6 +133,7 @@ export class Passkey implements SapientSigner, Witnessable { }, embedMetadata: options?.embedMetadata, metadata, + webauthn, }) if (options?.stateProvider) { @@ -132,8 +146,10 @@ export class Passkey implements SapientSigner, Witnessable { static async find( stateReader: State.Reader, extensions: Pick, + options?: FindPasskeyOptions, ): Promise { - const response = await WebAuthnP256.sign({ challenge: Hex.random(32) }) + const webauthn = options?.webauthn ?? WebAuthnP256 + const response = await webauthn.sign({ challenge: Hex.random(32) }) if (!response.raw) throw new Error('No credential returned') const authenticatorDataBytes = Bytes.fromHex(response.metadata.authenticatorData) @@ -218,7 +234,7 @@ export class Passkey implements SapientSigner, Witnessable { console.warn('Multiple signers found for passkey', flattened) } - return Passkey.loadFromWitness(stateReader, extensions, flattened[0]!.wallet, flattened[0]!.imageHash) + return Passkey.loadFromWitness(stateReader, extensions, flattened[0]!.wallet, flattened[0]!.imageHash, options) } async signSapient( @@ -234,7 +250,7 @@ export class Passkey implements SapientSigner, Witnessable { const challenge = Hex.fromBytes(Payload.hash(wallet, chainId, payload)) - const response = await WebAuthnP256.sign({ + const response = await this.webauthn.sign({ challenge, credentialId: this.credentialId, userVerification: this.publicKey.requireUserVerification ? 'required' : 'discouraged', @@ -260,7 +276,7 @@ export class Passkey implements SapientSigner, Witnessable { } } - async witness(stateWriter: State.Writer, wallet: Address.Address, extra?: Object): Promise { + async witness(stateWriter: State.Writer, wallet: Address.Address, extra?: object): Promise { const payload = Payload.fromMessage( Hex.fromString( JSON.stringify({ diff --git a/packages/wallet/core/src/signers/pk/encrypted.ts b/packages/wallet/core/src/signers/pk/encrypted.ts index becc2b41a..dce0eb3cc 100644 --- a/packages/wallet/core/src/signers/pk/encrypted.ts +++ b/packages/wallet/core/src/signers/pk/encrypted.ts @@ -1,9 +1,10 @@ import { Hex, Address, PublicKey, Secp256k1, Bytes } from 'ox' +import { resolveCoreEnv, type CoreEnv, type CryptoLike, type StorageLike, type TextEncodingLike } from '../../env.js' import { PkStore } from './index.js' export interface EncryptedData { - iv: Uint8Array - data: ArrayBuffer + iv: BufferSource + data: BufferSource keyPointer: string address: Address.Address publicKey: PublicKey.PublicKey @@ -17,6 +18,7 @@ export class EncryptedPksDb { constructor( private readonly localStorageKeyPrefix: string = 'e_pk_key_', tableName: string = 'e_pk', + private readonly env?: CoreEnv, ) { this.tableName = tableName } @@ -25,9 +27,59 @@ export class EncryptedPksDb { return `pk_${address.toLowerCase()}` } + private getIndexedDB(): IDBFactory { + const globalObj = globalThis as any + const indexedDb = this.env?.indexedDB ?? globalObj.indexedDB ?? globalObj.window?.indexedDB + if (!indexedDb) { + throw new Error('indexedDB is not available') + } + return indexedDb + } + + private getStorage(): StorageLike { + const storage = resolveCoreEnv(this.env).storage + if (!storage) { + throw new Error('storage is not available') + } + return storage + } + + private getCrypto(): CryptoLike { + const globalObj = globalThis as any + const crypto = this.env?.crypto ?? globalObj.crypto ?? globalObj.window?.crypto + if (!crypto?.subtle || !crypto?.getRandomValues) { + throw new Error('crypto.subtle is not available') + } + return crypto + } + + private getTextEncoderCtor(): TextEncodingLike['TextEncoder'] { + const globalObj = globalThis as any + if (this.env?.text && (!this.env.text.TextEncoder || !this.env.text.TextDecoder)) { + throw new Error('env.text must provide both TextEncoder and TextDecoder') + } + const encoderCtor = this.env?.text?.TextEncoder ?? globalObj.TextEncoder ?? globalObj.window?.TextEncoder + if (!encoderCtor) { + throw new Error('TextEncoder is not available') + } + return encoderCtor + } + + private getTextDecoderCtor(): TextEncodingLike['TextDecoder'] { + const globalObj = globalThis as any + if (this.env?.text && (!this.env.text.TextEncoder || !this.env.text.TextDecoder)) { + throw new Error('env.text must provide both TextEncoder and TextDecoder') + } + const decoderCtor = this.env?.text?.TextDecoder ?? globalObj.TextDecoder ?? globalObj.window?.TextDecoder + if (!decoderCtor) { + throw new Error('TextDecoder is not available') + } + return decoderCtor + } + private openDB(): Promise { return new Promise((resolve, reject) => { - const request = indexedDB.open(this.dbName, this.dbVersion) + const request = this.getIndexedDB().open(this.dbName, this.dbVersion) request.onupgradeneeded = () => { const db = request.result if (!db.objectStoreNames.contains(this.tableName)) { @@ -73,7 +125,11 @@ export class EncryptedPksDb { } async generateAndStore(): Promise { - const encryptionKey = await window.crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, [ + const crypto = this.getCrypto() + const storage = this.getStorage() + const TextEncoderCtor = this.getTextEncoderCtor() + + const encryptionKey = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, [ 'encrypt', 'decrypt', ]) @@ -84,13 +140,13 @@ export class EncryptedPksDb { const address = Address.fromPublicKey(publicKey) const keyPointer = this.localStorageKeyPrefix + address - const exportedKey = await window.crypto.subtle.exportKey('jwk', encryptionKey) - window.localStorage.setItem(keyPointer, JSON.stringify(exportedKey)) + const exportedKey = await crypto.subtle.exportKey('jwk', encryptionKey) + storage.setItem(keyPointer, JSON.stringify(exportedKey)) - const encoder = new TextEncoder() + const encoder = new TextEncoderCtor() const encodedPk = encoder.encode(privateKey) - const iv = window.crypto.getRandomValues(new Uint8Array(12)) - const encryptedBuffer = await window.crypto.subtle.encrypt({ name: 'AES-GCM', iv }, encryptionKey, encodedPk) + const iv = crypto.getRandomValues(new Uint8Array(12)) + const encryptedBuffer = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, encryptionKey, encodedPk) const encrypted: EncryptedData = { iv, @@ -113,7 +169,7 @@ export class EncryptedPksDb { async getEncryptedPkStore(address: Address.Address): Promise { const entry = await this.getEncryptedEntry(address) if (!entry) return - return new EncryptedPkStore(entry) + return new EncryptedPkStore(entry, this.env) } async listAddresses(): Promise { @@ -125,12 +181,41 @@ export class EncryptedPksDb { const dbKey = this.computeDbKey(address) await this.putData(dbKey, undefined) const keyPointer = this.localStorageKeyPrefix + address - window.localStorage.removeItem(keyPointer) + this.getStorage().removeItem(keyPointer) } } export class EncryptedPkStore implements PkStore { - constructor(private readonly encrypted: EncryptedData) {} + constructor( + private readonly encrypted: EncryptedData, + private readonly env?: CoreEnv, + ) {} + + private getStorage(): StorageLike { + const storage = resolveCoreEnv(this.env).storage + if (!storage) { + throw new Error('storage is not available') + } + return storage + } + + private getCrypto(): CryptoLike { + const globalObj = globalThis as any + const crypto = this.env?.crypto ?? globalObj.crypto ?? globalObj.window?.crypto + if (!crypto?.subtle) { + throw new Error('crypto.subtle is not available') + } + return crypto + } + + private getTextDecoderCtor(): TextEncodingLike['TextDecoder'] { + const globalObj = globalThis as any + const decoderCtor = this.env?.text?.TextDecoder ?? globalObj.TextDecoder ?? globalObj.window?.TextDecoder + if (!decoderCtor) { + throw new Error('TextDecoder is not available') + } + return decoderCtor + } address(): Address.Address { return this.encrypted.address @@ -141,16 +226,20 @@ export class EncryptedPkStore implements PkStore { } async signDigest(digest: Bytes.Bytes): Promise<{ r: bigint; s: bigint; yParity: number }> { - const keyJson = window.localStorage.getItem(this.encrypted.keyPointer) + const storage = this.getStorage() + const crypto = this.getCrypto() + const TextDecoderCtor = this.getTextDecoderCtor() + + const keyJson = storage.getItem(this.encrypted.keyPointer) if (!keyJson) throw new Error('Encryption key not found in localStorage') const jwk = JSON.parse(keyJson) - const encryptionKey = await window.crypto.subtle.importKey('jwk', jwk, { name: 'AES-GCM' }, false, ['decrypt']) - const decryptedBuffer = await window.crypto.subtle.decrypt( + const encryptionKey = await crypto.subtle.importKey('jwk', jwk, { name: 'AES-GCM' }, false, ['decrypt']) + const decryptedBuffer = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: this.encrypted.iv }, encryptionKey, this.encrypted.data, ) - const decoder = new TextDecoder() + const decoder = new TextDecoderCtor() const privateKey = decoder.decode(decryptedBuffer) as Hex.Hex return Secp256k1.sign({ payload: digest, privateKey }) } diff --git a/packages/wallet/core/src/signers/pk/index.ts b/packages/wallet/core/src/signers/pk/index.ts index 5c26b1dcb..b15fb3ecb 100644 --- a/packages/wallet/core/src/signers/pk/index.ts +++ b/packages/wallet/core/src/signers/pk/index.ts @@ -52,7 +52,7 @@ export class Pk implements SignerInterface, Witnessable { return { ...signature, type: 'hash' } } - async witness(stateWriter: State.Writer, wallet: Address.Address, extra?: Object): Promise { + async witness(stateWriter: State.Writer, wallet: Address.Address, extra?: object): Promise { const payload = Payload.fromMessage( Hex.fromString( JSON.stringify({ diff --git a/packages/wallet/core/src/signers/session-manager.ts b/packages/wallet/core/src/signers/session-manager.ts index ef3d81b3a..6b23cb7a6 100644 --- a/packages/wallet/core/src/signers/session-manager.ts +++ b/packages/wallet/core/src/signers/session-manager.ts @@ -18,6 +18,7 @@ import { SessionSigner, SessionSignerInvalidReason, isImplicitSessionSigner, + isIncrementCall, UsageLimit, } from './session/index.js' @@ -130,21 +131,16 @@ export class SessionManager implements SapientSigner { })) } - async findSignersForCalls(wallet: Address.Address, chainId: number, calls: Payload.Call[]): Promise { - // Only use signers that match the topology - const topology = await this.topology - const identitySigners = SessionConfig.getIdentitySigners(topology) - if (identitySigners.length === 0) { - throw new Error('Identity signers not found') - } - - // Prioritize implicit signers - const availableSigners = [...this._implicitSigners, ...this._explicitSigners] - if (availableSigners.length === 0) { - throw new Error('No signers match the topology') - } - - // Find supported signers for each call + /** + * Find one signer per call from the given candidate list (first that supports each call). + */ + private async findSignersForCallsWithCandidates( + wallet: Address.Address, + chainId: number, + calls: Payload.Call[], + topology: SessionConfig.SessionsTopology, + availableSigners: SessionSigner[], + ): Promise { const signers: SessionSigner[] = [] for (const call of calls) { let supported = false @@ -173,9 +169,67 @@ export class SessionManager implements SapientSigner { if (expiredSupportedSigner) { throw new Error(`Signer supporting call is expired: ${expiredSupportedSigner.address}`) } - throw new Error( - `No signer supported for call. ` + `Call: to=${call.to}, data=${call.data}, value=${call.value}, `, - ) + throw new Error(`No signer supported for call. Call: to=${call.to}, data=${call.data}, value=${call.value}, `) + } + } + return signers + } + + async findSignersForCalls(wallet: Address.Address, chainId: number, calls: Payload.Call[]): Promise { + const topology = await this.topology + const identitySigners = SessionConfig.getIdentitySigners(topology) + if (identitySigners.length === 0) { + throw new Error('Identity signers not found') + } + + const availableSigners = [...this._implicitSigners, ...this._explicitSigners] + if (availableSigners.length === 0) { + throw new Error('No signers match the topology') + } + + const nonIncrementCalls: Payload.Call[] = [] + const incrementCalls: Payload.Call[] = [] + for (const call of calls) { + if (isIncrementCall(call, this.address)) { + incrementCalls.push(call) + } else { + nonIncrementCalls.push(call) + } + } + + // Find signers for non-increment calls + const nonIncrementSigners = + nonIncrementCalls.length > 0 + ? await this.findSignersForCallsWithCandidates(wallet, chainId, nonIncrementCalls, topology, availableSigners) + : [] + + let incrementSigners: SessionSigner[] = [] + if (incrementCalls.length > 0) { + // Find signers for increment calls, preferring signers that signed non-increment calls + const incrementCandidates = [ + ...nonIncrementSigners, + ...availableSigners.filter((s) => !nonIncrementSigners.includes(s)), + ] + incrementSigners = await this.findSignersForCallsWithCandidates( + wallet, + chainId, + incrementCalls, + topology, + incrementCandidates, + ) + } + + // Merge back in original call order + const signers: SessionSigner[] = [] + let nonIncrementIndex = 0 + let incrementIndex = 0 + for (const call of calls) { + if (isIncrementCall(call, this.address)) { + signers.push(incrementSigners[incrementIndex]!) + incrementIndex++ + } else { + signers.push(nonIncrementSigners[nonIncrementIndex]!) + nonIncrementIndex++ } } return signers @@ -191,20 +245,23 @@ export class SessionManager implements SapientSigner { } const signers = await this.findSignersForCalls(wallet, chainId, calls) - // Create a map of signers to their associated calls - const signerToCalls = new Map() + // Map each signer to only their non-increment calls + const signerToNonIncrementCalls = new Map() signers.forEach((signer, index) => { const call = calls[index]! - const existingCalls = signerToCalls.get(signer) || [] - signerToCalls.set(signer, [...existingCalls, call]) + if (isIncrementCall(call, this.address)) { + return + } + const existing = signerToNonIncrementCalls.get(signer) || [] + signerToNonIncrementCalls.set(signer, [...existing, call]) }) - // Prepare increments for each explicit signer with their associated calls + // Prepare increments for each explicit signer from their non-increment calls only const increments: UsageLimit[] = ( await Promise.all( - Array.from(signerToCalls.entries()).map(async ([signer, associatedCalls]) => { + Array.from(signerToNonIncrementCalls.entries()).map(async ([signer, nonIncrementCalls]) => { if (isExplicitSessionSigner(signer)) { - return signer.prepareIncrements(wallet, chainId, associatedCalls, this.address, this._provider!) + return signer.prepareIncrements(wallet, chainId, nonIncrementCalls, this.address, this._provider!) } return [] }), diff --git a/packages/wallet/core/src/signers/session/explicit.ts b/packages/wallet/core/src/signers/session/explicit.ts index e373b0469..ef78c554a 100644 --- a/packages/wallet/core/src/signers/session/explicit.ts +++ b/packages/wallet/core/src/signers/session/explicit.ts @@ -1,14 +1,7 @@ -import { - Constants, - Extensions, - Payload, - Permission, - SessionConfig, - SessionSignature, -} from '@0xsequence/wallet-primitives' +import { Constants, Payload, Permission, SessionConfig, SessionSignature } from '@0xsequence/wallet-primitives' import { AbiFunction, AbiParameters, Address, Bytes, Hash, Hex, Provider } from 'ox' import { MemoryPkStore, PkStore } from '../pk/index.js' -import { ExplicitSessionSigner, SessionSignerValidity, UsageLimit } from './session.js' +import { ExplicitSessionSigner, isIncrementCall, SessionSignerValidity, UsageLimit } from './session.js' export type ExplicitParams = Omit @@ -215,11 +208,7 @@ export class Explicit implements ExplicitSessionSigner { sessionManagerAddress: Address.Address, provider?: Provider.Provider, ): Promise { - if ( - Address.isEqual(call.to, sessionManagerAddress) && - Hex.size(call.data) > 4 && - Hex.isEqual(Hex.slice(call.data, 0, 4), AbiFunction.getSelector(Constants.INCREMENT_USAGE_LIMIT)) - ) { + if (isIncrementCall(call, sessionManagerAddress)) { // Can sign increment usage calls return true } @@ -241,11 +230,7 @@ export class Explicit implements ExplicitSessionSigner { ): Promise { const call = payload.calls[callIdx]! let permissionIndex: number - if ( - Address.isEqual(call.to, sessionManagerAddress) && - Hex.size(call.data) > 4 && - Hex.isEqual(Hex.slice(call.data, 0, 4), AbiFunction.getSelector(Constants.INCREMENT_USAGE_LIMIT)) - ) { + if (isIncrementCall(call, sessionManagerAddress)) { // Permission check not required. Use the first permission permissionIndex = 0 } else { @@ -263,10 +248,7 @@ export class Explicit implements ExplicitSessionSigner { } // Sign it - const useDeprecatedHash = - Address.isEqual(sessionManagerAddress, Extensions.Dev1.sessions) || - Address.isEqual(sessionManagerAddress, Extensions.Dev2.sessions) - const callHash = SessionSignature.hashCallWithReplayProtection(payload, callIdx, chainId, useDeprecatedHash) + const callHash = SessionSignature.hashPayloadWithCallIdx(wallet, payload, callIdx, chainId, sessionManagerAddress) const sessionSignature = await this._privateKey.signDigest(Bytes.fromHex(callHash)) return { permissionIndex: BigInt(permissionIndex), @@ -326,7 +308,7 @@ export class Explicit implements ExplicitSessionSigner { Bytes.fromHex(call.data).slice(Number(rule.offset), Number(rule.offset) + 32), 32, ) - let value: Bytes.Bytes = callDataValue.map((b, i) => b & rule.mask[i]!) + const value: Bytes.Bytes = callDataValue.map((b, i) => b & rule.mask[i]!) if (Bytes.toBigInt(value) === 0n) continue // Add to list diff --git a/packages/wallet/core/src/signers/session/implicit.ts b/packages/wallet/core/src/signers/session/implicit.ts index 4e74c3a0f..8eb765808 100644 --- a/packages/wallet/core/src/signers/session/implicit.ts +++ b/packages/wallet/core/src/signers/session/implicit.ts @@ -1,6 +1,5 @@ import { Attestation, - Extensions, Payload, Signature as SequenceSignature, SessionConfig, @@ -96,7 +95,7 @@ export class Implicit implements ImplicitSessionSigner { ) const expectedResult = Bytes.toHex(Attestation.generateImplicitRequestMagic(this._attestation, wallet)) return acceptImplicitRequest === expectedResult - } catch (error) { + } catch { // console.log('implicit signer unsupported call', call, error) return false } @@ -115,10 +114,7 @@ export class Implicit implements ImplicitSessionSigner { if (!isSupported) { throw new Error('Unsupported call') } - const useDeprecatedHash = - Address.isEqual(sessionManagerAddress, Extensions.Dev1.sessions) || - Address.isEqual(sessionManagerAddress, Extensions.Dev2.sessions) - const callHash = SessionSignature.hashCallWithReplayProtection(payload, callIdx, chainId, useDeprecatedHash) + const callHash = SessionSignature.hashPayloadWithCallIdx(wallet, payload, callIdx, chainId, sessionManagerAddress) const sessionSignature = await this._privateKey.signDigest(Bytes.fromHex(callHash)) return { attestation: this._attestation, diff --git a/packages/wallet/core/src/signers/session/session.ts b/packages/wallet/core/src/signers/session/session.ts index 4bcc5bb77..08b2ade81 100644 --- a/packages/wallet/core/src/signers/session/session.ts +++ b/packages/wallet/core/src/signers/session/session.ts @@ -1,5 +1,5 @@ -import { Payload, SessionConfig, SessionSignature } from '@0xsequence/wallet-primitives' -import { Address, Hex, Provider } from 'ox' +import { Constants, Payload, SessionConfig, SessionSignature } from '@0xsequence/wallet-primitives' +import { AbiFunction, Address, Hex, Provider } from 'ox' export type SessionSignerInvalidReason = | 'Expired' @@ -68,3 +68,11 @@ export function isExplicitSessionSigner(signer: SessionSigner): signer is Explic export function isImplicitSessionSigner(signer: SessionSigner): signer is ImplicitSessionSigner { return 'identitySigner' in signer } + +export function isIncrementCall(call: Payload.Call, sessionManagerAddress: Address.Address): boolean { + return ( + Address.isEqual(call.to, sessionManagerAddress) && + Hex.size(call.data) >= 4 && + Hex.isEqual(Hex.slice(call.data, 0, 4), AbiFunction.getSelector(Constants.INCREMENT_USAGE_LIMIT)) + ) +} diff --git a/packages/wallet/core/src/state/arweave/arweave.ts b/packages/wallet/core/src/state/arweave/arweave.ts new file mode 100644 index 000000000..b3cadb734 --- /dev/null +++ b/packages/wallet/core/src/state/arweave/arweave.ts @@ -0,0 +1,115 @@ +export interface Options { + readonly namespace?: string + readonly owners?: string[] + readonly arweaveUrl?: string + readonly graphqlUrl?: string + readonly rateLimitRetryDelayMs?: number +} + +export const defaults = { + namespace: 'Sequence-Sessions', + owners: ['AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM'], + arweaveUrl: 'https://arweave.net', + graphqlUrl: 'https://arweave.net/graphql', + rateLimitRetryDelayMs: 5 * 60 * 1000, +} + +export async function findItems( + filter: { [name: string]: undefined | string | string[] }, + options?: Options & { pageSize?: number; maxResults?: number }, +): Promise<{ [id: string]: { [tag: string]: string } }> { + const namespace = options?.namespace ?? defaults.namespace + const owners = options?.owners ?? defaults.owners + const graphqlUrl = options?.graphqlUrl ?? defaults.graphqlUrl + const rateLimitRetryDelayMs = options?.rateLimitRetryDelayMs ?? defaults.rateLimitRetryDelayMs + const pageSize = options?.pageSize ?? 100 + const maxResults = options?.maxResults + + const tags = Object.entries(filter).flatMap(([name, values]) => + values === undefined + ? [] + : [ + `{ name: "${namespace ? `${namespace}-${name}` : name}", values: [${typeof values === 'string' ? `"${values}"` : values.map((value) => `"${value}"`).join(', ')}] }`, + ], + ) + + const edges: Array<{ cursor: string; node: { id: string; tags: Array<{ name: string; value: string }> } }> = [] + + for (let hasNextPage = true; hasNextPage && (maxResults === undefined || edges.length < maxResults); ) { + const results = maxResults === undefined ? pageSize : Math.min(pageSize, maxResults - edges.length) + + const query = ` + query { + transactions(sort: HEIGHT_DESC, ${edges.length ? `first: ${results}, after: "${edges[edges.length - 1]!.cursor}"` : `first: ${results}`}, tags: [${tags.join(', ')}]${owners.length ? `, owners: [${owners.map((owner) => `"${owner}"`).join(', ')}]` : ''}) { + pageInfo { + hasNextPage + } + edges { + cursor + node { + id + tags { + name + value + } + } + } + } + } + ` + + let response: Response + while (true) { + response = await fetch(graphqlUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query }), + redirect: 'follow', + }) + if (response.status !== 429) { + break + } + console.warn( + `rate limited by ${graphqlUrl}, trying again in ${rateLimitRetryDelayMs / 1000} seconds at ${new Date(Date.now() + rateLimitRetryDelayMs).toLocaleTimeString()}`, + ) + await new Promise((resolve) => setTimeout(resolve, rateLimitRetryDelayMs)) + } + + const { + data: { transactions }, + } = await response.json() + + edges.push(...transactions.edges.slice(0, results)) + + hasNextPage = transactions.pageInfo.hasNextPage + } + + return Object.fromEntries( + edges.map(({ node: { id, tags } }) => [ + id, + Object.fromEntries( + tags.map(({ name, value }) => [ + namespace && name.startsWith(`${namespace}-`) ? name.slice(namespace.length + 1) : name, + value, + ]), + ), + ]), + ) +} + +export async function fetchItem( + id: string, + rateLimitRetryDelayMs = defaults.rateLimitRetryDelayMs, + arweaveUrl = defaults.arweaveUrl, +): Promise { + while (true) { + const response = await fetch(`${arweaveUrl}/${id}`, { redirect: 'follow' }) + if (response.status !== 429) { + return response + } + console.warn( + `rate limited by ${arweaveUrl}, trying again in ${rateLimitRetryDelayMs / 1000} seconds at ${new Date(Date.now() + rateLimitRetryDelayMs).toLocaleTimeString()}`, + ) + await new Promise((resolve) => setTimeout(resolve, rateLimitRetryDelayMs)) + } +} diff --git a/packages/wallet/core/src/state/arweave/index.ts b/packages/wallet/core/src/state/arweave/index.ts new file mode 100644 index 000000000..b5f3400e6 --- /dev/null +++ b/packages/wallet/core/src/state/arweave/index.ts @@ -0,0 +1,1006 @@ +import { + Address as SequenceAddress, + Config, + Context, + GenericTree, + Payload, + Signature, +} from '@0xsequence/wallet-primitives' +import { Address, Bytes, Hex, Signature as OxSignature } from 'ox' +import { Reader as ReaderInterface, normalizeAddressKeys } from '../index.js' +import { defaults, fetchItem, findItems, Options } from './arweave.js' +import type { + ArweaveConfigRecord, + ArweaveObject, + ArweavePayloadRecord, + ArweaveSapientSignatureRecord, + ArweaveSignatureRecord, + ArweaveTreeRecord, + ArweaveV1ConfigUpdatePayloadRecord, + ArweaveV2ConfigUpdatePayloadRecord, + ArweaveV3ConfigUpdatePayloadRecord, + ArweaveWalletRecord, + CallData, + ConfigData, + SignatureType, + TreeData, + V1ConfigData, + V2ConfigTreeData, + V2ConfigData, + V3ConfigTreeData, + V3ConfigData, +} from './schema.js' + +type ArweaveConfigUpdatePayloadRecord = + | ArweaveV1ConfigUpdatePayloadRecord + | ArweaveV2ConfigUpdatePayloadRecord + | ArweaveV3ConfigUpdatePayloadRecord + +type ItemTags = { [tag: string]: string } +type ItemEntry = { id: string; tags: ItemTags } + +type Witness = { + chainId: number + payload: Payload.Parented + signature: TSignature +} + +type WitnessMap = { + [wallet: Address.Address]: Witness +} + +type Candidate = { + nextImageHash: Hex.Hex + checkpoint: bigint + noChainId: boolean + signatureEntries: Map +} + +type TopologyChoice = { + topology: Config.Topology + weight: bigint + signatures: number + size: number + signatureMask: string +} + +type TopologyChoiceSet = { + slotCount: number + choices: Map +} + +const PLAIN_SIGNATURE_TYPES = ['eip-712', 'eth_sign', 'erc-1271'] satisfies SignatureType[] +const SAPIENT_SIGNATURE_TYPES = ['sapient', 'sapient-compact'] satisfies SignatureType[] +const PAYLOAD_VERSION_FILTER = { + 'Major-Version': '1', + 'Minor-Version': '2', +} as const + +function isSapientSignatureType(type: SignatureType): type is (typeof SAPIENT_SIGNATURE_TYPES)[number] { + return (SAPIENT_SIGNATURE_TYPES as readonly SignatureType[]).includes(type) +} + +function normalizeHex(value: Hex.Hex): Hex.Hex { + return Hex.fromBytes(Hex.toBytes(value)) +} + +function normalizeAddress(value: Address.Address): Address.Address { + return Address.checksum(value) +} + +function signerKey(address: Address.Address): string { + return normalizeAddress(address).toLowerCase() +} + +function sapientSignerKey(address: Address.Address, imageHash: Hex.Hex): string { + return `${signerKey(address)}:${normalizeHex(imageHash).toLowerCase()}` +} + +function sameAddress(left: Address.Address | undefined, right: Address.Address | undefined): boolean { + return left === undefined && right === undefined + ? true + : left !== undefined && right !== undefined && Address.isEqual(left, right) +} + +function mergeConfigurations(base: Config.Config | undefined, next: Config.Config): Config.Config { + if (!base) { + return next + } + + if ( + base.threshold !== next.threshold || + base.checkpoint !== next.checkpoint || + !sameAddress(base.checkpointer, next.checkpointer) + ) { + throw new Error('conflicting configuration metadata for the same image hash') + } + + return { + ...base, + topology: Config.mergeTopology(base.topology, next.topology), + } +} + +function fromCallData(call: CallData): Payload.Call { + return { + to: normalizeAddress(call.to), + value: BigInt(call.value), + data: normalizeHex(call.data), + gasLimit: BigInt(call.gasLimit), + delegateCall: call.delegateCall, + onlyFallback: call.onlyFallback, + behaviorOnError: call.behaviorOnError, + } +} + +function fromPayloadRecord(record: ArweavePayloadRecord): Payload.Parented { + switch (record['Payload-Type']) { + case 'calls': + return { + type: 'call', + space: BigInt(record.Space), + nonce: BigInt(record.Nonce), + calls: record.data.map(fromCallData), + } + + case 'message': + return { + type: 'message', + message: normalizeHex(record.data), + } + + case 'config update': + return { + type: 'config-update', + imageHash: normalizeHex(record['To-Config']), + } + + case 'digest': + return { + type: 'digest', + digest: normalizeHex(record.Digest), + } + } +} + +function fromTreeData(tree: TreeData): GenericTree.Tree { + if (typeof tree === 'string') { + return normalizeHex(tree) + } + + if (Array.isArray(tree)) { + return tree.map(fromTreeData) as GenericTree.Branch + } + + return { + type: 'leaf', + value: Bytes.fromHex(tree.data), + } +} + +function fromV2ConfigTree(tree: V2ConfigTreeData): Config.Topology { + if (typeof tree === 'string') { + return normalizeHex(tree) + } + + if (Array.isArray(tree)) { + return [fromV2ConfigTree(tree[0]), fromV2ConfigTree(tree[1])] + } + + if ('address' in tree) { + return { + type: 'signer', + address: normalizeAddress(tree.address), + weight: BigInt(tree.weight), + } + } + + if ('tree' in tree) { + return { + type: 'nested', + weight: BigInt(tree.weight), + threshold: BigInt(tree.threshold), + tree: fromV2ConfigTree(tree.tree), + } + } + + return { + type: 'subdigest', + digest: normalizeHex(tree.subdigest), + } +} + +function fromV3ConfigTree(tree: V3ConfigTreeData): Config.Topology { + if (typeof tree === 'string') { + return normalizeHex(tree) + } + + if (Array.isArray(tree)) { + return [fromV3ConfigTree(tree[0]), fromV3ConfigTree(tree[1])] + } + + if ('address' in tree) { + if ('imageHash' in tree) { + return { + type: 'sapient-signer', + address: normalizeAddress(tree.address), + weight: BigInt(tree.weight), + imageHash: normalizeHex(tree.imageHash), + } + } + + return { + type: 'signer', + address: normalizeAddress(tree.address), + weight: BigInt(tree.weight), + } + } + + if ('tree' in tree) { + return { + type: 'nested', + weight: BigInt(tree.weight), + threshold: BigInt(tree.threshold), + tree: fromV3ConfigTree(tree.tree), + } + } + + return { + type: 'isAnyAddress' in tree && tree.isAnyAddress ? 'any-address-subdigest' : 'subdigest', + digest: normalizeHex(tree.subdigest), + } +} + +function fromConfigData(version: '1' | '2' | '3', data: ConfigData): Config.Config { + switch (version) { + case '1': { + const v1Data = data as V1ConfigData + if (v1Data.signers.length === 0) { + throw new Error('legacy configuration cannot be empty') + } + + return { + threshold: BigInt(v1Data.threshold), + checkpoint: 0n, + topology: Config.flatLeavesToTopology( + v1Data.signers.map((signer) => ({ + type: 'signer' as const, + address: normalizeAddress(signer.address), + weight: BigInt(signer.weight), + })), + ), + } + } + + case '2': { + const v2Data = data as V2ConfigData + return { + threshold: BigInt(v2Data.threshold), + checkpoint: BigInt(v2Data.checkpoint), + topology: fromV2ConfigTree(v2Data.tree), + } + } + + case '3': { + const v3Data = data as V3ConfigData + return { + threshold: BigInt(v3Data.threshold), + checkpoint: BigInt(v3Data.checkpoint), + checkpointer: v3Data.checkpointer ? normalizeAddress(v3Data.checkpointer) : undefined, + topology: fromV3ConfigTree(v3Data.tree), + } + } + } +} + +function fromConfigCarrier( + record: + | ArweaveConfigRecord + | ArweaveConfigUpdatePayloadRecord + | Extract, +): Config.Config { + switch (record.Type) { + case 'config': + return fromConfigData(record.Version, record.data) + + case 'payload': + return fromConfigData(record['To-Version'], record.data) + + case 'wallet': + return fromConfigData(record['Deploy-Version'], record.data) + } +} + +function fromSignatureRecord(record: ArweaveSignatureRecord): Signature.SignatureOfSignerLeaf { + switch (record['Signature-Type']) { + case 'eip-712': + return { type: 'hash', ...OxSignature.from(record.data) } + + case 'eth_sign': + return { type: 'eth_sign', ...OxSignature.from(record.data) } + + case 'erc-1271': + return { + type: 'erc1271', + address: normalizeAddress(record.Signer), + data: normalizeHex(record.data), + } + + case 'sapient': + case 'sapient-compact': + throw new Error(`unexpected sapient signature type ${record['Signature-Type']}`) + } +} + +function fromSapientSignatureRecord(record: ArweaveSapientSignatureRecord): Signature.SignatureOfSapientSignerLeaf { + switch (record['Signature-Type']) { + case 'sapient': + return { + type: 'sapient', + address: normalizeAddress(record.Signer), + data: normalizeHex(record.data), + } + + case 'sapient-compact': + return { + type: 'sapient_compact', + address: normalizeAddress(record.Signer), + data: normalizeHex(record.data), + } + + case 'eip-712': + case 'eth_sign': + case 'erc-1271': + throw new Error(`unexpected plain signature type ${record['Signature-Type']}`) + } +} + +function inferContext(record: ArweaveWalletRecord): Context.Context | undefined { + if ('Context-Factory' in record) { + return { + factory: normalizeAddress(record['Context-Factory']), + stage1: normalizeAddress(record['Context-Stage-1']), + stage2: normalizeAddress(record['Context-Stage-2']), + creationCode: normalizeHex(record['Context-Creation-Code']), + } + } + + const wallet = normalizeAddress(record.Wallet) + const imageHashBytes = Bytes.fromHex(normalizeHex(record['Deploy-Config'])) + + const knownContext = Context.KnownContexts.find((context) => + Address.isEqual(SequenceAddress.from(imageHashBytes, context), wallet), + ) + + if (!knownContext) { + return undefined + } + + return { + factory: knownContext.factory, + stage1: knownContext.stage1, + stage2: knownContext.stage2, + creationCode: knownContext.creationCode, + } +} + +function toRecoveredLikeTopology(topology: Config.Topology): Config.Topology { + if (Config.isNode(topology)) { + return [toRecoveredLikeTopology(topology[0]), toRecoveredLikeTopology(topology[1])] + } + + if (Config.isSignerLeaf(topology)) { + return topology.signature ? { ...topology, signed: true } : topology + } + + if (Config.isSapientSignerLeaf(topology)) { + if (topology.signature) { + return { ...topology, signed: true } + } + + return Hex.fromBytes(Config.hashConfiguration(topology)) + } + + if (Config.isNestedLeaf(topology)) { + return { + ...topology, + tree: toRecoveredLikeTopology(topology.tree), + } + } + + return topology +} + +function fillTopologyWithSignatures( + configuration: Config.Config, + signatures: Map, +): Config.Topology { + return Signature.fillLeaves(configuration.topology, (leaf) => { + if (Config.isSapientSignerLeaf(leaf)) { + const signature = signatures.get(sapientSignerKey(leaf.address, leaf.imageHash)) + return signature && Signature.isSignatureOfSapientSignerLeaf(signature) ? signature : undefined + } + + const signature = signatures.get(signerKey(leaf.address)) + return signature && !Signature.isSignatureOfSapientSignerLeaf(signature) ? signature : undefined + }) +} + +function clampWeight(weight: bigint, cap: bigint): bigint { + return weight > cap ? cap : weight +} + +function zeroMask(length: number): string { + return '0'.repeat(length) +} + +function compareChoices(left: TopologyChoice, right: TopologyChoice): number { + if (left.signatures !== right.signatures) { + return left.signatures - right.signatures + } + + if (left.size !== right.size) { + return left.size - right.size + } + + if (left.signatureMask !== right.signatureMask) { + return left.signatureMask > right.signatureMask ? -1 : 1 + } + + return 0 +} + +function dominatesChoice(left: TopologyChoice, right: TopologyChoice): boolean { + return ( + left.weight >= right.weight && + left.signatures <= right.signatures && + left.size <= right.size && + left.signatureMask >= right.signatureMask + ) +} + +function makeChoice( + topology: Config.Topology, + weight: bigint, + signatures: number, + signatureMask: string, +): TopologyChoice { + return { + topology, + weight, + signatures, + size: Signature.encodeTopology(topology).length, + signatureMask, + } +} + +function addChoice(choiceSet: TopologyChoiceSet, choice: TopologyChoice): void { + const key = choice.weight.toString() + const existing = choiceSet.choices.get(key) + + if (!existing || compareChoices(choice, existing) < 0) { + choiceSet.choices.set(key, choice) + } +} + +function pruneChoiceSet(choiceSet: TopologyChoiceSet): TopologyChoiceSet { + const choices = [...choiceSet.choices.values()] + const pruned = new Map() + + for (const candidate of choices) { + const dominated = choices.some((other) => other !== candidate && dominatesChoice(other, candidate)) + if (!dominated) { + pruned.set(candidate.weight.toString(), candidate) + } + } + + return { ...choiceSet, choices: pruned } +} + +function buildTopologyChoiceSet(topology: Config.Topology, cap: bigint): TopologyChoiceSet { + if (Signature.isSignedSignerLeaf(topology)) { + const choices: TopologyChoiceSet = { slotCount: 1, choices: new Map() } + addChoice(choices, makeChoice({ type: 'signer', address: topology.address, weight: topology.weight }, 0n, 0, '0')) + + if (topology.weight > 0n) { + addChoice(choices, makeChoice(topology, clampWeight(topology.weight, cap), 1, '1')) + } + + return choices + } + + if (Signature.isSignedSapientSignerLeaf(topology)) { + const choices: TopologyChoiceSet = { slotCount: 1, choices: new Map() } + addChoice(choices, makeChoice(Hex.fromBytes(Config.hashConfiguration(topology)), 0n, 0, '0')) + + if (topology.weight > 0n) { + addChoice(choices, makeChoice(topology, clampWeight(topology.weight, cap), 1, '1')) + } + + return choices + } + + if (Config.isSignerLeaf(topology)) { + return { + slotCount: 0, + choices: new Map([[0n.toString(), makeChoice(topology, 0n, 0, '')]]), + } + } + + if (Config.isSapientSignerLeaf(topology)) { + return { + slotCount: 0, + choices: new Map([[0n.toString(), makeChoice(Hex.fromBytes(Config.hashConfiguration(topology)), 0n, 0, '')]]), + } + } + + if (Config.isSubdigestLeaf(topology) || Config.isAnyAddressSubdigestLeaf(topology) || Config.isNodeLeaf(topology)) { + return { + slotCount: 0, + choices: new Map([[0n.toString(), makeChoice(topology, 0n, 0, '')]]), + } + } + + if (Config.isNestedLeaf(topology)) { + const treeChoices = buildTopologyChoiceSet(topology.tree, topology.threshold) + const choices: TopologyChoiceSet = { slotCount: treeChoices.slotCount, choices: new Map() } + addChoice( + choices, + makeChoice(Hex.fromBytes(Config.hashConfiguration(topology)), 0n, 0, zeroMask(treeChoices.slotCount)), + ) + + const satisfied = treeChoices.choices.get(topology.threshold.toString()) + if (satisfied && topology.weight > 0n) { + addChoice( + choices, + makeChoice( + { ...topology, tree: satisfied.topology }, + clampWeight(topology.weight, cap), + satisfied.signatures, + satisfied.signatureMask, + ), + ) + } + + return pruneChoiceSet(choices) + } + + const leftChoices = buildTopologyChoiceSet(topology[0], cap) + const rightChoices = buildTopologyChoiceSet(topology[1], cap) + const choices: TopologyChoiceSet = { + slotCount: leftChoices.slotCount + rightChoices.slotCount, + choices: new Map(), + } + + addChoice(choices, makeChoice(Hex.fromBytes(Config.hashConfiguration(topology)), 0n, 0, zeroMask(choices.slotCount))) + + for (const leftChoice of leftChoices.choices.values()) { + for (const rightChoice of rightChoices.choices.values()) { + addChoice( + choices, + makeChoice( + [leftChoice.topology, rightChoice.topology], + clampWeight(leftChoice.weight + rightChoice.weight, cap), + leftChoice.signatures + rightChoice.signatures, + `${leftChoice.signatureMask}${rightChoice.signatureMask}`, + ), + ) + } + } + + return pruneChoiceSet(choices) +} + +function minimizeTopologyForThreshold(topology: Config.Topology, threshold: bigint): Config.Topology | undefined { + return buildTopologyChoiceSet(topology, threshold).choices.get(threshold.toString())?.topology +} + +export class Reader implements ReaderInterface { + constructor(private readonly options: Options = defaults) {} + + private async findEntries( + filter: { [name: string]: undefined | string | string[] }, + options?: { maxResults?: number }, + ): Promise { + const items = await findItems(filter, { ...this.options, maxResults: options?.maxResults }) + return Object.entries(items).map(([id, tags]) => ({ id, tags })) + } + + private async loadRecord(entry: ItemEntry): Promise { + const response = await fetchItem(entry.id, this.options.rateLimitRetryDelayMs, this.options.arweaveUrl) + if (!response.ok) { + throw new Error(`failed to fetch arweave item ${entry.id}: ${response.status}`) + } + + const data = + entry.tags['Content-Type'] === 'application/json' ? await response.json() : (await response.text()).trim() + return { ...entry.tags, data } as T + } + + private async findFirstRecord(filter: { + [name: string]: undefined | string | string[] + }): Promise { + const [entry] = await this.findEntries(filter, { maxResults: 1 }) + return entry ? this.loadRecord(entry) : undefined + } + + async getConfiguration(imageHash: Hex.Hex): Promise { + const normalizedImageHash = normalizeHex(imageHash) + const configEntries = await this.findEntries({ Type: 'config', Config: normalizedImageHash }) + + let configuration: Config.Config | undefined + + for (const record of await Promise.all(configEntries.map((entry) => this.loadRecord(entry)))) { + configuration = mergeConfigurations(configuration, fromConfigCarrier(record)) + } + + if (configuration) { + return configuration + } + + const [walletEntries, payloadEntries] = await Promise.all([ + this.findEntries({ + Type: 'wallet', + 'Deploy-Config': normalizedImageHash, + 'Deploy-Config-Attached': 'true', + }), + this.findEntries({ + Type: 'payload', + ...PAYLOAD_VERSION_FILTER, + 'Payload-Type': 'config update', + 'To-Config': normalizedImageHash, + }), + ]) + + for (const record of await Promise.all(walletEntries.map((entry) => this.loadRecord(entry)))) { + if (record['Deploy-Config-Attached'] === 'true') { + configuration = mergeConfigurations(configuration, fromConfigCarrier(record)) + } + } + + for (const record of await Promise.all( + payloadEntries.map((entry) => this.loadRecord(entry)), + )) { + configuration = mergeConfigurations(configuration, fromConfigCarrier(record)) + } + + return configuration + } + + async getDeploy(wallet: Address.Address): Promise<{ imageHash: Hex.Hex; context: Context.Context } | undefined> { + const record = await this.findFirstRecord({ + Type: 'wallet', + Wallet: normalizeAddress(wallet), + }) + + if (!record) { + return undefined + } + + const context = inferContext(record) + if (!context) { + return undefined + } + + return { + imageHash: normalizeHex(record['Deploy-Config']), + context, + } + } + + private async getWalletsGeneric( + filter: { [name: string]: undefined | string | string[] }, + signatureFrom: (record: TRecord) => TSignature, + ): Promise> { + const payloads = new Map< + Hex.Hex, + Promise<{ chainId: number; payload: Payload.Parented; wallet: Address.Address } | undefined> + >() + const response: WitnessMap = {} + + for (const entry of await this.findEntries(filter)) { + const wallet = normalizeAddress(entry.tags.Wallet as Address.Address) + if (response[wallet]) { + continue + } + + const record = await this.loadRecord(entry) + const subdigest = normalizeHex(record.Subdigest) + const payloadPromise = payloads.get(subdigest) ?? this.getPayload(subdigest) + payloads.set(subdigest, payloadPromise) + const payload = await payloadPromise + + if (!payload) { + continue + } + + response[wallet] = { + chainId: payload.chainId, + payload: payload.payload, + signature: signatureFrom(record), + } + } + + return normalizeAddressKeys(response) + } + + async getWallets(signer: Address.Address): Promise<{ + [wallet: Address.Address]: { + chainId: number + payload: Payload.Parented + signature: Signature.SignatureOfSignerLeaf + } + }> { + return this.getWalletsGeneric( + { + Type: 'signature', + Signer: normalizeAddress(signer), + Witness: 'true', + 'Signature-Type': [...PLAIN_SIGNATURE_TYPES], + }, + fromSignatureRecord, + ) + } + + async getWalletsForSapient( + signer: Address.Address, + imageHash: Hex.Hex, + ): Promise<{ + [wallet: Address.Address]: { + chainId: number + payload: Payload.Parented + signature: Signature.SignatureOfSapientSignerLeaf + } + }> { + return this.getWalletsGeneric( + { + Type: 'signature', + Signer: normalizeAddress(signer), + 'Image-Hash': normalizeHex(imageHash), + Witness: 'true', + 'Signature-Type': [...SAPIENT_SIGNATURE_TYPES], + }, + fromSapientSignatureRecord, + ) + } + + private async getWitnessGeneric( + filter: { [name: string]: undefined | string | string[] }, + signatureFrom: (record: TRecord) => TSignature, + ): Promise | undefined> { + const entries = await this.findEntries(filter) + + for (const entry of entries) { + const record = await this.loadRecord(entry) + const payload = await this.getPayload(record.Subdigest) + if (!payload) { + continue + } + + return { + chainId: payload.chainId, + payload: payload.payload, + signature: signatureFrom(record), + } + } + } + + getWitnessFor( + wallet: Address.Address, + signer: Address.Address, + ): Promise<{ chainId: number; payload: Payload.Parented; signature: Signature.SignatureOfSignerLeaf } | undefined> { + return this.getWitnessGeneric( + { + Type: 'signature', + Wallet: normalizeAddress(wallet), + Signer: normalizeAddress(signer), + Witness: 'true', + 'Signature-Type': [...PLAIN_SIGNATURE_TYPES], + }, + fromSignatureRecord, + ) + } + + getWitnessForSapient( + wallet: Address.Address, + signer: Address.Address, + imageHash: Hex.Hex, + ): Promise< + { chainId: number; payload: Payload.Parented; signature: Signature.SignatureOfSapientSignerLeaf } | undefined + > { + return this.getWitnessGeneric( + { + Type: 'signature', + Wallet: normalizeAddress(wallet), + Signer: normalizeAddress(signer), + 'Image-Hash': normalizeHex(imageHash), + Witness: 'true', + 'Signature-Type': [...SAPIENT_SIGNATURE_TYPES], + }, + fromSapientSignatureRecord, + ) + } + + async getConfigurationUpdates( + wallet: Address.Address, + fromImageHash: Hex.Hex, + options?: { allUpdates?: boolean }, + ): Promise> { + const normalizedWallet = normalizeAddress(wallet) + const signatureRecords = new Map>() + const loadSignatureRecord = (entry: ItemEntry): Promise => { + const cached = signatureRecords.get(entry.id) + if (cached) { + return cached + } + + const promise = isSapientSignatureType(entry.tags['Signature-Type'] as SignatureType) + ? this.loadRecord(entry) + : this.loadRecord(entry) + + signatureRecords.set(entry.id, promise) + return promise + } + + const updates: Array<{ imageHash: Hex.Hex; signature: Signature.RawSignature }> = [] + let currentImageHash = normalizeHex(fromImageHash) + + top: while (true) { + const currentConfig = await this.getConfiguration(currentImageHash) + if (!currentConfig) { + return updates + } + + const { signers, sapientSigners } = Config.getSigners(currentConfig) + const [plainEntries, sapientEntries] = await Promise.all([ + signers.length + ? this.findEntries({ + Type: 'config update', + Wallet: normalizedWallet, + Signer: signers.map(normalizeAddress), + 'Signature-Type': [...PLAIN_SIGNATURE_TYPES], + }) + : Promise.resolve([]), + Promise.all( + sapientSigners.map(({ address, imageHash }) => + this.findEntries({ + Type: 'config update', + Wallet: normalizedWallet, + Signer: normalizeAddress(address), + 'Image-Hash': normalizeHex(imageHash), + 'Signature-Type': [...SAPIENT_SIGNATURE_TYPES], + }), + ), + ), + ]) + + const candidates = new Map() + const addCandidate = (entry: ItemEntry, key: string) => { + const checkpoint = BigInt(entry.tags['To-Checkpoint']!) + if (checkpoint <= currentConfig.checkpoint) { + return + } + + const nextImageHash = normalizeHex(entry.tags['To-Config'] as Hex.Hex) + const candidateKey = `${checkpoint}:${nextImageHash.toLowerCase()}` + const candidate = candidates.get(candidateKey) + + if (candidate) { + if (!candidate.signatureEntries.has(key)) { + candidate.signatureEntries.set(key, entry) + } + + return + } + + candidates.set(candidateKey, { + nextImageHash, + checkpoint, + noChainId: entry.tags['Major-Version'] !== '1', + signatureEntries: new Map([[key, entry]]), + }) + } + + for (const entry of plainEntries) { + addCandidate(entry, signerKey(entry.tags.Signer as Address.Address)) + } + + for (const entries of sapientEntries) { + for (const entry of entries) { + addCandidate( + entry, + sapientSignerKey(entry.tags.Signer as Address.Address, entry.tags['Image-Hash'] as Hex.Hex), + ) + } + } + + const sortedCandidates = [...candidates.values()].sort((left, right) => { + if (left.checkpoint === right.checkpoint) { + return 0 + } + + if (options?.allUpdates) { + return left.checkpoint < right.checkpoint ? -1 : 1 + } + + return left.checkpoint > right.checkpoint ? -1 : 1 + }) + + for (const candidate of sortedCandidates) { + const signatures = new Map() + const records = await Promise.all([...candidate.signatureEntries.values()].map(loadSignatureRecord)) + + for (const record of records) { + if (isSapientSignatureType(record['Signature-Type'])) { + const sapientRecord = record as ArweaveSapientSignatureRecord + signatures.set( + sapientSignerKey(sapientRecord.Signer, sapientRecord['Image-Hash']), + fromSapientSignatureRecord(sapientRecord), + ) + } else { + signatures.set(signerKey(record.Signer), fromSignatureRecord(record as ArweaveSignatureRecord)) + } + } + + const filledTopology = fillTopologyWithSignatures(currentConfig, signatures) + const minimalTopology = minimizeTopologyForThreshold(filledTopology, currentConfig.threshold) + if (!minimalTopology) { + continue + } + + const topology = toRecoveredLikeTopology(minimalTopology) + const { weight } = Config.getWeight(topology, () => false) + if (weight < currentConfig.threshold) { + continue + } + + updates.push({ + imageHash: candidate.nextImageHash, + signature: { + noChainId: candidate.noChainId, + configuration: { + threshold: currentConfig.threshold, + checkpoint: currentConfig.checkpoint, + checkpointer: currentConfig.checkpointer, + topology, + }, + }, + }) + + currentImageHash = candidate.nextImageHash + continue top + } + + return updates + } + } + + async getTree(imageHash: Hex.Hex): Promise { + const record = await this.findFirstRecord({ + Type: 'tree', + Tree: normalizeHex(imageHash), + }) + + return record ? fromTreeData(record.data) : undefined + } + + async getPayload( + digest: Hex.Hex, + ): Promise<{ chainId: number; payload: Payload.Parented; wallet: Address.Address } | undefined> { + const record = await this.findFirstRecord({ + Type: 'payload', + ...PAYLOAD_VERSION_FILTER, + Payload: normalizeHex(digest), + }) + + if (!record) { + return undefined + } + + return { + chainId: Number(record['Chain-ID']), + payload: fromPayloadRecord(record), + wallet: normalizeAddress(record.Address), + } + } +} diff --git a/packages/wallet/core/src/state/arweave/schema.ts b/packages/wallet/core/src/state/arweave/schema.ts new file mode 100644 index 000000000..059579cb0 --- /dev/null +++ b/packages/wallet/core/src/state/arweave/schema.ts @@ -0,0 +1,369 @@ +import { Address, Hex } from 'ox' + +export type BooleanString = 'true' | 'false' +export type IntegerString = `${number}` +export type ConfigVersion = '1' | '2' | '3' +export type WalletMajorVersion = '1' | '2' +export type PayloadType = 'calls' | 'message' | 'config update' | 'digest' +export type SignatureType = 'eip-712' | 'eth_sign' | 'erc-1271' | 'sapient' | 'sapient-compact' +export type BehaviorOnError = 'ignore' | 'revert' | 'abort' + +export interface V1ConfigSignerData { + weight: number + address: Address.Address +} + +export interface V1ConfigData { + threshold: number + signers: Array +} + +export interface V2ConfigAddressLeafData { + weight: number + address: Address.Address +} + +export interface V2ConfigNestedLeafData { + weight: number + threshold: number + tree: V2ConfigTreeData +} + +export interface V2ConfigSubdigestLeafData { + subdigest: Hex.Hex +} + +export type V2ConfigTreeData = + | Hex.Hex + | [V2ConfigTreeData, V2ConfigTreeData] + | V2ConfigAddressLeafData + | V2ConfigNestedLeafData + | V2ConfigSubdigestLeafData + +export interface V2ConfigData { + threshold: number + checkpoint: number + tree: V2ConfigTreeData +} + +export interface V3ConfigAddressLeafData { + weight: number + address: Address.Address +} + +export interface V3ConfigSapientSignerLeafData { + weight: number + address: Address.Address + imageHash: Hex.Hex +} + +export interface V3ConfigNestedLeafData { + weight: number + threshold: number + tree: V3ConfigTreeData +} + +export interface V3ConfigSubdigestLeafData { + subdigest: Hex.Hex +} + +export interface V3ConfigAnyAddressSubdigestLeafData { + subdigest: Hex.Hex + isAnyAddress: true +} + +export type V3ConfigTreeData = + | Hex.Hex + | [V3ConfigTreeData, V3ConfigTreeData] + | V3ConfigAddressLeafData + | V3ConfigSapientSignerLeafData + | V3ConfigNestedLeafData + | V3ConfigSubdigestLeafData + | V3ConfigAnyAddressSubdigestLeafData + +export interface V3ConfigData { + threshold: number + checkpoint: IntegerString + tree: V3ConfigTreeData + checkpointer?: Address.Address +} + +export type ConfigData = V1ConfigData | V2ConfigData | V3ConfigData + +export interface TreeLeafData { + data: Hex.Hex +} + +export type TreeData = Hex.Hex | TreeLeafData | Array + +export interface CallData { + to: Address.Address + value: IntegerString + data: Hex.Hex + gasLimit: IntegerString + delegateCall: boolean + onlyFallback: boolean + behaviorOnError: BehaviorOnError +} + +export interface ArweaveRecordBase< + TType extends string, + TMajorVersion extends string, + TMinorVersion extends string, + TContentType extends string, +> { + Type: TType + 'Major-Version': TMajorVersion + 'Minor-Version': TMinorVersion + 'Content-Type': TContentType +} + +export interface ArweaveConfigRecordBase extends ArweaveRecordBase<'config', '1', '0', 'application/json'> { + Config: Hex.Hex + Complete: BooleanString + 'Signers-Count': IntegerString + 'Signers-Bloom': Hex.Hex +} + +export interface ArweaveV1ConfigRecord extends ArweaveConfigRecordBase { + Version: '1' + data: V1ConfigData +} + +export interface ArweaveV2ConfigRecord extends ArweaveConfigRecordBase { + Version: '2' + data: V2ConfigData +} + +export interface ArweaveV3ConfigRecord extends ArweaveConfigRecordBase { + Version: '3' + data: V3ConfigData +} + +export type ArweaveConfigRecord = ArweaveV1ConfigRecord | ArweaveV2ConfigRecord | ArweaveV3ConfigRecord + +export interface ArweaveTreeRecord extends ArweaveRecordBase<'tree', '1', '0', 'application/json'> { + Tree: Hex.Hex + Complete: BooleanString + data: TreeData +} + +export interface ArweaveWalletRecordBase extends ArweaveRecordBase< + 'wallet', + WalletMajorVersion, + '0', + 'application/json' +> { + Wallet: Address.Address + 'Deploy-Config': Hex.Hex + 'Deploy-Version': ConfigVersion + 'Deploy-Config-Attached': BooleanString + 'Deploy-Config-Complete': BooleanString + 'Deploy-Signers-Count': IntegerString + 'Deploy-Signers-Bloom': Hex.Hex +} + +export interface ArweaveWalletDefaultContext { + 'Major-Version': '1' +} + +export interface ArweaveWalletCustomContext { + 'Major-Version': '2' + 'Context-Factory': Address.Address + 'Context-Stage-1': Address.Address + 'Context-Stage-2': Address.Address + 'Context-Guest': Address.Address + 'Context-Creation-Code': Hex.Hex +} + +export interface ArweaveWalletDetachedData { + 'Deploy-Config-Attached': 'false' + 'Deploy-Config-Complete': 'false' + data: null +} + +export interface ArweaveWalletWithV1DeployConfig { + 'Deploy-Config-Attached': 'true' + 'Deploy-Version': '1' + data: V1ConfigData +} + +export interface ArweaveWalletWithV2DeployConfig { + 'Deploy-Config-Attached': 'true' + 'Deploy-Version': '2' + data: V2ConfigData +} + +export interface ArweaveWalletWithV3DeployConfig { + 'Deploy-Config-Attached': 'true' + 'Deploy-Version': '3' + data: V3ConfigData +} + +export type ArweaveWalletRecord = ArweaveWalletRecordBase & + (ArweaveWalletDefaultContext | ArweaveWalletCustomContext) & + ( + | ArweaveWalletDetachedData + | ArweaveWalletWithV1DeployConfig + | ArweaveWalletWithV2DeployConfig + | ArweaveWalletWithV3DeployConfig + ) + +export interface ArweavePayloadRecordBase extends ArweaveRecordBase<'payload', '1', '2', 'application/json'> { + Payload: Hex.Hex + Address: Address.Address + 'Chain-ID': IntegerString + 'Payload-Type': PayloadType +} + +export interface ArweaveCallsPayloadRecord extends ArweavePayloadRecordBase { + 'Payload-Type': 'calls' + Space: IntegerString + Nonce: IntegerString + data: Array +} + +export interface ArweaveMessagePayloadRecord extends ArweavePayloadRecordBase { + 'Payload-Type': 'message' + data: Hex.Hex +} + +export interface ArweaveConfigUpdatePayloadRecordBase extends ArweavePayloadRecordBase { + 'Payload-Type': 'config update' + 'To-Config': Hex.Hex + 'To-Checkpoint': IntegerString + 'To-Config-Complete': BooleanString + 'To-Signers-Count': IntegerString + 'To-Signers-Bloom': Hex.Hex +} + +export interface ArweaveV1ConfigUpdatePayloadRecord extends ArweaveConfigUpdatePayloadRecordBase { + 'To-Version': '1' + data: V1ConfigData +} + +export interface ArweaveV2ConfigUpdatePayloadRecord extends ArweaveConfigUpdatePayloadRecordBase { + 'To-Version': '2' + data: V2ConfigData +} + +export interface ArweaveV3ConfigUpdatePayloadRecord extends ArweaveConfigUpdatePayloadRecordBase { + 'To-Version': '3' + data: V3ConfigData +} + +export interface ArweaveDigestPayloadRecord extends ArweavePayloadRecordBase { + 'Payload-Type': 'digest' + Digest: Hex.Hex + data: null +} + +export type ArweavePayloadRecord = + | ArweaveCallsPayloadRecord + | ArweaveMessagePayloadRecord + | ArweaveV1ConfigUpdatePayloadRecord + | ArweaveV2ConfigUpdatePayloadRecord + | ArweaveV3ConfigUpdatePayloadRecord + | ArweaveDigestPayloadRecord + +export interface ArweaveConfigUpdateTags { + Type: 'config update' + 'To-Config': Hex.Hex + 'To-Checkpoint': IntegerString + 'To-Config-Complete': BooleanString + 'To-Signers-Count': IntegerString + 'To-Signers-Bloom': Hex.Hex +} + +export interface ArweavePlainSignatureTags { + Type: 'signature' +} + +export interface ArweaveSignatureDigestTags { + 'Major-Version': '1' + Digest: Hex.Hex +} + +export interface ArweaveSignatureSubdigestTags { + 'Major-Version': '2' +} + +export interface ArweaveSignatureBlockTags { + 'Block-Number': IntegerString + 'Block-Hash': Hex.Hex +} + +export interface ArweaveSignatureRecordBase extends ArweaveRecordBase< + 'signature' | 'config update', + '1' | '2', + '0', + 'text/plain' +> { + 'Signature-Type': SignatureType + Signer: Address.Address + Subdigest: Hex.Hex + Wallet: Address.Address + 'Chain-ID': IntegerString + Witness: BooleanString + data: Hex.Hex +} + +export type ArweaveSignatureRecord = + | (ArweaveSignatureRecordBase & ArweavePlainSignatureTags & ArweaveSignatureDigestTags) + | (ArweaveSignatureRecordBase & ArweavePlainSignatureTags & ArweaveSignatureSubdigestTags) + | (ArweaveSignatureRecordBase & ArweavePlainSignatureTags & ArweaveSignatureDigestTags & ArweaveSignatureBlockTags) + | (ArweaveSignatureRecordBase & ArweavePlainSignatureTags & ArweaveSignatureSubdigestTags & ArweaveSignatureBlockTags) + | (ArweaveSignatureRecordBase & ArweaveConfigUpdateTags & ArweaveSignatureDigestTags) + | (ArweaveSignatureRecordBase & ArweaveConfigUpdateTags & ArweaveSignatureSubdigestTags) + | (ArweaveSignatureRecordBase & ArweaveConfigUpdateTags & ArweaveSignatureDigestTags & ArweaveSignatureBlockTags) + | (ArweaveSignatureRecordBase & ArweaveConfigUpdateTags & ArweaveSignatureSubdigestTags & ArweaveSignatureBlockTags) + +export interface ArweaveSapientSignatureRecordBase extends ArweaveRecordBase< + 'signature' | 'config update', + '2', + '0', + 'text/plain' +> { + 'Signature-Type': SignatureType + Signer: Address.Address + 'Image-Hash': Hex.Hex + Subdigest: Hex.Hex + Wallet: Address.Address + 'Chain-ID': IntegerString + 'Block-Number': IntegerString + 'Block-Hash': Hex.Hex + Witness: BooleanString + data: Hex.Hex +} + +export type ArweaveSapientSignatureRecord = + | (ArweaveSapientSignatureRecordBase & ArweavePlainSignatureTags) + | (ArweaveSapientSignatureRecordBase & ArweaveConfigUpdateTags) + +export interface ArweaveMigrationRecord extends ArweaveRecordBase<'migration', '1', '0', 'text/plain'> { + Migration: Address.Address + 'Chain-ID': IntegerString + 'From-Version': ConfigVersion + 'From-Config': Hex.Hex + 'From-Config-Complete': BooleanString + 'From-Signers-Count': IntegerString + 'From-Signers-Bloom': Hex.Hex + 'To-Version': ConfigVersion + 'To-Config': Hex.Hex + 'To-Config-Complete': BooleanString + 'To-Signers-Count': IntegerString + 'To-Signers-Bloom': Hex.Hex + Executor: Address.Address + data: Hex.Hex +} + +export type ArweaveRecord = + | ArweaveConfigRecord + | ArweaveTreeRecord + | ArweaveWalletRecord + | ArweavePayloadRecord + | ArweaveSignatureRecord + | ArweaveSapientSignatureRecord + | ArweaveMigrationRecord + +export type ArweaveObject = ArweaveRecord diff --git a/packages/wallet/core/src/state/index.ts b/packages/wallet/core/src/state/index.ts index a0a025267..4ad637632 100644 --- a/packages/wallet/core/src/state/index.ts +++ b/packages/wallet/core/src/state/index.ts @@ -98,9 +98,10 @@ export interface Writer { export type MaybePromise = T | Promise -export * as Local from './local/index.js' +export * from './cached.js' +export * from './debug.js' export * from './utils.js' +export * as Arweave from './arweave/index.js' +export * as Local from './local/index.js' export * as Remote from './remote/index.js' -export * from './cached.js' export * as Sequence from './sequence/index.js' -export * from './debug.js' diff --git a/packages/wallet/core/src/state/local/index.ts b/packages/wallet/core/src/state/local/index.ts index cde4f93da..46703d0a7 100644 --- a/packages/wallet/core/src/state/local/index.ts +++ b/packages/wallet/core/src/state/local/index.ts @@ -75,7 +75,7 @@ export interface Store { export class Provider implements ProviderInterface { constructor( private readonly store: Store = new MemoryStore(), - public readonly extensions: Extensions.Extensions = Extensions.Rc3, + public readonly extensions: Extensions.Extensions = Extensions.Rc5, ) {} getConfiguration(imageHash: Hex.Hex): Promise { @@ -212,7 +212,7 @@ export class Provider implements ProviderInterface { fromImageHash: Hex.Hex, options?: { allUpdates?: boolean }, ): Promise<{ imageHash: Hex.Hex; signature: Signature.RawSignature }[]> { - let fromConfig = await this.store.loadConfig(fromImageHash) + const fromConfig = await this.store.loadConfig(fromImageHash) if (!fromConfig) { return [] } @@ -393,7 +393,7 @@ export class Provider implements ProviderInterface { if (Signature.isSignatureOfSapientSignerLeaf(topology.signature)) { switch (topology.signature.address.toLowerCase()) { - case this.extensions.passkeys.toLowerCase(): + case this.extensions.passkeys.toLowerCase(): { const decoded = Extensions.Passkeys.decode(Bytes.fromHex(topology.signature.data)) if (!Extensions.Passkeys.isValidSignature(subdigest, decoded)) { @@ -406,6 +406,8 @@ export class Provider implements ProviderInterface { Extensions.Passkeys.rootFor(decoded.publicKey), topology.signature, ) + } + default: throw new Error(`Unsupported sapient signer: ${topology.signature.address}`) } diff --git a/packages/wallet/core/src/state/local/indexed-db.ts b/packages/wallet/core/src/state/local/indexed-db.ts index 56ada1e71..7f7e7cbc8 100644 --- a/packages/wallet/core/src/state/local/indexed-db.ts +++ b/packages/wallet/core/src/state/local/indexed-db.ts @@ -1,5 +1,6 @@ import { Context, Payload, Signature, Config, GenericTree } from '@0xsequence/wallet-primitives' import { Address, Hex } from 'ox' +import type { CoreEnv } from '../../env.js' import { Store } from './index.js' import { Migration } from '../index.js' @@ -18,15 +19,27 @@ export class IndexedDbStore implements Store { private _db: IDBDatabase | null = null private dbName: string - constructor(dbName: string = 'sequence-indexeddb') { + constructor( + dbName: string = 'sequence-indexeddb', + private readonly env?: CoreEnv, + ) { this.dbName = dbName } + private getIndexedDB(): IDBFactory { + const globalObj = globalThis as any + const indexedDb = this.env?.indexedDB ?? globalObj.indexedDB ?? globalObj.window?.indexedDB + if (!indexedDb) { + throw new Error('indexedDB is not available') + } + return indexedDb + } + private async openDB(): Promise { if (this._db) return this._db return new Promise((resolve, reject) => { - const request = indexedDB.open(this.dbName, DB_VERSION) + const request = this.getIndexedDB().open(this.dbName, DB_VERSION) request.onupgradeneeded = () => { const db = request.result diff --git a/packages/wallet/core/src/state/remote/dev-http.ts b/packages/wallet/core/src/state/remote/dev-http.ts index d7489b5fd..5120ff661 100644 --- a/packages/wallet/core/src/state/remote/dev-http.ts +++ b/packages/wallet/core/src/state/remote/dev-http.ts @@ -4,10 +4,16 @@ import { Migration, Provider } from '../index.js' export class DevHttpProvider implements Provider { private readonly baseUrl: string + private readonly fetcher: typeof fetch - constructor(baseUrl: string) { + constructor(baseUrl: string, fetcher?: typeof fetch) { // Remove trailing slash if present this.baseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl + const resolvedFetch = fetcher ?? (globalThis as any).fetch + if (!resolvedFetch) { + throw new Error('fetch is not available') + } + this.fetcher = resolvedFetch } private async request(method: 'GET' | 'POST', path: string, body?: any): Promise { @@ -24,7 +30,7 @@ export class DevHttpProvider implements Provider { let response: Response try { - response = await fetch(url, options) + response = await this.fetcher(url, options) } catch (networkError) { // Handle immediate network errors (e.g., DNS resolution failure, refused connection) console.error(`Network error during ${method} request to ${url}:`, networkError) @@ -38,12 +44,12 @@ export class DevHttpProvider implements Provider { const errorText = await response.text() const errorJson = await Utils.fromJSON(errorText) errorPayload = { ...errorPayload, ...errorJson } - } catch (e) { + } catch { try { // If JSON parsing fails, try getting text for better error message const errorText = await response.text() errorPayload.body = errorText - } catch (textErr) { + } catch { // Ignore if reading text also fails } } @@ -99,7 +105,7 @@ export class DevHttpProvider implements Provider { throw new Error( `Failed to parse JSON response from server. Status: ${response.status}. Body: "${text}". Original error: ${error instanceof Error ? error.message : String(error)}`, ) - } catch (readError) { + } catch { throw new Error( `Failed to parse JSON response from server and could not read response body as text. Status: ${response.status}. Original error: ${error instanceof Error ? error.message : String(error)}`, ) diff --git a/packages/wallet/core/src/utils/session/permission-builder.ts b/packages/wallet/core/src/utils/session/permission-builder.ts index 08b279509..c77580a18 100644 --- a/packages/wallet/core/src/utils/session/permission-builder.ts +++ b/packages/wallet/core/src/utils/session/permission-builder.ts @@ -59,8 +59,8 @@ export class PermissionBuilder { throw new Error(`cannot call exactCalldata() after calling allowAll() or adding rules`) } for (let offset = 0; offset < calldata.length; offset += 32) { - let value = calldata.slice(offset, offset + 32) - let mask = Permission.MASK.BYTES32 + let value: Bytes.Bytes = calldata.slice(offset, offset + 32) + let mask: Bytes.Bytes = Permission.MASK.BYTES32 if (value.length < 32) { mask = Bytes.fromHex(`0x${'ff'.repeat(value.length)}${'00'.repeat(32 - value.length)}`) value = Bytes.padRight(value, 32) diff --git a/packages/wallet/core/test/constants.ts b/packages/wallet/core/test/constants.ts index 0622a2db9..42b51d49b 100644 --- a/packages/wallet/core/test/constants.ts +++ b/packages/wallet/core/test/constants.ts @@ -1,9 +1,12 @@ import { config as dotenvConfig } from 'dotenv' import { Abi, AbiEvent, Address } from 'ox' +// eslint-disable-next-line turbo/no-undeclared-env-vars const envFile = process.env.CI ? '.env.test' : '.env.test.local' dotenvConfig({ path: envFile }) +// Contracts are deployed on Arbitrum + // Requires https://example.com redirectUrl export const EMITTER_ADDRESS1: Address.Address = '0xad90eB52BC180Bd9f66f50981E196f3E996278D3' // Requires https://another-example.com redirectUrl @@ -16,6 +19,5 @@ export const EMITTER_EVENT_TOPICS = [ export const USDC_ADDRESS: Address.Address = '0xaf88d065e77c8cc2239327c5edb3a432268e5831' // Environment variables +// eslint-disable-next-line turbo/no-undeclared-env-vars export const LOCAL_RPC_URL = process.env.LOCAL_RPC_URL || 'http://localhost:8545' -export const { RPC_URL, PRIVATE_KEY } = process.env -export const CAN_RUN_LIVE = !!RPC_URL && !!PRIVATE_KEY diff --git a/packages/wallet/core/test/envelope.test.ts b/packages/wallet/core/test/envelope.test.ts index 8ecc0e282..c92cf900e 100644 --- a/packages/wallet/core/test/envelope.test.ts +++ b/packages/wallet/core/test/envelope.test.ts @@ -6,7 +6,6 @@ import * as Envelope from '../src/envelope.js' // Test addresses and data const TEST_ADDRESS_1 = Address.from('0x1234567890123456789012345678901234567890') -const TEST_ADDRESS_2 = Address.from('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd') const TEST_ADDRESS_3 = Address.from('0x9876543210987654321098765432109876543210') const TEST_WALLET = Address.from('0xfedcbafedcbafedcbafedcbafedcbafedcbafe00') const TEST_IMAGE_HASH = Hex.from('0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef') diff --git a/packages/wallet/core/test/session-manager.test.ts b/packages/wallet/core/test/session-manager.test.ts index 6a0bd156b..7ba342ebc 100644 --- a/packages/wallet/core/test/session-manager.test.ts +++ b/packages/wallet/core/test/session-manager.test.ts @@ -1,20 +1,17 @@ +import { Constants, Extensions } from '@0xsequence/wallet-primitives' import { AbiEvent, AbiFunction, Address, Bytes, Hex, Provider, RpcTransport, Secp256k1 } from 'ox' import { describe, expect, it } from 'vitest' - import { Attestation, GenericTree, Payload, Permission, SessionConfig } from '../../primitives/src/index.js' import { Envelope, Signers, State, Utils, Wallet } from '../src/index.js' - +import { ExplicitSessionConfig } from '../src/utils/session/types.js' import { - EMITTER_FUNCTIONS, EMITTER_ADDRESS1, EMITTER_ADDRESS2, EMITTER_EVENT_TOPICS, + EMITTER_FUNCTIONS, LOCAL_RPC_URL, USDC_ADDRESS, } from './constants' -import { Extensions } from '@0xsequence/wallet-primitives' -import { ExplicitSessionConfig } from '../../wdk/src/sequence/types/sessions.js' - const { PermissionBuilder, ERC20PermissionBuilder } = Utils function randomAddress(): Address.Address { @@ -34,6 +31,14 @@ const ALL_EXTENSIONS = [ name: 'Rc3', ...Extensions.Rc3, }, + { + name: 'Rc4', + ...Extensions.Rc4, + }, + { + name: 'Rc5', + ...Extensions.Rc5, + }, ] // Handle the increment call being first or last depending on the session manager version @@ -561,7 +566,7 @@ for (const extension of ALL_EXTENSIONS) { } // Sign the transaction - expect(sessionManager.signSapient(wallet.address, chainId, payload, imageHash)).rejects.toThrow( + await expect(sessionManager.signSapient(wallet.address, chainId, payload, imageHash)).rejects.toThrow( `Signer supporting call is expired: ${explicitSigner.address}`, ) }, @@ -601,10 +606,26 @@ for (const extension of ALL_EXTENSIONS) { transaction: { to: Address.Address; data: Hex.Hex }, expectedEventTopic?: Hex.Hex, ) => { + // Generate and use a random sender address to prevent race conditions + const senderAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: Secp256k1.randomPrivateKey() })) + await provider.request({ + method: 'anvil_setBalance', + params: [senderAddress, Hex.fromNumber(1000000000000000000n)], + }) + await provider.request({ + method: 'anvil_impersonateAccount', + params: [senderAddress], + }) + console.log('Simulating transaction', transaction) const txHash = await provider.request({ method: 'eth_sendTransaction', - params: [transaction], + params: [ + { + ...transaction, + from: senderAddress, + }, + ], }) console.log('Transaction hash:', txHash) @@ -710,7 +731,7 @@ for (const extension of ALL_EXTENSIONS) { ) it( - 'signs a payload using an explicit session', + 'signs a payload using an explicit session with allowAll and value limit', async () => { const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) const chainId = Number(await provider.request({ method: 'eth_chainId' })) @@ -783,7 +804,7 @@ for (const extension of ALL_EXTENSIONS) { ) it( - 'signs a payload using an explicit session', + 'signs using explicit session with onlyOnce, consumes usage and rejects second call', async () => { const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) const chainId = Number(await provider.request({ method: 'eth_chainId' })) @@ -1336,5 +1357,347 @@ for (const extension of ALL_EXTENSIONS) { }, timeout, ) + + it( + 'two explicit sessions with same value limit: exhaust first then second send uses second session (increment from non-increment calls only)', + async () => { + const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) + const chainId = Number(await provider.request({ method: 'eth_chainId' })) + + const identityPrivateKey = Secp256k1.randomPrivateKey() + const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) + const stateProvider = new State.Local.Provider() + + const targetAddress = randomAddress() + const valueLimit = 500000000000000000n // 0.5 ETH per session + + const sessionPermission: ExplicitSessionConfig = { + chainId, + valueLimit, + deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), + permissions: [PermissionBuilder.for(targetAddress).allowAll().build()], + } + + const explicitPrivateKey1 = Secp256k1.randomPrivateKey() + const explicitSigner1 = new Signers.Session.Explicit(explicitPrivateKey1, { + ...sessionPermission, + }) + + const explicitPrivateKey2 = Secp256k1.randomPrivateKey() + const explicitSigner2 = new Signers.Session.Explicit(explicitPrivateKey2, { + ...sessionPermission, + }) + + let sessionTopology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { + ...sessionPermission, + signer: explicitSigner1.address, + }) + sessionTopology = SessionConfig.addExplicitSession(sessionTopology, { + ...sessionPermission, + signer: explicitSigner2.address, + }) + await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) + const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) + + const wallet = await Wallet.fromConfiguration( + { + threshold: 1n, + checkpoint: 0n, + topology: [{ type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, Hex.random(32)], + }, + { stateProvider }, + ) + // Fund wallet with 2 ETH so we can send 0.5 ETH twice (each tx needs value + gas) + await provider.request({ + method: 'anvil_setBalance', + params: [wallet.address, Hex.fromNumber(2n * 1000000000000000000n)], + }) + + const sessionManager = new Signers.SessionManager(wallet, { + provider, + sessionManagerAddress: extension.sessions, + explicitSigners: [explicitSigner1, explicitSigner2], + }) + + const call: Payload.Call = { + to: targetAddress, + value: valueLimit, // one full limit + data: '0x' as Hex.Hex, + gasLimit: 0n, + delegateCall: false, + onlyFallback: false, + behaviorOnError: 'revert', + } + + // First send: uses first session (exhausts it) + const increment1 = await sessionManager.prepareIncrement(wallet.address, chainId, [call]) + expect(increment1).not.toBeNull() + const calls1 = includeIncrement([call], increment1!, extension.sessions) + const tx1 = await buildAndSignCall(wallet, sessionManager, calls1, provider, chainId) + await simulateTransaction(provider, tx1) + + // Second send: same call. First session is exhausted so findSignersForCalls picks second session. + // Payload is [call, increment] (or [increment, call]). signSapient must derive expectedIncrement + // from non-increment calls only so it matches the increment we built for the second session. + const increment2 = await sessionManager.prepareIncrement(wallet.address, chainId, [call]) + expect(increment2).not.toBeNull() + const calls2 = includeIncrement([call], increment2!, extension.sessions) + const tx2 = await buildAndSignCall(wallet, sessionManager, calls2, provider, chainId) + await simulateTransaction(provider, tx2) + }, + timeout, + ) + + describe('increment built from non-increment calls only', () => { + it( + 'prepareIncrement returns null when calls contain only an increment call (no non-increment calls)', + async () => { + const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) + const chainId = Number(await provider.request({ method: 'eth_chainId' })) + + const identityPrivateKey = Secp256k1.randomPrivateKey() + const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) + const stateProvider = new State.Local.Provider() + + const sessionPermission: ExplicitSessionConfig = { + chainId, + valueLimit: 0n, + deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), + permissions: [PermissionBuilder.for(EMITTER_ADDRESS1).allowAll().build()], + } + const explicitSigner = new Signers.Session.Explicit(Secp256k1.randomPrivateKey(), sessionPermission) + const sessionTopology = SessionConfig.addExplicitSession( + SessionConfig.emptySessionsTopology(identityAddress), + { + ...sessionPermission, + signer: explicitSigner.address, + }, + ) + await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) + const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) + + const wallet = await Wallet.fromConfiguration( + { + threshold: 1n, + checkpoint: 0n, + topology: [ + { type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, + Hex.random(32), + ], + }, + { stateProvider }, + ) + const sessionManager = new Signers.SessionManager(wallet, { + provider, + sessionManagerAddress: extension.sessions, + explicitSigners: [explicitSigner], + }) + + // Only an increment call (no non-increment calls). Contract would reject payload with only self-call; we return null. + const incrementOnlyCall: Payload.Call = { + to: extension.sessions, + data: AbiFunction.encodeData(Constants.INCREMENT_USAGE_LIMIT, [[]]), + value: 0n, + gasLimit: 0n, + delegateCall: false, + onlyFallback: false, + behaviorOnError: 'revert', + } + const result = await sessionManager.prepareIncrement(wallet.address, chainId, [incrementOnlyCall]) + expect(result).toBeNull() + }, + timeout, + ) + + it( + 'prepareIncrement([increment, nonIncrementCall]) produces same increment data as prepareIncrement([nonIncrementCall])', + async () => { + const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) + const chainId = Number(await provider.request({ method: 'eth_chainId' })) + + const identityPrivateKey = Secp256k1.randomPrivateKey() + const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) + const stateProvider = new State.Local.Provider() + + const sessionPermission: ExplicitSessionConfig = { + chainId, + valueLimit: 0n, + deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), + permissions: [PermissionBuilder.for(EMITTER_ADDRESS1).forFunction(EMITTER_FUNCTIONS[0]).onlyOnce().build()], + } + const explicitSigner = new Signers.Session.Explicit(Secp256k1.randomPrivateKey(), sessionPermission) + const sessionTopology = SessionConfig.addExplicitSession( + SessionConfig.emptySessionsTopology(identityAddress), + { + ...sessionPermission, + signer: explicitSigner.address, + }, + ) + await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) + const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) + + const wallet = await Wallet.fromConfiguration( + { + threshold: 1n, + checkpoint: 0n, + topology: [ + { type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, + Hex.random(32), + ], + }, + { stateProvider }, + ) + const sessionManager = new Signers.SessionManager(wallet, { + provider, + sessionManagerAddress: extension.sessions, + explicitSigners: [explicitSigner], + }) + + const nonIncrementCall: Payload.Call = { + to: EMITTER_ADDRESS1, + value: 0n, + data: AbiFunction.encodeData(EMITTER_FUNCTIONS[0]), + gasLimit: 0n, + delegateCall: false, + onlyFallback: false, + behaviorOnError: 'revert', + } + + const fromNonIncrementOnly = await sessionManager.prepareIncrement(wallet.address, chainId, [ + nonIncrementCall, + ]) + expect(fromNonIncrementOnly).not.toBeNull() + + // Pass [staleIncrement, nonIncrementCall] — increment is ignored when building; result should match + const withStaleIncrement = await sessionManager.prepareIncrement(wallet.address, chainId, [ + fromNonIncrementOnly!, + nonIncrementCall, + ]) + expect(withStaleIncrement).not.toBeNull() + expect(withStaleIncrement!.data).toEqual(fromNonIncrementOnly!.data) + }, + timeout, + ) + + it( + 'payload with implicit and explicit: increment built only from explicit non-increment call', + async () => { + const provider = Provider.from(RpcTransport.fromHttp(LOCAL_RPC_URL)) + const chainId = Number(await provider.request({ method: 'eth_chainId' })) + + const identityPrivateKey = Secp256k1.randomPrivateKey() + const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) + const stateProvider = new State.Local.Provider() + + const redirectUrl = 'https://example.com' + const implicitSigner = await createImplicitSigner(redirectUrl, identityPrivateKey) + const sessionPermission: ExplicitSessionConfig = { + chainId, + valueLimit: 0n, + deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), + permissions: [PermissionBuilder.for(EMITTER_ADDRESS1).forFunction(EMITTER_FUNCTIONS[0]).onlyOnce().build()], + } + const explicitSigner = new Signers.Session.Explicit(Secp256k1.randomPrivateKey(), sessionPermission) + + // Topology: identity + blacklist (implicit) + explicit session + let sessionTopology = SessionConfig.emptySessionsTopology(identityAddress) + sessionTopology = SessionConfig.addExplicitSession(sessionTopology, { + ...sessionPermission, + signer: explicitSigner.address, + }) + await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) + const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) + + const wallet = await Wallet.fromConfiguration( + { + threshold: 1n, + checkpoint: 0n, + topology: [ + { type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, + Hex.random(32), + ], + }, + { stateProvider }, + ) + const sessionManager = new Signers.SessionManager(wallet, { + provider, + sessionManagerAddress: extension.sessions, + implicitSigners: [implicitSigner], + explicitSigners: [explicitSigner], + }) + + // Explicit call only (onlyOnce produces an increment; implicit does not match this target) + const call: Payload.Call = { + to: EMITTER_ADDRESS1, + value: 0n, + data: AbiFunction.encodeData(EMITTER_FUNCTIONS[0]), + gasLimit: 0n, + delegateCall: false, + onlyFallback: false, + behaviorOnError: 'revert', + } + const increment = await sessionManager.prepareIncrement(wallet.address, chainId, [call]) + expect(increment).not.toBeNull() + const calls = includeIncrement([call], increment!, extension.sessions) + const transaction = await buildAndSignCall(wallet, sessionManager, calls, provider, chainId) + await simulateTransaction(provider, transaction, EMITTER_EVENT_TOPICS[0]) + }, + timeout, + ) + + it('signSapient handles selector-only self increment call consistently', async () => { + const identityPrivateKey = Secp256k1.randomPrivateKey() + const identityAddress = Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey: identityPrivateKey })) + const stateProvider = new State.Local.Provider() + + const explicitSigner = new Signers.Session.Explicit(Secp256k1.randomPrivateKey(), { + chainId: 0, + valueLimit: 0n, + deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), + permissions: [PermissionBuilder.for(EMITTER_ADDRESS1).allowAll().build()], + }) + + const sessionTopology = SessionConfig.addExplicitSession(SessionConfig.emptySessionsTopology(identityAddress), { + ...explicitSigner.sessionPermissions, + signer: explicitSigner.address, + }) + await stateProvider.saveTree(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) + const imageHash = GenericTree.hash(SessionConfig.sessionsTopologyToConfigurationTree(sessionTopology)) + + const wallet = await Wallet.fromConfiguration( + { + threshold: 1n, + checkpoint: 0n, + topology: [{ type: 'sapient-signer', address: extension.sessions, weight: 1n, imageHash }, Hex.random(32)], + }, + { stateProvider }, + ) + const sessionManager = new Signers.SessionManager(wallet, { + sessionManagerAddress: extension.sessions, + explicitSigners: [explicitSigner], + }) + + const payload: Payload.Parented = { + type: 'call', + nonce: 0n, + space: 0n, + calls: [ + { + to: extension.sessions, + data: AbiFunction.getSelector(Constants.INCREMENT_USAGE_LIMIT), + value: 0n, + gasLimit: 0n, + delegateCall: false, + onlyFallback: false, + behaviorOnError: 'revert', + }, + ], + parentWallets: [wallet.address], + } + + const signature = await sessionManager.signSapient(wallet.address, 0, payload, imageHash) + expect(signature.type).toBe('sapient') + }) + }) }) } diff --git a/packages/wallet/core/test/signers-pk.test.ts b/packages/wallet/core/test/signers-pk.test.ts index 4121ffb68..7a696cf6c 100644 --- a/packages/wallet/core/test/signers-pk.test.ts +++ b/packages/wallet/core/test/signers-pk.test.ts @@ -143,7 +143,7 @@ describe('Private Key Signers', () => { await pk.witness(mockStateWriter, testWallet) expect(mockStateWriter.saveWitnesses).toHaveBeenCalledTimes(1) - const [wallet, chainId, payload, witness] = vi.mocked(mockStateWriter.saveWitnesses).mock.calls[0] + const [wallet, chainId, _payload, witness] = vi.mocked(mockStateWriter.saveWitnesses).mock.calls[0] expect(wallet).toBe(testWallet) expect(chainId).toBe(0) diff --git a/packages/wallet/core/test/signers-session-implicit.test.ts b/packages/wallet/core/test/signers-session-implicit.test.ts index ed66a50af..5b0a37082 100644 --- a/packages/wallet/core/test/signers-session-implicit.test.ts +++ b/packages/wallet/core/test/signers-session-implicit.test.ts @@ -236,7 +236,6 @@ describe('Implicit Session', () => { }, } const identitySignature = createValidIdentitySignature(attestation) - const topology = createValidTopology() // This should throw an error during construction due to future issued time expect(() => { @@ -250,7 +249,6 @@ describe('Implicit Session', () => { approvedSigner: randomAddress(), // Different approved signer } const identitySignature = createValidIdentitySignature(attestation) - const topology = createValidTopology() // This should throw an error during construction due to mismatched approved signer expect(() => { diff --git a/packages/wallet/core/test/state/arweave/arweave.test.ts b/packages/wallet/core/test/state/arweave/arweave.test.ts new file mode 100644 index 000000000..8c7aa5cd2 --- /dev/null +++ b/packages/wallet/core/test/state/arweave/arweave.test.ts @@ -0,0 +1,230 @@ +import { existsSync, readFileSync, writeFileSync } from 'node:fs' +import { Address } from 'ox' +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' + +import { Arweave, Reader, Sequence } from '../../../src/state/index' + +const TEST_TIMEOUT_MS = 20_000 +const RECORDING_FILE = new URL('./recording', import.meta.url) + +type RecordedRequest = { + method: string + url: string + headers: Record + body: string +} + +type RecordedResponse = { + status: number + statusText: string + headers: Record + body: string +} + +type RecordingEntry = { + request: RecordedRequest + response: RecordedResponse +} + +const tests: { [method in keyof Reader]: { [description: string]: Parameters } } = { + getConfiguration: { + 'image hash: 0xfd32e01d7e814292f49f57e79722ca66423833acf8f25eba770faf3483ff3e78': [ + '0xfd32e01d7e814292f49f57e79722ca66423833acf8f25eba770faf3483ff3e78', + ], + }, + getDeploy: { + 'wallet: 0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE': ['0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE'], + }, + getWallets: { + 'signer: 0x94835215CaA1aD3E304F9A7E2148623fe661dEB7': ['0x94835215CaA1aD3E304F9A7E2148623fe661dEB7'], + }, + getWalletsForSapient: { + 'signer: 0x000000000000AB36D17eB1150116371520565205, image hash: 0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3': + [ + '0x000000000000AB36D17eB1150116371520565205', + '0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3', + ], + }, + getWitnessFor: { + 'wallet: 0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE, signer: 0x94835215CaA1aD3E304F9A7E2148623fe661dEB7': [ + '0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE', + '0x94835215CaA1aD3E304F9A7E2148623fe661dEB7', + ], + }, + getWitnessForSapient: { + 'wallet: 0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE, signer: 0x000000000000AB36D17eB1150116371520565205, image hash: 0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3': + [ + '0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE', + '0x000000000000AB36D17eB1150116371520565205', + '0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3', + ], + }, + getConfigurationUpdates: { + 'wallet: 0x135769a58639b4Fa7d779a9df9B57A706FBCa816, from: 0xaa14aff91091e94d7521625ab1c713273e86a8c21a0afb6cee35be28af47738a': + [ + '0x135769a58639b4Fa7d779a9df9B57A706FBCa816', + '0xaa14aff91091e94d7521625ab1c713273e86a8c21a0afb6cee35be28af47738a', + ], + }, + getTree: { + 'image hash: 0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3': [ + '0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3', + ], + }, + getPayload: { + 'calls payload: 0xc78f3951686b7f16f39e25aea1fd5acc0e2177083c170b4c962be6cd45630576': [ + '0xc78f3951686b7f16f39e25aea1fd5acc0e2177083c170b4c962be6cd45630576', + ], + 'message payload: 0x3a841ba3163a7a19cd168373df1144d38130b2f46b8d6eac956127f06fffe4f4': [ + '0x3a841ba3163a7a19cd168373df1144d38130b2f46b8d6eac956127f06fffe4f4', + ], + 'config update payload: 0xcae631660ffa90bddc5e9b4fa9c11692a53062a61640fb958f3f2959d22fe54b': [ + '0xcae631660ffa90bddc5e9b4fa9c11692a53062a61640fb958f3f2959d22fe54b', + ], + 'digest payload: 0xcd3c291e0939f029aaa4b4f292d5d2b2ce43baf98046d9abc2a3e8284b253432': [ + '0xcd3c291e0939f029aaa4b4f292d5d2b2ce43baf98046d9abc2a3e8284b253432', + ], + }, +} + +function normalize(value: any): any { + switch (typeof value) { + case 'string': + if (Address.validate(value)) { + return Address.checksum(value) + } + + break + + case 'object': + if (value === null) { + return value + } + + if (Array.isArray(value)) { + return value.map(normalize) + } + + return Object.fromEntries( + Object.entries(value) + .filter(([, value]) => value !== undefined) + .map(([key, value]) => [Address.validate(key) ? Address.checksum(key) : key, normalize(value)]), + ) + } + + return value +} + +function normalizeHeaders(headers: Headers): Record { + return Object.fromEntries([...headers.entries()].sort(([left], [right]) => left.localeCompare(right))) +} + +async function serializeRequest(input: RequestInfo | URL, init?: RequestInit): Promise { + const request = new Request(input, init) + + return { + method: request.method.toUpperCase(), + url: request.url, + headers: normalizeHeaders(request.headers), + body: request.method === 'GET' || request.method === 'HEAD' ? '' : await request.clone().text(), + } +} + +function serializeResponse(response: Response, body: string): RecordedResponse { + return { + status: response.status, + statusText: response.statusText, + headers: normalizeHeaders(response.headers), + body, + } +} + +function requestKey(request: RecordedRequest): string { + return JSON.stringify(request) +} + +describe('Arweave state reader', () => { + let arweave: Arweave.Reader + let sequence: Sequence.Provider + let originalFetch: typeof globalThis.fetch | undefined + + beforeAll(() => { + originalFetch = globalThis.fetch + if (!originalFetch) { + throw new Error('fetch is not available') + } + + if (existsSync(RECORDING_FILE)) { + const entries = JSON.parse(readFileSync(RECORDING_FILE, 'utf8')) as RecordingEntry[] + const responsesByRequest = new Map() + + for (const entry of entries) { + const key = requestKey(entry.request) + const responses = responsesByRequest.get(key) + + if (responses) { + responses.push(entry.response) + } else { + responsesByRequest.set(key, [entry.response]) + } + } + + globalThis.fetch = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => { + const request = await serializeRequest(input, init) + const response = responsesByRequest.get(requestKey(request))?.shift() + + if (!response) { + throw new Error(`no recorded response for request ${JSON.stringify(request, null, 2)}`) + } + + return new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers: response.headers, + }) + }) as typeof fetch + } else { + const entries: RecordingEntry[] = [] + + globalThis.fetch = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => { + const request = await serializeRequest(input, init) + const response = await originalFetch!(input, init) + const body = await response.clone().text() + + entries.push({ request, response: serializeResponse(response, body) }) + writeFileSync(RECORDING_FILE, JSON.stringify(entries, null, 2)) + + return response + }) as typeof fetch + } + + arweave = new Arweave.Reader() + sequence = new Sequence.Provider() + }) + + afterAll(() => { + if (originalFetch) { + globalThis.fetch = originalFetch + } + }) + + const methods = Object.entries(tests).filter(([, methodTests]) => Object.keys(methodTests).length > 0) + if (methods.length === 0) { + it.skip('no configured test cases', () => {}) + } + + for (const [method, methodTests] of methods) { + describe(method, () => { + for (const [description, args] of Object.entries(methodTests)) { + it( + description, + async () => { + const [actual, expected] = await Promise.all([arweave[method](...args), sequence[method](...args)]) + expect(normalize(actual)).toEqual(normalize(expected)) + }, + TEST_TIMEOUT_MS, + ) + } + }) + } +}) diff --git a/packages/wallet/core/test/state/arweave/recording b/packages/wallet/core/test/state/arweave/recording new file mode 100644 index 000000000..f40e18f2e --- /dev/null +++ b/packages/wallet/core/test/state/arweave/recording @@ -0,0 +1,2455 @@ +[ + { + "request": { + "method": "POST", + "url": "https://keymachine.sequence.app/rpc/Sessions/Config", + "headers": { + "content-type": "application/json", + "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" + }, + "body": "{\"imageHash\":\"0xfd32e01d7e814292f49f57e79722ca66423833acf8f25eba770faf3483ff3e78\"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "alt-svc": "h3=\":443\"; ma=86400", + "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb2e8831a2cf-YUL", + "connection": "keep-alive", + "content-length": "888", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:24 GMT", + "expires": "Thu, 01 Jan 1970 00:00:00 GMT", + "pragma": "no-cache", + "server": "cloudflare", + "strict-transport-security": "max-age=2592000; includeSubDomains", + "vary": "Origin", + "via": "1.1 google", + "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" + }, + "body": "{\"version\":3,\"config\":{\"checkpoint\":\"0\",\"threshold\":2,\"tree\":[[[{\"address\":\"0x94835215CaA1aD3E304F9A7E2148623fe661dEB7\",\"weight\":1},{\"address\":\"0xCD8B2b62B2EBE631C54ED0CC6F366794da6fF921\",\"weight\":1}],{\"threshold\":1,\"tree\":[{\"address\":\"0x26f3D30F41FA897309Ae804A2AFf15CEb1dA5742\",\"weight\":1},{\"address\":\"0x007a47e6BF40C1e0ed5c01aE42fDC75879140bc4\",\"weight\":1}],\"weight\":1}],[{\"threshold\":2,\"tree\":[{\"address\":\"0x00000000000030Bcc832F7d657f50D6Be35C92b3\",\"imageHash\":\"0x812f1270473f4e1def6b6a6a8c63a029f7daa9813665c9987b91d5f495e8cd87\",\"weight\":1},{\"threshold\":1,\"tree\":[{\"address\":\"0xF6Bc87F5F2edAdb66737E32D37b46423901dfEF1\",\"weight\":1},{\"address\":\"0x007a47e6BF40C1e0ed5c01aE42fDC75879140bc4\",\"weight\":1}],\"weight\":1}],\"weight\":255},{\"address\":\"0x000000000000AB36D17eB1150116371520565205\",\"imageHash\":\"0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3\",\"weight\":255}]]}}" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config\\\"] }, { name: \\\"Sequence-Sessions-Config\\\", values: [\\\"0xfd32e01d7e814292f49f57e79722ca66423833acf8f25eba770faf3483ff3e78\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb2ef8f75a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:24 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=5,cfOrigin;dur=450", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "kpbR2IS45A+6Ufh8O1Qh02tddaebPFaIhBE+3wUVbVfW", + "x-77-nzt-ray": "331b5e0fe1a2ac959bd7df69036d5039", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk0NTk3LCJ3VE1XLVpndWhxQzgyZGNpek5jM0ctNWF0anlOMjJ4UkxfOW8waTNHX2swIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"wTMW-ZguhqC82dcizNc3G-5atjyN22xRL_9o0i3G_k0\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Config\",\"value\":\"0xfd32e01d7e814292f49f57e79722ca66423833acf8f25eba770faf3483ff3e78\"},{\"name\":\"Sequence-Sessions-Version\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Signers-Count\",\"value\":\"7\"},{\"name\":\"Sequence-Sessions-Signers-Bloom\",\"value\":\"0x0000008008000000802084048200000180000021400280141002000500020421\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/wTMW-ZguhqC82dcizNc3G-5atjyN22xRL_9o0i3G_k0", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:SnWk69XgOgnlTPEDUhZ6TNgWTJCmBUiMvvNY/xaSOxQ=:", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:24 GMT", + "sequence-sessions-complete": "true", + "sequence-sessions-config": "0xfd32e01d7e814292f49f57e79722ca66423833acf8f25eba770faf3483ff3e78", + "sequence-sessions-major-version": "1", + "sequence-sessions-minor-version": "0", + "sequence-sessions-signers-bloom": "0x0000008008000000802084048200000180000021400280141002000500020421", + "sequence-sessions-signers-count": "7", + "sequence-sessions-type": "config", + "sequence-sessions-version": "3", + "server": "CDN77-Turbo", + "signature": "comm-d6gtbzsyiobir2md4otysq5na7mzvhkjbgdadaxlgww=:/bnk/h4oAlnZ9L4ktaWXBfj7HuyUMt6ShWgm14KRw54=:, comm-wtmw-zguhqc82dciznc3g-5atjyn22xrl_9o0i3g_k0=:0nVpmzPqHnqRks1pepdX3cbGNhqiPS6Ol/kEVmluwqYFSdXi03u6JM/dsj+O7z45yu1mJoZqr0xAdi15BEE3Exw=:", + "signature-input": "comm-d6gtbzsyiobir2md4otysq5na7mzvhkjbgdadaxlgww=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-wtmw-zguhqc82dciznc3g-5atjyn22xrl_9o0i3g_k0=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmln, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:MA, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Config:MHhmZDMyZTAxZDdlODE0MjkyZjQ5ZjU3ZTc5NzIyY2E2NjQyMzgzM2FjZjhmMjVlYmE3NzBmYWYzNDgzZmYzZTc4, 6:Sequence-Sessions-Version:Mw, 7:Sequence-Sessions-Complete:dHJ1ZQ, 8:Sequence-Sessions-Signers-Count:Nw, 9:Sequence-Sessions-Signers-Bloom:MHgwMDAwMDA4MDA4MDAwMDAwODAyMDg0MDQ4MjAwMDAwMTgwMDAwMDIxNDAwMjgwMTQxMDAyMDAwNTAwMDIwNDIx\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "knpw3SVX5Mh6YEaxRsshOVYzMudH3if+g0tVslJtIwUt8A24pg", + "x-77-nzt-ray": "8705ec3425f5520e9cd7df696bdc2d25", + "x-77-pop": "newyorkUSNY" + }, + "body": "{\"checkpoint\":\"0\",\"threshold\":2,\"tree\":[[[{\"address\":\"0x94835215CaA1aD3E304F9A7E2148623fe661dEB7\",\"weight\":1},{\"address\":\"0xCD8B2b62B2EBE631C54ED0CC6F366794da6fF921\",\"weight\":1}],{\"threshold\":1,\"tree\":[{\"address\":\"0x26f3D30F41FA897309Ae804A2AFf15CEb1dA5742\",\"weight\":1},{\"address\":\"0x007a47e6BF40C1e0ed5c01aE42fDC75879140bc4\",\"weight\":1}],\"weight\":1}],[{\"threshold\":2,\"tree\":[{\"address\":\"0x00000000000030Bcc832F7d657f50D6Be35C92b3\",\"imageHash\":\"0x812f1270473f4e1def6b6a6a8c63a029f7daa9813665c9987b91d5f495e8cd87\",\"weight\":1},{\"threshold\":1,\"tree\":[{\"address\":\"0xF6Bc87F5F2edAdb66737E32D37b46423901dfEF1\",\"weight\":1},{\"address\":\"0x007a47e6BF40C1e0ed5c01aE42fDC75879140bc4\",\"weight\":1}],\"weight\":1}],\"weight\":255},{\"address\":\"0x000000000000AB36D17eB1150116371520565205\",\"imageHash\":\"0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3\",\"weight\":255}]]}" + } + }, + { + "request": { + "method": "POST", + "url": "https://keymachine.sequence.app/rpc/Sessions/DeployHash", + "headers": { + "content-type": "application/json", + "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" + }, + "body": "{\"wallet\":\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "alt-svc": "h3=\":443\"; ma=86400", + "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb3378b3a2cf-YUL", + "connection": "keep-alive", + "content-length": "467", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:24 GMT", + "expires": "Thu, 01 Jan 1970 00:00:00 GMT", + "pragma": "no-cache", + "server": "cloudflare", + "strict-transport-security": "max-age=2592000; includeSubDomains", + "vary": "Origin", + "via": "1.1 google", + "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" + }, + "body": "{\"deployHash\":\"0xfd32e01d7e814292f49f57e79722ca66423833acf8f25eba770faf3483ff3e78\",\"context\":{\"factory\":\"0x00000000000018A77519fcCCa060c2537c9D6d3F\",\"guestModule\":\"0x0000000000006Ac72ed1d192fa28f0058D3F8806\",\"mainModule\":\"0x0000000000001f3C39d61698ab21131a12134454\",\"mainModuleUpgradable\":\"0xD0ae8eF93b7DA4eabb32Ec4d81b7a501DCa04D4C\",\"version\":3,\"walletCreationCode\":\"0x6041600e3d396021805130553df33d3d36153402601f57363d3d373d363d30545af43d82803e903d91601f57fd5bf3\"}}" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 1, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"wallet\\\"] }, { name: \\\"Sequence-Sessions-Wallet\\\", values: [\\\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb339c615a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:25 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=8,cfOrigin;dur=348", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "kt/gjX4CVlM2ee69NnlYVC8osIhfZGiPqYCvAJSX1XIV", + "x-77-nzt-ray": "331b5e0fe1a2ac959cd7df69011f0f2b", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":true},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk0NjE3LCItVGVjZW5JSUNIclN5ckJPMXd2VDlReTk4bXFTWms4VnZ0NkN0X0tJYlhNIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"-TecenIICHrSyrBO1wvT9Qy98mqSZk8Vvt6Ct_KIbXM\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"wallet\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\"},{\"name\":\"Sequence-Sessions-Deploy-Config\",\"value\":\"0xfd32e01d7e814292f49f57e79722ca66423833acf8f25eba770faf3483ff3e78\"},{\"name\":\"Sequence-Sessions-Deploy-Version\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Deploy-Config-Attached\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Deploy-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Deploy-Signers-Count\",\"value\":\"7\"},{\"name\":\"Sequence-Sessions-Deploy-Signers-Bloom\",\"value\":\"0x0000008008000000802084048200000180000021400280141002000500020421\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-Context-Factory\",\"value\":\"0x00000000000018A77519fcCCa060c2537c9D6d3F\"},{\"name\":\"Sequence-Sessions-Context-Stage-1\",\"value\":\"0x0000000000001f3C39d61698ab21131a12134454\"},{\"name\":\"Sequence-Sessions-Context-Stage-2\",\"value\":\"0xD0ae8eF93b7DA4eabb32Ec4d81b7a501DCa04D4C\"},{\"name\":\"Sequence-Sessions-Context-Guest\",\"value\":\"0x0000000000006Ac72ed1d192fa28f0058D3F8806\"},{\"name\":\"Sequence-Sessions-Context-Creation-Code\",\"value\":\"0x6041600e3d396021805130553df33d3d36153402601f57363d3d373d363d30545af43d82803e903d91601f57fd5bf3\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/-TecenIICHrSyrBO1wvT9Qy98mqSZk8Vvt6Ct_KIbXM", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:SnWk69XgOgnlTPEDUhZ6TNgWTJCmBUiMvvNY/xaSOxQ=:", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:25 GMT", + "sequence-sessions-context-creation-code": "0x6041600e3d396021805130553df33d3d36153402601f57363d3d373d363d30545af43d82803e903d91601f57fd5bf3", + "sequence-sessions-context-factory": "0x00000000000018A77519fcCCa060c2537c9D6d3F", + "sequence-sessions-context-guest": "0x0000000000006Ac72ed1d192fa28f0058D3F8806", + "sequence-sessions-context-stage-1": "0x0000000000001f3C39d61698ab21131a12134454", + "sequence-sessions-context-stage-2": "0xD0ae8eF93b7DA4eabb32Ec4d81b7a501DCa04D4C", + "sequence-sessions-deploy-config": "0xfd32e01d7e814292f49f57e79722ca66423833acf8f25eba770faf3483ff3e78", + "sequence-sessions-deploy-config-attached": "true", + "sequence-sessions-deploy-config-complete": "true", + "sequence-sessions-deploy-signers-bloom": "0x0000008008000000802084048200000180000021400280141002000500020421", + "sequence-sessions-deploy-signers-count": "7", + "sequence-sessions-deploy-version": "3", + "sequence-sessions-major-version": "2", + "sequence-sessions-minor-version": "0", + "sequence-sessions-type": "wallet", + "sequence-sessions-wallet": "0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE", + "server": "CDN77-Turbo", + "signature": "comm--teceniichrsyrbo1wvt9qy98mqszk8vvt6ct_kibxm=:pxoQyhQGtlTMj25OLPFlmoFQD6+qgXzw9Mn/hhq2LUoqwg0JXwvxnnPPnZKa+ftI12jcxWydJUK0G6qYmqNqWxs=:, comm-qxsthx4mhrdeoltmaxgbf66wpeqlm_i5t_3mo9bfuau=:ZwH97MHIMAuwUTjyRuTtrkUiomCQM5+fS3Fj5B4XUZs=:", + "signature-input": "comm--teceniichrsyrbo1wvt9qy98mqszk8vvt6ct_kibxm=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-context-creation-code\" \"sequence-sessions-context-factory\" \"sequence-sessions-context-guest\" \"sequence-sessions-context-stage-1\" \"sequence-sessions-context-stage-2\" \"sequence-sessions-deploy-config\" \"sequence-sessions-deploy-config-attached\" \"sequence-sessions-deploy-config-complete\" \"sequence-sessions-deploy-signers-bloom\" \"sequence-sessions-deploy-signers-count\" \"sequence-sessions-deploy-version\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-type\" \"sequence-sessions-wallet\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:d2FsbGV0, 10:Sequence-Sessions-Deploy-Signers-Bloom:MHgwMDAwMDA4MDA4MDAwMDAwODAyMDg0MDQ4MjAwMDAwMTgwMDAwMDIxNDAwMjgwMTQxMDAyMDAwNTAwMDIwNDIx, 11:Sequence-Sessions-Major-Version:Mg, 12:Sequence-Sessions-Context-Factory:MHgwMDAwMDAwMDAwMDAxOEE3NzUxOWZjQ0NhMDYwYzI1MzdjOUQ2ZDNG, 13:Sequence-Sessions-Context-Stage-1:MHgwMDAwMDAwMDAwMDAxZjNDMzlkNjE2OThhYjIxMTMxYTEyMTM0NDU0, 14:Sequence-Sessions-Context-Stage-2:MHhEMGFlOGVGOTNiN0RBNGVhYmIzMkVjNGQ4MWI3YTUwMURDYTA0RDRD, 15:Sequence-Sessions-Context-Guest:MHgwMDAwMDAwMDAwMDA2QWM3MmVkMWQxOTJmYTI4ZjAwNThEM0Y4ODA2, 16:Sequence-Sessions-Context-Creation-Code:MHg2MDQxNjAwZTNkMzk2MDIxODA1MTMwNTUzZGYzM2QzZDM2MTUzNDAyNjAxZjU3MzYzZDNkMzczZDM2M2QzMDU0NWFmNDNkODI4MDNlOTAzZDkxNjAxZjU3ZmQ1YmYz, 2:Sequence-Sessions-Minor-Version:MA, 3:Content-Type:YXBwbGljYXRpb24vanNvbg, 4:Sequence-Sessions-Wallet:MHg0N0UwZTQ0REU2NDlCMzVDZjc4NjM5OThCZTZDNWE3RDVkOGM2M2JF, 5:Sequence-Sessions-Deploy-Config:MHhmZDMyZTAxZDdlODE0MjkyZjQ5ZjU3ZTc5NzIyY2E2NjQyMzgzM2FjZjhmMjVlYmE3NzBmYWYzNDgzZmYzZTc4, 6:Sequence-Sessions-Deploy-Version:Mw, 7:Sequence-Sessions-Deploy-Config-Attached:dHJ1ZQ, 8:Sequence-Sessions-Deploy-Config-Complete:dHJ1ZQ, 9:Sequence-Sessions-Deploy-Signers-Count:Nw\", comm-qxsthx4mhrdeoltmaxgbf66wpeqlm_i5t_3mo9bfuau=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-context-creation-code\" \"sequence-sessions-context-factory\" \"sequence-sessions-context-guest\" \"sequence-sessions-context-stage-1\" \"sequence-sessions-context-stage-2\" \"sequence-sessions-deploy-config\" \"sequence-sessions-deploy-config-attached\" \"sequence-sessions-deploy-config-complete\" \"sequence-sessions-deploy-signers-bloom\" \"sequence-sessions-deploy-signers-count\" \"sequence-sessions-deploy-version\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-type\" \"sequence-sessions-wallet\");alg=\"hmac-sha256\";keyid=\"constant:ao\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "kkAzyuQWwUzYj0WpINtvDJmekDtWJOum72Ty4sJb/fWjilgW3w", + "x-77-nzt-ray": "3f9f132567ed7bbc9dd7df69cbb53410", + "x-77-pop": "newyorkUSNY" + }, + "body": "{\"checkpoint\":\"0\",\"threshold\":2,\"tree\":[[[{\"address\":\"0x94835215CaA1aD3E304F9A7E2148623fe661dEB7\",\"weight\":1},{\"address\":\"0xCD8B2b62B2EBE631C54ED0CC6F366794da6fF921\",\"weight\":1}],{\"threshold\":1,\"tree\":[{\"address\":\"0x26f3D30F41FA897309Ae804A2AFf15CEb1dA5742\",\"weight\":1},{\"address\":\"0x007a47e6BF40C1e0ed5c01aE42fDC75879140bc4\",\"weight\":1}],\"weight\":1}],[{\"threshold\":2,\"tree\":[{\"address\":\"0x00000000000030Bcc832F7d657f50D6Be35C92b3\",\"imageHash\":\"0x812f1270473f4e1def6b6a6a8c63a029f7daa9813665c9987b91d5f495e8cd87\",\"weight\":1},{\"threshold\":1,\"tree\":[{\"address\":\"0xF6Bc87F5F2edAdb66737E32D37b46423901dfEF1\",\"weight\":1},{\"address\":\"0x007a47e6BF40C1e0ed5c01aE42fDC75879140bc4\",\"weight\":1}],\"weight\":1}],\"weight\":255},{\"address\":\"0x000000000000AB36D17eB1150116371520565205\",\"imageHash\":\"0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3\",\"weight\":255}]]}" + } + }, + { + "request": { + "method": "POST", + "url": "https://keymachine.sequence.app/rpc/Sessions/Wallets", + "headers": { + "content-type": "application/json", + "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" + }, + "body": "{\"signer\":\"0x94835215CaA1aD3E304F9A7E2148623fe661dEB7\"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "alt-svc": "h3=\":443\"; ma=86400", + "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb37c830a2cf-YUL", + "connection": "keep-alive", + "content-length": "772", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:25 GMT", + "expires": "Thu, 01 Jan 1970 00:00:00 GMT", + "pragma": "no-cache", + "server": "cloudflare", + "strict-transport-security": "max-age=2592000; includeSubDomains", + "vary": "Origin", + "via": "1.1 google", + "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" + }, + "body": "{\"wallets\":{\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\":{\"digest\":\"\",\"payload\":{\"message\":\"0x7b22616374696f6e223a22636f6e73656e742d746f2d62652d706172742d6f662d77616c6c6574222c2277616c6c6574223a22307834376530653434646536343962333563663738363339393862653663356137643564386336336265222c227369676e6572223a22307839343833353231354361413161443345333034463941374532313438363233666536363164454237222c2274696d657374616d70223a313737353539333838393336372c227369676e65724b696e64223a226c6f67696e2d676f6f676c65227d\",\"type\":\"message\"},\"toImageHash\":\"\",\"chainID\":\"0\",\"type\":\"EIP712\",\"signature\":\"0x8af459fb08d4e7c7b9189ec61aaa74f1b08664e1f415dae09e5ca026bcab63e96c7d2faad656a8c713856857243f3acb4427bf8e20b13b18d6f3474f5de9a0db1c\",\"sapientHash\":\"\",\"validOnBlockHash\":\"\"}},\"cursor\":4309735}" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"signature\\\"] }, { name: \\\"Sequence-Sessions-Signer\\\", values: [\\\"0x94835215CaA1aD3E304F9A7E2148623fe661dEB7\\\"] }, { name: \\\"Sequence-Sessions-Witness\\\", values: [\\\"true\\\"] }, { name: \\\"Sequence-Sessions-Signature-Type\\\", values: [\\\"eip-712\\\", \\\"eth_sign\\\", \\\"erc-1271\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb37ef8d5a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:25 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=9,cfOrigin;dur=251", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "kn3s8DIJvjUDlJ3OTuNzNuSM4/bbBuAW3a93q34v+ryr", + "x-77-nzt-ray": "331b5e0fe1a2ac959dd7df698b4b2418", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk0NjM1LCJVZ3hGMno4MUlzZDA0b3JWVTRtS1FqSjR1R2hXaWpnSmN3SXd2R0ljUEdNIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"UgxF2z81Isd04orVU4mKQjJ4uGhWijgJcwIwvGIcPGM\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"signature\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eip-712\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x94835215CaA1aD3E304F9A7E2148623fe661dEB7\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x1fd2c2afecfd00c28cff02d0608489e46f7222891169774c24e2b568e3e22107\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/UgxF2z81Isd04orVU4mKQjJ4uGhWijgJcwIwvGIcPGM", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:DQMtV30uXskqMMCu16zNMvZyWfa6yEsZvfOQURm4xFU=:", + "content-encoding": "gzip", + "content-type": "text/plain", + "date": "Wed, 15 Apr 2026 18:23:26 GMT", + "sequence-sessions-chain-id": "0", + "sequence-sessions-major-version": "2", + "sequence-sessions-minor-version": "0", + "sequence-sessions-signature-type": "eip-712", + "sequence-sessions-signer": "0x94835215CaA1aD3E304F9A7E2148623fe661dEB7", + "sequence-sessions-subdigest": "0x1fd2c2afecfd00c28cff02d0608489e46f7222891169774c24e2b568e3e22107", + "sequence-sessions-type": "signature", + "sequence-sessions-wallet": "0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE", + "sequence-sessions-witness": "true", + "server": "CDN77-Turbo", + "signature": "comm-mjddmuaq1suixwpqp6yzqijq0jnz-sxkj26f3u3tipw=:TP9EhccyKY5tLhKceOrZYXdXXyqjT3DfJwn6iBwTAVk=:, comm-ugxf2z81isd04orvu4mkqjj4ughwijgjcwiwvgicpgm=:sR5lQIrliBqj6gDxCHx+VOOKwmvqSnQyKpOgtmI2bfV3ROL1TODu9BYbIgtRCXabSSfY8JufjbK/kIMaFxgG1Rw=:", + "signature-input": "comm-mjddmuaq1suixwpqp6yzqijq0jnz-sxkj26f3u3tipw=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-ugxf2z81isd04orvu4mkqjj4ughwijgjcwiwvgicpgm=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:c2lnbmF0dXJl, 10:Sequence-Sessions-Major-Version:Mg, 2:Sequence-Sessions-Minor-Version:MA, 3:Content-Type:dGV4dC9wbGFpbg, 4:Sequence-Sessions-Signature-Type:ZWlwLTcxMg, 5:Sequence-Sessions-Signer:MHg5NDgzNTIxNUNhQTFhRDNFMzA0RjlBN0UyMTQ4NjIzZmU2NjFkRUI3, 6:Sequence-Sessions-Subdigest:MHgxZmQyYzJhZmVjZmQwMGMyOGNmZjAyZDA2MDg0ODllNDZmNzIyMjg5MTE2OTc3NGMyNGUyYjU2OGUzZTIyMTA3, 7:Sequence-Sessions-Wallet:MHg0N0UwZTQ0REU2NDlCMzVDZjc4NjM5OThCZTZDNWE3RDVkOGM2M2JF, 8:Sequence-Sessions-Chain-ID:MA, 9:Sequence-Sessions-Witness:dHJ1ZQ\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "kkQcVZpruA8xMS3x0uudlfeLetOaOl1vUtIgg4zT7+y7NtcUQw", + "x-77-nzt-ray": "f03d0613ac3d59c59dd7df69ba83d131", + "x-77-pop": "newyorkUSNY" + }, + "body": "0x8af459fb08d4e7c7b9189ec61aaa74f1b08664e1f415dae09e5ca026bcab63e96c7d2faad656a8c713856857243f3acb4427bf8e20b13b18d6f3474f5de9a0db1c" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 1, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"payload\\\"] }, { name: \\\"Sequence-Sessions-Major-Version\\\", values: [\\\"1\\\"] }, { name: \\\"Sequence-Sessions-Minor-Version\\\", values: [\\\"2\\\"] }, { name: \\\"Sequence-Sessions-Payload\\\", values: [\\\"0x1fd2c2afecfd00c28cff02d0608489e46f7222891169774c24e2b568e3e22107\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb3bdad15a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:26 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=8,cfOrigin;dur=374", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "klJ2qtcwZnoH8Z2gpSNApegfi+abKtYFw8FFk2M1CpNk", + "x-77-nzt-ray": "331b5e0fe1a2ac959ed7df693deb5502", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":true},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk2ODczLCJacEMxWDZQM2ttTUo2ZVBLRzhyZjVicEN4bjVzSlVnU2FqRGpTYlJ3ZjQ4Il0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"ZpC1X6P3kmMJ6ePKG8rf5bpCxn5sJUgSajDjSbRwf48\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"payload\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"2\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Payload\",\"value\":\"0x1fd2c2afecfd00c28cff02d0608489e46f7222891169774c24e2b568e3e22107\"},{\"name\":\"Sequence-Sessions-Address\",\"value\":\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Payload-Type\",\"value\":\"message\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/ZpC1X6P3kmMJ6ePKG8rf5bpCxn5sJUgSajDjSbRwf48", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:WrFeEzEBLlRPhslU5kXRAZENf+CRW4DvMJbK+3q4fBw=:", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:26 GMT", + "sequence-sessions-address": "0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE", + "sequence-sessions-chain-id": "0", + "sequence-sessions-major-version": "1", + "sequence-sessions-minor-version": "2", + "sequence-sessions-payload": "0x1fd2c2afecfd00c28cff02d0608489e46f7222891169774c24e2b568e3e22107", + "sequence-sessions-payload-type": "message", + "sequence-sessions-type": "payload", + "server": "CDN77-Turbo", + "signature": "comm-z6psoyqzpxcxfsjacq6u2tw2zwr5am0z-qxanycxlxk=:/XWIuXs2TLz6Uq6WLXmvwx3R0MrP0B8Hg/vL2cSg0tw=:, comm-zpc1x6p3kmmj6epkg8rf5bpcxn5sjugsajdjsbrwf48=:FFC2OB/9mHTDSiPM2jaM/W9DndWBvsNcKZGT81tluPk13ODkpIf8XYLaJ90yscVPami5RQscKmBY9OmxfzL3gBs=:", + "signature-input": "comm-z6psoyqzpxcxfsjacq6u2tw2zwr5am0z-qxanycxlxk=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-type\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-zpc1x6p3kmmj6epkg8rf5bpcxn5sjugsajdjsbrwf48=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-type\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:cGF5bG9hZA, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:Mg, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Payload:MHgxZmQyYzJhZmVjZmQwMGMyOGNmZjAyZDA2MDg0ODllNDZmNzIyMjg5MTE2OTc3NGMyNGUyYjU2OGUzZTIyMTA3, 6:Sequence-Sessions-Address:MHg0N0UwZTQ0REU2NDlCMzVDZjc4NjM5OThCZTZDNWE3RDVkOGM2M2JF, 7:Sequence-Sessions-Chain-ID:MA, 8:Sequence-Sessions-Payload-Type:bWVzc2FnZQ\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "klRRFUM66/ntQEiXsPNtmx1THYfl0HFEcIiLg9YiTZPnpccl+Q", + "x-77-nzt-ray": "f03d06133b5c02e39ed7df69da818923", + "x-77-pop": "newyorkUSNY" + }, + "body": "\"0x7b22616374696f6e223a22636f6e73656e742d746f2d62652d706172742d6f662d77616c6c6574222c2277616c6c6574223a22307834376530653434646536343962333563663738363339393862653663356137643564386336336265222c227369676e6572223a22307839343833353231354361413161443345333034463941374532313438363233666536363164454237222c2274696d657374616d70223a313737353539333838393336372c227369676e65724b696e64223a226c6f67696e2d676f6f676c65227d\"" + } + }, + { + "request": { + "method": "POST", + "url": "https://keymachine.sequence.app/rpc/Sessions/Wallets", + "headers": { + "content-type": "application/json", + "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" + }, + "body": "{\"signer\":\"0x000000000000AB36D17eB1150116371520565205\",\"sapientHash\":\"0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3\"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "alt-svc": "h3=\":443\"; ma=86400", + "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb3fad85a2cf-YUL", + "connection": "keep-alive", + "content-length": "25", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:26 GMT", + "expires": "Thu, 01 Jan 1970 00:00:00 GMT", + "pragma": "no-cache", + "server": "cloudflare", + "strict-transport-security": "max-age=2592000; includeSubDomains", + "vary": "Origin", + "via": "1.1 google", + "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" + }, + "body": "{\"wallets\":{},\"cursor\":0}" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"signature\\\"] }, { name: \\\"Sequence-Sessions-Signer\\\", values: [\\\"0x000000000000AB36D17eB1150116371520565205\\\"] }, { name: \\\"Sequence-Sessions-Image-Hash\\\", values: [\\\"0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3\\\"] }, { name: \\\"Sequence-Sessions-Witness\\\", values: [\\\"true\\\"] }, { name: \\\"Sequence-Sessions-Signature-Type\\\", values: [\\\"sapient\\\", \\\"sapient-compact\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb3fce2c5a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:26 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=11,cfOrigin;dur=213", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "khmh6JDIHNcpGLJnUaMbETHny2ujNPAg4nL2VQbfpRJU", + "x-77-nzt-ray": "331b5e0fe1a2ac959ed7df697da8d827", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[]}}}\n" + } + }, + { + "request": { + "method": "POST", + "url": "https://keymachine.sequence.app/rpc/Sessions/Witness", + "headers": { + "content-type": "application/json", + "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" + }, + "body": "{\"signer\":\"0x94835215CaA1aD3E304F9A7E2148623fe661dEB7\",\"wallet\":\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "alt-svc": "h3=\":443\"; ma=86400", + "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb41885ea2cf-YUL", + "connection": "keep-alive", + "content-length": "708", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:27 GMT", + "expires": "Thu, 01 Jan 1970 00:00:00 GMT", + "pragma": "no-cache", + "server": "cloudflare", + "strict-transport-security": "max-age=2592000; includeSubDomains", + "vary": "Origin", + "via": "1.1 google", + "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" + }, + "body": "{\"witness\":{\"digest\":\"\",\"payload\":{\"message\":\"0x7b22616374696f6e223a22636f6e73656e742d746f2d62652d706172742d6f662d77616c6c6574222c2277616c6c6574223a22307834376530653434646536343962333563663738363339393862653663356137643564386336336265222c227369676e6572223a22307839343833353231354361413161443345333034463941374532313438363233666536363164454237222c2274696d657374616d70223a313737353539333838393336372c227369676e65724b696e64223a226c6f67696e2d676f6f676c65227d\",\"type\":\"message\"},\"toImageHash\":\"\",\"chainID\":\"0\",\"type\":\"EIP712\",\"signature\":\"0x8af459fb08d4e7c7b9189ec61aaa74f1b08664e1f415dae09e5ca026bcab63e96c7d2faad656a8c713856857243f3acb4427bf8e20b13b18d6f3474f5de9a0db1c\",\"sapientHash\":\"\",\"validOnBlockHash\":\"\"}}" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"signature\\\"] }, { name: \\\"Sequence-Sessions-Wallet\\\", values: [\\\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\\\"] }, { name: \\\"Sequence-Sessions-Signer\\\", values: [\\\"0x94835215CaA1aD3E304F9A7E2148623fe661dEB7\\\"] }, { name: \\\"Sequence-Sessions-Witness\\\", values: [\\\"true\\\"] }, { name: \\\"Sequence-Sessions-Signature-Type\\\", values: [\\\"eip-712\\\", \\\"eth_sign\\\", \\\"erc-1271\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb41af855a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:27 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=9,cfOrigin;dur=244", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "kmdP+2wrt6KTzTa7i5NS39F0SWlUh55mmkJYE0vTU5j7", + "x-77-nzt-ray": "331b5e0fe1a2ac959ed7df692d4c8d39", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk0NjM1LCJVZ3hGMno4MUlzZDA0b3JWVTRtS1FqSjR1R2hXaWpnSmN3SXd2R0ljUEdNIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"UgxF2z81Isd04orVU4mKQjJ4uGhWijgJcwIwvGIcPGM\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"signature\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eip-712\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x94835215CaA1aD3E304F9A7E2148623fe661dEB7\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x1fd2c2afecfd00c28cff02d0608489e46f7222891169774c24e2b568e3e22107\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/UgxF2z81Isd04orVU4mKQjJ4uGhWijgJcwIwvGIcPGM", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:DQMtV30uXskqMMCu16zNMvZyWfa6yEsZvfOQURm4xFU=:", + "content-encoding": "gzip", + "content-type": "text/plain", + "date": "Wed, 15 Apr 2026 18:23:27 GMT", + "sequence-sessions-chain-id": "0", + "sequence-sessions-major-version": "2", + "sequence-sessions-minor-version": "0", + "sequence-sessions-signature-type": "eip-712", + "sequence-sessions-signer": "0x94835215CaA1aD3E304F9A7E2148623fe661dEB7", + "sequence-sessions-subdigest": "0x1fd2c2afecfd00c28cff02d0608489e46f7222891169774c24e2b568e3e22107", + "sequence-sessions-type": "signature", + "sequence-sessions-wallet": "0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE", + "sequence-sessions-witness": "true", + "server": "CDN77-Turbo", + "signature": "comm-mjddmuaq1suixwpqp6yzqijq0jnz-sxkj26f3u3tipw=:TP9EhccyKY5tLhKceOrZYXdXXyqjT3DfJwn6iBwTAVk=:, comm-ugxf2z81isd04orvu4mkqjj4ughwijgjcwiwvgicpgm=:sR5lQIrliBqj6gDxCHx+VOOKwmvqSnQyKpOgtmI2bfV3ROL1TODu9BYbIgtRCXabSSfY8JufjbK/kIMaFxgG1Rw=:", + "signature-input": "comm-mjddmuaq1suixwpqp6yzqijq0jnz-sxkj26f3u3tipw=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-ugxf2z81isd04orvu4mkqjj4ughwijgjcwiwvgicpgm=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:c2lnbmF0dXJl, 10:Sequence-Sessions-Major-Version:Mg, 2:Sequence-Sessions-Minor-Version:MA, 3:Content-Type:dGV4dC9wbGFpbg, 4:Sequence-Sessions-Signature-Type:ZWlwLTcxMg, 5:Sequence-Sessions-Signer:MHg5NDgzNTIxNUNhQTFhRDNFMzA0RjlBN0UyMTQ4NjIzZmU2NjFkRUI3, 6:Sequence-Sessions-Subdigest:MHgxZmQyYzJhZmVjZmQwMGMyOGNmZjAyZDA2MDg0ODllNDZmNzIyMjg5MTE2OTc3NGMyNGUyYjU2OGUzZTIyMTA3, 7:Sequence-Sessions-Wallet:MHg0N0UwZTQ0REU2NDlCMzVDZjc4NjM5OThCZTZDNWE3RDVkOGM2M2JF, 8:Sequence-Sessions-Chain-ID:MA, 9:Sequence-Sessions-Witness:dHJ1ZQ\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-age": "1", + "x-77-cache": "HIT", + "x-77-nzt": "ktcsB2vBzPXSPSGPD1IQ68FPpsz0JveQuLOk4aaBYBoRC7gHow", + "x-77-nzt-ray": "f03d0613ac3d59c59fd7df692252fe12", + "x-77-pop": "newyorkUSNY" + }, + "body": "0x8af459fb08d4e7c7b9189ec61aaa74f1b08664e1f415dae09e5ca026bcab63e96c7d2faad656a8c713856857243f3acb4427bf8e20b13b18d6f3474f5de9a0db1c" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 1, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"payload\\\"] }, { name: \\\"Sequence-Sessions-Major-Version\\\", values: [\\\"1\\\"] }, { name: \\\"Sequence-Sessions-Minor-Version\\\", values: [\\\"2\\\"] }, { name: \\\"Sequence-Sessions-Payload\\\", values: [\\\"0x1fd2c2afecfd00c28cff02d0608489e46f7222891169774c24e2b568e3e22107\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb3bdad15a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:27 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=8,cfOrigin;dur=374", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "kmDFEymCjaCQYBxiS+RUbDG8nDrbjToz2fktrzleS5uG", + "x-77-nzt-ray": "331b5e0fe1a2ac959fd7df691b96cd15", + "x-77-pop": "montrealCAQC" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":true},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk2ODczLCJacEMxWDZQM2ttTUo2ZVBLRzhyZjVicEN4bjVzSlVnU2FqRGpTYlJ3ZjQ4Il0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"ZpC1X6P3kmMJ6ePKG8rf5bpCxn5sJUgSajDjSbRwf48\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"payload\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"2\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Payload\",\"value\":\"0x1fd2c2afecfd00c28cff02d0608489e46f7222891169774c24e2b568e3e22107\"},{\"name\":\"Sequence-Sessions-Address\",\"value\":\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Payload-Type\",\"value\":\"message\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/ZpC1X6P3kmMJ6ePKG8rf5bpCxn5sJUgSajDjSbRwf48", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:WrFeEzEBLlRPhslU5kXRAZENf+CRW4DvMJbK+3q4fBw=:", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:27 GMT", + "sequence-sessions-address": "0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE", + "sequence-sessions-chain-id": "0", + "sequence-sessions-major-version": "1", + "sequence-sessions-minor-version": "2", + "sequence-sessions-payload": "0x1fd2c2afecfd00c28cff02d0608489e46f7222891169774c24e2b568e3e22107", + "sequence-sessions-payload-type": "message", + "sequence-sessions-type": "payload", + "server": "CDN77-Turbo", + "signature": "comm-z6psoyqzpxcxfsjacq6u2tw2zwr5am0z-qxanycxlxk=:/XWIuXs2TLz6Uq6WLXmvwx3R0MrP0B8Hg/vL2cSg0tw=:, comm-zpc1x6p3kmmj6epkg8rf5bpcxn5sjugsajdjsbrwf48=:FFC2OB/9mHTDSiPM2jaM/W9DndWBvsNcKZGT81tluPk13ODkpIf8XYLaJ90yscVPami5RQscKmBY9OmxfzL3gBs=:", + "signature-input": "comm-z6psoyqzpxcxfsjacq6u2tw2zwr5am0z-qxanycxlxk=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-type\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-zpc1x6p3kmmj6epkg8rf5bpcxn5sjugsajdjsbrwf48=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-type\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:cGF5bG9hZA, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:Mg, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Payload:MHgxZmQyYzJhZmVjZmQwMGMyOGNmZjAyZDA2MDg0ODllNDZmNzIyMjg5MTE2OTc3NGMyNGUyYjU2OGUzZTIyMTA3, 6:Sequence-Sessions-Address:MHg0N0UwZTQ0REU2NDlCMzVDZjc4NjM5OThCZTZDNWE3RDVkOGM2M2JF, 7:Sequence-Sessions-Chain-ID:MA, 8:Sequence-Sessions-Payload-Type:bWVzc2FnZQ\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-age": "1", + "x-77-cache": "HIT", + "x-77-nzt": "kto/Tx3txZyfUHYUjDdHYYatpm/yIhRWCo6zu8XkeGRdMPcm8g", + "x-77-nzt-ray": "f03d06133b5c02e39fd7df691d7c821b", + "x-77-pop": "newyorkUSNY" + }, + "body": "\"0x7b22616374696f6e223a22636f6e73656e742d746f2d62652d706172742d6f662d77616c6c6574222c2277616c6c6574223a22307834376530653434646536343962333563663738363339393862653663356137643564386336336265222c227369676e6572223a22307839343833353231354361413161443345333034463941374532313438363233666536363164454237222c2274696d657374616d70223a313737353539333838393336372c227369676e65724b696e64223a226c6f67696e2d676f6f676c65227d\"" + } + }, + { + "request": { + "method": "POST", + "url": "https://keymachine.sequence.app/rpc/Sessions/Witness", + "headers": { + "content-type": "application/json", + "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" + }, + "body": "{\"signer\":\"0x000000000000AB36D17eB1150116371520565205\",\"wallet\":\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\",\"sapientHash\":\"0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3\"}" + }, + "response": { + "status": 404, + "statusText": "Not Found", + "headers": { + "alt-svc": "h3=\":443\"; ma=86400", + "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb44cde6a2cf-YUL", + "connection": "keep-alive", + "content-length": "527", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:27 GMT", + "expires": "Thu, 01 Jan 1970 00:00:00 GMT", + "pragma": "no-cache", + "server": "cloudflare", + "strict-transport-security": "max-age=2592000; includeSubDomains", + "vary": "Origin", + "via": "1.1 google", + "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" + }, + "body": "{\"error\":\"NotFound\",\"code\":2,\"msg\":\"not found\",\"cause\":\"unable to find witness for sapient signer 0x000000000000ab36d17eb1150116371520565205 image hash 0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3 wallet 0x47e0e44de649b35cf7863998be6c5a7d5d8c63be: unable to find witness for sapient signer 0x000000000000ab36d17eb1150116371520565205 image hash 0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3 wallet 0x47e0e44de649b35cf7863998be6c5a7d5d8c63be: pgkit: no rows in result set\",\"status\":404}" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"signature\\\"] }, { name: \\\"Sequence-Sessions-Wallet\\\", values: [\\\"0x47E0e44DE649B35Cf7863998Be6C5a7D5d8c63bE\\\"] }, { name: \\\"Sequence-Sessions-Signer\\\", values: [\\\"0x000000000000AB36D17eB1150116371520565205\\\"] }, { name: \\\"Sequence-Sessions-Image-Hash\\\", values: [\\\"0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3\\\"] }, { name: \\\"Sequence-Sessions-Witness\\\", values: [\\\"true\\\"] }, { name: \\\"Sequence-Sessions-Signature-Type\\\", values: [\\\"sapient\\\", \\\"sapient-compact\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb44fa375a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:27 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=8,cfOrigin;dur=193", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "krTnhHzpB0qpko17WyAtyDvAyvrzk2wWisOQst+mtd9e", + "x-77-nzt-ray": "331b5e0fe1a2ac959fd7df69277a2f1d", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[]}}}\n" + } + }, + { + "request": { + "method": "POST", + "url": "https://keymachine.sequence.app/rpc/Sessions/ConfigUpdates", + "headers": { + "content-type": "application/json", + "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" + }, + "body": "{\"wallet\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\",\"fromImageHash\":\"0xaa14aff91091e94d7521625ab1c713273e86a8c21a0afb6cee35be28af47738a\"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "alt-svc": "h3=\":443\"; ma=86400", + "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb4688b6a2cf-YUL", + "connection": "keep-alive", + "content-length": "1715", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:27 GMT", + "expires": "Thu, 01 Jan 1970 00:00:00 GMT", + "pragma": "no-cache", + "server": "cloudflare", + "strict-transport-security": "max-age=2592000; includeSubDomains", + "vary": "Origin", + "via": "1.1 google", + "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" + }, + "body": "{\"updates\":[{\"toImageHash\":\"0xa411f8bf05839071761a3cfece2ac3ece2a59a257d989d915068d5f7ddaab294\",\"signature\":\"0x0600017126c7b33eb2463c323d6c12e14b128c935a28d41abfa9a200c12a2c622992ee81e16f14850f8ff490fc850c4740543eb0b96ab785eec461f669fe1261001df1b1\"},{\"toImageHash\":\"0xa7b4c81f8ce5d29dd1bb83e188e039db08e39a3c4de706f10827556fb44a6930\",\"signature\":\"0x0601027172830470d9a9a8f28d3f7554dae9806e14f34f75ffdc8cdff62f691566ff3bb3884ff61967f0180f0c7cb56c860a97cad1c810894becfae50768e1425486d3f371116bea5f536c7745117315baef9743ed6e9bd0bb4d6b0e0dcba7fffdab393a2a50ae10f526151f937cdf8fe67f6b2199e6127e4637017bc056dd7f08d959e0981188a25af00c5590a0583c7f4d994ec87e5cde0ba2\"},{\"toImageHash\":\"0xe60a2df731c3eb8e237b88b8b7c13fd6d4b56b48496d1adce8cf29a572ce6487\",\"signature\":\"0x060502113114b2ede71533c3100842b62e3773e930989a1e71d74d840a07de51b5012bc94fcf3a7715bfa1b7845b2674874a71bef4a7f3cd269eb75136e3eadfe33931302452d1cccca8819e3f0615fb36ba71aaeb8465b70571ecd8bcb73872ad91461bba93b42b363b7048d61bc29c813ef890ae5904cb5a0bbad5796934d1c54bac5ee162dbe9acf2ad16cb34e656b4e6c3cc1880c151cf1b\"},{\"toImageHash\":\"0xd430d9d056a14a874c22b86246902a60f4b25573fbe998f1148a7e193edf69ae\",\"signature\":\"0x06060171e375fd12b814b897838f13daf9e36bd9d66c78d606580b7822c1364ae2c56ca2b87ca29afec44adba341c80b4a0b45c072087043cd123ad084c7d57ba6b4d0721107a87dc7616f121951e81a997d32ed03c38566071149fca172d48f43f8e8e45685c1581d9ab14f9fae\"},{\"toImageHash\":\"0x19b94a7bd3dfbd5af528650ff492802bfdb909aff70608684feace24f3e08a01\",\"signature\":\"0x0608011143dc62e7f055b75948ba6d0288e233027f08f4df71af1656da7ca89e388d1c500826c5d7dd0ee670f68b83615b7e1aac78b0cae4c19cdab9e9c4af8fe5564aa49b0c50fad1218023dc6e3fa87307e29b23952a757011004702aad30e9fcb3af53a635f86c4a4f0dff2ef\"}]}" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config\\\"] }, { name: \\\"Sequence-Sessions-Config\\\", values: [\\\"0xaa14aff91091e94d7521625ab1c713273e86a8c21a0afb6cee35be28af47738a\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb46ab715a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:28 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=8,cfOrigin;dur=281", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "kof1ezmpOdlXDn9T8faYG8D1IuPZzf6IiLyidLSfg7Kc", + "x-77-nzt-ray": "331b5e0fe1a2ac959fd7df69317af82d", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJTOVNHYmpmbVdKVmxRSmxqbjZXOXBWZmthSTR4R2pkdmJaX2NzVmo1ZWlnIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"S9SGbjfmWJVlQJljn6W9pVfkaI4xGjdvbZ_csVj5eig\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Config\",\"value\":\"0xaa14aff91091e94d7521625ab1c713273e86a8c21a0afb6cee35be28af47738a\"},{\"name\":\"Sequence-Sessions-Version\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Signers-Count\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Signers-Bloom\",\"value\":\"0x0000000000000000020001000000000000000000000408000000000000000000\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/S9SGbjfmWJVlQJljn6W9pVfkaI4xGjdvbZ_csVj5eig", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:Gv6Njbd+6bmZJQS19Kpl/N2I/J9u5zxx0RWSNateHh8=:", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:28 GMT", + "sequence-sessions-complete": "true", + "sequence-sessions-config": "0xaa14aff91091e94d7521625ab1c713273e86a8c21a0afb6cee35be28af47738a", + "sequence-sessions-major-version": "1", + "sequence-sessions-minor-version": "0", + "sequence-sessions-signers-bloom": "0x0000000000000000020001000000000000000000000408000000000000000000", + "sequence-sessions-signers-count": "1", + "sequence-sessions-type": "config", + "sequence-sessions-version": "3", + "server": "CDN77-Turbo", + "signature": "comm-5q2kghl8cf8zhsqsfgox1fau_q0tyzru55pwlfnbi0c=:FDu+idLtiySYuW38oIhAvf6uLQ2fMB96MlBgdXz4mtc=:, comm-s9sgbjfmwjvlqjljn6w9pvfkai4xgjdvbz_csvj5eig=:n/6B3uOAtJhxMyQAtcAGLUtoFUen3JQ8JdOYJ5z+CKBw3k3rqZJFt5KUzi53pCISKhx1I5zmYNNRyhHhSYBfAxs=:", + "signature-input": "comm-5q2kghl8cf8zhsqsfgox1fau_q0tyzru55pwlfnbi0c=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-s9sgbjfmwjvlqjljn6w9pvfkai4xgjdvbz_csvj5eig=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmln, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:MA, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Config:MHhhYTE0YWZmOTEwOTFlOTRkNzUyMTYyNWFiMWM3MTMyNzNlODZhOGMyMWEwYWZiNmNlZTM1YmUyOGFmNDc3Mzhh, 6:Sequence-Sessions-Version:Mw, 7:Sequence-Sessions-Complete:dHJ1ZQ, 8:Sequence-Sessions-Signers-Count:MQ, 9:Sequence-Sessions-Signers-Bloom:MHgwMDAwMDAwMDAwMDAwMDAwMDIwMDAxMDAwMDAwMDAwMDAwMDAwMDAwMDAwNDA4MDAwMDAwMDAwMDAwMDAwMDAw\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "kiDzhOXXjZ+sIg4zjsnuzEoNTgPhUMI3GY9/WQVHZTb/t3PYgA", + "x-77-nzt-ray": "f03d0613653cfd25a0d7df69fbb68c0d", + "x-77-pop": "newyorkUSNY" + }, + "body": "{\"checkpoint\":\"0\",\"threshold\":1,\"tree\":{\"address\":\"0x1494927eC78E099321151C2b9d06c5bB98a21191\",\"weight\":1}}" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config update\\\"] }, { name: \\\"Sequence-Sessions-Wallet\\\", values: [\\\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\\\"] }, { name: \\\"Sequence-Sessions-Signer\\\", values: [\\\"0x1494927eC78E099321151C2b9d06c5bB98a21191\\\"] }, { name: \\\"Sequence-Sessions-Signature-Type\\\", values: [\\\"eip-712\\\", \\\"eth_sign\\\", \\\"erc-1271\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb4a6e725a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:28 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=6,cfOrigin;dur=175", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "knD6rYB3F2GDXZE2xiF+pjLj1xwpUGaRJTOITX3anJAL", + "x-77-nzt-ray": "331b5e0fe1a2ac95a0d7df6920e69515", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJpenQ4V3lWNDF0SkZiS1Yzdlo3cUxFLW54M05lMXVER0gycUF2SUxiUjhRIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"izt8WyV41tJFbKV3vZ7qLE-nx3Ne1uDGH2qAvILbR8Q\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x1494927eC78E099321151C2b9d06c5bB98a21191\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0xc68f343dd513b86c83a47c20218eb39d5dc7f2cfadd435c4253e268f7b7f4016\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xa411f8bf05839071761a3cfece2ac3ece2a59a257d989d915068d5f7ddaab294\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000502000000100000004500400000000800100000000000001080000000\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/izt8WyV41tJFbKV3vZ7qLE-nx3Ne1uDGH2qAvILbR8Q", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:5V17yO++KQTYC6pt/sLS3ygLP6/h0QJnF+9e2FHFQPY=:", + "content-encoding": "gzip", + "content-type": "text/plain", + "date": "Wed, 15 Apr 2026 18:23:28 GMT", + "sequence-sessions-chain-id": "0", + "sequence-sessions-major-version": "2", + "sequence-sessions-minor-version": "0", + "sequence-sessions-signature-type": "eth_sign", + "sequence-sessions-signer": "0x1494927eC78E099321151C2b9d06c5bB98a21191", + "sequence-sessions-subdigest": "0xc68f343dd513b86c83a47c20218eb39d5dc7f2cfadd435c4253e268f7b7f4016", + "sequence-sessions-to-checkpoint": "1", + "sequence-sessions-to-config": "0xa411f8bf05839071761a3cfece2ac3ece2a59a257d989d915068d5f7ddaab294", + "sequence-sessions-to-config-complete": "true", + "sequence-sessions-to-signers-bloom": "0x0000000502000000100000004500400000000800100000000000001080000000", + "sequence-sessions-to-signers-count": "3", + "sequence-sessions-type": "config update", + "sequence-sessions-wallet": "0x135769a58639b4Fa7d779a9df9B57A706FBCa816", + "sequence-sessions-witness": "true", + "server": "CDN77-Turbo", + "signature": "comm-izt8wyv41tjfbkv3vz7qle-nx3ne1udgh2qavilbr8q=:Rtry+88OHbpQW31vhv2P+YQaydNrVPtgldw2BJqd8qsDwDsjM7vcO4Yj54LiPMkuVn9nOGv4lqfQ5ODsfhROnRs=:, comm-l89rjriyuff5qj8_r92qjquo7xjqgirvu0_lu1b29xq=:KhFweURuUOR33tKJjhlPjpOO9f8kl7fPjyLMabPjwOE=:", + "signature-input": "comm-izt8wyv41tjfbkv3vz7qle-nx3ne1udgh2qavilbr8q=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmlnIHVwZGF0ZQ, 10:Sequence-Sessions-Major-Version:Mg, 11:Sequence-Sessions-To-Config:MHhhNDExZjhiZjA1ODM5MDcxNzYxYTNjZmVjZTJhYzNlY2UyYTU5YTI1N2Q5ODlkOTE1MDY4ZDVmN2RkYWFiMjk0, 12:Sequence-Sessions-To-Checkpoint:MQ, 13:Sequence-Sessions-To-Config-Complete:dHJ1ZQ, 14:Sequence-Sessions-To-Signers-Count:Mw, 15:Sequence-Sessions-To-Signers-Bloom:MHgwMDAwMDAwNTAyMDAwMDAwMTAwMDAwMDA0NTAwNDAwMDAwMDAwODAwMTAwMDAwMDAwMDAwMDAxMDgwMDAwMDAw, 2:Sequence-Sessions-Minor-Version:MA, 3:Content-Type:dGV4dC9wbGFpbg, 4:Sequence-Sessions-Signature-Type:ZXRoX3NpZ24, 5:Sequence-Sessions-Signer:MHgxNDk0OTI3ZUM3OEUwOTkzMjExNTFDMmI5ZDA2YzViQjk4YTIxMTkx, 6:Sequence-Sessions-Subdigest:MHhjNjhmMzQzZGQ1MTNiODZjODNhNDdjMjAyMThlYjM5ZDVkYzdmMmNmYWRkNDM1YzQyNTNlMjY4ZjdiN2Y0MDE2, 7:Sequence-Sessions-Wallet:MHgxMzU3NjlhNTg2MzliNEZhN2Q3NzlhOWRmOUI1N0E3MDZGQkNhODE2, 8:Sequence-Sessions-Chain-ID:MA, 9:Sequence-Sessions-Witness:dHJ1ZQ\", comm-l89rjriyuff5qj8_r92qjquo7xjqgirvu0_lu1b29xq=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"hmac-sha256\";keyid=\"constant:ao\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "kmaEcGv4HK6lYliBsjNRILzxFuSArvL/LGTD71iIv9gLs+oxXQ", + "x-77-nzt-ray": "f03d0613bf5c6c3ba0d7df69f68c742b", + "x-77-pop": "newyorkUSNY" + }, + "body": "0x26c7b33eb2463c323d6c12e14b128c935a28d41abfa9a200c12a2c622992ee81616f14850f8ff490fc850c4740543eb0b96ab785eec461f669fe1261001df1b11c" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config\\\"] }, { name: \\\"Sequence-Sessions-Config\\\", values: [\\\"0xa411f8bf05839071761a3cfece2ac3ece2a59a257d989d915068d5f7ddaab294\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb4d18905a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:29 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=8,cfOrigin;dur=185", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "kjlqZaVBXeQLSEYmXiAVLejJodr3HMepIYXYP3x9MM84", + "x-77-nzt-ray": "331b5e0fe1a2ac95a0d7df69c9e72d2f", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJOWjJIWWNGZDAtOThVZExudWJLc2N1S09valJ1dGlnX1RwcC1sMkgtSWtFIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"NZ2HYcFd0-98UdLnubKscuKOojRutig_Tpp-l2H-IkE\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Config\",\"value\":\"0xa411f8bf05839071761a3cfece2ac3ece2a59a257d989d915068d5f7ddaab294\"},{\"name\":\"Sequence-Sessions-Version\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Signers-Bloom\",\"value\":\"0x0000000502000000100000004500400000000800100000000000001080000000\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/NZ2HYcFd0-98UdLnubKscuKOojRutig_Tpp-l2H-IkE", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:SV08eWGigUB+aigp46hMh2a0MgdrwYXYXNl1hV8xqBc=:", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:29 GMT", + "sequence-sessions-complete": "true", + "sequence-sessions-config": "0xa411f8bf05839071761a3cfece2ac3ece2a59a257d989d915068d5f7ddaab294", + "sequence-sessions-major-version": "1", + "sequence-sessions-minor-version": "0", + "sequence-sessions-signers-bloom": "0x0000000502000000100000004500400000000800100000000000001080000000", + "sequence-sessions-signers-count": "3", + "sequence-sessions-type": "config", + "sequence-sessions-version": "3", + "server": "CDN77-Turbo", + "signature": "comm-hkjz12krcwpdpscafdqy2i1mtx_crmzwrygba7ev28i=:F016DF5NiP4rsrw3xFweRK7a6ufEShE27XbmIZ0mM0U=:, comm-nz2hycfd0-98udlnubkscukoojrutig_tpp-l2h-ike=:0szAi+qf/tbBpzdEDAb6YmZaWpdGZxgz8EwfVWItkCMqC1OCCco2um+SGlxB4upmVWuhuqvmPrd3n/r3FbIMqxs=:", + "signature-input": "comm-hkjz12krcwpdpscafdqy2i1mtx_crmzwrygba7ev28i=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-nz2hycfd0-98udlnubkscukoojrutig_tpp-l2h-ike=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmln, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:MA, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Config:MHhhNDExZjhiZjA1ODM5MDcxNzYxYTNjZmVjZTJhYzNlY2UyYTU5YTI1N2Q5ODlkOTE1MDY4ZDVmN2RkYWFiMjk0, 6:Sequence-Sessions-Version:Mw, 7:Sequence-Sessions-Complete:dHJ1ZQ, 8:Sequence-Sessions-Signers-Count:Mw, 9:Sequence-Sessions-Signers-Bloom:MHgwMDAwMDAwNTAyMDAwMDAwMTAwMDAwMDA0NTAwNDAwMDAwMDAwODAwMTAwMDAwMDAwMDAwMDAxMDgwMDAwMDAw\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "khsoikltqnrtzOqDWKW2HYe5dYh3rJZRF4vNeKiJmIIu4/HoPA", + "x-77-nzt-ray": "3f9f1325610a726ea1d7df69ec8dcd08", + "x-77-pop": "newyorkUSNY" + }, + "body": "{\"checkpoint\":\"1\",\"threshold\":2,\"tree\":[[{\"address\":\"0x3114b2Ede71533C3100842B62e3773e930989A1E\",\"weight\":1},{\"address\":\"0xb20E9ff31A75190B5Aa25fFfae646AF7821D4548\",\"weight\":1}],{\"address\":\"0x88A25Af00C5590a0583c7F4d994Ec87E5Cde0bA2\",\"weight\":1}]}" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config update\\\"] }, { name: \\\"Sequence-Sessions-Wallet\\\", values: [\\\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\\\"] }, { name: \\\"Sequence-Sessions-Signer\\\", values: [\\\"0x3114b2Ede71533C3100842B62e3773e930989A1E\\\", \\\"0xb20E9ff31A75190B5Aa25fFfae646AF7821D4548\\\", \\\"0x88A25Af00C5590a0583c7F4d994Ec87E5Cde0bA2\\\"] }, { name: \\\"Sequence-Sessions-Signature-Type\\\", values: [\\\"eip-712\\\", \\\"eth_sign\\\", \\\"erc-1271\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb4fbaa55a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:29 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=6,cfOrigin;dur=220", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "koCOMWdJBLCcnc0fzZurVvjw4i3Nn0rg5nRirg2TFixh", + "x-77-nzt-ray": "331b5e0fe1a2ac95a1d7df6936d7fc0c", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCI0VjZoTjI4M29XWW9WTFhwRGE5MGxIeTUtNEN0VmZFTlMzMjB4eXJobWxjIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"4V6hN283oWYoVLXpDa90lHy5-4CtVfENS320xyrhmlc\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x88A25Af00C5590a0583c7F4d994Ec87E5Cde0bA2\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x58d9ed51710c4e5de31ea3e486cbb6e45f204e9dd9a97ce49e354c19f12cf716\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xaa0482c0c3f36dc41941c9fde395fe7f77775353d7ace363df4f6a924cde404d\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000502000000100000004000400000000800100000000000100000002014\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJEX0huLUZETFJIeThORjFWN1BGOTI1dFlEZ3paSTVYWng2NFdDckhUd1ZzIl0sImluZGV4IjoxfQ==\",\"node\":{\"id\":\"D_Hn-FDLRHy8NF1V7PF925tYDgzZI5XZx64WCrHTwVs\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x3114b2Ede71533C3100842B62e3773e930989A1E\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x58d9ed51710c4e5de31ea3e486cbb6e45f204e9dd9a97ce49e354c19f12cf716\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xaa0482c0c3f36dc41941c9fde395fe7f77775353d7ace363df4f6a924cde404d\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000502000000100000004000400000000800100000000000100000002014\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJGejRFQmo3V0xmVXEyVlZzRzJ0emxWb0ttODZLbU9YRGJVRi1qNm1kOWpzIl0sImluZGV4IjoyfQ==\",\"node\":{\"id\":\"Fz4EBj7WLfUq2VVsG2tzlVoKm86KmOXDbUF-j6md9js\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0xb20E9ff31A75190B5Aa25fFfae646AF7821D4548\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x7bab5d0f4f3f4087736b9dc8500202e73ece7eeb645abdd4ee3252fd2db6f6c3\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xe60a2df731c3eb8e237b88b8b7c13fd6d4b56b48496d1adce8cf29a572ce6487\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"6\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0008000180000400020000000000000000000109200008010000400000000000\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJfRWNTa3dxT3BSQTdRWjlGUndfSjRHVlQxVDdIYmVvelJ1RVU1UnRTTnlJIl0sImluZGV4IjozfQ==\",\"node\":{\"id\":\"_EcSkwqOpRA7QZ9FRw_J4GVT1T7HbeozRuEU5RtSNyI\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0xb20E9ff31A75190B5Aa25fFfae646AF7821D4548\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0xe842d9fea808a29c77e53e06e5cc78e122374ab5b0c2494e8a0baacd1b48b6a0\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xa7b4c81f8ce5d29dd1bb83e188e039db08e39a3c4de706f10827556fb44a6930\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"5\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000502000100100000005000400000000800100002000000000020000000\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJlMXhjRE1scnJMNTVBZ2ZVWDZ1MkdzNGRlcm1KeDZ0SnQtTjhWdnNmQ2ZNIl0sImluZGV4Ijo0fQ==\",\"node\":{\"id\":\"e1xcDMlrrL55AgfUX6u2Gs4dermJx6tJt-N8VvsfCfM\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x3114b2Ede71533C3100842B62e3773e930989A1E\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x406a8fb3dee0ed7a025cc2bab7e1ac829b489b2e8692fea8f694bd3719b88eb5\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xd0dedc8b8e2407f4ca09d86b381a8da08e7e8fb551d5699f56a4daa6273bf13d\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x1000000502000000100000004000400000042800100000000000000000020000\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJsN1ZwTWJ3MV9nNEpNOU9XWHZYcHFneDlseVhlbGdVUldMVEQ0YVhKSmxvIl0sImluZGV4Ijo1fQ==\",\"node\":{\"id\":\"l7VpMbw1_g4JM9OWXvXpqgx9lyXelgURWLTD4aXJJlo\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x3114b2Ede71533C3100842B62e3773e930989A1E\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x568723a41b651b52c87c0a9769243e76e02a6ebdfcf4ff89d551429deac205c2\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xe0d9a803e3dbd676c10cea040e6501b80d011a7acf3c6312d0aad291326862c5\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"4\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000502000000100000004000400080000800100000000000020000000100\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJ3NU54RFJ2a0VCaTJWeG5KZ2p6NS02d2RXUEF2X3ZDUjRoWkwtV0RIaHNNIl0sImluZGV4Ijo2fQ==\",\"node\":{\"id\":\"w5NxDRvkEBi2VxnJgjz5-6wdWPAv_vCR4hZL-WDHhsM\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x3114b2Ede71533C3100842B62e3773e930989A1E\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0xe842d9fea808a29c77e53e06e5cc78e122374ab5b0c2494e8a0baacd1b48b6a0\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xa7b4c81f8ce5d29dd1bb83e188e039db08e39a3c4de706f10827556fb44a6930\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"5\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000502000100100000005000400000000800100002000000000020000000\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/Fz4EBj7WLfUq2VVsG2tzlVoKm86KmOXDbUF-j6md9js", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:gl4mTPi1DdVGbow10VFIzL14OgN2Y35fOYI2KgBElAU=:", + "content-encoding": "gzip", + "content-type": "text/plain", + "date": "Wed, 15 Apr 2026 18:23:29 GMT", + "sequence-sessions-chain-id": "0", + "sequence-sessions-major-version": "2", + "sequence-sessions-minor-version": "0", + "sequence-sessions-signature-type": "eth_sign", + "sequence-sessions-signer": "0xb20E9ff31A75190B5Aa25fFfae646AF7821D4548", + "sequence-sessions-subdigest": "0x7bab5d0f4f3f4087736b9dc8500202e73ece7eeb645abdd4ee3252fd2db6f6c3", + "sequence-sessions-to-checkpoint": "6", + "sequence-sessions-to-config": "0xe60a2df731c3eb8e237b88b8b7c13fd6d4b56b48496d1adce8cf29a572ce6487", + "sequence-sessions-to-config-complete": "true", + "sequence-sessions-to-signers-bloom": "0x0008000180000400020000000000000000000109200008010000400000000000", + "sequence-sessions-to-signers-count": "3", + "sequence-sessions-type": "config update", + "sequence-sessions-wallet": "0x135769a58639b4Fa7d779a9df9B57A706FBCa816", + "sequence-sessions-witness": "false", + "server": "CDN77-Turbo", + "signature": "comm-dkuigffhmyx2wop753phvp2o56fbn2efe-i-orzwsre=:NRXf0YANg7WO0IyBzvdHidfMGeMiGG1dCE2VWRMnn2U=:, comm-fz4ebj7wlfuq2vvsg2tzlvokm86kmoxdbuf-j6md9js=:PIRkvIK56BiA3gNYpQ3Y6Iqqh7iwqYFutzjp5RF8sy1nr+/VlYKTI5npT/LFyyBv13laNRFXgAV2mCc9uzrH9Rw=:", + "signature-input": "comm-dkuigffhmyx2wop753phvp2o56fbn2efe-i-orzwsre=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-fz4ebj7wlfuq2vvsg2tzlvokm86kmoxdbuf-j6md9js=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmlnIHVwZGF0ZQ, 10:Sequence-Sessions-Major-Version:Mg, 11:Sequence-Sessions-To-Config:MHhlNjBhMmRmNzMxYzNlYjhlMjM3Yjg4YjhiN2MxM2ZkNmQ0YjU2YjQ4NDk2ZDFhZGNlOGNmMjlhNTcyY2U2NDg3, 12:Sequence-Sessions-To-Checkpoint:Ng, 13:Sequence-Sessions-To-Config-Complete:dHJ1ZQ, 14:Sequence-Sessions-To-Signers-Count:Mw, 15:Sequence-Sessions-To-Signers-Bloom:MHgwMDA4MDAwMTgwMDAwNDAwMDIwMDAwMDAwMDAwMDAwMDAwMDAwMTA5MjAwMDA4MDEwMDAwNDAwMDAwMDAwMDAw, 2:Sequence-Sessions-Minor-Version:MA, 3:Content-Type:dGV4dC9wbGFpbg, 4:Sequence-Sessions-Signature-Type:ZXRoX3NpZ24, 5:Sequence-Sessions-Signer:MHhiMjBFOWZmMzFBNzUxOTBCNUFhMjVmRmZhZTY0NkFGNzgyMUQ0NTQ4, 6:Sequence-Sessions-Subdigest:MHg3YmFiNWQwZjRmM2Y0MDg3NzM2YjlkYzg1MDAyMDJlNzNlY2U3ZWViNjQ1YWJkZDRlZTMyNTJmZDJkYjZmNmMz, 7:Sequence-Sessions-Wallet:MHgxMzU3NjlhNTg2MzliNEZhN2Q3NzlhOWRmOUI1N0E3MDZGQkNhODE2, 8:Sequence-Sessions-Chain-ID:MA, 9:Sequence-Sessions-Witness:ZmFsc2U\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "ktuYrhp0DCHMjpvuQl6ghF489ixAO0MeTI6HIQ6uRTrrdrCatQ", + "x-77-nzt-ray": "8705ec34b6d394d7a1d7df693595b823", + "x-77-pop": "newyorkUSNY" + }, + "body": "0xd74d840a07de51b5012bc94fcf3a7715bfa1b7845b2674874a71bef4a7f3cd261eb75136e3eadfe33931302452d1cccca8819e3f0615fb36ba71aaeb8465b7051c" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/_EcSkwqOpRA7QZ9FRw_J4GVT1T7HbeozRuEU5RtSNyI", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:VBsXK1M8uZVlxPKBBimHHRZfzcKY+OP9ht9pd1e8OYY=:", + "content-encoding": "gzip", + "content-type": "text/plain", + "date": "Wed, 15 Apr 2026 18:23:29 GMT", + "sequence-sessions-chain-id": "0", + "sequence-sessions-major-version": "2", + "sequence-sessions-minor-version": "0", + "sequence-sessions-signature-type": "eth_sign", + "sequence-sessions-signer": "0xb20E9ff31A75190B5Aa25fFfae646AF7821D4548", + "sequence-sessions-subdigest": "0xe842d9fea808a29c77e53e06e5cc78e122374ab5b0c2494e8a0baacd1b48b6a0", + "sequence-sessions-to-checkpoint": "5", + "sequence-sessions-to-config": "0xa7b4c81f8ce5d29dd1bb83e188e039db08e39a3c4de706f10827556fb44a6930", + "sequence-sessions-to-config-complete": "true", + "sequence-sessions-to-signers-bloom": "0x0000000502000100100000005000400000000800100002000000000020000000", + "sequence-sessions-to-signers-count": "3", + "sequence-sessions-type": "config update", + "sequence-sessions-wallet": "0x135769a58639b4Fa7d779a9df9B57A706FBCa816", + "sequence-sessions-witness": "true", + "server": "CDN77-Turbo", + "signature": "comm-_ecskwqopra7qz9frw_j4gvt1t7hbeozrueu5rtsnyi=:mSJ7Wfbo2CDWKJ4Tc2PdmncNyLqy1ejvND6aZGdduvsmhVqizKMuM4rAWyFb38VxYD5NmaBz584iSsTvEzsHZRs=:, comm-sqp56wurfwh57gj_yhzmnwcwrflfaznkcus5vu0is28=:iX5RbPW4qPV3vnOamVp087pHbC3RE4Ux1daDnGuT6W0=:", + "signature-input": "comm-_ecskwqopra7qz9frw_j4gvt1t7hbeozrueu5rtsnyi=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmlnIHVwZGF0ZQ, 10:Sequence-Sessions-Major-Version:Mg, 11:Sequence-Sessions-To-Config:MHhhN2I0YzgxZjhjZTVkMjlkZDFiYjgzZTE4OGUwMzlkYjA4ZTM5YTNjNGRlNzA2ZjEwODI3NTU2ZmI0NGE2OTMw, 12:Sequence-Sessions-To-Checkpoint:NQ, 13:Sequence-Sessions-To-Config-Complete:dHJ1ZQ, 14:Sequence-Sessions-To-Signers-Count:Mw, 15:Sequence-Sessions-To-Signers-Bloom:MHgwMDAwMDAwNTAyMDAwMTAwMTAwMDAwMDA1MDAwNDAwMDAwMDAwODAwMTAwMDAyMDAwMDAwMDAwMDIwMDAwMDAw, 2:Sequence-Sessions-Minor-Version:MA, 3:Content-Type:dGV4dC9wbGFpbg, 4:Sequence-Sessions-Signature-Type:ZXRoX3NpZ24, 5:Sequence-Sessions-Signer:MHhiMjBFOWZmMzFBNzUxOTBCNUFhMjVmRmZhZTY0NkFGNzgyMUQ0NTQ4, 6:Sequence-Sessions-Subdigest:MHhlODQyZDlmZWE4MDhhMjljNzdlNTNlMDZlNWNjNzhlMTIyMzc0YWI1YjBjMjQ5NGU4YTBiYWFjZDFiNDhiNmEw, 7:Sequence-Sessions-Wallet:MHgxMzU3NjlhNTg2MzliNEZhN2Q3NzlhOWRmOUI1N0E3MDZGQkNhODE2, 8:Sequence-Sessions-Chain-ID:MA, 9:Sequence-Sessions-Witness:dHJ1ZQ\", comm-sqp56wurfwh57gj_yhzmnwcwrflfaznkcus5vu0is28=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"hmac-sha256\";keyid=\"constant:ao\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "kjf1MaCVro4tzO8Y+rtRj2DW+I0WgSG8mxMPrsRr+jU5xYziGg", + "x-77-nzt-ray": "3f9f13256fea358aa1d7df69c155482d", + "x-77-pop": "newyorkUSNY" + }, + "body": "0x116bea5f536c7745117315baef9743ed6e9bd0bb4d6b0e0dcba7fffdab393a2a50ae10f526151f937cdf8fe67f6b2199e6127e4637017bc056dd7f08d959e0981b" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/w5NxDRvkEBi2VxnJgjz5-6wdWPAv_vCR4hZL-WDHhsM", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:2ZuMVXKr8u5m20NWLWAyvvaN1rrF5LETVZPHv5u2sIw=:", + "content-encoding": "gzip", + "content-type": "text/plain", + "date": "Wed, 15 Apr 2026 18:23:29 GMT", + "sequence-sessions-chain-id": "0", + "sequence-sessions-major-version": "2", + "sequence-sessions-minor-version": "0", + "sequence-sessions-signature-type": "eth_sign", + "sequence-sessions-signer": "0x3114b2Ede71533C3100842B62e3773e930989A1E", + "sequence-sessions-subdigest": "0xe842d9fea808a29c77e53e06e5cc78e122374ab5b0c2494e8a0baacd1b48b6a0", + "sequence-sessions-to-checkpoint": "5", + "sequence-sessions-to-config": "0xa7b4c81f8ce5d29dd1bb83e188e039db08e39a3c4de706f10827556fb44a6930", + "sequence-sessions-to-config-complete": "true", + "sequence-sessions-to-signers-bloom": "0x0000000502000100100000005000400000000800100002000000000020000000", + "sequence-sessions-to-signers-count": "3", + "sequence-sessions-type": "config update", + "sequence-sessions-wallet": "0x135769a58639b4Fa7d779a9df9B57A706FBCa816", + "sequence-sessions-witness": "false", + "server": "CDN77-Turbo", + "signature": "comm-1lo1olkgrwhm3l5m_kzswkx4op49pdu_cddhrgaemdk=:DkOaOPc1X07fP3Pm80/Ic3FVqDNdqR6GoLK8eO0zwqA=:, comm-w5nxdrvkebi2vxnjgjz5-6wdwpav_vcr4hzl-wdhhsm=:lP/eLayDbnw09wD2+CDqLmugY5DxoBNt5A64ljWv+wAKZNuM6Qy9UJCE4OdLW58tpetZenD2nUBdfl3duCs0oRs=:", + "signature-input": "comm-1lo1olkgrwhm3l5m_kzswkx4op49pdu_cddhrgaemdk=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-w5nxdrvkebi2vxnjgjz5-6wdwpav_vcr4hzl-wdhhsm=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmlnIHVwZGF0ZQ, 10:Sequence-Sessions-Major-Version:Mg, 11:Sequence-Sessions-To-Config:MHhhN2I0YzgxZjhjZTVkMjlkZDFiYjgzZTE4OGUwMzlkYjA4ZTM5YTNjNGRlNzA2ZjEwODI3NTU2ZmI0NGE2OTMw, 12:Sequence-Sessions-To-Checkpoint:NQ, 13:Sequence-Sessions-To-Config-Complete:dHJ1ZQ, 14:Sequence-Sessions-To-Signers-Count:Mw, 15:Sequence-Sessions-To-Signers-Bloom:MHgwMDAwMDAwNTAyMDAwMTAwMTAwMDAwMDA1MDAwNDAwMDAwMDAwODAwMTAwMDAyMDAwMDAwMDAwMDIwMDAwMDAw, 2:Sequence-Sessions-Minor-Version:MA, 3:Content-Type:dGV4dC9wbGFpbg, 4:Sequence-Sessions-Signature-Type:ZXRoX3NpZ24, 5:Sequence-Sessions-Signer:MHgzMTE0YjJFZGU3MTUzM0MzMTAwODQyQjYyZTM3NzNlOTMwOTg5QTFF, 6:Sequence-Sessions-Subdigest:MHhlODQyZDlmZWE4MDhhMjljNzdlNTNlMDZlNWNjNzhlMTIyMzc0YWI1YjBjMjQ5NGU4YTBiYWFjZDFiNDhiNmEw, 7:Sequence-Sessions-Wallet:MHgxMzU3NjlhNTg2MzliNEZhN2Q3NzlhOWRmOUI1N0E3MDZGQkNhODE2, 8:Sequence-Sessions-Chain-ID:MA, 9:Sequence-Sessions-Witness:ZmFsc2U\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "kvh3BkK8TrK1nTICmKayfCCRR1hM+1BYVjHoi6xwQVy0WElnJA", + "x-77-nzt-ray": "8705ec341a2987e1a1d7df69ca29e131", + "x-77-pop": "newyorkUSNY" + }, + "body": "0x72830470d9a9a8f28d3f7554dae9806e14f34f75ffdc8cdff62f691566ff3bb3084ff61967f0180f0c7cb56c860a97cad1c810894becfae50768e1425486d3f31c" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config\\\"] }, { name: \\\"Sequence-Sessions-Config\\\", values: [\\\"0xa7b4c81f8ce5d29dd1bb83e188e039db08e39a3c4de706f10827556fb44a6930\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb542e345a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:30 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=8,cfOrigin;dur=207", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "khIGCL18wor0fRfALbLVb+Dna3rNs5tWWmLEe9up1Eoj", + "x-77-nzt-ray": "331b5e0fe1a2ac95a1d7df6975d36337", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJPNlFyTWV3VDhhWkpNQ0djNjBCSjlxQ3d1QTI5ME5RaUYwTnBreEhDNVk4Il0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"O6QrMewT8aZJMCGc60BJ9qCwuA290NQiF0NpkxHC5Y8\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Config\",\"value\":\"0xa7b4c81f8ce5d29dd1bb83e188e039db08e39a3c4de706f10827556fb44a6930\"},{\"name\":\"Sequence-Sessions-Version\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Signers-Bloom\",\"value\":\"0x0000000502000100100000005000400000000800100002000000000020000000\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/O6QrMewT8aZJMCGc60BJ9qCwuA290NQiF0NpkxHC5Y8", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:lOaTY4n1aPuJ6aIVdvkExjh8xxlEMMzle3itHhyVcqQ=:", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:30 GMT", + "sequence-sessions-complete": "true", + "sequence-sessions-config": "0xa7b4c81f8ce5d29dd1bb83e188e039db08e39a3c4de706f10827556fb44a6930", + "sequence-sessions-major-version": "1", + "sequence-sessions-minor-version": "0", + "sequence-sessions-signers-bloom": "0x0000000502000100100000005000400000000800100002000000000020000000", + "sequence-sessions-signers-count": "3", + "sequence-sessions-type": "config", + "sequence-sessions-version": "3", + "server": "CDN77-Turbo", + "signature": "comm-eq9xeugrr-8aivbdkcqvhhnrput45awkg5fu2w7n2bs=:bf/sXvC3hyBe+2Svwtz166VEvDFznyNqUtUiphysPzI=:, comm-o6qrmewt8azjmcgc60bj9qcwua290nqif0npkxhc5y8=:76kyiO7vdyBjhrDD7pMGU8GLU6xxEWc7E0sTjzPPVbZJWnDBWvIZs84oaBEjgl+WU2EkL41oyLGiM2f2QfFEDBw=:", + "signature-input": "comm-eq9xeugrr-8aivbdkcqvhhnrput45awkg5fu2w7n2bs=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-o6qrmewt8azjmcgc60bj9qcwua290nqif0npkxhc5y8=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmln, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:MA, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Config:MHhhN2I0YzgxZjhjZTVkMjlkZDFiYjgzZTE4OGUwMzlkYjA4ZTM5YTNjNGRlNzA2ZjEwODI3NTU2ZmI0NGE2OTMw, 6:Sequence-Sessions-Version:Mw, 7:Sequence-Sessions-Complete:dHJ1ZQ, 8:Sequence-Sessions-Signers-Count:Mw, 9:Sequence-Sessions-Signers-Bloom:MHgwMDAwMDAwNTAyMDAwMTAwMTAwMDAwMDA1MDAwNDAwMDAwMDAwODAwMTAwMDAyMDAwMDAwMDAwMDIwMDAwMDAw\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "ks4H+fRU5lxCmKBu64MCm0ROK5j1onKuklVRaCW11n86PbgNyg", + "x-77-nzt-ray": "f03d06130872167ba2d7df6903120c12", + "x-77-pop": "newyorkUSNY" + }, + "body": "{\"checkpoint\":\"5\",\"threshold\":2,\"tree\":[[{\"address\":\"0x3114b2Ede71533C3100842B62e3773e930989A1E\",\"weight\":1},{\"address\":\"0xb20E9ff31A75190B5Aa25fFfae646AF7821D4548\",\"weight\":1}],{\"address\":\"0xDbbF0590A29c203D15dbc462CE2ceF43C8210fB4\",\"weight\":1}]}" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config update\\\"] }, { name: \\\"Sequence-Sessions-Wallet\\\", values: [\\\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\\\"] }, { name: \\\"Sequence-Sessions-Signer\\\", values: [\\\"0x3114b2Ede71533C3100842B62e3773e930989A1E\\\", \\\"0xb20E9ff31A75190B5Aa25fFfae646AF7821D4548\\\", \\\"0xDbbF0590A29c203D15dbc462CE2ceF43C8210fB4\\\"] }, { name: \\\"Sequence-Sessions-Signature-Type\\\", values: [\\\"eip-712\\\", \\\"eth_sign\\\", \\\"erc-1271\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb5708f35a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:30 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=6,cfOrigin;dur=226", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "kjXn2kmQkyX8246oMcLumt2q+Ue5i54AU4j/EPpLph+d", + "x-77-nzt-ray": "331b5e0fe1a2ac95a2d7df6903837317", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJEX0huLUZETFJIeThORjFWN1BGOTI1dFlEZ3paSTVYWng2NFdDckhUd1ZzIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"D_Hn-FDLRHy8NF1V7PF925tYDgzZI5XZx64WCrHTwVs\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x3114b2Ede71533C3100842B62e3773e930989A1E\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x58d9ed51710c4e5de31ea3e486cbb6e45f204e9dd9a97ce49e354c19f12cf716\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xaa0482c0c3f36dc41941c9fde395fe7f77775353d7ace363df4f6a924cde404d\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000502000000100000004000400000000800100000000000100000002014\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJGejRFQmo3V0xmVXEyVlZzRzJ0emxWb0ttODZLbU9YRGJVRi1qNm1kOWpzIl0sImluZGV4IjoxfQ==\",\"node\":{\"id\":\"Fz4EBj7WLfUq2VVsG2tzlVoKm86KmOXDbUF-j6md9js\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0xb20E9ff31A75190B5Aa25fFfae646AF7821D4548\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x7bab5d0f4f3f4087736b9dc8500202e73ece7eeb645abdd4ee3252fd2db6f6c3\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xe60a2df731c3eb8e237b88b8b7c13fd6d4b56b48496d1adce8cf29a572ce6487\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"6\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0008000180000400020000000000000000000109200008010000400000000000\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJSSnlvbnFmZGtfeDU2a0JZVjhvdDNxYWRFa1BQcE5JSlBjVDNJcTR0U3lFIl0sImluZGV4IjoyfQ==\",\"node\":{\"id\":\"RJyonqfdk_x56kBYV8ot3qadEkPPpNIJPcT3Iq4tSyE\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0xDbbF0590A29c203D15dbc462CE2ceF43C8210fB4\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x7bab5d0f4f3f4087736b9dc8500202e73ece7eeb645abdd4ee3252fd2db6f6c3\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xe60a2df731c3eb8e237b88b8b7c13fd6d4b56b48496d1adce8cf29a572ce6487\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"6\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0008000180000400020000000000000000000109200008010000400000000000\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJfRWNTa3dxT3BSQTdRWjlGUndfSjRHVlQxVDdIYmVvelJ1RVU1UnRTTnlJIl0sImluZGV4IjozfQ==\",\"node\":{\"id\":\"_EcSkwqOpRA7QZ9FRw_J4GVT1T7HbeozRuEU5RtSNyI\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0xb20E9ff31A75190B5Aa25fFfae646AF7821D4548\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0xe842d9fea808a29c77e53e06e5cc78e122374ab5b0c2494e8a0baacd1b48b6a0\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xa7b4c81f8ce5d29dd1bb83e188e039db08e39a3c4de706f10827556fb44a6930\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"5\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000502000100100000005000400000000800100002000000000020000000\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJlMXhjRE1scnJMNTVBZ2ZVWDZ1MkdzNGRlcm1KeDZ0SnQtTjhWdnNmQ2ZNIl0sImluZGV4Ijo0fQ==\",\"node\":{\"id\":\"e1xcDMlrrL55AgfUX6u2Gs4dermJx6tJt-N8VvsfCfM\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x3114b2Ede71533C3100842B62e3773e930989A1E\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x406a8fb3dee0ed7a025cc2bab7e1ac829b489b2e8692fea8f694bd3719b88eb5\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xd0dedc8b8e2407f4ca09d86b381a8da08e7e8fb551d5699f56a4daa6273bf13d\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x1000000502000000100000004000400000042800100000000000000000020000\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJsN1ZwTWJ3MV9nNEpNOU9XWHZYcHFneDlseVhlbGdVUldMVEQ0YVhKSmxvIl0sImluZGV4Ijo1fQ==\",\"node\":{\"id\":\"l7VpMbw1_g4JM9OWXvXpqgx9lyXelgURWLTD4aXJJlo\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x3114b2Ede71533C3100842B62e3773e930989A1E\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x568723a41b651b52c87c0a9769243e76e02a6ebdfcf4ff89d551429deac205c2\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xe0d9a803e3dbd676c10cea040e6501b80d011a7acf3c6312d0aad291326862c5\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"4\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000502000000100000004000400080000800100000000000020000000100\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJ3NU54RFJ2a0VCaTJWeG5KZ2p6NS02d2RXUEF2X3ZDUjRoWkwtV0RIaHNNIl0sImluZGV4Ijo2fQ==\",\"node\":{\"id\":\"w5NxDRvkEBi2VxnJgjz5-6wdWPAv_vCR4hZL-WDHhsM\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x3114b2Ede71533C3100842B62e3773e930989A1E\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0xe842d9fea808a29c77e53e06e5cc78e122374ab5b0c2494e8a0baacd1b48b6a0\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xa7b4c81f8ce5d29dd1bb83e188e039db08e39a3c4de706f10827556fb44a6930\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"5\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000502000100100000005000400000000800100002000000000020000000\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/RJyonqfdk_x56kBYV8ot3qadEkPPpNIJPcT3Iq4tSyE", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:PEukna0+GD2hJOvo/DIg/ImTk2ZUTgAFkHUxpkl/mMk=:", + "content-encoding": "gzip", + "content-type": "text/plain", + "date": "Wed, 15 Apr 2026 18:23:30 GMT", + "sequence-sessions-chain-id": "0", + "sequence-sessions-major-version": "2", + "sequence-sessions-minor-version": "0", + "sequence-sessions-signature-type": "eth_sign", + "sequence-sessions-signer": "0xDbbF0590A29c203D15dbc462CE2ceF43C8210fB4", + "sequence-sessions-subdigest": "0x7bab5d0f4f3f4087736b9dc8500202e73ece7eeb645abdd4ee3252fd2db6f6c3", + "sequence-sessions-to-checkpoint": "6", + "sequence-sessions-to-config": "0xe60a2df731c3eb8e237b88b8b7c13fd6d4b56b48496d1adce8cf29a572ce6487", + "sequence-sessions-to-config-complete": "true", + "sequence-sessions-to-signers-bloom": "0x0008000180000400020000000000000000000109200008010000400000000000", + "sequence-sessions-to-signers-count": "3", + "sequence-sessions-type": "config update", + "sequence-sessions-wallet": "0x135769a58639b4Fa7d779a9df9B57A706FBCa816", + "sequence-sessions-witness": "true", + "server": "CDN77-Turbo", + "signature": "comm-fsvleswb_4wzsytpfbpcqjcck-uidvwsfoyzyhistd8=:4fue3tEjvSRint0i8SzqRVqnTcIuPVaFvXmjdXoAp/E=:, comm-rjyonqfdk_x56kbyv8ot3qadekpppnijpct3iq4tsye=:xybXj2uxTejbwNwuYv6CMlz07SZCHDc8v5Vo6zhGgm8bckzYbIdpas31XHDzBB7w4tjo9cyzUzt3ez3JFLBQQRs=:", + "signature-input": "comm-fsvleswb_4wzsytpfbpcqjcck-uidvwsfoyzyhistd8=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-rjyonqfdk_x56kbyv8ot3qadekpppnijpct3iq4tsye=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmlnIHVwZGF0ZQ, 10:Sequence-Sessions-Major-Version:Mg, 11:Sequence-Sessions-To-Config:MHhlNjBhMmRmNzMxYzNlYjhlMjM3Yjg4YjhiN2MxM2ZkNmQ0YjU2YjQ4NDk2ZDFhZGNlOGNmMjlhNTcyY2U2NDg3, 12:Sequence-Sessions-To-Checkpoint:Ng, 13:Sequence-Sessions-To-Config-Complete:dHJ1ZQ, 14:Sequence-Sessions-To-Signers-Count:Mw, 15:Sequence-Sessions-To-Signers-Bloom:MHgwMDA4MDAwMTgwMDAwNDAwMDIwMDAwMDAwMDAwMDAwMDAwMDAwMTA5MjAwMDA4MDEwMDAwNDAwMDAwMDAwMDAw, 2:Sequence-Sessions-Minor-Version:MA, 3:Content-Type:dGV4dC9wbGFpbg, 4:Sequence-Sessions-Signature-Type:ZXRoX3NpZ24, 5:Sequence-Sessions-Signer:MHhEYmJGMDU5MEEyOWMyMDNEMTVkYmM0NjJDRTJjZUY0M0M4MjEwZkI0, 6:Sequence-Sessions-Subdigest:MHg3YmFiNWQwZjRmM2Y0MDg3NzM2YjlkYzg1MDAyMDJlNzNlY2U3ZWViNjQ1YWJkZDRlZTMyNTJmZDJkYjZmNmMz, 7:Sequence-Sessions-Wallet:MHgxMzU3NjlhNTg2MzliNEZhN2Q3NzlhOWRmOUI1N0E3MDZGQkNhODE2, 8:Sequence-Sessions-Chain-ID:MA, 9:Sequence-Sessions-Witness:dHJ1ZQ\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "kspqVXqy0lxAq0Exzh68cxy5isFudoM5KH6a2vuaSHHeX+netw", + "x-77-nzt-ray": "8705ec347811ca07a2d7df691ca6912f", + "x-77-pop": "newyorkUSNY" + }, + "body": "0xecd8bcb73872ad91461bba93b42b363b7048d61bc29c813ef890ae5904cb5a0b3ad5796934d1c54bac5ee162dbe9acf2ad16cb34e656b4e6c3cc1880c151cf1b1c" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config\\\"] }, { name: \\\"Sequence-Sessions-Config\\\", values: [\\\"0xe60a2df731c3eb8e237b88b8b7c13fd6d4b56b48496d1adce8cf29a572ce6487\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb5a2bb75a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:31 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=5,cfOrigin;dur=185", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "ko5mQ8om74otrj47od4kQf7P4MGlI39VvitW/gEGOscM", + "x-77-nzt-ray": "331b5e0fe1a2ac95a2d7df690ddf0835", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCItQjRuenQ5blBYOGYtUVFGSlZVVXkzNlctamxrcjFSR1F3S3UxcjZ3WFowIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"-B4nzt9nPX8f-QQFJVUUy36W-jlkr1RGQwKu1r6wXZ0\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Config\",\"value\":\"0xe60a2df731c3eb8e237b88b8b7c13fd6d4b56b48496d1adce8cf29a572ce6487\"},{\"name\":\"Sequence-Sessions-Version\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Signers-Bloom\",\"value\":\"0x0008000180000400020000000000000000000109200008010000400000000000\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/-B4nzt9nPX8f-QQFJVUUy36W-jlkr1RGQwKu1r6wXZ0", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:ksByouZ0PZUghZHHFzg2DenqCafpnya2RRPuydP3cGk=:", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:31 GMT", + "sequence-sessions-complete": "true", + "sequence-sessions-config": "0xe60a2df731c3eb8e237b88b8b7c13fd6d4b56b48496d1adce8cf29a572ce6487", + "sequence-sessions-major-version": "1", + "sequence-sessions-minor-version": "0", + "sequence-sessions-signers-bloom": "0x0008000180000400020000000000000000000109200008010000400000000000", + "sequence-sessions-signers-count": "3", + "sequence-sessions-type": "config", + "sequence-sessions-version": "3", + "server": "CDN77-Turbo", + "signature": "comm--b4nzt9npx8f-qqfjvuuy36w-jlkr1rgqwku1r6wxz0=:tzZO2MoXIqZXnZvr1LXRRtCxCUQrFr5WWrl1Cnek9EM1owGM3nJ2cFoNDwamyCuLRCuyb3PbuMtadAARRgwvZxw=:, comm-7u-kon49dtsoauudnzs8fdrc6yo-vilu7sanv6dwqp4=:kYXPoiF9pt7xSqMyHiBPnetFHt4iwZJR9edbkLvVhtM=:", + "signature-input": "comm--b4nzt9npx8f-qqfjvuuy36w-jlkr1rgqwku1r6wxz0=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmln, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:MA, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Config:MHhlNjBhMmRmNzMxYzNlYjhlMjM3Yjg4YjhiN2MxM2ZkNmQ0YjU2YjQ4NDk2ZDFhZGNlOGNmMjlhNTcyY2U2NDg3, 6:Sequence-Sessions-Version:Mw, 7:Sequence-Sessions-Complete:dHJ1ZQ, 8:Sequence-Sessions-Signers-Count:Mw, 9:Sequence-Sessions-Signers-Bloom:MHgwMDA4MDAwMTgwMDAwNDAwMDIwMDAwMDAwMDAwMDAwMDAwMDAwMTA5MjAwMDA4MDEwMDAwNDAwMDAwMDAwMDAw\", comm-7u-kon49dtsoauudnzs8fdrc6yo-vilu7sanv6dwqp4=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"hmac-sha256\";keyid=\"constant:ao\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "ksmw8yXPXCv/QiVuFuJA0fM39DGJpQOz6gS2rTgGTcOlG2IHSw", + "x-77-nzt-ray": "3f9f13251bfbd5cea3d7df69ce140a0f", + "x-77-pop": "newyorkUSNY" + }, + "body": "{\"checkpoint\":\"6\",\"threshold\":1,\"tree\":[[{\"address\":\"0x8534Cb5487E769e510c5F71b790B2ca02D1819a0\",\"weight\":1},{\"address\":\"0x07a87DC7616F121951e81A997D32eD03c3856607\",\"weight\":1}],{\"address\":\"0x49FcA172d48f43f8e8E45685C1581D9aB14F9FAE\",\"weight\":1}]}" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config update\\\"] }, { name: \\\"Sequence-Sessions-Wallet\\\", values: [\\\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\\\"] }, { name: \\\"Sequence-Sessions-Signer\\\", values: [\\\"0x8534Cb5487E769e510c5F71b790B2ca02D1819a0\\\", \\\"0x07a87DC7616F121951e81A997D32eD03c3856607\\\", \\\"0x49FcA172d48f43f8e8E45685C1581D9aB14F9FAE\\\"] }, { name: \\\"Sequence-Sessions-Signature-Type\\\", values: [\\\"eip-712\\\", \\\"eth_sign\\\", \\\"erc-1271\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb5cddd05a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:31 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=6,cfOrigin;dur=206", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "kqPYwNmgDa7sX+6+d24x+6w0D4GaaBcyrq2OrEckWBl/", + "x-77-nzt-ray": "331b5e0fe1a2ac95a3d7df6939d52113", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJLYzd0MGNBWVZUc0o5b0FiZHF4aXlPaTV1MFpaLUF2TGZ4WXJkZUdKVUJRIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"Kc7t0cAYVTsJ9oAbdqxiyOi5u0ZZ-AvLfxYrdeGJUBQ\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x8534Cb5487E769e510c5F71b790B2ca02D1819a0\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x1debc90c68f232310b434b7fc8a672fb7aebe95422ad426707ec1d1e03fe0b89\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xd430d9d056a14a874c22b86246902a60f4b25573fbe998f1148a7e193edf69ae\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"8\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000000000000000000000020000004600100008100000000003000002010\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJzM1BWUVpZV2dDN2VJNHJNTTNRN2RaZEEzUWtzYURrV0JiSU5mU21EV0tZIl0sImluZGV4IjoxfQ==\",\"node\":{\"id\":\"s3PVQZYWgC7eI4rMM3Q7dZdA3QksaDkWBbINfSmDWKY\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x49FcA172d48f43f8e8E45685C1581D9aB14F9FAE\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0xbe193096e90de1cbeea21495f3c246168a715f7d34a3bcecc7852019e0784856\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0x6a484f49f59ea0d5d9134ac92bf36ccf0f06a15030e07624f576b4294a5fdd7c\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"7\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x000800019000000002000002000000000000010020000c010000000001000000\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJ4TTdfN1hQbUNWVUxHNElMYWk0dUZmcEFoU2VQcEN1cjJWQS1hNVNDYmVvIl0sImluZGV4IjoyfQ==\",\"node\":{\"id\":\"xM7_7XPmCVULG4ILai4uFfpAhSePpCur2VA-a5SCbeo\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x07a87DC7616F121951e81A997D32eD03c3856607\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x1debc90c68f232310b434b7fc8a672fb7aebe95422ad426707ec1d1e03fe0b89\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xd430d9d056a14a874c22b86246902a60f4b25573fbe998f1148a7e193edf69ae\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"8\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000000000000000000000020000004600100008100000000003000002010\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/xM7_7XPmCVULG4ILai4uFfpAhSePpCur2VA-a5SCbeo", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:3CJOMRYcb9BRY+qlR4qHnRoD+o7cCstWySxk6xTFYes=:", + "content-encoding": "gzip", + "content-type": "text/plain", + "date": "Wed, 15 Apr 2026 18:23:31 GMT", + "sequence-sessions-chain-id": "0", + "sequence-sessions-major-version": "2", + "sequence-sessions-minor-version": "0", + "sequence-sessions-signature-type": "eth_sign", + "sequence-sessions-signer": "0x07a87DC7616F121951e81A997D32eD03c3856607", + "sequence-sessions-subdigest": "0x1debc90c68f232310b434b7fc8a672fb7aebe95422ad426707ec1d1e03fe0b89", + "sequence-sessions-to-checkpoint": "8", + "sequence-sessions-to-config": "0xd430d9d056a14a874c22b86246902a60f4b25573fbe998f1148a7e193edf69ae", + "sequence-sessions-to-config-complete": "true", + "sequence-sessions-to-signers-bloom": "0x0000000000000000000000000020000004600100008100000000003000002010", + "sequence-sessions-to-signers-count": "3", + "sequence-sessions-type": "config update", + "sequence-sessions-wallet": "0x135769a58639b4Fa7d779a9df9B57A706FBCa816", + "sequence-sessions-witness": "true", + "server": "CDN77-Turbo", + "signature": "comm-f7rp8sdytpirgwvoy_bpvzninwx3fe32tw36amj0jxc=:5LC5m46+2oi/xeTp/QMdclEzludnK+ISrSmWazfO6kA=:, comm-xm7_7xpmcvulg4ilai4uffpahseppcur2va-a5scbeo=:BArCNITJliJDJrORialbgBAEjuKKGCM8y1NIJY3EoGY5uo00kzl0BohEUiN0znskgXjuQsm3Cd/msMtsOWsfrRw=:", + "signature-input": "comm-f7rp8sdytpirgwvoy_bpvzninwx3fe32tw36amj0jxc=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-xm7_7xpmcvulg4ilai4uffpahseppcur2va-a5scbeo=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmlnIHVwZGF0ZQ, 10:Sequence-Sessions-Major-Version:Mg, 11:Sequence-Sessions-To-Config:MHhkNDMwZDlkMDU2YTE0YTg3NGMyMmI4NjI0NjkwMmE2MGY0YjI1NTczZmJlOTk4ZjExNDhhN2UxOTNlZGY2OWFl, 12:Sequence-Sessions-To-Checkpoint:OA, 13:Sequence-Sessions-To-Config-Complete:dHJ1ZQ, 14:Sequence-Sessions-To-Signers-Count:Mw, 15:Sequence-Sessions-To-Signers-Bloom:MHgwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDIwMDAwMDA0NjAwMTAwMDA4MTAwMDAwMDAwMDAzMDAwMDAyMDEw, 2:Sequence-Sessions-Minor-Version:MA, 3:Content-Type:dGV4dC9wbGFpbg, 4:Sequence-Sessions-Signature-Type:ZXRoX3NpZ24, 5:Sequence-Sessions-Signer:MHgwN2E4N0RDNzYxNkYxMjE5NTFlODFBOTk3RDMyZUQwM2MzODU2NjA3, 6:Sequence-Sessions-Subdigest:MHgxZGViYzkwYzY4ZjIzMjMxMGI0MzRiN2ZjOGE2NzJmYjdhZWJlOTU0MjJhZDQyNjcwN2VjMWQxZTAzZmUwYjg5, 7:Sequence-Sessions-Wallet:MHgxMzU3NjlhNTg2MzliNEZhN2Q3NzlhOWRmOUI1N0E3MDZGQkNhODE2, 8:Sequence-Sessions-Chain-ID:MA, 9:Sequence-Sessions-Witness:dHJ1ZQ\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "kqNwqjfqmGocmde0Peb+pShGFRd1tnyeV8b+67b+/1QX0h+s7Q", + "x-77-nzt-ray": "3f9f1325f7fba7e2a3d7df698817de28", + "x-77-pop": "newyorkUSNY" + }, + "body": "0x5937db592a0799449fb2442eed3932ce32f2cef81ce8c359cbda05f8288da27f2fc09a0f17005e8c1fbd27ac58b47046c4feebf1278783c12f64fa0ab9ae12441c" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/Kc7t0cAYVTsJ9oAbdqxiyOi5u0ZZ-AvLfxYrdeGJUBQ", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:9d4wu0YAdl5mfAKuEkIXHdXO6uCAQkxgneeV3Zr9+9s=:", + "content-encoding": "gzip", + "content-type": "text/plain", + "date": "Wed, 15 Apr 2026 18:23:31 GMT", + "sequence-sessions-chain-id": "0", + "sequence-sessions-major-version": "2", + "sequence-sessions-minor-version": "0", + "sequence-sessions-signature-type": "eth_sign", + "sequence-sessions-signer": "0x8534Cb5487E769e510c5F71b790B2ca02D1819a0", + "sequence-sessions-subdigest": "0x1debc90c68f232310b434b7fc8a672fb7aebe95422ad426707ec1d1e03fe0b89", + "sequence-sessions-to-checkpoint": "8", + "sequence-sessions-to-config": "0xd430d9d056a14a874c22b86246902a60f4b25573fbe998f1148a7e193edf69ae", + "sequence-sessions-to-config-complete": "true", + "sequence-sessions-to-signers-bloom": "0x0000000000000000000000000020000004600100008100000000003000002010", + "sequence-sessions-to-signers-count": "3", + "sequence-sessions-type": "config update", + "sequence-sessions-wallet": "0x135769a58639b4Fa7d779a9df9B57A706FBCa816", + "sequence-sessions-witness": "true", + "server": "CDN77-Turbo", + "signature": "comm-2vhls37zqgon56ymq8tdapyh9dstpdixgd4yj-ittvu=:7VBwLsBh2Nn/42bwA0auDaeX6MwAD69evUz2q/00XPA=:, comm-kc7t0cayvtsj9oabdqxiyoi5u0zz-avlfxyrdegjubq=:4hG3iejxTU/Pph3BhyXkYkIT+WdnA2+ew9pW/8gFD6ZRK+/cieu2hIpQYFY0nmFneJqNkpK5a7yvEjsbXzMdQBw=:", + "signature-input": "comm-2vhls37zqgon56ymq8tdapyh9dstpdixgd4yj-ittvu=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-kc7t0cayvtsj9oabdqxiyoi5u0zz-avlfxyrdegjubq=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmlnIHVwZGF0ZQ, 10:Sequence-Sessions-Major-Version:Mg, 11:Sequence-Sessions-To-Config:MHhkNDMwZDlkMDU2YTE0YTg3NGMyMmI4NjI0NjkwMmE2MGY0YjI1NTczZmJlOTk4ZjExNDhhN2UxOTNlZGY2OWFl, 12:Sequence-Sessions-To-Checkpoint:OA, 13:Sequence-Sessions-To-Config-Complete:dHJ1ZQ, 14:Sequence-Sessions-To-Signers-Count:Mw, 15:Sequence-Sessions-To-Signers-Bloom:MHgwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDIwMDAwMDA0NjAwMTAwMDA4MTAwMDAwMDAwMDAzMDAwMDAyMDEw, 2:Sequence-Sessions-Minor-Version:MA, 3:Content-Type:dGV4dC9wbGFpbg, 4:Sequence-Sessions-Signature-Type:ZXRoX3NpZ24, 5:Sequence-Sessions-Signer:MHg4NTM0Q2I1NDg3RTc2OWU1MTBjNUY3MWI3OTBCMmNhMDJEMTgxOWEw, 6:Sequence-Sessions-Subdigest:MHgxZGViYzkwYzY4ZjIzMjMxMGI0MzRiN2ZjOGE2NzJmYjdhZWJlOTU0MjJhZDQyNjcwN2VjMWQxZTAzZmUwYjg5, 7:Sequence-Sessions-Wallet:MHgxMzU3NjlhNTg2MzliNEZhN2Q3NzlhOWRmOUI1N0E3MDZGQkNhODE2, 8:Sequence-Sessions-Chain-ID:MA, 9:Sequence-Sessions-Witness:dHJ1ZQ\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "knuK0dh8qHZJ1pa87ng+NmZAC5z178BwWKJB6obyHUru32Zq6A", + "x-77-nzt-ray": "3f9f1325f7fb6de3a3d7df69a61c8a2a", + "x-77-pop": "newyorkUSNY" + }, + "body": "0xe375fd12b814b897838f13daf9e36bd9d66c78d606580b7822c1364ae2c56ca2387ca29afec44adba341c80b4a0b45c072087043cd123ad084c7d57ba6b4d0721c" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config\\\"] }, { name: \\\"Sequence-Sessions-Config\\\", values: [\\\"0xd430d9d056a14a874c22b86246902a60f4b25573fbe998f1148a7e193edf69ae\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb5fd84a5a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:31 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=6,cfOrigin;dur=150", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "kuNvMAojgQ/yrO7l7csjoJWNJKVsJ2PuiCBCpJs68no5", + "x-77-nzt-ray": "331b5e0fe1a2ac95a3d7df69c404cc2f", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCIwMzNDeFAxLXplTmRMeTJ4X2dzbGlmRjZ1U2I5SS1EVng5b2RXcGFOYzljIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"033CxP1-zeNdLy2x_gslifF6uSb9I-DVx9odWpaNc9c\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Config\",\"value\":\"0xd430d9d056a14a874c22b86246902a60f4b25573fbe998f1148a7e193edf69ae\"},{\"name\":\"Sequence-Sessions-Version\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Signers-Bloom\",\"value\":\"0x0000000000000000000000000020000004600100008100000000003000002010\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/033CxP1-zeNdLy2x_gslifF6uSb9I-DVx9odWpaNc9c", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:pR1drysB449OQBdWWWfOfHQnU3tlTDtrhl+v9ns8yGk=:", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:32 GMT", + "sequence-sessions-complete": "true", + "sequence-sessions-config": "0xd430d9d056a14a874c22b86246902a60f4b25573fbe998f1148a7e193edf69ae", + "sequence-sessions-major-version": "1", + "sequence-sessions-minor-version": "0", + "sequence-sessions-signers-bloom": "0x0000000000000000000000000020000004600100008100000000003000002010", + "sequence-sessions-signers-count": "3", + "sequence-sessions-type": "config", + "sequence-sessions-version": "3", + "server": "CDN77-Turbo", + "signature": "comm-033cxp1-zendly2x_gsliff6usb9i-dvx9odwpanc9c=:jlvjFuDF312zUk/2peb5tOw5rrK43A3JH+LhrpiakgZa4uY4Q/Wlea3PUolzDLIMgCfIywbxyUlmpcAHX8tmLRs=:, comm-4kmu9zanvxaovs_arucd0j5fseiwphsln5kdzm_mpba=:il2HPBkL+GNyvIbm9yeo5XLbdLLCFjcxJEEhEntxLCg=:", + "signature-input": "comm-033cxp1-zendly2x_gsliff6usb9i-dvx9odwpanc9c=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmln, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:MA, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Config:MHhkNDMwZDlkMDU2YTE0YTg3NGMyMmI4NjI0NjkwMmE2MGY0YjI1NTczZmJlOTk4ZjExNDhhN2UxOTNlZGY2OWFl, 6:Sequence-Sessions-Version:Mw, 7:Sequence-Sessions-Complete:dHJ1ZQ, 8:Sequence-Sessions-Signers-Count:Mw, 9:Sequence-Sessions-Signers-Bloom:MHgwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDIwMDAwMDA0NjAwMTAwMDA4MTAwMDAwMDAwMDAzMDAwMDAyMDEw\", comm-4kmu9zanvxaovs_arucd0j5fseiwphsln5kdzm_mpba=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"hmac-sha256\";keyid=\"constant:ao\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "kqNEoZW5LBBaGsB+Te/VCXhiu7aHH5fEZ+97fC45ic8Ps8iMWA", + "x-77-nzt-ray": "3f9f1325cfd22cf6a4d7df694e8f1f07", + "x-77-pop": "newyorkUSNY" + }, + "body": "{\"checkpoint\":\"8\",\"threshold\":1,\"tree\":[[{\"address\":\"0x43dC62e7F055b75948BA6D0288e233027F08F4df\",\"weight\":1},{\"address\":\"0xAda52e758d6e6dff5e9Cf5E944cF6D3f21387F07\",\"weight\":1}],{\"address\":\"0x004702AAD30e9fCb3Af53a635f86C4a4F0Dff2eF\",\"weight\":1}]}" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config update\\\"] }, { name: \\\"Sequence-Sessions-Wallet\\\", values: [\\\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\\\"] }, { name: \\\"Sequence-Sessions-Signer\\\", values: [\\\"0x43dC62e7F055b75948BA6D0288e233027F08F4df\\\", \\\"0xAda52e758d6e6dff5e9Cf5E944cF6D3f21387F07\\\", \\\"0x004702AAD30e9fCb3Af53a635f86C4a4F0Dff2eF\\\"] }, { name: \\\"Sequence-Sessions-Signature-Type\\\", values: [\\\"eip-712\\\", \\\"eth_sign\\\", \\\"erc-1271\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb626a5f5a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:32 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=6,cfOrigin;dur=159", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "kpKBhuw0SzPTeV5TrX7UCPHXJhbUgZPL5A6okZgvuZX5", + "x-77-nzt-ray": "331b5e0fe1a2ac95a4d7df69fa19af0c", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCIxSmZCQ0w3bnpaSzlxcmZ6OHFPQTRZVjEtR1JmLU1FLWIwenVxY3J3NmRFIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"1JfBCL7nzZK9qrfz8qOA4YV1-GRf-ME-b0zuqcrw6dE\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0xAda52e758d6e6dff5e9Cf5E944cF6D3f21387F07\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x512485d4e46a9758b98e84105d4d54755ce6645bd3ddbab0e850c968a8ef25ba\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0x7178a76bb537253e8f37c2c6a400af1a85b7f5314ae9f06bfeb1a93df275fca3\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"11\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0040000000000000000000000000000000411100008100000000003000002010\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCI0OGdNdUE5V0NlUlB6RW9FNnFSSWE4RUs4Y3piZHBicENaNGdIRDNUQVhBIl0sImluZGV4IjoxfQ==\",\"node\":{\"id\":\"48gMuA9WCeRPzEoE6qRIa8EK8czbdpbpCZ4gHD3TAXA\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0x43dC62e7F055b75948BA6D0288e233027F08F4df\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x00d52df963e55d1be9b37ca9af01fc1529e76aa9e4adfc8258b9bfa96883d2cb\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xcde2e7ec78ebea962db5fc8c5bd1124b37b699131db4ccd99653f10e6b463b8c\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"9\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000000000000000000040000000000410100008100000000003004002090\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJHMHo4V29aYm85allnbzFsa01GbHRLcm9uc2c3em16WmVuUFRiVkZNaldjIl0sImluZGV4IjoyfQ==\",\"node\":{\"id\":\"G0z8WoZbo9jYgo1lkMFltKronsg7zmzZenPTbVFMjWc\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0xAda52e758d6e6dff5e9Cf5E944cF6D3f21387F07\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0xa3c45186be4593770ea3414abbd86c5e10f5791b093372b2f8a9a25482e9f826\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0x2bafea1dbc6381b39848d226dccb906f23171edf7661149da26ab609d15a1339\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"10\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000000000000000000000000000400400100408100000001003000082010\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJnZGdUclNoR09vUjBzYlFXSVI0M1hEUE5JTmI3THExUFVMUEY4SU9RNy1ZIl0sImluZGV4IjozfQ==\",\"node\":{\"id\":\"gdgTrShGOoR0sbQWIR43XDPNINb7Lq1PULPF8IOQ7-Y\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0xAda52e758d6e6dff5e9Cf5E944cF6D3f21387F07\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x64825b014c15f38a1464da1a4fc9694584a59f052ae2065dac457935f488059b\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0x19b94a7bd3dfbd5af528650ff492802bfdb909aff70608684feace24f3e08a01\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"12\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000120000404000000040000000000000100000000000000312000002010\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/gdgTrShGOoR0sbQWIR43XDPNINb7Lq1PULPF8IOQ7-Y", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:YvO0UJsvKhf3KcMrD0ijPMAIjZCYoeXF1YboroG7GFo=:", + "content-encoding": "gzip", + "content-type": "text/plain", + "date": "Wed, 15 Apr 2026 18:23:32 GMT", + "sequence-sessions-chain-id": "0", + "sequence-sessions-major-version": "2", + "sequence-sessions-minor-version": "0", + "sequence-sessions-signature-type": "eth_sign", + "sequence-sessions-signer": "0xAda52e758d6e6dff5e9Cf5E944cF6D3f21387F07", + "sequence-sessions-subdigest": "0x64825b014c15f38a1464da1a4fc9694584a59f052ae2065dac457935f488059b", + "sequence-sessions-to-checkpoint": "12", + "sequence-sessions-to-config": "0x19b94a7bd3dfbd5af528650ff492802bfdb909aff70608684feace24f3e08a01", + "sequence-sessions-to-config-complete": "true", + "sequence-sessions-to-signers-bloom": "0x0000000120000404000000040000000000000100000000000000312000002010", + "sequence-sessions-to-signers-count": "3", + "sequence-sessions-type": "config update", + "sequence-sessions-wallet": "0x135769a58639b4Fa7d779a9df9B57A706FBCa816", + "sequence-sessions-witness": "false", + "server": "CDN77-Turbo", + "signature": "comm-egncy3liqfquug_n0abtquya6qvhh071cvmvptlqg-4=:Dio++Bk3uNbWk+jRSV7664zTj16HKxvid9ULuWTD3tE=:, comm-gdgtrshgoor0sbqwir43xdpninb7lq1pulpf8ioq7-y=:C2XS2s2WWfgKn4IvalZanI1Mc1WCSGalLXxYwblW+45MEQYb6JCo/2s3b8rWFWuxCGbSKOAOl0wPjocnBDXfJBs=:", + "signature-input": "comm-egncy3liqfquug_n0abtquya6qvhh071cvmvptlqg-4=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-gdgtrshgoor0sbqwir43xdpninb7lq1pulpf8ioq7-y=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signature-type\" \"sequence-sessions-signer\" \"sequence-sessions-subdigest\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-wallet\" \"sequence-sessions-witness\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmlnIHVwZGF0ZQ, 10:Sequence-Sessions-Major-Version:Mg, 11:Sequence-Sessions-To-Config:MHgxOWI5NGE3YmQzZGZiZDVhZjUyODY1MGZmNDkyODAyYmZkYjkwOWFmZjcwNjA4Njg0ZmVhY2UyNGYzZTA4YTAx, 12:Sequence-Sessions-To-Checkpoint:MTI, 13:Sequence-Sessions-To-Config-Complete:dHJ1ZQ, 14:Sequence-Sessions-To-Signers-Count:Mw, 15:Sequence-Sessions-To-Signers-Bloom:MHgwMDAwMDAwMTIwMDAwNDA0MDAwMDAwMDQwMDAwMDAwMDAwMDAwMTAwMDAwMDAwMDAwMDAwMzEyMDAwMDAyMDEw, 2:Sequence-Sessions-Minor-Version:MA, 3:Content-Type:dGV4dC9wbGFpbg, 4:Sequence-Sessions-Signature-Type:ZXRoX3NpZ24, 5:Sequence-Sessions-Signer:MHhBZGE1MmU3NThkNmU2ZGZmNWU5Q2Y1RTk0NGNGNkQzZjIxMzg3RjA3, 6:Sequence-Sessions-Subdigest:MHg2NDgyNWIwMTRjMTVmMzhhMTQ2NGRhMWE0ZmM5Njk0NTg0YTU5ZjA1MmFlMjA2NWRhYzQ1NzkzNWY0ODgwNTli, 7:Sequence-Sessions-Wallet:MHgxMzU3NjlhNTg2MzliNEZhN2Q3NzlhOWRmOUI1N0E3MDZGQkNhODE2, 8:Sequence-Sessions-Chain-ID:MA, 9:Sequence-Sessions-Witness:ZmFsc2U\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "kvVh9MCLFtrHa/I5Tw0vQ/XufyHHGKaudXFCXE9xxdzG326htQ", + "x-77-nzt-ray": "8705ec3425fb5c4fa4d7df697537cf1f", + "x-77-pop": "newyorkUSNY" + }, + "body": "0xaf1656da7ca89e388d1c500826c5d7dd0ee670f68b83615b7e1aac78b0cae4c11cdab9e9c4af8fe5564aa49b0c50fad1218023dc6e3fa87307e29b23952a75701c" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config\\\"] }, { name: \\\"Sequence-Sessions-Config\\\", values: [\\\"0x19b94a7bd3dfbd5af528650ff492802bfdb909aff70608684feace24f3e08a01\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb64ec385a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:33 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=13,cfOrigin;dur=391", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "kkdNispgdvnwMIBxRgVePYUfIN5f7YWmXuDUaQLkUQCd", + "x-77-nzt-ray": "331b5e0fe1a2ac95a4d7df697f837524", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJiMzhSTG1QbktvdEU3cEo2SVhZRjgyT19PaEo5WGdRRk96RXB4ZDdHOWE4Il0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"b38RLmPnKotE7pJ6IXYF82O_OhJ9XgQFOzEpxd7G9a8\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Config\",\"value\":\"0x19b94a7bd3dfbd5af528650ff492802bfdb909aff70608684feace24f3e08a01\"},{\"name\":\"Sequence-Sessions-Version\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-Signers-Bloom\",\"value\":\"0x0000000120000404000000040000000000000100000000000000312000002010\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/b38RLmPnKotE7pJ6IXYF82O_OhJ9XgQFOzEpxd7G9a8", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:SiVzr20W80RCGWy7sZQeosNKLYQT9bClI+k7XXY7tZ8=:", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:33 GMT", + "sequence-sessions-complete": "true", + "sequence-sessions-config": "0x19b94a7bd3dfbd5af528650ff492802bfdb909aff70608684feace24f3e08a01", + "sequence-sessions-major-version": "1", + "sequence-sessions-minor-version": "0", + "sequence-sessions-signers-bloom": "0x0000000120000404000000040000000000000100000000000000312000002010", + "sequence-sessions-signers-count": "3", + "sequence-sessions-type": "config", + "sequence-sessions-version": "3", + "server": "CDN77-Turbo", + "signature": "comm-b38rlmpnkote7pj6ixyf82o_ohj9xgqfozepxd7g9a8=:VwZrTQ5NGtQYAghYnlBLUUWlY+4VH7Kv77zIbzHYkl4hnHj7BctL/+cwv6sum7MwR3gI8kDF/4N6mX7gZ7Pj9Bw=:, comm-il29hgsyyy8ti2jdr8t-9zseofap4epv1ew-_yjtrxq=:St6HmXse1t1+v1iZWEVNOTyg2CknotAMcf3FJ5B+ebI=:", + "signature-input": "comm-b38rlmpnkote7pj6ixyf82o_ohj9xgqfozepxd7g9a8=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:Y29uZmln, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:MA, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Config:MHgxOWI5NGE3YmQzZGZiZDVhZjUyODY1MGZmNDkyODAyYmZkYjkwOWFmZjcwNjA4Njg0ZmVhY2UyNGYzZTA4YTAx, 6:Sequence-Sessions-Version:Mw, 7:Sequence-Sessions-Complete:dHJ1ZQ, 8:Sequence-Sessions-Signers-Count:Mw, 9:Sequence-Sessions-Signers-Bloom:MHgwMDAwMDAwMTIwMDAwNDA0MDAwMDAwMDQwMDAwMDAwMDAwMDAwMTAwMDAwMDAwMDAwMDAwMzEyMDAwMDAyMDEw\", comm-il29hgsyyy8ti2jdr8t-9zseofap4epv1ew-_yjtrxq=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-config\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-signers-bloom\" \"sequence-sessions-signers-count\" \"sequence-sessions-type\" \"sequence-sessions-version\");alg=\"hmac-sha256\";keyid=\"constant:ao\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "kgitQmrvR9rzg6zMlMxn7wKABUI/mFKkvgQifxHMnmtqmyzTxg", + "x-77-nzt-ray": "3f9f132583faaf24a5d7df69caa95d0a", + "x-77-pop": "newyorkUSNY" + }, + "body": "{\"checkpoint\":\"12\",\"threshold\":2,\"tree\":[[{\"address\":\"0xAda52e758d6e6dff5e9Cf5E944cF6D3f21387F07\",\"weight\":1},{\"address\":\"0x4eEa115852357FC87Ad9E1AD4A4554fECE1e7ff7\",\"weight\":1}],{\"address\":\"0xEb80E4dF62074285675DD0b2Bc360272847444cb\",\"weight\":1}]}" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 100, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"config update\\\"] }, { name: \\\"Sequence-Sessions-Wallet\\\", values: [\\\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\\\"] }, { name: \\\"Sequence-Sessions-Signer\\\", values: [\\\"0xAda52e758d6e6dff5e9Cf5E944cF6D3f21387F07\\\", \\\"0x4eEa115852357FC87Ad9E1AD4A4554fECE1e7ff7\\\", \\\"0xEb80E4dF62074285675DD0b2Bc360272847444cb\\\"] }, { name: \\\"Sequence-Sessions-Signature-Type\\\", values: [\\\"eip-712\\\", \\\"eth_sign\\\", \\\"erc-1271\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb691f205a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:33 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=6,cfOrigin;dur=604", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "kgw/4qBeSg1P1uuTNx/Hcxi0jSn8niL83kSvawnOBygh", + "x-77-nzt-ray": "331b5e0fe1a2ac95a5d7df699e05d310", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":false},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCIxSmZCQ0w3bnpaSzlxcmZ6OHFPQTRZVjEtR1JmLU1FLWIwenVxY3J3NmRFIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"1JfBCL7nzZK9qrfz8qOA4YV1-GRf-ME-b0zuqcrw6dE\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0xAda52e758d6e6dff5e9Cf5E944cF6D3f21387F07\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x512485d4e46a9758b98e84105d4d54755ce6645bd3ddbab0e850c968a8ef25ba\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0x7178a76bb537253e8f37c2c6a400af1a85b7f5314ae9f06bfeb1a93df275fca3\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"11\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0040000000000000000000000000000000411100008100000000003000002010\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJHMHo4V29aYm85allnbzFsa01GbHRLcm9uc2c3em16WmVuUFRiVkZNaldjIl0sImluZGV4IjoxfQ==\",\"node\":{\"id\":\"G0z8WoZbo9jYgo1lkMFltKronsg7zmzZenPTbVFMjWc\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0xAda52e758d6e6dff5e9Cf5E944cF6D3f21387F07\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0xa3c45186be4593770ea3414abbd86c5e10f5791b093372b2f8a9a25482e9f826\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0x2bafea1dbc6381b39848d226dccb906f23171edf7661149da26ab609d15a1339\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"10\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000000000000000000000000000400400100408100000001003000082010\"}]}},{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk3NDg3LCJnZGdUclNoR09vUjBzYlFXSVI0M1hEUE5JTmI3THExUFVMUEY4SU9RNy1ZIl0sImluZGV4IjoyfQ==\",\"node\":{\"id\":\"gdgTrShGOoR0sbQWIR43XDPNINb7Lq1PULPF8IOQ7-Y\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"text/plain\"},{\"name\":\"Sequence-Sessions-Signature-Type\",\"value\":\"eth_sign\"},{\"name\":\"Sequence-Sessions-Signer\",\"value\":\"0xAda52e758d6e6dff5e9Cf5E944cF6D3f21387F07\"},{\"name\":\"Sequence-Sessions-Subdigest\",\"value\":\"0x64825b014c15f38a1464da1a4fc9694584a59f052ae2065dac457935f488059b\"},{\"name\":\"Sequence-Sessions-Wallet\",\"value\":\"0x135769a58639b4Fa7d779a9df9B57A706FBCa816\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Witness\",\"value\":\"false\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"2\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0x19b94a7bd3dfbd5af528650ff492802bfdb909aff70608684feace24f3e08a01\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"12\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000000120000404000000040000000000000100000000000000312000002010\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "POST", + "url": "https://keymachine.sequence.app/rpc/Sessions/Tree", + "headers": { + "content-type": "application/json", + "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" + }, + "body": "{\"imageHash\":\"0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3\"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "alt-svc": "h3=\":443\"; ma=86400", + "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb6d89d7c45b-YYZ", + "connection": "keep-alive", + "content-length": "250", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:34 GMT", + "expires": "Thu, 01 Jan 1970 00:00:00 GMT", + "pragma": "no-cache", + "server": "cloudflare", + "strict-transport-security": "max-age=2592000; includeSubDomains", + "vary": "Origin", + "via": "1.1 google", + "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" + }, + "body": "{\"version\":3,\"tree\":{\"data\":\"0x53657175656e6365207265636f76657279206c6561663a0acd8b2b62b2ebe631c54ed0cc6f366794da6ff9210000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000000000\"}}" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 1, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"tree\\\"] }, { name: \\\"Sequence-Sessions-Tree\\\", values: [\\\"0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb6d6abe5a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:34 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=10,cfOrigin;dur=860", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "ktvGusekN26TEaTojilG7xgDP1HgCOxZxFZos1oIuzfe", + "x-77-nzt-ray": "331b5e0fe1a2ac95a5d7df69b184a939", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":true},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk0NjAwLCJEWDNTWFZQM2JRMHVJdUszemdFRG01bGhOcVdobFZ5b1FSMVBCTlg1RVZRIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"DX3SXVP3bQ0uIuK3zgEDm5lhNqWhlVyoQR1PBNX5EVQ\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"tree\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"0\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Tree\",\"value\":\"0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3\"},{\"name\":\"Sequence-Sessions-Complete\",\"value\":\"true\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/DX3SXVP3bQ0uIuK3zgEDm5lhNqWhlVyoQR1PBNX5EVQ", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:qM7Bkyg+mTuQK5e9pXMhkxjPMYMlBs9jzrZPiEyacok=:", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:35 GMT", + "sequence-sessions-complete": "true", + "sequence-sessions-major-version": "1", + "sequence-sessions-minor-version": "0", + "sequence-sessions-tree": "0xeef69774e1cb488a71f6d235c858fa564134ee7c3acda9ff116b6c9d42b3cee3", + "sequence-sessions-type": "tree", + "server": "CDN77-Turbo", + "signature": "comm-a11d_hsfetaqh-a7f91x_ponikv_nsvqfihjthrz-py=:L1yxUCmwZ4/xlamH02g3fxhZ07XoyZN+lDpJ7qqeCkU=:, comm-dx3sxvp3bq0uiuk3zgedm5lhnqwhlvyoqr1pbnx5evq=:X3r/xo7yfg6fvD9WoIqEpBJ+lQwDyPjyYEGnb27OcidyahZZuAdsX1OlKKmrrqRsWKcOoY7oMrCG7mPPKpNfexw=:", + "signature-input": "comm-a11d_hsfetaqh-a7f91x_ponikv_nsvqfihjthrz-py=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-tree\" \"sequence-sessions-type\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-dx3sxvp3bq0uiuk3zgedm5lhnqwhlvyoqr1pbnx5evq=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-complete\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-tree\" \"sequence-sessions-type\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:dHJlZQ, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:MA, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Tree:MHhlZWY2OTc3NGUxY2I0ODhhNzFmNmQyMzVjODU4ZmE1NjQxMzRlZTdjM2FjZGE5ZmYxMTZiNmM5ZDQyYjNjZWUz, 6:Sequence-Sessions-Complete:dHJ1ZQ\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "kuPrk/ujs35DBlekVTvr5jJ5pCrbdv4t5IEOkG3tG5DDuN1X9A", + "x-77-nzt-ray": "f03d06137f7e9036a6d7df697c5d623b", + "x-77-pop": "newyorkUSNY" + }, + "body": "{\"data\":\"0x53657175656e6365207265636f76657279206c6561663a0acd8b2b62b2ebe631c54ed0cc6f366794da6ff9210000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000000000\"}" + } + }, + { + "request": { + "method": "POST", + "url": "https://keymachine.sequence.app/rpc/Sessions/Payload", + "headers": { + "content-type": "application/json", + "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" + }, + "body": "{\"digest\":\"0xc78f3951686b7f16f39e25aea1fd5acc0e2177083c170b4c962be6cd45630576\"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "alt-svc": "h3=\":443\"; ma=86400", + "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb743cf0c45b-YYZ", + "connection": "keep-alive", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:35 GMT", + "expires": "Thu, 01 Jan 1970 00:00:00 GMT", + "pragma": "no-cache", + "server": "cloudflare", + "strict-transport-security": "max-age=2592000; includeSubDomains", + "transfer-encoding": "chunked", + "vary": "Origin", + "via": "1.1 google", + "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" + }, + "body": "{\"version\":3,\"payload\":{\"calls\":[{\"behaviorOnError\":\"revert\",\"data\":\"0xe2a53ed00000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c3359000000000000000000000000000000000000000000000000000000000007d1f4\",\"delegateCall\":false,\"gasLimit\":\"0\",\"onlyFallback\":false,\"to\":\"0x4B3eC67c5812543924C12a07140369C29077071e\",\"value\":\"0\"},{\"behaviorOnError\":\"ignore\",\"data\":\"0x095ea7b3000000000000000000000000fdb42a198a932c8d3b506ffa5e855bc4b348a712ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\"delegateCall\":false,\"gasLimit\":\"0\",\"onlyFallback\":false,\"to\":\"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359\",\"value\":\"0\"},{\"behaviorOnError\":\"ignore\",\"data\":\"0xe6bd720e0000000000000000000000000000000000000000000000000000000000002337000000000000000000000000000000000000000000000000000000000000000100000000000000000000000068104a4e8a0da125cfcbe4918dc44c157da5cf5600000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000030d40000000000000000000000000000000000000000000000000000000000000001000000000000000000000000858db1cbf6d09d447c96a11603189b49b2d1c219\",\"delegateCall\":false,\"gasLimit\":\"0\",\"onlyFallback\":false,\"to\":\"0xfdb42A198a932C8D3B506Ffa5e855bC4b348a712\",\"value\":\"0\"},{\"behaviorOnError\":\"ignore\",\"data\":\"0xb8dc491b0000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c335900000000000000000000000068104a4e8a0da125cfcbe4918dc44c157da5cf56\",\"delegateCall\":true,\"gasLimit\":\"0\",\"onlyFallback\":true,\"to\":\"0xBaE357CBAA04a68cbfD5a560Ab06C4E9A3328A90\",\"value\":\"0\"},{\"behaviorOnError\":\"ignore\",\"data\":\"0x095ea7b3000000000000000000000000fdb42a198a932c8d3b506ffa5e855bc4b348a7120000000000000000000000000000000000000000000000000000000000000000\",\"delegateCall\":false,\"gasLimit\":\"0\",\"onlyFallback\":false,\"to\":\"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359\",\"value\":\"0\"},{\"behaviorOnError\":\"ignore\",\"data\":\"0xd86930726aa24a63\",\"delegateCall\":false,\"gasLimit\":\"0\",\"onlyFallback\":false,\"to\":\"0x0000000000000000000000000000000000000004\",\"value\":\"0\"}],\"nonce\":\"0\",\"space\":\"0\",\"type\":\"call\"},\"wallet\":\"0x2D733fB8F1fB7669B0AFA980C11F1A6dd4F630F5\",\"chainID\":\"137\"}" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 1, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"payload\\\"] }, { name: \\\"Sequence-Sessions-Major-Version\\\", values: [\\\"1\\\"] }, { name: \\\"Sequence-Sessions-Minor-Version\\\", values: [\\\"2\\\"] }, { name: \\\"Sequence-Sessions-Payload\\\", values: [\\\"0xc78f3951686b7f16f39e25aea1fd5acc0e2177083c170b4c962be6cd45630576\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb7458815a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:35 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=6,cfOrigin;dur=160", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "kky/ENH9JInPTEZqn2ndbNgN0+M4+SAb9s4G6PCc3KZq", + "x-77-nzt-ray": "331b5e0fe1a2ac95a7d7df69514c8a04", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":true},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk2ODY0LCIyUUdGMXBQVThxVjlpSnRMWkFDdXN5akRRak9NX1pQOVA1aXBXSmZNWnZZIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"2QGF1pPU8qV9iJtLZACusyjDQjOM_ZP9P5ipWJfMZvY\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"payload\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"2\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Payload\",\"value\":\"0xc78f3951686b7f16f39e25aea1fd5acc0e2177083c170b4c962be6cd45630576\"},{\"name\":\"Sequence-Sessions-Address\",\"value\":\"0x2D733fB8F1fB7669B0AFA980C11F1A6dd4F630F5\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"137\"},{\"name\":\"Sequence-Sessions-Payload-Type\",\"value\":\"calls\"},{\"name\":\"Sequence-Sessions-Space\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Nonce\",\"value\":\"0\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/2QGF1pPU8qV9iJtLZACusyjDQjOM_ZP9P5ipWJfMZvY", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:y73VlX9cN7fjXboylCRs43zrkkidSnrYBsg/a62Xmxo=:", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:35 GMT", + "sequence-sessions-address": "0x2D733fB8F1fB7669B0AFA980C11F1A6dd4F630F5", + "sequence-sessions-chain-id": "137", + "sequence-sessions-major-version": "1", + "sequence-sessions-minor-version": "2", + "sequence-sessions-nonce": "0", + "sequence-sessions-payload": "0xc78f3951686b7f16f39e25aea1fd5acc0e2177083c170b4c962be6cd45630576", + "sequence-sessions-payload-type": "calls", + "sequence-sessions-space": "0", + "sequence-sessions-type": "payload", + "server": "CDN77-Turbo", + "signature": "comm-2qgf1ppu8qv9ijtlzacusyjdqjom_zp9p5ipwjfmzvy=:EprEWA/QS88utcQNaUU9Mm9m7CxrUBn65vOfEq8sqBAJ4v4GO9UAm+0SOkeF2uN3GwKLbavIAK1QPIPKONw58hw=:, comm-q_y3jmjfuj2dbzu0ixgerlile_uk1vh_fwegfphj-kg=:2VqYVrvsUIA5dRuJeyHv4D8jGMtRVpkfB29bRQaoOv8=:", + "signature-input": "comm-2qgf1ppu8qv9ijtlzacusyjdqjom_zp9p5ipwjfmzvy=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-nonce\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-space\" \"sequence-sessions-type\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:cGF5bG9hZA, 10:Sequence-Sessions-Nonce:MA, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:Mg, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Payload:MHhjNzhmMzk1MTY4NmI3ZjE2ZjM5ZTI1YWVhMWZkNWFjYzBlMjE3NzA4M2MxNzBiNGM5NjJiZTZjZDQ1NjMwNTc2, 6:Sequence-Sessions-Address:MHgyRDczM2ZCOEYxZkI3NjY5QjBBRkE5ODBDMTFGMUE2ZGQ0RjYzMEY1, 7:Sequence-Sessions-Chain-ID:MTM3, 8:Sequence-Sessions-Payload-Type:Y2FsbHM, 9:Sequence-Sessions-Space:MA\", comm-q_y3jmjfuj2dbzu0ixgerlile_uk1vh_fwegfphj-kg=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-nonce\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-space\" \"sequence-sessions-type\");alg=\"hmac-sha256\";keyid=\"constant:ao\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "kvXz0wEERDnzpE02FEx7DASf9Vn8KQ1OK/HUGQ/AKAKRl8ru1A", + "x-77-nzt-ray": "3f9f132582d25d8aa7d7df696573b718", + "x-77-pop": "newyorkUSNY" + }, + "body": "[{\"behaviorOnError\":\"revert\",\"data\":\"0xe2a53ed00000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c3359000000000000000000000000000000000000000000000000000000000007d1f4\",\"delegateCall\":false,\"gasLimit\":\"0\",\"onlyFallback\":false,\"to\":\"0x4B3eC67c5812543924C12a07140369C29077071e\",\"value\":\"0\"},{\"behaviorOnError\":\"ignore\",\"data\":\"0x095ea7b3000000000000000000000000fdb42a198a932c8d3b506ffa5e855bc4b348a712ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\"delegateCall\":false,\"gasLimit\":\"0\",\"onlyFallback\":false,\"to\":\"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359\",\"value\":\"0\"},{\"behaviorOnError\":\"ignore\",\"data\":\"0xe6bd720e0000000000000000000000000000000000000000000000000000000000002337000000000000000000000000000000000000000000000000000000000000000100000000000000000000000068104a4e8a0da125cfcbe4918dc44c157da5cf5600000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000030d40000000000000000000000000000000000000000000000000000000000000001000000000000000000000000858db1cbf6d09d447c96a11603189b49b2d1c219\",\"delegateCall\":false,\"gasLimit\":\"0\",\"onlyFallback\":false,\"to\":\"0xfdb42A198a932C8D3B506Ffa5e855bC4b348a712\",\"value\":\"0\"},{\"behaviorOnError\":\"ignore\",\"data\":\"0xb8dc491b0000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c335900000000000000000000000068104a4e8a0da125cfcbe4918dc44c157da5cf56\",\"delegateCall\":true,\"gasLimit\":\"0\",\"onlyFallback\":true,\"to\":\"0xBaE357CBAA04a68cbfD5a560Ab06C4E9A3328A90\",\"value\":\"0\"},{\"behaviorOnError\":\"ignore\",\"data\":\"0x095ea7b3000000000000000000000000fdb42a198a932c8d3b506ffa5e855bc4b348a7120000000000000000000000000000000000000000000000000000000000000000\",\"delegateCall\":false,\"gasLimit\":\"0\",\"onlyFallback\":false,\"to\":\"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359\",\"value\":\"0\"},{\"behaviorOnError\":\"ignore\",\"data\":\"0xd86930726aa24a63\",\"delegateCall\":false,\"gasLimit\":\"0\",\"onlyFallback\":false,\"to\":\"0x0000000000000000000000000000000000000004\",\"value\":\"0\"}]" + } + }, + { + "request": { + "method": "POST", + "url": "https://keymachine.sequence.app/rpc/Sessions/Payload", + "headers": { + "content-type": "application/json", + "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" + }, + "body": "{\"digest\":\"0x3a841ba3163a7a19cd168373df1144d38130b2f46b8d6eac956127f06fffe4f4\"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "alt-svc": "h3=\":443\"; ma=86400", + "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb76eccec45b-YYZ", + "connection": "keep-alive", + "content-length": "531", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:35 GMT", + "expires": "Thu, 01 Jan 1970 00:00:00 GMT", + "pragma": "no-cache", + "server": "cloudflare", + "strict-transport-security": "max-age=2592000; includeSubDomains", + "vary": "Origin", + "via": "1.1 google", + "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" + }, + "body": "{\"version\":3,\"payload\":{\"message\":\"0x7b22616374696f6e223a22636f6e73656e742d746f2d62652d706172742d6f662d77616c6c6574222c2277616c6c6574223a22307844323437443562313932353732393438634365643444624131633634386262366662666539306543222c227369676e6572223a22307830643662303266623238306533353336303364356538613462646136623432366135633762343138222c2274696d657374616d70223a313736363038333632303439382c227369676e65724b696e64223a226c6f63616c2d646576696365227d\",\"type\":\"message\"},\"wallet\":\"0xD247D5b192572948cCed4DbA1c648bb6fbfe90eC\",\"chainID\":\"0\"}" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 1, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"payload\\\"] }, { name: \\\"Sequence-Sessions-Major-Version\\\", values: [\\\"1\\\"] }, { name: \\\"Sequence-Sessions-Minor-Version\\\", values: [\\\"2\\\"] }, { name: \\\"Sequence-Sessions-Payload\\\", values: [\\\"0x3a841ba3163a7a19cd168373df1144d38130b2f46b8d6eac956127f06fffe4f4\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb770ab35a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:35 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=8,cfOrigin;dur=374", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "kqX6v+4iErm5YUpn1+BYcdVbqzpv/72vAxv/CecuStWy", + "x-77-nzt-ray": "331b5e0fe1a2ac95a7d7df691e56231e", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":true},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk2ODY0LCIzSGYxUFdGaF9ZdzRhdTBDWjhBYU14NldCX0ZWQ1Q5UkJIMy1OOW1wVlJnIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"3Hf1PWFh_Yw4au0CZ8AaMx6WB_FVCT9RBH3-N9mpVRg\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"payload\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"2\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Payload\",\"value\":\"0x3a841ba3163a7a19cd168373df1144d38130b2f46b8d6eac956127f06fffe4f4\"},{\"name\":\"Sequence-Sessions-Address\",\"value\":\"0xD247D5b192572948cCed4DbA1c648bb6fbfe90eC\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Payload-Type\",\"value\":\"message\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/3Hf1PWFh_Yw4au0CZ8AaMx6WB_FVCT9RBH3-N9mpVRg", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:A1YMLxj1j3HT9nFpONVt2VC77V87xprIq9H/yLXq9j8=:", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:36 GMT", + "sequence-sessions-address": "0xD247D5b192572948cCed4DbA1c648bb6fbfe90eC", + "sequence-sessions-chain-id": "0", + "sequence-sessions-major-version": "1", + "sequence-sessions-minor-version": "2", + "sequence-sessions-payload": "0x3a841ba3163a7a19cd168373df1144d38130b2f46b8d6eac956127f06fffe4f4", + "sequence-sessions-payload-type": "message", + "sequence-sessions-type": "payload", + "server": "CDN77-Turbo", + "signature": "comm-3hf1pwfh_yw4au0cz8aamx6wb_fvct9rbh3-n9mpvrg=:790rl5TXvVj20BC8LJ98yNjsx4ejtsrgBSFEZm/obn9h5lK8UPI5ztHX+WA5psYNPhxi1XvTNfEhwZmyz9pG5xs=:, comm-fdqefal8-0i-shhtdty8o5mtdosf0h_yvt9gipd4ziu=:UoLz2MjgY7q35/CBTDrfiRRMA8ieQKTi5ATCLXg4YF0=:", + "signature-input": "comm-3hf1pwfh_yw4au0cz8aamx6wb_fvct9rbh3-n9mpvrg=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-type\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:cGF5bG9hZA, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:Mg, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Payload:MHgzYTg0MWJhMzE2M2E3YTE5Y2QxNjgzNzNkZjExNDRkMzgxMzBiMmY0NmI4ZDZlYWM5NTYxMjdmMDZmZmZlNGY0, 6:Sequence-Sessions-Address:MHhEMjQ3RDViMTkyNTcyOTQ4Y0NlZDREYkExYzY0OGJiNmZiZmU5MGVD, 7:Sequence-Sessions-Chain-ID:MA, 8:Sequence-Sessions-Payload-Type:bWVzc2FnZQ\", comm-fdqefal8-0i-shhtdty8o5mtdosf0h_yvt9gipd4ziu=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-type\");alg=\"hmac-sha256\";keyid=\"constant:ao\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "kon1rwdQeQ1znuXHKI8UDqFy/lgo04k2GpXNPxF/7tZEmWehhw", + "x-77-nzt-ray": "8705ec34712958dfa8d7df69d7f8d003", + "x-77-pop": "newyorkUSNY" + }, + "body": "\"0x7b22616374696f6e223a22636f6e73656e742d746f2d62652d706172742d6f662d77616c6c6574222c2277616c6c6574223a22307844323437443562313932353732393438634365643444624131633634386262366662666539306543222c227369676e6572223a22307830643662303266623238306533353336303364356538613462646136623432366135633762343138222c2274696d657374616d70223a313736363038333632303439382c227369676e65724b696e64223a226c6f63616c2d646576696365227d\"" + } + }, + { + "request": { + "method": "POST", + "url": "https://keymachine.sequence.app/rpc/Sessions/Payload", + "headers": { + "content-type": "application/json", + "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" + }, + "body": "{\"digest\":\"0xcae631660ffa90bddc5e9b4fa9c11692a53062a61640fb958f3f2959d22fe54b\"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "alt-svc": "h3=\":443\"; ma=86400", + "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb7b6a0cc45b-YYZ", + "connection": "keep-alive", + "content-length": "197", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:36 GMT", + "expires": "Thu, 01 Jan 1970 00:00:00 GMT", + "pragma": "no-cache", + "server": "cloudflare", + "strict-transport-security": "max-age=2592000; includeSubDomains", + "vary": "Origin", + "via": "1.1 google", + "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" + }, + "body": "{\"version\":3,\"payload\":{\"imageHash\":\"0xe25d51d076cbb34dd4f68b26421724e2f6f7be49dfd913dae3d0c0fa05e86d53\",\"type\":\"config-update\"},\"wallet\":\"0xC018D4EB963bDe912f3a440554Af070c7C0BF313\",\"chainID\":\"0\"}" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 1, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"payload\\\"] }, { name: \\\"Sequence-Sessions-Major-Version\\\", values: [\\\"1\\\"] }, { name: \\\"Sequence-Sessions-Minor-Version\\\", values: [\\\"2\\\"] }, { name: \\\"Sequence-Sessions-Payload\\\", values: [\\\"0xcae631660ffa90bddc5e9b4fa9c11692a53062a61640fb958f3f2959d22fe54b\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb7b8e245a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:36 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=7,cfOrigin;dur=171", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "kvQQ+5s8UFxvEgTO6p+xw8zMfYsSlUhNC3eI+QhmYpqk", + "x-77-nzt-ray": "331b5e0fe1a2ac95a8d7df69c0c4840d", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":true},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk2ODc3LCJUd20zU3NzTzlITTJSOG9CSkp0V1JwSUFaeThHemFjcFE5dnBFWUdLWnVFIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"Twm3SssO9HM2R8oBJJtWRpIAZy8GzacpQ9vpEYGKZuE\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"payload\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"2\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Payload\",\"value\":\"0xcae631660ffa90bddc5e9b4fa9c11692a53062a61640fb958f3f2959d22fe54b\"},{\"name\":\"Sequence-Sessions-Address\",\"value\":\"0xC018D4EB963bDe912f3a440554Af070c7C0BF313\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"0\"},{\"name\":\"Sequence-Sessions-Payload-Type\",\"value\":\"config update\"},{\"name\":\"Sequence-Sessions-To-Config\",\"value\":\"0xe25d51d076cbb34dd4f68b26421724e2f6f7be49dfd913dae3d0c0fa05e86d53\"},{\"name\":\"Sequence-Sessions-To-Version\",\"value\":\"3\"},{\"name\":\"Sequence-Sessions-To-Checkpoint\",\"value\":\"4\"},{\"name\":\"Sequence-Sessions-To-Config-Complete\",\"value\":\"true\"},{\"name\":\"Sequence-Sessions-To-Signers-Count\",\"value\":\"7\"},{\"name\":\"Sequence-Sessions-To-Signers-Bloom\",\"value\":\"0x0000008008000100802082840208090100000023400000140002000500020409\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/Twm3SssO9HM2R8oBJJtWRpIAZy8GzacpQ9vpEYGKZuE", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:sDSB/zhiOWptyMrGP5eMgYGCSjAG+AEX5ek1NCPhKTI=:", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:36 GMT", + "sequence-sessions-address": "0xC018D4EB963bDe912f3a440554Af070c7C0BF313", + "sequence-sessions-chain-id": "0", + "sequence-sessions-major-version": "1", + "sequence-sessions-minor-version": "2", + "sequence-sessions-payload": "0xcae631660ffa90bddc5e9b4fa9c11692a53062a61640fb958f3f2959d22fe54b", + "sequence-sessions-payload-type": "config update", + "sequence-sessions-to-checkpoint": "4", + "sequence-sessions-to-config": "0xe25d51d076cbb34dd4f68b26421724e2f6f7be49dfd913dae3d0c0fa05e86d53", + "sequence-sessions-to-config-complete": "true", + "sequence-sessions-to-signers-bloom": "0x0000008008000100802082840208090100000023400000140002000500020409", + "sequence-sessions-to-signers-count": "7", + "sequence-sessions-to-version": "3", + "sequence-sessions-type": "payload", + "server": "CDN77-Turbo", + "signature": "comm-5pnsazaxuuh8mktavldjkji_92nknngtzzri_wuxhia=:+TfdPyPtERmEfeW5geLa7tkcJ7ZcdKsRkDNgBG3oJos=:, comm-twm3ssso9hm2r8objjtwrpiazy8gzacpq9vpeygkzue=:UrFROJgchVKSsHYlhqdsLasc82cIbkqkHd7Ia9u+h5kCiuVuSHn/DGt7Dp4+fTxAYJlgr3uDy8yEK6FDPP0FVRs=:", + "signature-input": "comm-5pnsazaxuuh8mktavldjkji_92nknngtzzri_wuxhia=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-to-version\" \"sequence-sessions-type\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-twm3ssso9hm2r8objjtwrpiazy8gzacpq9vpeygkzue=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-to-checkpoint\" \"sequence-sessions-to-config\" \"sequence-sessions-to-config-complete\" \"sequence-sessions-to-signers-bloom\" \"sequence-sessions-to-signers-count\" \"sequence-sessions-to-version\" \"sequence-sessions-type\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:cGF5bG9hZA, 10:Sequence-Sessions-To-Version:Mw, 11:Sequence-Sessions-To-Checkpoint:NA, 12:Sequence-Sessions-To-Config-Complete:dHJ1ZQ, 13:Sequence-Sessions-To-Signers-Count:Nw, 14:Sequence-Sessions-To-Signers-Bloom:MHgwMDAwMDA4MDA4MDAwMTAwODAyMDgyODQwMjA4MDkwMTAwMDAwMDIzNDAwMDAwMTQwMDAyMDAwNTAwMDIwNDA5, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:Mg, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Payload:MHhjYWU2MzE2NjBmZmE5MGJkZGM1ZTliNGZhOWMxMTY5MmE1MzA2MmE2MTY0MGZiOTU4ZjNmMjk1OWQyMmZlNTRi, 6:Sequence-Sessions-Address:MHhDMDE4RDRFQjk2M2JEZTkxMmYzYTQ0MDU1NEFmMDcwYzdDMEJGMzEz, 7:Sequence-Sessions-Chain-ID:MA, 8:Sequence-Sessions-Payload-Type:Y29uZmlnIHVwZGF0ZQ, 9:Sequence-Sessions-To-Config:MHhlMjVkNTFkMDc2Y2JiMzRkZDRmNjhiMjY0MjE3MjRlMmY2ZjdiZTQ5ZGZkOTEzZGFlM2QwYzBmYTA1ZTg2ZDUz\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "kgtQOfxtm1ATCfanlgWwAEqGnZLSUhWuvzXjo6pK4TdefM3aSw", + "x-77-nzt-ray": "3f9f132578e09bbba8d7df6948a99d21", + "x-77-pop": "newyorkUSNY" + }, + "body": "{\"checkpoint\":\"4\",\"threshold\":2,\"tree\":[[[{\"address\":\"0xF223a6E051B743BfEC993e17555d31cee5ac9fE9\",\"weight\":1},{\"address\":\"0xB34EFD7629C431d2caDf69d9aFDEA984Ff6EC86b\",\"weight\":1}],{\"threshold\":1,\"tree\":[{\"address\":\"0x26f3D30F41FA897309Ae804A2AFf15CEb1dA5742\",\"weight\":1},{\"address\":\"0x007a47e6BF40C1e0ed5c01aE42fDC75879140bc4\",\"weight\":1}],\"weight\":1}],[{\"threshold\":2,\"tree\":[{\"address\":\"0x00000000000030Bcc832F7d657f50D6Be35C92b3\",\"imageHash\":\"0x507c907dd7fbb034d69ea4287e7a52c9d689d9795ac4d8ef38231bb87e38feec\",\"weight\":1},{\"threshold\":1,\"tree\":[{\"address\":\"0xF6Bc87F5F2edAdb66737E32D37b46423901dfEF1\",\"weight\":1},{\"address\":\"0x007a47e6BF40C1e0ed5c01aE42fDC75879140bc4\",\"weight\":1}],\"weight\":1}],\"weight\":255},{\"address\":\"0x000000000000AB36D17eB1150116371520565205\",\"imageHash\":\"0x5b59a76df21cc0aebe78bafe7d705f9dffb21d5ccf633d0f53a8cc7dd35730b7\",\"weight\":255}]]}" + } + }, + { + "request": { + "method": "POST", + "url": "https://keymachine.sequence.app/rpc/Sessions/Payload", + "headers": { + "content-type": "application/json", + "webrpc": "webrpc@v0.22.1;gen-typescript@v0.16.2;sessions@v0.0.1" + }, + "body": "{\"digest\":\"0xcd3c291e0939f029aaa4b4f292d5d2b2ce43baf98046d9abc2a3e8284b253432\"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "alt-svc": "h3=\":443\"; ma=86400", + "cache-control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb7e3a5cc45b-YYZ", + "connection": "keep-alive", + "content-length": "187", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:36 GMT", + "expires": "Thu, 01 Jan 1970 00:00:00 GMT", + "pragma": "no-cache", + "server": "cloudflare", + "strict-transport-security": "max-age=2592000; includeSubDomains", + "vary": "Origin", + "via": "1.1 google", + "webrpc": "webrpc@v0.22.1;gen-golang@v0.17.0;key-machine@v0.0.1" + }, + "body": "{\"version\":3,\"payload\":{\"digest\":\"0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef\",\"type\":\"digest\"},\"wallet\":\"0x1111111111111111111111111111111111111111\",\"chainID\":\"1\"}" + } + }, + { + "request": { + "method": "POST", + "url": "https://arweave.net/graphql", + "headers": { + "content-type": "application/json" + }, + "body": "{\"query\":\"\\n query {\\n transactions(sort: HEIGHT_DESC, first: 1, tags: [{ name: \\\"Sequence-Sessions-Type\\\", values: [\\\"payload\\\"] }, { name: \\\"Sequence-Sessions-Major-Version\\\", values: [\\\"1\\\"] }, { name: \\\"Sequence-Sessions-Minor-Version\\\", values: [\\\"2\\\"] }, { name: \\\"Sequence-Sessions-Payload\\\", values: [\\\"0xcd3c291e0939f029aaa4b4f292d5d2b2ce43baf98046d9abc2a3e8284b253432\\\"] }], owners: [\\\"AZ6R2mG8zxW9q7--iZXGrBknjegHoPzmG5IG-nxvMaM\\\"]) {\\n pageInfo {\\n hasNextPage\\n }\\n edges {\\n cursor\\n node {\\n id\\n tags {\\n name\\n value\\n }\\n }\\n }\\n }\\n }\\n \"}" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "cache-control": "no-store", + "cf-cache-status": "DYNAMIC", + "cf-ray": "9eccfb7e48075a46-IAD", + "connection": "keep-alive", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:36 GMT", + "server": "CDN77-Turbo", + "server-timing": "cfCacheStatus;desc=\"DYNAMIC\", cfEdge;dur=4,cfOrigin;dur=188", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding, Origin", + "x-77-cache": "MISS", + "x-77-nzt": "khTWKji6AMQLths5NYi/adhHHOlYkr5Pbz13CeYGRmzp", + "x-77-nzt-ray": "331b5e0fe1a2ac95a8d7df69e27a3228", + "x-77-pop": "montrealCAQC", + "x-upstream-url": "https://arweave-search.goldsky.com/graphql" + }, + "body": "{\"data\":{\"transactions\":{\"pageInfo\":{\"hasNextPage\":true},\"edges\":[{\"cursor\":\"eyJzZWFyY2hfYWZ0ZXIiOlsxODk2ODY3LCJsVmgyQUxiTTNrTHlKcDdtb0pBNUk4d1Y1Y1VsY25LeUI1bnJGSVNPdkVvIl0sImluZGV4IjowfQ==\",\"node\":{\"id\":\"lVh2ALbM3kLyJp7moJA5I8wV5cUlcnKyB5nrFISOvEo\",\"tags\":[{\"name\":\"Sequence-Sessions-Type\",\"value\":\"payload\"},{\"name\":\"Sequence-Sessions-Major-Version\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Minor-Version\",\"value\":\"2\"},{\"name\":\"Content-Type\",\"value\":\"application/json\"},{\"name\":\"Sequence-Sessions-Payload\",\"value\":\"0xcd3c291e0939f029aaa4b4f292d5d2b2ce43baf98046d9abc2a3e8284b253432\"},{\"name\":\"Sequence-Sessions-Address\",\"value\":\"0x1111111111111111111111111111111111111111\"},{\"name\":\"Sequence-Sessions-Chain-ID\",\"value\":\"1\"},{\"name\":\"Sequence-Sessions-Payload-Type\",\"value\":\"digest\"},{\"name\":\"Sequence-Sessions-Digest\",\"value\":\"0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef\"}]}}]}}}\n" + } + }, + { + "request": { + "method": "GET", + "url": "https://arweave.net/lVh2ALbM3kLyJp7moJA5I8wV5cUlcnKyB5nrFISOvEo", + "headers": {}, + "body": "" + }, + "response": { + "status": 200, + "statusText": "OK", + "headers": { + "access-control-allow-headers": "*", + "access-control-allow-methods": "GET, POST, HEAD, OPTIONS", + "access-control-allow-origin": "*", + "access-control-expose-headers": "X-ArNS-Resolved-Id, X-ArNS-TTL-Seconds, X-ArNS-Process-Id, X-ArNS-Undername-Limit, X-ArNS-Record-Index", + "ao-body-key": "data", + "ao-types": "status=\"integer\"", + "connection": "keep-alive", + "content-digest": "sha-256=:dCNOmK/nSY+12vHzasLXiswzlGT5UHA7jAGYkvmCuQs=:", + "content-encoding": "gzip", + "content-type": "application/json", + "date": "Wed, 15 Apr 2026 18:23:37 GMT", + "sequence-sessions-address": "0x1111111111111111111111111111111111111111", + "sequence-sessions-chain-id": "1", + "sequence-sessions-digest": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", + "sequence-sessions-major-version": "1", + "sequence-sessions-minor-version": "2", + "sequence-sessions-payload": "0xcd3c291e0939f029aaa4b4f292d5d2b2ce43baf98046d9abc2a3e8284b253432", + "sequence-sessions-payload-type": "digest", + "sequence-sessions-type": "payload", + "server": "CDN77-Turbo", + "signature": "comm-_jzqikhymam_lprvj7ycaaktslaa0sbqc7czeyhvbhg=:2nut0qs1ieXwfVSjm9pppo+WVEbavBKS+FfI0s0auYI=:, comm-lvh2albm3klyjp7moja5i8wv5culcnkyb5nrfisoveo=:ZINUHXdgCdrtP9TmAanj/BS4HaadRFWWaferNtdEEKtriq3gi/My1CCCpPc5MI7FwVsOHpYmIrTP3ZRIAhG+xBs=:", + "signature-input": "comm-_jzqikhymam_lprvj7ycaaktslaa0sbqc7czeyhvbhg=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-digest\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-type\");alg=\"hmac-sha256\";keyid=\"constant:ao\", comm-lvh2albm3klyjp7moja5i8wv5culcnkyb5nrfisoveo=(\"ao-body-key\" \"content-digest\" \"content-type\" \"sequence-sessions-address\" \"sequence-sessions-chain-id\" \"sequence-sessions-digest\" \"sequence-sessions-major-version\" \"sequence-sessions-minor-version\" \"sequence-sessions-payload\" \"sequence-sessions-payload-type\" \"sequence-sessions-type\");alg=\"ans104@1.0/ethereum\";keyid=\"publickey:BCwPuj81bBSAUGJtslnOolGAOVnjkrC-I2cb6NcN_LafVFnmIz_BbUxSWkYflq6AyqWMD2xcLZkWrgMij33mDD0\";bundle=\"false\";original-tags=\"1:Sequence-Sessions-Type:cGF5bG9hZA, 2:Sequence-Sessions-Major-Version:MQ, 3:Sequence-Sessions-Minor-Version:Mg, 4:Content-Type:YXBwbGljYXRpb24vanNvbg, 5:Sequence-Sessions-Payload:MHhjZDNjMjkxZTA5MzlmMDI5YWFhNGI0ZjI5MmQ1ZDJiMmNlNDNiYWY5ODA0NmQ5YWJjMmEzZTgyODRiMjUzNDMy, 6:Sequence-Sessions-Address:MHgxMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTEx, 7:Sequence-Sessions-Chain-ID:MQ, 8:Sequence-Sessions-Payload-Type:ZGlnZXN0, 9:Sequence-Sessions-Digest:MHhkZWFkYmVlZmRlYWRiZWVmZGVhZGJlZWZkZWFkYmVlZmRlYWRiZWVmZGVhZGJlZWZkZWFkYmVlZmRlYWRiZWVm\"", + "status": "200", + "transfer-encoding": "chunked", + "vary": "Accept-Encoding", + "x-77-cache": "MISS", + "x-77-nzt": "knv9xlzlzyl+8BAqY6Rzi6OmidE58jsyXeJYUQRAB8zDDWWidQ", + "x-77-nzt-ray": "f03d061342437487a9d7df69cd5e1901", + "x-77-pop": "newyorkUSNY" + }, + "body": "null" + } + } +] \ No newline at end of file diff --git a/packages/wallet/core/test/state/debug.test.ts b/packages/wallet/core/test/state/debug.test.ts index 4824297ab..6882f45a2 100644 --- a/packages/wallet/core/test/state/debug.test.ts +++ b/packages/wallet/core/test/state/debug.test.ts @@ -5,7 +5,6 @@ import { multiplex } from '../../src/state/debug.js' // Test data const TEST_ADDRESS = Address.from('0x1234567890123456789012345678901234567890') -const TEST_HEX = Hex.from('0xabcdef123456') const TEST_UINT8ARRAY = new Uint8Array([171, 205, 239, 18, 52, 86]) describe('State Debug', () => { @@ -241,13 +240,13 @@ describe('State Debug', () => { } const complexRef: ComplexInterface = { - async complexMethod(data) { + async complexMethod() { return 'complex-ref' }, } const complexCand: ComplexInterface = { - async complexMethod(data) { + async complexMethod() { return 'complex-cand' }, } diff --git a/packages/wallet/core/test/wallet-fee-options.test.ts b/packages/wallet/core/test/wallet-fee-options.test.ts new file mode 100644 index 000000000..633f47672 --- /dev/null +++ b/packages/wallet/core/test/wallet-fee-options.test.ts @@ -0,0 +1,106 @@ +import { describe, expect, it, vi } from 'vitest' +import { AbiFunction, Address, Bytes, Hex, Provider } from 'ox' + +import { Constants, Config, Context, Payload } from '../../primitives/src/index.js' +import { State, Wallet } from '../src/index.js' + +const SIGNER = '0x1234567890123456789012345678901234567890' as Address.Address +const TARGET = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd' as Address.Address +const FEE_OPTIONS_STUB_SIGNATURE = + '0x040001711fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0' as Hex.Hex + +const configuration: Config.Config = { + threshold: 1n, + checkpoint: 0n, + topology: { type: 'signer', address: SIGNER, weight: 1n }, +} + +const call: Payload.Call = { + to: TARGET, + value: 0n, + data: '0x', + gasLimit: 0n, + delegateCall: false, + onlyFallback: false, + behaviorOnError: 'revert', +} + +const payload: Payload.Calls = { + type: 'call', + space: 0n, + nonce: 0n, + calls: [call], +} + +function providerFor(options: { deployed: boolean; imageHash: Hex.Hex }): Provider.Provider { + return { + request: vi.fn(async (request: { method: string; params?: unknown[] }) => { + switch (request.method) { + case 'eth_chainId': + return '0x1' + + case 'eth_getCode': + return options.deployed ? '0x1234' : '0x' + + case 'eth_call': { + const rpcCall = request.params?.[0] as { data?: Hex.Hex } | undefined + + if (rpcCall?.data === AbiFunction.encodeData(Constants.GET_IMPLEMENTATION)) { + return options.deployed ? Hex.padLeft(Context.Dev2.stage2, 32) : '0x' + } + + if (rpcCall?.data === AbiFunction.encodeData(Constants.IMAGE_HASH)) { + return options.imageHash + } + + return '0x' + } + + default: + throw new Error(`Unexpected RPC method: ${request.method}`) + } + }), + } as unknown as Provider.Provider +} + +async function createWallet() { + const stateProvider = new State.Local.Provider() + const wallet = await Wallet.fromConfiguration(configuration, { stateProvider, context: Context.Dev2 }) + const imageHash = Hex.from(Config.hashConfiguration(configuration)) + + return { wallet, imageHash } +} + +describe('Wallet.buildFeeOptionsTransaction', () => { + it('targets the wallet execute method when the wallet is deployed', async () => { + const { wallet, imageHash } = await createWallet() + const transaction = await wallet.buildFeeOptionsTransaction(providerFor({ deployed: true, imageHash }), payload) + + const expectedData = AbiFunction.encodeData(Constants.EXECUTE, [ + Bytes.toHex(Payload.encode(payload)), + FEE_OPTIONS_STUB_SIGNATURE, + ]) + + expect(Address.isEqual(transaction.to, wallet.address)).toBe(true) + expect(transaction.data).toBe(expectedData) + }) + + it('targets the guest module and prefixes deployment when the wallet is undeployed', async () => { + const { wallet, imageHash } = await createWallet() + const deploy = await wallet.buildDeployTransaction() + const transaction = await wallet.buildFeeOptionsTransaction(providerFor({ deployed: false, imageHash }), payload) + const decoded = Payload.decode(Bytes.fromHex(transaction.data)) + + const expectedExecuteData = AbiFunction.encodeData(Constants.EXECUTE, [ + Bytes.toHex(Payload.encode(payload)), + FEE_OPTIONS_STUB_SIGNATURE, + ]) + + expect(Address.isEqual(transaction.to, Constants.DefaultGuestAddress)).toBe(true) + expect(decoded.calls).toHaveLength(2) + expect(Address.isEqual(decoded.calls[0]!.to, deploy.to)).toBe(true) + expect(decoded.calls[0]!.data).toBe(deploy.data) + expect(Address.isEqual(decoded.calls[1]!.to, wallet.address)).toBe(true) + expect(decoded.calls[1]!.data).toBe(expectedExecuteData) + }) +}) diff --git a/packages/wallet/core/vitest.config.ts b/packages/wallet/core/vitest.config.ts deleted file mode 100644 index 0b2f7c6c7..000000000 --- a/packages/wallet/core/vitest.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - test: { - poolOptions: { - singleThread: true, - }, - }, -}) diff --git a/packages/wallet/dapp-client/CHANGELOG.md b/packages/wallet/dapp-client/CHANGELOG.md new file mode 100644 index 000000000..7087e36b9 --- /dev/null +++ b/packages/wallet/dapp-client/CHANGELOG.md @@ -0,0 +1,410 @@ +# @0xsequence/dapp-client + +## 3.1.1 + +### Patch Changes + +- WDK fix +- Updated dependencies + - @0xsequence/guard@3.0.12 + - @0xsequence/relayer@3.1.1 + - @0xsequence/wallet-core@3.0.12 + - @0xsequence/wallet-primitives@3.0.12 + +## 3.1.0 + +### Minor Changes + +- 55121af: Add `isSponsored` for explicit sponsorship checks. + + `DappClient.isSponsored(chainId, transactions)` and + `ChainSessionManager.isSponsored(calls)` return `true` only when the relayer's + `/FeeOptions` endpoint explicitly reports sponsorship. Any error, network + failure, or absence of sponsorship returns `false`, so a `true` result is + always safe to surface as "free gas" in UI. + + Prefer this over inferring sponsorship from an empty `getFeeOptions` array — a + swallowed `/FeeOptions` error also produces an empty array, so the inference + can misclassify a failed quote as a real subsidy. The new method uses the + positive `sponsored: boolean` signal from `@0xsequence/relayer`'s widened + `feeOptions` return. + + `getFeeOptions` is unchanged. + +### Patch Changes + +- Fix for relayer sponsored fees +- Updated dependencies +- Updated dependencies [55121af] + - @0xsequence/guard@3.0.11 + - @0xsequence/relayer@3.1.0 + - @0xsequence/wallet-core@3.0.11 + - @0xsequence/wallet-primitives@3.0.11 + +## 3.0.10 + +### Patch Changes + +- Minor network updates, relayer interface +- Updated dependencies + - @0xsequence/guard@3.0.10 + - @0xsequence/relayer@3.0.10 + - @0xsequence/wallet-core@3.0.10 + - @0xsequence/wallet-primitives@3.0.10 + +## 3.0.9 + +### Patch Changes + +- Fee options fixes +- Updated dependencies + - @0xsequence/guard@3.0.9 + - @0xsequence/relayer@3.0.9 + - @0xsequence/wallet-core@3.0.9 + - @0xsequence/wallet-primitives@3.0.9 + +## 3.0.8 + +### Patch Changes + +- Bug fix for relayer fee options handling +- Updated dependencies + - @0xsequence/guard@3.0.8 + - @0xsequence/relayer@3.0.8 + - @0xsequence/wallet-core@3.0.8 + - @0xsequence/wallet-primitives@3.0.8 + +## 3.0.7 + +### Patch Changes + +- Minor bug fixes +- Updated dependencies + - @0xsequence/guard@3.0.7 + - @0xsequence/relayer@3.0.7 + - @0xsequence/wallet-core@3.0.7 + - @0xsequence/wallet-primitives@3.0.7 + +## 3.0.6 + +### Patch Changes + +- userdata upgrade, arweave support +- Updated dependencies + - @0xsequence/guard@3.0.6 + - @0xsequence/relayer@3.0.6 + - @0xsequence/wallet-core@3.0.6 + - @0xsequence/wallet-primitives@3.0.6 + +## 3.0.5 + +### Patch Changes + +- Account federation support +- Updated dependencies + - @0xsequence/guard@3.0.5 + - @0xsequence/relayer@3.0.5 + - @0xsequence/wallet-core@3.0.5 + - @0xsequence/wallet-primitives@3.0.5 + +## 3.0.4 + +### Patch Changes + +- id-token login support +- Updated dependencies + - @0xsequence/guard@3.0.4 + - @0xsequence/relayer@3.0.4 + - @0xsequence/wallet-core@3.0.4 + - @0xsequence/wallet-primitives@3.0.4 + +## 3.0.3 + +### Patch Changes + +- 3.0.3 +- Updated dependencies + - @0xsequence/guard@3.0.3 + - @0xsequence/relayer@3.0.3 + - @0xsequence/wallet-core@3.0.3 + - @0xsequence/wallet-primitives@3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer +- Updated dependencies + - @0xsequence/guard@3.0.2 + - @0xsequence/relayer@3.0.2 + - @0xsequence/wallet-core@3.0.2 + - @0xsequence/wallet-primitives@3.0.2 + +## 3.0.1 + +### Patch Changes + +- Network and session fixes +- Updated dependencies + - @0xsequence/guard@3.0.1 + - @0xsequence/relayer@3.0.1 + - @0xsequence/wallet-core@3.0.1 + - @0xsequence/wallet-primitives@3.0.1 + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade +- Updated dependencies [f68be62] +- Updated dependencies [49d8a2f] +- Updated dependencies [3411232] +- Updated dependencies [23cb9e9] +- Updated dependencies [f5f6a7a] +- Updated dependencies [e7de3b1] +- Updated dependencies [493836f] +- Updated dependencies [30e1f1a] +- Updated dependencies [d5017e8] +- Updated dependencies [24a5fab] +- Updated dependencies [e5e1a03] +- Updated dependencies [0b63113] +- Updated dependencies [a89134a] +- Updated dependencies [7c6c811] +- Updated dependencies +- Updated dependencies [98ce38b] +- Updated dependencies [747e6b5] +- Updated dependencies [40c19ff] +- Updated dependencies [6d5de25] +- Updated dependencies [934acd1] + - @0xsequence/guard@3.0.0 + - @0xsequence/relayer@3.0.0 + - @0xsequence/wallet-core@3.0.0 + - @0xsequence/wallet-primitives@3.0.0 + +## 3.0.0-beta.19 + +### Patch Changes + +- Final RC before 3.0.0 +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.19 + - @0xsequence/relayer@3.0.0-beta.19 + - @0xsequence/wallet-core@3.0.0-beta.19 + - @0xsequence/wallet-primitives@3.0.0-beta.19 + +## 3.0.0-beta.18 + +### Patch Changes + +- multicall3 optimization +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.18 + - @0xsequence/relayer@3.0.0-beta.18 + - @0xsequence/wallet-core@3.0.0-beta.18 + - @0xsequence/wallet-primitives@3.0.0-beta.18 + +## 3.0.0-beta.17 + +### Patch Changes + +- New chains, relayer rpc fix +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.17 + - @0xsequence/relayer@3.0.0-beta.17 + - @0xsequence/wallet-core@3.0.0-beta.17 + - @0xsequence/wallet-primitives@3.0.0-beta.17 + +## 3.0.0-beta.16 + +### Patch Changes + +- ethauth support +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.16 + - @0xsequence/relayer@3.0.0-beta.16 + - @0xsequence/wallet-core@3.0.0-beta.16 + - @0xsequence/wallet-primitives@3.0.0-beta.16 + +## 3.0.0-beta.15 + +### Patch Changes + +- New chains, minor fixes +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.15 + - @0xsequence/relayer@3.0.0-beta.15 + - @0xsequence/wallet-core@3.0.0-beta.15 + - @0xsequence/wallet-primitives@3.0.0-beta.15 + +## 3.0.0-beta.14 + +### Patch Changes + +- Relayer fee options fix +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.14 + - @0xsequence/relayer@3.0.0-beta.14 + - @0xsequence/wallet-core@3.0.0-beta.14 + - @0xsequence/wallet-primitives@3.0.0-beta.14 + +## 3.0.0-beta.13 + +### Patch Changes + +- Userdata service updates +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.13 + - @0xsequence/relayer@3.0.0-beta.13 + - @0xsequence/wallet-core@3.0.0-beta.13 + - @0xsequence/wallet-primitives@3.0.0-beta.13 + +## 3.0.0-beta.12 + +### Patch Changes + +- Beta release with dapp connector fixes +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.12 + - @0xsequence/relayer@3.0.0-beta.12 + - @0xsequence/wallet-core@3.0.0-beta.12 + - @0xsequence/wallet-primitives@3.0.0-beta.12 + +## 3.0.0-beta.11 + +### Patch Changes + +- 3.0.0 beta +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.11 + - @0xsequence/relayer@3.0.0-beta.11 + - @0xsequence/wallet-core@3.0.0-beta.11 + - @0xsequence/wallet-primitives@3.0.0-beta.11 + +## 3.0.0-beta.10 + +### Patch Changes + +- dapp-client updates +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.10 + - @0xsequence/relayer@3.0.0-beta.10 + - @0xsequence/wallet-core@3.0.0-beta.10 + - @0xsequence/wallet-primitives@3.0.0-beta.10 + +## 3.0.0-beta.9 + +### Patch Changes + +- dapp client updates for EOA login +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.9 + - @0xsequence/relayer@3.0.0-beta.9 + - @0xsequence/wallet-core@3.0.0-beta.9 + - @0xsequence/wallet-primitives@3.0.0-beta.9 + +## 3.0.0-beta.8 + +### Patch Changes + +- Apple auth fixes +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.8 + - @0xsequence/relayer@3.0.0-beta.8 + - @0xsequence/wallet-core@3.0.0-beta.8 + - @0xsequence/wallet-primitives@3.0.0-beta.8 + +## 3.0.0-beta.7 + +### Patch Changes + +- Apple auth fix +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.7 + - @0xsequence/relayer@3.0.0-beta.7 + - @0xsequence/wallet-core@3.0.0-beta.7 + - @0xsequence/wallet-primitives@3.0.0-beta.7 + +## 3.0.0-beta.6 + +### Patch Changes + +- Fix signer 404 error, minor fixes +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.6 + - @0xsequence/relayer@3.0.0-beta.6 + - @0xsequence/wallet-core@3.0.0-beta.6 + - @0xsequence/wallet-primitives@3.0.0-beta.6 + +## 3.0.0-beta.5 + +### Patch Changes + +- Beta release for v3 +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.5 + - @0xsequence/relayer@3.0.0-beta.5 + - @0xsequence/wallet-core@3.0.0-beta.5 + - @0xsequence/wallet-primitives@3.0.0-beta.5 + +## 3.0.0-beta.4 + +### Patch Changes + +- RC5 upgrade +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.4 + - @0xsequence/relayer@3.0.0-beta.4 + - @0xsequence/wallet-core@3.0.0-beta.4 + - @0xsequence/wallet-primitives@3.0.0-beta.4 + +## 3.0.0-beta.3 + +### Patch Changes + +- 3.0.0-beta.3 with fixes +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.3 + - @0xsequence/relayer@3.0.0-beta.3 + - @0xsequence/wallet-core@3.0.0-beta.3 + - @0xsequence/wallet-primitives@3.0.0-beta.3 + +## 3.0.0-beta.2 + +### Patch Changes + +- 3.0.0-beta.2 with identity instrument updates +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.2 + - @0xsequence/relayer@3.0.0-beta.2 + - @0xsequence/wallet-core@3.0.0-beta.2 + - @0xsequence/wallet-primitives@3.0.0-beta.2 + +## 3.0.0-beta.1 + +### Patch Changes + +- 3.0.0-beta.1 +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.1 + - @0xsequence/relayer@3.0.0-beta.1 + - @0xsequence/wallet-core@3.0.0-beta.1 + - @0xsequence/wallet-primitives@3.0.0-beta.1 diff --git a/packages/wallet/dapp-client/eslint.config.js b/packages/wallet/dapp-client/eslint.config.js new file mode 100644 index 000000000..cecf89b03 --- /dev/null +++ b/packages/wallet/dapp-client/eslint.config.js @@ -0,0 +1,4 @@ +import { config as baseConfig } from "@repo/eslint-config/base" + +/** @type {import("eslint").Linter.Config} */ +export default baseConfig diff --git a/packages/wallet/dapp-client/package.json b/packages/wallet/dapp-client/package.json index 8f57d8aae..59ecad054 100644 --- a/packages/wallet/dapp-client/package.json +++ b/packages/wallet/dapp-client/package.json @@ -1,6 +1,6 @@ { "name": "@0xsequence/dapp-client", - "version": "0.0.0", + "version": "3.1.1", "license": "Apache-2.0", "type": "module", "publishConfig": { @@ -11,7 +11,8 @@ "build": "tsc", "dev": "tsc --watch", "typecheck": "tsc --noEmit", - "clean": "rimraf dist" + "clean": "rimraf dist", + "lint": "eslint . --max-warnings 0" }, "exports": { ".": { @@ -20,20 +21,21 @@ } }, "devDependencies": { + "@repo/eslint-config": "workspace:^", "@repo/typescript-config": "workspace:^", - "@types/node": "^22.15.29", - "@vitest/coverage-v8": "^3.2.4", - "dotenv": "^16.5.0", - "fake-indexeddb": "^6.0.1", - "happy-dom": "^17.2.2", - "typescript": "^5.8.3", - "vitest": "^3.2.1" + "@types/node": "^25.3.0", + "@vitest/coverage-v8": "^4.0.18", + "dotenv": "^17.3.1", + "fake-indexeddb": "^6.2.5", + "happy-dom": "^20.8.9", + "typescript": "^6.0.3", + "vitest": "^4.0.18" }, "dependencies": { "@0xsequence/guard": "workspace:^", "@0xsequence/relayer": "workspace:^", "@0xsequence/wallet-core": "workspace:^", "@0xsequence/wallet-primitives": "workspace:^", - "ox": "^0.7.2" + "ox": "^0.9.17" } } diff --git a/packages/wallet/dapp-client/src/ChainSessionManager.ts b/packages/wallet/dapp-client/src/ChainSessionManager.ts index 6a7b3572e..d2ebd1a8e 100644 --- a/packages/wallet/dapp-client/src/ChainSessionManager.ts +++ b/packages/wallet/dapp-client/src/ChainSessionManager.ts @@ -37,12 +37,14 @@ import { TransportMode, GuardConfig, CreateNewSessionPayload, + EthAuthSettings, ModifyExplicitSessionPayload, SessionResponse, AddExplicitSessionPayload, FeeOption, OperationFailedStatus, OperationStatus, + ETHAuthProof, } from './types/index.js' import { CACHE_DB_NAME, VALUE_FORWARDER_ADDRESS } from './utils/constants.js' import { ExplicitSession, ImplicitSession, ExplicitSessionConfig } from './index.js' @@ -84,6 +86,11 @@ export class ChainSessionManager { public loginMethod: LoginMethod | null = null public userEmail: string | null = null private guard?: GuardConfig + private lastSignedCallCache?: { + fingerprint: string + signedCall: { to: Address.Address; data: Hex.Hex } + createdAtMs: number + } /** * @param chainId The ID of the chain this manager is responsible for. @@ -114,7 +121,8 @@ export class ChainSessionManager { const rpcUrl = getRpcUrl(chainId, nodesUrl, projectAccessKey) this.chainId = chainId - if (canUseIndexedDb) { + const canUseIndexedDbInEnv = canUseIndexedDb && typeof indexedDB !== 'undefined' + if (canUseIndexedDbInEnv) { this.stateProvider = new State.Cached({ source: new State.Sequence.Provider(keyMachineUrl), cache: new State.Local.Provider(new State.Local.IndexedDbStore(CACHE_DB_NAME)), @@ -150,11 +158,11 @@ export class ChainSessionManager { listener: ChainSessionManagerEventMap[K], ): () => void { if (!this.eventListeners[event]) { - this.eventListeners[event] = new Set() as any + this.eventListeners[event] = new Set() } - ;(this.eventListeners[event] as any).add(listener) + this.eventListeners[event].add(listener) return () => { - ;(this.eventListeners[event] as any)?.delete(listener) + this.eventListeners[event]?.delete(listener) } } @@ -221,7 +229,7 @@ export class ChainSessionManager { stateProvider: this.stateProvider, }) this.sessionManager = new Signers.SessionManager(this.wallet, { - sessionManagerAddress: Extensions.Rc3.sessions, + sessionManagerAddress: Extensions.Rc5.sessions, provider: this.provider!, }) this.isInitialized = true @@ -279,6 +287,7 @@ export class ChainSessionManager { preferredLoginMethod?: LoginMethod email?: string includeImplicitSession?: boolean + ethAuth?: EthAuthSettings } = {}, ): Promise { if (this.isInitialized) { @@ -305,6 +314,7 @@ export class ChainSessionManager { origin, session: completeSession as ExplicitSession | undefined, includeImplicitSession: options.includeImplicitSession ?? false, + ethAuth: options.ethAuth, preferredLoginMethod: options.preferredLoginMethod, email: options.preferredLoginMethod === 'email' ? options.email : undefined, } @@ -371,6 +381,10 @@ export class ChainSessionManager { this.guard = guard } + if (payload.ethAuth) { + await this._saveEthAuthProofIfProvided(connectResponse.ethAuthProof) + } + if (this.transport.mode === TransportMode.POPUP) { this.transport.closeWallet() } @@ -537,7 +551,7 @@ export class ChainSessionManager { const { userEmail, loginMethod, guard } = connectResponse const savedRequest = await this.sequenceStorage.peekPendingRequest() const savedPayload = savedRequest?.payload as CreateNewSessionPayload | undefined - const explicitSessionRequested = !!savedPayload?.session + const explicitSessionRequested = (savedPayload?.session?.permissions?.length ?? 0) > 0 const implicitSessionRequested = savedPayload?.includeImplicitSession ?? false const needsTempPk = explicitSessionRequested || implicitSessionRequested const tempPk = needsTempPk ? await this.sequenceStorage.getAndClearTempSessionPk() : null @@ -590,6 +604,10 @@ export class ChainSessionManager { this.userEmail = userEmail ?? null this.guard = guard } + + if (savedPayload?.ethAuth) { + await this._saveEthAuthProofIfProvided(connectResponse.ethAuthProof) + } } else if (response.action === RequestActionType.ADD_EXPLICIT_SESSION) { if (!this.walletAddress || !Address.isEqual(receivedAddress, this.walletAddress)) { throw new InitializationError('Received an explicit session for a wallet that is not active.') @@ -730,7 +748,7 @@ export class ChainSessionManager { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const tempManager = new Signers.SessionManager(this.wallet, { - sessionManagerAddress: Extensions.Rc3.sessions, + sessionManagerAddress: Extensions.Rc5.sessions, provider: this.provider, }) const topology = await tempManager.getTopology() @@ -810,19 +828,6 @@ export class ChainSessionManager { await this.sessionManager.findSignersForCalls(this.wallet.address, this.chainId, calls) return true } catch (error) { - if (error instanceof Error && error.message.includes('Signer supporting call is expired')) { - // Extract the expired signer address from the message with address regex - const expiredSignerAddress = error.message.match(/(0x[0-9a-fA-F]{40})/)?.[1] - if (expiredSignerAddress) { - // Refresh the session - await this._refreshExplicitSession(Address.from(expiredSignerAddress)) - // Retry the permission check - return this.hasPermission(transactions) - } else { - // Could not parse error message. Rethrow as this shouldn't happen. - throw error - } - } // An error from findSignersForCalls indicates a permission failure. console.warn( `Permission check failed for chain ${this.chainId}:`, @@ -850,13 +855,77 @@ export class ChainSessionManager { })) try { const signedCall = await this._buildAndSignCalls(callsToSend) - const feeOptions = await this.relayer.feeOptions(signedCall.to, this.chainId, callsToSend) + const fingerprint = this._fingerprintCalls(callsToSend) + if (fingerprint) { + this.lastSignedCallCache = { + fingerprint, + signedCall, + createdAtMs: Date.now(), + } + } + const walletAddress = this.walletAddress + if (!walletAddress) throw new InitializationError('Wallet is not initialized.') + const feeOptions = await this.relayer.feeOptions( + walletAddress, + this.chainId, + signedCall.to, + callsToSend, + signedCall.data, + ) return feeOptions.options } catch (err) { throw new FeeOptionError(`Failed to get fee options: ${err instanceof Error ? err.message : String(err)}`) } } + /** + * Checks whether the given transactions would be sponsored by an active + * relayer policy on this chain. + * + * Returns `true` only when the relayer's `/FeeOptions` endpoint explicitly + * reports sponsorship. A failed quote, network error, or absence of + * sponsorship all return `false`, so a `true` result is always safe to + * surface as "free gas" in UI. + */ + async isSponsored(calls: Transaction[]): Promise { + const callsToSend = calls.map((tx) => ({ + to: tx.to, + value: tx.value, + data: tx.data, + gasLimit: tx.gasLimit ?? BigInt(0), + delegateCall: tx.delegateCall ?? false, + onlyFallback: tx.onlyFallback ?? false, + behaviorOnError: tx.behaviorOnError ?? ('revert' as const), + })) + try { + const signedCall = await this._buildAndSignCalls(callsToSend) + const fingerprint = this._fingerprintCalls(callsToSend) + if (fingerprint) { + this.lastSignedCallCache = { + fingerprint, + signedCall, + createdAtMs: Date.now(), + } + } + const walletAddress = this.walletAddress + if (!walletAddress) throw new InitializationError('Wallet is not initialized.') + const feeOptions = await this.relayer.feeOptions( + walletAddress, + this.chainId, + signedCall.to, + callsToSend, + signedCall.data, + ) + return feeOptions.sponsored === true && !feeOptions.failed + } catch (err) { + console.warn( + `isSponsored check failed for chain ${this.chainId}:`, + err instanceof Error ? err.message : String(err), + ) + return false + } + } + /** * Builds, signs, and sends a batch of transactions. * @param transactions The transactions to be sent. @@ -906,7 +975,7 @@ export class ChainSessionManager { callsToSend.unshift(transferCall) } } - const signedCalls = await this._buildAndSignCalls(callsToSend) + const signedCalls = this._getCachedSignedCall(callsToSend) ?? (await this._buildAndSignCalls(callsToSend)) const hash = await this.relayer.relay(signedCalls.to, signedCalls.data, this.chainId) const status = await this._waitForTransactionReceipt(hash.opHash, this.chainId) if (status.status === 'confirmed') { @@ -1100,4 +1169,49 @@ export class ChainSessionManager { await this.sequenceStorage.clearExplicitSessions() await this.sequenceStorage.clearSessionlessConnection() } + + private async _saveEthAuthProofIfProvided(ethAuthProof?: ETHAuthProof): Promise { + if (!ethAuthProof) { + return + } + await this.sequenceStorage.saveEthAuthProof(ethAuthProof) + } + + private _getCachedSignedCall(calls: Payload.Call[]): { to: Address.Address; data: Hex.Hex } | null { + if (!this.lastSignedCallCache) { + return null + } + const ttlMs = 30_000 + if (Date.now() - this.lastSignedCallCache.createdAtMs > ttlMs) { + this.lastSignedCallCache = undefined + return null + } + const fingerprint = this._fingerprintCalls(calls) + if (!fingerprint) { + return null + } + if (fingerprint !== this.lastSignedCallCache.fingerprint) { + return null + } + return this.lastSignedCallCache.signedCall + } + + private _fingerprintCalls(calls: Payload.Call[]): string | null { + try { + return JSON.stringify( + calls.map((call) => ({ + to: call.to, + value: call.value?.toString() ?? '0', + data: call.data ?? '0x', + gasLimit: call.gasLimit?.toString() ?? '0', + delegateCall: call.delegateCall ?? false, + onlyFallback: call.onlyFallback ?? false, + behaviorOnError: call.behaviorOnError ?? 'revert', + })), + ) + } catch (error) { + console.warn('ChainSessionManager._fingerprintCalls failed:', error) + return null + } + } } diff --git a/packages/wallet/dapp-client/src/DappClient.ts b/packages/wallet/dapp-client/src/DappClient.ts index 6e6a182d8..12ec37466 100644 --- a/packages/wallet/dapp-client/src/DappClient.ts +++ b/packages/wallet/dapp-client/src/DappClient.ts @@ -5,7 +5,7 @@ import { type ExplicitSession, type ExplicitSessionConfig, type ImplicitSession, import { ChainSessionManager } from './ChainSessionManager.js' import { DappTransport } from './DappTransport.js' import { ConnectionError, InitializationError, SigningError, TransactionError } from './utils/errors.js' -import { SequenceStorage, WebStorage } from './utils/storage.js' +import { SequenceStorage, WebStorage, type SessionlessConnectionData } from './utils/storage.js' import { CreateNewSessionResponse, DappClientExplicitSessionEventListener, @@ -14,8 +14,10 @@ import { GetFeeTokensResponse, GuardConfig, LoginMethod, + EthAuthSettings, RandomPrivateKeyFn, RequestActionType, + ETHAuthProof, SendWalletTransactionPayload, SequenceSessionStorage, SignMessagePayload, @@ -30,7 +32,7 @@ import { KEYMACHINE_URL, NODES_URL, RELAYER_URL } from './utils/constants.js' import { getRelayerUrl, getRpcUrl } from './utils/index.js' import { Relayer } from '@0xsequence/relayer' -export type DappClientEventListener = (data?: any) => void +export type DappClientEventListener = (data?: unknown) => void interface DappClientEventMap { sessionsUpdated: () => void @@ -69,7 +71,8 @@ export class DappClient { private chainSessionManagers: Map = new Map() private walletUrl: string - private transport: DappTransport + private transport: DappTransport | null = null + private transportModeSetting: TransportMode private projectAccessKey: string private nodesUrl: string private relayerUrl: string @@ -85,10 +88,15 @@ export class DappClient { private walletAddress: Address.Address | null = null private hasSessionlessConnection = false + private cachedSessionlessConnection: SessionlessConnectionData | null = null private eventListeners: { [K in keyof DappClientEventMap]?: Set } = {} + private get isBrowser(): boolean { + return typeof window !== 'undefined' && typeof document !== 'undefined' + } + /** * @param walletUrl The URL of the Wallet Webapp. * @param origin The origin of the dapp @@ -133,17 +141,13 @@ export class DappClient { canUseIndexedDb = true, } = options || {} - this.transport = new DappTransport( - walletUrl, - transportMode, - undefined, - sequenceSessionStorage, - redirectActionHandler, - ) this.walletUrl = walletUrl + this.transportModeSetting = transportMode this.projectAccessKey = projectAccessKey this.nodesUrl = options?.nodesUrl || NODES_URL this.relayerUrl = options?.relayerUrl || RELAYER_URL + this.sequenceSessionStorage = sequenceSessionStorage + this.redirectActionHandler = redirectActionHandler this.origin = origin this.keymachineUrl = keymachineUrl this.sequenceStorage = sequenceStorage @@ -158,7 +162,7 @@ export class DappClient { * @returns The transport mode of the client. {@link TransportMode} */ public get transportMode(): TransportMode { - return this.transport.mode + return this.transport?.mode ?? this.transportModeSetting } /** @@ -180,11 +184,12 @@ export class DappClient { */ public on(event: K, listener: DappClientEventMap[K]): () => void { if (!this.eventListeners[event]) { - this.eventListeners[event] = new Set() as any + // @ts-expect-error - indexing into evenListeners will improperly create a union of all the possible types + this.eventListeners[event] = new Set() } - ;(this.eventListeners[event] as any).add(listener) + this.eventListeners[event].add(listener) return () => { - ;(this.eventListeners[event] as any)?.delete(listener) + this.eventListeners[event]?.delete(listener) } } @@ -273,10 +278,14 @@ export class DappClient { private async _loadStateFromStorage(): Promise { const implicitSession = await this.sequenceStorage.getImplicitSession() - const [explicitSessions, sessionlessConnection] = await Promise.all([ + const [explicitSessions, sessionlessConnection, sessionlessSnapshot] = await Promise.all([ this.sequenceStorage.getExplicitSessions(), this.sequenceStorage.getSessionlessConnection(), + this.sequenceStorage.getSessionlessConnectionSnapshot + ? this.sequenceStorage.getSessionlessConnectionSnapshot() + : Promise.resolve(null), ]) + this.cachedSessionlessConnection = sessionlessSnapshot ?? null const chainIdsToInitialize = new Set([ ...(implicitSession?.chainId !== undefined ? [implicitSession.chainId] : []), ...explicitSessions.map((s) => s.chainId), @@ -316,6 +325,10 @@ export class DappClient { this.userEmail = result[0]?.userEmail || null this.guard = implicitSession?.guard || explicitSessions.find((s) => !!s.guard)?.guard await this.sequenceStorage.clearSessionlessConnection() + if (this.sequenceStorage.clearSessionlessConnectionSnapshot) { + await this.sequenceStorage.clearSessionlessConnectionSnapshot() + } + this.cachedSessionlessConnection = null this.isInitialized = true this.emit('sessionsUpdated') @@ -369,6 +382,70 @@ export class DappClient { } } + /** + * Indicates if there is cached sessionless connection data that can be restored. + */ + public async hasRestorableSessionlessConnection(): Promise { + if (this.cachedSessionlessConnection) return true + this.cachedSessionlessConnection = this.sequenceStorage.getSessionlessConnectionSnapshot + ? await this.sequenceStorage.getSessionlessConnectionSnapshot() + : null + return this.cachedSessionlessConnection !== null + } + + /** + * Returns the cached sessionless connection metadata without altering client state. + * @returns The cached sessionless connection or null if none is available. + */ + public async getSessionlessConnectionInfo(): Promise { + if (!this.cachedSessionlessConnection) { + this.cachedSessionlessConnection = this.sequenceStorage.getSessionlessConnectionSnapshot + ? await this.sequenceStorage.getSessionlessConnectionSnapshot() + : null + } + if (!this.cachedSessionlessConnection) return null + return { + walletAddress: this.cachedSessionlessConnection.walletAddress, + loginMethod: this.cachedSessionlessConnection.loginMethod, + userEmail: this.cachedSessionlessConnection.userEmail, + guard: this.cachedSessionlessConnection.guard, + } + } + + /** + * Returns the latest persisted ETHAuth proof, if one has been received from the wallet. + */ + public async getEthAuthProof(): Promise { + return this.sequenceStorage.getEthAuthProof() + } + + /** + * Restores a sessionless connection that was previously persisted via {@link disconnect} or a connect flow. + * @returns A promise that resolves to true if a sessionless connection was applied. + */ + public async restoreSessionlessConnection(): Promise { + const sessionlessConnection = + this.cachedSessionlessConnection ?? + (this.sequenceStorage.getSessionlessConnectionSnapshot + ? await this.sequenceStorage.getSessionlessConnectionSnapshot() + : null) + if (!sessionlessConnection) { + return false + } + + await this.applySessionlessConnectionState( + sessionlessConnection.walletAddress, + sessionlessConnection.loginMethod, + sessionlessConnection.userEmail, + sessionlessConnection.guard, + ) + if (this.sequenceStorage.clearSessionlessConnectionSnapshot) { + await this.sequenceStorage.clearSessionlessConnectionSnapshot() + } + this.cachedSessionlessConnection = null + return true + } + /** * Handles the redirect response from the Wallet. * This is called automatically on `initialize()` for web environments but can be called manually @@ -379,7 +456,11 @@ export class DappClient { public async handleRedirectResponse(url?: string): Promise { const pendingRequest = await this.sequenceStorage.peekPendingRequest() - const response = await this.transport.getRedirectResponse(true, url) + if (!this.transport && this.transportMode === TransportMode.POPUP && !this.isBrowser) { + return + } + + const response = await this.ensureTransport().getRedirectResponse(true, url) if (!response) { return } @@ -490,6 +571,7 @@ export class DappClient { preferredLoginMethod?: LoginMethod email?: string includeImplicitSession?: boolean + ethAuth?: EthAuthSettings } = {}, ): Promise { if (this.isInitialized) { @@ -504,7 +586,7 @@ export class DappClient { // For popup mode, we need to manually update the state and emit an event. // For redirect mode, this code won't be reached; the page will navigate away. - if (this.transport.mode === TransportMode.POPUP) { + if (this.transportMode === TransportMode.POPUP) { const hasImplicitSession = !!chainSessionManager.getImplicitSession() const hasExplicitSessions = chainSessionManager.getExplicitSessions().length > 0 if (shouldCreateSession && (hasImplicitSession || hasExplicitSessions)) { @@ -545,6 +627,7 @@ export class DappClient { preferredLoginMethod?: LoginMethod email?: string includeImplicitSession?: boolean + ethAuth?: EthAuthSettings } = {}, ): Promise { if (!this.isInitialized || !this.hasSessionlessConnection || !this.walletAddress) { @@ -579,7 +662,7 @@ export class DappClient { chainSessionManager = chainSessionManager ?? this.getChainSessionManager(chainId) await chainSessionManager.createNewSession(this.origin, sessionConfig, options) - if (this.transport.mode === TransportMode.POPUP) { + if (this.transportMode === TransportMode.POPUP) { const hasImplicitSession = !!chainSessionManager.getImplicitSession() const hasExplicitSessions = chainSessionManager.getExplicitSessions().length > 0 @@ -653,7 +736,7 @@ export class DappClient { } await chainSessionManager.addExplicitSession(explicitSessionConfig) - if (this.transport.mode === TransportMode.POPUP) { + if (this.transportMode === TransportMode.POPUP) { await this._loadStateFromStorage() } } @@ -688,7 +771,7 @@ export class DappClient { } await chainSessionManager.modifyExplicitSession(explicitSession) - if (this.transport.mode === TransportMode.POPUP) { + if (this.transportMode === TransportMode.POPUP) { await this._loadStateFromStorage() } } @@ -725,6 +808,30 @@ export class DappClient { return await chainSessionManager.getFeeOptions(transactions) } + /** + * Checks whether the given transactions would be sponsored on `chainId`. + * + * Returns `true` only when the relayer's `/FeeOptions` endpoint explicitly + * reports sponsorship. A failed quote, network error, or absence of + * sponsorship all return `false`, so a `true` result is always safe to + * surface as "free gas" in UI. + * + * Prefer this over inferring sponsorship from an empty `getFeeOptions` + * array — a swallowed `/FeeOptions` error also produces an empty array. + * + * @example + * if (await dappClient.isSponsored(1, transactions)) { + * // safe to show "Free gas, sponsored by app" + * } else { + * const feeOptions = await dappClient.getFeeOptions(1, transactions) + * // present feeOptions[0..n] to the user as payment choices + * } + */ + async isSponsored(chainId: number, transactions: Transaction[]): Promise { + const chainSessionManager = await this.getOrInitializeChainManager(chainId) + return await chainSessionManager.isSponsored(transactions) + } + /** * Fetches fee tokens for a chain. * @returns A promise that resolves with the fee tokens response. {@link GetFeeTokensResponse} @@ -881,6 +988,8 @@ export class DappClient { /** * Disconnects the client, clearing all session data from browser storage. * @remarks This action does not revoke the sessions on-chain. Sessions remain active until they expire or are manually revoked by the user in their wallet. + * @param options Options to control the disconnection behavior. + * @param options.keepSessionlessConnection When true, retains the latest wallet metadata so it can be restored later as a sessionless connection. Defaults to true. * @returns A promise that resolves when disconnection is complete. * * @example @@ -888,23 +997,42 @@ export class DappClient { * await dappClient.initialize(); * * if (dappClient.isInitialized) { - * await dappClient.disconnect(); + * await dappClient.disconnect({ keepSessionlessConnection: true }); * } */ - async disconnect(): Promise { - const transportMode = this.transport.mode - - this.transport.destroy() - this.transport = new DappTransport( - this.walletUrl, - transportMode, - undefined, - this.sequenceSessionStorage, - this.redirectActionHandler, - ) + async disconnect(options?: { keepSessionlessConnection?: boolean }): Promise { + const keepSessionlessConnection = options?.keepSessionlessConnection ?? true + + if (this.transport) { + this.transport.destroy() + } + this.transport = null this.chainSessionManagers.clear() + const sessionlessSnapshot = + keepSessionlessConnection && this.walletAddress + ? { + walletAddress: this.walletAddress, + loginMethod: this.loginMethod ?? undefined, + userEmail: this.userEmail ?? undefined, + guard: this.guard, + } + : undefined + await this.sequenceStorage.clearAllData() + + if (sessionlessSnapshot) { + if (this.sequenceStorage.saveSessionlessConnectionSnapshot) { + await this.sequenceStorage.saveSessionlessConnectionSnapshot(sessionlessSnapshot) + } + this.cachedSessionlessConnection = sessionlessSnapshot + } else { + if (this.sequenceStorage.clearSessionlessConnectionSnapshot) { + await this.sequenceStorage.clearSessionlessConnectionSnapshot() + } + this.cachedSessionlessConnection = null + } + this.isInitialized = false this.walletAddress = null this.loginMethod = null @@ -926,6 +1054,22 @@ export class DappClient { } } + private ensureTransport(): DappTransport { + if (!this.transport) { + if (this.transportModeSetting === TransportMode.POPUP && !this.isBrowser) { + throw new InitializationError('Popup transport requires a browser environment.') + } + this.transport = new DappTransport( + this.walletUrl, + this.transportModeSetting, + undefined, + this.sequenceSessionStorage, + this.redirectActionHandler, + ) + } + return this.transport + } + private async applySessionlessConnectionState( walletAddress: Address.Address, loginMethod?: LoginMethod | null, @@ -939,6 +1083,7 @@ export class DappClient { this.guard = guard this.hasSessionlessConnection = true this.isInitialized = true + this.cachedSessionlessConnection = null this.emit('sessionsUpdated') if (persist) { await this.sequenceStorage.saveSessionlessConnection({ @@ -962,17 +1107,18 @@ export class DappClient { try { const redirectUrl = this.origin + (this.redirectPath ? this.redirectPath : '') const path = action === RequestActionType.SEND_WALLET_TRANSACTION ? '/request/transaction' : '/request/sign' + const transport = this.ensureTransport() - if (this.transport.mode === TransportMode.REDIRECT) { + if (transport.mode === TransportMode.REDIRECT) { await this.sequenceStorage.savePendingRequest({ action, payload, chainId: chainId, }) await this.sequenceStorage.setPendingRedirectRequest(true) - await this.transport.sendRequest(action, redirectUrl, payload, { path }) + await transport.sendRequest(action, redirectUrl, payload, { path }) } else { - const response = await this.transport.sendRequest(action, redirectUrl, payload, { + const response = await transport.sendRequest(action, redirectUrl, payload, { path, }) this.emit('walletActionResponse', { action, response, chainId }) @@ -982,7 +1128,7 @@ export class DappClient { this.emit('walletActionResponse', { action, error, chainId }) throw error } finally { - if (this.transport.mode === TransportMode.POPUP) { + if (this.transportMode === TransportMode.POPUP && this.transport) { this.transport.closeWallet() } } @@ -1018,9 +1164,10 @@ export class DappClient { private getChainSessionManager(chainId: number): ChainSessionManager { let chainSessionManager = this.chainSessionManagers.get(chainId) if (!chainSessionManager) { + const transport = this.ensureTransport() chainSessionManager = new ChainSessionManager( chainId, - this.transport, + transport, this.projectAccessKey, this.keymachineUrl, this.nodesUrl, diff --git a/packages/wallet/dapp-client/src/DappTransport.ts b/packages/wallet/dapp-client/src/DappTransport.ts index 072fa056f..12770e4a4 100644 --- a/packages/wallet/dapp-client/src/DappTransport.ts +++ b/packages/wallet/dapp-client/src/DappTransport.ts @@ -12,6 +12,28 @@ import { WalletSize, } from './types/index.js' +const isBrowserEnvironment = typeof window !== 'undefined' && typeof document !== 'undefined' + +const base64Encode = (value: string) => { + if (typeof Buffer !== 'undefined') { + return Buffer.from(value, 'utf-8').toString('base64') + } + if (typeof btoa !== 'undefined') { + return btoa(unescape(encodeURIComponent(value))) + } + throw new Error('Base64 encoding is not supported in this environment.') +} + +const base64Decode = (value: string) => { + if (typeof Buffer !== 'undefined') { + return Buffer.from(value, 'base64').toString('utf-8') + } + if (typeof atob !== 'undefined') { + return decodeURIComponent(escape(atob(value))) + } + throw new Error('Base64 decoding is not supported in this environment.') +} + enum ConnectionState { DISCONNECTED = 'DISCONNECTED', CONNECTING = 'CONNECTING', @@ -36,6 +58,7 @@ export class DappTransport { private readonly handshakeTimeoutMs: number private readonly sequenceSessionStorage: SequenceSessionStorage private readonly redirectActionHandler?: (url: string) => void + private readonly isBrowser: boolean public readonly walletOrigin: string @@ -46,6 +69,7 @@ export class DappTransport { sequenceSessionStorage?: SequenceSessionStorage, redirectActionHandler?: (url: string) => void, ) { + this.isBrowser = isBrowserEnvironment try { this.walletOrigin = new URL(walletUrl).origin } catch (e) { @@ -57,18 +81,26 @@ export class DappTransport { throw new Error('Invalid wallet origin derived from walletUrl.') } - if (sequenceSessionStorage) { - this.sequenceSessionStorage = sequenceSessionStorage - } else if (typeof window !== 'undefined' && window.sessionStorage) { - this.sequenceSessionStorage = window.sessionStorage - } else { - throw new Error('A storage implementation must be provided for non-browser environments.') - } + this.sequenceSessionStorage = + sequenceSessionStorage || + ({ + getItem: (key: string) => (this.isBrowser && window.sessionStorage ? window.sessionStorage.getItem(key) : null), + setItem: (key: string, value: string) => { + if (this.isBrowser && window.sessionStorage) { + window.sessionStorage.setItem(key, value) + } + }, + removeItem: (key: string) => { + if (this.isBrowser && window.sessionStorage) { + window.sessionStorage.removeItem(key) + } + }, + } satisfies SequenceSessionStorage) this.requestTimeoutMs = popupModeOptions.requestTimeoutMs ?? 300000 this.handshakeTimeoutMs = popupModeOptions.handshakeTimeoutMs ?? 15000 - if (this.mode === TransportMode.POPUP) { + if (this.mode === TransportMode.POPUP && this.isBrowser) { window.addEventListener('message', this.handleMessage) } @@ -91,13 +123,23 @@ export class DappTransport { payload?: TRequest, options: SendRequestOptions = {}, ): Promise { + if (!this.isBrowser && this.mode === TransportMode.POPUP) { + throw new Error( + 'Popup transport requires a browser environment. Use redirect mode or provide a redirect handler.', + ) + } + if (this.mode === TransportMode.REDIRECT) { const url = await this.getRequestRedirectUrl(action, payload, redirectUrl, options.path) if (this.redirectActionHandler) { this.redirectActionHandler(url) - } else { + } else if (this.isBrowser) { console.info('[DappTransport] No redirectActionHandler provided. Using window.location.href to navigate.') window.location.href = url + } else { + throw new Error( + 'Redirect navigation is not possible outside the browser without a redirectActionHandler. Provide a handler to perform navigation.', + ) } return new Promise(() => {}) } @@ -148,7 +190,7 @@ export class DappTransport { throw new Error('Could not save redirect state to storage. Redirect flow is unavailable.') } - const serializedPayload = btoa(JSON.stringify(payload || {}, jsonReplacers)) + const serializedPayload = base64Encode(JSON.stringify(payload || {}, jsonReplacers)) const fullWalletUrl = path ? `${this.walletUrl}${path}` : this.walletUrl const url = new URL(fullWalletUrl) url.searchParams.set('action', action) @@ -164,7 +206,12 @@ export class DappTransport { cleanState: boolean = true, url?: string, ): Promise<{ payload: TResponse; action: string } | { error: any; action: string } | null> { - const params = new URLSearchParams(url ? new URL(url).search : window.location.search) + if (!url && !this.isBrowser) { + throw new Error('A URL must be provided when handling redirect responses outside of a browser environment.') + } + + const search = url ? new URL(url).search : this.isBrowser ? window.location.search : '' + const params = new URLSearchParams(search) const responseId = params.get('id') if (!responseId) return null @@ -191,11 +238,9 @@ export class DappTransport { const responsePayloadB64 = params.get('payload') const responseErrorB64 = params.get('error') - const isBrowser = typeof window !== 'undefined' && window.history - if (cleanState) { await this.sequenceSessionStorage.removeItem(REDIRECT_REQUEST_KEY) - if (isBrowser && !url) { + if (this.isBrowser && !url && window.history) { const cleanUrl = new URL(window.location.href) ;['id', 'payload', 'error', 'mode'].forEach((p) => cleanUrl.searchParams.delete(p)) history.replaceState({}, document.title, cleanUrl.toString()) @@ -205,7 +250,7 @@ export class DappTransport { if (responseErrorB64) { try { return { - error: JSON.parse(atob(responseErrorB64), jsonRevivers), + error: JSON.parse(base64Decode(responseErrorB64), jsonRevivers), action: originalRequest.action, } } catch (e) { @@ -219,7 +264,7 @@ export class DappTransport { if (responsePayloadB64) { try { return { - payload: JSON.parse(atob(responsePayloadB64), jsonRevivers), + payload: JSON.parse(base64Decode(responsePayloadB64), jsonRevivers), action: originalRequest.action, } } catch (e) { @@ -240,6 +285,9 @@ export class DappTransport { if (this.mode === TransportMode.REDIRECT) { throw new Error("`openWallet` is not available in 'redirect' mode.") } + if (!this.isBrowser) { + throw new Error('Popup transport requires a browser environment.') + } if (this.connectionState !== ConnectionState.DISCONNECTED) { if (this.isWalletOpen) this.walletWindow?.focus() return this.readyPromise || Promise.resolve() @@ -314,12 +362,14 @@ export class DappTransport { } destroy(): void { - if (this.mode === TransportMode.POPUP) { + if (this.mode === TransportMode.POPUP && this.isBrowser) { window.removeEventListener('message', this.handleMessage) if (this.isWalletOpen) { this.walletWindow?.close() } this._resetConnection(new Error('Transport destroyed.'), 'Destroying transport...') + } else { + this._resetConnection(new Error('Transport destroyed.'), 'Destroying transport...') } } @@ -328,10 +378,7 @@ export class DappTransport { return } - const isPotentiallyValidSource = - this.walletWindow && (event.source === this.walletWindow || !this.walletWindow.closed) - - if (!isPotentiallyValidSource && event.data?.type !== MessageType.WALLET_OPENED) { + if (!this.walletWindow || event.source !== this.walletWindow) { return } @@ -494,7 +541,7 @@ export class DappTransport { const requestsToClear = new Map(this.pendingRequests) this.pendingRequests.clear() requestsToClear.forEach((pending) => { - window.clearTimeout(pending.timer) + clearTimeout(pending.timer) const errorToSend = reason instanceof Error ? reason : new Error(`Operation failed: ${reason}`) pending.reject(errorToSend) }) @@ -503,11 +550,11 @@ export class DappTransport { private clearTimeouts(): void { if (this.handshakeTimeoutId !== undefined) { - window.clearTimeout(this.handshakeTimeoutId) + clearTimeout(this.handshakeTimeoutId) this.handshakeTimeoutId = undefined } if (this.closeCheckIntervalId !== undefined) { - window.clearInterval(this.closeCheckIntervalId) + clearInterval(this.closeCheckIntervalId) this.closeCheckIntervalId = undefined } } diff --git a/packages/wallet/dapp-client/src/index.ts b/packages/wallet/dapp-client/src/index.ts index 9ffb334d9..ce976c13d 100644 --- a/packages/wallet/dapp-client/src/index.ts +++ b/packages/wallet/dapp-client/src/index.ts @@ -23,8 +23,11 @@ export type { GetFeeTokensResponse, FeeToken, FeeOption, + TransportMessage, + EthAuthSettings, + ETHAuthProof, } from './types/index.js' -export { RequestActionType, TransportMode } from './types/index.js' +export { RequestActionType, TransportMode, MessageType } from './types/index.js' export { FeeOptionError, TransactionError, @@ -34,16 +37,35 @@ export { SigningError, ModifyExplicitSessionError, } from './utils/errors.js' -export { getExplorerUrl, jsonReplacers, jsonRevivers } from './utils/index.js' +export { + createExplicitSessionConfig, + getExplorerUrl, + getNetwork, + getRelayerUrl, + getRpcUrl, + jsonReplacers, + jsonRevivers, + VALUE_FORWARDER_ADDRESS, +} from './utils/index.js' +export type { ExplicitSessionParams, NativeTokenSpending, SessionDuration } from './utils/index.js' export type { SequenceStorage, ExplicitSessionData, ImplicitSessionData, + SessionlessConnectionData, PendingRequestContext, PendingPayload, } from './utils/storage.js' export { WebStorage } from './utils/storage.js' -export { Attestation, Permission, Extensions, SessionConfig, Constants, Payload } from '@0xsequence/wallet-primitives' +export { + Attestation, + Permission, + Extensions, + SessionConfig, + Constants, + Payload, + Network, +} from '@0xsequence/wallet-primitives' export type { ExplicitSessionConfig, ExplicitSession, ImplicitSession, Session } from '@0xsequence/wallet-core' export { Signers, Wallet, Utils, Envelope, State } from '@0xsequence/wallet-core' diff --git a/packages/wallet/dapp-client/src/types/index.ts b/packages/wallet/dapp-client/src/types/index.ts index 72e3dbe11..0f023c2bb 100644 --- a/packages/wallet/dapp-client/src/types/index.ts +++ b/packages/wallet/dapp-client/src/types/index.ts @@ -21,19 +21,39 @@ export const RequestActionType = { SEND_WALLET_TRANSACTION: 'sendWalletTransaction', } as const -export type LoginMethod = 'google' | 'apple' | 'email' | 'passkey' | 'mnemonic' +export type LoginMethod = 'google' | 'apple' | 'email' | 'passkey' | 'mnemonic' | 'eoa' export interface GuardConfig { url: string moduleAddresses: Map } +export interface EthAuthSettings { + app?: string + /** expiry number (in seconds) that is used for ETHAuth proof. Default is 1 week in seconds. */ + expiry?: number + /** origin hint of the dapp's host opening the wallet. This value will automatically + * be determined and verified for integrity, and can be omitted. */ + origin?: string + /** authorizeNonce is an optional number to be passed as ETHAuth's nonce claim for replay protection. **/ + nonce?: number +} + +export interface ETHAuthProof { + // eip712 typed-data payload for ETHAuth domain as input + typedData: Payload.TypedDataToSign + + // signature encoded in an ETHAuth proof string + ewtString: string +} + // --- Payloads for Transport --- export interface CreateNewSessionPayload { origin?: string session?: ExplicitSession includeImplicitSession?: boolean + ethAuth?: EthAuthSettings preferredLoginMethod?: LoginMethod email?: string } @@ -81,6 +101,7 @@ export interface CreateNewSessionResponse { userEmail?: string loginMethod?: LoginMethod guard?: GuardConfig + ethAuthProof?: ETHAuthProof } export interface SignatureResponse { diff --git a/packages/wallet/dapp-client/src/utils/constants.ts b/packages/wallet/dapp-client/src/utils/constants.ts index 3e1355558..7d382d41c 100644 --- a/packages/wallet/dapp-client/src/utils/constants.ts +++ b/packages/wallet/dapp-client/src/utils/constants.ts @@ -1,5 +1,5 @@ export const CACHE_DB_NAME = 'sequence-cache' export const NODES_URL = 'https://nodes.sequence.app/{network}' export const RELAYER_URL = 'https://{network}-relayer.sequence.app' -export const KEYMACHINE_URL = 'https://v3-keymachine.sequence.app' +export const KEYMACHINE_URL = 'https://keymachine.sequence.app' export const VALUE_FORWARDER_ADDRESS = '0xABAAd93EeE2a569cF0632f39B10A9f5D734777ca' diff --git a/packages/wallet/dapp-client/src/utils/index.ts b/packages/wallet/dapp-client/src/utils/index.ts index cdb2c4e69..12bf312c3 100644 --- a/packages/wallet/dapp-client/src/utils/index.ts +++ b/packages/wallet/dapp-client/src/utils/index.ts @@ -1,6 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Network } from '@0xsequence/wallet-primitives' -import { Bytes, Hex } from 'ox' +import type { ExplicitSessionConfig } from '@0xsequence/wallet-core' +import { Network, Permission } from '@0xsequence/wallet-primitives' +import { Bytes, Hex, type Address } from 'ox' +export { VALUE_FORWARDER_ADDRESS } from './constants.js' type JsonReplacer = (key: string, value: any) => any type JsonReviver = (key: string, value: any) => any @@ -124,6 +126,50 @@ const uint8ArrayReviver: JsonReviver = (key, value) => { export const jsonRevivers = chainRevivers([mapReviver, bigIntReviver, uint8ArrayReviver]) export const jsonReplacers = chainReplacers([mapReplacer, bigIntReplacer, uint8ArrayReplacer]) +export type SessionDuration = { + days?: number + hours?: number + minutes?: number +} + +export type NativeTokenSpending = { + valueLimit: bigint + allowedRecipients?: Address.Address[] +} + +export type ExplicitSessionParams = { + chainId: number + expiresIn: SessionDuration + permissions: Permission.Permission[] + nativeTokenSpending?: NativeTokenSpending +} + +export const createExplicitSessionConfig = (params: ExplicitSessionParams): ExplicitSessionConfig => { + const nowInSeconds = BigInt(Math.floor(Date.now() / 1000)) + const { days = 0, hours = 0, minutes = 0 } = params.expiresIn + const sessionLifetimeSeconds = days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60 + const deadline = nowInSeconds + BigInt(sessionLifetimeSeconds) + + if (params.permissions.length === 0) { + throw new Error('createExplicitSessionConfig: At least one permission is required.') + } + + const nativeTokenSpending = params.nativeTokenSpending + const valueLimit = nativeTokenSpending?.valueLimit ?? 0n + const nativeTokenReceivers = [...(nativeTokenSpending?.allowedRecipients || [])] + const nativeTokenSpendingPermissions = nativeTokenReceivers.map((receiver) => ({ + target: receiver, + rules: [], + })) + + return { + chainId: params.chainId, + valueLimit, + deadline, + permissions: [...params.permissions, ...nativeTokenSpendingPermissions], + } +} + /** * Apply a template to a string. * diff --git a/packages/wallet/dapp-client/src/utils/storage.ts b/packages/wallet/dapp-client/src/utils/storage.ts index 2fa2eaac4..8a56015ba 100644 --- a/packages/wallet/dapp-client/src/utils/storage.ts +++ b/packages/wallet/dapp-client/src/utils/storage.ts @@ -5,6 +5,7 @@ import { SignMessagePayload, SignTypedDataPayload, GuardConfig, + ETHAuthProof, SendWalletTransactionPayload, ModifyExplicitSessionPayload, CreateNewSessionPayload, @@ -13,6 +14,10 @@ import { import { Attestation } from '../index.js' +const isBrowser = typeof window !== 'undefined' +const hasSessionStorage = isBrowser && typeof sessionStorage !== 'undefined' +const hasIndexedDb = typeof indexedDB !== 'undefined' + export interface ExplicitSessionData { pk: Hex.Hex walletAddress: Address.Address @@ -77,6 +82,14 @@ export interface SequenceStorage { getSessionlessConnection(): Promise clearSessionlessConnection(): Promise + saveEthAuthProof(proof: ETHAuthProof): Promise + getEthAuthProof(): Promise + clearEthAuthProof(): Promise + + saveSessionlessConnectionSnapshot?(sessionData: SessionlessConnectionData): Promise + getSessionlessConnectionSnapshot?(): Promise + clearSessionlessConnectionSnapshot?(): Promise + clearAllData(): Promise } @@ -86,13 +99,20 @@ const STORE_NAME = 'userKeys' const IMPLICIT_SESSIONS_IDB_KEY = 'SequenceImplicitSession' const EXPLICIT_SESSIONS_IDB_KEY = 'SequenceExplicitSession' const SESSIONLESS_CONNECTION_IDB_KEY = 'SequenceSessionlessConnection' +const ETH_AUTH_PROOF_IDB_KEY = 'SequenceEthAuthProof' +const SESSIONLESS_CONNECTION_SNAPSHOT_IDB_KEY = 'SequenceSessionlessConnectionSnapshot' const PENDING_REDIRECT_REQUEST_KEY = 'SequencePendingRedirect' const TEMP_SESSION_PK_KEY = 'SequencePendingTempSessionPk' const PENDING_REQUEST_CONTEXT_KEY = 'SequencePendingRequestContext' export class WebStorage implements SequenceStorage { + private inMemoryDb = new Map() + private openDB(): Promise { + if (!hasIndexedDb) { + return Promise.reject(new Error('IndexedDB is not available in this environment.')) + } return new Promise((resolve, reject) => { const request = indexedDB.open(DB_NAME, DB_VERSION) request.onerror = (event) => reject(`IndexedDB error: ${(event.target as IDBRequest).error}`) @@ -107,6 +127,9 @@ export class WebStorage implements SequenceStorage { } private async getIDBItem(key: IDBValidKey): Promise { + if (!hasIndexedDb) { + return this.inMemoryDb.get(key) as T | undefined + } const db = await this.openDB() return new Promise((resolve, reject) => { const request = db.transaction(STORE_NAME, 'readonly').objectStore(STORE_NAME).get(key) @@ -116,6 +139,10 @@ export class WebStorage implements SequenceStorage { } private async setIDBItem(key: IDBValidKey, value: unknown): Promise { + if (!hasIndexedDb) { + this.inMemoryDb.set(key, value) + return + } const db = await this.openDB() return new Promise((resolve, reject) => { const request = db.transaction(STORE_NAME, 'readwrite').objectStore(STORE_NAME).put(value, key) @@ -125,6 +152,10 @@ export class WebStorage implements SequenceStorage { } private async deleteIDBItem(key: IDBValidKey): Promise { + if (!hasIndexedDb) { + this.inMemoryDb.delete(key) + return + } const db = await this.openDB() return new Promise((resolve, reject) => { const request = db.transaction(STORE_NAME, 'readwrite').objectStore(STORE_NAME).delete(key) @@ -135,11 +166,9 @@ export class WebStorage implements SequenceStorage { async setPendingRedirectRequest(isPending: boolean): Promise { try { - if (isPending) { - sessionStorage.setItem(PENDING_REDIRECT_REQUEST_KEY, 'true') - } else { - sessionStorage.removeItem(PENDING_REDIRECT_REQUEST_KEY) - } + if (!hasSessionStorage) return + if (isPending) sessionStorage.setItem(PENDING_REDIRECT_REQUEST_KEY, 'true') + else sessionStorage.removeItem(PENDING_REDIRECT_REQUEST_KEY) } catch (error) { console.error('Failed to set pending redirect flag:', error) } @@ -147,6 +176,7 @@ export class WebStorage implements SequenceStorage { async isRedirectRequestPending(): Promise { try { + if (!hasSessionStorage) return false return sessionStorage.getItem(PENDING_REDIRECT_REQUEST_KEY) === 'true' } catch (error) { console.error('Failed to check pending redirect flag:', error) @@ -156,6 +186,7 @@ export class WebStorage implements SequenceStorage { async saveTempSessionPk(pk: Hex.Hex): Promise { try { + if (!hasSessionStorage) return sessionStorage.setItem(TEMP_SESSION_PK_KEY, pk) } catch (error) { console.error('Failed to save temp session PK:', error) @@ -164,6 +195,7 @@ export class WebStorage implements SequenceStorage { async getAndClearTempSessionPk(): Promise { try { + if (!hasSessionStorage) return null const pk = sessionStorage.getItem(TEMP_SESSION_PK_KEY) sessionStorage.removeItem(TEMP_SESSION_PK_KEY) return pk as Hex.Hex | null @@ -175,6 +207,7 @@ export class WebStorage implements SequenceStorage { async savePendingRequest(context: PendingRequestContext): Promise { try { + if (!hasSessionStorage) return sessionStorage.setItem(PENDING_REQUEST_CONTEXT_KEY, JSON.stringify(context, jsonReplacers)) } catch (error) { console.error('Failed to save pending request context:', error) @@ -183,6 +216,7 @@ export class WebStorage implements SequenceStorage { async getAndClearPendingRequest(): Promise { try { + if (!hasSessionStorage) return null const context = sessionStorage.getItem(PENDING_REQUEST_CONTEXT_KEY) if (!context) return null sessionStorage.removeItem(PENDING_REQUEST_CONTEXT_KEY) @@ -195,6 +229,7 @@ export class WebStorage implements SequenceStorage { async peekPendingRequest(): Promise { try { + if (!hasSessionStorage) return null const context = sessionStorage.getItem(PENDING_REQUEST_CONTEXT_KEY) if (!context) return null return JSON.parse(context, jsonRevivers) @@ -276,6 +311,15 @@ export class WebStorage implements SequenceStorage { } } + async saveEthAuthProof(proof: ETHAuthProof): Promise { + try { + await this.setIDBItem(ETH_AUTH_PROOF_IDB_KEY, proof) + } catch (error) { + console.error('Failed to save ETHAuth proof:', error) + throw error + } + } + async getSessionlessConnection(): Promise { try { return (await this.getIDBItem(SESSIONLESS_CONNECTION_IDB_KEY)) ?? null @@ -285,6 +329,15 @@ export class WebStorage implements SequenceStorage { } } + async getEthAuthProof(): Promise { + try { + return (await this.getIDBItem(ETH_AUTH_PROOF_IDB_KEY)) ?? null + } catch (error) { + console.error('Failed to retrieve ETHAuth proof:', error) + return null + } + } + async clearSessionlessConnection(): Promise { try { await this.deleteIDBItem(SESSIONLESS_CONNECTION_IDB_KEY) @@ -294,17 +347,57 @@ export class WebStorage implements SequenceStorage { } } + async clearEthAuthProof(): Promise { + try { + await this.deleteIDBItem(ETH_AUTH_PROOF_IDB_KEY) + } catch (error) { + console.error('Failed to clear ETHAuth proof:', error) + throw error + } + } + + async saveSessionlessConnectionSnapshot(sessionData: SessionlessConnectionData): Promise { + try { + await this.setIDBItem(SESSIONLESS_CONNECTION_SNAPSHOT_IDB_KEY, sessionData) + } catch (error) { + console.error('Failed to save sessionless connection snapshot:', error) + throw error + } + } + + async getSessionlessConnectionSnapshot(): Promise { + try { + return (await this.getIDBItem(SESSIONLESS_CONNECTION_SNAPSHOT_IDB_KEY)) ?? null + } catch (error) { + console.error('Failed to retrieve sessionless connection snapshot:', error) + return null + } + } + + async clearSessionlessConnectionSnapshot(): Promise { + try { + await this.deleteIDBItem(SESSIONLESS_CONNECTION_SNAPSHOT_IDB_KEY) + } catch (error) { + console.error('Failed to clear sessionless connection snapshot:', error) + throw error + } + } + async clearAllData(): Promise { try { // Clear all session storage items - sessionStorage.removeItem(PENDING_REDIRECT_REQUEST_KEY) - sessionStorage.removeItem(TEMP_SESSION_PK_KEY) - sessionStorage.removeItem(PENDING_REQUEST_CONTEXT_KEY) + if (hasSessionStorage) { + sessionStorage.removeItem(PENDING_REDIRECT_REQUEST_KEY) + sessionStorage.removeItem(TEMP_SESSION_PK_KEY) + sessionStorage.removeItem(PENDING_REQUEST_CONTEXT_KEY) + } // Clear all IndexedDB items await this.clearExplicitSessions() await this.clearImplicitSession() await this.clearSessionlessConnection() + await this.clearEthAuthProof() + await this.clearSessionlessConnectionSnapshot() } catch (error) { console.error('Failed to clear all data:', error) throw error diff --git a/packages/wallet/dapp-client/test/ethauth-proof.test.ts b/packages/wallet/dapp-client/test/ethauth-proof.test.ts new file mode 100644 index 000000000..93273f0d6 --- /dev/null +++ b/packages/wallet/dapp-client/test/ethauth-proof.test.ts @@ -0,0 +1,207 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' + +import { DappClient } from '../src/DappClient.js' +import { DappTransport } from '../src/DappTransport.js' +import { RequestActionType, TransportMode } from '../src/types/index.js' +import { WebStorage } from '../src/utils/storage.js' + +describe('ETHAuth proof persistence', () => { + afterEach(() => { + vi.restoreAllMocks() + vi.unstubAllGlobals() + }) + + const createSequenceStorageMock = () => ({ + setPendingRedirectRequest: vi.fn().mockResolvedValue(undefined), + isRedirectRequestPending: vi.fn().mockResolvedValue(false), + saveTempSessionPk: vi.fn().mockResolvedValue(undefined), + getAndClearTempSessionPk: vi.fn().mockResolvedValue(null), + savePendingRequest: vi.fn().mockResolvedValue(undefined), + getAndClearPendingRequest: vi.fn().mockResolvedValue(null), + peekPendingRequest: vi.fn().mockResolvedValue(null), + saveExplicitSession: vi.fn().mockResolvedValue(undefined), + getExplicitSessions: vi.fn().mockResolvedValue([]), + clearExplicitSessions: vi.fn().mockResolvedValue(undefined), + saveImplicitSession: vi.fn().mockResolvedValue(undefined), + getImplicitSession: vi.fn().mockResolvedValue(null), + clearImplicitSession: vi.fn().mockResolvedValue(undefined), + saveSessionlessConnection: vi.fn().mockResolvedValue(undefined), + getSessionlessConnection: vi.fn().mockResolvedValue(null), + clearSessionlessConnection: vi.fn().mockResolvedValue(undefined), + saveEthAuthProof: vi.fn().mockResolvedValue(undefined), + getEthAuthProof: vi.fn().mockResolvedValue(null), + clearEthAuthProof: vi.fn().mockResolvedValue(undefined), + clearAllData: vi.fn().mockResolvedValue(undefined), + }) + + it('persists ETHAuth proof when connect requests ethAuth in redirect mode', async () => { + const fetchMock = vi.fn() + vi.stubGlobal('fetch', fetchMock) + vi.stubGlobal('window', { fetch: fetchMock }) + + const ethAuthProof = { + typedData: { + domain: {}, + types: {}, + message: {}, + }, + ewtString: 'proof-string', + } + + const sequenceStorage = createSequenceStorageMock() + const sendRequestMock = vi.spyOn(DappTransport.prototype, 'sendRequest').mockResolvedValue({ + walletAddress: '0x1111111111111111111111111111111111111111', + ethAuthProof, + }) + + const client = new DappClient('https://wallet.example', 'https://dapp.example', 'test-project-access-key', { + sequenceStorage, + transportMode: TransportMode.REDIRECT, + canUseIndexedDb: false, + redirectActionHandler: vi.fn(), + }) + + await client.connect(1, undefined, { + ethAuth: { + app: 'app-name', + }, + }) + + expect(sendRequestMock).toHaveBeenCalledWith( + RequestActionType.CREATE_NEW_SESSION, + 'https://dapp.example', + expect.objectContaining({ + ethAuth: { + app: 'app-name', + }, + }), + expect.any(Object), + ) + expect(sequenceStorage.saveEthAuthProof).toHaveBeenCalledWith(ethAuthProof) + }) + + it('persists ETHAuth proof when connect requests ethAuth in popup mode', async () => { + const fetchMock = vi.fn() + vi.stubGlobal('fetch', fetchMock) + vi.stubGlobal('window', { fetch: fetchMock }) + vi.stubGlobal('document', {}) + + const ethAuthProof = { + typedData: { + domain: {}, + types: {}, + message: {}, + }, + ewtString: 'proof-string', + } + + const sequenceStorage = createSequenceStorageMock() + const sendRequestMock = vi.spyOn(DappTransport.prototype, 'sendRequest').mockResolvedValue({ + walletAddress: '0x1111111111111111111111111111111111111111', + ethAuthProof, + }) + + const client = new DappClient('https://wallet.example', 'https://dapp.example', 'test-project-access-key', { + sequenceStorage, + transportMode: TransportMode.POPUP, + canUseIndexedDb: false, + }) + + await client.connect(1, undefined, { + ethAuth: { + app: 'app-name', + }, + }) + + expect(sendRequestMock).toHaveBeenCalledWith( + RequestActionType.CREATE_NEW_SESSION, + 'https://dapp.example', + expect.objectContaining({ + ethAuth: { + app: 'app-name', + }, + }), + expect.any(Object), + ) + expect(sequenceStorage.saveEthAuthProof).toHaveBeenCalledWith(ethAuthProof) + }) + + it('does not persist ETHAuth proof when connect does not request ethAuth', async () => { + const fetchMock = vi.fn() + vi.stubGlobal('fetch', fetchMock) + vi.stubGlobal('window', { fetch: fetchMock }) + + const ethAuthProof = { + typedData: { + domain: {}, + types: {}, + message: {}, + }, + ewtString: 'proof-string', + } + + const sequenceStorage = createSequenceStorageMock() + const sendRequestMock = vi.spyOn(DappTransport.prototype, 'sendRequest').mockResolvedValue({ + walletAddress: '0x1111111111111111111111111111111111111111', + ethAuthProof, + }) + + const client = new DappClient('https://wallet.example', 'https://dapp.example', 'test-project-access-key', { + sequenceStorage, + transportMode: TransportMode.REDIRECT, + canUseIndexedDb: false, + redirectActionHandler: vi.fn(), + }) + + await client.connect(1) + + expect(sendRequestMock).toHaveBeenCalledWith( + RequestActionType.CREATE_NEW_SESSION, + 'https://dapp.example', + expect.not.objectContaining({ + ethAuth: expect.anything(), + }), + expect.any(Object), + ) + expect(sequenceStorage.saveEthAuthProof).not.toHaveBeenCalled() + }) + + it('clears ETHAuth proof on disconnect', async () => { + const fetchMock = vi.fn() + vi.stubGlobal('fetch', fetchMock) + vi.stubGlobal('window', { fetch: fetchMock }) + + const ethAuthProof = { + typedData: { + domain: {}, + types: {}, + message: {}, + }, + ewtString: 'proof-string', + } + + vi.spyOn(DappTransport.prototype, 'sendRequest').mockResolvedValue({ + walletAddress: '0x1111111111111111111111111111111111111111', + ethAuthProof, + }) + + const client = new DappClient('https://wallet.example', 'https://dapp.example', 'test-project-access-key', { + sequenceStorage: new WebStorage(), + transportMode: TransportMode.REDIRECT, + canUseIndexedDb: false, + redirectActionHandler: vi.fn(), + }) + + await client.connect(1, undefined, { + ethAuth: { + app: 'app-name', + }, + }) + + expect(await client.getEthAuthProof()).toEqual(ethAuthProof) + + await client.disconnect() + + expect(await client.getEthAuthProof()).toBeNull() + }) +}) diff --git a/packages/wallet/primitives-cli/eslint.config.js b/packages/wallet/primitives-cli/eslint.config.js new file mode 100644 index 000000000..cecf89b03 --- /dev/null +++ b/packages/wallet/primitives-cli/eslint.config.js @@ -0,0 +1,4 @@ +import { config as baseConfig } from "@repo/eslint-config/base" + +/** @type {import("eslint").Linter.Config} */ +export default baseConfig diff --git a/packages/wallet/primitives-cli/eslint.config.mjs b/packages/wallet/primitives-cli/eslint.config.mjs deleted file mode 100644 index 19170f88e..000000000 --- a/packages/wallet/primitives-cli/eslint.config.mjs +++ /dev/null @@ -1,4 +0,0 @@ -import { config } from "@repo/eslint-config/react-internal"; - -/** @type {import("eslint").Linter.Config} */ -export default config; diff --git a/packages/wallet/primitives-cli/package.json b/packages/wallet/primitives-cli/package.json index b01f2eb98..b91c0a885 100644 --- a/packages/wallet/primitives-cli/package.json +++ b/packages/wallet/primitives-cli/package.json @@ -1,6 +1,8 @@ { "name": "@0xsequence/wallet-primitives-cli", "type": "module", + "bin": "./dist/index.js", + "private": true, "scripts": { "build": "tsc", "build:esbuild": "esbuild src/index.ts --bundle --platform=node --target=node16 --outfile=dist/index.js", @@ -19,18 +21,18 @@ } }, "devDependencies": { - "@repo/eslint-config": "workspace:*", + "@repo/eslint-config": "workspace:^", "@repo/typescript-config": "workspace:^", - "@types/node": "^22.15.29", - "@types/yargs": "^17.0.33", - "concurrently": "^8.2.2", - "esbuild": "^0.25.5", - "nodemon": "^3.1.10", - "typescript": "^5.8.3" + "@types/node": "^25.3.0", + "@types/yargs": "^17.0.35", + "concurrently": "^9.2.1", + "esbuild": "^0.27.3", + "nodemon": "^3.1.14", + "typescript": "^6.0.3" }, "dependencies": { "@0xsequence/wallet-primitives": "workspace:^", - "ox": "^0.7.2", - "yargs": "^17.7.2" + "ox": "^0.9.17", + "yargs": "^18.0.0" } } diff --git a/packages/wallet/primitives-cli/src/subcommands/address.ts b/packages/wallet/primitives-cli/src/subcommands/address.ts index b418f1406..062349efc 100644 --- a/packages/wallet/primitives-cli/src/subcommands/address.ts +++ b/packages/wallet/primitives-cli/src/subcommands/address.ts @@ -45,7 +45,7 @@ const addressCommand: CommandModule = { .option('creationCode', { type: 'string', description: 'Creation code (optional)', - default: Context.Rc3.creationCode, + default: Context.Rc5.creationCode, }) }, async (argv) => { diff --git a/packages/wallet/primitives-cli/tsconfig.json b/packages/wallet/primitives-cli/tsconfig.json index 1e325a596..39eeeb094 100644 --- a/packages/wallet/primitives-cli/tsconfig.json +++ b/packages/wallet/primitives-cli/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "rootDir": "src", "outDir": "dist", - "sourceMap": true + "sourceMap": true, + "types": ["node"] }, "include": ["src"], "exclude": ["node_modules", "dist"] diff --git a/packages/wallet/primitives/CHANGELOG.md b/packages/wallet/primitives/CHANGELOG.md new file mode 100644 index 000000000..657ffa65c --- /dev/null +++ b/packages/wallet/primitives/CHANGELOG.md @@ -0,0 +1,212 @@ +# @0xsequence/wallet-primitives + +## 3.0.12 + +### Patch Changes + +- WDK fix + +## 3.0.11 + +### Patch Changes + +- Fix for relayer sponsored fees + +## 3.0.10 + +### Patch Changes + +- Minor network updates, relayer interface + +## 3.0.9 + +### Patch Changes + +- Fee options fixes + +## 3.0.8 + +### Patch Changes + +- Bug fix for relayer fee options handling + +## 3.0.7 + +### Patch Changes + +- Minor bug fixes + +## 3.0.6 + +### Patch Changes + +- userdata upgrade, arweave support + +## 3.0.5 + +### Patch Changes + +- Account federation support + +## 3.0.4 + +### Patch Changes + +- id-token login support + +## 3.0.3 + +### Patch Changes + +- 3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer + +## 3.0.1 + +### Patch Changes + +- Network and session fixes + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade + +## 3.0.0-beta.19 + +### Patch Changes + +- Final RC before 3.0.0 + +## 3.0.0-beta.18 + +### Patch Changes + +- multicall3 optimization + +## 3.0.0-beta.17 + +### Patch Changes + +- New chains, relayer rpc fix + +## 3.0.0-beta.16 + +### Patch Changes + +- ethauth support + +## 3.0.0-beta.15 + +### Patch Changes + +- New chains, minor fixes + +## 3.0.0-beta.14 + +### Patch Changes + +- Relayer fee options fix + +## 3.0.0-beta.13 + +### Patch Changes + +- Userdata service updates + +## 3.0.0-beta.12 + +### Patch Changes + +- Beta release with dapp connector fixes + +## 3.0.0-beta.11 + +### Patch Changes + +- 3.0.0 beta + +## 3.0.0-beta.10 + +### Patch Changes + +- dapp-client updates + +## 3.0.0-beta.9 + +### Patch Changes + +- dapp client updates for EOA login + +## 3.0.0-beta.8 + +### Patch Changes + +- Apple auth fixes + +## 3.0.0-beta.7 + +### Patch Changes + +- Apple auth fix + +## 3.0.0-beta.6 + +### Patch Changes + +- Fix signer 404 error, minor fixes + +## 3.0.0-beta.5 + +### Patch Changes + +- Beta release for v3 + +## 3.0.0-beta.4 + +### Patch Changes + +- RC5 upgrade + +## 3.0.0-beta.3 + +### Patch Changes + +- 3.0.0-beta.3 with fixes + +## 3.0.0-beta.2 + +### Patch Changes + +- 3.0.0-beta.2 with identity instrument updates + +## 3.0.0-beta.1 + +### Patch Changes + +- 3.0.0-beta.1 diff --git a/packages/wallet/primitives/eslint.config.js b/packages/wallet/primitives/eslint.config.js new file mode 100644 index 000000000..d10bbd1e9 --- /dev/null +++ b/packages/wallet/primitives/eslint.config.js @@ -0,0 +1,12 @@ +import { config as baseConfig } from '@repo/eslint-config/base' + +/** @type {import("eslint").Linter.Config} */ +export default [ + ...baseConfig, + { + // files: ['**/*.{test,spec}.ts'], + rules: { + '@typescript-eslint/no-explicit-any': 'off', + }, + }, +] diff --git a/packages/wallet/primitives/eslint.config.mjs b/packages/wallet/primitives/eslint.config.mjs deleted file mode 100644 index 19170f88e..000000000 --- a/packages/wallet/primitives/eslint.config.mjs +++ /dev/null @@ -1,4 +0,0 @@ -import { config } from "@repo/eslint-config/react-internal"; - -/** @type {import("eslint").Linter.Config} */ -export default config; diff --git a/packages/wallet/primitives/package.json b/packages/wallet/primitives/package.json index ead0a7823..14e64c1b5 100644 --- a/packages/wallet/primitives/package.json +++ b/packages/wallet/primitives/package.json @@ -1,6 +1,6 @@ { "name": "@0xsequence/wallet-primitives", - "version": "0.0.0", + "version": "3.0.12", "license": "Apache-2.0", "type": "module", "publishConfig": { @@ -13,7 +13,8 @@ "test": "vitest run", "test:coverage": "vitest run --coverage", "typecheck": "tsc --noEmit", - "clean": "rimraf dist" + "clean": "rimraf dist", + "lint": "eslint . --max-warnings 0" }, "exports": { ".": { @@ -22,12 +23,13 @@ } }, "devDependencies": { + "@repo/eslint-config": "workspace:^", "@repo/typescript-config": "workspace:^", - "@vitest/coverage-v8": "^3.2.4", - "typescript": "^5.8.3", - "vitest": "^3.2.1" + "@vitest/coverage-v8": "^4.0.18", + "typescript": "^6.0.3", + "vitest": "^4.0.18" }, "dependencies": { - "ox": "^0.7.2" + "ox": "^0.9.17" } } diff --git a/packages/wallet/primitives/src/attestation.ts b/packages/wallet/primitives/src/attestation.ts index 78795862e..4f576f7a0 100644 --- a/packages/wallet/primitives/src/attestation.ts +++ b/packages/wallet/primitives/src/attestation.ts @@ -1,4 +1,4 @@ -import { Address, Bytes, Hash } from 'ox' +import { Address, Bytes, Hash, Hex } from 'ox' export type Attestation = { approvedSigner: Address.Address @@ -14,6 +14,18 @@ export type AuthData = { issuedAt: bigint // uint64 } +type EncodedAttestation = { + approvedSigner: Address.Address + identityType: Hex.Hex + issuerHash: Hex.Hex + audienceHash: Hex.Hex + applicationData: Hex.Hex + authData: { + redirectUrl: string + issuedAt: string + } +} + // Encoding and decoding export function encode(attestation: Attestation): Bytes.Bytes { @@ -76,9 +88,9 @@ export function toJson(attestation: Attestation, indent?: number): string { return JSON.stringify(encodeForJson(attestation), null, indent) } -export function encodeForJson(attestation: Attestation): any { +export function encodeForJson(attestation: Attestation): EncodedAttestation { return { - approvedSigner: attestation.approvedSigner.toString(), + approvedSigner: attestation.approvedSigner, identityType: Bytes.toHex(attestation.identityType), issuerHash: Bytes.toHex(attestation.issuerHash), audienceHash: Bytes.toHex(attestation.audienceHash), @@ -94,7 +106,7 @@ export function fromJson(json: string): Attestation { return fromParsed(JSON.parse(json)) } -export function fromParsed(parsed: any): Attestation { +export function fromParsed(parsed: EncodedAttestation): Attestation { return { approvedSigner: Address.from(parsed.approvedSigner), identityType: Bytes.fromHex(parsed.identityType), diff --git a/packages/wallet/primitives/src/config.ts b/packages/wallet/primitives/src/config.ts index a42dbe64b..20c54dc49 100644 --- a/packages/wallet/primitives/src/config.ts +++ b/packages/wallet/primitives/src/config.ts @@ -54,6 +54,49 @@ export type Leaf = SignerLeaf | SapientSignerLeaf | SubdigestLeaf | AnyAddressSu export type Topology = Node | Leaf +/** Encoded topology types for JSON serialization */ +type EncodedSignerLeaf = { + type: 'signer' + address: Address.Address + weight: string +} + +type EncodedSapientSignerLeaf = { + type: 'sapient-signer' + address: Address.Address + weight: string + imageHash: Hex.Hex +} + +type EncodedSubdigestLeaf = { + type: 'subdigest' + digest: Hex.Hex +} + +type EncodedAnyAddressSubdigestLeaf = { + type: 'any-address-subdigest' + digest: Hex.Hex +} + +type EncodedNestedLeaf = { + type: 'nested' + tree: EncodedTopology + weight: string + threshold: string +} + +type EncodedNodeLeaf = Hex.Hex + +export type EncodedLeaf = + | EncodedSignerLeaf + | EncodedSapientSignerLeaf + | EncodedSubdigestLeaf + | EncodedAnyAddressSubdigestLeaf + | EncodedNestedLeaf + | EncodedNodeLeaf +export type EncodedNode = [EncodedTopology, EncodedTopology] +export type EncodedTopology = EncodedNode | EncodedLeaf + export type Config = { threshold: bigint checkpoint: bigint @@ -61,39 +104,39 @@ export type Config = { checkpointer?: Address.Address } -export function isSignerLeaf(cand: any): cand is SignerLeaf { - return typeof cand === 'object' && cand !== null && cand.type === 'signer' +export function isSignerLeaf(cand: unknown): cand is SignerLeaf { + return typeof cand === 'object' && cand !== null && 'type' in cand && cand.type === 'signer' } -export function isSapientSignerLeaf(cand: any): cand is SapientSignerLeaf { - return typeof cand === 'object' && cand !== null && cand.type === 'sapient-signer' +export function isSapientSignerLeaf(cand: unknown): cand is SapientSignerLeaf { + return typeof cand === 'object' && cand !== null && 'type' in cand && cand.type === 'sapient-signer' } -export function isSubdigestLeaf(cand: any): cand is SubdigestLeaf { - return typeof cand === 'object' && cand !== null && cand.type === 'subdigest' +export function isSubdigestLeaf(cand: unknown): cand is SubdigestLeaf { + return typeof cand === 'object' && cand !== null && 'type' in cand && cand.type === 'subdigest' } -export function isAnyAddressSubdigestLeaf(cand: any): cand is AnyAddressSubdigestLeaf { - return typeof cand === 'object' && cand !== null && cand.type === 'any-address-subdigest' +export function isAnyAddressSubdigestLeaf(cand: unknown): cand is AnyAddressSubdigestLeaf { + return typeof cand === 'object' && cand !== null && 'type' in cand && cand.type === 'any-address-subdigest' } -export function isNodeLeaf(cand: any): cand is NodeLeaf { - return Hex.validate(cand) && cand.length === 66 +export function isNodeLeaf(cand: unknown): cand is NodeLeaf { + return typeof cand === 'string' && Hex.validate(cand) && cand.length === 66 } -export function isNestedLeaf(cand: any): cand is NestedLeaf { - return typeof cand === 'object' && cand !== null && cand.type === 'nested' +export function isNestedLeaf(cand: unknown): cand is NestedLeaf { + return typeof cand === 'object' && cand !== null && 'type' in cand && cand.type === 'nested' } -export function isNode(cand: any): cand is Node { +export function isNode(cand: unknown): cand is Node { return Array.isArray(cand) && cand.length === 2 && isTopology(cand[0]) && isTopology(cand[1]) } -export function isConfig(cand: any): cand is Config { - return typeof cand === 'object' && 'threshold' in cand && 'checkpoint' in cand && 'topology' in cand +export function isConfig(cand: unknown): cand is Config { + return typeof cand === 'object' && cand !== null && 'threshold' in cand && 'checkpoint' in cand && 'topology' in cand } -export function isLeaf(cand: Topology): cand is Leaf { +export function isLeaf(cand: unknown): cand is Leaf { return ( isSignerLeaf(cand) || isSapientSignerLeaf(cand) || @@ -104,7 +147,7 @@ export function isLeaf(cand: Topology): cand is Leaf { ) } -export function isTopology(cand: any): cand is Topology { +export function isTopology(cand: unknown): cand is Topology { return isNode(cand) || isLeaf(cand) } @@ -309,7 +352,7 @@ export function configFromJson(json: string): Config { } } -function encodeTopology(top: Topology): any { +function encodeTopology(top: Topology): EncodedTopology { if (isNode(top)) { return [encodeTopology(top[0]), encodeTopology(top[1])] } else if (isSignerLeaf(top)) { @@ -349,7 +392,7 @@ function encodeTopology(top: Topology): any { throw new Error('Invalid topology') } -function decodeTopology(obj: any): Topology { +function decodeTopology(obj: EncodedTopology): Topology { if (Array.isArray(obj)) { if (obj.length !== 2) { throw new Error('Invalid node structure in JSON') @@ -626,3 +669,39 @@ function mergeLeaf(a: Leaf, b: Leaf): Leaf { throw new Error('Topology mismatch: incompatible leaf types') } + +export function replaceAddress( + topology: Topology, + targetAddress: Address.Address, + replacementAddress: Address.Address, +): Topology { + // 1. Handle Branches/Nodes (Recursion) + if (isNode(topology)) { + return [ + replaceAddress(topology[0], targetAddress, replacementAddress), + replaceAddress(topology[1], targetAddress, replacementAddress), + ] + } + + // 2. Handle Nested Leaves (Recursion) + if (isNestedLeaf(topology)) { + return { + ...topology, + tree: replaceAddress(topology.tree, targetAddress, replacementAddress), + } + } + + // 3. Handle Leaves (Replacement) + if (isSignerLeaf(topology) || isSapientSignerLeaf(topology)) { + // If this leaf holds the placeholder address, swap it + if (Address.isEqual(topology.address, targetAddress)) { + return { + ...topology, + address: replacementAddress, + } + } + } + + // 4. Return other leaf types unchanged (Subdigest, NodeLeaf, etc.) + return topology +} diff --git a/packages/wallet/primitives/src/constants.ts b/packages/wallet/primitives/src/constants.ts index 46185a49d..763f94389 100644 --- a/packages/wallet/primitives/src/constants.ts +++ b/packages/wallet/primitives/src/constants.ts @@ -1,8 +1,9 @@ import { Abi } from 'ox' export const ZeroAddress = '0x0000000000000000000000000000000000000000' as const +export const PlaceholderAddress = '0xffff0000ffff0000ffff0000ffff0000ffff0000' as const -export const DefaultGuestAddress = '0x0000000000601fcA38f0cCA649453F6739436d6C' as const +export const DefaultGuestAddress = '0x0000000000006Ac72ed1d192fa28f0058D3F8806' as const // ERC1271 export const IS_VALID_SIGNATURE = Abi.from([ diff --git a/packages/wallet/primitives/src/context.ts b/packages/wallet/primitives/src/context.ts index f397cf6a7..fad04caa0 100644 --- a/packages/wallet/primitives/src/context.ts +++ b/packages/wallet/primitives/src/context.ts @@ -59,6 +59,44 @@ export const Rc3_4337: Context = { }, } +export const Rc4: Context = { + factory: '0x00000000000018A77519fcCCa060c2537c9D6d3F', + stage1: '0x0000000000003DF093bc4257E6dCE45D937EF161', + stage2: '0x10bE1Abf3cD0918bb1079ECc6b8220c177F34088', + creationCode: '0x6041600e3d396021805130553df33d3d36153402601f57363d3d373d363d30545af43d82803e903d91601f57fd5bf3', +} + +export const Rc4_4337: Context = { + factory: '0x00000000000018A77519fcCCa060c2537c9D6d3F', + stage1: '0x0000000000003add039FF84b064B7347Fc23C444', + stage2: '0x4B3E5735665057A0A15eE448A7293bC01e3b4De9', + creationCode: '0x6041600e3d396021805130553df33d3d36153402601f57363d3d373d363d30545af43d82803e903d91601f57fd5bf3', + capabilities: { + erc4337: { + entrypoint: '0x0000000071727De22E5E9d8BAf0edAc6f37da032', + }, + }, +} + +export const Rc5: Context = { + factory: '0x00000000000018A77519fcCCa060c2537c9D6d3F', + stage1: '0x0000000000001f3C39d61698ab21131a12134454', + stage2: '0xD0ae8eF93b7DA4eabb32Ec4d81b7a501DCa04D4C', + creationCode: '0x6041600e3d396021805130553df33d3d36153402601f57363d3d373d363d30545af43d82803e903d91601f57fd5bf3', +} + +export const Rc5_4337: Context = { + factory: '0x00000000000018A77519fcCCa060c2537c9D6d3F', + stage1: '0x0000000000009caFdeDb6f64Bf5F31a22124B2a8', + stage2: '0xcBca3328a731deffE6Ce4c2fb51b585c3c37FB92', + creationCode: '0x6041600e3d396021805130553df33d3d36153402601f57363d3d373d363d30545af43d82803e903d91601f57fd5bf3', + capabilities: { + erc4337: { + entrypoint: '0x0000000071727De22E5E9d8BAf0edAc6f37da032', + }, + }, +} + export type KnownContext = Context & { name: string development: boolean @@ -70,6 +108,10 @@ export const KnownContexts: KnownContext[] = [ { name: 'Dev2_4337', development: true, ...Dev2_4337 }, { name: 'Rc3', development: true, ...Rc3 }, { name: 'Rc3_4337', development: true, ...Rc3_4337 }, + { name: 'Rc4', development: false, ...Rc4 }, + { name: 'Rc4_4337', development: false, ...Rc4_4337 }, + { name: 'Rc5', development: false, ...Rc5 }, + { name: 'Rc5_4337', development: false, ...Rc5_4337 }, ] export function isContext(context: any): context is Context { diff --git a/packages/wallet/primitives/src/erc-6492.ts b/packages/wallet/primitives/src/erc-6492.ts index a07de019c..868350edf 100644 --- a/packages/wallet/primitives/src/erc-6492.ts +++ b/packages/wallet/primitives/src/erc-6492.ts @@ -1,5 +1,5 @@ import { AbiFunction, AbiParameters, Address, Bytes, Hex, Provider } from 'ox' -import { WrappedSignature } from 'ox/erc6492' +import { SignatureErc6492 } from 'ox/erc6492' import { DEPLOY } from './constants.js' import { Context } from './context.js' @@ -29,7 +29,7 @@ export function wrap( [{ type: 'address' }, { type: 'bytes' }, { type: 'bytes' }], [to, Hex.from(data), Hex.from(signature)], ), - WrappedSignature.magicBytes, + SignatureErc6492.magicBytes, ) switch (typeof signature) { @@ -46,12 +46,12 @@ export function decode( switch (typeof signature) { case 'object': if ( - Bytes.toHex(signature.subarray(-WrappedSignature.magicBytes.slice(2).length / 2)) === - WrappedSignature.magicBytes + Bytes.toHex(signature.subarray(-SignatureErc6492.magicBytes.slice(2).length / 2)) === + SignatureErc6492.magicBytes ) { const [to, data, decoded] = AbiParameters.decode( [{ type: 'address' }, { type: 'bytes' }, { type: 'bytes' }], - signature.subarray(0, -WrappedSignature.magicBytes.slice(2).length / 2), + signature.subarray(0, -SignatureErc6492.magicBytes.slice(2).length / 2), ) return { signature: Hex.toBytes(decoded) as T, erc6492: { to, data: Hex.toBytes(data) as T } } } else { @@ -59,11 +59,11 @@ export function decode( } case 'string': - if (signature.endsWith(WrappedSignature.magicBytes.slice(2))) { + if (signature.endsWith(SignatureErc6492.magicBytes.slice(2))) { try { const [to, data, decoded] = AbiParameters.decode( [{ type: 'address' }, { type: 'bytes' }, { type: 'bytes' }], - signature.slice(0, -WrappedSignature.magicBytes.slice(2).length) as Hex.Hex, + signature.slice(0, -SignatureErc6492.magicBytes.slice(2).length) as Hex.Hex, ) return { signature: decoded as T, erc6492: { to, data: data as T } } } catch { diff --git a/packages/wallet/primitives/src/extensions/index.ts b/packages/wallet/primitives/src/extensions/index.ts index 3d7582cbc..2ff8ac16b 100644 --- a/packages/wallet/primitives/src/extensions/index.ts +++ b/packages/wallet/primitives/src/extensions/index.ts @@ -24,5 +24,17 @@ export const Rc3: Extensions = { sessions: '0x0000000000CC58810c33F3a0D78aA1Ed80FaDcD8', } +export const Rc4: Extensions = { + passkeys: '0x0000000000005204F3711851EAD52CC9c241499a', + recovery: '0x000000000001FC499c3E177DD56Febb0A4bc15b7', + sessions: '0x00000000000030Bcc832F7d657f50D6Be35C92b3', +} + +export const Rc5: Extensions = { + passkeys: '0x0000000000005204F3711851EAD52CC9c241499a', + recovery: '0x000000000000AB36D17eB1150116371520565205', + sessions: '0x00000000000030Bcc832F7d657f50D6Be35C92b3', +} + export * as Passkeys from './passkeys.js' export * as Recovery from './recovery.js' diff --git a/packages/wallet/primitives/src/extensions/recovery.ts b/packages/wallet/primitives/src/extensions/recovery.ts index 7073272e5..43cd05b9c 100644 --- a/packages/wallet/primitives/src/extensions/recovery.ts +++ b/packages/wallet/primitives/src/extensions/recovery.ts @@ -1,7 +1,6 @@ import { Abi, AbiFunction, Address, Bytes, Hex, Provider } from 'ox' import * as GenericTree from '../generic-tree.js' import { Signature } from '../index.js' -import * as Network from '../network.js' import * as Payload from '../payload.js' import { packRSY } from '../utils.js' @@ -56,21 +55,21 @@ export type Tree = Branch | GenericTree.Node | RecoveryLeaf /** * Type guard to check if a value is a RecoveryLeaf */ -export function isRecoveryLeaf(cand: any): cand is RecoveryLeaf { - return typeof cand === 'object' && cand !== null && cand.type === 'leaf' +export function isRecoveryLeaf(cand: unknown): cand is RecoveryLeaf { + return typeof cand === 'object' && cand !== null && 'type' in cand && cand.type === 'leaf' } /** * Type guard to check if a value is a Node (pair of subtrees) */ -export function isBranch(cand: any): cand is Branch { +export function isBranch(cand: unknown): cand is Branch { return Array.isArray(cand) && cand.length === 2 && isTree(cand[0]) && isTree(cand[1]) } /** * Type guard to check if a value is a Topology */ -export function isTree(cand: any): cand is Tree { +export function isTree(cand: unknown): cand is Tree { return isRecoveryLeaf(cand) || GenericTree.isNode(cand) || isBranch(cand) } @@ -203,7 +202,7 @@ export function parseBranch(encoded: Bytes.Bytes): { nodes: Tree[]; leftover: By */ export function trimTopology(topology: Tree, signer: Address.Address): Tree { if (isRecoveryLeaf(topology)) { - if (topology.signer === signer) { + if (Address.isEqual(topology.signer, signer)) { return topology } else { return hashConfiguration(topology) @@ -437,10 +436,10 @@ export function fromGenericTree(tree: GenericTree.Tree): Tree { */ export function encodeCalldata( wallet: Address.Address, - payload: Payload.Recovery, + payload: Payload.Recovery, signer: Address.Address, signature: Signature.SignatureOfSignerLeaf, -) { +): Hex.Hex { let signatureBytes: Hex.Hex if (signature.type === 'erc1271') { diff --git a/packages/wallet/primitives/src/network.ts b/packages/wallet/primitives/src/network.ts index b0ef2bbde..498c7a793 100644 --- a/packages/wallet/primitives/src/network.ts +++ b/packages/wallet/primitives/src/network.ts @@ -1,3 +1,8 @@ +import { Address } from 'ox' + +const DEFAULT_MULTICALL3_ADDRESS: Address.Address = '0xcA11bde05977b3631167028862bE2a173976CA11' +const SEQUENCE_MULTICALL3_ADDRESS: Address.Address = '0xae96419a81516f063744206d4b5E36f3168280f8' + export enum NetworkType { MAINNET = 'mainnet', TESTNET = 'testnet', @@ -21,8 +26,11 @@ export interface Network { name: string decimals: number } - ensAddress?: string deprecated?: true + contracts?: { + multicall3?: Address.Address + ensUniversalResolver?: Address.Address + } } export const ChainId = { @@ -34,7 +42,6 @@ export const ChainId = { // Polygon POLYGON: 137, - POLYGON_ZKEVM: 1101, POLYGON_AMOY: 80002, // BSC @@ -67,10 +74,6 @@ export const ChainId = { HOMEVERSE_TESTNET: 40875, HOMEVERSE: 19011, - // Xai - XAI: 660279, - XAI_SEPOLIA: 37714555429, - // TELOS TELOS: 40, TELOS_TESTNET: 41, @@ -83,10 +86,6 @@ export const ChainId = { APECHAIN: 33139, APECHAIN_TESTNET: 33111, - // Blast - BLAST: 81457, - BLAST_SEPOLIA: 168587773, - // SKALE Nebula SKALE_NEBULA: 1482601649, SKALE_NEBULA_TESTNET: 37084624, @@ -95,16 +94,13 @@ export const ChainId = { SONEIUM_MINATO: 1946, SONEIUM: 1868, - // TOY Testnet - TOY_TESTNET: 21000000, - // Immutable zkEVM IMMUTABLE_ZKEVM: 13371, IMMUTABLE_ZKEVM_TESTNET: 13473, // ETHERLINK ETHERLINK: 42793, - ETHERLINK_TESTNET: 128123, + ETHERLINK_SHADOWNET_TESTNET: 127823, // MOONBEAM MOONBEAM: 1284, @@ -112,23 +108,25 @@ export const ChainId = { // MONAD MONAD: 143, - MONAD_TESTNET: 10143, // SOMNIA SOMNIA_TESTNET: 50312, SOMNIA: 5031, - // INCENTIV - INCENTIV_TESTNET_V2: 28802, - // KATANA KATANA: 747474, - // SANDBOX - SANDBOX_TESTNET: 6252, - // ARC ARC_TESTNET: 5042002, + + // HYPEREVM + HYPEREVM: 999, + + // SONIC + SONIC: 146, + + // BERACHAIN + BERACHAIN: 80094, } as const export type ChainId = (typeof ChainId)[keyof typeof ChainId] @@ -150,7 +148,10 @@ export const ALL: Network[] = [ name: 'Ether', decimals: 18, }, - ensAddress: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + ensUniversalResolver: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', + }, }, { chainId: ChainId.SEPOLIA, @@ -168,6 +169,9 @@ export const ALL: Network[] = [ name: 'Sepolia Ether', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.POLYGON, @@ -185,6 +189,9 @@ export const ALL: Network[] = [ name: 'POL', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.POLYGON_AMOY, @@ -202,22 +209,8 @@ export const ALL: Network[] = [ name: 'Amoy POL', decimals: 18, }, - }, - { - chainId: ChainId.POLYGON_ZKEVM, - type: NetworkType.MAINNET, - name: 'polygon-zkevm', - title: 'Polygon zkEVM', - rpcUrl: getRpcUrl('polygon-zkevm'), - logoUrl: getLogoUrl(ChainId.POLYGON_ZKEVM), - blockExplorer: { - name: 'Polygonscan (zkEVM)', - url: 'https://zkevm.polygonscan.com/', - }, - nativeCurrency: { - symbol: 'ETH', - name: 'Ether', - decimals: 18, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, }, }, { @@ -236,6 +229,9 @@ export const ALL: Network[] = [ name: 'BNB', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.BSC_TESTNET, @@ -253,6 +249,9 @@ export const ALL: Network[] = [ name: 'Testnet BNB', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.OPTIMISM, @@ -270,6 +269,9 @@ export const ALL: Network[] = [ name: 'Ether', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.OPTIMISM_SEPOLIA, @@ -287,6 +289,9 @@ export const ALL: Network[] = [ name: 'Sepolia Ether', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.ARBITRUM, @@ -304,6 +309,9 @@ export const ALL: Network[] = [ name: 'Ether', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.ARBITRUM_SEPOLIA, @@ -321,6 +329,9 @@ export const ALL: Network[] = [ name: 'Sepolia Ether', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.ARBITRUM_NOVA, @@ -338,6 +349,9 @@ export const ALL: Network[] = [ name: 'Ether', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.AVALANCHE, @@ -355,6 +369,9 @@ export const ALL: Network[] = [ name: 'AVAX', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.AVALANCHE_TESTNET, @@ -372,6 +389,9 @@ export const ALL: Network[] = [ name: 'Testnet AVAX', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.GNOSIS, @@ -389,12 +409,15 @@ export const ALL: Network[] = [ name: 'XDAI', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.BASE, type: NetworkType.MAINNET, name: 'base', - title: 'Base (Coinbase)', + title: 'Base', rpcUrl: getRpcUrl('base'), logoUrl: getLogoUrl(ChainId.BASE), blockExplorer: { @@ -406,6 +429,9 @@ export const ALL: Network[] = [ name: 'Ether', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.BASE_SEPOLIA, @@ -423,6 +449,9 @@ export const ALL: Network[] = [ name: 'Sepolia Ether', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.HOMEVERSE, @@ -440,6 +469,9 @@ export const ALL: Network[] = [ name: 'OAS', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.HOMEVERSE_TESTNET, @@ -457,39 +489,8 @@ export const ALL: Network[] = [ name: 'Testnet OAS', decimals: 18, }, - }, - { - chainId: ChainId.XAI, - type: NetworkType.MAINNET, - name: 'xai', - title: 'Xai', - rpcUrl: getRpcUrl('xai'), - logoUrl: getLogoUrl(ChainId.XAI), - blockExplorer: { - name: 'Xai Explorer', - url: 'https://explorer.xai-chain.net/', - }, - nativeCurrency: { - symbol: 'XAI', - name: 'XAI', - decimals: 18, - }, - }, - { - chainId: ChainId.XAI_SEPOLIA, - type: NetworkType.TESTNET, - name: 'xai-sepolia', - title: 'Xai Sepolia', - rpcUrl: getRpcUrl('xai-sepolia'), - logoUrl: getLogoUrl(ChainId.XAI_SEPOLIA), - blockExplorer: { - name: 'Xai Sepolia Explorer', - url: 'https://testnet-explorer-v2.xai-chain.net/', - }, - nativeCurrency: { - symbol: 'sXAI', - name: 'Sepolia XAI', - decimals: 18, + contracts: { + multicall3: SEQUENCE_MULTICALL3_ADDRESS, }, }, { @@ -508,6 +509,9 @@ export const ALL: Network[] = [ name: 'Ether', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.B3_SEPOLIA, @@ -525,6 +529,9 @@ export const ALL: Network[] = [ name: 'Ether', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.APECHAIN, @@ -542,6 +549,9 @@ export const ALL: Network[] = [ name: 'ApeCoin', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.APECHAIN_TESTNET, @@ -559,39 +569,8 @@ export const ALL: Network[] = [ name: 'ApeCoin', decimals: 18, }, - }, - { - chainId: ChainId.BLAST, - type: NetworkType.MAINNET, - name: 'blast', - title: 'Blast', - rpcUrl: getRpcUrl('blast'), - logoUrl: getLogoUrl(ChainId.BLAST), - blockExplorer: { - name: 'Blast Explorer', - url: 'https://blastscan.io/', - }, - nativeCurrency: { - symbol: 'ETH', - name: 'Ether', - decimals: 18, - }, - }, - { - chainId: ChainId.BLAST_SEPOLIA, - type: NetworkType.TESTNET, - name: 'blast-sepolia', - title: 'Blast Sepolia', - rpcUrl: getRpcUrl('blast-sepolia'), - logoUrl: getLogoUrl(ChainId.BLAST_SEPOLIA), - blockExplorer: { - name: 'Blast Sepolia Explorer', - url: 'https://sepolia.blastexplorer.io/', - }, - nativeCurrency: { - symbol: 'ETH', - name: 'Ether', - decimals: 18, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, }, }, { @@ -610,6 +589,9 @@ export const ALL: Network[] = [ name: 'TLOS', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.TELOS_TESTNET, @@ -627,6 +609,9 @@ export const ALL: Network[] = [ name: 'TLOS', decimals: 18, }, + contracts: { + multicall3: SEQUENCE_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.SKALE_NEBULA, @@ -644,6 +629,9 @@ export const ALL: Network[] = [ name: 'SKALE Fuel', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.SKALE_NEBULA_TESTNET, @@ -661,6 +649,9 @@ export const ALL: Network[] = [ name: 'SKALE Fuel', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.SONEIUM, @@ -678,6 +669,9 @@ export const ALL: Network[] = [ name: 'Ether', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.SONEIUM_MINATO, @@ -695,22 +689,8 @@ export const ALL: Network[] = [ name: 'Ether', decimals: 18, }, - }, - { - chainId: ChainId.TOY_TESTNET, - type: NetworkType.TESTNET, - name: 'toy-testnet', - title: 'TOY (Testnet)', - rpcUrl: getRpcUrl('toy-testnet'), - logoUrl: getLogoUrl(ChainId.TOY_TESTNET), - blockExplorer: { - name: 'TOY Testnet Explorer', - url: 'https://toy-chain-testnet.explorer.caldera.xyz/', - }, - nativeCurrency: { - symbol: 'TOY', - name: 'TOY', - decimals: 18, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, }, }, { @@ -729,6 +709,9 @@ export const ALL: Network[] = [ name: 'IMX', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.IMMUTABLE_ZKEVM_TESTNET, @@ -746,6 +729,9 @@ export const ALL: Network[] = [ name: 'IMX', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.MOONBEAM, @@ -763,6 +749,9 @@ export const ALL: Network[] = [ name: 'GLMR', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.MOONBASE_ALPHA, @@ -780,6 +769,9 @@ export const ALL: Network[] = [ name: 'GLMR', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.ETHERLINK, @@ -797,23 +789,29 @@ export const ALL: Network[] = [ name: 'Tez', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { - chainId: ChainId.ETHERLINK_TESTNET, + chainId: ChainId.ETHERLINK_SHADOWNET_TESTNET, type: NetworkType.TESTNET, - name: 'etherlink-testnet', - title: 'Etherlink Testnet', - rpcUrl: getRpcUrl('etherlink-testnet'), - logoUrl: getLogoUrl(ChainId.ETHERLINK_TESTNET), + name: 'etherlink-shadownet-testnet', + title: 'Etherlink Shadownet Testnet', + rpcUrl: getRpcUrl('etherlink-shadownet-testnet'), + logoUrl: getLogoUrl(ChainId.ETHERLINK_SHADOWNET_TESTNET), blockExplorer: { - name: 'Etherlink Testnet Explorer', - url: 'https://testnet.explorer.etherlink.com/', + name: 'Etherlink Shadownet Testnet Explorer', + url: 'https://shadownet.explorer.etherlink.com/', }, nativeCurrency: { symbol: 'XTZ', name: 'Tez', decimals: 18, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, }, { chainId: ChainId.MONAD, @@ -831,25 +829,10 @@ export const ALL: Network[] = [ name: 'MON', decimals: 18, }, - }, - { - chainId: ChainId.MONAD_TESTNET, - type: NetworkType.TESTNET, - name: 'monad-testnet', - title: 'Monad Testnet', - rpcUrl: getRpcUrl('monad-testnet'), - logoUrl: getLogoUrl(ChainId.MONAD_TESTNET), - blockExplorer: { - name: 'Monad Testnet Explorer', - url: 'https://testnet.monadexplorer.com/', - }, - nativeCurrency: { - symbol: 'MON', - name: 'MON', - decimals: 18, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, }, }, - { chainId: ChainId.SOMNIA, type: NetworkType.MAINNET, @@ -866,6 +849,9 @@ export const ALL: Network[] = [ name: 'SOMI', decimals: 18, }, + contracts: { + multicall3: SEQUENCE_MULTICALL3_ADDRESS, + }, }, { @@ -884,23 +870,8 @@ export const ALL: Network[] = [ name: 'STT', decimals: 18, }, - }, - - { - chainId: ChainId.INCENTIV_TESTNET_V2, - type: NetworkType.TESTNET, - name: 'incentiv-testnet-v2', - title: 'Incentiv Testnet', - rpcUrl: getRpcUrl('incentiv-testnet-v2'), - logoUrl: getLogoUrl(ChainId.INCENTIV_TESTNET_V2), - blockExplorer: { - name: 'Incentiv Testnet Explorer', - url: 'https://explorer.testnet.incentiv.net/', - }, - nativeCurrency: { - symbol: 'TCENT', - name: 'TCENT', - decimals: 18, + contracts: { + multicall3: SEQUENCE_MULTICALL3_ADDRESS, }, }, @@ -920,23 +891,8 @@ export const ALL: Network[] = [ name: 'ETH', decimals: 18, }, - }, - - { - chainId: ChainId.SANDBOX_TESTNET, - type: NetworkType.TESTNET, - name: 'sandbox-testnet', - title: 'Sandbox Testnet', - rpcUrl: getRpcUrl('sandbox-testnet'), - logoUrl: getLogoUrl(ChainId.SANDBOX_TESTNET), - blockExplorer: { - name: 'Sandbox Testnet Explorer', - url: 'https://sandbox-testnet.explorer.caldera.xyz/', - }, - nativeCurrency: { - symbol: 'SAND', - name: 'SAND', - decimals: 18, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, }, }, @@ -956,6 +912,63 @@ export const ALL: Network[] = [ name: 'USDC', decimals: 6, }, + contracts: { + multicall3: DEFAULT_MULTICALL3_ADDRESS, + }, + }, + + { + chainId: ChainId.HYPEREVM, + type: NetworkType.MAINNET, + name: 'hyperevm', + title: 'HyperEVM', + rpcUrl: getRpcUrl('hyperevm'), + logoUrl: getLogoUrl(ChainId.HYPEREVM), + blockExplorer: { + name: 'HyperEVM Explorer', + url: 'https://www.hyperscan.com/', + }, + nativeCurrency: { + symbol: 'HYPE', + name: 'HYPE', + decimals: 18, + }, + }, + + { + chainId: ChainId.BERACHAIN, + type: NetworkType.MAINNET, + name: 'berachain', + title: 'Berachain', + rpcUrl: getRpcUrl('berachain'), + logoUrl: getLogoUrl(ChainId.BERACHAIN), + blockExplorer: { + name: 'Berachain Explorer', + url: 'https://berascan.com/', + }, + nativeCurrency: { + symbol: 'BEAR', + name: 'BEAR', + decimals: 18, + }, + }, + + { + chainId: ChainId.SONIC, + type: NetworkType.MAINNET, + name: 'sonic', + title: 'Sonic', + rpcUrl: getRpcUrl('sonic'), + logoUrl: getLogoUrl(ChainId.SONIC), + blockExplorer: { + name: 'Sonic Explorer', + url: 'https://sonicscan.com/', + }, + nativeCurrency: { + symbol: 'S', + name: 'Sonic', + decimals: 18, + }, }, ] diff --git a/packages/wallet/primitives/src/payload.ts b/packages/wallet/primitives/src/payload.ts index c8f05ee8d..c933a6118 100644 --- a/packages/wallet/primitives/src/payload.ts +++ b/packages/wallet/primitives/src/payload.ts @@ -1,7 +1,7 @@ import { AbiFunction, AbiParameters, Address, Bytes, Hash, Hex } from 'ox' import { getSignPayload } from 'ox/TypedData' import { EXECUTE_USER_OP, RECOVER_SAPIENT_SIGNATURE } from './constants.js' -import { Attestation, Network } from './index.js' +import { Attestation } from './index.js' import { minBytesFor } from './utils.js' import { UserOperation } from 'ox/erc4337' @@ -184,6 +184,10 @@ export function isCalls4337_07(payload: Payload): payload is Calls4337_07 { return payload.type === 'call_4337_07' } +export function isParented(payload: Payload): payload is Parented { + return 'parentWallets' in payload +} + export function toRecovery(payload: T): Recovery { if (isRecovery(payload)) { return payload diff --git a/packages/wallet/primitives/src/session-signature.ts b/packages/wallet/primitives/src/session-signature.ts index a9f5144a1..c3f67ca24 100644 --- a/packages/wallet/primitives/src/session-signature.ts +++ b/packages/wallet/primitives/src/session-signature.ts @@ -1,4 +1,5 @@ import { Address, Bytes, Hash, Hex } from 'ox' +import { Attestation, Extensions, Payload } from './index.js' import { MAX_PERMISSIONS_COUNT } from './permission.js' import { decodeSessionsTopology, @@ -10,7 +11,6 @@ import { } from './session-config.js' import { RSY } from './signature.js' import { minBytesFor, packRSY, unpackRSY } from './utils.js' -import { Attestation, Payload } from './index.js' export type ImplicitSessionCallSignature = { attestation: Attestation.Attestation @@ -273,22 +273,48 @@ export function decodeSessionSignature(encodedSignatures: Bytes.Bytes): { // Call encoding -export function hashCallWithReplayProtection( - payload: Payload.Calls, +/** + * Hashes a call with replay protection parameters. + * @param payload The payload to hash. + * @param callIdx The index of the call to hash. + * @param chainId The chain ID. Use 0 when noChainId enabled. + * @param sessionManagerAddress The session manager address to compile the hash for. Only required to support deprecated hash encodings for Dev1, Dev2 and Rc3. + * @returns The hash of the call with replay protection parameters for sessions. + */ +export function hashPayloadWithCallIdx( + wallet: Address.Address, + payload: Payload.Calls & Payload.Parent, callIdx: number, chainId: number, - skipCallIdx: boolean = false, // Deprecated. Dev1 and Dev2 support + sessionManagerAddress?: Address.Address, ): Hex.Hex { - const call = payload.calls[callIdx]! - return Hex.fromBytes( - Hash.keccak256( - Bytes.concat( - Bytes.fromNumber(chainId, { size: 32 }), - Bytes.fromNumber(payload.space, { size: 32 }), - Bytes.fromNumber(payload.nonce, { size: 32 }), - skipCallIdx ? Bytes.from([]) : Bytes.fromNumber(callIdx, { size: 32 }), - Bytes.fromHex(Payload.hashCall(call)), + // Support deprecated hashes for Dev1, Dev2 and Rc3 + const deprecatedHashing = + sessionManagerAddress && + (Address.isEqual(sessionManagerAddress, Extensions.Dev1.sessions) || + Address.isEqual(sessionManagerAddress, Extensions.Dev2.sessions) || + Address.isEqual(sessionManagerAddress, Extensions.Rc3.sessions)) + if (deprecatedHashing) { + const call = payload.calls[callIdx]! + const ignoreCallIdx = !Address.isEqual(sessionManagerAddress, Extensions.Rc3.sessions) + return Hex.fromBytes( + Hash.keccak256( + Bytes.concat( + Bytes.fromNumber(chainId, { size: 32 }), + Bytes.fromNumber(payload.space, { size: 32 }), + Bytes.fromNumber(payload.nonce, { size: 32 }), + ignoreCallIdx ? Bytes.from([]) : Bytes.fromNumber(callIdx, { size: 32 }), + Bytes.fromHex(Payload.hashCall(call)), + ), ), - ), - ) + ) + } + // Current hashing scheme uses entire payload hash and call index (without last parent) + const parentWallets = payload.parentWallets + if (payload.parentWallets && payload.parentWallets.length > 0) { + payload.parentWallets.pop() + } + const payloadHash = Payload.hash(wallet, chainId, payload) + payload.parentWallets = parentWallets + return Hex.fromBytes(Hash.keccak256(Bytes.concat(payloadHash, Bytes.fromNumber(callIdx, { size: 32 })))) } diff --git a/packages/wallet/primitives/src/signature.ts b/packages/wallet/primitives/src/signature.ts index 2e5d3212c..6ba93f9c1 100644 --- a/packages/wallet/primitives/src/signature.ts +++ b/packages/wallet/primitives/src/signature.ts @@ -1,4 +1,4 @@ -import { AbiFunction, AbiParameters, Address, Bytes, Hash, Hex, Provider, Secp256k1, Signature } from 'ox' +import { AbiFunction, AbiParameters, Address, Bytes, Hash, Hex, Provider, Secp256k1 } from 'ox' import { Config, Leaf, @@ -22,7 +22,7 @@ import { RECOVER_SAPIENT_SIGNATURE, RECOVER_SAPIENT_SIGNATURE_COMPACT, IS_VALID_ import { wrap, decode } from './erc-6492.js' import { fromConfigUpdate, hash, Parented } from './payload.js' import { minBytesFor, packRSY, unpackRSY } from './utils.js' -import { Constants, Network } from './index.js' +import { Constants } from './index.js' export const FLAG_SIGNATURE_HASH = 0 export const FLAG_ADDRESS = 1 @@ -618,7 +618,7 @@ export function fillLeaves( export function encodeChainedSignature(signatures: RawSignature[]): Uint8Array { let flag = 0x01 - let sigForCheckpointer = signatures[signatures.length - 1] + const sigForCheckpointer = signatures[signatures.length - 1] if (sigForCheckpointer?.configuration.checkpointer) { flag |= 0x40 @@ -723,7 +723,7 @@ export function encodeTopology( const isBranching = isNode(topology[1]!) || isRawNode(topology[1]!) if (isBranching) { - let encoded1Size = minBytesFor(BigInt(encoded1.length)) + const encoded1Size = minBytesFor(BigInt(encoded1.length)) if (encoded1Size > 15) { throw new Error('Branch too large') } @@ -799,7 +799,7 @@ export function encodeTopology( } else if (topology.signature.type === 'erc1271') { let flag = FLAG_SIGNATURE_ERC1271 << 4 - let bytesForSignatureSize = minBytesFor(BigInt(topology.signature.data.length)) + const bytesForSignatureSize = minBytesFor(BigInt(topology.signature.data.length)) if (bytesForSignatureSize > 3) { throw new Error('Signature too large') } @@ -826,7 +826,7 @@ export function encodeTopology( let flag = (topology.signature.type === 'sapient' ? FLAG_SIGNATURE_SAPIENT : FLAG_SIGNATURE_SAPIENT_COMPACT) << 4 const signatureBytes = Bytes.fromHex(topology.signature.data) - let bytesForSignatureSize = minBytesFor(BigInt(signatureBytes.length)) + const bytesForSignatureSize = minBytesFor(BigInt(signatureBytes.length)) if (bytesForSignatureSize > 3) { throw new Error('Signature too large') } @@ -1218,7 +1218,7 @@ async function recoverTopology( weight: topology.weight, } - default: + default: { const provider = 'provider' in options!.provider ? options!.provider.provider : options!.provider const block = 'block' in options!.provider ? options!.provider.block : undefined @@ -1254,6 +1254,7 @@ async function recoverTopology( } } } + } } case 'sapient': @@ -1264,7 +1265,7 @@ async function recoverTopology( case 'assume-valid': throw new Error(`unable to validate sapient signer ${topology.signature.address} signature`) - default: + default: { const provider = 'provider' in options!.provider ? options!.provider.provider : options!.provider const block = 'block' in options!.provider ? options!.provider.block : undefined @@ -1298,6 +1299,7 @@ async function recoverTopology( }, weight: topology.weight, } + } } } } else if (isRawNestedLeaf(topology)) { diff --git a/packages/wallet/primitives/src/utils.ts b/packages/wallet/primitives/src/utils.ts index 7a70fb087..3a2b28d46 100644 --- a/packages/wallet/primitives/src/utils.ts +++ b/packages/wallet/primitives/src/utils.ts @@ -1,4 +1,4 @@ -import { AbiParameters, Bytes, Hash, Hex } from 'ox' +import { Bytes } from 'ox' export function minBytesFor(val: bigint): number { return Math.ceil(val.toString(16).length / 2) @@ -7,7 +7,7 @@ export function minBytesFor(val: bigint): number { // ERC-2098 export function packRSY({ r, s, yParity }: { r: bigint; s: bigint; yParity: number }): Bytes.Bytes { const rBytes = Bytes.padLeft(Bytes.fromNumber(r), 32) - let sBytes = Bytes.padLeft(Bytes.fromNumber(s), 32) + const sBytes = Bytes.padLeft(Bytes.fromNumber(s), 32) if (yParity % 2 === 1) { sBytes[0]! |= 0x80 } diff --git a/packages/wallet/primitives/test/address.test.ts b/packages/wallet/primitives/test/address.test.ts index c53ada141..b004dc96d 100644 --- a/packages/wallet/primitives/test/address.test.ts +++ b/packages/wallet/primitives/test/address.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest' import { Address, Bytes, Hash, Hex } from 'ox' import { from } from '../src/address.js' -import { Context, Dev1, Dev2, Rc3 } from '../src/context.js' +import { Context, Dev1, Dev2, Rc3, Rc4, Rc5 } from '../src/context.js' import { Config, hashConfiguration } from '../src/config.js' describe('Address', () => { @@ -88,7 +88,7 @@ describe('Address', () => { }) it('should work with Dev1 context', () => { - const { stage2, ...dev1Context } = Dev1 + const { stage2: _, ...dev1Context } = Dev1 const address = from(sampleConfig, dev1Context) expect(() => Address.assert(address)).not.toThrow() @@ -96,27 +96,53 @@ describe('Address', () => { }) it('should work with Dev2 context', () => { - const { stage2, ...dev2Context } = Dev2 + const { stage2: _stage2_1, ...dev2Context } = Dev2 const address = from(sampleConfig, dev2Context) expect(() => Address.assert(address)).not.toThrow() expect(address).toMatch(/^0x[a-fA-F0-9]{40}$/) // Should be different from Dev1 - const { stage2: _, ...dev1Context } = Dev1 + const { stage2: _stage2_2, ...dev1Context } = Dev1 const dev1Address = from(sampleConfig, dev1Context) expect(address).not.toBe(dev1Address) }) it('should work with Rc3 context', () => { - const { stage2, ...rc3Context } = Rc3 + const { stage2: _stage2_1, ...rc3Context } = Rc3 const address = from(sampleConfig, rc3Context) expect(() => Address.assert(address)).not.toThrow() expect(address).toMatch(/^0x[a-fA-F0-9]{40}$/) // Should be different from Dev2 - const { stage2: _, ...dev2Context } = Dev2 + const { stage2: _stage2_2, ...dev2Context } = Dev2 + const dev2Address = from(sampleConfig, dev2Context) + expect(address).not.toBe(dev2Address) + }) + + it('should work with Rc4 context', () => { + const { stage2: _stage2_1, ...rc4Context } = Rc4 + const address = from(sampleConfig, rc4Context) + + expect(() => Address.assert(address)).not.toThrow() + expect(address).toMatch(/^0x[a-fA-F0-9]{40}$/) + + // Should be different from Dev2 + const { stage2: _stage2_2, ...dev2Context } = Dev2 + const dev2Address = from(sampleConfig, dev2Context) + expect(address).not.toBe(dev2Address) + }) + + it('should work with Rc5 context', () => { + const { stage2: _stage2_1, ...rc5Context } = Rc5 + const address = from(sampleConfig, rc5Context) + + expect(() => Address.assert(address)).not.toThrow() + expect(address).toMatch(/^0x[a-fA-F0-9]{40}$/) + + // Should be different from Dev2 + const { stage2: _stage2_2, ...dev2Context } = Dev2 const dev2Address = from(sampleConfig, dev2Context) expect(address).not.toBe(dev2Address) }) diff --git a/packages/wallet/primitives/test/attestation.test.ts b/packages/wallet/primitives/test/attestation.test.ts index 41a75da07..3708132a7 100644 --- a/packages/wallet/primitives/test/attestation.test.ts +++ b/packages/wallet/primitives/test/attestation.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { Address, Bytes, Hash, Hex } from 'ox' +import { Bytes, Hash } from 'ox' import { Attestation, diff --git a/packages/wallet/primitives/test/config.test.ts b/packages/wallet/primitives/test/config.test.ts index 7df3a5807..b4ff8e6d9 100644 --- a/packages/wallet/primitives/test/config.test.ts +++ b/packages/wallet/primitives/test/config.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { Address, Bytes, Hash, Hex } from 'ox' +import { Bytes } from 'ox' import { Config, @@ -33,11 +33,13 @@ import { maximumDepth, evaluateConfigurationSafety, normalizeSignerSignature, + replaceAddress, } from '../src/config.js' describe('Config', () => { const testAddress1 = '0x742d35cc6635c0532925a3b8d563a6b35b7f05f1' const testAddress2 = '0x8ba1f109551bd432803012645aac136c776056c0' + const replacementAddress = '0x1111111111111111111111111111111111111111' const testImageHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' const testDigest = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef' @@ -316,6 +318,67 @@ describe('Config', () => { }) }) + describe('replaceAddress', () => { + it('should replace signer leaf addresses', () => { + const signerLeaf: SignerLeaf = { ...sampleSignerLeaf } + + const result = replaceAddress(signerLeaf, testAddress1, replacementAddress) + + expect(result).toEqual({ ...sampleSignerLeaf, address: replacementAddress }) + expect(result).not.toBe(signerLeaf) + expect(signerLeaf.address).toBe(testAddress1) + }) + + it('should replace sapient signer leaf addresses', () => { + const sapientLeaf: SapientSignerLeaf = { ...sampleSapientSignerLeaf } + + const result = replaceAddress(sapientLeaf, testAddress2, replacementAddress) + + expect(result).toEqual({ ...sampleSapientSignerLeaf, address: replacementAddress }) + expect(result).not.toBe(sapientLeaf) + expect(sapientLeaf.address).toBe(testAddress2) + }) + + it('should recurse through nodes and nested leaves', () => { + const nestedSapient: SapientSignerLeaf = { ...sampleSapientSignerLeaf, address: testAddress1 } + const nestedLeaf: NestedLeaf = { + type: 'nested', + tree: [nestedSapient, sampleSubdigestLeaf], + weight: 5n, + threshold: 1n, + } + const topology: Topology = [{ ...sampleSignerLeaf }, nestedLeaf] + + const result = replaceAddress(topology, testAddress1, replacementAddress) + + expect(result).toEqual([ + { ...sampleSignerLeaf, address: replacementAddress }, + { + ...nestedLeaf, + tree: [{ ...nestedSapient, address: replacementAddress }, sampleSubdigestLeaf], + }, + ]) + expect(nestedSapient.address).toBe(testAddress1) + expect((nestedLeaf.tree as Node)[1]).toBe(sampleSubdigestLeaf) + }) + + it('should return the original topology when no address matches', () => { + const sapientLeaf: SapientSignerLeaf = { ...sampleSapientSignerLeaf } + + const result = replaceAddress(sapientLeaf, replacementAddress, testAddress1) + + expect(result).toBe(sapientLeaf) + }) + + it('should leave non-signer leaves unchanged', () => { + expect(replaceAddress(sampleSubdigestLeaf, testAddress1, replacementAddress)).toBe(sampleSubdigestLeaf) + expect(replaceAddress(sampleAnyAddressSubdigestLeaf, testAddress1, replacementAddress)).toBe( + sampleAnyAddressSubdigestLeaf, + ) + expect(replaceAddress(sampleNodeLeaf, testAddress1, replacementAddress)).toBe(sampleNodeLeaf) + }) + }) + describe('getWeight', () => { it('should return correct weight for signer leaf with canSign true', () => { const result = getWeight(sampleSignerLeaf, () => true) diff --git a/packages/wallet/primitives/test/erc-6492.test.ts b/packages/wallet/primitives/test/erc-6492.test.ts index dcb7688ee..7398f58db 100644 --- a/packages/wallet/primitives/test/erc-6492.test.ts +++ b/packages/wallet/primitives/test/erc-6492.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest' import { Address, Bytes, Hex, Provider } from 'ox' -import { WrappedSignature } from 'ox/erc6492' +import { SignatureErc6492 } from 'ox/erc6492' import { deploy, wrap, decode, isValid } from '../src/erc-6492.js' import { Context } from '../src/context.js' @@ -82,7 +82,7 @@ describe('ERC-6492', () => { expect(result.startsWith('0x')).toBe(true) // Should end with the magic bytes - expect(result.endsWith(WrappedSignature.magicBytes.slice(2))).toBe(true) + expect(result.endsWith(SignatureErc6492.magicBytes.slice(2))).toBe(true) // Should contain the original signature data somewhere expect(result.length).toBeGreaterThan(testSignature.length) @@ -96,7 +96,7 @@ describe('ERC-6492', () => { // Convert to hex to check magic bytes const resultHex = Bytes.toHex(result) - expect(resultHex.endsWith(WrappedSignature.magicBytes.slice(2))).toBe(true) + expect(resultHex.endsWith(SignatureErc6492.magicBytes.slice(2))).toBe(true) }) it('should return same type as input signature', () => { @@ -133,7 +133,7 @@ describe('ERC-6492', () => { // The wrapped signature should contain encoded: address, bytes (data), bytes (signature) expect(result.length).toBeGreaterThan(testSignature.length + deployData.data.length) expect(result).toContain(testAddress.slice(2)) // Address without 0x - expect(result.endsWith(WrappedSignature.magicBytes.slice(2))).toBe(true) + expect(result.endsWith(SignatureErc6492.magicBytes.slice(2))).toBe(true) }) }) @@ -209,7 +209,7 @@ describe('ERC-6492', () => { it('should handle malformed wrapped signature gracefully', () => { // Create a signature that ends with magic bytes but has invalid encoding - const malformedSig = ('0x1234' + WrappedSignature.magicBytes.slice(2)) as Hex.Hex + const malformedSig = ('0x1234' + SignatureErc6492.magicBytes.slice(2)) as Hex.Hex const result = decode(malformedSig) // Should return original signature when decoding fails @@ -391,7 +391,7 @@ describe('ERC-6492', () => { // 2. Wrap signature with deploy data const wrappedSig = wrap(testSignature, deployCall) - expect(wrappedSig.endsWith(WrappedSignature.magicBytes.slice(2))).toBe(true) + expect(wrappedSig.endsWith(SignatureErc6492.magicBytes.slice(2))).toBe(true) // 3. Decode wrapped signature const decoded = decode(wrappedSig) @@ -457,7 +457,7 @@ describe('ERC-6492', () => { it('should handle signatures that accidentally contain magic bytes', () => { // Create a signature that contains the magic bytes but isn't wrapped - const magicInSignature = (testSignature + WrappedSignature.magicBytes.slice(2) + '1234') as Hex.Hex + const magicInSignature = (testSignature + SignatureErc6492.magicBytes.slice(2) + '1234') as Hex.Hex const result = decode(magicInSignature) // Should try to decode, but if it fails, should return original diff --git a/packages/wallet/primitives/test/passkeys.test.ts b/packages/wallet/primitives/test/passkeys.test.ts index b6aa0990f..ddfd59b9e 100644 --- a/packages/wallet/primitives/test/passkeys.test.ts +++ b/packages/wallet/primitives/test/passkeys.test.ts @@ -104,13 +104,6 @@ describe('Passkeys', () => { y: testPublicKeyY, } - const samplePublicKeyWithHashMetadata: PublicKey = { - requireUserVerification: true, - x: testPublicKeyX, - y: testPublicKeyY, - metadata: testMetadataHash, - } - // Realistic authenticator data based on WebAuthn spec and ox patterns // This represents actual WebAuthn authenticator data structure const sampleAuthenticatorData = Bytes.fromHex( @@ -196,7 +189,7 @@ describe('Passkeys', () => { { name: 'special chars', credentialId: '!@#$%^&*()_+{}|:"<>?[]\\;\',./' }, ] - testCases.forEach(({ name, credentialId }) => { + testCases.forEach(({ credentialId }) => { const metadata: PasskeyMetadata = { credentialId } const tree = metadataTree(metadata) expect(GenericTree.isLeaf(tree)).toBe(true) @@ -709,7 +702,7 @@ describe('Passkeys', () => { }, ] - specialCharTests.forEach(({ name, credentialId, clientData }) => { + specialCharTests.forEach(({ credentialId, clientData }) => { if (credentialId) { const unicodeMetadata: PasskeyMetadata = { credentialId } const tree = metadataTree(unicodeMetadata) @@ -787,7 +780,7 @@ describe('Passkeys', () => { { userVerification: true, metadata: true, description: 'User verification with metadata' }, ] - testCombinations.forEach(({ userVerification, metadata, description }) => { + testCombinations.forEach(({ userVerification, metadata }) => { const pubKey = createValidPublicKey({ requireUserVerification: userVerification, ...(metadata && { metadata: samplePasskeyMetadata }), diff --git a/packages/wallet/primitives/test/payload.test.ts b/packages/wallet/primitives/test/payload.test.ts index 72b831d22..33884dbdb 100644 --- a/packages/wallet/primitives/test/payload.test.ts +++ b/packages/wallet/primitives/test/payload.test.ts @@ -1,6 +1,5 @@ -import { describe, expect, it, vi } from 'vitest' -import { Address, Bytes, Hash, Hex } from 'ox' -import { UserOperation } from 'ox/erc4337' +import { describe, expect, it } from 'vitest' +import { Address, Bytes, Hex } from 'ox' import { KIND_TRANSACTIONS, @@ -17,11 +16,7 @@ import { Digest, SessionImplicitAuthorize, Calls4337_07, - Recovery, - MayRecoveryPayload, - Payload, Parented, - TypedDataToSign, SolidityDecoded, fromMessage, fromConfigUpdate, diff --git a/packages/wallet/primitives/test/precondition.test.ts b/packages/wallet/primitives/test/precondition.test.ts index c994e8a52..84f919d62 100644 --- a/packages/wallet/primitives/test/precondition.test.ts +++ b/packages/wallet/primitives/test/precondition.test.ts @@ -1,7 +1,6 @@ import { describe, expect, it } from 'vitest' import { - Precondition, NativeBalancePrecondition, Erc20BalancePrecondition, Erc20ApprovalPrecondition, @@ -10,7 +9,6 @@ import { Erc1155BalancePrecondition, Erc1155ApprovalPrecondition, AnyPrecondition, - IntentPrecondition, isValidPreconditionType, createPrecondition, createIntentPrecondition, @@ -225,13 +223,13 @@ describe('Precondition', () => { }) it('should throw for null precondition', () => { - expect(() => createPrecondition(null as any)).toThrow( + expect(() => createPrecondition(null as unknown as AnyPrecondition)).toThrow( "Invalid precondition object: missing or invalid 'type' property.", ) }) it('should throw for undefined precondition', () => { - expect(() => createPrecondition(undefined as any)).toThrow( + expect(() => createPrecondition(undefined as unknown as AnyPrecondition)).toThrow( "Invalid precondition object: missing or invalid 'type' property.", ) }) @@ -240,7 +238,7 @@ describe('Precondition', () => { const invalidPrecondition = { address: testAddress, min: testMinAmount, - } as any + } as unknown as AnyPrecondition expect(() => createPrecondition(invalidPrecondition)).toThrow( "Invalid precondition object: missing or invalid 'type' property.", ) @@ -250,7 +248,7 @@ describe('Precondition', () => { const invalidPrecondition = { type: 'invalid-type', address: testAddress, - } as any + } as unknown as AnyPrecondition expect(() => createPrecondition(invalidPrecondition)).toThrow( "Invalid precondition object: missing or invalid 'type' property.", ) @@ -260,7 +258,7 @@ describe('Precondition', () => { const invalidPrecondition = { type: 123, address: testAddress, - } as any + } as unknown as AnyPrecondition expect(() => createPrecondition(invalidPrecondition)).toThrow( "Invalid precondition object: missing or invalid 'type' property.", ) @@ -386,7 +384,7 @@ describe('Precondition', () => { const invalidPrecondition = { type: 'invalid-type', address: testAddress, - } as any + } as unknown as AnyPrecondition expect(() => createIntentPrecondition(invalidPrecondition)).toThrow('Invalid precondition type: invalid-type') }) diff --git a/packages/wallet/primitives/test/session-signature.test.ts b/packages/wallet/primitives/test/session-signature.test.ts index b8efe28c2..a1fd0fe23 100644 --- a/packages/wallet/primitives/test/session-signature.test.ts +++ b/packages/wallet/primitives/test/session-signature.test.ts @@ -11,7 +11,7 @@ import { encodeSessionCallSignatureForJson, encodeSessionSignature, ExplicitSessionCallSignature, - hashCallWithReplayProtection, + hashPayloadWithCallIdx, ImplicitSessionCallSignature, isExplicitSessionCallSignature, isImplicitSessionCallSignature, @@ -21,6 +21,7 @@ import { sessionCallSignatureToJson, } from '../src/session-signature.js' import { RSY } from '../src/signature.js' +import { Extensions } from '../src/index.js' describe('Session Signature', () => { // Test data @@ -444,24 +445,25 @@ describe('Session Signature', () => { }) describe('Helper Functions', () => { - describe('hashCallWithReplayProtection', () => { + describe('hashPayloadWithCallIdx', () => { it('should hash call with replay protection parameters', () => { - const result = hashCallWithReplayProtection(samplePayload, 0, testChainId) + const result = hashPayloadWithCallIdx(testAddress1, samplePayload, 0, testChainId) expect(result).toMatch(/^0x[0-9a-f]{64}$/) // 32-byte hex string expect(Hex.size(result)).toBe(32) }) it('should produce different hashes for different chain IDs', () => { - const hash1 = hashCallWithReplayProtection(samplePayload, 0, ChainId.MAINNET) - const hash2 = hashCallWithReplayProtection(samplePayload, 0, ChainId.POLYGON) + const hash1 = hashPayloadWithCallIdx(testAddress1, samplePayload, 0, ChainId.MAINNET) + const hash2 = hashPayloadWithCallIdx(testAddress1, samplePayload, 0, ChainId.POLYGON) expect(hash1).not.toBe(hash2) }) it('should produce different hashes for different spaces', () => { - const hash1 = hashCallWithReplayProtection(samplePayload, 0, testChainId) - const hash2 = hashCallWithReplayProtection( + const hash1 = hashPayloadWithCallIdx(testAddress1, samplePayload, 0, testChainId) + const hash2 = hashPayloadWithCallIdx( + testAddress1, { ...samplePayload, space: samplePayload.space + 1n }, 0, testChainId, @@ -471,8 +473,9 @@ describe('Session Signature', () => { }) it('should produce different hashes for different nonces', () => { - const hash1 = hashCallWithReplayProtection(samplePayload, 0, testChainId) - const hash2 = hashCallWithReplayProtection( + const hash1 = hashPayloadWithCallIdx(testAddress1, samplePayload, 0, testChainId) + const hash2 = hashPayloadWithCallIdx( + testAddress1, { ...samplePayload, nonce: samplePayload.nonce + 1n }, 0, testChainId, @@ -488,17 +491,51 @@ describe('Session Signature', () => { } const payload2 = { ...samplePayload, calls: [call2] } - const hash1 = hashCallWithReplayProtection(samplePayload, 0, testChainId) - const hash2 = hashCallWithReplayProtection(payload2, 0, testChainId) + const hash1 = hashPayloadWithCallIdx(testAddress1, samplePayload, 0, testChainId) + const hash2 = hashPayloadWithCallIdx(testAddress1, payload2, 0, testChainId) expect(hash1).not.toBe(hash2) }) + it('should produce different hashes for different wallets', () => { + const payload = { ...samplePayload, calls: [sampleCall, sampleCall] } + + const hash1 = hashPayloadWithCallIdx(testAddress1, payload, 0, testChainId) + const hash2 = hashPayloadWithCallIdx(testAddress2, payload, 0, testChainId) + + expect(hash1).not.toBe(hash2) + }) + + it('should NOT produce different hashes for different wallets when using deprecated hash encoding for Dev1 and Dev2', () => { + // This is ONLY for backward compatibility with Dev1 and Dev2 + // This is exploitable and should not be used in practice + const payload = { ...samplePayload, calls: [sampleCall, sampleCall] } + + const hash1 = hashPayloadWithCallIdx(testAddress1, payload, 0, testChainId, Extensions.Dev1.sessions) + const hash2 = hashPayloadWithCallIdx(testAddress2, payload, 0, testChainId, Extensions.Dev2.sessions) + + expect(hash1).toBe(hash2) + }) + + it('should produce different hashes for different wallets when using deprecated hash encoding for Dev1/2, Rc3 and latest', () => { + // This is ONLY for backward compatibility with Rc3 + // This is exploitable and should not be used in practice + const payload = { ...samplePayload, calls: [sampleCall, sampleCall] } + + const hash1 = hashPayloadWithCallIdx(testAddress1, payload, 0, testChainId, Extensions.Dev1.sessions) + const hash2 = hashPayloadWithCallIdx(testAddress2, payload, 0, testChainId, Extensions.Rc3.sessions) + const hash3 = hashPayloadWithCallIdx(testAddress2, payload, 0, testChainId) + + expect(hash1).not.toBe(hash2) + expect(hash1).not.toBe(hash3) + expect(hash2).not.toBe(hash3) + }) + it('should produce different hashes for same call at different index', () => { const payload = { ...samplePayload, calls: [sampleCall, sampleCall] } - const hash1 = hashCallWithReplayProtection(payload, 0, testChainId) - const hash2 = hashCallWithReplayProtection(payload, 1, testChainId) + const hash1 = hashPayloadWithCallIdx(testAddress1, payload, 0, testChainId) + const hash2 = hashPayloadWithCallIdx(testAddress1, payload, 1, testChainId) expect(hash1).not.toBe(hash2) }) @@ -508,15 +545,15 @@ describe('Session Signature', () => { // This is exploitable and should not be used in practice const payload = { ...samplePayload, calls: [sampleCall, sampleCall] } - const hash1 = hashCallWithReplayProtection(payload, 0, testChainId, true) - const hash2 = hashCallWithReplayProtection(payload, 1, testChainId, true) + const hash1 = hashPayloadWithCallIdx(testAddress1, payload, 0, testChainId, Extensions.Dev1.sessions) + const hash2 = hashPayloadWithCallIdx(testAddress1, payload, 1, testChainId, Extensions.Dev1.sessions) expect(hash1).toBe(hash2) }) it('should be deterministic', () => { - const hash1 = hashCallWithReplayProtection(samplePayload, 0, testChainId) - const hash2 = hashCallWithReplayProtection(samplePayload, 0, testChainId) + const hash1 = hashPayloadWithCallIdx(testAddress1, samplePayload, 0, testChainId) + const hash2 = hashPayloadWithCallIdx(testAddress1, samplePayload, 0, testChainId) expect(hash1).toBe(hash2) }) @@ -526,7 +563,8 @@ describe('Session Signature', () => { const largeSpace = 2n ** 16n const largeNonce = 2n ** 24n - const result = hashCallWithReplayProtection( + const result = hashPayloadWithCallIdx( + testAddress1, { ...samplePayload, space: largeSpace, nonce: largeNonce }, 0, largeChainId, @@ -535,7 +573,7 @@ describe('Session Signature', () => { }) it('should handle zero values', () => { - const result = hashCallWithReplayProtection({ ...samplePayload, space: 0n, nonce: 0n }, 0, 0) + const result = hashPayloadWithCallIdx(testAddress1, { ...samplePayload, space: 0n, nonce: 0n }, 0, 0) expect(result).toMatch(/^0x[0-9a-f]{64}$/) }) @@ -546,7 +584,7 @@ describe('Session Signature', () => { } const payload = { ...samplePayload, calls: [callWithEmptyData] } - const result = hashCallWithReplayProtection(payload, 0, testChainId) + const result = hashPayloadWithCallIdx(testAddress1, payload, 0, testChainId) expect(result).toMatch(/^0x[0-9a-f]{64}$/) }) @@ -557,8 +595,8 @@ describe('Session Signature', () => { } const payload = { ...samplePayload, calls: [delegateCall] } - const hash1 = hashCallWithReplayProtection(samplePayload, 0, testChainId) - const hash2 = hashCallWithReplayProtection(payload, 0, testChainId) + const hash1 = hashPayloadWithCallIdx(testAddress1, samplePayload, 0, testChainId) + const hash2 = hashPayloadWithCallIdx(testAddress1, payload, 0, testChainId) expect(hash1).not.toBe(hash2) }) @@ -735,12 +773,15 @@ describe('Session Signature', () => { const calls: Payload.Call[] = [ sampleCall, { ...sampleCall, to: testAddress2 }, + { ...sampleCall, to: testAddress2 }, // Repeat call { ...sampleCall, value: 500000000000000000n }, ] const payload = { ...samplePayload, calls: calls } // Generate hashes for each call - const hashes = calls.map((call) => hashCallWithReplayProtection(payload, calls.indexOf(call), testChainId)) + const hashes = calls.map((call) => + hashPayloadWithCallIdx(testAddress1, payload, calls.indexOf(call), testChainId), + ) // All hashes should be valid and different for (let i = 0; i < hashes.length; i++) { diff --git a/packages/wallet/primitives/test/signature.test.ts b/packages/wallet/primitives/test/signature.test.ts index 8738b9bcf..e3261948a 100644 --- a/packages/wallet/primitives/test/signature.test.ts +++ b/packages/wallet/primitives/test/signature.test.ts @@ -14,7 +14,6 @@ import { FLAG_SIGNATURE_SAPIENT, FLAG_SIGNATURE_SAPIENT_COMPACT, RSY, - SignatureOfSignerLeafEthSign, SignatureOfSignerLeafHash, SignatureOfSignerLeafErc1271, SignatureOfSapientSignerLeaf, @@ -42,7 +41,7 @@ import { recover, } from '../src/signature.js' import { packRSY } from '../src/utils.js' -import { Config, SignerLeaf, SapientSignerLeaf } from '../src/config.js' +import { SignerLeaf, SapientSignerLeaf, SubdigestLeaf, Topology, NestedLeaf } from '../src/config.js' import * as Payload from '../src/payload.js' import { ChainId } from '../src/network.js' @@ -63,11 +62,6 @@ describe('Signature', () => { ...sampleRSY, } - const sampleEthSignSignature: SignatureOfSignerLeafEthSign = { - type: 'eth_sign', - ...sampleRSY, - } - const sampleErc1271Signature: SignatureOfSignerLeafErc1271 = { type: 'erc1271', address: testAddress, @@ -377,7 +371,7 @@ describe('Signature', () => { expect(result.nodes).toHaveLength(1) expect(result.leftover).toHaveLength(0) - const node = result.nodes[0] as any + const node = result.nodes[0] as SubdigestLeaf expect(node.type).toBe('subdigest') expect(node.digest).toBe(digest) }) @@ -517,7 +511,7 @@ describe('Signature', () => { }) it('should throw for invalid topology', () => { - expect(() => encodeTopology({} as any)).toThrow('Invalid topology') + expect(() => encodeTopology({} as Topology)).toThrow('Invalid topology') }) }) @@ -696,9 +690,9 @@ describe('Signature', () => { const signatureProvider = () => sampleHashSignature - const result = fillLeaves(nestedTopology, signatureProvider) - expect((result as any).type).toBe('nested') - expect((result as any).tree).toHaveProperty('signature') + const result = fillLeaves(nestedTopology, signatureProvider) as NestedLeaf + expect(result.type).toBe('nested') + expect(result.tree).toHaveProperty('signature') }) it('should handle topology without signatures', () => { @@ -751,12 +745,12 @@ describe('Signature', () => { const result = fillLeaves(binaryTree, signatureProvider) expect(Array.isArray(result)).toBe(true) - expect((result as any)[0]).toHaveProperty('signature') - expect((result as any)[1]).toHaveProperty('signature') + expect(result[0]).toHaveProperty('signature') + expect(result[1]).toHaveProperty('signature') }) it('should throw for invalid topology', () => { - expect(() => fillLeaves({} as any, () => undefined)).toThrow('Invalid topology') + expect(() => fillLeaves({} as Topology, () => undefined)).toThrow('Invalid topology') }) }) }) @@ -917,7 +911,7 @@ describe('Signature', () => { }, ] - signatures.forEach(({ topology, name }) => { + signatures.forEach(({ topology }) => { const signature = { noChainId: false, configuration: { @@ -1760,7 +1754,7 @@ describe('Signature', () => { }, ] - for (const { name, payload } of payloadTypes) { + for (const { payload } of payloadTypes) { const sapientSignature: RawSignature = { noChainId: false, configuration: { diff --git a/packages/wallet/primitives/test/utils.test.ts b/packages/wallet/primitives/test/utils.test.ts index 76ba462f0..dc8392c32 100644 --- a/packages/wallet/primitives/test/utils.test.ts +++ b/packages/wallet/primitives/test/utils.test.ts @@ -248,7 +248,7 @@ describe('Utils', () => { it('should revive Uint8Array values', () => { const reviver = createJSONReviver() - const result = reviver('test', { __uint8array: [1, 2, 3, 255] }) + const result = reviver('test', { __uint8array: [1, 2, 3, 255] }) as Uint8Array expect(result).toBeInstanceOf(Uint8Array) expect(Array.from(result)).toEqual([1, 2, 3, 255]) @@ -314,7 +314,7 @@ describe('Utils', () => { it('should handle empty Uint8Array', () => { const reviver = createJSONReviver() - const result = reviver('test', { __uint8array: [] }) + const result = reviver('test', { __uint8array: [] }) as Uint8Array expect(result).toBeInstanceOf(Uint8Array) expect(result.length).toBe(0) @@ -401,7 +401,7 @@ describe('Utils', () => { it('should deserialize objects with BigInt', () => { const json = '{"value":{"__bigint":"0x75bcd15"},"name":"test"}' - const result = fromJSON(json) + const result = fromJSON(json) as { value: bigint; name: string } expect(result.value).toBe(123456789n) expect(result.name).toBe('test') @@ -409,7 +409,7 @@ describe('Utils', () => { it('should deserialize objects with Uint8Array', () => { const json = '{"data":{"__uint8array":[1,2,3]},"name":"test"}' - const result = fromJSON(json) + const result = fromJSON(json) as { data: Uint8Array; name: string } expect(result.data).toBeInstanceOf(Uint8Array) expect(Array.from(result.data)).toEqual([1, 2, 3]) diff --git a/packages/wallet/wdk/.env.test b/packages/wallet/wdk/.env.test deleted file mode 100644 index 84a53e8c0..000000000 --- a/packages/wallet/wdk/.env.test +++ /dev/null @@ -1,5 +0,0 @@ -PRIVATE_KEY= -# When using an RPC, use cors-anywhere. docker run -d -p 8080:8080 redocly/cors-anywhere. http://localhost:8080/https... -RPC_URL= -RELAYER_PK= - diff --git a/packages/wallet/wdk/CHANGELOG.md b/packages/wallet/wdk/CHANGELOG.md new file mode 100644 index 000000000..6793c3f98 --- /dev/null +++ b/packages/wallet/wdk/CHANGELOG.md @@ -0,0 +1,445 @@ +# @0xsequence/wallet-wdk + +## 3.1.1 + +### Patch Changes + +- WDK fix +- Updated dependencies + - @0xsequence/guard@3.0.12 + - @0xsequence/identity-instrument@3.0.12 + - @0xsequence/relayer@3.1.1 + - @0xsequence/wallet-core@3.0.12 + - @0xsequence/wallet-primitives@3.0.12 + +## 3.1.0 + +### Minor Changes + +- 55121af: Carry `sponsored` / `failed` through `StandardRelayerOption`. + + `StandardRelayerOption` now exposes two optional fields: + - `sponsored?: boolean` — populated from the relayer SDK's new `feeOptions` + return field. `true` means the server confirmed an active sponsorship policy + match; `false` means it did not (or the quote failed). + - `failed?: boolean` — `true` when the relayer's `feeOptions` call was swallowed + due to a transport or server error. + + Both fields are populated on the empty-options construction branch and the + per-option mapping branch in `transactions.ts`. The new `isStandardRelayerOption` + and `isERC4337RelayerOption` runtime helpers are now re-exported from the + package root for consumers that need to narrow `RelayerOption` before reading + the new fields. + + UI / wallet consumers that previously classified sponsorship by "no `feeOption` + attached" should switch to `sponsored === true` so a real subsidy is no longer + indistinguishable from a swallowed `/FeeOptions` error. + +### Patch Changes + +- Fix for relayer sponsored fees +- Updated dependencies +- Updated dependencies [55121af] + - @0xsequence/guard@3.0.11 + - @0xsequence/identity-instrument@3.0.11 + - @0xsequence/relayer@3.1.0 + - @0xsequence/wallet-core@3.0.11 + - @0xsequence/wallet-primitives@3.0.11 + +## 3.0.10 + +### Patch Changes + +- Minor network updates, relayer interface +- Updated dependencies + - @0xsequence/guard@3.0.10 + - @0xsequence/identity-instrument@3.0.10 + - @0xsequence/relayer@3.0.10 + - @0xsequence/wallet-core@3.0.10 + - @0xsequence/wallet-primitives@3.0.10 + +## 3.0.9 + +### Patch Changes + +- Fee options fixes +- Updated dependencies + - @0xsequence/guard@3.0.9 + - @0xsequence/identity-instrument@3.0.9 + - @0xsequence/relayer@3.0.9 + - @0xsequence/wallet-core@3.0.9 + - @0xsequence/wallet-primitives@3.0.9 + +## 3.0.8 + +### Patch Changes + +- Bug fix for relayer fee options handling +- Updated dependencies + - @0xsequence/guard@3.0.8 + - @0xsequence/identity-instrument@3.0.8 + - @0xsequence/relayer@3.0.8 + - @0xsequence/wallet-core@3.0.8 + - @0xsequence/wallet-primitives@3.0.8 + +## 3.0.7 + +### Patch Changes + +- Minor bug fixes +- Updated dependencies + - @0xsequence/guard@3.0.7 + - @0xsequence/identity-instrument@3.0.7 + - @0xsequence/relayer@3.0.7 + - @0xsequence/wallet-core@3.0.7 + - @0xsequence/wallet-primitives@3.0.7 + +## 3.0.6 + +### Patch Changes + +- userdata upgrade, arweave support +- Updated dependencies + - @0xsequence/guard@3.0.6 + - @0xsequence/identity-instrument@3.0.6 + - @0xsequence/relayer@3.0.6 + - @0xsequence/wallet-core@3.0.6 + - @0xsequence/wallet-primitives@3.0.6 + +## 3.0.5 + +### Patch Changes + +- Account federation support +- Updated dependencies + - @0xsequence/guard@3.0.5 + - @0xsequence/identity-instrument@3.0.5 + - @0xsequence/relayer@3.0.5 + - @0xsequence/wallet-core@3.0.5 + - @0xsequence/wallet-primitives@3.0.5 + +## 3.0.4 + +### Patch Changes + +- id-token login support +- Updated dependencies + - @0xsequence/guard@3.0.4 + - @0xsequence/identity-instrument@3.0.4 + - @0xsequence/relayer@3.0.4 + - @0xsequence/wallet-core@3.0.4 + - @0xsequence/wallet-primitives@3.0.4 + +## 3.0.3 + +### Patch Changes + +- 3.0.3 +- Updated dependencies + - @0xsequence/guard@3.0.3 + - @0xsequence/identity-instrument@3.0.3 + - @0xsequence/relayer@3.0.3 + - @0xsequence/wallet-core@3.0.3 + - @0xsequence/wallet-primitives@3.0.3 + +## 3.0.2 + +### Patch Changes + +- allow native self transfer +- Updated dependencies + - @0xsequence/guard@3.0.2 + - @0xsequence/identity-instrument@3.0.2 + - @0xsequence/relayer@3.0.2 + - @0xsequence/wallet-core@3.0.2 + - @0xsequence/wallet-primitives@3.0.2 + +## 3.0.1 + +### Patch Changes + +- Network and session fixes +- Updated dependencies + - @0xsequence/guard@3.0.1 + - @0xsequence/identity-instrument@3.0.1 + - @0xsequence/relayer@3.0.1 + - @0xsequence/wallet-core@3.0.1 + - @0xsequence/wallet-primitives@3.0.1 + +## 3.0.0 + +### Patch Changes + +- f68be62: ethauth support +- 49d8a2f: New chains, minor fixes +- 3411232: Beta release with dapp connector fixes +- 23cb9e9: New chains, relayer rpc fix +- f5f6a7a: dapp-client updates +- e7de3b1: Fix signer 404 error, minor fixes +- 493836f: multicall3 optimization +- 30e1f1a: 3.0.0 beta +- d5017e8: Beta release for v3 +- 24a5fab: Final RC before 3.0.0 +- e5e1a03: Apple auth fixes +- 0b63113: Apple auth fix +- a89134a: Userdata service updates +- 7c6c811: 3.0.0-beta.3 with fixes +- 3.0.0 release +- 98ce38b: 3.0.0-beta.2 with identity instrument updates +- 747e6b5: Relayer fee options fix +- 40c19ff: dapp client updates for EOA login +- 6d5de25: 3.0.0-beta.1 +- 934acd1: RC5 upgrade +- Updated dependencies [f68be62] +- Updated dependencies [49d8a2f] +- Updated dependencies [3411232] +- Updated dependencies [23cb9e9] +- Updated dependencies [f5f6a7a] +- Updated dependencies [e7de3b1] +- Updated dependencies [493836f] +- Updated dependencies [30e1f1a] +- Updated dependencies [d5017e8] +- Updated dependencies [24a5fab] +- Updated dependencies [e5e1a03] +- Updated dependencies [0b63113] +- Updated dependencies [a89134a] +- Updated dependencies [7c6c811] +- Updated dependencies +- Updated dependencies [98ce38b] +- Updated dependencies [747e6b5] +- Updated dependencies [40c19ff] +- Updated dependencies [6d5de25] +- Updated dependencies [934acd1] + - @0xsequence/guard@3.0.0 + - @0xsequence/identity-instrument@3.0.0 + - @0xsequence/relayer@3.0.0 + - @0xsequence/wallet-core@3.0.0 + - @0xsequence/wallet-primitives@3.0.0 + +## 3.0.0-beta.19 + +### Patch Changes + +- Final RC before 3.0.0 +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.19 + - @0xsequence/identity-instrument@3.0.0-beta.19 + - @0xsequence/relayer@3.0.0-beta.19 + - @0xsequence/wallet-core@3.0.0-beta.19 + - @0xsequence/wallet-primitives@3.0.0-beta.19 + +## 3.0.0-beta.18 + +### Patch Changes + +- multicall3 optimization +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.18 + - @0xsequence/identity-instrument@3.0.0-beta.18 + - @0xsequence/relayer@3.0.0-beta.18 + - @0xsequence/wallet-core@3.0.0-beta.18 + - @0xsequence/wallet-primitives@3.0.0-beta.18 + +## 3.0.0-beta.17 + +### Patch Changes + +- New chains, relayer rpc fix +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.17 + - @0xsequence/identity-instrument@3.0.0-beta.17 + - @0xsequence/relayer@3.0.0-beta.17 + - @0xsequence/wallet-core@3.0.0-beta.17 + - @0xsequence/wallet-primitives@3.0.0-beta.17 + +## 3.0.0-beta.16 + +### Patch Changes + +- ethauth support +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.16 + - @0xsequence/identity-instrument@3.0.0-beta.16 + - @0xsequence/relayer@3.0.0-beta.16 + - @0xsequence/wallet-core@3.0.0-beta.16 + - @0xsequence/wallet-primitives@3.0.0-beta.16 + +## 3.0.0-beta.15 + +### Patch Changes + +- New chains, minor fixes +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.15 + - @0xsequence/identity-instrument@3.0.0-beta.15 + - @0xsequence/relayer@3.0.0-beta.15 + - @0xsequence/wallet-core@3.0.0-beta.15 + - @0xsequence/wallet-primitives@3.0.0-beta.15 + +## 3.0.0-beta.14 + +### Patch Changes + +- Relayer fee options fix +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.14 + - @0xsequence/identity-instrument@3.0.0-beta.14 + - @0xsequence/relayer@3.0.0-beta.14 + - @0xsequence/wallet-core@3.0.0-beta.14 + - @0xsequence/wallet-primitives@3.0.0-beta.14 + +## 3.0.0-beta.13 + +### Patch Changes + +- Userdata service updates +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.13 + - @0xsequence/identity-instrument@3.0.0-beta.13 + - @0xsequence/relayer@3.0.0-beta.13 + - @0xsequence/wallet-core@3.0.0-beta.13 + - @0xsequence/wallet-primitives@3.0.0-beta.13 + +## 3.0.0-beta.12 + +### Patch Changes + +- Beta release with dapp connector fixes +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.12 + - @0xsequence/identity-instrument@3.0.0-beta.12 + - @0xsequence/relayer@3.0.0-beta.12 + - @0xsequence/wallet-core@3.0.0-beta.12 + - @0xsequence/wallet-primitives@3.0.0-beta.12 + +## 3.0.0-beta.11 + +### Patch Changes + +- 3.0.0 beta +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.11 + - @0xsequence/identity-instrument@3.0.0-beta.11 + - @0xsequence/relayer@3.0.0-beta.11 + - @0xsequence/wallet-core@3.0.0-beta.11 + - @0xsequence/wallet-primitives@3.0.0-beta.11 + +## 3.0.0-beta.10 + +### Patch Changes + +- dapp-client updates +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.10 + - @0xsequence/identity-instrument@3.0.0-beta.10 + - @0xsequence/relayer@3.0.0-beta.10 + - @0xsequence/wallet-core@3.0.0-beta.10 + - @0xsequence/wallet-primitives@3.0.0-beta.10 + +## 3.0.0-beta.9 + +### Patch Changes + +- dapp client updates for EOA login +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.9 + - @0xsequence/identity-instrument@3.0.0-beta.9 + - @0xsequence/relayer@3.0.0-beta.9 + - @0xsequence/wallet-core@3.0.0-beta.9 + - @0xsequence/wallet-primitives@3.0.0-beta.9 + +## 3.0.0-beta.8 + +### Patch Changes + +- Apple auth fixes +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.8 + - @0xsequence/identity-instrument@3.0.0-beta.8 + - @0xsequence/relayer@3.0.0-beta.8 + - @0xsequence/wallet-core@3.0.0-beta.8 + - @0xsequence/wallet-primitives@3.0.0-beta.8 + +## 3.0.0-beta.7 + +### Patch Changes + +- Apple auth fix +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.7 + - @0xsequence/identity-instrument@3.0.0-beta.7 + - @0xsequence/relayer@3.0.0-beta.7 + - @0xsequence/wallet-core@3.0.0-beta.7 + - @0xsequence/wallet-primitives@3.0.0-beta.7 + +## 3.0.0-beta.6 + +### Patch Changes + +- Fix signer 404 error, minor fixes +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.6 + - @0xsequence/identity-instrument@3.0.0-beta.6 + - @0xsequence/relayer@3.0.0-beta.6 + - @0xsequence/wallet-core@3.0.0-beta.6 + - @0xsequence/wallet-primitives@3.0.0-beta.6 + +## 3.0.0-beta.5 + +### Patch Changes + +- Beta release for v3 +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.5 + - @0xsequence/identity-instrument@3.0.0-beta.5 + - @0xsequence/relayer@3.0.0-beta.5 + - @0xsequence/wallet-core@3.0.0-beta.5 + - @0xsequence/wallet-primitives@3.0.0-beta.5 + +## 3.0.0-beta.4 + +### Patch Changes + +- RC5 upgrade +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.4 + - @0xsequence/identity-instrument@3.0.0-beta.4 + - @0xsequence/relayer@3.0.0-beta.4 + - @0xsequence/wallet-core@3.0.0-beta.4 + - @0xsequence/wallet-primitives@3.0.0-beta.4 + +## 3.0.0-beta.3 + +### Patch Changes + +- 3.0.0-beta.3 with fixes +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.3 + - @0xsequence/identity-instrument@3.0.0-beta.3 + - @0xsequence/relayer@3.0.0-beta.3 + - @0xsequence/wallet-core@3.0.0-beta.3 + - @0xsequence/wallet-primitives@3.0.0-beta.3 + +## 3.0.0-beta.2 + +### Patch Changes + +- 3.0.0-beta.2 with identity instrument updates +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.2 + - @0xsequence/identity-instrument@3.0.0-beta.2 + - @0xsequence/relayer@3.0.0-beta.2 + - @0xsequence/wallet-core@3.0.0-beta.2 + - @0xsequence/wallet-primitives@3.0.0-beta.2 + +## 3.0.0-beta.1 + +### Patch Changes + +- 3.0.0-beta.1 +- Updated dependencies + - @0xsequence/guard@3.0.0-beta.1 + - @0xsequence/identity-instrument@3.0.0-beta.1 + - @0xsequence/relayer@3.0.0-beta.1 + - @0xsequence/wallet-core@3.0.0-beta.1 + - @0xsequence/wallet-primitives@3.0.0-beta.1 diff --git a/packages/wallet/wdk/eslint.config.js b/packages/wallet/wdk/eslint.config.js new file mode 100644 index 000000000..d10bbd1e9 --- /dev/null +++ b/packages/wallet/wdk/eslint.config.js @@ -0,0 +1,12 @@ +import { config as baseConfig } from '@repo/eslint-config/base' + +/** @type {import("eslint").Linter.Config} */ +export default [ + ...baseConfig, + { + // files: ['**/*.{test,spec}.ts'], + rules: { + '@typescript-eslint/no-explicit-any': 'off', + }, + }, +] diff --git a/packages/wallet/wdk/package.json b/packages/wallet/wdk/package.json index 9bbf85a4c..875f24c81 100644 --- a/packages/wallet/wdk/package.json +++ b/packages/wallet/wdk/package.json @@ -1,6 +1,6 @@ { "name": "@0xsequence/wallet-wdk", - "version": "0.0.0", + "version": "3.1.1", "license": "Apache-2.0", "type": "module", "publishConfig": { @@ -10,10 +10,12 @@ "scripts": { "build": "tsc", "dev": "tsc --watch", - "test": "vitest run", + "test": "vitest run && npm run test:ssr", "test:coverage": "vitest run --coverage", + "test:ssr": "node test/test-ssr-safety.js", "typecheck": "tsc --noEmit", - "clean": "rimraf dist" + "clean": "rimraf dist", + "lint": "eslint . --max-warnings 0" }, "exports": { ".": { @@ -22,14 +24,15 @@ } }, "devDependencies": { + "@repo/eslint-config": "workspace:^", "@repo/typescript-config": "workspace:^", - "@types/node": "^22.15.29", - "@vitest/coverage-v8": "^3.2.4", - "dotenv": "^16.5.0", - "fake-indexeddb": "^6.0.1", - "happy-dom": "^17.2.2", - "typescript": "^5.8.3", - "vitest": "^3.2.1" + "@types/node": "^25.3.0", + "@vitest/coverage-v8": "^4.0.18", + "dotenv": "^17.3.1", + "fake-indexeddb": "^6.2.5", + "happy-dom": "^20.8.9", + "typescript": "^6.0.3", + "vitest": "^4.0.18" }, "dependencies": { "@0xsequence/guard": "workspace:^", @@ -38,9 +41,9 @@ "@0xsequence/tee-verifier": "^0.1.2", "@0xsequence/wallet-core": "workspace:^", "@0xsequence/wallet-primitives": "workspace:^", - "idb": "^7.1.1", + "idb": "^8.0.3", "jwt-decode": "^4.0.0", - "ox": "^0.7.2", - "uuid": "^11.1.0" + "ox": "^0.9.17", + "uuid": "^14.0.0" } } diff --git a/packages/wallet/wdk/src/dbs/auth-commitments.ts b/packages/wallet/wdk/src/dbs/auth-commitments.ts index dcb97e566..613d4bd57 100644 --- a/packages/wallet/wdk/src/dbs/auth-commitments.ts +++ b/packages/wallet/wdk/src/dbs/auth-commitments.ts @@ -1,18 +1,21 @@ -import { Generic, Migration } from './generic.js' +import { Generic } from './generic.js' import { IDBPDatabase, IDBPTransaction } from 'idb' const TABLE_NAME = 'auth-commitments' +export type CommitAuthArgs = + | { type: 'auth'; state?: string } + | { type: 'reauth'; state: string; signer: string } + | { type: 'add-signer'; wallet: string; state?: string } + export type AuthCommitment = { id: string - kind: 'google-pkce' | 'apple' + kind: 'google-pkce' | 'apple' | `custom-${string}` metadata: { [key: string]: string } verifier?: string challenge?: string target: string - isSignUp: boolean - signer?: string -} +} & ({ type: 'auth' } | { type: 'reauth'; signer: string } | { type: 'add-signer'; wallet: string }) export class AuthCommitments extends Generic { constructor(dbName: string = 'sequence-auth-commitments') { diff --git a/packages/wallet/wdk/src/dbs/auth-keys.ts b/packages/wallet/wdk/src/dbs/auth-keys.ts index 56cf1ddf9..690d18cd9 100644 --- a/packages/wallet/wdk/src/dbs/auth-keys.ts +++ b/packages/wallet/wdk/src/dbs/auth-keys.ts @@ -1,5 +1,6 @@ -import { Generic, Migration } from './generic.js' +import { Generic } from './generic.js' import { IDBPDatabase, IDBPTransaction } from 'idb' +import type { WdkEnv } from '../env.js' const TABLE_NAME = 'auth-keys' @@ -11,9 +12,12 @@ export type AuthKey = { } export class AuthKeys extends Generic { - private expirationTimers = new Map() + private expirationTimers = new Map>() - constructor(dbName: string = 'sequence-auth-keys') { + constructor( + dbName: string = 'sequence-auth-keys', + private readonly env?: WdkEnv, + ) { super(dbName, TABLE_NAME, 'address', [ ( db: IDBPDatabase, @@ -64,7 +68,10 @@ export class AuthKeys extends Generic { if (result !== undefined) { return result } else if (attempt < 2) { - await new Promise((resolve) => setTimeout(resolve, 50)) + const setTimeoutFn = this.env?.timers?.setTimeout ?? (globalThis as any).setTimeout + if (setTimeoutFn) { + await new Promise((resolve) => setTimeoutFn(resolve, 50)) + } return this.getBySigner(signer, attempt + 1) } else { try { @@ -108,7 +115,11 @@ export class AuthKeys extends Generic { await this.del(authKey.address.toLowerCase()) return } - const timer = window.setTimeout(() => { + const setTimeoutFn = this.env?.timers?.setTimeout ?? (globalThis as any).setTimeout + if (!setTimeoutFn) { + return + } + const timer = setTimeoutFn(() => { console.log('removing expired auth key', authKey) this.del(authKey.address.toLowerCase()) }, delay) @@ -118,7 +129,10 @@ export class AuthKeys extends Generic { private clearExpiration(address: string): void { const timer = this.expirationTimers.get(address.toLowerCase()) if (timer) { - window.clearTimeout(timer) + const clearTimeoutFn = this.env?.timers?.clearTimeout ?? (globalThis as any).clearTimeout + if (clearTimeoutFn) { + clearTimeoutFn(timer) + } this.expirationTimers.delete(address.toLowerCase()) } } diff --git a/packages/wallet/wdk/src/dbs/index.ts b/packages/wallet/wdk/src/dbs/index.ts index 797997b94..1cf7f30f0 100644 --- a/packages/wallet/wdk/src/dbs/index.ts +++ b/packages/wallet/wdk/src/dbs/index.ts @@ -1,4 +1,4 @@ -export type { AuthCommitment } from './auth-commitments.js' +export type { AuthCommitment, CommitAuthArgs } from './auth-commitments.js' export { AuthCommitments } from './auth-commitments.js' export type { AuthKey } from './auth-keys.js' diff --git a/packages/wallet/wdk/src/dbs/messages.ts b/packages/wallet/wdk/src/dbs/messages.ts index 8a076ea05..54459f06f 100644 --- a/packages/wallet/wdk/src/dbs/messages.ts +++ b/packages/wallet/wdk/src/dbs/messages.ts @@ -1,5 +1,5 @@ import { Message } from '../sequence/types/message-request.js' -import { Generic, Migration } from './generic.js' +import { Generic } from './generic.js' import { IDBPDatabase, IDBPTransaction } from 'idb' const TABLE_NAME = 'messages' diff --git a/packages/wallet/wdk/src/dbs/passkey-credentials.ts b/packages/wallet/wdk/src/dbs/passkey-credentials.ts index 9fdb2abc4..5e9e6bc31 100644 --- a/packages/wallet/wdk/src/dbs/passkey-credentials.ts +++ b/packages/wallet/wdk/src/dbs/passkey-credentials.ts @@ -1,4 +1,4 @@ -import { Generic, Migration } from './generic.js' +import { Generic } from './generic.js' import { IDBPDatabase, IDBPTransaction } from 'idb' import { Address } from 'ox' import { Extensions } from '@0xsequence/wallet-primitives' diff --git a/packages/wallet/wdk/src/dbs/recovery.ts b/packages/wallet/wdk/src/dbs/recovery.ts index 164c7082b..e035825f3 100644 --- a/packages/wallet/wdk/src/dbs/recovery.ts +++ b/packages/wallet/wdk/src/dbs/recovery.ts @@ -1,4 +1,4 @@ -import { Generic, Migration } from './generic.js' +import { Generic } from './generic.js' import { QueuedRecoveryPayload } from '../sequence/types/recovery.js' import { IDBPDatabase, IDBPTransaction } from 'idb' diff --git a/packages/wallet/wdk/src/dbs/signatures.ts b/packages/wallet/wdk/src/dbs/signatures.ts index a108d113c..3f7328187 100644 --- a/packages/wallet/wdk/src/dbs/signatures.ts +++ b/packages/wallet/wdk/src/dbs/signatures.ts @@ -1,5 +1,5 @@ import { BaseSignatureRequest } from '../sequence/index.js' -import { Generic, Migration } from './generic.js' +import { Generic } from './generic.js' import { IDBPDatabase, IDBPTransaction } from 'idb' const TABLE_NAME = 'envelopes' diff --git a/packages/wallet/wdk/src/dbs/transactions.ts b/packages/wallet/wdk/src/dbs/transactions.ts index d9019702d..48f7680c8 100644 --- a/packages/wallet/wdk/src/dbs/transactions.ts +++ b/packages/wallet/wdk/src/dbs/transactions.ts @@ -1,5 +1,5 @@ import { Transaction } from '../sequence/types/transaction-request.js' -import { Generic, Migration } from './generic.js' +import { Generic } from './generic.js' import { IDBPDatabase, IDBPTransaction } from 'idb' const TABLE_NAME = 'transactions' diff --git a/packages/wallet/wdk/src/dbs/wallets.ts b/packages/wallet/wdk/src/dbs/wallets.ts index bd9829eb5..4b1777610 100644 --- a/packages/wallet/wdk/src/dbs/wallets.ts +++ b/packages/wallet/wdk/src/dbs/wallets.ts @@ -1,4 +1,4 @@ -import { Generic, Migration } from './generic.js' +import { Generic } from './generic.js' import { Wallet } from '../sequence/types/wallet.js' import { IDBPDatabase, IDBPTransaction } from 'idb' diff --git a/packages/wallet/wdk/src/env.ts b/packages/wallet/wdk/src/env.ts new file mode 100644 index 000000000..4e3cecb1a --- /dev/null +++ b/packages/wallet/wdk/src/env.ts @@ -0,0 +1,58 @@ +import type { CoreEnv } from '@0xsequence/wallet-core' +import { resolveCoreEnv } from '@0xsequence/wallet-core' + +export type TimersLike = { + setTimeout: typeof setTimeout + clearTimeout: typeof clearTimeout + setInterval: typeof setInterval + clearInterval: typeof clearInterval +} + +export type LockManagerLike = { + request: (name: string, callback: (lock: Lock | null) => Promise | void) => Promise +} + +export type NavigationLike = { + getPathname: () => string + redirect: (url: string) => void +} + +export type WdkEnv = CoreEnv & { + timers?: TimersLike + locks?: LockManagerLike + navigation?: NavigationLike + urlSearchParams?: typeof URLSearchParams +} + +export function resolveWdkEnv(env?: WdkEnv): WdkEnv { + const core = resolveCoreEnv(env) + const globalObj = globalThis as any + const windowObj = typeof window !== 'undefined' ? window : (globalObj.window ?? {}) + const location = windowObj.location ?? globalObj.location + + return { + ...core, + timers: + env?.timers ?? + (typeof globalObj.setTimeout === 'function' + ? { + setTimeout: globalObj.setTimeout.bind(globalObj), + clearTimeout: globalObj.clearTimeout.bind(globalObj), + setInterval: globalObj.setInterval.bind(globalObj), + clearInterval: globalObj.clearInterval.bind(globalObj), + } + : undefined), + locks: env?.locks ?? globalObj.navigator?.locks ?? windowObj.navigator?.locks, + navigation: + env?.navigation ?? + (location + ? { + getPathname: () => location.pathname, + redirect: (url: string) => { + location.href = url + }, + } + : undefined), + urlSearchParams: env?.urlSearchParams ?? globalObj.URLSearchParams ?? windowObj.URLSearchParams, + } +} diff --git a/packages/wallet/wdk/src/identity/signer.ts b/packages/wallet/wdk/src/identity/signer.ts index 6204f1285..0cb74bbbd 100644 --- a/packages/wallet/wdk/src/identity/signer.ts +++ b/packages/wallet/wdk/src/identity/signer.ts @@ -1,23 +1,28 @@ -import { Address, Signature, Hex, Bytes, PersonalMessage } from 'ox' -import { Signers, State } from '@0xsequence/wallet-core' -import { IdentityInstrument, KeyType } from '@0xsequence/identity-instrument' +import { Address, Signature, Hex, Bytes } from 'ox' +import { Signers, State, type CryptoLike } from '@0xsequence/wallet-core' +import { IdentityInstrument } from '@0xsequence/identity-instrument' import { AuthKey } from '../dbs/auth-keys.js' import { Payload, Signature as SequenceSignature } from '@0xsequence/wallet-primitives' import * as Identity from '@0xsequence/identity-instrument' -export function toIdentityAuthKey(authKey: AuthKey): Identity.AuthKey { +export function toIdentityAuthKey(authKey: AuthKey, crypto?: CryptoLike): Identity.AuthKey { + const globalObj = globalThis as any + const resolvedCrypto = crypto ?? globalObj.window?.crypto ?? globalObj.crypto + if (!resolvedCrypto?.subtle) { + throw new Error('crypto.subtle is not available') + } return { address: authKey.address, keyType: Identity.KeyType.WebCrypto_Secp256r1, signer: authKey.identitySigner, async sign(digest: Bytes.Bytes) { - const authKeySignature = await window.crypto.subtle.sign( + const authKeySignature = await resolvedCrypto.subtle.sign( { name: 'ECDSA', hash: 'SHA-256', }, authKey.privateKey, - digest, + new Uint8Array(digest), ) return Hex.fromBytes(new Uint8Array(authKeySignature)) }, @@ -28,6 +33,7 @@ export class IdentitySigner implements Signers.Signer { constructor( readonly identityInstrument: IdentityInstrument, readonly authKey: AuthKey, + private readonly crypto?: CryptoLike, ) {} get address(): Address.Address { @@ -47,7 +53,7 @@ export class IdentitySigner implements Signers.Signer { } async signDigest(digest: Bytes.Bytes): Promise { - const sigHex = await this.identityInstrument.sign(toIdentityAuthKey(this.authKey), digest) + const sigHex = await this.identityInstrument.sign(toIdentityAuthKey(this.authKey, this.crypto), digest) const sig = Signature.fromHex(sigHex) return { type: 'hash', @@ -55,7 +61,7 @@ export class IdentitySigner implements Signers.Signer { } } - async witness(stateWriter: State.Writer, wallet: Address.Address, extra?: Object): Promise { + async witness(stateWriter: State.Writer, wallet: Address.Address, extra?: object): Promise { const payload = Payload.fromMessage( Hex.fromString( JSON.stringify({ diff --git a/packages/wallet/wdk/src/index.ts b/packages/wallet/wdk/src/index.ts index 973ec785a..003abdd72 100644 --- a/packages/wallet/wdk/src/index.ts +++ b/packages/wallet/wdk/src/index.ts @@ -1,2 +1,3 @@ export * as Identity from './identity/signer.js' export * as Sequence from './sequence/index.js' +export * from './env.js' diff --git a/packages/wallet/wdk/src/sequence/cron.ts b/packages/wallet/wdk/src/sequence/cron.ts index a1640a7cf..f95117109 100644 --- a/packages/wallet/wdk/src/sequence/cron.ts +++ b/packages/wallet/wdk/src/sequence/cron.ts @@ -17,12 +17,14 @@ export class Cron { private readonly STORAGE_KEY = 'sequence-cron-jobs' private isStopping: boolean = false private currentCheckJobsPromise: Promise = Promise.resolve() + private readonly env: Shared['env'] /** * Initializes the Cron scheduler and starts the periodic job checker. * @param shared Shared context for modules and logging. */ constructor(private readonly shared: Shared) { + this.env = shared.env this.start() } @@ -33,7 +35,11 @@ export class Cron { private start() { if (this.isStopping) return this.executeCheckJobsChain() - this.checkInterval = setInterval(() => this.executeCheckJobsChain(), 60 * 1000) + const setIntervalFn = this.env.timers?.setInterval ?? (globalThis as any).setInterval + if (!setIntervalFn) { + return + } + this.checkInterval = setIntervalFn(() => this.executeCheckJobsChain(), 60 * 1000) } /** @@ -58,7 +64,10 @@ export class Cron { this.isStopping = true if (this.checkInterval) { - clearInterval(this.checkInterval) + const clearIntervalFn = this.env.timers?.clearInterval ?? (globalThis as any).clearInterval + if (clearIntervalFn) { + clearIntervalFn(this.checkInterval) + } this.checkInterval = undefined this.shared.modules.logger.log('Cron: Interval cleared.') } @@ -104,48 +113,22 @@ export class Cron { } try { - await navigator.locks.request('sequence-cron-jobs', async (lock: Lock | null) => { - if (this.isStopping) { - return - } - if (!lock) { - return - } - - const now = Date.now() - const storage = await this.getStorageState() - - for (const [id, job] of this.jobs) { + const locks = this.env.locks ?? (globalThis as any).navigator?.locks + if (locks?.request) { + await locks.request('sequence-cron-jobs', async (lock: Lock | null) => { if (this.isStopping) { - break + return } - - const lastRun = storage.get(id)?.lastRun ?? job.lastRun - const timeSinceLastRun = now - lastRun - - if (timeSinceLastRun >= job.interval) { - try { - await job.handler() - if (!this.isStopping) { - job.lastRun = now - storage.set(id, { lastRun: now }) - } - } catch (error) { - if (error instanceof DOMException && error.name === 'AbortError') { - this.shared.modules.logger.log(`Cron: Job ${id} was aborted.`) - } else { - console.error(`Cron job ${id} failed:`, error) - } - } + if (!lock) { + return } - } - - if (!this.isStopping) { - await this.syncWithStorage() - } - }) + await this.runJobs() + }) + } else { + await this.runJobs() + } } catch (error) { - if (error instanceof DOMException && error.name === 'AbortError') { + if (this.isAbortError(error)) { this.shared.modules.logger.log('Cron: navigator.locks.request was aborted.') } else { console.error('Cron: Error in navigator.locks.request:', error) @@ -153,13 +136,51 @@ export class Cron { } } + private async runJobs(): Promise { + const now = Date.now() + const storage = await this.getStorageState() + + for (const [id, job] of this.jobs) { + if (this.isStopping) { + break + } + + const lastRun = storage.get(id)?.lastRun ?? job.lastRun + const timeSinceLastRun = now - lastRun + + if (timeSinceLastRun >= job.interval) { + try { + await job.handler() + if (!this.isStopping) { + job.lastRun = now + storage.set(id, { lastRun: now }) + } + } catch (error) { + if (this.isAbortError(error)) { + this.shared.modules.logger.log(`Cron: Job ${id} was aborted.`) + } else { + console.error(`Cron job ${id} failed:`, error) + } + } + } + } + + if (!this.isStopping) { + await this.syncWithStorage() + } + } + /** * Loads the persisted last run times for jobs from localStorage. * @returns Map of job IDs to their last run times. */ private async getStorageState(): Promise> { if (this.isStopping) return new Map() - const state = localStorage.getItem(this.STORAGE_KEY) + const storage = this.env.storage + if (!storage) { + return new Map() + } + const state = storage.getItem(this.STORAGE_KEY) return new Map(state ? JSON.parse(state) : []) } @@ -168,7 +189,19 @@ export class Cron { */ private async syncWithStorage() { if (this.isStopping) return + const storage = this.env.storage + if (!storage) { + return + } const state = Array.from(this.jobs.entries()).map(([id, job]) => [id, { lastRun: job.lastRun }]) - localStorage.setItem(this.STORAGE_KEY, JSON.stringify(state)) + storage.setItem(this.STORAGE_KEY, JSON.stringify(state)) + } + + private isAbortError(error: unknown): boolean { + const domException = (globalThis as any).DOMException + if (domException && error instanceof domException) { + return (error as DOMException).name === 'AbortError' + } + return (error as any)?.name === 'AbortError' } } diff --git a/packages/wallet/wdk/src/sequence/guards.ts b/packages/wallet/wdk/src/sequence/guards.ts index c85054794..a005a1619 100644 --- a/packages/wallet/wdk/src/sequence/guards.ts +++ b/packages/wallet/wdk/src/sequence/guards.ts @@ -1,8 +1,8 @@ -import { Address } from 'ox' +import { Address, Bytes } from 'ox' import { Shared } from './manager.js' import * as Guard from '@0xsequence/guard' import { Signers } from '@0xsequence/wallet-core' -import { Config } from '@0xsequence/wallet-primitives' +import { Config, Constants } from '@0xsequence/wallet-primitives' export type GuardRole = 'wallet' | 'sessions' @@ -28,17 +28,28 @@ export class Guards { return undefined } - topology(role: GuardRole): Config.NestedLeaf | undefined { + topology(role: GuardRole): Config.Topology | undefined { const guardAddress = this.shared.sequence.guardAddresses[role] if (!guardAddress) { return undefined } - return { - type: 'nested', - weight: 1n, - threshold: 1n, - tree: { ...this.shared.sequence.defaultGuardTopology, address: guardAddress }, + const topology = Config.replaceAddress( + this.shared.sequence.defaultGuardTopology, + Constants.PlaceholderAddress, + guardAddress, + ) + + // If the imageHash did not change it means the replacement failed + if ( + Bytes.isEqual( + Config.hashConfiguration(topology), + Config.hashConfiguration(this.shared.sequence.defaultGuardTopology), + ) + ) { + throw new Error(`Guard address replacement failed for role ${role}`) } + + return topology } } diff --git a/packages/wallet/wdk/src/sequence/handlers/authcode-pkce.ts b/packages/wallet/wdk/src/sequence/handlers/authcode-pkce.ts index 6029f4114..308e2809f 100644 --- a/packages/wallet/wdk/src/sequence/handlers/authcode-pkce.ts +++ b/packages/wallet/wdk/src/sequence/handlers/authcode-pkce.ts @@ -1,45 +1,54 @@ -import { Hex, Address, Bytes } from 'ox' +import { Hex, Bytes } from 'ox' import { Handler } from './handler.js' import * as Db from '../../dbs/index.js' import { Signatures } from '../signatures.js' import * as Identity from '@0xsequence/identity-instrument' import { IdentitySigner } from '../../identity/signer.js' import { AuthCodeHandler } from './authcode.js' +import type { WdkEnv } from '../../env.js' +import type { CommitAuthArgs } from '../../dbs/auth-commitments.js' export class AuthCodePkceHandler extends AuthCodeHandler implements Handler { constructor( - signupKind: 'google-pkce', + signupKind: 'google-pkce' | `custom-${string}`, issuer: string, + oauthUrl: string, audience: string, nitro: Identity.IdentityInstrument, signatures: Signatures, commitments: Db.AuthCommitments, authKeys: Db.AuthKeys, + env?: WdkEnv, ) { - super(signupKind, issuer, audience, nitro, signatures, commitments, authKeys) + super(signupKind, issuer, oauthUrl, audience, nitro, signatures, commitments, authKeys, env) } - public async commitAuth(target: string, isSignUp: boolean, state?: string, signer?: string) { + public async commitAuth(target: string, args: CommitAuthArgs) { let challenge = new Identity.AuthCodePkceChallenge(this.issuer, this.audience, this.redirectUri) - if (signer) { - challenge = challenge.withSigner({ address: signer, keyType: Identity.KeyType.Ethereum_Secp256k1 }) + if (args.type === 'reauth') { + challenge = challenge.withSigner({ address: args.signer, keyType: Identity.KeyType.Ethereum_Secp256k1 }) } const { verifier, loginHint, challenge: codeChallenge } = await this.nitroCommitVerifier(challenge) - if (!state) { - state = Hex.fromBytes(Bytes.random(32)) - } + const state = args.state ?? Hex.fromBytes(Bytes.random(32)) - await this.commitments.set({ + const base = { id: state, - kind: this.signupKind, + kind: this.signupKind as Db.AuthCommitment['kind'], verifier, challenge: codeChallenge, target, metadata: {}, - isSignUp, - }) + } + + if (args.type === 'reauth') { + await this.commitments.set({ ...base, type: 'reauth', signer: args.signer }) + } else if (args.type === 'add-signer') { + await this.commitments.set({ ...base, type: 'add-signer', wallet: args.wallet }) + } else { + await this.commitments.set({ ...base, type: 'auth' }) + } - const searchParams = new URLSearchParams({ + const searchParams = this.serializeQuery({ code_challenge: codeChallenge, code_challenge_method: 'S256', client_id: this.audience, @@ -50,8 +59,7 @@ export class AuthCodePkceHandler extends AuthCodeHandler implements Handler { state, }) - const oauthUrl = this.oauthUrl() - return `${oauthUrl}?${searchParams.toString()}` + return `${this.oauthUrl}?${searchParams}` } public async completeAuth( diff --git a/packages/wallet/wdk/src/sequence/handlers/authcode.ts b/packages/wallet/wdk/src/sequence/handlers/authcode.ts index bb8a1b315..4b5d5922b 100644 --- a/packages/wallet/wdk/src/sequence/handlers/authcode.ts +++ b/packages/wallet/wdk/src/sequence/handlers/authcode.ts @@ -6,23 +6,33 @@ import * as Identity from '@0xsequence/identity-instrument' import { SignerUnavailable, SignerReady, SignerActionable, BaseSignatureRequest } from '../types/signature-request.js' import { IdentitySigner } from '../../identity/signer.js' import { IdentityHandler } from './identity.js' +import { Kinds } from '../types/signer.js' +import type { NavigationLike, WdkEnv } from '../../env.js' +import type { CommitAuthArgs } from '../../dbs/auth-commitments.js' export class AuthCodeHandler extends IdentityHandler implements Handler { protected redirectUri: string = '' constructor( - public readonly signupKind: 'apple' | 'google-pkce', + public readonly signupKind: 'apple' | 'google-pkce' | `custom-${string}`, public readonly issuer: string, + protected readonly oauthUrl: string, public readonly audience: string, nitro: Identity.IdentityInstrument, signatures: Signatures, protected readonly commitments: Db.AuthCommitments, authKeys: Db.AuthKeys, + env?: WdkEnv, ) { - super(nitro, authKeys, signatures, Identity.IdentityType.OIDC) + super(nitro, authKeys, signatures, Identity.IdentityType.OIDC, env) } public get kind() { + if (this.signupKind === 'google-pkce') { + // Keep Google PKCE on the canonical kind so Google signers created before + // canonicalization still resolve as `login-google`. + return Kinds.LoginGoogle + } return 'login-' + this.signupKind } @@ -30,30 +40,33 @@ export class AuthCodeHandler extends IdentityHandler implements Handler { this.redirectUri = redirectUri } - public async commitAuth(target: string, isSignUp: boolean, state?: string, signer?: string) { - if (!state) { - state = Hex.fromBytes(Bytes.random(32)) - } + public async commitAuth(target: string, args: CommitAuthArgs) { + const state = args.state ?? Hex.fromBytes(Bytes.random(32)) - await this.commitments.set({ + const base = { id: state, - kind: this.signupKind, - signer, + kind: this.signupKind as Db.AuthCommitment['kind'], target, metadata: {}, - isSignUp, - }) + } - const searchParams = new URLSearchParams({ + if (args.type === 'reauth') { + await this.commitments.set({ ...base, type: 'reauth', signer: args.signer }) + } else if (args.type === 'add-signer') { + await this.commitments.set({ ...base, type: 'add-signer', wallet: args.wallet }) + } else { + await this.commitments.set({ ...base, type: 'auth' }) + } + + const searchParams = this.serializeQuery({ client_id: this.audience, redirect_uri: this.redirectUri, response_type: 'code', - scope: 'openid', state, + ...(this.signupKind === 'apple' ? {} : { scope: 'openid profile email' }), }) - const oauthUrl = this.oauthUrl() - return `${oauthUrl}?${searchParams.toString()}` + return `${this.oauthUrl}?${searchParams}` } public async completeAuth( @@ -61,7 +74,7 @@ export class AuthCodeHandler extends IdentityHandler implements Handler { code: string, ): Promise<[IdentitySigner, { [key: string]: string }]> { let challenge = new Identity.AuthCodeChallenge(this.issuer, this.audience, this.redirectUri, code) - if (commitment.signer) { + if (commitment.type === 'reauth') { challenge = challenge.withSigner({ address: commitment.signer, keyType: Identity.KeyType.Ethereum_Secp256k1 }) } await this.nitroCommitVerifier(challenge) @@ -94,21 +107,33 @@ export class AuthCodeHandler extends IdentityHandler implements Handler { status: 'actionable', message: 'request-redirect', handle: async () => { - const url = await this.commitAuth(window.location.pathname, false, request.id, address) - window.location.href = url + const navigation = this.getNavigation() + const url = await this.commitAuth(navigation.getPathname(), { + type: 'reauth', + state: request.id, + signer: address, + }) + navigation.redirect(url) return true }, } } - protected oauthUrl() { - switch (this.issuer) { - case 'https://accounts.google.com': - return 'https://accounts.google.com/o/oauth2/v2/auth' - case 'https://appleid.apple.com': - return 'https://appleid.apple.com/auth/authorize' - default: - throw new Error('unsupported-issuer') + protected serializeQuery(params: Record): string { + const searchParamsCtor = this.env.urlSearchParams ?? (globalThis as any).URLSearchParams + if (searchParamsCtor) { + return new searchParamsCtor(params).toString() + } + return Object.entries(params) + .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) + .join('&') + } + + private getNavigation(): NavigationLike { + const navigation = this.env.navigation + if (!navigation) { + throw new Error('navigation is not available') } + return navigation } } diff --git a/packages/wallet/wdk/src/sequence/handlers/devices.ts b/packages/wallet/wdk/src/sequence/handlers/devices.ts index 9da0f8971..4d525d37e 100644 --- a/packages/wallet/wdk/src/sequence/handlers/devices.ts +++ b/packages/wallet/wdk/src/sequence/handlers/devices.ts @@ -13,7 +13,7 @@ export class DevicesHandler implements Handler { private readonly devices: Devices, ) {} - onStatusChange(cb: () => void): () => void { + onStatusChange(_cb: () => void): () => void { return () => {} } diff --git a/packages/wallet/wdk/src/sequence/handlers/guard.ts b/packages/wallet/wdk/src/sequence/handlers/guard.ts index 0afb52a87..b495c95d8 100644 --- a/packages/wallet/wdk/src/sequence/handlers/guard.ts +++ b/packages/wallet/wdk/src/sequence/handlers/guard.ts @@ -1,25 +1,30 @@ import { Address, Hex } from 'ox' import * as Guard from '@0xsequence/guard' +import { Signers } from '@0xsequence/wallet-core' import { Handler } from './handler.js' import { BaseSignatureRequest, SignerUnavailable, SignerReady, SignerActionable, Kinds } from '../types/index.js' import { Signatures } from '../signatures.js' import { Guards } from '../guards.js' +type RespondFn = (token: Signers.GuardToken) => Promise + +export type PromptCodeHandler = ( + request: BaseSignatureRequest, + codeType: 'TOTP' | 'PIN', + respond: RespondFn, +) => Promise + export class GuardHandler implements Handler { kind = Kinds.Guard - private onPromptCode: - | undefined - | ((codeType: 'TOTP' | 'PIN', respond: (code: string) => Promise) => Promise) + private onPromptCode: undefined | PromptCodeHandler constructor( private readonly signatures: Signatures, private readonly guards: Guards, ) {} - public registerUI( - onPromptCode: (codeType: 'TOTP' | 'PIN', respond: (code: string) => Promise) => Promise, - ) { + public registerUI(onPromptCode: PromptCodeHandler) { this.onPromptCode = onPromptCode return () => { this.onPromptCode = undefined @@ -30,7 +35,7 @@ export class GuardHandler implements Handler { this.onPromptCode = undefined } - onStatusChange(cb: () => void): () => void { + onStatusChange(_cb: () => void): () => void { return () => {} } @@ -82,30 +87,28 @@ export class GuardHandler implements Handler { address, handler: this, status: 'ready', - handle: () => - new Promise(async (resolve, reject) => { + handle: () => { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { try { const signature = await guard.signEnvelope(request.envelope) await this.signatures.addSignature(request.id, signature) resolve(true) } catch (e) { if (e instanceof Guard.AuthRequiredError) { - const respond = async (code: string) => { - try { - const signature = await guard.signEnvelope(request.envelope, { id: e.id, code }) - await this.signatures.addSignature(request.id, signature) - resolve(true) - } catch (e) { - reject(e) - } + const respond: RespondFn = async (token) => { + const signature = await guard.signEnvelope(request.envelope, token) + await this.signatures.addSignature(request.id, signature) + resolve(true) } - await onPromptCode(e.id, respond) + await onPromptCode(request, e.id, respond) } else { reject(e) } } - }), + }) + }, } } } diff --git a/packages/wallet/wdk/src/sequence/handlers/identity.ts b/packages/wallet/wdk/src/sequence/handlers/identity.ts index f18245222..a991e8863 100644 --- a/packages/wallet/wdk/src/sequence/handlers/identity.ts +++ b/packages/wallet/wdk/src/sequence/handlers/identity.ts @@ -1,9 +1,10 @@ -import { Hex, Bytes } from 'ox' +import { Hex } from 'ox' import * as Db from '../../dbs/index.js' import * as Identity from '@0xsequence/identity-instrument' import { Signatures } from '../signatures.js' import { BaseSignatureRequest } from '../types/signature-request.js' import { IdentitySigner, toIdentityAuthKey } from '../../identity/signer.js' +import { resolveWdkEnv, type WdkEnv } from '../../env.js' export const identityTypeToHex = (identityType?: Identity.IdentityType): Hex.Hex => { // Bytes4 @@ -19,12 +20,17 @@ export const identityTypeToHex = (identityType?: Identity.IdentityType): Hex.Hex } export class IdentityHandler { + protected readonly env: WdkEnv + constructor( private readonly nitro: Identity.IdentityInstrument, private readonly authKeys: Db.AuthKeys, private readonly signatures: Signatures, public readonly identityType: Identity.IdentityType, - ) {} + env?: WdkEnv, + ) { + this.env = resolveWdkEnv(env) + } public onStatusChange(cb: () => void): () => void { return this.authKeys.addListener(cb) @@ -37,7 +43,7 @@ export class IdentityHandler { throw new Error('no-auth-key') } - const res = await this.nitro.commitVerifier(toIdentityAuthKey(authKey), challenge) + const res = await this.nitro.commitVerifier(toIdentityAuthKey(authKey, this.env.crypto), challenge) return res } @@ -47,7 +53,7 @@ export class IdentityHandler { throw new Error('no-auth-key') } - const res = await this.nitro.completeAuth(toIdentityAuthKey(authKey), challenge) + const res = await this.nitro.completeAuth(toIdentityAuthKey(authKey, this.env.crypto), challenge) authKey.identitySigner = res.signer.address authKey.expiresAt = new Date(Date.now() + 1000 * 60 * 3) // 3 minutes @@ -55,7 +61,7 @@ export class IdentityHandler { await this.authKeys.delBySigner(authKey.identitySigner) await this.authKeys.set(authKey) - const signer = new IdentitySigner(this.nitro, authKey) + const signer = new IdentitySigner(this.nitro, authKey, this.env.crypto) return { signer, email: res.identity.email } } @@ -72,13 +78,21 @@ export class IdentityHandler { if (!authKey) { return undefined } - return new IdentitySigner(this.nitro, authKey) + return new IdentitySigner(this.nitro, authKey, this.env.crypto) + } + + protected async clearAuthKeySigner(address: string): Promise { + await this.authKeys.delBySigner(address) } private async getAuthKey(signer: string): Promise { let authKey = await this.authKeys.getBySigner(signer) if (!signer && !authKey) { - const keyPair = await window.crypto.subtle.generateKey( + const crypto = this.env.crypto ?? (globalThis as any).crypto + if (!crypto?.subtle) { + throw new Error('crypto.subtle is not available') + } + const keyPair = await crypto.subtle.generateKey( { name: 'ECDSA', namedCurve: 'P-256', @@ -86,7 +100,7 @@ export class IdentityHandler { false, ['sign', 'verify'], ) - const publicKey = await window.crypto.subtle.exportKey('raw', keyPair.publicKey) + const publicKey = await crypto.subtle.exportKey('raw', keyPair.publicKey) authKey = { address: Hex.fromBytes(new Uint8Array(publicKey)), identitySigner: '', diff --git a/packages/wallet/wdk/src/sequence/handlers/idtoken.ts b/packages/wallet/wdk/src/sequence/handlers/idtoken.ts new file mode 100644 index 000000000..58d5b5df9 --- /dev/null +++ b/packages/wallet/wdk/src/sequence/handlers/idtoken.ts @@ -0,0 +1,146 @@ +import { Address, Hex } from 'ox' +import { Signers } from '@0xsequence/wallet-core' +import { Handler } from './handler.js' +import * as Identity from '@0xsequence/identity-instrument' +import * as Db from '../../dbs/index.js' +import { Signatures } from '../signatures.js' +import { SignerActionable, SignerReady, SignerUnavailable, BaseSignatureRequest } from '../types/signature-request.js' +import { IdentitySigner } from '../../identity/signer.js' +import { IdentityHandler } from './identity.js' +import { Kinds } from '../types/signer.js' +import type { WdkEnv } from '../../env.js' + +type RespondFn = (idToken: string) => Promise + +export type PromptIdTokenHandler = ( + kind: 'google-id-token' | 'apple-id-token' | `custom-${string}`, + respond: RespondFn, +) => Promise + +export class IdTokenHandler extends IdentityHandler implements Handler { + private onPromptIdToken: undefined | PromptIdTokenHandler + + constructor( + public readonly signupKind: 'google-id-token' | 'apple-id-token' | `custom-${string}`, + public readonly issuer: string, + public readonly audience: string, + nitro: Identity.IdentityInstrument, + signatures: Signatures, + authKeys: Db.AuthKeys, + env?: WdkEnv, + ) { + super(nitro, authKeys, signatures, Identity.IdentityType.OIDC, env) + } + + public get kind() { + if (this.signupKind === 'google-id-token') { + return Kinds.LoginGoogle + } + if (this.signupKind === 'apple-id-token') { + return Kinds.LoginApple + } + return 'login-' + this.signupKind + } + + public registerUI(onPromptIdToken: PromptIdTokenHandler) { + this.onPromptIdToken = onPromptIdToken + return () => { + this.onPromptIdToken = undefined + } + } + + public unregisterUI() { + this.onPromptIdToken = undefined + } + + public async completeAuth(idToken: string): Promise<[IdentitySigner, { [key: string]: string }]> { + const challenge = new Identity.IdTokenChallenge(this.issuer, this.audience, idToken) + await this.nitroCommitVerifier(challenge) + const { signer: identitySigner, email } = await this.nitroCompleteAuth(challenge) + + return [identitySigner, { email }] + } + + public async getSigner(): Promise<{ signer: Signers.Signer & Signers.Witnessable; email: string }> { + const onPromptIdToken = this.onPromptIdToken + if (!onPromptIdToken) { + throw new Error('id-token-handler-ui-not-registered') + } + + return await this.handleAuth(onPromptIdToken) + } + + async status( + address: Address.Address, + _imageHash: Hex.Hex | undefined, + request: BaseSignatureRequest, + ): Promise { + const signer = await this.getAuthKeySigner(address) + if (signer) { + return { + address, + handler: this, + status: 'ready', + handle: async () => { + await this.sign(signer, request) + return true + }, + } + } + + const onPromptIdToken = this.onPromptIdToken + if (!onPromptIdToken) { + return { + address, + handler: this, + reason: 'ui-not-registered', + status: 'unavailable', + } + } + + return { + address, + handler: this, + status: 'actionable', + message: 'request-id-token', + handle: async () => { + try { + const { signer } = await this.handleAuth(onPromptIdToken) + const signerAddress = (await signer.address) as Address.Address + if (!Address.isEqual(signerAddress, address)) { + // ID-token auth prompts are keyed by provider kind, not the requested signer address. + // For example, a user can pick a different Google account in the account picker and + // return a token for a different identity than this request expects. + await this.clearAuthKeySigner(signerAddress) + throw new Error('id-token-signer-mismatch') + } + return true + } catch { + return false + } + }, + } + } + + private handleAuth( + onPromptIdToken: PromptIdTokenHandler, + ): Promise<{ signer: Signers.Signer & Signers.Witnessable; email: string }> { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + try { + const respond: RespondFn = async (idToken) => { + try { + const [signer, metadata] = await this.completeAuth(idToken) + resolve({ signer, email: metadata.email || '' }) + } catch (error) { + reject(error) + } + } + + await onPromptIdToken(this.signupKind, respond) + } catch (error) { + reject(error) + } + }) + } +} diff --git a/packages/wallet/wdk/src/sequence/handlers/index.ts b/packages/wallet/wdk/src/sequence/handlers/index.ts index 0cac943b9..9c1b1f0f5 100644 --- a/packages/wallet/wdk/src/sequence/handlers/index.ts +++ b/packages/wallet/wdk/src/sequence/handlers/index.ts @@ -3,4 +3,5 @@ export { DevicesHandler } from './devices.js' export { PasskeysHandler } from './passkeys.js' export { OtpHandler } from './otp.js' export { AuthCodePkceHandler } from './authcode-pkce.js' +export { IdTokenHandler } from './idtoken.js' export { MnemonicHandler } from './mnemonic.js' diff --git a/packages/wallet/wdk/src/sequence/handlers/mnemonic.ts b/packages/wallet/wdk/src/sequence/handlers/mnemonic.ts index e24a32900..3d932a533 100644 --- a/packages/wallet/wdk/src/sequence/handlers/mnemonic.ts +++ b/packages/wallet/wdk/src/sequence/handlers/mnemonic.ts @@ -7,15 +7,17 @@ import { SignerReady, SignerUnavailable, BaseSignatureRequest, SignerActionable type RespondFn = (mnemonic: string) => Promise +export type PromptMnemonicHandler = (respond: RespondFn) => Promise + export class MnemonicHandler implements Handler { kind = Kinds.LoginMnemonic - private onPromptMnemonic: undefined | ((respond: RespondFn) => Promise) + private onPromptMnemonic: undefined | PromptMnemonicHandler private readySigners = new Map() constructor(private readonly signatures: Signatures) {} - public registerUI(onPromptMnemonic: (respond: RespondFn) => Promise) { + public registerUI(onPromptMnemonic: PromptMnemonicHandler) { this.onPromptMnemonic = onPromptMnemonic return () => { this.onPromptMnemonic = undefined @@ -91,9 +93,10 @@ export class MnemonicHandler implements Handler { handler: this, status: 'actionable', message: 'enter-mnemonic', - handle: () => - new Promise(async (resolve, reject) => { - const respond = async (mnemonic: string) => { + handle: () => { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + const respond: RespondFn = async (mnemonic) => { const signer = MnemonicHandler.toSigner(mnemonic) if (!signer) { return reject('invalid-mnemonic') @@ -115,7 +118,8 @@ export class MnemonicHandler implements Handler { resolve(true) } await onPromptMnemonic(respond) - }), + }) + }, } } } diff --git a/packages/wallet/wdk/src/sequence/handlers/otp.ts b/packages/wallet/wdk/src/sequence/handlers/otp.ts index c6cb53aa8..42bf85c9e 100644 --- a/packages/wallet/wdk/src/sequence/handlers/otp.ts +++ b/packages/wallet/wdk/src/sequence/handlers/otp.ts @@ -8,19 +8,22 @@ import { SignerUnavailable, SignerReady, SignerActionable, BaseSignatureRequest import { Kinds } from '../types/signer.js' import { IdentityHandler } from './identity.js' import { AnswerIncorrectError, ChallengeExpiredError, TooManyAttemptsError } from '../errors.js' +import type { WdkEnv } from '../../env.js' type RespondFn = (otp: string) => Promise +export type PromptOtpHandler = (recipient: string, respond: RespondFn) => Promise + export class OtpHandler extends IdentityHandler implements Handler { kind = Kinds.LoginEmailOtp - private onPromptOtp: undefined | ((recipient: string, respond: RespondFn) => Promise) + private onPromptOtp: undefined | PromptOtpHandler - constructor(nitro: Identity.IdentityInstrument, signatures: Signatures, authKeys: Db.AuthKeys) { - super(nitro, authKeys, signatures, Identity.IdentityType.Email) + constructor(nitro: Identity.IdentityInstrument, signatures: Signatures, authKeys: Db.AuthKeys, env?: WdkEnv) { + super(nitro, authKeys, signatures, Identity.IdentityType.Email, env) } - public registerUI(onPromptOtp: (recipient: string, respond: RespondFn) => Promise) { + public registerUI(onPromptOtp: PromptOtpHandler) { this.onPromptOtp = onPromptOtp return () => { this.onPromptOtp = undefined @@ -82,7 +85,7 @@ export class OtpHandler extends IdentityHandler implements Handler { try { await this.handleAuth(challenge, onPromptOtp) return true - } catch (e) { + } catch { return false } }, @@ -91,13 +94,14 @@ export class OtpHandler extends IdentityHandler implements Handler { private handleAuth( challenge: Identity.OtpChallenge, - onPromptOtp: (recipient: string, respond: RespondFn) => Promise, + onPromptOtp: PromptOtpHandler, ): Promise<{ signer: Signers.Signer & Signers.Witnessable; email: string }> { + // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve, reject) => { try { const { loginHint, challenge: codeChallenge } = await this.nitroCommitVerifier(challenge) - const respond = async (otp: string) => { + const respond: RespondFn = async (otp) => { try { const { signer, email: returnedEmail } = await this.nitroCompleteAuth( challenge.withAnswer(codeChallenge, otp), diff --git a/packages/wallet/wdk/src/sequence/handlers/passkeys.ts b/packages/wallet/wdk/src/sequence/handlers/passkeys.ts index da3db7ff3..17c3196ca 100644 --- a/packages/wallet/wdk/src/sequence/handlers/passkeys.ts +++ b/packages/wallet/wdk/src/sequence/handlers/passkeys.ts @@ -1,33 +1,35 @@ -import { Signers, State } from '@0xsequence/wallet-core' +import { State } from '@0xsequence/wallet-core' import { Address, Hex } from 'ox' import { Kinds } from '../types/signer.js' import { Signatures } from '../signatures.js' -import { Extensions } from '@0xsequence/wallet-primitives' +import { Config, Extensions } from '@0xsequence/wallet-primitives' import { Handler } from './handler.js' import { SignerActionable, SignerUnavailable, BaseSignatureRequest } from '../types/index.js' +import type { PasskeyProvider, PasskeySigner } from '../passkeys-provider.js' export class PasskeysHandler implements Handler { kind = Kinds.LoginPasskey - private readySigners = new Map() + private readySigners = new Map() constructor( private readonly signatures: Signatures, private readonly extensions: Pick, private readonly stateReader: State.Reader, + private readonly passkeyProvider: PasskeyProvider, ) {} - onStatusChange(cb: () => void): () => void { + onStatusChange(_cb: () => void): () => void { return () => {} } - public addReadySigner(signer: Signers.Passkey.Passkey) { + public addReadySigner(signer: PasskeySigner) { // Use credentialId as key to match specific passkey instances this.readySigners.set(signer.credentialId, signer) } - private async loadPasskey(wallet: Address.Address, imageHash: Hex.Hex): Promise { + private async loadPasskey(wallet: Address.Address, imageHash: Hex.Hex): Promise { try { - return await Signers.Passkey.Passkey.loadFromWitness(this.stateReader, this.extensions, wallet, imageHash) + return await this.passkeyProvider.loadFromWitness(this.stateReader, this.extensions, wallet, imageHash) } catch (e) { console.warn('Failed to load passkey:', e) return undefined @@ -55,7 +57,7 @@ export class PasskeysHandler implements Handler { } // First check if we have a ready signer that matches the imageHash - let passkey: Signers.Passkey.Passkey | undefined + let passkey: PasskeySigner | undefined // Look for a ready signer with matching imageHash for (const readySigner of this.readySigners.values()) { @@ -91,12 +93,10 @@ export class PasskeysHandler implements Handler { message: 'request-interaction-with-passkey', imageHash: imageHash, handle: async () => { - const signature = await passkey.signSapient( - request.envelope.wallet, - request.envelope.chainId, - request.envelope.payload, - imageHash, + const normalized = Config.normalizeSignerSignature( + passkey.signSapient(request.envelope.wallet, request.envelope.chainId, request.envelope.payload, imageHash), ) + const signature = await normalized.signature await this.signatures.addSignature(request.id, { address, imageHash, diff --git a/packages/wallet/wdk/src/sequence/handlers/recovery.ts b/packages/wallet/wdk/src/sequence/handlers/recovery.ts index 57921de72..9c0ca654d 100644 --- a/packages/wallet/wdk/src/sequence/handlers/recovery.ts +++ b/packages/wallet/wdk/src/sequence/handlers/recovery.ts @@ -76,7 +76,7 @@ export class RecoveryHandler implements Handler { return true }, } - } catch (e) { + } catch { return { address, handler: this, diff --git a/packages/wallet/wdk/src/sequence/index.ts b/packages/wallet/wdk/src/sequence/index.ts index 37729a477..b3081a2a9 100644 --- a/packages/wallet/wdk/src/sequence/index.ts +++ b/packages/wallet/wdk/src/sequence/index.ts @@ -3,16 +3,23 @@ export { Network as Networks } export type { ManagerOptions, Databases, Sequence, Modules, Shared } from './manager.js' export { ManagerOptionsDefaults, CreateWalletOptionsDefaults, applyManagerOptionsDefaults, Manager } from './manager.js' +export { defaultPasskeyProvider } from './passkeys-provider.js' +export type { PasskeyProvider, PasskeySigner } from './passkeys-provider.js' export { Sessions } from './sessions.js' export { Signatures } from './signatures.js' export type { StartSignUpWithRedirectArgs, + StartAddLoginSignerWithRedirectArgs, CommonSignupArgs, PasskeySignupArgs, MnemonicSignupArgs, EmailOtpSignupArgs, CompleteRedirectArgs, + CompleteRedirectWithMetadataArgs, + CompleteRedirectMetadataResult, SignupArgs, + AddLoginSignerArgs, + RemoveLoginSignerArgs, LoginToWalletArgs, LoginToMnemonicArgs, LoginToPasskeyArgs, diff --git a/packages/wallet/wdk/src/sequence/manager.ts b/packages/wallet/wdk/src/sequence/manager.ts index 2405e6f2d..c7eecf329 100644 --- a/packages/wallet/wdk/src/sequence/manager.ts +++ b/packages/wallet/wdk/src/sequence/manager.ts @@ -5,6 +5,7 @@ import { createAttestationVerifyingFetch } from '@0xsequence/tee-verifier' import { Config, Constants, Context, Extensions, Network } from '@0xsequence/wallet-primitives' import { Address } from 'ox' import * as Db from '../dbs/index.js' +import { resolveWdkEnv, type WdkEnv } from '../env.js' import { Cron } from './cron.js' import { Devices } from './devices.js' import { Guards, GuardRole } from './guards.js' @@ -13,6 +14,7 @@ import { AuthCodePkceHandler, DevicesHandler, Handler, + IdTokenHandler, MnemonicHandler, OtpHandler, PasskeysHandler, @@ -27,14 +29,34 @@ import { Signers } from './signers.js' import { Transactions, TransactionsInterface } from './transactions.js' import { Kinds } from './types/signer.js' import { Wallets, WalletsInterface } from './wallets.js' -import { GuardHandler } from './handlers/guard.js' +import { GuardHandler, PromptCodeHandler } from './handlers/guard.js' import { PasskeyCredential } from '../dbs/index.js' +import { PromptMnemonicHandler } from './handlers/mnemonic.js' +import { PromptIdTokenHandler } from './handlers/idtoken.js' +import { PromptOtpHandler } from './handlers/otp.js' +import { defaultPasskeyProvider, type PasskeyProvider } from './passkeys-provider.js' + +type CustomIdentityProvider = + | { + kind: `custom-${string}` + authMethod: 'id-token' + issuer: string + clientId: string + } + | { + kind: `custom-${string}` + authMethod: 'authcode' | 'authcode-pkce' + issuer: string + oauthUrl: string + clientId: string + } export type ManagerOptions = { verbose?: boolean extensions?: Extensions.Extensions context?: Context.Context + context4337?: Context.Context guest?: Address.Address encryptedPksDb?: CoreSigners.Pk.Encrypted.EncryptedPksDb @@ -49,6 +71,9 @@ export type ManagerOptions = { dbPruningInterval?: number + env?: WdkEnv + passkeyProvider?: PasskeyProvider + stateProvider?: State.Provider networks?: Network.Network[] relayers?: Relayer.Relayer[] | (() => Relayer.Relayer[]) @@ -56,7 +81,10 @@ export type ManagerOptions = { guardUrl?: string guardAddresses?: Record - defaultGuardTopology?: Config.SignerLeaf + nonWitnessableSigners?: Address.Address[] + + // The default guard topology MUST have a placeholder address for the guard address + defaultGuardTopology?: Config.Topology defaultRecoverySettings?: RecoverySettings // EIP-6963 support @@ -64,7 +92,7 @@ export type ManagerOptions = { identity?: { url?: string - fetch?: typeof window.fetch + fetch?: typeof fetch verifyAttestation?: boolean expectedPcr0?: string[] scope?: string @@ -74,20 +102,85 @@ export type ManagerOptions = { google?: { enabled: boolean clientId: string + authMethod?: 'authcode-pkce' | 'id-token' } apple?: { enabled: boolean clientId: string + authMethod?: 'authcode' | 'id-token' } + customProviders?: CustomIdentityProvider[] + } +} + +export type ResolvedIdentityOptions = { + url: string + fetch?: typeof fetch + verifyAttestation: boolean + expectedPcr0?: string[] + scope?: string + email: { + enabled: boolean + } + google: { + enabled: boolean + clientId: string + authMethod: 'authcode-pkce' | 'id-token' } + apple: { + enabled: boolean + clientId: string + authMethod: 'authcode' | 'id-token' + } + customProviders?: CustomIdentityProvider[] +} + +export type ResolvedManagerOptions = { + verbose: boolean + + extensions: Extensions.Extensions + context: Context.Context + context4337: Context.Context + guest: Address.Address + + encryptedPksDb: CoreSigners.Pk.Encrypted.EncryptedPksDb + managerDb: Db.Wallets + transactionsDb: Db.Transactions + signaturesDb: Db.Signatures + messagesDb: Db.Messages + authCommitmentsDb: Db.AuthCommitments + authKeysDb: Db.AuthKeys + recoveryDb: Db.Recovery + passkeyCredentialsDb: Db.PasskeyCredentials + + dbPruningInterval: number + + env: WdkEnv + passkeyProvider: PasskeyProvider + + stateProvider: State.Provider + networks: Network.Network[] + relayers: Relayer.Relayer[] | (() => Relayer.Relayer[]) + bundlers: Bundler.Bundler[] + guardUrl: string + guardAddresses: Record + + nonWitnessableSigners: Address.Address[] + + defaultGuardTopology: Config.Topology + defaultRecoverySettings: RecoverySettings + + multiInjectedProviderDiscovery: boolean + + identity: ResolvedIdentityOptions } export const ManagerOptionsDefaults = { verbose: false, - extensions: Extensions.Rc3, - context: Context.Rc3, - context4337: Context.Rc3_4337, + extensions: Extensions.Rc5, + context: Context.Rc5, + context4337: Context.Rc5_4337, guest: Constants.DefaultGuestAddress, encryptedPksDb: new CoreSigners.Pk.Encrypted.EncryptedPksDb(), @@ -102,26 +195,46 @@ export const ManagerOptionsDefaults = { dbPruningInterval: 1000 * 60 * 60 * 24, // 24 hours - stateProvider: new State.Sequence.Provider(), + passkeyProvider: defaultPasskeyProvider, + + stateProvider: typeof fetch !== 'undefined' ? new State.Sequence.Provider(undefined, fetch) : undefined, networks: Network.ALL, - relayers: () => [Relayer.LocalRelayer.createFromWindow(window)].filter((r) => r !== undefined), + relayers: () => { + if (typeof window !== 'undefined') { + return [Relayer.LocalRelayer.createFromWindow(window)].filter((r) => r !== undefined) + } + return [] + }, bundlers: [], - guardUrl: 'https://dev-guard.sequence.app', + nonWitnessableSigners: [] as Address.Address[], + + guardUrl: 'https://guard.sequence.app', guardAddresses: { - wallet: '0xa2e70CeaB3Eb145F32d110383B75B330fA4e288a', - sessions: '0x18002Fc09deF9A47437cc64e270843dE094f5984', - } as Record, // TODO: change to the actual guard address + wallet: '0x26f3D30F41FA897309Ae804A2AFf15CEb1dA5742', + sessions: '0xF6Bc87F5F2edAdb66737E32D37b46423901dfEF1', + } as Record, defaultGuardTopology: { - // TODO: Move this somewhere else - type: 'signer', - address: '0x0000000000000000000000000000000000000000', // will be replaced by the actual guard address + type: 'nested', weight: 1n, - } as Config.SignerLeaf, + threshold: 1n, + tree: [ + { + type: 'signer', + address: Constants.PlaceholderAddress, + weight: 1n, + }, + { + type: 'signer', + // Sequence dev multisig, as recovery guard signer + address: '0x007a47e6BF40C1e0ed5c01aE42fDC75879140bc4', + weight: 1n, + }, + ], + } as Config.NestedLeaf, defaultSessionsTopology: { - // TODO: Move this somewhere else type: 'sapient-signer', weight: 1n, } as Omit, @@ -129,14 +242,14 @@ export const ManagerOptionsDefaults = { defaultRecoverySettings: { requiredDeltaTime: 2592000n, // 30 days (in seconds) minTimestamp: 0n, + includeTestnets: false, }, multiInjectedProviderDiscovery: true, identity: { - // TODO: change to prod url once deployed - url: 'https://dev-identity.sequence-dev.app', - fetch: window.fetch, + url: 'https://identity.sequence.app', + fetch: typeof window !== 'undefined' ? window.fetch : undefined, verifyAttestation: true, email: { enabled: false, @@ -144,10 +257,12 @@ export const ManagerOptionsDefaults = { google: { enabled: false, clientId: '', + authMethod: 'authcode-pkce' as const, }, apple: { enabled: false, clientId: '', + authMethod: 'authcode' as const, }, }, } @@ -156,17 +271,117 @@ export const CreateWalletOptionsDefaults = { useGuard: false, } -export function applyManagerOptionsDefaults(options?: ManagerOptions) { +export function applyManagerOptionsDefaults(options?: ManagerOptions): ResolvedManagerOptions { + const env = resolveWdkEnv(options?.env) + + const identity: ResolvedIdentityOptions = { + ...ManagerOptionsDefaults.identity, + ...options?.identity, + email: { ...ManagerOptionsDefaults.identity.email, ...options?.identity?.email }, + google: { ...ManagerOptionsDefaults.identity.google, ...options?.identity?.google }, + apple: { ...ManagerOptionsDefaults.identity.apple, ...options?.identity?.apple }, + } + + if (!identity.fetch && env.fetch) { + identity.fetch = env.fetch + } + + let encryptedPksDb = options?.encryptedPksDb ?? ManagerOptionsDefaults.encryptedPksDb + if (!options?.encryptedPksDb && options?.env) { + encryptedPksDb = new CoreSigners.Pk.Encrypted.EncryptedPksDb(undefined, undefined, env) + } + + let authKeysDb = options?.authKeysDb ?? ManagerOptionsDefaults.authKeysDb + if (!options?.authKeysDb && options?.env) { + authKeysDb = new Db.AuthKeys(undefined, env) + } + + let stateProvider = options?.stateProvider ?? ManagerOptionsDefaults.stateProvider + if (!options?.stateProvider && options?.env?.fetch) { + stateProvider = new State.Sequence.Provider(undefined, options.env.fetch) + } else if (!stateProvider && env.fetch) { + stateProvider = new State.Sequence.Provider(undefined, env.fetch) + } + + if (!stateProvider) { + throw new Error('stateProvider is required. Provide ManagerOptions.stateProvider or env.fetch') + } + + const extensions = options?.extensions ?? ManagerOptionsDefaults.extensions + const defaultGuardTopology = options?.defaultGuardTopology ?? ManagerOptionsDefaults.defaultGuardTopology + + // Merge and normalize non-witnessable signers. + // We always include the sessions extension address for the active extensions set. + const nonWitnessable = new Set() + for (const address of ManagerOptionsDefaults.nonWitnessableSigners ?? []) { + nonWitnessable.add(address.toLowerCase()) + } + for (const address of options?.nonWitnessableSigners ?? []) { + nonWitnessable.add(address.toLowerCase()) + } + nonWitnessable.add(extensions.sessions.toLowerCase()) + + // Include static signer leaves from the guard topology (e.g. recovery guard signer), + // but ignore the placeholder address that is later replaced per-role. + if (defaultGuardTopology) { + const guardTopologySigners = Config.getSigners(defaultGuardTopology) + for (const signer of guardTopologySigners.signers) { + if (Address.isEqual(signer, Constants.PlaceholderAddress)) { + continue + } + nonWitnessable.add(signer.toLowerCase()) + } + for (const signer of guardTopologySigners.sapientSigners) { + nonWitnessable.add(signer.address.toLowerCase()) + } + } + return { - ...ManagerOptionsDefaults, - ...options, - identity: { ...ManagerOptionsDefaults.identity, ...options?.identity }, + verbose: options?.verbose ?? ManagerOptionsDefaults.verbose, + + extensions, + context: options?.context ?? ManagerOptionsDefaults.context, + context4337: options?.context4337 ?? ManagerOptionsDefaults.context4337, + guest: options?.guest ?? ManagerOptionsDefaults.guest, + + encryptedPksDb, + managerDb: options?.managerDb ?? ManagerOptionsDefaults.managerDb, + transactionsDb: options?.transactionsDb ?? ManagerOptionsDefaults.transactionsDb, + signaturesDb: options?.signaturesDb ?? ManagerOptionsDefaults.signaturesDb, + messagesDb: options?.messagesDb ?? ManagerOptionsDefaults.messagesDb, + authCommitmentsDb: options?.authCommitmentsDb ?? ManagerOptionsDefaults.authCommitmentsDb, + recoveryDb: options?.recoveryDb ?? ManagerOptionsDefaults.recoveryDb, + authKeysDb, + passkeyCredentialsDb: options?.passkeyCredentialsDb ?? ManagerOptionsDefaults.passkeyCredentialsDb, + + dbPruningInterval: options?.dbPruningInterval ?? ManagerOptionsDefaults.dbPruningInterval, + + env, + passkeyProvider: options?.passkeyProvider ?? ManagerOptionsDefaults.passkeyProvider, + + stateProvider, + networks: options?.networks ?? ManagerOptionsDefaults.networks, + relayers: options?.relayers ?? ManagerOptionsDefaults.relayers, + bundlers: options?.bundlers ?? ManagerOptionsDefaults.bundlers, + guardUrl: options?.guardUrl ?? ManagerOptionsDefaults.guardUrl, + guardAddresses: options?.guardAddresses ?? ManagerOptionsDefaults.guardAddresses, + + nonWitnessableSigners: Array.from(nonWitnessable) as Address.Address[], + + defaultGuardTopology, + defaultRecoverySettings: options?.defaultRecoverySettings ?? ManagerOptionsDefaults.defaultRecoverySettings, + + multiInjectedProviderDiscovery: + options?.multiInjectedProviderDiscovery ?? ManagerOptionsDefaults.multiInjectedProviderDiscovery, + + identity, } } export type RecoverySettings = { requiredDeltaTime: bigint minTimestamp: bigint + includeTestnets?: boolean } export type Databases = { @@ -195,7 +410,9 @@ export type Sequence = { readonly relayers: Relayer.Relayer[] readonly bundlers: Bundler.Bundler[] - readonly defaultGuardTopology: Config.SignerLeaf + readonly nonWitnessableSigners: ReadonlySet + + readonly defaultGuardTopology: Config.Topology readonly defaultRecoverySettings: RecoverySettings readonly guardUrl: string @@ -221,6 +438,8 @@ export type Shared = { readonly sequence: Sequence readonly databases: Databases + readonly env: WdkEnv + readonly passkeyProvider: PasskeyProvider readonly handlers: Map @@ -353,7 +572,7 @@ export class Manager { const ops = applyManagerOptionsDefaults(options) // Build relayers list - let relayers: Relayer.Relayer[] = [] + const relayers: Relayer.Relayer[] = [] // Add EIP-6963 relayers if enabled if (ops.multiInjectedProviderDiscovery) { @@ -382,6 +601,10 @@ export class Manager { relayers, bundlers: ops.bundlers, + nonWitnessableSigners: new Set( + (ops.nonWitnessableSigners ?? []).map((address) => address.toLowerCase() as Address.Address), + ), + defaultGuardTopology: ops.defaultGuardTopology, defaultRecoverySettings: ops.defaultRecoverySettings, @@ -403,6 +626,9 @@ export class Manager { pruningInterval: ops.dbPruningInterval, }, + env: ops.env, + passkeyProvider: ops.passkeyProvider, + modules: {} as any, handlers: new Map(), } @@ -435,6 +661,7 @@ export class Manager { modules.signatures, shared.sequence.extensions, shared.sequence.stateProvider, + shared.passkeyProvider, ) shared.handlers.set(Kinds.LoginPasskey, this.passkeysHandler) @@ -457,36 +684,124 @@ export class Manager { const identityInstrument = new IdentityInstrument(ops.identity.url, ops.identity.scope, verifyingFetch) if (ops.identity.email?.enabled) { - this.otpHandler = new OtpHandler(identityInstrument, modules.signatures, shared.databases.authKeys) + this.otpHandler = new OtpHandler(identityInstrument, modules.signatures, shared.databases.authKeys, shared.env) shared.handlers.set(Kinds.LoginEmailOtp, this.otpHandler) } if (ops.identity.google?.enabled) { - shared.handlers.set( - Kinds.LoginGooglePkce, - new AuthCodePkceHandler( - 'google-pkce', - 'https://accounts.google.com', - ops.identity.google.clientId, - identityInstrument, - modules.signatures, - shared.databases.authCommitments, - shared.databases.authKeys, - ), - ) + if (ops.identity.google.authMethod === 'id-token') { + shared.handlers.set( + Kinds.LoginGoogle, + new IdTokenHandler( + 'google-id-token', + 'https://accounts.google.com', + ops.identity.google.clientId, + identityInstrument, + modules.signatures, + shared.databases.authKeys, + shared.env, + ), + ) + } else { + shared.handlers.set( + Kinds.LoginGoogle, + new AuthCodePkceHandler( + 'google-pkce', + 'https://accounts.google.com', + 'https://accounts.google.com/o/oauth2/v2/auth', + ops.identity.google.clientId, + identityInstrument, + modules.signatures, + shared.databases.authCommitments, + shared.databases.authKeys, + shared.env, + ), + ) + } } if (ops.identity.apple?.enabled) { - shared.handlers.set( - Kinds.LoginApple, - new AuthCodeHandler( - 'apple', - 'https://appleid.apple.com', - ops.identity.apple.clientId, - identityInstrument, - modules.signatures, - shared.databases.authCommitments, - shared.databases.authKeys, - ), - ) + if (ops.identity.apple.authMethod === 'id-token') { + shared.handlers.set( + Kinds.LoginApple, + new IdTokenHandler( + 'apple-id-token', + 'https://appleid.apple.com', + ops.identity.apple.clientId, + identityInstrument, + modules.signatures, + shared.databases.authKeys, + shared.env, + ), + ) + } else { + shared.handlers.set( + Kinds.LoginApple, + new AuthCodeHandler( + 'apple', + 'https://appleid.apple.com', + 'https://appleid.apple.com/auth/authorize', + ops.identity.apple.clientId, + identityInstrument, + modules.signatures, + shared.databases.authCommitments, + shared.databases.authKeys, + shared.env, + ), + ) + } + } + if (ops.identity.customProviders?.length) { + for (const provider of ops.identity.customProviders) { + switch (provider.authMethod) { + case 'id-token': + shared.handlers.set( + provider.kind, + new IdTokenHandler( + provider.kind, + provider.issuer, + provider.clientId, + identityInstrument, + modules.signatures, + shared.databases.authKeys, + shared.env, + ), + ) + break + case 'authcode': + shared.handlers.set( + provider.kind, + new AuthCodeHandler( + provider.kind, + provider.issuer, + provider.oauthUrl, + provider.clientId, + identityInstrument, + modules.signatures, + shared.databases.authCommitments, + shared.databases.authKeys, + shared.env, + ), + ) + break + case 'authcode-pkce': + shared.handlers.set( + provider.kind, + new AuthCodePkceHandler( + provider.kind, + provider.issuer, + provider.oauthUrl, + provider.clientId, + identityInstrument, + modules.signatures, + shared.databases.authCommitments, + shared.databases.authKeys, + shared.env, + ), + ) + break + default: + throw new Error('unsupported auth method') + } + } } shared.modules = modules @@ -500,18 +815,30 @@ export class Manager { } } - public registerMnemonicUI(onPromptMnemonic: (respond: (mnemonic: string) => Promise) => Promise) { + public registerMnemonicUI(onPromptMnemonic: PromptMnemonicHandler) { return this.mnemonicHandler.registerUI(onPromptMnemonic) } - public registerOtpUI(onPromptOtp: (recipient: string, respond: (otp: string) => Promise) => Promise) { + public registerOtpUI(onPromptOtp: PromptOtpHandler) { return this.otpHandler?.registerUI(onPromptOtp) || (() => {}) } - public registerGuardUI( - onPromptOtp: (codeType: 'TOTP' | 'PIN', respond: (otp: string) => Promise) => Promise, - ) { - return this.guardHandler?.registerUI(onPromptOtp) || (() => {}) + public registerIdTokenUI(onPromptIdToken: PromptIdTokenHandler) { + const unregisters: (() => void)[] = [] + + this.shared.handlers.forEach((handler) => { + if (handler instanceof IdTokenHandler) { + unregisters.push(handler.registerUI(onPromptIdToken)) + } + }) + + return () => { + unregisters.forEach((unregister) => unregister()) + } + } + + public registerGuardUI(onPromptCode: PromptCodeHandler) { + return this.guardHandler?.registerUI(onPromptCode) || (() => {}) } public async setRedirectPrefix(prefix: string) { diff --git a/packages/wallet/wdk/src/sequence/messages.ts b/packages/wallet/wdk/src/sequence/messages.ts index 131aae3b1..d8fe480c2 100644 --- a/packages/wallet/wdk/src/sequence/messages.ts +++ b/packages/wallet/wdk/src/sequence/messages.ts @@ -242,7 +242,7 @@ export class Messages implements MessagesInterface { const message = await this.getByMessageOrSignatureId(messageOrSignatureId) await this.shared.databases.signatures.del(message.signatureId) await this.shared.databases.messages.del(message.id) - } catch (error) { + } catch { // Ignore } } diff --git a/packages/wallet/wdk/src/sequence/passkeys-provider.ts b/packages/wallet/wdk/src/sequence/passkeys-provider.ts new file mode 100644 index 000000000..3ed2068fe --- /dev/null +++ b/packages/wallet/wdk/src/sequence/passkeys-provider.ts @@ -0,0 +1,55 @@ +import { Signers, State } from '@0xsequence/wallet-core' +import type { Extensions } from '@0xsequence/wallet-primitives' +import type { Address, Hex } from 'ox' + +export type PasskeySigner = Signers.SapientSigner & + Signers.Witnessable & { + credentialId: string + publicKey: Extensions.Passkeys.PublicKey + imageHash: Hex.Hex + } + +export type PasskeyProvider = { + create: ( + extensions: Pick, + options?: Signers.Passkey.CreatePasskeyOptions, + ) => Promise + find: ( + stateReader: State.Reader, + extensions: Pick, + options?: Signers.Passkey.FindPasskeyOptions, + ) => Promise + loadFromWitness: ( + stateReader: State.Reader, + extensions: Pick, + wallet: Address.Address, + imageHash: Hex.Hex, + options?: Signers.Passkey.FindPasskeyOptions, + ) => Promise + fromCredential: (args: { + credentialId: string + publicKey: Extensions.Passkeys.PublicKey + extensions: Pick + embedMetadata?: boolean + metadata?: Extensions.Passkeys.PasskeyMetadata + webauthn?: Signers.Passkey.WebAuthnLike + }) => PasskeySigner + isSigner?: (signer: unknown) => signer is PasskeySigner +} + +export const defaultPasskeyProvider: PasskeyProvider = { + create: (extensions, options) => Signers.Passkey.Passkey.create(extensions, options), + find: (stateReader, extensions, options) => Signers.Passkey.Passkey.find(stateReader, extensions, options), + loadFromWitness: (stateReader, extensions, wallet, imageHash, options) => + Signers.Passkey.Passkey.loadFromWitness(stateReader, extensions, wallet, imageHash, options), + fromCredential: ({ credentialId, publicKey, extensions, embedMetadata, metadata, webauthn }) => + new Signers.Passkey.Passkey({ + credentialId, + publicKey, + extensions, + embedMetadata, + metadata, + webauthn, + }), + isSigner: (signer: unknown): signer is PasskeySigner => signer instanceof Signers.Passkey.Passkey, +} diff --git a/packages/wallet/wdk/src/sequence/recovery.ts b/packages/wallet/wdk/src/sequence/recovery.ts index 600fb7674..2e6074364 100644 --- a/packages/wallet/wdk/src/sequence/recovery.ts +++ b/packages/wallet/wdk/src/sequence/recovery.ts @@ -1,12 +1,16 @@ import { Envelope } from '@0xsequence/wallet-core' import { Config, Constants, Extensions, GenericTree, Payload } from '@0xsequence/wallet-primitives' -import { Address, Hex, Provider, RpcTransport } from 'ox' +import { Abi, AbiFunction, Address, Hex, Provider, RpcTransport } from 'ox' import { MnemonicHandler } from './handlers/mnemonic.js' import { Shared } from './manager.js' import { Actions, Module } from './types/index.js' import { QueuedRecoveryPayload } from './types/recovery.js' import { Kinds, RecoverySigner } from './types/signer.js' +const AGGREGATE3 = Abi.from([ + 'function aggregate3((address target, bool allowFailure, bytes callData)[] calls) external payable returns ((bool success, bytes returnData)[])', +])[0]! + export interface RecoveryInterface { /** * Retrieves the list of configured recovery signers for a given wallet. @@ -284,7 +288,7 @@ export class Recovery implements RecoveryInterface { } await this.updateRecoveryModule(modules, (leaves) => { - const next = leaves.filter((l) => l.signer !== address) + const next = leaves.filter((l) => !Address.isEqual(l.signer, address)) if (next.length === 0) { return [ { @@ -514,9 +518,16 @@ export class Recovery implements RecoveryInterface { async fetchQueuedPayloads(wallet: Address.Address, chainId?: number): Promise { // Create providers for each network const providers = this.shared.sequence.networks - .filter((network) => (chainId ? network.chainId === chainId : true)) + .filter((network) => + chainId + ? network.chainId === chainId + : !this.shared.sequence.defaultRecoverySettings.includeTestnets + ? network.type !== 'testnet' + : true, + ) .map((network) => ({ chainId: network.chainId, + multicall3Address: network.contracts?.multicall3, provider: Provider.from(RpcTransport.fromHttp(network.rpcUrl)), })) @@ -526,69 +537,167 @@ export class Recovery implements RecoveryInterface { return [] } + const recoveryExtension = this.shared.sequence.extensions.recovery const payloads: QueuedRecoveryPayload[] = [] - for (const signer of signers) { - for (const { chainId, provider } of providers) { - const totalPayloads = await Extensions.Recovery.totalQueuedPayloads( - provider, - this.shared.sequence.extensions.recovery, - wallet, - signer.address, - ) - - for (let i = 0n; i < totalPayloads; i++) { - const payloadHash = await Extensions.Recovery.queuedPayloadHashOf( - provider, - this.shared.sequence.extensions.recovery, - wallet, - signer.address, - i, - ) - - const timestamp = await Extensions.Recovery.timestampForQueuedPayload( - provider, - this.shared.sequence.extensions.recovery, - wallet, - signer.address, - payloadHash, - ) - - const payload = await this.shared.sequence.stateProvider.getPayload(payloadHash) - - // If ready, we need to check if it was executed already - // for this, we check if the wallet nonce for the given space - // is greater than the nonce in the payload - if (timestamp < Date.now() / 1000 && payload && Payload.isCalls(payload.payload)) { - const nonce = await this.shared.modules.wallets.getNonce(chainId, wallet, payload.payload.space) - if (nonce > i) { - continue + await Promise.all( + providers.map(async ({ chainId, provider, multicall3Address }) => { + try { + let totalPayloadsBySigner: bigint[] + + if (multicall3Address) { + try { + // Batch all totalQueuedPayloads calls for every signer into a single Multicall3 request. + // This reduces N signer calls per network down to 1 call per network. + totalPayloadsBySigner = await this.fetchTotalQueuedPayloadsBatched( + provider, + recoveryExtension, + wallet, + signers, + multicall3Address, + ) + } catch (err) { + console.error( + `Recovery.fetchQueuedPayloads multicall3 failed for chainId ${chainId}, retrying with individual calls:`, + err, + ) + totalPayloadsBySigner = await this.fetchTotalQueuedPayloadsFallback( + provider, + recoveryExtension, + wallet, + signers, + ) } + } else { + totalPayloadsBySigner = await this.fetchTotalQueuedPayloadsFallback( + provider, + recoveryExtension, + wallet, + signers, + ) } - // The id is the index + signer address + chainId + wallet address - const id = `${i}-${signer.address}-${chainId}-${wallet}` - - // Create a new payload - const payloadEntry: QueuedRecoveryPayload = { - id, - index: i, - recoveryModule: this.shared.sequence.extensions.recovery, - wallet: wallet, - signer: signer.address, - chainId, - startTimestamp: timestamp, - endTimestamp: timestamp + signer.requiredDeltaTime, - payloadHash, - payload: payload?.payload, + for (let s = 0; s < signers.length; s++) { + const signer = signers[s]! + const totalPayloads = totalPayloadsBySigner[s]! + if (totalPayloads === 0n) continue + + // Only make individual calls for the rare case where payloads actually exist + for (let i = 0n; i < totalPayloads; i++) { + const payloadHash = await Extensions.Recovery.queuedPayloadHashOf( + provider, + recoveryExtension, + wallet, + signer.address, + i, + ) + + const timestamp = await Extensions.Recovery.timestampForQueuedPayload( + provider, + recoveryExtension, + wallet, + signer.address, + payloadHash, + ) + + const payload = await this.shared.sequence.stateProvider.getPayload(payloadHash) + + // If ready, we need to check if it was executed already + // for this, we check if the wallet nonce for the given space + // is greater than the nonce in the payload + if (timestamp < Date.now() / 1000 && payload && Payload.isCalls(payload.payload)) { + const nonce = await this.shared.modules.wallets.getNonce(chainId, wallet, payload.payload.space) + if (nonce > i) { + continue + } + } + + // The id is the index + signer address + chainId + wallet address + const id = `${i}-${signer.address}-${chainId}-${wallet}` + + const payloadEntry: QueuedRecoveryPayload = { + id, + index: i, + recoveryModule: recoveryExtension, + wallet: wallet, + signer: signer.address, + chainId, + startTimestamp: timestamp, + endTimestamp: timestamp + signer.requiredDeltaTime, + payloadHash, + payload: payload?.payload, + } + + payloads.push(payloadEntry) + } } - - payloads.push(payloadEntry) + } catch (err) { + console.error(`Recovery.fetchQueuedPayloads error for chainId ${chainId}:`, err) } + }), + ) + + return payloads + } + + private async fetchTotalQueuedPayloadsBatched( + provider: Provider.Provider, + recoveryExtension: Address.Address, + wallet: Address.Address, + signers: RecoverySigner[], + multicall3Address: Address.Address, + ): Promise { + const calls = signers.map((signer) => ({ + target: recoveryExtension, + allowFailure: true, + callData: AbiFunction.encodeData(Extensions.Recovery.TOTAL_QUEUED_PAYLOADS, [wallet, signer.address]), + })) + + const response = await provider.request({ + method: 'eth_call', + params: [ + { + to: multicall3Address, + data: AbiFunction.encodeData(AGGREGATE3, [calls]), + }, + 'latest', + ], + }) + + const results = AbiFunction.decodeResult(AGGREGATE3, response) as readonly { + success: boolean + returnData: Hex.Hex + }[] + + return results.map((result) => { + if (!result.success || result.returnData === '0x') { + return 0n } + return Hex.toBigInt(result.returnData) + }) + } + + private fetchTotalQueuedPayloadsFallback = async ( + provider: Provider.Provider, + recoveryExtension: Address.Address, + wallet: Address.Address, + signers: RecoverySigner[], + ): Promise => { + const result: bigint[] = signers.map(() => 0n) + + // Fallback to individual calls if the multicall3 call fails + for (let s = 0; s < signers.length; s++) { + const signer = signers[s]! + const totalPayloads = await Extensions.Recovery.totalQueuedPayloads( + provider, + recoveryExtension, + wallet, + signer.address, + ) + result[s] = totalPayloads } - return payloads + return result } async encodeRecoverySignature(imageHash: Hex.Hex, signer: Address.Address) { diff --git a/packages/wallet/wdk/src/sequence/sessions.ts b/packages/wallet/wdk/src/sequence/sessions.ts index d96f0a316..198258279 100644 --- a/packages/wallet/wdk/src/sequence/sessions.ts +++ b/packages/wallet/wdk/src/sequence/sessions.ts @@ -9,7 +9,9 @@ import { SessionConfig, } from '@0xsequence/wallet-primitives' import { Address, Bytes, Hash, Hex } from 'ox' +import { AuthCodeHandler } from './handlers/authcode.js' import { AuthCodePkceHandler } from './handlers/authcode-pkce.js' +import { IdTokenHandler } from './handlers/idtoken.js' import { IdentityHandler, identityTypeToHex } from './handlers/identity.js' import { Handler } from './handlers/index.js' import { ManagerOptionsDefaults, Shared } from './manager.js' @@ -253,7 +255,7 @@ export class Sessions implements SessionsInterface { return modules.some((m) => Address.isEqual(m.sapientLeaf.address, this.shared.sequence.extensions.sessions)) } - async initSessionModule(modules: Module[], identitySigners: Address.Address[], guardTopology?: Config.NestedLeaf) { + async initSessionModule(modules: Module[], identitySigners: Address.Address[], guardTopology?: Config.Topology) { if (this.hasSessionModule(modules)) { throw new Error('session-module-already-initialized') } @@ -336,11 +338,20 @@ export class Sessions implements SessionsInterface { continue } const iHandler = this.shared.handlers.get(identityKind) - if (iHandler) { - handler = iHandler - identitySignerAddress = identitySigner - break + if (!iHandler) { + continue + } + if (identityKind === Kinds.LocalDevice) { + const hasLocalDevice = await this.shared.modules.devices.has(identitySigner) + if (!hasLocalDevice) { + console.warn('Identity signer not on this device, skipping', identitySigner) + continue + } } + + handler = iHandler + identitySignerAddress = identitySigner + break } if (!handler || !identitySignerAddress) { @@ -353,7 +364,11 @@ export class Sessions implements SessionsInterface { let audienceHash: Hex.Hex = '0x' if (handler instanceof IdentityHandler) { identityType = handler.identityType - if (handler instanceof AuthCodePkceHandler) { + if ( + handler instanceof AuthCodeHandler || + handler instanceof AuthCodePkceHandler || + handler instanceof IdTokenHandler + ) { issuerHash = Hash.keccak256(Hex.fromString(handler.issuer)) audienceHash = Hash.keccak256(Hex.fromString(handler.audience)) } diff --git a/packages/wallet/wdk/src/sequence/signers.ts b/packages/wallet/wdk/src/sequence/signers.ts index 051e03270..ecb43cab7 100644 --- a/packages/wallet/wdk/src/sequence/signers.ts +++ b/packages/wallet/wdk/src/sequence/signers.ts @@ -8,7 +8,16 @@ export function isWitnessExtraSignerKind(extra: any): extra is WitnessExtraSigne } function toKnownKind(kind: string): Kind { - if (Object.values(Kinds).includes(kind as Kind)) { + if (kind.startsWith('custom-')) { + return kind as Kind + } + + if (kind === 'login-google-pkce') { + // Normalize legacy Google PKCE witnesses while the canonical signer kind is `login-google`. + return Kinds.LoginGoogle + } + + if (Object.values(Kinds).includes(kind as (typeof Kinds)[keyof typeof Kinds])) { return kind as Kind } @@ -44,6 +53,19 @@ export class Signers { return Kinds.Guard } + // Passkeys are a sapient signer module: the address alone identifies the kind. + // Metadata (credential id, public key, etc.) is loaded later by the PasskeysHandler + // via the witness payload, so we can skip the witness probe here. + if (Address.isEqual(this.shared.sequence.extensions.passkeys, address)) { + return Kinds.LoginPasskey + } + + // Some signers are known to never publish a witness record (e.g. module signers). + // Skip probing the Sessions/Witness endpoint for them. + if (this.shared.sequence.nonWitnessableSigners.has(address.toLowerCase() as Address.Address)) { + return undefined + } + // We need to use the state provider (and witness) this will tell us the kind of signer // NOTICE: This looks expensive, but this operation should be cached by the state provider const witness = await (imageHash @@ -64,7 +86,9 @@ export class Signers { if (isWitnessExtraSignerKind(message)) { return toKnownKind(message.signerKind) } - } catch {} + } catch { + // ignore + } return undefined } diff --git a/packages/wallet/wdk/src/sequence/transactions.ts b/packages/wallet/wdk/src/sequence/transactions.ts index 824cb00f6..c5549f28d 100644 --- a/packages/wallet/wdk/src/sequence/transactions.ts +++ b/packages/wallet/wdk/src/sequence/transactions.ts @@ -310,22 +310,28 @@ export class Transactions implements TransactionsInterface { throw new Error(`Transaction ${transactionId} is not in the requested state`) } + if (!Payload.isCalls(tx.envelope.payload)) { + throw new Error(`Transaction ${transactionId} is not a calls payload`) + } + + const payload = tx.envelope.payload + // Modify the envelope with the changes if (changes?.nonce) { - tx.envelope.payload.nonce = changes.nonce + payload.nonce = changes.nonce } if (changes?.space) { - tx.envelope.payload.space = changes.space + payload.space = changes.space } if (changes?.calls) { - if (changes.calls.length !== tx.envelope.payload.calls.length) { + if (changes.calls.length !== payload.calls.length) { throw new Error(`Invalid number of calls for transaction ${transactionId}`) } for (let i = 0; i < changes.calls.length; i++) { - tx.envelope.payload.calls[i]!.gasLimit = changes.calls[i]!.gasLimit + payload.calls[i]!.gasLimit = changes.calls[i]!.gasLimit } } @@ -335,6 +341,7 @@ export class Transactions implements TransactionsInterface { throw new Error(`Network not found for ${tx.envelope.chainId}`) } const provider = Provider.from(RpcTransport.fromHttp(network.rpcUrl)) + const feeOptionsTransaction = await wallet.buildFeeOptionsTransaction(provider, payload) // Get relayer and relayer options const [allRelayerOptions, allBundlerOptions] = await Promise.all([ @@ -347,7 +354,13 @@ export class Transactions implements TransactionsInterface { return [] } - const feeOptions = await relayer.feeOptions(tx.wallet, tx.envelope.chainId, tx.envelope.payload.calls) + const feeOptions = await relayer.feeOptions( + tx.wallet, + tx.envelope.chainId, + feeOptionsTransaction.to, + payload.calls, + feeOptionsTransaction.data, + ) if (feeOptions.options.length === 0) { const { name, icon } = relayer instanceof Relayer.EIP6963.EIP6963Relayer ? relayer.info : {} @@ -358,6 +371,8 @@ export class Transactions implements TransactionsInterface { id: uuidv7(), relayerType: relayer.type, relayerId: relayer.id, + sponsored: feeOptions.sponsored, + failed: feeOptions.failed, name, icon, } as StandardRelayerOption, @@ -370,6 +385,8 @@ export class Transactions implements TransactionsInterface { feeOption, relayerType: relayer.type, relayerId: relayer.id, + sponsored: feeOptions.sponsored, + failed: feeOptions.failed, quote: feeOptions.quote, })) }), @@ -388,8 +405,8 @@ export class Transactions implements TransactionsInterface { } try { - const erc4337Op = await wallet.prepare4337Transaction(provider, tx.envelope.payload.calls, { - space: tx.envelope.payload.space, + const erc4337Op = await wallet.prepare4337Transaction(provider, payload.calls, { + space: payload.space, }) const erc4337OpsWithEstimatedLimits = await bundler.estimateLimits(tx.wallet, erc4337Op.payload) @@ -492,7 +509,7 @@ export class Transactions implements TransactionsInterface { let tx: Transaction | undefined try { tx = await this.get(transactionOrSignatureId) - } catch (e) { + } catch { // If not found, it might be a signature ID const signature = await this.shared.modules.signatures.get(transactionOrSignatureId) if (!signature) { diff --git a/packages/wallet/wdk/src/sequence/types/index.ts b/packages/wallet/wdk/src/sequence/types/index.ts index 066e8a071..946ee9bf3 100644 --- a/packages/wallet/wdk/src/sequence/types/index.ts +++ b/packages/wallet/wdk/src/sequence/types/index.ts @@ -27,5 +27,6 @@ export type { TransactionRequest, TransactionRequested, } from './transaction-request.js' +export { isERC4337RelayerOption, isStandardRelayerOption } from './transaction-request.js' export type { Wallet } from './wallet.js' export type { Module } from './module.js' diff --git a/packages/wallet/wdk/src/sequence/types/module.ts b/packages/wallet/wdk/src/sequence/types/module.ts index df254a648..014a97ae6 100644 --- a/packages/wallet/wdk/src/sequence/types/module.ts +++ b/packages/wallet/wdk/src/sequence/types/module.ts @@ -3,5 +3,5 @@ import { Config } from '@0xsequence/wallet-primitives' export type Module = { weight: bigint sapientLeaf: Config.SapientSignerLeaf - guardLeaf?: Config.NestedLeaf + guardLeaf?: Config.Topology } diff --git a/packages/wallet/wdk/src/sequence/types/signature-request.ts b/packages/wallet/wdk/src/sequence/types/signature-request.ts index cbce933da..e3cb1b787 100644 --- a/packages/wallet/wdk/src/sequence/types/signature-request.ts +++ b/packages/wallet/wdk/src/sequence/types/signature-request.ts @@ -13,6 +13,8 @@ export type ActionToPayload = { [Actions.Recovery]: Payload.Recovery [Actions.AddRecoverySigner]: Payload.ConfigUpdate [Actions.RemoveRecoverySigner]: Payload.ConfigUpdate + [Actions.AddLoginSigner]: Payload.ConfigUpdate + [Actions.RemoveLoginSigner]: Payload.ConfigUpdate [Actions.SessionImplicitAuthorize]: Payload.SessionImplicitAuthorize } @@ -26,6 +28,8 @@ export const Actions = { Recovery: 'recovery', AddRecoverySigner: 'add-recovery-signer', RemoveRecoverySigner: 'remove-recovery-signer', + AddLoginSigner: 'add-login-signer', + RemoveLoginSigner: 'remove-login-signer', SessionImplicitAuthorize: 'session-implicit-authorize', } as const diff --git a/packages/wallet/wdk/src/sequence/types/signer.ts b/packages/wallet/wdk/src/sequence/types/signer.ts index ac4b5b51f..eb46db52f 100644 --- a/packages/wallet/wdk/src/sequence/types/signer.ts +++ b/packages/wallet/wdk/src/sequence/types/signer.ts @@ -5,14 +5,14 @@ export const Kinds = { LoginPasskey: 'login-passkey', LoginMnemonic: 'login-mnemonic', // Todo: do not name it login-mnemonic, just mnemonic LoginEmailOtp: 'login-email-otp', - LoginGooglePkce: 'login-google-pkce', + LoginGoogle: 'login-google', LoginApple: 'login-apple', Recovery: 'recovery-extension', Guard: 'guard-extension', Unknown: 'unknown', } as const -export type Kind = (typeof Kinds)[keyof typeof Kinds] +export type Kind = (typeof Kinds)[keyof typeof Kinds] | `custom-${string}` export type WitnessExtraSignerKind = { signerKind: string diff --git a/packages/wallet/wdk/src/sequence/types/transaction-request.ts b/packages/wallet/wdk/src/sequence/types/transaction-request.ts index 51160a049..5511bc61b 100644 --- a/packages/wallet/wdk/src/sequence/types/transaction-request.ts +++ b/packages/wallet/wdk/src/sequence/types/transaction-request.ts @@ -21,6 +21,8 @@ export type StandardRelayerOption = BaseRelayerOption & { kind: 'standard' feeOption?: Relayer.FeeOption quote?: Relayer.FeeQuote + sponsored?: boolean + failed?: boolean name?: string icon?: string } diff --git a/packages/wallet/wdk/src/sequence/types/wallet.ts b/packages/wallet/wdk/src/sequence/types/wallet.ts index dd754a05b..c1e12a990 100644 --- a/packages/wallet/wdk/src/sequence/types/wallet.ts +++ b/packages/wallet/wdk/src/sequence/types/wallet.ts @@ -36,7 +36,7 @@ export interface Wallet { /** * A string identifier for the authentication method used for this session. - * Examples: 'login-mnemonic', 'login-passkey', 'login-google-pkce'. + * Examples: 'login-mnemonic', 'login-passkey', 'login-google'. * @property */ loginType: string diff --git a/packages/wallet/wdk/src/sequence/wallets.ts b/packages/wallet/wdk/src/sequence/wallets.ts index a859ca797..2d3af321b 100644 --- a/packages/wallet/wdk/src/sequence/wallets.ts +++ b/packages/wallet/wdk/src/sequence/wallets.ts @@ -1,24 +1,68 @@ import { Wallet as CoreWallet, Envelope, Signers, State } from '@0xsequence/wallet-core' -import { Config, Constants, GenericTree, Payload, SessionConfig } from '@0xsequence/wallet-primitives' +import { Config, Constants, Payload } from '@0xsequence/wallet-primitives' import { Address, Hex, Provider, RpcTransport } from 'ox' import { AuthCommitment } from '../dbs/auth-commitments.js' import { AuthCodeHandler } from './handlers/authcode.js' +import { IdTokenHandler } from './handlers/idtoken.js' import { MnemonicHandler } from './handlers/mnemonic.js' import { OtpHandler } from './handlers/otp.js' -import { ManagerOptionsDefaults, Shared } from './manager.js' +import { Shared } from './manager.js' import { Device } from './types/device.js' -import { Action, Module } from './types/index.js' +import { Action, Actions, Module } from './types/index.js' import { Kinds, SignerWithKind, WitnessExtraSignerKind } from './types/signer.js' import { Wallet, WalletSelectionUiHandler } from './types/wallet.js' import { PasskeysHandler } from './handlers/passkeys.js' -import { GuardRole } from './guards.js' +import type { PasskeySigner } from './passkeys-provider.js' + +function getSignupHandlerKey(kind: SignupArgs['kind'] | StartSignUpWithRedirectArgs['kind'] | AuthCommitment['kind']) { + if (kind === 'google-pkce') { + return Kinds.LoginGoogle + } + if (kind.startsWith('custom-')) { + return kind + } + return 'login-' + kind +} + +function getSignerKindForSignup(kind: SignupArgs['kind'] | AuthCommitment['kind']) { + if (kind === 'google-id-token' || kind === 'google-pkce') { + return Kinds.LoginGoogle + } + if (kind === 'apple-id-token' || kind === 'apple') { + return Kinds.LoginApple + } + if (kind.startsWith('custom-')) { + return kind + } + return ('login-' + kind) as string +} + +function getIdTokenSignupHandler( + shared: Shared, + kind: typeof Kinds.LoginGoogle | typeof Kinds.LoginApple | `custom-${string}`, +): IdTokenHandler { + const handler = shared.handlers.get(kind) + if (!handler) { + throw new Error('handler-not-registered') + } + if (!(handler instanceof IdTokenHandler)) { + throw new Error('handler-does-not-support-id-token') + } + return handler +} export type StartSignUpWithRedirectArgs = { - kind: 'google-pkce' | 'apple' + kind: 'google-pkce' | 'apple' | `custom-${string}` target: string metadata: { [key: string]: string } } +export type StartAddLoginSignerWithRedirectArgs = { + wallet: Address.Address + kind: 'google-pkce' | 'apple' | `custom-${string}` + target: string +} + export type SignupStatus = | { type: 'login-signer-created'; address: Address.Address } | { type: 'device-signer-created'; address: Address.Address } @@ -49,20 +93,60 @@ export type EmailOtpSignupArgs = CommonSignupArgs & { email: string } +export type IdTokenSignupArgs = CommonSignupArgs & { + kind: 'google-id-token' | 'apple-id-token' | `custom-${string}` + idToken: string +} + export type CompleteRedirectArgs = CommonSignupArgs & { state: string code: string + includeMetadata?: false +} + +export type CompleteRedirectWithMetadataArgs = Omit & { + includeMetadata: true +} + +export type CompleteRedirectMetadataResult = { + target: string + addedLoginSigner?: { + wallet: Address.Address + signer: { + address: Address.Address + kind: string + email?: string + } + } } export type AuthCodeSignupArgs = CommonSignupArgs & { - kind: 'google-pkce' | 'apple' + kind: 'google-pkce' | 'apple' | `custom-${string}` commitment: AuthCommitment code: string target: string isRedirect: boolean } -export type SignupArgs = PasskeySignupArgs | MnemonicSignupArgs | EmailOtpSignupArgs | AuthCodeSignupArgs +export type SignupArgs = + | PasskeySignupArgs + | MnemonicSignupArgs + | EmailOtpSignupArgs + | IdTokenSignupArgs + | AuthCodeSignupArgs + +export type AddLoginSignerArgs = { + wallet: Address.Address +} & ( + | { kind: 'mnemonic'; mnemonic: string } + | { kind: 'email-otp'; email: string } + | { kind: 'google-id-token' | 'apple-id-token' | `custom-${string}`; idToken: string } +) + +export type RemoveLoginSignerArgs = { + wallet: Address.Address + signerAddress: Address.Address +} export type LoginToWalletArgs = { wallet: Address.Address @@ -180,6 +264,7 @@ export interface WalletsInterface { * - `kind: 'mnemonic'`: Uses a mnemonic phrase as the login credential. * - `kind: 'passkey'`: Prompts the user to create a WebAuthn passkey. * - `kind: 'email-otp'`: Initiates an OTP flow to the user's email. + * - `kind: 'google-id-token' | 'apple-id-token'`: Completes an OIDC ID token flow when the provider is configured with `authMethod: 'id-token'`. * - `kind: 'google-pkce' | 'apple'`: Completes an OAuth redirect flow. * Common options like `noGuard` or `noRecovery` can customize the wallet's security features. * @returns A promise that resolves to the address of the newly created wallet, or `undefined` if the sign-up was aborted. @@ -210,6 +295,7 @@ export interface WalletsInterface { * @param args The arguments containing the `state` and `code` from the redirect, along with original sign-up options. * @returns A promise that resolves to target path that should be redirected to. */ + completeRedirect(args: CompleteRedirectWithMetadataArgs): Promise completeRedirect(args: CompleteRedirectArgs): Promise /** @@ -243,6 +329,66 @@ export interface WalletsInterface { */ completeLogin(requestId: string): Promise + /** + * Adds a new login signer to an existing wallet, enabling account federation. + * + * This allows a user to link a new login method (e.g., Google, email OTP, mnemonic) to a wallet + * that was originally created with a different credential. After federation, the wallet can be + * discovered and accessed via any of its linked login methods. + * + * @param args The arguments specifying the wallet and the new login credential to add. + * @returns A promise that resolves to a `requestId` for the configuration update signature request. + * @see {completeAddLoginSigner} + */ + addLoginSigner(args: AddLoginSignerArgs): Promise + + /** + * Completes the add-login-signer process after the configuration update has been signed. + * + * @param requestId The ID of the completed signature request returned by `addLoginSigner`. + * @returns A promise that resolves when the configuration update has been submitted. + */ + completeAddLoginSigner(requestId: string): Promise + + /** + * Initiates an add-login-signer process that involves an OAuth redirect. + * + * This is the first step for adding a social login signer (e.g., Google, Apple) to an existing wallet + * via a redirect-based OAuth flow. It validates the wallet, generates the necessary challenges and state, + * stores them locally, and returns a URL. Your application should redirect the user to this URL. + * + * After the redirect callback, call `completeRedirect` with the returned state and code. This will + * create a pending `AddLoginSigner` signature request internally. The caller can then discover + * the pending request through the signatures module and call `completeAddLoginSigner` with the requestId + * once it has been signed. + * + * @param args Arguments specifying the wallet, provider (`kind`), and the `target` URL for the redirect callback. + * @returns A promise that resolves to the full OAuth URL to which the user should be redirected. + * @see {completeRedirect} for the second step of this flow. + * @see {completeAddLoginSigner} to finalize after signing. + */ + startAddLoginSignerWithRedirect(args: StartAddLoginSignerWithRedirectArgs): Promise + + /** + * Removes a login signer from an existing wallet, enabling account defederation. + * + * This allows a user to unlink a login method from a wallet. A safety guard ensures + * at least one login signer always remains. + * + * @param args The arguments specifying the wallet and the signer address to remove. + * @returns A promise that resolves to a `requestId` for the configuration update signature request. + * @see {completeRemoveLoginSigner} + */ + removeLoginSigner(args: RemoveLoginSignerArgs): Promise + + /** + * Completes the remove-login-signer process after the configuration update has been signed. + * + * @param requestId The ID of the completed signature request returned by `removeLoginSigner`. + * @returns A promise that resolves when the configuration update has been submitted. + */ + completeRemoveLoginSigner(requestId: string): Promise + /** * Logs out from a given wallet, ending the current session. * @@ -361,7 +507,25 @@ export function isLoginToPasskeyArgs(args: LoginArgs): args is LoginToPasskeyArg } export function isAuthCodeArgs(args: SignupArgs): args is AuthCodeSignupArgs { - return 'kind' in args && (args.kind === 'google-pkce' || args.kind === 'apple') + return 'code' in args && 'commitment' in args +} + +export function isIdTokenArgs(args: SignupArgs): args is IdTokenSignupArgs { + return 'idToken' in args +} + +function addLoginSignerToSignupArgs(args: AddLoginSignerArgs): SignupArgs { + switch (args.kind) { + case 'mnemonic': + return { kind: 'mnemonic', mnemonic: args.mnemonic } + case 'email-otp': + return { kind: 'email-otp', email: args.email } + default: { + // google-id-token, apple-id-token, custom-* + const _args = args as { kind: string; idToken: string } + return { kind: _args.kind as IdTokenSignupArgs['kind'], idToken: _args.idToken } + } + } } function buildCappedTree(members: { address: Address.Address; imageHash?: Hex.Hex }[]): Config.Topology { @@ -417,24 +581,24 @@ function buildCappedTree(members: { address: Address.Address; imageHash?: Hex.He } as Config.NestedLeaf } -function buildCappedTreeFromTopology(weight: bigint, topology: Config.Topology): Config.Topology { - // We may optimize this for some topology types - // but it is not worth it, because the topology - // that we will use for prod won't be optimizable - return { - type: 'nested', - weight: weight, - threshold: weight, - tree: topology, - } -} +// function buildCappedTreeFromTopology(weight: bigint, topology: Config.Topology): Config.Topology { +// // We may optimize this for some topology types +// // but it is not worth it, because the topology +// // that we will use for prod won't be optimizable +// return { +// type: 'nested', +// weight: weight, +// threshold: weight, +// tree: topology, +// } +// } function toConfig( checkpoint: bigint, loginTopology: Config.Topology, devicesTopology: Config.Topology, modules: Module[], - guardTopology?: Config.NestedLeaf, + guardTopology?: Config.Topology, ): Config.Config { if (!guardTopology) { return { @@ -467,7 +631,7 @@ function toModulesTopology(modules: Module[]): Config.Topology { return { type: 'nested', weight: module.weight, - threshold: module.sapientLeaf.weight + module.guardLeaf.weight, + threshold: module.sapientLeaf.weight + Config.getWeight(module.guardLeaf, () => true).maxWeight, tree: [module.sapientLeaf, module.guardLeaf], } as Config.NestedLeaf } else { @@ -491,8 +655,7 @@ function fromModulesTopology(topology: Config.Topology): Module[] { } else if ( Config.isNestedLeaf(topology) && Config.isNode(topology.tree) && - Config.isSapientSignerLeaf(topology.tree[0]) && - Config.isNestedLeaf(topology.tree[1]) + Config.isSapientSignerLeaf(topology.tree[0]) ) { modules.push({ sapientLeaf: topology.tree[0], @@ -513,7 +676,7 @@ function fromConfig(config: Config.Config): { loginTopology: Config.Topology devicesTopology: Config.Topology modules: Module[] - guardTopology?: Config.NestedLeaf + guardTopology?: Config.Topology } { if (config.threshold === 1n) { if (Config.isNode(config.topology) && Config.isNode(config.topology[0])) { @@ -530,7 +693,7 @@ function fromConfig(config: Config.Config): { Config.isNode(config.topology) && Config.isNode(config.topology[0]) && Config.isNode(config.topology[0][0]) && - Config.isNestedLeaf(config.topology[0][1]) + Config.isTopology(config.topology[0][1]) ) { return { loginTopology: config.topology[0][0][0], @@ -626,8 +789,8 @@ export class Wallets implements WalletsInterface { loginEmail?: string }> { switch (args.kind) { - case 'passkey': - const passkeySigner = await Signers.Passkey.Passkey.create(this.shared.sequence.extensions, { + case 'passkey': { + const passkeySigner = await this.shared.passkeyProvider.create(this.shared.sequence.extensions, { stateProvider: this.shared.sequence.stateProvider, credentialName: args.name, }) @@ -639,8 +802,9 @@ export class Wallets implements WalletsInterface { signerKind: Kinds.LoginPasskey, }, } + } - case 'mnemonic': + case 'mnemonic': { const mnemonicSigner = MnemonicHandler.toSigner(args.mnemonic) if (!mnemonicSigner) { throw new Error('invalid-mnemonic') @@ -654,6 +818,7 @@ export class Wallets implements WalletsInterface { signerKind: Kinds.LoginMnemonic, }, } + } case 'email-otp': { const handler = this.shared.handlers.get(Kinds.LoginEmailOtp) as OtpHandler @@ -673,9 +838,28 @@ export class Wallets implements WalletsInterface { } } + case 'google-id-token': + case 'apple-id-token': { + const handler = getIdTokenSignupHandler( + this.shared, + args.kind === 'google-id-token' ? Kinds.LoginGoogle : Kinds.LoginApple, + ) + const [signer, metadata] = await handler.completeAuth(args.idToken) + const loginEmail = metadata.email + this.shared.modules.logger.log('Created new id token signer:', signer.address) + + return { + signer, + extra: { + signerKind: getSignerKindForSignup(args.kind), + }, + loginEmail, + } + } + case 'google-pkce': case 'apple': { - const handler = this.shared.handlers.get('login-' + args.kind) as AuthCodeHandler + const handler = this.shared.handlers.get(getSignupHandlerKey(args.kind)) as AuthCodeHandler if (!handler) { throw new Error('handler-not-registered') } @@ -687,52 +871,178 @@ export class Wallets implements WalletsInterface { return { signer, extra: { - signerKind: 'login-' + args.kind, + signerKind: getSignerKindForSignup(args.kind), }, loginEmail, } } } + + if (args.kind.startsWith('custom-')) { + if (isIdTokenArgs(args)) { + const handler = getIdTokenSignupHandler(this.shared, args.kind) + const [signer, metadata] = await handler.completeAuth(args.idToken) + return { + signer, + extra: { + signerKind: args.kind, + }, + loginEmail: metadata.email, + } + } + + const handler = this.shared.handlers.get(args.kind) as AuthCodeHandler + if (!handler) { + throw new Error('handler-not-registered') + } + + const [signer, metadata] = await handler.completeAuth(args.commitment, args.code) + return { + signer, + extra: { + signerKind: args.kind, + }, + loginEmail: metadata.email, + } + } + + throw new Error('invalid-signup-kind') } async startSignUpWithRedirect(args: StartSignUpWithRedirectArgs) { - const handler = this.shared.handlers.get('login-' + args.kind) as AuthCodeHandler + const kind = getSignupHandlerKey(args.kind) + const handler = this.shared.handlers.get(kind) + if (!handler) { + throw new Error('handler-not-registered') + } + if (!(handler instanceof AuthCodeHandler)) { + throw new Error('handler-does-not-support-redirect') + } + return handler.commitAuth(args.target, { type: 'auth' }) + } + + async startAddLoginSignerWithRedirect(args: StartAddLoginSignerWithRedirectArgs) { + const walletEntry = await this.get(args.wallet) + if (!walletEntry) { + throw new Error('wallet-not-found') + } + if (walletEntry.status !== 'ready') { + throw new Error('wallet-not-ready') + } + + const kind = getSignupHandlerKey(args.kind) + const handler = this.shared.handlers.get(kind) if (!handler) { throw new Error('handler-not-registered') } - return handler.commitAuth(args.target, true) + if (!(handler instanceof AuthCodeHandler)) { + throw new Error('handler-does-not-support-redirect') + } + return handler.commitAuth(args.target, { type: 'add-signer', wallet: args.wallet }) } - async completeRedirect(args: CompleteRedirectArgs): Promise { + async completeRedirect(args: CompleteRedirectWithMetadataArgs): Promise + async completeRedirect(args: CompleteRedirectArgs): Promise + async completeRedirect( + args: CompleteRedirectArgs | CompleteRedirectWithMetadataArgs, + ): Promise { const commitment = await this.shared.databases.authCommitments.get(args.state) if (!commitment) { throw new Error('invalid-state') } - // commitment.isSignUp and signUp also mean 'signIn' from wallet's perspective - if (commitment.isSignUp) { - await this.signUp({ - kind: commitment.kind, - commitment, - code: args.code, - noGuard: args.noGuard, - target: commitment.target, - isRedirect: true, - use4337: args.use4337, - }) - } else { - const handler = this.shared.handlers.get('login-' + commitment.kind) as AuthCodeHandler - if (!handler) { - throw new Error('handler-not-registered') + let addedLoginSigner: CompleteRedirectMetadataResult['addedLoginSigner'] + + switch (commitment.type) { + case 'add-signer': { + const handlerKind = getSignupHandlerKey(commitment.kind) + const handler = this.shared.handlers.get(handlerKind) + if (!handler) { + throw new Error('handler-not-registered') + } + if (!(handler instanceof AuthCodeHandler)) { + throw new Error('handler-does-not-support-redirect') + } + + const walletAddress = commitment.wallet as Address.Address + const walletEntry = await this.get(walletAddress) + if (!walletEntry) { + throw new Error('wallet-not-found') + } + if (walletEntry.status !== 'ready') { + throw new Error('wallet-not-ready') + } + + const [signer, metadata] = await handler.completeAuth(commitment, args.code) + const signerKind = getSignerKindForSignup(commitment.kind) + const signerAddress = await signer.address + + await this.addLoginSignerFromPrepared(walletAddress, { + signer, + extra: { signerKind }, + }) + + const addedSigner: { + address: Address.Address + kind: string + email?: string + } = { + address: signerAddress, + kind: signerKind, + } + if (metadata?.email !== undefined) { + addedSigner.email = metadata.email + } + + addedLoginSigner = { + wallet: walletAddress, + signer: addedSigner, + } + break } - await handler.completeAuth(commitment, args.code) + case 'auth': { + await this.signUp({ + kind: commitment.kind, + commitment, + code: args.code, + noGuard: args.noGuard, + target: commitment.target, + isRedirect: true, + use4337: args.use4337, + }) + break + } + + case 'reauth': { + const handlerKind = getSignupHandlerKey(commitment.kind) + const handler = this.shared.handlers.get(handlerKind) + if (!handler) { + throw new Error('handler-not-registered') + } + if (!(handler instanceof AuthCodeHandler)) { + throw new Error('handler-does-not-support-redirect') + } + + await handler.completeAuth(commitment, args.code) + break + } } if (!commitment.target) { throw new Error('invalid-state') } + if (args.includeMetadata) { + const result: CompleteRedirectMetadataResult = { + target: commitment.target, + } + if (addedLoginSigner) { + result.addedLoginSigner = addedLoginSigner + } + return result + } + return commitment.target } @@ -814,7 +1124,7 @@ export class Wallets implements WalletsInterface { const sessionsGuardTopology = args.noGuard ? undefined : this.shared.modules.guards.topology('sessions') // Add modules - let modules: Module[] = [] + const modules: Module[] = [] if (!args.noSessionManager) { const identitySigners = [device.address] @@ -871,7 +1181,7 @@ export class Wallets implements WalletsInterface { } // Store passkey credential ID mapping if this is a passkey signup - if (args.kind === 'passkey' && loginSigner.signer instanceof Signers.Passkey.Passkey) { + if (args.kind === 'passkey' && this.isPasskeySigner(loginSigner.signer)) { try { await this.shared.databases.passkeyCredentials.saveCredential( loginSigner.signer.credentialId, @@ -1023,8 +1333,6 @@ export class Wallets implements WalletsInterface { }) return requestId - } catch (error) { - throw error } finally { this.pendingMnemonicOrPasskeyLogin = undefined } @@ -1055,7 +1363,7 @@ export class Wallets implements WalletsInterface { } if (isLoginToPasskeyArgs(args)) { - let passkeySigner: Signers.Passkey.Passkey + let passkeySigner: PasskeySigner if (args.credentialId) { // Application-controlled login: use the provided credentialId @@ -1067,7 +1375,7 @@ export class Wallets implements WalletsInterface { } // Create passkey signer from stored credential - passkeySigner = new Signers.Passkey.Passkey({ + passkeySigner = this.shared.passkeyProvider.fromCredential({ credentialId: credential.credentialId, publicKey: credential.publicKey, extensions: this.shared.sequence.extensions, @@ -1078,7 +1386,7 @@ export class Wallets implements WalletsInterface { // Default discovery behavior: use WebAuthn discovery this.shared.modules.logger.log('No credentialId provided, using discovery method') - const foundPasskeySigner = await Signers.Passkey.Passkey.find( + const foundPasskeySigner = await this.shared.passkeyProvider.find( this.shared.sequence.stateProvider, this.shared.sequence.extensions, ) @@ -1133,6 +1441,20 @@ export class Wallets implements WalletsInterface { throw new Error('invalid-login-args') } + private isPasskeySigner(signer: unknown): signer is PasskeySigner { + const guard = this.shared.passkeyProvider.isSigner + if (guard) { + return guard(signer) + } + return ( + typeof signer === 'object' && + signer !== null && + 'credentialId' in signer && + 'publicKey' in signer && + 'imageHash' in signer + ) + } + async completeLogin(requestId: string) { const request = await this.shared.modules.signatures.get(requestId) @@ -1150,6 +1472,87 @@ export class Wallets implements WalletsInterface { }) } + async addLoginSigner(args: AddLoginSignerArgs): Promise { + const walletEntry = await this.get(args.wallet) + if (!walletEntry) { + throw new Error('wallet-not-found') + } + if (walletEntry.status !== 'ready') { + throw new Error('wallet-not-ready') + } + + const signupArgs = addLoginSignerToSignupArgs(args) + const loginSigner = await this.prepareSignUp(signupArgs) + return this.addLoginSignerFromPrepared(args.wallet, loginSigner) + } + + async completeAddLoginSigner(requestId: string): Promise { + const request = await this.shared.modules.signatures.get(requestId) + if (request.action !== Actions.AddLoginSigner) { + throw new Error('invalid-request-action') + } + await this.completeConfigurationUpdate(requestId) + } + + async removeLoginSigner(args: RemoveLoginSignerArgs): Promise { + const walletEntry = await this.get(args.wallet) + if (!walletEntry) { + throw new Error('wallet-not-found') + } + if (walletEntry.status !== 'ready') { + throw new Error('wallet-not-ready') + } + + const { loginTopology, modules } = await this.getConfigurationParts(args.wallet) + + const existingSigners = Config.getSigners(loginTopology) + const allExistingAddresses = [...existingSigners.signers, ...existingSigners.sapientSigners.map((s) => s.address)] + + if (!allExistingAddresses.some((addr) => Address.isEqual(addr, args.signerAddress))) { + throw new Error('signer-not-found') + } + + const remainingMembers = [ + ...existingSigners.signers + .filter((x) => x !== Constants.ZeroAddress && !Address.isEqual(x, args.signerAddress)) + .map((x) => ({ address: x })), + ...existingSigners.sapientSigners + .filter((x) => !Address.isEqual(x.address, args.signerAddress)) + .map((x) => ({ address: x.address, imageHash: x.imageHash })), + ] + + if (remainingMembers.length < 1) { + throw new Error('cannot-remove-last-login-signer') + } + + const nextLoginTopology = buildCappedTree(remainingMembers) + + if (this.shared.modules.sessions.hasSessionModule(modules)) { + await this.shared.modules.sessions.removeIdentitySignerFromModules(modules, args.signerAddress) + } + + if (this.shared.modules.recovery.hasRecoveryModule(modules)) { + await this.shared.modules.recovery.removeRecoverySignerFromModules(modules, args.signerAddress) + } + + const requestId = await this.requestConfigurationUpdate( + args.wallet, + { loginTopology: nextLoginTopology, modules }, + Actions.RemoveLoginSigner, + 'wallet-webapp', + ) + + return requestId + } + + async completeRemoveLoginSigner(requestId: string): Promise { + const request = await this.shared.modules.signatures.get(requestId) + if (request.action !== Actions.RemoveLoginSigner) { + throw new Error('invalid-request-action') + } + await this.completeConfigurationUpdate(requestId) + } + async logout( wallet: Address.Address, options?: T, @@ -1159,12 +1562,6 @@ export class Wallets implements WalletsInterface { throw new Error('wallet-not-found') } - // Prevent starting logout if already logging out or not ready - if (walletEntry.status !== 'ready') { - console.warn(`Logout called on wallet ${wallet} with status ${walletEntry.status}. Aborting.`) - throw new Error(`Wallet is not in 'ready' state for logout (current: ${walletEntry.status})`) - } - if (options?.skipRemoveDevice) { await Promise.all([ this.shared.databases.manager.del(wallet), @@ -1173,6 +1570,12 @@ export class Wallets implements WalletsInterface { return undefined as any } + // Prevent starting logout if already logging out or not ready + if (walletEntry.status !== 'ready') { + console.warn(`Logout called on wallet ${wallet} with status ${walletEntry.status}. Aborting.`) + throw new Error(`Wallet is not in 'ready' state for logout (current: ${walletEntry.status})`) + } + const device = await this.shared.modules.devices.get(walletEntry.device) if (!device) { throw new Error('device-not-found') @@ -1200,7 +1603,7 @@ export class Wallets implements WalletsInterface { return requestId } - async completeLogout(requestId: string, options?: { skipValidateSave?: boolean }) { + async completeLogout(requestId: string, _options?: { skipValidateSave?: boolean }) { const request = await this.shared.modules.signatures.get(requestId) const walletEntry = await this.shared.databases.manager.get(request.wallet) if (!walletEntry) { @@ -1380,4 +1783,54 @@ export class Wallets implements WalletsInterface { return requestId } + + private async addLoginSignerFromPrepared( + wallet: Address.Address, + loginSigner: { + signer: (Signers.Signer | Signers.SapientSigner) & Signers.Witnessable + extra: WitnessExtraSignerKind + }, + ): Promise { + const newSignerAddress = await loginSigner.signer.address + + const { loginTopology, modules } = await this.getConfigurationParts(wallet) + + // Check for duplicate signer + const existingSigners = Config.getSigners(loginTopology) + const allExistingAddresses = [...existingSigners.signers, ...existingSigners.sapientSigners.map((s) => s.address)] + if (allExistingAddresses.some((addr) => Address.isEqual(addr, newSignerAddress))) { + throw new Error('signer-already-exists') + } + + // Build new login topology with the additional signer + const existingMembers = [ + ...existingSigners.signers.filter((x) => x !== Constants.ZeroAddress).map((x) => ({ address: x })), + ...existingSigners.sapientSigners.map((x) => ({ address: x.address, imageHash: x.imageHash })), + ] + const newMember = { + address: newSignerAddress, + imageHash: Signers.isSapientSigner(loginSigner.signer) ? await loginSigner.signer.imageHash : undefined, + } + const nextLoginTopology = buildCappedTree([...existingMembers, newMember]) + + // Add non-sapient login signer to sessions module identity signers + if (!Signers.isSapientSigner(loginSigner.signer) && this.shared.modules.sessions.hasSessionModule(modules)) { + await this.shared.modules.sessions.addIdentitySignerToModules(modules, newSignerAddress) + } + + // Add to recovery module if present + if (this.shared.modules.recovery.hasRecoveryModule(modules)) { + await this.shared.modules.recovery.addRecoverySignerToModules(modules, newSignerAddress) + } + + // Witness so the wallet becomes discoverable via the new credential + await loginSigner.signer.witness(this.shared.sequence.stateProvider, wallet, loginSigner.extra) + + return this.requestConfigurationUpdate( + wallet, + { loginTopology: nextLoginTopology, modules }, + Actions.AddLoginSigner, + 'wallet-webapp', + ) + } } diff --git a/packages/wallet/wdk/test/authcode-pkce.test.ts b/packages/wallet/wdk/test/authcode-pkce.test.ts index dc6ecfc73..d9b46bd87 100644 --- a/packages/wallet/wdk/test/authcode-pkce.test.ts +++ b/packages/wallet/wdk/test/authcode-pkce.test.ts @@ -1,10 +1,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { Address, Hex, Bytes } from 'ox' import * as Identity from '@0xsequence/identity-instrument' -import { AuthCodePkceHandler } from '../src/sequence/handlers/authcode-pkce' -import { Signatures } from '../src/sequence/signatures' -import * as Db from '../src/dbs' -import { IdentitySigner } from '../src/identity/signer' +import { AuthCodePkceHandler } from '../src/sequence/handlers/authcode-pkce.js' +import { Signatures } from '../src/sequence/signatures.js' +import * as Db from '../src/dbs/index.js' +import { IdentitySigner } from '../src/identity/signer.js' describe('AuthCodePkceHandler', () => { let handler: AuthCodePkceHandler @@ -56,6 +55,7 @@ describe('AuthCodePkceHandler', () => { handler = new AuthCodePkceHandler( 'google-pkce', 'https://accounts.google.com', + 'https://accounts.google.com/o/oauth2/v2/auth', 'test-google-client-id', mockNitroInstrument, mockSignatures, @@ -67,7 +67,7 @@ describe('AuthCodePkceHandler', () => { handler.setRedirectUri('https://example.com/auth/callback') // Mock inherited methods - vi.spyOn(handler as any, 'nitroCommitVerifier').mockImplementation(async (challenge) => { + vi.spyOn(handler as any, 'nitroCommitVerifier').mockImplementation(async () => { return { verifier: 'mock-verifier-code', loginHint: 'user@example.com', @@ -75,14 +75,12 @@ describe('AuthCodePkceHandler', () => { } }) - vi.spyOn(handler as any, 'nitroCompleteAuth').mockImplementation(async (challenge) => { + vi.spyOn(handler as any, 'nitroCompleteAuth').mockImplementation(async () => { return { signer: mockIdentitySigner, email: 'user@example.com', } }) - - vi.spyOn(handler as any, 'oauthUrl').mockReturnValue('https://accounts.google.com/oauth/authorize') }) afterEach(() => { @@ -92,9 +90,8 @@ describe('AuthCodePkceHandler', () => { describe('commitAuth', () => { it('Should create Google PKCE auth commitment and return OAuth URL', async () => { const target = 'https://example.com/success' - const isSignUp = true - const result = await handler.commitAuth(target, isSignUp) + const result = await handler.commitAuth(target, { type: 'auth' }) // Verify nitroCommitVerifier was called with correct challenge expect(handler['nitroCommitVerifier']).toHaveBeenCalledWith( @@ -112,11 +109,11 @@ describe('AuthCodePkceHandler', () => { challenge: 'mock-challenge-hash', target, metadata: {}, - isSignUp, + type: 'auth', }) // Verify OAuth URL is constructed correctly - expect(result).toMatch(/^https:\/\/accounts\.google\.com\/oauth\/authorize\?/) + expect(result).toMatch(/^https:\/\/accounts\.google\.com\/o\/oauth2\/v2\/auth\?/) expect(result).toContain('code_challenge=mock-challenge-hash') expect(result).toContain('code_challenge_method=S256') expect(result).toContain('client_id=test-google-client-id') @@ -129,10 +126,13 @@ describe('AuthCodePkceHandler', () => { it('Should use provided state instead of generating random one', async () => { const target = 'https://example.com/success' - const isSignUp = false const customState = 'custom-state-123' - const result = await handler.commitAuth(target, isSignUp, customState) + const result = await handler.commitAuth(target, { + type: 'reauth', + state: customState, + signer: '0x1234567890123456789012345678901234567890', + }) // Verify commitment was saved with custom state expect(mockCommitments.set).toHaveBeenCalledWith({ @@ -142,7 +142,8 @@ describe('AuthCodePkceHandler', () => { challenge: 'mock-challenge-hash', target, metadata: {}, - isSignUp, + type: 'reauth', + signer: '0x1234567890123456789012345678901234567890', }) // Verify URL contains custom state @@ -151,10 +152,9 @@ describe('AuthCodePkceHandler', () => { it('Should include signer in challenge when provided', async () => { const target = 'https://example.com/success' - const isSignUp = true const signer = '0x9876543210987654321098765432109876543210' - await handler.commitAuth(target, isSignUp, undefined, signer) + await handler.commitAuth(target, { type: 'reauth', state: 'test-state', signer }) // Verify nitroCommitVerifier was called with signer in challenge expect(handler['nitroCommitVerifier']).toHaveBeenCalledWith( @@ -166,9 +166,8 @@ describe('AuthCodePkceHandler', () => { it('Should generate random state when not provided', async () => { const target = 'https://example.com/success' - const isSignUp = true - const result = await handler.commitAuth(target, isSignUp) + const result = await handler.commitAuth(target, { type: 'auth' }) // Verify that a state parameter is present and looks like a hex string expect(result).toMatch(/state=0x[a-f0-9]+/) @@ -183,18 +182,22 @@ describe('AuthCodePkceHandler', () => { const target = 'https://example.com/success' // Test signup - await handler.commitAuth(target, true) + await handler.commitAuth(target, { type: 'auth' }) expect(mockCommitments.set).toHaveBeenLastCalledWith( expect.objectContaining({ - isSignUp: true, + type: 'auth', }), ) // Test login - await handler.commitAuth(target, false) + await handler.commitAuth(target, { + type: 'reauth', + state: 'test-state', + signer: '0x1234567890123456789012345678901234567890', + }) expect(mockCommitments.set).toHaveBeenLastCalledWith( expect.objectContaining({ - isSignUp: false, + type: 'reauth', }), ) }) @@ -202,13 +205,17 @@ describe('AuthCodePkceHandler', () => { it('Should handle errors from nitroCommitVerifier', async () => { vi.spyOn(handler as any, 'nitroCommitVerifier').mockRejectedValue(new Error('Nitro service unavailable')) - await expect(handler.commitAuth('https://example.com/success', true)).rejects.toThrow('Nitro service unavailable') + await expect(handler.commitAuth('https://example.com/success', { type: 'auth' })).rejects.toThrow( + 'Nitro service unavailable', + ) }) it('Should handle database errors during commitment storage', async () => { vi.mocked(mockCommitments.set).mockRejectedValue(new Error('Database write failed')) - await expect(handler.commitAuth('https://example.com/success', true)).rejects.toThrow('Database write failed') + await expect(handler.commitAuth('https://example.com/success', { type: 'auth' })).rejects.toThrow( + 'Database write failed', + ) }) }) @@ -223,7 +230,7 @@ describe('AuthCodePkceHandler', () => { challenge: 'test-challenge-hash', target: 'https://example.com/success', metadata: { scope: 'openid profile email' }, - isSignUp: true, + type: 'auth', } }) @@ -328,26 +335,23 @@ describe('AuthCodePkceHandler', () => { describe('Integration and Edge Cases', () => { it('Should have correct kind property', () => { - expect(handler.kind).toBe('login-google-pkce') + expect(handler.kind).toBe('login-google') }) it('Should handle redirect URI configuration', () => { const newRedirectUri = 'https://newdomain.com/callback' handler.setRedirectUri(newRedirectUri) - // Verify redirect URI is used in OAuth URL construction - const mockUrl = 'https://accounts.google.com/oauth/authorize' - vi.spyOn(handler as any, 'oauthUrl').mockReturnValue(mockUrl) - - return handler.commitAuth('https://example.com/success', true).then((result) => { + return handler.commitAuth('https://example.com/success', { type: 'auth' }).then((result) => { expect(result).toContain(`redirect_uri=${encodeURIComponent(newRedirectUri)}`) }) }) it('Should work with different issuer and audience configurations', () => { const customHandler = new AuthCodePkceHandler( - 'google-pkce', + 'custom-provider', 'https://custom-issuer.com', + 'https://custom-issuer.com/o/oauth2/v2/auth', 'custom-client-id', mockNitroInstrument, mockSignatures, @@ -357,7 +361,7 @@ describe('AuthCodePkceHandler', () => { expect(customHandler['issuer']).toBe('https://custom-issuer.com') expect(customHandler['audience']).toBe('custom-client-id') - expect(customHandler.signupKind).toBe('google-pkce') + expect(customHandler.signupKind).toBe('custom-provider') }) }) }) diff --git a/packages/wallet/wdk/test/authcode.test.ts b/packages/wallet/wdk/test/authcode.test.ts index 06e4372af..f673f8d42 100644 --- a/packages/wallet/wdk/test/authcode.test.ts +++ b/packages/wallet/wdk/test/authcode.test.ts @@ -1,12 +1,12 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { Address, Hex, Bytes } from 'ox' +import { Address, Hex } from 'ox' import { Network, Payload } from '@0xsequence/wallet-primitives' import { IdentityInstrument, IdentityType, KeyType, AuthCodeChallenge } from '@0xsequence/identity-instrument' -import { AuthCodeHandler } from '../src/sequence/handlers/authcode' -import { Signatures } from '../src/sequence/signatures' -import * as Db from '../src/dbs' -import { IdentitySigner } from '../src/identity/signer' -import { BaseSignatureRequest } from '../src/sequence/types/signature-request' +import { AuthCodeHandler } from '../src/sequence/handlers/authcode.js' +import { Signatures } from '../src/sequence/signatures.js' +import * as Db from '../src/dbs/index.js' +import { IdentitySigner } from '../src/identity/signer.js' +import { BaseSignatureRequest } from '../src/sequence/types/signature-request.js' // Mock the global crypto API const mockCryptoSubtle = { @@ -45,8 +45,8 @@ class MockURLSearchParams { } } -// @ts-ignore - Override global URLSearchParams for testing -global.URLSearchParams = MockURLSearchParams as any +// Override global URLSearchParams for testing +globalThis.URLSearchParams = MockURLSearchParams as any // Mock dependencies with proper vi.fn() types const mockCommitVerifier = vi.fn() @@ -113,7 +113,7 @@ describe('AuthCodeHandler', () => { kind: 'google-pkce', metadata: {}, target: '/test-target', - isSignUp: false, + type: 'reauth', signer: testWallet, } @@ -129,6 +129,7 @@ describe('AuthCodeHandler', () => { authCodeHandler = new AuthCodeHandler( 'google-pkce', 'https://accounts.google.com', + 'https://accounts.google.com/o/oauth2/v2/auth', 'test-audience', mockIdentityInstrument, mockSignatures, @@ -148,6 +149,7 @@ describe('AuthCodeHandler', () => { const handler = new AuthCodeHandler( 'google-pkce', 'https://accounts.google.com', + 'https://accounts.google.com/o/oauth2/v2/auth', 'google-client-id', mockIdentityInstrument, mockSignatures, @@ -165,6 +167,7 @@ describe('AuthCodeHandler', () => { const handler = new AuthCodeHandler( 'apple', 'https://appleid.apple.com', + 'https://appleid.apple.com/auth/authorize', 'apple-client-id', mockIdentityInstrument, mockSignatures, @@ -185,10 +188,11 @@ describe('AuthCodeHandler', () => { // === KIND GETTER === describe('kind getter', () => { - it('Should return login-google-pkce for Google PKCE handler', () => { + it('Should return login-google for Google PKCE handler', () => { const googleHandler = new AuthCodeHandler( 'google-pkce', 'https://accounts.google.com', + 'https://accounts.google.com/o/oauth2/v2/auth', 'test-audience', mockIdentityInstrument, mockSignatures, @@ -196,13 +200,14 @@ describe('AuthCodeHandler', () => { mockAuthKeys, ) - expect(googleHandler.kind).toBe('login-google-pkce') + expect(googleHandler.kind).toBe('login-google') }) it('Should return login-apple for Apple handler', () => { const appleHandler = new AuthCodeHandler( 'apple', 'https://appleid.apple.com', + 'https://appleid.apple.com/auth/authorize', 'test-audience', mockIdentityInstrument, mockSignatures, @@ -242,20 +247,17 @@ describe('AuthCodeHandler', () => { it('Should create auth commitment and return OAuth URL', async () => { const target = '/test-target' - const isSignUp = true - const signer = testWallet - const result = await authCodeHandler.commitAuth(target, isSignUp, undefined, signer) + const result = await authCodeHandler.commitAuth(target, { type: 'auth' }) // Verify commitment was saved expect(mockAuthCommitmentsSet).toHaveBeenCalledOnce() - const commitmentCall = mockAuthCommitmentsSet.mock.calls[0][0] + const commitmentCall = mockAuthCommitmentsSet.mock.calls[0]![0]! expect(commitmentCall.kind).toBe('google-pkce') - expect(commitmentCall.signer).toBe(signer) expect(commitmentCall.target).toBe(target) expect(commitmentCall.metadata).toEqual({}) - expect(commitmentCall.isSignUp).toBe(isSignUp) + expect(commitmentCall.type).toBe('auth') expect(commitmentCall.id).toBeDefined() expect(typeof commitmentCall.id).toBe('string') @@ -271,18 +273,21 @@ describe('AuthCodeHandler', () => { it('Should use provided state parameter', async () => { const customState = 'custom-state-123' - const result = await authCodeHandler.commitAuth('/target', false, customState) + const result = await authCodeHandler.commitAuth('/target', { + type: 'reauth', + state: customState, + signer: testWallet, + }) // Verify commitment uses custom state - const commitmentCall = mockAuthCommitmentsSet.mock.calls[0][0] + const commitmentCall = mockAuthCommitmentsSet.mock.calls[0]![0]! expect(commitmentCall.id).toBe(customState) expect(result).toContain(`state=${customState}`) }) it('Should generate random state when not provided', async () => { - const result = await authCodeHandler.commitAuth('/target', false) - - const commitmentCall = mockAuthCommitmentsSet.mock.calls[0][0] + await authCodeHandler.commitAuth('/target', { type: 'auth' }) + const commitmentCall = mockAuthCommitmentsSet.mock.calls[0]![0]! expect(commitmentCall.id).toBeDefined() expect(typeof commitmentCall.id).toBe('string') expect(commitmentCall.id.startsWith('0x')).toBe(true) @@ -293,6 +298,7 @@ describe('AuthCodeHandler', () => { const appleHandler = new AuthCodeHandler( 'apple', 'https://appleid.apple.com', + 'https://appleid.apple.com/auth/authorize', 'apple-client-id', mockIdentityInstrument, mockSignatures, @@ -301,18 +307,19 @@ describe('AuthCodeHandler', () => { ) appleHandler.setRedirectUri('https://example.com/callback') - const result = await appleHandler.commitAuth('/target', false) + const result = await appleHandler.commitAuth('/target', { type: 'auth' }) expect(result).toContain('https://appleid.apple.com/auth/authorize?') expect(result).toContain('client_id=apple-client-id') + const resultUrl = new URL(result) + expect(resultUrl.searchParams.has('scope')).toBe(false) }) it('Should create commitment without signer', async () => { - const result = await authCodeHandler.commitAuth('/target', true) - - const commitmentCall = mockAuthCommitmentsSet.mock.calls[0][0] + await authCodeHandler.commitAuth('/target', { type: 'auth' }) + const commitmentCall = mockAuthCommitmentsSet.mock.calls[0]![0]! expect(commitmentCall.signer).toBeUndefined() - expect(commitmentCall.isSignUp).toBe(true) + expect(commitmentCall.type).toBe('auth') }) }) @@ -321,7 +328,7 @@ describe('AuthCodeHandler', () => { describe('completeAuth()', () => { it('Should complete auth flow with code and return signer', async () => { const authCode = 'test-auth-code-123' - const mockSigner = {} as IdentitySigner + // const mockSigner = {} as IdentitySigner const mockEmail = 'test@example.com' mockCommitVerifier.mockResolvedValueOnce(undefined) @@ -342,12 +349,12 @@ describe('AuthCodeHandler', () => { // Verify commitVerifier was called expect(mockCommitVerifier).toHaveBeenCalledOnce() - const commitVerifierCall = mockCommitVerifier.mock.calls[0] + const commitVerifierCall = mockCommitVerifier.mock.calls[0]! expect(commitVerifierCall[1]).toBeInstanceOf(AuthCodeChallenge) // Verify completeAuth was called expect(mockCompleteAuth).toHaveBeenCalledOnce() - const completeAuthCall = mockCompleteAuth.mock.calls[0] + const completeAuthCall = mockCompleteAuth.mock.calls[0]! expect(completeAuthCall[1]).toBeInstanceOf(AuthCodeChallenge) // Verify results @@ -484,20 +491,21 @@ describe('AuthCodeHandler', () => { expect(window.location.href).toContain('https://accounts.google.com/o/oauth2/v2/auth') expect(mockAuthCommitmentsSet).toHaveBeenCalledOnce() - const commitmentCall = mockAuthCommitmentsSet.mock.calls[0][0] + const commitmentCall = mockAuthCommitmentsSet.mock.calls[0]![0]! expect(commitmentCall.target).toBe(window.location.pathname) - expect(commitmentCall.isSignUp).toBe(false) + expect(commitmentCall.type).toBe('reauth') expect(commitmentCall.signer).toBe(testWallet) }) }) - // === OAUTH URL METHOD === + // === OAUTH URL PROPERTY === - describe('oauthUrl()', () => { + describe('oauthUrl', () => { it('Should return Google OAuth URL for Google issuer', () => { const googleHandler = new AuthCodeHandler( 'google-pkce', 'https://accounts.google.com', + 'https://accounts.google.com/o/oauth2/v2/auth', 'test-audience', mockIdentityInstrument, mockSignatures, @@ -505,7 +513,7 @@ describe('AuthCodeHandler', () => { mockAuthKeys, ) - const url = googleHandler['oauthUrl']() + const url = googleHandler['oauthUrl'] expect(url).toBe('https://accounts.google.com/o/oauth2/v2/auth') }) @@ -513,6 +521,7 @@ describe('AuthCodeHandler', () => { const appleHandler = new AuthCodeHandler( 'apple', 'https://appleid.apple.com', + 'https://appleid.apple.com/auth/authorize', 'test-audience', mockIdentityInstrument, mockSignatures, @@ -520,23 +529,9 @@ describe('AuthCodeHandler', () => { mockAuthKeys, ) - const url = appleHandler['oauthUrl']() + const url = appleHandler['oauthUrl'] expect(url).toBe('https://appleid.apple.com/auth/authorize') }) - - it('Should throw error for unsupported issuer', () => { - const unsupportedHandler = new AuthCodeHandler( - 'google-pkce', - 'https://unsupported.provider.com', - 'test-audience', - mockIdentityInstrument, - mockSignatures, - mockAuthCommitments, - mockAuthKeys, - ) - - expect(() => unsupportedHandler['oauthUrl']()).toThrow('unsupported-issuer') - }) }) // === INHERITED METHODS FROM IDENTITYHANDLER === @@ -660,7 +655,7 @@ describe('AuthCodeHandler', () => { it('Should handle auth commitments database errors', async () => { mockAuthCommitmentsSet.mockRejectedValueOnce(new Error('Database error')) - await expect(authCodeHandler.commitAuth('/target', false)).rejects.toThrow('Database error') + await expect(authCodeHandler.commitAuth('/target', { type: 'auth' })).rejects.toThrow('Database error') }) it('Should handle auth keys database errors', async () => { @@ -677,7 +672,11 @@ describe('AuthCodeHandler', () => { authCodeHandler.setRedirectUri('https://example.com/callback') // Step 1: Commit auth - const commitUrl = await authCodeHandler.commitAuth('/test-target', false, 'test-state', testWallet) + const commitUrl = await authCodeHandler.commitAuth('/test-target', { + type: 'reauth', + state: 'test-state', + signer: testWallet, + }) expect(commitUrl).toContain('state=test-state') expect(mockAuthCommitmentsSet).toHaveBeenCalledWith( @@ -685,7 +684,7 @@ describe('AuthCodeHandler', () => { id: 'test-state', kind: 'google-pkce', target: '/test-target', - isSignUp: false, + type: 'reauth', signer: testWallet, }), ) @@ -711,53 +710,21 @@ describe('AuthCodeHandler', () => { expect(metadata.email).toBe('test@example.com') }) - it('Should handle different OAuth providers correctly', async () => { - const providers = [ - { - signupKind: 'google-pkce' as const, - issuer: 'https://accounts.google.com', - expectedUrl: 'https://accounts.google.com/o/oauth2/v2/auth', - }, - { - signupKind: 'apple' as const, - issuer: 'https://appleid.apple.com', - expectedUrl: 'https://appleid.apple.com/auth/authorize', - }, - ] - - for (const provider of providers) { - const handler = new AuthCodeHandler( - provider.signupKind, - provider.issuer, - 'test-audience', - mockIdentityInstrument, - mockSignatures, - mockAuthCommitments, - mockAuthKeys, - ) - handler.setRedirectUri('https://example.com/callback') - - const url = await handler.commitAuth('/target', false) - expect(url).toContain(provider.expectedUrl) - expect(handler.kind).toBe(`login-${provider.signupKind}`) - } - }) - it('Should handle signup vs login flows correctly', async () => { authCodeHandler.setRedirectUri('https://example.com/callback') // Test signup flow - await authCodeHandler.commitAuth('/signup-target', true, 'signup-state') + await authCodeHandler.commitAuth('/signup-target', { type: 'auth', state: 'signup-state' }) - const signupCall = mockAuthCommitmentsSet.mock.calls[0][0] - expect(signupCall.isSignUp).toBe(true) + const signupCall = mockAuthCommitmentsSet.mock.calls[0]![0]! + expect(signupCall.type).toBe('auth') expect(signupCall.target).toBe('/signup-target') // Test login flow - await authCodeHandler.commitAuth('/login-target', false, 'login-state') + await authCodeHandler.commitAuth('/login-target', { type: 'reauth', state: 'login-state', signer: testWallet }) - const loginCall = mockAuthCommitmentsSet.mock.calls[1][0] - expect(loginCall.isSignUp).toBe(false) + const loginCall = mockAuthCommitmentsSet.mock.calls[1]![0]! + expect(loginCall.type).toBe('reauth') expect(loginCall.target).toBe('/login-target') }) }) diff --git a/packages/wallet/wdk/test/constants.ts b/packages/wallet/wdk/test/constants.ts index 4ace92092..855884c8b 100644 --- a/packages/wallet/wdk/test/constants.ts +++ b/packages/wallet/wdk/test/constants.ts @@ -1,11 +1,13 @@ import { config as dotenvConfig } from 'dotenv' import { Abi, Address, Provider, RpcTransport } from 'ox' -import { Manager, ManagerOptions, ManagerOptionsDefaults } from '../src/sequence' -import { mockEthereum } from './setup' -import { Signers as CoreSigners, State, Relayer } from '@0xsequence/wallet-core' -import * as Db from '../src/dbs' +import { Manager, ManagerOptions, ManagerOptionsDefaults } from '../src/sequence/index.js' +import { mockEthereum } from './setup.js' +import { Signers as CoreSigners, State, Bundler } from '@0xsequence/wallet-core' +import { Relayer } from '@0xsequence/relayer' +import * as Db from '../src/dbs/index.js' import { Network } from '@0xsequence/wallet-primitives' +// eslint-disable-next-line turbo/no-undeclared-env-vars const envFile = process.env.CI ? '.env.test' : '.env.test.local' dotenvConfig({ path: envFile }) @@ -13,8 +15,7 @@ export const EMITTER_ADDRESS: Address.Address = '0xb7bE532959236170064cf099e1a33 export const EMITTER_ABI = Abi.from(['function explicitEmit()', 'function implicitEmit()']) // Environment variables -export const { RPC_URL, PRIVATE_KEY } = process.env -export const CAN_RUN_LIVE = !!RPC_URL && !!PRIVATE_KEY +// eslint-disable-next-line turbo/no-undeclared-env-vars export const LOCAL_RPC_URL = process.env.LOCAL_RPC_URL || 'http://localhost:8545' let testIdCounter = 0 @@ -82,17 +83,17 @@ export function newRemoteManager( ? `_${remoteManagerOptions.tag}_testrun_${testIdCounter}` : `_testrun_${testIdCounter}` - let relayers: Relayer.Relayer[] = [] - let bundlers: Relayer.Bundler[] = [] + const relayers: Relayer.Relayer[] = [] + const bundlers: Bundler.Bundler[] = [] if (remoteManagerOptions.network.relayerPk) { const provider = Provider.from(RpcTransport.fromHttp(remoteManagerOptions.network.rpcUrl)) - relayers.push(new Relayer.Standard.PkRelayer(remoteManagerOptions.network.relayerPk as `0x${string}`, provider)) + relayers.push(new Relayer.PkRelayer(remoteManagerOptions.network.relayerPk as `0x${string}`, provider)) } if (remoteManagerOptions.network.bundlerUrl) { bundlers.push( - new Relayer.Bundlers.PimlicoBundler( + new Bundler.Bundlers.PimlicoBundler( remoteManagerOptions.network.bundlerUrl, Provider.from(RpcTransport.fromHttp(remoteManagerOptions.network.rpcUrl)), ), diff --git a/packages/wallet/wdk/test/guard.test.ts b/packages/wallet/wdk/test/guard.test.ts index 136a072e4..ffb117c3f 100644 --- a/packages/wallet/wdk/test/guard.test.ts +++ b/packages/wallet/wdk/test/guard.test.ts @@ -1,11 +1,11 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { Manager } from '../src/sequence' -import { GuardHandler } from '../src/sequence/handlers/guard' +import { Manager } from '../src/sequence/index.js' +import { GuardHandler } from '../src/sequence/handlers/guard.js' import { Address, Bytes, Hex, TypedData } from 'ox' -import { Network, Payload } from '@0xsequence/wallet-primitives' -import { Kinds } from '../src/sequence/types/signer' -import { newManager } from './constants' -import { GuardRole, Guards } from '../src/sequence/guards' +import { Config, Constants, Network, Payload } from '@0xsequence/wallet-primitives' +import { Kinds } from '../src/sequence/types/signer.js' +import { newManager } from './constants.js' +import { Guards } from '../src/sequence/guards.js' // Mock fetch globally for guard API calls const mockFetch = vi.fn() @@ -16,8 +16,8 @@ describe('GuardHandler', () => { let guards: Guards let testWallet: Address.Address let testPayload: Payload.Payload - let testMessageDigest: Bytes.Bytes - let testMessage: Hex.Hex + let _testMessageDigest: Bytes.Bytes + let _testMessage: Hex.Hex beforeEach(async () => { vi.clearAllMocks() @@ -28,8 +28,8 @@ describe('GuardHandler', () => { testWallet = '0x1234567890123456789012345678901234567890' as Address.Address testPayload = Payload.fromMessage(Hex.fromString('Test message')) - testMessage = TypedData.encode(Payload.toTyped(testWallet, Network.ChainId.ARBITRUM, testPayload)) - testMessageDigest = Payload.hash(testWallet, Network.ChainId.ARBITRUM, testPayload) + _testMessage = TypedData.encode(Payload.toTyped(testWallet, Network.ChainId.ARBITRUM, testPayload)) + _testMessageDigest = Payload.hash(testWallet, Network.ChainId.ARBITRUM, testPayload) }) afterEach(async () => { @@ -163,7 +163,7 @@ describe('GuardHandler', () => { expect(result).toBe(true) expect(mockAddSignature).toHaveBeenCalledOnce() - const [requestId, signatureData] = mockAddSignature.mock.calls[0] + const [requestId, signatureData] = mockAddSignature.mock.calls[0]! expect(requestId).toBe('test-request-id') expect(signatureData.address).toBe(guards.getByRole('wallet').address) expect(signatureData.signature).toBeDefined() @@ -223,7 +223,7 @@ describe('GuardHandler', () => { const mockAddSignature = vi.fn() signatures.addSignature = mockAddSignature - const mockCallback = vi.fn().mockImplementation(async (codeType, respond) => { + const mockCallback = vi.fn().mockImplementation(async (request, codeType, respond) => { expect(codeType).toBe('TOTP') await respond('123456') }) @@ -247,7 +247,7 @@ describe('GuardHandler', () => { expect(mockCallback).toHaveBeenCalledOnce() expect(mockAddSignature).toHaveBeenCalledOnce() - const [requestId, signatureData] = mockAddSignature.mock.calls[0] + const [requestId, signatureData] = mockAddSignature.mock.calls[0]! expect(requestId).toBe('test-request-id') expect(signatureData.address).toBe(guards.getByRole('wallet').address) expect(signatureData.signature).toBeDefined() @@ -300,7 +300,7 @@ describe('GuardHandler', () => { signatures: [], }) - expect(mockFetch.mock.calls[0][0]).toContain(customGuardUrl) + expect(mockFetch.mock.calls[0]![0]).toContain(customGuardUrl) await customManager.stop() }) @@ -315,4 +315,60 @@ describe('GuardHandler', () => { expect(sharedConfig.guardAddresses).toBeDefined() }) }) + + describe('Guard Topology', () => { + it('should replace the placeholder guard address', () => { + const guardAddress = (manager as any).shared.sequence.guardAddresses.wallet + const defaultTopology = (manager as any).shared.sequence.defaultGuardTopology + + const topology = guards.topology('wallet') + + expect(topology).toBeDefined() + expect(Config.findSignerLeaf(topology!, guardAddress)).toBeDefined() + expect(Config.findSignerLeaf(topology!, Constants.PlaceholderAddress as Address.Address)).toBeUndefined() + expect(Config.findSignerLeaf(defaultTopology, guardAddress)).toBeUndefined() + expect(Config.hashConfiguration(topology!)).not.toEqual(Config.hashConfiguration(defaultTopology)) + }) + + it('should throw when the placeholder is missing in the default topology', async () => { + const customManager = newManager( + { + defaultGuardTopology: { + type: 'signer', + address: '0x0000000000000000000000000000000000000001', + weight: 1n, + }, + }, + undefined, + `guard_topology_${Date.now()}`, + ) + + const customGuards = (customManager as any).shared.modules.guards as Guards + + try { + expect(() => customGuards.topology('wallet')).toThrow('Guard address replacement failed for role wallet') + } finally { + await customManager.stop() + } + }) + + it('should return undefined when no guard address is set for a role', async () => { + const defaultWalletGuard = (manager as any).shared.sequence.guardAddresses.wallet + const customManager = newManager( + { + guardAddresses: { + wallet: defaultWalletGuard, + } as any, + }, + undefined, + `guard_missing_${Date.now()}`, + ) + + const customGuards = (customManager as any).shared.modules.guards as Guards + + expect(customGuards.topology('sessions')).toBeUndefined() + + await customManager.stop() + }) + }) }) diff --git a/packages/wallet/wdk/test/identity-auth-dbs.test.ts b/packages/wallet/wdk/test/identity-auth-dbs.test.ts index 43f6e0b5b..bba408ef3 100644 --- a/packages/wallet/wdk/test/identity-auth-dbs.test.ts +++ b/packages/wallet/wdk/test/identity-auth-dbs.test.ts @@ -1,9 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { Manager } from '../src/sequence' -import { Address, Hex, Bytes } from 'ox' -import { IdentityInstrument } from '@0xsequence/identity-instrument' -import * as Db from '../src/dbs' -import { LOCAL_RPC_URL } from './constants' +import { Manager } from '../src/sequence/index.js' +import * as Db from '../src/dbs/index.js' +import { LOCAL_RPC_URL } from './constants.js' import { State } from '@0xsequence/wallet-core' import { Network } from '@0xsequence/wallet-primitives' @@ -36,7 +34,7 @@ describe('Identity Authentication Databases', () => { verifier: 'test-verifier-code', challenge: 'test-challenge-hash', target: 'test-target-url', - isSignUp: true, + type: 'reauth', signer: '0x1234567890123456789012345678901234567890', } @@ -68,7 +66,7 @@ describe('Identity Authentication Databases', () => { response_mode: 'form_post', }, target: 'apple-redirect-url', - isSignUp: false, + type: 'auth', } await authCommitmentsDb.set(appleCommitment) @@ -76,7 +74,7 @@ describe('Identity Authentication Databases', () => { expect(retrieved).toBeDefined() expect(retrieved!.kind).toBe('apple') - expect(retrieved!.isSignUp).toBe(false) + expect(retrieved!.type).toBe('auth') expect(retrieved!.metadata.response_type).toBe('code id_token') }) @@ -87,21 +85,22 @@ describe('Identity Authentication Databases', () => { kind: 'google-pkce', metadata: {}, target: 'target-1', - isSignUp: true, + type: 'auth', }, { id: 'commit-2', kind: 'apple', metadata: {}, target: 'target-2', - isSignUp: false, + type: 'reauth', + signer: '0x1234567890123456789012345678901234567890', }, { id: 'commit-3', kind: 'google-pkce', metadata: {}, target: 'target-3', - isSignUp: true, + type: 'auth', }, ] @@ -131,7 +130,7 @@ describe('Identity Authentication Databases', () => { kind: 'google-pkce', metadata: {}, target: 'init-target', - isSignUp: true, + type: 'auth', } await freshDb.set(testCommitment) @@ -353,9 +352,84 @@ describe('Identity Authentication Databases', () => { }, }) - // Verify that Google handler is registered and uses our databases + // Verify that Google is registered under the canonical signer kind while + // still using the PKCE flow by default. const handlers = (manager as any).shared.handlers - expect(handlers.has('login-google-pkce')).toBe(true) + expect(handlers.has('login-google')).toBe(true) + expect(handlers.has('login-google-pkce')).toBe(false) + }) + + it('Should register the Google ID token handler when configured explicitly', async () => { + manager = new Manager({ + stateProvider: new State.Local.Provider(new State.Local.IndexedDbStore(`manager-google-idtoken-${Date.now()}`)), + networks: [ + { + name: 'Test Network', + type: Network.NetworkType.MAINNET, + rpcUrl: LOCAL_RPC_URL, + chainId: Network.ChainId.ARBITRUM, + blockExplorer: { url: 'https://arbiscan.io' }, + nativeCurrency: { + name: 'Ether', + symbol: 'ETH', + decimals: 18, + }, + }, + ], + relayers: [], + authCommitmentsDb, + authKeysDb, + identity: { + url: 'https://dev-identity.sequence-dev.app', + fetch: window.fetch, + google: { + enabled: true, + clientId: 'test-google-client-id', + authMethod: 'id-token', + }, + }, + }) + + const handlers = (manager as any).shared.handlers + expect(handlers.has('login-google-id-token')).toBe(false) + expect(handlers.has('login-google')).toBe(true) + expect(handlers.has('login-google-pkce')).toBe(false) + }) + + it('Should register the Apple ID token handler when configured explicitly', async () => { + manager = new Manager({ + stateProvider: new State.Local.Provider(new State.Local.IndexedDbStore(`manager-apple-idtoken-${Date.now()}`)), + networks: [ + { + name: 'Test Network', + type: Network.NetworkType.MAINNET, + rpcUrl: LOCAL_RPC_URL, + chainId: Network.ChainId.ARBITRUM, + blockExplorer: { url: 'https://arbiscan.io' }, + nativeCurrency: { + name: 'Ether', + symbol: 'ETH', + decimals: 18, + }, + }, + ], + relayers: [], + authCommitmentsDb, + authKeysDb, + identity: { + url: 'https://dev-identity.sequence-dev.app', + fetch: window.fetch, + apple: { + enabled: true, + clientId: 'test-apple-client-id', + authMethod: 'id-token', + }, + }, + }) + + const handlers = (manager as any).shared.handlers + expect(handlers.has('login-apple-id-token')).toBe(false) + expect(handlers.has('login-apple')).toBe(true) }) it('Should use auth databases when email authentication is enabled', async () => { @@ -426,5 +500,50 @@ describe('Identity Authentication Databases', () => { const handlers = (manager as any).shared.handlers expect(handlers.has('login-apple')).toBe(true) }) + + it('Should register custom ID token providers without enabling redirect flow for them', async () => { + manager = new Manager({ + stateProvider: new State.Local.Provider(new State.Local.IndexedDbStore(`manager-custom-idtoken-${Date.now()}`)), + networks: [ + { + name: 'Test Network', + type: Network.NetworkType.MAINNET, + rpcUrl: LOCAL_RPC_URL, + chainId: Network.ChainId.ARBITRUM, + blockExplorer: { url: 'https://arbiscan.io' }, + nativeCurrency: { + name: 'Ether', + symbol: 'ETH', + decimals: 18, + }, + }, + ], + relayers: [], + authCommitmentsDb, + authKeysDb, + identity: { + url: 'https://dev-identity.sequence-dev.app', + fetch: window.fetch, + customProviders: [ + { + kind: 'custom-google-native', + authMethod: 'id-token', + issuer: 'https://accounts.google.com', + clientId: 'test-google-client-id', + }, + ], + }, + }) + + const handlers = (manager as any).shared.handlers + expect(handlers.has('custom-google-native')).toBe(true) + await expect( + manager.wallets.startSignUpWithRedirect({ + kind: 'custom-google-native', + target: '/home', + metadata: {}, + }), + ).rejects.toThrow('handler-does-not-support-redirect') + }) }) }) diff --git a/packages/wallet/wdk/test/identity-signer.test.ts b/packages/wallet/wdk/test/identity-signer.test.ts index 5eb9c42b4..526177aa8 100644 --- a/packages/wallet/wdk/test/identity-signer.test.ts +++ b/packages/wallet/wdk/test/identity-signer.test.ts @@ -1,10 +1,10 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { Address, Hex, Bytes } from 'ox' +import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest' +import { Address, Hex } from 'ox' import { Network, Payload } from '@0xsequence/wallet-primitives' import { IdentityInstrument, KeyType } from '@0xsequence/identity-instrument' import { State } from '@0xsequence/wallet-core' -import { IdentitySigner, toIdentityAuthKey } from '../src/identity/signer' -import { AuthKey } from '../src/dbs/auth-keys' +import { IdentitySigner, toIdentityAuthKey } from '../src/identity/signer.js' +import { AuthKey } from '../src/dbs/auth-keys.js' // Mock the global crypto API const mockCryptoSubtle = { @@ -13,7 +13,7 @@ const mockCryptoSubtle = { exportKey: vi.fn(), } -Object.defineProperty(global, 'window', { +Object.defineProperty(globalThis, 'window', { value: { crypto: { subtle: mockCryptoSubtle, @@ -31,7 +31,7 @@ describe('Identity Signer', () => { let testAuthKey: AuthKey let testWallet: Address.Address let mockStateWriter: State.Writer - let mockSignFn: ReturnType + let mockSignFn: Mock beforeEach(() => { vi.clearAllMocks() @@ -179,7 +179,7 @@ describe('Identity Signer', () => { // Verify that identityInstrument.sign was called with correct parameters expect(mockSignFn).toHaveBeenCalledOnce() - const [authKeyArg, digestArg] = mockSignFn.mock.calls[0] + const [authKeyArg, digestArg] = mockSignFn.mock.calls[0]! expect(authKeyArg.address).toBe(testAuthKey.address) expect(authKeyArg.signer).toBe(testAuthKey.identitySigner) expect(digestArg).toBeDefined() @@ -196,7 +196,7 @@ describe('Identity Signer', () => { expect(mockSignFn).toHaveBeenCalledOnce() // The digest should be different for different chainIds - const [, digestArg] = mockSignFn.mock.calls[0] + const [, digestArg] = mockSignFn.mock.calls[0]! expect(digestArg).toBeDefined() }) @@ -255,7 +255,7 @@ describe('Identity Signer', () => { } expect(mockSignFn).toHaveBeenCalledOnce() - const [authKeyArg, digestArg] = mockSignFn.mock.calls[0] + const [authKeyArg, digestArg] = mockSignFn.mock.calls[0]! expect(authKeyArg.address).toBe(testAuthKey.address) expect(digestArg).toBe(digest) }) @@ -295,7 +295,7 @@ describe('Identity Signer', () => { it('Should handle malformed signature from identity instrument', async () => { const digest = Hex.toBytes('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef') - mockSignFn.mockResolvedValueOnce('invalid-signature') + mockSignFn.mockResolvedValueOnce('invalid-signature' as any) await expect(identitySigner.signDigest(digest)).rejects.toThrow() // Should throw when Signature.fromHex fails }) @@ -317,7 +317,7 @@ describe('Identity Signer', () => { // Verify witness was saved expect(mockSaveWitnesses).toHaveBeenCalledOnce() - const [wallet, chainId, payload, witness] = mockSaveWitnesses.mock.calls[0] + const [wallet, chainId, payload, witness] = mockSaveWitnesses.mock.calls[0]! expect(wallet).toBe(testWallet) expect(chainId).toBe(0) // Witness signatures use chainId 0 @@ -338,7 +338,7 @@ describe('Identity Signer', () => { await identitySigner.witness(mockStateWriter, testWallet) // Extract the payload that was signed - const [, , payload] = mockSaveWitnesses.mock.calls[0] + const [, , payload] = mockSaveWitnesses.mock.calls[0]! // Parse the message content to verify consent structure const messageHex = payload.message @@ -367,7 +367,7 @@ describe('Identity Signer', () => { await identitySigner.witness(mockStateWriter, testWallet, extraData) // Extract and verify extra data was included - const [, , payload] = mockSaveWitnesses.mock.calls[0] + const [, , payload] = mockSaveWitnesses.mock.calls[0]! const messageString = Hex.toString(payload.message) const consentData = JSON.parse(messageString) @@ -435,7 +435,7 @@ describe('Identity Signer', () => { expect(mockSaveWitnesses).toHaveBeenCalledOnce() // Verify witness payload includes extra context - const [, , witnessPayload] = mockSaveWitnesses.mock.calls[0] + const [, , witnessPayload] = mockSaveWitnesses.mock.calls[0]! const witnessMessage = JSON.parse(Hex.toString(witnessPayload.message)) expect(witnessMessage.signatureId).toBe('sig-123') expect(witnessMessage.purpose).toBe('authentication') @@ -469,8 +469,8 @@ describe('Identity Signer', () => { expect(mockSignFn).toHaveBeenCalledTimes(2) // Verify different payloads produce different hashes - const [, messageDigest] = mockSignFn.mock.calls[0] - const [, transactionDigest] = mockSignFn.mock.calls[1] + const [, messageDigest] = mockSignFn.mock.calls[0]! + const [, transactionDigest] = mockSignFn.mock.calls[1]! expect(messageDigest).not.toEqual(transactionDigest) }) }) @@ -500,7 +500,7 @@ describe('Identity Signer', () => { it('Should handle malformed hex signatures', async () => { const digest = Hex.toBytes('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef') - mockSignFn.mockResolvedValueOnce('not-a-hex-string') + mockSignFn.mockResolvedValueOnce('not-a-hex-string' as any) await expect(identitySigner.signDigest(digest)).rejects.toThrow() }) diff --git a/packages/wallet/wdk/test/idtoken.test.ts b/packages/wallet/wdk/test/idtoken.test.ts new file mode 100644 index 000000000..7badcb89a --- /dev/null +++ b/packages/wallet/wdk/test/idtoken.test.ts @@ -0,0 +1,343 @@ +import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest' +import { Address, Hex } from 'ox' +import { Network, Payload } from '@0xsequence/wallet-primitives' +import { IdentityInstrument, IdentityType } from '@0xsequence/identity-instrument' +import { IdTokenHandler, PromptIdTokenHandler } from '../src/sequence/handlers/idtoken.js' +import { Signatures } from '../src/sequence/signatures.js' +import * as Db from '../src/dbs/index.js' +import { IdentitySigner } from '../src/identity/signer.js' +import { BaseSignatureRequest } from '../src/sequence/types/signature-request.js' +import { Kinds } from '../src/sequence/types/signer.js' + +describe('IdTokenHandler', () => { + let idTokenHandler: IdTokenHandler + let mockNitroInstrument: IdentityInstrument + let mockSignatures: Signatures + let mockAuthKeys: Db.AuthKeys + let mockIdentitySigner: IdentitySigner + let testWallet: Address.Address + let testRequest: BaseSignatureRequest + let mockPromptIdToken: Mock + + beforeEach(() => { + vi.clearAllMocks() + + testWallet = '0x1234567890123456789012345678901234567890' as Address.Address + + mockNitroInstrument = { + commitVerifier: vi.fn(), + completeAuth: vi.fn(), + } as unknown as IdentityInstrument + + mockSignatures = { + addSignature: vi.fn(), + } as unknown as Signatures + + mockAuthKeys = { + set: vi.fn(), + get: vi.fn(), + del: vi.fn(), + delBySigner: vi.fn(), + getBySigner: vi.fn(), + addListener: vi.fn(), + } as unknown as Db.AuthKeys + + mockIdentitySigner = { + address: testWallet, + sign: vi.fn(), + } as unknown as IdentitySigner + + testRequest = { + id: 'test-request-id', + envelope: { + wallet: testWallet, + chainId: Network.ChainId.ARBITRUM, + payload: Payload.fromMessage(Hex.fromString('Test message')), + }, + } as BaseSignatureRequest + + mockPromptIdToken = vi.fn() + + idTokenHandler = new IdTokenHandler( + 'google-id-token', + 'https://accounts.google.com', + 'test-google-client-id', + mockNitroInstrument, + mockSignatures, + mockAuthKeys, + ) + + vi.spyOn(idTokenHandler as any, 'nitroCommitVerifier').mockResolvedValue({ + verifier: 'unused-verifier', + loginHint: '', + challenge: '', + }) + + vi.spyOn(idTokenHandler as any, 'nitroCompleteAuth').mockResolvedValue({ + signer: mockIdentitySigner, + email: 'user@example.com', + }) + }) + + afterEach(() => { + vi.resetAllMocks() + }) + + describe('Constructor', () => { + it('Should create IdTokenHandler with correct properties', () => { + const handler = new IdTokenHandler( + 'google-id-token', + 'https://accounts.google.com', + 'test-google-client-id', + mockNitroInstrument, + mockSignatures, + mockAuthKeys, + ) + + expect(handler.signupKind).toBe('google-id-token') + expect(handler.issuer).toBe('https://accounts.google.com') + expect(handler.audience).toBe('test-google-client-id') + expect(handler.identityType).toBe(IdentityType.OIDC) + expect(handler.kind).toBe(Kinds.LoginGoogle) + }) + + it('Should normalize apple-id-token handlers to login-apple', () => { + const handler = new IdTokenHandler( + 'apple-id-token', + 'https://appleid.apple.com', + 'test-apple-client-id', + mockNitroInstrument, + mockSignatures, + mockAuthKeys, + ) + + expect(handler.signupKind).toBe('apple-id-token') + expect(handler.issuer).toBe('https://appleid.apple.com') + expect(handler.audience).toBe('test-apple-client-id') + expect(handler.kind).toBe(Kinds.LoginApple) + }) + + it('Should initialize without a registered UI callback', () => { + expect(idTokenHandler['onPromptIdToken']).toBeUndefined() + }) + }) + + describe('UI Registration', () => { + it('Should register ID token UI callback', () => { + const unregister = idTokenHandler.registerUI(mockPromptIdToken) + + expect(idTokenHandler['onPromptIdToken']).toBe(mockPromptIdToken) + expect(typeof unregister).toBe('function') + }) + + it('Should unregister UI callback when returned function is called', () => { + const unregister = idTokenHandler.registerUI(mockPromptIdToken) + expect(idTokenHandler['onPromptIdToken']).toBe(mockPromptIdToken) + + unregister() + + expect(idTokenHandler['onPromptIdToken']).toBeUndefined() + }) + + it('Should unregister UI callback directly', () => { + idTokenHandler.registerUI(mockPromptIdToken) + expect(idTokenHandler['onPromptIdToken']).toBe(mockPromptIdToken) + + idTokenHandler.unregisterUI() + + expect(idTokenHandler['onPromptIdToken']).toBeUndefined() + }) + + it('Should allow multiple registrations by overwriting the previous callback', () => { + const secondCallback = vi.fn() + + idTokenHandler.registerUI(mockPromptIdToken) + expect(idTokenHandler['onPromptIdToken']).toBe(mockPromptIdToken) + + idTokenHandler.registerUI(secondCallback) + + expect(idTokenHandler['onPromptIdToken']).toBe(secondCallback) + }) + }) + + describe('completeAuth()', () => { + it('Should complete auth using an OIDC ID token challenge', async () => { + const idToken = 'eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.' + + const [signer, metadata] = await idTokenHandler.completeAuth(idToken) + + expect(idTokenHandler['nitroCommitVerifier']).toHaveBeenCalledWith( + expect.objectContaining({ + issuer: 'https://accounts.google.com', + audience: 'test-google-client-id', + idToken, + }), + ) + expect(idTokenHandler['nitroCompleteAuth']).toHaveBeenCalledWith( + expect.objectContaining({ + issuer: 'https://accounts.google.com', + audience: 'test-google-client-id', + idToken, + }), + ) + expect(signer).toBe(mockIdentitySigner) + expect(metadata).toEqual({ email: 'user@example.com' }) + }) + }) + + describe('getSigner()', () => { + it('Should throw when UI is not registered', async () => { + await expect(idTokenHandler.getSigner()).rejects.toThrow('id-token-handler-ui-not-registered') + }) + + it('Should acquire a signer by prompting for a fresh ID token', async () => { + const idToken = 'header.payload.signature' + const completeAuthSpy = vi + .spyOn(idTokenHandler, 'completeAuth') + .mockResolvedValue([mockIdentitySigner, { email: 'user@example.com' }]) + + mockPromptIdToken.mockImplementation(async (kind, respond) => { + expect(kind).toBe('google-id-token') + await respond(idToken) + }) + + idTokenHandler.registerUI(mockPromptIdToken) + + const result = await idTokenHandler.getSigner() + + expect(result).toEqual({ + signer: mockIdentitySigner, + email: 'user@example.com', + }) + expect(completeAuthSpy).toHaveBeenCalledWith(idToken) + expect(mockPromptIdToken).toHaveBeenCalledWith('google-id-token', expect.any(Function)) + }) + + it('Should surface authentication failures from completeAuth', async () => { + const error = new Error('Authentication failed') + vi.spyOn(idTokenHandler, 'completeAuth').mockRejectedValue(error) + + mockPromptIdToken.mockImplementation(async (_kind, respond) => { + await respond('header.payload.signature') + }) + + idTokenHandler.registerUI(mockPromptIdToken) + + await expect(idTokenHandler.getSigner()).rejects.toThrow('Authentication failed') + }) + + it('Should surface UI callback errors', async () => { + mockPromptIdToken.mockRejectedValue(new Error('UI callback failed')) + idTokenHandler.registerUI(mockPromptIdToken) + + await expect(idTokenHandler.getSigner()).rejects.toThrow('UI callback failed') + }) + }) + + describe('status()', () => { + it('Should return ready status when an auth key signer is available', async () => { + vi.spyOn(idTokenHandler as any, 'getAuthKeySigner').mockResolvedValue(mockIdentitySigner) + + const status = await idTokenHandler.status(testWallet, undefined, testRequest) + + expect(status.status).toBe('ready') + expect(status.handler).toBe(idTokenHandler) + }) + + it('Should sign the request when ready handle is invoked', async () => { + vi.spyOn(idTokenHandler as any, 'getAuthKeySigner').mockResolvedValue(mockIdentitySigner) + const signSpy = vi.spyOn(idTokenHandler as any, 'sign').mockResolvedValue(undefined) + + const status = await idTokenHandler.status(testWallet, undefined, testRequest) + const handled = await (status as any).handle() + + expect(handled).toBe(true) + expect(signSpy).toHaveBeenCalledWith(mockIdentitySigner, testRequest) + }) + + it('Should return unavailable when no auth key signer exists and UI is not registered', async () => { + vi.spyOn(idTokenHandler as any, 'getAuthKeySigner').mockResolvedValue(undefined) + + const status = await idTokenHandler.status(testWallet, undefined, testRequest) + + expect(status).toMatchObject({ + address: testWallet, + handler: idTokenHandler, + status: 'unavailable', + reason: 'ui-not-registered', + }) + }) + + it('Should return actionable when no auth key signer exists and UI is registered', async () => { + vi.spyOn(idTokenHandler as any, 'getAuthKeySigner').mockResolvedValue(undefined) + idTokenHandler.registerUI(mockPromptIdToken) + + const status = await idTokenHandler.status(testWallet, undefined, testRequest) + + expect(status.status).toBe('actionable') + expect(status.address).toBe(testWallet) + expect(status.handler).toBe(idTokenHandler) + expect((status as any).message).toBe('request-id-token') + expect(typeof (status as any).handle).toBe('function') + }) + + it('Should reacquire the signer when actionable handle is invoked', async () => { + vi.spyOn(idTokenHandler as any, 'getAuthKeySigner').mockResolvedValue(undefined) + const completeAuthSpy = vi + .spyOn(idTokenHandler, 'completeAuth') + .mockResolvedValue([mockIdentitySigner, { email: 'user@example.com' }]) + + mockPromptIdToken.mockImplementation(async (_kind, respond) => { + await respond('header.payload.signature') + }) + + idTokenHandler.registerUI(mockPromptIdToken) + + const status = await idTokenHandler.status(testWallet, undefined, testRequest) + const handled = await (status as any).handle() + + expect(handled).toBe(true) + expect(completeAuthSpy).toHaveBeenCalledWith('header.payload.signature') + }) + + it('Should return false when actionable handle authentication fails', async () => { + vi.spyOn(idTokenHandler as any, 'getAuthKeySigner').mockResolvedValue(undefined) + vi.spyOn(idTokenHandler, 'completeAuth').mockRejectedValue(new Error('Authentication failed')) + + mockPromptIdToken.mockImplementation(async (_kind, respond) => { + await respond('header.payload.signature') + }) + + idTokenHandler.registerUI(mockPromptIdToken) + + const status = await idTokenHandler.status(testWallet, undefined, testRequest) + const handled = await (status as any).handle() + + expect(handled).toBe(false) + }) + + it('Should return false when actionable handle authenticates the wrong signer', async () => { + vi.spyOn(idTokenHandler as any, 'getAuthKeySigner').mockResolvedValue(undefined) + const wrongSigner = '0x9999999999999999999999999999999999999999' as Address.Address + vi.spyOn(idTokenHandler, 'completeAuth').mockResolvedValue([ + { + ...mockIdentitySigner, + address: wrongSigner, + } as unknown as IdentitySigner, + { email: 'other-user@example.com' }, + ]) + + mockPromptIdToken.mockImplementation(async (_kind, respond) => { + await respond('header.payload.signature') + }) + + idTokenHandler.registerUI(mockPromptIdToken) + + const status = await idTokenHandler.status(testWallet, undefined, testRequest) + const handled = await (status as any).handle() + + expect(handled).toBe(false) + expect(mockAuthKeys.delBySigner).toHaveBeenCalledWith(wrongSigner) + }) + }) +}) diff --git a/packages/wallet/wdk/test/messages.test.ts b/packages/wallet/wdk/test/messages.test.ts index 9420ca000..32d68ffe5 100644 --- a/packages/wallet/wdk/test/messages.test.ts +++ b/packages/wallet/wdk/test/messages.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest' -import { Manager, SignerActionable } from '../src/sequence' +import { Manager, SignerActionable } from '../src/sequence/index.js' import { Mnemonic } from 'ox' -import { newManager } from './constants' +import { newManager } from './constants.js' import { Network } from '@0xsequence/wallet-primitives' describe('Messages', () => { @@ -41,12 +41,13 @@ describe('Messages', () => { // Verify message appears in list const messages = await manager.messages.list() expect(messages.length).toBe(1) - expect(messages[0].wallet).toBe(wallet) - expect(messages[0].message).toBe(testMessage) - expect(messages[0].status).toBe('requested') - expect(messages[0].signatureId).toBe(signatureId) - expect(messages[0].source).toBe('unknown') - expect(messages[0].id).toBeDefined() + const message = messages[0]! + expect(message.wallet).toBe(wallet) + expect(message.message).toBe(testMessage) + expect(message.status).toBe('requested') + expect(message.signatureId).toBe(signatureId) + expect(message.source).toBe('unknown') + expect(message.id).toBeDefined() }) it('Should create message request with custom source', async () => { @@ -63,8 +64,11 @@ describe('Messages', () => { const messages = await manager.messages.list() expect(messages.length).toBe(1) - expect(messages[0].source).toBe(customSource) - expect(messages[0].message).toBe(testMessage) + + const message = messages[0]! + + expect(message.source).toBe(customSource) + expect(message.message).toBe(testMessage) }) it('Should get message by ID', async () => { @@ -79,7 +83,7 @@ describe('Messages', () => { const messages = await manager.messages.list() expect(messages.length).toBe(1) - const messageId = messages[0].id + const messageId = messages[0]!.id // Get by message ID const retrievedMessage = await manager.messages.get(messageId) @@ -269,7 +273,7 @@ describe('Messages', () => { const signatureId = await manager.messages.request(wallet!, testMessage) const messages = await manager.messages.list() - const messageId = messages[0].id + const messageId = messages[0]!.id let updateCallCount = 0 let lastMessage: any @@ -315,7 +319,7 @@ describe('Messages', () => { await manager.messages.request(wallet!, testMessage) const messages = await manager.messages.list() - const messageId = messages[0].id + const messageId = messages[0]!.id let callCount = 0 let receivedMessage: any diff --git a/packages/wallet/wdk/test/otp.test.ts b/packages/wallet/wdk/test/otp.test.ts index 1ad450227..8229dd761 100644 --- a/packages/wallet/wdk/test/otp.test.ts +++ b/packages/wallet/wdk/test/otp.test.ts @@ -1,13 +1,13 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest' import { Address, Hex } from 'ox' import { Network, Payload } from '@0xsequence/wallet-primitives' -import { IdentityInstrument, IdentityType, KeyType, OtpChallenge } from '@0xsequence/identity-instrument' -import { OtpHandler } from '../src/sequence/handlers/otp' -import { Signatures } from '../src/sequence/signatures' -import * as Db from '../src/dbs' -import { IdentitySigner } from '../src/identity/signer' -import { BaseSignatureRequest } from '../src/sequence/types/signature-request' -import { Kinds } from '../src/sequence/types/signer' +import { IdentityInstrument, IdentityType, KeyType } from '@0xsequence/identity-instrument' +import { OtpHandler, PromptOtpHandler } from '../src/sequence/handlers/otp.js' +import { Signatures } from '../src/sequence/signatures.js' +import * as Db from '../src/dbs/index.js' +import { IdentitySigner } from '../src/identity/signer.js' +import { BaseSignatureRequest } from '../src/sequence/types/signature-request.js' +import { Kinds } from '../src/sequence/types/signer.js' // Mock the global crypto API const mockCryptoSubtle = { @@ -69,7 +69,7 @@ describe('OtpHandler', () => { let otpHandler: OtpHandler let testWallet: Address.Address let testRequest: BaseSignatureRequest - let mockPromptOtp: ReturnType + let mockPromptOtp: Mock beforeEach(() => { vi.clearAllMocks() diff --git a/packages/wallet/wdk/test/passkeys.test.ts b/packages/wallet/wdk/test/passkeys.test.ts index 857258c23..e73d0f4e3 100644 --- a/packages/wallet/wdk/test/passkeys.test.ts +++ b/packages/wallet/wdk/test/passkeys.test.ts @@ -1,12 +1,12 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { Address, Hex } from 'ox' import { Network, Payload } from '@0xsequence/wallet-primitives' -import { Signers, State } from '@0xsequence/wallet-core' +import { State } from '@0xsequence/wallet-core' import { Extensions } from '@0xsequence/wallet-primitives' -import { PasskeysHandler } from '../src/sequence/handlers/passkeys' -import { Signatures } from '../src/sequence/signatures' -import { BaseSignatureRequest } from '../src/sequence/types/signature-request' -import { Kinds } from '../src/sequence/types/signer' +import { PasskeysHandler } from '../src/sequence/handlers/passkeys.js' +import { Signatures } from '../src/sequence/signatures.js' +import { BaseSignatureRequest } from '../src/sequence/types/signature-request.js' +import { Kinds } from '../src/sequence/types/signer.js' // Mock dependencies with proper vi.fn() types const mockAddSignature = vi.fn() diff --git a/packages/wallet/wdk/test/recovery.test.ts b/packages/wallet/wdk/test/recovery.test.ts index 9401260ab..ebaab9d3a 100644 --- a/packages/wallet/wdk/test/recovery.test.ts +++ b/packages/wallet/wdk/test/recovery.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest' -import { Manager, QueuedRecoveryPayload, SignerReady, TransactionDefined } from '../src/sequence' +import { QueuedRecoveryPayload, SignerReady, TransactionDefined } from '../src/sequence/index.js' import { Bytes, Hex, Mnemonic, Provider, RpcTransport } from 'ox' import { Network, Payload } from '@0xsequence/wallet-primitives' -import { LOCAL_RPC_URL, newManager } from './constants' +import { LOCAL_RPC_URL, newManager } from './constants.js' describe('Recovery', () => { it('Should execute a recovery', async () => { @@ -159,7 +159,7 @@ describe('Recovery', () => { expect(tx.status).toBe('defined') expect((tx as TransactionDefined).relayerOptions.length).toBe(1) - const localRelayer = (tx as TransactionDefined).relayerOptions[0] + const localRelayer = (tx as TransactionDefined).relayerOptions[0]! expect(localRelayer).toBeDefined() expect(localRelayer.relayerId).toBe('local') @@ -174,7 +174,7 @@ describe('Recovery', () => { const recoverySigner = request4.signers.find((s) => s.handler?.kind === 'recovery-extension') expect(recoverySigner).toBeDefined() expect(recoverySigner!.status).toBe('ready') - 1 + // Handle the recovery signer const result4 = await (recoverySigner as SignerReady).handle() expect(result4).toBeDefined() @@ -303,9 +303,9 @@ describe('Recovery', () => { }) // Set up mnemonic handler and sign the payload - let handledMnemonic2 = 0 + let _handledMnemonic2 = 0 const unregisterHandler = manager.registerMnemonicUI(async (respond) => { - handledMnemonic2++ + _handledMnemonic2++ await respond(mnemonic2) }) @@ -332,7 +332,7 @@ describe('Recovery', () => { expect(Array.isArray(fetchedPayloads)).toBeTruthy() expect(fetchedPayloads.length).toBe(1) - const fetchedPayload = fetchedPayloads[0] + const fetchedPayload = fetchedPayloads[0]! expect(fetchedPayload).toBeDefined() expect(fetchedPayload.wallet).toBe(wallet) expect(fetchedPayload.chainId).toBe(Network.ChainId.ARBITRUM) @@ -390,8 +390,8 @@ describe('Recovery', () => { expect(updatedPayloads.length).toBe(fetchedPayloads2.length) if (updatedPayloads.length > 0 && fetchedPayloads.length > 0) { - const updated = updatedPayloads[0] - const fetched = fetchedPayloads[0] + const updated = updatedPayloads[0]! + const fetched = fetchedPayloads[0]! expect(updated.id).toBe(fetched.id) expect(updated.wallet).toBe(fetched.wallet) diff --git a/packages/wallet/wdk/test/sessions-idtoken.test.ts b/packages/wallet/wdk/test/sessions-idtoken.test.ts new file mode 100644 index 000000000..7c080af5c --- /dev/null +++ b/packages/wallet/wdk/test/sessions-idtoken.test.ts @@ -0,0 +1,98 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { Hash, Hex, Mnemonic, Secp256k1, Address as OxAddress } from 'ox' +import { Payload } from '@0xsequence/wallet-primitives' +import { newManager } from './constants.js' +import { Manager } from '../src/sequence/index.js' +import { Kinds } from '../src/sequence/types/signer.js' + +describe('Sessions ID token attestation', () => { + let manager: Manager | undefined + + afterEach(async () => { + await manager?.stop() + }) + + it('Should include issuer and audience hashes for google-id-token implicit session authorization', async () => { + manager = newManager({ + identity: { + google: { + enabled: true, + clientId: 'test-google-client-id', + authMethod: 'id-token', + }, + }, + }) + + const wallet = await manager.wallets.signUp({ + mnemonic: Mnemonic.random(Mnemonic.english), + kind: 'mnemonic', + noGuard: true, + }) + expect(wallet).toBeDefined() + + const signersModule = (manager as any).shared.modules.signers + vi.spyOn(signersModule, 'kindOf').mockResolvedValue(Kinds.LoginGoogle) + + const sessionAddress = OxAddress.fromPublicKey(Secp256k1.getPublicKey({ privateKey: Secp256k1.randomPrivateKey() })) + const requestId = await manager.sessions.prepareAuthorizeImplicitSession(wallet!, sessionAddress, { + target: 'https://example.com', + applicationData: '0x1234', + }) + + const request = await manager.signatures.get(requestId) + expect(request.action).toBe('session-implicit-authorize') + expect(Payload.isSessionImplicitAuthorize(request.envelope.payload)).toBe(true) + + if (!Payload.isSessionImplicitAuthorize(request.envelope.payload)) { + throw new Error('Expected session implicit authorize payload') + } + + const attestation = request.envelope.payload.attestation + expect(Hex.fromBytes(attestation.issuerHash)).toBe(Hash.keccak256(Hex.fromString('https://accounts.google.com'))) + expect(Hex.fromBytes(attestation.audienceHash)).toBe(Hash.keccak256(Hex.fromString('test-google-client-id'))) + expect(Hex.fromBytes(attestation.applicationData)).toBe('0x1234') + expect(Hex.fromBytes(attestation.identityType)).toBe('0x00000002') + }) + + it('Should include issuer and audience hashes for apple implicit session authorization', async () => { + manager = newManager({ + identity: { + apple: { + enabled: true, + clientId: 'test-apple-client-id', + authMethod: 'id-token', + }, + }, + }) + + const wallet = await manager.wallets.signUp({ + mnemonic: Mnemonic.random(Mnemonic.english), + kind: 'mnemonic', + noGuard: true, + }) + expect(wallet).toBeDefined() + + const signersModule = (manager as any).shared.modules.signers + vi.spyOn(signersModule, 'kindOf').mockResolvedValue(Kinds.LoginApple) + + const sessionAddress = OxAddress.fromPublicKey(Secp256k1.getPublicKey({ privateKey: Secp256k1.randomPrivateKey() })) + const requestId = await manager.sessions.prepareAuthorizeImplicitSession(wallet!, sessionAddress, { + target: 'https://example.com', + applicationData: '0x1234', + }) + + const request = await manager.signatures.get(requestId) + expect(request.action).toBe('session-implicit-authorize') + expect(Payload.isSessionImplicitAuthorize(request.envelope.payload)).toBe(true) + + if (!Payload.isSessionImplicitAuthorize(request.envelope.payload)) { + throw new Error('Expected session implicit authorize payload') + } + + const attestation = request.envelope.payload.attestation + expect(Hex.fromBytes(attestation.issuerHash)).toBe(Hash.keccak256(Hex.fromString('https://appleid.apple.com'))) + expect(Hex.fromBytes(attestation.audienceHash)).toBe(Hash.keccak256(Hex.fromString('test-apple-client-id'))) + expect(Hex.fromBytes(attestation.applicationData)).toBe('0x1234') + expect(Hex.fromBytes(attestation.identityType)).toBe('0x00000002') + }) +}) diff --git a/packages/wallet/wdk/test/setup.ts b/packages/wallet/wdk/test/setup.ts index 70482040c..4aa336a55 100644 --- a/packages/wallet/wdk/test/setup.ts +++ b/packages/wallet/wdk/test/setup.ts @@ -14,7 +14,7 @@ import { } from 'fake-indexeddb' import { Provider, RpcTransport } from 'ox' import { vi } from 'vitest' -import { LOCAL_RPC_URL } from './constants' +import { LOCAL_RPC_URL } from './constants.js' // Add IndexedDB support to the test environment using fake-indexeddb global.indexedDB = indexedDB diff --git a/packages/wallet/wdk/test/signers-kindof.test.ts b/packages/wallet/wdk/test/signers-kindof.test.ts new file mode 100644 index 000000000..06b01cb78 --- /dev/null +++ b/packages/wallet/wdk/test/signers-kindof.test.ts @@ -0,0 +1,62 @@ +import { describe, expect, it, vi } from 'vitest' + +import { Kinds } from '../src/sequence/index.js' +import { newManager } from './constants.js' + +describe('Signers.kindOf', () => { + it('does not probe Sessions/Witness for non-witnessable signers', async () => { + const getWitnessFor = vi.fn().mockResolvedValue(undefined) + const getWitnessForSapient = vi.fn().mockResolvedValue(undefined) + + const manager = newManager({ + stateProvider: { + getWitnessFor, + getWitnessForSapient, + } as any, + }) + + const signers = (manager as any).shared.modules.signers + const extensions = (manager as any).shared.sequence.extensions + + const wallet = '0x1111111111111111111111111111111111111111' + const imageHash = ('0x' + '00'.repeat(32)) as `0x${string}` + + // Sessions extension signer (sapient leaf) never publishes a witness. + await signers.kindOf(wallet, extensions.sessions, imageHash) + + // Passkeys module is a known sapient signer kind. + expect(await signers.kindOf(wallet, extensions.passkeys, imageHash)).toBe(Kinds.LoginPasskey) + + // Sequence dev multisig (default guard topology leaf) never publishes a witness. + await signers.kindOf(wallet, '0x007a47e6BF40C1e0ed5c01aE42fDC75879140bc4') + + expect(getWitnessFor).not.toHaveBeenCalled() + expect(getWitnessForSapient).not.toHaveBeenCalled() + + // Unknown signers still rely on a witness probe. + await signers.kindOf(wallet, '0x2222222222222222222222222222222222222222') + expect(getWitnessFor).toHaveBeenCalledTimes(1) + }) + + it('normalizes legacy Google PKCE signer kind to the canonical Google signer kind', async () => { + const getWitnessFor = vi.fn().mockResolvedValue({ + payload: { + type: 'message', + message: '0x' + Buffer.from(JSON.stringify({ signerKind: 'login-google-pkce' }), 'utf8').toString('hex'), + }, + }) + + const manager = newManager({ + stateProvider: { + getWitnessFor, + getWitnessForSapient: vi.fn(), + } as any, + }) + + const signers = (manager as any).shared.modules.signers + const wallet = '0x1111111111111111111111111111111111111111' + const signer = '0x2222222222222222222222222222222222222222' + + await expect(signers.kindOf(wallet, signer)).resolves.toBe(Kinds.LoginGoogle) + }) +}) diff --git a/packages/wallet/wdk/test/test-ssr-safety.js b/packages/wallet/wdk/test/test-ssr-safety.js new file mode 100644 index 000000000..71eb36170 --- /dev/null +++ b/packages/wallet/wdk/test/test-ssr-safety.js @@ -0,0 +1,314 @@ +#!/usr/bin/env node +/* global console, process */ +/** + * Comprehensive SSR Safety Test (Runtime Execution) + * + * This script tests that the entire wdk package can be imported and used in a Node.js + * environment (SSR context) without throwing errors about missing window. + * + * It executes the code at runtime to catch any SSR issues. + * + * Run with: node test-ssr-comprehensive.mjs + */ + +import { readFile } from 'fs/promises' +import { fileURLToPath } from 'url' +import { dirname, join } from 'path' +import { createRequire } from 'module' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) +const require = createRequire(import.meta.url) + +console.log('Testing SSR safety with runtime execution...\n') + +// Ensure we're in a Node.js environment (no window) +if (typeof window !== 'undefined') { + console.error('ERROR: window is defined! This should not happen in Node.js.') + process.exit(1) +} + +console.log('āœ“ window is undefined (as expected in Node.js)\n') + +const errors = [] +const warnings = [] + +// Read package.json to get package name and exports +let packageJson +try { + const packageJsonPath = join(__dirname, '..', 'package.json') + packageJson = JSON.parse(await readFile(packageJsonPath, 'utf-8')) +} catch (err) { + console.error('Failed to read package.json:', err.message) + process.exit(1) +} + +// Test 1: Import main module via package name +console.log('='.repeat(60)) +console.log('Test 1: Importing package via package name') +console.log('='.repeat(60)) + +let wdk +try { + // Use the package name from package.json + const packageName = packageJson.name + console.log(`Importing ${packageName}...`) + + // Try to resolve the package + const packagePath = require.resolve(packageName) + console.log(` Package resolved to: ${packagePath}`) + + // Import the package + wdk = await import(packageName) + console.log('āœ“ Successfully imported package') + console.log(' Top-level exports:', Object.keys(wdk)) +} catch (error) { + // Check if it's an SSR-related error + if ( + error.message.includes('window is not defined') || + error.message.includes('window') || + error.message.includes('document is not defined') || + error.message.includes('document') || + error.message.includes('localStorage') || + error.message.includes('sessionStorage') + ) { + errors.push(`SSR ERROR: Package accesses browser globals at module load time: ${error.message}`) + if (error.stack) { + console.error('\nError stack:') + console.error(error.stack) + } + } else { + errors.push(`Failed to import package: ${error.message}`) + if (error.stack) { + console.error('Stack:', error.stack) + } + } + + // Don't exit immediately - let the summary show the error + if (errors.length > 0) { + // Skip remaining tests if import failed + wdk = null + } +} + +// Test 2: Recursively access and test all exports +console.log('\n' + '='.repeat(60)) +console.log('Test 2: Accessing and testing all exports') +console.log('='.repeat(60)) + +if (!wdk) { + console.log('Skipping - package import failed') +} else { + async function testExports(obj, path = '', depth = 0) { + if (depth > 5) return // Prevent infinite recursion + + for (const [key, value] of Object.entries(obj)) { + const currentPath = path ? `${path}.${key}` : key + + try { + // Skip if it's a circular reference or already tested + if (value === null || value === undefined) { + continue + } + + // Test accessing the value (this executes any getters) + const accessed = value + + // Test different types + if (typeof accessed === 'function') { + // Try to get function properties + try { + const props = Object.getOwnPropertyNames(accessed) + if (props.length > 0 && depth < 3) { + // Test static properties on functions + for (const prop of props.slice(0, 3)) { + try { + const propValue = accessed[prop] + if (typeof propValue === 'object' && propValue !== null && depth < 2) { + await testExports(propValue, `${currentPath}.${prop}`, depth + 1) + } + } catch (err) { + if (err.message.includes('window') || err.message.includes('document')) { + errors.push(`${currentPath}.${prop}: ${err.message}`) + } + } + } + } + } catch (err) { + if (err.message.includes('window') || err.message.includes('document')) { + errors.push(`${currentPath}: ${err.message}`) + } + } + } else if (typeof accessed === 'object' && accessed !== null) { + // Test object properties + if (Array.isArray(accessed)) { + // Test array elements + for (let i = 0; i < Math.min(accessed.length, 3); i++) { + try { + const item = accessed[i] + if (typeof item === 'object' && item !== null && depth < 3) { + await testExports(item, `${currentPath}[${i}]`, depth + 1) + } + } catch (err) { + if (err.message.includes('window') || err.message.includes('document')) { + errors.push(`${currentPath}[${i}]: ${err.message}`) + } + } + } + } else { + // Test object properties recursively + await testExports(accessed, currentPath, depth + 1) + } + } + } catch (error) { + // Check if it's an SSR-related error + if ( + error.message.includes('window is not defined') || + error.message.includes('window') || + error.message.includes('document is not defined') || + error.message.includes('document') || + error.message.includes('localStorage') || + error.message.includes('sessionStorage') + ) { + errors.push(`${currentPath}: ${error.message}`) + } else { + // Other errors are warnings (might be expected, like missing dependencies) + warnings.push(`${currentPath}: ${error.message}`) + } + } + } + } + + // Test all top-level exports + console.log('Testing all exports recursively...') + await testExports(wdk) +} + +// Test 3: Try to access specific critical exports and use them +console.log('\n' + '='.repeat(60)) +console.log('Test 3: Testing critical exports with actual usage') +console.log('='.repeat(60)) + +if (!wdk) { + console.log('Skipping - package import failed') +} else { + // Test ManagerOptionsDefaults + try { + if (wdk.Sequence?.ManagerOptionsDefaults) { + console.log('Testing ManagerOptionsDefaults...') + const defaults = wdk.Sequence.ManagerOptionsDefaults + + // Access all properties + Object.keys(defaults).forEach((key) => { + try { + const value = defaults[key] + console.log(` āœ“ ${key}: ${typeof value}`) + + // If it's a function, try calling it + if (typeof value === 'function' && key === 'relayers') { + const result = value() + console.log( + ` Called ${key}(), returned:`, + Array.isArray(result) ? `${result.length} items` : typeof result, + ) + } + } catch (err) { + if (err.message.includes('window') || err.message.includes('document')) { + errors.push(`ManagerOptionsDefaults.${key}: ${err.message}`) + } + } + }) + } + } catch (err) { + if (err.message.includes('window') || err.message.includes('document')) { + errors.push(`ManagerOptionsDefaults: ${err.message}`) + } + } + + // Test applyManagerOptionsDefaults function + try { + if (wdk.Sequence?.applyManagerOptionsDefaults) { + console.log('Testing applyManagerOptionsDefaults...') + const result = wdk.Sequence.applyManagerOptionsDefaults() + console.log(' āœ“ Function executed successfully') + console.log(' Result keys:', Object.keys(result).slice(0, 5).join(', '), '...') + } + } catch (err) { + if (err.message.includes('window') || err.message.includes('document')) { + errors.push(`applyManagerOptionsDefaults: ${err.message}`) + } + } +} + +// Test 4: Try importing sub-modules that might be imported separately +console.log('\n' + '='.repeat(60)) +console.log('Test 4: Testing sub-module imports') +console.log('='.repeat(60)) + +if (!wdk) { + console.log('Skipping - package import failed') +} else { + // Get the package path and try importing from dist + try { + const packagePath = require.resolve(packageJson.name) + const packageDir = dirname(packagePath) + + // Try to import from the exports field if available + if (packageJson.exports) { + for (const [exportPath, exportConfig] of Object.entries(packageJson.exports)) { + if (exportPath === '.') { + const modulePath = exportConfig.default || exportConfig.types + if (modulePath) { + try { + const fullPath = join(packageDir, '..', modulePath) + console.log(`Testing import from ${exportPath}...`) + const subModule = await import(fullPath) + console.log(` āœ“ Imported successfully`) + + // Test accessing exports + const subExports = Object.keys(subModule) + if (subExports.length > 0) { + console.log(` Exports: ${subExports.slice(0, 5).join(', ')}${subExports.length > 5 ? '...' : ''}`) + } + } catch (err) { + if (err.message.includes('window') || err.message.includes('document')) { + errors.push(`Import ${exportPath}: ${err.message}`) + } else if (!err.message.includes('Cannot find module')) { + warnings.push(`Import ${exportPath}: ${err.message}`) + } + } + } + } + } + } + } catch (err) { + warnings.push(`Could not test sub-modules: ${err.message}`) + } +} + +// Summary +console.log('\n' + '='.repeat(60)) +console.log('Test Summary') +console.log('='.repeat(60)) + +if (errors.length === 0) { + console.log('\nāœ… All SSR Safety Tests PASSED!') + console.log('The package can be safely imported and used in a Node.js/SSR environment.') + if (warnings.length > 0) { + console.log(`\nāš ļø ${warnings.length} warning(s) (non-SSR related):`) + warnings.slice(0, 5).forEach((warn) => console.log(` - ${warn}`)) + if (warnings.length > 5) { + console.log(` ... and ${warnings.length - 5} more`) + } + } + process.exit(0) +} else { + console.log('\nāŒ ERRORS FOUND:') + errors.forEach((err) => console.log(` - ${err}`)) + console.log('\nāŒ SSR Safety Test FAILED!') + if (warnings.length > 0) { + console.log(`\nāš ļø ${warnings.length} warning(s):`) + warnings.slice(0, 5).forEach((warn) => console.log(` - ${warn}`)) + } + process.exit(1) +} diff --git a/packages/wallet/wdk/test/transactions.test.ts b/packages/wallet/wdk/test/transactions.test.ts index 91bffa56a..8d494347d 100644 --- a/packages/wallet/wdk/test/transactions.test.ts +++ b/packages/wallet/wdk/test/transactions.test.ts @@ -1,7 +1,14 @@ -import { afterEach, describe, expect, it, vi } from 'vitest' -import { Manager, SignerActionable, Transaction, TransactionDefined, TransactionRelayed } from '../src/sequence' +import { afterEach, describe, expect, it } from 'vitest' +import { + isStandardRelayerOption, + Manager, + SignerActionable, + Transaction, + TransactionDefined, + TransactionRelayed, +} from '../src/sequence/index.js' import { Address, Hex, Mnemonic, Provider, RpcTransport } from 'ox' -import { LOCAL_RPC_URL, newManager } from './constants' +import { LOCAL_RPC_URL, newManager } from './constants.js' import { Payload, Network } from '@0xsequence/wallet-primitives' describe('Transactions', () => { @@ -48,9 +55,17 @@ describe('Transactions', () => { } expect(tx.relayerOptions.length).toBe(1) - expect(tx.relayerOptions[0].id).toBeDefined() + expect(tx.relayerOptions[0]!.id).toBeDefined() + + // PkRelayer/LocalRelayer never report sponsorship, so sponsored should be + // explicitly false (not undefined) and failed should remain undefined. + const firstOption = tx.relayerOptions[0]! + if (isStandardRelayerOption(firstOption)) { + expect(firstOption.sponsored).toBe(false) + expect(firstOption.failed).toBeUndefined() + } - const sigId = await manager.transactions.selectRelayer(txId!, tx.relayerOptions[0].id) + const sigId = await manager.transactions.selectRelayer(txId!, tx.relayerOptions[0]!.id) expect(sigId).toBeDefined() tx = await manager.transactions.get(txId!) @@ -161,9 +176,17 @@ describe('Transactions', () => { } expect(tx.relayerOptions.length).toBe(1) - expect(tx.relayerOptions[0].id).toBeDefined() + expect(tx.relayerOptions[0]!.id).toBeDefined() + + // PkRelayer/LocalRelayer never report sponsorship, so sponsored should be + // explicitly false (not undefined) and failed should remain undefined. + const firstOption = tx.relayerOptions[0]! + if (isStandardRelayerOption(firstOption)) { + expect(firstOption.sponsored).toBe(false) + expect(firstOption.failed).toBeUndefined() + } - const sigId = await manager.transactions.selectRelayer(txId!, tx.relayerOptions[0].id) + const sigId = await manager.transactions.selectRelayer(txId!, tx.relayerOptions[0]!.id) expect(sigId).toBeDefined() tx = await manager.transactions.get(txId!) @@ -230,11 +253,12 @@ describe('Transactions', () => { expect(calledTimes).toBe(1) expect(transactions.length).toBe(1) - expect(transactions[0].status).toBe('requested') - expect(transactions[0].wallet).toBe(wallet!) - expect(transactions[0].requests.length).toBe(1) - expect(transactions[0].requests[0].to).toEqual(to) - expect(transactions[0].requests[0].value).toEqual(9n) + const tx = transactions[0]! + expect(tx.status).toBe('requested') + expect(tx.wallet).toBe(wallet!) + expect(tx.requests.length).toBe(1) + expect(tx.requests[0]!.to).toEqual(to) + expect(tx.requests[0]!.value).toEqual(9n) }) it('Should call onTransactionUpdate when a transaction is defined, relayer selected and relayed', async () => { @@ -273,12 +297,12 @@ describe('Transactions', () => { expect(tx!.status).toBe('defined') expect(tx!.wallet).toBe(wallet!) expect(tx!.requests.length).toBe(1) - expect(tx!.requests[0].to).toEqual(to) - expect(tx!.requests[0].value).toBeUndefined() - expect(tx!.requests[0].gasLimit).toBeUndefined() - expect(tx!.requests[0].data).toBeUndefined() + expect(tx!.requests[0]!.to).toEqual(to) + expect(tx!.requests[0]!.value).toBeUndefined() + expect(tx!.requests[0]!.gasLimit).toBeUndefined() + expect(tx!.requests[0]!.data).toBeUndefined() - const sigId = await manager.transactions.selectRelayer(txId!, (tx as TransactionDefined).relayerOptions[0].id) + const sigId = await manager.transactions.selectRelayer(txId!, (tx as TransactionDefined).relayerOptions[0]!.id) expect(sigId).toBeDefined() while (calledTimes < 2) { @@ -374,7 +398,7 @@ describe('Transactions', () => { expect(tx).toBeDefined() expect(tx!.status).toBe('defined') - const sigId = await manager.transactions.selectRelayer(txId!, (tx as TransactionDefined).relayerOptions[0].id) + const sigId = await manager.transactions.selectRelayer(txId!, (tx as TransactionDefined).relayerOptions[0]!.id) expect(sigId).toBeDefined() await manager.transactions.delete(txId!) @@ -433,12 +457,12 @@ describe('Transactions', () => { // The first call should be to the random address // and the second one should be a call to self - const call1 = (tx.envelope.payload as Payload.Calls).calls[0] - const call2 = (tx.envelope.payload as Payload.Calls).calls[1] + const call1 = (tx.envelope.payload as Payload.Calls).calls[0]! + const call2 = (tx.envelope.payload as Payload.Calls).calls[1]! expect(call1.to).toEqual(randomAddress) expect(call2.to).toEqual(wallet) - const sigId = await manager.transactions.selectRelayer(txId!, (tx as TransactionDefined).relayerOptions[0].id) + const sigId = await manager.transactions.selectRelayer(txId!, (tx as TransactionDefined).relayerOptions[0]!.id) expect(sigId).toBeDefined() tx = await manager.transactions.get(txId!) @@ -474,12 +498,31 @@ describe('Transactions', () => { const txId1 = manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ { to: wallet!, + data: '0x1234', }, ]) await expect(txId1).rejects.toThrow() }) + it('Should allow native token transfer to self in safe mode', async () => { + const manager = newManager() + const wallet = await manager.wallets.signUp({ + mnemonic: Mnemonic.random(Mnemonic.english), + kind: 'mnemonic', + noGuard: true, + }) + + const txId1 = await manager.transactions.request(wallet!, Network.ChainId.ARBITRUM, [ + { + to: wallet!, + value: 1n, + }, + ]) + + expect(txId1).toBeDefined() + }) + it('Should allow transactions to self in unsafe mode', async () => { const manager = newManager() const wallet = await manager.wallets.signUp({ @@ -515,12 +558,12 @@ describe('Transactions', () => { }) let transactionsList: Transaction[] = [] - let updateCount = 0 + let _updateCount = 0 // Use onTransactionsUpdate to verify list functionality const unsubscribe = manager.transactions.onTransactionsUpdate((txs) => { transactionsList = txs - updateCount++ + _updateCount++ }) // Initially should be empty @@ -540,9 +583,10 @@ describe('Transactions', () => { // Should now have one transaction expect(transactionsList.length).toBe(1) - expect(transactionsList[0].id).toBe(txId) - expect(transactionsList[0].status).toBe('requested') - expect(transactionsList[0].wallet).toBe(wallet) + const tx = transactionsList[0]! + expect(tx.id).toBe(txId) + expect(tx.status).toBe('requested') + expect(tx.wallet).toBe(wallet) unsubscribe() }) @@ -577,7 +621,7 @@ describe('Transactions', () => { expect(callCount).toBe(1) expect(receivedTransactions.length).toBe(1) - expect(receivedTransactions[0].id).toBe(txId) + expect(receivedTransactions[0]!.id).toBe(txId) unsubscribe() }) @@ -697,8 +741,8 @@ describe('Transactions', () => { const tx = await manager.transactions.get(txId) expect(tx.status).toBe('defined') - expect(tx.envelope.payload.calls[0].gasLimit).toBe(50000n) - expect(tx.envelope.payload.calls[1].gasLimit).toBe(75000n) + expect(tx.envelope.payload.calls[0]!.gasLimit).toBe(50000n) + expect(tx.envelope.payload.calls[1]!.gasLimit).toBe(75000n) }) it('Should throw error when defining transaction not in requested state', async () => { @@ -780,8 +824,8 @@ describe('Transactions', () => { expect(tx.status).toBe('requested') expect(tx.source).toBe('test-dapp') expect(tx.envelope.payload.space).toBe(customSpace) - expect(tx.requests[0].data).toBe('0x1234') - expect(tx.requests[0].gasLimit).toBe(21000n) + expect(tx.requests[0]!.data).toBe('0x1234') + expect(tx.requests[0]!.gasLimit).toBe(21000n) }) it('Should throw error for unknown network', async () => { @@ -820,12 +864,12 @@ describe('Transactions', () => { const tx = await manager.transactions.get(txId) expect(tx.status).toBe('requested') - expect(tx.envelope.payload.calls[0].value).toBe(0n) - expect(tx.envelope.payload.calls[0].data).toBe('0x') - expect(tx.envelope.payload.calls[0].gasLimit).toBe(0n) - expect(tx.envelope.payload.calls[0].delegateCall).toBe(false) - expect(tx.envelope.payload.calls[0].onlyFallback).toBe(false) - expect(tx.envelope.payload.calls[0].behaviorOnError).toBe('revert') + expect(tx.envelope.payload.calls[0]!.value).toBe(0n) + expect(tx.envelope.payload.calls[0]!.data).toBe('0x') + expect(tx.envelope.payload.calls[0]!.gasLimit).toBe(0n) + expect(tx.envelope.payload.calls[0]!.delegateCall).toBe(false) + expect(tx.envelope.payload.calls[0]!.onlyFallback).toBe(false) + expect(tx.envelope.payload.calls[0]!.behaviorOnError).toBe('revert') }) it('Should handle relay with signature ID instead of transaction ID', async () => { @@ -856,7 +900,7 @@ describe('Transactions', () => { throw new Error('Transaction not defined') } - const sigId = await manager.transactions.selectRelayer(txId, tx.relayerOptions[0].id) + const sigId = await manager.transactions.selectRelayer(txId, tx.relayerOptions[0]!.id) // Sign the transaction const sigRequest = await manager.signatures.get(sigId) diff --git a/packages/wallet/wdk/test/wallets.test.ts b/packages/wallet/wdk/test/wallets.test.ts index 45db67613..d2d1a6ee8 100644 --- a/packages/wallet/wdk/test/wallets.test.ts +++ b/packages/wallet/wdk/test/wallets.test.ts @@ -1,8 +1,13 @@ -import { afterEach, describe, expect, it } from 'vitest' -import { Manager, SignerActionable, SignerReady } from '../src/sequence' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { Manager, SignerActionable, SignerReady } from '../src/sequence/index.js' import { Mnemonic, Address } from 'ox' -import { newManager } from './constants' -import { Network } from '@0xsequence/wallet-primitives' +import { newManager } from './constants.js' +import { Config, Constants, Network } from '@0xsequence/wallet-primitives' +import { AuthCodePkceHandler } from '../src/sequence/handlers/authcode-pkce.js' +import { IdTokenHandler } from '../src/sequence/handlers/idtoken.js' +import { IdentitySigner } from '../src/identity/signer.js' +import { MnemonicHandler } from '../src/sequence/handlers/mnemonic.js' +import { Kinds } from '../src/sequence/types/signer.js' describe('Wallets', () => { let manager: Manager | undefined @@ -24,6 +29,305 @@ describe('Wallets', () => { await expect(manager.wallets.has(wallet!)).resolves.toBeTruthy() }) + it('Should create a new wallet using google-id-token when Google ID token auth is enabled', async () => { + manager = newManager({ + identity: { + google: { + enabled: true, + clientId: 'test-google-client-id', + authMethod: 'id-token', + }, + }, + }) + + const handler = (manager as any).shared.handlers.get(Kinds.LoginGoogle) as IdTokenHandler + const loginMnemonic = Mnemonic.random(Mnemonic.english) + const loginSigner = MnemonicHandler.toSigner(loginMnemonic) + if (!loginSigner) { + throw new Error('Failed to create login signer for test') + } + + const completeAuthSpy = vi + .spyOn(handler, 'completeAuth') + .mockResolvedValue([loginSigner as unknown as IdentitySigner, { email: 'google-user@example.com' }]) + + const wallet = await manager.wallets.signUp({ + kind: 'google-id-token', + idToken: 'eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.', + noGuard: true, + }) + + expect(wallet).toBeDefined() + expect(completeAuthSpy).toHaveBeenCalledWith('eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.') + await expect(manager.wallets.has(wallet!)).resolves.toBeTruthy() + + const walletEntry = await manager.wallets.get(wallet!) + expect(walletEntry).toBeDefined() + expect(walletEntry!.loginType).toBe(Kinds.LoginGoogle) + expect(walletEntry!.loginEmail).toBe('google-user@example.com') + + const configuration = await manager.wallets.getConfiguration(wallet!) + expect(configuration.login).toHaveLength(1) + expect(configuration.login[0]!.kind).toBe(Kinds.LoginGoogle) + }) + + it('Should create a new wallet using apple-id-token when Apple ID token auth is enabled', async () => { + manager = newManager({ + identity: { + apple: { + enabled: true, + clientId: 'test-apple-client-id', + authMethod: 'id-token', + }, + }, + }) + + const handler = (manager as any).shared.handlers.get(Kinds.LoginApple) as IdTokenHandler + const loginMnemonic = Mnemonic.random(Mnemonic.english) + const loginSigner = MnemonicHandler.toSigner(loginMnemonic) + if (!loginSigner) { + throw new Error('Failed to create login signer for test') + } + + const completeAuthSpy = vi + .spyOn(handler, 'completeAuth') + .mockResolvedValue([loginSigner as unknown as IdentitySigner, { email: 'apple-user@example.com' }]) + + const wallet = await manager.wallets.signUp({ + kind: 'apple-id-token', + idToken: 'eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.', + noGuard: true, + }) + + expect(wallet).toBeDefined() + expect(completeAuthSpy).toHaveBeenCalledWith('eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.') + await expect(manager.wallets.has(wallet!)).resolves.toBeTruthy() + + const walletEntry = await manager.wallets.get(wallet!) + expect(walletEntry).toBeDefined() + expect(walletEntry!.loginType).toBe(Kinds.LoginApple) + expect(walletEntry!.loginEmail).toBe('apple-user@example.com') + + const configuration = await manager.wallets.getConfiguration(wallet!) + expect(configuration.login).toHaveLength(1) + expect(configuration.login[0]!.kind).toBe(Kinds.LoginApple) + }) + + it('Should register and unregister Google ID token UI callbacks through the manager', async () => { + manager = newManager({ + identity: { + google: { + enabled: true, + clientId: 'test-google-client-id', + authMethod: 'id-token', + }, + }, + }) + + const handler = (manager as any).shared.handlers.get(Kinds.LoginGoogle) as IdTokenHandler + const promptIdToken = vi.fn() + + const unregister = manager.registerIdTokenUI(promptIdToken) + + expect(handler['onPromptIdToken']).toBe(promptIdToken) + + unregister() + + expect(handler['onPromptIdToken']).toBeUndefined() + }) + + it('Should keep Google PKCE redirect flow as the default when authMethod is not specified', async () => { + manager = newManager({ + identity: { + google: { + enabled: true, + clientId: 'test-google-client-id', + }, + }, + }) + + const handler = (manager as any).shared.handlers.get(Kinds.LoginGoogle) as AuthCodePkceHandler + expect(handler).toBeInstanceOf(AuthCodePkceHandler) + + const commitAuthSpy = vi + .spyOn(handler, 'commitAuth') + .mockResolvedValue('https://accounts.google.com/o/oauth2/v2/auth?state=test-state') + + const url = await manager.wallets.startSignUpWithRedirect({ + kind: 'google-pkce', + target: '/auth/return', + metadata: {}, + }) + + expect(url).toBe('https://accounts.google.com/o/oauth2/v2/auth?state=test-state') + expect(commitAuthSpy).toHaveBeenCalledWith('/auth/return', { type: 'auth' }) + }) + + it('Should expose added login signer metadata from redirect when requested', async () => { + manager = newManager({ + identity: { + google: { + enabled: true, + clientId: 'test-google-client-id', + }, + }, + }) + + const wallet = await manager.wallets.signUp({ + mnemonic: Mnemonic.random(Mnemonic.english), + kind: 'mnemonic', + noGuard: true, + }) + expect(wallet).toBeDefined() + + const handler = (manager as any).shared.handlers.get(Kinds.LoginGoogle) as AuthCodePkceHandler + const addedSigner = MnemonicHandler.toSigner(Mnemonic.random(Mnemonic.english)) + if (!addedSigner) { + throw new Error('Failed to create added login signer for test') + } + + const completeAuthSpy = vi + .spyOn(handler, 'completeAuth') + .mockResolvedValue([addedSigner as unknown as IdentitySigner, { email: 'secondary-google-user@example.com' }]) + + const state = 'add-signer-state-with-metadata' + await (manager as any).shared.databases.authCommitments.set({ + id: state, + kind: 'google-pkce', + metadata: {}, + target: '/account/signers', + type: 'add-signer', + wallet: wallet!, + }) + + const result = await manager.wallets.completeRedirect({ + state, + code: 'auth-code', + includeMetadata: true, + }) + + expect(completeAuthSpy).toHaveBeenCalledWith(expect.objectContaining({ id: state }), 'auth-code') + expect(result).toEqual({ + target: '/account/signers', + addedLoginSigner: { + wallet, + signer: { + address: await addedSigner.address, + kind: Kinds.LoginGoogle, + email: 'secondary-google-user@example.com', + }, + }, + }) + }) + + it('Should keep returning the redirect target string when metadata is not requested', async () => { + manager = newManager({ + identity: { + google: { + enabled: true, + clientId: 'test-google-client-id', + }, + }, + }) + + const wallet = await manager.wallets.signUp({ + mnemonic: Mnemonic.random(Mnemonic.english), + kind: 'mnemonic', + noGuard: true, + }) + expect(wallet).toBeDefined() + + const handler = (manager as any).shared.handlers.get(Kinds.LoginGoogle) as AuthCodePkceHandler + const addedSigner = MnemonicHandler.toSigner(Mnemonic.random(Mnemonic.english)) + if (!addedSigner) { + throw new Error('Failed to create added login signer for test') + } + + vi.spyOn(handler, 'completeAuth').mockResolvedValue([ + addedSigner as unknown as IdentitySigner, + { email: 'secondary-google-user@example.com' }, + ]) + + const state = 'add-signer-state-without-metadata' + await (manager as any).shared.databases.authCommitments.set({ + id: state, + kind: 'google-pkce', + metadata: {}, + target: '/account/signers', + type: 'add-signer', + wallet: wallet!, + }) + + const result = await manager.wallets.completeRedirect({ + state, + code: 'auth-code', + }) + + expect(result).toBe('/account/signers') + }) + + it('Should reject google-id-token signup when Google is configured for redirect auth', async () => { + manager = newManager({ + identity: { + google: { + enabled: true, + clientId: 'test-google-client-id', + }, + }, + }) + + await expect( + manager.wallets.signUp({ + kind: 'google-id-token', + idToken: 'eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.', + noGuard: true, + }), + ).rejects.toThrow('handler-does-not-support-id-token') + }) + + it('Should reject apple-id-token signup when Apple is configured for redirect auth', async () => { + manager = newManager({ + identity: { + apple: { + enabled: true, + clientId: 'test-apple-client-id', + }, + }, + }) + + await expect( + manager.wallets.signUp({ + kind: 'apple-id-token', + idToken: 'eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.', + noGuard: true, + }), + ).rejects.toThrow('handler-does-not-support-id-token') + }) + + it('Should reject custom ID token signup when the provider uses redirect auth', async () => { + manager = newManager({ + identity: { + customProviders: [ + { + kind: 'custom-oidc', + authMethod: 'authcode', + issuer: 'https://issuer.example.com', + oauthUrl: 'https://issuer.example.com/oauth/authorize', + clientId: 'test-custom-client-id', + }, + ], + }, + }) + + await expect( + manager.wallets.signUp({ + kind: 'custom-oidc', + idToken: 'eyJhbGciOiJub25lIn0.eyJleHAiOjQxMDI0NDQ4MDB9.', + noGuard: true, + }), + ).rejects.toThrow('handler-does-not-support-id-token') + }) + it('Should get a specific wallet by address', async () => { manager = newManager() const mnemonic = Mnemonic.random(Mnemonic.english) @@ -65,7 +369,7 @@ describe('Wallets', () => { const walletsAfterFirst = await manager.wallets.list() expect(walletsAfterFirst.length).toBe(1) - expect(walletsAfterFirst[0].address).toBe(wallet1) + expect(walletsAfterFirst[0]!.address).toBe(wallet1) }) // === WALLET SELECTOR REGISTRATION === @@ -73,9 +377,9 @@ describe('Wallets', () => { it('Should register and unregister wallet selector', async () => { manager = newManager() - let selectorCalls = 0 + let _selectorCalls = 0 const mockSelector = async () => { - selectorCalls++ + _selectorCalls++ return 'create-new' as const } @@ -160,7 +464,7 @@ describe('Wallets', () => { const mnemonic = Mnemonic.random(Mnemonic.english) // Create initial wallet - const firstWallet = await manager!.wallets.signUp({ + const _firstWallet = await manager!.wallets.signUp({ mnemonic, kind: 'mnemonic', noGuard: true, @@ -283,14 +587,14 @@ describe('Wallets', () => { expect(config.devices).toBeDefined() expect(config.devices.length).toBe(1) - expect(config.devices[0].kind).toBe('local-device') - expect(config.devices[0].address).toBeDefined() + expect(config.devices[0]!.kind).toBe('local-device') + expect(config.devices[0]!.address).toBeDefined() expect(config.login).toBeDefined() expect(config.login.length).toBe(1) - expect(config.login[0].kind).toBe('login-mnemonic') + expect(config.login[0]!.kind).toBe('login-mnemonic') - expect(config.guard).not.toBeDefined() // No guard for noGuard: true + expect(config.walletGuard).not.toBeDefined() // No guard for noGuard: true expect(config.raw).toBeDefined() expect(config.raw.loginTopology).toBeDefined() @@ -298,6 +602,99 @@ describe('Wallets', () => { expect(config.raw.modules).toBeDefined() }) + it('Should include guard configuration when enabled', async () => { + manager = newManager(undefined, undefined, `guard_enabled_${Date.now()}`) + const guardAddress = (manager as any).shared.sequence.guardAddresses.wallet + const sessionsGuardAddress = (manager as any).shared.sequence.guardAddresses.sessions + const sessionsModuleAddress = (manager as any).shared.sequence.extensions.sessions + + const wallet = await manager.wallets.signUp({ + mnemonic: Mnemonic.random(Mnemonic.english), + kind: 'mnemonic', + noGuard: false, + }) + + const config = await manager.wallets.getConfiguration(wallet!) + + expect(config.walletGuard?.address).toBe(guardAddress) + expect(config.raw.guardTopology).toBeDefined() + expect(Config.findSignerLeaf(config.raw.guardTopology!, guardAddress)).toBeDefined() + expect( + Config.findSignerLeaf(config.raw.guardTopology!, Constants.PlaceholderAddress as Address.Address), + ).toBeUndefined() + + const sessionsModule = config.raw.modules.find((m: any) => + Address.isEqual(m.sapientLeaf.address, sessionsModuleAddress), + ) + expect(sessionsModule?.guardLeaf).toBeDefined() + expect(Config.findSignerLeaf(sessionsModule!.guardLeaf!, sessionsGuardAddress)).toBeDefined() + expect( + Config.findSignerLeaf(sessionsModule!.guardLeaf!, Constants.PlaceholderAddress as Address.Address), + ).toBeUndefined() + + expect(config.moduleGuards.get(sessionsModuleAddress as Address.Address)?.address).toBe(sessionsGuardAddress) + }) + + it('Should support non-nested guard topologies', async () => { + manager = newManager( + { + defaultGuardTopology: { + type: 'signer', + address: Constants.PlaceholderAddress, + weight: 1n, + }, + }, + undefined, + `flat_guard_${Date.now()}`, + ) + + const guardAddress = (manager as any).shared.sequence.guardAddresses.wallet + const sessionsGuardAddress = (manager as any).shared.sequence.guardAddresses.sessions + const wallet = await manager.wallets.signUp({ + mnemonic: Mnemonic.random(Mnemonic.english), + kind: 'mnemonic', + noGuard: false, + }) + + const config = await manager.wallets.getConfiguration(wallet!) + + expect(config.walletGuard?.address).toBe(guardAddress) + expect(config.raw.guardTopology).toBeDefined() + expect(Config.findSignerLeaf(config.raw.guardTopology!, guardAddress)).toBeDefined() + expect( + Config.findSignerLeaf(config.raw.guardTopology!, Constants.PlaceholderAddress as Address.Address), + ).toBeUndefined() + + const sessionsModuleAddress = (manager as any).shared.sequence.extensions.sessions + const sessionsModule = config.raw.modules.find((m: any) => + Address.isEqual(m.sapientLeaf.address, sessionsModuleAddress), + ) + expect(sessionsModule?.guardLeaf).toBeDefined() + expect(Config.findSignerLeaf(sessionsModule!.guardLeaf!, sessionsGuardAddress)).toBeDefined() + }) + + it('Should fail signup when default guard topology lacks placeholder address', async () => { + manager = newManager( + { + defaultGuardTopology: { + type: 'signer', + address: '0x0000000000000000000000000000000000000001', + weight: 1n, + }, + }, + undefined, + `guard_missing_placeholder_${Date.now()}`, + ) + + await expect( + manager.wallets.signUp({ + mnemonic: Mnemonic.random(Mnemonic.english), + kind: 'mnemonic', + noGuard: false, + }), + ).rejects.toThrow('Guard address replacement failed for role wallet') + }) + // === ERROR HANDLING === it('Should throw error when trying to get configuration for non-existent wallet', async () => { @@ -317,7 +714,7 @@ describe('Wallets', () => { const mnemonic = Mnemonic.random(Mnemonic.english) await manager.wallets.signUp({ mnemonic, kind: 'mnemonic', noGuard: true }) - await manager.wallets.logout(await manager.wallets.list().then((w) => w[0].address), { skipRemoveDevice: true }) + await manager.wallets.logout(await manager.wallets.list().then((w) => w[0]!.address), { skipRemoveDevice: true }) const invalidSelector = async () => 'invalid-result' as any manager.wallets.registerWalletSelector(invalidSelector) @@ -337,7 +734,7 @@ describe('Wallets', () => { const wallets = await manager.wallets.list() expect(wallets.length).toBe(1) - expect(wallets[0].address).toBe(wallet!) + expect(wallets[0]!.address).toBe(wallet!) const requestId = await manager.wallets.logout(wallet!) expect(requestId).toBeDefined() @@ -377,16 +774,16 @@ describe('Wallets', () => { const wallets = await manager.wallets.list() expect(wallets.length).toBe(1) - expect(wallets[0].address).toBe(wallet!) - expect(wallets[0].status).toBe('ready') + expect(wallets[0]!.address).toBe(wallet!) + expect(wallets[0]!.status).toBe('ready') const requestId = await manager.wallets.logout(wallet!) expect(requestId).toBeDefined() const wallets2 = await manager.wallets.list() expect(wallets2.length).toBe(1) - expect(wallets2[0].address).toBe(wallet!) - expect(wallets2[0].status).toBe('logging-out') + expect(wallets2[0]!.address).toBe(wallet!) + expect(wallets2[0]!.status).toBe('logging-out') const request = await manager.signatures.get(requestId) expect(request).toBeDefined() @@ -422,8 +819,8 @@ describe('Wallets', () => { const wallets = await manager.wallets.list() expect(wallets.length).toBe(1) - expect(wallets[0].address).toBe(wallet!) - expect(wallets[0].status).toBe('logging-in') + expect(wallets[0]!.address).toBe(wallet!) + expect(wallets[0]!.status).toBe('logging-in') let signRequests = 0 const unregistedUI = manager.registerMnemonicUI(async (respond) => { @@ -450,8 +847,8 @@ describe('Wallets', () => { expect((await manager.signatures.get(requestId1!))?.status).toBe('completed') const wallets2 = await manager.wallets.list() expect(wallets2.length).toBe(1) - expect(wallets2[0].address).toBe(wallet!) - expect(wallets2[0].status).toBe('ready') + expect(wallets2[0]!.address).toBe(wallet!) + expect(wallets2[0]!.status).toBe('ready') // The wallet should have 2 device keys and 2 recovery keys const config = await manager.wallets.getConfiguration(wallet!) @@ -471,7 +868,7 @@ describe('Wallets', () => { const wallets = await manager.wallets.list() expect(wallets.length).toBe(1) - expect(wallets[0].address).toBe(wallet!) + expect(wallets[0]!.address).toBe(wallet!) const requestId = await manager.wallets.logout(wallet!) expect(requestId).toBeDefined() @@ -520,7 +917,7 @@ describe('Wallets', () => { expect((await manager.signatures.get(requestId2!))?.status).toBe('completed') const wallets3 = await manager.wallets.list() expect(wallets3.length).toBe(1) - expect(wallets3[0].address).toBe(wallet!) + expect(wallets3[0]!.address).toBe(wallet!) // The wallet should have a single device key and a single recovery key const config = await manager.wallets.getConfiguration(wallet!) @@ -529,10 +926,10 @@ describe('Wallets', () => { expect(recovery?.length).toBe(1) // The kind of the device key should be 'local-device' - expect(config.devices[0].kind).toBe('local-device') + expect(config.devices[0]!.kind).toBe('local-device') // The kind of the recovery key should be 'local-recovery' - expect(recovery?.[0].kind).toBe('local-device') + expect(recovery?.[0]!.kind).toBe('local-device') }) it('Should fail to logout from a non-existent wallet', async () => { @@ -583,8 +980,8 @@ describe('Wallets', () => { const wallets = await manager.wallets.list() expect(wallets.length).toBe(1) - expect(wallets[0].address).toBe(wallet!) - expect(wallets[0].status).toBe('logging-in') + expect(wallets[0]!.address).toBe(wallet!) + expect(wallets[0]!.status).toBe('logging-in') const request = await manager.signatures.get(requestId!) expect(request).toBeDefined() @@ -606,14 +1003,14 @@ describe('Wallets', () => { expect((await manager.signatures.get(requestId!))?.status).toBe('completed') const wallets2 = await manager.wallets.list() expect(wallets2.length).toBe(1) - expect(wallets2[0].address).toBe(wallet!) - expect(wallets2[0].status).toBe('ready') + expect(wallets2[0]!.address).toBe(wallet!) + expect(wallets2[0]!.status).toBe('ready') }) it('Should trigger an update when a wallet is logged in', async () => { const manager = newManager() - - let wallet: any | undefined + // eslint-disable-next-line + let wallet: Address.Address | undefined let callbackCalls = 0 let unregisterCallback: (() => void) | undefined @@ -622,8 +1019,8 @@ describe('Wallets', () => { unregisterCallback = manager.wallets.onWalletsUpdate((wallets) => { callbackCalls++ expect(wallets.length).toBe(1) - expect(wallets[0].address).toBe(wallet!) - expect(wallets[0].status).toBe('ready') + expect(wallets[0]!.address).toBe(wallet!) + expect(wallets[0]!.status).toBe('ready') resolve() }) }) @@ -684,8 +1081,8 @@ describe('Wallets', () => { unregisterCallback = manager.wallets.onWalletsUpdate((wallets) => { callbackCalls++ expect(wallets.length).toBe(1) - expect(wallets[0].address).toBe(wallet!) - expect(wallets[0].status).toBe('logging-out') + expect(wallets[0]!.address).toBe(wallet!) + expect(wallets[0]!.status).toBe('logging-out') resolve() }) }) @@ -707,9 +1104,9 @@ describe('Wallets', () => { const devices = await manager.wallets.listDevices(wallet!) expect(devices.length).toBe(1) - expect(devices[0].address).not.toBe(wallet) - expect(devices[0].isLocal).toBe(true) expect(devices[0]).toBeDefined() + expect(devices[0]!.address).not.toBe(wallet) + expect(devices[0]!.isLocal).toBe(true) }) it('Should list all active devices for a wallet, including a new remote device', async () => { @@ -727,8 +1124,8 @@ describe('Wallets', () => { // Verify initial state from Device 1's perspective const devices1 = await managerDevice1.wallets.listDevices(wallet!) expect(devices1.length).toBe(1) - expect(devices1[0].isLocal).toBe(true) - const device1Address = devices1[0].address + expect(devices1[0]!.isLocal).toBe(true) + const device1Address = devices1[0]!.address // Wallet logs in on device 2 const managerDevice2 = newManager(undefined, undefined, 'device-2') @@ -845,8 +1242,8 @@ describe('Wallets', () => { const finalDevices = await managerDevice1.wallets.listDevices(wallet!) console.log('Final devices', finalDevices) expect(finalDevices.length).toBe(1) - expect(finalDevices[0].isLocal).toBe(true) - expect(finalDevices[0].address).not.toBe(device2Address) + expect(finalDevices[0]!.isLocal).toBe(true) + expect(finalDevices[0]!.address).not.toBe(device2Address) await managerDevice1.stop() await managerDevice2.stop() @@ -863,7 +1260,7 @@ describe('Wallets', () => { const devices = await manager.wallets.listDevices(wallet!) expect(devices.length).toBe(1) - const localDeviceAddress = devices[0].address + const localDeviceAddress = devices[0]!.address const remoteLogoutPromise = manager.wallets.remoteLogout(wallet!, localDeviceAddress) diff --git a/packages/wallet/wdk/vitest.config.ts b/packages/wallet/wdk/vitest.config.ts index 813dc499d..9c2092c0c 100644 --- a/packages/wallet/wdk/vitest.config.ts +++ b/packages/wallet/wdk/vitest.config.ts @@ -1,4 +1,3 @@ -import { BrowserNavigationCrossOriginPolicyEnum } from 'happy-dom' import { defineConfig } from 'vitest/config' export default defineConfig({ diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 59a78ba9a..bd308b020 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,7 @@ +minimumReleaseAge: 10080 # 60 * 24 * 7 = do not install package releases that are not at least 1 week old +# DO NOT REMOVE OR MODIFY minimumReleaseAge without approval +# list packages to be excluded from minimum release limitation + packages: - extras/* - packages/* diff --git a/repo/eslint-config/CHANGELOG.md b/repo/eslint-config/CHANGELOG.md new file mode 100644 index 000000000..2df72f2e7 --- /dev/null +++ b/repo/eslint-config/CHANGELOG.md @@ -0,0 +1,20 @@ +# @repo/eslint-config + +## 0.0.1 + +### Patch Changes + +- d5017e8: Beta release for v3 +- 7c6c811: 3.0.0-beta.3 with fixes + +## 0.0.1-beta.1 + +### Patch Changes + +- Beta release for v3 + +## 0.0.1-beta.0 + +### Patch Changes + +- 3.0.0-beta.3 with fixes diff --git a/repo/eslint-config/package.json b/repo/eslint-config/package.json index 8e609c8f5..4b4d2db7f 100644 --- a/repo/eslint-config/package.json +++ b/repo/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@repo/eslint-config", - "version": "0.0.0", + "version": "0.0.1", "type": "module", "private": true, "exports": { @@ -9,16 +9,16 @@ "./react-internal": "./react-internal.js" }, "devDependencies": { - "@eslint/js": "^9.28.0", - "@next/eslint-plugin-next": "^15.3.3", - "eslint": "^9.28.0", - "eslint-config-prettier": "^9.1.0", + "@eslint/js": "^9.39.2", + "@next/eslint-plugin-next": "^15.5.9", + "eslint": "^9.39.2", + "eslint-config-prettier": "^10.1.8", "eslint-plugin-only-warn": "^1.1.0", "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-turbo": "^2.5.4", - "globals": "^15.15.0", - "typescript": "^5.8.3", - "typescript-eslint": "^8.33.1" + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-turbo": "^2.6.3", + "globals": "^16.5.0", + "typescript": "^6.0.3", + "typescript-eslint": "^8.49.0" } } diff --git a/repo/typescript-config/CHANGELOG.md b/repo/typescript-config/CHANGELOG.md new file mode 100644 index 000000000..3e5ecbabf --- /dev/null +++ b/repo/typescript-config/CHANGELOG.md @@ -0,0 +1,20 @@ +# @repo/typescript-config + +## 0.0.1 + +### Patch Changes + +- d5017e8: Beta release for v3 +- 7c6c811: 3.0.0-beta.3 with fixes + +## 0.0.1-beta.1 + +### Patch Changes + +- Beta release for v3 + +## 0.0.1-beta.0 + +### Patch Changes + +- 3.0.0-beta.3 with fixes diff --git a/repo/typescript-config/next-css-side-effect.d.ts b/repo/typescript-config/next-css-side-effect.d.ts new file mode 100644 index 000000000..6a748979a --- /dev/null +++ b/repo/typescript-config/next-css-side-effect.d.ts @@ -0,0 +1,2 @@ +/** Plain `.css` side-effect imports (e.g. `app/globals.css`). `*.module.css` is covered by Next. */ +declare module '*.css' {} diff --git a/repo/typescript-config/package.json b/repo/typescript-config/package.json index 27c0e6043..ed931bce6 100644 --- a/repo/typescript-config/package.json +++ b/repo/typescript-config/package.json @@ -1,6 +1,6 @@ { "name": "@repo/typescript-config", - "version": "0.0.0", + "version": "0.0.1", "private": true, "license": "MIT", "publishConfig": { diff --git a/repo/ui/CHANGELOG.md b/repo/ui/CHANGELOG.md new file mode 100644 index 000000000..232f9accb --- /dev/null +++ b/repo/ui/CHANGELOG.md @@ -0,0 +1,20 @@ +# @repo/ui + +## 0.0.1 + +### Patch Changes + +- d5017e8: Beta release for v3 +- 7c6c811: 3.0.0-beta.3 with fixes + +## 0.0.1-beta.1 + +### Patch Changes + +- Beta release for v3 + +## 0.0.1-beta.0 + +### Patch Changes + +- 3.0.0-beta.3 with fixes diff --git a/packages/wallet/dapp-client/eslint.config.mjs b/repo/ui/eslint.config.js similarity index 100% rename from packages/wallet/dapp-client/eslint.config.mjs rename to repo/ui/eslint.config.js diff --git a/repo/ui/eslint.config.mjs b/repo/ui/eslint.config.mjs deleted file mode 100644 index 19170f88e..000000000 --- a/repo/ui/eslint.config.mjs +++ /dev/null @@ -1,4 +0,0 @@ -import { config } from "@repo/eslint-config/react-internal"; - -/** @type {import("eslint").Linter.Config} */ -export default config; diff --git a/repo/ui/package.json b/repo/ui/package.json index 51af743ed..4c3634444 100644 --- a/repo/ui/package.json +++ b/repo/ui/package.json @@ -1,7 +1,8 @@ { "name": "@repo/ui", - "version": "0.0.0", + "version": "0.0.1", "private": true, + "type": "module", "exports": { "./button": "./src/button.tsx", "./card": "./src/card.tsx", @@ -13,16 +14,16 @@ "typecheck": "tsc --noEmit" }, "devDependencies": { - "@repo/eslint-config": "workspace:*", - "@repo/typescript-config": "workspace:*", + "@repo/eslint-config": "workspace:^", + "@repo/typescript-config": "workspace:^", "@turbo/gen": "^1.13.4", - "@types/node": "^20.17.57", - "@types/react": "18.3.0", - "@types/react-dom": "18.3.1", - "typescript": "5.5.4" + "@types/node": "^25.3.0", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "typescript": "^6.0.3" }, "dependencies": { - "react": "^19.1.0", - "react-dom": "^19.1.0" + "react": "^19.2.3", + "react-dom": "^19.2.3" } } diff --git a/turbo.json b/turbo.json index 563d48c60..139d2ea57 100644 --- a/turbo.json +++ b/turbo.json @@ -1,5 +1,5 @@ { - "$schema": "https://turbo.build/schema.json", + "$schema": "https://v2-8-10.turborepo.dev/schema.json", "ui": "tui", "tasks": { "build": { @@ -25,7 +25,9 @@ "repo/**/*.ts", "repo/**/*.tsx", "test/**/*.ts", - "test/**/*.tsx" + "test/**/*.tsx", + "test/**/*.js", + "test/**/*.mjs" ], "outputs": [] },