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 +4 -2
- package/build/index.js +39 -26
- package/build/x402-payment.d.ts +14 -0
- package/build/x402-payment.js +20 -0
- package/package.json +1 -1
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
|
-
**
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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:
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
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 (
|
|
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:
|
|
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 (
|
|
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:
|
|
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,
|
package/build/x402-payment.d.ts
CHANGED
|
@@ -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;
|
package/build/x402-payment.js
CHANGED
|
@@ -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.
|
|
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",
|