agentwallet-mcp 1.14.7 → 1.14.8
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 +3 -2
- package/build/index.js +84 -19
- package/build/policy.d.ts +22 -169
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ Use `AGENTWALLET_KEYFILE=/path/to/key` instead if you would rather keep the key
|
|
|
43
43
|
|
|
44
44
|
`AGENTWALLET_MAX_TX_NATIVE` is a per-transaction ceiling in native units (ETH, MATIC and so on). In hosted mode the server enforces limits; in local mode there is no server, so these guards are the only ones there are. Set them.
|
|
45
45
|
|
|
46
|
-
`AGENTWALLET_MAX_TX_TOKEN` is the equivalent ceiling for ERC-20 movement, in human units of the token. **Set this one too if you hold stablecoins.** The native cap cannot see a token transfer: an ERC-20 send carries `value = 0` with the amount in the calldata, so `AGENTWALLET_MAX_TX_NATIVE` alone leaves a USDC balance uncapped. `AGENTWALLET_MAX_TX_TOKEN` covers `transfer`, `transferFrom`, `approve`, `increaseAllowance`, Permit2 `approve`, and `deposit()` / `withdraw(uint256)` on the chain's own wrapped-native contract (so `wrap_eth` and `unwrap_eth` keep working under the cap), the approvals because an unbounded allowance is a drain waiting to happen. Any other calldata sent to a token contract or to Permit2 is refused while the cap is set, because the guard cannot price it; set `AGENTWALLET_ALLOW_UNKNOWN_TOKEN_CALLS=1` to allow such calls deliberately. `increaseAllowance` is judged on the resulting total: the current allowance is read first and the call refuses if it cannot be, so two compliant increments cannot stack a standing allowance above the cap. Calls to other contracts (routers, bridges) pass, since they can only pull what an approval already allowed; a contract counts as "other" only when it demonstrably declines `decimals()`, so an absurd or malformed answer, or an RPC that cannot be reached, refuses rather than allows. Decimals are resolved locally from the trusted registry, then the token's own `decimals()`; a token that resolves neither way is evaluated at 0 decimals, the only value that cannot fail open. For a token outside the registry the RPC is the only source of decimals, and an RPC you do not control could scale the amount and the cap together; pin such tokens with `AGENTWALLET_TOKEN_DECIMALS` (for example `8453:0x<token>=6`) and the pin is used everywhere the RPC's answer would have been.
|
|
46
|
+
`AGENTWALLET_MAX_TX_TOKEN` is the equivalent ceiling for ERC-20 movement, in human units of the token. **Set this one too if you hold stablecoins.** The native cap cannot see a token transfer: an ERC-20 send carries `value = 0` with the amount in the calldata, so `AGENTWALLET_MAX_TX_NATIVE` alone leaves a USDC balance uncapped. `AGENTWALLET_MAX_TX_TOKEN` covers `transfer`, `transferFrom`, `approve`, `increaseAllowance`, Permit2 `approve`, and `deposit()` / `withdraw(uint256)` on the chain's own wrapped-native contract (so `wrap_eth` and `unwrap_eth` keep working under the cap), the approvals because an unbounded allowance is a drain waiting to happen. ERC-721 and ERC-1155 calldata (`setApprovalForAll`, `safeTransferFrom`) is refused while the cap is set, since nothing prices an NFT; without the cap it passes the way an ERC-20 transfer of any size does, except that an operator grant is an unlimited approval and is refused everywhere by default (see `AGENTWALLET_ALLOW_UNLIMITED_APPROVAL`). Any other calldata sent to a token contract or to Permit2 is refused while the cap is set, because the guard cannot price it; set `AGENTWALLET_ALLOW_UNKNOWN_TOKEN_CALLS=1` to allow such calls deliberately. `increaseAllowance` is judged on the resulting total: the current allowance is read first and the call refuses if it cannot be, so two compliant increments cannot stack a standing allowance above the cap. Calls to other contracts (routers, bridges) pass, since they can only pull what an approval already allowed; a contract counts as "other" only when it demonstrably declines `decimals()`, so an absurd or malformed answer, or an RPC that cannot be reached, refuses rather than allows. Decimals are resolved locally from the trusted registry, then the token's own `decimals()`; a token that resolves neither way is evaluated at 0 decimals, the only value that cannot fail open. For a token outside the registry the RPC is the only source of decimals, and an RPC you do not control could scale the amount and the cap together; pin such tokens with `AGENTWALLET_TOKEN_DECIMALS` (for example `8453:0x<token>=6`) and the pin is used everywhere the RPC's answer would have been.
|
|
47
47
|
|
|
48
48
|
### Solana local signing
|
|
49
49
|
|
|
@@ -340,6 +340,7 @@ Which guards apply depends on who holds the key.
|
|
|
340
340
|
- `AGENTWALLET_MAX_FEE_NATIVE`: the most one transaction may burn in fees, gas x max fee per gas, in native units (default `0.01`). The value cap alone did not bound fees: a node answering an absurd tip or gas estimate could take the whole balance as priority fee on a cap-compliant transfer. Caller-supplied `gas_limit` / `max_fee` / `priority_fee` are honoured and then bounded by this cap
|
|
341
341
|
- `AGENTWALLET_MAX_TX_TOKEN`: ceiling per ERC-20 transfer or approval, in human units of the token
|
|
342
342
|
- `AGENTWALLET_ALLOW_UNKNOWN_TOKEN_CALLS`: set to `1` to let calldata the token cap cannot price reach a token contract or Permit2 (refused by default while the cap is set)
|
|
343
|
+
- `AGENTWALLET_ALLOW_UNLIMITED_APPROVAL`: set to `1` to let an unlimited ERC-20 allowance (`approve_token` with `"max"`, `approve_permit2` with no amount) or an NFT operator grant (`setApprovalForAll`) through while no spend policy is set; refused by default, and always refused under a policy
|
|
343
344
|
- `AGENTWALLET_TOKEN_DECIMALS`: pin decimals for tokens outside the registry (`8453:0x<token>=6,<mint>=9`); a pin is used for amounts and caps instead of asking the RPC
|
|
344
345
|
- `AGENTWALLET_MAX_TX_SOL`: ceiling per native SOL transfer (also charged for the rent of a recipient token account an SPL send has to create). It does not cover SPL token amounts: `AGENTWALLET_MAX_TX_TOKEN` does, on Solana as on EVM
|
|
345
346
|
- `AGENTWALLET_MAX_AUTOPAY`: ceiling per x402 auto-payment in stablecoin units (default `1`); `AGENTWALLET_AUTOPAY_ASSETS` opts native or non-stable assets in; `AGENTWALLET_X402_MAX_TIMEOUT` bounds how long a signed authorization stays valid (default 3600 seconds)
|
|
@@ -375,7 +376,7 @@ A per-transaction cap bounds one signature. It cannot see that the five hundredt
|
|
|
375
376
|
|
|
376
377
|
- **`budget`**: rolling dollar budgets (last 24 hours, last 30 days, lifetime) across every chain, counting x402 payments, stablecoin transfers and stablecoin approvals in registry stablecoins. A spend the budget cannot price (a token outside the registry) is refused while a budget is set, unless `"unpriced_spends": "allow"`. An SPL send is priced at the mint's own decimals; a `token_decimals` hint that disagrees with the mint is refused before any request is made.
|
|
377
378
|
- **`native_budget`**: the same per chain, in the chain's own coin, counting native transfers and the native value sent into any contract (`wrap_eth`, `send_transaction` with value and calldata), including value carried on a token transfer or approval call (a payable token contract keeps it, so it is a second spend, to that contract) and, in local Solana mode, the rent an SPL send pays to create the recipient's token account (the recipient owns that account and can close it for the lamports; the ledger records it as its own line, method `rent`, to the recipient, and `assets.allow` does not need to list `native` for it).
|
|
378
|
-
- **Approvals are spends.** An ERC-20 allowance (`approve_token`, `approve_permit2`, `increaseAllowance`, `approveAndCall`, Permit2 `approve`, or the same calldata through `send_transaction`) is what the spender will take later, so it is priced, checked and reserved on the ledger when it is granted, the spender is checked against the recipient lists, and an unlimited approval (2^128 base units or more) is refused
|
|
379
|
+
- **Approvals are spends.** An ERC-20 allowance (`approve_token`, `approve_permit2`, `increaseAllowance`, `approveAndCall`, Permit2 `approve`, or the same calldata through `send_transaction`) is what the spender will take later, so it is priced, checked and reserved on the ledger when it is granted, the spender is checked against the recipient lists, and an unlimited approval (2^128 base units or more) is refused, since no budget can count it; grant an exact amount. An NFT operator grant (`setApprovalForAll`, ERC-777 `authorizeOperator`) is unbounded by construction and refused the same way, and an ERC-721 or ERC-1155 transfer is a spend of the collection to the recipient that no budget can price, so it is refused under a dollar budget unless `unpriced_spends` is `"allow"`, with the recipient checked either way. A zero-value call to any other contract (a router, a bridge) reserves nothing: it can only pull what an approval already granted.
|
|
379
380
|
- **`hosts`**, **`per_host`**: which endpoints `pay_x402` may pay, and how much per host per day, per month and per payment. A denied host is never contacted, not even for a quote. A host is matched by its canonical DNS name (lowercase, the root label's trailing dot removed), so `api.example.com.` and its percent-encoded spelling are `api.example.com` to every list and to the ledger's per-host sums.
|
|
380
381
|
- **`recipients`**: where funds may go (transfers, x402 `payTo`, approval spenders, contracts that receive native value). The allow list covers those funds destinations; the contract a zero-value call is aimed at (a token being approved, a router) is checked against the deny list and the chain list.
|
|
381
382
|
- **`chains`**, **`assets`**: allow lists; an asset entry can be scoped `"8453:0x..."` or be the word `native`.
|
package/build/index.js
CHANGED
|
@@ -13,13 +13,15 @@ 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
15
|
// Validation errors used to echo an unknown key or enum value unbounded, a reflector for a second-stage injection (round 6).
|
|
16
|
-
z.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
z.config({
|
|
17
|
+
customError: (issue) => {
|
|
18
|
+
const cut = (v) => { const t = String(v); return t.length > 80 ? t.slice(0, 80) + '…' : t; };
|
|
19
|
+
if (issue.code === 'unrecognized_keys')
|
|
20
|
+
return `Unrecognized key(s) in object: ${issue.keys.map((k) => `'${cut(k)}'`).join(', ')}`;
|
|
21
|
+
if (issue.code === 'invalid_value')
|
|
22
|
+
return `Invalid enum value. Expected ${issue.values.map((o) => `'${String(o)}'`).join(' | ')}, received '${cut(issue.input)}'`;
|
|
23
|
+
return undefined;
|
|
24
|
+
},
|
|
23
25
|
});
|
|
24
26
|
import { assertPublicUrl, safeFetch, finalUrlOf } from './ssrf-guard.js';
|
|
25
27
|
import { deriveX402Payment, isWithinCap, lookupTrustedDecimals, assertDeclaredDecimals, nativeDecimals, SOLANA_CHAIN_IDS, assetLabel, autopayAssetAllowed, autopayEnvCap, effectiveAutopayCap, maxAuthWindowSeconds, parseDecimalPins, approvalRefusal, authorizationResource, resolveNetworkChainId, normalizeRawAmount, parseAutopayAssets, assetSupportsExact, approvalResourceUrl, } from './x402-payment.js';
|
|
@@ -132,8 +134,43 @@ const SPEND_LAYOUTS = {
|
|
|
132
134
|
'39509351': { kind: 'approval', what: 'increaseAllowance', toIndex: 0, amountIndex: 1 }, // increaseAllowance(address,uint256)
|
|
133
135
|
cae9ca51: { kind: 'approval', what: 'approveAndCall', toIndex: 0, amountIndex: 1 }, // approveAndCall(address,uint256,bytes)
|
|
134
136
|
'87517c45': { kind: 'approval', what: 'Permit2 approve', toIndex: 1, amountIndex: 2, tokenIndex: 0, permit2Only: true }, // Permit2.approve(address,address,uint160,uint48)
|
|
137
|
+
// ERC-721 / ERC-1155 movers and operator grants (reported privately
|
|
138
|
+
// 2026-10-26, 1.14.8): none of these was decoded, so an NFT approval or
|
|
139
|
+
// transfer fell through to 'contract' with value 0 and reached the signer
|
|
140
|
+
// under a policy with no budget line, no ledger line and no recipient check.
|
|
141
|
+
// An operator grant is an unbounded approval by construction (`operator`;
|
|
142
|
+
// amountIndex -1 means there is no amount word, otherwise the bool at that
|
|
143
|
+
// word says grant or revoke). A transfer moves a token no budget can price
|
|
144
|
+
// (`nft`; amountIndex -1 means one token), so under a dollar budget it is an
|
|
145
|
+
// unpriced spend, refused unless unpriced_spends allows it, and its
|
|
146
|
+
// recipient is checked either way.
|
|
147
|
+
a22cb465: { kind: 'approval', what: 'setApprovalForAll', toIndex: 0, amountIndex: 1, nft: true, operator: true }, // setApprovalForAll(address,bool)
|
|
148
|
+
'959b8c3f': { kind: 'approval', what: 'ERC-777 authorizeOperator', toIndex: 0, amountIndex: -1, operator: true }, // authorizeOperator(address)
|
|
149
|
+
'42842e0e': { kind: 'token', what: 'ERC-721 safeTransferFrom', toIndex: 1, amountIndex: -1, nft: true }, // safeTransferFrom(address,address,uint256)
|
|
150
|
+
b88d4fde: { kind: 'token', what: 'ERC-721 safeTransferFrom', toIndex: 1, amountIndex: -1, nft: true }, // safeTransferFrom(address,address,uint256,bytes)
|
|
151
|
+
f242432a: { kind: 'token', what: 'ERC-1155 safeTransferFrom', toIndex: 1, amountIndex: 3, nft: true }, // safeTransferFrom(address,address,uint256,uint256,bytes)
|
|
152
|
+
'2eb2c2d6': { kind: 'token', what: 'ERC-1155 safeBatchTransferFrom', toIndex: 1, amountIndex: 3, nft: true, batch: true }, // safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)
|
|
135
153
|
};
|
|
136
154
|
const UNBOUNDED_APPROVAL = 1n << 128n;
|
|
155
|
+
const UINT256_MAX = (1n << 256n) - 1n;
|
|
156
|
+
/**
|
|
157
|
+
* The sum of the `amounts` array of an ERC-1155 safeBatchTransferFrom, read
|
|
158
|
+
* through the caller's word reader so a short word refuses the whole call.
|
|
159
|
+
* `offsetWord` holds the array's byte offset from the start of the arguments.
|
|
160
|
+
*/
|
|
161
|
+
function batchTotal(wordAt, offsetWord) {
|
|
162
|
+
const offset = BigInt('0x' + wordAt(offsetWord));
|
|
163
|
+
if (offset % 32n !== 0n || offset > 1n << 20n)
|
|
164
|
+
throw new Error('policy: calldata for ERC-1155 safeBatchTransferFrom has a malformed amounts offset; refused rather than treated as a plain contract call. Nothing was signed.');
|
|
165
|
+
const base = Number(offset / 32n);
|
|
166
|
+
const count = BigInt('0x' + wordAt(base));
|
|
167
|
+
if (count > 4096n)
|
|
168
|
+
throw new Error('policy: calldata for ERC-1155 safeBatchTransferFrom names more than 4096 amounts; refused. Nothing was signed.');
|
|
169
|
+
let total = 0n;
|
|
170
|
+
for (let i = 0; i < Number(count); i++)
|
|
171
|
+
total += BigInt('0x' + wordAt(base + 1 + i));
|
|
172
|
+
return total;
|
|
173
|
+
}
|
|
137
174
|
async function describeSend(body, chainId) {
|
|
138
175
|
if (body.token_mint) {
|
|
139
176
|
const mint = String(body.token_mint);
|
|
@@ -175,14 +212,27 @@ async function describeSend(body, chainId) {
|
|
|
175
212
|
};
|
|
176
213
|
const token = layout.tokenIndex === undefined ? to : '0x' + wordAt(layout.tokenIndex).slice(24);
|
|
177
214
|
const counterparty = '0x' + wordAt(layout.toIndex).slice(24);
|
|
178
|
-
const amount = BigInt('0x' + wordAt(layout.amountIndex));
|
|
179
215
|
// The call's own value does not vanish because the calldata was
|
|
180
216
|
// recognised: the contract keeps whatever rides along. It is a second
|
|
181
217
|
// spend, native, to the contract (reported privately 2026-10-23, 1.14.4).
|
|
182
218
|
const carried = BigInt(value) > 0n ? { raw: value, to } : undefined;
|
|
219
|
+
if (layout.operator) {
|
|
220
|
+
// setApprovalForAll(operator, false) takes a grant away and moves
|
|
221
|
+
// nothing: a zero-value call to the collection, reserving nothing.
|
|
222
|
+
const granted = layout.amountIndex < 0 || BigInt('0x' + wordAt(layout.amountIndex)) !== 0n;
|
|
223
|
+
if (!granted)
|
|
224
|
+
return { kind: 'contract', to, asset: '', raw: value, decimals: nativeDecimals(chainId) };
|
|
225
|
+
return { kind: 'approval', to: counterparty, asset: token, raw: UINT256_MAX.toString(), decimals: 0, unbounded: true, ...(carried ? { carried } : {}) };
|
|
226
|
+
}
|
|
227
|
+
const amount = layout.amountIndex < 0 ? 1n : layout.batch ? batchTotal(wordAt, layout.amountIndex) : BigInt('0x' + wordAt(layout.amountIndex));
|
|
183
228
|
if (layout.kind === 'approval' && amount >= UNBOUNDED_APPROVAL) {
|
|
184
229
|
return { kind: 'approval', to: counterparty, asset: token, raw: amount.toString(), decimals: 0, unbounded: true, ...(carried ? { carried } : {}) };
|
|
185
230
|
}
|
|
231
|
+
if (layout.nft) {
|
|
232
|
+
// A collection has no decimals and no price: whole tokens, unpriced,
|
|
233
|
+
// to the recipient, recorded under the collection's address.
|
|
234
|
+
return { kind: layout.kind, to: counterparty, asset: token, raw: amount.toString(), decimals: 0, ...(carried ? { carried } : {}) };
|
|
235
|
+
}
|
|
186
236
|
// Decimals matter here only to price a registry stablecoin (resolved
|
|
187
237
|
// offline) and to print the ledger amount. A token the registry and the
|
|
188
238
|
// node cannot place is unpriced whatever its decimals, so a failed lookup
|
|
@@ -238,12 +288,25 @@ function assertSendTarget(d, chainId) {
|
|
|
238
288
|
* carried on a token or approval call is a second spend, native, to the
|
|
239
289
|
* called contract (1.14.4); the rent a local SPL send pays for the recipient's
|
|
240
290
|
* token account is a native spend to the recipient, method 'rent' (1.14.7).
|
|
241
|
-
* Throws for an unbounded approval under a policy
|
|
291
|
+
* Throws for an unbounded approval: under a policy always, and otherwise
|
|
292
|
+
* unless the operator allowed it.
|
|
242
293
|
*/
|
|
294
|
+
const unlimitedApprovalsAllowed = () => !POLICY.path && process.env.AGENTWALLET_ALLOW_UNLIMITED_APPROVAL === '1';
|
|
295
|
+
const unlimitedApprovalsReport = () => POLICY.path
|
|
296
|
+
? 'refused (a spend policy is set; no budget can count an unlimited allowance)'
|
|
297
|
+
: unlimitedApprovalsAllowed() ? 'allowed by the operator (AGENTWALLET_ALLOW_UNLIMITED_APPROVAL=1)' : 'refused by default (the operator can set AGENTWALLET_ALLOW_UNLIMITED_APPROVAL=1 while no policy is set)';
|
|
243
298
|
function spendInputsOf(d, chainId, tool, wallet, ctx = {}) {
|
|
244
|
-
if (d.kind === 'approval' && d.unbounded
|
|
245
|
-
|
|
246
|
-
|
|
299
|
+
if (d.kind === 'approval' && d.unbounded) {
|
|
300
|
+
// The README promised an unlimited approval was "refused outright" while
|
|
301
|
+
// the code refused it only under a policy (reported privately 2026-10-26,
|
|
302
|
+
// 1.14.8). Now the default everywhere; only the operator opens it, and
|
|
303
|
+
// only where no policy is set.
|
|
304
|
+
if (POLICY.path)
|
|
305
|
+
throw new Error(`policy: an unlimited approval of ${assetLabel(chainId, d.asset)} to ${d.to} cannot be counted against any budget, and under a spend policy every approval is a bounded spend. ` +
|
|
306
|
+
`Approve an exact amount instead. Nothing was signed.`);
|
|
307
|
+
if (!unlimitedApprovalsAllowed())
|
|
308
|
+
throw new Error(`an unlimited approval of ${assetLabel(chainId, d.asset)} to ${d.to} is refused by default: it is a standing grant over the whole balance. ` +
|
|
309
|
+
`Approve an exact amount instead. Only the operator can allow unlimited approvals (AGENTWALLET_ALLOW_UNLIMITED_APPROVAL=1, never under a spend policy). Nothing was signed.`);
|
|
247
310
|
}
|
|
248
311
|
const base = { tool, mode: anyLocalMode() ? 'local' : 'hosted', wallet: anyLocalMode() ? 'local' : wallet, chainId, host: ctx.host ?? null, resource: ctx.resource ?? null };
|
|
249
312
|
const out = [];
|
|
@@ -1066,7 +1129,7 @@ function validateGuardEnv() {
|
|
|
1066
1129
|
throw new Error(`Unknown environment variable ${k}. AgentWallet refuses to start on an unrecognised AGENTWALLET_* name so that a misspelled cap cannot run uncapped. Known names: ${[...KNOWN_ENV].join(', ')}, AGENTWALLET_RPC_<chainId>, AGENTWALLET_SOLANA_RPC_<chainId>.`);
|
|
1067
1130
|
}
|
|
1068
1131
|
}
|
|
1069
|
-
const KNOWN_ENV = new Set(["AGENTWALLET_ALLOW_UNKNOWN_TOKEN_CALLS", "AGENTWALLET_API_URL", "AGENTWALLET_APPROVALS", "AGENTWALLET_AUTOPAY_ASSETS", "AGENTWALLET_CHAIN_ID", "AGENTWALLET_DASHBOARD_URL", "AGENTWALLET_KEYFILE", "AGENTWALLET_KEYFILE_PASSPHRASE", "AGENTWALLET_KEYFILE_PASSPHRASE_FILE", "AGENTWALLET_LEDGER", "AGENTWALLET_LEGACY_PAYWALL_ORIGINS", "AGENTWALLET_MAX_AUTOPAY", "AGENTWALLET_MAX_AUTOPAY_PER_HOUR", "AGENTWALLET_MAX_FEE_NATIVE", "AGENTWALLET_MAX_TX_NATIVE", "AGENTWALLET_MAX_TX_SOL", "AGENTWALLET_MAX_TX_TOKEN", "AGENTWALLET_PASS", "AGENTWALLET_PERMIT2_AUTO_APPROVE", "AGENTWALLET_POLICY", "AGENTWALLET_PRIVATE_KEY", "AGENTWALLET_RPC_URL", "AGENTWALLET_SOLANA_KEY", "AGENTWALLET_SOLANA_KEYFILE", "AGENTWALLET_SOLANA_RPC", "AGENTWALLET_TOKEN_DECIMALS", "AGENTWALLET_TOKEN_RISK", "AGENTWALLET_USER", "AGENTWALLET_WALLET_ID", "AGENTWALLET_WEBHOOK_SECRET", "AGENTWALLET_WEBHOOK_URL", "AGENTWALLET_X402_MAX_TIMEOUT"]);
|
|
1132
|
+
const KNOWN_ENV = new Set(["AGENTWALLET_ALLOW_UNKNOWN_TOKEN_CALLS", "AGENTWALLET_ALLOW_UNLIMITED_APPROVAL", "AGENTWALLET_API_URL", "AGENTWALLET_APPROVALS", "AGENTWALLET_AUTOPAY_ASSETS", "AGENTWALLET_CHAIN_ID", "AGENTWALLET_DASHBOARD_URL", "AGENTWALLET_KEYFILE", "AGENTWALLET_KEYFILE_PASSPHRASE", "AGENTWALLET_KEYFILE_PASSPHRASE_FILE", "AGENTWALLET_LEDGER", "AGENTWALLET_LEGACY_PAYWALL_ORIGINS", "AGENTWALLET_MAX_AUTOPAY", "AGENTWALLET_MAX_AUTOPAY_PER_HOUR", "AGENTWALLET_MAX_FEE_NATIVE", "AGENTWALLET_MAX_TX_NATIVE", "AGENTWALLET_MAX_TX_SOL", "AGENTWALLET_MAX_TX_TOKEN", "AGENTWALLET_PASS", "AGENTWALLET_PERMIT2_AUTO_APPROVE", "AGENTWALLET_POLICY", "AGENTWALLET_PRIVATE_KEY", "AGENTWALLET_RPC_URL", "AGENTWALLET_SOLANA_KEY", "AGENTWALLET_SOLANA_KEYFILE", "AGENTWALLET_SOLANA_RPC", "AGENTWALLET_TOKEN_DECIMALS", "AGENTWALLET_TOKEN_RISK", "AGENTWALLET_USER", "AGENTWALLET_WALLET_ID", "AGENTWALLET_WEBHOOK_SECRET", "AGENTWALLET_WEBHOOK_URL", "AGENTWALLET_X402_MAX_TIMEOUT"]);
|
|
1070
1133
|
/**
|
|
1071
1134
|
* Register a tool whose arguments are strict: an unknown key (max_payemnt for
|
|
1072
1135
|
* max_payment) is an error that names it, not a silently dropped guard. The
|
|
@@ -1192,7 +1255,7 @@ function resolveChainId(network) {
|
|
|
1192
1255
|
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');
|
|
1193
1256
|
const server = new McpServer({
|
|
1194
1257
|
name: 'agentwallet',
|
|
1195
|
-
version: '1.14.
|
|
1258
|
+
version: '1.14.8',
|
|
1196
1259
|
}, {
|
|
1197
1260
|
instructions: `AgentWallet gives AI agents their own blockchain wallets. ${anyLocalMode()
|
|
1198
1261
|
? '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 environment caps (AGENTWALLET_MAX_TX_NATIVE / _TOKEN / _SOL per transaction, AGENTWALLET_MAX_AUTOPAY and AGENTWALLET_AUTOPAY_ASSETS for x402, AGENTWALLET_X402_MAX_TIMEOUT for authorization windows). There is no server-side pause, daily limit or approval email; pause_wallet, unpause_wallet and delete_wallet are refused.'
|
|
@@ -1537,12 +1600,12 @@ tool('call_contract', 'Execute a read-only call against a smart contract (eth_ca
|
|
|
1537
1600
|
// ─── Tool: approve_token ────────────────────────────────────────
|
|
1538
1601
|
tool('approve_token', 'Approve a spender contract to transfer ERC-20 tokens on your behalf. ' +
|
|
1539
1602
|
'Required before interacting with any DeFi protocol (DEXs, lending, etc.). ' +
|
|
1540
|
-
'
|
|
1541
|
-
'Under a spend policy an approval is a spend: it counts against the budgets
|
|
1603
|
+
'Specify an exact amount. "max" (an unlimited allowance) is refused by default and always under a spend policy; only the operator can allow it, with AGENTWALLET_ALLOW_UNLIMITED_APPROVAL=1 while no policy is set. ' +
|
|
1604
|
+
'Under a spend policy an approval is a spend: it counts against the budgets and the spender is checked as a recipient.', {
|
|
1542
1605
|
wallet_id: z.number().int().describe('Wallet ID'),
|
|
1543
1606
|
token: z.string().regex(/^0x[a-fA-F0-9]{40}$/).describe('ERC-20 token contract address'),
|
|
1544
1607
|
spender: z.string().regex(/^0x[a-fA-F0-9]{40}$/).describe('Contract address to approve as spender'),
|
|
1545
|
-
amount: z.string().describe('Amount to approve in human-readable format (e.g. "1000")
|
|
1608
|
+
amount: z.string().describe('Amount to approve in human-readable format (e.g. "1000"); "max" means unlimited and is refused unless the operator allowed it'),
|
|
1546
1609
|
chain_id: z.number().int().describe('Chain ID'),
|
|
1547
1610
|
decimals: z.number().int().min(0).max(36).optional().describe('Token decimals. Resolved from the registry or the token contract when omitted; if given, it must match'),
|
|
1548
1611
|
}, async ({ wallet_id, token, spender, amount, chain_id, decimals: givenDecimals }) => {
|
|
@@ -2442,8 +2505,8 @@ tool('check_approval', 'Status of a payment approval created when pay_x402 excee
|
|
|
2442
2505
|
'Returns pending, approved, denied or expired. Once approved, call pay_x402 again with approval_id.', { approval_id: z.string().describe('Approval id returned by pay_x402') }, async ({ approval_id }) => jsonResponse(await api(`/approvals/${encodeURIComponent(approval_id)}`, 'GET', undefined, { 'X-AGW-SKIP-X402': 'true' })));
|
|
2443
2506
|
// ─── Tool: approve_permit2 ──────────────────────────────────────
|
|
2444
2507
|
tool('approve_permit2', 'One-time ERC-20 approval of a token to the Permit2 contract, needed before paying x402 "upto" endpoints with that token. ' +
|
|
2445
|
-
'A normal on-chain transaction (needs gas).
|
|
2446
|
-
'
|
|
2508
|
+
'A normal on-chain transaction (needs gas). Pass amount to grant a bounded allowance; with no amount it asks for the maximum, which is refused by default, under AGENTWALLET_MAX_TX_TOKEN, and always under a spend policy (where the allowance counts against the budgets). ' +
|
|
2509
|
+
'Only the operator can allow a maximum approval, with AGENTWALLET_ALLOW_UNLIMITED_APPROVAL=1 while no policy is set. ' +
|
|
2447
2510
|
'Permit2 itself only moves what each signed authorization allows.', {
|
|
2448
2511
|
wallet_id: z.number().int().describe('Wallet ID'),
|
|
2449
2512
|
token: z.string().regex(/^0x[a-fA-F0-9]{40}$/).describe('ERC-20 token address (e.g. USDC on Base)'),
|
|
@@ -2907,6 +2970,7 @@ tool('wallet_mode', 'Report whether this server is signing locally (self-custody
|
|
|
2907
2970
|
api_base: redactUrl(API_BASE),
|
|
2908
2971
|
spend_policy: POLICY.path ? { path: POLICY.path, sha256: POLICY.hash, name: POLICY.policy.name ?? null, ledger: POLICY.ledgerPath, see: 'spend_policy' } : 'none (AGENTWALLET_POLICY not set)',
|
|
2909
2972
|
client_guards: {
|
|
2973
|
+
unlimited_approvals: unlimitedApprovalsReport(),
|
|
2910
2974
|
autopay_cap_usd: autopayEnvCap(),
|
|
2911
2975
|
internal_autopay_armed: Boolean(X402_WALLET_ID),
|
|
2912
2976
|
max_autopay_per_hour: AUTOPAY_PER_HOUR,
|
|
@@ -2933,6 +2997,7 @@ tool('wallet_mode', 'Report whether this server is signing locally (self-custody
|
|
|
2933
2997
|
x402_max_timeout_seconds: maxAuthWindowSeconds(),
|
|
2934
2998
|
per_tx_cap_token: (process.env.AGENTWALLET_MAX_TX_TOKEN || '').trim() || 'not set (ERC-20 and SPL transfers are uncapped)',
|
|
2935
2999
|
allow_unknown_token_calls: process.env.AGENTWALLET_ALLOW_UNKNOWN_TOKEN_CALLS === '1',
|
|
3000
|
+
unlimited_approvals: unlimitedApprovalsReport(),
|
|
2936
3001
|
token_decimals_pins: parseDecimalPins(process.env.AGENTWALLET_TOKEN_DECIMALS).size,
|
|
2937
3002
|
token_risk_lookup: process.env.AGENTWALLET_TOKEN_RISK !== '0',
|
|
2938
3003
|
permit2_auto_approve: process.env.AGENTWALLET_PERMIT2_AUTO_APPROVE === '1',
|
package/build/policy.d.ts
CHANGED
|
@@ -2,197 +2,50 @@ import { z } from 'zod';
|
|
|
2
2
|
export declare const PolicySchema: z.ZodObject<{
|
|
3
3
|
version: z.ZodLiteral<1>;
|
|
4
4
|
name: z.ZodOptional<z.ZodString>;
|
|
5
|
-
/** Rolling budgets in stablecoin (dollar) units across every chain: x402 payments and stablecoin transfers. */
|
|
6
5
|
budget: z.ZodOptional<z.ZodObject<{
|
|
7
6
|
daily: z.ZodOptional<z.ZodString>;
|
|
8
7
|
monthly: z.ZodOptional<z.ZodString>;
|
|
9
8
|
total: z.ZodOptional<z.ZodString>;
|
|
10
|
-
},
|
|
11
|
-
total?: string | undefined;
|
|
12
|
-
daily?: string | undefined;
|
|
13
|
-
monthly?: string | undefined;
|
|
14
|
-
}, {
|
|
15
|
-
total?: string | undefined;
|
|
16
|
-
daily?: string | undefined;
|
|
17
|
-
monthly?: string | undefined;
|
|
18
|
-
}>>;
|
|
19
|
-
/** Rolling budgets for the chain's own coin, keyed by chain id, in that coin's units. */
|
|
9
|
+
}, z.core.$strict>>;
|
|
20
10
|
native_budget: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
21
11
|
daily: z.ZodOptional<z.ZodString>;
|
|
22
12
|
monthly: z.ZodOptional<z.ZodString>;
|
|
23
13
|
total: z.ZodOptional<z.ZodString>;
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}, {
|
|
29
|
-
total?: string | undefined;
|
|
30
|
-
daily?: string | undefined;
|
|
31
|
-
monthly?: string | undefined;
|
|
32
|
-
}>>>;
|
|
33
|
-
/** What to do with a spend the budget cannot price (a token outside the registry). Default refuse when a budget is set. */
|
|
34
|
-
unpriced_spends: z.ZodOptional<z.ZodEnum<["refuse", "allow"]>>;
|
|
35
|
-
hosts: z.ZodOptional<z.ZodObject<{
|
|
36
|
-
allow: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
37
|
-
deny: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
38
|
-
}, "strict", z.ZodTypeAny, {
|
|
39
|
-
allow?: string[] | undefined;
|
|
40
|
-
deny?: string[] | undefined;
|
|
41
|
-
}, {
|
|
42
|
-
allow?: string[] | undefined;
|
|
43
|
-
deny?: string[] | undefined;
|
|
14
|
+
}, z.core.$strict>>>;
|
|
15
|
+
unpriced_spends: z.ZodOptional<z.ZodEnum<{
|
|
16
|
+
allow: "allow";
|
|
17
|
+
refuse: "refuse";
|
|
44
18
|
}>>;
|
|
45
|
-
|
|
19
|
+
hosts: z.ZodOptional<z.ZodObject<{
|
|
20
|
+
allow: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
21
|
+
deny: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
22
|
+
}, z.core.$strict>>;
|
|
46
23
|
per_host: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
47
24
|
daily: z.ZodOptional<z.ZodString>;
|
|
48
25
|
monthly: z.ZodOptional<z.ZodString>;
|
|
49
26
|
per_payment: z.ZodOptional<z.ZodString>;
|
|
50
|
-
},
|
|
51
|
-
daily?: string | undefined;
|
|
52
|
-
monthly?: string | undefined;
|
|
53
|
-
per_payment?: string | undefined;
|
|
54
|
-
}, {
|
|
55
|
-
daily?: string | undefined;
|
|
56
|
-
monthly?: string | undefined;
|
|
57
|
-
per_payment?: string | undefined;
|
|
58
|
-
}>>>;
|
|
27
|
+
}, z.core.$strict>>>;
|
|
59
28
|
recipients: z.ZodOptional<z.ZodObject<{
|
|
60
|
-
allow: z.ZodOptional<z.ZodArray<z.ZodString
|
|
61
|
-
deny: z.ZodOptional<z.ZodArray<z.ZodString
|
|
62
|
-
},
|
|
63
|
-
allow?: string[] | undefined;
|
|
64
|
-
deny?: string[] | undefined;
|
|
65
|
-
}, {
|
|
66
|
-
allow?: string[] | undefined;
|
|
67
|
-
deny?: string[] | undefined;
|
|
68
|
-
}>>;
|
|
29
|
+
allow: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
30
|
+
deny: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
31
|
+
}, z.core.$strict>>;
|
|
69
32
|
chains: z.ZodOptional<z.ZodObject<{
|
|
70
|
-
allow: z.ZodOptional<z.ZodArray<z.ZodNumber
|
|
71
|
-
},
|
|
72
|
-
allow?: number[] | undefined;
|
|
73
|
-
}, {
|
|
74
|
-
allow?: number[] | undefined;
|
|
75
|
-
}>>;
|
|
33
|
+
allow: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
34
|
+
}, z.core.$strict>>;
|
|
76
35
|
assets: z.ZodOptional<z.ZodObject<{
|
|
77
|
-
allow: z.ZodOptional<z.ZodArray<z.ZodString
|
|
78
|
-
},
|
|
79
|
-
allow?: string[] | undefined;
|
|
80
|
-
}, {
|
|
81
|
-
allow?: string[] | undefined;
|
|
82
|
-
}>>;
|
|
83
|
-
/** Ask the human (MCP elicitation) above `above`; in local mode an accepted ask may admit up to `max`, above the per-payment cap. */
|
|
36
|
+
allow: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
37
|
+
}, z.core.$strict>>;
|
|
84
38
|
approval: z.ZodOptional<z.ZodObject<{
|
|
85
39
|
above: z.ZodOptional<z.ZodString>;
|
|
86
40
|
max: z.ZodOptional<z.ZodString>;
|
|
87
41
|
timeout_seconds: z.ZodOptional<z.ZodNumber>;
|
|
88
|
-
},
|
|
89
|
-
max?: string | undefined;
|
|
90
|
-
above?: string | undefined;
|
|
91
|
-
timeout_seconds?: number | undefined;
|
|
92
|
-
}, {
|
|
93
|
-
max?: string | undefined;
|
|
94
|
-
above?: string | undefined;
|
|
95
|
-
timeout_seconds?: number | undefined;
|
|
96
|
-
}>>;
|
|
42
|
+
}, z.core.$strict>>;
|
|
97
43
|
tools: z.ZodOptional<z.ZodObject<{
|
|
98
|
-
allow: z.ZodOptional<z.ZodArray<z.ZodString
|
|
99
|
-
deny: z.ZodOptional<z.ZodArray<z.ZodString
|
|
100
|
-
},
|
|
101
|
-
allow?: string[] | undefined;
|
|
102
|
-
deny?: string[] | undefined;
|
|
103
|
-
}, {
|
|
104
|
-
allow?: string[] | undefined;
|
|
105
|
-
deny?: string[] | undefined;
|
|
106
|
-
}>>;
|
|
107
|
-
/** Ledger path; relative paths resolve against the policy file's directory. */
|
|
44
|
+
allow: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
45
|
+
deny: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
46
|
+
}, z.core.$strict>>;
|
|
108
47
|
ledger: z.ZodOptional<z.ZodString>;
|
|
109
|
-
},
|
|
110
|
-
version: 1;
|
|
111
|
-
name?: string | undefined;
|
|
112
|
-
approval?: {
|
|
113
|
-
max?: string | undefined;
|
|
114
|
-
above?: string | undefined;
|
|
115
|
-
timeout_seconds?: number | undefined;
|
|
116
|
-
} | undefined;
|
|
117
|
-
budget?: {
|
|
118
|
-
total?: string | undefined;
|
|
119
|
-
daily?: string | undefined;
|
|
120
|
-
monthly?: string | undefined;
|
|
121
|
-
} | undefined;
|
|
122
|
-
native_budget?: Record<string, {
|
|
123
|
-
total?: string | undefined;
|
|
124
|
-
daily?: string | undefined;
|
|
125
|
-
monthly?: string | undefined;
|
|
126
|
-
}> | undefined;
|
|
127
|
-
unpriced_spends?: "allow" | "refuse" | undefined;
|
|
128
|
-
hosts?: {
|
|
129
|
-
allow?: string[] | undefined;
|
|
130
|
-
deny?: string[] | undefined;
|
|
131
|
-
} | undefined;
|
|
132
|
-
per_host?: Record<string, {
|
|
133
|
-
daily?: string | undefined;
|
|
134
|
-
monthly?: string | undefined;
|
|
135
|
-
per_payment?: string | undefined;
|
|
136
|
-
}> | undefined;
|
|
137
|
-
recipients?: {
|
|
138
|
-
allow?: string[] | undefined;
|
|
139
|
-
deny?: string[] | undefined;
|
|
140
|
-
} | undefined;
|
|
141
|
-
chains?: {
|
|
142
|
-
allow?: number[] | undefined;
|
|
143
|
-
} | undefined;
|
|
144
|
-
assets?: {
|
|
145
|
-
allow?: string[] | undefined;
|
|
146
|
-
} | undefined;
|
|
147
|
-
tools?: {
|
|
148
|
-
allow?: string[] | undefined;
|
|
149
|
-
deny?: string[] | undefined;
|
|
150
|
-
} | undefined;
|
|
151
|
-
ledger?: string | undefined;
|
|
152
|
-
}, {
|
|
153
|
-
version: 1;
|
|
154
|
-
name?: string | undefined;
|
|
155
|
-
approval?: {
|
|
156
|
-
max?: string | undefined;
|
|
157
|
-
above?: string | undefined;
|
|
158
|
-
timeout_seconds?: number | undefined;
|
|
159
|
-
} | undefined;
|
|
160
|
-
budget?: {
|
|
161
|
-
total?: string | undefined;
|
|
162
|
-
daily?: string | undefined;
|
|
163
|
-
monthly?: string | undefined;
|
|
164
|
-
} | undefined;
|
|
165
|
-
native_budget?: Record<string, {
|
|
166
|
-
total?: string | undefined;
|
|
167
|
-
daily?: string | undefined;
|
|
168
|
-
monthly?: string | undefined;
|
|
169
|
-
}> | undefined;
|
|
170
|
-
unpriced_spends?: "allow" | "refuse" | undefined;
|
|
171
|
-
hosts?: {
|
|
172
|
-
allow?: string[] | undefined;
|
|
173
|
-
deny?: string[] | undefined;
|
|
174
|
-
} | undefined;
|
|
175
|
-
per_host?: Record<string, {
|
|
176
|
-
daily?: string | undefined;
|
|
177
|
-
monthly?: string | undefined;
|
|
178
|
-
per_payment?: string | undefined;
|
|
179
|
-
}> | undefined;
|
|
180
|
-
recipients?: {
|
|
181
|
-
allow?: string[] | undefined;
|
|
182
|
-
deny?: string[] | undefined;
|
|
183
|
-
} | undefined;
|
|
184
|
-
chains?: {
|
|
185
|
-
allow?: number[] | undefined;
|
|
186
|
-
} | undefined;
|
|
187
|
-
assets?: {
|
|
188
|
-
allow?: string[] | undefined;
|
|
189
|
-
} | undefined;
|
|
190
|
-
tools?: {
|
|
191
|
-
allow?: string[] | undefined;
|
|
192
|
-
deny?: string[] | undefined;
|
|
193
|
-
} | undefined;
|
|
194
|
-
ledger?: string | undefined;
|
|
195
|
-
}>;
|
|
48
|
+
}, z.core.$strict>;
|
|
196
49
|
export type Policy = z.infer<typeof PolicySchema>;
|
|
197
50
|
export declare const PRECISION = 18;
|
|
198
51
|
/** Decimal string to an integer at 18 places. */
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/hifriendbot/agentwallet-mcp.git"
|
|
7
7
|
},
|
|
8
|
-
"version": "1.14.
|
|
8
|
+
"version": "1.14.8",
|
|
9
9
|
"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.",
|
|
10
10
|
"type": "module",
|
|
11
11
|
"main": "build/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"scripts": {
|
|
21
21
|
"build": "tsc",
|
|
22
22
|
"start": "node build/index.js",
|
|
23
|
-
"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 && node test/audit-11310.test.mjs && node test/audit-11311.test.mjs && node test/audit-11312.test.mjs && node test/audit-11313.test.mjs && node test/audit-11316.test.mjs && node test/spend-policy.test.mjs && node test/policy-path-resolution.test.mjs",
|
|
23
|
+
"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 && node test/audit-11310.test.mjs && node test/audit-11311.test.mjs && node test/audit-11312.test.mjs && node test/audit-11313.test.mjs && node test/audit-11316.test.mjs && node test/spend-policy.test.mjs && node test/policy-path-resolution.test.mjs && node test/server-json-env.test.mjs",
|
|
24
24
|
"prepublishOnly": "npm run build"
|
|
25
25
|
},
|
|
26
26
|
"keywords": [
|
|
@@ -59,12 +59,12 @@
|
|
|
59
59
|
"node": ">=22.19.0"
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
62
|
+
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
63
63
|
"@solana/web3.js": "^1.99.0",
|
|
64
64
|
"bs58": "^6.0.0",
|
|
65
|
-
"undici": "^8.
|
|
65
|
+
"undici": "^8.12.3",
|
|
66
66
|
"viem": "^2.56.9",
|
|
67
|
-
"zod": "^
|
|
67
|
+
"zod": "^4.6.7"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@types/node": "^20.0.0",
|