agentwallet-mcp 1.7.5 → 1.8.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 CHANGED
@@ -160,7 +160,7 @@ AgentWallet natively supports the [x402 open payment standard](https://x402.org)
160
160
  4. Retries the request with proof of payment
161
161
  5. Returns the final response
162
162
 
163
- **Always set `max_payment` to control spending:**
163
+ **Set `max_payment` to control spending:**
164
164
 
165
165
  ```
166
166
  pay_x402(
@@ -170,7 +170,9 @@ pay_x402(
170
170
  )
171
171
  ```
172
172
 
173
- Supports ERC-20 tokens, SPL tokens, and native tokens on EVM and Solana. Compatible with x402 V1 and V2 (CAIP-2 chain identifiers).
173
+ `max_payment` is enforced as a hard per-payment cap. If you omit it, `pay_x402` falls back to `AGENTWALLET_MAX_AUTOPAY` (default `1`), the same cap the auto-pay path uses, so a malicious or compromised 402 endpoint can never authorize an unbounded payment. Set `max_payment` explicitly (or raise `AGENTWALLET_MAX_AUTOPAY`) to allow a larger single payment.
174
+
175
+ Supports ERC-20 tokens, SPL tokens, and native tokens on EVM and Solana. Compatible with x402 V1 and V2 (CAIP-2 chain identifiers), and reads the token address from the standard x402 `asset` field (falling back to `extra.token`).
174
176
 
175
177
  ## x402 Acceptance
176
178
 
package/build/index.js CHANGED
@@ -12,7 +12,7 @@
12
12
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
13
13
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
14
14
  import { z } from 'zod';
15
- import { deriveX402Payment } from './x402-payment.js';
15
+ import { deriveX402Payment, isWithinCap } from './x402-payment.js';
16
16
  // ─── Configuration ──────────────────────────────────────────────
17
17
  const API_BASE = process.env.AGENTWALLET_API_URL || 'https://hifriendbot.com/wp-json/agentwallet/v1';
18
18
  const API_USER = process.env.AGENTWALLET_USER || '';
@@ -221,7 +221,7 @@ function resolveChainId(network) {
221
221
  // ─── Server ──────────────────────────────────────────────────────
222
222
  const server = new McpServer({
223
223
  name: 'agentwallet',
224
- version: '1.7.2',
224
+ version: '1.8.0',
225
225
  }, {
226
226
  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.
227
227
 
@@ -744,7 +744,10 @@ server.tool('pay_x402', 'Handle an x402 payment flow. Fetches a URL, and if the
744
744
  headers: z.string().optional().describe('Optional JSON string of additional request headers'),
745
745
  body: z.string().optional().describe('Optional request body for POST/PUT requests'),
746
746
  max_payment: z.string().optional().describe('Maximum payment in human-readable format (e.g. "1.00" for 1 USDC). ' +
747
- 'Rejects payments above this amount. Strongly recommended to prevent overspending.'),
747
+ 'Rejects payments above this amount. If omitted, a default cap of ' +
748
+ 'AGENTWALLET_MAX_AUTOPAY (default "1") is applied, so an untrusted 402 ' +
749
+ 'endpoint can never authorize an unbounded payment. Set this explicitly ' +
750
+ 'to allow a larger single payment.'),
748
751
  prefer_chain: z.number().int().optional().describe('Preferred chain ID if the server accepts payment on multiple chains ' +
749
752
  '(e.g. 8453 for Base, 1 for Ethereum)'),
750
753
  }, async ({ url, wallet_id, method, headers: headersJson, body: reqBody, max_payment, prefer_chain }) => {
@@ -826,35 +829,45 @@ server.tool('pay_x402', 'Handle an x402 payment flow. Fetches a URL, and if the
826
829
  }
827
830
  // Calculate human-readable amount
828
831
  const amount = formatUnits(option.maxAmountRequired, option.requiredDecimals);
829
- // Step 4: Check spending limit
830
- if (max_payment) {
831
- const maxRaw = parseUnits(max_payment, option.requiredDecimals);
832
- if (BigInt(option.maxAmountRequired) > BigInt(maxRaw)) {
833
- return jsonResponse({
834
- status: 402,
835
- payment_required: true,
836
- payment_made: false,
837
- error: `Payment of ${amount} ${option.extra?.name || 'tokens'} exceeds your max_payment limit of ${max_payment}`,
838
- required_amount: amount,
839
- max_allowed: max_payment,
840
- token: option.extra?.name || 'native',
841
- network: option.network,
842
- chain_id: chainId,
843
- pay_to: option.payTo,
844
- description: option.description,
845
- });
846
- }
832
+ // Step 4: Enforce a hard per-payment cap. ALWAYS applied — if the caller
833
+ // omits max_payment we fall back to AGENTWALLET_MAX_AUTOPAY (default "1"),
834
+ // mirroring the internal auto-pay path (deriveX402Payment). Without this, an
835
+ // untrusted or compromised x402 endpoint could return an arbitrarily large
836
+ // maxAmountRequired and drain the wallet. The cap is never silently skipped.
837
+ const capSource = max_payment ? 'max_payment' : 'AGENTWALLET_MAX_AUTOPAY';
838
+ const effectiveMax = max_payment || process.env.AGENTWALLET_MAX_AUTOPAY || '1';
839
+ if (!isWithinCap(option.maxAmountRequired, option.requiredDecimals, effectiveMax)) {
840
+ return jsonResponse({
841
+ status: 402,
842
+ payment_required: true,
843
+ payment_made: false,
844
+ error: `Payment of ${amount} ${option.extra?.name || 'tokens'} exceeds the ` +
845
+ `${effectiveMax} cap (from ${capSource}).` +
846
+ (max_payment ? '' : ' No max_payment was provided, so the default cap was applied. ' +
847
+ 'Pass max_payment (or raise AGENTWALLET_MAX_AUTOPAY) to allow a larger payment.'),
848
+ required_amount: amount,
849
+ max_allowed: effectiveMax,
850
+ cap_source: capSource,
851
+ token: option.extra?.name || 'native',
852
+ network: option.network,
853
+ chain_id: chainId,
854
+ pay_to: option.payTo,
855
+ description: option.description,
856
+ });
847
857
  }
858
+ // Standard x402 puts the token address in `asset`; AgentWallet's own server
859
+ // currently emits it under extra.token. Prefer the standard field, fall back.
860
+ const tokenAddress = option.asset || option.extra?.token || '';
848
861
  // Step 5: Execute payment
849
862
  let txResult;
850
863
  if (isSolanaChain(chainId)) {
851
864
  // Solana payment
852
- if (option.extra?.token) {
865
+ if (tokenAddress) {
853
866
  // SPL token payment (e.g. USDC on Solana)
854
867
  txResult = (await api(`/wallets/${wallet_id}/send`, 'POST', {
855
868
  to: option.payTo,
856
869
  value: option.maxAmountRequired,
857
- token_mint: option.extra.token,
870
+ token_mint: tokenAddress,
858
871
  token_decimals: option.requiredDecimals,
859
872
  chain_id: chainId,
860
873
  }));
@@ -868,11 +881,11 @@ server.tool('pay_x402', 'Handle an x402 payment flow. Fetches a URL, and if the
868
881
  }));
869
882
  }
870
883
  }
871
- else if (option.extra?.token) {
884
+ else if (tokenAddress) {
872
885
  // EVM ERC-20 token payment (e.g. USDC)
873
886
  const calldata = '0xa9059cbb' + padAddress(option.payTo) + encodeUint256(option.maxAmountRequired);
874
887
  txResult = (await api(`/wallets/${wallet_id}/send`, 'POST', {
875
- to: option.extra.token,
888
+ to: tokenAddress,
876
889
  value: '0',
877
890
  data: calldata,
878
891
  chain_id: chainId,
@@ -921,7 +934,7 @@ server.tool('pay_x402', 'Handle an x402 payment flow. Fetches a URL, and if the
921
934
  payment_made: true,
922
935
  amount,
923
936
  token: option.extra?.name || 'native',
924
- token_address: option.extra?.token || null,
937
+ token_address: option.asset || option.extra?.token || null,
925
938
  network: option.network,
926
939
  chain_id: chainId,
927
940
  pay_to: option.payTo,
@@ -32,3 +32,17 @@ export declare function deriveX402Payment(accept: X402AcceptLike, maxAutopayHuma
32
32
  rawAmount: string;
33
33
  decimals: number;
34
34
  };
35
+ /**
36
+ * Returns true if an x402 required amount (already in base/atomic units) is
37
+ * within a human-readable per-payment cap.
38
+ *
39
+ * Shared by both x402 payment paths so neither can authorize an unbounded
40
+ * transfer: the internal auto-pay path and the public pay_x402 MCP tool both
41
+ * gate on this. pay_x402 uses it (instead of deriveX402Payment's throw) so it
42
+ * can return a structured rejection to the calling agent.
43
+ *
44
+ * @param rawAmountRequired x402 maxAmountRequired, already in base units
45
+ * @param decimals token decimals
46
+ * @param capHuman cap in human-readable units of the asset (e.g. "1")
47
+ */
48
+ export declare function isWithinCap(rawAmountRequired: string, decimals: number, capHuman: string): boolean;
@@ -44,3 +44,23 @@ export function deriveX402Payment(accept, maxAutopayHuman) {
44
44
  }
45
45
  return { tokenAddress, rawAmount, decimals };
46
46
  }
47
+ /**
48
+ * Returns true if an x402 required amount (already in base/atomic units) is
49
+ * within a human-readable per-payment cap.
50
+ *
51
+ * Shared by both x402 payment paths so neither can authorize an unbounded
52
+ * transfer: the internal auto-pay path and the public pay_x402 MCP tool both
53
+ * gate on this. pay_x402 uses it (instead of deriveX402Payment's throw) so it
54
+ * can return a structured rejection to the calling agent.
55
+ *
56
+ * @param rawAmountRequired x402 maxAmountRequired, already in base units
57
+ * @param decimals token decimals
58
+ * @param capHuman cap in human-readable units of the asset (e.g. "1")
59
+ */
60
+ export function isWithinCap(rawAmountRequired, decimals, capHuman) {
61
+ if (!/^\d+$/.test(rawAmountRequired)) {
62
+ throw new Error(`Invalid maxAmountRequired "${rawAmountRequired}" (expected an integer base-unit amount).`);
63
+ }
64
+ const capRaw = toBaseUnits(capHuman, decimals);
65
+ return BigInt(rawAmountRequired) <= BigInt(capRaw);
66
+ }
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.7.5",
4
+ "version": "1.8.0",
5
5
  "description": "MCP server for AgentWallet: lets AI agents create wallets, sign transactions, broadcast on EVM + Solana, pay x402 invoices, and accept x402 payments",
6
6
  "type": "module",
7
7
  "main": "build/index.js",