From 03fb99a35dddad4ab27dd869e9574fe60fc6a0e1 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 7 Apr 2026 22:26:17 -0400 Subject: [PATCH] Add first-class Swig MCP wallet support --- .env.example | 7 + README.md | 3 +- container/agent-runner/package-lock.json | 1388 +++++++++++++++++++++- container/agent-runner/package.json | 1 + container/agent-runner/src/index.ts | 210 +++- container/skills/solana-agent/SKILL.md | 115 +- container/skills/swig/SKILL.md | 42 +- docs/SPEC.md | 1 + setup/environment.ts | 39 +- setup/solana.ts | 941 ++++++++++++--- src/container-runner.ts | 26 +- src/env.test.ts | 44 + src/env.ts | 56 +- src/solana/config.test.ts | 77 ++ src/solana/config.ts | 30 +- tools/lib/wallet.ts | 64 +- 16 files changed, 2738 insertions(+), 306 deletions(-) create mode 100644 src/env.test.ts create mode 100644 src/solana/config.test.ts diff --git a/.env.example b/.env.example index 89b87254ec7..59966739bf3 100644 --- a/.env.example +++ b/.env.example @@ -7,3 +7,10 @@ TELEGRAM_ONLY= # Axiom metrics (optional — metrics disabled if AXIOM_TOKEN is not set) AXIOM_TOKEN= AXIOM_DATASET=solclaw + +# Swig smart wallet (optional — used when SOLANA_SIGNING_METHOD=swig) +SWIG_AUTHORITY_PRIVATE_KEY= +SWIG_PAYMASTER_API_KEY= +SWIG_PAYMASTER_PUBKEY= +SWIG_PAYMASTER_NETWORK= +GAS_SPONSOR_URL= diff --git a/README.md b/README.md index f5f8631e804..6736367b1b3 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ ## Features - šŸ”— **60+ Solana Actions** - Trade, stake, lend, deploy tokens, mint NFTs +- 🧠 **Swig Smart Wallets** - Create and operate Swig wallets with first-class MCP support - šŸ’¬ **Natural Language** - "Swap 0.1 SOL for USDC" → Executes on-chain - šŸ“± **WhatsApp/Telegram** - Chat with your agent from your phone - šŸ” **Secure** - Isolated containers, private key protection @@ -19,7 +20,7 @@ cd solclaw claude ``` -and after claude code has been started run ```/setup ``` in the claude code itself +and after claude code has been started run `/setup` in Claude Code itself ## Example Commands diff --git a/container/agent-runner/package-lock.json b/container/agent-runner/package-lock.json index 9c52a7cdb56..b7706210c45 100644 --- a/container/agent-runner/package-lock.json +++ b/container/agent-runner/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.2.70", "@modelcontextprotocol/sdk": "^1.12.1", + "@swig-wallet/mcp-server": "^1.1.0", "cron-parser": "^5.0.0", "zod": "^4.0.0" }, @@ -430,16 +431,903 @@ } } }, + "node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@solana-program/token": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@solana-program/token/-/token-0.5.1.tgz", + "integrity": "sha512-bJvynW5q9SFuVOZ5vqGVkmaPGA0MCC+m9jgJj1nk5m20I389/ms69ASnhWGoOPNcie7S9OwBX0gTj2fiyWpfag==", + "license": "Apache-2.0", + "peerDependencies": { + "@solana/kit": "^2.1.0" + } + }, + "node_modules/@solana/accounts": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/accounts/-/accounts-2.3.0.tgz", + "integrity": "sha512-QgQTj404Z6PXNOyzaOpSzjgMOuGwG8vC66jSDB+3zHaRcEPRVRd2sVSrd1U6sHtnV3aiaS6YyDuPQMheg4K2jw==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/rpc-spec": "2.3.0", + "@solana/rpc-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/addresses": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/addresses/-/addresses-2.3.0.tgz", + "integrity": "sha512-ypTNkY2ZaRFpHLnHAgaW8a83N0/WoqdFvCqf4CQmnMdFsZSdC7qOwcbd7YzdaQn9dy+P2hybewzB+KP7LutxGA==", + "license": "MIT", + "dependencies": { + "@solana/assertions": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/nominal-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/assertions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/assertions/-/assertions-2.3.0.tgz", + "integrity": "sha512-Ekoet3khNg3XFLN7MIz8W31wPQISpKUGDGTylLptI+JjCDWx3PIa88xjEMqFo02WJ8sBj2NLV64Xg1sBcsHjZQ==", + "license": "MIT", + "dependencies": { + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "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", + "dependencies": { + "buffer": "~6.0.3" + }, + "engines": { + "node": ">=5.10" + } + }, + "node_modules/@solana/codecs": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs/-/codecs-2.3.0.tgz", + "integrity": "sha512-JVqGPkzoeyU262hJGdH64kNLH0M+Oew2CIPOa/9tR3++q2pEd4jU2Rxdfye9sd0Ce3XJrR5AIa8ZfbyQXzjh+g==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/codecs-data-structures": "2.3.0", + "@solana/codecs-numbers": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/options": "2.3.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", + "dependencies": { + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/codecs-data-structures": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-data-structures/-/codecs-data-structures-2.3.0.tgz", + "integrity": "sha512-qvU5LE5DqEdYMYgELRHv+HMOx73sSoV1ZZkwIrclwUmwTbTaH8QAJURBj0RhQ/zCne7VuLLOZFFGv6jGigWhSw==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/codecs-numbers": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "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==", + "license": "MIT", + "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": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-strings/-/codecs-strings-2.3.0.tgz", + "integrity": "sha512-y5pSBYwzVziXu521hh+VxqUtp0hYGTl1eWGoc1W+8mdvBdC1kTqm/X7aYQw33J42hw03JjryvYOvmGgk3Qz/Ug==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/codecs-numbers": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "fastestsmallesttextencoderdecoder": "^1.0.22", + "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==", + "license": "MIT", + "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/fast-stable-stringify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/fast-stable-stringify/-/fast-stable-stringify-2.3.0.tgz", + "integrity": "sha512-KfJPrMEieUg6D3hfQACoPy0ukrAV8Kio883llt/8chPEG3FVTX9z/Zuf4O01a15xZmBbmQ7toil2Dp0sxMJSxw==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/functional": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/functional/-/functional-2.3.0.tgz", + "integrity": "sha512-AgsPh3W3tE+nK3eEw/W9qiSfTGwLYEvl0rWaxHht/lRcuDVwfKRzeSa5G79eioWFFqr+pTtoCr3D3OLkwKz02Q==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/instructions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/instructions/-/instructions-2.3.0.tgz", + "integrity": "sha512-PLMsmaIKu7hEAzyElrk2T7JJx4D+9eRwebhFZpy2PXziNSmFF929eRHKUsKqBFM3cYR1Yy3m6roBZfA+bGE/oQ==", + "license": "MIT", + "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/keys": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/keys/-/keys-2.3.0.tgz", + "integrity": "sha512-ZVVdga79pNH+2pVcm6fr2sWz9HTwfopDVhYb0Lh3dh+WBmJjwkabXEIHey2rUES7NjFa/G7sV8lrUn/v8LDCCQ==", + "license": "MIT", + "dependencies": { + "@solana/assertions": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/nominal-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/kit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/kit/-/kit-2.3.0.tgz", + "integrity": "sha512-sb6PgwoW2LjE5oTFu4lhlS/cGt/NB3YrShEyx7JgWFWysfgLdJnhwWThgwy/4HjNsmtMrQGWVls0yVBHcMvlMQ==", + "license": "MIT", + "dependencies": { + "@solana/accounts": "2.3.0", + "@solana/addresses": "2.3.0", + "@solana/codecs": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/functional": "2.3.0", + "@solana/instructions": "2.3.0", + "@solana/keys": "2.3.0", + "@solana/programs": "2.3.0", + "@solana/rpc": "2.3.0", + "@solana/rpc-parsed-types": "2.3.0", + "@solana/rpc-spec-types": "2.3.0", + "@solana/rpc-subscriptions": "2.3.0", + "@solana/rpc-types": "2.3.0", + "@solana/signers": "2.3.0", + "@solana/sysvars": "2.3.0", + "@solana/transaction-confirmation": "2.3.0", + "@solana/transaction-messages": "2.3.0", + "@solana/transactions": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/nominal-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/nominal-types/-/nominal-types-2.3.0.tgz", + "integrity": "sha512-uKlMnlP4PWW5UTXlhKM8lcgIaNj8dvd8xO4Y9l+FVvh9RvW2TO0GwUO6JCo7JBzCB0PSqRJdWWaQ8pu1Ti/OkA==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/options": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/options/-/options-2.3.0.tgz", + "integrity": "sha512-PPnnZBRCWWoZQ11exPxf//DRzN2C6AoFsDI/u2AsQfYih434/7Kp4XLpfOMT/XESi+gdBMFNNfbES5zg3wAIkw==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/codecs-data-structures": "2.3.0", + "@solana/codecs-numbers": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/programs": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/programs/-/programs-2.3.0.tgz", + "integrity": "sha512-UXKujV71VCI5uPs+cFdwxybtHZAIZyQkqDiDnmK+DawtOO9mBn4Nimdb/6RjR2CXT78mzO9ZCZ3qfyX+ydcB7w==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/promises": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/promises/-/promises-2.3.0.tgz", + "integrity": "sha512-GjVgutZKXVuojd9rWy1PuLnfcRfqsaCm7InCiZc8bqmJpoghlyluweNc7ml9Y5yQn1P2IOyzh9+p/77vIyNybQ==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc/-/rpc-2.3.0.tgz", + "integrity": "sha512-ZWN76iNQAOCpYC7yKfb3UNLIMZf603JckLKOOLTHuy9MZnTN8XV6uwvDFhf42XvhglgUjGCEnbUqWtxQ9pa/pQ==", + "license": "MIT", + "dependencies": { + "@solana/errors": "2.3.0", + "@solana/fast-stable-stringify": "2.3.0", + "@solana/functional": "2.3.0", + "@solana/rpc-api": "2.3.0", + "@solana/rpc-spec": "2.3.0", + "@solana/rpc-spec-types": "2.3.0", + "@solana/rpc-transformers": "2.3.0", + "@solana/rpc-transport-http": "2.3.0", + "@solana/rpc-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-api": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-api/-/rpc-api-2.3.0.tgz", + "integrity": "sha512-UUdiRfWoyYhJL9PPvFeJr4aJ554ob2jXcpn4vKmRVn9ire0sCbpQKYx6K8eEKHZWXKrDW8IDspgTl0gT/aJWVg==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/keys": "2.3.0", + "@solana/rpc-parsed-types": "2.3.0", + "@solana/rpc-spec": "2.3.0", + "@solana/rpc-transformers": "2.3.0", + "@solana/rpc-types": "2.3.0", + "@solana/transaction-messages": "2.3.0", + "@solana/transactions": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-parsed-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-parsed-types/-/rpc-parsed-types-2.3.0.tgz", + "integrity": "sha512-B5pHzyEIbBJf9KHej+zdr5ZNAdSvu7WLU2lOUPh81KHdHQs6dEb310LGxcpCc7HVE8IEdO20AbckewDiAN6OCg==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-spec": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-spec/-/rpc-spec-2.3.0.tgz", + "integrity": "sha512-fA2LMX4BMixCrNB2n6T83AvjZ3oUQTu7qyPLyt8gHQaoEAXs8k6GZmu6iYcr+FboQCjUmRPgMaABbcr9j2J9Sw==", + "license": "MIT", + "dependencies": { + "@solana/errors": "2.3.0", + "@solana/rpc-spec-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-spec-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-spec-types/-/rpc-spec-types-2.3.0.tgz", + "integrity": "sha512-xQsb65lahjr8Wc9dMtP7xa0ZmDS8dOE2ncYjlvfyw/h4mpdXTUdrSMi6RtFwX33/rGuztQ7Hwaid5xLNSLvsFQ==", + "license": "MIT", + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-subscriptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions/-/rpc-subscriptions-2.3.0.tgz", + "integrity": "sha512-Uyr10nZKGVzvCOqwCZgwYrzuoDyUdwtgQRefh13pXIrdo4wYjVmoLykH49Omt6abwStB0a4UL5gX9V4mFdDJZg==", + "license": "MIT", + "dependencies": { + "@solana/errors": "2.3.0", + "@solana/fast-stable-stringify": "2.3.0", + "@solana/functional": "2.3.0", + "@solana/promises": "2.3.0", + "@solana/rpc-spec-types": "2.3.0", + "@solana/rpc-subscriptions-api": "2.3.0", + "@solana/rpc-subscriptions-channel-websocket": "2.3.0", + "@solana/rpc-subscriptions-spec": "2.3.0", + "@solana/rpc-transformers": "2.3.0", + "@solana/rpc-types": "2.3.0", + "@solana/subscribable": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-subscriptions-api": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-api/-/rpc-subscriptions-api-2.3.0.tgz", + "integrity": "sha512-9mCjVbum2Hg9KGX3LKsrI5Xs0KX390lS+Z8qB80bxhar6MJPugqIPH8uRgLhCW9GN3JprAfjRNl7our8CPvsPQ==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/keys": "2.3.0", + "@solana/rpc-subscriptions-spec": "2.3.0", + "@solana/rpc-transformers": "2.3.0", + "@solana/rpc-types": "2.3.0", + "@solana/transaction-messages": "2.3.0", + "@solana/transactions": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-subscriptions-channel-websocket": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-channel-websocket/-/rpc-subscriptions-channel-websocket-2.3.0.tgz", + "integrity": "sha512-2oL6ceFwejIgeWzbNiUHI2tZZnaOxNTSerszcin7wYQwijxtpVgUHiuItM/Y70DQmH9sKhmikQp+dqeGalaJxw==", + "license": "MIT", + "dependencies": { + "@solana/errors": "2.3.0", + "@solana/functional": "2.3.0", + "@solana/rpc-subscriptions-spec": "2.3.0", + "@solana/subscribable": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3", + "ws": "^8.18.0" + } + }, + "node_modules/@solana/rpc-subscriptions-spec": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-spec/-/rpc-subscriptions-spec-2.3.0.tgz", + "integrity": "sha512-rdmVcl4PvNKQeA2l8DorIeALCgJEMSu7U8AXJS1PICeb2lQuMeaR+6cs/iowjvIB0lMVjYN2sFf6Q3dJPu6wWg==", + "license": "MIT", + "dependencies": { + "@solana/errors": "2.3.0", + "@solana/promises": "2.3.0", + "@solana/rpc-spec-types": "2.3.0", + "@solana/subscribable": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-transformers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-transformers/-/rpc-transformers-2.3.0.tgz", + "integrity": "sha512-UuHYK3XEpo9nMXdjyGKkPCOr7WsZsxs7zLYDO1A5ELH3P3JoehvrDegYRAGzBS2VKsfApZ86ZpJToP0K3PhmMA==", + "license": "MIT", + "dependencies": { + "@solana/errors": "2.3.0", + "@solana/functional": "2.3.0", + "@solana/nominal-types": "2.3.0", + "@solana/rpc-spec-types": "2.3.0", + "@solana/rpc-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-transport-http": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-transport-http/-/rpc-transport-http-2.3.0.tgz", + "integrity": "sha512-HFKydmxGw8nAF5N+S0NLnPBDCe5oMDtI2RAmW8DMqP4U3Zxt2XWhvV1SNkAldT5tF0U1vP+is6fHxyhk4xqEvg==", + "license": "MIT", + "dependencies": { + "@solana/errors": "2.3.0", + "@solana/rpc-spec": "2.3.0", + "@solana/rpc-spec-types": "2.3.0", + "undici-types": "^7.11.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/rpc-transport-http/node_modules/undici-types": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.7.tgz", + "integrity": "sha512-XA+gOBkzYD3C74sZowtCLTpgtaCdqZhqCvR6y9LXvrKTt/IVU6bz49T4D+BPi475scshCCkb0IklJRw6T1ZlgQ==", + "license": "MIT" + }, + "node_modules/@solana/rpc-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-types/-/rpc-types-2.3.0.tgz", + "integrity": "sha512-O09YX2hED2QUyGxrMOxQ9GzH1LlEwwZWu69QbL4oYmIf6P5dzEEHcqRY6L1LsDVqc/dzAdEs/E1FaPrcIaIIPw==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/codecs-numbers": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/nominal-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/signers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/signers/-/signers-2.3.0.tgz", + "integrity": "sha512-OSv6fGr/MFRx6J+ZChQMRqKNPGGmdjkqarKkRzkwmv7v8quWsIRnJT5EV8tBy3LI4DLO/A8vKiNSPzvm1TdaiQ==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/instructions": "2.3.0", + "@solana/keys": "2.3.0", + "@solana/nominal-types": "2.3.0", + "@solana/transaction-messages": "2.3.0", + "@solana/transactions": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/subscribable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/subscribable/-/subscribable-2.3.0.tgz", + "integrity": "sha512-DkgohEDbMkdTWiKAoatY02Njr56WXx9e/dKKfmne8/Ad6/2llUIrax78nCdlvZW9quXMaXPTxZvdQqo9N669Og==", + "license": "MIT", + "dependencies": { + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/sysvars": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/sysvars/-/sysvars-2.3.0.tgz", + "integrity": "sha512-LvjADZrpZ+CnhlHqfI5cmsRzX9Rpyb1Ox2dMHnbsRNzeKAMhu9w4ZBIaeTdO322zsTr509G1B+k2ABD3whvUBA==", + "license": "MIT", + "dependencies": { + "@solana/accounts": "2.3.0", + "@solana/codecs": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/rpc-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/transaction-confirmation": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/transaction-confirmation/-/transaction-confirmation-2.3.0.tgz", + "integrity": "sha512-UiEuiHCfAAZEKdfne/XljFNJbsKAe701UQHKXEInYzIgBjRbvaeYZlBmkkqtxwcasgBTOmEaEKT44J14N9VZDw==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/keys": "2.3.0", + "@solana/promises": "2.3.0", + "@solana/rpc": "2.3.0", + "@solana/rpc-subscriptions": "2.3.0", + "@solana/rpc-types": "2.3.0", + "@solana/transaction-messages": "2.3.0", + "@solana/transactions": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/transaction-messages": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/transaction-messages/-/transaction-messages-2.3.0.tgz", + "integrity": "sha512-bgqvWuy3MqKS5JdNLH649q+ngiyOu5rGS3DizSnWwYUd76RxZl1kN6CoqHSrrMzFMvis6sck/yPGG3wqrMlAww==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/codecs-data-structures": "2.3.0", + "@solana/codecs-numbers": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/functional": "2.3.0", + "@solana/instructions": "2.3.0", + "@solana/nominal-types": "2.3.0", + "@solana/rpc-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/transactions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/transactions/-/transactions-2.3.0.tgz", + "integrity": "sha512-LnTvdi8QnrQtuEZor5Msje61sDpPstTVwKg4y81tNxDhiyomjuvnSNLAq6QsB9gIxUqbNzPZgOG9IU4I4/Uaug==", + "license": "MIT", + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/codecs-data-structures": "2.3.0", + "@solana/codecs-numbers": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/functional": "2.3.0", + "@solana/instructions": "2.3.0", + "@solana/keys": "2.3.0", + "@solana/nominal-types": "2.3.0", + "@solana/rpc-types": "2.3.0", + "@solana/transaction-messages": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "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", + "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/@solana/web3.js/node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@solana/web3.js/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "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", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@swig-wallet/api": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@swig-wallet/api/-/api-1.3.0.tgz", + "integrity": "sha512-JwLNKH0/0GbWLfKiPyjAbW/V6Ev7lIGn9a1FtpWQJygoPIqDeke3xn6PW0xiEgg6p2w6SXmiSIRRcCCTGxVnHQ==", + "license": "Apache-2.0" + }, + "node_modules/@swig-wallet/classic": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@swig-wallet/classic/-/classic-1.9.0.tgz", + "integrity": "sha512-nYymevuqTBcYZT8hToAy0E/WYO4frE5o99FYMCoTgNjgvqy3blP4n2qr7aDZzPDsutsK/iWUwXpxEXuWQKJQog==", + "license": "Apache-2.0", + "dependencies": { + "@noble/curves": "^1.8.2", + "@solana-program/token": "^0.5.1", + "@solana/kit": "^2.1.0", + "@swig-wallet/coder": "1.9.0", + "@swig-wallet/lib": "1.9.0", + "bn.js": "^5.2.2" + }, + "peerDependencies": { + "@solana/web3.js": "^1.98.0" + } + }, + "node_modules/@swig-wallet/coder": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@swig-wallet/coder/-/coder-1.9.0.tgz", + "integrity": "sha512-yQp0Z4r+plAFRVqe18VkC659jyv8Z2hPT48vBsT1iukl0rl4lJSJPkeFS52RZ7dvKeRWBd4q/HJIRVLrW4k54A==", + "license": "Apache-2.0", + "dependencies": { + "@solana/kit": "^2.1.0" + } + }, + "node_modules/@swig-wallet/lib": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@swig-wallet/lib/-/lib-1.9.0.tgz", + "integrity": "sha512-dM6pwT+ouWyiNKxRuny/RbmCntjGPk5EaeWoGe1dVlX7kuTqAlEzaHdNhCGi2sHihtwv2tqE52s1HUv59fUdbQ==", + "license": "Apache-2.0", + "dependencies": { + "@noble/curves": "^1.8.2", + "@noble/hashes": "^1.7.0", + "@solana-program/token": "^0.5.1", + "@solana/kit": "^2.1.0", + "@swig-wallet/coder": "1.9.0", + "bn.js": "^5.2.2" + } + }, + "node_modules/@swig-wallet/mcp-server": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@swig-wallet/mcp-server/-/mcp-server-1.1.0.tgz", + "integrity": "sha512-6RCykrGH4NiL8LcMvFJyoBaCambsD3AnnCtwNzAzAHI26sS+rhyHs+k8wq9eK4wFKtwq45SSNgah7AT8qzBd8g==", + "license": "Apache-2.0", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.1", + "@solana/web3.js": "^1.98.0", + "@swig-wallet/classic": "1.9.0", + "@swig-wallet/paymaster-classic": "1.0.5", + "bs58": "^6.0.0", + "zod": "^3.23.0" + }, + "bin": { + "swig-mcp-server": "dist/index.js" + } + }, + "node_modules/@swig-wallet/mcp-server/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@swig-wallet/paymaster-classic": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@swig-wallet/paymaster-classic/-/paymaster-classic-1.0.5.tgz", + "integrity": "sha512-GSjG8ClxalAXc0mjSerQuYG6Y2kDLIRl6VdeU/25BC96vcF1G0vvzL89OJAHSmSps3OnBpuZ1AxPr9G+ciRKuA==", + "license": "Apache-2.0", + "dependencies": { + "@solana/kit": "^2.1.0", + "@swig-wallet/paymaster-core": "1.0.5", + "bn.js": "^5.2.2" + }, + "peerDependencies": { + "@solana/web3.js": "^1.98.0" + } + }, + "node_modules/@swig-wallet/paymaster-core": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@swig-wallet/paymaster-core/-/paymaster-core-1.0.5.tgz", + "integrity": "sha512-UIcEgOcRZMTPVQ7qZGNGFM83YYe/fMw0kMAqsq4dibu4iZOOJ36tQnUoCqO70/VAvqmFK6tZ66PYJTokKMToew==", + "license": "Apache-2.0", + "dependencies": { + "@solana/kit": "^2.1.0", + "@swig-wallet/api": "1.3.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", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "22.19.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", + "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -453,6 +1341,18 @@ "node": ">= 0.6" } }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", @@ -486,6 +1386,38 @@ } } }, + "node_modules/base-x": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz", + "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bn.js": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", + "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==", + "license": "MIT" + }, "node_modules/body-parser": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", @@ -510,6 +1442,82 @@ "url": "https://opencollective.com/express" } }, + "node_modules/borsh": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz", + "integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==", + "license": "Apache-2.0", + "dependencies": { + "bn.js": "^5.2.0", + "bs58": "^4.0.0", + "text-encoding-utf-8": "^1.0.2" + } + }, + "node_modules/borsh/node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/borsh/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/bs58": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", + "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", + "license": "MIT", + "dependencies": { + "base-x": "^5.0.0" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/bufferutil": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", + "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -548,6 +1556,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "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", + "engines": { + "node": ">=20" + } + }, "node_modules/content-disposition": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", @@ -648,6 +1677,18 @@ } } }, + "node_modules/delay": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", + "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -716,6 +1757,21 @@ "node": ">= 0.4" } }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT" + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "license": "MIT", + "dependencies": { + "es6-promise": "^4.0.3" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -731,6 +1787,12 @@ "node": ">= 0.6" } }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, "node_modules/eventsource": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", @@ -813,12 +1875,26 @@ "express": ">= 4.11" } }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "engines": { + "node": "> 0.1.90" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, + "node_modules/fast-stable-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz", + "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==", + "license": "MIT" + }, "node_modules/fast-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", @@ -835,6 +1911,13 @@ ], "license": "BSD-3-Clause" }, + "node_modules/fastestsmallesttextencoderdecoder": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz", + "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==", + "license": "CC0-1.0", + "peer": true + }, "node_modules/finalhandler": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", @@ -985,6 +2068,15 @@ "url": "https://opencollective.com/express" } }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "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", @@ -1001,6 +2093,26 @@ "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" + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -1037,6 +2149,74 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/isomorphic-ws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/jayson": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.3.0.tgz", + "integrity": "sha512-AauzHcUcqs8OBnCHOkJY280VaTiCm57AbuO7lqzcw7JapGj50BisE3xhksye4zlTSR1+1tAz67wLTl8tEH1obQ==", + "license": "MIT", + "dependencies": { + "@types/connect": "^3.4.33", + "@types/node": "^12.12.54", + "@types/ws": "^7.4.4", + "commander": "^2.20.3", + "delay": "^5.0.0", + "es6-promisify": "^5.0.0", + "eyes": "^0.1.8", + "isomorphic-ws": "^4.0.1", + "json-stringify-safe": "^5.0.1", + "stream-json": "^1.9.1", + "uuid": "^8.3.2", + "ws": "^7.5.10" + }, + "bin": { + "jayson": "bin/jayson.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jayson/node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "license": "MIT" + }, + "node_modules/jayson/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/jayson/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/jose": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", @@ -1071,6 +2251,12 @@ "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", "license": "BSD-2-Clause" }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, "node_modules/luxon": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", @@ -1150,6 +2336,38 @@ "node": ">= 0.6" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1306,6 +2524,71 @@ "node": ">= 18" } }, + "node_modules/rpc-websockets": { + "version": "9.3.7", + "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-9.3.7.tgz", + "integrity": "sha512-dQal1U0yKH2umW0DgqSecP4G1jNxyPUGY60uUMB8bLoXabC2aWT3Cag9hOhZXsH/52QJEcggxNNWhF+Fp48ykw==", + "license": "LGPL-3.0-only", + "dependencies": { + "@swc/helpers": "^0.5.11", + "@types/uuid": "^10.0.0", + "@types/ws": "^8.2.2", + "buffer": "^6.0.3", + "eventemitter3": "^5.0.1", + "uuid": "^11.0.0", + "ws": "^8.5.0" + }, + "funding": { + "type": "paypal", + "url": "https://paypal.me/kozjak" + }, + "optionalDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^6.0.0" + } + }, + "node_modules/rpc-websockets/node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/rpc-websockets/node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -1465,6 +2748,35 @@ "node": ">= 0.8" } }, + "node_modules/stream-chain": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", + "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==", + "license": "BSD-3-Clause" + }, + "node_modules/stream-json": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz", + "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==", + "license": "BSD-3-Clause", + "dependencies": { + "stream-chain": "^2.2.5" + } + }, + "node_modules/superstruct": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz", + "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/text-encoding-utf-8": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", + "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==" + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1474,12 +2786,24 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/ts-algebra": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", "license": "MIT" }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", @@ -1498,7 +2822,6 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -1512,7 +2835,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" }, "node_modules/unpipe": { @@ -1524,6 +2846,29 @@ "node": ">= 0.8" } }, + "node_modules/utf-8-validate": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.6.tgz", + "integrity": "sha512-q3l3P9UtEEiAHcsgsqTgf9PPjctrDWoIXW3NpOHFdRDbLvu4DLIcxHangJ4RLrWkBcKjmcs/6NkerI8T/rE4LA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -1533,6 +2878,22 @@ "node": ">= 0.8" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1554,6 +2915,27 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/zod": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", diff --git a/container/agent-runner/package.json b/container/agent-runner/package.json index 00dabb8fa01..3f0abe056ec 100644 --- a/container/agent-runner/package.json +++ b/container/agent-runner/package.json @@ -11,6 +11,7 @@ "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.2.70", "@modelcontextprotocol/sdk": "^1.12.1", + "@swig-wallet/mcp-server": "^1.1.0", "cron-parser": "^5.0.0", "zod": "^4.0.0" }, diff --git a/container/agent-runner/src/index.ts b/container/agent-runner/src/index.ts index 60829213c0c..14fdf650aad 100644 --- a/container/agent-runner/src/index.ts +++ b/container/agent-runner/src/index.ts @@ -15,6 +15,7 @@ */ import fs from 'fs'; +import { createRequire } from 'module'; import path from 'path'; import { query, @@ -69,6 +70,17 @@ import { const IPC_POLL_MS = 500; const SYNC_API_URL = 'https://api.breeze.baby/agent/stats-sync-up'; +interface LoadedSwigConfig { + rpcUrl: string; + authorityPublicKey: string; + walletAddress?: string; + swigAccountAddress?: string; + feeMode?: 'paymaster' | 'gas-sponsor' | 'self-funded'; + swigPaymasterPubkey?: string; + swigPaymasterNetwork?: 'mainnet' | 'devnet'; + gasSponsorUrl?: string; +} + /** * Push-based async iterable for streaming user messages to the SDK. * Keeps the iterable alive until end() is called, preventing isSingleUserTurn. @@ -132,6 +144,96 @@ function log(message: string): void { console.error(`[agent-runner] ${message}`); } +function loadSwigConfig(isMain: boolean): LoadedSwigConfig | null { + if (!isMain) return null; + + const configPath = '/workspace/project/config/solana-config.json'; + if (!fs.existsSync(configPath)) return null; + + try { + const raw = JSON.parse(fs.readFileSync(configPath, 'utf-8')) as { + wallet?: { + signingMethod?: string; + publicKey?: string; + authorityPublicKey?: string; + swigWalletAddress?: string; + swigAccountAddress?: string; + feeMode?: 'paymaster' | 'gas-sponsor' | 'self-funded'; + swigPaymasterPubkey?: string; + swigPaymasterNetwork?: 'mainnet' | 'devnet'; + gasSponsorUrl?: string; + }; + preferences?: { rpcUrl?: string }; + }; + + if (raw.wallet?.signingMethod !== 'swig') return null; + + const authorityPublicKey = + raw.wallet.authorityPublicKey || raw.wallet.publicKey; + const rpcUrl = raw.preferences?.rpcUrl; + if (!authorityPublicKey || !rpcUrl) { + log( + 'Swig config is missing authorityPublicKey or rpcUrl; skipping MCP registration', + ); + return null; + } + + return { + rpcUrl, + authorityPublicKey, + walletAddress: raw.wallet.swigWalletAddress, + swigAccountAddress: raw.wallet.swigAccountAddress, + feeMode: raw.wallet.feeMode, + swigPaymasterPubkey: raw.wallet.swigPaymasterPubkey, + swigPaymasterNetwork: raw.wallet.swigPaymasterNetwork, + gasSponsorUrl: raw.wallet.gasSponsorUrl, + }; + } catch (err) { + log( + `Failed to read Swig config: ${err instanceof Error ? err.message : String(err)}`, + ); + return null; + } +} + +function buildSwigSystemPromptAppend(config: LoadedSwigConfig): string { + const lines = [ + '## Swig Wallet Mode', + 'This workspace is configured to use Swig as the Solana wallet backend.', + 'Prefer `mcp__swig_wallet__*` tools over local CLI tools whenever the request involves wallet state, authority management, SOL transfers, or custom transaction execution.', + `RPC URL: ${config.rpcUrl}`, + `Authority public key: ${config.authorityPublicKey}`, + `Stored Swig wallet address: ${config.walletAddress || 'not created yet'}`, + `Stored Swig account address: ${config.swigAccountAddress || 'not created yet'}`, + ]; + + if (config.feeMode) { + lines.push(`Fee mode: ${config.feeMode}`); + } + if (config.feeMode === 'paymaster') { + lines.push( + `Swig paymaster pubkey: ${config.swigPaymasterPubkey || 'missing'}`, + `Swig paymaster network: ${config.swigPaymasterNetwork || 'missing'}`, + 'Before sending transactions, call `configure_paymaster` using the stored paymaster settings plus the `SWIG_PAYMASTER_API_KEY` value from the environment.', + ); + } + if (config.feeMode === 'gas-sponsor') { + lines.push( + `Gas sponsor URL: ${config.gasSponsorUrl || 'missing'}`, + 'Before sending transactions, call `configure_gas_sponsor` with the stored gas sponsor URL.', + ); + } + + lines.push( + 'Always call `configure_rpc` first with the stored RPC URL.', + 'If `SWIG_AUTHORITY_PRIVATE_KEY` is available in the environment, load it with `configure_agent_keypair` before wallet operations.', + 'If no wallet exists yet, create it with `create_swig_wallet`.', + 'After creating a wallet or discovering updated Swig addresses, persist `swigWalletAddress` and `swigAccountAddress` back into `config/solana-config.json`.', + ); + + return lines.join('\n'); +} + function getSessionSummary( sessionId: string, transcriptPath: string, @@ -441,6 +543,20 @@ async function runQuery( let messageCount = 0; let resultCount = 0; + const swigConfig = loadSwigConfig(containerInput.isMain); + let swigMcpServerPath: string | null = null; + if (swigConfig) { + try { + const require = createRequire(import.meta.url); + swigMcpServerPath = + require.resolve('@swig-wallet/mcp-server/dist/index.js'); + } catch (err) { + log( + `Swig MCP server is configured but the package is unavailable: ${err instanceof Error ? err.message : String(err)}`, + ); + } + } + // Load global CLAUDE.md as additional system context (shared across all groups) const globalClaudeMdPath = '/workspace/global/CLAUDE.md'; let globalClaudeMd: string | undefined; @@ -468,10 +584,14 @@ For SOL transactions, use wSOL mint: So11111111111111111111111111111111111111112 This applies to ALL transaction types: swaps, transfers, stakes, account creation, lending, borrowing, NFT mints, etc. `; - // Prepend tx logging instruction to the system prompt append - const systemPromptAppend = globalClaudeMd - ? txLoggingInstruction + '\n' + globalClaudeMd - : txLoggingInstruction; + const systemPromptSections = [txLoggingInstruction]; + if (swigConfig && swigMcpServerPath) { + systemPromptSections.push(buildSwigSystemPromptAppend(swigConfig)); + } + if (globalClaudeMd) { + systemPromptSections.push(globalClaudeMd); + } + const systemPromptAppend = systemPromptSections.join('\n\n'); // Discover additional directories mounted at /workspace/extra/* // These are passed to the SDK so their CLAUDE.md files are loaded automatically @@ -489,6 +609,54 @@ This applies to ALL transaction types: swaps, transfers, stakes, account creatio log(`Additional directories: ${extraDirs.join(', ')}`); } + const allowedTools = [ + 'Bash', + 'Read', + 'Write', + 'Edit', + 'Glob', + 'Grep', + 'WebSearch', + 'WebFetch', + 'Task', + 'TaskOutput', + 'TaskStop', + 'TeamCreate', + 'TeamDelete', + 'SendMessage', + 'TodoWrite', + 'ToolSearch', + 'Skill', + 'NotebookEdit', + 'mcp__nanoclaw__*', + ]; + + if (swigConfig && swigMcpServerPath) { + allowedTools.push('mcp__swig_wallet__*'); + } + + const mcpServers: Record< + string, + { command: string; args: string[]; env?: Record } + > = { + nanoclaw: { + command: 'node', + args: [mcpServerPath], + env: { + SOLCLAW_CHAT_JID: containerInput.chatJid, + SOLCLAW_GROUP_FOLDER: containerInput.groupFolder, + SOLCLAW_IS_MAIN: containerInput.isMain ? '1' : '0', + }, + }, + }; + + if (swigConfig && swigMcpServerPath) { + mcpServers.swig_wallet = { + command: 'node', + args: [swigMcpServerPath], + }; + } + for await (const message of query({ prompt: stream, options: { @@ -501,42 +669,12 @@ This applies to ALL transaction types: swaps, transfers, stakes, account creatio preset: 'claude_code' as const, append: systemPromptAppend, }, - allowedTools: [ - 'Bash', - 'Read', - 'Write', - 'Edit', - 'Glob', - 'Grep', - 'WebSearch', - 'WebFetch', - 'Task', - 'TaskOutput', - 'TaskStop', - 'TeamCreate', - 'TeamDelete', - 'SendMessage', - 'TodoWrite', - 'ToolSearch', - 'Skill', - 'NotebookEdit', - 'mcp__nanoclaw__*', - ], + allowedTools, env: sdkEnv, permissionMode: 'bypassPermissions', allowDangerouslySkipPermissions: true, settingSources: ['project', 'user'], - mcpServers: { - nanoclaw: { - command: 'node', - args: [mcpServerPath], - env: { - SOLCLAW_CHAT_JID: containerInput.chatJid, - SOLCLAW_GROUP_FOLDER: containerInput.groupFolder, - SOLCLAW_IS_MAIN: containerInput.isMain ? '1' : '0', - }, - }, - }, + mcpServers, hooks: { PreCompact: [ { hooks: [createPreCompactHook(containerInput.assistantName)] }, diff --git a/container/skills/solana-agent/SKILL.md b/container/skills/solana-agent/SKILL.md index 9f9a72d154c..02d7bfa3615 100644 --- a/container/skills/solana-agent/SKILL.md +++ b/container/skills/solana-agent/SKILL.md @@ -5,11 +5,12 @@ description: Access Solana blockchain data and execute transactions using the co # Solana Agent -Execute Solana operations using CLI tools (standard signing) or Crossmint MCP tools (custodial signing). +Execute Solana operations using CLI tools (standard signing), Swig MCP tools (smart wallet signing), or Crossmint MCP tools (custodial signing). ## When to Use Use this skill when the user asks about: + - Wallet balances ("What's my balance?", "How much SOL do I have?") - Token prices ("Price of SOL", "What's BONK worth?") - Swapping tokens ("Swap 0.1 SOL for USDC", "Trade SOL to BONK") @@ -25,6 +26,7 @@ node -e "const c=JSON.parse(require('fs').readFileSync('config/solana-config.jso ``` - If output is `standard` → use **CLI Tools** (below) +- If output is `swig` → use **Swig MCP Tools** (below) - If output is `crossmint` → use **Crossmint MCP Tools** (bottom of this file) - If file not found → tell user to run setup: `npm run setup` @@ -41,6 +43,7 @@ cd /workspace/project && npx tsx tools/solana-balance.ts Output: `{ "sol": 1.234, "address": "..." }` SPL token balance: + ```bash cd /workspace/project && npx tsx tools/solana-balance.ts --token USDC ``` @@ -64,17 +67,58 @@ Output: `{ "signature": "...", "status": "submitted", "inputAmount": "0.03 SOL", ### Transfer Tokens SOL: + ```bash cd /workspace/project && npx tsx tools/solana-transfer.ts --to
--amount 0.5 ``` SPL Token: + ```bash cd /workspace/project && npx tsx tools/solana-transfer.ts --to
--amount 10 --token USDC ``` Output: `{ "signature": "...", "amount": "0.5 SOL", "to": "...", "explorer": "https://solscan.io/tx/..." }` +## Swig Path: MCP Tools + +If signing method is `swig`, use the Swig MCP server instead of the local CLI tools. + +### Required startup flow + +1. Read `config/solana-config.json` to get: + - `preferences.rpcUrl` + - `wallet.authorityPublicKey` + - `wallet.swigWalletAddress` + - `wallet.swigAccountAddress` + - `wallet.feeMode` +2. Call `configure_rpc` with the configured RPC URL. +3. If `SWIG_AUTHORITY_PRIVATE_KEY` is available, call `configure_agent_keypair` with it. +4. If fee mode is `paymaster`, call `configure_paymaster` using: + - `SWIG_PAYMASTER_API_KEY` from the environment + - `wallet.swigPaymasterPubkey` + - `wallet.swigPaymasterNetwork` +5. If fee mode is `gas-sponsor`, call `configure_gas_sponsor` with `wallet.gasSponsorUrl`. + +### Primary Swig tools + +- `get_balance` — check the authority or wallet balance +- `create_swig_wallet` — create a new Swig wallet when one does not exist yet +- `fetch_swig_wallet` — inspect the current wallet, authorities, and addresses +- `add_authority` — add a new authority with permissions +- `remove_authority` — remove an authority +- `update_authority` — update authority permissions +- `transact_sol_transfer` — transfer SOL from the Swig wallet +- `transact_custom` — execute arbitrary instructions through the Swig wallet + +### Persistence rule + +If `create_swig_wallet` returns new Swig addresses, update `config/solana-config.json` so future sessions can reuse: + +- `wallet.swigWalletAddress` +- `wallet.swigAccountAddress` +- `wallet.publicKey` (set this to the Swig wallet address) + ## Crossmint Path: MCP Tools If signing method is `crossmint`, use these MCP tools instead of CLI tools: @@ -89,38 +133,38 @@ For swaps with Crossmint, get a quote from Jupiter first, then sign with `crossm The CLI tools resolve these symbols automatically. You can also pass raw mint addresses. -| Symbol | Mint Address | -|--------|-------------| -| SOL | So11111111111111111111111111111111111111112 | -| USDC | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v | -| USDT | Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB | -| PYUSD | 2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo | -| EURC | HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr | -| JUP | JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN | -| RAY | 4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R | -| ORCA | orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE | -| DRIFT | DriFtupJYLTosbwoN8koMbEYSx54aFAVLddWsbksjwg7 | -| KMNO | KMNo3nJsBXfcpJTVhZcXLW7RmTwTt4GVFE7suUBo9sS | -| MSOL | mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So | -| JITOSOL | J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn | -| JTO | jtojtomepa8beP8AuQc6eXt5FriJwfFMwQx2v2f9mCL | -| JLP | 27G8MtK7VtTcCHkpASjSDdkWWYfoqT6ggEuKidVJidD4 | -| PYTH | HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3 | -| W | 85VBFQZC9TZkfaptBWjvUw7YbZjy52A6mjtPGjstQAmQ | -| TNSR | TNSRxcUxoT9xBG3de7PiJyTDYu7kskLqcpddxnEJAS6 | -| PENGU | 2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv | -| RENDER | rndrizKT3MK1iimdxRdWabcF7Zg7AR5T4nud4EkHBof | -| HNT | hntyVP6YFm1Hg25TN9WGLqM12b8TQmcknKrdu1oxWux | -| MOBILE | mb1eu7TzEc71KxDpsmsKoucSSuuoGLv1drys1oP2jh6 | -| GRASS | Grass7B4RdKfBCjTKgSqnXkqjwiGvQyFbuSCUJr3XXjs | -| BONK | DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263 | -| WIF | EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm | -| POPCAT | 7GCihgDB8fe6KNjn2MYtkzZcRjQy3t9GHdC8uHYmW2hr | -| MEW | MEW1gQWJ3nEXg2qgERiKu7FAFj79PHvQVREQUzScPP5 | -| TRUMP | 6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN | +| Symbol | Mint Address | +| -------- | -------------------------------------------- | +| SOL | So11111111111111111111111111111111111111112 | +| USDC | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v | +| USDT | Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB | +| PYUSD | 2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo | +| EURC | HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr | +| JUP | JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN | +| RAY | 4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R | +| ORCA | orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE | +| DRIFT | DriFtupJYLTosbwoN8koMbEYSx54aFAVLddWsbksjwg7 | +| KMNO | KMNo3nJsBXfcpJTVhZcXLW7RmTwTt4GVFE7suUBo9sS | +| MSOL | mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So | +| JITOSOL | J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn | +| JTO | jtojtomepa8beP8AuQc6eXt5FriJwfFMwQx2v2f9mCL | +| JLP | 27G8MtK7VtTcCHkpASjSDdkWWYfoqT6ggEuKidVJidD4 | +| PYTH | HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3 | +| W | 85VBFQZC9TZkfaptBWjvUw7YbZjy52A6mjtPGjstQAmQ | +| TNSR | TNSRxcUxoT9xBG3de7PiJyTDYu7kskLqcpddxnEJAS6 | +| PENGU | 2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv | +| RENDER | rndrizKT3MK1iimdxRdWabcF7Zg7AR5T4nud4EkHBof | +| HNT | hntyVP6YFm1Hg25TN9WGLqM12b8TQmcknKrdu1oxWux | +| MOBILE | mb1eu7TzEc71KxDpsmsKoucSSuuoGLv1drys1oP2jh6 | +| GRASS | Grass7B4RdKfBCjTKgSqnXkqjwiGvQyFbuSCUJr3XXjs | +| BONK | DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263 | +| WIF | EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm | +| POPCAT | 7GCihgDB8fe6KNjn2MYtkzZcRjQy3t9GHdC8uHYmW2hr | +| MEW | MEW1gQWJ3nEXg2qgERiKu7FAFj79PHvQVREQUzScPP5 | +| TRUMP | 6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN | | FARTCOIN | 9BB6NFEcjBCtnNLFko2FqVQBq8HHM13kCyYcdQbgpump | -| WBTC | 3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh | -| ETH | 7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs | +| WBTC | 3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh | +| ETH | 7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs | See `tools/lib/wallet.ts` for the full list of 50+ supported symbols. @@ -129,12 +173,14 @@ See `tools/lib/wallet.ts` for the full list of 50+ supported symbols. When responding to user: **Balance Check:** + ``` Balance: 1.234567 SOL Address: 9wsmkna3YUau2oyXb62b7z373Drq2Nah1pX6WcPoqMgB ``` **Swap:** + ``` Swap Successful @@ -144,12 +190,14 @@ Transaction: https://solscan.io/tx/4k2j3...abc123 ``` **Price:** + ``` SOL: $142.35 USDC: $1.00 ``` **Transfer:** + ``` Sent 0.5 SOL to 9wsmkna... @@ -169,11 +217,13 @@ Transaction: https://solscan.io/tx/4k2j3...abc123 The `tools/` directory has its own `package.json` and `node_modules/`, isolated from the host. When you need a protocol SDK that isn't already available: 1. Install it into `tools/`: + ```bash cd /workspace/project/tools && npm install @whatever/sdk ``` 2. Create a new script in `tools/` following the existing pattern: + ```bash cat > /workspace/project/tools/solana-my-action.ts << 'EOF' #!/usr/bin/env npx tsx @@ -187,6 +237,7 @@ EOF ``` 3. Run it: + ```bash cd /workspace/project && npx tsx tools/solana-my-action.ts ``` diff --git a/container/skills/swig/SKILL.md b/container/skills/swig/SKILL.md index 54beee58401..23804e8b689 100644 --- a/container/skills/swig/SKILL.md +++ b/container/skills/swig/SKILL.md @@ -11,11 +11,47 @@ description: > # Swig Smart Wallet Skill -You are an AI agent that can create and manage Solana smart wallets using the Swig protocol. Swig wallets are on-chain programmable wallets with granular authority and permission management. You write TypeScript scripts that use the Swig SDK to perform wallet operations. +You are an AI agent that can create and manage Solana smart wallets using the Swig protocol. Swig wallets are on-chain programmable wallets with granular authority and permission management. + +In SolClaw, prefer the Swig MCP tools first: + +- `configure_rpc` +- `configure_paymaster` +- `configure_gas_sponsor` +- `configure_agent_keypair` +- `get_balance` +- `create_swig_wallet` +- `fetch_swig_wallet` +- `add_authority` +- `remove_authority` +- `update_authority` +- `transact_sol_transfer` +- `transact_custom` + +Use SDK scripts only when MCP cannot do the job or when the user explicitly asks for code. ## Prerequisites -Before doing anything, you MUST gather the following from the user: +Before asking the user for new information, first check whether SolClaw is already configured: + +1. Read `config/solana-config.json` +2. If `wallet.signingMethod === "swig"`, reuse: + - `preferences.rpcUrl` + - `wallet.authorityPublicKey` + - `wallet.swigWalletAddress` + - `wallet.swigAccountAddress` + - `wallet.feeMode` + - `wallet.swigPaymasterPubkey` + - `wallet.swigPaymasterNetwork` + - `wallet.gasSponsorUrl` +3. If `SWIG_AUTHORITY_PRIVATE_KEY` is available in the environment, use it with `configure_agent_keypair` +4. If the config already contains a Swig wallet address, fetch it before making changes + +Only ask the user for missing values that are not already stored. + +If you create a new wallet or learn new Swig addresses, update `config/solana-config.json` so the next session can reuse them. + +If SolClaw is not configured yet, gather the following from the user: ### 1. Solana RPC Endpoint @@ -531,4 +567,4 @@ Always wrap transactions in try/catch and handle common errors: - Permission denied (authority lacks required actions) - Spend limit exceeded (authority has already used its allowance) -Always call `swig.refetch()` before building new instructions to ensure you have the latest on-chain state. \ No newline at end of file +Always call `swig.refetch()` before building new instructions to ensure you have the latest on-chain state. diff --git a/docs/SPEC.md b/docs/SPEC.md index 11809ed2b7d..edd5b44af28 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -63,6 +63,7 @@ A personal Claude assistant accessible via WhatsApp, with persistent memory per │ │ • WebSearch, WebFetch (internet access) │ │ │ │ • agent-browser (browser automation) │ │ │ │ • mcp__nanoclaw__* (scheduler tools via IPC) │ │ +│ │ • mcp__swig_wallet__* (when Swig signing is configured) │ │ │ │ │ │ │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ │ │ │ diff --git a/setup/environment.ts b/setup/environment.ts index 9699d72d160..2c2726d2b48 100644 --- a/setup/environment.ts +++ b/setup/environment.ts @@ -43,22 +43,28 @@ export async function run(_args: string[]): Promise { const hasEnv = fs.existsSync(path.join(projectRoot, '.env')); const authDir = path.join(projectRoot, 'store', 'auth'); - const hasAuth = - fs.existsSync(authDir) && - fs.readdirSync(authDir).length > 0; + const hasAuth = fs.existsSync(authDir) && fs.readdirSync(authDir).length > 0; // Check Solana config - const solanaConfigPath = path.join(projectRoot, 'config', 'solana-config.json'); + const solanaConfigPath = path.join( + projectRoot, + 'config', + 'solana-config.json', + ); let hasSolanaConfig = false; let solanaPublicKey = ''; let solanaNetwork = ''; let solanaSigningMethod = ''; if (fs.existsSync(solanaConfigPath)) { try { - const solanaConfig = JSON.parse(fs.readFileSync(solanaConfigPath, 'utf-8')); + const solanaConfig = JSON.parse( + fs.readFileSync(solanaConfigPath, 'utf-8'), + ); if (solanaConfig.setupComplete && solanaConfig.wallet?.publicKey) { hasSolanaConfig = true; - solanaPublicKey = solanaConfig.wallet.publicKey; + solanaPublicKey = + solanaConfig.wallet.swigWalletAddress || + solanaConfig.wallet.publicKey; solanaNetwork = solanaConfig.preferences?.rpcUrl || ''; solanaSigningMethod = solanaConfig.wallet?.signingMethod || ''; } @@ -77,9 +83,9 @@ export async function run(_args: string[]): Promise { if (fs.existsSync(dbPath)) { try { const db = new Database(dbPath, { readonly: true }); - const row = db.prepare( - 'SELECT COUNT(*) as count FROM registered_groups', - ).get() as { count: number }; + const row = db + .prepare('SELECT COUNT(*) as count FROM registered_groups') + .get() as { count: number }; if (row.count > 0) hasRegisteredGroups = true; db.close(); } catch { @@ -88,8 +94,19 @@ export async function run(_args: string[]): Promise { } } - logger.info({ platform, wsl, appleContainer, docker, hasEnv, hasAuth, hasRegisteredGroups, hasSolanaConfig }, - 'Environment check complete'); + logger.info( + { + platform, + wsl, + appleContainer, + docker, + hasEnv, + hasAuth, + hasRegisteredGroups, + hasSolanaConfig, + }, + 'Environment check complete', + ); emitStatus('CHECK_ENVIRONMENT', { PLATFORM: platform, diff --git a/setup/solana.ts b/setup/solana.ts index 86553f1dbe1..2218a75b6cf 100644 --- a/setup/solana.ts +++ b/setup/solana.ts @@ -1,9 +1,9 @@ /** * Solana setup step - * Auto-generates a local keypair and configures Solana wallet + * Configures either a standard local keypair or a Swig smart-wallet authority. */ -import { input, confirm, password } from '@inquirer/prompts'; +import { input, confirm, password, select } from '@inquirer/prompts'; import fs from 'fs/promises'; import path from 'path'; import { Keypair } from '@solana/web3.js'; @@ -12,15 +12,74 @@ import chalk from 'chalk'; import * as QRCode from 'qrcode'; import { emitStatus } from './status.js'; +const DEFAULT_MAINNET_RPC = 'https://api.breeze.baby/agent/rpc-mainnet-beta'; +const DEFAULT_DEVNET_RPC = 'https://api.devnet.solana.com'; +const DEFAULT_TESTNET_RPC = 'https://api.testnet.solana.com'; + +type SigningMethod = 'standard' | 'swig'; +type SwigFeeMode = 'paymaster' | 'gas-sponsor' | 'self-funded'; + +interface SolanaSetupConfig { + wallet?: { + signingMethod?: string; + privateKey?: string; + publicKey?: string; + authorityPublicKey?: string; + swigWalletAddress?: string; + swigAccountAddress?: string; + feeMode?: SwigFeeMode; + swigPaymasterPubkey?: string; + swigPaymasterNetwork?: 'mainnet' | 'devnet'; + gasSponsorUrl?: string; + }; + preferences?: { + rpcUrl?: string; + defaultSlippage?: number; + }; + setupComplete?: boolean; + setupDate?: string; +} + +interface StandardWalletSetup { + signingMethod: 'standard'; + publicKey: string; + privateKey: string; + walletIsInjected: boolean; + walletSource?: string; +} + +interface SwigWalletSetup { + signingMethod: 'swig'; + publicKey: string; + authorityPublicKey: string; + authorityPrivateKey: string; + walletAddress?: string; + swigAccountAddress?: string; + feeMode: SwigFeeMode; + swigPaymasterPubkey?: string; + swigPaymasterNetwork?: 'mainnet' | 'devnet'; + swigPaymasterApiKey?: string; + gasSponsorUrl?: string; + walletIsInjected: boolean; + walletSource?: string; +} + +type WalletSetup = StandardWalletSetup | SwigWalletSetup; + /** * Parse CLI args into a key-value map. * Supports: --network mainnet --slippage 50 --rpc-url + * --signing-method swig --swig-fee-mode paymaster * --dflow-key --jupiter-key --breeze-key --helius-key */ function parseArgs(args: string[]): Record { const result: Record = {}; for (let i = 0; i < args.length; i++) { - if (args[i].startsWith('--') && i + 1 < args.length && !args[i + 1].startsWith('--')) { + if ( + args[i].startsWith('--') && + i + 1 < args.length && + !args[i + 1].startsWith('--') + ) { result[args[i].slice(2)] = args[i + 1]; i++; } @@ -28,137 +87,600 @@ function parseArgs(args: string[]): Record { return result; } +async function readJsonIfExists(filePath: string): Promise { + try { + const raw = await fs.readFile(filePath, 'utf-8'); + return JSON.parse(raw) as T; + } catch { + return null; + } +} + +async function readSimpleEnvFile( + filePath: string, +): Promise> { + const result: Record = {}; + + try { + const content = await fs.readFile(filePath, 'utf-8'); + for (const line of content.split('\n')) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) continue; + const eqIdx = trimmed.indexOf('='); + if (eqIdx === -1) continue; + const key = trimmed.slice(0, eqIdx).trim(); + let value = trimmed.slice(eqIdx + 1).trim(); + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + if (key && value) result[key] = value; + } + } catch { + // Missing env file is normal during first-time setup. + } + + return result; +} + +function derivePublicKey(privateKey: string): string { + const secretKey = bs58.decode(privateKey); + return Keypair.fromSecretKey(secretKey).publicKey.toBase58(); +} + +function resolvePaymasterNetwork( + raw: string | undefined, +): 'mainnet' | 'devnet' | undefined { + if (!raw) return undefined; + if (raw === 'mainnet' || raw === 'devnet') return raw; + throw new Error( + `Invalid Swig paymaster network "${raw}". Use "mainnet" or "devnet".`, + ); +} + /** * Generate and display a QR code in the terminal for a Solana address */ export async function displayWalletQR(address: string): Promise { const solanaPayUrl = `solana:${address}`; - const qrText = await QRCode.toString(solanaPayUrl, { type: 'terminal', small: true }); + const qrText = await QRCode.toString(solanaPayUrl, { + type: 'terminal', + small: true, + }); console.log(qrText); console.log(chalk.cyan(` ${address}\n`)); } -export async function run(args: string[]): Promise { - console.log(chalk.cyan.bold('\nšŸ¦€ Solana Configuration\n')); +async function resolveSigningMethod( + cliArgs: Record, + nonInteractive: boolean, +): Promise { + const cliMethod = cliArgs['signing-method']; + if (cliMethod) { + if (cliMethod === 'standard' || cliMethod === 'swig') return cliMethod; + throw new Error( + `Invalid signing method "${cliMethod}". Use "standard" or "swig".`, + ); + } - emitStatus('SOLANA_SETUP', { STATUS: 'starting' }); + if (nonInteractive) return 'standard'; - const cliArgs = parseArgs(args); - const nonInteractive = !!cliArgs.network; + return select({ + message: 'Select signing method:', + choices: [ + { + name: 'Standard local keypair', + value: 'standard', + description: 'Direct signing with a local Solana keypair', + }, + { + name: 'Swig smart wallet', + value: 'swig', + description: + 'Use a Swig smart wallet with MCP tools for wallet creation and transactions', + }, + ], + default: 'standard', + }); +} - try { - // Step 1: Resolve wallet — use injected key if present, otherwise generate - console.log(chalk.yellow('Step 1: Wallet')); +async function resolveStandardWallet( + existingConfig: SolanaSetupConfig | null, +): Promise { + let publicKey: string; + let privateKey: string; + let walletIsInjected = false; + let walletSource: string | undefined; + + const injectedKey = process.env.SOLCLAW_WALLET_PRIVATE_KEY; + if (injectedKey) { + try { + publicKey = derivePublicKey(injectedKey); + privateKey = injectedKey; + walletIsInjected = true; + walletSource = 'Dashboard-assigned'; + console.log(chalk.green('\nāœ“ Using dashboard-assigned wallet\n')); + } catch { + console.log( + chalk.yellow( + '⚠ Invalid SOLCLAW_WALLET_PRIVATE_KEY, generating new keypair instead\n', + ), + ); + const kp = Keypair.generate(); + privateKey = bs58.encode(kp.secretKey); + publicKey = kp.publicKey.toBase58(); + } + } else if ( + existingConfig?.wallet?.signingMethod === 'standard' && + existingConfig.wallet.privateKey && + existingConfig.wallet.publicKey + ) { + publicKey = existingConfig.wallet.publicKey; + privateKey = existingConfig.wallet.privateKey; + walletIsInjected = true; + walletSource = 'Existing config'; + console.log(chalk.green('\nāœ“ Existing wallet found, reusing\n')); + } else { + console.log('Auto-generating wallet keypair.\n'); + const kp = Keypair.generate(); + privateKey = bs58.encode(kp.secretKey); + publicKey = kp.publicKey.toBase58(); + } - let publicKey: string; - let privateKey: string; - let walletIsInjected = false; - - // Dashboard-injected key takes priority (one-click cloud deployment) - const injectedKey = process.env.SOLCLAW_WALLET_PRIVATE_KEY; - if (injectedKey) { - try { - const secretKey = bs58.decode(injectedKey); - const kp = Keypair.fromSecretKey(secretKey); - publicKey = kp.publicKey.toBase58(); - privateKey = injectedKey; - walletIsInjected = true; - console.log(chalk.green('\nāœ“ Using dashboard-assigned wallet\n')); - console.log(chalk.white.bold(' Public Key')); - console.log(chalk.cyan(` ${publicKey}\n`)); - } catch { - console.log(chalk.yellow('⚠ Invalid SOLCLAW_WALLET_PRIVATE_KEY, generating new keypair instead\n')); - const kp = Keypair.generate(); - privateKey = bs58.encode(kp.secretKey); - publicKey = kp.publicKey.toBase58(); - } - } else { - // Check for existing config before generating - const existingConfigPath = path.join(process.cwd(), 'config', 'solana-config.json'); - let existingConfig: any = null; - try { - const raw = await fs.readFile(existingConfigPath, 'utf-8'); - existingConfig = JSON.parse(raw); - } catch { - // No existing config + console.log(chalk.white.bold(' Public Key')); + console.log(chalk.cyan(` ${publicKey}\n`)); + console.log(chalk.white.bold(' Private Key')); + console.log(chalk.yellow(` ${privateKey}\n`)); + + if (!walletIsInjected) { + console.log(chalk.green('\nāœ“ New keypair generated!\n')); + console.log( + chalk.red.bold( + '╔══════════════════════════════════════════════════════════════════╗', + ), + ); + console.log( + chalk.red.bold( + 'ā•‘ ā•‘', + ), + ); + console.log( + chalk.red.bold( + 'ā•‘ SAVE YOUR PRIVATE KEY NOW — YOU WILL NOT SEE IT AGAIN ā•‘', + ), + ); + console.log( + chalk.red.bold( + 'ā•‘ ā•‘', + ), + ); + console.log( + chalk.red.bold( + 'ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•', + ), + ); + console.log(''); + console.log( + chalk.gray( + ' Copy your private key and store it somewhere safe (password', + ), + ); + console.log( + chalk.gray( + ' manager, encrypted note, etc). This is the ONLY time it will', + ), + ); + console.log( + chalk.gray(' be displayed. If you lose it, the wallet and any funds in'), + ); + console.log(chalk.gray(' it are gone forever.')); + console.log(''); + } + + return { + signingMethod: 'standard', + publicKey, + privateKey, + walletIsInjected, + walletSource, + }; +} + +async function resolveSwigWallet( + cliArgs: Record, + nonInteractive: boolean, + existingConfig: SolanaSetupConfig | null, + existingSolanaEnv: Record, +): Promise { + const existingWallet = + existingConfig?.wallet?.signingMethod === 'swig' + ? existingConfig.wallet + : undefined; + + let authorityPrivateKey = + cliArgs['swig-authority-private-key'] || + process.env.SWIG_AUTHORITY_PRIVATE_KEY || + existingSolanaEnv.SWIG_AUTHORITY_PRIVATE_KEY; + let walletIsInjected = false; + let walletSource: string | undefined; + + if (authorityPrivateKey) { + walletIsInjected = true; + walletSource = process.env.SWIG_AUTHORITY_PRIVATE_KEY + ? 'Environment' + : existingSolanaEnv.SWIG_AUTHORITY_PRIVATE_KEY + ? 'Existing config' + : 'CLI argument'; + console.log(chalk.green('\nāœ“ Reusing existing Swig authority key\n')); + } else if (!nonInteractive) { + const authorityMode = await select<'generate' | 'paste'>({ + message: 'How should SolClaw authenticate to Swig?', + choices: [ + { + name: 'Generate a new authority keypair', + value: 'generate', + description: 'Recommended when setting up a new Swig wallet', + }, + { + name: 'Paste an existing authority private key', + value: 'paste', + description: 'Reuse an authority you already manage', + }, + ], + default: 'generate', + }); + + if (authorityMode === 'paste') { + authorityPrivateKey = await password({ + message: 'Paste Swig authority private key (base58):', + validate: (value) => { + try { + const decoded = bs58.decode(value); + return decoded.length === 64 + ? true + : 'Invalid key length (expected 64 bytes)'; + } catch { + return 'Invalid base58 format'; + } + }, + }); + } + } + + if (!authorityPrivateKey) { + const kp = Keypair.generate(); + authorityPrivateKey = bs58.encode(kp.secretKey); + console.log(chalk.green('\nāœ“ Generated a new Swig authority keypair\n')); + } + + const authorityPublicKey = derivePublicKey(authorityPrivateKey); + console.log(chalk.white.bold(' Authority Public Key')); + console.log(chalk.cyan(` ${authorityPublicKey}\n`)); + + let walletAddress = + cliArgs['swig-wallet-address'] || existingWallet?.swigWalletAddress; + let swigAccountAddress = + cliArgs['swig-account-address'] || existingWallet?.swigAccountAddress; + + if (!walletAddress && !nonInteractive) { + const hasExistingWallet = await confirm({ + message: 'Do you already have a Swig wallet to reuse?', + default: false, + }); + + if (hasExistingWallet) { + walletAddress = await input({ + message: 'Swig wallet address:', + validate: (value) => + value.trim() ? true : 'Wallet address is required', + }); + swigAccountAddress = await input({ + message: 'Swig account address:', + validate: (value) => + value.trim() ? true : 'Swig account address is required', + }); + } + } + + const feeMode = + (cliArgs['swig-fee-mode'] as SwigFeeMode | undefined) || + existingWallet?.feeMode || + (nonInteractive + ? 'self-funded' + : await select({ + message: 'How should Swig transaction fees be handled?', + choices: [ + { + name: 'Swig Paymaster', + value: 'paymaster', + description: 'Recommended for production gasless transactions', + }, + { + name: 'Custom gas sponsor', + value: 'gas-sponsor', + description: 'Use your own sponsorship server', + }, + { + name: 'Self-funded authority', + value: 'self-funded', + description: 'Authority keypair pays fees directly', + }, + ], + default: existingWallet?.feeMode || 'self-funded', + })); + + if ( + feeMode !== 'paymaster' && + feeMode !== 'gas-sponsor' && + feeMode !== 'self-funded' + ) { + throw new Error( + `Invalid Swig fee mode "${feeMode}". Use paymaster, gas-sponsor, or self-funded.`, + ); + } + + let swigPaymasterPubkey = existingWallet?.swigPaymasterPubkey; + let swigPaymasterNetwork = existingWallet?.swigPaymasterNetwork; + let gasSponsorUrl = existingWallet?.gasSponsorUrl; + let swigPaymasterApiKey = process.env.SWIG_PAYMASTER_API_KEY; + + if (feeMode === 'paymaster') { + swigPaymasterPubkey = + cliArgs['swig-paymaster-pubkey'] || swigPaymasterPubkey; + swigPaymasterNetwork = + resolvePaymasterNetwork(cliArgs['swig-paymaster-network']) || + swigPaymasterNetwork; + swigPaymasterApiKey = + cliArgs['swig-paymaster-api-key'] || + swigPaymasterApiKey || + existingSolanaEnv.SWIG_PAYMASTER_API_KEY; + + if (!swigPaymasterPubkey) { + if (nonInteractive) { + throw new Error( + 'Swig paymaster mode requires --swig-paymaster-pubkey.', + ); } + swigPaymasterPubkey = await input({ + message: 'Swig paymaster public key:', + validate: (value) => + value.trim() ? true : 'Swig paymaster public key is required', + }); + } - if (existingConfig?.wallet?.privateKey && existingConfig?.wallet?.publicKey) { - publicKey = existingConfig.wallet.publicKey; - privateKey = existingConfig.wallet.privateKey; - walletIsInjected = true; // reuse flag to skip "save your key" warning - console.log(chalk.green('\nāœ“ Existing wallet found, reusing\n')); - console.log(chalk.white.bold(' Public Key')); - console.log(chalk.cyan(` ${publicKey}\n`)); + if (!swigPaymasterNetwork) { + if (nonInteractive) { + swigPaymasterNetwork = 'mainnet'; } else { - console.log('Auto-generating wallet keypair.\n'); - const kp = Keypair.generate(); - privateKey = bs58.encode(kp.secretKey); - publicKey = kp.publicKey.toBase58(); + swigPaymasterNetwork = await select<'mainnet' | 'devnet'>({ + message: 'Swig paymaster network:', + choices: [ + { name: 'Mainnet', value: 'mainnet' }, + { name: 'Devnet', value: 'devnet' }, + ], + default: 'mainnet', + }); } } - if (!walletIsInjected) { - console.log(chalk.green('\nāœ“ New keypair generated!\n')); - - console.log(chalk.red.bold('╔══════════════════════════════════════════════════════════════════╗')); - console.log(chalk.red.bold('ā•‘ ā•‘')); - console.log(chalk.red.bold('ā•‘ SAVE YOUR PRIVATE KEY NOW — YOU WILL NOT SEE IT AGAIN ā•‘')); - console.log(chalk.red.bold('ā•‘ ā•‘')); - console.log(chalk.red.bold('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•')); - console.log(''); + if (!swigPaymasterApiKey) { + if (nonInteractive) { + throw new Error( + 'Swig paymaster mode requires --swig-paymaster-api-key or SWIG_PAYMASTER_API_KEY.', + ); + } + swigPaymasterApiKey = await password({ + message: 'Swig paymaster API key:', + validate: (value) => + value.trim() ? true : 'Swig paymaster API key is required', + }); } + } - console.log(chalk.white.bold(' Public Key')); - console.log(chalk.cyan(` ${publicKey}`)); - console.log(''); - console.log(chalk.white.bold(' Private Key')); - console.log(chalk.yellow(` ${privateKey}`)); - console.log(''); + if (feeMode === 'gas-sponsor') { + gasSponsorUrl = cliArgs['gas-sponsor-url'] || gasSponsorUrl; - if (!walletIsInjected) { - console.log(chalk.gray(' Copy your private key and store it somewhere safe (password')); - console.log(chalk.gray(' manager, encrypted note, etc). This is the ONLY time it will')); - console.log(chalk.gray(' be displayed. If you lose it, the wallet and any funds in')); - console.log(chalk.gray(' it are gone forever.')); - console.log(''); + if (!gasSponsorUrl) { + if (nonInteractive) { + throw new Error('Swig gas sponsor mode requires --gas-sponsor-url.'); + } + gasSponsorUrl = await input({ + message: 'Custom gas sponsor URL:', + validate: (value) => + value.trim() ? true : 'Gas sponsor URL is required', + }); } + } - // Step 2: RPC Configuration - console.log(chalk.yellow('Step 2: RPC Configuration')); + const publicKey = walletAddress || authorityPublicKey; + + return { + signingMethod: 'swig', + publicKey, + authorityPublicKey, + authorityPrivateKey, + walletAddress: walletAddress || undefined, + swigAccountAddress: swigAccountAddress || undefined, + feeMode, + swigPaymasterPubkey: + feeMode === 'paymaster' ? swigPaymasterPubkey : undefined, + swigPaymasterNetwork: + feeMode === 'paymaster' ? swigPaymasterNetwork : undefined, + swigPaymasterApiKey: + feeMode === 'paymaster' ? swigPaymasterApiKey : undefined, + gasSponsorUrl: feeMode === 'gas-sponsor' ? gasSponsorUrl : undefined, + walletIsInjected, + walletSource, + }; +} - let rpcUrl: string; - - if (cliArgs.network === 'mainnet' || (!cliArgs.network && nonInteractive)) { - rpcUrl = 'https://api.breeze.baby/agent/rpc-mainnet-beta'; - console.log(chalk.cyan('Using Mainnet')); - } else if (cliArgs.network === 'devnet') { - rpcUrl = 'https://api.devnet.solana.com'; - console.log(chalk.cyan('Using Devnet')); - console.log(chalk.gray('Get free SOL: solana airdrop 1 ' + publicKey + ' --url devnet')); - } else if (cliArgs.network === 'testnet') { - rpcUrl = 'https://api.testnet.solana.com'; - console.log(chalk.cyan('Using Testnet')); - } else if (cliArgs['rpc-url']) { - rpcUrl = cliArgs['rpc-url']; - } else { - // Default to mainnet - rpcUrl = 'https://api.breeze.baby/agent/rpc-mainnet-beta'; - console.log(chalk.cyan('Using Mainnet (default)')); +function resolveRpcUrl( + cliArgs: Record, + nonInteractive: boolean, + walletSetup: WalletSetup, + existingConfig: SolanaSetupConfig | null, +): string { + if (cliArgs['rpc-url']) { + return cliArgs['rpc-url']; + } + + if (cliArgs.network === 'mainnet' || (!cliArgs.network && nonInteractive)) { + console.log(chalk.cyan('Using Mainnet')); + return DEFAULT_MAINNET_RPC; + } + + if (cliArgs.network === 'devnet') { + console.log(chalk.cyan('Using Devnet')); + const fundingAddress = + walletSetup.signingMethod === 'swig' + ? walletSetup.authorityPublicKey + : walletSetup.publicKey; + console.log( + chalk.gray( + `Get free SOL: solana airdrop 1 ${fundingAddress} --url devnet`, + ), + ); + return DEFAULT_DEVNET_RPC; + } + + if (cliArgs.network === 'testnet') { + console.log(chalk.cyan('Using Testnet')); + return DEFAULT_TESTNET_RPC; + } + + if (existingConfig?.preferences?.rpcUrl) { + console.log(chalk.cyan('Using existing RPC configuration')); + return existingConfig.preferences.rpcUrl; + } + + console.log(chalk.cyan('Using Mainnet (default)')); + return DEFAULT_MAINNET_RPC; +} + +async function writeProtocolApiKeys( + protocolKeys: Record, +): Promise { + if (Object.keys(protocolKeys).length === 0) return; + + const mainEnvPath = path.join(process.cwd(), '.env'); + let existing = ''; + try { + existing = await fs.readFile(mainEnvPath, 'utf-8'); + } catch { + // .env doesn't exist yet, that's fine. + } + + const filteredLines = existing + .split('\n') + .filter((line) => + line.trim() + ? !Object.keys(protocolKeys).some((key) => line.startsWith(`${key}=`)) + : true, + ); + + const nextLines = filteredLines.filter( + (_, index, arr) => !(index === arr.length - 1 && arr[index] === ''), + ); + + if (nextLines.length > 0) nextLines.push(''); + nextLines.push('# Protocol API Keys (added by Solana setup)'); + for (const [key, value] of Object.entries(protocolKeys)) { + nextLines.push(`${key}=${value}`); + } + nextLines.push(''); + + await fs.writeFile(mainEnvPath, nextLines.join('\n')); +} + +function buildSolanaEnvLines( + rpcUrl: string, + walletSetup: WalletSetup, +): string[] { + const lines = [ + '# SolClaw Solana Configuration', + '# Generated during setup', + '', + `SOLANA_RPC_URL=${rpcUrl}`, + `SOLANA_SIGNING_METHOD=${walletSetup.signingMethod}`, + ]; + + if (walletSetup.signingMethod === 'standard') { + lines.push(`SOLANA_PRIVATE_KEY=${walletSetup.privateKey}`); + } else { + lines.push(`SWIG_AUTHORITY_PRIVATE_KEY=${walletSetup.authorityPrivateKey}`); + if (walletSetup.feeMode === 'paymaster') { + lines.push( + `SWIG_PAYMASTER_PUBKEY=${walletSetup.swigPaymasterPubkey!}`, + `SWIG_PAYMASTER_NETWORK=${walletSetup.swigPaymasterNetwork!}`, + ); + if (walletSetup.swigPaymasterApiKey) { + lines.push(`SWIG_PAYMASTER_API_KEY=${walletSetup.swigPaymasterApiKey}`); + } } + if (walletSetup.feeMode === 'gas-sponsor' && walletSetup.gasSponsorUrl) { + lines.push(`GAS_SPONSOR_URL=${walletSetup.gasSponsorUrl}`); + } + } - const defaultSlippage = cliArgs.slippage || '50'; + lines.push(''); + return lines; +} + +export async function run(args: string[]): Promise { + console.log(chalk.cyan.bold('\nšŸ¦€ Solana Configuration\n')); + + emitStatus('SOLANA_SETUP', { STATUS: 'starting' }); + + const cliArgs = parseArgs(args); + const nonInteractive = !!cliArgs.network; + const configPath = path.join(process.cwd(), 'config', 'solana-config.json'); + const envPath = path.join(process.cwd(), '.env.solana'); + const existingConfig = await readJsonIfExists(configPath); + const existingSolanaEnv = await readSimpleEnvFile(envPath); + + try { + console.log(chalk.yellow('Step 1: Wallet')); + + const signingMethod = await resolveSigningMethod(cliArgs, nonInteractive); + const walletSetup = + signingMethod === 'swig' + ? await resolveSwigWallet( + cliArgs, + nonInteractive, + existingConfig, + existingSolanaEnv, + ) + : await resolveStandardWallet(existingConfig); + + console.log(chalk.yellow('Step 2: RPC Configuration')); + const rpcUrl = resolveRpcUrl( + cliArgs, + nonInteractive, + walletSetup, + existingConfig, + ); + const defaultSlippage = + cliArgs.slippage || + String(existingConfig?.preferences?.defaultSlippage || 50); - // Step 3: Optional Protocol API Keys console.log(chalk.yellow('\nStep 3: Optional Protocol API Keys')); - console.log(chalk.gray('These are optional. The agent works without them but some protocols offer better rates or features with an API key.\n')); + console.log( + chalk.gray( + 'These are optional. The agent works without them but some protocols offer better rates or features with an API key.\n', + ), + ); const protocolKeys: Record = {}; if (cliArgs['dflow-key']) { protocolKeys.DFLOW_API_KEY = cliArgs['dflow-key']; } else if (!nonInteractive) { - const wantsDflow = await confirm({ message: 'Do you have a DFlow API key?', default: false }); + const wantsDflow = await confirm({ + message: 'Do you have a DFlow API key?', + default: false, + }); if (wantsDflow) { const key = await password({ message: 'DFlow API key:' }); if (key) protocolKeys.DFLOW_API_KEY = key; @@ -168,7 +690,10 @@ export async function run(args: string[]): Promise { if (cliArgs['jupiter-key']) { protocolKeys.JUPITER_API_KEY = cliArgs['jupiter-key']; } else if (!nonInteractive) { - const wantsJupiter = await confirm({ message: 'Do you have a Jupiter API key?', default: false }); + const wantsJupiter = await confirm({ + message: 'Do you have a Jupiter API key?', + default: false, + }); if (wantsJupiter) { const key = await password({ message: 'Jupiter API key:' }); if (key) protocolKeys.JUPITER_API_KEY = key; @@ -178,7 +703,10 @@ export async function run(args: string[]): Promise { if (cliArgs['breeze-key']) { protocolKeys.BREEZE_API_KEY = cliArgs['breeze-key']; } else if (!nonInteractive) { - const wantsBreeze = await confirm({ message: 'Do you have a Breeze API key?', default: false }); + const wantsBreeze = await confirm({ + message: 'Do you have a Breeze API key?', + default: false, + }); if (wantsBreeze) { const key = await password({ message: 'Breeze API key:' }); if (key) protocolKeys.BREEZE_API_KEY = key; @@ -188,83 +716,116 @@ export async function run(args: string[]): Promise { if (cliArgs['helius-key']) { protocolKeys.HELIUS_API_KEY = cliArgs['helius-key']; } else if (!nonInteractive) { - const wantsHelius = await confirm({ message: 'Do you have a Helius API key?', default: false }); + const wantsHelius = await confirm({ + message: 'Do you have a Helius API key?', + default: false, + }); if (wantsHelius) { const key = await password({ message: 'Helius API key:' }); if (key) protocolKeys.HELIUS_API_KEY = key; } } - // Build config - const config: Record = { - wallet: { - signingMethod: 'standard', - publicKey, - privateKey, - }, + const config: SolanaSetupConfig = { + wallet: + walletSetup.signingMethod === 'swig' + ? { + signingMethod: 'swig', + publicKey: walletSetup.publicKey, + authorityPublicKey: walletSetup.authorityPublicKey, + swigWalletAddress: walletSetup.walletAddress, + swigAccountAddress: walletSetup.swigAccountAddress, + feeMode: walletSetup.feeMode, + swigPaymasterPubkey: walletSetup.swigPaymasterPubkey, + swigPaymasterNetwork: walletSetup.swigPaymasterNetwork, + gasSponsorUrl: walletSetup.gasSponsorUrl, + } + : { + signingMethod: 'standard', + publicKey: walletSetup.publicKey, + privateKey: walletSetup.privateKey, + }, preferences: { rpcUrl, - defaultSlippage: parseInt(defaultSlippage), + defaultSlippage: parseInt(defaultSlippage, 10), }, setupComplete: true, setupDate: new Date().toISOString(), }; - // Save config - const configPath = path.join(process.cwd(), 'config', 'solana-config.json'); await fs.mkdir(path.dirname(configPath), { recursive: true }); await fs.writeFile(configPath, JSON.stringify(config, null, 2)); + await fs.writeFile( + envPath, + buildSolanaEnvLines(rpcUrl, walletSetup).join('\n'), + ); + await writeProtocolApiKeys(protocolKeys); - // Create .env file - const envLines = [ - '# SolClaw Solana Configuration', - '# Generated during setup', - '', - `SOLANA_RPC_URL=${rpcUrl}`, - `SOLANA_SIGNING_METHOD=standard`, - `SOLANA_PRIVATE_KEY=${privateKey}`, - '', - ]; - - const envPath = path.join(process.cwd(), '.env.solana'); - await fs.writeFile(envPath, envLines.join('\n')); - - // Append protocol API keys to .env (read by container-runner) - if (Object.keys(protocolKeys).length > 0) { - const mainEnvPath = path.join(process.cwd(), '.env'); - let existing = ''; - try { - existing = await fs.readFile(mainEnvPath, 'utf-8'); - } catch { - // .env doesn't exist yet, that's fine - } - - const newLines: string[] = []; - if (existing && !existing.endsWith('\n')) newLines.push(''); - newLines.push('# Protocol API Keys (added by Solana setup)'); - for (const [key, value] of Object.entries(protocolKeys)) { - if (existing.includes(`${key}=`)) { - existing = existing - .split('\n') - .filter((line) => !line.startsWith(`${key}=`)) - .join('\n'); - } - newLines.push(`${key}=${value}`); - } - newLines.push(''); - - await fs.writeFile(mainEnvPath, existing + newLines.join('\n')); - } - - // Summary with QR code console.log(chalk.green.bold('\nāœ… Solana Configuration Complete!\n')); console.log(chalk.white.bold(' Wallet Summary')); - console.log(chalk.white(` Address: ${chalk.cyan(publicKey)}`)); - console.log(chalk.white(` Signing Method: ${chalk.cyan('Standard (local keypair)')}`)); + console.log( + chalk.white(` Address: ${chalk.cyan(walletSetup.publicKey)}`), + ); + console.log( + chalk.white( + ` Signing Method: ${chalk.cyan( + walletSetup.signingMethod === 'swig' + ? 'Swig smart wallet' + : 'Standard (local keypair)', + )}`, + ), + ); console.log(chalk.white(` Network: ${chalk.cyan(rpcUrl)}`)); - if (walletIsInjected) { - console.log(chalk.white(` Source: ${chalk.cyan(injectedKey ? 'Dashboard-assigned' : 'Existing config')}`)); + if (walletSetup.walletIsInjected && walletSetup.walletSource) { + console.log( + chalk.white( + ` Source: ${chalk.cyan(walletSetup.walletSource)}`, + ), + ); + } + if (walletSetup.signingMethod === 'swig') { + console.log( + chalk.white( + ` Authority: ${chalk.cyan(walletSetup.authorityPublicKey)}`, + ), + ); + console.log( + chalk.white( + ` Swig Wallet: ${chalk.cyan( + walletSetup.walletAddress || 'Not created yet', + )}`, + ), + ); + console.log( + chalk.white( + ` Swig Account: ${chalk.cyan( + walletSetup.swigAccountAddress || 'Not created yet', + )}`, + ), + ); + console.log( + chalk.white(` Fee Mode: ${chalk.cyan(walletSetup.feeMode)}`), + ); + if (walletSetup.feeMode === 'paymaster') { + console.log( + chalk.white( + ` Paymaster Key: ${chalk.cyan(walletSetup.swigPaymasterPubkey!)}`, + ), + ); + console.log( + chalk.white( + ` Paymaster Net: ${chalk.cyan(walletSetup.swigPaymasterNetwork!)}`, + ), + ); + } + if (walletSetup.feeMode === 'gas-sponsor' && walletSetup.gasSponsorUrl) { + console.log( + chalk.white( + ` Gas Sponsor: ${chalk.cyan(walletSetup.gasSponsorUrl)}`, + ), + ); + } } console.log(''); @@ -274,23 +835,65 @@ export async function run(args: string[]): Promise { console.log(''); console.log(chalk.white.bold(' Capabilities')); - console.log(chalk.cyan(' • Check wallet balances')); - console.log(chalk.cyan(' • Get token prices via Jupiter')); - console.log(chalk.cyan(' • Swap tokens via Jupiter Ultra')); - console.log(chalk.cyan(' • Transfer SOL and SPL tokens')); - console.log(chalk.cyan(' • Access DeFi protocols via skills')); + if (walletSetup.signingMethod === 'swig') { + console.log(chalk.cyan(' • Create and fetch Swig wallets via MCP')); + console.log(chalk.cyan(' • Add, update, and remove Swig authorities')); + console.log(chalk.cyan(' • Transfer SOL from the Swig wallet')); + console.log(chalk.cyan(' • Execute custom transactions through Swig')); + console.log( + chalk.cyan( + ' • Reuse the configured RPC and fee strategy automatically', + ), + ); + } else { + console.log(chalk.cyan(' • Check wallet balances')); + console.log(chalk.cyan(' • Get token prices via Jupiter')); + console.log(chalk.cyan(' • Swap tokens via Jupiter Ultra')); + console.log(chalk.cyan(' • Transfer SOL and SPL tokens')); + console.log(chalk.cyan(' • Access DeFi protocols via skills')); + } console.log(''); - console.log(chalk.white.bold(' Fund Your Wallet (scan QR or send SOL to address):')); - await displayWalletQR(publicKey); - - console.log(chalk.yellow(' Send SOL to this address to start trading.\n')); + if (walletSetup.signingMethod === 'swig') { + if (walletSetup.feeMode === 'self-funded') { + console.log( + chalk.white.bold( + ' Fund Your Swig Authority (scan QR or send SOL to address):', + ), + ); + await displayWalletQR(walletSetup.authorityPublicKey); + console.log( + chalk.yellow( + ' Send a small amount of SOL to the authority before creating or using the Swig wallet.\n', + ), + ); + } else if (walletSetup.walletAddress) { + console.log(chalk.white.bold(' Current Swig Wallet Address:')); + await displayWalletQR(walletSetup.walletAddress); + } else { + console.log( + chalk.yellow( + ' No Swig wallet address is stored yet. Ask SolClaw to create one with the Swig MCP tools.\n', + ), + ); + } + } else { + console.log( + chalk.white.bold( + ' Fund Your Wallet (scan QR or send SOL to address):', + ), + ); + await displayWalletQR(walletSetup.publicKey); + console.log( + chalk.yellow(' Send SOL to this address to start trading.\n'), + ); + } emitStatus('SOLANA_SETUP', { STATUS: 'complete', - PUBLIC_KEY: publicKey, + PUBLIC_KEY: walletSetup.publicKey, RPC_URL: rpcUrl, - SIGNING_METHOD: 'standard', + SIGNING_METHOD: walletSetup.signingMethod, }); } catch (error) { const message = error instanceof Error ? error.message : String(error); diff --git a/src/container-runner.ts b/src/container-runner.ts index 166216bd742..68be81fdb25 100644 --- a/src/container-runner.ts +++ b/src/container-runner.ts @@ -231,15 +231,23 @@ function buildVolumeMounts( * Secrets are never written to disk or mounted as files. */ function readSecrets(): Record { - const secrets = readEnvFile([ - 'CLAUDE_CODE_OAUTH_TOKEN', - 'ANTHROPIC_API_KEY', - 'DFLOW_API_KEY', - 'JUPITER_API_KEY', - 'BREEZE_API_KEY', - 'HELIUS_API_KEY', - 'SOLCLAW_WALLET_PRIVATE_KEY', - ]); + const secrets = readEnvFile( + [ + 'CLAUDE_CODE_OAUTH_TOKEN', + 'ANTHROPIC_API_KEY', + 'DFLOW_API_KEY', + 'JUPITER_API_KEY', + 'BREEZE_API_KEY', + 'HELIUS_API_KEY', + 'SOLCLAW_WALLET_PRIVATE_KEY', + 'SWIG_AUTHORITY_PRIVATE_KEY', + 'SWIG_PAYMASTER_API_KEY', + 'SWIG_PAYMASTER_PUBKEY', + 'SWIG_PAYMASTER_NETWORK', + 'GAS_SPONSOR_URL', + ], + ['.env', '.env.solana'], + ); // Also check process.env for dashboard-injected wallet key // (cloud deployments set this directly, not via .env) diff --git a/src/env.test.ts b/src/env.test.ts new file mode 100644 index 00000000000..c451c2ee927 --- /dev/null +++ b/src/env.test.ts @@ -0,0 +1,44 @@ +import fs from 'fs/promises'; +import os from 'os'; +import path from 'path'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; + +import { readEnvFile } from './env.js'; + +describe('readEnvFile', () => { + let previousCwd: string; + let tempDir: string; + + beforeEach(async () => { + previousCwd = process.cwd(); + tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'solclaw-env-test-')); + process.chdir(tempDir); + }); + + afterEach(async () => { + process.chdir(previousCwd); + await fs.rm(tempDir, { recursive: true, force: true }); + }); + + it('merges keys from multiple env files with later files winning', async () => { + await fs.writeFile( + path.join(tempDir, '.env'), + 'AXIOM_TOKEN=base-token\nOVERRIDE=from-env\n', + ); + await fs.writeFile( + path.join(tempDir, '.env.solana'), + 'SWIG_AUTHORITY_PRIVATE_KEY=secret\nOVERRIDE=from-solana\n', + ); + + const result = readEnvFile( + ['AXIOM_TOKEN', 'SWIG_AUTHORITY_PRIVATE_KEY', 'OVERRIDE'], + ['.env', '.env.solana'], + ); + + expect(result).toEqual({ + AXIOM_TOKEN: 'base-token', + SWIG_AUTHORITY_PRIVATE_KEY: 'secret', + OVERRIDE: 'from-solana', + }); + }); +}); diff --git a/src/env.ts b/src/env.ts index 988b59ee3fc..776cde0f27e 100644 --- a/src/env.ts +++ b/src/env.ts @@ -3,39 +3,45 @@ import path from 'path'; import { logger } from './logger.js'; /** - * Parse the .env file and return values for the requested keys. + * Parse one or more env files and return values for the requested keys. * Does NOT load anything into process.env — callers decide what to * do with the values. This keeps secrets out of the process environment * so they don't leak to child processes. */ -export function readEnvFile(keys: string[]): Record { - const envFile = path.join(process.cwd(), '.env'); - let content: string; - try { - content = fs.readFileSync(envFile, 'utf-8'); - } catch (err) { - logger.debug({ err }, '.env file not found, using defaults'); - return {}; - } - +export function readEnvFile( + keys: string[], + files: string | string[] = '.env', +): Record { const result: Record = {}; const wanted = new Set(keys); + const envFiles = Array.isArray(files) ? files : [files]; + + for (const file of envFiles) { + const envFile = path.join(process.cwd(), file); + let content: string; + try { + content = fs.readFileSync(envFile, 'utf-8'); + } catch (err) { + logger.debug({ err, envFile }, 'Env file not found, using defaults'); + continue; + } - for (const line of content.split('\n')) { - const trimmed = line.trim(); - if (!trimmed || trimmed.startsWith('#')) continue; - const eqIdx = trimmed.indexOf('='); - if (eqIdx === -1) continue; - const key = trimmed.slice(0, eqIdx).trim(); - if (!wanted.has(key)) continue; - let value = trimmed.slice(eqIdx + 1).trim(); - if ( - (value.startsWith('"') && value.endsWith('"')) || - (value.startsWith("'") && value.endsWith("'")) - ) { - value = value.slice(1, -1); + for (const line of content.split('\n')) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) continue; + const eqIdx = trimmed.indexOf('='); + if (eqIdx === -1) continue; + const key = trimmed.slice(0, eqIdx).trim(); + if (!wanted.has(key)) continue; + let value = trimmed.slice(eqIdx + 1).trim(); + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + if (value) result[key] = value; } - if (value) result[key] = value; } return result; diff --git a/src/solana/config.test.ts b/src/solana/config.test.ts new file mode 100644 index 00000000000..a7ff1071f42 --- /dev/null +++ b/src/solana/config.test.ts @@ -0,0 +1,77 @@ +import fs from 'fs/promises'; +import os from 'os'; +import path from 'path'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; + +import { isSolanaConfigured } from './config.js'; + +describe('isSolanaConfigured', () => { + let previousCwd: string; + let tempDir: string; + + beforeEach(async () => { + previousCwd = process.cwd(); + tempDir = await fs.mkdtemp( + path.join(os.tmpdir(), 'solclaw-solana-config-'), + ); + process.chdir(tempDir); + await fs.mkdir(path.join(tempDir, 'config'), { recursive: true }); + }); + + afterEach(async () => { + process.chdir(previousCwd); + await fs.rm(tempDir, { recursive: true, force: true }); + }); + + it('accepts a self-funded Swig configuration', async () => { + await fs.writeFile( + path.join(tempDir, 'config', 'solana-config.json'), + JSON.stringify( + { + wallet: { + signingMethod: 'swig', + publicKey: 'wallet-address', + authorityPublicKey: 'authority-address', + feeMode: 'self-funded', + }, + preferences: { + rpcUrl: 'https://api.devnet.solana.com', + defaultSlippage: 50, + }, + setupComplete: true, + setupDate: '2026-04-07T00:00:00.000Z', + }, + null, + 2, + ), + ); + + await expect(isSolanaConfigured()).resolves.toBe(true); + }); + + it('requires the paymaster pubkey for paymaster-backed Swig config', async () => { + await fs.writeFile( + path.join(tempDir, 'config', 'solana-config.json'), + JSON.stringify( + { + wallet: { + signingMethod: 'swig', + publicKey: 'wallet-address', + authorityPublicKey: 'authority-address', + feeMode: 'paymaster', + }, + preferences: { + rpcUrl: 'https://api.devnet.solana.com', + defaultSlippage: 50, + }, + setupComplete: true, + setupDate: '2026-04-07T00:00:00.000Z', + }, + null, + 2, + ), + ); + + await expect(isSolanaConfigured()).resolves.toBe(false); + }); +}); diff --git a/src/solana/config.ts b/src/solana/config.ts index 687de064dfa..f5bda402b45 100644 --- a/src/solana/config.ts +++ b/src/solana/config.ts @@ -8,11 +8,18 @@ import path from 'path'; export interface SolanaConfig { wallet: { - signingMethod: 'standard' | 'crossmint'; + signingMethod: 'standard' | 'crossmint' | 'swig'; privateKey?: string; publicKey: string; crossmintApiKey?: string; crossmintEnvironment?: string; + authorityPublicKey?: string; + swigWalletAddress?: string; + swigAccountAddress?: string; + feeMode?: 'paymaster' | 'gas-sponsor' | 'self-funded'; + swigPaymasterPubkey?: string; + swigPaymasterNetwork?: 'mainnet' | 'devnet'; + gasSponsorUrl?: string; }; preferences: { rpcUrl: string; @@ -51,9 +58,10 @@ export async function loadSolanaConfig( throw new Error('Setup incomplete. Run: npm run setup'); } - // Dashboard-injected key overrides config file wallet + // Dashboard-injected key overrides config file wallet for direct keypair mode. + // Swig uses a separate authority key and should not be coerced into standard mode. const injectedKey = process.env.SOLCLAW_WALLET_PRIVATE_KEY; - if (injectedKey) { + if (injectedKey && config.wallet.signingMethod !== 'swig') { const { Keypair } = await import('@solana/web3.js'); const bs58 = (await import('bs58')).default; const secretKey = bs58.decode(injectedKey); @@ -81,6 +89,22 @@ export async function isSolanaConfigured( return config.setupComplete && !!config.wallet.crossmintApiKey; } + if (config.wallet.signingMethod === 'swig') { + const authorityPublicKey = + config.wallet.authorityPublicKey || config.wallet.publicKey; + if (!authorityPublicKey) return false; + + if (config.wallet.feeMode === 'paymaster') { + return config.setupComplete && !!config.wallet.swigPaymasterPubkey; + } + + if (config.wallet.feeMode === 'gas-sponsor') { + return config.setupComplete && !!config.wallet.gasSponsorUrl; + } + + return config.setupComplete; + } + // Standard signing return config.setupComplete && !!config.wallet.privateKey; } catch { diff --git a/tools/lib/wallet.ts b/tools/lib/wallet.ts index 4649cc4f6bb..01ded796e45 100644 --- a/tools/lib/wallet.ts +++ b/tools/lib/wallet.ts @@ -85,7 +85,9 @@ export interface WalletConfig { rpcUrl: string; } -export function loadWallet(configPath = 'config/solana-config.json'): WalletConfig { +export function loadWallet( + configPath = 'config/solana-config.json', +): WalletConfig { // Dashboard-injected key takes priority over config file const injectedKey = process.env.SOLCLAW_WALLET_PRIVATE_KEY; if (injectedKey) { @@ -107,21 +109,42 @@ export function loadWallet(configPath = 'config/solana-config.json'): WalletConf const raw = JSON.parse(fs.readFileSync(configPath, 'utf-8')); - const method = raw.wallet?.signingMethod ?? (raw.wallet?.provider === 'solana-agent-kit' ? 'standard' : raw.wallet?.signingMethod); + const method = + raw.wallet?.signingMethod ?? + (raw.wallet?.provider === 'solana-agent-kit' + ? 'standard' + : raw.wallet?.signingMethod); if (method === 'crossmint') { - console.error('Error: This tool requires a local keypair. Your config uses Crossmint signing.'); - console.error('Use the Crossmint MCP tools instead (crossmint_get_balance, crossmint_transfer, etc.).'); + console.error( + 'Error: This tool requires a local keypair. Your config uses Crossmint signing.', + ); + console.error( + 'Use the Crossmint MCP tools instead (crossmint_get_balance, crossmint_transfer, etc.).', + ); + process.exit(1); + } + + if (method === 'swig') { + console.error( + 'Error: This tool requires direct keypair signing. Your config uses a Swig smart wallet.', + ); + console.error( + 'Use the Swig MCP tools instead (configure_rpc, fetch_swig_wallet, transact_sol_transfer, etc.).', + ); process.exit(1); } if (!raw.wallet?.privateKey) { - console.error('Error: No private key found in config. Run: npm run setup:solana'); + console.error( + 'Error: No private key found in config. Run: npm run setup:solana', + ); process.exit(1); } const secretKey = bs58.decode(raw.wallet.privateKey); const keypair = Keypair.fromSecretKey(secretKey); - const rpcUrl = raw.preferences?.rpcUrl ?? 'https://api.breeze.baby/agent/rpc-mainnet-beta'; + const rpcUrl = + raw.preferences?.rpcUrl ?? 'https://api.breeze.baby/agent/rpc-mainnet-beta'; const connection = new Connection(rpcUrl, 'confirmed'); return { keypair, connection, publicKey: keypair.publicKey, rpcUrl }; @@ -135,7 +158,9 @@ export function resolveMint(tokenOrMint: string): string { /** Jupiter API base — uses free lite-api unless JUPITER_API_KEY is set */ export function jupiterBase(): string { - return process.env.JUPITER_API_KEY ? 'https://api.jup.ag' : 'https://lite-api.jup.ag'; + return process.env.JUPITER_API_KEY + ? 'https://api.jup.ag' + : 'https://lite-api.jup.ag'; } const IPC_TRANSACTIONS_DIR = '/data/ipc/transactions'; @@ -151,8 +176,12 @@ async function syncTransactionToApi( const apiUrl = process.env.TRANSACTION_SYNC_API_URL || 'https://api.breeze.baby/agent/stats-sync-up'; - const entry: Record = { signature, protocol, wallet_address: walletAddress }; - if (mint) entry.mint = mint; + const entry: Record = { + signature, + protocol, + wallet_address: walletAddress, + }; + if (mint) entry.mint = mint; if (amount) entry.amount = parseFloat(amount); try { await fetch(apiUrl, { @@ -190,7 +219,9 @@ function logTransactionIpc( fs.writeFileSync(tempPath, JSON.stringify(data, null, 2)); fs.renameSync(tempPath, filepath); } catch (err) { - console.error(`[logTransactionIpc] Failed to log transaction: ${err instanceof Error ? err.message : String(err)}`); + console.error( + `[logTransactionIpc] Failed to log transaction: ${err instanceof Error ? err.message : String(err)}`, + ); } // Fire-and-forget direct API sync @@ -222,9 +253,8 @@ export async function signSendAndLog( opts: SendAndLogOpts, ): Promise { const { VersionedTransaction, Transaction } = await import('@solana/web3.js'); - const bytes = typeof txData === 'string' - ? Buffer.from(txData, 'base64') - : txData; + const bytes = + typeof txData === 'string' ? Buffer.from(txData, 'base64') : txData; let sig: string; try { @@ -238,7 +268,13 @@ export async function signSendAndLog( } await connection.confirmTransaction(sig, opts.commitment || 'confirmed'); - logTransactionIpc(sig, opts.protocol, keypair.publicKey.toBase58(), opts.mint, opts.amount); + logTransactionIpc( + sig, + opts.protocol, + keypair.publicKey.toBase58(), + opts.mint, + opts.amount, + ); return sig; }