x402-proxy 0.2.0 → 0.3.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 CHANGED
@@ -1,54 +1,27 @@
1
1
  # x402-proxy
2
2
 
3
- CLI and library for paying [x402](https://www.x402.org/) resources. Auto-pays HTTP 402 responses with USDC on Base or Solana.
3
+ `curl` for [x402](https://www.x402.org/) paid APIs. Auto-pays HTTP 402 responses with USDC on Base and Solana - zero crypto code on the buyer side.
4
4
 
5
5
  ## Quick Start
6
6
 
7
7
  ```bash
8
- npx x402-proxy setup # generate wallet from BIP-39 mnemonic
9
- npx x402-proxy wallet fund # see where to send USDC
10
- npx x402-proxy https://example.com # make a paid request
8
+ npx x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi
11
9
  ```
12
10
 
13
- **Done.** Your wallet derives both EVM (Base) and Solana keypairs from a single mnemonic. Fund either chain and start paying for x402 resources.
11
+ That's it. The endpoint returns 402, x402-proxy pays and streams the response.
14
12
 
15
- ## Commands
13
+ First time? Set up a wallet:
16
14
 
17
15
  ```bash
18
- x402-proxy <url> # paid HTTP request (default command)
19
- x402-proxy mcp <url> # MCP stdio proxy for agents (alpha)
20
- x402-proxy setup # onboarding wizard
21
- x402-proxy status # config + wallet + spend summary
22
- x402-proxy wallet # show addresses
23
- x402-proxy wallet history # payment history
24
- x402-proxy wallet fund # funding instructions
25
- x402-proxy wallet export-key <chain> # bare key to stdout (evm|solana)
16
+ npx x402-proxy setup # generate wallet from BIP-39 mnemonic
17
+ npx x402-proxy wallet fund # see where to send USDC
26
18
  ```
27
19
 
28
- All commands support `--help` for details.
20
+ One mnemonic derives both EVM (Base) and Solana keypairs. Fund either chain and go.
29
21
 
30
- ## Fetch (HTTP Client)
22
+ ## MCP Proxy
31
23
 
32
- ```bash
33
- # GET request
34
- x402-proxy https://twitter.surf.cascade.fyi/search?q=x402
35
-
36
- # POST with body and headers
37
- x402-proxy --method POST \
38
- --header "Content-Type: application/json" \
39
- --body '{"url":"https://x402.org"}' \
40
- https://web.surf.cascade.fyi/v1/crawl
41
- ```
42
-
43
- Response body streams to stdout, payment info goes to stderr. Pipe-safe:
44
-
45
- ```bash
46
- x402-proxy https://api.example.com/data | jq '.results'
47
- ```
48
-
49
- ## MCP Proxy (Alpha)
50
-
51
- Wraps a remote MCP server with automatic x402 payment. Configure in your MCP client:
24
+ Let your AI agent consume any paid MCP server. Configure in Claude, Cursor, or any MCP client:
52
25
 
53
26
  ```json
54
27
  {
@@ -64,6 +37,41 @@ Wraps a remote MCP server with automatic x402 payment. Configure in your MCP cli
64
37
  }
65
38
  ```
66
39
 
40
+ The proxy sits between your agent and the remote server, intercepting 402 responses, paying automatically, and forwarding the result. Your agent never touches crypto.
41
+
42
+ ## HTTP Requests
43
+
44
+ Works like curl. Response body streams to stdout, payment info goes to stderr.
45
+
46
+ ```bash
47
+ # GET request
48
+ x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi
49
+
50
+ # POST with body and headers
51
+ x402-proxy --method POST \
52
+ --header "Content-Type: application/json" \
53
+ --body '{"url":"https://x402.org"}' \
54
+ https://web.surf.cascade.fyi/v1/crawl
55
+
56
+ # Pipe-safe
57
+ x402-proxy https://api.example.com/data | jq '.results'
58
+ ```
59
+
60
+ ## Commands
61
+
62
+ ```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 fund # funding instructions
70
+ x402-proxy wallet export-key <chain> # bare key to stdout (evm|solana)
71
+ ```
72
+
73
+ All commands support `--help` for details.
74
+
67
75
  ## Wallet
68
76
 
69
77
  A single BIP-39 mnemonic derives both chains:
package/dist/bin/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { S as saveWalletFile, _ as getConfigDir, a as readHistory, b as isConfigured, c as info, d as buildX402Client, f as resolveWallet, g as ensureConfigDir, h as generateMnemonic, i as formatTxLine, l as isTTY, m as deriveSolanaKeypair, n as appendHistory, o as dim, p as deriveEvmKeypair, r as calcSpend, s as error, t as statusCommand, u as warn, v as getHistoryPath, x as saveConfig, y as getWalletPath } from "../status-DeCY-cLR.js";
2
+ import { C as calcSpend, S as appendHistory, T as readHistory, _ as getWalletPath, a as resolveWallet, b as saveConfig, c as generateMnemonic, d as info, f as isTTY, g as getHistoryPath, h as getConfigDirShort, i as buildX402Client, l as dim, m as ensureConfigDir, n as statusCommand, o as deriveEvmKeypair, p as warn, r as walletInfoCommand, s as deriveSolanaKeypair, u as error, v as isConfigured, w as formatTxLine, x as saveWalletFile, y as loadConfig } from "../status-DRSZjSq9.js";
3
3
  import { buildApplication, buildCommand, buildRouteMap, run } from "@stricli/core";
4
4
  import pc from "picocolors";
5
5
  import { decodePaymentResponseHeader, wrapFetchWithPayment } from "@x402/fetch";
@@ -49,15 +49,22 @@ function createX402ProxyHandler(opts) {
49
49
  //#endregion
50
50
  //#region src/commands/fetch.ts
51
51
  const fetchCommand = buildCommand({
52
- docs: { brief: "Make a paid HTTP request (default command)" },
52
+ docs: {
53
+ brief: "Make a paid HTTP request (default command)",
54
+ fullDescription: `Make a paid HTTP request. Payment is automatic when the server returns 402.
55
+
56
+ Examples:
57
+ $ x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi
58
+ $ x402-proxy -X POST -d '{"url":"https://x402.org"}' https://web.surf.cascade.fyi/v1/crawl
59
+ $ x402-proxy https://api.example.com/data | jq '.results'`
60
+ },
53
61
  parameters: {
54
62
  flags: {
55
63
  method: {
56
64
  kind: "parsed",
57
65
  brief: "HTTP method",
58
66
  parse: String,
59
- default: "GET",
60
- optional: true
67
+ default: "GET"
61
68
  },
62
69
  body: {
63
70
  kind: "parsed",
@@ -102,18 +109,38 @@ const fetchCommand = buildCommand({
102
109
  async func(flags, url) {
103
110
  if (!url) {
104
111
  if (isConfigured()) {
105
- const { statusCommand } = await import("../status-CPBQ0UBZ.js");
106
- statusCommand.func.call(this, {});
112
+ const { displayStatus } = await import("../status-BFDDA6oN.js");
113
+ await displayStatus();
114
+ console.log();
115
+ console.log(pc.dim(" Commands:"));
116
+ console.log(` ${pc.cyan("$ npx x402-proxy <url>")} Fetch a paid API`);
117
+ console.log(` ${pc.cyan("$ npx x402-proxy mcp <url>")} MCP proxy for AI agents`);
118
+ console.log(` ${pc.cyan("$ npx x402-proxy setup")} Reconfigure wallet`);
119
+ console.log(` ${pc.cyan("$ npx x402-proxy wallet")} Addresses and balances`);
120
+ 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
+ console.log();
123
+ console.log(pc.dim(" try: ") + pc.cyan("$ npx x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi"));
124
+ console.log();
125
+ console.log(pc.dim(" https://github.com/cascade-protocol/x402-proxy"));
126
+ console.log();
107
127
  } else {
108
128
  console.log();
109
- console.log(pc.cyan("x402-proxy") + pc.dim(" - pay for any x402 resource"));
129
+ console.log(pc.cyan(pc.bold("x402-proxy")));
130
+ console.log(pc.dim("curl for x402 paid APIs"));
131
+ console.log();
132
+ console.log(pc.dim(" Commands:"));
133
+ console.log(` ${pc.cyan("$ npx x402-proxy setup")} Create a wallet`);
134
+ console.log(` ${pc.cyan("$ npx x402-proxy <url>")} Fetch a paid API`);
135
+ console.log(` ${pc.cyan("$ npx x402-proxy mcp <url>")} MCP proxy for AI agents`);
136
+ console.log(` ${pc.cyan("$ npx x402-proxy wallet")} Addresses and balances`);
137
+ console.log(` ${pc.cyan("$ npx x402-proxy wallet history")} Payment history`);
138
+ console.log(` ${pc.cyan("$ npx x402-proxy wallet fund")} Funding instructions`);
139
+ console.log(` ${pc.cyan("$ npx x402-proxy --help")} All options`);
110
140
  console.log();
111
- console.log(pc.dim(" Get started:"));
112
- console.log(` ${pc.cyan("x402-proxy setup")} Create a wallet`);
113
- console.log(` ${pc.cyan("x402-proxy <url>")} Make a paid request`);
114
- console.log(` ${pc.cyan("x402-proxy mcp <url>")} MCP proxy for agents`);
115
- console.log(` ${pc.cyan("x402-proxy wallet")} Wallet info`);
116
- console.log(` ${pc.cyan("x402-proxy --help")} All commands`);
141
+ console.log(pc.dim(" try: ") + pc.cyan("$ npx x402-proxy setup"));
142
+ console.log();
143
+ console.log(pc.dim(" https://github.com/cascade-protocol/x402-proxy"));
117
144
  console.log();
118
145
  }
119
146
  return;
@@ -134,7 +161,12 @@ const fetchCommand = buildCommand({
134
161
  console.error(pc.dim(`Run ${pc.cyan("x402-proxy setup")} or set X402_PROXY_WALLET_MNEMONIC`));
135
162
  process.exit(1);
136
163
  }
137
- const { x402Fetch, shiftPayment } = createX402ProxyHandler({ client: await buildX402Client(wallet) });
164
+ const config = loadConfig();
165
+ const { x402Fetch, shiftPayment } = createX402ProxyHandler({ client: await buildX402Client(wallet, {
166
+ preferredNetwork: config?.defaultNetwork,
167
+ spendLimitDaily: config?.spendLimitDaily,
168
+ spendLimitPerTx: config?.spendLimitPerTx
169
+ }) });
138
170
  const headers = new Headers();
139
171
  if (flags.header) for (const h of flags.header) {
140
172
  const idx = h.indexOf(":");
@@ -199,7 +231,15 @@ const fetchCommand = buildCommand({
199
231
  //#endregion
200
232
  //#region src/commands/mcp.ts
201
233
  const mcpCommand = buildCommand({
202
- docs: { brief: "Start MCP stdio proxy with x402 payment (alpha)" },
234
+ docs: {
235
+ brief: "Start MCP stdio proxy with x402 payment (alpha)",
236
+ fullDescription: `Start an MCP stdio proxy with automatic x402 payment for AI agents.
237
+
238
+ Add to your MCP client config (Claude, Cursor, etc.):
239
+ "command": "npx",
240
+ "args": ["x402-proxy", "mcp", "https://mcp.example.com/sse"],
241
+ "env": { "X402_PROXY_WALLET_MNEMONIC": "your 24 words" }`
242
+ },
203
243
  parameters: {
204
244
  flags: {
205
245
  evmKey: {
@@ -236,7 +276,12 @@ const mcpCommand = buildCommand({
236
276
  dim(`x402-proxy MCP proxy -> ${remoteUrl}`);
237
277
  if (wallet.evmAddress) dim(` EVM: ${wallet.evmAddress}`);
238
278
  if (wallet.solanaAddress) dim(` Solana: ${wallet.solanaAddress}`);
239
- const x402PaymentClient = await buildX402Client(wallet);
279
+ const config = loadConfig();
280
+ const x402PaymentClient = await buildX402Client(wallet, {
281
+ preferredNetwork: config?.defaultNetwork,
282
+ spendLimitDaily: config?.spendLimitDaily,
283
+ spendLimitPerTx: config?.spendLimitPerTx
284
+ });
240
285
  const { Client } = await import("@modelcontextprotocol/sdk/client/index.js");
241
286
  const { SSEClientTransport } = await import("@modelcontextprotocol/sdk/client/sse.js");
242
287
  const { StreamableHTTPClientTransport } = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
@@ -245,12 +290,12 @@ const mcpCommand = buildCommand({
245
290
  const { x402MCPClient } = await import("@x402/mcp");
246
291
  const x402Mcp = new x402MCPClient(new Client({
247
292
  name: "x402-proxy",
248
- version: "0.2.0"
293
+ version: "0.3.0"
249
294
  }), x402PaymentClient, {
250
295
  autoPayment: true,
251
296
  onPaymentRequested: (ctx) => {
252
297
  const accept = ctx.paymentRequired.accepts?.[0];
253
- if (accept) warn(` Payment: ${accept.maxAmountRequired ?? accept.amount ?? "?"} on ${accept.network} for tool "${ctx.toolName}"`);
298
+ if (accept) warn(` Payment: ${accept.amount} on ${accept.network} for tool "${ctx.toolName}"`);
254
299
  return true;
255
300
  }
256
301
  });
@@ -290,7 +335,7 @@ const mcpCommand = buildCommand({
290
335
  dim(` ${tools.length} tools available`);
291
336
  const localServer = new McpServer({
292
337
  name: "x402-proxy",
293
- version: "0.2.0"
338
+ version: "0.3.0"
294
339
  });
295
340
  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) => {
296
341
  const result = await x402Mcp.callTool(tool.name, args);
@@ -365,7 +410,7 @@ const setupCommand = buildCommand({
365
410
  } else {
366
411
  const input = await prompts.text({
367
412
  message: "Enter your 24-word mnemonic:",
368
- validate: (v) => {
413
+ validate: (v = "") => {
369
414
  const words = v.trim().split(/\s+/);
370
415
  if (words.length !== 12 && words.length !== 24) return "Mnemonic must be 12 or 24 words";
371
416
  }
@@ -378,7 +423,7 @@ const setupCommand = buildCommand({
378
423
  }
379
424
  const evm = deriveEvmKeypair(mnemonic);
380
425
  const sol = deriveSolanaKeypair(mnemonic);
381
- prompts.log.success(`EVM address: ${pc.green(evm.address)}`);
426
+ prompts.log.success(`Base address: ${pc.green(evm.address)}`);
382
427
  prompts.log.success(`Solana address: ${pc.green(sol.address)}`);
383
428
  saveWalletFile({
384
429
  version: 1,
@@ -389,18 +434,61 @@ const setupCommand = buildCommand({
389
434
  }
390
435
  });
391
436
  saveConfig({});
392
- prompts.log.info(`Config directory: ${pc.dim(getConfigDir())}`);
437
+ prompts.log.info(`Config directory: ${pc.dim(getConfigDirShort())}`);
393
438
  prompts.log.step("Fund your wallets to start using x402 resources:");
394
439
  prompts.log.message(` Solana (USDC): Send USDC to ${pc.cyan(sol.address)}`);
395
- prompts.log.message(` EVM (USDC): Send USDC to ${pc.cyan(evm.address)} on Base`);
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")}`);
396
443
  prompts.outro(pc.green("Setup complete!"));
397
444
  }
398
445
  });
399
446
 
400
447
  //#endregion
401
- //#region src/commands/wallet.ts
402
- const walletInfoCommand = buildCommand({
403
- docs: { brief: "Show wallet addresses" },
448
+ //#region src/commands/wallet-export.ts
449
+ const walletExportCommand = buildCommand({
450
+ docs: { brief: "Export private key to stdout (pipe-safe)" },
451
+ parameters: {
452
+ flags: {},
453
+ positional: {
454
+ kind: "tuple",
455
+ parameters: [{
456
+ brief: "Chain to export: evm or solana",
457
+ parse: (input) => {
458
+ const v = input.toLowerCase();
459
+ if (v !== "evm" && v !== "solana") throw new Error("Must be 'evm' or 'solana'");
460
+ return v;
461
+ }
462
+ }]
463
+ }
464
+ },
465
+ async func(flags, chain) {
466
+ const wallet = resolveWallet();
467
+ if (wallet.source === "none") {
468
+ error("No wallet configured.");
469
+ process.exit(1);
470
+ }
471
+ if (chain === "evm" && !wallet.evmKey) {
472
+ error("No EVM key available.");
473
+ process.exit(1);
474
+ }
475
+ if (chain === "solana" && !wallet.solanaKey) {
476
+ error("No Solana key available.");
477
+ process.exit(1);
478
+ }
479
+ if (process.stdout.isTTY) {
480
+ const confirmed = await prompts.confirm({ message: "This will print your private key to the terminal. Continue?" });
481
+ if (prompts.isCancel(confirmed) || !confirmed) process.exit(0);
482
+ } else warn("Warning: private key will be printed to stdout.");
483
+ if (chain === "evm") process.stdout.write(wallet.evmKey);
484
+ else process.stdout.write(base58.encode(wallet.solanaKey.slice(0, 32)));
485
+ }
486
+ });
487
+
488
+ //#endregion
489
+ //#region src/commands/wallet-fund.ts
490
+ const walletFundCommand = buildCommand({
491
+ docs: { brief: "Show wallet funding instructions" },
404
492
  parameters: {
405
493
  flags: {},
406
494
  positional: {
@@ -413,15 +501,24 @@ const walletInfoCommand = buildCommand({
413
501
  if (wallet.source === "none") {
414
502
  console.log(pc.yellow("No wallet configured."));
415
503
  console.log(pc.dim(`Run ${pc.cyan("x402-proxy setup")} to create one.`));
416
- console.log(pc.dim(`Or set ${pc.cyan("X402_PROXY_WALLET_MNEMONIC")} environment variable.`));
417
504
  process.exit(1);
418
505
  }
419
506
  console.log();
420
- info("Wallet");
507
+ info("Funding Instructions");
421
508
  console.log();
422
- console.log(pc.dim(` Source: ${wallet.source}`));
423
- if (wallet.evmAddress) console.log(` EVM: ${pc.green(wallet.evmAddress)}`);
424
- if (wallet.solanaAddress) console.log(` Solana: ${pc.green(wallet.solanaAddress)}`);
509
+ if (wallet.solanaAddress) {
510
+ console.log(pc.bold(" Solana (USDC):"));
511
+ console.log(` Send USDC to: ${pc.green(wallet.solanaAddress)}`);
512
+ console.log(pc.dim(" Network: Solana Mainnet"));
513
+ console.log();
514
+ }
515
+ if (wallet.evmAddress) {
516
+ console.log(pc.bold(" Base (USDC):"));
517
+ console.log(` Send USDC to: ${pc.green(wallet.evmAddress)}`);
518
+ console.log(pc.dim(" Network: Base (Chain ID 8453)"));
519
+ console.log();
520
+ }
521
+ console.log(pc.dim(" Tip: Most x402 services accept USDC on Base or Solana."));
425
522
  console.log();
426
523
  }
427
524
  });
@@ -475,86 +572,6 @@ const walletHistoryCommand = buildCommand({
475
572
  }
476
573
  });
477
574
 
478
- //#endregion
479
- //#region src/commands/wallet-fund.ts
480
- const walletFundCommand = buildCommand({
481
- docs: { brief: "Show wallet funding instructions" },
482
- parameters: {
483
- flags: {},
484
- positional: {
485
- kind: "tuple",
486
- parameters: []
487
- }
488
- },
489
- func() {
490
- const wallet = resolveWallet();
491
- if (wallet.source === "none") {
492
- console.log(pc.yellow("No wallet configured."));
493
- console.log(pc.dim(`Run ${pc.cyan("x402-proxy setup")} to create one.`));
494
- process.exit(1);
495
- }
496
- console.log();
497
- info("Funding Instructions");
498
- console.log();
499
- if (wallet.solanaAddress) {
500
- console.log(pc.bold(" Solana (USDC):"));
501
- console.log(` Send USDC to: ${pc.green(wallet.solanaAddress)}`);
502
- console.log(pc.dim(" Network: Solana Mainnet"));
503
- console.log();
504
- }
505
- if (wallet.evmAddress) {
506
- console.log(pc.bold(" Base (USDC):"));
507
- console.log(` Send USDC to: ${pc.green(wallet.evmAddress)}`);
508
- console.log(pc.dim(" Network: Base (Chain ID 8453)"));
509
- console.log();
510
- }
511
- console.log(pc.dim(" Tip: Most x402 services accept USDC on Base or Solana."));
512
- console.log();
513
- }
514
- });
515
-
516
- //#endregion
517
- //#region src/commands/wallet-export.ts
518
- const walletExportCommand = buildCommand({
519
- docs: { brief: "Export private key to stdout (pipe-safe)" },
520
- parameters: {
521
- flags: {},
522
- positional: {
523
- kind: "tuple",
524
- parameters: [{
525
- brief: "Chain to export: evm or solana",
526
- parse: (input) => {
527
- const v = input.toLowerCase();
528
- if (v !== "evm" && v !== "solana") throw new Error("Must be 'evm' or 'solana'");
529
- return v;
530
- }
531
- }]
532
- }
533
- },
534
- func(_flags, chain) {
535
- const wallet = resolveWallet();
536
- if (wallet.source === "none") {
537
- error("No wallet configured.");
538
- process.exit(1);
539
- }
540
- if (chain === "evm") {
541
- if (!wallet.evmKey) {
542
- error("No EVM key available.");
543
- process.exit(1);
544
- }
545
- warn("Warning: private key will be printed to stdout.");
546
- process.stdout.write(wallet.evmKey);
547
- } else {
548
- if (!wallet.solanaKey) {
549
- error("No Solana key available.");
550
- process.exit(1);
551
- }
552
- warn("Warning: private key will be printed to stdout.");
553
- process.stdout.write(base58.encode(wallet.solanaKey.slice(0, 32)));
554
- }
555
- }
556
- });
557
-
558
575
  //#endregion
559
576
  //#region src/app.ts
560
577
  const routes = buildRouteMap({
@@ -575,11 +592,11 @@ const routes = buildRouteMap({
575
592
  status: statusCommand
576
593
  },
577
594
  defaultCommand: "fetch",
578
- docs: { brief: "x402 payment proxy - pay for any x402 resource" }
595
+ docs: { brief: "curl for x402 paid APIs" }
579
596
  });
580
597
  const app = buildApplication(routes, {
581
598
  name: "x402-proxy",
582
- versionInfo: { currentVersion: "0.2.0" },
599
+ versionInfo: { currentVersion: "0.3.0" },
583
600
  scanner: { caseStyle: "allow-kebab-for-camel" }
584
601
  });
585
602
 
package/dist/index.d.ts CHANGED
@@ -66,7 +66,9 @@ declare function calcSpend(records: TxRecord[]): {
66
66
  count: number;
67
67
  };
68
68
  declare function explorerUrl(net: string, tx: string): string;
69
- declare function formatTxLine(r: TxRecord): string;
69
+ declare function formatTxLine(r: TxRecord, opts?: {
70
+ verbose?: boolean;
71
+ }): string;
70
72
  //#endregion
71
73
  //#region src/wallet.d.ts
72
74
  /**
package/dist/index.js CHANGED
@@ -131,7 +131,13 @@ function shortModel(model) {
131
131
  const parts = model.split("/");
132
132
  return parts[parts.length - 1].replace(/-\d{6,8}$/, "").replace(/-\d{4}$/, "");
133
133
  }
134
- function formatTxLine(r) {
134
+ function shortNetwork(net) {
135
+ if (net === "eip155:8453") return "base";
136
+ if (net.startsWith("eip155:")) return `evm:${net.split(":")[1]}`;
137
+ if (net.startsWith("solana:")) return "sol";
138
+ return net;
139
+ }
140
+ function formatTxLine(r, opts) {
135
141
  const time = new Date(r.t).toLocaleTimeString("en-US", {
136
142
  hour: "2-digit",
137
143
  minute: "2-digit",
@@ -143,6 +149,11 @@ function formatTxLine(r) {
143
149
  if (r.label) parts.push(r.label);
144
150
  if (r.ok && r.amount != null && r.token) parts.push(formatAmount(r.amount, r.token));
145
151
  else if (r.ok && r.kind === "sell" && r.meta?.pct != null) parts.push(`${r.meta.pct}%`);
152
+ parts.push(shortNetwork(r.net));
153
+ if (opts?.verbose && r.tx) {
154
+ const short = r.tx.length > 20 ? `${r.tx.slice(0, 10)}...${r.tx.slice(-6)}` : r.tx;
155
+ parts.push(short);
156
+ }
146
157
  return ` ${timeStr} ${r.ok ? "" : "✗ "}${parts.join(" · ")}`;
147
158
  }
148
159
 
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { n as statusCommand, t as displayStatus } from "./status-DRSZjSq9.js";
3
+
4
+ export { displayStatus };
@@ -1,32 +1,150 @@
1
1
  #!/usr/bin/env node
2
2
  import { buildCommand } from "@stricli/core";
3
3
  import pc from "picocolors";
4
+ import { x402Client } from "@x402/fetch";
4
5
  import fs, { appendFileSync, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
5
6
  import os from "node:os";
6
7
  import path from "node:path";
7
8
  import { parse, stringify } from "yaml";
9
+ import { base58 } from "@scure/base";
8
10
  import { ExactEvmScheme, toClientEvmSigner } from "@x402/evm";
9
- import { x402Client } from "@x402/fetch";
10
11
  import { ExactSvmScheme } from "@x402/svm/exact/client";
11
- import { base58 } from "@scure/base";
12
12
  import { createPublicClient, http } from "viem";
13
13
  import { privateKeyToAccount } from "viem/accounts";
14
14
  import { base } from "viem/chains";
15
15
  import { ed25519 } from "@noble/curves/ed25519.js";
16
16
  import { secp256k1 } from "@noble/curves/secp256k1.js";
17
17
  import { hmac } from "@noble/hashes/hmac.js";
18
- import { keccak_256 } from "@noble/hashes/sha3.js";
19
18
  import { sha512 } from "@noble/hashes/sha2.js";
19
+ import { keccak_256 } from "@noble/hashes/sha3.js";
20
20
  import { HDKey } from "@scure/bip32";
21
21
  import { generateMnemonic, mnemonicToSeedSync } from "@scure/bip39";
22
22
  import { wordlist } from "@scure/bip39/wordlists/english.js";
23
23
 
24
+ //#region src/history.ts
25
+ const HISTORY_MAX_LINES = 1e3;
26
+ const HISTORY_KEEP_LINES = 500;
27
+ function appendHistory(historyPath, record) {
28
+ try {
29
+ appendFileSync(historyPath, `${JSON.stringify(record)}\n`);
30
+ if (existsSync(historyPath)) {
31
+ if (statSync(historyPath).size > HISTORY_MAX_LINES * 200) {
32
+ const lines = readFileSync(historyPath, "utf-8").trimEnd().split("\n");
33
+ if (lines.length > HISTORY_MAX_LINES) writeFileSync(historyPath, `${lines.slice(-HISTORY_KEEP_LINES).join("\n")}\n`);
34
+ }
35
+ }
36
+ } catch {}
37
+ }
38
+ function readHistory(historyPath) {
39
+ try {
40
+ if (!existsSync(historyPath)) return [];
41
+ const content = readFileSync(historyPath, "utf-8").trimEnd();
42
+ if (!content) return [];
43
+ return content.split("\n").flatMap((line) => {
44
+ try {
45
+ const parsed = JSON.parse(line);
46
+ if (typeof parsed.t !== "number" || typeof parsed.kind !== "string") return [];
47
+ return [parsed];
48
+ } catch {
49
+ return [];
50
+ }
51
+ });
52
+ } catch {
53
+ return [];
54
+ }
55
+ }
56
+ function calcSpend(records) {
57
+ const todayStart = /* @__PURE__ */ new Date();
58
+ todayStart.setUTCHours(0, 0, 0, 0);
59
+ const todayMs = todayStart.getTime();
60
+ let today = 0;
61
+ let total = 0;
62
+ let count = 0;
63
+ for (const r of records) {
64
+ if (!r.ok || r.amount == null) continue;
65
+ if (r.token !== "USDC") continue;
66
+ total += r.amount;
67
+ count++;
68
+ if (r.t >= todayMs) today += r.amount;
69
+ }
70
+ return {
71
+ today,
72
+ total,
73
+ count
74
+ };
75
+ }
76
+ function formatAmount(amount, token) {
77
+ if (token === "USDC") {
78
+ if (amount >= .01) return `${amount.toFixed(2)} USDC`;
79
+ if (amount >= .001) return `${amount.toFixed(3)} USDC`;
80
+ if (amount >= 1e-4) return `${amount.toFixed(4)} USDC`;
81
+ return `${amount.toFixed(6)} USDC`;
82
+ }
83
+ if (token === "SOL") return `${amount} SOL`;
84
+ return `${amount} ${token}`;
85
+ }
86
+ const KIND_LABELS = {
87
+ x402_inference: "inference",
88
+ x402_payment: "payment",
89
+ transfer: "transfer",
90
+ buy: "buy",
91
+ sell: "sell",
92
+ mint: "mint",
93
+ swap: "swap"
94
+ };
95
+ function explorerUrl(net, tx) {
96
+ if (net.startsWith("eip155:")) {
97
+ if (net.split(":")[1] === "8453") return `https://basescan.org/tx/${tx}`;
98
+ return `https://basescan.org/tx/${tx}`;
99
+ }
100
+ return `https://solscan.io/tx/${tx}`;
101
+ }
102
+ /** Strip provider prefix and OpenRouter date suffixes from model IDs.
103
+ * e.g. "minimax/minimax-m2.5-20260211" -> "minimax-m2.5"
104
+ * "moonshotai/kimi-k2.5-0127" -> "kimi-k2.5" */
105
+ function shortModel(model) {
106
+ const parts = model.split("/");
107
+ return parts[parts.length - 1].replace(/-\d{6,8}$/, "").replace(/-\d{4}$/, "");
108
+ }
109
+ function shortNetwork(net) {
110
+ if (net === "eip155:8453") return "base";
111
+ if (net.startsWith("eip155:")) return `evm:${net.split(":")[1]}`;
112
+ if (net.startsWith("solana:")) return "sol";
113
+ return net;
114
+ }
115
+ function formatTxLine(r, opts) {
116
+ const time = new Date(r.t).toLocaleTimeString("en-US", {
117
+ hour: "2-digit",
118
+ minute: "2-digit",
119
+ hour12: false,
120
+ timeZone: "UTC"
121
+ });
122
+ const timeStr = r.tx ? `[${time}](${explorerUrl(r.net, r.tx)})` : time;
123
+ const parts = [r.kind === "x402_inference" && r.model ? shortModel(r.model) : KIND_LABELS[r.kind] ?? r.kind];
124
+ if (r.label) parts.push(r.label);
125
+ if (r.ok && r.amount != null && r.token) parts.push(formatAmount(r.amount, r.token));
126
+ else if (r.ok && r.kind === "sell" && r.meta?.pct != null) parts.push(`${r.meta.pct}%`);
127
+ parts.push(shortNetwork(r.net));
128
+ if (opts?.verbose && r.tx) {
129
+ const short = r.tx.length > 20 ? `${r.tx.slice(0, 10)}...${r.tx.slice(-6)}` : r.tx;
130
+ parts.push(short);
131
+ }
132
+ return ` ${timeStr} ${r.ok ? "" : "✗ "}${parts.join(" · ")}`;
133
+ }
134
+
135
+ //#endregion
24
136
  //#region src/lib/config.ts
25
137
  const APP_NAME = "x402-proxy";
26
138
  function getConfigDir() {
27
139
  const xdg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
28
140
  return path.join(xdg, APP_NAME);
29
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
+ }
30
148
  function getWalletPath() {
31
149
  return path.join(getConfigDir(), "wallet.json");
32
150
  }
@@ -72,9 +190,7 @@ function loadConfig() {
72
190
  return JSON.parse(stripped);
73
191
  }
74
192
  return JSON.parse(raw);
75
- } catch {
76
- continue;
77
- }
193
+ } catch {}
78
194
  }
79
195
  return null;
80
196
  }
@@ -90,6 +206,24 @@ function isConfigured() {
90
206
  return loadWalletFile() !== null;
91
207
  }
92
208
 
209
+ //#endregion
210
+ //#region src/lib/output.ts
211
+ function isTTY() {
212
+ return !!process.stderr.isTTY;
213
+ }
214
+ function info(msg) {
215
+ process.stderr.write(`${isTTY() ? pc.cyan(msg) : msg}\n`);
216
+ }
217
+ function warn(msg) {
218
+ process.stderr.write(`${isTTY() ? pc.yellow(msg) : msg}\n`);
219
+ }
220
+ function error(msg) {
221
+ process.stderr.write(`${isTTY() ? pc.red(`✗ ${msg}`) : `✗ ${msg}`}\n`);
222
+ }
223
+ function dim(msg) {
224
+ process.stderr.write(`${isTTY() ? pc.dim(msg) : msg}\n`);
225
+ }
226
+
93
227
  //#endregion
94
228
  //#region src/lib/derive.ts
95
229
  /**
@@ -221,11 +355,23 @@ function parsesolanaKey(input) {
221
355
  }
222
356
  return base58.decode(trimmed);
223
357
  }
358
+ function networkToCaipPrefix(name) {
359
+ switch (name.toLowerCase()) {
360
+ case "base": return "eip155:8453";
361
+ case "solana": return "solana:";
362
+ default: return name;
363
+ }
364
+ }
224
365
  /**
225
366
  * Build a configured x402Client from resolved wallet keys.
226
367
  */
227
- async function buildX402Client(wallet) {
228
- const client = new x402Client();
368
+ async function buildX402Client(wallet, opts) {
369
+ const client = new x402Client(opts?.preferredNetwork ? (() => {
370
+ const prefix = networkToCaipPrefix(opts.preferredNetwork);
371
+ return (_version, accepts) => {
372
+ return accepts.find((r) => r.network.startsWith(prefix)) || accepts[0];
373
+ };
374
+ })() : void 0);
229
375
  if (wallet.evmKey) {
230
376
  const hex = wallet.evmKey;
231
377
  const signer = toClientEvmSigner(privateKeyToAccount(hex), createPublicClient({
@@ -240,169 +386,190 @@ async function buildX402Client(wallet) {
240
386
  client.register("solana:mainnet", new ExactSvmScheme(signer));
241
387
  client.register("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", new ExactSvmScheme(signer));
242
388
  }
389
+ const daily = opts?.spendLimitDaily;
390
+ const perTx = opts?.spendLimitPerTx;
391
+ if (daily || perTx) client.registerPolicy((_version, reqs) => {
392
+ if (daily) {
393
+ const spend = calcSpend(readHistory(getHistoryPath()));
394
+ if (spend.today >= daily) throw new Error(`Daily spend limit reached (${spend.today.toFixed(4)}/${daily} USDC)`);
395
+ const remaining = daily - spend.today;
396
+ reqs = reqs.filter((r) => Number(r.amount) / 1e6 <= remaining);
397
+ if (reqs.length === 0) throw new Error(`Daily spend limit of ${daily} USDC would be exceeded (${spend.today.toFixed(4)} spent today)`);
398
+ }
399
+ if (perTx) {
400
+ const before = reqs.length;
401
+ reqs = reqs.filter((r) => Number(r.amount) / 1e6 <= perTx);
402
+ if (reqs.length === 0 && before > 0) throw new Error(`Payment exceeds per-transaction limit of ${perTx} USDC`);
403
+ }
404
+ return reqs;
405
+ });
243
406
  return client;
244
407
  }
245
408
 
246
409
  //#endregion
247
- //#region src/lib/output.ts
248
- function isTTY() {
249
- return !!process.stderr.isTTY;
250
- }
251
- function info(msg) {
252
- process.stderr.write(`${isTTY() ? pc.cyan(msg) : msg}\n`);
410
+ //#region src/commands/wallet.ts
411
+ const BASE_RPC = "https://mainnet.base.org";
412
+ const SOLANA_RPC = "https://api.mainnet-beta.solana.com";
413
+ const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
414
+ const USDC_SOLANA_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
415
+ async function rpcCall(url, method, params) {
416
+ return (await fetch(url, {
417
+ method: "POST",
418
+ headers: { "Content-Type": "application/json" },
419
+ body: JSON.stringify({
420
+ jsonrpc: "2.0",
421
+ method,
422
+ params,
423
+ id: 1
424
+ })
425
+ })).json();
253
426
  }
254
- function warn(msg) {
255
- process.stderr.write(`${isTTY() ? pc.yellow(msg) : msg}\n`);
256
- }
257
- function error(msg) {
258
- process.stderr.write(`${isTTY() ? pc.red(msg) : msg}\n`);
259
- }
260
- function dim(msg) {
261
- process.stderr.write(`${isTTY() ? pc.dim(msg) : msg}\n`);
262
- }
263
-
264
- //#endregion
265
- //#region src/history.ts
266
- const HISTORY_MAX_LINES = 1e3;
267
- const HISTORY_KEEP_LINES = 500;
268
- function appendHistory(historyPath, record) {
269
- try {
270
- appendFileSync(historyPath, `${JSON.stringify(record)}\n`);
271
- if (existsSync(historyPath)) {
272
- if (statSync(historyPath).size > HISTORY_MAX_LINES * 200) {
273
- const lines = readFileSync(historyPath, "utf-8").trimEnd().split("\n");
274
- if (lines.length > HISTORY_MAX_LINES) writeFileSync(historyPath, `${lines.slice(-HISTORY_KEEP_LINES).join("\n")}\n`);
275
- }
276
- }
277
- } catch {}
278
- }
279
- function readHistory(historyPath) {
280
- try {
281
- if (!existsSync(historyPath)) return [];
282
- const content = readFileSync(historyPath, "utf-8").trimEnd();
283
- if (!content) return [];
284
- return content.split("\n").flatMap((line) => {
285
- try {
286
- const parsed = JSON.parse(line);
287
- if (typeof parsed.t !== "number" || typeof parsed.kind !== "string") return [];
288
- return [parsed];
289
- } catch {
290
- return [];
291
- }
292
- });
293
- } catch {
294
- return [];
295
- }
296
- }
297
- function calcSpend(records) {
298
- const todayStart = /* @__PURE__ */ new Date();
299
- todayStart.setUTCHours(0, 0, 0, 0);
300
- const todayMs = todayStart.getTime();
301
- let today = 0;
302
- let total = 0;
303
- let count = 0;
304
- for (const r of records) {
305
- if (!r.ok || r.amount == null) continue;
306
- if (r.token !== "USDC") continue;
307
- total += r.amount;
308
- count++;
309
- if (r.t >= todayMs) today += r.amount;
310
- }
427
+ async function fetchEvmBalances(address) {
428
+ const usdcData = `0x70a08231${address.slice(2).padStart(64, "0")}`;
429
+ const [ethRes, usdcRes] = await Promise.all([rpcCall(BASE_RPC, "eth_getBalance", [address, "latest"]), rpcCall(BASE_RPC, "eth_call", [{
430
+ to: USDC_BASE,
431
+ data: usdcData
432
+ }, "latest"])]);
311
433
  return {
312
- today,
313
- total,
314
- count
434
+ eth: ethRes.result ? (Number(BigInt(ethRes.result)) / 0xde0b6b3a7640000).toFixed(6) : "?",
435
+ usdc: usdcRes.result ? (Number(BigInt(usdcRes.result)) / 1e6).toFixed(2) : "?"
315
436
  };
316
437
  }
317
- function formatAmount(amount, token) {
318
- if (token === "USDC") {
319
- if (amount >= .01) return `${amount.toFixed(2)} USDC`;
320
- if (amount >= .001) return `${amount.toFixed(3)} USDC`;
321
- if (amount >= 1e-4) return `${amount.toFixed(4)} USDC`;
322
- return `${amount.toFixed(6)} USDC`;
323
- }
324
- if (token === "SOL") return `${amount} SOL`;
325
- return `${amount} ${token}`;
326
- }
327
- const KIND_LABELS = {
328
- x402_inference: "inference",
329
- x402_payment: "payment",
330
- transfer: "transfer",
331
- buy: "buy",
332
- sell: "sell",
333
- mint: "mint",
334
- swap: "swap"
335
- };
336
- function explorerUrl(net, tx) {
337
- if (net.startsWith("eip155:")) {
338
- if (net.split(":")[1] === "8453") return `https://basescan.org/tx/${tx}`;
339
- return `https://basescan.org/tx/${tx}`;
340
- }
341
- return `https://solscan.io/tx/${tx}`;
438
+ async function fetchSolanaBalances(address) {
439
+ const [solRes, usdcRes] = await Promise.all([rpcCall(SOLANA_RPC, "getBalance", [address]), rpcCall(SOLANA_RPC, "getTokenAccountsByOwner", [
440
+ address,
441
+ { mint: USDC_SOLANA_MINT },
442
+ { encoding: "jsonParsed" }
443
+ ])]);
444
+ const sol = solRes.result?.value != null ? (solRes.result.value / 1e9).toFixed(6) : "?";
445
+ const accounts = usdcRes.result?.value;
446
+ return {
447
+ sol,
448
+ usdc: accounts?.length ? Number(accounts[0].account.data.parsed.info.tokenAmount.uiAmountString).toFixed(2) : "0.00"
449
+ };
342
450
  }
343
- /** Strip provider prefix and OpenRouter date suffixes from model IDs.
344
- * e.g. "minimax/minimax-m2.5-20260211" -> "minimax-m2.5"
345
- * "moonshotai/kimi-k2.5-0127" -> "kimi-k2.5" */
346
- function shortModel(model) {
347
- const parts = model.split("/");
348
- return parts[parts.length - 1].replace(/-\d{6,8}$/, "").replace(/-\d{4}$/, "");
451
+ function balanceLine(usdc, native, nativeSymbol) {
452
+ return pc.dim(` (${usdc} USDC, ${native} ${nativeSymbol})`);
349
453
  }
350
- function formatTxLine(r) {
351
- const time = new Date(r.t).toLocaleTimeString("en-US", {
352
- hour: "2-digit",
353
- minute: "2-digit",
354
- hour12: false,
355
- timeZone: "UTC"
356
- });
357
- const timeStr = r.tx ? `[${time}](${explorerUrl(r.net, r.tx)})` : time;
358
- const parts = [r.kind === "x402_inference" && r.model ? shortModel(r.model) : KIND_LABELS[r.kind] ?? r.kind];
359
- if (r.label) parts.push(r.label);
360
- if (r.ok && r.amount != null && r.token) parts.push(formatAmount(r.amount, r.token));
361
- else if (r.ok && r.kind === "sell" && r.meta?.pct != null) parts.push(`${r.meta.pct}%`);
362
- return ` ${timeStr} ${r.ok ? "" : "✗ "}${parts.join(" · ")}`;
363
- }
364
-
365
- //#endregion
366
- //#region src/commands/status.ts
367
- const statusCommand = buildCommand({
368
- docs: { brief: "Show configuration and wallet status" },
454
+ const walletInfoCommand = buildCommand({
455
+ docs: { brief: "Show wallet addresses and balances" },
369
456
  parameters: {
370
- flags: {},
457
+ flags: { verbose: {
458
+ kind: "boolean",
459
+ brief: "Show transaction IDs",
460
+ default: false
461
+ } },
371
462
  positional: {
372
463
  kind: "tuple",
373
464
  parameters: []
374
465
  }
375
466
  },
376
- func() {
467
+ async func(flags) {
377
468
  const wallet = resolveWallet();
378
- const config = loadConfig();
469
+ if (wallet.source === "none") {
470
+ console.log(pc.yellow("No wallet configured."));
471
+ console.log(pc.dim(`Run ${pc.cyan("x402-proxy setup")} to create one.`));
472
+ console.log(pc.dim(`Or set ${pc.cyan("X402_PROXY_WALLET_MNEMONIC")} environment variable.`));
473
+ process.exit(1);
474
+ }
379
475
  console.log();
380
- info("x402-proxy status");
476
+ info("Wallet");
381
477
  console.log();
382
- dim(` Config directory: ${getConfigDir()}`);
383
- if (config) {
384
- if (config.spendLimit) dim(` Spend limit: ${config.spendLimit} USDC`);
385
- if (config.defaultNetwork) dim(` Default network: ${config.defaultNetwork}`);
478
+ console.log(pc.dim(` Source: ${wallet.source}`));
479
+ const [evmResult, solResult] = await Promise.allSettled([wallet.evmAddress ? fetchEvmBalances(wallet.evmAddress) : Promise.resolve(null), wallet.solanaAddress ? fetchSolanaBalances(wallet.solanaAddress) : Promise.resolve(null)]);
480
+ const evm = evmResult.status === "fulfilled" ? evmResult.value : null;
481
+ const sol = solResult.status === "fulfilled" ? solResult.value : null;
482
+ if (wallet.evmAddress) {
483
+ const bal = evm ? balanceLine(evm.usdc, evm.eth, "ETH") : pc.dim(" (network error)");
484
+ console.log(` Base: ${pc.green(wallet.evmAddress)}${bal}`);
485
+ }
486
+ if (wallet.solanaAddress) {
487
+ const bal = sol ? balanceLine(sol.usdc, sol.sol, "SOL") : pc.dim(" (network error)");
488
+ console.log(` Solana: ${pc.green(wallet.solanaAddress)}${bal}`);
386
489
  }
387
490
  console.log();
388
- if (wallet.source === "none") {
389
- console.log(pc.yellow(" No wallet configured."));
390
- console.log(pc.dim(` Run ${pc.cyan("x402-proxy setup")} to create one.`));
391
- } else {
392
- dim(` Wallet source: ${wallet.source}`);
393
- if (wallet.evmAddress) console.log(` EVM: ${pc.green(wallet.evmAddress)}`);
394
- if (wallet.solanaAddress) console.log(` Solana: ${pc.green(wallet.solanaAddress)}`);
491
+ const records = readHistory(getHistoryPath());
492
+ if (records.length > 0) {
493
+ const spend = calcSpend(records);
494
+ const recent = records.slice(-10);
495
+ dim(" Recent transactions:");
496
+ for (const r of recent) {
497
+ const line = formatTxLine(r, { verbose: flags.verbose }).replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
498
+ console.log(line);
499
+ }
500
+ console.log();
501
+ console.log(pc.dim(` Today: ${spend.today.toFixed(4)} USDC | Total: ${spend.total.toFixed(4)} USDC | ${spend.count} tx`));
502
+ } else dim(" No transactions yet.");
503
+ console.log();
504
+ console.log(pc.dim(" See also: wallet history, wallet fund, wallet export-key"));
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);
395
551
  }
396
552
  console.log();
397
- const spend = calcSpend(readHistory(getHistoryPath()));
398
- if (spend.count > 0) {
399
- dim(` Transactions: ${spend.count}`);
400
- dim(` Today: ${spend.today.toFixed(4)} USDC`);
401
- dim(` Total: ${spend.total.toFixed(4)} USDC`);
402
- } else dim(" No payment history yet.");
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();
403
570
  console.log();
404
571
  }
405
572
  });
406
573
 
407
574
  //#endregion
408
- export { saveWalletFile as S, getConfigDir as _, readHistory as a, isConfigured as b, info as c, buildX402Client as d, resolveWallet as f, ensureConfigDir as g, generateMnemonic$1 as h, formatTxLine as i, isTTY as l, deriveSolanaKeypair as m, appendHistory as n, dim as o, deriveEvmKeypair as p, calcSpend as r, error as s, statusCommand as t, warn as u, getHistoryPath as v, saveConfig as x, getWalletPath as y };
575
+ export { calcSpend as C, appendHistory as S, readHistory as T, getWalletPath as _, resolveWallet as a, saveConfig as b, generateMnemonic$1 as c, info as d, isTTY as f, getHistoryPath as g, getConfigDirShort as h, buildX402Client as i, dim as l, ensureConfigDir as m, statusCommand as n, deriveEvmKeypair as o, warn as p, walletInfoCommand as r, deriveSolanaKeypair as s, displayStatus as t, error as u, isConfigured as v, formatTxLine as w, saveWalletFile as x, loadConfig as y };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "x402-proxy",
3
- "version": "0.2.0",
4
- "description": "CLI and library for x402 paid HTTP requests and MCP proxy",
3
+ "version": "0.3.0",
4
+ "description": "curl for x402 paid APIs. Auto-pays any endpoint on Base and Solana.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "main": "./dist/index.js",
@@ -26,26 +26,22 @@
26
26
  "@scure/base": "^2.0.0",
27
27
  "@scure/bip32": "^2.0.1",
28
28
  "@scure/bip39": "^2.0.1",
29
+ "@solana/kit": "^6.0.0",
29
30
  "@stricli/core": "^1.2.6",
30
31
  "@x402/evm": "^2.6.0",
31
32
  "@x402/fetch": "^2.6.0",
32
33
  "@x402/mcp": "^2.6.0",
33
34
  "@x402/svm": "^2.6.0",
35
+ "ethers": "^6.0.0",
34
36
  "picocolors": "^1.1.1",
37
+ "viem": "^2.0.0",
35
38
  "yaml": "^2.8.2"
36
39
  },
37
- "peerDependencies": {
38
- "@solana/kit": "^6.0.0",
39
- "ethers": "^6.0.0",
40
- "viem": "^2.0.0"
41
- },
42
40
  "devDependencies": {
43
- "@solana/kit": "^6.0.0",
44
41
  "@types/node": "^22.0.0",
45
42
  "publint": "^0.3.17",
46
43
  "tsdown": "^0.20.3",
47
- "typescript": "^5.9.0",
48
- "viem": "^2.0.0"
44
+ "typescript": "^5.9.0"
49
45
  },
50
46
  "files": [
51
47
  "dist/**",
@@ -54,15 +50,16 @@
54
50
  ],
55
51
  "keywords": [
56
52
  "x402",
57
- "payment",
58
- "proxy",
53
+ "http-402",
59
54
  "mcp",
60
- "solana",
61
- "evm",
55
+ "mcp-proxy",
56
+ "ai-agent",
57
+ "agentic-commerce",
62
58
  "base",
59
+ "solana",
63
60
  "usdc",
64
- "ai-agent",
65
- "micropayment"
61
+ "coinbase",
62
+ "cli"
66
63
  ],
67
64
  "license": "Apache-2.0",
68
65
  "engines": {
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env node
2
- import { t as statusCommand } from "./status-DeCY-cLR.js";
3
-
4
- export { statusCommand };