agentwallet-mcp 1.13.8 → 1.13.9
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 +14 -6
- package/build/x402-eip3009.js +23 -4
- package/package.json +2 -2
package/build/index.js
CHANGED
|
@@ -20,7 +20,7 @@ import { localSignAuthorization, localEthCall, localGetCode, localSignPermit2Upt
|
|
|
20
20
|
import { WRAPPED_NATIVE } from './wrapped-native.js';
|
|
21
21
|
import { buildUptoAuthorization, uptoPayload, permit2AllowanceCalldata, permit2ApproveCalldata, decodeUint, permit2Address, } from './x402-permit2.js';
|
|
22
22
|
import { assessTokenRisk } from './token-risk.js';
|
|
23
|
-
import { isLegacyAgentWalletAccept, requiredAmount, buildAuthorization, buildPaymentPayload, buildPaymentPayloadRaw, paymentHeaderFor, parsePaymentRequired, parseSettlement, pickOption, knownTokenDomain, parseEip7702Delegation, erc1271ProbeCalldata, classifyErc1271Probe, settlementVerdict, legacyReceiptAllowed, } from './x402-eip3009.js';
|
|
23
|
+
import { isLegacyAgentWalletAccept, requiredAmount, buildAuthorization, buildPaymentPayload, buildPaymentPayloadRaw, paymentHeaderFor, parsePaymentRequired, parseSettlement, pickOption, knownTokenDomain, decodeAbiString as decodeDomainString, parseEip7702Delegation, erc1271ProbeCalldata, classifyErc1271Probe, settlementVerdict, legacyReceiptAllowed, } from './x402-eip3009.js';
|
|
24
24
|
import { isSolanaLocalMode, getSolanaAddress, solanaWalletRecord, localSolBalance, localSplBalance, localSolTransfer, localSplTransfer, resolveSolanaRpc, mintDecimalsStrict, } from './local-solana.js';
|
|
25
25
|
/** True when either chain family is running non-custodially. */
|
|
26
26
|
function anyLocalMode() {
|
|
@@ -679,7 +679,7 @@ function resolveChainId(network) {
|
|
|
679
679
|
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');
|
|
680
680
|
const server = new McpServer({
|
|
681
681
|
name: 'agentwallet',
|
|
682
|
-
version: '1.13.
|
|
682
|
+
version: '1.13.9',
|
|
683
683
|
}, {
|
|
684
684
|
instructions: `AgentWallet gives AI agents their own blockchain wallets. ${anyLocalMode()
|
|
685
685
|
? 'This server runs in LOCAL (self-custody) mode: the operator supplied the key, signing happens in this process, and the only spend guards are the AGENTWALLET_MAX_TX_* environment caps. There is no server-side pause, daily limit or approval email; pause_wallet, unpause_wallet and delete_wallet are refused.'
|
|
@@ -1290,8 +1290,11 @@ async function resolveTokenDomain(chainId, asset, extra) {
|
|
|
1290
1290
|
return declared;
|
|
1291
1291
|
let name = '', version = '';
|
|
1292
1292
|
try {
|
|
1293
|
-
|
|
1294
|
-
|
|
1293
|
+
// The signing decoder (256 bytes, strict UTF-8), not the 64-char display sanitiser
|
|
1294
|
+
// get_token_info uses: a truncated or replaced name signs under a domain the
|
|
1295
|
+
// token never had (round 3).
|
|
1296
|
+
name = decodeDomainString(await ethCallHex(chainId, asset, '0x06fdde03')); // name()
|
|
1297
|
+
version = decodeDomainString(await ethCallHex(chainId, asset, '0x54fd4d50')); // version()
|
|
1295
1298
|
}
|
|
1296
1299
|
catch { /* refused below */ }
|
|
1297
1300
|
if (!name || !version) {
|
|
@@ -1609,7 +1612,7 @@ server.tool('pay_x402', 'Handle an x402 payment flow. Fetches a URL, and if the
|
|
|
1609
1612
|
const [, live] = other;
|
|
1610
1613
|
return jsonResponse({
|
|
1611
1614
|
status: 402, payment_required: true, payment_made: false,
|
|
1612
|
-
error: `This resource already holds a live authorization signed ${Math.round((Date.now() - live.signedAt) / 1000)} s ago for ${live.amount} ${tokenLabel} (the endpoint's requirement changed between answers: amount, recipient, asset or scheme). It is re-sent by calling pay_x402 again with the same arguments once the endpoint asks for the same requirement
|
|
1615
|
+
error: `This resource already holds a live authorization signed ${Math.round((Date.now() - live.signedAt) / 1000)} s ago for ${live.amount} ${tokenLabel} (the endpoint's requirement changed between answers: amount, recipient, asset or scheme). It is re-sent by calling pay_x402 again with the same arguments once the endpoint asks for the same requirement. Nothing more is signed for this resource until that authorization expires.`,
|
|
1613
1616
|
live_authorization: { amount: live.amount, valid_until: new Date(live.until).toISOString(), payer: live.payer },
|
|
1614
1617
|
requested_amount: amount, token: tokenLabel, pay_to: option.payTo, chain_id: chainId,
|
|
1615
1618
|
});
|
|
@@ -1654,7 +1657,7 @@ server.tool('pay_x402', 'Handle an x402 payment flow. Fetches a URL, and if the
|
|
|
1654
1657
|
token: tokenLabel, token_address: tokenAddress, chain_id: chainId, payer, permit2: permit2Address(chainId),
|
|
1655
1658
|
error: `This endpoint uses the x402 "upto" scheme, which settles through Permit2. Wallet ${payer} has not approved ` +
|
|
1656
1659
|
`${tokenLabel} to Permit2 on chain ${chainId}. Call approve_permit2 once for this token (a normal transaction that needs gas), ` +
|
|
1657
|
-
`
|
|
1660
|
+
`then call pay_x402 again.` + (assetPolicy.via === 'stablecoin' ? ' The operator may instead set AGENTWALLET_PERMIT2_AUTO_APPROVE=1 (registry stablecoins only); that is the operator\'s setting, not the agent\'s.' : ''),
|
|
1658
1661
|
});
|
|
1659
1662
|
}
|
|
1660
1663
|
}
|
|
@@ -1749,6 +1752,8 @@ server.tool('pay_x402', 'Handle an x402 payment flow. Fetches a URL, and if the
|
|
|
1749
1752
|
const retryServedFrom = finalUrlOf(retryRes, url);
|
|
1750
1753
|
const retryCrossOrigin = new URL(retryServedFrom).origin !== new URL(url).origin;
|
|
1751
1754
|
const verdict = settlementVerdict(retryRes.status, settlement, retryCrossOrigin);
|
|
1755
|
+
const claimedRaw = typeof settlement?.amount === 'string' && /^\d+$/.test(settlement.amount) ? settlement.amount : null;
|
|
1756
|
+
const claimedAboveMax = claimedRaw !== null && BigInt(claimedRaw) > BigInt(rawAmount);
|
|
1752
1757
|
if (retryCrossOrigin) {
|
|
1753
1758
|
retryError = `The paid request was redirected to another origin (${new URL(retryServedFrom).origin}); the payment header is never forwarded across origins, so this response did not see the payment.`;
|
|
1754
1759
|
}
|
|
@@ -1773,6 +1778,9 @@ server.tool('pay_x402', 'Handle an x402 payment flow. Fetches a URL, and if the
|
|
|
1773
1778
|
permit2_approval_tx: permit2ApprovalTx,
|
|
1774
1779
|
x402_version: x402Version,
|
|
1775
1780
|
amount,
|
|
1781
|
+
// For upto the amount is the MAXIMUM the facilitator may settle, not what was
|
|
1782
|
+
// taken; the server's claimed settled amount is shown apart and unverified (round 3).
|
|
1783
|
+
...(isUpto ? { amount_is_maximum: true, authorized_max: amount, settled_amount_claimed: serverText(settlement?.amount), settled_claim_inconsistent: claimedAboveMax } : {}),
|
|
1776
1784
|
token: tokenLabel,
|
|
1777
1785
|
token_address: tokenAddress || null,
|
|
1778
1786
|
network: option.network,
|
package/build/x402-eip3009.js
CHANGED
|
@@ -121,7 +121,14 @@ export function decodeAbiString(hex) {
|
|
|
121
121
|
if (!Number.isFinite(len) || len <= 0 || len > 256)
|
|
122
122
|
return '';
|
|
123
123
|
const bytes = Buffer.from(h.slice(128, 128 + len * 2), 'hex');
|
|
124
|
-
|
|
124
|
+
// The domain separator is keccak of the raw bytes: a name that is not valid
|
|
125
|
+
// UTF-8 cannot be reproduced from a string, so it is refused, never guessed.
|
|
126
|
+
try {
|
|
127
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return '';
|
|
131
|
+
}
|
|
125
132
|
}
|
|
126
133
|
/** Build the payment payload the resource server expects for this x402 version. */
|
|
127
134
|
export function buildPaymentPayload(x402Version, req, auth, signature, resource, extensions) {
|
|
@@ -188,7 +195,11 @@ export function parsePaymentRequired(getHeader, body) {
|
|
|
188
195
|
network and payTo are strings. A null entry, or an array where an address
|
|
189
196
|
should be, used to reach the pickers and surface a raw TypeError or a
|
|
190
197
|
coerced value (2026-10-04 round 2). Numeric amounts are kept as strings. */
|
|
191
|
-
const MAX_ACCEPTS = 64;
|
|
198
|
+
const MAX_ACCEPTS = 64;
|
|
199
|
+
/** An all-uppercase hex address is not an EIP-55 checksum and viem refuses it at signing; lowercase has no checksum to fail (round 3). Mixed case is kept: it IS a checksum. */
|
|
200
|
+
function lowerIfAllCaps(v) {
|
|
201
|
+
return (typeof v === 'string' && /^0x[0-9A-F]{40}$/.test(v) && /[A-F]/.test(v)) ? v.toLowerCase() : v;
|
|
202
|
+
} // a bound on the picker's work, well above anything a real server lists
|
|
192
203
|
export function sanitizeRequired(req) {
|
|
193
204
|
const str = (v) => (typeof v === 'string' ? v : typeof v === 'number' && Number.isFinite(v) ? String(v) : undefined);
|
|
194
205
|
const accepts = [];
|
|
@@ -196,7 +207,7 @@ export function sanitizeRequired(req) {
|
|
|
196
207
|
if (!a || typeof a !== 'object' || Array.isArray(a))
|
|
197
208
|
continue;
|
|
198
209
|
const o = a;
|
|
199
|
-
const scheme = str(o.scheme), network = str(o.network), payTo = str(o.payTo);
|
|
210
|
+
const scheme = str(o.scheme), network = str(o.network), payTo = lowerIfAllCaps(str(o.payTo));
|
|
200
211
|
if (scheme === undefined || network === undefined || payTo === undefined)
|
|
201
212
|
continue;
|
|
202
213
|
const extraIn = (o.extra && typeof o.extra === 'object' && !Array.isArray(o.extra)) ? o.extra : undefined;
|
|
@@ -206,6 +217,8 @@ export function sanitizeRequired(req) {
|
|
|
206
217
|
if (k in extra && typeof extra[k] !== 'string')
|
|
207
218
|
delete extra[k];
|
|
208
219
|
}
|
|
220
|
+
if (extra && typeof extra.token === 'string')
|
|
221
|
+
extra.token = lowerIfAllCaps(extra.token);
|
|
209
222
|
const out = { ...o, scheme, network, payTo };
|
|
210
223
|
for (const k of ['amount', 'maxAmountRequired', 'asset', 'description', 'resource', 'mimeType']) {
|
|
211
224
|
const v = str(o[k]);
|
|
@@ -214,6 +227,8 @@ export function sanitizeRequired(req) {
|
|
|
214
227
|
else
|
|
215
228
|
out[k] = v;
|
|
216
229
|
}
|
|
230
|
+
if (typeof out.asset === 'string')
|
|
231
|
+
out.asset = lowerIfAllCaps(out.asset);
|
|
217
232
|
if (extra)
|
|
218
233
|
out.extra = extra;
|
|
219
234
|
else
|
|
@@ -267,7 +282,11 @@ export function pickOption(accepts, resolveChainId, preferChain) {
|
|
|
267
282
|
const payable = [...exact, ...upto];
|
|
268
283
|
if (payable.length === 0) {
|
|
269
284
|
const schemes = Array.from(new Set(accepts.map(a => String(a.scheme).slice(0, 40)))).slice(0, 10).join(', ') || 'none';
|
|
270
|
-
|
|
285
|
+
if (onChain.length === 0 && accepts.length > 0) {
|
|
286
|
+
const nets = Array.from(new Set(accepts.map(a => String(a.network).slice(0, 40)))).slice(0, 10).join(', ');
|
|
287
|
+
return { option: null, chainId: null, reason: `No payable option: the offered network(s) ${nets} are not ones this client knows (pass a CAIP-2 id like eip155:8453 or a chain id).` };
|
|
288
|
+
}
|
|
289
|
+
const reason = onChain.some(a => a.scheme === 'upto')
|
|
271
290
|
? 'This endpoint offers the x402 "upto" scheme without an extra.facilitatorAddress (or without an asset), so the ' +
|
|
272
291
|
'Permit2 authorization cannot be bound to a facilitator. AgentWallet will not approximate it with an upfront transfer. ' +
|
|
273
292
|
'Ask the endpoint operator to publish facilitatorAddress, or an "exact" option.'
|
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.13.
|
|
4
|
+
"version": "1.13.9",
|
|
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/eip3009.test.mjs && node test/permit2-risk.test.mjs && node test/token-cap.test.mjs && node test/spl-cap.test.mjs && node test/unknown-decimals-cap.test.mjs && node test/decimals-range-cap.test.mjs && node test/local-mode.test.mjs && node test/ssrf-guard.test.mjs && node test/x402-policy.test.mjs && node test/local-guard-shape.test.mjs && node test/allowance-total-cap.test.mjs && node test/autopay-scheme.test.mjs && node test/spl-recipient-guard.test.mjs && node test/approval-binding.test.mjs && node test/ata-rent-size.test.mjs && node test/final-url-guard.test.mjs && node test/audit-1135.test.mjs && node test/local-no-autopay.test.mjs && node test/audit-1136.test.mjs && node test/delete-confirm.test.mjs && node test/audit-1138.test.mjs",
|
|
19
|
+
"test": "node test/x402-payment.test.mjs && node test/eip3009.test.mjs && node test/permit2-risk.test.mjs && node test/token-cap.test.mjs && node test/spl-cap.test.mjs && node test/unknown-decimals-cap.test.mjs && node test/decimals-range-cap.test.mjs && node test/local-mode.test.mjs && node test/ssrf-guard.test.mjs && node test/x402-policy.test.mjs && node test/local-guard-shape.test.mjs && node test/allowance-total-cap.test.mjs && node test/autopay-scheme.test.mjs && node test/spl-recipient-guard.test.mjs && node test/approval-binding.test.mjs && node test/ata-rent-size.test.mjs && node test/final-url-guard.test.mjs && node test/audit-1135.test.mjs && node test/local-no-autopay.test.mjs && node test/audit-1136.test.mjs && node test/delete-confirm.test.mjs && node test/audit-1138.test.mjs && node test/audit-1139.test.mjs",
|
|
20
20
|
"prepublishOnly": "npm run build"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|