@wafermapxyz/cli 0.4.7 → 0.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +5 -3
- package/dist/cli.js +71 -21
- package/dist/index.js +51 -7
- package/dist/types/shared/x402client.d.ts +1 -1
- package/dist/types/src/core.d.ts +4 -1
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.9 (7 Nov 2026)
|
|
4
|
+
|
|
5
|
+
**`wafermap call <llm> --stream`: watch the answer arrive.**
|
|
6
|
+
|
|
7
|
+
- With `--stream`, an LLM resource answers as server-sent events and the CLI prints each piece as it comes. The payment is the same signature as before; Wafermap settles it only after the last piece, so a model that stops halfway charges nothing (the CLI then exits with `stream_broken`).
|
|
8
|
+
- Free calls ($WAFERMAP Pro) and `--pay wafermap` still answer in one JSON body; `--stream` prints that the usual way.
|
|
9
|
+
- `Wafermap.call()` takes `onDelta(text)` for the same thing from code.
|
|
10
|
+
|
|
11
|
+
## 0.4.8 (5 Nov 2026)
|
|
12
|
+
|
|
13
|
+
**Two more payment networks: Arc and Soneium.**
|
|
14
|
+
|
|
15
|
+
- `--network arc` pays in USDC on Arc (eip155:5042, the ERC-20 face of Arc's native USDC at `0x3600…0000`, 6 decimals) and `--network soneium` in bridged USDC.e on Soneium (eip155:1868). Both are EIP-3009 transfers, gasless for you; Wafermap settles them through the Elsa facilitator. The EIP-712 domains (`"USDC", "2"` and `"Bridged USDC (Soneium)", "2"`) were checked against each token's `DOMAIN_SEPARATOR()`.
|
|
16
|
+
- `balance`, `withdraw` and `history --verify` cover both. RPCs: `WAFERMAP_RPC_ARC`, `WAFERMAP_RPC_SONEIUM` (public ones by default). On Arc the gas token is USDC itself; on Soneium it is ETH.
|
|
17
|
+
- The MCP server's `network` option takes `arc` and `soneium`.
|
|
18
|
+
- LLM resources can stream: pass `accept: text/event-stream` to `x402fetch` and read `delta`, `receipt` and `done` events (https://wafermap.xyz/docs#streaming). Payment works as before and settles once the model finished.
|
|
19
|
+
|
|
3
20
|
## 0.4.7 (4 Nov 2026)
|
|
4
21
|
|
|
5
22
|
**`history --verify` checks Solana payments.** Until now they showed `unknown`.
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ The CLI, SDK and MCP server behind [Wafermap](https://wafermap.xyz): an x402 wal
|
|
|
6
6
|
- **discover** third-party x402 services in the open x402 index and **pay** them
|
|
7
7
|
- **swap** tokens through the KyberSwap aggregator
|
|
8
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
|
|
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, Polygon, Arc or Soneium and are gasless; swaps need a little ETH for gas.
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
@@ -35,7 +35,7 @@ wafermap swap 0.01 ETH USDG
|
|
|
35
35
|
wafermap history
|
|
36
36
|
```
|
|
37
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.
|
|
38
|
+
Add `--yes` to skip the confirmation prompt (caps still apply), `--network base`, `arbitrum`, `polygon`, `arc`, `soneium` 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
39
|
|
|
40
40
|
## MCP server
|
|
41
41
|
|
|
@@ -82,7 +82,9 @@ Pass `approve: (a) => boolean` to confirm each payment or swap yourself, and `le
|
|
|
82
82
|
|
|
83
83
|
## $WAFERMAP holder discount
|
|
84
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…, or run `wafermap holder`.
|
|
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, Arc, Soneium). 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…, or run `wafermap holder`.
|
|
86
|
+
|
|
87
|
+
LLM resources can **stream**: `wafermap call gemma-4 prompt="hi" --stream` prints the answer as it arrives, and the payment settles only once the model finishes.
|
|
86
88
|
|
|
87
89
|
Wafermap's own resources can also be **paid in $WAFERMAP at 50% off, and the tokens are burned**: `wafermap approve-wafermap` once (a Permit2 approval, one Robinhood Chain transaction), then `wafermap call <id> --pay wafermap`. Each payment is a Permit2 signature for one exact amount, usable for two minutes only by Wafermap's relayer (fixed in the client), checked against a market price the client reads itself.
|
|
88
90
|
|
package/dist/cli.js
CHANGED
|
@@ -447,7 +447,7 @@ function choose(q, prefer = CHAIN_KEYS) {
|
|
|
447
447
|
const r = q.accepts.find((a) => a.chain.key === k);
|
|
448
448
|
if (r) return r;
|
|
449
449
|
}
|
|
450
|
-
throw new X402Error("no_supported_payment", q.offered.length ? `This service only accepts ${[...new Set(q.offered)].join(", ")}. Wafermap pays USDG on Robinhood Chain or USDC on Solana, Base, Arbitrum or
|
|
450
|
+
throw new X402Error("no_supported_payment", q.offered.length ? `This service only accepts ${[...new Set(q.offered)].join(", ")}. Wafermap pays USDG on Robinhood Chain or USDC on Solana, Base, Arbitrum, Polygon, Arc or Soneium.` : "The service offered no payment option.");
|
|
451
451
|
}
|
|
452
452
|
async function signPayment(signer, req, resource, solana) {
|
|
453
453
|
if (req.chain.vm === "svm") {
|
|
@@ -535,7 +535,10 @@ var init_x402client = __esm({
|
|
|
535
535
|
{ key: "solana", vm: "svm", id: 0, caip: SOLANA_CAIP, v1: "solana", stable: SOLANA_USDC, symbol: "USDC", name: "Solana" },
|
|
536
536
|
{ key: "base", vm: "evm", id: 8453, caip: "eip155:8453", v1: "base", stable: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", symbol: "USDC", name: "Base", domain: USDC_DOMAIN },
|
|
537
537
|
{ key: "arbitrum", vm: "evm", id: 42161, caip: "eip155:42161", v1: "arbitrum", stable: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", symbol: "USDC", name: "Arbitrum", domain: USDC_DOMAIN },
|
|
538
|
-
{ key: "polygon", vm: "evm", id: 137, caip: "eip155:137", v1: "polygon", stable: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", symbol: "USDC", name: "Polygon", domain: USDC_DOMAIN }
|
|
538
|
+
{ key: "polygon", vm: "evm", id: 137, caip: "eip155:137", v1: "polygon", stable: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", symbol: "USDC", name: "Polygon", domain: USDC_DOMAIN },
|
|
539
|
+
// EIP-712 domains read from the token contracts (name(), version()) and checked against DOMAIN_SEPARATOR().
|
|
540
|
+
{ key: "arc", vm: "evm", id: 5042, caip: "eip155:5042", v1: "arc", stable: "0x3600000000000000000000000000000000000000", symbol: "USDC", name: "Arc", domain: { name: "USDC", version: "2" } },
|
|
541
|
+
{ key: "soneium", vm: "evm", id: 1868, caip: "eip155:1868", v1: "soneium", stable: "0xbA9986D2381edf1DA03B0B9c1f8b00dc4AacC369", symbol: "USDC", name: "Soneium", domain: { name: "Bridged USDC (Soneium)", version: "2" } }
|
|
539
542
|
];
|
|
540
543
|
EVM_CHAINS = PAY_CHAINS.filter((c) => c.vm === "evm");
|
|
541
544
|
CHAIN_KEYS = PAY_CHAINS.map((c) => c.key);
|
|
@@ -643,7 +646,7 @@ __export(core_exports, {
|
|
|
643
646
|
});
|
|
644
647
|
import { createPublicClient, createWalletClient, erc20Abi as erc20Abi2, formatUnits as formatUnits2, http, pad, parseAbiItem, toEventSelector } from "viem";
|
|
645
648
|
import { privateKeyToAccount } from "viem/accounts";
|
|
646
|
-
import { arbitrum, base, polygon, robinhood } from "viem/chains";
|
|
649
|
+
import { arbitrum, arc, base, polygon, robinhood, soneium } from "viem/chains";
|
|
647
650
|
function mergeLog(entries) {
|
|
648
651
|
const outcomes = new Map(entries.filter((e) => e.of).map((e) => [e.of, e]));
|
|
649
652
|
return entries.filter((e) => !e.of).map((e) => {
|
|
@@ -748,7 +751,7 @@ var init_core = __esm({
|
|
|
748
751
|
}
|
|
749
752
|
code;
|
|
750
753
|
};
|
|
751
|
-
CHAINS = { robinhood, base, arbitrum, polygon };
|
|
754
|
+
CHAINS = { robinhood, base, arbitrum, polygon, arc, soneium };
|
|
752
755
|
AUTH_STATE_ABI = [{ type: "function", name: "authorizationState", stateMutability: "view", inputs: [{ name: "authorizer", type: "address" }, { name: "nonce", type: "bytes32" }], outputs: [{ type: "bool" }] }];
|
|
753
756
|
AUTH_USED = parseAbiItem("event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce)");
|
|
754
757
|
DEFAULT_LIMITS = { perPayment: 0.1, perDay: 1, perSwap: 25 };
|
|
@@ -929,19 +932,59 @@ var init_core = __esm({
|
|
|
929
932
|
throw new WafermapError("approve_failed", e?.shortMessage ?? e?.message ?? "The approval failed.");
|
|
930
933
|
}
|
|
931
934
|
}
|
|
935
|
+
/** Reads /v1's server-sent events for an LLM call: delta pieces as they arrive, then receipt (or error), then done. */
|
|
936
|
+
async readStream(res, onDelta) {
|
|
937
|
+
const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
938
|
+
let buf = "";
|
|
939
|
+
let receipt = null;
|
|
940
|
+
let done = null;
|
|
941
|
+
let error = null;
|
|
942
|
+
for (; ; ) {
|
|
943
|
+
const { value, done: end } = await reader.read();
|
|
944
|
+
if (end) break;
|
|
945
|
+
buf += value;
|
|
946
|
+
let i;
|
|
947
|
+
while ((i = buf.indexOf("\n\n")) >= 0) {
|
|
948
|
+
const block = buf.slice(0, i);
|
|
949
|
+
buf = buf.slice(i + 2);
|
|
950
|
+
const ev = /^event: (.+)$/m.exec(block)?.[1];
|
|
951
|
+
let data = null;
|
|
952
|
+
try {
|
|
953
|
+
data = JSON.parse(/^data: (.*)$/m.exec(block)?.[1] ?? "null");
|
|
954
|
+
} catch {
|
|
955
|
+
}
|
|
956
|
+
if (ev === "delta" && typeof data?.text === "string") onDelta(data.text);
|
|
957
|
+
else if (ev === "receipt") receipt = data;
|
|
958
|
+
else if (ev === "done") done = data;
|
|
959
|
+
else if (ev === "error") error = data;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
return { receipt, done, error };
|
|
963
|
+
}
|
|
932
964
|
async call(id, input = {}, o = {}) {
|
|
933
965
|
const inToken = o.payIn === "wafermap";
|
|
966
|
+
const stream = !!o.onDelta && !inToken;
|
|
934
967
|
if (inToken && id.startsWith("m/")) throw new WafermapError("token_not_offered", "Marketplace listings are paid in USDG or USDC to their seller, not in $WAFERMAP.");
|
|
935
968
|
if (inToken && !await this.wafermapApproved().catch(() => true)) throw new WafermapError("permit2_not_approved", "Paying in $WAFERMAP needs a one-time approval of Permit2. Run `wafermap approve-wafermap` (one transaction on Robinhood Chain, paid in ETH), then call again.");
|
|
936
969
|
const pass = id.startsWith("m/") ? null : await this.holderPass();
|
|
937
|
-
const go = () => this.paidFetch("resource", id, `${this.baseUrl}/v1/${id}`, { method: "POST", headers: { "content-type": "application/json", "x-wafermap-payer": this.address, ...pass ? { [PASS_HEADER]: pass } : {} }, body: JSON.stringify(input) }, o.network, inToken ? "wafermap" : void 0);
|
|
970
|
+
const go = () => this.paidFetch("resource", id, `${this.baseUrl}/v1/${id}`, { method: "POST", headers: { "content-type": "application/json", "x-wafermap-payer": this.address, ...stream ? { accept: "text/event-stream" } : {}, ...pass ? { [PASS_HEADER]: pass } : {} }, body: JSON.stringify(input) }, o.network, inToken ? "wafermap" : void 0);
|
|
938
971
|
const settle = (r2, out2) => {
|
|
939
972
|
if (!r2.paid || !r2.id) return;
|
|
940
|
-
const status = r2.res.status === 402 ? "refused" : r2.res.ok ? "ok" : "failed";
|
|
973
|
+
const status = r2.res.status === 402 ? "refused" : r2.res.ok && !out2?.streamError ? "ok" : "failed";
|
|
941
974
|
this.outcome(r2.id, "resource", id, r2.paid, status, out2?.receipt?.tx ?? r2.paid.tx, typeof out2?.receipt?.amountUsd === "number" ? out2.receipt.amountUsd : null);
|
|
942
975
|
};
|
|
943
976
|
const host = new URL(this.baseUrl).host;
|
|
944
|
-
const read = (r2) => this.body(
|
|
977
|
+
const read = (r2) => this.body(
|
|
978
|
+
r2.res,
|
|
979
|
+
async () => {
|
|
980
|
+
if (!(stream && r2.res.ok && (r2.res.headers.get("content-type") ?? "").includes("text/event-stream"))) return r2.res.json();
|
|
981
|
+
const s = await this.readStream(r2.res, o.onDelta);
|
|
982
|
+
return { resource: id, result: { text: s.done?.text ?? "", model: s.done?.model, usage: s.done?.usage }, receipt: s.receipt, streamError: s.error };
|
|
983
|
+
},
|
|
984
|
+
{},
|
|
985
|
+
host,
|
|
986
|
+
r2.paid?.auth ?? (r2.paid ? {} : null)
|
|
987
|
+
);
|
|
945
988
|
let r = await go();
|
|
946
989
|
let out = await read(r);
|
|
947
990
|
settle(r, out);
|
|
@@ -956,6 +999,7 @@ var init_core = __esm({
|
|
|
956
999
|
const { res, paid } = r;
|
|
957
1000
|
const refused = this.rejected(res, out, paid);
|
|
958
1001
|
if (refused) throw refused;
|
|
1002
|
+
if (out?.streamError) throw new WafermapError(out.streamError.code ?? "stream_failed", out.streamError.message ?? "The answer did not finish. You were not charged.");
|
|
959
1003
|
if (!res.ok) throw new WafermapError(out?.error?.code ?? "http_error", out?.error?.message ?? (typeof out?.error === "string" ? out.error : `HTTP ${res.status}`));
|
|
960
1004
|
return out;
|
|
961
1005
|
}
|
|
@@ -1226,7 +1270,7 @@ function openWallet(password2, approve) {
|
|
|
1226
1270
|
limits: c.limits,
|
|
1227
1271
|
ledger: { entries: spendLog, record: appendSpend },
|
|
1228
1272
|
approve,
|
|
1229
|
-
rpc: { robinhood: process.env.WAFERMAP_RPC_ROBINHOOD, base: process.env.WAFERMAP_RPC_BASE, arbitrum: process.env.WAFERMAP_RPC_ARBITRUM, polygon: process.env.WAFERMAP_RPC_POLYGON, solana: process.env.WAFERMAP_RPC_SOLANA }
|
|
1273
|
+
rpc: { robinhood: process.env.WAFERMAP_RPC_ROBINHOOD, base: process.env.WAFERMAP_RPC_BASE, arbitrum: process.env.WAFERMAP_RPC_ARBITRUM, polygon: process.env.WAFERMAP_RPC_POLYGON, arc: process.env.WAFERMAP_RPC_ARC, soneium: process.env.WAFERMAP_RPC_SONEIUM, solana: process.env.WAFERMAP_RPC_SOLANA }
|
|
1230
1274
|
});
|
|
1231
1275
|
if (wm.solanaAddress) rememberSolana(wm.solanaAddress);
|
|
1232
1276
|
return wm;
|
|
@@ -1244,7 +1288,7 @@ var VERSION;
|
|
|
1244
1288
|
var init_version = __esm({
|
|
1245
1289
|
"src/version.ts"() {
|
|
1246
1290
|
"use strict";
|
|
1247
|
-
VERSION = "0.4.
|
|
1291
|
+
VERSION = "0.4.9";
|
|
1248
1292
|
}
|
|
1249
1293
|
});
|
|
1250
1294
|
|
|
@@ -1363,7 +1407,7 @@ async function runMcp() {
|
|
|
1363
1407
|
"wafermap_discover",
|
|
1364
1408
|
{
|
|
1365
1409
|
title: "Discover x402 services",
|
|
1366
|
-
description: "Search the open x402 index for third-party paid APIs (weather, search, enrichment, data\u2026). Services marked payable accept USDG on Robinhood Chain or USDC on Solana, Base, Arbitrum or
|
|
1410
|
+
description: "Search the open x402 index for third-party paid APIs (weather, search, enrichment, data\u2026). Services marked payable accept USDG on Robinhood Chain or USDC on Solana, Base, Arbitrum, Polygon, Arc or Soneium and can be called with wafermap_pay.",
|
|
1367
1411
|
inputSchema: { query: z.string().min(2).describe("What you need, in a few words."), limit: z.number().int().min(1).max(20).optional() }
|
|
1368
1412
|
},
|
|
1369
1413
|
async ({ query, limit }) => {
|
|
@@ -1441,7 +1485,7 @@ var init_mcp = __esm({
|
|
|
1441
1485
|
init_local();
|
|
1442
1486
|
init_store();
|
|
1443
1487
|
init_version();
|
|
1444
|
-
NET = z.enum(["robinhood", "solana", "base", "arbitrum", "polygon"]).optional().describe("Payment network: robinhood (USDG), or solana, base, arbitrum or
|
|
1488
|
+
NET = z.enum(["robinhood", "solana", "base", "arbitrum", "polygon", "arc", "soneium"]).optional().describe("Payment network: robinhood (USDG), or solana, base, arbitrum, polygon, arc or soneium (USDC). Defaults to your configured network.");
|
|
1445
1489
|
SWAP_NET = z.enum(["robinhood", "base"]).optional().describe("Swap network: robinhood or base. Defaults to your configured network when it is one of these, else robinhood.");
|
|
1446
1490
|
}
|
|
1447
1491
|
});
|
|
@@ -1775,7 +1819,9 @@ var PAY_NETWORKS = {
|
|
|
1775
1819
|
solana: { id: 0, caip: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", name: "Solana", asset: "USDC", explorer: "https://solscan.io", vm: "svm" },
|
|
1776
1820
|
base: { ...NETWORKS.base, vm: "evm" },
|
|
1777
1821
|
arbitrum: { id: 42161, caip: "eip155:42161", name: "Arbitrum", asset: "USDC", explorer: "https://arbiscan.io", vm: "evm" },
|
|
1778
|
-
polygon: { id: 137, caip: "eip155:137", name: "Polygon", asset: "USDC", explorer: "https://polygonscan.com", vm: "evm" }
|
|
1822
|
+
polygon: { id: 137, caip: "eip155:137", name: "Polygon", asset: "USDC", explorer: "https://polygonscan.com", vm: "evm" },
|
|
1823
|
+
arc: { id: 5042, caip: "eip155:5042", name: "Arc", asset: "USDC", explorer: "https://explorer.arc.io", vm: "evm" },
|
|
1824
|
+
soneium: { id: 1868, caip: "eip155:1868", name: "Soneium", asset: "USDC", explorer: "https://soneium.blockscout.com", vm: "evm" }
|
|
1779
1825
|
};
|
|
1780
1826
|
function payNetworkOf(v) {
|
|
1781
1827
|
const s = String(v ?? "").toLowerCase();
|
|
@@ -1790,7 +1836,7 @@ init_local();
|
|
|
1790
1836
|
init_solana();
|
|
1791
1837
|
init_store();
|
|
1792
1838
|
init_version();
|
|
1793
|
-
var HELP = `wafermap ${VERSION}: an x402 wallet for agents (Robinhood Chain, Solana, Base, Arbitrum, Polygon)
|
|
1839
|
+
var HELP = `wafermap ${VERSION}: an x402 wallet for agents (Robinhood Chain, Solana, Base, Arbitrum, Polygon, Arc, Soneium)
|
|
1794
1840
|
|
|
1795
1841
|
Wallet
|
|
1796
1842
|
wafermap init [--import] create (or import) the agent wallet, encrypted with a password
|
|
@@ -1805,6 +1851,7 @@ Pay
|
|
|
1805
1851
|
wafermap resources [words] Wafermap's pay-per-call catalog
|
|
1806
1852
|
wafermap call <id> [field=value ...] run a resource and pay its price (--json '{...}' for complex input)
|
|
1807
1853
|
wafermap call <id> ... --pay wafermap pay a Wafermap resource in $WAFERMAP at 50% off (the tokens are burned)
|
|
1854
|
+
wafermap call <llm> ... --stream print the answer as it arrives; you pay only once the model finishes
|
|
1808
1855
|
wafermap approve-wafermap one-time approval so the wallet can pay in $WAFERMAP (Robinhood Chain, ETH gas)
|
|
1809
1856
|
wafermap holder what this wallet's $WAFERMAP unlocks: holder price, Pro free calls left today
|
|
1810
1857
|
wafermap pass print a holder pass (X-Wafermap-Pass) for scripts and x402 Tracker Pro
|
|
@@ -1817,7 +1864,7 @@ Trade
|
|
|
1817
1864
|
Agents
|
|
1818
1865
|
wafermap mcp run as an MCP server (stdio); password from \`wafermap keychain on\` or WAFERMAP_PASSWORD
|
|
1819
1866
|
|
|
1820
|
-
Common flags: --network robinhood|solana|base|arbitrum|polygon --yes (skip the confirmation)
|
|
1867
|
+
Common flags: --network robinhood|solana|base|arbitrum|polygon|arc|soneium --yes (skip the confirmation)
|
|
1821
1868
|
Files live in ${HOME}. Docs: https://wafermap.xyz/docs#cli`;
|
|
1822
1869
|
function parse(argv) {
|
|
1823
1870
|
const out = { _: [], flags: {} };
|
|
@@ -1826,7 +1873,7 @@ function parse(argv) {
|
|
|
1826
1873
|
if (a.startsWith("--")) {
|
|
1827
1874
|
const [k, v] = a.slice(2).split("=", 2);
|
|
1828
1875
|
if (v !== void 0) out.flags[k] = v;
|
|
1829
|
-
else if (argv[i + 1] && !argv[i + 1].startsWith("--") && !["yes", "post", "import"].includes(k)) out.flags[k] = argv[++i];
|
|
1876
|
+
else if (argv[i + 1] && !argv[i + 1].startsWith("--") && !["yes", "post", "import", "stream"].includes(k)) out.flags[k] = argv[++i];
|
|
1830
1877
|
else out.flags[k] = true;
|
|
1831
1878
|
} else out._.push(a);
|
|
1832
1879
|
}
|
|
@@ -1919,7 +1966,7 @@ async function main() {
|
|
|
1919
1966
|
console.log(`\u2713 Agent wallet ${acct.address}
|
|
1920
1967
|
Solana address ${sol} (derived from the same key)
|
|
1921
1968
|
Key encrypted in ${HOME}/keystore.json. Forget the password and the key is gone: run \`wafermap export-key\` to back it up.
|
|
1922
|
-
Fund it with a few dollars of USDC on Solana (Solana address), or USDG on Robinhood Chain or USDC on Base, Arbitrum or
|
|
1969
|
+
Fund it with a few dollars of USDC on Solana (Solana address), or USDG on Robinhood Chain or USDC on Base, Arbitrum, Polygon, Arc or Soneium (EVM address). Payments are gasless; swaps need a little ETH.
|
|
1923
1970
|
Caps: $0.10 per payment, $1 per day, $25 per swap. Change with \`wafermap limits\`.`);
|
|
1924
1971
|
return;
|
|
1925
1972
|
}
|
|
@@ -1994,7 +2041,7 @@ async function main() {
|
|
|
1994
2041
|
const rows = mergeLog(spendLog()).filter((e) => e.at >= since).reverse();
|
|
1995
2042
|
if (!rows.length) return console.log(`Nothing in the last ${days} days.`);
|
|
1996
2043
|
const verify = a.flags.verify !== void 0;
|
|
1997
|
-
const RPC = { robinhood: process.env.WAFERMAP_RPC_ROBINHOOD, base: process.env.WAFERMAP_RPC_BASE, arbitrum: process.env.WAFERMAP_RPC_ARBITRUM, polygon: process.env.WAFERMAP_RPC_POLYGON, solana: process.env.WAFERMAP_RPC_SOLANA };
|
|
2044
|
+
const RPC = { robinhood: process.env.WAFERMAP_RPC_ROBINHOOD, base: process.env.WAFERMAP_RPC_BASE, arbitrum: process.env.WAFERMAP_RPC_ARBITRUM, polygon: process.env.WAFERMAP_RPC_POLYGON, arc: process.env.WAFERMAP_RPC_ARC, soneium: process.env.WAFERMAP_RPC_SONEIUM, solana: process.env.WAFERMAP_RPC_SOLANA };
|
|
1998
2045
|
const chain = [];
|
|
1999
2046
|
if (verify) {
|
|
2000
2047
|
const txCount = /* @__PURE__ */ new Map();
|
|
@@ -2039,18 +2086,21 @@ Total $${total.toFixed(4)} in ${pays.length} payments. From 0.3.3 every payment
|
|
|
2039
2086
|
${s.description.slice(0, 140)}${s.input ? `
|
|
2040
2087
|
input: ${JSON.stringify(s.input).slice(0, 140)}` : ""}`);
|
|
2041
2088
|
return console.log(`
|
|
2042
|
-
\u25CF payable with USDG (Robinhood Chain) or USDC (Solana, Base, Arbitrum, Polygon) \u25CB other networks only`);
|
|
2089
|
+
\u25CF payable with USDG (Robinhood Chain) or USDC (Solana, Base, Arbitrum, Polygon, Arc, Soneium) \u25CB other networks only`);
|
|
2043
2090
|
}
|
|
2044
2091
|
case "call": {
|
|
2045
2092
|
const id = a._[0] ?? die("Which resource? e.g. wafermap call gas-now network=base");
|
|
2046
2093
|
const wm = openWallet(await password(), approver(yes));
|
|
2047
2094
|
const payIn = a.flags.pay === "wafermap" ? "wafermap" : a.flags.pay === void 0 || a.flags.pay === "usd" ? void 0 : die("--pay must be wafermap (or leave it out to pay in USDG/USDC)");
|
|
2048
|
-
|
|
2095
|
+
let streamed = false;
|
|
2096
|
+
const onDelta = a.flags.stream === true ? (t) => (streamed = true, process.stdout.write(t)) : void 0;
|
|
2097
|
+
const out = await wm.call(id, inputOf(a, 1), { network: net(a.flags.network), payIn, onDelta });
|
|
2098
|
+
if (streamed) process.stdout.write("\n");
|
|
2049
2099
|
if (out.receipt?.asset === "WAFERMAP") console.error(`\u2713 Paid ${out.receipt.amountTokens} $WAFERMAP (\u2248 $${out.receipt.amountUsd}), burned \xB7 ${EXPLORER(out.receipt.network)}/tx/${out.receipt.tx}`);
|
|
2050
2100
|
else if (out.receipt) console.error(`\u2713 Paid $${out.receipt.amountUsd} ${out.receipt.asset} \xB7 ${EXPLORER(out.receipt.network)}/tx/${out.receipt.tx}`);
|
|
2051
2101
|
else if (out.free) console.error(`\u2713 Free call ($WAFERMAP Pro) \xB7 ${out.free.left} of ${out.free.perDay} left today. Nothing was paid.`);
|
|
2052
2102
|
if (typeof a.flags.out === "string") await saveMedia(out.result, a.flags.out);
|
|
2053
|
-
return printResult(out.result);
|
|
2103
|
+
return streamed ? void 0 : printResult(out.result);
|
|
2054
2104
|
}
|
|
2055
2105
|
case "pay": {
|
|
2056
2106
|
const url = a._[0] ?? die("Which URL? e.g. wafermap pay https://example.com/api city=Paris");
|
|
@@ -2063,7 +2113,7 @@ Total $${total.toFixed(4)} in ${pays.length} payments. From 0.3.3 every payment
|
|
|
2063
2113
|
case "balance": {
|
|
2064
2114
|
const addr = keystoreAddress() ?? die("No wallet yet. Run `wafermap init`.");
|
|
2065
2115
|
const { Wafermap: Wafermap2 } = await Promise.resolve().then(() => (init_core(), core_exports));
|
|
2066
|
-
const wm = new Wafermap2({ account: { ...privateKeyToAccount2(generatePrivateKey()), address: addr }, solanaAddress: keystoreSolana() ?? void 0, baseUrl: process.env.WAFERMAP_URL, rpc: { robinhood: process.env.WAFERMAP_RPC_ROBINHOOD, base: process.env.WAFERMAP_RPC_BASE, arbitrum: process.env.WAFERMAP_RPC_ARBITRUM, polygon: process.env.WAFERMAP_RPC_POLYGON, solana: process.env.WAFERMAP_RPC_SOLANA } });
|
|
2116
|
+
const wm = new Wafermap2({ account: { ...privateKeyToAccount2(generatePrivateKey()), address: addr }, solanaAddress: keystoreSolana() ?? void 0, baseUrl: process.env.WAFERMAP_URL, rpc: { robinhood: process.env.WAFERMAP_RPC_ROBINHOOD, base: process.env.WAFERMAP_RPC_BASE, arbitrum: process.env.WAFERMAP_RPC_ARBITRUM, polygon: process.env.WAFERMAP_RPC_POLYGON, arc: process.env.WAFERMAP_RPC_ARC, soneium: process.env.WAFERMAP_RPC_SONEIUM, solana: process.env.WAFERMAP_RPC_SOLANA } });
|
|
2067
2117
|
const b = await wm.balances();
|
|
2068
2118
|
console.log(addr);
|
|
2069
2119
|
for (const n of NETS) console.log(` ${n.padEnd(10)} ${b[n] ? `${Number(b[n].stable).toFixed(4)} ${b[n].symbol} ${Number(b[n].eth).toPrecision(4)} ${b[n].gas}` : n === "solana" && !wm.solanaAddress ? "run `wafermap address` once with WAFERMAP_PASSWORD set" : "unreadable (RPC did not answer)"}`);
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/core.ts
|
|
2
2
|
import { createPublicClient, createWalletClient, erc20Abi as erc20Abi2, formatUnits as formatUnits2, http, pad, parseAbiItem, toEventSelector } from "viem";
|
|
3
3
|
import { privateKeyToAccount } from "viem/accounts";
|
|
4
|
-
import { arbitrum, base, polygon, robinhood } from "viem/chains";
|
|
4
|
+
import { arbitrum, arc, base, polygon, robinhood, soneium } from "viem/chains";
|
|
5
5
|
|
|
6
6
|
// shared/swap.ts
|
|
7
7
|
import { decodeFunctionData, erc20Abi, formatUnits, getAddress, isAddress, parseAbi, parseUnits } from "viem";
|
|
@@ -357,7 +357,10 @@ var PAY_CHAINS = [
|
|
|
357
357
|
{ key: "solana", vm: "svm", id: 0, caip: SOLANA_CAIP, v1: "solana", stable: SOLANA_USDC, symbol: "USDC", name: "Solana" },
|
|
358
358
|
{ key: "base", vm: "evm", id: 8453, caip: "eip155:8453", v1: "base", stable: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", symbol: "USDC", name: "Base", domain: USDC_DOMAIN },
|
|
359
359
|
{ key: "arbitrum", vm: "evm", id: 42161, caip: "eip155:42161", v1: "arbitrum", stable: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", symbol: "USDC", name: "Arbitrum", domain: USDC_DOMAIN },
|
|
360
|
-
{ key: "polygon", vm: "evm", id: 137, caip: "eip155:137", v1: "polygon", stable: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", symbol: "USDC", name: "Polygon", domain: USDC_DOMAIN }
|
|
360
|
+
{ key: "polygon", vm: "evm", id: 137, caip: "eip155:137", v1: "polygon", stable: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", symbol: "USDC", name: "Polygon", domain: USDC_DOMAIN },
|
|
361
|
+
// EIP-712 domains read from the token contracts (name(), version()) and checked against DOMAIN_SEPARATOR().
|
|
362
|
+
{ key: "arc", vm: "evm", id: 5042, caip: "eip155:5042", v1: "arc", stable: "0x3600000000000000000000000000000000000000", symbol: "USDC", name: "Arc", domain: { name: "USDC", version: "2" } },
|
|
363
|
+
{ key: "soneium", vm: "evm", id: 1868, caip: "eip155:1868", v1: "soneium", stable: "0xbA9986D2381edf1DA03B0B9c1f8b00dc4AacC369", symbol: "USDC", name: "Soneium", domain: { name: "Bridged USDC (Soneium)", version: "2" } }
|
|
361
364
|
];
|
|
362
365
|
var EVM_CHAINS = PAY_CHAINS.filter((c) => c.vm === "evm");
|
|
363
366
|
var CHAIN_KEYS = PAY_CHAINS.map((c) => c.key);
|
|
@@ -466,7 +469,7 @@ function choose(q, prefer = CHAIN_KEYS) {
|
|
|
466
469
|
const r = q.accepts.find((a) => a.chain.key === k);
|
|
467
470
|
if (r) return r;
|
|
468
471
|
}
|
|
469
|
-
throw new X402Error("no_supported_payment", q.offered.length ? `This service only accepts ${[...new Set(q.offered)].join(", ")}. Wafermap pays USDG on Robinhood Chain or USDC on Solana, Base, Arbitrum or
|
|
472
|
+
throw new X402Error("no_supported_payment", q.offered.length ? `This service only accepts ${[...new Set(q.offered)].join(", ")}. Wafermap pays USDG on Robinhood Chain or USDC on Solana, Base, Arbitrum, Polygon, Arc or Soneium.` : "The service offered no payment option.");
|
|
470
473
|
}
|
|
471
474
|
var TYPES = {
|
|
472
475
|
TransferWithAuthorization: [
|
|
@@ -601,7 +604,7 @@ var WafermapError = class extends Error {
|
|
|
601
604
|
}
|
|
602
605
|
code;
|
|
603
606
|
};
|
|
604
|
-
var CHAINS = { robinhood, base, arbitrum, polygon };
|
|
607
|
+
var CHAINS = { robinhood, base, arbitrum, polygon, arc, soneium };
|
|
605
608
|
var AUTH_STATE_ABI = [{ type: "function", name: "authorizationState", stateMutability: "view", inputs: [{ name: "authorizer", type: "address" }, { name: "nonce", type: "bytes32" }], outputs: [{ type: "bool" }] }];
|
|
606
609
|
function mergeLog(entries) {
|
|
607
610
|
const outcomes = new Map(entries.filter((e) => e.of).map((e) => [e.of, e]));
|
|
@@ -868,19 +871,59 @@ var Wafermap = class {
|
|
|
868
871
|
throw new WafermapError("approve_failed", e?.shortMessage ?? e?.message ?? "The approval failed.");
|
|
869
872
|
}
|
|
870
873
|
}
|
|
874
|
+
/** Reads /v1's server-sent events for an LLM call: delta pieces as they arrive, then receipt (or error), then done. */
|
|
875
|
+
async readStream(res, onDelta) {
|
|
876
|
+
const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
877
|
+
let buf = "";
|
|
878
|
+
let receipt = null;
|
|
879
|
+
let done = null;
|
|
880
|
+
let error = null;
|
|
881
|
+
for (; ; ) {
|
|
882
|
+
const { value, done: end } = await reader.read();
|
|
883
|
+
if (end) break;
|
|
884
|
+
buf += value;
|
|
885
|
+
let i;
|
|
886
|
+
while ((i = buf.indexOf("\n\n")) >= 0) {
|
|
887
|
+
const block = buf.slice(0, i);
|
|
888
|
+
buf = buf.slice(i + 2);
|
|
889
|
+
const ev = /^event: (.+)$/m.exec(block)?.[1];
|
|
890
|
+
let data = null;
|
|
891
|
+
try {
|
|
892
|
+
data = JSON.parse(/^data: (.*)$/m.exec(block)?.[1] ?? "null");
|
|
893
|
+
} catch {
|
|
894
|
+
}
|
|
895
|
+
if (ev === "delta" && typeof data?.text === "string") onDelta(data.text);
|
|
896
|
+
else if (ev === "receipt") receipt = data;
|
|
897
|
+
else if (ev === "done") done = data;
|
|
898
|
+
else if (ev === "error") error = data;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
return { receipt, done, error };
|
|
902
|
+
}
|
|
871
903
|
async call(id, input = {}, o = {}) {
|
|
872
904
|
const inToken = o.payIn === "wafermap";
|
|
905
|
+
const stream = !!o.onDelta && !inToken;
|
|
873
906
|
if (inToken && id.startsWith("m/")) throw new WafermapError("token_not_offered", "Marketplace listings are paid in USDG or USDC to their seller, not in $WAFERMAP.");
|
|
874
907
|
if (inToken && !await this.wafermapApproved().catch(() => true)) throw new WafermapError("permit2_not_approved", "Paying in $WAFERMAP needs a one-time approval of Permit2. Run `wafermap approve-wafermap` (one transaction on Robinhood Chain, paid in ETH), then call again.");
|
|
875
908
|
const pass = id.startsWith("m/") ? null : await this.holderPass();
|
|
876
|
-
const go = () => this.paidFetch("resource", id, `${this.baseUrl}/v1/${id}`, { method: "POST", headers: { "content-type": "application/json", "x-wafermap-payer": this.address, ...pass ? { [PASS_HEADER]: pass } : {} }, body: JSON.stringify(input) }, o.network, inToken ? "wafermap" : void 0);
|
|
909
|
+
const go = () => this.paidFetch("resource", id, `${this.baseUrl}/v1/${id}`, { method: "POST", headers: { "content-type": "application/json", "x-wafermap-payer": this.address, ...stream ? { accept: "text/event-stream" } : {}, ...pass ? { [PASS_HEADER]: pass } : {} }, body: JSON.stringify(input) }, o.network, inToken ? "wafermap" : void 0);
|
|
877
910
|
const settle = (r2, out2) => {
|
|
878
911
|
if (!r2.paid || !r2.id) return;
|
|
879
|
-
const status = r2.res.status === 402 ? "refused" : r2.res.ok ? "ok" : "failed";
|
|
912
|
+
const status = r2.res.status === 402 ? "refused" : r2.res.ok && !out2?.streamError ? "ok" : "failed";
|
|
880
913
|
this.outcome(r2.id, "resource", id, r2.paid, status, out2?.receipt?.tx ?? r2.paid.tx, typeof out2?.receipt?.amountUsd === "number" ? out2.receipt.amountUsd : null);
|
|
881
914
|
};
|
|
882
915
|
const host = new URL(this.baseUrl).host;
|
|
883
|
-
const read = (r2) => this.body(
|
|
916
|
+
const read = (r2) => this.body(
|
|
917
|
+
r2.res,
|
|
918
|
+
async () => {
|
|
919
|
+
if (!(stream && r2.res.ok && (r2.res.headers.get("content-type") ?? "").includes("text/event-stream"))) return r2.res.json();
|
|
920
|
+
const s = await this.readStream(r2.res, o.onDelta);
|
|
921
|
+
return { resource: id, result: { text: s.done?.text ?? "", model: s.done?.model, usage: s.done?.usage }, receipt: s.receipt, streamError: s.error };
|
|
922
|
+
},
|
|
923
|
+
{},
|
|
924
|
+
host,
|
|
925
|
+
r2.paid?.auth ?? (r2.paid ? {} : null)
|
|
926
|
+
);
|
|
884
927
|
let r = await go();
|
|
885
928
|
let out = await read(r);
|
|
886
929
|
settle(r, out);
|
|
@@ -895,6 +938,7 @@ var Wafermap = class {
|
|
|
895
938
|
const { res, paid } = r;
|
|
896
939
|
const refused = this.rejected(res, out, paid);
|
|
897
940
|
if (refused) throw refused;
|
|
941
|
+
if (out?.streamError) throw new WafermapError(out.streamError.code ?? "stream_failed", out.streamError.message ?? "The answer did not finish. You were not charged.");
|
|
898
942
|
if (!res.ok) throw new WafermapError(out?.error?.code ?? "http_error", out?.error?.message ?? (typeof out?.error === "string" ? out.error : `HTTP ${res.status}`));
|
|
899
943
|
return out;
|
|
900
944
|
}
|
|
@@ -10,7 +10,7 @@ export type Signer = {
|
|
|
10
10
|
message: any;
|
|
11
11
|
}) => Promise<Hex>;
|
|
12
12
|
};
|
|
13
|
-
export type ChainKey = "robinhood" | "base" | "arbitrum" | "polygon" | "solana";
|
|
13
|
+
export type ChainKey = "robinhood" | "base" | "arbitrum" | "polygon" | "arc" | "soneium" | "solana";
|
|
14
14
|
export type Chain = {
|
|
15
15
|
key: ChainKey;
|
|
16
16
|
vm: "evm" | "svm";
|
package/dist/types/src/core.d.ts
CHANGED
|
@@ -61,7 +61,7 @@ export type WafermapOptions = {
|
|
|
61
61
|
solanaAddress?: string;
|
|
62
62
|
/** Default https://wafermap.xyz */
|
|
63
63
|
baseUrl?: string;
|
|
64
|
-
/** Preferred payment network. Default robinhood (USDG); base, arbitrum, polygon and solana pay in USDC. */
|
|
64
|
+
/** Preferred payment network. Default robinhood (USDG); base, arbitrum, polygon, arc, soneium and solana pay in USDC. */
|
|
65
65
|
network?: Net;
|
|
66
66
|
limits?: Partial<Limits>;
|
|
67
67
|
/** Where spending is remembered for the daily cap. Default: in memory. */
|
|
@@ -155,9 +155,12 @@ export declare class Wafermap {
|
|
|
155
155
|
* transaction on Robinhood Chain, paid in ETH. Each payment after that is a signature for one exact amount,
|
|
156
156
|
* spendable only by Wafermap's relayer for two minutes. */
|
|
157
157
|
approveWafermap(): Promise<string>;
|
|
158
|
+
/** Reads /v1's server-sent events for an LLM call: delta pieces as they arrive, then receipt (or error), then done. */
|
|
159
|
+
private readStream;
|
|
158
160
|
call(id: string, input?: Record<string, unknown>, o?: {
|
|
159
161
|
network?: Net;
|
|
160
162
|
payIn?: "stablecoin" | "wafermap";
|
|
163
|
+
onDelta?: (text: string) => void;
|
|
161
164
|
}): Promise<{
|
|
162
165
|
resource: string;
|
|
163
166
|
result: any;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
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.",
|
|
3
|
+
"version": "0.4.9",
|
|
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, Arc, Soneium), and swaps on Robinhood Chain and Base. CLI, SDK and MCP server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"wafermap": "dist/cli.js"
|
|
@@ -36,6 +36,8 @@
|
|
|
36
36
|
"base",
|
|
37
37
|
"arbitrum",
|
|
38
38
|
"polygon",
|
|
39
|
+
"arc",
|
|
40
|
+
"soneium",
|
|
39
41
|
"solana",
|
|
40
42
|
"usdg",
|
|
41
43
|
"usdc"
|