From bc08669c024cab4dfab5c4eaa48a377ce992a3bb Mon Sep 17 00:00:00 2001 From: Zuhwa Date: Fri, 12 Jun 2026 14:10:13 +0800 Subject: [PATCH 01/24] feat(wallet): add Solana wallet support with commands for address, balance, message signing, and transfers --- README.md | 31 +- SKILL.md | 10 + package-lock.json | 979 +--------------------------------------- package.json | 2 +- src/commands/wallet.ts | 199 +++++++- src/lib/agentFactory.ts | 81 ++++ src/lib/api/agent.ts | 2 + src/lib/chains.ts | 21 + src/lib/errors.ts | 2 + src/lib/walletGate.ts | 18 +- 10 files changed, 384 insertions(+), 961 deletions(-) diff --git a/README.md b/README.md index 640c7be..1defa2b 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The CLI is organized around two pillars. They're independent — use whichever ( Operate an agent as a first-class economic actor, even if you never touch the marketplace. -- **[Wallet](#wallet)** — EVM wallet per agent, with balances, message/typed-data signing, transaction broadcast, and on-ramp topup via Coinbase, card, or QR. +- **[Wallet](#wallet)** — EVM wallet per agent, with balances, message/typed-data signing, transaction broadcast, and on-ramp topup via Coinbase, card, or QR. Plus a Solana wallet (`wallet sol`) for SOL/SPL balances, transfers, and message signing. - **[Agent Email](#agent-email)** — provision a dedicated inbox for the agent, send/receive/search mail, view threads, extract OTPs and links, download attachments. - **[Agent Card](#agent-card)** — issue single-use virtual cards backed by agentcard.ai using a spend-request model with Stripe-attached payment methods, spend limits, and 3DS challenge handling. - **[Signers](#agent-management)** — P256 keys stored in the OS keychain, approved via browser flow. @@ -281,6 +281,35 @@ acp wallet topup --chain-id 8453 --method card --amount 50 --email user@example. acp wallet topup --chain-id 8453 --method qr ``` +#### Solana wallet (`wallet sol`) + +The agent's Privy wallet also holds a Solana address (signed by the same key). Solana operations live under `wallet sol`. The cluster is implied by the environment (`IS_TESTNET` → devnet, otherwise mainnet) with an optional `--cluster devnet|mainnet` override — there's no `--chain-id` here. + +```bash +# Show the agent's Solana address +acp wallet sol address + +# SOL + SPL token balances +acp wallet sol balance + +# Sign a plaintext message (returns a base58 signature) +acp wallet sol sign-message --message "hello world" + +# Send SOL (amount is in SOL) +acp wallet sol transfer --to --amount 0.01 + +# Send an SPL token (amount in token units; the recipient's token account is +# created automatically if it doesn't exist yet) +acp wallet sol transfer --to --amount 1 --token + +# Send a raw instruction set (advanced) — JSON array of +# { programAddress, accounts: [{ address, role }], data } (data is base64 or 0x-hex; +# role is writable_signer | writable | readonly_signer | readonly) +acp wallet sol send-instructions --instructions '[{"programAddress":"…","accounts":[],"data":""}]' +``` + +`transfer`, `sign-message`, and `send-instructions` require a signer (`acp agent add-signer`); they sign through the ACP server. `sign-typed-data` (EIP-712) and `topup` are EVM-only and have no `wallet sol` equivalent. + ### Agent Email Each agent can provision a dedicated email identity, send and receive email, diff --git a/SKILL.md b/SKILL.md index b7e8d34..eac219b 100644 --- a/SKILL.md +++ b/SKILL.md @@ -149,6 +149,16 @@ If you're unsure which the human wants, ask before running. | `acp wallet sign-typed-data --data --chain-id --json` | Sign EIP-712 (signer required) | `{signature}` | | `acp wallet send-transaction --chain-id --to [--value ] [--data ] --json` | Broadcast (signer + dashboard prerequisites — see callout below) | `{transactionHash}` | +**Solana wallet** (`acp wallet sol …`). Same agent, its Solana address (same signer). No `--chain-id` — the cluster is implied by `IS_TESTNET` (devnet on testnet, else mainnet), override with `--cluster devnet|mainnet`. Amounts are human units (SOL, or token units). `transfer`/`sign-message`/`send-instructions` need a signer; `sign-typed-data`/`topup` are EVM-only. + +| Command | What it does | Response shape | +|---|---|---| +| `acp wallet sol address --json` | Show the agent's Solana address | `{address}` | +| `acp wallet sol balance [--cluster ] --json` | SOL + SPL balances (server-side) | `{chainId, network, address, tokens:[{tokenAddress, tokenBalance, tokenMetadata:{symbol, name, decimals}}]}` (native SOL has `tokenAddress:null`; `tokenBalance` raw, decimal-shift by `decimals`) | +| `acp wallet sol sign-message --message [--cluster ] --json` | Sign plaintext (signer required) | `{signature}` (base58) | +| `acp wallet sol transfer --to --amount [--token ] [--cluster ] --json` | Send SOL, or an SPL token with `--token` (auto-creates the recipient's token account) | `{signature}` | +| `acp wallet sol send-instructions --instructions [--cluster ] --json` | Send a raw instruction set (advanced); `` = `[{programAddress, accounts:[{address, role}], data}]`, `data` base64/0x-hex, `role` ∈ writable_signer\|writable\|readonly_signer\|readonly | `{signature}` | + > **CRITICAL — YOU run topup; never tell the human to run it.** When the wallet needs funds, **you (the agent) run the topup command yourself** and relay the resulting link. Do **NOT** print a command like `acp wallet topup --chain-id 8453` and ask the human to run it — that is the single most common failure here. The human's only job is to click the link you give them; they should never touch the CLI. > > Concretely, when the wallet is empty (or a command fails for lack of funds): diff --git a/package-lock.json b/package-lock.json index dc741ed..1fa963a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@privy-io/node": "^0.11.0", "@virtuals-protocol/acp-node": "^0.3.0-beta.40", - "@virtuals-protocol/acp-node-v2": "^0.1.3", + "@virtuals-protocol/acp-node-v2": "^0.1.4", "ajv": "^8.18.0", "commander": "^13.0.0", "cross-keychain": "^1.1.0", @@ -35,6 +35,28 @@ "node": ">=20.19.0" } }, + "../acp-node-v2": { + "name": "@virtuals-protocol/acp-node-v2", + "version": "0.1.4", + "license": "ISC", + "dependencies": { + "@account-kit/infra": "^4.84.1", + "@alchemy/wallet-apis": "5.0.0-beta.9", + "@privy-io/node": "^0.16.0", + "@solana/kit": "5.1.0", + "ajv": "^8.18.0", + "ajv-formats": "^3.0.1", + "eventsource": "^4.1.0", + "ox": "^0.14.17", + "socket.io-client": "^4.8.3", + "viem": "^2.47.0" + }, + "devDependencies": { + "@types/node": "^25.3.3", + "dotenv": "^17.3.1", + "typescript": "^5.9.3" + } + }, "node_modules/@aa-sdk/core": { "version": "4.87.0", "resolved": "https://registry.npmjs.org/@aa-sdk/core/-/core-4.87.0.tgz", @@ -125,132 +147,6 @@ "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", "license": "MIT" }, - "node_modules/@alchemy/common": { - "version": "5.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@alchemy/common/-/common-5.0.0-beta.9.tgz", - "integrity": "sha512-gdqTtDvr9szw1fm9lAd2Sj6FmlvYQFyVYgUeiajiKtABLWB9iStsYlErRJu/2Wg9kM4wCjEuKlwT2ibZoZl2cw==", - "license": "MIT", - "dependencies": { - "zod": "^3.23.0" - }, - "peerDependencies": { - "viem": "^2.45.0" - } - }, - "node_modules/@alchemy/wallet-api-types": { - "version": "0.1.0-alpha.26", - "resolved": "https://registry.npmjs.org/@alchemy/wallet-api-types/-/wallet-api-types-0.1.0-alpha.26.tgz", - "integrity": "sha512-bOA7BNMyYEDPoNC/f9gAPqmOfALioRnfQhipIhP8nRLyvsFC9cadBZyhf3s1nsccIonfqXn9LlbclZEMfzz49g==", - "dependencies": { - "@alchemy/common": "0.0.0-alpha.13", - "deep-equal": "^2.2.3", - "ox": "^0.6.12", - "typebox": "^1.0.81", - "viem": "^2.32.0" - }, - "peerDependencies": { - "typescript": "^5.8.2" - } - }, - "node_modules/@alchemy/wallet-api-types/node_modules/@alchemy/common": { - "version": "0.0.0-alpha.13", - "resolved": "https://registry.npmjs.org/@alchemy/common/-/common-0.0.0-alpha.13.tgz", - "integrity": "sha512-2YRLIeswvdiVHCH245SefRwQgBw3hGRsKWIElhBmcwNOT5QuonTRmORSamffW7SstVwrGS6L7Cr3WXc1iZHfpA==", - "license": "MIT", - "dependencies": { - "viem": "^2.32.0" - } - }, - "node_modules/@alchemy/wallet-api-types/node_modules/ox": { - "version": "0.6.12", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.12.tgz", - "integrity": "sha512-78hziRGLj0qTDa0UW4+ynv9tW2Cp1vmCfGokL8D7kiSDh6Y0LAfHL+HaDN4l2a9jcrOG3fexTDtLNtDNkEwLtg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@adraffy/ens-normalize": "^1.10.1", - "@noble/curves": "^1.6.0", - "@noble/hashes": "^1.5.0", - "@scure/bip32": "^1.5.0", - "@scure/bip39": "^1.4.0", - "abitype": "^1.0.6", - "eventemitter3": "5.0.1" - }, - "peerDependencies": { - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@alchemy/wallet-apis": { - "version": "5.0.0-beta.9", - "resolved": "https://registry.npmjs.org/@alchemy/wallet-apis/-/wallet-apis-5.0.0-beta.9.tgz", - "integrity": "sha512-v1czOSBBLSWVU9vklr3vDbOtSK0d3N928KsP65lYoz8W2cxD5JG7CBTpw1wKayIY5SfdL53CqTtQVe12oSBy/A==", - "license": "MIT", - "dependencies": { - "@alchemy/common": "5.0.0-beta.9", - "@alchemy/wallet-api-types": "0.1.0-alpha.26", - "deep-equal": "^2.2.3", - "ox": "^0.11.1", - "typebox": "^1.0.81" - }, - "peerDependencies": { - "viem": "^2.45.0" - } - }, - "node_modules/@alchemy/wallet-apis/node_modules/@noble/curves": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", - "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.8.0" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@alchemy/wallet-apis/node_modules/ox": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.11.3.tgz", - "integrity": "sha512-1bWYGk/xZel3xro3l8WGg6eq4YEKlaqvyMtVhfMFpbJzK2F6rj4EDRtqDCWVEJMkzcmEi9uW2QxsqELokOlarw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@adraffy/ens-normalize": "^1.11.0", - "@noble/ciphers": "^1.3.0", - "@noble/curves": "1.9.1", - "@noble/hashes": "^1.8.0", - "@scure/bip32": "^1.7.0", - "@scure/bip39": "^1.6.0", - "abitype": "^1.2.3", - "eventemitter3": "5.0.1" - }, - "peerDependencies": { - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/@babel/runtime": { "version": "7.29.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", @@ -2762,66 +2658,8 @@ } }, "node_modules/@virtuals-protocol/acp-node-v2": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@virtuals-protocol/acp-node-v2/-/acp-node-v2-0.1.3.tgz", - "integrity": "sha512-GLmX9lJ2T2177lSIhHILG+pH5LPTK/SUO1HaL949sHODVUw6y3uc5EcLzr71PEbiSW0lrDopHFt8ytOLv8OGhQ==", - "license": "ISC", - "dependencies": { - "@account-kit/infra": "^4.84.1", - "@alchemy/wallet-apis": "5.0.0-beta.9", - "@privy-io/node": "^0.11.0", - "ajv": "^8.18.0", - "ajv-formats": "^3.0.1", - "eventsource": "^4.1.0", - "ox": "^0.14.17", - "socket.io-client": "^4.8.3", - "viem": "^2.47.0" - } - }, - "node_modules/@virtuals-protocol/acp-node-v2/node_modules/@noble/curves": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", - "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.8.0" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@virtuals-protocol/acp-node-v2/node_modules/ox": { - "version": "0.14.20", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.20.tgz", - "integrity": "sha512-rby38C3nDn8eQkf29Zgw4hkCZJ64Qqi0zRPWL8ENUQ7JVuoITqrVtwWQgM/He19SCMUEc7hS/Sjw0jIOSLJhOw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@adraffy/ens-normalize": "^1.11.0", - "@noble/ciphers": "^1.3.0", - "@noble/curves": "1.9.1", - "@noble/hashes": "^1.8.0", - "@scure/bip32": "^1.7.0", - "@scure/bip39": "^1.6.0", - "abitype": "^1.2.3", - "eventemitter3": "5.0.1" - }, - "peerDependencies": { - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } + "resolved": "../acp-node-v2", + "link": true }, "node_modules/@virtuals-protocol/acp-node/node_modules/@noble/curves": { "version": "1.9.1", @@ -2937,23 +2775,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, "node_modules/alchemy-sdk": { "version": "3.6.5", "resolved": "https://registry.npmjs.org/alchemy-sdk/-/alchemy-sdk-3.6.5.tgz", @@ -3008,43 +2829,12 @@ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", "license": "MIT" }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/axios": { "version": "1.14.0", "resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz", @@ -3193,24 +2983,6 @@ "node": ">=8" } }, - "node_modules/call-bind": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", - "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "get-intrinsic": "^1.3.0", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -3224,22 +2996,6 @@ "node": ">= 0.4" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/canonicalize": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/canonicalize/-/canonicalize-2.1.0.tgz", @@ -3396,72 +3152,6 @@ } } }, - "node_modules/deep-equal": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", - "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.5", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.2", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/delay": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", @@ -3588,26 +3278,6 @@ "node": ">= 0.4" } }, - "node_modules/es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -3769,27 +3439,6 @@ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "license": "MIT" }, - "node_modules/eventsource": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-4.1.0.tgz", - "integrity": "sha512-2GuF51iuHX6A9xdTccMTsNb7VO0lHZihApxhvQzJB5A03DvHDd2FQepodbMaztPBmBcE/ox7o2gqaxGhYB9LhQ==", - "license": "MIT", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", - "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/ext": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", @@ -3892,21 +3541,6 @@ } } }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", @@ -3946,15 +3580,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -4017,30 +3642,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -4156,112 +3757,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-arguments": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", - "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -4271,112 +3766,6 @@ "node": ">=8" } }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -4384,40 +3773,6 @@ "license": "MIT", "optional": true }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "license": "MIT" - }, "node_modules/isomorphic-ws": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", @@ -4789,63 +4144,6 @@ "node": ">=0.10.0" } }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-is": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", - "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/ox": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.0.tgz", @@ -4935,15 +4233,6 @@ "pathe": "^2.0.1" } }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/postcss-load-config": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", @@ -5016,26 +4305,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -5177,23 +4446,6 @@ "license": "MIT", "optional": true }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -5207,110 +4459,6 @@ "license": "MIT", "optional": true }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -5370,19 +4518,6 @@ "fast-sha256": "^1.3.0" } }, - "node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/stream-chain": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", @@ -5639,12 +4774,6 @@ "license": "ISC", "optional": true }, - "node_modules/typebox": { - "version": "1.1.37", - "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.37.tgz", - "integrity": "sha512-jb7jp6KvOvvy5sd+11AfJ0/e0F0AS9RcOXd55oGi2ZnRHIGmFvrTaNF+ZidRmGBmmNTkM5KKl0Z37KzxJ+owEQ==", - "license": "MIT" - }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -5840,64 +4969,6 @@ "webidl-conversions": "^3.0.0" } }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "license": "MIT", - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.20", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", - "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", diff --git a/package.json b/package.json index b62ef1f..c4c14b0 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "dependencies": { "@privy-io/node": "^0.11.0", "@virtuals-protocol/acp-node": "^0.3.0-beta.40", - "@virtuals-protocol/acp-node-v2": "^0.1.3", + "@virtuals-protocol/acp-node-v2": "^0.1.4", "ajv": "^8.18.0", "commander": "^13.0.0", "cross-keychain": "^1.1.0", diff --git a/src/commands/wallet.ts b/src/commands/wallet.ts index 651697e..5969f4a 100644 --- a/src/commands/wallet.ts +++ b/src/commands/wallet.ts @@ -1,19 +1,45 @@ import type { Command } from "commander"; import * as readline from "readline"; -import { formatUnits, isAddress, isHex } from "viem"; +import { formatUnits, parseUnits, isAddress, isHex } from "viem"; +import { + buildSolTransferIx, + buildSplTransferInstructions, + getSplTokenBalance, + AccountRole, + type SolanaInstructionLike, +} from "@virtuals-protocol/acp-node-v2"; import { isJson, outputResult, outputError, isTTY } from "../lib/output"; -import { getWalletAddress } from "../lib/agentFactory"; +import { getWalletAddress, getSolanaWalletAddress } from "../lib/agentFactory"; import { getClient } from "../lib/api/client"; import { getAgentId, getActiveWallet } from "../lib/config"; import { CHAIN_NETWORK_MAP } from "../lib/api/agent"; import { CliError } from "../lib/errors"; -import { assertSponsoredChainId } from "../lib/chains"; +import { assertSponsoredChainId, solanaChainId } from "../lib/chains"; import { c } from "../lib/color"; import { openBrowser } from "../lib/browser"; import { selectOption, prompt } from "../lib/prompt"; -import { withApprovalGate } from "../lib/walletGate"; +import { withApprovalGate, withSolanaWallet } from "../lib/walletGate"; import qrcode from "qrcode-terminal"; +// Address type accepted by the SDK Solana helpers (branded), derived without a +// direct @solana/kit import. +type SolAddr = Parameters[0]; + +const ACCOUNT_ROLE_BY_NAME: Record = { + writable_signer: AccountRole.WRITABLE_SIGNER, + writable: AccountRole.WRITABLE, + readonly_signer: AccountRole.READONLY_SIGNER, + readonly: AccountRole.READONLY, +}; + +// Instruction data accepts hex (0x…) or base64. +function decodeIxData(data: string): Uint8Array { + const buf = data.startsWith("0x") + ? Buffer.from(data.slice(2), "hex") + : Buffer.from(data, "base64"); + return Uint8Array.from(buf); +} + // In --json mode the funding URL goes to stdout as JSON for machine parsing, // but many agent harnesses buffer or suppress stdout while passing stderr // through to the human. Mirroring a plain, copy-pasteable line to stderr @@ -414,4 +440,169 @@ export function registerWalletCommands(program: Command): void { outputError(json, err instanceof Error ? err : String(err)); } }); + + // ----------------------------------------------------------------------- + // Solana wallet (`wallet sol …`). The cluster is implied by IS_TESTNET + // (devnet on testnet, mainnet otherwise); `--cluster` overrides. No chain-id. + // ----------------------------------------------------------------------- + const sol = wallet.command("sol").description("Solana wallet commands"); + + sol + .command("address") + .description("Show the active agent's Solana address") + .action(async (_opts, cmd) => { + const json = isJson(cmd); + try { + const address = await getSolanaWalletAddress(); + outputResult(json, { address }); + } catch (err) { + outputError(json, err instanceof Error ? err : String(err)); + } + }); + + sol + .command("balance") + .description("Show SOL + SPL token balances") + .option("--cluster ", "devnet | mainnet (default from env)") + .action(async (opts, cmd) => { + const json = isJson(cmd); + try { + const chainId = solanaChainId(opts.cluster); + const network = CHAIN_NETWORK_MAP[chainId]!; + const activeWallet = getActiveWallet(); + const agentId = activeWallet ? getAgentId(activeWallet) : undefined; + if (!agentId) { + throw new CliError( + "Agent ID not found for active wallet.", + "NO_ACTIVE_AGENT", + "Run `acp agent list` or `acp agent use` to set an active agent." + ); + } + const address = await getSolanaWalletAddress(); + const { agentApi } = await getClient(); + const assets = await agentApi.getAgentAssets(agentId, [network]); + const tokens = assets.data.tokens; + + if (json) { + outputResult(json, { chainId, network, address, tokens }); + return; + } + + console.log(`\n${c.bold(`Solana balance on ${network}`)}\n`); + console.log(` ${c.bold("Address:")} ${c.dim(address)}\n`); + if (tokens.length === 0) { + console.log(" No tokens found.\n"); + } else { + for (const t of tokens) { + const isNative = t.tokenAddress === null; + const symbol = t.tokenMetadata.symbol ?? (isNative ? "SOL" : "???"); + const decimals = t.tokenMetadata.decimals ?? (isNative ? 9 : 0); + const balance = formatUnits(BigInt(t.tokenBalance), decimals); + console.log(` ${c.cyan(symbol.padEnd(10))}${balance}`); + } + console.log(""); + } + } catch (err) { + outputError(json, err instanceof Error ? err : String(err)); + } + }); + + sol + .command("sign-message") + .description("Sign a plaintext message with the Solana wallet") + .requiredOption("--message ", "Message to sign") + .option("--cluster ", "devnet | mainnet (default from env)") + .action(async (opts, cmd) => { + const json = isJson(cmd); + try { + const chainId = solanaChainId(opts.cluster); + const signature = await withSolanaWallet(chainId, (p) => + p.signMessage(opts.message) + ); + outputResult(json, { signature }); + } catch (err) { + outputError(json, err instanceof Error ? err : String(err)); + } + }); + + sol + .command("transfer") + .description("Send SOL, or an SPL token with --token") + .requiredOption("--to
", "Recipient address") + .requiredOption("--amount ", "Amount in human units (e.g. 0.001)") + .option("--token ", "SPL mint address (omit for native SOL)") + .option("--cluster ", "devnet | mainnet (default from env)") + .action(async (opts, cmd) => { + const json = isJson(cmd); + try { + const chainId = solanaChainId(opts.cluster); + const signature = await withSolanaWallet(chainId, async (provider) => { + const me = (await provider.getAddress()) as SolAddr; + const to = opts.to as SolAddr; + if (opts.token) { + const mint = opts.token as SolAddr; + const { decimals } = await getSplTokenBalance( + provider.getRpc(), + me, + mint + ); + const amount = parseUnits(opts.amount, decimals); + const ixs = await buildSplTransferInstructions({ + owner: me, + recipient: to, + mint, + amount, + payer: me, + }); + return provider.sendInstructions(ixs); + } + const lamports = parseUnits(opts.amount, 9); + return provider.sendInstructions([buildSolTransferIx(me, to, lamports)]); + }); + outputResult(json, { signature }); + } catch (err) { + outputError(json, err instanceof Error ? err : String(err)); + } + }); + + sol + .command("send-instructions") + .description("Send a raw Solana instruction set (advanced)") + .requiredOption( + "--instructions ", + 'JSON array: [{ programAddress, accounts: [{ address, role }], data }]' + ) + .option("--cluster ", "devnet | mainnet (default from env)") + .action(async (opts, cmd) => { + const json = isJson(cmd); + try { + const chainId = solanaChainId(opts.cluster); + const parsed = JSON.parse(opts.instructions) as Array<{ + programAddress: string; + accounts: { address: string; role: string }[]; + data: string; + }>; + const ixs: SolanaInstructionLike[] = parsed.map((ix) => ({ + programAddress: ix.programAddress as SolAddr, + accounts: ix.accounts.map((a) => { + const role = ACCOUNT_ROLE_BY_NAME[a.role.toLowerCase()]; + if (role === undefined) { + throw new CliError( + `Unknown account role "${a.role}".`, + "VALIDATION_ERROR", + "Use writable_signer | writable | readonly_signer | readonly." + ); + } + return { address: a.address as SolAddr, role }; + }), + data: decodeIxData(ix.data), + })); + const signature = await withSolanaWallet(chainId, (p) => + p.sendInstructions(ixs) + ); + outputResult(json, { signature }); + } catch (err) { + outputError(json, err instanceof Error ? err : String(err)); + } + }); } diff --git a/src/lib/agentFactory.ts b/src/lib/agentFactory.ts index 8682378..06e8ab6 100644 --- a/src/lib/agentFactory.ts +++ b/src/lib/agentFactory.ts @@ -10,9 +10,11 @@ import { TESTNET_PRIVY_APP_ID, SseTransport, AcpApiClient, + PrivySolanaProviderAdapter, } from "@virtuals-protocol/acp-node-v2"; import type { IEvmProviderAdapter, + ISolanaProviderAdapter, SupportedStreams, } from "@virtuals-protocol/acp-node-v2"; import { @@ -227,3 +229,82 @@ export function getWalletAddress(): string { } return addr; } + +// --------------------------------------------------------------------------- +// Solana wallet +// --------------------------------------------------------------------------- + +/** + * Resolve the agent's Solana address + Privy wallet id from the server. The + * Privy wallet hosts both the EVM and Solana addresses under the same signer, + * so the same P256 signFn authorizes Solana operations too. + */ +async function getSolanaWalletInfo( + walletAddress: string +): Promise<{ solWalletAddress: string; walletId: string }> { + const { agentApi } = await getClient(); + const agentList = await agentApi.list(); + const agent = agentList.data.find((a) => a.walletAddress === walletAddress); + if (!agent) { + throw new CliError( + `Agent not found for wallet address: ${walletAddress}`, + "AGENT_NOT_FOUND" + ); + } + const solProvider = agent.walletProviders.find( + (wp) => wp.chainType === "SOLANA" + ); + if (!agent.solWalletAddress || !solProvider?.metadata.walletId) { + throw new CliError( + "This agent has no Solana wallet.", + "NO_SOLANA_WALLET", + "The agent's Privy wallet has no Solana provider configured." + ); + } + return { + solWalletAddress: agent.solWalletAddress, + walletId: solProvider.metadata.walletId, + }; +} + +/** The active agent's Solana address. */ +export async function getSolanaWalletAddress(): Promise { + const { solWalletAddress } = await getSolanaWalletInfo(getWalletAddress()); + return solWalletAddress; +} + +/** + * Build a Solana provider adapter for the active agent, reusing the same + * P256 signFn as the EVM provider (RPC + signing are routed through the ACP + * server proxy / Privy). + */ +export async function createSolanaProviderAdapter( + chainId: number +): Promise { + const isTestnet = process.env.IS_TESTNET === "true"; + const serverUrl = isTestnet ? ACP_TESTNET_SERVER_URL : ACP_SERVER_URL; + const privyAppId = isTestnet ? TESTNET_PRIVY_APP_ID : PRIVY_APP_ID; + + const walletAddress = getWalletAddress(); + const publicKey = getPublicKey(walletAddress); + if (!publicKey) { + throw new CliError( + "No signer configured for this agent.", + "NO_SIGNER", + "Run `acp agent add-signer` to generate and register a signing key." + ); + } + + const { solWalletAddress, walletId } = + await getSolanaWalletInfo(walletAddress); + const signFn = createSignFn(publicKey); + + return PrivySolanaProviderAdapter.create({ + walletAddress: solWalletAddress, + walletId, + signFn, + chainId, + serverUrl, + privyAppId, + }); +} diff --git a/src/lib/api/agent.ts b/src/lib/api/agent.ts index 7a516c3..32a0dab 100644 --- a/src/lib/api/agent.ts +++ b/src/lib/api/agent.ts @@ -575,6 +575,8 @@ export const CHAIN_NETWORK_MAP: Record = { 137: "polygon-mainnet", 10: "opt-mainnet", 143: "monad-mainnet", + 500: "solana-devnet", + 501: "solana-mainnet", }; export interface UpdateAgentBody { diff --git a/src/lib/chains.ts b/src/lib/chains.ts index 2f0b047..ff6c174 100644 --- a/src/lib/chains.ts +++ b/src/lib/chains.ts @@ -1,9 +1,30 @@ import { ERC20_SPONSORED_CHAINS, getEvmChainByChainId, + SOLANA_DEVNET_CHAIN_ID, + SOLANA_MAINNET_CHAIN_ID, } from "@virtuals-protocol/acp-node-v2"; import { CliError } from "./errors"; +// Resolve the Solana chainId for the `wallet sol` commands. The cluster is +// implied by the environment (testnet → devnet, mainnet → mainnet), with an +// optional explicit override. The user never passes a numeric chain id. +export function solanaChainId(cluster?: string): number { + if (cluster) { + const c = cluster.toLowerCase(); + if (c === "devnet") return SOLANA_DEVNET_CHAIN_ID; + if (c === "mainnet" || c === "mainnet-beta") return SOLANA_MAINNET_CHAIN_ID; + throw new CliError( + `Unknown Solana cluster "${cluster}".`, + "VALIDATION_ERROR", + "Use --cluster devnet or --cluster mainnet." + ); + } + return process.env.IS_TESTNET === "true" + ? SOLANA_DEVNET_CHAIN_ID + : SOLANA_MAINNET_CHAIN_ID; +} + export const SPONSORED_CHAIN_IDS = ERC20_SPONSORED_CHAINS.map( (chain) => chain.id ); diff --git a/src/lib/errors.ts b/src/lib/errors.ts index 6b033c9..d7dda8c 100644 --- a/src/lib/errors.ts +++ b/src/lib/errors.ts @@ -2,6 +2,8 @@ export type ErrorCode = | "NOT_AUTHENTICATED" | "NO_ACTIVE_AGENT" | "NO_SIGNER" + | "AGENT_NOT_FOUND" + | "NO_SOLANA_WALLET" | "SESSION_NOT_FOUND" | "VALIDATION_ERROR" | "API_ERROR" diff --git a/src/lib/walletGate.ts b/src/lib/walletGate.ts index 6b4769d..40089d6 100644 --- a/src/lib/walletGate.ts +++ b/src/lib/walletGate.ts @@ -1,8 +1,13 @@ import { STREAMS, type IEvmProviderAdapter, + type ISolanaProviderAdapter, } from "@virtuals-protocol/acp-node-v2"; -import { createProviderAdapter, createSseTransport } from "./agentFactory"; +import { + createProviderAdapter, + createSolanaProviderAdapter, + createSseTransport, +} from "./agentFactory"; export async function withApprovalGate( fn: (provider: IEvmProviderAdapter) => Promise @@ -15,3 +20,14 @@ export async function withApprovalGate( void Promise.resolve(transport.disconnect()).catch(() => {}); } } + +// Solana wallet operations sign + broadcast through the ACP server proxy / +// Privy directly (no EVM-style approval SSE stream), so this simply builds the +// Solana provider for the resolved chainId and runs the operation. +export async function withSolanaWallet( + chainId: number, + fn: (provider: ISolanaProviderAdapter) => Promise +): Promise { + const provider = await createSolanaProviderAdapter(chainId); + return fn(provider); +} From 484cb9863506da1f72a88433a564696ba77cf578 Mon Sep 17 00:00:00 2001 From: brianna Date: Fri, 12 Jun 2026 18:59:20 +0800 Subject: [PATCH 02/24] =?UTF-8?q?feat(trade):=20sign=20Solana=20trade=20le?= =?UTF-8?q?gs=20=E2=80=94=20ownership=20proofs=20(base64)=20and=20versione?= =?UTF-8?q?d=20txs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wires the trade loop to the backend's new Solana sign actions: - sigType 'solana-message': Privy signs the raw challenge bytes (no envelope); the adapter's base58 output is re-encoded to BASE64 — the Treasures ownership-proof contract (base58 is their #1 documented rejection cause). - sigType 'solana-tx': sign the serialized versioned tx WITHOUT broadcasting (server/venue broadcast the signed bytes), via the adapter's Privy signTransaction (base64 in/out). - solWallet rides every /plan that could route through Solana: explicit sol venue/source, or a tokenized-stock BUY with no venue pinned — the backend then quotes both venues and executes the better one. Sells stay explicit (the backend can't see which venue holds shares). Verified against the live Privy signer for Mochi3DSTest: the signature checks out as raw ed25519 over the exact challenge bytes (local ed25519.verify — the same check Treasures runs server-side). Co-Authored-By: Claude Fable 5 --- src/commands/trade.ts | 107 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 98 insertions(+), 9 deletions(-) diff --git a/src/commands/trade.ts b/src/commands/trade.ts index 77df08b..3ddde69 100644 --- a/src/commands/trade.ts +++ b/src/commands/trade.ts @@ -47,6 +47,8 @@ import { CliError, type ErrorCode } from "../lib/errors"; import { getApiContext } from "../lib/api/client"; import { createProviderAdapter, + createSolanaProviderAdapter, + getSolanaWalletAddress, getWalletAddress, } from "../lib/agentFactory"; import type { IEvmProviderAdapter } from "@virtuals-protocol/acp-node-v2"; @@ -68,13 +70,60 @@ interface SendAction { interface SignAction { kind: "sign"; label: string; - sigType: "personal" | "eip712"; + // solana-message: ed25519 over the UTF-8 bytes of `message`, posted back + // BASE64-encoded (Treasures' ownership-proof contract — not base58). + // solana-tx: sign the serialized versioned tx in `txBase64` WITHOUT + // broadcasting; post back the fully-signed tx, base64 (the server or the + // venue broadcasts the signed bytes). + sigType: "personal" | "eip712" | "solana-message" | "solana-tx"; chainId: number; - message?: string; // personal_sign + message?: string; // personal_sign / solana-message typedData?: unknown; // EIP-712 + txBase64?: string; // solana-tx expectedSignKind?: string; timeoutMs?: number; } + +// The methods the trade loop needs from the Privy Solana adapter. They exist +// at runtime on PrivySolanaProviderAdapter; the published .d.ts lags behind, +// hence the local shape + cast at the create site. +type SolanaTradeSigner = { + signMessage(message: string): Promise; // base58-encoded signature + signTransactionViaPrivy(txBase64: string): Promise; // signed tx, base64 +}; + +// Solana mainnet Privy chain id. Trade legs are always mainnet — Treasures +// staging settles against mainnet, and LiFi Solana legs are mainnet-only. +const SOLANA_MAINNET_PRIVY_CHAIN_ID = 501; + +// Backend chain references that mean Solana (the LiFi chain id + aliases). +function isSolanaChainRef(v: unknown): boolean { + if (v === undefined) return false; + const s = String(v).trim().toLowerCase(); + return s === "sol" || s === "solana" || s === "1151111081099710"; +} + +// Minimal base58 decode (Solana alphabet) — the adapter returns base58 +// signatures but the trade wire format is base64 of the raw bytes. +const B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; +function base58Decode(s: string): Uint8Array { + let n = 0n; + for (const c of s) { + const i = B58_ALPHABET.indexOf(c); + if (i < 0) throw new Error(`invalid base58 character: ${c}`); + n = n * 58n + BigInt(i); + } + const bytes: number[] = []; + while (n > 0n) { + bytes.unshift(Number(n & 0xffn)); + n >>= 8n; + } + for (const c of s) { + if (c === "1") bytes.unshift(0); + else break; + } + return Uint8Array.from(bytes); +} interface WaitAction { kind: "wait"; label: string; @@ -296,6 +345,25 @@ async function runTrade(opts: Record, json: boolean): Promise> { let action = plan.action; let step = plan.step; + // Created on the first Solana sign action and reused for the trade's + // remaining legs (proof + tx legs share one adapter). + let solSigner: SolanaTradeSigner | undefined; while (true) { if (action.kind === "done") return action.result; @@ -361,15 +432,33 @@ export async function runTradeLoop( } } else if (action.kind === "sign") { // The server asks the CLI to produce a signature (NOT broadcast a tx): - // an EIP-191 personal_sign or an EIP-712 typed-data signature. We sign - // with the keystore-backed signer and post the signature back. Used by - // the Treasures flow (ownership proof + Fusion orders the server submits). + // EIP-191 personal_sign, EIP-712 typed data, or — for Solana trade legs + // — an ed25519 message signature / a signed versioned transaction. We + // sign with the keystore/Privy-backed signer and post the result back; + // the server submits/broadcasts. Used by the Treasures flow (ownership + // proofs + orders) and Solana-source LiFi bridges. progress(json, `[step ${step + 1}] ${action.label}`); try { - const signature = - action.sigType === "eip712" - ? await provider.signTypedData(action.chainId, action.typedData) - : await provider.signMessage(action.chainId, action.message ?? ""); + let signature: string; + if (action.sigType === "solana-message" || action.sigType === "solana-tx") { + solSigner ??= (await createSolanaProviderAdapter( + SOLANA_MAINNET_PRIVY_CHAIN_ID + )) as unknown as SolanaTradeSigner; + if (action.sigType === "solana-message") { + // Privy signs the raw bytes (no envelope); the adapter returns + // base58 but the wire contract is base64 of the raw signature. + const b58 = await solSigner.signMessage(action.message ?? ""); + signature = Buffer.from(base58Decode(b58)).toString("base64"); + } else { + // Sign the serialized versioned tx WITHOUT broadcasting — the + // server (LiFi legs) or the venue (Treasures) broadcasts it. + signature = await solSigner.signTransactionViaPrivy(action.txBase64 ?? ""); + } + } else if (action.sigType === "eip712") { + signature = await provider.signTypedData(action.chainId, action.typedData); + } else { + signature = await provider.signMessage(action.chainId, action.message ?? ""); + } nextBody = { tradeId: plan.tradeId, step, signature }; } catch (err) { const message = err instanceof Error ? err.message : String(err); From d561aef5f99ad32b465c5854f9c2c91a88622850 Mon Sep 17 00:00:00 2001 From: brianna Date: Sat, 13 Jun 2026 21:42:09 +0800 Subject: [PATCH 03/24] feat(trade): run sponsored solana-instructions actions (native-SOL gas top-up) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The backend emits a 'solana-instructions' action before a sol-venue trade when the wallet's native SOL is below the gas floor — a Jupiter USDC→SOL swap. Run it through the adapter's sponsored sendInstructions (Alchemy fee payer) so a zero-SOL wallet can bootstrap, mapping the backend's role strings to AccountRole bitflags and posting back the broadcast signature. Co-Authored-By: Claude Fable 5 --- src/commands/trade.ts | 70 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/commands/trade.ts b/src/commands/trade.ts index 3ddde69..c7c5a87 100644 --- a/src/commands/trade.ts +++ b/src/commands/trade.ts @@ -90,8 +90,34 @@ interface SignAction { type SolanaTradeSigner = { signMessage(message: string): Promise; // base58-encoded signature signTransactionViaPrivy(txBase64: string): Promise; // signed tx, base64 + // Build + sponsor + broadcast a Solana instruction set (Alchemy fee payer), + // returning the broadcast signature. Lets a zero-SOL wallet bootstrap gas. + sendInstructions(instructions: SolTradeInstruction[]): Promise; }; +// The instruction shape the Privy adapter's sendInstructions consumes (mirrors +// `acp wallet sol send-instructions`). Account roles map from the backend's +// role strings; data is base64. +type SolTradeInstruction = { + programAddress: string; + accounts: Array<{ address: string; role: number }>; + data: Uint8Array; +}; +const SOL_ACCOUNT_ROLE: Record = { + // @solana/kit AccountRole bitflags: bit1=writable, bit0=signer. + readonly: 0, + writable: 1, + readonly_signer: 2, + writable_signer: 3, +}; +function decodeSolIxData(data: string): Uint8Array { + return Uint8Array.from( + data.startsWith("0x") + ? Buffer.from(data.slice(2), "hex") + : Buffer.from(data, "base64") + ); +} + // Solana mainnet Privy chain id. Trade legs are always mainnet — Treasures // staging settles against mainnet, and LiFi Solana legs are mainnet-only. const SOLANA_MAINNET_PRIVY_CHAIN_ID = 501; @@ -124,6 +150,18 @@ function base58Decode(s: string): Uint8Array { } return Uint8Array.from(bytes); } +interface SolanaInstructionsAction { + kind: "solana-instructions"; + label: string; + chainId: number; + instructions: Array<{ + programAddress: string; + accounts: Array<{ address: string; role: string }>; + data: string; // base64 + }>; + expectedTxKind?: string; + timeoutMs?: number; +} interface WaitAction { kind: "wait"; label: string; @@ -154,6 +192,7 @@ interface PreviewAction { type Action = | SendAction | SignAction + | SolanaInstructionsAction | WaitAction | DoneAction | ErrorAction @@ -468,6 +507,37 @@ export async function runTradeLoop( error: { code: "SIGN_FAILED", message }, }; } + } else if (action.kind === "solana-instructions") { + // Sponsored Solana instruction set (e.g. the native-SOL gas top-up): the + // adapter attaches the Alchemy fee payer and broadcasts, so a zero-SOL + // wallet can run it. Post back the broadcast signature. + progress(json, `[step ${step + 1}] ${action.label}`); + try { + solSigner ??= (await createSolanaProviderAdapter( + SOLANA_MAINNET_PRIVY_CHAIN_ID + )) as unknown as SolanaTradeSigner; + const ixs: SolTradeInstruction[] = action.instructions.map((ix) => ({ + programAddress: ix.programAddress, + accounts: ix.accounts.map((a) => { + const role = SOL_ACCOUNT_ROLE[a.role]; + if (role === undefined) { + throw new Error(`Unknown Solana account role: ${a.role}`); + } + return { address: a.address, role }; + }), + data: decodeSolIxData(ix.data), + })); + const res = await solSigner.sendInstructions(ixs); + const signature = Array.isArray(res) ? res[res.length - 1] : res; + nextBody = { tradeId: plan.tradeId, step, signature }; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + nextBody = { + tradeId: plan.tradeId, + step, + error: { code: "SIGN_FAILED", message }, + }; + } } else if (action.kind === "wait") { progress(json, `[step ${step + 1}] ${action.label} (waiting ${action.delaySec}s)`); await sleep(action.delaySec * 1000); From de43d63b87cf25807130dbdbe6b569d15279b46d Mon Sep 17 00:00:00 2001 From: brianna Date: Sat, 13 Jun 2026 22:17:31 +0800 Subject: [PATCH 04/24] Revert "feat(trade): run sponsored solana-instructions actions (native-SOL gas top-up)" This reverts commit d561aef5f99ad32b465c5854f9c2c91a88622850. --- src/commands/trade.ts | 70 ------------------------------------------- 1 file changed, 70 deletions(-) diff --git a/src/commands/trade.ts b/src/commands/trade.ts index c7c5a87..3ddde69 100644 --- a/src/commands/trade.ts +++ b/src/commands/trade.ts @@ -90,34 +90,8 @@ interface SignAction { type SolanaTradeSigner = { signMessage(message: string): Promise; // base58-encoded signature signTransactionViaPrivy(txBase64: string): Promise; // signed tx, base64 - // Build + sponsor + broadcast a Solana instruction set (Alchemy fee payer), - // returning the broadcast signature. Lets a zero-SOL wallet bootstrap gas. - sendInstructions(instructions: SolTradeInstruction[]): Promise; }; -// The instruction shape the Privy adapter's sendInstructions consumes (mirrors -// `acp wallet sol send-instructions`). Account roles map from the backend's -// role strings; data is base64. -type SolTradeInstruction = { - programAddress: string; - accounts: Array<{ address: string; role: number }>; - data: Uint8Array; -}; -const SOL_ACCOUNT_ROLE: Record = { - // @solana/kit AccountRole bitflags: bit1=writable, bit0=signer. - readonly: 0, - writable: 1, - readonly_signer: 2, - writable_signer: 3, -}; -function decodeSolIxData(data: string): Uint8Array { - return Uint8Array.from( - data.startsWith("0x") - ? Buffer.from(data.slice(2), "hex") - : Buffer.from(data, "base64") - ); -} - // Solana mainnet Privy chain id. Trade legs are always mainnet — Treasures // staging settles against mainnet, and LiFi Solana legs are mainnet-only. const SOLANA_MAINNET_PRIVY_CHAIN_ID = 501; @@ -150,18 +124,6 @@ function base58Decode(s: string): Uint8Array { } return Uint8Array.from(bytes); } -interface SolanaInstructionsAction { - kind: "solana-instructions"; - label: string; - chainId: number; - instructions: Array<{ - programAddress: string; - accounts: Array<{ address: string; role: string }>; - data: string; // base64 - }>; - expectedTxKind?: string; - timeoutMs?: number; -} interface WaitAction { kind: "wait"; label: string; @@ -192,7 +154,6 @@ interface PreviewAction { type Action = | SendAction | SignAction - | SolanaInstructionsAction | WaitAction | DoneAction | ErrorAction @@ -507,37 +468,6 @@ export async function runTradeLoop( error: { code: "SIGN_FAILED", message }, }; } - } else if (action.kind === "solana-instructions") { - // Sponsored Solana instruction set (e.g. the native-SOL gas top-up): the - // adapter attaches the Alchemy fee payer and broadcasts, so a zero-SOL - // wallet can run it. Post back the broadcast signature. - progress(json, `[step ${step + 1}] ${action.label}`); - try { - solSigner ??= (await createSolanaProviderAdapter( - SOLANA_MAINNET_PRIVY_CHAIN_ID - )) as unknown as SolanaTradeSigner; - const ixs: SolTradeInstruction[] = action.instructions.map((ix) => ({ - programAddress: ix.programAddress, - accounts: ix.accounts.map((a) => { - const role = SOL_ACCOUNT_ROLE[a.role]; - if (role === undefined) { - throw new Error(`Unknown Solana account role: ${a.role}`); - } - return { address: a.address, role }; - }), - data: decodeSolIxData(ix.data), - })); - const res = await solSigner.sendInstructions(ixs); - const signature = Array.isArray(res) ? res[res.length - 1] : res; - nextBody = { tradeId: plan.tradeId, step, signature }; - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - nextBody = { - tradeId: plan.tradeId, - step, - error: { code: "SIGN_FAILED", message }, - }; - } } else if (action.kind === "wait") { progress(json, `[step ${step + 1}] ${action.label} (waiting ${action.delaySec}s)`); await sleep(action.delaySec * 1000); From bf1a6ac6aec467c61bdaf3fcbc9fd2dc07d17705 Mon Sep 17 00:00:00 2001 From: brianna Date: Sun, 14 Jun 2026 04:06:46 +0800 Subject: [PATCH 05/24] fix(trade): address Bugbot review on PR #44 - package-lock: re-resolve @virtuals-protocol/acp-node-v2 to the published 0.1.4 registry tarball instead of the sibling ../acp-node-v2 link, so `npm ci` works on machines without that local path (was breaking CI/releases). - trade: recognize the Privy Solana chain ids (500 devnet / 501 mainnet) in isSolanaChainRef, so a swap with --chain-in 501 attaches solWallet. - trade: route opts.chain through isSolanaChainRef instead of an exact `=== "sol"` match, so a Treasures sell with --chain solana (or other casing) also attaches solWallet. isSolanaChainRef is now the single source of truth. Co-Authored-By: Claude Opus 4.8 (1M context) --- package-lock.json | 3113 ++++++++++++++++++++++++++++++++++++++--- src/commands/trade.ts | 25 +- 2 files changed, 2959 insertions(+), 179 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1fa963a..70ca594 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,28 +35,6 @@ "node": ">=20.19.0" } }, - "../acp-node-v2": { - "name": "@virtuals-protocol/acp-node-v2", - "version": "0.1.4", - "license": "ISC", - "dependencies": { - "@account-kit/infra": "^4.84.1", - "@alchemy/wallet-apis": "5.0.0-beta.9", - "@privy-io/node": "^0.16.0", - "@solana/kit": "5.1.0", - "ajv": "^8.18.0", - "ajv-formats": "^3.0.1", - "eventsource": "^4.1.0", - "ox": "^0.14.17", - "socket.io-client": "^4.8.3", - "viem": "^2.47.0" - }, - "devDependencies": { - "@types/node": "^25.3.3", - "dotenv": "^17.3.1", - "typescript": "^5.9.3" - } - }, "node_modules/@aa-sdk/core": { "version": "4.87.0", "resolved": "https://registry.npmjs.org/@aa-sdk/core/-/core-4.87.0.tgz", @@ -147,6 +125,132 @@ "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", "license": "MIT" }, + "node_modules/@alchemy/common": { + "version": "5.0.0-beta.9", + "resolved": "https://registry.npmjs.org/@alchemy/common/-/common-5.0.0-beta.9.tgz", + "integrity": "sha512-gdqTtDvr9szw1fm9lAd2Sj6FmlvYQFyVYgUeiajiKtABLWB9iStsYlErRJu/2Wg9kM4wCjEuKlwT2ibZoZl2cw==", + "license": "MIT", + "dependencies": { + "zod": "^3.23.0" + }, + "peerDependencies": { + "viem": "^2.45.0" + } + }, + "node_modules/@alchemy/wallet-api-types": { + "version": "0.1.0-alpha.26", + "resolved": "https://registry.npmjs.org/@alchemy/wallet-api-types/-/wallet-api-types-0.1.0-alpha.26.tgz", + "integrity": "sha512-bOA7BNMyYEDPoNC/f9gAPqmOfALioRnfQhipIhP8nRLyvsFC9cadBZyhf3s1nsccIonfqXn9LlbclZEMfzz49g==", + "dependencies": { + "@alchemy/common": "0.0.0-alpha.13", + "deep-equal": "^2.2.3", + "ox": "^0.6.12", + "typebox": "^1.0.81", + "viem": "^2.32.0" + }, + "peerDependencies": { + "typescript": "^5.8.2" + } + }, + "node_modules/@alchemy/wallet-api-types/node_modules/@alchemy/common": { + "version": "0.0.0-alpha.13", + "resolved": "https://registry.npmjs.org/@alchemy/common/-/common-0.0.0-alpha.13.tgz", + "integrity": "sha512-2YRLIeswvdiVHCH245SefRwQgBw3hGRsKWIElhBmcwNOT5QuonTRmORSamffW7SstVwrGS6L7Cr3WXc1iZHfpA==", + "license": "MIT", + "dependencies": { + "viem": "^2.32.0" + } + }, + "node_modules/@alchemy/wallet-api-types/node_modules/ox": { + "version": "0.6.12", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.12.tgz", + "integrity": "sha512-78hziRGLj0qTDa0UW4+ynv9tW2Cp1vmCfGokL8D7kiSDh6Y0LAfHL+HaDN4l2a9jcrOG3fexTDtLNtDNkEwLtg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.10.1", + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0", + "@scure/bip32": "^1.5.0", + "@scure/bip39": "^1.4.0", + "abitype": "^1.0.6", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@alchemy/wallet-apis": { + "version": "5.0.0-beta.9", + "resolved": "https://registry.npmjs.org/@alchemy/wallet-apis/-/wallet-apis-5.0.0-beta.9.tgz", + "integrity": "sha512-v1czOSBBLSWVU9vklr3vDbOtSK0d3N928KsP65lYoz8W2cxD5JG7CBTpw1wKayIY5SfdL53CqTtQVe12oSBy/A==", + "license": "MIT", + "dependencies": { + "@alchemy/common": "5.0.0-beta.9", + "@alchemy/wallet-api-types": "0.1.0-alpha.26", + "deep-equal": "^2.2.3", + "ox": "^0.11.1", + "typebox": "^1.0.81" + }, + "peerDependencies": { + "viem": "^2.45.0" + } + }, + "node_modules/@alchemy/wallet-apis/node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@alchemy/wallet-apis/node_modules/ox": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.11.3.tgz", + "integrity": "sha512-1bWYGk/xZel3xro3l8WGg6eq4YEKlaqvyMtVhfMFpbJzK2F6rj4EDRtqDCWVEJMkzcmEi9uW2QxsqELokOlarw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.2.3", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@babel/runtime": { "version": "7.29.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", @@ -2472,27 +2576,33 @@ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "license": "MIT" }, - "node_modules/@solana/buffer-layout": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz", - "integrity": "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==", + "node_modules/@solana/accounts": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/accounts/-/accounts-5.1.0.tgz", + "integrity": "sha512-Q1KzykCrl/YjLUH2RXF8vPq65U/ehAV2SHZicPbZ0jvgQUU6X1+Eca+0ilxA9xH8srYn3YTVDyEs/LYdfbY/2A==", "license": "MIT", - "optional": true, "dependencies": { - "buffer": "~6.0.3" + "@solana/addresses": "5.1.0", + "@solana/codecs-core": "5.1.0", + "@solana/codecs-strings": "5.1.0", + "@solana/errors": "5.1.0", + "@solana/rpc-spec": "5.1.0", + "@solana/rpc-types": "5.1.0" }, "engines": { - "node": ">=5.10" + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" } }, - "node_modules/@solana/codecs-core": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.3.0.tgz", - "integrity": "sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==", + "node_modules/@solana/accounts/node_modules/@solana/codecs-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-5.1.0.tgz", + "integrity": "sha512-vDwi03mxWeWCS5Il6BCdNdifYdOoHVz97YOmbWGIt45b77Ivu5NUYeSD2+ccl6fSw8eYQ6QaqqKXMjbSfsXv4g==", "license": "MIT", - "optional": true, "dependencies": { - "@solana/errors": "2.3.0" + "@solana/errors": "5.1.0" }, "engines": { "node": ">=20.18.0" @@ -2501,15 +2611,17 @@ "typescript": ">=5.3.3" } }, - "node_modules/@solana/codecs-numbers": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz", - "integrity": "sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==", + "node_modules/@solana/accounts/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", "license": "MIT", - "optional": true, "dependencies": { - "@solana/codecs-core": "2.3.0", - "@solana/errors": "2.3.0" + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" }, "engines": { "node": ">=20.18.0" @@ -2518,15 +2630,57 @@ "typescript": ">=5.3.3" } }, - "node_modules/@solana/errors": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.3.0.tgz", - "integrity": "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==", + "node_modules/@solana/accounts/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/addresses": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/addresses/-/addresses-5.1.0.tgz", + "integrity": "sha512-X84qSZLgve9YeYsyxGI49WnfEre53tdFu4x9/4oULBgoj8d0A+P9VGLYzmRJ0YFYKRcZG7U4u3MQpI5uLZ1AsQ==", "license": "MIT", - "optional": true, "dependencies": { - "chalk": "^5.4.1", - "commander": "^14.0.0" + "@solana/assertions": "5.1.0", + "@solana/codecs-core": "5.1.0", + "@solana/codecs-strings": "5.1.0", + "@solana/errors": "5.1.0", + "@solana/nominal-types": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/addresses/node_modules/@solana/codecs-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-5.1.0.tgz", + "integrity": "sha512-vDwi03mxWeWCS5Il6BCdNdifYdOoHVz97YOmbWGIt45b77Ivu5NUYeSD2+ccl6fSw8eYQ6QaqqKXMjbSfsXv4g==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/addresses/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" }, "bin": { "errors": "bin/cli.mjs" @@ -2538,87 +2692,1834 @@ "typescript": ">=5.3.3" } }, - "node_modules/@solana/errors/node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "node_modules/@solana/addresses/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", "license": "MIT", - "optional": true, "engines": { "node": ">=20" } }, - "node_modules/@solana/web3.js": { - "version": "1.98.4", - "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.4.tgz", - "integrity": "sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==", + "node_modules/@solana/assertions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/assertions/-/assertions-5.1.0.tgz", + "integrity": "sha512-5But2wyxuvGXMIOnD0jBMQ9yq1QQF2LSK3IbIRSkAkXbD3DS6O2tRvKUHNhogd+BpkPyCGOQHBycezgnxmStlg==", "license": "MIT", - "optional": true, "dependencies": { - "@babel/runtime": "^7.25.0", - "@noble/curves": "^1.4.2", - "@noble/hashes": "^1.4.0", - "@solana/buffer-layout": "^4.0.1", - "@solana/codecs-numbers": "^2.1.0", - "agentkeepalive": "^4.5.0", - "bn.js": "^5.2.1", - "borsh": "^0.7.0", - "bs58": "^4.0.1", - "buffer": "6.0.3", - "fast-stable-stringify": "^1.0.0", - "jayson": "^4.1.1", - "node-fetch": "^2.7.0", - "rpc-websockets": "^9.0.2", - "superstruct": "^2.0.2" + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" } }, - "node_modules/@stablelib/base64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", - "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", - "license": "MIT" - }, - "node_modules/@swc/helpers": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz", - "integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==", - "license": "Apache-2.0", - "optional": true, + "node_modules/@solana/assertions/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", "dependencies": { - "tslib": "^2.8.0" + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" } }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "node_modules/@solana/assertions/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" + "engines": { + "node": ">=20" } }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.19.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", - "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", - "devOptional": true, + "node_modules/@solana/buffer-layout": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz", + "integrity": "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==", "license": "MIT", + "optional": true, "dependencies": { - "undici-types": "~6.21.0" + "buffer": "~6.0.3" + }, + "engines": { + "node": ">=5.10" } }, - "node_modules/@types/qrcode-terminal": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@types/qrcode-terminal/-/qrcode-terminal-0.12.2.tgz", - "integrity": "sha512-v+RcIEJ+Uhd6ygSQ0u5YYY7ZM+la7GgPbs0V/7l/kFs2uO4S8BcIUEMoP7za4DNIqNnUD5npf0A/7kBhrCKG5Q==", - "dev": true, + "node_modules/@solana/codecs": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs/-/codecs-5.1.0.tgz", + "integrity": "sha512-krSuf/E2Sa/4oASZ/jb/5KGUG58m1/bQdLrKvBnoAFhYj7zZf+8V4UqHGTV5n2NCQfmMyORsg9n2saKjkUzo8w==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.1.0", + "@solana/codecs-data-structures": "5.1.0", + "@solana/codecs-numbers": "5.1.0", + "@solana/codecs-strings": "5.1.0", + "@solana/options": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/codecs-core": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.3.0.tgz", + "integrity": "sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/codecs-data-structures": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-data-structures/-/codecs-data-structures-5.1.0.tgz", + "integrity": "sha512-ftAwL/jsurFrk9kFVhkTLdQ8fGZ8I0PcbVH+V1a0dIP2aKDofGePvK0XbwZE/ohizC9gEIZxyBX5IgRKk5PXyg==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.1.0", + "@solana/codecs-numbers": "5.1.0", + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/codecs-data-structures/node_modules/@solana/codecs-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-5.1.0.tgz", + "integrity": "sha512-vDwi03mxWeWCS5Il6BCdNdifYdOoHVz97YOmbWGIt45b77Ivu5NUYeSD2+ccl6fSw8eYQ6QaqqKXMjbSfsXv4g==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/codecs-data-structures/node_modules/@solana/codecs-numbers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-5.1.0.tgz", + "integrity": "sha512-Ea5/9yjDNOrDZcI40UGzzi6Aq1JNsmzM4m5pOk6Xb3JRZ0YdKOv/MwuCqb6jRgzZ7SQjHhkfGL43kHLJA++bOw==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.1.0", + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/codecs-data-structures/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/codecs-data-structures/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/codecs-numbers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz", + "integrity": "sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/codecs-strings": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-strings/-/codecs-strings-5.1.0.tgz", + "integrity": "sha512-014xwl5T/3VnGW0gceizF47DUs5EURRtgGmbWIR5+Z32yxgQ6hT9Zl0atZbL268RHbUQ03/J8Ush1StQgy7sfQ==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.1.0", + "@solana/codecs-numbers": "5.1.0", + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "fastestsmallesttextencoderdecoder": "^1.0.22", + "typescript": ">=5.3.3" + }, + "peerDependenciesMeta": { + "fastestsmallesttextencoderdecoder": { + "optional": true + } + } + }, + "node_modules/@solana/codecs-strings/node_modules/@solana/codecs-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-5.1.0.tgz", + "integrity": "sha512-vDwi03mxWeWCS5Il6BCdNdifYdOoHVz97YOmbWGIt45b77Ivu5NUYeSD2+ccl6fSw8eYQ6QaqqKXMjbSfsXv4g==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/codecs-strings/node_modules/@solana/codecs-numbers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-5.1.0.tgz", + "integrity": "sha512-Ea5/9yjDNOrDZcI40UGzzi6Aq1JNsmzM4m5pOk6Xb3JRZ0YdKOv/MwuCqb6jRgzZ7SQjHhkfGL43kHLJA++bOw==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.1.0", + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/codecs-strings/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/codecs-strings/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/codecs/node_modules/@solana/codecs-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-5.1.0.tgz", + "integrity": "sha512-vDwi03mxWeWCS5Il6BCdNdifYdOoHVz97YOmbWGIt45b77Ivu5NUYeSD2+ccl6fSw8eYQ6QaqqKXMjbSfsXv4g==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/codecs/node_modules/@solana/codecs-numbers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-5.1.0.tgz", + "integrity": "sha512-Ea5/9yjDNOrDZcI40UGzzi6Aq1JNsmzM4m5pOk6Xb3JRZ0YdKOv/MwuCqb6jRgzZ7SQjHhkfGL43kHLJA++bOw==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.1.0", + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/codecs/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/codecs/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/errors": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.3.0.tgz", + "integrity": "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "chalk": "^5.4.1", + "commander": "^14.0.0" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/errors/node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/fast-stable-stringify": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/fast-stable-stringify/-/fast-stable-stringify-5.1.0.tgz", + "integrity": "sha512-ACZo7cH/5EXsBmruw/0gU2/PXL2l4aET0YpL93H6QEaZwEAICFD8cLkj20nBcfLTf4srEiuKtwuSDeONTWIulw==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/functional": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/functional/-/functional-5.1.0.tgz", + "integrity": "sha512-R6jacWU0Gr+j49lTDp+FSECBolqw2Gq7JlC22rI0JkcxJiiAlp3G80v6zAYq0FkHzxZbjyR6//JYUXSwliem5g==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/instruction-plans": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/instruction-plans/-/instruction-plans-5.1.0.tgz", + "integrity": "sha512-friMgHt0z5jQlCyyTDXfwAMYjCAagI7QYR+hLWB/BmvSuRpai0ddToWbWJoqrNRM312xZ+Oy/qjC3+Ftzi0DLA==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.1.0", + "@solana/instructions": "5.1.0", + "@solana/keys": "5.1.0", + "@solana/promises": "5.1.0", + "@solana/transaction-messages": "5.1.0", + "@solana/transactions": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/instruction-plans/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/instruction-plans/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/instructions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/instructions/-/instructions-5.1.0.tgz", + "integrity": "sha512-fkwpUwwqk5K14T/kZDnCrfeR0kww49HBx+BK8xdSeJx+bt4QTwAHa9YeOkGhGrHEFVEJEUf8FKoxxTzZzJZtKQ==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.1.0", + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/instructions/node_modules/@solana/codecs-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-5.1.0.tgz", + "integrity": "sha512-vDwi03mxWeWCS5Il6BCdNdifYdOoHVz97YOmbWGIt45b77Ivu5NUYeSD2+ccl6fSw8eYQ6QaqqKXMjbSfsXv4g==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/instructions/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/instructions/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/keys": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/keys/-/keys-5.1.0.tgz", + "integrity": "sha512-ma4zTTuSOmtTCvATHMfUGNTw0Vqah/6XPe1VmLc66ohwXMI3yqatX1FQPXgDZozr15SvLAesfs7/bgl2TRoe9w==", + "license": "MIT", + "dependencies": { + "@solana/assertions": "5.1.0", + "@solana/codecs-core": "5.1.0", + "@solana/codecs-strings": "5.1.0", + "@solana/errors": "5.1.0", + "@solana/nominal-types": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/keys/node_modules/@solana/codecs-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-5.1.0.tgz", + "integrity": "sha512-vDwi03mxWeWCS5Il6BCdNdifYdOoHVz97YOmbWGIt45b77Ivu5NUYeSD2+ccl6fSw8eYQ6QaqqKXMjbSfsXv4g==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/keys/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/keys/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/kit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/kit/-/kit-5.1.0.tgz", + "integrity": "sha512-oNQRzI0+mGWmXy05psO0J7r9Boy8PF7LH5H0Y9Jxvs10AbG4oSOBtyj20EccsRrr+jkqLw42fqb/4rNuASfvsA==", + "license": "MIT", + "dependencies": { + "@solana/accounts": "5.1.0", + "@solana/addresses": "5.1.0", + "@solana/codecs": "5.1.0", + "@solana/errors": "5.1.0", + "@solana/functional": "5.1.0", + "@solana/instruction-plans": "5.1.0", + "@solana/instructions": "5.1.0", + "@solana/keys": "5.1.0", + "@solana/offchain-messages": "5.1.0", + "@solana/programs": "5.1.0", + "@solana/rpc": "5.1.0", + "@solana/rpc-parsed-types": "5.1.0", + "@solana/rpc-spec-types": "5.1.0", + "@solana/rpc-subscriptions": "5.1.0", + "@solana/rpc-types": "5.1.0", + "@solana/signers": "5.1.0", + "@solana/sysvars": "5.1.0", + "@solana/transaction-confirmation": "5.1.0", + "@solana/transaction-messages": "5.1.0", + "@solana/transactions": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/kit/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/kit/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/nominal-types": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/nominal-types/-/nominal-types-5.1.0.tgz", + "integrity": "sha512-+4Cm+SpK+D811i9giqv4Up93ZlmUcZfLDHkSH24F4in61+Y2TKA+XKuRtKhNytQMmqCfbvJZ9MHFaIeZw5g+Bg==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/offchain-messages": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/offchain-messages/-/offchain-messages-5.1.0.tgz", + "integrity": "sha512-6FUXjiIJprjWa7y/T4E3rUb3HKi3P5zpBweBEwDflEEJ/QlieWUw7xlGAOvZ1eF3Wi+6LfcrdtZOwIkuv6o9Sg==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "5.1.0", + "@solana/codecs-core": "5.1.0", + "@solana/codecs-data-structures": "5.1.0", + "@solana/codecs-numbers": "5.1.0", + "@solana/codecs-strings": "5.1.0", + "@solana/errors": "5.1.0", + "@solana/keys": "5.1.0", + "@solana/nominal-types": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/offchain-messages/node_modules/@solana/codecs-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-5.1.0.tgz", + "integrity": "sha512-vDwi03mxWeWCS5Il6BCdNdifYdOoHVz97YOmbWGIt45b77Ivu5NUYeSD2+ccl6fSw8eYQ6QaqqKXMjbSfsXv4g==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/offchain-messages/node_modules/@solana/codecs-numbers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-5.1.0.tgz", + "integrity": "sha512-Ea5/9yjDNOrDZcI40UGzzi6Aq1JNsmzM4m5pOk6Xb3JRZ0YdKOv/MwuCqb6jRgzZ7SQjHhkfGL43kHLJA++bOw==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.1.0", + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/offchain-messages/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/offchain-messages/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/options": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/options/-/options-5.1.0.tgz", + "integrity": "sha512-PqgfALd0yhK+QFaYIbRFTV6hBpiy5xwdu07zSw1RLoNvt1sg+MRsRFDk9R8ZdEdiM69PY/cKiClVSjpNzLLcJg==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.1.0", + "@solana/codecs-data-structures": "5.1.0", + "@solana/codecs-numbers": "5.1.0", + "@solana/codecs-strings": "5.1.0", + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/options/node_modules/@solana/codecs-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-5.1.0.tgz", + "integrity": "sha512-vDwi03mxWeWCS5Il6BCdNdifYdOoHVz97YOmbWGIt45b77Ivu5NUYeSD2+ccl6fSw8eYQ6QaqqKXMjbSfsXv4g==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/options/node_modules/@solana/codecs-numbers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-5.1.0.tgz", + "integrity": "sha512-Ea5/9yjDNOrDZcI40UGzzi6Aq1JNsmzM4m5pOk6Xb3JRZ0YdKOv/MwuCqb6jRgzZ7SQjHhkfGL43kHLJA++bOw==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.1.0", + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/options/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/options/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/programs": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/programs/-/programs-5.1.0.tgz", + "integrity": "sha512-zAghXyRGixWNcarShlrnpjMD2115BZTF9JMLIcgkCYDOwjDPFIB/Y0hwDCH87N5uSjzlgkDpxKEL4ILewoZTRQ==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "5.1.0", + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/programs/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/programs/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/promises": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/promises/-/promises-5.1.0.tgz", + "integrity": "sha512-LU9wwS1PvGc/It610dclfq+JCuUEZSIWjvaF0+sqMP7QCk12Uz7MK2m9TtvLcjTvvKTIrucglRZP6qKroWRqGg==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc/-/rpc-5.1.0.tgz", + "integrity": "sha512-j+ByLxFCoHWw9TnsGzkAVMFUfBDIUE53nIosJAYEsERpImD2mjwc33uDE6YXLKoaKRoYO4tc7IUzkKY1fQp/CA==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.1.0", + "@solana/fast-stable-stringify": "5.1.0", + "@solana/functional": "5.1.0", + "@solana/rpc-api": "5.1.0", + "@solana/rpc-spec": "5.1.0", + "@solana/rpc-spec-types": "5.1.0", + "@solana/rpc-transformers": "5.1.0", + "@solana/rpc-transport-http": "5.1.0", + "@solana/rpc-types": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-api/-/rpc-api-5.1.0.tgz", + "integrity": "sha512-eI1tY0i3gmih1C65gFECYbfPRpHEYqFp+9IKjpknZtYpQIe9BqBKSpfYpGiCAbKdN/TMadBNPOzdK15ewhkkvQ==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "5.1.0", + "@solana/codecs-core": "5.1.0", + "@solana/codecs-strings": "5.1.0", + "@solana/errors": "5.1.0", + "@solana/keys": "5.1.0", + "@solana/rpc-parsed-types": "5.1.0", + "@solana/rpc-spec": "5.1.0", + "@solana/rpc-transformers": "5.1.0", + "@solana/rpc-types": "5.1.0", + "@solana/transaction-messages": "5.1.0", + "@solana/transactions": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-api/node_modules/@solana/codecs-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-5.1.0.tgz", + "integrity": "sha512-vDwi03mxWeWCS5Il6BCdNdifYdOoHVz97YOmbWGIt45b77Ivu5NUYeSD2+ccl6fSw8eYQ6QaqqKXMjbSfsXv4g==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-api/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-api/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/rpc-parsed-types": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-parsed-types/-/rpc-parsed-types-5.1.0.tgz", + "integrity": "sha512-ZJoXHNItALMNa1zmGrNnIh96RBlc9GpIqoaZkdE14mAQ7gWe7Oc0ejYavUeSCmcL0wZcvIFh50AsfVxrHr4+2Q==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-spec": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-spec/-/rpc-spec-5.1.0.tgz", + "integrity": "sha512-y8B6fUWA1EBKXUsNo6b9EiFcQPsaJREPLlcIDbo4b6TucQNwvl7FHfpf1VHJL64SkI/WE69i2WEkiOJYjmLO0A==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.1.0", + "@solana/rpc-spec-types": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-spec-types": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-spec-types/-/rpc-spec-types-5.1.0.tgz", + "integrity": "sha512-B8/WyjmHpC34vXtAmTpZyPwRCm7WwoSkmjBcBouaaY1uilJ9+Wp2nptbq2cJyWairOoMSoI7v5kvvnrJuquq4Q==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-spec/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-spec/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/rpc-subscriptions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions/-/rpc-subscriptions-5.1.0.tgz", + "integrity": "sha512-u/mafVzBbdqvYDD7x/98T5/5xk4Bl2C/90TaHiKx7FmutVC/H4QsritPTY0v9JG1dOVWbgIfUgfZ0C0DPkiYnA==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.1.0", + "@solana/fast-stable-stringify": "5.1.0", + "@solana/functional": "5.1.0", + "@solana/promises": "5.1.0", + "@solana/rpc-spec-types": "5.1.0", + "@solana/rpc-subscriptions-api": "5.1.0", + "@solana/rpc-subscriptions-channel-websocket": "5.1.0", + "@solana/rpc-subscriptions-spec": "5.1.0", + "@solana/rpc-transformers": "5.1.0", + "@solana/rpc-types": "5.1.0", + "@solana/subscribable": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-subscriptions-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-api/-/rpc-subscriptions-api-5.1.0.tgz", + "integrity": "sha512-84e2AsgqAGiVloW3G4RzpHPkInknu3rEuFPut2/69eq3Ab97TiTz2s5kc9gJpprtGM+xbgnIfeuGqr5F+2bXQA==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "5.1.0", + "@solana/keys": "5.1.0", + "@solana/rpc-subscriptions-spec": "5.1.0", + "@solana/rpc-transformers": "5.1.0", + "@solana/rpc-types": "5.1.0", + "@solana/transaction-messages": "5.1.0", + "@solana/transactions": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-subscriptions-channel-websocket": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-channel-websocket/-/rpc-subscriptions-channel-websocket-5.1.0.tgz", + "integrity": "sha512-FzAEmHzXtlckNn7T/1dzDS7r5HmekYPstrtZKjDcVxuGMVBUkZTnb69t7EJvKNuKw1wYZEUd0EEegtC2K/9dZA==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.1.0", + "@solana/functional": "5.1.0", + "@solana/rpc-subscriptions-spec": "5.1.0", + "@solana/subscribable": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3", + "ws": "^8.18.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + } + } + }, + "node_modules/@solana/rpc-subscriptions-channel-websocket/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-subscriptions-channel-websocket/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/rpc-subscriptions-spec": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-spec/-/rpc-subscriptions-spec-5.1.0.tgz", + "integrity": "sha512-ORfjKtainnYisql6z4YsXByVwY8/rWsedVWn5oe/V7Og9LyetTM7hwJ8FbUdRDZwyLlUrI0cEE1aG+3ma/8tPw==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.1.0", + "@solana/promises": "5.1.0", + "@solana/rpc-spec-types": "5.1.0", + "@solana/subscribable": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-subscriptions-spec/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-subscriptions-spec/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/rpc-subscriptions/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-subscriptions/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/rpc-transformers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-transformers/-/rpc-transformers-5.1.0.tgz", + "integrity": "sha512-6v93xi/ewGS/xEiSktNQ0bh0Uiv1/q9nR5oiFMn3BiAJRC+FdMRMxCjp6H+/Tua7wdhpClaPKrZYBQHoIp59tw==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.1.0", + "@solana/functional": "5.1.0", + "@solana/nominal-types": "5.1.0", + "@solana/rpc-spec-types": "5.1.0", + "@solana/rpc-types": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-transformers/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-transformers/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/rpc-transport-http": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-transport-http/-/rpc-transport-http-5.1.0.tgz", + "integrity": "sha512-XoGX+2n/iXzoGb3Xrltbx8avnzp15vCfCGXuZpQWFL+xUg3P4CGl217XyDGjS5VxuUml+f/30xzWl18RaAIEcw==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.1.0", + "@solana/rpc-spec": "5.1.0", + "@solana/rpc-spec-types": "5.1.0", + "undici-types": "^7.16.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-transport-http/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-transport-http/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/rpc-transport-http/node_modules/undici-types": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.27.2.tgz", + "integrity": "sha512-cH9f42mHuljpNuoS47sWDDWXVxWnJgYCzHVUlr3tn7+HVx0L6QSO+VG5qgzT4kXkR2K8ZsReaT5bupam6RNAEQ==", + "license": "MIT" + }, + "node_modules/@solana/rpc-types": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-types/-/rpc-types-5.1.0.tgz", + "integrity": "sha512-Rnpt5BuHQvnULPNXUC/yRqB+7iPbon95CSCeyRvPj5tJ4fx2JibvX3s/UEoud5vC+kRjPi/R0BGJ8XFvd3eDWg==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "5.1.0", + "@solana/codecs-core": "5.1.0", + "@solana/codecs-numbers": "5.1.0", + "@solana/codecs-strings": "5.1.0", + "@solana/errors": "5.1.0", + "@solana/nominal-types": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-types/node_modules/@solana/codecs-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-5.1.0.tgz", + "integrity": "sha512-vDwi03mxWeWCS5Il6BCdNdifYdOoHVz97YOmbWGIt45b77Ivu5NUYeSD2+ccl6fSw8eYQ6QaqqKXMjbSfsXv4g==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-types/node_modules/@solana/codecs-numbers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-5.1.0.tgz", + "integrity": "sha512-Ea5/9yjDNOrDZcI40UGzzi6Aq1JNsmzM4m5pOk6Xb3JRZ0YdKOv/MwuCqb6jRgzZ7SQjHhkfGL43kHLJA++bOw==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.1.0", + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-types/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-types/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/rpc/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/signers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/signers/-/signers-5.1.0.tgz", + "integrity": "sha512-B8xO0SGN1ZWYfJROL+da3id279qNbXbXoqud+AuT5gur51RrS4YhNkTQ6khVbGtAOpPMAhkoZN0jnfCC1r33jQ==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "5.1.0", + "@solana/codecs-core": "5.1.0", + "@solana/errors": "5.1.0", + "@solana/instructions": "5.1.0", + "@solana/keys": "5.1.0", + "@solana/nominal-types": "5.1.0", + "@solana/offchain-messages": "5.1.0", + "@solana/transaction-messages": "5.1.0", + "@solana/transactions": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/signers/node_modules/@solana/codecs-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-5.1.0.tgz", + "integrity": "sha512-vDwi03mxWeWCS5Il6BCdNdifYdOoHVz97YOmbWGIt45b77Ivu5NUYeSD2+ccl6fSw8eYQ6QaqqKXMjbSfsXv4g==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/signers/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/signers/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/subscribable": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/subscribable/-/subscribable-5.1.0.tgz", + "integrity": "sha512-OeW5AJwKzHh18+PIPtghuuPJTmEep2Mhb3Lsrq4alas4fibmMGkr39z1HXxVF6l6e2lu/YGhHIDtuhouWmY7ow==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/subscribable/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/subscribable/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/sysvars": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/sysvars/-/sysvars-5.1.0.tgz", + "integrity": "sha512-FJ9YIsLTAaajnOrYEYn54znstXJsvKndRhyCrlyiAEN1IXHw5HtZHploLF3ZZ78b7YU3uv3tFJMziXFBwPOn4Q==", + "license": "MIT", + "dependencies": { + "@solana/accounts": "5.1.0", + "@solana/codecs": "5.1.0", + "@solana/errors": "5.1.0", + "@solana/rpc-types": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/sysvars/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/sysvars/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/transaction-confirmation": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/transaction-confirmation/-/transaction-confirmation-5.1.0.tgz", + "integrity": "sha512-6HnL0uH8tWZXJVuaoeTbCQp/FS11Bsc4GSlq+k0N21GdhTbFuqBhsxlAYWbzPWs9+/kYRGHqqXvBPCReWxT7BA==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "5.1.0", + "@solana/codecs-strings": "5.1.0", + "@solana/errors": "5.1.0", + "@solana/keys": "5.1.0", + "@solana/promises": "5.1.0", + "@solana/rpc": "5.1.0", + "@solana/rpc-subscriptions": "5.1.0", + "@solana/rpc-types": "5.1.0", + "@solana/transaction-messages": "5.1.0", + "@solana/transactions": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/transaction-confirmation/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/transaction-confirmation/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/transaction-messages": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/transaction-messages/-/transaction-messages-5.1.0.tgz", + "integrity": "sha512-9rNV2YJhd85WIMvnwa/vUY4xUw3ZTU17jP1KDo/fFZWk55a0ov0ATJJPyC5HAR1i6hT1cmJzGH/UHhnD9m/Q3w==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "5.1.0", + "@solana/codecs-core": "5.1.0", + "@solana/codecs-data-structures": "5.1.0", + "@solana/codecs-numbers": "5.1.0", + "@solana/errors": "5.1.0", + "@solana/functional": "5.1.0", + "@solana/instructions": "5.1.0", + "@solana/nominal-types": "5.1.0", + "@solana/rpc-types": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/transaction-messages/node_modules/@solana/codecs-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-5.1.0.tgz", + "integrity": "sha512-vDwi03mxWeWCS5Il6BCdNdifYdOoHVz97YOmbWGIt45b77Ivu5NUYeSD2+ccl6fSw8eYQ6QaqqKXMjbSfsXv4g==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/transaction-messages/node_modules/@solana/codecs-numbers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-5.1.0.tgz", + "integrity": "sha512-Ea5/9yjDNOrDZcI40UGzzi6Aq1JNsmzM4m5pOk6Xb3JRZ0YdKOv/MwuCqb6jRgzZ7SQjHhkfGL43kHLJA++bOw==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.1.0", + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/transaction-messages/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/transaction-messages/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/transactions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/transactions/-/transactions-5.1.0.tgz", + "integrity": "sha512-06JwSPtz+38ozNgpysAXS2eTMPQCufIisXB6K88X8J4GF8ziqs4nkq0BpXAXn+MpZTkuMt+JeW2RxP3HKhXe5g==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "5.1.0", + "@solana/codecs-core": "5.1.0", + "@solana/codecs-data-structures": "5.1.0", + "@solana/codecs-numbers": "5.1.0", + "@solana/codecs-strings": "5.1.0", + "@solana/errors": "5.1.0", + "@solana/functional": "5.1.0", + "@solana/instructions": "5.1.0", + "@solana/keys": "5.1.0", + "@solana/nominal-types": "5.1.0", + "@solana/rpc-types": "5.1.0", + "@solana/transaction-messages": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/transactions/node_modules/@solana/codecs-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-5.1.0.tgz", + "integrity": "sha512-vDwi03mxWeWCS5Il6BCdNdifYdOoHVz97YOmbWGIt45b77Ivu5NUYeSD2+ccl6fSw8eYQ6QaqqKXMjbSfsXv4g==", + "license": "MIT", + "dependencies": { + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/transactions/node_modules/@solana/codecs-numbers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-5.1.0.tgz", + "integrity": "sha512-Ea5/9yjDNOrDZcI40UGzzi6Aq1JNsmzM4m5pOk6Xb3JRZ0YdKOv/MwuCqb6jRgzZ7SQjHhkfGL43kHLJA++bOw==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "5.1.0", + "@solana/errors": "5.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/transactions/node_modules/@solana/errors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.1.0.tgz", + "integrity": "sha512-JlTyekErWa6Fdcwu1Hrh+jZxjM4YxyorGCFDRVZlmHZFkp5N00DWKcYnSGZrTF8E6ZZEP9pfS2XwM8y7p7HPww==", + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "commander": "14.0.2" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/transactions/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@solana/web3.js": { + "version": "1.98.4", + "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.4.tgz", + "integrity": "sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.25.0", + "@noble/curves": "^1.4.2", + "@noble/hashes": "^1.4.0", + "@solana/buffer-layout": "^4.0.1", + "@solana/codecs-numbers": "^2.1.0", + "agentkeepalive": "^4.5.0", + "bn.js": "^5.2.1", + "borsh": "^0.7.0", + "bs58": "^4.0.1", + "buffer": "6.0.3", + "fast-stable-stringify": "^1.0.0", + "jayson": "^4.1.1", + "node-fetch": "^2.7.0", + "rpc-websockets": "^9.0.2", + "superstruct": "^2.0.2" + } + }, + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz", + "integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", + "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/qrcode-terminal": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/qrcode-terminal/-/qrcode-terminal-0.12.2.tgz", + "integrity": "sha512-v+RcIEJ+Uhd6ygSQ0u5YYY7ZM+la7GgPbs0V/7l/kFs2uO4S8BcIUEMoP7za4DNIqNnUD5npf0A/7kBhrCKG5Q==", + "dev": true, "license": "MIT" }, "node_modules/@types/uuid": { @@ -2658,8 +4559,108 @@ } }, "node_modules/@virtuals-protocol/acp-node-v2": { - "resolved": "../acp-node-v2", - "link": true + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@virtuals-protocol/acp-node-v2/-/acp-node-v2-0.1.4.tgz", + "integrity": "sha512-G/TQOBykAEXlrRAAcRCJUY1YDm6ipJItBQQm9w/JrjQjqDkTJfU+ruOI9pvktvGjrY5yIFQw4WD3vXDi1lj1Fg==", + "license": "ISC", + "dependencies": { + "@account-kit/infra": "^4.84.1", + "@alchemy/wallet-apis": "5.0.0-beta.9", + "@privy-io/node": "^0.16.0", + "@solana/kit": "5.1.0", + "ajv": "^8.18.0", + "ajv-formats": "^3.0.1", + "eventsource": "^4.1.0", + "ox": "^0.14.17", + "socket.io-client": "^4.8.3", + "viem": "^2.47.0" + } + }, + "node_modules/@virtuals-protocol/acp-node-v2/node_modules/@privy-io/node": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@privy-io/node/-/node-0.16.0.tgz", + "integrity": "sha512-PvNowuXsKZhRqyjN5AHu46czPakq9/zraw4uMGuTTs5wpH45/wNthKoAGnmnTf9+ubgGUMAxdwNYm1kb+mVaCw==", + "license": "Apache-2.0", + "dependencies": { + "@hpke/chacha20poly1305": "^1.7.1", + "@hpke/core": "^1.7.5", + "@noble/curves": "^1.9.7", + "@noble/hashes": "^1.8.0", + "@scure/base": "^1.2.5", + "canonicalize": "^2.1.0", + "jose": "^6.1.0", + "lru-cache": "^11.1.0", + "svix": ">=1.29.0 <= 1.37.0 || ^1.40.0" + }, + "peerDependencies": { + "@solana/kit": "^5.1.0", + "@x402/evm": "^2.3.0", + "@x402/fetch": "^2.3.0", + "@x402/svm": "^2.3.0", + "viem": "^2.24.1" + }, + "peerDependenciesMeta": { + "@solana/kit": { + "optional": true + }, + "@x402/evm": { + "optional": true + }, + "@x402/fetch": { + "optional": true + }, + "@x402/svm": { + "optional": true + }, + "viem": { + "optional": true + } + } + }, + "node_modules/@virtuals-protocol/acp-node-v2/node_modules/ox": { + "version": "0.14.29", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.29.tgz", + "integrity": "sha512-M5j87Ec4V99MQdRct/g09eWXW60g6zhHTUs1lr4deUtrPDnezBdCJTgKd7pxqTpSZBFveV0ALi9jMMuT1qKyNg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.2.3", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@virtuals-protocol/acp-node-v2/node_modules/ox/node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } }, "node_modules/@virtuals-protocol/acp-node/node_modules/@noble/curves": { "version": "1.9.1", @@ -2775,6 +4776,23 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/alchemy-sdk": { "version": "3.6.5", "resolved": "https://registry.npmjs.org/alchemy-sdk/-/alchemy-sdk-3.6.5.tgz", @@ -2829,12 +4847,43 @@ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", "license": "MIT" }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/axios": { "version": "1.14.0", "resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz", @@ -2983,6 +5032,24 @@ "node": ">=8" } }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -2996,6 +5063,22 @@ "node": ">= 0.4" } }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/canonicalize": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/canonicalize/-/canonicalize-2.1.0.tgz", @@ -3010,7 +5093,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "license": "MIT", - "optional": true, "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -3121,35 +5203,101 @@ "@napi-rs/keyring": "^1.2.0" } }, - "node_modules/d": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", - "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", - "license": "ISC", - "optional": true, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "license": "ISC", + "optional": true, + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", "dependencies": { - "es5-ext": "^0.10.64", - "type": "^2.7.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { - "node": ">=0.12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" }, "engines": { - "node": ">=6.0" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/delay": { @@ -3278,6 +5426,26 @@ "node": ">= 0.4" } }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -3439,6 +5607,27 @@ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "license": "MIT" }, + "node_modules/eventsource": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-4.1.0.tgz", + "integrity": "sha512-2GuF51iuHX6A9xdTccMTsNb7VO0lHZihApxhvQzJB5A03DvHDd2FQepodbMaztPBmBcE/ox7o2gqaxGhYB9LhQ==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/ext": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", @@ -3541,6 +5730,21 @@ } } }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", @@ -3580,6 +5784,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -3642,6 +5855,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -3714,56 +5951,268 @@ "ms": "^2.0.0" } }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause", - "optional": true - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-typedarray": { @@ -3773,6 +6222,40 @@ "license": "MIT", "optional": true }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, "node_modules/isomorphic-ws": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", @@ -4144,6 +6627,63 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/ox": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.0.tgz", @@ -4233,6 +6773,15 @@ "pathe": "^2.0.1" } }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss-load-config": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", @@ -4305,6 +6854,26 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -4446,6 +7015,23 @@ "license": "MIT", "optional": true }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -4459,6 +7045,110 @@ "license": "MIT", "optional": true }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -4518,6 +7208,19 @@ "fast-sha256": "^1.3.0" } }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/stream-chain": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", @@ -4774,6 +7477,12 @@ "license": "ISC", "optional": true }, + "node_modules/typebox": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.2.9.tgz", + "integrity": "sha512-lsZLOj4acd0WSuwUqF7CTvvgkCZphs5+qbzFwxcccmeBiUMBIhytf6WQORmj90qQk1N9tlRax7BMIMy1POFZAA==", + "license": "MIT" + }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -4969,6 +7678,64 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", diff --git a/src/commands/trade.ts b/src/commands/trade.ts index 3ddde69..ea99a8f 100644 --- a/src/commands/trade.ts +++ b/src/commands/trade.ts @@ -92,15 +92,28 @@ type SolanaTradeSigner = { signTransactionViaPrivy(txBase64: string): Promise; // signed tx, base64 }; -// Solana mainnet Privy chain id. Trade legs are always mainnet — Treasures -// staging settles against mainnet, and LiFi Solana legs are mainnet-only. +// Solana Privy chain ids. Trade legs are always mainnet — Treasures staging +// settles against mainnet, and LiFi Solana legs are mainnet-only — but the +// devnet id is still a valid Solana reference a caller might pass (it's what +// `wallet sol balance` shows on testnet), so isSolanaChainRef accepts both. const SOLANA_MAINNET_PRIVY_CHAIN_ID = 501; +const SOLANA_DEVNET_PRIVY_CHAIN_ID = 500; -// Backend chain references that mean Solana (the LiFi chain id + aliases). +// Every chain reference that means Solana, normalized to one check: the name +// aliases, the LiFi chain id, and the Privy chain ids (500 devnet / 501 mainnet) +// that `wallet sol` surfaces. Case-insensitive. Used both to detect a Solana +// source/venue and to decide whether to attach solWallet — keep it the single +// source of truth so no spelling slips through and silently drops the wallet. function isSolanaChainRef(v: unknown): boolean { if (v === undefined) return false; const s = String(v).trim().toLowerCase(); - return s === "sol" || s === "solana" || s === "1151111081099710"; + return ( + s === "sol" || + s === "solana" || + s === "1151111081099710" || + s === String(SOLANA_MAINNET_PRIVY_CHAIN_ID) || + s === String(SOLANA_DEVNET_PRIVY_CHAIN_ID) + ); } // Minimal base58 decode (Solana alphabet) — the adapter returns base58 @@ -350,9 +363,9 @@ async function runTrade(opts: Record, json: boolean): Promise Date: Sun, 14 Jun 2026 04:10:05 +0800 Subject: [PATCH 06/24] fix(trade): attach solWallet for Solana --chain-out too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bugbot follow-up on PR #44: couldRouteViaSolana covered --chain and --chain-in but not --chain-out, so a swap/bridge whose destination is Solana reached /trade/plan without the agent's Solana pubkey — the recipient the backend needs to route/sign the destination leg. Add the symmetric isSolanaChainRef(opts.chainOut) check. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/commands/trade.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/commands/trade.ts b/src/commands/trade.ts index ea99a8f..283f5fe 100644 --- a/src/commands/trade.ts +++ b/src/commands/trade.ts @@ -359,14 +359,17 @@ async function runTrade(opts: Record, json: boolean): Promise Date: Sun, 14 Jun 2026 04:14:05 +0800 Subject: [PATCH 07/24] fix(trade): don't attach solWallet when a non-sol venue is pinned Bugbot follow-up on PR #44: the unpinned-buy heuristic fired on --token alone, so `--token AAPL --amount-usdc 1 --chain eth` still attached solWallet. With solWallet present the backend quotes both venues and may pick sol, overriding the user's explicit --chain eth pin. Gate the buy clause on opts.chain === undefined; an explicit --chain sol still routes via the isSolanaChainRef(opts.chain) clause. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/commands/trade.ts | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/commands/trade.ts b/src/commands/trade.ts index 283f5fe..77cb8e4 100644 --- a/src/commands/trade.ts +++ b/src/commands/trade.ts @@ -360,17 +360,25 @@ async function runTrade(opts: Record, json: boolean): Promise Date: Sun, 14 Jun 2026 04:17:44 +0800 Subject: [PATCH 08/24] fix(trade): define unpinned Treasures buy positively (require spend amount) Bugbot follow-up on PR #44: the negative formulation classified any --token without --side/--amount-shares/--chain as an unpinned buy, so a malformed perp like `--token BTC --size 0.01` (missing --side) attached solWallet. Define a buy positively: --token plus a spend amount (--amount-usdc on eth, or --amount-in funded from another chain). This covers both documented buy shapes and excludes perp/incomplete shapes that carry no spend signal. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/commands/trade.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/commands/trade.ts b/src/commands/trade.ts index 77cb8e4..f131a9b 100644 --- a/src/commands/trade.ts +++ b/src/commands/trade.ts @@ -360,17 +360,24 @@ async function runTrade(opts: Record, json: boolean): Promise Date: Sun, 14 Jun 2026 04:21:30 +0800 Subject: [PATCH 09/24] fix(trade): only swallow genuine no-Solana-wallet errors Bugbot follow-up on PR #44: the empty catch around getSolanaWalletAddress flattened every failure (network, auth, agent lookup) into "no wallet", silently dropping solWallet. Swallow only the NO_SOLANA_WALLET signal, and only for a speculative unpinned buy; real failures now surface, and an explicit Solana route (--chain/--chain-in/--chain-out sol) propagates any error rather than planning a route it can't sign. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/commands/trade.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/commands/trade.ts b/src/commands/trade.ts index f131a9b..788a58c 100644 --- a/src/commands/trade.ts +++ b/src/commands/trade.ts @@ -381,17 +381,25 @@ async function runTrade(opts: Record, json: boolean): Promise Date: Sun, 14 Jun 2026 05:11:59 +0800 Subject: [PATCH 10/24] docs: document tokenized-stock trading + Solana routing (README + SKILL) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Solana/Treasures work added spot tokenized-stock buy/sell, sol-venue auto-routing, and swaps in/out of Solana — none of which were in the docs. Adds to both README and SKILL.md: - intent-routing rows for Solana-source swaps and tokenized-stock spot - the stock-vs-perp rule (route by FLAG --amount-usdc/-shares vs --side, never by the ticker — AAPL is both a stock and an HL equity perp) - examples: buy with held USDC, buy funded from another chain, sell, USDC@sol → USDC@Base - command-table row + capability description for tokenized stocks Co-Authored-By: Claude Fable 5 --- README.md | 25 ++++++++++++++++++++++++- SKILL.md | 18 +++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1defa2b..b2bc10b 100644 --- a/README.md +++ b/README.md @@ -643,8 +643,16 @@ Each event line includes the job ID, chain ID, status, your roles, available act | EVM | **1337** | **Deposit** USDC into Hyperliquid | | **1337** | **1337** | **Spot** order on the Hyperliquid order book | | **1337** | EVM | **Withdraw** USDC from Hyperliquid | +| **Solana** | EVM | **Swap** out of Solana (USDC@sol → an EVM token) | -Perps are the one exception — a leveraged position isn't a token conversion, so they use `--side long|short` (with `--token`). Hyperliquid's perp markets span more than crypto: you can take leveraged positions on **stocks/equities, currencies/FX, and commodities** too, all through the same `--side`/`--token` flags. Running `acp trade` bare in a terminal opens an interactive picker (humans only). +Two intents don't use the chain-pair shape: + +- **Perps** — `--side long|short` (with `--token`). Leveraged positions on **crypto, stocks/equities, FX/currencies, and commodities**. +- **Tokenized stocks (spot)** — `--token ` plus `--amount-usdc` (buy) or `--amount-shares` (sell), and **no `--side`**. Buys/sells real tokenized equity (you own the share token), distinct from an equity *perp*. The backend auto-picks the venue/chain. + +> **Stock vs perp routes by FLAG, not the ticker.** `AAPL` is both a tokenized stock and an HL equity perp — `--amount-usdc`/`--amount-shares` (no `--side`) buys the spot stock; `--side` opens the leveraged perp. + +Running `acp trade` bare in a terminal opens an interactive picker (humans only). **Auto-balancing.** Hyperliquid keeps perp (collateral) and spot USDC in separate wallets, and deposits land in the *perp* wallet. You don't have to manage that: before an HL order the CLI checks the funding wallet and, if it's short, moves the shortfall over automatically (perp→spot for a spot buy, spot→perp for a perp). It's an instant, free L1 transfer — agents never think about sub-wallets. @@ -675,6 +683,21 @@ Bridging USDC to chain `1337` credits your Hyperliquid account (keyed by the sam The command **blocks until the bridge settles** — it signs the source-chain tx, then the server polls the bridge every 10s. Typically **~10–30s** (the Relay route into HL is near-instant); the poll cap is **10 minutes** for slower routes. You may see a poll cycle or two even on a fast bridge while LiFi indexes the source tx — that's normal, not a failure. +**Tokenized stocks (spot buy/sell):** + +Buy or sell real tokenized equities. Spot — you receive the share token, no leverage or funding. The backend auto-routes the venue and chain; you never specify one. Buys can spend USDC you already hold or be funded from another chain (it bridges first). Sells need an explicit `--chain eth|sol` (the server can't see which chain holds your shares). + +```bash +# Buy $5 of AAPL with USDC you hold (venue auto-picked) +acp trade --token AAPL --amount-usdc 5 + +# Buy funded from another chain — bridges VIRTUAL@Base → USDC, then buys +acp trade --token AAPL --token-in virtual --chain-in 8453 --amount-in 8 + +# Sell 0.01 AAPL shares (delivers USDC; --chain required on sells) +acp trade --token AAPL --amount-shares 0.01 --chain sol +``` + **Hyperliquid — perps:** Hyperliquid perps aren't limited to crypto — it lists leveraged perp markets across **crypto, equities/stocks, FX/currencies, and commodities**. The command is the same for all of them: pass the Hyperliquid market symbol as `--token`, and `--side`, `--size`, and (optionally) `--leverage` work identically regardless of asset class. diff --git a/SKILL.md b/SKILL.md index eac219b..019d95e 100644 --- a/SKILL.md +++ b/SKILL.md @@ -2,7 +2,7 @@ name: acp-cli metadata: acpCliVersion: 1.0.9 -description: "Run autonomous agent operations on Virtuals Protocol — agent identity (on-chain wallet, dedicated email inbox, single-use virtual payment cards, P256 signers, ERC-8004 registration, tokenization), inference and compute for the agent's own AI workloads (paid from the agent's wallet, tokenized-agent trading fees, or marketplace revenue; managed via the Virtuals dashboard, not this CLI), and the Agent Commerce Protocol (ACP) marketplace (hire other agents or sell services via on-chain USDC-escrow jobs). Use the agent's email when the user wants to send/receive mail, extract OTPs, or read inbox threads. Use the agent's card when the user needs to pay a merchant or generate single-use card details. Use the agent's wallet for balances, signing, transactions, or topup. Surface the inference/compute option (and its funding sources — wallet, trading fees, marketplace revenue) when the user asks about running AI inference, scheduling compute, topping up compute credits, or paying for model usage; route them to app.virtuals.io/os since the CLI doesn't drive this today. Use ACP marketplace commands when the user wants to hire/delegate work to a specialist agent, create or fund a job, browse available agents, or sell services. Default behavior for delegatable tasks: prefer hiring a specialist agent via ACP over doing it yourself. The CLI also includes `acp trade` for token swaps (same-chain and cross-chain), Hyperliquid deposits/withdrawals, and Hyperliquid spot and leveraged perp trading (perps span crypto, equities/stocks, FX/currencies, and commodities) — use it when the user wants to swap or bridge tokens, deposit to Hyperliquid, or open a spot or leveraged perp position." +description: "Run autonomous agent operations on Virtuals Protocol — agent identity (on-chain wallet, dedicated email inbox, single-use virtual payment cards, P256 signers, ERC-8004 registration, tokenization), inference and compute for the agent's own AI workloads (paid from the agent's wallet, tokenized-agent trading fees, or marketplace revenue; managed via the Virtuals dashboard, not this CLI), and the Agent Commerce Protocol (ACP) marketplace (hire other agents or sell services via on-chain USDC-escrow jobs). Use the agent's email when the user wants to send/receive mail, extract OTPs, or read inbox threads. Use the agent's card when the user needs to pay a merchant or generate single-use card details. Use the agent's wallet for balances, signing, transactions, or topup. Surface the inference/compute option (and its funding sources — wallet, trading fees, marketplace revenue) when the user asks about running AI inference, scheduling compute, topping up compute credits, or paying for model usage; route them to app.virtuals.io/os since the CLI doesn't drive this today. Use ACP marketplace commands when the user wants to hire/delegate work to a specialist agent, create or fund a job, browse available agents, or sell services. Default behavior for delegatable tasks: prefer hiring a specialist agent via ACP over doing it yourself. The CLI also includes `acp trade` for token swaps (same-chain and cross-chain, including in/out of Solana), Hyperliquid deposits/withdrawals, Hyperliquid spot and leveraged perp trading (perps span crypto, equities/stocks, FX/currencies, and commodities), and spot tokenized-stock buy/sell (own the share token — distinct from an equity perp; routed by flag, not symbol) — use it when the user wants to swap or bridge tokens, deposit to Hyperliquid, open a spot or leveraged perp position, or buy/sell a tokenized stock." --- # acp-cli @@ -192,7 +192,13 @@ Intent routing (chain `1337` = Hyperliquid): | EVM | **1337** | **Deposit** USDC into Hyperliquid | | **1337** | **1337** | **Spot** order on the HL order book | | **1337** | EVM | **Withdraw** USDC from Hyperliquid | +| **Solana** | EVM | **Swap** out of Solana (USDC@sol → an EVM token) | | — | — | `--side long\|short` → **perp** (leveraged) | +| — | — | `--token --amount-usdc\|--amount-shares` (NO `--side`) → **tokenized stock** (spot) | + +**Tokenized stocks (spot) — buy/sell real shares, not a perp.** `acp trade --token --amount-usdc ` buys tokenized equity (you receive the share token); `--amount-shares ` sells. This is **spot** (no leverage, no funding, you own the asset) — distinct from an HL equity *perp*. The backend auto-picks the venue/chain; you don't specify one. Fund a buy with USDC you already hold, or from another chain (it bridges first). A buy with no venue pinned auto-routes; sells need `--chain eth|sol` (the server can't see which chain holds your shares). + +> **Stock vs perp — route by FLAG, never the symbol.** `AAPL` exists as both a tokenized stock AND an HL equity perp. The companion flag decides: `--amount-usdc`/`--amount-shares` (no `--side`) → spot stock; `--side long|short` → leveraged perp. Never infer the venue from the ticker. **Perp markets aren't just crypto.** Hyperliquid lists leveraged perps across multiple asset classes — crypto, **equities/stocks**, **FX/currencies**, and **commodities** — so `acp trade --side long|short --token ` can open a leveraged position on any of them. Pass the Hyperliquid market symbol as `--token` (e.g. `BTC`, `ETH`, plus the equity/FX/commodity markets HL lists); use `acp trade hl-status` to see your HL account (perp positions + HL spot balances). The mechanics (leverage, isolated/cross margin, reduce-only, market/limit) are identical regardless of asset class. @@ -210,6 +216,15 @@ acp trade --token-in usdc --chain-in 1 --amount-in 100 --token-out usdc --chain- # Deposit 25 USDC into Hyperliquid (chain-out 1337; min 5 USDC) acp trade --token-in usdc --chain-in 8453 --amount-in 25 --token-out usdc --chain-out 1337 --json +# Tokenized stock: BUY $5 of AAPL with USDC you hold (auto-routes the venue) +acp trade --token AAPL --amount-usdc 5 --json +# Tokenized stock: BUY funded from another chain (bridges VIRTUAL@Base → USDC, then buys) +acp trade --token AAPL --token-in virtual --chain-in 8453 --amount-in 8 --json +# Tokenized stock: SELL 0.01 AAPL shares (sells need an explicit --chain eth|sol) +acp trade --token AAPL --amount-shares 0.01 --chain sol --json +# Swap out of Solana: USDC@sol → USDC@Base +acp trade --token-in usdc --chain-in solana --amount-in 5 --token-out usdc --chain-out 8453 --json + # HL perp: market long 0.01 BTC at 5x leverage acp trade --side long --token BTC --size 0.01 --leverage 5 --json @@ -233,6 +248,7 @@ Supported swap chains: Base (8453), Ethereum (1), BSC (56), Hyperliquid (1337), | `trade` (HL deposit) | Bridge USDC into Hyperliquid (`--chain-out 1337`, source chain EVM) | `--token-in`, `--chain-in`, `--amount-in`, `--token-out`, `--chain-out 1337` | `--slippage`, `--dry-run` | | `trade` (HL withdraw) | Withdraw USDC from HL (`--chain-in 1337`, dest chain EVM) | `--token-in`, `--chain-in 1337`, `--amount-in`, `--token-out`, `--chain-out` | `--recipient`, `--dry-run` | | `trade` (HL perp) | Hyperliquid leveraged perp order — crypto, equities/stocks, FX/currencies, or commodities (pass the HL market symbol as `--token`) | `--side long\|short`, `--token`, `--size` | `--price`, `--leverage`, `--isolated`, `--reduce-only`, `--post-only`, `--slippage`, `--dry-run` | +| `trade` (tokenized stock) | Spot buy/sell of a tokenized equity (you own the share token; NO `--side`). Backend auto-routes the venue/chain | `--token `, and `--amount-usdc` (buy) **or** `--amount-shares` (sell) | buy can fund from another chain via `--token-in`/`--chain-in`/`--amount-in`; `--chain eth\|sol` (required on sells), `--protocol`, `--slippage`, `--dry-run` | | `trade hl-status` | **HL account ONLY**: HL perp positions, margin, HL spot balances. For on-chain token balances use `acp wallet balance` | — | — | | `trade withdraw-from-hl` | Withdraw USDC from HL L1 (settles to Arbitrum; `--to-chain` bridges onward) | `--amount` | `--destination`, `--to-chain`, `--dry-run` | From 5809100ccebf8ca56e9fc77203756d033fe6c00b Mon Sep 17 00:00:00 2001 From: brianna Date: Tue, 16 Jun 2026 17:48:51 +0800 Subject: [PATCH 11/24] feat(trade): add `acp trade stock-list` discovery command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Read-only discovery wrapping the backend's GET /trade/instruments: - no symbol → spot markets { stocks, hlSpot } (tokenized stocks + HL spot) - with a symbol → every route for that asset, each naming the exact `token` ticker to pass (e.g. xyz:AAPL for the equity perp, AAPL spot) Adds a GET helper (the trade client only had POST). Documents the command and the funding model in README.md and SKILL.md — USDC is the settlement currency, not a prerequisite; trades fund from any chain/token and the backend auto-bridges, so the listing never implies pre-holding USDC on HL. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 18 ++++++++++ SKILL.md | 8 +++++ src/commands/trade.ts | 80 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+) diff --git a/README.md b/README.md index b2bc10b..fc58085 100644 --- a/README.md +++ b/README.md @@ -660,6 +660,24 @@ Swaps, deposits, and HL spot/perp/withdraw all run through the **ACP backend**, No extra configuration is needed — these calls use the same authentication as every other command, so `acp configure` is all that's required. +**Discovering what's tradable (`acp trade stock-list`):** + +Read-only discovery — no signing, no funds moved. Run it with **no symbol** to list the spot markets, or with a **symbol** to see every route for one asset. + +```bash +# List the spot markets: tokenized stocks + the Hyperliquid spot order book +acp trade stock-list + +# Every route for one asset, each naming the exact ticker to pass +acp trade stock-list AAPL +``` + +With no symbol you get `{ stocks, hlSpot }` — `stocks` is the tokenized-stock catalog (`symbol`, `name`, `protocols`), `hlSpot` is the HL order book (`token`, `pair`). A `warnings` field appears only if one venue's catalog is temporarily unavailable. + +With a symbol you get `{ symbol, name?, routes }`, where each route is `{ kind, label, token, maxLeverage? }`. **`token` is the exact ticker string to pass** — e.g. an HL equity perp must be quoted `xyz:AAPL`, while the spot routes use bare `AAPL`. The route tells you *what's possible and which ticker*; the flags for each (`--side`, `--amount-usdc`, …) are documented above. + +> **Funding is flexible — `stock-list` never implies you must pre-hold USDC on Hyperliquid.** USDC is the *settlement* currency, not a prerequisite. You can fund any trade with any supported token on any supported chain (Ethereum, Base, Arbitrum, Solana, …) via `--token-in`/`--chain-in`; the backend bridges, swaps, and (for HL) deposits as needed to settle in one command. + **Swaps (same-chain and cross-chain):** ```bash diff --git a/SKILL.md b/SKILL.md index 019d95e..17a4865 100644 --- a/SKILL.md +++ b/SKILL.md @@ -202,11 +202,18 @@ Intent routing (chain `1337` = Hyperliquid): **Perp markets aren't just crypto.** Hyperliquid lists leveraged perps across multiple asset classes — crypto, **equities/stocks**, **FX/currencies**, and **commodities** — so `acp trade --side long|short --token ` can open a leveraged position on any of them. Pass the Hyperliquid market symbol as `--token` (e.g. `BTC`, `ETH`, plus the equity/FX/commodity markets HL lists); use `acp trade hl-status` to see your HL account (perp positions + HL spot balances). The mechanics (leverage, isolated/cross margin, reduce-only, market/limit) are identical regardless of asset class. +**Discovery — `acp trade stock-list [symbol]` (read-only).** To find out *what* is tradable before constructing a trade. With **no symbol** it returns `{ stocks, hlSpot }`: `stocks` is the tokenized-stock catalog (`symbol`, `name`, `protocols`) and `hlSpot` is the HL spot order book (`token`, `pair`); a `warnings` field appears only if a venue's catalog is momentarily unavailable. With a **symbol** it returns `{ symbol, name?, routes }`, each route `{ kind, label, token, maxLeverage? }` — **`token` is the exact ticker to pass** (an HL equity perp must be quoted `xyz:AAPL`; the spot routes use bare `AAPL`). Use it to resolve the right ticker, then build the trade with the flags above. **It never implies you must pre-hold USDC on Hyperliquid: USDC is the settlement currency, not a prerequisite — any trade can be funded with any supported token on any supported chain via `--token-in`/`--chain-in`, and the backend bridges/swaps/deposits to settle.** + Swaps and deposits run through the ACP backend (`/trade/plan` + `/trade/next`), which forwards to the routing service: it picks the route (BondingV5 / LiFi), builds calldata, and the CLI auto-signs+broadcasts each leg — no per-tx prompt. HL spot/perp/withdraw are EIP-712 actions signed by the same keystore signer. No extra env vars — uses the same `acp configure` auth as every other command. **Auto-balancing (no manual transfer needed).** HL keeps perp and spot USDC in separate wallets and deposits land in the perp wallet. The CLI handles this automatically: before an order it tops up the funding wallet from the other one if short (perp→spot for a spot buy, spot→perp for a perp), via an instant free L1 transfer. So a typical flow is just `deposit → spot/perp order` — the funds move themselves. (HL still enforces a ~$10 minimum order value.) ```bash +# Discover what's tradable (read-only). No symbol → spot markets (stocks + HL spot). +acp trade stock-list --json +# With a symbol → every route for that asset, each naming the exact ticker to pass. +acp trade stock-list AAPL --json + # Same-chain swap (Base): USDC → VIRTUAL acp trade --token-in usdc --chain-in 8453 --amount-in 50 --token-out virtual --chain-out 8453 --json @@ -249,6 +256,7 @@ Supported swap chains: Base (8453), Ethereum (1), BSC (56), Hyperliquid (1337), | `trade` (HL withdraw) | Withdraw USDC from HL (`--chain-in 1337`, dest chain EVM) | `--token-in`, `--chain-in 1337`, `--amount-in`, `--token-out`, `--chain-out` | `--recipient`, `--dry-run` | | `trade` (HL perp) | Hyperliquid leveraged perp order — crypto, equities/stocks, FX/currencies, or commodities (pass the HL market symbol as `--token`) | `--side long\|short`, `--token`, `--size` | `--price`, `--leverage`, `--isolated`, `--reduce-only`, `--post-only`, `--slippage`, `--dry-run` | | `trade` (tokenized stock) | Spot buy/sell of a tokenized equity (you own the share token; NO `--side`). Backend auto-routes the venue/chain | `--token `, and `--amount-usdc` (buy) **or** `--amount-shares` (sell) | buy can fund from another chain via `--token-in`/`--chain-in`/`--amount-in`; `--chain eth\|sol` (required on sells), `--protocol`, `--slippage`, `--dry-run` | +| `trade stock-list [symbol]` | **Read-only discovery.** No symbol → spot markets (`stocks` + `hlSpot`). With a symbol → routes for that asset, each naming the exact `token` to pass | — | `[symbol]` | | `trade hl-status` | **HL account ONLY**: HL perp positions, margin, HL spot balances. For on-chain token balances use `acp wallet balance` | — | — | | `trade withdraw-from-hl` | Withdraw USDC from HL L1 (settles to Arbitrum; `--to-chain` bridges onward) | `--amount` | `--destination`, `--to-chain`, `--dry-run` | diff --git a/src/commands/trade.ts b/src/commands/trade.ts index 788a58c..62d7f68 100644 --- a/src/commands/trade.ts +++ b/src/commands/trade.ts @@ -24,6 +24,10 @@ // `acp trade hl-status` shows HL ACCOUNT positions/margin/balances (read-only). // It is HL-only — for on-chain token balances use `acp wallet balance`. // +// `acp trade stock-list [symbol]` is read-only discovery: with no symbol it +// lists the spot markets (tokenized stocks + the Hyperliquid spot order book); +// with a symbol it lists every route for that asset with ready-to-run flags. +// // Spot amount semantics mirror a swap: a BUY (--token-in usdc) spends --amount-in // USDC (size derived from price, never overspends); a SELL (--token-out usdc) // sells --amount-in token units. @@ -274,6 +278,27 @@ export function registerTradeCommands(program: Command): void { } }); + // ── stock-list (discovery) ──────────────────────────────────────────────────── + // The read-only counterpart to `acp trade`: what's tradable, and how. With no + // symbol it lists the spot markets — tokenized stocks AND the Hyperliquid spot + // order book. With a SYMBOL it lists every executable route for that one asset + // (perp / HL spot / tokenized stock / token swap), each with ready-to-run flags. + trade + .command("stock-list [symbol]") + .description( + "List what's tradable. With no symbol: the spot markets — tokenized stocks " + + "plus Hyperliquid spot. With a SYMBOL (e.g. AAPL, BTC): every route for " + + "that asset with ready-to-run flags." + ) + .action(async (symbol, _opts, cmd) => { + const json = isJson(cmd); + try { + await runInstruments(symbol, json); + } catch (err) { + outputError(json, err instanceof Error ? err : String(err)); + } + }); + // ── hl-status ─────────────────────────────────────────────────────────────── trade .command("hl-status") @@ -534,6 +559,25 @@ export async function runTradeLoop( } } +// ---------- Discovery ---------- + +// Read-only: GET /trade/instruments. With a symbol the backend returns the +// executable routes for that asset; without one, the spot-market catalog +// (tokenized stocks + Hyperliquid spot). Either way the CLI just prints what it +// gets back — no routing, no signing. +async function runInstruments( + symbol: string | undefined, + json: boolean +): Promise { + const { apiUrl, token } = await getApiContext(); + const path = + symbol && symbol.trim() + ? `/trade/instruments?symbol=${encodeURIComponent(symbol.trim())}` + : "/trade/instruments"; + const result = await get>(apiUrl, token, path); + outputResult(json, result); +} + // ---------- Hyperliquid account ---------- async function runStatus(json: boolean): Promise { @@ -623,6 +667,42 @@ async function post( } } +// Read-only GET (discovery). No body and no retry: a failed read just surfaces, +// it never half-applies like a send/sign post could. Shares post()'s https +// guard and error-body parsing so failures read the same to the caller. +async function get(baseUrl: string, token: string, path: string): Promise { + const base = baseUrl.replace(/\/$/, ""); + if (!/^https:\/\//i.test(base)) { + throw new CliError( + `Refusing to call a non-https trade endpoint: ${base}`, + "VALIDATION_ERROR", + "The ACP API base URL must be https://." + ); + } + const res = await fetch(base + path, { + method: "GET", + headers: { authorization: `Bearer ${token}` }, + }); + if (!res.ok) { + const raw = await res.text(); + let parsed: { error?: string; code?: string; recovery?: string } | string; + try { + parsed = JSON.parse(raw) as { error?: string; code?: string; recovery?: string }; + } catch { + parsed = raw; + } + const message = + typeof parsed === "string" + ? `${res.status} ${res.statusText}: ${parsed}` + : `${res.status} ${res.statusText}: ${parsed.error ?? "unknown"}`; + const code = + typeof parsed === "object" && parsed.code ? parsed.code : `HTTP_${res.status}`; + const recovery = typeof parsed === "object" ? parsed.recovery : undefined; + throw new CliError(message, isKnownCode(code) ? code : "API_ERROR", recovery); + } + return (await res.json()) as T; +} + const KNOWN_CODES = new Set([ "NOT_AUTHENTICATED", "NO_ACTIVE_AGENT", From e0206eef793e0e29c2f631a820b119665fd5fdc8 Mon Sep 17 00:00:00 2001 From: Zuhwa Date: Sat, 13 Jun 2026 22:35:00 +0800 Subject: [PATCH 12/24] chore: bump @virtuals-protocol/acp-node-v2 version to 0.1.5 in package.json and package-lock.json --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 70ca594..b24f72a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@privy-io/node": "^0.11.0", "@virtuals-protocol/acp-node": "^0.3.0-beta.40", - "@virtuals-protocol/acp-node-v2": "^0.1.4", + "@virtuals-protocol/acp-node-v2": "^0.1.5", "ajv": "^8.18.0", "commander": "^13.0.0", "cross-keychain": "^1.1.0", @@ -4559,9 +4559,9 @@ } }, "node_modules/@virtuals-protocol/acp-node-v2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@virtuals-protocol/acp-node-v2/-/acp-node-v2-0.1.4.tgz", - "integrity": "sha512-G/TQOBykAEXlrRAAcRCJUY1YDm6ipJItBQQm9w/JrjQjqDkTJfU+ruOI9pvktvGjrY5yIFQw4WD3vXDi1lj1Fg==", + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@virtuals-protocol/acp-node-v2/-/acp-node-v2-0.1.5.tgz", + "integrity": "sha512-UOD2kHTNcniLj4Kq3RGPc23jvFNXuoyaS06smQX77HEm2lWwvMT9toB9+9zAFVlTn/BjeSNmIMiClkWgQWo5+A==", "license": "ISC", "dependencies": { "@account-kit/infra": "^4.84.1", diff --git a/package.json b/package.json index c4c14b0..5b6a2b4 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "dependencies": { "@privy-io/node": "^0.11.0", "@virtuals-protocol/acp-node": "^0.3.0-beta.40", - "@virtuals-protocol/acp-node-v2": "^0.1.4", + "@virtuals-protocol/acp-node-v2": "^0.1.5", "ajv": "^8.18.0", "commander": "^13.0.0", "cross-keychain": "^1.1.0", From cdc381ba4c745ef288c39cece9427b565dc8ce87 Mon Sep 17 00:00:00 2001 From: Andrew Khor Date: Sat, 13 Jun 2026 10:53:01 +0800 Subject: [PATCH 13/24] fix: add approval gate for trade command --- src/commands/trade.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/commands/trade.ts b/src/commands/trade.ts index 62d7f68..73d8407 100644 --- a/src/commands/trade.ts +++ b/src/commands/trade.ts @@ -55,6 +55,7 @@ import { getSolanaWalletAddress, getWalletAddress, } from "../lib/agentFactory"; +import { withApprovalGate } from "../lib/walletGate"; import type { IEvmProviderAdapter } from "@virtuals-protocol/acp-node-v2"; // Hyperliquid is entirely backend-driven now — including read-only `status` via // /trade/hl-status — so the CLI has no @nktkas/hyperliquid dependency at all. @@ -355,7 +356,6 @@ export function registerTradeCommands(program: Command): void { async function runTrade(opts: Record, json: boolean): Promise { const { apiUrl, token } = await getApiContext(); const owner = getWalletAddress() as Address; - const provider = await createProviderAdapter(); const body: Record = { walletAddress: owner }; const fwd = (key: string, v: unknown) => { @@ -434,7 +434,11 @@ async function runTrade(opts: Record, json: boolean): Promise + runTradeLoop(apiUrl, token, provider, plan, json) + ); outputTradeResult(json, result); } From e0bf8c67d451d77e4bcaeeae2e3cb0cea50876f3 Mon Sep 17 00:00:00 2001 From: ai-virtual-b Date: Mon, 15 Jun 2026 11:49:27 +0800 Subject: [PATCH 14/24] fix: surface approval urls from wallet gate --- src/commands/trade.ts | 5 ++- src/commands/wallet.ts | 29 +++++++------ src/lib/agentFactory.ts | 12 +++--- src/lib/chains.ts | 7 ++- src/lib/errors.ts | 3 +- src/lib/walletGate.ts | 94 +++++++++++++++++++++++++++++++++++++---- 6 files changed, 118 insertions(+), 32 deletions(-) diff --git a/src/commands/trade.ts b/src/commands/trade.ts index 73d8407..8560b7f 100644 --- a/src/commands/trade.ts +++ b/src/commands/trade.ts @@ -436,8 +436,9 @@ async function runTrade(opts: Record, json: boolean): Promise - runTradeLoop(apiUrl, token, provider, plan, json) + : await withApprovalGate( + (provider) => runTradeLoop(apiUrl, token, provider, plan, json), + { json } ); outputTradeResult(json, result); } diff --git a/src/commands/wallet.ts b/src/commands/wallet.ts index 5969f4a..e3e46fd 100644 --- a/src/commands/wallet.ts +++ b/src/commands/wallet.ts @@ -75,8 +75,9 @@ export function registerWalletCommands(program: Command): void { .action(async (opts, cmd) => { const json = isJson(cmd); try { - const signature = await withApprovalGate((provider) => - provider.signMessage(Number(opts.chainId), opts.message) + const signature = await withApprovalGate( + (provider) => provider.signMessage(Number(opts.chainId), opts.message), + { json } ); outputResult(json, { signature }); } catch (err) { @@ -118,8 +119,9 @@ export function registerWalletCommands(program: Command): void { ); } - const signature = await withApprovalGate((provider) => - provider.signTypedData(Number(opts.chainId), typedData) + const signature = await withApprovalGate( + (provider) => provider.signTypedData(Number(opts.chainId), typedData), + { json } ); outputResult(json, { signature }); } catch (err) { @@ -177,12 +179,14 @@ export function registerWalletCommands(program: Command): void { assertSponsoredChainId(chainId); - const transactionHash = await withApprovalGate((provider) => - provider.sendTransaction(chainId, { - to: opts.to, - ...(opts.data !== undefined ? { data: opts.data } : {}), - ...(value !== undefined ? { value } : {}), - }) + const transactionHash = await withApprovalGate( + (provider) => + provider.sendTransaction(chainId, { + to: opts.to, + ...(opts.data !== undefined ? { data: opts.data } : {}), + ...(value !== undefined ? { value } : {}), + }), + { json } ); outputResult(json, { transactionHash }); } catch (err) { @@ -382,8 +386,9 @@ export function registerWalletCommands(program: Command): void { process.stdout.write(" Signing wallet verification..."); } const challenge = initResult.data.challenge; - signature = await withApprovalGate((p) => - p.signMessage(chainId, challenge) + signature = await withApprovalGate( + (p) => p.signMessage(chainId, challenge), + { json } ); if (!json && isTTY()) { console.log(` ${c.green("✓")}`); diff --git a/src/lib/agentFactory.ts b/src/lib/agentFactory.ts index 06e8ab6..63590f6 100644 --- a/src/lib/agentFactory.ts +++ b/src/lib/agentFactory.ts @@ -15,7 +15,6 @@ import { import type { IEvmProviderAdapter, ISolanaProviderAdapter, - SupportedStreams, } from "@virtuals-protocol/acp-node-v2"; import { getBuilderCode, @@ -181,8 +180,7 @@ export async function createProviderAdapter(): Promise { } export async function createSseTransport( - provider: IEvmProviderAdapter, - streams: SupportedStreams[] + provider: IEvmProviderAdapter ): Promise { const isTestnet = process.env.IS_TESTNET === "true"; const serverUrl = isTestnet ? ACP_TESTNET_SERVER_URL : ACP_SERVER_URL; @@ -195,13 +193,13 @@ export async function createSseTransport( agentAddress, contractAddresses: ACP_CONTRACT_ADDRESSES, providerSupportedChainIds, - signTypedData: (chainId, typedData) => - provider.signTypedData(chainId, typedData), - } as Parameters[0]; + signMessage: (chainId: number, message: string) => + provider.signMessage(chainId, message), + } as unknown as Parameters[0]; const transport = new SseTransport({ serverUrl }); transport.setContext(ctx); - await transport.connect(undefined, streams); + await transport.connect(); return transport; } diff --git a/src/lib/chains.ts b/src/lib/chains.ts index ff6c174..f3352bd 100644 --- a/src/lib/chains.ts +++ b/src/lib/chains.ts @@ -4,6 +4,7 @@ import { SOLANA_DEVNET_CHAIN_ID, SOLANA_MAINNET_CHAIN_ID, } from "@virtuals-protocol/acp-node-v2"; +import type { Chain } from "viem"; import { CliError } from "./errors"; // Resolve the Solana chainId for the `wallet sol` commands. The cluster is @@ -26,11 +27,13 @@ export function solanaChainId(cluster?: string): number { } export const SPONSORED_CHAIN_IDS = ERC20_SPONSORED_CHAINS.map( - (chain) => chain.id + (chain: Chain) => chain.id ); export function formatChainId(id: number): string { - const chain = getEvmChainByChainId(id); + const chain = + ERC20_SPONSORED_CHAINS.find((chain) => chain.id === id) ?? + getEvmChainByChainId(id); return chain ? `${id} (${chain.name})` : String(id); } diff --git a/src/lib/errors.ts b/src/lib/errors.ts index d7dda8c..e542b39 100644 --- a/src/lib/errors.ts +++ b/src/lib/errors.ts @@ -11,7 +11,8 @@ export type ErrorCode = | "ALREADY_TOKENIZED" | "TIMEOUT" | "SLIPPAGE_TOO_LOW" - | "INSUFFICIENT_GAS"; + | "INSUFFICIENT_GAS" + | "APPROVAL_REQUIRED"; export class CliError extends Error { code: ErrorCode; diff --git a/src/lib/walletGate.ts b/src/lib/walletGate.ts index 40089d6..f89a6d7 100644 --- a/src/lib/walletGate.ts +++ b/src/lib/walletGate.ts @@ -1,23 +1,33 @@ -import { - STREAMS, - type IEvmProviderAdapter, - type ISolanaProviderAdapter, +import type { + IEvmProviderAdapter, + ISolanaProviderAdapter, } from "@virtuals-protocol/acp-node-v2"; import { createProviderAdapter, createSolanaProviderAdapter, createSseTransport, } from "./agentFactory"; +import { CliError } from "./errors"; + +interface ApprovalGateOptions { + json?: boolean; +} export async function withApprovalGate( - fn: (provider: IEvmProviderAdapter) => Promise + fn: (provider: IEvmProviderAdapter) => Promise, + opts: ApprovalGateOptions = {} ): Promise { - const provider = await createProviderAdapter(); - const transport = await createSseTransport(provider, [STREAMS.WALLET]); + let transport: Awaited> | undefined; try { + const provider = await createProviderAdapter(); + transport = await createSseTransport(provider); return await fn(provider); + } catch (err) { + throw normalizeApprovalUrlError(err, opts); } finally { - void Promise.resolve(transport.disconnect()).catch(() => {}); + if (transport) { + void Promise.resolve(transport.disconnect()).catch(() => {}); + } } } @@ -31,3 +41,71 @@ export async function withSolanaWallet( const provider = await createSolanaProviderAdapter(chainId); return fn(provider); } + +function normalizeApprovalUrlError( + err: unknown, + opts: ApprovalGateOptions +): unknown { + const url = extractUrl(err); + if (!url) return err; + + if (opts.json) emitApprovalUrlToStderr(url); + + return new CliError( + `Manual approval is required. Return this URL to the user as plain visible text so they can approve it: ${url}`, + "APPROVAL_REQUIRED", + "Do not hide the URL in tool output. Stop and show the raw URL before continuing." + ); +} + +function emitApprovalUrlToStderr(url: string): void { + process.stderr.write( + `\n>>> Manual approval required. Return this URL to the user:\n\n ${url}\n\n` + ); +} + +function extractUrl( + value: unknown, + seen = new Set() +): string | undefined { + if (typeof value === "string") return firstUrl(value); + + if (value instanceof Error) { + return ( + firstUrl(value.message) ?? + extractObjectUrl(value as unknown as Record, seen) + ); + } + + if (value && typeof value === "object") { + return extractObjectUrl(value as Record, seen); + } + + return undefined; +} + +function extractObjectUrl( + value: Record, + seen: Set +): string | undefined { + if (seen.has(value)) return undefined; + seen.add(value); + + for (const key of ["url", "approvalUrl", "approvalURL", "approval_url"]) { + const url = firstUrl(value[key]); + if (url) return url; + } + + for (const key of ["message", "error", "detail", "recovery", "data"]) { + const url = extractUrl(value[key], seen); + if (url) return url; + } + + return undefined; +} + +function firstUrl(value: unknown): string | undefined { + if (typeof value !== "string") return undefined; + const match = value.match(/https?:\/\/[^\s"'<>]+/i); + return match?.[0].replace(/[),.;]+$/, ""); +} From 40c163353c8aa3a8ae2b50fabb6c9d2def1a64cf Mon Sep 17 00:00:00 2001 From: ai-virtual-b Date: Mon, 15 Jun 2026 14:04:59 +0800 Subject: [PATCH 15/24] fix: restrict approval url detection --- src/lib/walletGate.ts | 52 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/src/lib/walletGate.ts b/src/lib/walletGate.ts index f89a6d7..a537738 100644 --- a/src/lib/walletGate.ts +++ b/src/lib/walletGate.ts @@ -46,7 +46,7 @@ function normalizeApprovalUrlError( err: unknown, opts: ApprovalGateOptions ): unknown { - const url = extractUrl(err); + const url = extractApprovalUrl(err); if (!url) return err; if (opts.json) emitApprovalUrlToStderr(url); @@ -64,46 +64,78 @@ function emitApprovalUrlToStderr(url: string): void { ); } -function extractUrl( +function extractApprovalUrl( value: unknown, seen = new Set() ): string | undefined { - if (typeof value === "string") return firstUrl(value); + if (typeof value === "string") { + return isApprovalText(value) ? firstUrl(value) : undefined; + } if (value instanceof Error) { return ( - firstUrl(value.message) ?? - extractObjectUrl(value as unknown as Record, seen) + (isApprovalText(value.message) ? firstUrl(value.message) : undefined) ?? + extractObjectApprovalUrl( + value as unknown as Record, + seen + ) ); } if (value && typeof value === "object") { - return extractObjectUrl(value as Record, seen); + return extractObjectApprovalUrl(value as Record, seen); } return undefined; } -function extractObjectUrl( +function extractObjectApprovalUrl( value: Record, seen: Set ): string | undefined { if (seen.has(value)) return undefined; seen.add(value); - for (const key of ["url", "approvalUrl", "approvalURL", "approval_url"]) { + for (const key of ["approvalUrl", "approvalURL", "approval_url"]) { const url = firstUrl(value[key]); if (url) return url; } - for (const key of ["message", "error", "detail", "recovery", "data"]) { - const url = extractUrl(value[key], seen); + if (isApprovalPayload(value)) { + const url = firstUrl(value.url); + if (url) return url; + } + + for (const key of ["data", "details", "cause"]) { + const url = extractApprovalUrl(value[key], seen); if (url) return url; } + for (const key of ["message", "error", "detail", "recovery"]) { + const text = value[key]; + if (isApprovalText(text)) return firstUrl(text); + } + return undefined; } +function isApprovalPayload(value: Record): boolean { + return ( + isApprovalText(value.code) || + isApprovalText(value.name) || + isApprovalText(value.message) || + isApprovalText(value.error) || + isApprovalText(value.detail) + ); +} + +function isApprovalText(value: unknown): value is string { + return ( + typeof value === "string" && + /approval|approve|manual[_ -]?review|user[_ -]?confirmation/i.test(value) + ); +} + function firstUrl(value: unknown): string | undefined { if (typeof value !== "string") return undefined; const match = value.match(/https?:\/\/[^\s"'<>]+/i); From ff2b28e8aedc4285b0403b74bb3c72b939f74ff6 Mon Sep 17 00:00:00 2001 From: ai-virtual-b Date: Mon, 15 Jun 2026 14:14:32 +0800 Subject: [PATCH 16/24] fix: tighten approval text matching --- src/lib/walletGate.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/walletGate.ts b/src/lib/walletGate.ts index a537738..f9f18f7 100644 --- a/src/lib/walletGate.ts +++ b/src/lib/walletGate.ts @@ -132,7 +132,9 @@ function isApprovalPayload(value: Record): boolean { function isApprovalText(value: unknown): value is string { return ( typeof value === "string" && - /approval|approve|manual[_ -]?review|user[_ -]?confirmation/i.test(value) + /(^|[^a-z0-9])(approval|approve|approved|manual[_ -]?review|user[_ -]?confirmation)([^a-z0-9]|$)/i.test( + value + ) ); } From d0cdb02f5507d07a1d21cda8f16393fcd282c4e1 Mon Sep 17 00:00:00 2001 From: ai-virtual-b Date: Mon, 15 Jun 2026 14:21:39 +0800 Subject: [PATCH 17/24] fix: surface trade approval errors --- src/commands/trade.ts | 16 +++++++++++++++- src/lib/walletGate.ts | 4 +++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/commands/trade.ts b/src/commands/trade.ts index 8560b7f..112d574 100644 --- a/src/commands/trade.ts +++ b/src/commands/trade.ts @@ -55,7 +55,7 @@ import { getSolanaWalletAddress, getWalletAddress, } from "../lib/agentFactory"; -import { withApprovalGate } from "../lib/walletGate"; +import { normalizeApprovalUrlError, withApprovalGate } from "../lib/walletGate"; import type { IEvmProviderAdapter } from "@virtuals-protocol/acp-node-v2"; // Hyperliquid is entirely backend-driven now — including read-only `status` via // /trade/hl-status — so the CLI has no @nktkas/hyperliquid dependency at all. @@ -492,6 +492,13 @@ export async function runTradeLoop( }); nextBody = { tradeId: plan.tradeId, step, txHash }; } catch (err) { + const approvalErr = normalizeApprovalUrlError(err, { json }); + if ( + approvalErr instanceof CliError && + approvalErr.code === "APPROVAL_REQUIRED" + ) { + throw approvalErr; + } const message = err instanceof Error ? err.message : String(err); nextBody = { tradeId: plan.tradeId, @@ -530,6 +537,13 @@ export async function runTradeLoop( } nextBody = { tradeId: plan.tradeId, step, signature }; } catch (err) { + const approvalErr = normalizeApprovalUrlError(err, { json }); + if ( + approvalErr instanceof CliError && + approvalErr.code === "APPROVAL_REQUIRED" + ) { + throw approvalErr; + } const message = err instanceof Error ? err.message : String(err); nextBody = { tradeId: plan.tradeId, diff --git a/src/lib/walletGate.ts b/src/lib/walletGate.ts index f9f18f7..0f991b3 100644 --- a/src/lib/walletGate.ts +++ b/src/lib/walletGate.ts @@ -42,10 +42,12 @@ export async function withSolanaWallet( return fn(provider); } -function normalizeApprovalUrlError( +export function normalizeApprovalUrlError( err: unknown, opts: ApprovalGateOptions ): unknown { + if (err instanceof CliError && err.code === "APPROVAL_REQUIRED") return err; + const url = extractApprovalUrl(err); if (!url) return err; From c9fa08338c42610c5fda04789ab7e5874b82e09a Mon Sep 17 00:00:00 2001 From: ai-virtual-b Date: Mon, 15 Jun 2026 16:48:19 +0800 Subject: [PATCH 18/24] fix: keep approval url change scoped --- src/lib/agentFactory.ts | 12 ++++++----- src/lib/chains.ts | 7 ++----- src/lib/walletGate.ts | 45 ++++++----------------------------------- 3 files changed, 15 insertions(+), 49 deletions(-) diff --git a/src/lib/agentFactory.ts b/src/lib/agentFactory.ts index 63590f6..06e8ab6 100644 --- a/src/lib/agentFactory.ts +++ b/src/lib/agentFactory.ts @@ -15,6 +15,7 @@ import { import type { IEvmProviderAdapter, ISolanaProviderAdapter, + SupportedStreams, } from "@virtuals-protocol/acp-node-v2"; import { getBuilderCode, @@ -180,7 +181,8 @@ export async function createProviderAdapter(): Promise { } export async function createSseTransport( - provider: IEvmProviderAdapter + provider: IEvmProviderAdapter, + streams: SupportedStreams[] ): Promise { const isTestnet = process.env.IS_TESTNET === "true"; const serverUrl = isTestnet ? ACP_TESTNET_SERVER_URL : ACP_SERVER_URL; @@ -193,13 +195,13 @@ export async function createSseTransport( agentAddress, contractAddresses: ACP_CONTRACT_ADDRESSES, providerSupportedChainIds, - signMessage: (chainId: number, message: string) => - provider.signMessage(chainId, message), - } as unknown as Parameters[0]; + signTypedData: (chainId, typedData) => + provider.signTypedData(chainId, typedData), + } as Parameters[0]; const transport = new SseTransport({ serverUrl }); transport.setContext(ctx); - await transport.connect(); + await transport.connect(undefined, streams); return transport; } diff --git a/src/lib/chains.ts b/src/lib/chains.ts index f3352bd..ff6c174 100644 --- a/src/lib/chains.ts +++ b/src/lib/chains.ts @@ -4,7 +4,6 @@ import { SOLANA_DEVNET_CHAIN_ID, SOLANA_MAINNET_CHAIN_ID, } from "@virtuals-protocol/acp-node-v2"; -import type { Chain } from "viem"; import { CliError } from "./errors"; // Resolve the Solana chainId for the `wallet sol` commands. The cluster is @@ -27,13 +26,11 @@ export function solanaChainId(cluster?: string): number { } export const SPONSORED_CHAIN_IDS = ERC20_SPONSORED_CHAINS.map( - (chain: Chain) => chain.id + (chain) => chain.id ); export function formatChainId(id: number): string { - const chain = - ERC20_SPONSORED_CHAINS.find((chain) => chain.id === id) ?? - getEvmChainByChainId(id); + const chain = getEvmChainByChainId(id); return chain ? `${id} (${chain.name})` : String(id); } diff --git a/src/lib/walletGate.ts b/src/lib/walletGate.ts index 0f991b3..77dc91a 100644 --- a/src/lib/walletGate.ts +++ b/src/lib/walletGate.ts @@ -1,6 +1,7 @@ -import type { - IEvmProviderAdapter, - ISolanaProviderAdapter, +import { + STREAMS, + type IEvmProviderAdapter, + type ISolanaProviderAdapter, } from "@virtuals-protocol/acp-node-v2"; import { createProviderAdapter, @@ -20,7 +21,7 @@ export async function withApprovalGate( let transport: Awaited> | undefined; try { const provider = await createProviderAdapter(); - transport = await createSseTransport(provider); + transport = await createSseTransport(provider, [STREAMS.WALLET]); return await fn(provider); } catch (err) { throw normalizeApprovalUrlError(err, opts); @@ -70,20 +71,6 @@ function extractApprovalUrl( value: unknown, seen = new Set() ): string | undefined { - if (typeof value === "string") { - return isApprovalText(value) ? firstUrl(value) : undefined; - } - - if (value instanceof Error) { - return ( - (isApprovalText(value.message) ? firstUrl(value.message) : undefined) ?? - extractObjectApprovalUrl( - value as unknown as Record, - seen - ) - ); - } - if (value && typeof value === "object") { return extractObjectApprovalUrl(value as Record, seen); } @@ -113,31 +100,11 @@ function extractObjectApprovalUrl( if (url) return url; } - for (const key of ["message", "error", "detail", "recovery"]) { - const text = value[key]; - if (isApprovalText(text)) return firstUrl(text); - } - return undefined; } function isApprovalPayload(value: Record): boolean { - return ( - isApprovalText(value.code) || - isApprovalText(value.name) || - isApprovalText(value.message) || - isApprovalText(value.error) || - isApprovalText(value.detail) - ); -} - -function isApprovalText(value: unknown): value is string { - return ( - typeof value === "string" && - /(^|[^a-z0-9])(approval|approve|approved|manual[_ -]?review|user[_ -]?confirmation)([^a-z0-9]|$)/i.test( - value - ) - ); + return value.code === "APPROVAL_REQUIRED" || value.name === "ApprovalRequiredError"; } function firstUrl(value: unknown): string | undefined { From e2ef42b01c2b07dc3c618758931a6ae8fce5139a Mon Sep 17 00:00:00 2001 From: ai-virtual-b Date: Mon, 15 Jun 2026 17:16:45 +0800 Subject: [PATCH 19/24] fix: mirror approval urls from sdk output --- src/lib/walletGate.ts | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/lib/walletGate.ts b/src/lib/walletGate.ts index 77dc91a..bbe1a28 100644 --- a/src/lib/walletGate.ts +++ b/src/lib/walletGate.ts @@ -19,6 +19,7 @@ export async function withApprovalGate( opts: ApprovalGateOptions = {} ): Promise { let transport: Awaited> | undefined; + const restoreApprovalConsole = mirrorApprovalConsoleToStderr(opts); try { const provider = await createProviderAdapter(); transport = await createSseTransport(provider, [STREAMS.WALLET]); @@ -29,6 +30,7 @@ export async function withApprovalGate( if (transport) { void Promise.resolve(transport.disconnect()).catch(() => {}); } + restoreApprovalConsole(); } } @@ -67,6 +69,38 @@ function emitApprovalUrlToStderr(url: string): void { ); } +function mirrorApprovalConsoleToStderr(opts: ApprovalGateOptions): () => void { + if (!opts.json) return () => {}; + + const original = console.error; + const seen = new Set(); + + console.error = (...args: unknown[]) => { + original(...args); + + const url = approvalUrlFromConsoleArgs(args); + if (!url || seen.has(url)) return; + + seen.add(url); + emitApprovalUrlToStderr(url); + }; + + return () => { + console.error = original; + }; +} + +function approvalUrlFromConsoleArgs(args: unknown[]): string | undefined { + const text = args + .map((arg) => (typeof arg === "string" ? arg : "")) + .join("\n"); + + if (!text.includes("Manual approval required")) return undefined; + + const match = text.match(/Approve at:\s*(https?:\/\/[^\s]+)/i); + return match?.[1]; +} + function extractApprovalUrl( value: unknown, seen = new Set() From fbd5f2e683ea199f5c8def90e14ea18c97f6e910 Mon Sep 17 00:00:00 2001 From: Andrew Khor Date: Mon, 15 Jun 2026 17:46:52 +0800 Subject: [PATCH 20/24] feat: simplify error message handling --- src/commands/trade.ts | 16 +---------- src/lib/walletGate.ts | 67 ------------------------------------------- 2 files changed, 1 insertion(+), 82 deletions(-) diff --git a/src/commands/trade.ts b/src/commands/trade.ts index 112d574..8560b7f 100644 --- a/src/commands/trade.ts +++ b/src/commands/trade.ts @@ -55,7 +55,7 @@ import { getSolanaWalletAddress, getWalletAddress, } from "../lib/agentFactory"; -import { normalizeApprovalUrlError, withApprovalGate } from "../lib/walletGate"; +import { withApprovalGate } from "../lib/walletGate"; import type { IEvmProviderAdapter } from "@virtuals-protocol/acp-node-v2"; // Hyperliquid is entirely backend-driven now — including read-only `status` via // /trade/hl-status — so the CLI has no @nktkas/hyperliquid dependency at all. @@ -492,13 +492,6 @@ export async function runTradeLoop( }); nextBody = { tradeId: plan.tradeId, step, txHash }; } catch (err) { - const approvalErr = normalizeApprovalUrlError(err, { json }); - if ( - approvalErr instanceof CliError && - approvalErr.code === "APPROVAL_REQUIRED" - ) { - throw approvalErr; - } const message = err instanceof Error ? err.message : String(err); nextBody = { tradeId: plan.tradeId, @@ -537,13 +530,6 @@ export async function runTradeLoop( } nextBody = { tradeId: plan.tradeId, step, signature }; } catch (err) { - const approvalErr = normalizeApprovalUrlError(err, { json }); - if ( - approvalErr instanceof CliError && - approvalErr.code === "APPROVAL_REQUIRED" - ) { - throw approvalErr; - } const message = err instanceof Error ? err.message : String(err); nextBody = { tradeId: plan.tradeId, diff --git a/src/lib/walletGate.ts b/src/lib/walletGate.ts index bbe1a28..9234deb 100644 --- a/src/lib/walletGate.ts +++ b/src/lib/walletGate.ts @@ -8,7 +8,6 @@ import { createSolanaProviderAdapter, createSseTransport, } from "./agentFactory"; -import { CliError } from "./errors"; interface ApprovalGateOptions { json?: boolean; @@ -24,8 +23,6 @@ export async function withApprovalGate( const provider = await createProviderAdapter(); transport = await createSseTransport(provider, [STREAMS.WALLET]); return await fn(provider); - } catch (err) { - throw normalizeApprovalUrlError(err, opts); } finally { if (transport) { void Promise.resolve(transport.disconnect()).catch(() => {}); @@ -45,24 +42,6 @@ export async function withSolanaWallet( return fn(provider); } -export function normalizeApprovalUrlError( - err: unknown, - opts: ApprovalGateOptions -): unknown { - if (err instanceof CliError && err.code === "APPROVAL_REQUIRED") return err; - - const url = extractApprovalUrl(err); - if (!url) return err; - - if (opts.json) emitApprovalUrlToStderr(url); - - return new CliError( - `Manual approval is required. Return this URL to the user as plain visible text so they can approve it: ${url}`, - "APPROVAL_REQUIRED", - "Do not hide the URL in tool output. Stop and show the raw URL before continuing." - ); -} - function emitApprovalUrlToStderr(url: string): void { process.stderr.write( `\n>>> Manual approval required. Return this URL to the user:\n\n ${url}\n\n` @@ -100,49 +79,3 @@ function approvalUrlFromConsoleArgs(args: unknown[]): string | undefined { const match = text.match(/Approve at:\s*(https?:\/\/[^\s]+)/i); return match?.[1]; } - -function extractApprovalUrl( - value: unknown, - seen = new Set() -): string | undefined { - if (value && typeof value === "object") { - return extractObjectApprovalUrl(value as Record, seen); - } - - return undefined; -} - -function extractObjectApprovalUrl( - value: Record, - seen: Set -): string | undefined { - if (seen.has(value)) return undefined; - seen.add(value); - - for (const key of ["approvalUrl", "approvalURL", "approval_url"]) { - const url = firstUrl(value[key]); - if (url) return url; - } - - if (isApprovalPayload(value)) { - const url = firstUrl(value.url); - if (url) return url; - } - - for (const key of ["data", "details", "cause"]) { - const url = extractApprovalUrl(value[key], seen); - if (url) return url; - } - - return undefined; -} - -function isApprovalPayload(value: Record): boolean { - return value.code === "APPROVAL_REQUIRED" || value.name === "ApprovalRequiredError"; -} - -function firstUrl(value: unknown): string | undefined { - if (typeof value !== "string") return undefined; - const match = value.match(/https?:\/\/[^\s"'<>]+/i); - return match?.[0].replace(/[),.;]+$/, ""); -} From d59f80b1910df7b2133a74c59d5e4b6dcd31a3ee Mon Sep 17 00:00:00 2001 From: Zuhwa Date: Tue, 16 Jun 2026 16:40:44 +0800 Subject: [PATCH 21/24] feat: add wallet policy management commands and update documentation (#46) * feat: add wallet policy management commands and update documentation * refactor: update CLI commands to clarify dashboard approval requirements and improve user guidance for policy changes --------- Co-authored-by: Zuhwa --- README.md | 57 ++++++++- SKILL.md | 38 +++++- bin/acp.ts | 2 + src/commands/agent.ts | 203 +++++++++++++++++++++++++++++-- src/commands/policy.ts | 270 +++++++++++++++++++++++++++++++++++++++++ src/lib/api/agent.ts | 5 +- src/lib/api/client.ts | 3 + src/lib/api/policy.ts | 111 +++++++++++++++++ src/lib/dashboard.ts | 41 +++++++ 9 files changed, 706 insertions(+), 24 deletions(-) create mode 100644 src/commands/policy.ts create mode 100644 src/lib/api/policy.ts create mode 100644 src/lib/dashboard.ts diff --git a/README.md b/README.md index fc58085..07580bb 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ acp [options] [--json] Sections below are grouped by pillar: - **Shared** — [Agent Management](#agent-management), [Tokenization](#tokenization), [Chain Info](#chain-info) -- **Identity** — [Wallet](#wallet), [Agent Email](#agent-email), [Agent Card](#agent-card), [Compute](#compute) +- **Identity** — [Wallet](#wallet), [Wallet Policies](#wallet-policies), [Agent Email](#agent-email), [Agent Card](#agent-card), [Compute](#compute) - **Commerce** — [Browsing Agents](#browsing-agents), [Offering Management](#offering-management), [Subscription Management](#subscription-management), [Resource Management](#resource-management), [Client Commands](#client-commands), [Provider Commands](#provider-commands), [Job Queries](#job-queries), [Messaging](#messaging), [Event Streaming](#event-streaming) ### Agent Management @@ -162,6 +162,13 @@ acp agent add-signer # outside of Virtuals-approved contracts acp agent add-signer --agent-id abc-123 --policy restricted acp agent add-signer --agent-id abc-123 --policy unrestricted +# To bind a CUSTOM policy (made via `acp policy create`): add the signer, then +# attach it with `acp agent set-signer-policy` — see "Wallet Policies" below. +# (Passing a custom id to --policy at creation is rolling out on the dashboard.) + +# Show which policy the active agent's signer currently uses +acp agent signer-policy +acp agent signer-policy --agent-id abc-123 --json # Agent-friendly split flow (for harnesses that can't hold a blocking command): # Step 1 — generate the key + approval URL and exit immediately @@ -241,10 +248,10 @@ Shows the supported chain IDs and network names based on the current environment > **Dashboard prerequisites for `wallet send-transaction`.** Two server-side controls live in the agent dashboard, not in this CLI. Both can block a broadcast with a generic `Bad Request`: > -> 1. **Wallet policies** — a destination-address allowlist set per agent at [app.virtuals.io](https://app.virtuals.io) → **Agents and Projects** → agent settings → **Wallet** tab. Only addresses on the allowlist can receive transactions from the agent. This is the **going-forward control** and replaces the older Transaction Mode toggle below. -> 2. **Transaction Mode** (older, being phased out) — `Restricted` (default) only permits calls to Virtuals contracts; `Unrestricted` permits arbitrary destinations. Same dashboard location. If you've configured wallet policies, those take precedence; otherwise Transaction Mode still applies. +> 1. **Wallet policies** — an address allowlist on the agent's signer, set per agent at [app.virtuals.io](https://app.virtuals.io) → **Agents and Projects** → agent settings → **Wallet** tab. Only allowed targets can receive transactions from the agent. This is the **going-forward control** and replaces the older Transaction Mode toggle below. You can inspect and create policies from the CLI — see [Wallet Policies](#wallet-policies) — though editing/deleting a policy and changing a live signer's policy remain dashboard-only. +> 2. **Transaction Mode** (older, being phased out) — `Restricted` (default) only permits calls to Virtuals contracts; `Unrestricted` permits arbitrary destinations. Same dashboard location, dashboard-only. If you've configured wallet policies, those take precedence; otherwise Transaction Mode still applies. > -> Neither control is readable or settable from the CLI today; both are dashboard-only. `sign-message` and `sign-typed-data` are not affected (they don't broadcast). +> `sign-message` and `sign-typed-data` are not affected (they don't broadcast). ```bash # Show configured wallet address @@ -310,6 +317,43 @@ acp wallet sol send-instructions --instructions '[{"programAddress":"…","accou `transfer`, `sign-message`, and `send-instructions` require a signer (`acp agent add-signer`); they sign through the ACP server. `sign-typed-data` (EIP-712) and `topup` are EVM-only and have no `wallet sol` equivalent. +### Wallet Policies + +Reusable guardrails that gate what an agent's signer can do. A policy is an allowlist of contract/wallet addresses; it's attached to a signer and enforced server-side on every transaction that signer makes. Three platform presets exist (`ACP_ONLY` = "Virtuals Only", `DENY_ALL`, and "No Policy" = none attached), and you can create **custom** policies. + +A policy and the agent wallet are owned by your Privy account, so *mutating* one requires that account's session signature — which only the dashboard can produce. As a result the CLI can **create and read** policies, but **editing/deleting** a policy or **changing a live signer's policy** deep-links you to the dashboard (`ACP_DASHBOARD_URL` overrides that base URL). Attaching a policy when you first run `acp agent add-signer` works from the CLI, since that already routes through the browser approval flow. + +```bash +# Create a custom policy (ETHEREUM only). --contract is repeatable; +# use Label=0xaddr to name an entry. +acp policy create --name "My Routers" \ + --contract 0x1111111111111111111111111111111111111111 \ + --contract "Uniswap=0x2222222222222222222222222222222222222222" + +# List your custom policies +acp policy list +acp policy list --limit 50 --chain-type ETHEREUM + +# Show one policy (local record + Privy definition) +acp policy show + +# List the platform presets and their policy ids (DENY_ALL, ACP_ONLY) +acp policy global + +# Editing / deleting a policy needs wallet-owner approval — these open the +# dashboard straight to that policy's edit/delete dialog +acp policy edit +acp policy delete + +# Show which policy the active signer currently uses +acp agent signer-policy + +# Change or remove a live signer's policy — opens the dashboard (owner approval) +acp agent set-signer-policy +``` + +To attach a **custom** policy to a signer, use `acp agent set-signer-policy` — it opens the Signers tab where the owner-signed attach happens, and is the supported path today. Binding a custom policy at signer creation (`acp agent add-signer --policy `) is still rolling out on the dashboard; until then a custom id falls back to `ACP_ONLY` at creation, so attach afterward with `set-signer-policy`. The three presets work at creation today. + ### Agent Email Each agent can provision a dedicated email identity, send and receive email, @@ -799,7 +843,8 @@ bin/ src/ commands/ configure.ts Browser-based auth flow; saves token to OS keychain - agent.ts Agent management (create, list, use, whoami, add-signer, update, tokenize, migrate, register-erc8004) + agent.ts Agent management (create, list, use, whoami, add-signer, signer-policy, set-signer-policy, update, tokenize, migrate, register-erc8004) + policy.ts Wallet policy management (create, list, show, global; edit/delete deep-link to dashboard) offering.ts Offering management (list, create, update, delete; subscription attachments) subscription.ts Subscription management (list, create, update, delete) resource.ts Resource management (list, create, update, delete) @@ -822,6 +867,7 @@ src/ browser.ts Open-URL helper for OAuth / approval flows chains.ts Chain metadata color.ts picocolors wrapper + dashboard.ts Dashboard deep-link URLs for owner-signed policy ops (ACP_DASHBOARD_URL override) errors.ts CliError class with structured codes prompt.ts Interactive CLI helpers (prompt, select, table) output.ts JSON / human-readable output formatting @@ -833,6 +879,7 @@ src/ client.ts Authenticated HTTP client auth.ts Auth API (CLI login flow) agent.ts Agent API (CRUD, offerings, resources, quorum/signer) + policy.ts Policy API (create/list/show policies, global presets, wallet signers) job.ts Job API (queries, history) ``` diff --git a/SKILL.md b/SKILL.md index 17a4865..f50c136 100644 --- a/SKILL.md +++ b/SKILL.md @@ -173,13 +173,36 @@ If you're unsure which the human wants, ask before running. > > `--method qr` returns no URL — it just shows the wallet address to send USDC to. Never substitute "run `acp wallet topup`" for actually running it. -> **Dashboard prerequisites for `send-transaction` only.** Two controls at [app.virtuals.io/os](https://app.virtuals.io/os) → **Agents and Projects** → agent settings → **Wallet** tab can block a broadcast with a generic `Bad Request`. The CLI can't read or change either — **remind the user proactively, don't wait for the failure**: +> **Dashboard prerequisites for `send-transaction` only.** Two controls at [app.virtuals.io/os](https://app.virtuals.io/os) → **Agents and Projects** → agent settings → **Wallet** tab can block a broadcast with a generic `Bad Request` — **remind the user proactively, don't wait for the failure**: > -> 1. **Wallet policies** (going-forward) — a destination-address allowlist. If the recipient isn't on the list, the broadcast fails. -> 2. **Transaction Mode** (older, being phased out) — `Restricted` (default) permits only Virtuals contracts; `Unrestricted` permits arbitrary destinations. Wallet policies take precedence when configured. +> 1. **Wallet policies** (going-forward) — an address allowlist on the agent's signer. If the call's target isn't allowed, the broadcast fails. You *can* inspect and create these from the CLI (`acp policy …`, `acp agent signer-policy` — see [Wallet policies](#wallet-policies)); editing/deleting a policy or changing a live signer's policy stays dashboard-only. +> 2. **Transaction Mode** (older, being phased out) — `Restricted` (default) permits only Virtuals contracts; `Unrestricted` permits arbitrary destinations. Dashboard-only. Wallet policies take precedence when configured. > > `sign-message` / `sign-typed-data` are not affected (they don't broadcast). Tokenization and marketplace job actions also need a signer; see [Marketplace flows](#marketplace-flows) for the latter. +### Wallet policies + +Policies are reusable guardrails — an allowlist of contract/wallet addresses an agent signer may interact with. Three platform presets exist (`ACP_ONLY` = "Virtuals Only", `DENY_ALL`, and "No Policy" = none attached); you can also create **custom** policies that whitelist specific addresses. A policy is attached to a signer, and what it permits is enforced server-side on every transaction that signer makes. + +**What the CLI can do vs. what needs the dashboard.** A policy and the agent wallet are owned by the user's Privy account, and *mutating* an owned resource requires that account's session signature — which only the dashboard can produce (the CLI's signer key can't). So: + +- **CLI, no approval:** create a policy, list/show policies, list platform presets, read the current signer's policy. +- **Dashboard only (the CLI deep-links you there — relay the URL):** edit/delete a policy, change or remove a *live* signer's policy. Attaching a policy when you **first add a signer** works from the CLI, because that routes through the browser approval URL already. + +| Command | What it does | Response shape | +|---|---|---| +| `acp policy create --name --contract --json` | Create a custom policy. `--contract` is repeatable; use `Label=0xaddr` to name an entry. ETHEREUM only. | `{id, policyId, chainType, name, contracts[]}` | +| `acp policy list [--limit --cursor --chain-type] --json` | List your custom policies | `{data:[{id, policyId, name, contracts[]}], meta:{pagination:{nextCursor}}}` | +| `acp policy show --json` | One policy (local + Privy definition) | `{policy:{...}, remote:{...}}` | +| `acp policy global --json` | Platform presets and their policy ids | `{data:[{name, policyId}]}` | +| `acp policy edit ` / `acp policy delete ` | **Cannot run in the CLI** — returns a `url` the user must open to edit/delete that policy in the dashboard (owner-signed) | `{reason, url}` | +| `acp agent signer-policy [--agent-id] --json` | Show which policy the active signer uses | `{signerId, policyIds[], policy}` | +| `acp agent set-signer-policy [--agent-id]` | **Cannot run in the CLI** — returns a `url` the user must open to change/remove a live signer's policy in the dashboard | `{reason, url}` | + +> **The three commands marked "Cannot run in the CLI" only return a `url` — the user must open it to perform the action.** `policy edit`, `policy delete`, and `agent set-signer-policy` need the wallet owner's dashboard session to sign, so they make no change themselves; each returns `{"reason": "...", "url": "..."}`. Treat that `url` exactly like the `add-signer`/`configure`/`topup` links: **STOP and post the raw `url` as plain visible text to the human** (e.g. `Approve the change here: https://...`), and do **not** report the edit/delete/policy-change as done — it stays pending until they complete it in the browser. + +To attach a **custom** policy to a signer, use `acp agent signer-policy` to confirm the current one, then `acp agent set-signer-policy` — it deep-links to the Signers tab where the owner-signed attach happens. This is the supported, working path. (Binding a custom policy at signer creation via `acp agent add-signer --policy ` is still rolling out on the dashboard; until then a custom id falls back to `ACP_ONLY` at creation, so attach with `set-signer-policy` afterward. The three presets work at creation today.) `ACP_DASHBOARD_URL` overrides the dashboard base used for the deep-links. + ### Trading (`acp trade`) `acp trade` is a single command. **Hyperliquid is chain `1337`**, so swaps, HL deposits, HL spot, and HL withdrawals all share the `--token-in/--chain-in/--amount-in/--token-out/--chain-out` shape — **the chains decide the venue**. Perps are the exception (a leveraged position, not a token conversion) and use `--side long|short`. Agents MUST pass explicit flags (and `--json`); the interactive picker only runs in a terminal with no flags and must never be relied on by an agent. @@ -293,8 +316,10 @@ Quick pointers: | `acp agent use [--agent-id]` | Switch active agent | | `acp agent whoami --json` | Show details of the active agent (per-chain tokenization status, ERC-8004 IDs, offerings, resources) | | `acp agent update [--name --description --image]` | Update active agent metadata | -| `acp agent add-signer [--agent-id] [--no-wait] --policy ` | Generate P256 signer, browser-approve, persist to OS keychain. **Always pass `--policy` explicitly** (don't rely on the `restricted` default): `restricted` (authorized for all ACP transactions), `deny-all` (manual approval for all transactions), `unrestricted` (authorizes everything, no approval). `--no-wait` returns `{signerUrl, requestId, publicKey}` and exits for the split flow | +| `acp agent add-signer [--agent-id] [--no-wait] --policy ` | Generate P256 signer, browser-approve, persist to OS keychain. **Always pass `--policy` explicitly** (don't rely on the `restricted` default): `restricted` (authorized for all ACP transactions), `deny-all` (manual approval for all transactions), `unrestricted` (authorizes everything, no approval). For a **custom** policy, add the signer then attach it via `acp agent set-signer-policy` (binding a custom id at creation is rolling out — see [Wallet policies](#wallet-policies)). `--no-wait` returns `{signerUrl, requestId, publicKey}` and exits for the split flow | | `acp agent signer-status --request-id --public-key [--agent-id --wait --timeout]` | Complete a split `add-signer --no-wait`: check approval, persist signer. `{status:'pending'}` until approved | +| `acp agent signer-policy [--agent-id]` | Show which wallet policy the active agent's signer is currently using (resolves preset/custom names). See [Wallet policies](#wallet-policies) | +| `acp agent set-signer-policy [--agent-id] [--open]` | Change or remove a live signer's policy — deep-links to the dashboard (requires wallet-owner approval) | | `acp agent tokenize [--chain-id --symbol --anti-sniper <0\|1\|2> --prebuy --acf --60-days --airdrop-percent --robotics --configure]` | Launch a tradeable token (signer + VIRTUAL launch fee + ETH gas). See [docs/tokenization.md](docs/tokenization.md). | | `acp agent register-erc8004 [--agent-id --chain-id]` | Register on the ERC-8004 identity registry (signer required) | | `acp agent migrate [--agent-id --complete]` | Migrate a legacy v1 agent to v2 (two phases) | @@ -599,7 +624,7 @@ Most commands print structured JSON errors to stderr on `--json`: ### Known issues - **`wallet send-transaction` fails with a generic `Bad Request`** (no useful body). Two dashboard-side controls can produce this; check at [app.virtuals.io/os](https://app.virtuals.io/os) → **Agents and Projects** → agent settings → **Wallet** tab: - 1. **Wallet policies** (the going-forward control): destination-address allowlist. If the recipient isn't on the list, the broadcast fails. Have the user add the destination (or remove the policy for unrestricted), then retry. + 1. **Wallet policies** (the going-forward control): address allowlist on the signer. If the target isn't allowed, the broadcast fails. Inspect with `acp agent signer-policy` / `acp policy list` (see [Wallet policies](#wallet-policies)); to change which policy a live signer uses, the user must approve it in the dashboard (`acp agent set-signer-policy` deep-links there). Or remove the policy for unrestricted, then retry. 2. **Transaction Mode** (older, being phased out): when no wallet policy is configured, `Restricted` (default) only permits Virtuals contracts. Have the user switch to `Unrestricted`, then retry. Check wallet policies first; fall back to Transaction Mode if no policies are set. @@ -622,7 +647,8 @@ bin/acp-cli-signer-* Platform signer binaries (linux/macos/windows) src/ commands/ configure.ts Browser-based auth flow; saves token to OS keychain - agent.ts Agent management (create, list, use, whoami, add-signer, update, tokenize, migrate, register-erc8004) + agent.ts Agent management (create, list, use, whoami, add-signer, signer-policy, set-signer-policy, update, tokenize, migrate, register-erc8004) + policy.ts Wallet policy management (create, list, show, global; edit/delete deep-link to dashboard) offering.ts Offering management (list, create, update, delete; subscription attachments) subscription.ts Subscription management resource.ts Resource management diff --git a/bin/acp.ts b/bin/acp.ts index 6c50309..0ce6840 100755 --- a/bin/acp.ts +++ b/bin/acp.ts @@ -10,6 +10,7 @@ import { registerMessageCommands } from "../src/commands/message"; import { registerWalletCommands } from "../src/commands/wallet"; import { registerConfigureCommand } from "../src/commands/configure"; import { registerAgentCommands } from "../src/commands/agent"; +import { registerPolicyCommands } from "../src/commands/policy"; import { registerBrowseCommand } from "../src/commands/browse"; import { registerOfferingCommands } from "../src/commands/offering"; import { registerResourceCommands } from "../src/commands/resource"; @@ -56,6 +57,7 @@ registerMessageCommands(program); registerWalletCommands(program); registerConfigureCommand(program); registerAgentCommands(program); +registerPolicyCommands(program); registerBrowseCommand(program); registerOfferingCommands(program); registerResourceCommands(program); diff --git a/src/commands/agent.ts b/src/commands/agent.ts index 8da3d5c..438353f 100644 --- a/src/commands/agent.ts +++ b/src/commands/agent.ts @@ -20,6 +20,8 @@ import { SIGNER_POLICIES, } from "../lib/api/agent"; import { getClient } from "../lib/api/client"; +import type { GlobalPolicy, Policy, WalletSigner } from "../lib/api/policy"; +import { dashboardSignerPolicyUrl } from "../lib/dashboard"; import { prompt, selectFromList, @@ -133,7 +135,7 @@ async function startAddSignerFlow( json: boolean, agent: Agent, open: boolean, - policy: SignerPolicy = "restricted" + policy: string = "restricted" ): Promise<{ publicKey: string; signerUrl: string; requestId: string } | null> { let publicKey: string; try { @@ -251,7 +253,7 @@ async function runAddSignerFlow( api: AgentApi, json: boolean, agent: Agent, - policy: SignerPolicy = "restricted" + policy: string = "restricted" ): Promise { const started = await startAddSignerFlow(api, json, agent, !json, policy); if (!started) return false; @@ -401,21 +403,25 @@ export function registerAgentCommands(program: Command): void { SIGNER_POLICIES.map( (p) => `${p}: ${SIGNER_POLICY_DESCRIPTIONS[p]}` ).join("; ") + - ".", + ". Or pass a custom policy id from `acp policy list`.", "restricted" ) .action(async (opts, cmd) => { const json = isJson(cmd); - let policy = String(opts.policy) as SignerPolicy; + // A preset name (SIGNER_POLICIES) or a custom policy id from + // `acp policy list`. Both are forwarded verbatim to the approval URL. + let policy = String(opts.policy).trim(); const policyFromCli = cmd.getOptionValueSource("policy") === "cli"; - if (!SIGNER_POLICIES.includes(policy)) { + if (!policy) { outputError( json, new CliError( - `Invalid policy "${opts.policy}".`, + "Policy cannot be empty.", "VALIDATION_ERROR", - `Use one of: ${SIGNER_POLICIES.join(", ")}.` + `Use a preset (${SIGNER_POLICIES.join( + ", " + )}) or a custom policy id from \`acp policy list\`.` ) ); return; @@ -754,7 +760,7 @@ export function registerAgentCommands(program: Command): void { SIGNER_POLICIES.map( (p) => `${p}: ${SIGNER_POLICY_DESCRIPTIONS[p]}` ).join("; ") + - ".", + ". Or pass a custom policy id from `acp policy list`.", "restricted" ) .option( @@ -764,14 +770,18 @@ export function registerAgentCommands(program: Command): void { .action(async (opts, cmd) => { const json = isJson(cmd); - const policy = String(opts.policy) as SignerPolicy; - if (!SIGNER_POLICIES.includes(policy)) { + // A preset name (SIGNER_POLICIES) or a custom policy id from + // `acp policy list`. Both are forwarded verbatim to the approval URL. + const policy = String(opts.policy).trim(); + if (!policy) { outputError( json, new CliError( - `Invalid policy "${opts.policy}".`, + "Policy cannot be empty.", "VALIDATION_ERROR", - `Use one of: ${SIGNER_POLICIES.join(", ")}.` + `Use a preset (${SIGNER_POLICIES.join( + ", " + )}) or a custom policy id from \`acp policy list\`.` ) ); return; @@ -922,6 +932,175 @@ export function registerAgentCommands(program: Command): void { }); }); + agent + .command("signer-policy") + .description( + "Show which wallet policy the active agent's signer is currently using." + ) + .option("--agent-id ", "Agent ID (defaults to the active agent)") + .action(async (opts, cmd) => { + const { agentApi, policyApi } = await getClient(); + const json = isJson(cmd); + + // Resolve the target agent (explicit --agent-id, else the active one). + let agentId: string | undefined; + let walletAddress: string | undefined; + const selected = await resolveAgent(agentApi, opts, json); + if (selected) { + agentId = selected.id; + walletAddress = selected.walletAddress; + } else { + walletAddress = getActiveWallet(); + if (!walletAddress) { + outputError( + json, + new CliError( + "No active agent set.", + "NO_ACTIVE_AGENT", + "Pass --agent-id or set an active agent with `acp agent use`." + ) + ); + return; + } + agentId = getAgentId(walletAddress); + if (!agentId) { + outputError( + json, + new CliError( + "Agent ID not found for active wallet.", + "NO_ACTIVE_AGENT", + "Run `acp agent list` or `acp agent use` to populate it." + ) + ); + return; + } + } + + let signers: WalletSigner[]; + try { + const res = await policyApi.getWalletSigners(agentId); + signers = res.data ?? []; + } catch (err) { + outputError( + json, + `Failed to fetch signers: ${ + err instanceof Error ? err.message : String(err) + }` + ); + return; + } + + // Build policyId -> human name from custom + global policies so we can + // render names instead of opaque Privy ids. Best-effort: unresolved ids + // fall back to the raw id. + const nameById = new Map(); + try { + const [custom, global] = await Promise.all([ + policyApi.listPolicies({ limit: 100 }), + policyApi.getGlobalPolicies(), + ]); + (custom.data ?? []).forEach((p: Policy) => + nameById.set(p.policyId, p.name) + ); + (global.data ?? []).forEach((g: GlobalPolicy) => + nameById.set(g.policyId, g.name) + ); + } catch { + // Name resolution is a nicety; raw ids are still meaningful. + } + const resolve = (id: string) => nameById.get(id) ?? id; + const describe = (s: WalletSigner) => { + const ids = s.policy_ids ?? []; + return ids.length === 0 + ? "No Policy (no approval required)" + : ids.map(resolve).join(", "); + }; + + // Match the CLI's own signer key against each signer's authorization_keys. + const publicKey = walletAddress ? getPublicKey(walletAddress) : undefined; + const mine = + (publicKey && + signers.find((s) => + (s.authorization_keys ?? []).some( + (k) => k.public_key === publicKey + ) + )) || + (signers.length === 1 ? signers[0] : undefined); + + if (mine) { + if (json) { + outputResult(json, { + signerId: mine.id, + policyIds: mine.policy_ids ?? [], + policy: describe(mine), + }); + return; + } + printTable([ + ["Signer", mine.display_name || mine.id], + ["Policy", describe(mine)], + ]); + return; + } + + // No confident match — show every signer's policy so the user can tell. + if (json) { + outputResult(json, { + matched: false, + signers: signers.map((s) => ({ + signerId: s.id, + policyIds: s.policy_ids ?? [], + policy: describe(s), + })), + }); + return; + } + if (signers.length === 0) { + console.log("\nThis agent has no signers.\n"); + return; + } + console.log( + `\n${c.yellow( + "Could not match this CLI's signer key; showing all signers:" + )}\n` + ); + printTable( + signers.map((s) => [s.display_name || s.id, describe(s)]) + ); + }); + + agent + .command("set-signer-policy") + .description( + "Change or remove the active signer's policy (opens the dashboard — requires wallet-owner approval)." + ) + .option("--agent-id ", "Agent ID (defaults to the active agent)") + .option("--open", "Also open the dashboard in a browser") + .action(async (opts, cmd) => { + const json = isJson(cmd); + const { agentApi } = await getClient(); + const selected = await resolveAgent(agentApi, opts, json); + const agentId = + selected?.id ?? + (getActiveWallet() ? getAgentId(getActiveWallet()!) : undefined); + const url = dashboardSignerPolicyUrl(agentId); + + if (json) { + outputResult(json, { + reason: + "Changing a live signer's policy requires wallet-owner approval — open the url in a browser to continue.", + url, + }); + return; + } + console.log( + `\n${c.yellow("Changing a signer's policy requires wallet-owner approval.")}\n` + + `This signs with your wallet owner's session, which only the dashboard can do.\n\n` + + `Open it here:\n\n ${url}\n` + ); + if (opts.open === true) openBrowser(url); + }); + agent .command("generate-signer-key") .description( diff --git a/src/commands/policy.ts b/src/commands/policy.ts new file mode 100644 index 0000000..45b85e1 --- /dev/null +++ b/src/commands/policy.ts @@ -0,0 +1,270 @@ +import type { Command } from "commander"; +import { isAddress } from "viem"; +import { isJson, outputResult, outputError } from "../lib/output"; +import { CliError } from "../lib/errors"; +import { c } from "../lib/color"; +import { printTable } from "../lib/prompt"; +import { getClient } from "../lib/api/client"; +import type { ContractEntry, PolicyChainType } from "../lib/api/policy"; +import { dashboardWalletPoliciesUrl } from "../lib/dashboard"; +import { openBrowser } from "../lib/browser"; + +const CHAIN_TYPES: PolicyChainType[] = ["ETHEREUM", "SOLANA", "TRON", "SUI"]; + +// Parse a `--contract` entry. Accepts a bare address (`0xabc…`) or a labeled +// form (`My Router=0xabc…`); the part before the first `=` becomes the label. +function parseContractEntry(raw: string): ContractEntry { + const eq = raw.indexOf("="); + if (eq === -1) return { address: raw.trim() }; + return { + name: raw.slice(0, eq).trim() || null, + address: raw.slice(eq + 1).trim(), + }; +} + +// Why deep-link instead of doing it here: editing/deleting a policy mutates a +// resource owned by the user's Privy account and requires their session +// signature, which the CLI's signer key cannot produce. Only the dashboard can. +// We pass policyId + action so the dashboard opens that policy's dialog directly. +function deferToDashboard( + json: boolean, + label: string, + open: boolean, + policyId: string, + action: "edit" | "delete" +): void { + const url = dashboardWalletPoliciesUrl({ policyId, action }); + if (json) { + outputResult(json, { + reason: `${label} requires wallet-owner approval — open the url in a browser to continue.`, + url, + }); + return; + } + console.log( + `\n${c.yellow(`${label} requires wallet-owner approval.`)}\n` + + `This signs with your wallet owner's session, which only the dashboard can do.\n\n` + + `Open Wallet Policies here:\n\n ${url}\n` + ); + if (open) openBrowser(url); +} + +export function registerPolicyCommands(program: Command): void { + const policy = program + .command("policy") + .description("Manage reusable wallet policies (guardrails for agent signers)"); + + policy + .command("create") + .description( + "Create a custom wallet policy: an allowlist of contract/wallet addresses a signer may interact with. ETHEREUM only." + ) + .requiredOption("--name ", "Policy name (max 50 chars)") + .requiredOption( + "--contract ", + "Allowlisted address(es). Repeatable. Use `0xaddr` or `Label=0xaddr` to attach a name." + ) + .action(async (opts, cmd) => { + const json = isJson(cmd); + + const name = String(opts.name).trim(); + if (!name || name.length > 50) { + outputError( + json, + new CliError( + "Invalid policy name.", + "VALIDATION_ERROR", + "Provide a non-empty --name of at most 50 characters." + ) + ); + return; + } + + const rawContracts: string[] = Array.isArray(opts.contract) + ? opts.contract + : [opts.contract]; + const contracts = rawContracts.map(parseContractEntry); + + const bad = contracts.find((entry) => !isAddress(entry.address)); + if (bad) { + outputError( + json, + new CliError( + `Invalid contract address: "${bad.address}".`, + "VALIDATION_ERROR", + "Each --contract must be a valid EVM address (0x…), optionally as `Label=0xaddr`." + ) + ); + return; + } + + const { policyApi } = await getClient(); + try { + const res = await policyApi.createPolicy({ name, contracts }); + const created = res.data.policy; + if (json) { + outputResult(json, created as unknown as Record); + return; + } + console.log(`\n${c.green(`Policy "${created.name}" created.`)}\n`); + printTable([ + ["ID", created.id], + ["Privy Policy ID", created.policyId], + ["Chain", created.chainType], + ["Contracts", created.contracts.join("\n ")], + ]); + } catch (err) { + outputError( + json, + `Failed to create policy: ${ + err instanceof Error ? err.message : String(err) + }` + ); + } + }); + + policy + .command("list") + .description("List your custom wallet policies") + .option("--limit ", "Page size (1-100, default 20)") + .option("--cursor ", "Pagination cursor from a previous page") + .option( + "--chain-type ", + `Filter by chain type: ${CHAIN_TYPES.join(", ")}` + ) + .action(async (opts, cmd) => { + const json = isJson(cmd); + + let chainType: PolicyChainType | undefined; + if (opts.chainType) { + const upper = String(opts.chainType).toUpperCase() as PolicyChainType; + if (!CHAIN_TYPES.includes(upper)) { + outputError( + json, + new CliError( + `Invalid chain type "${opts.chainType}".`, + "VALIDATION_ERROR", + `Use one of: ${CHAIN_TYPES.join(", ")}.` + ) + ); + return; + } + chainType = upper; + } + + const { policyApi } = await getClient(); + try { + const res = await policyApi.listPolicies({ + limit: opts.limit ? Number(opts.limit) : undefined, + cursor: opts.cursor, + chainType, + }); + if (json) { + outputResult(json, res as unknown as Record); + return; + } + const policies = res.data ?? []; + if (policies.length === 0) { + console.log("\nNo custom policies yet.\n"); + return; + } + console.log(""); + for (const p of policies) { + console.log( + `${c.bold(p.name)} ${c.dim(p.id)}\n ${p.contracts.length} address(es): ${p.contracts.join(", ")}` + ); + } + const next = res.meta?.pagination?.nextCursor; + if (next) console.log(`\n${c.dim(`Next page: --cursor ${next}`)}`); + } catch (err) { + outputError( + json, + `Failed to list policies: ${ + err instanceof Error ? err.message : String(err) + }` + ); + } + }); + + policy + .command("show ") + .description("Show a single policy's details (local + Privy definition)") + .action(async (id, _opts, cmd) => { + const json = isJson(cmd); + const { policyApi } = await getClient(); + try { + const res = await policyApi.getPolicy(String(id)); + if (json) { + outputResult(json, res.data as unknown as Record); + return; + } + const p = res.data.policy; + printTable([ + ["Name", p.name], + ["ID", p.id], + ["Privy Policy ID", p.policyId], + ["Chain", p.chainType], + ["Contracts", p.contracts.join("\n ")], + ]); + } catch (err) { + outputError( + json, + `Failed to fetch policy: ${ + err instanceof Error ? err.message : String(err) + }` + ); + } + }); + + policy + .command("global") + .description("List platform-managed policy presets (e.g. DENY_ALL, ACP_ONLY)") + .action(async (_opts, cmd) => { + const json = isJson(cmd); + const { policyApi } = await getClient(); + try { + const res = await policyApi.getGlobalPolicies(); + if (json) { + outputResult(json, { data: res.data }); + return; + } + console.log(""); + printTable(res.data.map((g) => [g.name, g.policyId])); + } catch (err) { + outputError( + json, + `Failed to fetch global policies: ${ + err instanceof Error ? err.message : String(err) + }` + ); + } + }); + + policy + .command("edit ") + .description("Edit a policy (opens the dashboard — requires wallet-owner approval)") + .option("--open", "Also open the dashboard in a browser") + .action((id, opts, cmd) => { + deferToDashboard( + isJson(cmd), + "Editing a policy", + opts.open === true, + String(id), + "edit" + ); + }); + + policy + .command("delete ") + .description("Delete a policy (opens the dashboard — requires wallet-owner approval)") + .option("--open", "Also open the dashboard in a browser") + .action((id, opts, cmd) => { + deferToDashboard( + isJson(cmd), + "Deleting a policy", + opts.open === true, + String(id), + "delete" + ); + }); +} diff --git a/src/lib/api/agent.ts b/src/lib/api/agent.ts index 32a0dab..aeea50f 100644 --- a/src/lib/api/agent.ts +++ b/src/lib/api/agent.ts @@ -664,9 +664,12 @@ export class AgentApi { return this.client.get("/agents/search", params); } + // `policy` is a preset name (SignerPolicy) or a custom policy id. The backend + // forwards it verbatim into the browser approval URL, where the user's Privy + // session attaches it — so any value the dashboard understands is valid here. async addSignerWithUrl( agentId: string, - policy: SignerPolicy = "restricted" + policy: string = "restricted" ): Promise { return this.client.post(`/agents/${agentId}/signer`, { policy }); } diff --git a/src/lib/api/client.ts b/src/lib/api/client.ts index eb54041..cfe86da 100644 --- a/src/lib/api/client.ts +++ b/src/lib/api/client.ts @@ -8,6 +8,7 @@ import { import { CliError } from "../errors"; import { AuthApi } from "./auth"; import { AgentApi } from "./agent"; +import { PolicyApi } from "./policy"; import { ACP_SERVER_URL, ACP_TESTNET_SERVER_URL, @@ -131,6 +132,7 @@ async function resolveToken(apiUrl: string): Promise { export async function getClient(unauthenticated?: boolean): Promise<{ agentApi: AgentApi; authApi: AuthApi; + policyApi: PolicyApi; }> { const isTestnet = process.env.IS_TESTNET === "true"; const apiUrl = isTestnet ? ACP_TESTNET_SERVER_URL : ACP_SERVER_URL; @@ -139,6 +141,7 @@ export async function getClient(unauthenticated?: boolean): Promise<{ return { agentApi: new AgentApi(httpClient), authApi: new AuthApi(httpClient), + policyApi: new PolicyApi(httpClient), }; } diff --git a/src/lib/api/policy.ts b/src/lib/api/policy.ts new file mode 100644 index 0000000..661ba28 --- /dev/null +++ b/src/lib/api/policy.ts @@ -0,0 +1,111 @@ +import { ApiClient } from "./client"; + +// Mirrors the backend PolicyChainType enum. Policies are only attachable to +// EVM signers today, so ETHEREUM is the only value the CLI creates. +export type PolicyChainType = "ETHEREUM" | "SOLANA" | "TRON" | "SUI"; + +// One entry in a policy's contract allowlist. The optional `name` is a +// human label; `address` is the (checksummed) contract/wallet address the +// signer is permitted to interact with. +export interface ContractEntry { + name?: string | null; + address: string; +} + +// A custom wallet policy as stored by the backend. +export interface Policy { + id: string; + policyId: string; + userId: string; + chainType: PolicyChainType; + name: string; + contracts: string[]; + createdAt?: string; + updatedAt?: string; +} + +export interface CreatePolicyBody { + name: string; + contracts: ContractEntry[]; +} + +export interface CreatePolicyResponse { + message: string; + data: { policy: Policy; rules: unknown[] }; +} + +export interface ListPoliciesResponse { + data: Policy[]; + meta?: { + pagination?: { limit?: number; total?: number; nextCursor?: string | null }; + }; +} + +export interface GetPolicyResponse { + data: { policy: Policy; remote: unknown }; +} + +// A platform-managed (global) policy preset, e.g. DENY_ALL / ACP_ONLY. +export interface GlobalPolicy { + name: string; + policyId: string; +} + +export interface GlobalPoliciesResponse { + data: GlobalPolicy[]; +} + +export interface AuthorizationKey { + public_key: string; + display_name: string | null; +} + +// A signer (Privy key quorum) attached to an agent wallet. `policy_ids` holds +// the Privy policy ids currently overriding this signer (empty/null = no policy). +export interface WalletSigner { + id: string; + display_name: string; + authorization_keys: AuthorizationKey[]; + policy_ids: string[] | null; +} + +export interface WalletSignersResponse { + message: string; + data: WalletSigner[]; +} + +export class PolicyApi { + private client: ApiClient; + + constructor(client: ApiClient) { + this.client = client; + } + + async createPolicy(body: CreatePolicyBody): Promise { + return this.client.post("/policies", body); + } + + async listPolicies(opts?: { + limit?: number; + cursor?: string; + chainType?: PolicyChainType; + }): Promise { + const params: Record = {}; + if (opts?.limit !== undefined) params.limit = String(opts.limit); + if (opts?.cursor) params.cursor = opts.cursor; + if (opts?.chainType) params.chainType = opts.chainType; + return this.client.get("/policies", params); + } + + async getPolicy(id: string): Promise { + return this.client.get(`/policies/${id}`); + } + + async getGlobalPolicies(): Promise { + return this.client.get("/common/policies/global"); + } + + async getWalletSigners(agentId: string): Promise { + return this.client.get(`/agents/${agentId}/signers`); + } +} diff --git a/src/lib/dashboard.ts b/src/lib/dashboard.ts new file mode 100644 index 0000000..a739f7a --- /dev/null +++ b/src/lib/dashboard.ts @@ -0,0 +1,41 @@ +// Base URL for the Virtuals web dashboard (the `virtual-protocol-app` +// frontend), where wallet-owner-gated operations — editing/deleting a policy, +// changing a live signer's policy — are signed with the user's Privy session. +// +// These are the dashboard's own canonical hosts (see virtual-protocol-app +// src/pages/acp/constants/url.ts; the `/acp/*` routes are served there). +// Override with ACP_DASHBOARD_URL if they ever change. +export function dashboardBaseUrl(): string { + const override = process.env.ACP_DASHBOARD_URL?.trim(); + if (override) return override.replace(/\/$/, ""); + + const isTestnet = process.env.IS_TESTNET === "true"; + return isTestnet + ? "https://app-dev.virtuals.io" + : "https://app.virtuals.io"; +} + +// The Wallet Policies library page (create/edit/delete custom policies). The +// page reads `policyId`/`action` query params to open a specific policy's +// edit (default) or delete dialog directly, so pass them to land the user on +// the exact policy rather than just the list. +export function dashboardWalletPoliciesUrl(opts?: { + policyId?: string; + action?: "edit" | "delete"; +}): string { + const base = `${dashboardBaseUrl()}/acp/wallet-policies`; + if (!opts?.policyId) return base; + const params = new URLSearchParams({ policyId: opts.policyId }); + if (opts.action) params.set("action", opts.action); + return `${base}?${params.toString()}`; +} + +// Deep link to where a *signer's* policy is changed: the agent's Wallet tab, +// Signers sub-tab. The FE reads `tab`/`subTab` query params to open directly. +// Falls back to the agents list when no agentId is known. +export function dashboardSignerPolicyUrl(agentId?: string): string { + const base = dashboardBaseUrl(); + return agentId + ? `${base}/acp/agents/${agentId}?tab=wallet&subTab=signers` + : `${base}/acp/agents`; +} From 23fc7fbb54d7b8c5d05cc9313046dbe0ea7d8224 Mon Sep 17 00:00:00 2001 From: Zuhwa Date: Tue, 16 Jun 2026 17:17:34 +0800 Subject: [PATCH 22/24] =?UTF-8?q?feat:=20enhance=20wallet=20balance=20comm?= =?UTF-8?q?and=20to=20support=20querying=20all=20supporte=E2=80=A6=20(#47)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: enhance wallet balance command to support querying all supported chain * feat: add native currency resolution for token display in wallet commands --------- Co-authored-by: Zuhwa --- src/commands/wallet.ts | 217 +++++++++++++++++++++++++++++------------ src/lib/chains.ts | 14 +++ 2 files changed, 171 insertions(+), 60 deletions(-) diff --git a/src/commands/wallet.ts b/src/commands/wallet.ts index e3e46fd..a316b8a 100644 --- a/src/commands/wallet.ts +++ b/src/commands/wallet.ts @@ -13,8 +13,14 @@ import { getWalletAddress, getSolanaWalletAddress } from "../lib/agentFactory"; import { getClient } from "../lib/api/client"; import { getAgentId, getActiveWallet } from "../lib/config"; import { CHAIN_NETWORK_MAP } from "../lib/api/agent"; +import type { TokenInfo } from "../lib/api/agent"; import { CliError } from "../lib/errors"; -import { assertSponsoredChainId, solanaChainId } from "../lib/chains"; +import { + assertSponsoredChainId, + getEnvSponsoredChainIds, + getNativeCurrency, + solanaChainId, +} from "../lib/chains"; import { c } from "../lib/color"; import { openBrowser } from "../lib/browser"; import { selectOption, prompt } from "../lib/prompt"; @@ -51,6 +57,58 @@ function emitTopupUrlToStderr(url: string): void { ); } +// Resolves the native currency (name + symbol) for a token's network, used as +// the fallback label for native-token balances so non-ETH chains (BNB, POL, +// MON, …) aren't mislabeled as ETH. +type NativeResolver = ( + network: string +) => { name: string; symbol: string } | undefined; + +// Derive the displayable symbol/name/balance/usd for a token. Shared by the +// single-chain and all-chains balance views so formatting stays identical. +function formatToken( + t: TokenInfo, + native?: { name: string; symbol: string } +): { + symbol: string; + name: string; + balance: string; + usd: string; + contract: string; +} { + const isNative = t.tokenAddress === null; + const symbol = + t.tokenMetadata.symbol ?? (isNative ? native?.symbol ?? "ETH" : "???"); + const name = + t.tokenMetadata.name ?? (isNative ? native?.name ?? "Ether" : ""); + const decimals = t.tokenMetadata.decimals ?? 18; + const balance = formatUnits(BigInt(t.tokenBalance), decimals); + const unitPrice = parseFloat(t.tokenPrices?.[0]?.value ?? "0"); + const value = unitPrice * parseFloat(balance); + return { + symbol, + name, + balance, + usd: `$${value.toFixed(2)}`, + contract: t.tokenAddress ?? "native", + }; +} + +// Render a token table for a single network's tokens (TTY mode). +function printTokenTable(tokens: TokenInfo[], nativeFor: NativeResolver): void { + const header = ` ${c.dim("TOKEN".padEnd(10))}${c.dim( + "NAME".padEnd(22) + )}${c.dim("BALANCE".padEnd(24))}${c.dim("USD")}`; + console.log(header); + for (const t of tokens) { + const { symbol, name, balance, usd } = formatToken(t, nativeFor(t.network)); + const bal = balance.length > 22 ? balance.slice(0, 22) : balance; + console.log( + ` ${c.cyan(symbol.padEnd(10))}${name.padEnd(22)}${bal.padEnd(24)}${usd}` + ); + } +} + export function registerWalletCommands(program: Command): void { const wallet = program.command("wallet").description("Wallet commands"); @@ -196,23 +254,45 @@ export function registerWalletCommands(program: Command): void { wallet .command("balance") - .description("Show token balances for the active wallet") - .requiredOption("--chain-id ", "Chain ID") + .description( + "Show token balances for the active wallet. Omit --chain-id to show all sponsored chains for the current environment." + ) + .option("--chain-id ", "Chain ID (omit to query all sponsored chains)") .action(async (opts, cmd) => { const json = isJson(cmd); try { - const chainId = Number(opts.chainId); - assertSponsoredChainId(chainId); + // Resolve the set of chains to query: a single explicit --chain-id, or + // every sponsored chain for the current environment when omitted. + const explicit = opts.chainId !== undefined; + let chainIds: number[]; + if (explicit) { + const chainId = Number(opts.chainId); + assertSponsoredChainId(chainId); + chainIds = [chainId]; + } else { + chainIds = getEnvSponsoredChainIds(); + } - const network = CHAIN_NETWORK_MAP[chainId]; - if (!network) { - throw new CliError( - `No network mapping for chain ID: ${chainId}`, - "VALIDATION_ERROR", - `Known networks: ${Object.entries(CHAIN_NETWORK_MAP) - .map(([id, name]) => `${id} (${name})`) - .join(", ")}` - ); + // Map each chain id to its network string and build a reverse lookup so + // returned tokens can be grouped back to their chain. + const networks: string[] = []; + const networkToChainId = new Map(); + for (const id of chainIds) { + const network = CHAIN_NETWORK_MAP[id]; + if (!network) { + if (explicit) { + throw new CliError( + `No network mapping for chain ID: ${id}`, + "VALIDATION_ERROR", + `Known networks: ${Object.entries(CHAIN_NETWORK_MAP) + .map(([cid, name]) => `${cid} (${name})`) + .join(", ")}` + ); + } + continue; + } + networks.push(network); + networkToChainId.set(network, id); } const walletAddress = getWalletAddress(); @@ -227,65 +307,82 @@ export function registerWalletCommands(program: Command): void { } const { agentApi } = await getClient(); - const assets = await agentApi.getAgentAssets(agentId, [network]); + const assets = await agentApi.getAgentAssets(agentId, networks); const tokens = assets.data.tokens; + // Native-currency fallback label, resolved per token via its network. + const nativeFor: NativeResolver = (network) => { + const id = networkToChainId.get(network); + return id !== undefined ? getNativeCurrency(id) : undefined; + }; + if (json) { - outputResult(json, { - chainId, - network, - address: walletAddress, - tokens, - }); + if (explicit) { + outputResult(json, { + chainId: chainIds[0], + network: networks[0], + address: walletAddress, + tokens, + }); + } else { + outputResult(json, { + chains: networks.map((network) => ({ + chainId: networkToChainId.get(network), + network, + })), + address: walletAddress, + tokens, + }); + } return; } if (isTTY()) { - console.log( - `\n${c.bold(`Wallet Balance on ${network} (${chainId})`)}\n` - ); - console.log(` ${c.bold("Address:")} ${c.dim(walletAddress)}\n`); - - if (tokens.length === 0) { - console.log(" No tokens found.\n"); + if (explicit) { + console.log( + `\n${c.bold( + `Wallet Balance on ${networks[0]} (${chainIds[0]})` + )}\n` + ); + console.log(` ${c.bold("Address:")} ${c.dim(walletAddress)}\n`); + if (tokens.length === 0) { + console.log(" No tokens found.\n"); + } else { + printTokenTable(tokens, nativeFor); + console.log(""); + } } else { - const header = ` ${c.dim("TOKEN".padEnd(10))}${c.dim( - "NAME".padEnd(22) - )}${c.dim("BALANCE".padEnd(24))}${c.dim("USD")}`; - console.log(header); - for (const t of tokens) { - const isNative = t.tokenAddress === null; - const symbol = - t.tokenMetadata.symbol ?? (isNative ? "ETH" : "???"); - const name = t.tokenMetadata.name ?? (isNative ? "Ether" : ""); - const decimals = t.tokenMetadata.decimals ?? 18; - const balance = formatUnits(BigInt(t.tokenBalance), decimals); - const bal = balance.length > 22 ? balance.slice(0, 22) : balance; - const unitPrice = parseFloat(t.tokenPrices?.[0]?.value ?? "0"); - const value = unitPrice * parseFloat(balance); - const price = `$${value.toFixed(2)}`; - console.log( - ` ${c.cyan(symbol.padEnd(10))}${name.padEnd(22)}${bal.padEnd( - 24 - )}${price}` - ); + console.log(`\n${c.bold("Wallet Balance")}\n`); + console.log(` ${c.bold("Address:")} ${c.dim(walletAddress)}\n`); + + // Group tokens by network, preserving the queried order. Networks + // with no tokens are hidden. + let printedAny = false; + for (const network of networks) { + const group = tokens.filter((t) => t.network === network); + if (group.length === 0) continue; + printedAny = true; + const chainId = networkToChainId.get(network); + console.log(` ${c.bold(`${network} (${chainId})`)}`); + printTokenTable(group, nativeFor); + console.log(""); + } + if (!printedAny) { + console.log(" No tokens found.\n"); } - console.log(""); + console.log( + ` ${c.dim(`Checked: ${networks.join(", ")} (${networks.length} chains)`)}\n` + ); } } else { - console.log("TOKEN\tNAME\tBALANCE\tUSD\tCONTRACT"); + console.log("NETWORK\tTOKEN\tNAME\tBALANCE\tUSD\tCONTRACT"); for (const t of tokens) { - const isNative = t.tokenAddress === null; - const symbol = t.tokenMetadata.symbol ?? (isNative ? "ETH" : "???"); - const name = t.tokenMetadata.name ?? (isNative ? "Ether" : ""); - const decimals = t.tokenMetadata.decimals ?? 18; - const balance = formatUnits(BigInt(t.tokenBalance), decimals); - const unitPrice = parseFloat(t.tokenPrices?.[0]?.value ?? "0"); - const value = unitPrice * parseFloat(balance); + const { symbol, name, balance, usd, contract } = formatToken( + t, + nativeFor(t.network) + ); console.log( - `${symbol}\t${name}\t${balance}\t$${value.toFixed(2)}\t${ - t.tokenAddress ?? "native" - }` + `${t.network}\t${symbol}\t${name}\t${balance}\t${usd}\t${contract}` ); } } diff --git a/src/lib/chains.ts b/src/lib/chains.ts index ff6c174..b092c8a 100644 --- a/src/lib/chains.ts +++ b/src/lib/chains.ts @@ -29,6 +29,20 @@ export const SPONSORED_CHAIN_IDS = ERC20_SPONSORED_CHAINS.map( (chain) => chain.id ); +export function getEnvSponsoredChainIds(): number[] { + const isTestnet = process.env.IS_TESTNET === "true"; + return ERC20_SPONSORED_CHAINS.filter( + (chain) => Boolean(chain.testnet) === isTestnet + ).map((chain) => chain.id); +} + +export function getNativeCurrency( + chainId: number +): { name: string; symbol: string } | undefined { + return ERC20_SPONSORED_CHAINS.find((chain) => chain.id === chainId) + ?.nativeCurrency; +} + export function formatChainId(id: number): string { const chain = getEvmChainByChainId(id); return chain ? `${id} (${chain.name})` : String(id); From 819778408f7428757b07331f4cfd71347386b257 Mon Sep 17 00:00:00 2001 From: Zuhwa Date: Tue, 16 Jun 2026 17:55:57 +0800 Subject: [PATCH 23/24] feat: update wallet balance command to include Solana support and improve documentation --- README.md | 15 ++- SKILL.md | 6 +- src/commands/wallet.ts | 263 ++++++++++++++++++++++++----------------- src/lib/chains.ts | 9 +- 4 files changed, 175 insertions(+), 118 deletions(-) diff --git a/README.md b/README.md index 07580bb..277dd7a 100644 --- a/README.md +++ b/README.md @@ -257,8 +257,13 @@ Shows the supported chain IDs and network names based on the current environment # Show configured wallet address acp wallet address -# Show token balances +# Show token balances. With no flags, shows every sponsored EVM chain plus +# Solana for the current environment, grouped by chain. +acp wallet balance +# Narrow to one EVM chain: acp wallet balance --chain-id 8453 +# Solana only (chain id 500/501 or --cluster): +acp wallet balance --cluster mainnet # Sign a plaintext message (no dashboard prerequisites) acp wallet sign-message --message "hello world" --chain-id 8453 @@ -290,13 +295,13 @@ acp wallet topup --chain-id 8453 --method qr #### Solana wallet (`wallet sol`) -The agent's Privy wallet also holds a Solana address (signed by the same key). Solana operations live under `wallet sol`. The cluster is implied by the environment (`IS_TESTNET` → devnet, otherwise mainnet) with an optional `--cluster devnet|mainnet` override — there's no `--chain-id` here. +The agent's Privy wallet also holds a Solana address (signed by the same key). Solana operations live under `wallet sol`. The cluster is implied by the environment (`IS_TESTNET` → devnet, otherwise mainnet) with an optional `--cluster devnet|mainnet` override — there's no `--chain-id` here. (Balances also appear in the unified `acp wallet balance` view alongside EVM chains; `wallet sol balance` is a Solana-only shortcut.) ```bash # Show the agent's Solana address acp wallet sol address -# SOL + SPL token balances +# SOL + SPL token balances (same formatting as `acp wallet balance`) acp wallet sol balance # Sign a plaintext message (returns a base58 signature) @@ -782,8 +787,8 @@ acp trade --side short --token BTC --size 0.01 --reduce-only ```bash # Show Hyperliquid ACCOUNT status ONLY — HL perp positions, margin, and HL spot balances. -# This is the one HL-specific read. For on-chain token balances on any EVM chain -# (Ethereum, Arbitrum, Base, …), use `acp wallet balance --chain-id ` instead. +# This is the one HL-specific read. For on-chain token balances +# EVM chains + Solana), use `acp wallet balance` instead. acp trade hl-status # Withdraw USDC off Hyperliquid (settles to Arbitrum; --to-chain bridges onward) diff --git a/SKILL.md b/SKILL.md index f50c136..f2e5d65 100644 --- a/SKILL.md +++ b/SKILL.md @@ -143,18 +143,18 @@ If you're unsure which the human wants, ask before running. | Command | What it does | Response shape | |---|---|---| | `acp wallet address --json` | Show wallet address | `{address}` | -| `acp wallet balance --chain-id --json` | Token balances on a chain | `{chainId, network, address, tokens:[{tokenAddress, tokenBalance, tokenMetadata:{symbol, name, decimals}, tokenPrices:[{value}]}]}` (`tokenBalance` is the raw integer; decimal-shift by `tokenMetadata.decimals`) | +| `acp wallet balance [--chain-id ] [--cluster ] --json` | Token balances. No flags → all sponsored EVM chains + Solana for the env. `--chain-id` narrows to one chain (EVM, or `500`/`501` for Solana); `--cluster devnet\|mainnet` → Solana only | Single chain (`--chain-id`/`--cluster`): `{chainId, network, address, tokens:[…]}`. All-chains (no flags): `{chains:[{chainId, network}], address, solanaAddress, tokens:[…]}` — `tokens` each carry `network`; group by it. Each token: `{tokenAddress, tokenBalance, tokenMetadata:{symbol, name, decimals}, tokenPrices:[{value}]}` (`tokenBalance` raw; decimal-shift by `decimals`, native token has `tokenAddress:null`) | | `acp wallet topup --chain-id --method coinbase \| card \| qr [--amount ] [--email ] [--us] --json` | On-ramp via Coinbase Pay, Crossmint card, or QR | Coinbase: `{walletAddress, method:"coinbase", url}`. Card: `{walletAddress, method:"card", checkoutUrl}`. QR: `{walletAddress, method:"qr", chainId}` | | `acp wallet sign-message --message --chain-id --json` | Sign plaintext (signer required) | `{signature}` | | `acp wallet sign-typed-data --data --chain-id --json` | Sign EIP-712 (signer required) | `{signature}` | | `acp wallet send-transaction --chain-id --to [--value ] [--data ] --json` | Broadcast (signer + dashboard prerequisites — see callout below) | `{transactionHash}` | -**Solana wallet** (`acp wallet sol …`). Same agent, its Solana address (same signer). No `--chain-id` — the cluster is implied by `IS_TESTNET` (devnet on testnet, else mainnet), override with `--cluster devnet|mainnet`. Amounts are human units (SOL, or token units). `transfer`/`sign-message`/`send-instructions` need a signer; `sign-typed-data`/`topup` are EVM-only. +**Solana wallet** (`acp wallet sol …`). Same agent, its Solana address (same signer). No `--chain-id` — the cluster is implied by `IS_TESTNET` (devnet on testnet, else mainnet), override with `--cluster devnet|mainnet`. Amounts are human units (SOL, or token units). `transfer`/`sign-message`/`send-instructions` need a signer; `sign-typed-data`/`topup` are EVM-only. (Solana balances also appear in the unified `acp wallet balance`; `wallet sol balance` is a Solana-only shortcut with identical output.) | Command | What it does | Response shape | |---|---|---| | `acp wallet sol address --json` | Show the agent's Solana address | `{address}` | -| `acp wallet sol balance [--cluster ] --json` | SOL + SPL balances (server-side) | `{chainId, network, address, tokens:[{tokenAddress, tokenBalance, tokenMetadata:{symbol, name, decimals}}]}` (native SOL has `tokenAddress:null`; `tokenBalance` raw, decimal-shift by `decimals`) | +| `acp wallet sol balance [--cluster ] --json` | SOL + SPL balances (server-side) | `{chainId, network, address, tokens:[{tokenAddress, tokenBalance, tokenMetadata:{symbol, name, decimals}, tokenPrices:[{value}]}]}` (native SOL has `tokenAddress:null`; `tokenBalance` raw, decimal-shift by `decimals`) | | `acp wallet sol sign-message --message [--cluster ] --json` | Sign plaintext (signer required) | `{signature}` (base58) | | `acp wallet sol transfer --to --amount [--token ] [--cluster ] --json` | Send SOL, or an SPL token with `--token` (auto-creates the recipient's token account) | `{signature}` | | `acp wallet sol send-instructions --instructions [--cluster ] --json` | Send a raw instruction set (advanced); `` = `[{programAddress, accounts:[{address, role}], data}]`, `data` base64/0x-hex, `role` ∈ writable_signer\|writable\|readonly_signer\|readonly | `{signature}` | diff --git a/src/commands/wallet.ts b/src/commands/wallet.ts index a316b8a..9792df4 100644 --- a/src/commands/wallet.ts +++ b/src/commands/wallet.ts @@ -19,6 +19,7 @@ import { assertSponsoredChainId, getEnvSponsoredChainIds, getNativeCurrency, + isSolanaChainId, solanaChainId, } from "../lib/chains"; import { c } from "../lib/color"; @@ -57,18 +58,18 @@ function emitTopupUrlToStderr(url: string): void { ); } -// Resolves the native currency (name + symbol) for a token's network, used as -// the fallback label for native-token balances so non-ETH chains (BNB, POL, -// MON, …) aren't mislabeled as ETH. +// Resolves the native currency (name + symbol + decimals) for a token's network, +// used as the fallback label for native-token balances so non-ETH chains (BNB, +// POL, MON, SOL, …) aren't mislabeled as ETH. type NativeResolver = ( network: string -) => { name: string; symbol: string } | undefined; +) => { name: string; symbol: string; decimals: number } | undefined; // Derive the displayable symbol/name/balance/usd for a token. Shared by the // single-chain and all-chains balance views so formatting stays identical. function formatToken( t: TokenInfo, - native?: { name: string; symbol: string } + native?: { name: string; symbol: string; decimals: number } ): { symbol: string; name: string; @@ -81,7 +82,8 @@ function formatToken( t.tokenMetadata.symbol ?? (isNative ? native?.symbol ?? "ETH" : "???"); const name = t.tokenMetadata.name ?? (isNative ? native?.name ?? "Ether" : ""); - const decimals = t.tokenMetadata.decimals ?? 18; + const decimals = + t.tokenMetadata.decimals ?? (isNative ? native?.decimals ?? 18 : 18); const balance = formatUnits(BigInt(t.tokenBalance), decimals); const unitPrice = parseFloat(t.tokenPrices?.[0]?.value ?? "0"); const value = unitPrice * parseFloat(balance); @@ -109,6 +111,101 @@ function printTokenTable(tokens: TokenInfo[], nativeFor: NativeResolver): void { } } +// Shared balance renderer for both the unified `wallet balance` and the +// `wallet sol balance` shortcut, so EVM and Solana output stay identical +// (JSON / grouped TTY table with USD / piped TSV). +function renderBalances(opts: { + json: boolean; + networks: string[]; // queried order; drives grouping + the "Checked" line + networkToChainId: Map; + tokens: TokenInfo[]; + evmAddress?: string; + solAddress?: string; +}): void { + const { json, networks, networkToChainId, tokens, evmAddress, solAddress } = + opts; + const single = networks.length === 1; + const nativeFor: NativeResolver = (network) => { + const id = networkToChainId.get(network); + return id !== undefined ? getNativeCurrency(id) : undefined; + }; + + if (json) { + if (single) { + const network = networks[0]; + const chainId = networkToChainId.get(network); + const address = isSolanaChainId(chainId ?? -1) ? solAddress : evmAddress; + outputResult(json, { chainId, network, address, tokens }); + } else { + outputResult(json, { + chains: networks.map((network) => ({ + chainId: networkToChainId.get(network), + network, + })), + address: evmAddress, + solanaAddress: solAddress, + tokens, + }); + } + return; + } + + if (isTTY()) { + if (single) { + const network = networks[0]; + const chainId = networkToChainId.get(network); + const address = isSolanaChainId(chainId ?? -1) ? solAddress : evmAddress; + console.log(`\n${c.bold(`Wallet Balance on ${network} (${chainId})`)}\n`); + console.log(` ${c.bold("Address:")} ${c.dim(address ?? "")}\n`); + if (tokens.length === 0) { + console.log(" No tokens found.\n"); + } else { + printTokenTable(tokens, nativeFor); + console.log(""); + } + } else { + console.log(`\n${c.bold("Wallet Balance")}\n`); + if (evmAddress) { + console.log(` ${c.bold("EVM:")} ${c.dim(evmAddress)}`); + } + if (solAddress) { + console.log(` ${c.bold("Solana:")} ${c.dim(solAddress)}`); + } + console.log(""); + + // Group tokens by network, preserving the queried order. Networks with + // no tokens are hidden. + let printedAny = false; + for (const network of networks) { + const group = tokens.filter((t) => t.network === network); + if (group.length === 0) continue; + printedAny = true; + const chainId = networkToChainId.get(network); + console.log(` ${c.bold(`${network} (${chainId})`)}`); + printTokenTable(group, nativeFor); + console.log(""); + } + if (!printedAny) { + console.log(" No tokens found.\n"); + } + console.log( + ` ${c.dim(`Checked: ${networks.join(", ")} (${networks.length} chains)`)}\n` + ); + } + } else { + console.log("NETWORK\tTOKEN\tNAME\tBALANCE\tUSD\tCONTRACT"); + for (const t of tokens) { + const { symbol, name, balance, usd, contract } = formatToken( + t, + nativeFor(t.network) + ); + console.log( + `${t.network}\t${symbol}\t${name}\t${balance}\t${usd}\t${contract}` + ); + } + } +} + export function registerWalletCommands(program: Command): void { const wallet = program.command("wallet").description("Wallet commands"); @@ -255,22 +352,28 @@ export function registerWalletCommands(program: Command): void { wallet .command("balance") .description( - "Show token balances for the active wallet. Omit --chain-id to show all sponsored chains for the current environment." + "Show token balances. With no flags, shows all sponsored EVM chains plus Solana for the current environment. Narrow with --chain-id or --cluster." ) - .option("--chain-id ", "Chain ID (omit to query all sponsored chains)") + .option("--chain-id ", "Chain ID (EVM, or 500/501 for Solana)") + .option("--cluster ", "Solana only: devnet | mainnet") .action(async (opts, cmd) => { const json = isJson(cmd); try { - // Resolve the set of chains to query: a single explicit --chain-id, or - // every sponsored chain for the current environment when omitted. - const explicit = opts.chainId !== undefined; + // Resolve the chain-id set to query and whether this is an explicit + // single-chain request (vs the default all-chains view). let chainIds: number[]; - if (explicit) { + let explicit: boolean; + if (opts.chainId !== undefined) { const chainId = Number(opts.chainId); - assertSponsoredChainId(chainId); + if (!isSolanaChainId(chainId)) assertSponsoredChainId(chainId); chainIds = [chainId]; + explicit = true; + } else if (opts.cluster !== undefined) { + chainIds = [solanaChainId(opts.cluster)]; + explicit = true; } else { - chainIds = getEnvSponsoredChainIds(); + chainIds = [...getEnvSponsoredChainIds(), solanaChainId()]; + explicit = false; } // Map each chain id to its network string and build a reverse lookup so @@ -306,86 +409,40 @@ export function registerWalletCommands(program: Command): void { ); } + // Resolve the Solana address only if a Solana network is in scope. In + // the default all-chains view a missing Solana wallet is skipped + // silently; an explicit Solana request surfaces the error. + let solAddress: string | undefined; + const hasSolana = networks.some((n) => + isSolanaChainId(networkToChainId.get(n) ?? -1) + ); + if (hasSolana) { + try { + solAddress = await getSolanaWalletAddress(); + } catch (err) { + if (explicit) throw err; + // Drop the Solana network(s) and continue with EVM only. + for (const [network, id] of [...networkToChainId.entries()]) { + if (isSolanaChainId(id)) { + networks.splice(networks.indexOf(network), 1); + networkToChainId.delete(network); + } + } + } + } + const { agentApi } = await getClient(); const assets = await agentApi.getAgentAssets(agentId, networks); const tokens = assets.data.tokens; - // Native-currency fallback label, resolved per token via its network. - const nativeFor: NativeResolver = (network) => { - const id = networkToChainId.get(network); - return id !== undefined ? getNativeCurrency(id) : undefined; - }; - - if (json) { - if (explicit) { - outputResult(json, { - chainId: chainIds[0], - network: networks[0], - address: walletAddress, - tokens, - }); - } else { - outputResult(json, { - chains: networks.map((network) => ({ - chainId: networkToChainId.get(network), - network, - })), - address: walletAddress, - tokens, - }); - } - return; - } - - if (isTTY()) { - if (explicit) { - console.log( - `\n${c.bold( - `Wallet Balance on ${networks[0]} (${chainIds[0]})` - )}\n` - ); - console.log(` ${c.bold("Address:")} ${c.dim(walletAddress)}\n`); - if (tokens.length === 0) { - console.log(" No tokens found.\n"); - } else { - printTokenTable(tokens, nativeFor); - console.log(""); - } - } else { - console.log(`\n${c.bold("Wallet Balance")}\n`); - console.log(` ${c.bold("Address:")} ${c.dim(walletAddress)}\n`); - - // Group tokens by network, preserving the queried order. Networks - // with no tokens are hidden. - let printedAny = false; - for (const network of networks) { - const group = tokens.filter((t) => t.network === network); - if (group.length === 0) continue; - printedAny = true; - const chainId = networkToChainId.get(network); - console.log(` ${c.bold(`${network} (${chainId})`)}`); - printTokenTable(group, nativeFor); - console.log(""); - } - if (!printedAny) { - console.log(" No tokens found.\n"); - } - console.log( - ` ${c.dim(`Checked: ${networks.join(", ")} (${networks.length} chains)`)}\n` - ); - } - } else { - console.log("NETWORK\tTOKEN\tNAME\tBALANCE\tUSD\tCONTRACT"); - for (const t of tokens) { - const { symbol, name, balance, usd, contract } = formatToken( - t, - nativeFor(t.network) - ); - console.log( - `${t.network}\t${symbol}\t${name}\t${balance}\t${usd}\t${contract}` - ); - } - } + renderBalances({ + json, + networks, + networkToChainId, + tokens, + evmAddress: walletAddress, + solAddress, + }); } catch (err) { outputError(json, err instanceof Error ? err : String(err)); } @@ -585,25 +642,13 @@ export function registerWalletCommands(program: Command): void { const assets = await agentApi.getAgentAssets(agentId, [network]); const tokens = assets.data.tokens; - if (json) { - outputResult(json, { chainId, network, address, tokens }); - return; - } - - console.log(`\n${c.bold(`Solana balance on ${network}`)}\n`); - console.log(` ${c.bold("Address:")} ${c.dim(address)}\n`); - if (tokens.length === 0) { - console.log(" No tokens found.\n"); - } else { - for (const t of tokens) { - const isNative = t.tokenAddress === null; - const symbol = t.tokenMetadata.symbol ?? (isNative ? "SOL" : "???"); - const decimals = t.tokenMetadata.decimals ?? (isNative ? 9 : 0); - const balance = formatUnits(BigInt(t.tokenBalance), decimals); - console.log(` ${c.cyan(symbol.padEnd(10))}${balance}`); - } - console.log(""); - } + renderBalances({ + json, + networks: [network], + networkToChainId: new Map([[network, chainId]]), + tokens, + solAddress: address, + }); } catch (err) { outputError(json, err instanceof Error ? err : String(err)); } diff --git a/src/lib/chains.ts b/src/lib/chains.ts index b092c8a..fcf932e 100644 --- a/src/lib/chains.ts +++ b/src/lib/chains.ts @@ -36,9 +36,16 @@ export function getEnvSponsoredChainIds(): number[] { ).map((chain) => chain.id); } +export function isSolanaChainId(id: number): boolean { + return id === SOLANA_DEVNET_CHAIN_ID || id === SOLANA_MAINNET_CHAIN_ID; +} + export function getNativeCurrency( chainId: number -): { name: string; symbol: string } | undefined { +): { name: string; symbol: string; decimals: number } | undefined { + if (isSolanaChainId(chainId)) { + return { name: "Solana", symbol: "SOL", decimals: 9 }; + } return ERC20_SPONSORED_CHAINS.find((chain) => chain.id === chainId) ?.nativeCurrency; } From d359fcab7239917afa8ef8cc0007d06940292dbc Mon Sep 17 00:00:00 2001 From: Zuhwa Date: Tue, 16 Jun 2026 17:57:23 +0800 Subject: [PATCH 24/24] docs: update HL account status documentation to clarify on-chain token balance support for all sponsored EVM chains and Solana --- SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SKILL.md b/SKILL.md index f2e5d65..546e28e 100644 --- a/SKILL.md +++ b/SKILL.md @@ -262,7 +262,7 @@ acp trade --side long --token BTC --size 0.01 --leverage 5 --json acp trade --side short --token ETH --size 0.5 --price 4000 --post-only --json # HL ACCOUNT status (read-only) — HL perp positions + HL spot balances ONLY. -# For on-chain token balances (Ethereum/Arbitrum/Base/…), use `acp wallet balance` instead. +# For on-chain token balances (all sponsored EVM chains + Solana), use `acp wallet balance` instead. acp trade hl-status --json # Withdraw USDC off Hyperliquid (settles to Arbitrum; --to-chain bridges onward) acp trade withdraw-from-hl --amount 25 --json