agentwallet-mcp 1.13.2 → 1.13.4
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 +9 -6
- package/build/local-solana.d.ts +10 -0
- package/build/local-solana.js +63 -7
- package/build/local-wallet.js +4 -2
- package/build/ssrf-guard.d.ts +8 -0
- package/build/ssrf-guard.js +32 -8
- package/build/x402-payment.d.ts +6 -0
- package/build/x402-payment.js +9 -0
- package/package.json +2 -2
package/build/index.js
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
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 { assertPublicUrl, safeFetch,
|
|
16
|
-
import { deriveX402Payment, isWithinCap, lookupTrustedDecimals, assertDeclaredDecimals, nativeDecimals, SOLANA_CHAIN_IDS, assetLabel, autopayAssetAllowed, autopayEnvCap, effectiveAutopayCap, maxAuthWindowSeconds, parseDecimalPins, approvalRefusal, } from './x402-payment.js';
|
|
15
|
+
import { assertPublicUrl, safeFetch, finalUrlOf } from './ssrf-guard.js';
|
|
16
|
+
import { deriveX402Payment, isWithinCap, lookupTrustedDecimals, assertDeclaredDecimals, nativeDecimals, SOLANA_CHAIN_IDS, assetLabel, autopayAssetAllowed, autopayEnvCap, effectiveAutopayCap, maxAuthWindowSeconds, parseDecimalPins, approvalRefusal, authorizationResource, } from './x402-payment.js';
|
|
17
17
|
import { isLocalMode, getLocalAddress, localWalletRecord, localSend, localNativeBalance, localTokenBalance, localSignMessage, resolveRpcUrl, } from './local-wallet.js';
|
|
18
18
|
import { localSignAuthorization, localEthCall, localGetCode, localSignPermit2Upto } from './local-wallet.js';
|
|
19
19
|
import { WRAPPED_NATIVE } from './wrapped-native.js';
|
|
@@ -561,7 +561,7 @@ function resolveChainId(network) {
|
|
|
561
561
|
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');
|
|
562
562
|
const server = new McpServer({
|
|
563
563
|
name: 'agentwallet',
|
|
564
|
-
version: '1.13.
|
|
564
|
+
version: '1.13.4',
|
|
565
565
|
}, {
|
|
566
566
|
instructions: `AgentWallet gives AI agents their own blockchain wallets. ${anyLocalMode()
|
|
567
567
|
? '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,7 +1290,7 @@ server.tool('pay_x402', 'Handle an x402 payment flow. Fetches a URL, and if the
|
|
|
1290
1290
|
// A 402 that arrived through a cross-origin redirect cannot be paid here: the
|
|
1291
1291
|
// payment header would be stripped on the same hop and the money would go
|
|
1292
1292
|
// to whoever the redirecting server named. The agent should call the final URL.
|
|
1293
|
-
const servedFrom = initialRes
|
|
1293
|
+
const servedFrom = finalUrlOf(initialRes, url); // recorded by safeFetch itself, never read from a header the server could set
|
|
1294
1294
|
if (new URL(servedFrom).origin !== new URL(url).origin) {
|
|
1295
1295
|
return jsonResponse({ status: 402, payment_required: true, payment_made: false,
|
|
1296
1296
|
error: `The endpoint redirected to another origin (${new URL(servedFrom).origin}) before asking for payment. Call pay_x402 with that URL directly if you trust it.`,
|
|
@@ -1420,7 +1420,10 @@ server.tool('pay_x402', 'Handle an x402 payment flow. Fetches a URL, and if the
|
|
|
1420
1420
|
let permit2ApprovalTx = null;
|
|
1421
1421
|
let payerDelegationInfo = null;
|
|
1422
1422
|
let authorizationReused = false;
|
|
1423
|
-
|
|
1423
|
+
// The resource is the whole URL including its query: /buy?id=1 and /buy?id=2
|
|
1424
|
+
// are different purchases, and reusing the first signature for the second
|
|
1425
|
+
// would report a payment the second resource never received.
|
|
1426
|
+
const cacheKey = [wallet_id, chainId, tokenAddress.toLowerCase(), option.payTo.toLowerCase(), rawAmount, option.scheme, authorizationResource(url)].join('|');
|
|
1424
1427
|
const cachedAuth = fresh_authorization ? undefined : signedPayments.get(cacheKey);
|
|
1425
1428
|
for (const [k, v] of signedPayments)
|
|
1426
1429
|
if (v.until <= Date.now())
|
|
@@ -1518,7 +1521,7 @@ server.tool('pay_x402', 'Handle an x402 payment flow. Fetches a URL, and if the
|
|
|
1518
1521
|
// A second 402 means the facilitator declined (unfunded payer, expired window, bad domain...). v2 servers
|
|
1519
1522
|
// put the reason in a fresh PAYMENT-REQUIRED header, v1 servers in the body; surface it instead of {}.
|
|
1520
1523
|
let retryError = null;
|
|
1521
|
-
const retryServedFrom = retryRes
|
|
1524
|
+
const retryServedFrom = finalUrlOf(retryRes, url);
|
|
1522
1525
|
const retryCrossOrigin = new URL(retryServedFrom).origin !== new URL(url).origin;
|
|
1523
1526
|
if (retryCrossOrigin) {
|
|
1524
1527
|
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.`;
|
package/build/local-solana.d.ts
CHANGED
|
@@ -23,6 +23,16 @@ export declare function localSolBalance(chainId?: number): Promise<{
|
|
|
23
23
|
balance: string;
|
|
24
24
|
mode: string;
|
|
25
25
|
}>;
|
|
26
|
+
/**
|
|
27
|
+
* Bytes the Associated Token Account program allocates for a new token account
|
|
28
|
+
* of this mint. Classic SPL is always 165. Token-2022 adds an account-type byte
|
|
29
|
+
* and one TLV entry per account extension: ImmutableOwner on every associated
|
|
30
|
+
* account, plus whatever the mint's own extensions require.
|
|
31
|
+
*
|
|
32
|
+
* The rent charged follows this size, so the SOL cap has to be checked against
|
|
33
|
+
* it. A flat 165 under-counted every Token-2022 account (2026-10-02 report).
|
|
34
|
+
*/
|
|
35
|
+
export declare function associatedAccountSize(token2022: boolean, mintData: Uint8Array): number;
|
|
26
36
|
export declare function localSplBalance(mintStr: string, chainId?: number): Promise<{
|
|
27
37
|
address: string;
|
|
28
38
|
chain_id: number;
|
package/build/local-solana.js
CHANGED
|
@@ -222,14 +222,69 @@ export async function localSolBalance(chainId = 900) {
|
|
|
222
222
|
mode: 'local',
|
|
223
223
|
};
|
|
224
224
|
}
|
|
225
|
-
/** Which token program owns this mint
|
|
226
|
-
async function
|
|
225
|
+
/** Which token program owns this mint, and how big a token account for it is. */
|
|
226
|
+
async function mintLayout(conn, mint) {
|
|
227
227
|
const info = await conn.getAccountInfo(mint);
|
|
228
228
|
if (!info)
|
|
229
229
|
throw new Error(`Mint ${mint.toBase58()} not found on this cluster.`);
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
230
|
+
const token2022 = info.owner.equals(TOKEN_2022_PROGRAM);
|
|
231
|
+
return {
|
|
232
|
+
tokenProgram: token2022 ? TOKEN_2022_PROGRAM : TOKEN_PROGRAM,
|
|
233
|
+
ataSize: associatedAccountSize(token2022, info.data),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/** Which token program owns this mint: classic SPL or Token-2022. */
|
|
237
|
+
async function tokenProgramFor(conn, mint) {
|
|
238
|
+
return (await mintLayout(conn, mint)).tokenProgram;
|
|
239
|
+
}
|
|
240
|
+
/* Token-2022 mint extensions that make every token account for the mint carry
|
|
241
|
+
an extension of its own, and the size of that account extension's value.
|
|
242
|
+
Mirrors required_init_account_extensions in the token-2022 program. */
|
|
243
|
+
const ACCOUNT_EXTENSION_FOR_MINT_EXTENSION = {
|
|
244
|
+
1: 8, // TransferFeeConfig -> TransferFeeAmount (withheld amount, u64)
|
|
245
|
+
9: 0, // NonTransferable -> NonTransferableAccount (plus ImmutableOwner, always present below)
|
|
246
|
+
14: 1, // TransferHook -> TransferHookAccount (one flag byte)
|
|
247
|
+
26: 0, // Pausable -> PausableAccount
|
|
248
|
+
};
|
|
249
|
+
/** Highest extension type this table was written against (PermissionedBurn). */
|
|
250
|
+
const HIGHEST_KNOWN_EXTENSION = 28;
|
|
251
|
+
/** Charged for a mint extension newer than the table, so the estimate errs high, never low. */
|
|
252
|
+
const UNKNOWN_EXTENSION_ALLOWANCE = 64;
|
|
253
|
+
const TLV_HEADER = 4; // u16 type + u16 length
|
|
254
|
+
const MULTISIG_SIZE = 355; // the program pads an account that would collide with a multisig's size
|
|
255
|
+
/**
|
|
256
|
+
* Bytes the Associated Token Account program allocates for a new token account
|
|
257
|
+
* of this mint. Classic SPL is always 165. Token-2022 adds an account-type byte
|
|
258
|
+
* and one TLV entry per account extension: ImmutableOwner on every associated
|
|
259
|
+
* account, plus whatever the mint's own extensions require.
|
|
260
|
+
*
|
|
261
|
+
* The rent charged follows this size, so the SOL cap has to be checked against
|
|
262
|
+
* it. A flat 165 under-counted every Token-2022 account (2026-10-02 report).
|
|
263
|
+
*/
|
|
264
|
+
export function associatedAccountSize(token2022, mintData) {
|
|
265
|
+
if (!token2022)
|
|
266
|
+
return TOKEN_ACCOUNT_SIZE;
|
|
267
|
+
let tlvBytes = TLV_HEADER; // ImmutableOwner, zero-length value
|
|
268
|
+
const seen = new Set();
|
|
269
|
+
// Mint layout: 82 bytes of base state padded to 165, the account-type byte, then TLV entries.
|
|
270
|
+
let offset = TOKEN_ACCOUNT_SIZE + 1;
|
|
271
|
+
while (offset + TLV_HEADER <= mintData.length) {
|
|
272
|
+
const type = mintData[offset] | (mintData[offset + 1] << 8);
|
|
273
|
+
const length = mintData[offset + 2] | (mintData[offset + 3] << 8);
|
|
274
|
+
if (type === 0)
|
|
275
|
+
break; // uninitialized: the rest is padding
|
|
276
|
+
if (!seen.has(type)) {
|
|
277
|
+
seen.add(type);
|
|
278
|
+
const accountValue = ACCOUNT_EXTENSION_FOR_MINT_EXTENSION[type];
|
|
279
|
+
if (accountValue !== undefined)
|
|
280
|
+
tlvBytes += TLV_HEADER + accountValue;
|
|
281
|
+
else if (type > HIGHEST_KNOWN_EXTENSION)
|
|
282
|
+
tlvBytes += TLV_HEADER + UNKNOWN_EXTENSION_ALLOWANCE;
|
|
283
|
+
}
|
|
284
|
+
offset += TLV_HEADER + length;
|
|
285
|
+
}
|
|
286
|
+
const size = TOKEN_ACCOUNT_SIZE + 1 + tlvBytes;
|
|
287
|
+
return size === MULTISIG_SIZE ? size + 2 : size;
|
|
233
288
|
}
|
|
234
289
|
function associatedTokenAddress(owner, mint, tokenProgram) {
|
|
235
290
|
const [ata] = PublicKey.findProgramAddressSync([owner.toBuffer(), tokenProgram.toBuffer(), mint.toBuffer()], ASSOCIATED_TOKEN_PROGRAM);
|
|
@@ -327,7 +382,7 @@ export async function localSplTransfer(mintStr, to, rawAmount, decimals, chainId
|
|
|
327
382
|
const mint = new PublicKey(mintStr);
|
|
328
383
|
const recipient = new PublicKey(to);
|
|
329
384
|
await assertRecipientIsWallet(conn, recipient);
|
|
330
|
-
const tokenProgram = await
|
|
385
|
+
const { tokenProgram, ataSize } = await mintLayout(conn, mint);
|
|
331
386
|
// The instruction's decimals byte is the mint's real value, not whatever the
|
|
332
387
|
// caller passed; TransferChecked would reject a wrong one on-chain anyway.
|
|
333
388
|
decimals = await resolveMintDecimals(mintStr, chainId);
|
|
@@ -338,7 +393,8 @@ export async function localSplTransfer(mintStr, to, rawAmount, decimals, chainId
|
|
|
338
393
|
if (!destInfo) {
|
|
339
394
|
// Creating the recipient's token account costs the payer rent in SOL, an
|
|
340
395
|
// outflow the SOL cap must see (0.002 SOL a time adds up over many sends).
|
|
341
|
-
|
|
396
|
+
// Rent follows the account's real size, which for Token-2022 is above 165.
|
|
397
|
+
const rent = await conn.getMinimumBalanceForRentExemption(ataSize);
|
|
342
398
|
assertWithinSolCap(BigInt(rent));
|
|
343
399
|
tx.add(createAtaIdempotentIx(payer.publicKey, destAta, recipient, mint, tokenProgram));
|
|
344
400
|
}
|
package/build/local-wallet.js
CHANGED
|
@@ -294,7 +294,9 @@ const CAP_LAYOUTS = {
|
|
|
294
294
|
* checked, and unpriced calldata aimed at a token contract or at Permit2 is
|
|
295
295
|
* refused, because any state change there can move funds. Calls to other
|
|
296
296
|
* contracts (routers, bridges) still pass: they can only pull what an approval
|
|
297
|
-
* already
|
|
297
|
+
* already allows. Approvals granted through this guard are bounded by it; an
|
|
298
|
+
* allowance that existed before the cap was set, or was granted elsewhere, is
|
|
299
|
+
* not, and a router call can spend up to that allowance (noted 2026-10-04).
|
|
298
300
|
*
|
|
299
301
|
* Reported privately 2026-09-27: that refusal also caught our own wrap_eth and
|
|
300
302
|
* unwrap_eth, whose WETH deposit() / withdraw(uint256) calldata was unpriced,
|
|
@@ -335,7 +337,7 @@ async function assertWithinTokenCap(chainId, to, data, valueWei = 0n) {
|
|
|
335
337
|
`or deposit/withdraw on the chain's wrapped-native contract, ` +
|
|
336
338
|
`or set AGENTWALLET_ALLOW_UNKNOWN_TOKEN_CALLS=1 to allow it deliberately.`);
|
|
337
339
|
}
|
|
338
|
-
return; // an ordinary contract call; it can only pull what an approval
|
|
340
|
+
return; // an ordinary contract call; it can only pull what an existing approval allows (see the note above)
|
|
339
341
|
}
|
|
340
342
|
const wordAt = (i) => hex.slice(8 + i * 64, 8 + (i + 1) * 64);
|
|
341
343
|
let amount;
|
package/build/ssrf-guard.d.ts
CHANGED
|
@@ -98,6 +98,14 @@ export declare function headersForRedirect(headers: HeadersInit | undefined, fro
|
|
|
98
98
|
export declare const MAX_RESPONSE_BYTES: number;
|
|
99
99
|
/** Header safeFetch adds to every response: the URL the body actually came from, after redirects. */
|
|
100
100
|
export declare const FINAL_URL_HEADER = "x-agw-final-url";
|
|
101
|
+
/** The URL a safeFetch response actually came from, after every redirect. Only safeFetch can set it. */
|
|
102
|
+
export declare function finalUrlOf(res: Response, fallback: string): string;
|
|
103
|
+
/**
|
|
104
|
+
* The synthetic Response safeFetch hands back: the server's headers minus the
|
|
105
|
+
* transfer-level ones (the body is already decoded) and minus any header only
|
|
106
|
+
* this client may set, plus the final URL, which is also recorded out of band.
|
|
107
|
+
*/
|
|
108
|
+
export declare function buildFetchedResponse(status: number, statusText: string, serverHeaders: Iterable<[string, string]>, body: ArrayBuffer | null, finalUrl: string): Response;
|
|
101
109
|
/**
|
|
102
110
|
* fetch() that validates the target, and every redirect hop, against
|
|
103
111
|
* resolvePublicUrl, and connects only to the addresses that validation
|
package/build/ssrf-guard.js
CHANGED
|
@@ -330,6 +330,36 @@ export function headersForRedirect(headers, from, to, bodyDropped) {
|
|
|
330
330
|
export const MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
331
331
|
/** Header safeFetch adds to every response: the URL the body actually came from, after redirects. */
|
|
332
332
|
export const FINAL_URL_HEADER = 'x-agw-final-url';
|
|
333
|
+
/* The final URL is also recorded here, keyed by the Response object, and
|
|
334
|
+
finalUrlOf() reads this rather than the header. A server could send a
|
|
335
|
+
header of the same name (it was copied straight through and read back
|
|
336
|
+
as the real one, 2026-10-04 report); nothing a server sends can reach
|
|
337
|
+
this map. */
|
|
338
|
+
const finalUrls = new WeakMap();
|
|
339
|
+
/** The URL a safeFetch response actually came from, after every redirect. Only safeFetch can set it. */
|
|
340
|
+
export function finalUrlOf(res, fallback) {
|
|
341
|
+
return finalUrls.get(res) ?? fallback;
|
|
342
|
+
}
|
|
343
|
+
/** Response headers that only this client may set on what it hands back. */
|
|
344
|
+
const CLIENT_ONLY_RESPONSE_HEADERS = new Set([FINAL_URL_HEADER]);
|
|
345
|
+
/**
|
|
346
|
+
* The synthetic Response safeFetch hands back: the server's headers minus the
|
|
347
|
+
* transfer-level ones (the body is already decoded) and minus any header only
|
|
348
|
+
* this client may set, plus the final URL, which is also recorded out of band.
|
|
349
|
+
*/
|
|
350
|
+
export function buildFetchedResponse(status, statusText, serverHeaders, body, finalUrl) {
|
|
351
|
+
const headers = [];
|
|
352
|
+
for (const [k, v] of serverHeaders) {
|
|
353
|
+
const name = k.toLowerCase();
|
|
354
|
+
if (name === 'content-encoding' || name === 'content-length' || CLIENT_ONLY_RESPONSE_HEADERS.has(name))
|
|
355
|
+
continue;
|
|
356
|
+
headers.push([k, v]);
|
|
357
|
+
}
|
|
358
|
+
headers.push([FINAL_URL_HEADER, finalUrl]);
|
|
359
|
+
const out = new Response(body, { status, statusText, headers });
|
|
360
|
+
finalUrls.set(out, finalUrl);
|
|
361
|
+
return out;
|
|
362
|
+
}
|
|
333
363
|
/** Read a body stream into memory, refusing past `limit` decoded bytes. */
|
|
334
364
|
async function readBounded(stream, limit) {
|
|
335
365
|
if (!stream)
|
|
@@ -389,20 +419,14 @@ export async function safeFetch(url, options = {}, maxHops = 3) {
|
|
|
389
419
|
status = res.status;
|
|
390
420
|
statusText = res.statusText;
|
|
391
421
|
headers = [];
|
|
392
|
-
for (const [k, v] of res.headers)
|
|
393
|
-
// The body below is already decoded, so the transfer-level description
|
|
394
|
-
// of it would be wrong if carried over.
|
|
395
|
-
if (k === 'content-encoding' || k === 'content-length')
|
|
396
|
-
continue;
|
|
422
|
+
for (const [k, v] of res.headers)
|
|
397
423
|
headers.push([k, v]);
|
|
398
|
-
}
|
|
399
424
|
body = NULL_BODY_STATUS.has(status) ? null : await readBounded(res.body, MAX_RESPONSE_BYTES);
|
|
400
425
|
}
|
|
401
426
|
finally {
|
|
402
427
|
await agent.destroy();
|
|
403
428
|
}
|
|
404
|
-
|
|
405
|
-
const out = new Response(body, { status, statusText, headers });
|
|
429
|
+
const out = buildFetchedResponse(status, statusText, headers, body, current);
|
|
406
430
|
const isRedirect = status >= 300 && status < 400 && out.headers.has('location');
|
|
407
431
|
if (!isRedirect)
|
|
408
432
|
return out;
|
package/build/x402-payment.d.ts
CHANGED
|
@@ -191,6 +191,12 @@ export interface ApprovalWant {
|
|
|
191
191
|
* outright, before any lookup. For a hosted wallet every bound field is checked
|
|
192
192
|
* here, the wallet included, and the server checks them again when it signs.
|
|
193
193
|
*/
|
|
194
|
+
/**
|
|
195
|
+
* The part of a URL that names the resource an authorization was signed for:
|
|
196
|
+
* origin, path and query. /buy?id=1 and /buy?id=2 are different purchases
|
|
197
|
+
* (2026-10-04 report); the fragment is never sent, so it is left out.
|
|
198
|
+
*/
|
|
199
|
+
export declare function authorizationResource(url: string): string;
|
|
194
200
|
export declare function approvalRefusal(approvalId: string, row: ApprovalRow | null | undefined, want: ApprovalWant, localMode: boolean): {
|
|
195
201
|
error: string;
|
|
196
202
|
approval_status?: string | null;
|
package/build/x402-payment.js
CHANGED
|
@@ -334,6 +334,15 @@ export function isWithinCap(rawAmountRequired, trustedDecimals, capHuman) {
|
|
|
334
334
|
* outright, before any lookup. For a hosted wallet every bound field is checked
|
|
335
335
|
* here, the wallet included, and the server checks them again when it signs.
|
|
336
336
|
*/
|
|
337
|
+
/**
|
|
338
|
+
* The part of a URL that names the resource an authorization was signed for:
|
|
339
|
+
* origin, path and query. /buy?id=1 and /buy?id=2 are different purchases
|
|
340
|
+
* (2026-10-04 report); the fragment is never sent, so it is left out.
|
|
341
|
+
*/
|
|
342
|
+
export function authorizationResource(url) {
|
|
343
|
+
const u = new URL(url);
|
|
344
|
+
return u.origin + u.pathname + u.search;
|
|
345
|
+
}
|
|
337
346
|
export function approvalRefusal(approvalId, row, want, localMode) {
|
|
338
347
|
if (localMode) {
|
|
339
348
|
return {
|
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.4",
|
|
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",
|
|
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",
|
|
20
20
|
"prepublishOnly": "npm run build"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|