agentwallet-mcp 1.10.4 → 1.10.6
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/build/index.js +75 -75
- package/build/local-solana.js +34 -7
- package/build/local-wallet.js +43 -6
- package/package.json +2 -2
package/build/index.js
CHANGED
|
@@ -424,82 +424,82 @@ function resolveChainId(network) {
|
|
|
424
424
|
const AddressSchema = z.string().regex(/^(0x[a-fA-F0-9]{40}|[1-9A-HJ-NP-Za-km-z]{32,44})$/, 'Must be a 0x-prefixed 40-hex EVM address or a base58 Solana address');
|
|
425
425
|
const server = new McpServer({
|
|
426
426
|
name: 'agentwallet',
|
|
427
|
-
version: '1.10.
|
|
427
|
+
version: '1.10.6',
|
|
428
428
|
}, {
|
|
429
|
-
instructions: `AgentWallet gives AI agents their own blockchain wallets. Private keys are encrypted server-side and never exposed — agents sign and broadcast transactions without ever touching raw keys.
|
|
430
|
-
|
|
431
|
-
## Getting Started
|
|
432
|
-
1. Call list_wallets to see existing wallets, or create_wallet to make a new one.
|
|
433
|
-
2. Fund the wallet by sending tokens to its address from an external source.
|
|
434
|
-
3. Use transfer (native tokens) or transfer_token (ERC-20/SPL tokens) to send funds.
|
|
435
|
-
|
|
436
|
-
## Supported Chains
|
|
437
|
-
- **EVM chains:** Ethereum (1), Base (8453), Arbitrum (42161), Optimism (10), Polygon (137), Avalanche (43114), BSC (56), Zora (7777777), PulseChain (369)
|
|
438
|
-
- **Solana:** Mainnet (900), Devnet (901)
|
|
439
|
-
- Call get_chains for the full list with native tokens and stablecoin contract addresses.
|
|
440
|
-
|
|
441
|
-
## EVM vs Solana
|
|
442
|
-
- **Addresses:** EVM uses 0x-prefixed hex (42 chars). Solana uses Base58 (32-44 chars).
|
|
443
|
-
- **Native tokens:** EVM = ETH/POL/BNB/etc (18 decimals, wei). Solana = SOL (9 decimals, lamports).
|
|
444
|
-
- **Token standards:** EVM uses ERC-20. Solana uses SPL tokens.
|
|
445
|
-
- **EVM-only tools:** approve_token, get_allowance, wrap_eth, unwrap_eth, call_contract, get_token_info. These do not work on Solana.
|
|
446
|
-
- **Works on both:** create_wallet, transfer, transfer_token, send_transaction, sign_transaction, get_balance, get_token_balance.
|
|
447
|
-
|
|
448
|
-
## Common Workflows
|
|
449
|
-
|
|
450
|
-
### Send native tokens (ETH, SOL, etc.)
|
|
451
|
-
Use the transfer tool with a human-readable amount (e.g. "0.1" ETH). It auto-converts to wei/lamports.
|
|
452
|
-
|
|
453
|
-
### Send ERC-20 or SPL tokens
|
|
454
|
-
Use transfer_token. You need: wallet_id, token contract address, recipient, amount, chain_id, and decimals (6 for USDC, 18 for most ERC-20s). Call get_chains to find stablecoin addresses.
|
|
455
|
-
|
|
456
|
-
### Interact with DeFi (EVM only)
|
|
457
|
-
1. Check allowance with get_allowance.
|
|
458
|
-
2. If needed, call approve_token to let the DeFi contract spend your tokens.
|
|
459
|
-
3. Use send_transaction with the contract's calldata to execute the interaction.
|
|
460
|
-
|
|
461
|
-
### Wrap/unwrap native tokens (EVM only)
|
|
462
|
-
Use wrap_eth to convert ETH → WETH (required by many DeFi protocols). Use unwrap_eth to convert back.
|
|
463
|
-
|
|
464
|
-
### Read on-chain data (EVM only)
|
|
465
|
-
Use call_contract for gas-free read-only calls (eth_call). Provide ABI-encoded calldata.
|
|
466
|
-
|
|
467
|
-
## x402 Payments — Paying for Resources
|
|
468
|
-
The pay_x402 tool handles the full x402 payment flow automatically:
|
|
469
|
-
1. Fetches the URL.
|
|
470
|
-
2. If the server returns HTTP 402, parses the payment requirements.
|
|
471
|
-
3. Pays on-chain using your wallet.
|
|
472
|
-
4. Retries the request with proof of payment.
|
|
473
|
-
Always set max_payment to prevent overspending (e.g. "1.00" for max 1 USDC).
|
|
474
|
-
|
|
475
|
-
## x402 Paywalls — Accepting Payments
|
|
476
|
-
Create paywalls to charge other agents for accessing your resources:
|
|
477
|
-
1. create_paywall — set price, token, chain, and the resource URL to protect.
|
|
478
|
-
2. Share the access URL. Clients that pay on-chain get the resource; others get HTTP 402.
|
|
479
|
-
3. list_paywalls and get_paywall_payments to track revenue.
|
|
480
|
-
4. get_x402_revenue for aggregate stats across all paywalls.
|
|
481
|
-
|
|
482
|
-
## Wallet Security
|
|
483
|
-
- Use pause_wallet to immediately freeze a wallet if compromised. No transactions can be signed while paused.
|
|
484
|
-
- Use unpause_wallet to resume operations.
|
|
485
|
-
- Use delete_wallet to permanently disable a wallet.
|
|
486
|
-
- Wallets have server-enforced spending limits.
|
|
487
|
-
|
|
488
|
-
## Tool Selection Guide
|
|
489
|
-
| Goal | Tool |
|
|
490
|
-
|------|------|
|
|
491
|
-
| Send ETH/SOL/native tokens | transfer |
|
|
492
|
-
| Send USDC/ERC-20/SPL tokens | transfer_token |
|
|
493
|
-
| Check native balance | get_balance |
|
|
494
|
-
| Check token balance | get_token_balance |
|
|
495
|
-
| Look up token details | get_token_info (EVM only) |
|
|
496
|
-
| Approve DeFi spending | approve_token (EVM only) |
|
|
497
|
-
| Check approval amount | get_allowance (EVM only) |
|
|
498
|
-
| Read smart contract | call_contract (EVM only) |
|
|
499
|
-
| Custom transaction | send_transaction |
|
|
500
|
-
| Sign without broadcasting | sign_transaction |
|
|
501
|
-
| Pay for x402 resource | pay_x402 |
|
|
502
|
-
| Charge for your resource | create_paywall |
|
|
429
|
+
instructions: `AgentWallet gives AI agents their own blockchain wallets. Private keys are encrypted server-side and never exposed — agents sign and broadcast transactions without ever touching raw keys.
|
|
430
|
+
|
|
431
|
+
## Getting Started
|
|
432
|
+
1. Call list_wallets to see existing wallets, or create_wallet to make a new one.
|
|
433
|
+
2. Fund the wallet by sending tokens to its address from an external source.
|
|
434
|
+
3. Use transfer (native tokens) or transfer_token (ERC-20/SPL tokens) to send funds.
|
|
435
|
+
|
|
436
|
+
## Supported Chains
|
|
437
|
+
- **EVM chains:** Ethereum (1), Base (8453), Arbitrum (42161), Optimism (10), Polygon (137), Avalanche (43114), BSC (56), Zora (7777777), PulseChain (369)
|
|
438
|
+
- **Solana:** Mainnet (900), Devnet (901)
|
|
439
|
+
- Call get_chains for the full list with native tokens and stablecoin contract addresses.
|
|
440
|
+
|
|
441
|
+
## EVM vs Solana
|
|
442
|
+
- **Addresses:** EVM uses 0x-prefixed hex (42 chars). Solana uses Base58 (32-44 chars).
|
|
443
|
+
- **Native tokens:** EVM = ETH/POL/BNB/etc (18 decimals, wei). Solana = SOL (9 decimals, lamports).
|
|
444
|
+
- **Token standards:** EVM uses ERC-20. Solana uses SPL tokens.
|
|
445
|
+
- **EVM-only tools:** approve_token, get_allowance, wrap_eth, unwrap_eth, call_contract, get_token_info. These do not work on Solana.
|
|
446
|
+
- **Works on both:** create_wallet, transfer, transfer_token, send_transaction, sign_transaction, get_balance, get_token_balance.
|
|
447
|
+
|
|
448
|
+
## Common Workflows
|
|
449
|
+
|
|
450
|
+
### Send native tokens (ETH, SOL, etc.)
|
|
451
|
+
Use the transfer tool with a human-readable amount (e.g. "0.1" ETH). It auto-converts to wei/lamports.
|
|
452
|
+
|
|
453
|
+
### Send ERC-20 or SPL tokens
|
|
454
|
+
Use transfer_token. You need: wallet_id, token contract address, recipient, amount, chain_id, and decimals (6 for USDC, 18 for most ERC-20s). Call get_chains to find stablecoin addresses.
|
|
455
|
+
|
|
456
|
+
### Interact with DeFi (EVM only)
|
|
457
|
+
1. Check allowance with get_allowance.
|
|
458
|
+
2. If needed, call approve_token to let the DeFi contract spend your tokens.
|
|
459
|
+
3. Use send_transaction with the contract's calldata to execute the interaction.
|
|
460
|
+
|
|
461
|
+
### Wrap/unwrap native tokens (EVM only)
|
|
462
|
+
Use wrap_eth to convert ETH → WETH (required by many DeFi protocols). Use unwrap_eth to convert back.
|
|
463
|
+
|
|
464
|
+
### Read on-chain data (EVM only)
|
|
465
|
+
Use call_contract for gas-free read-only calls (eth_call). Provide ABI-encoded calldata.
|
|
466
|
+
|
|
467
|
+
## x402 Payments — Paying for Resources
|
|
468
|
+
The pay_x402 tool handles the full x402 payment flow automatically:
|
|
469
|
+
1. Fetches the URL.
|
|
470
|
+
2. If the server returns HTTP 402, parses the payment requirements.
|
|
471
|
+
3. Pays on-chain using your wallet.
|
|
472
|
+
4. Retries the request with proof of payment.
|
|
473
|
+
Always set max_payment to prevent overspending (e.g. "1.00" for max 1 USDC).
|
|
474
|
+
|
|
475
|
+
## x402 Paywalls — Accepting Payments
|
|
476
|
+
Create paywalls to charge other agents for accessing your resources:
|
|
477
|
+
1. create_paywall — set price, token, chain, and the resource URL to protect.
|
|
478
|
+
2. Share the access URL. Clients that pay on-chain get the resource; others get HTTP 402.
|
|
479
|
+
3. list_paywalls and get_paywall_payments to track revenue.
|
|
480
|
+
4. get_x402_revenue for aggregate stats across all paywalls.
|
|
481
|
+
|
|
482
|
+
## Wallet Security
|
|
483
|
+
- Use pause_wallet to immediately freeze a wallet if compromised. No transactions can be signed while paused.
|
|
484
|
+
- Use unpause_wallet to resume operations.
|
|
485
|
+
- Use delete_wallet to permanently disable a wallet.
|
|
486
|
+
- Wallets have server-enforced spending limits.
|
|
487
|
+
|
|
488
|
+
## Tool Selection Guide
|
|
489
|
+
| Goal | Tool |
|
|
490
|
+
|------|------|
|
|
491
|
+
| Send ETH/SOL/native tokens | transfer |
|
|
492
|
+
| Send USDC/ERC-20/SPL tokens | transfer_token |
|
|
493
|
+
| Check native balance | get_balance |
|
|
494
|
+
| Check token balance | get_token_balance |
|
|
495
|
+
| Look up token details | get_token_info (EVM only) |
|
|
496
|
+
| Approve DeFi spending | approve_token (EVM only) |
|
|
497
|
+
| Check approval amount | get_allowance (EVM only) |
|
|
498
|
+
| Read smart contract | call_contract (EVM only) |
|
|
499
|
+
| Custom transaction | send_transaction |
|
|
500
|
+
| Sign without broadcasting | sign_transaction |
|
|
501
|
+
| Pay for x402 resource | pay_x402 |
|
|
502
|
+
| Charge for your resource | create_paywall |
|
|
503
503
|
| Check usage/billing | get_usage |`,
|
|
504
504
|
});
|
|
505
505
|
// ─── Tool: create_wallet ─────────────────────────────────────────
|
package/build/local-solana.js
CHANGED
|
@@ -122,19 +122,46 @@ function assertWithinSolCap(lamports) {
|
|
|
122
122
|
* AGENTWALLET_MAX_TX_TOKEN on the EVM side and uses the same variable so an
|
|
123
123
|
* operator sets one token cap, not one per chain family.
|
|
124
124
|
*
|
|
125
|
-
* Decimals for the cap come from the trusted table when the mint is known
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
125
|
+
* Decimals for the cap come from the trusted table when the mint is known, then
|
|
126
|
+
* from the mint account itself, and finally 0. The caller-supplied value is
|
|
127
|
+
* never used for the ceiling: an inflated one would inflate the cap exactly
|
|
128
|
+
* like the x402 bypass.
|
|
129
|
+
*
|
|
130
|
+
* 0 is the only safe fallback. Assuming 6 for an unknown mint is what AW-001
|
|
131
|
+
* exploited: for a mint with d decimals, a ceiling evaluated at 6 is 10**(6-d)
|
|
132
|
+
* times too permissive, and SPL mints with 0 to 5 decimals are common.
|
|
129
133
|
*/
|
|
130
|
-
|
|
134
|
+
const splDecimalsCache = new Map();
|
|
135
|
+
async function resolveMintDecimals(mint, chainId) {
|
|
136
|
+
const known = lookupTrustedDecimals(900, mint);
|
|
137
|
+
if (typeof known === 'number')
|
|
138
|
+
return known;
|
|
139
|
+
const cached = splDecimalsCache.get(mint);
|
|
140
|
+
if (typeof cached === 'number')
|
|
141
|
+
return cached;
|
|
142
|
+
try {
|
|
143
|
+
const conn = connection(chainId);
|
|
144
|
+
const info = await conn.getParsedAccountInfo(new PublicKey(mint));
|
|
145
|
+
const data = info?.value?.data;
|
|
146
|
+
const d = Number(data?.parsed?.info?.decimals);
|
|
147
|
+
if (Number.isInteger(d) && d >= 0 && d <= 36) {
|
|
148
|
+
splDecimalsCache.set(mint, d);
|
|
149
|
+
return d;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Unreachable RPC or non-standard mint: fall through to the safe floor.
|
|
154
|
+
}
|
|
155
|
+
return 0;
|
|
156
|
+
}
|
|
157
|
+
async function assertWithinSplCap(mint, rawAmount, chainId = 900) {
|
|
131
158
|
const cap = (process.env.AGENTWALLET_MAX_TX_TOKEN || '').trim();
|
|
132
159
|
if (!cap)
|
|
133
160
|
return;
|
|
134
161
|
if (!/^\d+(\.\d+)?$/.test(cap)) {
|
|
135
162
|
throw new Error(`AGENTWALLET_MAX_TX_TOKEN must be a decimal number, got "${cap}".`);
|
|
136
163
|
}
|
|
137
|
-
const decimals =
|
|
164
|
+
const decimals = await resolveMintDecimals(mint, chainId);
|
|
138
165
|
const [whole, frac = ''] = cap.split('.');
|
|
139
166
|
const capRaw = BigInt(whole + frac.padEnd(decimals, '0').slice(0, decimals));
|
|
140
167
|
if (rawAmount > capRaw) {
|
|
@@ -252,7 +279,7 @@ function createAtaIdempotentIx(payer, ata, owner, mint, tokenProgram) {
|
|
|
252
279
|
export async function localSplTransfer(mintStr, to, rawAmount, decimals, chainId = 900) {
|
|
253
280
|
// Checked before any RPC round trip: a blocked transfer should cost nothing
|
|
254
281
|
// and should not announce itself to the RPC provider first.
|
|
255
|
-
assertWithinSplCap(mintStr, BigInt(rawAmount));
|
|
282
|
+
await assertWithinSplCap(mintStr, BigInt(rawAmount), chainId);
|
|
256
283
|
const conn = connection(chainId);
|
|
257
284
|
const payer = getSolanaKeypair();
|
|
258
285
|
const mint = new PublicKey(mintStr);
|
package/build/local-wallet.js
CHANGED
|
@@ -178,11 +178,48 @@ export async function localTokenBalance(chainId, token) {
|
|
|
178
178
|
* waiting to happen.
|
|
179
179
|
*
|
|
180
180
|
* Opt-in via AGENTWALLET_MAX_TX_TOKEN, expressed in human units of the token.
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
181
|
+
*
|
|
182
|
+
* Decimals decide the ceiling, so getting them wrong breaks the guard. They are
|
|
183
|
+
* resolved in this order: the trusted registry, then the token's own decimals()
|
|
184
|
+
* on chain (cached), and finally 0. Assuming 0 for an unresolvable token is the
|
|
185
|
+
* only value that cannot fail open: any real token has decimals >= 0, so a cap
|
|
186
|
+
* evaluated at 0 is never larger than the true one.
|
|
187
|
+
*
|
|
188
|
+
* Do not "assume 6 because it is the smallest in common use". It is not: GUSD
|
|
189
|
+
* and EURS use 2, and some tokens use 0. For a token with d decimals, a ceiling
|
|
190
|
+
* evaluated at 6 is 10**(6-d) times too permissive, which is how AW-001 let a
|
|
191
|
+
* 2-decimal token move 10,000x the configured cap.
|
|
184
192
|
*/
|
|
185
|
-
|
|
193
|
+
const decimalsCache = new Map();
|
|
194
|
+
async function resolveTokenDecimals(chainId, token) {
|
|
195
|
+
const known = lookupTrustedDecimals(chainId, token);
|
|
196
|
+
if (typeof known === 'number')
|
|
197
|
+
return known;
|
|
198
|
+
const key = `${chainId}:${token.toLowerCase()}`;
|
|
199
|
+
const cached = decimalsCache.get(key);
|
|
200
|
+
if (typeof cached === 'number')
|
|
201
|
+
return cached;
|
|
202
|
+
try {
|
|
203
|
+
const { pub } = clients(chainId);
|
|
204
|
+
const onChain = await pub.readContract({
|
|
205
|
+
address: token,
|
|
206
|
+
abi: ERC20_ABI,
|
|
207
|
+
functionName: 'decimals',
|
|
208
|
+
});
|
|
209
|
+
const d = Number(onChain);
|
|
210
|
+
// Reject nonsense rather than trusting it; an out-of-range value would
|
|
211
|
+
// widen the ceiling exactly like the old assumption did.
|
|
212
|
+
if (Number.isInteger(d) && d >= 0 && d <= 36) {
|
|
213
|
+
decimalsCache.set(key, d);
|
|
214
|
+
return d;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// Unreachable RPC, non-standard token, anything at all: fall through to 0.
|
|
219
|
+
}
|
|
220
|
+
return 0;
|
|
221
|
+
}
|
|
222
|
+
async function assertWithinTokenCap(chainId, token, data) {
|
|
186
223
|
const cap = (process.env.AGENTWALLET_MAX_TX_TOKEN || '').trim();
|
|
187
224
|
if (!cap)
|
|
188
225
|
return;
|
|
@@ -200,7 +237,7 @@ function assertWithinTokenCap(chainId, token, data) {
|
|
|
200
237
|
if (word.length !== 64)
|
|
201
238
|
return; // malformed; leave it to the node to reject
|
|
202
239
|
const amount = BigInt('0x' + word);
|
|
203
|
-
const decimals =
|
|
240
|
+
const decimals = await resolveTokenDecimals(chainId, token);
|
|
204
241
|
const [whole, frac = ''] = cap.split('.');
|
|
205
242
|
const capRaw = BigInt(whole + frac.padEnd(decimals, '0').slice(0, decimals));
|
|
206
243
|
if (amount > capRaw) {
|
|
@@ -213,7 +250,7 @@ function assertWithinTokenCap(chainId, token, data) {
|
|
|
213
250
|
export async function localSend(chainId, to, valueWei, data) {
|
|
214
251
|
assertWithinNativeCap(valueWei);
|
|
215
252
|
if (data && data.length >= 10)
|
|
216
|
-
assertWithinTokenCap(chainId, to, data);
|
|
253
|
+
await assertWithinTokenCap(chainId, to, data);
|
|
217
254
|
const { wallet } = clients(chainId);
|
|
218
255
|
const hash = await wallet.sendTransaction({
|
|
219
256
|
to,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentwallet-mcp",
|
|
3
3
|
"mcpName": "io.github.hifriendbot/agentwallet",
|
|
4
|
-
"version": "1.10.
|
|
4
|
+
"version": "1.10.6",
|
|
5
5
|
"description": "MCP server for AgentWallet: self-custody local signing on EVM and Solana, or hosted wallets. Sign, broadcast, pay x402 invoices, and accept x402 payments.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "build/index.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "tsc",
|
|
18
18
|
"start": "node build/index.js",
|
|
19
|
-
"test": "node test/x402-payment.test.mjs && node test/token-cap.test.mjs && node test/spl-cap.test.mjs && node test/local-mode.test.mjs && node test/ssrf-guard.test.mjs",
|
|
19
|
+
"test": "node test/x402-payment.test.mjs && node test/token-cap.test.mjs && node test/spl-cap.test.mjs && node test/unknown-decimals-cap.test.mjs && node test/local-mode.test.mjs && node test/ssrf-guard.test.mjs",
|
|
20
20
|
"prepublishOnly": "npm run build"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|