agentwallet-mcp 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -12
- package/build/index.js +217 -69
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AgentWallet MCP Server
|
|
2
2
|
|
|
3
|
-
Permissionless
|
|
3
|
+
Permissionless wallet infrastructure for AI agents. Create wallets, sign transactions, and broadcast on-chain — on any EVM chain and Solana. Up to 50% less expensive than Coinbase.
|
|
4
4
|
|
|
5
5
|
**No KYC. No KYT. No transaction monitoring. No one can block your wallet.**
|
|
6
6
|
|
|
@@ -11,15 +11,16 @@ Permissionless EVM wallet infrastructure for AI agents. Create wallets, sign tra
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
13
|
- **28 MCP tools** — create wallets, send transactions, approve tokens, wrap ETH, pay and accept x402 payments, and more
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
14
|
+
- **EVM + Solana** — Ethereum, Base, Polygon, BSC, Arbitrum, Optimism, Avalanche, Zora, PulseChain, Solana, and any other EVM-compatible chain
|
|
15
|
+
- **SOL + SPL tokens** — native SOL transfers and SPL token transfers (USDC, USDT, etc.) with automatic account creation
|
|
16
|
+
- **x402 payments** — pay for x402-enabled APIs automatically, or accept x402 payments on your own endpoints (EVM and Solana)
|
|
16
17
|
- **Secure** — Private keys encrypted at rest, decrypted only during signing, zeroed from memory immediately after
|
|
17
18
|
- **Permissionless** — No identity verification, no compliance gatekeeping. Create a wallet and transact immediately.
|
|
18
19
|
|
|
19
20
|
## Pricing
|
|
20
21
|
|
|
21
|
-
- **$0.
|
|
22
|
-
- **6,000 free
|
|
22
|
+
- **$0.00345 per operation** (31% less expensive than Coinbase CDP)
|
|
23
|
+
- **6,000 free operations/month**
|
|
23
24
|
- **$0.0005 per x402 verification** (50% less expensive than Coinbase)
|
|
24
25
|
- **1,000 free x402 verifications/month**
|
|
25
26
|
- No monthly fee, no tiers — just pay as you go
|
|
@@ -81,14 +82,14 @@ Add to your settings:
|
|
|
81
82
|
|
|
82
83
|
| Tool | Description |
|
|
83
84
|
|------|-------------|
|
|
84
|
-
| `create_wallet` | Create a new
|
|
85
|
+
| `create_wallet` | Create a new EVM or Solana wallet |
|
|
85
86
|
| `list_wallets` | List all your wallets |
|
|
86
87
|
| `get_wallet` | Get wallet details by ID |
|
|
87
88
|
| `get_balance` | Check native token balance on any chain |
|
|
88
|
-
| `get_token_balance` | Check ERC-20 token balance |
|
|
89
|
+
| `get_token_balance` | Check ERC-20 or SPL token balance |
|
|
89
90
|
| `get_token_info` | Get ERC-20 token name, symbol, and decimals |
|
|
90
|
-
| `transfer` | Send native tokens (ETH, POL, BNB, etc.) |
|
|
91
|
-
| `transfer_token` | Send ERC-20 tokens (USDC, USDT, etc.) |
|
|
91
|
+
| `transfer` | Send native tokens (ETH, SOL, POL, BNB, etc.) |
|
|
92
|
+
| `transfer_token` | Send ERC-20 or SPL tokens (USDC, USDT, etc.) |
|
|
92
93
|
| `send_transaction` | Sign and broadcast a raw transaction |
|
|
93
94
|
| `sign_transaction` | Sign a transaction without broadcasting |
|
|
94
95
|
| `call_contract` | Read-only contract call (eth_call) |
|
|
@@ -123,6 +124,8 @@ Add to your settings:
|
|
|
123
124
|
| Avalanche | 43114 | AVAX | USDC |
|
|
124
125
|
| Zora | 7777777 | ETH | USDC |
|
|
125
126
|
| PulseChain | 369 | PLS | USDC |
|
|
127
|
+
| Solana | 900 | SOL | USDC |
|
|
128
|
+
| Solana Devnet | 901 | SOL | USDC |
|
|
126
129
|
|
|
127
130
|
## Use Case: GuessMarket
|
|
128
131
|
|
|
@@ -156,7 +159,7 @@ pay_x402(
|
|
|
156
159
|
)
|
|
157
160
|
```
|
|
158
161
|
|
|
159
|
-
Supports ERC-20 tokens
|
|
162
|
+
Supports ERC-20 tokens, SPL tokens, and native tokens on EVM and Solana. Compatible with x402 V1 and V2 (CAIP-2 chain identifiers).
|
|
160
163
|
|
|
161
164
|
## x402 Acceptance
|
|
162
165
|
|
|
@@ -180,13 +183,14 @@ When an agent hits the paywall URL:
|
|
|
180
183
|
3. Retries with proof of payment
|
|
181
184
|
4. Receives the protected content
|
|
182
185
|
|
|
183
|
-
On-chain verification ensures every payment is real. Replay protection prevents double-spending. Revenue tracking shows you who paid, how much, and when.
|
|
186
|
+
On-chain verification ensures every payment is real. Replay protection prevents double-spending. Revenue tracking shows you who paid, how much, and when. 1,000 free verifications/month, then $0.0005 each — 50% less expensive than Coinbase.
|
|
184
187
|
|
|
185
188
|
## Security
|
|
186
189
|
|
|
187
190
|
- Private keys are generated server-side and encrypted at rest
|
|
188
191
|
- Keys are decrypted only during transaction signing and zeroed from memory immediately after
|
|
189
|
-
- EIP-1559 transactions
|
|
192
|
+
- EIP-1559 transactions on EVM with gas safety caps
|
|
193
|
+
- Ed25519 signing on Solana with finalized commitment level
|
|
190
194
|
- Bug bounty program: $50–$500 for responsible disclosure ([details](https://hifriendbot.com/wallet))
|
|
191
195
|
|
|
192
196
|
## Links
|
package/build/index.js
CHANGED
|
@@ -41,6 +41,23 @@ async function api(path, method = 'GET', body) {
|
|
|
41
41
|
function jsonResponse(data) {
|
|
42
42
|
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
43
43
|
}
|
|
44
|
+
// ─── Solana Helpers ───────────────────────────────────────────────
|
|
45
|
+
const SOLANA_CHAIN_IDS = new Set([900, 901, 902]);
|
|
46
|
+
function isSolanaChain(chainId) {
|
|
47
|
+
return SOLANA_CHAIN_IDS.has(chainId);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Validate an address — accepts both EVM (0x...) and Solana (Base58).
|
|
51
|
+
*/
|
|
52
|
+
function isValidAddress(address) {
|
|
53
|
+
// EVM: 0x + 40 hex chars
|
|
54
|
+
if (/^0x[a-fA-F0-9]{40}$/.test(address))
|
|
55
|
+
return true;
|
|
56
|
+
// Solana: 32-44 chars, Base58 alphabet (no 0, O, I, l)
|
|
57
|
+
if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address))
|
|
58
|
+
return true;
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
44
61
|
// ─── EVM Helpers ──────────────────────────────────────────────────
|
|
45
62
|
/**
|
|
46
63
|
* Convert a human-readable amount (e.g. "0.1") to raw units string given decimals.
|
|
@@ -92,17 +109,33 @@ const X402_NETWORKS = {
|
|
|
92
109
|
'avalanche': 43114,
|
|
93
110
|
'zora': 7777777,
|
|
94
111
|
'pulsechain': 369,
|
|
112
|
+
'solana': 900,
|
|
113
|
+
'solana-devnet': 901,
|
|
114
|
+
};
|
|
115
|
+
// Solana genesis hash prefixes for CAIP-2
|
|
116
|
+
const SOLANA_GENESIS = {
|
|
117
|
+
900: '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', // mainnet-beta
|
|
118
|
+
901: 'EtWTRABZaYq6iMfeYKouRu166VU2xqa1', // devnet
|
|
95
119
|
};
|
|
96
120
|
/**
|
|
97
121
|
* Resolve an x402 network identifier to a chain ID.
|
|
98
122
|
* Supports plain names ("base"), CAIP-2 format ("eip155:8453"), and raw chain IDs ("8453").
|
|
99
123
|
*/
|
|
100
124
|
function resolveChainId(network) {
|
|
101
|
-
|
|
102
|
-
|
|
125
|
+
const lower = network.toLowerCase();
|
|
126
|
+
if (X402_NETWORKS[lower])
|
|
127
|
+
return X402_NETWORKS[lower];
|
|
103
128
|
const caipMatch = network.match(/^eip155:(\d+)$/);
|
|
104
129
|
if (caipMatch)
|
|
105
130
|
return parseInt(caipMatch[1], 10);
|
|
131
|
+
// Solana CAIP-2: solana:<genesis_prefix>
|
|
132
|
+
const solanaMatch = network.match(/^solana:(.+)$/);
|
|
133
|
+
if (solanaMatch) {
|
|
134
|
+
for (const [chainId, genesis] of Object.entries(SOLANA_GENESIS)) {
|
|
135
|
+
if (solanaMatch[1] === genesis)
|
|
136
|
+
return parseInt(chainId, 10);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
106
139
|
const num = parseInt(network, 10);
|
|
107
140
|
if (!isNaN(num) && num > 0)
|
|
108
141
|
return num;
|
|
@@ -111,13 +144,13 @@ function resolveChainId(network) {
|
|
|
111
144
|
// ─── Server ──────────────────────────────────────────────────────
|
|
112
145
|
const server = new McpServer({
|
|
113
146
|
name: 'agentwallet',
|
|
114
|
-
version: '1.
|
|
147
|
+
version: '1.7.0',
|
|
115
148
|
});
|
|
116
149
|
// ─── Tool: create_wallet ─────────────────────────────────────────
|
|
117
|
-
server.tool('create_wallet', 'Create a new EVM wallet. Returns the wallet ID and address. ' +
|
|
150
|
+
server.tool('create_wallet', 'Create a new EVM or Solana wallet. Returns the wallet ID and address. ' +
|
|
118
151
|
'Private key is encrypted server-side and never exposed.', {
|
|
119
152
|
label: z.string().default('').describe('Friendly name for the wallet'),
|
|
120
|
-
chain_id: z.number().int().default(8453).describe('Default chain ID (1=Ethereum, 8453=Base, 42161=Arbitrum, 10=Optimism, 137=Polygon, 43114=Avalanche, 56=BSC, 7777777=Zora, 369=PulseChain)'),
|
|
153
|
+
chain_id: z.number().int().default(8453).describe('Default chain ID (1=Ethereum, 8453=Base, 42161=Arbitrum, 10=Optimism, 137=Polygon, 43114=Avalanche, 56=BSC, 7777777=Zora, 369=PulseChain, 900=Solana, 901=Solana Devnet)'),
|
|
121
154
|
}, async ({ label, chain_id }) => {
|
|
122
155
|
const data = await api('/wallets', 'POST', { label, chain_id });
|
|
123
156
|
return jsonResponse(data);
|
|
@@ -138,7 +171,7 @@ server.tool('get_wallet', 'Get details for a specific wallet by ID. ' +
|
|
|
138
171
|
});
|
|
139
172
|
// ─── Tool: get_balance ───────────────────────────────────────────
|
|
140
173
|
server.tool('get_balance', 'Get the native token balance for a wallet on a specific chain. ' +
|
|
141
|
-
'Returns balance in both wei and human-readable format.', {
|
|
174
|
+
'Returns balance in both wei (or lamports for Solana) and human-readable format.', {
|
|
142
175
|
wallet_id: z.number().int().describe('Wallet ID'),
|
|
143
176
|
chain_id: z.number().int().optional().describe('Chain ID to check (defaults to wallet\'s default chain)'),
|
|
144
177
|
}, async ({ wallet_id, chain_id }) => {
|
|
@@ -147,72 +180,114 @@ server.tool('get_balance', 'Get the native token balance for a wallet on a speci
|
|
|
147
180
|
return jsonResponse(data);
|
|
148
181
|
});
|
|
149
182
|
// ─── Tool: sign_transaction ──────────────────────────────────────
|
|
150
|
-
server.tool('sign_transaction', 'Sign
|
|
151
|
-
'
|
|
183
|
+
server.tool('sign_transaction', 'Sign a transaction with a wallet\'s private key. ' +
|
|
184
|
+
'For EVM: returns signed raw transaction hex. For Solana: returns base64 signed transaction. ' +
|
|
152
185
|
'Does NOT broadcast — use send_transaction for sign + broadcast.', {
|
|
153
186
|
wallet_id: z.number().int().describe('Wallet ID'),
|
|
154
|
-
to: z.string().
|
|
187
|
+
to: z.string().describe('Destination address (0x-prefixed for EVM, Base58 for Solana)'),
|
|
155
188
|
chain_id: z.number().int().optional().describe('Chain ID (defaults to wallet\'s default)'),
|
|
156
|
-
value: z.string().default('0').describe('Value in wei (decimal string)'),
|
|
157
|
-
data: z.string().default('').describe('Hex-encoded calldata (0x-prefixed) for contract calls'),
|
|
158
|
-
gas_limit: z.string().optional().describe('Gas limit (auto-estimated if omitted)'),
|
|
159
|
-
max_fee: z.string().optional().describe('Max fee per gas in wei (auto if omitted)'),
|
|
160
|
-
priority_fee: z.string().optional().describe('Max priority fee per gas in wei (auto if omitted)'),
|
|
161
|
-
|
|
162
|
-
|
|
189
|
+
value: z.string().default('0').describe('Value in wei/lamports (decimal string)'),
|
|
190
|
+
data: z.string().default('').describe('Hex-encoded calldata (0x-prefixed) for EVM contract calls'),
|
|
191
|
+
gas_limit: z.string().optional().describe('Gas limit — EVM only (auto-estimated if omitted)'),
|
|
192
|
+
max_fee: z.string().optional().describe('Max fee per gas in wei — EVM only (auto if omitted)'),
|
|
193
|
+
priority_fee: z.string().optional().describe('Max priority fee per gas in wei — EVM only (auto if omitted)'),
|
|
194
|
+
token_mint: z.string().optional().describe('SPL token mint address — Solana only (for SPL token transfers)'),
|
|
195
|
+
token_decimals: z.number().int().optional().describe('SPL token decimals — Solana only (6 for USDC)'),
|
|
196
|
+
}, async ({ wallet_id, to, chain_id, value, data, gas_limit, max_fee, priority_fee, token_mint, token_decimals }) => {
|
|
197
|
+
// Validate address format
|
|
198
|
+
if (!isValidAddress(to)) {
|
|
199
|
+
throw new Error(`Invalid address "${to}". Use 0x-prefixed hex for EVM or Base58 for Solana.`);
|
|
200
|
+
}
|
|
201
|
+
const body = { to, value };
|
|
163
202
|
if (chain_id)
|
|
164
203
|
body.chain_id = chain_id;
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
204
|
+
if (isSolanaChain(chain_id ?? 0)) {
|
|
205
|
+
// Solana-specific params
|
|
206
|
+
if (token_mint)
|
|
207
|
+
body.token_mint = token_mint;
|
|
208
|
+
if (token_decimals !== undefined)
|
|
209
|
+
body.token_decimals = token_decimals;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
// EVM-specific params
|
|
213
|
+
body.data = data;
|
|
214
|
+
if (gas_limit)
|
|
215
|
+
body.gas_limit = gas_limit;
|
|
216
|
+
if (max_fee)
|
|
217
|
+
body.max_fee = max_fee;
|
|
218
|
+
if (priority_fee)
|
|
219
|
+
body.priority_fee = priority_fee;
|
|
220
|
+
}
|
|
171
221
|
const result = await api(`/wallets/${wallet_id}/sign`, 'POST', body);
|
|
172
222
|
return jsonResponse(result);
|
|
173
223
|
});
|
|
174
224
|
// ─── Tool: send_transaction ──────────────────────────────────────
|
|
175
|
-
server.tool('send_transaction', 'Sign and broadcast
|
|
176
|
-
'Returns the transaction hash on success. ' +
|
|
225
|
+
server.tool('send_transaction', 'Sign and broadcast a transaction. ' +
|
|
226
|
+
'Returns the transaction hash (EVM) or signature (Solana) on success. ' +
|
|
177
227
|
'The transaction is signed server-side and broadcast via RPC.', {
|
|
178
228
|
wallet_id: z.number().int().describe('Wallet ID'),
|
|
179
|
-
to: z.string().
|
|
229
|
+
to: z.string().describe('Destination address (0x-prefixed for EVM, Base58 for Solana)'),
|
|
180
230
|
chain_id: z.number().int().optional().describe('Chain ID (defaults to wallet\'s default)'),
|
|
181
|
-
value: z.string().default('0').describe('Value in wei (decimal string)'),
|
|
182
|
-
data: z.string().default('').describe('Hex-encoded calldata (0x-prefixed) for contract calls'),
|
|
183
|
-
gas_limit: z.string().optional().describe('Gas limit (auto-estimated if omitted)'),
|
|
184
|
-
max_fee: z.string().optional().describe('Max fee per gas in wei (auto if omitted)'),
|
|
185
|
-
priority_fee: z.string().optional().describe('Max priority fee per gas in wei (auto if omitted)'),
|
|
186
|
-
|
|
187
|
-
|
|
231
|
+
value: z.string().default('0').describe('Value in wei/lamports (decimal string)'),
|
|
232
|
+
data: z.string().default('').describe('Hex-encoded calldata (0x-prefixed) for EVM contract calls'),
|
|
233
|
+
gas_limit: z.string().optional().describe('Gas limit — EVM only (auto-estimated if omitted)'),
|
|
234
|
+
max_fee: z.string().optional().describe('Max fee per gas in wei — EVM only (auto if omitted)'),
|
|
235
|
+
priority_fee: z.string().optional().describe('Max priority fee per gas in wei — EVM only (auto if omitted)'),
|
|
236
|
+
token_mint: z.string().optional().describe('SPL token mint address — Solana only (for SPL token transfers)'),
|
|
237
|
+
token_decimals: z.number().int().optional().describe('SPL token decimals — Solana only (6 for USDC)'),
|
|
238
|
+
}, async ({ wallet_id, to, chain_id, value, data, gas_limit, max_fee, priority_fee, token_mint, token_decimals }) => {
|
|
239
|
+
// Validate address format
|
|
240
|
+
if (!isValidAddress(to)) {
|
|
241
|
+
throw new Error(`Invalid address "${to}". Use 0x-prefixed hex for EVM or Base58 for Solana.`);
|
|
242
|
+
}
|
|
243
|
+
const body = { to, value };
|
|
188
244
|
if (chain_id)
|
|
189
245
|
body.chain_id = chain_id;
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
246
|
+
if (isSolanaChain(chain_id ?? 0)) {
|
|
247
|
+
// Solana-specific params
|
|
248
|
+
if (token_mint)
|
|
249
|
+
body.token_mint = token_mint;
|
|
250
|
+
if (token_decimals !== undefined)
|
|
251
|
+
body.token_decimals = token_decimals;
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
// EVM-specific params
|
|
255
|
+
body.data = data;
|
|
256
|
+
if (gas_limit)
|
|
257
|
+
body.gas_limit = gas_limit;
|
|
258
|
+
if (max_fee)
|
|
259
|
+
body.max_fee = max_fee;
|
|
260
|
+
if (priority_fee)
|
|
261
|
+
body.priority_fee = priority_fee;
|
|
262
|
+
}
|
|
196
263
|
const result = await api(`/wallets/${wallet_id}/send`, 'POST', body);
|
|
197
264
|
return jsonResponse(result);
|
|
198
265
|
});
|
|
199
266
|
// ─── Tool: transfer ─────────────────────────────────────────────
|
|
200
|
-
server.tool('transfer', 'Send native tokens (ETH, AVAX, BNB, POL, PLS) to an address. ' +
|
|
267
|
+
server.tool('transfer', 'Send native tokens (ETH, AVAX, BNB, POL, PLS, SOL) to an address. ' +
|
|
201
268
|
'Specify the amount in human-readable format (e.g. "0.1" for 0.1 ETH). ' +
|
|
202
|
-
'The amount is converted to wei automatically. Signs and broadcasts the transaction.', {
|
|
269
|
+
'The amount is converted to wei/lamports automatically. Signs and broadcasts the transaction.', {
|
|
203
270
|
wallet_id: z.number().int().describe('Wallet ID to send from'),
|
|
204
|
-
to: z.string().
|
|
271
|
+
to: z.string().describe('Destination address (0x-prefixed for EVM, Base58 for Solana)'),
|
|
205
272
|
amount: z.string().describe('Amount to send in human-readable format (e.g. "0.1" for 0.1 ETH)'),
|
|
206
|
-
chain_id: z.number().int().describe('Chain ID (1=Ethereum, 8453=Base, 42161=Arbitrum, 10=Optimism, 137=Polygon, 43114=Avalanche, 56=BSC, 7777777=Zora, 369=PulseChain)'),
|
|
273
|
+
chain_id: z.number().int().describe('Chain ID (1=Ethereum, 8453=Base, 42161=Arbitrum, 10=Optimism, 137=Polygon, 43114=Avalanche, 56=BSC, 7777777=Zora, 369=PulseChain, 900=Solana, 901=Solana Devnet)'),
|
|
207
274
|
}, async ({ wallet_id, to, amount, chain_id }) => {
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
275
|
+
// Validate address format
|
|
276
|
+
if (!isValidAddress(to)) {
|
|
277
|
+
throw new Error(`Invalid address "${to}". Use 0x-prefixed hex for EVM or Base58 for Solana.`);
|
|
278
|
+
}
|
|
279
|
+
// SOL uses 9 decimals (lamports), EVM native tokens use 18 decimals (wei)
|
|
280
|
+
const decimals = isSolanaChain(chain_id) ? 9 : 18;
|
|
281
|
+
const valueRaw = parseUnits(amount, decimals);
|
|
282
|
+
const body = {
|
|
211
283
|
to,
|
|
212
|
-
value:
|
|
284
|
+
value: valueRaw,
|
|
213
285
|
chain_id,
|
|
214
|
-
|
|
215
|
-
|
|
286
|
+
};
|
|
287
|
+
if (!isSolanaChain(chain_id)) {
|
|
288
|
+
body.data = '';
|
|
289
|
+
}
|
|
290
|
+
const result = await api(`/wallets/${wallet_id}/send`, 'POST', body);
|
|
216
291
|
return jsonResponse({
|
|
217
292
|
...result,
|
|
218
293
|
amount,
|
|
@@ -220,17 +295,30 @@ server.tool('transfer', 'Send native tokens (ETH, AVAX, BNB, POL, PLS) to an add
|
|
|
220
295
|
});
|
|
221
296
|
});
|
|
222
297
|
// ─── Tool: get_token_balance ────────────────────────────────────
|
|
223
|
-
server.tool('get_token_balance', 'Get the ERC-20 token balance for a wallet on a specific chain. ' +
|
|
298
|
+
server.tool('get_token_balance', 'Get the ERC-20 or SPL token balance for a wallet on a specific chain. ' +
|
|
224
299
|
'Returns the raw balance and human-readable balance. ' +
|
|
225
300
|
'Use get_chains to find stablecoin addresses for each chain.', {
|
|
226
301
|
wallet_id: z.number().int().describe('Wallet ID to check'),
|
|
227
|
-
token: z.string().
|
|
302
|
+
token: z.string().describe('Token address (0x-prefixed ERC-20 contract for EVM, Base58 mint for Solana)'),
|
|
228
303
|
chain_id: z.number().int().describe('Chain ID to check on'),
|
|
229
304
|
decimals: z.number().int().default(18).describe('Token decimals (6 for USDC, 18 for most tokens)'),
|
|
230
305
|
}, async ({ wallet_id, token, chain_id, decimals }) => {
|
|
306
|
+
// Validate token address format
|
|
307
|
+
if (!isValidAddress(token)) {
|
|
308
|
+
throw new Error(`Invalid token address "${token}". Use 0x-prefixed hex for EVM or Base58 for Solana.`);
|
|
309
|
+
}
|
|
231
310
|
const params = `?chain_id=${chain_id}&token=${token}`;
|
|
232
311
|
const data = await api(`/wallets/${wallet_id}/token-balance${params}`);
|
|
233
|
-
|
|
312
|
+
// Solana API returns balance_formatted + decimals directly
|
|
313
|
+
if (isSolanaChain(chain_id) && data.balance_formatted !== undefined) {
|
|
314
|
+
return jsonResponse({
|
|
315
|
+
...data,
|
|
316
|
+
balance: data.balance_formatted,
|
|
317
|
+
decimals: data.decimals ?? decimals,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
// EVM: format from raw
|
|
321
|
+
const balanceFormatted = formatUnits(data.balance_raw || '0', decimals);
|
|
234
322
|
return jsonResponse({
|
|
235
323
|
...data,
|
|
236
324
|
balance: balanceFormatted,
|
|
@@ -238,25 +326,45 @@ server.tool('get_token_balance', 'Get the ERC-20 token balance for a wallet on a
|
|
|
238
326
|
});
|
|
239
327
|
});
|
|
240
328
|
// ─── Tool: transfer_token ───────────────────────────────────────
|
|
241
|
-
server.tool('transfer_token', 'Send ERC-20 tokens (
|
|
329
|
+
server.tool('transfer_token', 'Send ERC-20 tokens (EVM) or SPL tokens (Solana) to an address. ' +
|
|
242
330
|
'Specify the amount in human-readable format (e.g. "100" for 100 USDC). ' +
|
|
243
331
|
'Signs and broadcasts the transaction. Use get_chains to find stablecoin addresses.', {
|
|
244
332
|
wallet_id: z.number().int().describe('Wallet ID to send from'),
|
|
245
|
-
token: z.string().
|
|
246
|
-
to: z.string().
|
|
333
|
+
token: z.string().describe('Token address (0x-prefixed ERC-20 contract for EVM, Base58 mint for Solana)'),
|
|
334
|
+
to: z.string().describe('Recipient address (0x-prefixed for EVM, Base58 for Solana)'),
|
|
247
335
|
amount: z.string().describe('Amount in human-readable format (e.g. "100" for 100 USDC)'),
|
|
248
336
|
chain_id: z.number().int().describe('Chain ID'),
|
|
249
337
|
decimals: z.number().int().default(18).describe('Token decimals (6 for USDC, 18 for most tokens)'),
|
|
250
338
|
}, async ({ wallet_id, token, to, amount, chain_id, decimals }) => {
|
|
251
|
-
//
|
|
339
|
+
// Validate addresses
|
|
340
|
+
if (!isValidAddress(token)) {
|
|
341
|
+
throw new Error(`Invalid token address "${token}". Use 0x-prefixed hex for EVM or Base58 for Solana.`);
|
|
342
|
+
}
|
|
343
|
+
if (!isValidAddress(to)) {
|
|
344
|
+
throw new Error(`Invalid recipient address "${to}". Use 0x-prefixed hex for EVM or Base58 for Solana.`);
|
|
345
|
+
}
|
|
252
346
|
const rawAmount = parseUnits(amount, decimals);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
347
|
+
let result;
|
|
348
|
+
if (isSolanaChain(chain_id)) {
|
|
349
|
+
// Solana SPL transfer — the server handles ATA derivation + instruction building
|
|
350
|
+
result = await api(`/wallets/${wallet_id}/send`, 'POST', {
|
|
351
|
+
to,
|
|
352
|
+
value: rawAmount,
|
|
353
|
+
token_mint: token,
|
|
354
|
+
token_decimals: decimals,
|
|
355
|
+
chain_id,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
// EVM ERC-20 transfer(address, uint256) calldata
|
|
360
|
+
const calldata = '0xa9059cbb' + padAddress(to) + encodeUint256(rawAmount);
|
|
361
|
+
result = await api(`/wallets/${wallet_id}/send`, 'POST', {
|
|
362
|
+
to: token, // Send TX to the token contract
|
|
363
|
+
value: '0', // No native value for token transfers
|
|
364
|
+
data: calldata,
|
|
365
|
+
chain_id,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
260
368
|
return jsonResponse({
|
|
261
369
|
...result,
|
|
262
370
|
token,
|
|
@@ -273,6 +381,9 @@ server.tool('call_contract', 'Execute a read-only call against a smart contract
|
|
|
273
381
|
to: z.string().regex(/^0x[a-fA-F0-9]{40}$/).describe('Contract address'),
|
|
274
382
|
data: z.string().describe('ABI-encoded calldata (0x-prefixed hex)'),
|
|
275
383
|
}, async ({ chain_id, to, data }) => {
|
|
384
|
+
if (isSolanaChain(chain_id)) {
|
|
385
|
+
throw new Error('call_contract is not supported on Solana. Use Solana-specific RPC methods instead.');
|
|
386
|
+
}
|
|
276
387
|
const result = await api('/eth-call', 'POST', { chain_id, to, data });
|
|
277
388
|
return jsonResponse(result);
|
|
278
389
|
});
|
|
@@ -287,6 +398,9 @@ server.tool('approve_token', 'Approve a spender contract to transfer ERC-20 toke
|
|
|
287
398
|
chain_id: z.number().int().describe('Chain ID'),
|
|
288
399
|
decimals: z.number().int().default(18).describe('Token decimals (6 for USDC, 18 for most tokens)'),
|
|
289
400
|
}, async ({ wallet_id, token, spender, amount, chain_id, decimals }) => {
|
|
401
|
+
if (isSolanaChain(chain_id)) {
|
|
402
|
+
throw new Error('approve_token is not supported on Solana. Solana SPL tokens do not use ERC-20 style approvals.');
|
|
403
|
+
}
|
|
290
404
|
// approve(address spender, uint256 amount) — selector: 0x095ea7b3
|
|
291
405
|
let rawAmount;
|
|
292
406
|
if (amount.toLowerCase() === 'max') {
|
|
@@ -319,6 +433,9 @@ server.tool('get_allowance', 'Check how many ERC-20 tokens a spender is approved
|
|
|
319
433
|
chain_id: z.number().int().describe('Chain ID'),
|
|
320
434
|
decimals: z.number().int().default(18).describe('Token decimals (6 for USDC, 18 for most tokens)'),
|
|
321
435
|
}, async ({ wallet_id, token, spender, chain_id, decimals }) => {
|
|
436
|
+
if (isSolanaChain(chain_id)) {
|
|
437
|
+
throw new Error('get_allowance is not supported on Solana. Solana SPL tokens do not use ERC-20 style allowances.');
|
|
438
|
+
}
|
|
322
439
|
// First get the wallet address
|
|
323
440
|
const wallet = await api(`/wallets/${wallet_id}`);
|
|
324
441
|
// allowance(address owner, address spender) — selector: 0xdd62ed3e
|
|
@@ -357,6 +474,9 @@ server.tool('wrap_eth', 'Wrap native tokens (ETH, AVAX, BNB, POL, PLS) into thei
|
|
|
357
474
|
amount: z.string().describe('Amount to wrap in human-readable format (e.g. "0.5")'),
|
|
358
475
|
chain_id: z.number().int().describe('Chain ID'),
|
|
359
476
|
}, async ({ wallet_id, amount, chain_id }) => {
|
|
477
|
+
if (isSolanaChain(chain_id)) {
|
|
478
|
+
throw new Error('wrap_eth is not supported on Solana. Solana does not use wrapped native tokens like WETH.');
|
|
479
|
+
}
|
|
360
480
|
const wrapped = WRAPPED_NATIVE[chain_id];
|
|
361
481
|
if (!wrapped) {
|
|
362
482
|
throw new Error(`No wrapped native token configured for chain ${chain_id}`);
|
|
@@ -383,6 +503,9 @@ server.tool('unwrap_eth', 'Unwrap wrapped tokens (WETH, WAVAX, WBNB, etc.) back
|
|
|
383
503
|
amount: z.string().describe('Amount to unwrap in human-readable format (e.g. "0.5")'),
|
|
384
504
|
chain_id: z.number().int().describe('Chain ID'),
|
|
385
505
|
}, async ({ wallet_id, amount, chain_id }) => {
|
|
506
|
+
if (isSolanaChain(chain_id)) {
|
|
507
|
+
throw new Error('unwrap_eth is not supported on Solana. Solana does not use wrapped native tokens like WETH.');
|
|
508
|
+
}
|
|
386
509
|
const wrapped = WRAPPED_NATIVE[chain_id];
|
|
387
510
|
if (!wrapped) {
|
|
388
511
|
throw new Error(`No wrapped native token configured for chain ${chain_id}`);
|
|
@@ -436,6 +559,9 @@ server.tool('get_token_info', 'Get the name, symbol, and decimals of any ERC-20
|
|
|
436
559
|
token: z.string().regex(/^0x[a-fA-F0-9]{40}$/).describe('ERC-20 token contract address'),
|
|
437
560
|
chain_id: z.number().int().describe('Chain ID'),
|
|
438
561
|
}, async ({ token, chain_id }) => {
|
|
562
|
+
if (isSolanaChain(chain_id)) {
|
|
563
|
+
throw new Error('get_token_info is not supported on Solana. Use Solana token metadata programs to query SPL token details.');
|
|
564
|
+
}
|
|
439
565
|
// Make 3 parallel eth_call requests: name(), symbol(), decimals()
|
|
440
566
|
const [nameResult, symbolResult, decimalsResult] = await Promise.all([
|
|
441
567
|
api('/eth-call', 'POST', { chain_id, to: token, data: '0x06fdde03' }).catch(() => ({ result: '0x' })),
|
|
@@ -568,8 +694,29 @@ server.tool('pay_x402', 'Handle an x402 payment flow. Fetches a URL, and if the
|
|
|
568
694
|
}
|
|
569
695
|
// Step 5: Execute payment
|
|
570
696
|
let txResult;
|
|
571
|
-
if (
|
|
572
|
-
//
|
|
697
|
+
if (isSolanaChain(chainId)) {
|
|
698
|
+
// Solana payment
|
|
699
|
+
if (option.extra?.token) {
|
|
700
|
+
// SPL token payment (e.g. USDC on Solana)
|
|
701
|
+
txResult = (await api(`/wallets/${wallet_id}/send`, 'POST', {
|
|
702
|
+
to: option.payTo,
|
|
703
|
+
value: option.maxAmountRequired,
|
|
704
|
+
token_mint: option.extra.token,
|
|
705
|
+
token_decimals: option.requiredDecimals,
|
|
706
|
+
chain_id: chainId,
|
|
707
|
+
}));
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
// Native SOL payment
|
|
711
|
+
txResult = (await api(`/wallets/${wallet_id}/send`, 'POST', {
|
|
712
|
+
to: option.payTo,
|
|
713
|
+
value: option.maxAmountRequired,
|
|
714
|
+
chain_id: chainId,
|
|
715
|
+
}));
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
else if (option.extra?.token) {
|
|
719
|
+
// EVM ERC-20 token payment (e.g. USDC)
|
|
573
720
|
const calldata = '0xa9059cbb' + padAddress(option.payTo) + encodeUint256(option.maxAmountRequired);
|
|
574
721
|
txResult = (await api(`/wallets/${wallet_id}/send`, 'POST', {
|
|
575
722
|
to: option.extra.token,
|
|
@@ -579,7 +726,7 @@ server.tool('pay_x402', 'Handle an x402 payment flow. Fetches a URL, and if the
|
|
|
579
726
|
}));
|
|
580
727
|
}
|
|
581
728
|
else {
|
|
582
|
-
//
|
|
729
|
+
// EVM native token payment (ETH, etc.)
|
|
583
730
|
txResult = (await api(`/wallets/${wallet_id}/send`, 'POST', {
|
|
584
731
|
to: option.payTo,
|
|
585
732
|
value: option.maxAmountRequired,
|
|
@@ -587,7 +734,8 @@ server.tool('pay_x402', 'Handle an x402 payment flow. Fetches a URL, and if the
|
|
|
587
734
|
chain_id: chainId,
|
|
588
735
|
}));
|
|
589
736
|
}
|
|
590
|
-
|
|
737
|
+
// Solana returns 'signature', EVM returns 'tx_hash'
|
|
738
|
+
const txHash = (txResult.tx_hash || txResult.signature);
|
|
591
739
|
// Step 6: Build x402 payment proof and retry
|
|
592
740
|
const paymentProof = {
|
|
593
741
|
x402Version: paymentInfo.x402Version || 1,
|
|
@@ -650,7 +798,7 @@ server.tool('unpause_wallet', 'Resume a paused wallet so transactions can be sig
|
|
|
650
798
|
return jsonResponse(data);
|
|
651
799
|
});
|
|
652
800
|
// ─── Tool: get_chains ────────────────────────────────────────────
|
|
653
|
-
server.tool('get_chains', 'List all supported EVM
|
|
801
|
+
server.tool('get_chains', 'List all supported chains (EVM + Solana) with their chain IDs, native tokens, ' +
|
|
654
802
|
'stablecoins, and RPC configuration status.', {}, async () => {
|
|
655
803
|
const data = await api('/chains');
|
|
656
804
|
return jsonResponse(data);
|
|
@@ -671,8 +819,8 @@ server.tool('create_paywall', 'Create an x402 paywall that charges agents/client
|
|
|
671
819
|
name: z.string().describe('Human-readable paywall name (e.g. "Premium API Access")'),
|
|
672
820
|
description: z.string().default('').describe('Description shown in the 402 response'),
|
|
673
821
|
amount: z.string().describe('Price in human-readable format (e.g. "0.01" for 0.01 USDC)'),
|
|
674
|
-
token_type: z.enum(['erc20', 'native']).default('erc20').describe('"erc20" for
|
|
675
|
-
token_address: z.string().default('').describe('ERC-20
|
|
822
|
+
token_type: z.enum(['erc20', 'spl', 'native']).default('erc20').describe('"erc20" for EVM stablecoins, "spl" for Solana SPL tokens, "native" for ETH/SOL/POL/etc.'),
|
|
823
|
+
token_address: z.string().default('').describe('Token contract address (ERC-20 for EVM, SPL mint Base58 for Solana). Required if token_type is "erc20" or "spl". Use get_chains to find stablecoin addresses.'),
|
|
676
824
|
token_decimals: z.number().int().default(6).describe('Token decimals (6 for USDC, 18 for ETH/most tokens)'),
|
|
677
825
|
token_name: z.string().default('USDC').describe('Token display name (e.g. "USDC", "ETH")'),
|
|
678
826
|
chain_id: z.number().int().default(8453).describe('Chain ID for payments (8453=Base, 1=Ethereum, etc.)'),
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentwallet-mcp",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "MCP server for AgentWallet — lets AI agents create wallets, sign transactions, broadcast on
|
|
3
|
+
"version": "1.7.0",
|
|
4
|
+
"description": "MCP server for AgentWallet — lets AI agents create wallets, sign transactions, broadcast on EVM + Solana, pay x402 invoices, and accept x402 payments",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
"bsc",
|
|
34
34
|
"zora",
|
|
35
35
|
"pulsechain",
|
|
36
|
+
"solana",
|
|
37
|
+
"spl-token",
|
|
36
38
|
"erc20",
|
|
37
39
|
"token-transfer",
|
|
38
40
|
"transaction-signing",
|