@wafermapxyz/cli 0.4.2 → 0.4.3
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/CHANGELOG.md +53 -46
- package/README.md +118 -118
- package/dist/cli.js +20 -9
- package/dist/index.js +16 -6
- package/dist/types/src/core.d.ts +2 -0
- package/dist/types/src/index.d.ts +1 -1
- package/package.json +69 -68
package/CHANGELOG.md
CHANGED
|
@@ -1,46 +1,53 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 0.4.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.4.3 (2 Nov 2026)
|
|
4
|
+
|
|
5
|
+
The two open points from the third public review by @whoiskn0why (8/10), thank you.
|
|
6
|
+
|
|
7
|
+
- **`wafermap resources` has a time limit.** It was the one command that still used a bare `fetch` and could wait forever on a server that never answered. It now gives up after 60 seconds with `timeout`, like every other request, and reports `network_error` instead of printing nothing when the catalog cannot be read. The SDK exports the same function as `listResources(baseUrl?, timeoutMs?)`; `Wafermap.resources()` uses it. `WAFERMAP_TIMEOUT_MS` sets the limit for the CLI and the MCP server.
|
|
8
|
+
- **npm provenance.** From this version the package is published by GitHub Actions (`.github/workflows/publish.yml`) through npm trusted publishing, with a signed provenance statement that ties the tarball on npm to the commit and workflow run that built it. Check it on the npm page ("Provenance") or with `npm audit signatures`. CI holds no npm token: npm trusts this one workflow file in this repo, and nothing else.
|
|
9
|
+
|
|
10
|
+
## 0.4.2 (2 Nov 2026)
|
|
11
|
+
|
|
12
|
+
**$WAFERMAP holder discount.** `call()` (CLI `wafermap call`, MCP `wafermap_call`) now names the paying wallet to Wafermap in an `X-Wafermap-Payer` header, so a wallet holding at least 100,000 $WAFERMAP on Robinhood Chain is quoted 50% less for Wafermap's own resources, on EVM networks. Wafermap checks the balance again against the signer of the payment, so the header only ever lowers the price for the wallet that actually holds. The header goes to Wafermap only: `pay()` sends nothing extra to third-party services. Marketplace listings keep their seller's price.
|
|
13
|
+
|
|
14
|
+
## 0.4.1 (2 Nov 2026)
|
|
15
|
+
|
|
16
|
+
On npm as **`@wafermapxyz/cli`**: `npm i -g @wafermapxyz/cli` (SDK: `import { Wafermap } from "@wafermapxyz/cli"`). The command stays `wafermap`, the wallet stays in `~/.wafermap`. The registry refuses the bare name `wafermap` for everyone, so the package lives under our own scope. If you installed from the tarball, run `npm rm -g wafermap` before installing the scoped package. No code changes since 0.4.0.
|
|
17
|
+
|
|
18
|
+
## 0.4.0 (2 Nov 2026)
|
|
19
|
+
|
|
20
|
+
From the second public review by @whoiskn0why, thank you.
|
|
21
|
+
|
|
22
|
+
- **Shorter payment authorizations.** EVM payments are signed for at most 90 seconds (was up to 300), or less when the server asks for less. That is the window in which "a failed call costs $0" rests on the server's word; `wafermap history --verify` still checks every one on-chain afterwards.
|
|
23
|
+
- **Time limits.** Requests to paid resources and services, Wafermap's API, the swap API and Solana RPC give up after 60 seconds (`timeoutMs` in the SDK, `DEFAULT_TIMEOUT_MS`); EVM RPC keeps viem's time-outs. Before, a server that never answered held a call forever. A paid call that times out after signing says the payment stays counted (and, for EVM payments, until when it can be executed); one that times out before signing says nothing was paid.
|
|
24
|
+
- **Swaps are checked before anything is signed.** The build now comes first and the approval after it. The quote and the build must name KyberSwap's router (`KYBER_ROUTER`, `0x6131B5fae19EA4f9D964eAc0408E4408b66337b5`). The swap calldata is decoded (`decodeSwap`) and must trade the quoted tokens, spend exactly the quoted amount, pay the output to this wallet, take no fee, and set an on-chain minimum output no lower than the built output less your slippage; the built output must be within your slippage of the quote, and ETH is sent only when selling ETH. Otherwise nothing is approved or sent (`unexpected_router`, `unexpected_calldata`, `build_mismatch`, `price_moved`). `amountOutMin` is now the minimum encoded in the transaction.
|
|
25
|
+
- **The MCP password can leave the config file.** `wafermap keychain on` stores it with Windows DPAPI, the macOS Keychain or the Linux Secret Service; `wafermap mcp` reads it from there, and warns when `WAFERMAP_PASSWORD` is still set in plain text.
|
|
26
|
+
- An empty `WAFERMAP_PASSWORD` counts as not set. `wafermap keychain off` checks that the password is really gone.
|
|
27
|
+
- Tests for all of the above (`test/hardening.test.mjs`: real DPAPI on Windows; the macOS and Linux stores are exercised against a stub). CI also runs on Windows and macOS.
|
|
28
|
+
- The SDK exports `quoteSwap`, `executeSwap`, `decodeSwap`, `KYBER_ROUTER`, `MAX_VALIDITY_SECONDS` and `DEFAULT_TIMEOUT_MS`.
|
|
29
|
+
|
|
30
|
+
npm: the name `wafermap` is refused by the registry for everyone (too similar to an existing package). From 0.4.1 the package is `@wafermapxyz/cli`.
|
|
31
|
+
|
|
32
|
+
## 0.3.3 (1 Nov 2026)
|
|
33
|
+
|
|
34
|
+
**Fix: the daily cap no longer trusts the server.** Reported publicly in a code review by @whoiskn0why, thank you.
|
|
35
|
+
|
|
36
|
+
Before: the spend record was written after the call, from the server's receipt (`call()`), or as $0 when the server answered with an error (`pay()`). A server that reported $0, sent no receipt, answered with an error or dropped the connection could take signed payments that never counted towards the daily cap. With a $0.10 cap, $0.15 in signed payments could go through.
|
|
37
|
+
|
|
38
|
+
Now:
|
|
39
|
+
- Every payment is recorded at its full signed amount the moment it is signed, before it is sent, and stays counted whatever the server answers. The outcome (ok / failed / refused) is written as a second line that adds nothing to the total.
|
|
40
|
+
- Check, sign and record run one payment at a time within a process, so concurrent calls cannot both pass the cap on the same remaining budget (found while writing the tests). Across processes sharing one wallet (two MCP clients, CLI + MCP), each signed payment is checked again against the shared log before it is sent and dropped, never sent, if another process used the budget meanwhile (found in our own adversarial review).
|
|
41
|
+
- `wafermap history --verify` (SDK: `checkPayment`) reads `authorizationState` on the token contract to show which signed EVM payments were executed and which expired unused, without trusting any server.
|
|
42
|
+
- `wafermap history` shows each payment's status and notes that log lines from before 0.3.3 hold what the server reported.
|
|
43
|
+
- Tests (`npm test`): the caps against a local mock server that lies, errors, refuses or hangs up; CI on Node 20 and 22.
|
|
44
|
+
|
|
45
|
+
Older `spend.jsonl` lines are still read; `mergeLog` joins the new two-line records.
|
|
46
|
+
|
|
47
|
+
## 0.3.2
|
|
48
|
+
|
|
49
|
+
Opt-in per-payment confirmation for the MCP server (`wafermap limits --mcp-confirm on`).
|
|
50
|
+
|
|
51
|
+
## 0.3.1
|
|
52
|
+
|
|
53
|
+
First public source release: CLI, SDK and MCP server.
|
package/README.md
CHANGED
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
# wafermap
|
|
2
|
-
|
|
3
|
-
The CLI, SDK and MCP server behind [Wafermap](https://wafermap.xyz): an x402 wallet for AI agents. One local, encrypted key that can:
|
|
4
|
-
|
|
5
|
-
- **call** Wafermap's pay-per-call resources (LLMs, image and audio models, web reading, on-chain data, tokenized stocks, prediction markets)
|
|
6
|
-
- **discover** third-party x402 services in the open x402 index and **pay** them
|
|
7
|
-
- **swap** tokens through the KyberSwap aggregator
|
|
8
|
-
|
|
9
|
-
Every payment is checked against a per-payment cap and a daily cap before anything is signed. Swaps have their own per-swap cap. Payments settle in USDG on Robinhood Chain or USDC on Solana, Base, Arbitrum or Polygon and are gasless; swaps need a little ETH for gas.
|
|
10
|
-
|
|
11
|
-
## Install
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
npm i -g @wafermapxyz/cli
|
|
15
|
-
wafermap init
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
The command is `wafermap`. The package is **`@wafermapxyz/cli`** on npm (the `wafermapxyz` scope, after the wafermap.xyz domain), the same build as https://wafermap.xyz/cli/wafermap.tgz and this repository. The bare name `wafermap` on npm is **not** this package: the registry refuses it for everyone. Coming from the tarball install? Run `npm rm -g wafermap` first; your wallet in `~/.wafermap` stays.
|
|
19
|
-
|
|
20
|
-
`init` creates a key and encrypts it with your password (scrypt + AES-256-GCM) in `~/.wafermap/keystore.json`. Back it up with `wafermap export-key`. Then send a few dollars of USDC on Solana to the Solana address it prints, or USDG (Robinhood Chain) or USDC (Base, Arbitrum, Polygon) to its EVM address.
|
|
21
|
-
|
|
22
|
-
The Solana address comes from the same key (derived with HMAC-SHA256), so one backup covers both. `wafermap export-key --solana` prints the Solana secret key in the form Phantom and Solflare import. `wafermap address` shows both addresses.
|
|
23
|
-
|
|
24
|
-
## CLI
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
wafermap balance
|
|
28
|
-
wafermap limits --per-payment 0.1 --per-day 1 --per-swap 25 --network robinhood
|
|
29
|
-
wafermap resources stock
|
|
30
|
-
wafermap call prediction-markets query="fed rates"
|
|
31
|
-
wafermap call flux-2-dev prompt="a glossy silicon wafer" --out wafer.png
|
|
32
|
-
wafermap discover weather
|
|
33
|
-
wafermap pay https://x402.agentindex.world/weather city=Jakarta
|
|
34
|
-
wafermap swap 0.01 ETH USDG
|
|
35
|
-
wafermap history
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Add `--yes` to skip the confirmation prompt (caps still apply), `--network base`, `arbitrum`, `polygon` or `solana` to pay in USDC on that network (swaps run on `robinhood` or `base`). Set `WAFERMAP_PASSWORD` to avoid the password prompt in scripts.
|
|
39
|
-
|
|
40
|
-
## MCP server
|
|
41
|
-
|
|
42
|
-
Give Claude Desktop, Cursor or any MCP client the same wallet. First put the wallet password in your operating system's keychain, once, in a terminal:
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
wafermap keychain on
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Then the client config holds no secret:
|
|
49
|
-
|
|
50
|
-
```json
|
|
51
|
-
{
|
|
52
|
-
"mcpServers": {
|
|
53
|
-
"wafermap": {
|
|
54
|
-
"command": "wafermap",
|
|
55
|
-
"args": ["mcp"]
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
`wafermap keychain on` stores the password with Windows DPAPI (a file only your Windows user can decrypt), in the macOS Keychain, or in the Secret Service on Linux (GNOME Keyring, KWallet, through `secret-tool`). `wafermap keychain` shows whether it is set and still unlocks the wallet; `wafermap keychain off` removes it. This keeps the password out of config files, backups and screenshots. It does not stop a program already running as you, which could also ask the OS for it. Setting `WAFERMAP_PASSWORD` in the client's `env` still works, but the password then sits in plain text in that config file, and the server says so when it starts.
|
|
62
|
-
|
|
63
|
-
Tools: `wafermap_wallet`, `wafermap_resources`, `wafermap_call`, `wafermap_discover`, `wafermap_pay`, `wafermap_swap_quote`, `wafermap_swap`, `wafermap_history`. Swaps from MCP are off until you run `wafermap limits --mcp-swaps on`.
|
|
64
|
-
|
|
65
|
-
To approve every payment yourself, run `wafermap limits --mcp-confirm on` and restart the MCP server. Each payment and swap then waits for you in the client, showing the amount, asset, recipient, network and resource, before the wallet signs. It uses MCP elicitation; a client that cannot show the prompt gets a refusal and nothing is paid. The caps still apply first.
|
|
66
|
-
|
|
67
|
-
## SDK
|
|
68
|
-
|
|
69
|
-
```ts
|
|
70
|
-
import { Wafermap } from "@wafermapxyz/cli";
|
|
71
|
-
|
|
72
|
-
const wm = new Wafermap({ privateKey: process.env.AGENT_KEY as `0x${string}`, limits: { perPayment: 0.05, perDay: 2 } });
|
|
73
|
-
|
|
74
|
-
const { result, receipt } = await wm.call("stock-signal", { symbol: "TSLA" });
|
|
75
|
-
const services = await wm.discover("token price");
|
|
76
|
-
const paid = await wm.pay(services[0].url, { method: "POST", input: { symbol: "ETH" }, network: "base" });
|
|
77
|
-
const quote = await wm.swapQuote("ETH", "USDG", "0.01");
|
|
78
|
-
await wm.swap(quote);
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Pass `approve: (a) => boolean` to confirm each payment or swap yourself, and `ledger` to persist spending for the daily cap. Lower level: `x402fetch(url, init, { signer })` pays any x402 v1 or v2 endpoint.
|
|
82
|
-
|
|
83
|
-
## $WAFERMAP holder discount
|
|
84
|
-
|
|
85
|
-
A wallet that holds at least 100,000 $WAFERMAP on Robinhood Chain pays 50% less for Wafermap's own resources when it pays on an EVM network (Robinhood Chain, Base, Arbitrum, Polygon). Nothing to set up: `wafermap call` names the wallet to Wafermap and the quote comes back discounted; the server re-checks the balance of the wallet that signs. Marketplace listings and Solana payments keep the regular price. Check a wallet: https://wafermap.xyz/api/holder?address=0x…
|
|
86
|
-
|
|
87
|
-
## Where the limits live
|
|
88
|
-
|
|
89
|
-
The caps sit with the key, on your machine. Before the wallet signs anything it reads the 402 quote and refuses when the price is above the per-payment cap or would take today's spend past the daily cap, so nothing is signed and nothing can be charged. With `--mcp-confirm on`, you also approve each MCP payment yourself before it is signed. A budget written in a prompt is an instruction, not a limit: set it with `wafermap limits`. More: https://wafermap.xyz/docs#limits
|
|
90
|
-
|
|
91
|
-
**The daily cap counts what the wallet signed, not what a server says it charged.** Every payment is written to `spend.jsonl` at its full signed amount the moment it is signed, before it is sent, and stays counted whatever the server answers: an error, a receipt saying $0, no receipt, or no answer at all. Check, sign and record run one payment at a time within a process, so calls started together cannot share the same remaining budget. Across processes on the same wallet (two MCP clients, or the CLI next to the MCP server), the wallet re-reads the shared log right after recording a signed payment and, if the cap is now passed, drops that payment before it is sent. (Before 0.3.3 the cap trusted the server's receipt; see [CHANGELOG.md](CHANGELOG.md).)
|
|
92
|
-
|
|
93
|
-
**What the wallet cannot enforce.** An x402 `exact` payment is a signed authorization (EIP-3009 on EVM chains, a partially signed transaction on Solana). Whoever receives it can execute it once until it expires. The wallet signs EVM authorizations for at most 90 seconds, or less when the server asks for less (300 before 0.4.0). "A failed call costs $0" is therefore a promise of the server you pay: Wafermap's server settles only after the resource worked and otherwise lets the authorization expire unused. You do not have to take that on trust: `wafermap history --verify` asks each token contract (`authorizationState`) whether every signed payment was executed, and shows the ones that expired unused (`error` means the RPC did not answer). Solana payments are not checked yet. The caps bound the worst case either way.
|
|
94
|
-
|
|
95
|
-
**Time limits.** Requests to paid resources and services, to Wafermap's API, to the swap API and to Solana RPC give up after 60 seconds (`timeoutMs` in the SDK); EVM RPC calls keep viem's own time-outs. A paid call that times out after the payment was signed says so: the payment stays counted, and for EVM payments the message says until when it can be executed.
|
|
96
|
-
|
|
97
|
-
**Swaps.** The wallet builds the swap, checks it, and only then approves and sends. The swap must go to KyberSwap's router (`0x6131B5fae19EA4f9D964eAc0408E4408b66337b5` on Robinhood Chain and Base). The calldata itself is decoded and must trade the quoted tokens, spend exactly the quoted amount, send the output to this wallet, take no fee, and carry an on-chain minimum output of the built output less your slippage, where the built output must be within your slippage of the quote. ETH is sent only when selling ETH, and an ERC-20 approval is for exactly the amount, to that router, and only when the allowance is lower. The router and the route it executes are KyberSwap's: the wallet checks what the call asks for, not what the router contract does.
|
|
98
|
-
|
|
99
|
-
The checks are in [`src/core.ts`](src/core.ts) (`gate`, `paidFetch`, `checkPayment`), the keystore in [`src/store.ts`](src/store.ts), payment signing in [`shared/x402client.ts`](shared/x402client.ts) and [`shared/solana.ts`](shared/solana.ts). The tests in [`test/caps.test.mjs`](test/caps.test.mjs) run the caps against a local server that lies; [`test/hardening.test.mjs`](test/hardening.test.mjs) covers expiry, time limits, swap checks and the keychain (real DPAPI on Windows; on macOS and Linux the `security` and `secret-tool` calls run against a stub, not a real keychain). CI runs them on Linux, Windows and macOS.
|
|
100
|
-
|
|
101
|
-
## Build from source
|
|
102
|
-
|
|
103
|
-
```bash
|
|
104
|
-
git clone https://github.com/wafermap/wafermap-cli.git && cd wafermap-cli
|
|
105
|
-
npm ci
|
|
106
|
-
npm run build # dist/cli.js, dist/index.js and type declarations (strict tsc)
|
|
107
|
-
npm test # the caps against a mock server that misbehaves
|
|
108
|
-
node dist/cli.js --help
|
|
109
|
-
npm i -g . # optional: install this build as `wafermap`
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
Layout: `src/` is the CLI, SDK and MCP server; `shared/` is the x402 client, the Solana payment builder, the swap helpers and the resource catalog, the same files the Wafermap site uses.
|
|
113
|
-
|
|
114
|
-
## Files
|
|
115
|
-
|
|
116
|
-
`~/.wafermap/keystore.json` (encrypted key), `config.json` (caps, network), `spend.jsonl` (history). Override the folder with `WAFERMAP_HOME`, the RPCs with `WAFERMAP_RPC_ROBINHOOD` / `WAFERMAP_RPC_BASE`.
|
|
117
|
-
|
|
118
|
-
Docs: https://wafermap.xyz/docs#cli · Security: [SECURITY.md](SECURITY.md) · License: [MIT](LICENSE)
|
|
1
|
+
# wafermap
|
|
2
|
+
|
|
3
|
+
The CLI, SDK and MCP server behind [Wafermap](https://wafermap.xyz): an x402 wallet for AI agents. One local, encrypted key that can:
|
|
4
|
+
|
|
5
|
+
- **call** Wafermap's pay-per-call resources (LLMs, image and audio models, web reading, on-chain data, tokenized stocks, prediction markets)
|
|
6
|
+
- **discover** third-party x402 services in the open x402 index and **pay** them
|
|
7
|
+
- **swap** tokens through the KyberSwap aggregator
|
|
8
|
+
|
|
9
|
+
Every payment is checked against a per-payment cap and a daily cap before anything is signed. Swaps have their own per-swap cap. Payments settle in USDG on Robinhood Chain or USDC on Solana, Base, Arbitrum or Polygon and are gasless; swaps need a little ETH for gas.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm i -g @wafermapxyz/cli
|
|
15
|
+
wafermap init
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The command is `wafermap`. The package is **`@wafermapxyz/cli`** on npm (the `wafermapxyz` scope, after the wafermap.xyz domain), the same build as https://wafermap.xyz/cli/wafermap.tgz and this repository. The bare name `wafermap` on npm is **not** this package: the registry refuses it for everyone. Coming from the tarball install? Run `npm rm -g wafermap` first; your wallet in `~/.wafermap` stays.
|
|
19
|
+
|
|
20
|
+
`init` creates a key and encrypts it with your password (scrypt + AES-256-GCM) in `~/.wafermap/keystore.json`. Back it up with `wafermap export-key`. Then send a few dollars of USDC on Solana to the Solana address it prints, or USDG (Robinhood Chain) or USDC (Base, Arbitrum, Polygon) to its EVM address.
|
|
21
|
+
|
|
22
|
+
The Solana address comes from the same key (derived with HMAC-SHA256), so one backup covers both. `wafermap export-key --solana` prints the Solana secret key in the form Phantom and Solflare import. `wafermap address` shows both addresses.
|
|
23
|
+
|
|
24
|
+
## CLI
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
wafermap balance
|
|
28
|
+
wafermap limits --per-payment 0.1 --per-day 1 --per-swap 25 --network robinhood
|
|
29
|
+
wafermap resources stock
|
|
30
|
+
wafermap call prediction-markets query="fed rates"
|
|
31
|
+
wafermap call flux-2-dev prompt="a glossy silicon wafer" --out wafer.png
|
|
32
|
+
wafermap discover weather
|
|
33
|
+
wafermap pay https://x402.agentindex.world/weather city=Jakarta
|
|
34
|
+
wafermap swap 0.01 ETH USDG
|
|
35
|
+
wafermap history
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Add `--yes` to skip the confirmation prompt (caps still apply), `--network base`, `arbitrum`, `polygon` or `solana` to pay in USDC on that network (swaps run on `robinhood` or `base`). Set `WAFERMAP_PASSWORD` to avoid the password prompt in scripts.
|
|
39
|
+
|
|
40
|
+
## MCP server
|
|
41
|
+
|
|
42
|
+
Give Claude Desktop, Cursor or any MCP client the same wallet. First put the wallet password in your operating system's keychain, once, in a terminal:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
wafermap keychain on
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then the client config holds no secret:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"mcpServers": {
|
|
53
|
+
"wafermap": {
|
|
54
|
+
"command": "wafermap",
|
|
55
|
+
"args": ["mcp"]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`wafermap keychain on` stores the password with Windows DPAPI (a file only your Windows user can decrypt), in the macOS Keychain, or in the Secret Service on Linux (GNOME Keyring, KWallet, through `secret-tool`). `wafermap keychain` shows whether it is set and still unlocks the wallet; `wafermap keychain off` removes it. This keeps the password out of config files, backups and screenshots. It does not stop a program already running as you, which could also ask the OS for it. Setting `WAFERMAP_PASSWORD` in the client's `env` still works, but the password then sits in plain text in that config file, and the server says so when it starts.
|
|
62
|
+
|
|
63
|
+
Tools: `wafermap_wallet`, `wafermap_resources`, `wafermap_call`, `wafermap_discover`, `wafermap_pay`, `wafermap_swap_quote`, `wafermap_swap`, `wafermap_history`. Swaps from MCP are off until you run `wafermap limits --mcp-swaps on`.
|
|
64
|
+
|
|
65
|
+
To approve every payment yourself, run `wafermap limits --mcp-confirm on` and restart the MCP server. Each payment and swap then waits for you in the client, showing the amount, asset, recipient, network and resource, before the wallet signs. It uses MCP elicitation; a client that cannot show the prompt gets a refusal and nothing is paid. The caps still apply first.
|
|
66
|
+
|
|
67
|
+
## SDK
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { Wafermap } from "@wafermapxyz/cli";
|
|
71
|
+
|
|
72
|
+
const wm = new Wafermap({ privateKey: process.env.AGENT_KEY as `0x${string}`, limits: { perPayment: 0.05, perDay: 2 } });
|
|
73
|
+
|
|
74
|
+
const { result, receipt } = await wm.call("stock-signal", { symbol: "TSLA" });
|
|
75
|
+
const services = await wm.discover("token price");
|
|
76
|
+
const paid = await wm.pay(services[0].url, { method: "POST", input: { symbol: "ETH" }, network: "base" });
|
|
77
|
+
const quote = await wm.swapQuote("ETH", "USDG", "0.01");
|
|
78
|
+
await wm.swap(quote);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Pass `approve: (a) => boolean` to confirm each payment or swap yourself, and `ledger` to persist spending for the daily cap. Lower level: `x402fetch(url, init, { signer })` pays any x402 v1 or v2 endpoint.
|
|
82
|
+
|
|
83
|
+
## $WAFERMAP holder discount
|
|
84
|
+
|
|
85
|
+
A wallet that holds at least 100,000 $WAFERMAP on Robinhood Chain pays 50% less for Wafermap's own resources when it pays on an EVM network (Robinhood Chain, Base, Arbitrum, Polygon). Nothing to set up: `wafermap call` names the wallet to Wafermap and the quote comes back discounted; the server re-checks the balance of the wallet that signs. Marketplace listings and Solana payments keep the regular price. Check a wallet: https://wafermap.xyz/api/holder?address=0x…
|
|
86
|
+
|
|
87
|
+
## Where the limits live
|
|
88
|
+
|
|
89
|
+
The caps sit with the key, on your machine. Before the wallet signs anything it reads the 402 quote and refuses when the price is above the per-payment cap or would take today's spend past the daily cap, so nothing is signed and nothing can be charged. With `--mcp-confirm on`, you also approve each MCP payment yourself before it is signed. A budget written in a prompt is an instruction, not a limit: set it with `wafermap limits`. More: https://wafermap.xyz/docs#limits
|
|
90
|
+
|
|
91
|
+
**The daily cap counts what the wallet signed, not what a server says it charged.** Every payment is written to `spend.jsonl` at its full signed amount the moment it is signed, before it is sent, and stays counted whatever the server answers: an error, a receipt saying $0, no receipt, or no answer at all. Check, sign and record run one payment at a time within a process, so calls started together cannot share the same remaining budget. Across processes on the same wallet (two MCP clients, or the CLI next to the MCP server), the wallet re-reads the shared log right after recording a signed payment and, if the cap is now passed, drops that payment before it is sent. (Before 0.3.3 the cap trusted the server's receipt; see [CHANGELOG.md](CHANGELOG.md).)
|
|
92
|
+
|
|
93
|
+
**What the wallet cannot enforce.** An x402 `exact` payment is a signed authorization (EIP-3009 on EVM chains, a partially signed transaction on Solana). Whoever receives it can execute it once until it expires. The wallet signs EVM authorizations for at most 90 seconds, or less when the server asks for less (300 before 0.4.0). "A failed call costs $0" is therefore a promise of the server you pay: Wafermap's server settles only after the resource worked and otherwise lets the authorization expire unused. You do not have to take that on trust: `wafermap history --verify` asks each token contract (`authorizationState`) whether every signed payment was executed, and shows the ones that expired unused (`error` means the RPC did not answer). Solana payments are not checked yet. The caps bound the worst case either way.
|
|
94
|
+
|
|
95
|
+
**Time limits.** Requests to paid resources and services, to Wafermap's API, to the swap API and to Solana RPC give up after 60 seconds (`timeoutMs` in the SDK); EVM RPC calls keep viem's own time-outs. `wafermap resources` uses the same limit. Set `WAFERMAP_TIMEOUT_MS` to change it for the CLI and the MCP server. A paid call that times out after the payment was signed says so: the payment stays counted, and for EVM payments the message says until when it can be executed.
|
|
96
|
+
|
|
97
|
+
**Swaps.** The wallet builds the swap, checks it, and only then approves and sends. The swap must go to KyberSwap's router (`0x6131B5fae19EA4f9D964eAc0408E4408b66337b5` on Robinhood Chain and Base). The calldata itself is decoded and must trade the quoted tokens, spend exactly the quoted amount, send the output to this wallet, take no fee, and carry an on-chain minimum output of the built output less your slippage, where the built output must be within your slippage of the quote. ETH is sent only when selling ETH, and an ERC-20 approval is for exactly the amount, to that router, and only when the allowance is lower. The router and the route it executes are KyberSwap's: the wallet checks what the call asks for, not what the router contract does.
|
|
98
|
+
|
|
99
|
+
The checks are in [`src/core.ts`](src/core.ts) (`gate`, `paidFetch`, `checkPayment`), the keystore in [`src/store.ts`](src/store.ts), payment signing in [`shared/x402client.ts`](shared/x402client.ts) and [`shared/solana.ts`](shared/solana.ts). The tests in [`test/caps.test.mjs`](test/caps.test.mjs) run the caps against a local server that lies; [`test/hardening.test.mjs`](test/hardening.test.mjs) covers expiry, time limits, swap checks and the keychain (real DPAPI on Windows; on macOS and Linux the `security` and `secret-tool` calls run against a stub, not a real keychain). CI runs them on Linux, Windows and macOS.
|
|
100
|
+
|
|
101
|
+
## Build from source
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
git clone https://github.com/wafermap/wafermap-cli.git && cd wafermap-cli
|
|
105
|
+
npm ci
|
|
106
|
+
npm run build # dist/cli.js, dist/index.js and type declarations (strict tsc)
|
|
107
|
+
npm test # the caps against a mock server that misbehaves
|
|
108
|
+
node dist/cli.js --help
|
|
109
|
+
npm i -g . # optional: install this build as `wafermap`
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Layout: `src/` is the CLI, SDK and MCP server; `shared/` is the x402 client, the Solana payment builder, the swap helpers and the resource catalog, the same files the Wafermap site uses.
|
|
113
|
+
|
|
114
|
+
## Files
|
|
115
|
+
|
|
116
|
+
`~/.wafermap/keystore.json` (encrypted key), `config.json` (caps, network), `spend.jsonl` (history). Override the folder with `WAFERMAP_HOME`, the RPCs with `WAFERMAP_RPC_ROBINHOOD` / `WAFERMAP_RPC_BASE`.
|
|
117
|
+
|
|
118
|
+
Docs: https://wafermap.xyz/docs#cli · Security: [SECURITY.md](SECURITY.md) · License: [MIT](LICENSE)
|
package/dist/cli.js
CHANGED
|
@@ -447,6 +447,7 @@ __export(core_exports, {
|
|
|
447
447
|
WafermapError: () => WafermapError,
|
|
448
448
|
checkPayment: () => checkPayment,
|
|
449
449
|
isNet: () => isNet,
|
|
450
|
+
listResources: () => listResources,
|
|
450
451
|
mergeLog: () => mergeLog
|
|
451
452
|
});
|
|
452
453
|
import { createPublicClient, createWalletClient, erc20Abi as erc20Abi2, formatUnits as formatUnits2, http } from "viem";
|
|
@@ -472,6 +473,20 @@ async function checkPayment(e, rpc) {
|
|
|
472
473
|
return "error";
|
|
473
474
|
}
|
|
474
475
|
}
|
|
476
|
+
async function listResources(baseUrl = "https://wafermap.xyz", timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
477
|
+
const host = new URL(baseUrl).host;
|
|
478
|
+
const gaveUp = () => new WafermapError("timeout", `${host} did not answer within ${Math.round(timeoutMs / 1e3)} s. Nothing was paid.`);
|
|
479
|
+
const signal = AbortSignal.timeout(timeoutMs);
|
|
480
|
+
const r = await fetch(`${baseUrl.replace(/\/$/, "")}/v1/resources`, { signal }).catch((e) => {
|
|
481
|
+
throw timedOut(e) ? gaveUp() : new WafermapError("network_error", `${host}: ${e?.message ?? "request failed"}`);
|
|
482
|
+
});
|
|
483
|
+
const j = await r.json().catch((e) => {
|
|
484
|
+
if (timedOut(e)) throw gaveUp();
|
|
485
|
+
return null;
|
|
486
|
+
});
|
|
487
|
+
if (!r.ok || !Array.isArray(j?.data)) throw new WafermapError("network_error", `${host} returned no catalog (HTTP ${r.status}).`);
|
|
488
|
+
return j.data;
|
|
489
|
+
}
|
|
475
490
|
var NETS, isNet, isSwapNet, WafermapError, CHAINS, AUTH_STATE_ABI, DEFAULT_LIMITS, DEFAULT_TIMEOUT_MS, timedOut, Wafermap;
|
|
476
491
|
var init_core = __esm({
|
|
477
492
|
"src/core.ts"() {
|
|
@@ -632,12 +647,7 @@ var init_core = __esm({
|
|
|
632
647
|
}
|
|
633
648
|
// ---- Wafermap resources ----
|
|
634
649
|
async resources() {
|
|
635
|
-
|
|
636
|
-
const r = await this.fetch(`${this.baseUrl}/v1/resources`).catch((e) => {
|
|
637
|
-
throw timedOut(e) ? this.timeoutError(host) : new WafermapError("network_error", `${host}: ${e?.message ?? "request failed"}`);
|
|
638
|
-
});
|
|
639
|
-
const j = await this.body(r, () => r.json(), null, host);
|
|
640
|
-
return j?.data ?? [];
|
|
650
|
+
return listResources(this.baseUrl, this.timeoutMs);
|
|
641
651
|
}
|
|
642
652
|
async call(id, input = {}, o = {}) {
|
|
643
653
|
const go = () => this.paidFetch("resource", id, `${this.baseUrl}/v1/${id}`, { method: "POST", headers: { "content-type": "application/json", "x-wafermap-payer": this.address }, body: JSON.stringify(input) }, o.network);
|
|
@@ -927,6 +937,7 @@ function openWallet(password2, approve) {
|
|
|
927
937
|
const wm = new Wafermap({
|
|
928
938
|
privateKey: loadKey(password2),
|
|
929
939
|
baseUrl: process.env.WAFERMAP_URL,
|
|
940
|
+
timeoutMs: Number(process.env.WAFERMAP_TIMEOUT_MS) || void 0,
|
|
930
941
|
network: c.network,
|
|
931
942
|
limits: c.limits,
|
|
932
943
|
ledger: { entries: spendLog, record: appendSpend },
|
|
@@ -949,7 +960,7 @@ var VERSION;
|
|
|
949
960
|
var init_version = __esm({
|
|
950
961
|
"src/version.ts"() {
|
|
951
962
|
"use strict";
|
|
952
|
-
VERSION = "0.4.
|
|
963
|
+
VERSION = "0.4.3";
|
|
953
964
|
}
|
|
954
965
|
});
|
|
955
966
|
|
|
@@ -1680,8 +1691,8 @@ Total $${total.toFixed(4)} in ${pays.length} payments. From 0.3.3 every payment
|
|
|
1680
1691
|
}
|
|
1681
1692
|
case "resources": {
|
|
1682
1693
|
const q = a._.join(" ").toLowerCase();
|
|
1683
|
-
const
|
|
1684
|
-
for (const x of
|
|
1694
|
+
const list = await listResources(process.env.WAFERMAP_URL, Number(process.env.WAFERMAP_TIMEOUT_MS) || void 0);
|
|
1695
|
+
for (const x of list.filter((x2) => !q || `${x2.id} ${x2.name} ${x2.summary} ${x2.category}`.toLowerCase().includes(q))) console.log(`${x.id.padEnd(22)} $${String(x.price).padEnd(7)} ${x.summary}`);
|
|
1685
1696
|
return;
|
|
1686
1697
|
}
|
|
1687
1698
|
case "discover": {
|
package/dist/index.js
CHANGED
|
@@ -442,6 +442,20 @@ async function checkPayment(e, rpc) {
|
|
|
442
442
|
var DEFAULT_LIMITS = { perPayment: 0.1, perDay: 1, perSwap: 25 };
|
|
443
443
|
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
444
444
|
var timedOut = (e) => e?.name === "TimeoutError" || e?.cause?.name === "TimeoutError";
|
|
445
|
+
async function listResources(baseUrl = "https://wafermap.xyz", timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
446
|
+
const host = new URL(baseUrl).host;
|
|
447
|
+
const gaveUp = () => new WafermapError("timeout", `${host} did not answer within ${Math.round(timeoutMs / 1e3)} s. Nothing was paid.`);
|
|
448
|
+
const signal = AbortSignal.timeout(timeoutMs);
|
|
449
|
+
const r = await fetch(`${baseUrl.replace(/\/$/, "")}/v1/resources`, { signal }).catch((e) => {
|
|
450
|
+
throw timedOut(e) ? gaveUp() : new WafermapError("network_error", `${host}: ${e?.message ?? "request failed"}`);
|
|
451
|
+
});
|
|
452
|
+
const j = await r.json().catch((e) => {
|
|
453
|
+
if (timedOut(e)) throw gaveUp();
|
|
454
|
+
return null;
|
|
455
|
+
});
|
|
456
|
+
if (!r.ok || !Array.isArray(j?.data)) throw new WafermapError("network_error", `${host} returned no catalog (HTTP ${r.status}).`);
|
|
457
|
+
return j.data;
|
|
458
|
+
}
|
|
445
459
|
var Wafermap = class {
|
|
446
460
|
account;
|
|
447
461
|
baseUrl;
|
|
@@ -580,12 +594,7 @@ var Wafermap = class {
|
|
|
580
594
|
}
|
|
581
595
|
// ---- Wafermap resources ----
|
|
582
596
|
async resources() {
|
|
583
|
-
|
|
584
|
-
const r = await this.fetch(`${this.baseUrl}/v1/resources`).catch((e) => {
|
|
585
|
-
throw timedOut(e) ? this.timeoutError(host) : new WafermapError("network_error", `${host}: ${e?.message ?? "request failed"}`);
|
|
586
|
-
});
|
|
587
|
-
const j = await this.body(r, () => r.json(), null, host);
|
|
588
|
-
return j?.data ?? [];
|
|
597
|
+
return listResources(this.baseUrl, this.timeoutMs);
|
|
589
598
|
}
|
|
590
599
|
async call(id, input = {}, o = {}) {
|
|
591
600
|
const go = () => this.paidFetch("resource", id, `${this.baseUrl}/v1/${id}`, { method: "POST", headers: { "content-type": "application/json", "x-wafermap-payer": this.address }, body: JSON.stringify(input) }, o.network);
|
|
@@ -711,6 +720,7 @@ export {
|
|
|
711
720
|
choose,
|
|
712
721
|
decodeSwap,
|
|
713
722
|
executeSwap,
|
|
723
|
+
listResources,
|
|
714
724
|
mergeLog,
|
|
715
725
|
quoteSwap,
|
|
716
726
|
readQuote,
|
package/dist/types/src/core.d.ts
CHANGED
|
@@ -87,6 +87,8 @@ export declare function mergeLog(entries: SpendEntry[]): SpendEntry[];
|
|
|
87
87
|
export declare function checkPayment(e: Pick<SpendEntry, "network" | "payer" | "nonce" | "validBefore">, rpc?: string): Promise<"used" | "unused" | "pending" | "unknown" | "error">;
|
|
88
88
|
export declare const DEFAULT_LIMITS: Limits;
|
|
89
89
|
export declare const DEFAULT_TIMEOUT_MS = 60000;
|
|
90
|
+
/** Wafermap's resource catalog. Needs no wallet; gives up after `timeoutMs` like every other request. */
|
|
91
|
+
export declare function listResources(baseUrl?: string, timeoutMs?: number): Promise<any[]>;
|
|
90
92
|
export declare class Wafermap {
|
|
91
93
|
readonly account: LocalAccount;
|
|
92
94
|
readonly baseUrl: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { Wafermap, WafermapError, DEFAULT_LIMITS, DEFAULT_TIMEOUT_MS, checkPayment, mergeLog } from "./core.js";
|
|
1
|
+
export { Wafermap, WafermapError, DEFAULT_LIMITS, DEFAULT_TIMEOUT_MS, checkPayment, listResources, mergeLog } from "./core.js";
|
|
2
2
|
export type { Approval, Limits, Net, PaymentStatus, SpendEntry, SwapQuote, Token, WafermapOptions } from "./core.js";
|
|
3
3
|
export { x402fetch, readQuote, choose, signPayment, PAY_CHAINS, MAX_VALIDITY_SECONDS, X402Error } from "../shared/x402client";
|
|
4
4
|
export { quoteSwap, executeSwap, decodeSwap, KYBER_ROUTER, SwapError } from "../shared/swap";
|
package/package.json
CHANGED
|
@@ -1,68 +1,69 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@wafermapxyz/cli",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "Wafermap for agents: an encrypted local wallet that pays x402 resources, discovers and pays third-party x402 services (USDG on Robinhood Chain; USDC on Solana, Base, Arbitrum, Polygon), and swaps on Robinhood Chain and Base. CLI, SDK and MCP server.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"wafermap": "dist/cli.js"
|
|
8
|
-
},
|
|
9
|
-
"main": "dist/index.js",
|
|
10
|
-
"types": "dist/types/src/index.d.ts",
|
|
11
|
-
"exports": {
|
|
12
|
-
".": {
|
|
13
|
-
"types": "./dist/types/src/index.d.ts",
|
|
14
|
-
"default": "./dist/index.js"
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
"files": [
|
|
18
|
-
"dist",
|
|
19
|
-
"README.md",
|
|
20
|
-
"CHANGELOG.md",
|
|
21
|
-
"LICENSE"
|
|
22
|
-
],
|
|
23
|
-
"engines": {
|
|
24
|
-
"node": ">=20"
|
|
25
|
-
},
|
|
26
|
-
"scripts": {
|
|
27
|
-
"build": "node build.mjs",
|
|
28
|
-
"test": "node --test"
|
|
29
|
-
},
|
|
30
|
-
"keywords": [
|
|
31
|
-
"x402",
|
|
32
|
-
"agents",
|
|
33
|
-
"mcp",
|
|
34
|
-
"wallet",
|
|
35
|
-
"robinhood-chain",
|
|
36
|
-
"base",
|
|
37
|
-
"arbitrum",
|
|
38
|
-
"polygon",
|
|
39
|
-
"solana",
|
|
40
|
-
"usdg",
|
|
41
|
-
"usdc"
|
|
42
|
-
],
|
|
43
|
-
"homepage": "https://wafermap.xyz/docs#cli",
|
|
44
|
-
"repository": {
|
|
45
|
-
"type": "git",
|
|
46
|
-
"url": "git+https://github.com/wafermap/wafermap-cli.git"
|
|
47
|
-
},
|
|
48
|
-
"bugs": {
|
|
49
|
-
"url": "https://github.com/wafermap/wafermap-cli/issues"
|
|
50
|
-
},
|
|
51
|
-
"license": "MIT",
|
|
52
|
-
"publishConfig": {
|
|
53
|
-
"access": "public"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"@
|
|
58
|
-
"@noble/
|
|
59
|
-
"@
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
|
|
68
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@wafermapxyz/cli",
|
|
3
|
+
"version": "0.4.3",
|
|
4
|
+
"description": "Wafermap for agents: an encrypted local wallet that pays x402 resources, discovers and pays third-party x402 services (USDG on Robinhood Chain; USDC on Solana, Base, Arbitrum, Polygon), and swaps on Robinhood Chain and Base. CLI, SDK and MCP server.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"wafermap": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"types": "dist/types/src/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/types/src/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"CHANGELOG.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "node build.mjs",
|
|
28
|
+
"test": "node --test"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"x402",
|
|
32
|
+
"agents",
|
|
33
|
+
"mcp",
|
|
34
|
+
"wallet",
|
|
35
|
+
"robinhood-chain",
|
|
36
|
+
"base",
|
|
37
|
+
"arbitrum",
|
|
38
|
+
"polygon",
|
|
39
|
+
"solana",
|
|
40
|
+
"usdg",
|
|
41
|
+
"usdc"
|
|
42
|
+
],
|
|
43
|
+
"homepage": "https://wafermap.xyz/docs#cli",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/wafermap/wafermap-cli.git"
|
|
47
|
+
},
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/wafermap/wafermap-cli/issues"
|
|
50
|
+
},
|
|
51
|
+
"license": "MIT",
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public",
|
|
54
|
+
"provenance": true
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@modelcontextprotocol/sdk": "1.32.1",
|
|
58
|
+
"@noble/curves": "^1.9.1",
|
|
59
|
+
"@noble/hashes": "^1.8.0",
|
|
60
|
+
"@scure/base": "^1.2.6",
|
|
61
|
+
"viem": "^2.56.8",
|
|
62
|
+
"zod": "4.6.7"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@types/node": "^22.10.0",
|
|
66
|
+
"esbuild": "^0.28.1",
|
|
67
|
+
"typescript": "^7.0.2"
|
|
68
|
+
}
|
|
69
|
+
}
|