@wafermapxyz/cli 0.0.0-stage → 0.4.1

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/dist/index.js ADDED
@@ -0,0 +1,719 @@
1
+ // src/core.ts
2
+ import { createPublicClient, createWalletClient, erc20Abi as erc20Abi2, formatUnits as formatUnits2, http } from "viem";
3
+ import { privateKeyToAccount } from "viem/accounts";
4
+ import { arbitrum, base, polygon, robinhood } from "viem/chains";
5
+
6
+ // shared/swap.ts
7
+ import { decodeFunctionData, erc20Abi, formatUnits, getAddress, isAddress, parseAbi, parseUnits } from "viem";
8
+ var NATIVE = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
9
+ var TOKENS = {
10
+ robinhood: [
11
+ { symbol: "ETH", name: "Ether", address: NATIVE, decimals: 18, kind: "native" },
12
+ { symbol: "WETH", name: "Wrapped Ether", address: "0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73", decimals: 18, kind: "wrapped" },
13
+ { symbol: "USDG", name: "Global Dollar", address: "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168", decimals: 6, kind: "stable" }
14
+ ],
15
+ base: [
16
+ { symbol: "ETH", name: "Ether", address: NATIVE, decimals: 18, kind: "native" },
17
+ { symbol: "WETH", name: "Wrapped Ether", address: "0x4200000000000000000000000000000000000006", decimals: 18, kind: "wrapped" },
18
+ { symbol: "USDC", name: "USD Coin", address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", decimals: 6, kind: "stable" }
19
+ ]
20
+ };
21
+ var KYBER_ROUTER = {
22
+ robinhood: "0x6131B5fae19EA4f9D964eAc0408E4408b66337b5",
23
+ base: "0x6131B5fae19EA4f9D964eAc0408E4408b66337b5"
24
+ };
25
+ function checkRouter(net, router) {
26
+ const r = typeof router === "string" && isAddress(router) ? getAddress(router) : null;
27
+ if (r !== KYBER_ROUTER[net]) throw new SwapError("unexpected_router", `The swap API named router ${String(router)}, not KyberSwap's router ${KYBER_ROUTER[net]} on ${net}. Nothing was approved or sent.`);
28
+ return r;
29
+ }
30
+ var KYBER = (net) => `https://aggregator-api.kyberswap.com/${net}/api/v1`;
31
+ var HEADERS = { "x-client-id": "wafermap", accept: "application/json" };
32
+ var SwapError = class extends Error {
33
+ constructor(code, message) {
34
+ super(message);
35
+ this.code = code;
36
+ }
37
+ code;
38
+ };
39
+ async function resolveToken(net, ref, client, lookup) {
40
+ const r = ref.trim();
41
+ let t = null;
42
+ if (isAddress(r)) t = TOKENS[net].find((x) => x.address.toLowerCase() === r.toLowerCase()) ?? { symbol: "?", name: "", address: getAddress(r), decimals: null };
43
+ else t = TOKENS[net].find((x) => x.symbol.toLowerCase() === r.toLowerCase()) ?? (lookup ? await lookup(net, r) : null);
44
+ if (!t) throw new SwapError("unknown_token", `Unknown token "${r}" on ${net}. Use a symbol (ETH, ${net === "base" ? "USDC" : "USDG, TSLA"}) or a 0x address.`);
45
+ if (t.decimals === null || t.symbol === "?") {
46
+ const [dec, sym] = await client.multicall({ contracts: [{ address: t.address, abi: erc20Abi, functionName: "decimals" }, { address: t.address, abi: erc20Abi, functionName: "symbol" }], allowFailure: true });
47
+ if (dec.status !== "success") throw new SwapError("not_a_token", `${t.address} is not an ERC-20 token on ${net}.`);
48
+ t = { ...t, decimals: Number(dec.result), symbol: t.symbol === "?" && sym.status === "success" ? String(sym.result) : t.symbol };
49
+ }
50
+ return t;
51
+ }
52
+ async function quoteSwap(o) {
53
+ const f = o.fetch ?? fetch;
54
+ if (o.tokenIn.address.toLowerCase() === o.tokenOut.address.toLowerCase()) throw new SwapError("same_token", "Pick two different tokens.");
55
+ let units;
56
+ try {
57
+ units = parseUnits(o.amount, o.tokenIn.decimals ?? 18);
58
+ } catch {
59
+ throw new SwapError("invalid_amount", `"${o.amount}" is not an amount.`);
60
+ }
61
+ if (units <= 0n) throw new SwapError("invalid_amount", "Amount must be above zero.");
62
+ const q = new URLSearchParams({ tokenIn: o.tokenIn.address, tokenOut: o.tokenOut.address, amountIn: units.toString(), gasInclude: "true" });
63
+ const res = await f(`${KYBER(o.network)}/routes?${q}`, { headers: HEADERS }).catch(() => null);
64
+ const j = await res?.json().catch(() => null);
65
+ const s = j?.data?.routeSummary;
66
+ if (!res?.ok || !s) throw new SwapError("no_route", j?.message && j.message !== "successfully" ? `No route: ${j.message}` : "No swap route found for that pair and size.");
67
+ const inUsd = Number(s.amountInUsd);
68
+ const outUsd = Number(s.amountOutUsd);
69
+ return {
70
+ network: o.network,
71
+ tokenIn: o.tokenIn,
72
+ tokenOut: o.tokenOut,
73
+ amountIn: o.amount,
74
+ amountOut: formatUnits(BigInt(s.amountOut), o.tokenOut.decimals ?? 18),
75
+ amountInUsd: Number.isFinite(inUsd) && inUsd > 0 ? inUsd : null,
76
+ amountOutUsd: Number.isFinite(outUsd) && outUsd > 0 ? outUsd : null,
77
+ gasUsd: Number(s.gasUsd) || null,
78
+ impactPct: inUsd > 0 && outUsd > 0 ? Math.round((1 - outUsd / inUsd) * 1e4) / 100 : null,
79
+ routerAddress: checkRouter(o.network, j.data.routerAddress),
80
+ route: s,
81
+ at: Date.now()
82
+ };
83
+ }
84
+ var SWAP_DESC = "(address srcToken, address dstToken, address[] srcReceivers, uint256[] srcAmounts, address[] feeReceivers, uint256[] feeAmounts, address dstReceiver, uint256 amount, uint256 minReturnAmount, uint256 flags, bytes permit)";
85
+ var ROUTER_ABI = parseAbi([
86
+ `function swap((address callTarget, address approveTarget, bytes targetData, ${SWAP_DESC} desc, bytes clientData) execution) payable returns (uint256, uint256)`,
87
+ `function swapSimpleMode(address caller, ${SWAP_DESC} desc, bytes executorData, bytes clientData) returns (uint256, uint256)`
88
+ ]);
89
+ function decodeSwap(data) {
90
+ let d;
91
+ try {
92
+ d = decodeFunctionData({ abi: ROUTER_ABI, data });
93
+ } catch {
94
+ throw new SwapError("unexpected_calldata", "The swap API returned a call this wallet does not recognise. Nothing was sent.");
95
+ }
96
+ const desc = d.functionName === "swap" ? d.args[0].desc : d.args[1];
97
+ return { fn: d.functionName, srcToken: getAddress(desc.srcToken), dstToken: getAddress(desc.dstToken), receiver: getAddress(desc.dstReceiver), amount: desc.amount, minReturn: desc.minReturnAmount, feeReceivers: desc.feeReceivers, feeAmounts: desc.feeAmounts };
98
+ }
99
+ async function executeSwap(o) {
100
+ const f = o.fetch ?? fetch;
101
+ const { quote } = o;
102
+ const account = o.wallet.account;
103
+ if (!account) throw new SwapError("no_account", "No wallet account to swap from.");
104
+ if (Date.now() - quote.at > 9e4) throw new SwapError("stale_quote", "The quote is older than 90 seconds. Get a fresh one.");
105
+ checkRouter(quote.network, quote.routerAddress);
106
+ const from = account.address;
107
+ const amountIn = parseUnits(quote.amountIn, quote.tokenIn.decimals ?? 18);
108
+ const slippageBps = o.slippageBps ?? 100;
109
+ if (!(slippageBps > 0 && slippageBps <= 500)) throw new SwapError("invalid_slippage", "Slippage must be above 0 and at most 5%.");
110
+ const native = quote.tokenIn.address.toLowerCase() === NATIVE.toLowerCase();
111
+ const eth = await o.client.getBalance({ address: from });
112
+ if (eth === 0n) throw new SwapError("no_gas", `The wallet has no ETH on ${quote.network} to pay gas. Send a little ETH first.`);
113
+ if (native ? eth < amountIn : await o.client.readContract({ address: quote.tokenIn.address, abi: erc20Abi, functionName: "balanceOf", args: [from] }) < amountIn)
114
+ throw new SwapError("insufficient_balance", `Not enough ${quote.tokenIn.symbol} in ${from.slice(0, 6)}\u2026${from.slice(-4)}.`);
115
+ o.step?.("building");
116
+ const res = await f(`${KYBER(quote.network)}/route/build`, {
117
+ method: "POST",
118
+ headers: { ...HEADERS, "content-type": "application/json" },
119
+ body: JSON.stringify({ routeSummary: quote.route, sender: from, recipient: from, slippageTolerance: slippageBps, deadline: Math.floor(Date.now() / 1e3) + 1200, source: "wafermap" })
120
+ }).catch(() => null);
121
+ const j = await res?.json().catch(() => null);
122
+ if (!res?.ok || !j?.data?.data) throw new SwapError("build_failed", j?.message ? `Route build failed: ${j.message}` : "Could not build the swap. Get a fresh quote.");
123
+ const b = j.data;
124
+ const to = checkRouter(quote.network, b.routerAddress);
125
+ const big = (v) => (typeof v === "string" || typeof v === "number") && /^\d+$/.test(String(v)) ? BigInt(v) : null;
126
+ const value = big(b.transactionValue ?? "0");
127
+ const builtOut = big(b.amountOut);
128
+ const quotedOut = parseUnits(quote.amountOut, quote.tokenOut.decimals ?? 18);
129
+ const call = decodeSwap(b.data);
130
+ const mismatch = (what) => {
131
+ throw new SwapError("build_mismatch", `The built swap ${what}. Nothing was approved or sent.`);
132
+ };
133
+ if (call.srcToken !== getAddress(quote.tokenIn.address) || call.dstToken !== getAddress(quote.tokenOut.address)) mismatch("trades other tokens than the quote");
134
+ if (call.amount !== amountIn) mismatch("spends a different amount than the quote");
135
+ if (call.receiver !== getAddress(from)) mismatch(`sends the output to ${call.receiver}, not to this wallet`);
136
+ if (call.feeAmounts.some((x) => x > 0n)) mismatch("takes a fee");
137
+ if (value === null || value !== (native ? amountIn : 0n)) mismatch("sends a different amount of ETH than the quote");
138
+ if (builtOut === null || builtOut * 10000n < quotedOut * BigInt(1e4 - slippageBps)) throw new SwapError("price_moved", "The price moved past your slippage since the quote. Nothing was sent; get a fresh quote.");
139
+ if (call.minReturn < builtOut * BigInt(1e4 - slippageBps) / 10000n) mismatch("accepts less output than your slippage allows");
140
+ let approveTx;
141
+ if (!native) {
142
+ const allowance = await o.client.readContract({ address: quote.tokenIn.address, abi: erc20Abi, functionName: "allowance", args: [from, to] });
143
+ if (allowance < amountIn) {
144
+ o.step?.("approving");
145
+ approveTx = await o.wallet.writeContract({ address: quote.tokenIn.address, abi: erc20Abi, functionName: "approve", args: [to, amountIn], account, chain: o.wallet.chain });
146
+ const r = await o.client.waitForTransactionReceipt({ hash: approveTx });
147
+ if (r.status !== "success") throw new SwapError("approve_failed", "The approval transaction failed.");
148
+ }
149
+ }
150
+ o.step?.("swapping");
151
+ const tx = await o.wallet.sendTransaction({ to, data: b.data, value, account, chain: o.wallet.chain });
152
+ const receipt = await o.client.waitForTransactionReceipt({ hash: tx });
153
+ if (receipt.status !== "success") throw new SwapError("swap_reverted", "The swap reverted on-chain (price moved past your slippage). Nothing was swapped; gas was spent.");
154
+ return { approveTx, tx, amountOutMin: formatUnits(call.minReturn, quote.tokenOut.decimals ?? 18) };
155
+ }
156
+
157
+ // shared/solana.ts
158
+ import { ed25519 } from "@noble/curves/ed25519";
159
+ import { hmac } from "@noble/hashes/hmac";
160
+ import { sha256 } from "@noble/hashes/sha2";
161
+ import { base58, base64 } from "@scure/base";
162
+ var SOLANA_CAIP = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
163
+ var SOLANA_USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
164
+ var USDC_DECIMALS = 6;
165
+ var TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
166
+ var ATA_PROGRAM = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
167
+ var COMPUTE_BUDGET = "ComputeBudget111111111111111111111111111111";
168
+ var MEMO_PROGRAM = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr";
169
+ var CU_LIMIT = 2e4;
170
+ var CU_PRICE_MICROLAMPORTS = 1n;
171
+ var MAX_MEMO_BYTES = 256;
172
+ var utf8 = (s) => new TextEncoder().encode(s);
173
+ var concat = (...parts) => {
174
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
175
+ let o = 0;
176
+ for (const p of parts) out.set(p, o), o += p.length;
177
+ return out;
178
+ };
179
+ var hexBytes = (h) => {
180
+ const s = h.replace(/^0x/, "");
181
+ if (!/^[0-9a-fA-F]{64}$/.test(s)) throw new Error("Expected a 32-byte hex private key.");
182
+ return Uint8Array.from(s.match(/../g), (b) => parseInt(b, 16));
183
+ };
184
+ var le = (v, bytes) => Uint8Array.from({ length: bytes }, (_, i) => Number(v >> BigInt(8 * i) & 0xffn));
185
+ var shortvec = (n) => {
186
+ const out = [];
187
+ for (let v = n; ; ) {
188
+ const b = v & 127;
189
+ v >>= 7;
190
+ if (!v) return Uint8Array.from([...out, b]);
191
+ out.push(b | 128);
192
+ }
193
+ };
194
+ function isSolanaAddress(v) {
195
+ try {
196
+ return base58.decode(v).length === 32;
197
+ } catch {
198
+ return false;
199
+ }
200
+ }
201
+ function solanaKeyFromEvm(privateKey) {
202
+ const seed = hmac(sha256, hexBytes(privateKey), utf8("wafermap/solana/v1"));
203
+ const pub = ed25519.getPublicKey(seed);
204
+ return {
205
+ address: base58.encode(pub),
206
+ /** seed ‖ public key, base58: the format Phantom and Solflare import. */
207
+ secretKey: base58.encode(concat(seed, pub)),
208
+ sign: (message) => ed25519.sign(message, seed)
209
+ };
210
+ }
211
+ var onCurve = (b) => {
212
+ try {
213
+ ed25519.ExtendedPoint.fromHex(b);
214
+ return true;
215
+ } catch {
216
+ return false;
217
+ }
218
+ };
219
+ function ataOf(owner, mint = SOLANA_USDC) {
220
+ const seeds = concat(base58.decode(owner), base58.decode(TOKEN_PROGRAM), base58.decode(mint));
221
+ const tail = concat(base58.decode(ATA_PROGRAM), utf8("ProgramDerivedAddress"));
222
+ for (let bump = 255; bump >= 0; bump--) {
223
+ const h = sha256(concat(seeds, Uint8Array.of(bump), tail));
224
+ if (!onCurve(h)) return base58.encode(h);
225
+ }
226
+ throw new Error("No associated token account address.");
227
+ }
228
+ async function solanaPayment(payer, q) {
229
+ if (q.asset !== SOLANA_USDC) throw new Error("Only USDC is paid on Solana.");
230
+ if (!isSolanaAddress(q.feePayer) || !isSolanaAddress(q.payTo)) throw new Error("The quote has no valid Solana fee payer or recipient.");
231
+ if (q.feePayer === payer.address) throw new Error("The fee payer cannot be the paying wallet.");
232
+ const memo = utf8(q.memo ?? [...crypto.getRandomValues(new Uint8Array(16))].map((b) => b.toString(16).padStart(2, "0")).join(""));
233
+ if (memo.length > MAX_MEMO_BYTES) throw new Error("The seller's memo is too long.");
234
+ const source = ataOf(payer.address, q.asset);
235
+ const dest = ataOf(q.payTo, q.asset);
236
+ const keys = [q.feePayer, payer.address, source, dest, q.asset, TOKEN_PROGRAM, COMPUTE_BUDGET, MEMO_PROGRAM];
237
+ if (new Set(keys).size !== keys.length) throw new Error("The quote reuses an account it should not.");
238
+ const at = (k) => keys.indexOf(k);
239
+ const ixs = [
240
+ { program: COMPUTE_BUDGET, accounts: [], data: concat(Uint8Array.of(2), le(BigInt(CU_LIMIT), 4)) },
241
+ { program: COMPUTE_BUDGET, accounts: [], data: concat(Uint8Array.of(3), le(CU_PRICE_MICROLAMPORTS, 8)) },
242
+ { program: TOKEN_PROGRAM, accounts: [source, q.asset, dest, payer.address], data: concat(Uint8Array.of(12), le(BigInt(q.amount), 8), Uint8Array.of(USDC_DECIMALS)) },
243
+ { program: MEMO_PROGRAM, accounts: [], data: memo }
244
+ ];
245
+ const message = concat(
246
+ Uint8Array.of(128, 2, 1, 4),
247
+ // v0; 2 signatures, 1 of them readonly; 4 readonly non-signers
248
+ shortvec(keys.length),
249
+ ...keys.map((k) => base58.decode(k)),
250
+ base58.decode(await payer.blockhash()),
251
+ shortvec(ixs.length),
252
+ ...ixs.map((ix) => concat(Uint8Array.of(at(ix.program)), shortvec(ix.accounts.length), Uint8Array.from(ix.accounts.map(at)), shortvec(ix.data.length), ix.data)),
253
+ shortvec(0)
254
+ // no address lookup tables
255
+ );
256
+ const signature = await payer.sign(message);
257
+ return base64.encode(concat(shortvec(2), new Uint8Array(64), signature, message));
258
+ }
259
+ async function solanaRpc(url, method, params, f = fetch) {
260
+ const r = await f(url, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }) });
261
+ const j = await r.json().catch(() => null);
262
+ if (!j) throw new Error(`Solana RPC answered ${r.status}.`);
263
+ if (j.error) throw new Error(j.error.message ?? "Solana RPC error.");
264
+ return j.result;
265
+ }
266
+ var latestBlockhash = async (url, f) => (await solanaRpc(url, "getLatestBlockhash", [{ commitment: "confirmed" }], f)).value.blockhash;
267
+ async function solanaBalances(url, owner, f) {
268
+ const [usdc, lamports] = await Promise.all([
269
+ solanaRpc(url, "getAccountInfo", [ataOf(owner), { commitment: "confirmed", encoding: "jsonParsed" }], f).then((r) => String(r.value?.data?.parsed?.info?.tokenAmount?.uiAmountString ?? "0")),
270
+ solanaRpc(url, "getBalance", [owner, { commitment: "confirmed" }], f).then((r) => Number(r.value ?? 0))
271
+ ]);
272
+ return { usdc, sol: String(lamports / 1e9) };
273
+ }
274
+
275
+ // shared/x402client.ts
276
+ var USDC_DOMAIN = { name: "USD Coin", version: "2" };
277
+ var PAY_CHAINS = [
278
+ { key: "robinhood", vm: "evm", id: 4663, caip: "eip155:4663", v1: "robinhood", stable: "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168", symbol: "USDG", name: "Robinhood Chain", domain: { name: "Global Dollar", version: "1" } },
279
+ { key: "solana", vm: "svm", id: 0, caip: SOLANA_CAIP, v1: "solana", stable: SOLANA_USDC, symbol: "USDC", name: "Solana" },
280
+ { key: "base", vm: "evm", id: 8453, caip: "eip155:8453", v1: "base", stable: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", symbol: "USDC", name: "Base", domain: USDC_DOMAIN },
281
+ { key: "arbitrum", vm: "evm", id: 42161, caip: "eip155:42161", v1: "arbitrum", stable: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", symbol: "USDC", name: "Arbitrum", domain: USDC_DOMAIN },
282
+ { key: "polygon", vm: "evm", id: 137, caip: "eip155:137", v1: "polygon", stable: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", symbol: "USDC", name: "Polygon", domain: USDC_DOMAIN }
283
+ ];
284
+ var EVM_CHAINS = PAY_CHAINS.filter((c) => c.vm === "evm");
285
+ var CHAIN_KEYS = PAY_CHAINS.map((c) => c.key);
286
+ var X402Error = class extends Error {
287
+ constructor(code, message) {
288
+ super(message);
289
+ this.code = code;
290
+ }
291
+ code;
292
+ };
293
+ var b64encode = (s) => {
294
+ const bytes = new TextEncoder().encode(s);
295
+ let bin = "";
296
+ for (let i = 0; i < bytes.length; i += 32768) bin += String.fromCharCode(...bytes.subarray(i, i + 32768));
297
+ return btoa(bin);
298
+ };
299
+ var b64decode = (s) => new TextDecoder().decode(Uint8Array.from(atob(s), (c) => c.charCodeAt(0)));
300
+ function chainOf(network) {
301
+ const n = String(network ?? "").toLowerCase();
302
+ return PAY_CHAINS.find((c) => c.caip.toLowerCase() === n || c.v1 === n || c.id > 0 && String(c.id) === n);
303
+ }
304
+ async function readQuote(res) {
305
+ let body = null;
306
+ const header = res.headers.get("payment-required");
307
+ if (header) {
308
+ try {
309
+ body = JSON.parse(b64decode(header));
310
+ } catch {
311
+ }
312
+ }
313
+ if (!body) body = await res.json().catch(() => null);
314
+ if (!body || !Array.isArray(body.accepts)) throw new X402Error("not_x402", "The service answered 402 without x402 payment details.");
315
+ const version = Number(body.x402Version) === 1 ? 1 : 2;
316
+ const accepts = [];
317
+ const offered = [];
318
+ for (const r of body.accepts) {
319
+ offered.push(`${r.scheme ?? "?"}/${r.network ?? "?"}`);
320
+ const chain = chainOf(r.network);
321
+ if (!chain || r.scheme !== "exact") continue;
322
+ if (chain.vm === "evm" ? String(r.asset ?? "").toLowerCase() !== chain.stable.toLowerCase() : r.asset !== chain.stable) continue;
323
+ const amount = String(version === 1 ? r.maxAmountRequired : r.amount);
324
+ if (!/^\d+$/.test(amount)) continue;
325
+ let extra;
326
+ if (chain.vm === "svm") {
327
+ if (typeof r.extra?.feePayer !== "string") continue;
328
+ extra = { feePayer: r.extra.feePayer, ...typeof r.extra.memo === "string" ? { memo: r.extra.memo } : {} };
329
+ } else extra = r.extra?.name && r.extra?.version ? { name: String(r.extra.name), version: String(r.extra.version) } : chain.domain;
330
+ accepts.push({ version, chain, amount, usd: Number(amount) / 1e6, payTo: r.payTo, asset: r.asset, maxTimeoutSeconds: Number(r.maxTimeoutSeconds) || 120, extra, raw: r });
331
+ }
332
+ const inputError = body.inputError?.message ? { code: String(body.inputError.code ?? "invalid_input"), message: String(body.inputError.message) } : void 0;
333
+ return { version, resource: body.resource ?? null, accepts, offered, inputError };
334
+ }
335
+ function choose(q, prefer = CHAIN_KEYS) {
336
+ for (const k of prefer) {
337
+ const r = q.accepts.find((a) => a.chain.key === k);
338
+ if (r) return r;
339
+ }
340
+ 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 Polygon.` : "The service offered no payment option.");
341
+ }
342
+ var TYPES = {
343
+ TransferWithAuthorization: [
344
+ { name: "from", type: "address" },
345
+ { name: "to", type: "address" },
346
+ { name: "value", type: "uint256" },
347
+ { name: "validAfter", type: "uint256" },
348
+ { name: "validBefore", type: "uint256" },
349
+ { name: "nonce", type: "bytes32" }
350
+ ]
351
+ };
352
+ var MAX_VALIDITY_SECONDS = 90;
353
+ var randomNonce = () => "0x" + [...crypto.getRandomValues(new Uint8Array(32))].map((b) => b.toString(16).padStart(2, "0")).join("");
354
+ async function signPayment(signer, req, resource, solana) {
355
+ if (req.chain.vm === "svm") {
356
+ if (!solana) throw new X402Error("no_solana_wallet", "This payment is on Solana and no Solana wallet was given.");
357
+ let transaction;
358
+ try {
359
+ transaction = await solanaPayment(solana, { amount: req.amount, payTo: req.payTo, asset: req.asset, feePayer: req.extra.feePayer, memo: req.extra.memo });
360
+ } catch (e) {
361
+ if (e?.name === "TimeoutError" || e?.cause?.name === "TimeoutError") throw e;
362
+ throw new X402Error("sign_failed", e?.message ?? "Could not build the Solana payment.");
363
+ }
364
+ if (req.version === 1) return { name: "X-PAYMENT", value: b64encode(JSON.stringify({ x402Version: 1, scheme: "exact", network: req.raw.network, payload: { transaction } })) };
365
+ return { name: "PAYMENT-SIGNATURE", value: b64encode(JSON.stringify({ x402Version: 2, resource, accepted: req.raw, payload: { transaction } })) };
366
+ }
367
+ const nonce = randomNonce();
368
+ const validBefore = BigInt(Math.floor(Date.now() / 1e3) + Math.min(req.maxTimeoutSeconds, MAX_VALIDITY_SECONDS));
369
+ const message = { from: signer.address, to: req.payTo, value: BigInt(req.amount), validAfter: 0n, validBefore, nonce };
370
+ let signature;
371
+ try {
372
+ signature = await signer.signTypedData({ domain: { name: req.extra.name, version: req.extra.version, chainId: req.chain.id, verifyingContract: req.asset }, types: TYPES, primaryType: "TransferWithAuthorization", message });
373
+ } catch (e) {
374
+ throw new X402Error("sign_rejected", e?.shortMessage ?? e?.message ?? "Signature rejected.");
375
+ }
376
+ const authorization = { from: signer.address, to: req.payTo, value: req.amount, validAfter: "0", validBefore: validBefore.toString(), nonce };
377
+ const auth = { nonce, validBefore: Number(validBefore) };
378
+ if (req.version === 1) return { name: "X-PAYMENT", value: b64encode(JSON.stringify({ x402Version: 1, scheme: "exact", network: req.raw.network, payload: { signature, authorization } })), auth };
379
+ return { name: "PAYMENT-SIGNATURE", value: b64encode(JSON.stringify({ x402Version: 2, resource, accepted: req.raw, payload: { signature, authorization } })), auth };
380
+ }
381
+ function readSettlement(res) {
382
+ const h = res.headers.get("payment-response") ?? res.headers.get("x-payment-response");
383
+ if (!h) return null;
384
+ try {
385
+ const j = JSON.parse(b64decode(h));
386
+ return { tx: j.transaction ?? j.txHash ?? j.tx, network: j.network, success: j.success };
387
+ } catch {
388
+ return null;
389
+ }
390
+ }
391
+ async function x402fetch(url, init, opts) {
392
+ const f = opts.fetch ?? fetch;
393
+ const first = await f(url, init);
394
+ if (first.status !== 402) return { res: first, paid: null };
395
+ const quote = await readQuote(first);
396
+ if (quote.inputError) throw new X402Error(quote.inputError.code, quote.inputError.message);
397
+ const req = choose(quote, (opts.prefer ?? CHAIN_KEYS).filter((k) => k !== "solana" || opts.solana));
398
+ if (opts.approve) await opts.approve(req);
399
+ const svm = req.chain.vm === "svm";
400
+ const signer = svm ? null : typeof opts.signer === "function" ? await opts.signer() : opts.signer;
401
+ const header = await signPayment(signer, req, quote.resource, opts.solana);
402
+ const signed = { req, from: svm ? opts.solana.address : signer.address, ...header.auth ? { auth: header.auth } : {} };
403
+ opts.onSigned?.(signed);
404
+ const headers = new Headers(init.headers);
405
+ headers.set(header.name, header.value);
406
+ const res = await f(url, { ...init, headers });
407
+ return { res, paid: { ...signed, tx: readSettlement(res)?.tx ?? null } };
408
+ }
409
+
410
+ // src/core.ts
411
+ var NETS = CHAIN_KEYS;
412
+ var isSwapNet = (v) => v === "robinhood" || v === "base";
413
+ var WafermapError = class extends Error {
414
+ constructor(code, message) {
415
+ super(message);
416
+ this.code = code;
417
+ }
418
+ code;
419
+ };
420
+ var CHAINS = { robinhood, base, arbitrum, polygon };
421
+ var AUTH_STATE_ABI = [{ type: "function", name: "authorizationState", stateMutability: "view", inputs: [{ name: "authorizer", type: "address" }, { name: "nonce", type: "bytes32" }], outputs: [{ type: "bool" }] }];
422
+ function mergeLog(entries) {
423
+ const outcomes = new Map(entries.filter((e) => e.of).map((e) => [e.of, e]));
424
+ return entries.filter((e) => !e.of).map((e) => {
425
+ const o = e.id ? outcomes.get(e.id) : void 0;
426
+ if (!o) return e;
427
+ return { ...e, status: o.status, tx: o.tx ?? e.tx, reported: o.reported ?? null, ...o.status === "aborted" ? { usd: 0 } : {} };
428
+ });
429
+ }
430
+ async function checkPayment(e, rpc) {
431
+ if (e.network === "solana" || !e.nonce || !(e.network in CHAINS)) return "unknown";
432
+ const chain = PAY_CHAINS.find((c) => c.key === e.network);
433
+ try {
434
+ const client = createPublicClient({ chain: CHAINS[e.network], transport: http(rpc) });
435
+ const used = await client.readContract({ address: chain.stable, abi: AUTH_STATE_ABI, functionName: "authorizationState", args: [e.payer, e.nonce] });
436
+ if (used) return "used";
437
+ return e.validBefore && Date.now() / 1e3 > e.validBefore ? "unused" : "pending";
438
+ } catch {
439
+ return "error";
440
+ }
441
+ }
442
+ var DEFAULT_LIMITS = { perPayment: 0.1, perDay: 1, perSwap: 25 };
443
+ var DEFAULT_TIMEOUT_MS = 6e4;
444
+ var timedOut = (e) => e?.name === "TimeoutError" || e?.cause?.name === "TimeoutError";
445
+ var Wafermap = class {
446
+ account;
447
+ baseUrl;
448
+ network;
449
+ limits;
450
+ ledger;
451
+ approveFn;
452
+ rpc;
453
+ clients = {};
454
+ solana;
455
+ solanaAddress;
456
+ timeoutMs;
457
+ /** fetch with this wallet's time limit: a server that never answers cannot hold a call forever. */
458
+ fetch;
459
+ constructor(o) {
460
+ const account = o.account ?? (o.privateKey ? privateKeyToAccount(o.privateKey) : null);
461
+ if (!account) throw new WafermapError("no_account", "Pass privateKey or account.");
462
+ this.account = account;
463
+ this.baseUrl = (o.baseUrl ?? "https://wafermap.xyz").replace(/\/$/, "");
464
+ this.network = o.network ?? "robinhood";
465
+ this.limits = { ...DEFAULT_LIMITS, ...o.limits };
466
+ const mem = [];
467
+ this.ledger = o.ledger ?? { entries: () => mem, record: (e) => void mem.push(e) };
468
+ this.approveFn = o.approve;
469
+ this.rpc = o.rpc ?? {};
470
+ this.timeoutMs = o.timeoutMs && o.timeoutMs > 0 ? o.timeoutMs : DEFAULT_TIMEOUT_MS;
471
+ this.fetch = (input, init) => fetch(input, { ...init, signal: init?.signal ?? AbortSignal.timeout(this.timeoutMs) });
472
+ const sol = o.solana ?? (o.privateKey ? solanaKeyFromEvm(o.privateKey) : null);
473
+ const solRpc = this.rpc.solana ?? `${this.baseUrl}/api/solana`;
474
+ this.solana = sol ? { address: sol.address, sign: sol.sign, blockhash: () => latestBlockhash(solRpc, this.fetch) } : null;
475
+ this.solanaAddress = sol?.address ?? o.solanaAddress ?? null;
476
+ }
477
+ get address() {
478
+ return this.account.address;
479
+ }
480
+ client(n) {
481
+ return this.clients[n] ??= createPublicClient({ chain: CHAINS[n], transport: http(this.rpc[n]) });
482
+ }
483
+ /** USD signed today (local midnight) on payments, not counting swaps. Every signed payment
484
+ * counts at its full amount, whatever the server answered, because a signed payment can be
485
+ * executed until it expires. `checkPayment` shows on-chain which ones were never used. */
486
+ spentToday() {
487
+ const start = /* @__PURE__ */ new Date();
488
+ start.setHours(0, 0, 0, 0);
489
+ return this.ledger.entries().filter((e) => e.kind !== "swap" && e.at >= start.getTime() && (e.payer.toLowerCase() === this.address.toLowerCase() || e.payer === this.solanaAddress)).reduce((a, e) => a + e.usd, 0);
490
+ }
491
+ async gate(a) {
492
+ if (a.kind === "swap") {
493
+ if (a.usd > this.limits.perSwap) throw new WafermapError("over_swap_cap", `$${a.usd.toFixed(2)} is above the per-swap cap of $${this.limits.perSwap}.`);
494
+ } else {
495
+ if (a.usd > this.limits.perPayment) throw new WafermapError("over_payment_cap", `$${a.usd} is above the per-payment cap of $${this.limits.perPayment}.`);
496
+ if (this.spentToday() + a.usd > this.limits.perDay + 1e-9) throw new WafermapError("over_daily_cap", `This would pass the daily cap of $${this.limits.perDay} ($${this.spentToday().toFixed(4)} spent today).`);
497
+ }
498
+ if (this.approveFn && !await this.approveFn(a)) throw new WafermapError("declined", "Declined.");
499
+ }
500
+ prefer(n) {
501
+ const first = n ?? this.network;
502
+ return [first, ...NETS.filter((x) => x !== first)];
503
+ }
504
+ /** A second 402 means the payment itself was refused (usually: not enough balance). */
505
+ rejected(res, body, paid) {
506
+ if (!paid || res.status !== 402) return null;
507
+ const reason = typeof body?.error === "string" ? body.error : body?.error?.message ?? "rejected";
508
+ return new WafermapError("payment_rejected", `Payment refused (${reason}). Check that ${paid.req.chain.vm === "svm" ? this.solanaAddress : this.address} holds at least $${paid.req.usd} ${paid.req.chain.symbol} on ${paid.req.chain.name}.`);
509
+ }
510
+ /** Write the signed payment to the ledger before it leaves the machine. */
511
+ booked(kind, what, s) {
512
+ const id = crypto.randomUUID();
513
+ this.ledger.record({ at: Date.now(), kind, what, usd: s.req.usd, network: s.req.chain.key, tx: null, payer: s.from, id, status: "signed", ...s.auth ? { nonce: s.auth.nonce, validBefore: s.auth.validBefore } : {} });
514
+ return id;
515
+ }
516
+ /** The outcome line for a booked payment. It adds nothing to the daily total. */
517
+ outcome(id, kind, what, s, status, tx, reported = null) {
518
+ this.ledger.record({ at: Date.now(), kind, what, usd: 0, network: s.req.chain.key, tx, payer: s.from, of: id, status, reported });
519
+ }
520
+ /** The timeout error for `host`, saying whether a payment was signed and until when it can be executed. */
521
+ timeoutError(host, signed) {
522
+ const after = signed ? ` A payment had been signed and sent. It stays counted${signed.validBefore ? ` and can only be executed until ${new Date(signed.validBefore * 1e3).toLocaleTimeString()}` : ""}; check with \`wafermap history --verify\`.` : " Nothing was paid.";
523
+ return new WafermapError("timeout", `${host} did not answer within ${Math.round(this.timeoutMs / 1e3)} s.${after}`);
524
+ }
525
+ /** Read a JSON body; a time-out while reading is reported, anything else gives `fallback`. */
526
+ async body(res, read, fallback, host, signed) {
527
+ try {
528
+ return await read();
529
+ } catch (e) {
530
+ if (timedOut(e)) throw this.timeoutError(host, signed);
531
+ return fallback;
532
+ }
533
+ }
534
+ // Check-sign-book runs one payment at a time in this process, so two calls started together
535
+ // cannot both pass the daily cap on the same remaining budget. Across processes, see onSigned.
536
+ queue = Promise.resolve();
537
+ acquire() {
538
+ let release;
539
+ const held = new Promise((r) => release = r);
540
+ const prev = this.queue;
541
+ this.queue = prev.then(() => held);
542
+ return prev.then(() => release);
543
+ }
544
+ async paidFetch(kind, what, url, init, network) {
545
+ let id = null;
546
+ let release = null;
547
+ const unlock = () => {
548
+ release?.();
549
+ release = null;
550
+ };
551
+ try {
552
+ const r = await x402fetch(url, init, {
553
+ signer: this.account,
554
+ solana: this.solana ?? void 0,
555
+ prefer: this.prefer(network),
556
+ fetch: this.fetch,
557
+ approve: async (req) => {
558
+ release = await this.acquire();
559
+ await this.gate({ kind, what, usd: req.usd, network: req.chain.key, detail: `${req.chain.symbol} to ${req.payTo}` });
560
+ },
561
+ onSigned: (s) => {
562
+ id = this.booked(kind, what, s);
563
+ unlock();
564
+ if (this.spentToday() > this.limits.perDay + 1e-9) {
565
+ this.ledger.record({ at: Date.now(), kind, what, usd: -s.req.usd, network: s.req.chain.key, tx: null, payer: s.from, of: id, status: "aborted" });
566
+ id = null;
567
+ throw new WafermapError("over_daily_cap", `Another payment from this wallet used the daily cap of $${this.limits.perDay} at the same time. Nothing was sent.`);
568
+ }
569
+ }
570
+ });
571
+ return { ...r, id };
572
+ } catch (e) {
573
+ if (e instanceof WafermapError) throw e;
574
+ if (e instanceof X402Error) throw new WafermapError(e.code, e.message);
575
+ if (timedOut(e)) throw this.timeoutError(new URL(url).host, id ? this.ledger.entries().find((x) => x.id === id) ?? {} : null);
576
+ throw new WafermapError("network_error", `${new URL(url).host}: ${e?.cause?.message ?? e?.message ?? "request failed"}`);
577
+ } finally {
578
+ unlock();
579
+ }
580
+ }
581
+ // ---- Wafermap resources ----
582
+ async resources() {
583
+ const host = new URL(this.baseUrl).host;
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 ?? [];
589
+ }
590
+ async call(id, input = {}, o = {}) {
591
+ const go = () => this.paidFetch("resource", id, `${this.baseUrl}/v1/${id}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(input) }, o.network);
592
+ const settle = (r2, out2) => {
593
+ if (!r2.paid || !r2.id) return;
594
+ const status = r2.res.status === 402 ? "refused" : r2.res.ok ? "ok" : "failed";
595
+ this.outcome(r2.id, "resource", id, r2.paid, status, out2?.receipt?.tx ?? r2.paid.tx, typeof out2?.receipt?.amountUsd === "number" ? out2.receipt.amountUsd : null);
596
+ };
597
+ const host = new URL(this.baseUrl).host;
598
+ const read = (r2) => this.body(r2.res, () => r2.res.json(), {}, host, r2.paid?.auth ?? (r2.paid ? {} : null));
599
+ let r = await go();
600
+ let out = await read(r);
601
+ settle(r, out);
602
+ if (r.paid?.req.chain.vm === "svm" && r.res.status === 402 && /fee_payer/i.test(JSON.stringify(out))) {
603
+ r = await go().catch((e) => {
604
+ if (e?.code === "over_daily_cap") throw new WafermapError("over_daily_cap", `The facilitator refused the first Solana payment (its fee payer rotated). That signed payment still counts until it expires, and a second one would pass the daily cap of $${this.limits.perDay}. Try again in a few minutes or pay on another network.`);
605
+ throw e;
606
+ });
607
+ out = await read(r);
608
+ settle(r, out);
609
+ }
610
+ const { res, paid } = r;
611
+ const refused = this.rejected(res, out, paid);
612
+ if (refused) throw refused;
613
+ if (!res.ok) throw new WafermapError(out?.error?.code ?? "http_error", out?.error?.message ?? (typeof out?.error === "string" ? out.error : `HTTP ${res.status}`));
614
+ return out;
615
+ }
616
+ // ---- Third-party x402 services ----
617
+ async discover(query, limit = 10) {
618
+ const host = new URL(this.baseUrl).host;
619
+ const r = await this.fetch(`${this.baseUrl}/api/discover?q=${encodeURIComponent(query)}&limit=${limit}`).catch((e) => {
620
+ throw timedOut(e) ? this.timeoutError(host) : new WafermapError("network_error", `${host}: ${e?.message ?? "request failed"}`);
621
+ });
622
+ const j = await this.body(r, () => r.json(), null, host);
623
+ if (!r.ok || !Array.isArray(j?.services)) throw new WafermapError(j?.error?.code ?? "discovery_failed", j?.error?.message ?? "Discovery is unavailable.");
624
+ return j.services;
625
+ }
626
+ /** Call any x402 URL and pay it if it asks. GET sends `input` as the query string, POST as JSON. */
627
+ async pay(url, o = {}) {
628
+ const method = o.method ?? "GET";
629
+ const qs = method === "GET" && o.input && Object.keys(o.input).length ? new URLSearchParams(Object.entries(o.input).map(([k, v]) => [k, String(v)])).toString() : "";
630
+ const target = qs ? `${url}${url.includes("?") ? "&" : "?"}${qs}` : url;
631
+ const init = method === "GET" ? { method } : { method, headers: { "content-type": "application/json" }, body: JSON.stringify(o.input ?? {}) };
632
+ const u = new URL(url);
633
+ const what = `${u.host}${u.pathname}`;
634
+ const { res, paid, id } = await this.paidFetch("service", what, target, init, o.network);
635
+ if (paid && id) this.outcome(id, "service", what, paid, res.status === 402 ? "refused" : res.ok ? "ok" : "failed", paid.tx);
636
+ const type = res.headers.get("content-type") ?? "";
637
+ const signed = paid?.auth ?? (paid ? {} : null);
638
+ const result = /json/.test(type) ? await this.body(res, () => res.json(), null, u.host, signed) : /^(image|audio|video)\//.test(type) ? { bytes: (await this.body(res, () => res.arrayBuffer(), new ArrayBuffer(0), u.host, signed)).byteLength, contentType: type } : await this.body(res, () => res.text(), "", u.host, signed);
639
+ const refused = this.rejected(res, result, paid);
640
+ if (refused) throw refused;
641
+ if (!res.ok) throw new WafermapError(paid ? "payment_failed" : "http_error", `${u.host} answered ${res.status}: ${JSON.stringify(result).slice(0, 300)}`);
642
+ return { status: res.status, result, paid: paid ? { usd: paid.req.usd, network: paid.req.chain.key, tx: paid.tx } : null };
643
+ }
644
+ // ---- Wallet ----
645
+ async balances() {
646
+ const out = Object.fromEntries(NETS.map((n) => [n, null]));
647
+ const sol = this.solanaAddress;
648
+ await Promise.all([
649
+ sol ? solanaBalances(this.rpc.solana ?? `${this.baseUrl}/api/solana`, sol, this.fetch).then(
650
+ (b) => void (out.solana = { eth: b.sol, gas: "SOL", stable: b.usdc, symbol: "USDC" }),
651
+ () => {
652
+ }
653
+ ) : null,
654
+ ...NETS.filter((n) => n !== "solana").map(async (n) => {
655
+ try {
656
+ const c = this.client(n);
657
+ const chain = PAY_CHAINS.find((x) => x.key === n);
658
+ const [eth, usd] = await Promise.all([c.getBalance({ address: this.address }), c.readContract({ address: chain.stable, abi: erc20Abi2, functionName: "balanceOf", args: [this.address] })]);
659
+ out[n] = { eth: formatUnits2(eth, 18), gas: CHAINS[n].nativeCurrency.symbol, stable: formatUnits2(usd, 6), symbol: chain.symbol };
660
+ } catch {
661
+ out[n] = null;
662
+ }
663
+ })
664
+ ]);
665
+ return out;
666
+ }
667
+ // ---- Swaps ----
668
+ tokenCache = {};
669
+ async lookup(n, symbol) {
670
+ if (!this.tokenCache[n]) {
671
+ const j = await this.fetch(`${this.baseUrl}/api/tokens?network=${n}`).then((r) => r.json()).catch(() => null);
672
+ this.tokenCache[n] = j?.tokens ?? TOKENS[n];
673
+ }
674
+ return this.tokenCache[n].find((t) => t.symbol.toLowerCase() === symbol.toLowerCase()) ?? null;
675
+ }
676
+ async swapQuote(from, to, amount, network) {
677
+ const n = network ?? (isSwapNet(this.network) ? this.network : "robinhood");
678
+ if (!isSwapNet(n)) throw new WafermapError("unsupported_network", "Swaps run on Robinhood Chain or Base. Use --network robinhood or base.");
679
+ const c = this.client(n);
680
+ try {
681
+ const [tokenIn, tokenOut] = await Promise.all([resolveToken(n, from, c, (net, s) => this.lookup(net, s)), resolveToken(n, to, c, (net, s) => this.lookup(net, s))]);
682
+ return await quoteSwap({ network: n, tokenIn, tokenOut, amount, fetch: this.fetch });
683
+ } catch (e) {
684
+ throw new WafermapError(e?.code ?? "quote_failed", e?.shortMessage ?? e?.message ?? "No quote.");
685
+ }
686
+ }
687
+ async swap(quote, o = {}) {
688
+ if (!quote.amountInUsd) throw new WafermapError("no_usd_value", "This swap has no USD value, so the per-swap cap cannot be checked. Not sent.");
689
+ await this.gate({ kind: "swap", what: `${quote.amountIn} ${quote.tokenIn.symbol} \u2192 ${quote.tokenOut.symbol}`, usd: quote.amountInUsd, network: quote.network, detail: `about ${quote.amountOut} ${quote.tokenOut.symbol}` });
690
+ const wallet = createWalletClient({ account: this.account, chain: CHAINS[quote.network], transport: http(this.rpc[quote.network]) });
691
+ try {
692
+ const r = await executeSwap({ quote, wallet, client: this.client(quote.network), slippageBps: o.slippageBps, step: o.step, fetch: this.fetch });
693
+ this.ledger.record({ at: Date.now(), kind: "swap", what: `${quote.amountIn} ${quote.tokenIn.symbol} \u2192 ${quote.tokenOut.symbol}`, usd: 0, network: quote.network, tx: r.tx, payer: this.address });
694
+ return r;
695
+ } catch (e) {
696
+ throw new WafermapError(e?.code ?? "swap_failed", e?.shortMessage ?? e?.message ?? "Swap failed.");
697
+ }
698
+ }
699
+ };
700
+ export {
701
+ DEFAULT_LIMITS,
702
+ DEFAULT_TIMEOUT_MS,
703
+ KYBER_ROUTER,
704
+ MAX_VALIDITY_SECONDS,
705
+ PAY_CHAINS,
706
+ SwapError,
707
+ Wafermap,
708
+ WafermapError,
709
+ X402Error,
710
+ checkPayment,
711
+ choose,
712
+ decodeSwap,
713
+ executeSwap,
714
+ mergeLog,
715
+ quoteSwap,
716
+ readQuote,
717
+ signPayment,
718
+ x402fetch
719
+ };