agentwallet-mcp 1.10.5 → 1.10.7

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 CHANGED
@@ -328,6 +328,14 @@ resolve public for the check and private for the connection. The hostname is
328
328
  still used for the `Host` header and for TLS SNI and certificate validation, so
329
329
  pinning is invisible to legitimate endpoints.
330
330
 
331
+ Credentials stay with the origin they were given to. If an endpoint redirects
332
+ `pay_x402` to a different origin, the caller-supplied `Authorization`, `Cookie`
333
+ and `X-PAYMENT` headers, and every other custom header, are dropped before the
334
+ next hop; only content-negotiation headers (`Accept`, `Accept-Language`,
335
+ `Accept-Encoding`, `User-Agent`, `Content-Type`) are carried. A redirect that
336
+ turns a POST into a GET also sheds the body headers. Same-origin redirects keep
337
+ everything, as a browser would.
338
+
331
339
  Report security issues privately to security@hifriendbot.com.
332
340
 
333
341
  ## License
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.4',
427
+ version: '1.10.7',
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 ─────────────────────────────────────────
@@ -944,7 +944,9 @@ server.tool('pay_x402', 'Handle an x402 payment flow. Fetches a URL, and if the
944
944
  url: z.string().url().describe('The URL to access (will handle 402 payment if required)'),
945
945
  wallet_id: z.number().int().describe('Wallet ID to pay from'),
946
946
  method: z.string().default('GET').describe('HTTP method (GET, POST, PUT, DELETE)'),
947
- headers: z.string().optional().describe('Optional JSON string of additional request headers'),
947
+ headers: z.string().optional().describe('Optional JSON string of additional request headers. Credentials placed here ' +
948
+ '(Authorization, Cookie, API keys) are sent only to the origin in `url`; if the ' +
949
+ 'endpoint redirects to a different origin they are dropped, not forwarded.'),
948
950
  body: z.string().optional().describe('Optional request body for POST/PUT requests'),
949
951
  max_payment: z.string().optional().describe('Maximum payment in human-readable format (e.g. "1.00" for 1 USDC). ' +
950
952
  'Rejects payments above this amount. If omitted, a default cap of ' +
@@ -21,8 +21,14 @@
21
21
  * still used for the Host header and for TLS SNI and certificate validation,
22
22
  * so pinning is invisible to legitimate endpoints.
23
23
  *
24
- * Reported privately by ARC Security Research, 2026-07-31 (spelling bypasses)
25
- * and by an anonymous researcher, 2026-08-02 (the DNS rebinding race).
24
+ * Redirects are followed by hand, and a redirect to a different origin drops
25
+ * every caller-supplied header except a short content-negotiation list. A
26
+ * bearer token meant for api.example.com must not be replayed to whatever
27
+ * host api.example.com chooses to redirect to.
28
+ *
29
+ * Reported privately by ARC Security Research, 2026-07-31 (spelling bypasses),
30
+ * by an anonymous researcher, 2026-08-02 (the DNS rebinding race), and by
31
+ * Arthur Kijkrittaya, 2026-09-08 (credentials surviving cross-origin redirects).
26
32
  */
27
33
  /**
28
34
  * Reduce a host to the IP address it actually denotes.
@@ -64,6 +70,17 @@ type LookupCallback = (err: NodeJS.ErrnoException | null, addressOrList?: string
64
70
  * second query cannot reach the socket.
65
71
  */
66
72
  export declare function createPinnedLookup(addresses: ValidatedAddress[]): (_hostname: string, options: unknown, callback?: LookupCallback) => void;
73
+ /** True when two URLs share scheme, host and port (the web origin). */
74
+ export declare function sameOrigin(a: string, b: string): boolean;
75
+ /**
76
+ * Decide which request headers follow a redirect from `from` to `to`.
77
+ *
78
+ * Same origin: everything is kept, as a browser would. Different origin: only
79
+ * CROSS_ORIGIN_SAFE_HEADERS survive. When the redirect also turned the request
80
+ * into a body-less GET (`bodyDropped`), the entity headers go too, on either
81
+ * kind of hop.
82
+ */
83
+ export declare function headersForRedirect(headers: HeadersInit | undefined, from: string, to: string, bodyDropped: boolean): Record<string, string>;
67
84
  /**
68
85
  * fetch() that validates the target, and every redirect hop, against
69
86
  * resolvePublicUrl, and connects only to the addresses that validation
@@ -71,6 +88,9 @@ export declare function createPinnedLookup(addresses: ValidatedAddress[]): (_hos
71
88
  * would happily land on a private address after a public first hop, and
72
89
  * because each hop needs its own resolve-then-pin cycle.
73
90
  *
91
+ * Headers are re-derived on every redirect (see headersForRedirect): a hop to
92
+ * another origin carries no credential the caller attached for the first one.
93
+ *
74
94
  * The body is buffered so the pinned connection can be torn down before the
75
95
  * response is handed back.
76
96
  */
@@ -21,8 +21,14 @@
21
21
  * still used for the Host header and for TLS SNI and certificate validation,
22
22
  * so pinning is invisible to legitimate endpoints.
23
23
  *
24
- * Reported privately by ARC Security Research, 2026-07-31 (spelling bypasses)
25
- * and by an anonymous researcher, 2026-08-02 (the DNS rebinding race).
24
+ * Redirects are followed by hand, and a redirect to a different origin drops
25
+ * every caller-supplied header except a short content-negotiation list. A
26
+ * bearer token meant for api.example.com must not be replayed to whatever
27
+ * host api.example.com chooses to redirect to.
28
+ *
29
+ * Reported privately by ARC Security Research, 2026-07-31 (spelling bypasses),
30
+ * by an anonymous researcher, 2026-08-02 (the DNS rebinding race), and by
31
+ * Arthur Kijkrittaya, 2026-09-08 (credentials surviving cross-origin redirects).
26
32
  */
27
33
  import { isIP } from 'node:net';
28
34
  import { lookup as dnsLookup } from 'node:dns/promises';
@@ -212,6 +218,66 @@ export function createPinnedLookup(addresses) {
212
218
  }
213
219
  /** Statuses whose Response must be constructed with a null body. */
214
220
  const NULL_BODY_STATUS = new Set([101, 103, 204, 205, 304]);
221
+ /**
222
+ * The only request headers that may travel to a different origin on a
223
+ * redirect. Everything else the caller supplied (Authorization, Cookie,
224
+ * X-PAYMENT, API keys under any custom name) is bound to the origin the caller
225
+ * addressed and is dropped. An allow list is used rather than a deny list so a
226
+ * secret under an unanticipated header name cannot slip through.
227
+ */
228
+ const CROSS_ORIGIN_SAFE_HEADERS = new Set([
229
+ 'accept',
230
+ 'accept-language',
231
+ 'accept-encoding',
232
+ 'user-agent',
233
+ 'content-type',
234
+ ]);
235
+ /** Headers that describe a request body and are wrong once the body is gone. */
236
+ const ENTITY_HEADERS = new Set(['content-type', 'content-length', 'content-encoding', 'transfer-encoding']);
237
+ /** True when two URLs share scheme, host and port (the web origin). */
238
+ export function sameOrigin(a, b) {
239
+ const ua = new URL(a);
240
+ const ub = new URL(b);
241
+ return ua.protocol === ub.protocol && ua.host === ub.host;
242
+ }
243
+ function headersToRecord(headers) {
244
+ const out = {};
245
+ if (!headers)
246
+ return out;
247
+ if (typeof headers.forEach === 'function' && !Array.isArray(headers)) {
248
+ headers.forEach((v, k) => { out[k] = v; });
249
+ return out;
250
+ }
251
+ if (Array.isArray(headers)) {
252
+ for (const [k, v] of headers)
253
+ out[k] = v;
254
+ return out;
255
+ }
256
+ for (const [k, v] of Object.entries(headers))
257
+ out[k] = v;
258
+ return out;
259
+ }
260
+ /**
261
+ * Decide which request headers follow a redirect from `from` to `to`.
262
+ *
263
+ * Same origin: everything is kept, as a browser would. Different origin: only
264
+ * CROSS_ORIGIN_SAFE_HEADERS survive. When the redirect also turned the request
265
+ * into a body-less GET (`bodyDropped`), the entity headers go too, on either
266
+ * kind of hop.
267
+ */
268
+ export function headersForRedirect(headers, from, to, bodyDropped) {
269
+ const crossOrigin = !sameOrigin(from, to);
270
+ const out = {};
271
+ for (const [k, v] of Object.entries(headersToRecord(headers))) {
272
+ const name = k.toLowerCase();
273
+ if (bodyDropped && ENTITY_HEADERS.has(name))
274
+ continue;
275
+ if (crossOrigin && !CROSS_ORIGIN_SAFE_HEADERS.has(name))
276
+ continue;
277
+ out[k] = v;
278
+ }
279
+ return out;
280
+ }
215
281
  /**
216
282
  * fetch() that validates the target, and every redirect hop, against
217
283
  * resolvePublicUrl, and connects only to the addresses that validation
@@ -219,6 +285,9 @@ const NULL_BODY_STATUS = new Set([101, 103, 204, 205, 304]);
219
285
  * would happily land on a private address after a public first hop, and
220
286
  * because each hop needs its own resolve-then-pin cycle.
221
287
  *
288
+ * Headers are re-derived on every redirect (see headersForRedirect): a hop to
289
+ * another origin carries no credential the caller attached for the first one.
290
+ *
222
291
  * The body is buffered so the pinned connection can be torn down before the
223
292
  * response is handed back.
224
293
  */
@@ -262,8 +331,12 @@ export async function safeFetch(url, options = {}, maxHops = 3) {
262
331
  if (!isRedirect)
263
332
  return out;
264
333
  const next = new URL(out.headers.get('location'), current).toString();
265
- // A redirected request must not replay the body or method blindly.
266
- if (status === 303 || ((status === 301 || status === 302) && opts.method && opts.method !== 'GET')) {
334
+ // A redirected request must not replay the body or method blindly, and it
335
+ // must not replay the caller's credentials to a different origin at all.
336
+ const bodyDropped = status === 303
337
+ || ((status === 301 || status === 302) && !!opts.method && opts.method !== 'GET');
338
+ opts = { ...opts, headers: headersForRedirect(opts.headers, current, next, bodyDropped) };
339
+ if (bodyDropped) {
267
340
  opts = { ...opts, method: 'GET', body: undefined };
268
341
  }
269
342
  current = next;
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.5",
4
+ "version": "1.10.7",
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",