x402-proxy 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -21
- package/dist/bin/cli.js +135 -136
- package/dist/derive-CISr_ond.js +161 -0
- package/dist/index.js +1 -1
- package/dist/setup-Crq9TylJ.js +4 -0
- package/dist/setup-gla-Qyqi.js +88 -0
- package/dist/{status-CeXiUR6L.js → status-0rAVbrke.js} +1 -1
- package/dist/status-B7iFmj8f.js +72 -0
- package/dist/wallet-BX5vQtUz.js +4 -0
- package/dist/{status-BAzavcCj.js → wallet-Vp9zRQzC.js} +49 -238
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -10,14 +10,7 @@ npx x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi
|
|
|
10
10
|
|
|
11
11
|
That's it. The endpoint returns 402, x402-proxy pays and streams the response.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npx x402-proxy setup # generate wallet from BIP-39 mnemonic
|
|
17
|
-
npx x402-proxy wallet fund # see where to send USDC
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
One mnemonic derives both EVM (Base) and Solana keypairs. Fund either chain and go.
|
|
13
|
+
No wallet? It'll walk you through setup automatically. One mnemonic derives both EVM (Base) and Solana keypairs. Fund either chain and go.
|
|
21
14
|
|
|
22
15
|
## MCP Proxy
|
|
23
16
|
|
|
@@ -45,29 +38,31 @@ Works like curl. Response body streams to stdout, payment info goes to stderr.
|
|
|
45
38
|
|
|
46
39
|
```bash
|
|
47
40
|
# GET request
|
|
48
|
-
x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi
|
|
41
|
+
$ npx x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi
|
|
49
42
|
|
|
50
43
|
# POST with body and headers
|
|
51
|
-
x402-proxy --method POST \
|
|
44
|
+
$ npx x402-proxy --method POST \
|
|
52
45
|
--header "Content-Type: application/json" \
|
|
53
46
|
--body '{"url":"https://x402.org"}' \
|
|
54
47
|
https://web.surf.cascade.fyi/v1/crawl
|
|
55
48
|
|
|
49
|
+
# Force a specific network
|
|
50
|
+
$ npx x402-proxy --network base https://api.example.com/data
|
|
51
|
+
|
|
56
52
|
# Pipe-safe
|
|
57
|
-
x402-proxy https://api.example.com/data | jq '.results'
|
|
53
|
+
$ npx x402-proxy https://api.example.com/data | jq '.results'
|
|
58
54
|
```
|
|
59
55
|
|
|
60
56
|
## Commands
|
|
61
57
|
|
|
62
58
|
```bash
|
|
63
|
-
x402-proxy <url> # paid HTTP request (default command)
|
|
64
|
-
x402-proxy mcp <url> # MCP stdio proxy for agents
|
|
65
|
-
x402-proxy setup # onboarding wizard
|
|
66
|
-
x402-proxy status # config + wallet + spend summary
|
|
67
|
-
x402-proxy wallet # show addresses
|
|
68
|
-
x402-proxy wallet history # payment history
|
|
69
|
-
x402-proxy wallet
|
|
70
|
-
x402-proxy wallet export-key <target> # bare key/mnemonic to stdout (evm|solana|mnemonic)
|
|
59
|
+
$ npx x402-proxy <url> # paid HTTP request (default command)
|
|
60
|
+
$ npx x402-proxy mcp <url> # MCP stdio proxy for agents
|
|
61
|
+
$ npx x402-proxy setup # onboarding wizard
|
|
62
|
+
$ npx x402-proxy status # config + wallet + spend summary
|
|
63
|
+
$ npx x402-proxy wallet # show addresses and balances
|
|
64
|
+
$ npx x402-proxy wallet history # payment history
|
|
65
|
+
$ npx x402-proxy wallet export-key <target> # bare key/mnemonic to stdout (evm|solana|mnemonic)
|
|
71
66
|
```
|
|
72
67
|
|
|
73
68
|
All commands support `--help` for details.
|
|
@@ -84,8 +79,8 @@ Config stored at `$XDG_CONFIG_HOME/x402-proxy/` (default `~/.config/x402-proxy/`
|
|
|
84
79
|
|
|
85
80
|
```bash
|
|
86
81
|
# Pipe-safe - outputs bare key/mnemonic to stdout
|
|
87
|
-
MY_KEY=$(npx x402-proxy wallet export-key evm)
|
|
88
|
-
MY_MNEMONIC=$(npx x402-proxy wallet export-key mnemonic)
|
|
82
|
+
$ MY_KEY=$(npx x402-proxy wallet export-key evm)
|
|
83
|
+
$ MY_MNEMONIC=$(npx x402-proxy wallet export-key mnemonic)
|
|
89
84
|
```
|
|
90
85
|
|
|
91
86
|
## Env Vars
|
package/dist/bin/cli.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { a as buildX402Client, c as error, d as warn, f as appendHistory, g as readHistory, h as formatTxLine, i as walletInfoCommand, l as info, m as displayNetwork, o as resolveWallet, p as calcSpend, s as dim, u as isTTY } from "../wallet-Vp9zRQzC.js";
|
|
3
|
+
import { c as isConfigured, i as ensureConfigDir, l as loadConfig, o as getHistoryPath, u as loadWalletFile } from "../derive-CISr_ond.js";
|
|
4
|
+
import { n as setupCommand } from "../setup-gla-Qyqi.js";
|
|
5
|
+
import { n as statusCommand } from "../status-B7iFmj8f.js";
|
|
3
6
|
import { buildApplication, buildCommand, buildRouteMap, run } from "@stricli/core";
|
|
4
7
|
import pc from "picocolors";
|
|
5
8
|
import { decodePaymentResponseHeader, wrapFetchWithPayment } from "@x402/fetch";
|
|
@@ -36,7 +39,7 @@ function createX402ProxyHandler(opts) {
|
|
|
36
39
|
paymentQueue.push({
|
|
37
40
|
network: hookCtx.selectedRequirements.network,
|
|
38
41
|
payTo: hookCtx.selectedRequirements.payTo,
|
|
39
|
-
amount: raw
|
|
42
|
+
amount: raw,
|
|
40
43
|
asset: hookCtx.selectedRequirements.asset
|
|
41
44
|
});
|
|
42
45
|
});
|
|
@@ -91,6 +94,12 @@ Examples:
|
|
|
91
94
|
parse: String,
|
|
92
95
|
optional: true
|
|
93
96
|
},
|
|
97
|
+
network: {
|
|
98
|
+
kind: "parsed",
|
|
99
|
+
brief: "Require specific network (base, solana)",
|
|
100
|
+
parse: String,
|
|
101
|
+
optional: true
|
|
102
|
+
},
|
|
94
103
|
json: {
|
|
95
104
|
kind: "boolean",
|
|
96
105
|
brief: "Force JSON output",
|
|
@@ -109,7 +118,7 @@ Examples:
|
|
|
109
118
|
async func(flags, url) {
|
|
110
119
|
if (!url) {
|
|
111
120
|
if (isConfigured()) {
|
|
112
|
-
const { displayStatus } = await import("../status-
|
|
121
|
+
const { displayStatus } = await import("../status-0rAVbrke.js");
|
|
113
122
|
await displayStatus();
|
|
114
123
|
console.log();
|
|
115
124
|
console.log(pc.dim(" Commands:"));
|
|
@@ -118,7 +127,6 @@ Examples:
|
|
|
118
127
|
console.log(` ${pc.cyan("$ npx x402-proxy setup")} Reconfigure wallet`);
|
|
119
128
|
console.log(` ${pc.cyan("$ npx x402-proxy wallet")} Addresses and balances`);
|
|
120
129
|
console.log(` ${pc.cyan("$ npx x402-proxy wallet history")} Full payment history`);
|
|
121
|
-
console.log(` ${pc.cyan("$ npx x402-proxy wallet fund")} Funding instructions`);
|
|
122
130
|
console.log();
|
|
123
131
|
console.log(pc.dim(" try: ") + pc.cyan("$ npx x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi"));
|
|
124
132
|
console.log();
|
|
@@ -135,7 +143,6 @@ Examples:
|
|
|
135
143
|
console.log(` ${pc.cyan("$ npx x402-proxy mcp <url>")} MCP proxy for AI agents`);
|
|
136
144
|
console.log(` ${pc.cyan("$ npx x402-proxy wallet")} Addresses and balances`);
|
|
137
145
|
console.log(` ${pc.cyan("$ npx x402-proxy wallet history")} Payment history`);
|
|
138
|
-
console.log(` ${pc.cyan("$ npx x402-proxy wallet fund")} Funding instructions`);
|
|
139
146
|
console.log(` ${pc.cyan("$ npx x402-proxy --help")} All options`);
|
|
140
147
|
console.log();
|
|
141
148
|
console.log(pc.dim(" try: ") + pc.cyan("$ npx x402-proxy setup"));
|
|
@@ -152,18 +159,36 @@ Examples:
|
|
|
152
159
|
error(`Invalid URL: ${url}`);
|
|
153
160
|
process.exit(1);
|
|
154
161
|
}
|
|
155
|
-
|
|
162
|
+
let wallet = resolveWallet({
|
|
156
163
|
evmKey: flags.evmKey,
|
|
157
164
|
solanaKey: flags.solanaKey
|
|
158
165
|
});
|
|
159
166
|
if (wallet.source === "none") {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
167
|
+
if (!isTTY()) {
|
|
168
|
+
error("No wallet configured.");
|
|
169
|
+
console.error(pc.dim(`Run:\n ${pc.cyan("$ npx x402-proxy setup")}\n\nOr set X402_PROXY_WALLET_MNEMONIC`));
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
dim(" No wallet found. Let's set one up first.\n");
|
|
173
|
+
const { runSetup } = await import("../setup-Crq9TylJ.js");
|
|
174
|
+
await runSetup();
|
|
175
|
+
console.log();
|
|
176
|
+
wallet = resolveWallet();
|
|
177
|
+
if (wallet.source === "none") return;
|
|
163
178
|
}
|
|
164
179
|
const config = loadConfig();
|
|
180
|
+
let preferredNetwork = config?.defaultNetwork;
|
|
181
|
+
if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
|
|
182
|
+
const { fetchEvmBalances, fetchSolanaBalances } = await import("../wallet-BX5vQtUz.js");
|
|
183
|
+
const [evmBal, solBal] = await Promise.allSettled([fetchEvmBalances(wallet.evmAddress), fetchSolanaBalances(wallet.solanaAddress)]);
|
|
184
|
+
const evmUsdc = evmBal.status === "fulfilled" ? Number(evmBal.value?.usdc ?? 0) : 0;
|
|
185
|
+
const solUsdc = solBal.status === "fulfilled" ? Number(solBal.value?.usdc ?? 0) : 0;
|
|
186
|
+
if (evmUsdc > solUsdc) preferredNetwork = "base";
|
|
187
|
+
else if (solUsdc > evmUsdc) preferredNetwork = "solana";
|
|
188
|
+
}
|
|
165
189
|
const { x402Fetch, shiftPayment } = createX402ProxyHandler({ client: await buildX402Client(wallet, {
|
|
166
|
-
preferredNetwork
|
|
190
|
+
preferredNetwork,
|
|
191
|
+
network: flags.network,
|
|
167
192
|
spendLimitDaily: config?.spendLimitDaily,
|
|
168
193
|
spendLimitPerTx: config?.spendLimitPerTx
|
|
169
194
|
}) });
|
|
@@ -190,8 +215,81 @@ Examples:
|
|
|
190
215
|
const elapsedMs = Date.now() - startMs;
|
|
191
216
|
const payment = shiftPayment();
|
|
192
217
|
const txSig = extractTxSignature(response);
|
|
218
|
+
if (response.status === 402 && isTTY()) {
|
|
219
|
+
const prHeader = response.headers.get("PAYMENT-REQUIRED") ?? response.headers.get("X-PAYMENT-REQUIRED");
|
|
220
|
+
let accepts = [];
|
|
221
|
+
if (prHeader) try {
|
|
222
|
+
accepts = JSON.parse(Buffer.from(prHeader, "base64").toString()).accepts ?? [];
|
|
223
|
+
} catch {}
|
|
224
|
+
let costNum = 0;
|
|
225
|
+
let costStr = "?";
|
|
226
|
+
if (accepts.length > 0) {
|
|
227
|
+
const cheapest = accepts.reduce((min, a) => Number(a.amount) < Number(min.amount) ? a : min);
|
|
228
|
+
costNum = Number(cheapest.amount) / 1e6;
|
|
229
|
+
costStr = costNum.toFixed(4);
|
|
230
|
+
}
|
|
231
|
+
const hasEvm = accepts.some((a) => a.network.startsWith("eip155:"));
|
|
232
|
+
const hasSolana = accepts.some((a) => a.network.startsWith("solana:"));
|
|
233
|
+
const hasOther = accepts.some((a) => !a.network.startsWith("eip155:") && !a.network.startsWith("solana:"));
|
|
234
|
+
const { fetchEvmBalances, fetchSolanaBalances } = await import("../wallet-BX5vQtUz.js");
|
|
235
|
+
let evmUsdc = 0;
|
|
236
|
+
let solUsdc = 0;
|
|
237
|
+
if (hasEvm && wallet.evmAddress) try {
|
|
238
|
+
const bal = await fetchEvmBalances(wallet.evmAddress);
|
|
239
|
+
evmUsdc = Number(bal.usdc);
|
|
240
|
+
} catch {}
|
|
241
|
+
if (hasSolana && wallet.solanaAddress) try {
|
|
242
|
+
const bal = await fetchSolanaBalances(wallet.solanaAddress);
|
|
243
|
+
solUsdc = Number(bal.usdc);
|
|
244
|
+
} catch {}
|
|
245
|
+
if (hasEvm && evmUsdc >= costNum || hasSolana && solUsdc >= costNum) {
|
|
246
|
+
let serverReason;
|
|
247
|
+
try {
|
|
248
|
+
const body = await response.text();
|
|
249
|
+
if (body) {
|
|
250
|
+
const parsed = JSON.parse(body);
|
|
251
|
+
serverReason = parsed.error || parsed.message;
|
|
252
|
+
}
|
|
253
|
+
} catch {}
|
|
254
|
+
error(`Payment failed: ${costStr} USDC`);
|
|
255
|
+
console.error();
|
|
256
|
+
if (payment) dim(" Payment was signed and sent but rejected by the server.");
|
|
257
|
+
else dim(" Payment was not attempted despite sufficient balance.");
|
|
258
|
+
if (serverReason) dim(` Reason: ${serverReason}`);
|
|
259
|
+
if (hasEvm && wallet.evmAddress && evmUsdc > 0) console.error(` Base: ${pc.cyan(wallet.evmAddress)} ${pc.dim(`(${evmUsdc.toFixed(4)} USDC)`)}`);
|
|
260
|
+
if (hasSolana && wallet.solanaAddress && solUsdc > 0) console.error(` Solana: ${pc.cyan(wallet.solanaAddress)} ${pc.dim(`(${solUsdc.toFixed(4)} USDC)`)}`);
|
|
261
|
+
console.error();
|
|
262
|
+
dim(" This may be a temporary server-side issue. Try again in a moment.");
|
|
263
|
+
console.error();
|
|
264
|
+
} else {
|
|
265
|
+
error(`Payment required: ${costStr} USDC`);
|
|
266
|
+
if (hasEvm || hasSolana) {
|
|
267
|
+
console.error();
|
|
268
|
+
dim(" Fund your wallet with USDC:");
|
|
269
|
+
if (hasEvm && wallet.evmAddress) {
|
|
270
|
+
const balHint = evmUsdc > 0 ? pc.dim(` (${evmUsdc.toFixed(4)} USDC)`) : "";
|
|
271
|
+
console.error(` Base: ${pc.cyan(wallet.evmAddress)}${balHint}`);
|
|
272
|
+
}
|
|
273
|
+
if (hasSolana && wallet.solanaAddress) {
|
|
274
|
+
const balHint = solUsdc > 0 ? pc.dim(` (${solUsdc.toFixed(4)} USDC)`) : "";
|
|
275
|
+
console.error(` Solana: ${pc.cyan(wallet.solanaAddress)}${balHint}`);
|
|
276
|
+
}
|
|
277
|
+
if (hasEvm && !wallet.evmAddress) dim(" Base: endpoint accepts EVM but no EVM wallet configured");
|
|
278
|
+
if (hasSolana && !wallet.solanaAddress) dim(" Solana: endpoint accepts Solana but no Solana wallet configured");
|
|
279
|
+
} else if (hasOther) {
|
|
280
|
+
const networks = [...new Set(accepts.map((a) => a.network))].join(", ");
|
|
281
|
+
console.error();
|
|
282
|
+
error(`This endpoint only accepts payment on unsupported networks: ${networks}`);
|
|
283
|
+
}
|
|
284
|
+
console.error();
|
|
285
|
+
dim(" Then re-run:");
|
|
286
|
+
console.error(` ${pc.cyan(`$ npx x402-proxy ${url}`)}`);
|
|
287
|
+
console.error();
|
|
288
|
+
}
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
193
291
|
if (payment && isTTY()) {
|
|
194
|
-
info(` Payment: ${payment.amount
|
|
292
|
+
info(` Payment: ${payment.amount ? (Number(payment.amount) / 1e6).toFixed(4) : "?"} USDC (${displayNetwork(payment.network ?? "unknown")})`);
|
|
195
293
|
if (txSig) dim(` Tx: ${txSig}`);
|
|
196
294
|
}
|
|
197
295
|
if (isTTY()) dim(` ${response.status} ${response.statusText} (${elapsedMs}ms)`);
|
|
@@ -253,6 +351,12 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
253
351
|
brief: "Solana private key (base58)",
|
|
254
352
|
parse: String,
|
|
255
353
|
optional: true
|
|
354
|
+
},
|
|
355
|
+
network: {
|
|
356
|
+
kind: "parsed",
|
|
357
|
+
brief: "Require specific network (base, solana)",
|
|
358
|
+
parse: String,
|
|
359
|
+
optional: true
|
|
256
360
|
}
|
|
257
361
|
},
|
|
258
362
|
positional: {
|
|
@@ -269,7 +373,7 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
269
373
|
solanaKey: flags.solanaKey
|
|
270
374
|
});
|
|
271
375
|
if (wallet.source === "none") {
|
|
272
|
-
error("No wallet configured
|
|
376
|
+
error("No wallet configured.\nRun:\n $ npx x402-proxy setup\n\nOr set X402_PROXY_WALLET_MNEMONIC");
|
|
273
377
|
process.exit(1);
|
|
274
378
|
}
|
|
275
379
|
warn("Note: MCP proxy is alpha - please report issues.");
|
|
@@ -277,8 +381,18 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
277
381
|
if (wallet.evmAddress) dim(` EVM: ${wallet.evmAddress}`);
|
|
278
382
|
if (wallet.solanaAddress) dim(` Solana: ${wallet.solanaAddress}`);
|
|
279
383
|
const config = loadConfig();
|
|
384
|
+
let preferredNetwork = config?.defaultNetwork;
|
|
385
|
+
if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
|
|
386
|
+
const { fetchEvmBalances, fetchSolanaBalances } = await import("../wallet-BX5vQtUz.js");
|
|
387
|
+
const [evmBal, solBal] = await Promise.allSettled([fetchEvmBalances(wallet.evmAddress), fetchSolanaBalances(wallet.solanaAddress)]);
|
|
388
|
+
const evmUsdc = evmBal.status === "fulfilled" ? Number(evmBal.value?.usdc ?? 0) : 0;
|
|
389
|
+
const solUsdc = solBal.status === "fulfilled" ? Number(solBal.value?.usdc ?? 0) : 0;
|
|
390
|
+
if (evmUsdc > solUsdc) preferredNetwork = "base";
|
|
391
|
+
else if (solUsdc > evmUsdc) preferredNetwork = "solana";
|
|
392
|
+
}
|
|
280
393
|
const x402PaymentClient = await buildX402Client(wallet, {
|
|
281
|
-
preferredNetwork
|
|
394
|
+
preferredNetwork,
|
|
395
|
+
network: flags.network,
|
|
282
396
|
spendLimitDaily: config?.spendLimitDaily,
|
|
283
397
|
spendLimitPerTx: config?.spendLimitPerTx
|
|
284
398
|
});
|
|
@@ -290,26 +404,28 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
290
404
|
const { x402MCPClient } = await import("@x402/mcp");
|
|
291
405
|
const x402Mcp = new x402MCPClient(new Client({
|
|
292
406
|
name: "x402-proxy",
|
|
293
|
-
version: "0.
|
|
407
|
+
version: "0.4.0"
|
|
294
408
|
}), x402PaymentClient, {
|
|
295
409
|
autoPayment: true,
|
|
296
410
|
onPaymentRequested: (ctx) => {
|
|
297
411
|
const accept = ctx.paymentRequired.accepts?.[0];
|
|
298
|
-
if (accept) warn(` Payment: ${accept.amount} on ${accept.network} for tool "${ctx.toolName}"`);
|
|
412
|
+
if (accept) warn(` Payment: ${accept.amount ? (Number(accept.amount) / 1e6).toFixed(4) : "?"} USDC on ${displayNetwork(accept.network)} for tool "${ctx.toolName}"`);
|
|
299
413
|
return true;
|
|
300
414
|
}
|
|
301
415
|
});
|
|
302
416
|
x402Mcp.onAfterPayment(async (ctx) => {
|
|
303
417
|
ensureConfigDir();
|
|
418
|
+
const accepted = ctx.paymentPayload.accepted;
|
|
304
419
|
const tx = ctx.settleResponse?.transaction;
|
|
305
|
-
const accept = ctx.paymentPayload;
|
|
306
420
|
const record = {
|
|
307
421
|
t: Date.now(),
|
|
308
422
|
ok: true,
|
|
309
423
|
kind: "x402_payment",
|
|
310
|
-
net:
|
|
424
|
+
net: accepted?.network ?? "unknown",
|
|
311
425
|
from: wallet.evmAddress ?? wallet.solanaAddress ?? "unknown",
|
|
426
|
+
to: accepted?.payTo,
|
|
312
427
|
tx: typeof tx === "string" ? tx : void 0,
|
|
428
|
+
amount: accepted?.amount ? Number(accepted.amount) / 1e6 : void 0,
|
|
313
429
|
token: "USDC",
|
|
314
430
|
label: `mcp:${ctx.toolName}`
|
|
315
431
|
};
|
|
@@ -335,7 +451,7 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
335
451
|
dim(` ${tools.length} tools available`);
|
|
336
452
|
const localServer = new McpServer({
|
|
337
453
|
name: "x402-proxy",
|
|
338
|
-
version: "0.
|
|
454
|
+
version: "0.4.0"
|
|
339
455
|
});
|
|
340
456
|
for (const tool of tools) localServer.tool(tool.name, tool.description ?? "", tool.inputSchema?.properties ? Object.fromEntries(Object.entries(tool.inputSchema.properties).map(([k, v]) => [k, v])) : {}, async (args) => {
|
|
341
457
|
const result = await x402Mcp.callTool(tool.name, args);
|
|
@@ -366,84 +482,6 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
366
482
|
}
|
|
367
483
|
});
|
|
368
484
|
|
|
369
|
-
//#endregion
|
|
370
|
-
//#region src/commands/setup.ts
|
|
371
|
-
const setupCommand = buildCommand({
|
|
372
|
-
docs: { brief: "Set up x402-proxy with a new wallet" },
|
|
373
|
-
parameters: {
|
|
374
|
-
flags: { force: {
|
|
375
|
-
kind: "boolean",
|
|
376
|
-
brief: "Overwrite existing configuration",
|
|
377
|
-
default: false
|
|
378
|
-
} },
|
|
379
|
-
positional: {
|
|
380
|
-
kind: "tuple",
|
|
381
|
-
parameters: []
|
|
382
|
-
}
|
|
383
|
-
},
|
|
384
|
-
async func(flags) {
|
|
385
|
-
if (isConfigured() && !flags.force) {
|
|
386
|
-
prompts.log.warn(`Already configured. Wallet at ${pc.dim(getWalletPath())}\nUse ${pc.cyan("x402-proxy setup --force")} to reconfigure.`);
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
prompts.intro(pc.cyan("x402-proxy setup"));
|
|
390
|
-
prompts.log.info("This will generate a single BIP-39 mnemonic that derives wallets for both Solana and EVM chains.");
|
|
391
|
-
const action = await prompts.select({
|
|
392
|
-
message: "How would you like to set up your wallet?",
|
|
393
|
-
options: [{
|
|
394
|
-
value: "generate",
|
|
395
|
-
label: "Generate a new mnemonic"
|
|
396
|
-
}, {
|
|
397
|
-
value: "import",
|
|
398
|
-
label: "Import an existing mnemonic"
|
|
399
|
-
}]
|
|
400
|
-
});
|
|
401
|
-
if (prompts.isCancel(action)) {
|
|
402
|
-
prompts.cancel("Setup cancelled.");
|
|
403
|
-
process.exit(0);
|
|
404
|
-
}
|
|
405
|
-
let mnemonic;
|
|
406
|
-
if (action === "generate") {
|
|
407
|
-
mnemonic = generateMnemonic();
|
|
408
|
-
prompts.log.warn("Write down your mnemonic and store it safely. It will NOT be shown again.");
|
|
409
|
-
prompts.log.message(pc.bold(mnemonic));
|
|
410
|
-
} else {
|
|
411
|
-
const input = await prompts.text({
|
|
412
|
-
message: "Enter your 24-word mnemonic:",
|
|
413
|
-
validate: (v = "") => {
|
|
414
|
-
const words = v.trim().split(/\s+/);
|
|
415
|
-
if (words.length !== 12 && words.length !== 24) return "Mnemonic must be 12 or 24 words";
|
|
416
|
-
}
|
|
417
|
-
});
|
|
418
|
-
if (prompts.isCancel(input)) {
|
|
419
|
-
prompts.cancel("Setup cancelled.");
|
|
420
|
-
process.exit(0);
|
|
421
|
-
}
|
|
422
|
-
mnemonic = input.trim();
|
|
423
|
-
}
|
|
424
|
-
const evm = deriveEvmKeypair(mnemonic);
|
|
425
|
-
const sol = deriveSolanaKeypair(mnemonic);
|
|
426
|
-
prompts.log.success(`Base address: ${pc.green(evm.address)}`);
|
|
427
|
-
prompts.log.success(`Solana address: ${pc.green(sol.address)}`);
|
|
428
|
-
saveWalletFile({
|
|
429
|
-
version: 1,
|
|
430
|
-
mnemonic,
|
|
431
|
-
addresses: {
|
|
432
|
-
evm: evm.address,
|
|
433
|
-
solana: sol.address
|
|
434
|
-
}
|
|
435
|
-
});
|
|
436
|
-
saveConfig({});
|
|
437
|
-
prompts.log.info(`Config directory: ${pc.dim(getConfigDirShort())}`);
|
|
438
|
-
prompts.log.step("Fund your wallets to start using x402 resources:");
|
|
439
|
-
prompts.log.message(` Solana (USDC): Send USDC to ${pc.cyan(sol.address)}`);
|
|
440
|
-
prompts.log.message(` Base (USDC): Send USDC to ${pc.cyan(evm.address)}`);
|
|
441
|
-
prompts.log.step("Try your first request:");
|
|
442
|
-
prompts.log.message(` ${pc.cyan("$ npx x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi")}`);
|
|
443
|
-
prompts.outro(pc.green("Setup complete!"));
|
|
444
|
-
}
|
|
445
|
-
});
|
|
446
|
-
|
|
447
485
|
//#endregion
|
|
448
486
|
//#region src/commands/wallet-export.ts
|
|
449
487
|
const walletExportCommand = buildCommand({
|
|
@@ -498,44 +536,6 @@ const walletExportCommand = buildCommand({
|
|
|
498
536
|
}
|
|
499
537
|
});
|
|
500
538
|
|
|
501
|
-
//#endregion
|
|
502
|
-
//#region src/commands/wallet-fund.ts
|
|
503
|
-
const walletFundCommand = buildCommand({
|
|
504
|
-
docs: { brief: "Show wallet funding instructions" },
|
|
505
|
-
parameters: {
|
|
506
|
-
flags: {},
|
|
507
|
-
positional: {
|
|
508
|
-
kind: "tuple",
|
|
509
|
-
parameters: []
|
|
510
|
-
}
|
|
511
|
-
},
|
|
512
|
-
func() {
|
|
513
|
-
const wallet = resolveWallet();
|
|
514
|
-
if (wallet.source === "none") {
|
|
515
|
-
console.log(pc.yellow("No wallet configured."));
|
|
516
|
-
console.log(pc.dim(`Run ${pc.cyan("x402-proxy setup")} to create one.`));
|
|
517
|
-
process.exit(1);
|
|
518
|
-
}
|
|
519
|
-
console.log();
|
|
520
|
-
info("Funding Instructions");
|
|
521
|
-
console.log();
|
|
522
|
-
if (wallet.solanaAddress) {
|
|
523
|
-
console.log(pc.bold(" Solana (USDC):"));
|
|
524
|
-
console.log(` Send USDC to: ${pc.green(wallet.solanaAddress)}`);
|
|
525
|
-
console.log(pc.dim(" Network: Solana Mainnet"));
|
|
526
|
-
console.log();
|
|
527
|
-
}
|
|
528
|
-
if (wallet.evmAddress) {
|
|
529
|
-
console.log(pc.bold(" Base (USDC):"));
|
|
530
|
-
console.log(` Send USDC to: ${pc.green(wallet.evmAddress)}`);
|
|
531
|
-
console.log(pc.dim(" Network: Base (Chain ID 8453)"));
|
|
532
|
-
console.log();
|
|
533
|
-
}
|
|
534
|
-
console.log(pc.dim(" Tip: Most x402 services accept USDC on Base or Solana."));
|
|
535
|
-
console.log();
|
|
536
|
-
}
|
|
537
|
-
});
|
|
538
|
-
|
|
539
539
|
//#endregion
|
|
540
540
|
//#region src/commands/wallet-history.ts
|
|
541
541
|
const walletHistoryCommand = buildCommand({
|
|
@@ -595,7 +595,6 @@ const routes = buildRouteMap({
|
|
|
595
595
|
routes: {
|
|
596
596
|
info: walletInfoCommand,
|
|
597
597
|
history: walletHistoryCommand,
|
|
598
|
-
fund: walletFundCommand,
|
|
599
598
|
"export-key": walletExportCommand
|
|
600
599
|
},
|
|
601
600
|
defaultCommand: "info",
|
|
@@ -609,7 +608,7 @@ const routes = buildRouteMap({
|
|
|
609
608
|
});
|
|
610
609
|
const app = buildApplication(routes, {
|
|
611
610
|
name: "x402-proxy",
|
|
612
|
-
versionInfo: { currentVersion: "0.
|
|
611
|
+
versionInfo: { currentVersion: "0.4.0" },
|
|
613
612
|
scanner: { caseStyle: "allow-kebab-for-camel" }
|
|
614
613
|
});
|
|
615
614
|
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { parse, stringify } from "yaml";
|
|
6
|
+
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
7
|
+
import { base58 } from "@scure/base";
|
|
8
|
+
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
9
|
+
import { hmac } from "@noble/hashes/hmac.js";
|
|
10
|
+
import { sha512 } from "@noble/hashes/sha2.js";
|
|
11
|
+
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
12
|
+
import { HDKey } from "@scure/bip32";
|
|
13
|
+
import { generateMnemonic, mnemonicToSeedSync } from "@scure/bip39";
|
|
14
|
+
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
15
|
+
|
|
16
|
+
//#region src/lib/config.ts
|
|
17
|
+
const APP_NAME = "x402-proxy";
|
|
18
|
+
function getConfigDir() {
|
|
19
|
+
const xdg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
20
|
+
return path.join(xdg, APP_NAME);
|
|
21
|
+
}
|
|
22
|
+
/** Config dir with $HOME replaced by ~ for display */
|
|
23
|
+
function getConfigDirShort() {
|
|
24
|
+
const dir = getConfigDir();
|
|
25
|
+
const home = os.homedir();
|
|
26
|
+
return dir.startsWith(home) ? `~${dir.slice(home.length)}` : dir;
|
|
27
|
+
}
|
|
28
|
+
function getWalletPath() {
|
|
29
|
+
return path.join(getConfigDir(), "wallet.json");
|
|
30
|
+
}
|
|
31
|
+
function getHistoryPath() {
|
|
32
|
+
return path.join(getConfigDir(), "history.jsonl");
|
|
33
|
+
}
|
|
34
|
+
function ensureConfigDir() {
|
|
35
|
+
fs.mkdirSync(getConfigDir(), { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
function loadWalletFile() {
|
|
38
|
+
const p = getWalletPath();
|
|
39
|
+
if (!fs.existsSync(p)) return null;
|
|
40
|
+
try {
|
|
41
|
+
const data = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
42
|
+
if (data.version === 1 && typeof data.mnemonic === "string") return data;
|
|
43
|
+
return null;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function saveWalletFile(wallet) {
|
|
49
|
+
ensureConfigDir();
|
|
50
|
+
fs.writeFileSync(getWalletPath(), JSON.stringify(wallet, null, 2), {
|
|
51
|
+
mode: 384,
|
|
52
|
+
encoding: "utf-8"
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function loadConfig() {
|
|
56
|
+
const dir = getConfigDir();
|
|
57
|
+
for (const name of [
|
|
58
|
+
"config.yaml",
|
|
59
|
+
"config.yml",
|
|
60
|
+
"config.jsonc",
|
|
61
|
+
"config.json"
|
|
62
|
+
]) {
|
|
63
|
+
const p = path.join(dir, name);
|
|
64
|
+
if (!fs.existsSync(p)) continue;
|
|
65
|
+
try {
|
|
66
|
+
const raw = fs.readFileSync(p, "utf-8");
|
|
67
|
+
if (name.endsWith(".yaml") || name.endsWith(".yml")) return parse(raw);
|
|
68
|
+
if (name.endsWith(".jsonc")) {
|
|
69
|
+
const stripped = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
70
|
+
return JSON.parse(stripped);
|
|
71
|
+
}
|
|
72
|
+
return JSON.parse(raw);
|
|
73
|
+
} catch {}
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
function saveConfig(config) {
|
|
78
|
+
ensureConfigDir();
|
|
79
|
+
const p = path.join(getConfigDir(), "config.yaml");
|
|
80
|
+
fs.writeFileSync(p, stringify(config), "utf-8");
|
|
81
|
+
}
|
|
82
|
+
function isConfigured() {
|
|
83
|
+
if (process.env.X402_PROXY_WALLET_MNEMONIC) return true;
|
|
84
|
+
if (process.env.X402_PROXY_WALLET_EVM_KEY) return true;
|
|
85
|
+
if (process.env.X402_PROXY_WALLET_SOLANA_KEY) return true;
|
|
86
|
+
return loadWalletFile() !== null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/lib/derive.ts
|
|
91
|
+
/**
|
|
92
|
+
* Wallet derivation from BIP-39 mnemonic.
|
|
93
|
+
*
|
|
94
|
+
* A single 24-word mnemonic is the root secret. Both Solana and EVM keypairs
|
|
95
|
+
* are deterministically derived from it.
|
|
96
|
+
*
|
|
97
|
+
* Solana: SLIP-10 Ed25519 at m/44'/501'/0'/0'
|
|
98
|
+
* EVM: BIP-32 secp256k1 at m/44'/60'/0'/0/0
|
|
99
|
+
*
|
|
100
|
+
* Ported from agentbox/packages/openclaw-x402/src/wallet.ts
|
|
101
|
+
*/
|
|
102
|
+
function generateMnemonic$1() {
|
|
103
|
+
return generateMnemonic(wordlist, 256);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* SLIP-10 Ed25519 derivation at m/44'/501'/0'/0' (Phantom/Backpack compatible).
|
|
107
|
+
*/
|
|
108
|
+
const enc = new TextEncoder();
|
|
109
|
+
function deriveSolanaKeypair(mnemonic) {
|
|
110
|
+
const seed = mnemonicToSeedSync(mnemonic);
|
|
111
|
+
let I = hmac(sha512, enc.encode("ed25519 seed"), seed);
|
|
112
|
+
let key = I.slice(0, 32);
|
|
113
|
+
let chainCode = I.slice(32);
|
|
114
|
+
for (const index of [
|
|
115
|
+
2147483692,
|
|
116
|
+
2147484149,
|
|
117
|
+
2147483648,
|
|
118
|
+
2147483648
|
|
119
|
+
]) {
|
|
120
|
+
const data = new Uint8Array(37);
|
|
121
|
+
data[0] = 0;
|
|
122
|
+
data.set(key, 1);
|
|
123
|
+
data[33] = index >>> 24 & 255;
|
|
124
|
+
data[34] = index >>> 16 & 255;
|
|
125
|
+
data[35] = index >>> 8 & 255;
|
|
126
|
+
data[36] = index & 255;
|
|
127
|
+
I = hmac(sha512, chainCode, data);
|
|
128
|
+
key = I.slice(0, 32);
|
|
129
|
+
chainCode = I.slice(32);
|
|
130
|
+
}
|
|
131
|
+
const secretKey = new Uint8Array(key);
|
|
132
|
+
const publicKey = ed25519.getPublicKey(secretKey);
|
|
133
|
+
return {
|
|
134
|
+
secretKey,
|
|
135
|
+
publicKey,
|
|
136
|
+
address: base58.encode(publicKey)
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* BIP-32 secp256k1 derivation at m/44'/60'/0'/0/0.
|
|
141
|
+
*/
|
|
142
|
+
function deriveEvmKeypair(mnemonic) {
|
|
143
|
+
const seed = mnemonicToSeedSync(mnemonic);
|
|
144
|
+
const derived = HDKey.fromMasterSeed(seed).derive("m/44'/60'/0'/0/0");
|
|
145
|
+
if (!derived.privateKey) throw new Error("Failed to derive EVM private key");
|
|
146
|
+
const privateKey = `0x${Buffer.from(derived.privateKey).toString("hex")}`;
|
|
147
|
+
const hash = keccak_256(secp256k1.getPublicKey(derived.privateKey, false).slice(1));
|
|
148
|
+
return {
|
|
149
|
+
privateKey,
|
|
150
|
+
address: checksumAddress(Buffer.from(hash.slice(-20)).toString("hex"))
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function checksumAddress(addr) {
|
|
154
|
+
const hash = Buffer.from(keccak_256(enc.encode(addr))).toString("hex");
|
|
155
|
+
let out = "0x";
|
|
156
|
+
for (let i = 0; i < 40; i++) out += Number.parseInt(hash[i], 16) >= 8 ? addr[i].toUpperCase() : addr[i];
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
//#endregion
|
|
161
|
+
export { getConfigDirShort as a, isConfigured as c, saveConfig as d, saveWalletFile as f, ensureConfigDir as i, loadConfig as l, deriveSolanaKeypair as n, getHistoryPath as o, generateMnemonic$1 as r, getWalletPath as s, deriveEvmKeypair as t, loadWalletFile as u };
|
package/dist/index.js
CHANGED
|
@@ -35,7 +35,7 @@ function createX402ProxyHandler(opts) {
|
|
|
35
35
|
paymentQueue.push({
|
|
36
36
|
network: hookCtx.selectedRequirements.network,
|
|
37
37
|
payTo: hookCtx.selectedRequirements.payTo,
|
|
38
|
-
amount: raw
|
|
38
|
+
amount: raw,
|
|
39
39
|
asset: hookCtx.selectedRequirements.asset
|
|
40
40
|
});
|
|
41
41
|
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { a as getConfigDirShort, c as isConfigured, d as saveConfig, f as saveWalletFile, n as deriveSolanaKeypair, r as generateMnemonic, s as getWalletPath, t as deriveEvmKeypair } from "./derive-CISr_ond.js";
|
|
3
|
+
import { buildCommand } from "@stricli/core";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
import * as prompts from "@clack/prompts";
|
|
6
|
+
|
|
7
|
+
//#region src/commands/setup.ts
|
|
8
|
+
async function runSetup(opts) {
|
|
9
|
+
if (isConfigured() && !opts?.force) {
|
|
10
|
+
prompts.log.warn(`Already configured. Wallet at ${pc.dim(getWalletPath())}\nTo reconfigure, run:\n ${pc.cyan("$ npx x402-proxy setup --force")}`);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
prompts.intro(pc.cyan("x402-proxy setup"));
|
|
14
|
+
prompts.log.info("This will generate a single BIP-39 mnemonic that derives wallets for both Solana and EVM chains.");
|
|
15
|
+
const action = await prompts.select({
|
|
16
|
+
message: "How would you like to set up your wallet?",
|
|
17
|
+
options: [{
|
|
18
|
+
value: "generate",
|
|
19
|
+
label: "Generate a new mnemonic"
|
|
20
|
+
}, {
|
|
21
|
+
value: "import",
|
|
22
|
+
label: "Import an existing mnemonic"
|
|
23
|
+
}]
|
|
24
|
+
});
|
|
25
|
+
if (prompts.isCancel(action)) {
|
|
26
|
+
prompts.cancel("Setup cancelled.");
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
let mnemonic;
|
|
30
|
+
if (action === "generate") {
|
|
31
|
+
mnemonic = generateMnemonic();
|
|
32
|
+
prompts.log.warn("Write down your mnemonic and store it safely. It will NOT be shown again.");
|
|
33
|
+
prompts.log.message(pc.bold(mnemonic));
|
|
34
|
+
} else {
|
|
35
|
+
const input = await prompts.text({
|
|
36
|
+
message: "Enter your 24-word mnemonic:",
|
|
37
|
+
validate: (v = "") => {
|
|
38
|
+
const words = v.trim().split(/\s+/);
|
|
39
|
+
if (words.length !== 12 && words.length !== 24) return "Mnemonic must be 12 or 24 words";
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
if (prompts.isCancel(input)) {
|
|
43
|
+
prompts.cancel("Setup cancelled.");
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
mnemonic = input.trim();
|
|
47
|
+
}
|
|
48
|
+
const evm = deriveEvmKeypair(mnemonic);
|
|
49
|
+
const sol = deriveSolanaKeypair(mnemonic);
|
|
50
|
+
prompts.log.success(`Base address: ${pc.green(evm.address)}`);
|
|
51
|
+
prompts.log.success(`Solana address: ${pc.green(sol.address)}`);
|
|
52
|
+
saveWalletFile({
|
|
53
|
+
version: 1,
|
|
54
|
+
mnemonic,
|
|
55
|
+
addresses: {
|
|
56
|
+
evm: evm.address,
|
|
57
|
+
solana: sol.address
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
saveConfig({});
|
|
61
|
+
prompts.log.info(`Config directory: ${pc.dim(getConfigDirShort())}`);
|
|
62
|
+
prompts.log.step("Fund your wallets to start using x402 resources:");
|
|
63
|
+
prompts.log.message(` Solana (USDC): Send USDC to ${pc.cyan(sol.address)}`);
|
|
64
|
+
prompts.log.message(` Base (USDC): Send USDC to ${pc.cyan(evm.address)}`);
|
|
65
|
+
prompts.log.step("Try your first request:");
|
|
66
|
+
prompts.log.message(` ${pc.cyan("$ npx x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi")}`);
|
|
67
|
+
prompts.outro(pc.green("Setup complete!"));
|
|
68
|
+
}
|
|
69
|
+
const setupCommand = buildCommand({
|
|
70
|
+
docs: { brief: "Set up x402-proxy with a new wallet" },
|
|
71
|
+
parameters: {
|
|
72
|
+
flags: { force: {
|
|
73
|
+
kind: "boolean",
|
|
74
|
+
brief: "Overwrite existing configuration",
|
|
75
|
+
default: false
|
|
76
|
+
} },
|
|
77
|
+
positional: {
|
|
78
|
+
kind: "tuple",
|
|
79
|
+
parameters: []
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
async func(flags) {
|
|
83
|
+
await runSetup({ force: flags.force });
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
//#endregion
|
|
88
|
+
export { setupCommand as n, runSetup as t };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { g as readHistory, h as formatTxLine, n as fetchEvmBalances, o as resolveWallet, p as calcSpend, r as fetchSolanaBalances, s as dim, t as balanceLine } from "./wallet-Vp9zRQzC.js";
|
|
3
|
+
import { a as getConfigDirShort, l as loadConfig, o as getHistoryPath } from "./derive-CISr_ond.js";
|
|
4
|
+
import { buildCommand } from "@stricli/core";
|
|
5
|
+
import pc from "picocolors";
|
|
6
|
+
|
|
7
|
+
//#region src/commands/status.ts
|
|
8
|
+
async function displayStatus() {
|
|
9
|
+
const wallet = resolveWallet();
|
|
10
|
+
const config = loadConfig();
|
|
11
|
+
const records = readHistory(getHistoryPath());
|
|
12
|
+
const spend = calcSpend(records);
|
|
13
|
+
console.log();
|
|
14
|
+
console.log(pc.cyan(pc.bold("x402-proxy")));
|
|
15
|
+
console.log(pc.dim("curl for x402 paid APIs"));
|
|
16
|
+
console.log();
|
|
17
|
+
if (wallet.source === "none") {
|
|
18
|
+
console.log(pc.yellow(" No wallet configured."));
|
|
19
|
+
console.log(pc.dim(` Run ${pc.cyan("$ npx x402-proxy setup")} to create one.`));
|
|
20
|
+
} else {
|
|
21
|
+
const [evmResult, solResult] = await Promise.allSettled([wallet.evmAddress ? fetchEvmBalances(wallet.evmAddress) : Promise.resolve(null), wallet.solanaAddress ? fetchSolanaBalances(wallet.solanaAddress) : Promise.resolve(null)]);
|
|
22
|
+
const evm = evmResult.status === "fulfilled" ? evmResult.value : null;
|
|
23
|
+
const sol = solResult.status === "fulfilled" ? solResult.value : null;
|
|
24
|
+
if (wallet.evmAddress) {
|
|
25
|
+
const bal = evm ? balanceLine(evm.usdc, evm.eth, "ETH") : pc.dim(" (network error)");
|
|
26
|
+
console.log(` Base: ${pc.green(wallet.evmAddress)}${bal}`);
|
|
27
|
+
}
|
|
28
|
+
if (wallet.solanaAddress) {
|
|
29
|
+
const bal = sol ? balanceLine(sol.usdc, sol.sol, "SOL") : pc.dim(" (network error)");
|
|
30
|
+
console.log(` Solana: ${pc.green(wallet.solanaAddress)}${bal}`);
|
|
31
|
+
}
|
|
32
|
+
if (config?.spendLimitDaily || config?.spendLimitPerTx) {
|
|
33
|
+
console.log();
|
|
34
|
+
if (config.spendLimitDaily) {
|
|
35
|
+
const pct = config.spendLimitDaily > 0 ? Math.round(spend.today / config.spendLimitDaily * 100) : 0;
|
|
36
|
+
dim(` Daily limit: ${spend.today.toFixed(4)} / ${config.spendLimitDaily} USDC (${pct}%)`);
|
|
37
|
+
}
|
|
38
|
+
if (config.spendLimitPerTx) dim(` Per-tx limit: ${config.spendLimitPerTx} USDC`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
console.log();
|
|
42
|
+
if (spend.count > 0) {
|
|
43
|
+
const recent = records.slice(-5);
|
|
44
|
+
dim(" Recent transactions:");
|
|
45
|
+
for (const r of recent) {
|
|
46
|
+
const line = formatTxLine(r).replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
47
|
+
console.log(line);
|
|
48
|
+
}
|
|
49
|
+
console.log();
|
|
50
|
+
dim(` Today: ${spend.today.toFixed(4)} USDC | Total: ${spend.total.toFixed(4)} USDC | ${spend.count} tx`);
|
|
51
|
+
} else dim(" No payment history yet.");
|
|
52
|
+
console.log();
|
|
53
|
+
if (config?.defaultNetwork) dim(` Network: ${config.defaultNetwork}`);
|
|
54
|
+
dim(` Config: ${getConfigDirShort()}`);
|
|
55
|
+
}
|
|
56
|
+
const statusCommand = buildCommand({
|
|
57
|
+
docs: { brief: "Show configuration and wallet status" },
|
|
58
|
+
parameters: {
|
|
59
|
+
flags: {},
|
|
60
|
+
positional: {
|
|
61
|
+
kind: "tuple",
|
|
62
|
+
parameters: []
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
async func() {
|
|
66
|
+
await displayStatus();
|
|
67
|
+
console.log();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
//#endregion
|
|
72
|
+
export { statusCommand as n, displayStatus as t };
|
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { n as deriveSolanaKeypair, o as getHistoryPath, t as deriveEvmKeypair, u as loadWalletFile } from "./derive-CISr_ond.js";
|
|
2
3
|
import { buildCommand } from "@stricli/core";
|
|
3
4
|
import pc from "picocolors";
|
|
4
5
|
import { x402Client } from "@x402/fetch";
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import path from "node:path";
|
|
8
|
-
import { parse, stringify } from "yaml";
|
|
6
|
+
import { appendFileSync, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
9
8
|
import { base58 } from "@scure/base";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import { toClientEvmSigner } from "@x402/evm";
|
|
10
|
+
import { registerExactEvmScheme } from "@x402/evm/exact/client";
|
|
11
|
+
import { registerExactSvmScheme } from "@x402/svm/exact/client";
|
|
12
12
|
import { createPublicClient, http } from "viem";
|
|
13
13
|
import { privateKeyToAccount } from "viem/accounts";
|
|
14
14
|
import { base } from "viem/chains";
|
|
15
|
-
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
16
|
-
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
17
|
-
import { hmac } from "@noble/hashes/hmac.js";
|
|
18
|
-
import { sha512 } from "@noble/hashes/sha2.js";
|
|
19
|
-
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
20
|
-
import { HDKey } from "@scure/bip32";
|
|
21
|
-
import { generateMnemonic, mnemonicToSeedSync } from "@scure/bip39";
|
|
22
|
-
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
23
15
|
|
|
24
16
|
//#region src/history.ts
|
|
25
17
|
const HISTORY_MAX_LINES = 1e3;
|
|
@@ -106,6 +98,12 @@ function shortModel(model) {
|
|
|
106
98
|
const parts = model.split("/");
|
|
107
99
|
return parts[parts.length - 1].replace(/-\d{6,8}$/, "").replace(/-\d{4}$/, "");
|
|
108
100
|
}
|
|
101
|
+
function displayNetwork(net) {
|
|
102
|
+
if (net === "eip155:8453") return "Base";
|
|
103
|
+
if (net.startsWith("eip155:")) return `EVM (${net.split(":")[1]})`;
|
|
104
|
+
if (net.startsWith("solana:")) return "Solana";
|
|
105
|
+
return net;
|
|
106
|
+
}
|
|
109
107
|
function shortNetwork(net) {
|
|
110
108
|
if (net === "eip155:8453") return "base";
|
|
111
109
|
if (net.startsWith("eip155:")) return `evm:${net.split(":")[1]}`;
|
|
@@ -132,80 +130,6 @@ function formatTxLine(r, opts) {
|
|
|
132
130
|
return ` ${timeStr} ${r.ok ? "" : "✗ "}${parts.join(" · ")}`;
|
|
133
131
|
}
|
|
134
132
|
|
|
135
|
-
//#endregion
|
|
136
|
-
//#region src/lib/config.ts
|
|
137
|
-
const APP_NAME = "x402-proxy";
|
|
138
|
-
function getConfigDir() {
|
|
139
|
-
const xdg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
140
|
-
return path.join(xdg, APP_NAME);
|
|
141
|
-
}
|
|
142
|
-
/** Config dir with $HOME replaced by ~ for display */
|
|
143
|
-
function getConfigDirShort() {
|
|
144
|
-
const dir = getConfigDir();
|
|
145
|
-
const home = os.homedir();
|
|
146
|
-
return dir.startsWith(home) ? `~${dir.slice(home.length)}` : dir;
|
|
147
|
-
}
|
|
148
|
-
function getWalletPath() {
|
|
149
|
-
return path.join(getConfigDir(), "wallet.json");
|
|
150
|
-
}
|
|
151
|
-
function getHistoryPath() {
|
|
152
|
-
return path.join(getConfigDir(), "history.jsonl");
|
|
153
|
-
}
|
|
154
|
-
function ensureConfigDir() {
|
|
155
|
-
fs.mkdirSync(getConfigDir(), { recursive: true });
|
|
156
|
-
}
|
|
157
|
-
function loadWalletFile() {
|
|
158
|
-
const p = getWalletPath();
|
|
159
|
-
if (!fs.existsSync(p)) return null;
|
|
160
|
-
try {
|
|
161
|
-
const data = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
162
|
-
if (data.version === 1 && typeof data.mnemonic === "string") return data;
|
|
163
|
-
return null;
|
|
164
|
-
} catch {
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
function saveWalletFile(wallet) {
|
|
169
|
-
ensureConfigDir();
|
|
170
|
-
fs.writeFileSync(getWalletPath(), JSON.stringify(wallet, null, 2), {
|
|
171
|
-
mode: 384,
|
|
172
|
-
encoding: "utf-8"
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
function loadConfig() {
|
|
176
|
-
const dir = getConfigDir();
|
|
177
|
-
for (const name of [
|
|
178
|
-
"config.yaml",
|
|
179
|
-
"config.yml",
|
|
180
|
-
"config.jsonc",
|
|
181
|
-
"config.json"
|
|
182
|
-
]) {
|
|
183
|
-
const p = path.join(dir, name);
|
|
184
|
-
if (!fs.existsSync(p)) continue;
|
|
185
|
-
try {
|
|
186
|
-
const raw = fs.readFileSync(p, "utf-8");
|
|
187
|
-
if (name.endsWith(".yaml") || name.endsWith(".yml")) return parse(raw);
|
|
188
|
-
if (name.endsWith(".jsonc")) {
|
|
189
|
-
const stripped = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
190
|
-
return JSON.parse(stripped);
|
|
191
|
-
}
|
|
192
|
-
return JSON.parse(raw);
|
|
193
|
-
} catch {}
|
|
194
|
-
}
|
|
195
|
-
return null;
|
|
196
|
-
}
|
|
197
|
-
function saveConfig(config) {
|
|
198
|
-
ensureConfigDir();
|
|
199
|
-
const p = path.join(getConfigDir(), "config.yaml");
|
|
200
|
-
fs.writeFileSync(p, stringify(config), "utf-8");
|
|
201
|
-
}
|
|
202
|
-
function isConfigured() {
|
|
203
|
-
if (process.env.X402_PROXY_WALLET_MNEMONIC) return true;
|
|
204
|
-
if (process.env.X402_PROXY_WALLET_EVM_KEY) return true;
|
|
205
|
-
if (process.env.X402_PROXY_WALLET_SOLANA_KEY) return true;
|
|
206
|
-
return loadWalletFile() !== null;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
133
|
//#endregion
|
|
210
134
|
//#region src/lib/output.ts
|
|
211
135
|
function isTTY() {
|
|
@@ -224,77 +148,6 @@ function dim(msg) {
|
|
|
224
148
|
process.stderr.write(`${isTTY() ? pc.dim(msg) : msg}\n`);
|
|
225
149
|
}
|
|
226
150
|
|
|
227
|
-
//#endregion
|
|
228
|
-
//#region src/lib/derive.ts
|
|
229
|
-
/**
|
|
230
|
-
* Wallet derivation from BIP-39 mnemonic.
|
|
231
|
-
*
|
|
232
|
-
* A single 24-word mnemonic is the root secret. Both Solana and EVM keypairs
|
|
233
|
-
* are deterministically derived from it.
|
|
234
|
-
*
|
|
235
|
-
* Solana: SLIP-10 Ed25519 at m/44'/501'/0'/0'
|
|
236
|
-
* EVM: BIP-32 secp256k1 at m/44'/60'/0'/0/0
|
|
237
|
-
*
|
|
238
|
-
* Ported from agentbox/packages/openclaw-x402/src/wallet.ts
|
|
239
|
-
*/
|
|
240
|
-
function generateMnemonic$1() {
|
|
241
|
-
return generateMnemonic(wordlist, 256);
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* SLIP-10 Ed25519 derivation at m/44'/501'/0'/0' (Phantom/Backpack compatible).
|
|
245
|
-
*/
|
|
246
|
-
const enc = new TextEncoder();
|
|
247
|
-
function deriveSolanaKeypair(mnemonic) {
|
|
248
|
-
const seed = mnemonicToSeedSync(mnemonic);
|
|
249
|
-
let I = hmac(sha512, enc.encode("ed25519 seed"), seed);
|
|
250
|
-
let key = I.slice(0, 32);
|
|
251
|
-
let chainCode = I.slice(32);
|
|
252
|
-
for (const index of [
|
|
253
|
-
2147483692,
|
|
254
|
-
2147484149,
|
|
255
|
-
2147483648,
|
|
256
|
-
2147483648
|
|
257
|
-
]) {
|
|
258
|
-
const data = new Uint8Array(37);
|
|
259
|
-
data[0] = 0;
|
|
260
|
-
data.set(key, 1);
|
|
261
|
-
data[33] = index >>> 24 & 255;
|
|
262
|
-
data[34] = index >>> 16 & 255;
|
|
263
|
-
data[35] = index >>> 8 & 255;
|
|
264
|
-
data[36] = index & 255;
|
|
265
|
-
I = hmac(sha512, chainCode, data);
|
|
266
|
-
key = I.slice(0, 32);
|
|
267
|
-
chainCode = I.slice(32);
|
|
268
|
-
}
|
|
269
|
-
const secretKey = new Uint8Array(key);
|
|
270
|
-
const publicKey = ed25519.getPublicKey(secretKey);
|
|
271
|
-
return {
|
|
272
|
-
secretKey,
|
|
273
|
-
publicKey,
|
|
274
|
-
address: base58.encode(publicKey)
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
/**
|
|
278
|
-
* BIP-32 secp256k1 derivation at m/44'/60'/0'/0/0.
|
|
279
|
-
*/
|
|
280
|
-
function deriveEvmKeypair(mnemonic) {
|
|
281
|
-
const seed = mnemonicToSeedSync(mnemonic);
|
|
282
|
-
const derived = HDKey.fromMasterSeed(seed).derive("m/44'/60'/0'/0/0");
|
|
283
|
-
if (!derived.privateKey) throw new Error("Failed to derive EVM private key");
|
|
284
|
-
const privateKey = `0x${Buffer.from(derived.privateKey).toString("hex")}`;
|
|
285
|
-
const hash = keccak_256(secp256k1.getPublicKey(derived.privateKey, false).slice(1));
|
|
286
|
-
return {
|
|
287
|
-
privateKey,
|
|
288
|
-
address: checksumAddress(Buffer.from(hash.slice(-20)).toString("hex"))
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
function checksumAddress(addr) {
|
|
292
|
-
const hash = Buffer.from(keccak_256(enc.encode(addr))).toString("hex");
|
|
293
|
-
let out = "0x";
|
|
294
|
-
for (let i = 0; i < 40; i++) out += Number.parseInt(hash[i], 16) >= 8 ? addr[i].toUpperCase() : addr[i];
|
|
295
|
-
return out;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
151
|
//#endregion
|
|
299
152
|
//#region src/lib/resolve-wallet.ts
|
|
300
153
|
/**
|
|
@@ -312,7 +165,10 @@ function resolveWallet(opts) {
|
|
|
312
165
|
result.evmKey = hex;
|
|
313
166
|
result.evmAddress = privateKeyToAccount(hex).address;
|
|
314
167
|
}
|
|
315
|
-
if (opts.solanaKey)
|
|
168
|
+
if (opts.solanaKey) {
|
|
169
|
+
result.solanaKey = parsesolanaKey(opts.solanaKey);
|
|
170
|
+
result.solanaAddress = solanaAddressFromKey(result.solanaKey);
|
|
171
|
+
}
|
|
316
172
|
return result;
|
|
317
173
|
}
|
|
318
174
|
const envEvm = process.env.X402_PROXY_WALLET_EVM_KEY;
|
|
@@ -324,7 +180,10 @@ function resolveWallet(opts) {
|
|
|
324
180
|
result.evmKey = hex;
|
|
325
181
|
result.evmAddress = privateKeyToAccount(hex).address;
|
|
326
182
|
}
|
|
327
|
-
if (envSol)
|
|
183
|
+
if (envSol) {
|
|
184
|
+
result.solanaKey = parsesolanaKey(envSol);
|
|
185
|
+
result.solanaAddress = solanaAddressFromKey(result.solanaKey);
|
|
186
|
+
}
|
|
328
187
|
return result;
|
|
329
188
|
}
|
|
330
189
|
const envMnemonic = process.env.X402_PROXY_WALLET_MNEMONIC;
|
|
@@ -355,6 +214,10 @@ function parsesolanaKey(input) {
|
|
|
355
214
|
}
|
|
356
215
|
return base58.decode(trimmed);
|
|
357
216
|
}
|
|
217
|
+
function solanaAddressFromKey(keyBytes) {
|
|
218
|
+
if (keyBytes.length >= 64) return base58.encode(keyBytes.slice(32));
|
|
219
|
+
return base58.encode(ed25519.getPublicKey(keyBytes));
|
|
220
|
+
}
|
|
358
221
|
function networkToCaipPrefix(name) {
|
|
359
222
|
switch (name.toLowerCase()) {
|
|
360
223
|
case "base": return "eip155:8453";
|
|
@@ -374,17 +237,25 @@ async function buildX402Client(wallet, opts) {
|
|
|
374
237
|
})() : void 0);
|
|
375
238
|
if (wallet.evmKey) {
|
|
376
239
|
const hex = wallet.evmKey;
|
|
377
|
-
|
|
240
|
+
registerExactEvmScheme(client, { signer: toClientEvmSigner(privateKeyToAccount(hex), createPublicClient({
|
|
378
241
|
chain: base,
|
|
379
242
|
transport: http()
|
|
380
|
-
}));
|
|
381
|
-
client.register("eip155:8453", new ExactEvmScheme(signer));
|
|
243
|
+
})) });
|
|
382
244
|
}
|
|
383
245
|
if (wallet.solanaKey) {
|
|
384
246
|
const { createKeyPairSignerFromBytes } = await import("@solana/kit");
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
247
|
+
registerExactSvmScheme(client, { signer: await createKeyPairSignerFromBytes(wallet.solanaKey) });
|
|
248
|
+
}
|
|
249
|
+
if (opts?.network) {
|
|
250
|
+
const prefix = networkToCaipPrefix(opts.network);
|
|
251
|
+
client.registerPolicy((_version, reqs) => {
|
|
252
|
+
const filtered = reqs.filter((r) => r.network.startsWith(prefix));
|
|
253
|
+
if (filtered.length === 0) {
|
|
254
|
+
const available = [...new Set(reqs.map((r) => displayNetwork(r.network)))].join(", ");
|
|
255
|
+
throw new Error(`Network '${opts.network}' not accepted. Available: ${available}`);
|
|
256
|
+
}
|
|
257
|
+
return filtered;
|
|
258
|
+
});
|
|
388
259
|
}
|
|
389
260
|
const daily = opts?.spendLimitDaily;
|
|
390
261
|
const perTx = opts?.spendLimitPerTx;
|
|
@@ -432,7 +303,7 @@ async function fetchEvmBalances(address) {
|
|
|
432
303
|
}, "latest"])]);
|
|
433
304
|
return {
|
|
434
305
|
eth: ethRes.result ? (Number(BigInt(ethRes.result)) / 0xde0b6b3a7640000).toFixed(6) : "?",
|
|
435
|
-
usdc: usdcRes.result ? (Number(BigInt(usdcRes.result)) / 1e6).toFixed(
|
|
306
|
+
usdc: usdcRes.result ? (Number(BigInt(usdcRes.result)) / 1e6).toFixed(4) : "?"
|
|
436
307
|
};
|
|
437
308
|
}
|
|
438
309
|
async function fetchSolanaBalances(address) {
|
|
@@ -445,7 +316,7 @@ async function fetchSolanaBalances(address) {
|
|
|
445
316
|
const accounts = usdcRes.result?.value;
|
|
446
317
|
return {
|
|
447
318
|
sol,
|
|
448
|
-
usdc: accounts?.length ? Number(accounts[0].account.data.parsed.info.tokenAmount.uiAmountString).toFixed(
|
|
319
|
+
usdc: accounts?.length ? Number(accounts[0].account.data.parsed.info.tokenAmount.uiAmountString).toFixed(4) : "0.0000"
|
|
449
320
|
};
|
|
450
321
|
}
|
|
451
322
|
function balanceLine(usdc, native, nativeSymbol) {
|
|
@@ -468,8 +339,7 @@ const walletInfoCommand = buildCommand({
|
|
|
468
339
|
const wallet = resolveWallet();
|
|
469
340
|
if (wallet.source === "none") {
|
|
470
341
|
console.log(pc.yellow("No wallet configured."));
|
|
471
|
-
console.log(pc.dim(
|
|
472
|
-
console.log(pc.dim(`Or set ${pc.cyan("X402_PROXY_WALLET_MNEMONIC")} environment variable.`));
|
|
342
|
+
console.log(pc.dim(`\nRun:\n ${pc.cyan("$ npx x402-proxy setup")}\n\nOr set ${pc.cyan("X402_PROXY_WALLET_MNEMONIC")} environment variable.`));
|
|
473
343
|
process.exit(1);
|
|
474
344
|
}
|
|
475
345
|
console.log();
|
|
@@ -487,6 +357,12 @@ const walletInfoCommand = buildCommand({
|
|
|
487
357
|
const bal = sol ? balanceLine(sol.usdc, sol.sol, "SOL") : pc.dim(" (network error)");
|
|
488
358
|
console.log(` Solana: ${pc.green(wallet.solanaAddress)}${bal}`);
|
|
489
359
|
}
|
|
360
|
+
const evmEmpty = !evm || evm.usdc === "0.0000";
|
|
361
|
+
const solEmpty = !sol || sol.usdc === "0.0000";
|
|
362
|
+
if (evmEmpty && solEmpty) {
|
|
363
|
+
console.log();
|
|
364
|
+
dim(" Send USDC to either address above to start using x402 APIs.");
|
|
365
|
+
}
|
|
490
366
|
console.log();
|
|
491
367
|
const records = readHistory(getHistoryPath());
|
|
492
368
|
if (records.length > 0) {
|
|
@@ -501,75 +377,10 @@ const walletInfoCommand = buildCommand({
|
|
|
501
377
|
console.log(pc.dim(` Today: ${spend.today.toFixed(4)} USDC | Total: ${spend.total.toFixed(4)} USDC | ${spend.count} tx`));
|
|
502
378
|
} else dim(" No transactions yet.");
|
|
503
379
|
console.log();
|
|
504
|
-
console.log(pc.dim(" See also: wallet history, wallet
|
|
505
|
-
console.log();
|
|
506
|
-
}
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
//#endregion
|
|
510
|
-
//#region src/commands/status.ts
|
|
511
|
-
async function displayStatus() {
|
|
512
|
-
const wallet = resolveWallet();
|
|
513
|
-
const config = loadConfig();
|
|
514
|
-
const records = readHistory(getHistoryPath());
|
|
515
|
-
const spend = calcSpend(records);
|
|
516
|
-
console.log();
|
|
517
|
-
console.log(pc.cyan(pc.bold("x402-proxy")));
|
|
518
|
-
console.log(pc.dim("curl for x402 paid APIs"));
|
|
519
|
-
console.log();
|
|
520
|
-
if (wallet.source === "none") {
|
|
521
|
-
console.log(pc.yellow(" No wallet configured."));
|
|
522
|
-
console.log(pc.dim(` Run ${pc.cyan("$ npx x402-proxy setup")} to create one.`));
|
|
523
|
-
} else {
|
|
524
|
-
const [evmResult, solResult] = await Promise.allSettled([wallet.evmAddress ? fetchEvmBalances(wallet.evmAddress) : Promise.resolve(null), wallet.solanaAddress ? fetchSolanaBalances(wallet.solanaAddress) : Promise.resolve(null)]);
|
|
525
|
-
const evm = evmResult.status === "fulfilled" ? evmResult.value : null;
|
|
526
|
-
const sol = solResult.status === "fulfilled" ? solResult.value : null;
|
|
527
|
-
if (wallet.evmAddress) {
|
|
528
|
-
const bal = evm ? balanceLine(evm.usdc, evm.eth, "ETH") : pc.dim(" (network error)");
|
|
529
|
-
console.log(` Base: ${pc.green(wallet.evmAddress)}${bal}`);
|
|
530
|
-
}
|
|
531
|
-
if (wallet.solanaAddress) {
|
|
532
|
-
const bal = sol ? balanceLine(sol.usdc, sol.sol, "SOL") : pc.dim(" (network error)");
|
|
533
|
-
console.log(` Solana: ${pc.green(wallet.solanaAddress)}${bal}`);
|
|
534
|
-
}
|
|
535
|
-
if (config?.spendLimitDaily || config?.spendLimitPerTx) {
|
|
536
|
-
console.log();
|
|
537
|
-
if (config.spendLimitDaily) {
|
|
538
|
-
const pct = config.spendLimitDaily > 0 ? Math.round(spend.today / config.spendLimitDaily * 100) : 0;
|
|
539
|
-
dim(` Daily limit: ${spend.today.toFixed(4)} / ${config.spendLimitDaily} USDC (${pct}%)`);
|
|
540
|
-
}
|
|
541
|
-
if (config.spendLimitPerTx) dim(` Per-tx limit: ${config.spendLimitPerTx} USDC`);
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
console.log();
|
|
545
|
-
if (spend.count > 0) {
|
|
546
|
-
const recent = records.slice(-5);
|
|
547
|
-
dim(" Recent transactions:");
|
|
548
|
-
for (const r of recent) {
|
|
549
|
-
const line = formatTxLine(r).replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
550
|
-
console.log(line);
|
|
551
|
-
}
|
|
552
|
-
console.log();
|
|
553
|
-
dim(` Today: ${spend.today.toFixed(4)} USDC | Total: ${spend.total.toFixed(4)} USDC | ${spend.count} tx`);
|
|
554
|
-
} else dim(" No payment history yet.");
|
|
555
|
-
console.log();
|
|
556
|
-
if (config?.defaultNetwork) dim(` Network: ${config.defaultNetwork}`);
|
|
557
|
-
dim(` Config: ${getConfigDirShort()}`);
|
|
558
|
-
}
|
|
559
|
-
const statusCommand = buildCommand({
|
|
560
|
-
docs: { brief: "Show configuration and wallet status" },
|
|
561
|
-
parameters: {
|
|
562
|
-
flags: {},
|
|
563
|
-
positional: {
|
|
564
|
-
kind: "tuple",
|
|
565
|
-
parameters: []
|
|
566
|
-
}
|
|
567
|
-
},
|
|
568
|
-
async func() {
|
|
569
|
-
await displayStatus();
|
|
380
|
+
console.log(pc.dim(" See also: wallet history, wallet export-key"));
|
|
570
381
|
console.log();
|
|
571
382
|
}
|
|
572
383
|
});
|
|
573
384
|
|
|
574
385
|
//#endregion
|
|
575
|
-
export {
|
|
386
|
+
export { buildX402Client as a, error as c, warn as d, appendHistory as f, readHistory as g, formatTxLine as h, walletInfoCommand as i, info as l, displayNetwork as m, fetchEvmBalances as n, resolveWallet as o, calcSpend as p, fetchSolanaBalances as r, dim as s, balanceLine as t, isTTY as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "x402-proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "curl for x402 paid APIs. Auto-pays any endpoint on Base and Solana.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
"@types/node": "^22.0.0",
|
|
42
42
|
"publint": "^0.3.17",
|
|
43
43
|
"tsdown": "^0.20.3",
|
|
44
|
-
"typescript": "^5.9.0"
|
|
44
|
+
"typescript": "^5.9.0",
|
|
45
|
+
"vitest": "^4.0.18"
|
|
45
46
|
},
|
|
46
47
|
"files": [
|
|
47
48
|
"dist/**",
|
|
@@ -78,6 +79,7 @@
|
|
|
78
79
|
"build": "rm -rf dist && tsdown --publint",
|
|
79
80
|
"dev": "tsdown --watch",
|
|
80
81
|
"type-check": "tsc --noEmit",
|
|
82
|
+
"test": "vitest run",
|
|
81
83
|
"check": "pnpm type-check && biome check --write"
|
|
82
84
|
}
|
|
83
85
|
}
|