x402-proxy 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  ## Quick Start
6
6
 
7
7
  ```bash
8
- npx x402-proxy https://twitter.surf.cascade.fyi/search?q=cascade_fyi
8
+ npx x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi
9
9
  ```
10
10
 
11
11
  That's it. The endpoint returns 402, x402-proxy pays and streams the response.
@@ -45,7 +45,7 @@ Works like curl. Response body streams to stdout, payment info goes to stderr.
45
45
 
46
46
  ```bash
47
47
  # GET request
48
- x402-proxy https://twitter.surf.cascade.fyi/search?q=x402
48
+ x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi
49
49
 
50
50
  # POST with body and headers
51
51
  x402-proxy --method POST \
@@ -67,7 +67,7 @@ x402-proxy status # config + wallet + spend summary
67
67
  x402-proxy wallet # show addresses
68
68
  x402-proxy wallet history # payment history
69
69
  x402-proxy wallet fund # funding instructions
70
- x402-proxy wallet export-key <chain> # bare key to stdout (evm|solana)
70
+ x402-proxy wallet export-key <target> # bare key/mnemonic to stdout (evm|solana|mnemonic)
71
71
  ```
72
72
 
73
73
  All commands support `--help` for details.
@@ -83,8 +83,9 @@ Config stored at `$XDG_CONFIG_HOME/x402-proxy/` (default `~/.config/x402-proxy/`
83
83
  ### Export keys for other tools
84
84
 
85
85
  ```bash
86
- # Pipe-safe - outputs bare key to stdout
86
+ # Pipe-safe - outputs bare key/mnemonic to stdout
87
87
  MY_KEY=$(npx x402-proxy wallet export-key evm)
88
+ MY_MNEMONIC=$(npx x402-proxy wallet export-key mnemonic)
88
89
  ```
89
90
 
90
91
  ## Env Vars
package/dist/bin/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { C as readHistory, S as formatTxLine, _ as isConfigured, a as deriveEvmKeypair, b as appendHistory, c as dim, d as isTTY, f as warn, g as getWalletPath, h as getHistoryPath, i as resolveWallet, l as error, m as getConfigDir, n as statusCommand, o as deriveSolanaKeypair, p as ensureConfigDir, r as buildX402Client, s as generateMnemonic, u as info, v as saveConfig, x as calcSpend, y as saveWalletFile } from "../status-J-RoszDZ.js";
2
+ import { C as appendHistory, E as readHistory, S as saveWalletFile, T as formatTxLine, _ as getWalletPath, a as resolveWallet, b as loadWalletFile, 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 calcSpend, x as saveConfig, y as loadConfig } from "../status-BAzavcCj.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,7 +49,15 @@ 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: {
@@ -101,18 +109,38 @@ const fetchCommand = buildCommand({
101
109
  async func(flags, url) {
102
110
  if (!url) {
103
111
  if (isConfigured()) {
104
- const { displayStatus } = await import("../status-DnxuVdOP.js");
105
- displayStatus();
112
+ const { displayStatus } = await import("../status-CeXiUR6L.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();
106
127
  } else {
107
128
  console.log();
108
- 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"));
109
131
  console.log();
110
- console.log(pc.dim(" Get started:"));
111
- console.log(` ${pc.cyan("x402-proxy setup")} Create a wallet`);
112
- console.log(` ${pc.cyan("x402-proxy <url>")} Make a paid request`);
113
- console.log(` ${pc.cyan("x402-proxy mcp <url>")} MCP proxy for agents`);
114
- console.log(` ${pc.cyan("x402-proxy wallet")} Wallet info`);
115
- console.log(` ${pc.cyan("x402-proxy --help")} All commands`);
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`);
140
+ console.log();
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"));
116
144
  console.log();
117
145
  }
118
146
  return;
@@ -133,7 +161,12 @@ const fetchCommand = buildCommand({
133
161
  console.error(pc.dim(`Run ${pc.cyan("x402-proxy setup")} or set X402_PROXY_WALLET_MNEMONIC`));
134
162
  process.exit(1);
135
163
  }
136
- 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
+ }) });
137
170
  const headers = new Headers();
138
171
  if (flags.header) for (const h of flags.header) {
139
172
  const idx = h.indexOf(":");
@@ -198,7 +231,15 @@ const fetchCommand = buildCommand({
198
231
  //#endregion
199
232
  //#region src/commands/mcp.ts
200
233
  const mcpCommand = buildCommand({
201
- 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
+ },
202
243
  parameters: {
203
244
  flags: {
204
245
  evmKey: {
@@ -235,7 +276,12 @@ const mcpCommand = buildCommand({
235
276
  dim(`x402-proxy MCP proxy -> ${remoteUrl}`);
236
277
  if (wallet.evmAddress) dim(` EVM: ${wallet.evmAddress}`);
237
278
  if (wallet.solanaAddress) dim(` Solana: ${wallet.solanaAddress}`);
238
- 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
+ });
239
285
  const { Client } = await import("@modelcontextprotocol/sdk/client/index.js");
240
286
  const { SSEClientTransport } = await import("@modelcontextprotocol/sdk/client/sse.js");
241
287
  const { StreamableHTTPClientTransport } = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
@@ -244,7 +290,7 @@ const mcpCommand = buildCommand({
244
290
  const { x402MCPClient } = await import("@x402/mcp");
245
291
  const x402Mcp = new x402MCPClient(new Client({
246
292
  name: "x402-proxy",
247
- version: "0.2.0"
293
+ version: "0.3.0"
248
294
  }), x402PaymentClient, {
249
295
  autoPayment: true,
250
296
  onPaymentRequested: (ctx) => {
@@ -289,7 +335,7 @@ const mcpCommand = buildCommand({
289
335
  dim(` ${tools.length} tools available`);
290
336
  const localServer = new McpServer({
291
337
  name: "x402-proxy",
292
- version: "0.2.0"
338
+ version: "0.3.0"
293
339
  });
294
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) => {
295
341
  const result = await x402Mcp.callTool(tool.name, args);
@@ -377,7 +423,7 @@ const setupCommand = buildCommand({
377
423
  }
378
424
  const evm = deriveEvmKeypair(mnemonic);
379
425
  const sol = deriveSolanaKeypair(mnemonic);
380
- prompts.log.success(`EVM address: ${pc.green(evm.address)}`);
426
+ prompts.log.success(`Base address: ${pc.green(evm.address)}`);
381
427
  prompts.log.success(`Solana address: ${pc.green(sol.address)}`);
382
428
  saveWalletFile({
383
429
  version: 1,
@@ -388,82 +434,67 @@ const setupCommand = buildCommand({
388
434
  }
389
435
  });
390
436
  saveConfig({});
391
- prompts.log.info(`Config directory: ${pc.dim(getConfigDir())}`);
437
+ prompts.log.info(`Config directory: ${pc.dim(getConfigDirShort())}`);
392
438
  prompts.log.step("Fund your wallets to start using x402 resources:");
393
439
  prompts.log.message(` Solana (USDC): Send USDC to ${pc.cyan(sol.address)}`);
394
- 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")}`);
395
443
  prompts.outro(pc.green("Setup complete!"));
396
444
  }
397
445
  });
398
446
 
399
- //#endregion
400
- //#region src/commands/wallet.ts
401
- const walletInfoCommand = buildCommand({
402
- docs: { brief: "Show wallet addresses" },
403
- parameters: {
404
- flags: {},
405
- positional: {
406
- kind: "tuple",
407
- parameters: []
408
- }
409
- },
410
- func() {
411
- const wallet = resolveWallet();
412
- if (wallet.source === "none") {
413
- console.log(pc.yellow("No wallet configured."));
414
- console.log(pc.dim(`Run ${pc.cyan("x402-proxy setup")} to create one.`));
415
- console.log(pc.dim(`Or set ${pc.cyan("X402_PROXY_WALLET_MNEMONIC")} environment variable.`));
416
- process.exit(1);
417
- }
418
- console.log();
419
- info("Wallet");
420
- console.log();
421
- console.log(pc.dim(` Source: ${wallet.source}`));
422
- if (wallet.evmAddress) console.log(` EVM: ${pc.green(wallet.evmAddress)}`);
423
- if (wallet.solanaAddress) console.log(` Solana: ${pc.green(wallet.solanaAddress)}`);
424
- console.log();
425
- }
426
- });
427
-
428
447
  //#endregion
429
448
  //#region src/commands/wallet-export.ts
430
449
  const walletExportCommand = buildCommand({
431
- docs: { brief: "Export private key to stdout (pipe-safe)" },
450
+ docs: { brief: "Export private key or mnemonic to stdout (pipe-safe)" },
432
451
  parameters: {
433
452
  flags: {},
434
453
  positional: {
435
454
  kind: "tuple",
436
455
  parameters: [{
437
- brief: "Chain to export: evm or solana",
456
+ brief: "What to export: evm, solana, or mnemonic",
438
457
  parse: (input) => {
439
458
  const v = input.toLowerCase();
440
- if (v !== "evm" && v !== "solana") throw new Error("Must be 'evm' or 'solana'");
459
+ if (v !== "evm" && v !== "solana" && v !== "mnemonic") throw new Error("Must be 'evm', 'solana', or 'mnemonic'");
441
460
  return v;
442
461
  }
443
462
  }]
444
463
  }
445
464
  },
446
- func(flags, chain) {
465
+ async func(flags, chain) {
466
+ if (chain === "mnemonic") {
467
+ const mnemonic = process.env.X402_PROXY_WALLET_MNEMONIC || loadWalletFile()?.mnemonic;
468
+ if (!mnemonic) {
469
+ error("No mnemonic available. Wallet may have been configured with individual keys.");
470
+ process.exit(1);
471
+ }
472
+ if (process.stdout.isTTY) {
473
+ const confirmed = await prompts.confirm({ message: "This will print your mnemonic to the terminal. Continue?" });
474
+ if (prompts.isCancel(confirmed) || !confirmed) process.exit(0);
475
+ } else warn("Warning: mnemonic will be printed to stdout.");
476
+ process.stdout.write(mnemonic);
477
+ return;
478
+ }
447
479
  const wallet = resolveWallet();
448
480
  if (wallet.source === "none") {
449
481
  error("No wallet configured.");
450
482
  process.exit(1);
451
483
  }
452
- if (chain === "evm") {
453
- if (!wallet.evmKey) {
454
- error("No EVM key available.");
455
- process.exit(1);
456
- }
457
- warn("Warning: private key will be printed to stdout.");
458
- process.stdout.write(wallet.evmKey);
459
- } else {
460
- if (!wallet.solanaKey) {
461
- error("No Solana key available.");
462
- process.exit(1);
463
- }
464
- warn("Warning: private key will be printed to stdout.");
465
- process.stdout.write(base58.encode(wallet.solanaKey.slice(0, 32)));
484
+ if (chain === "evm" && !wallet.evmKey) {
485
+ error("No EVM key available.");
486
+ process.exit(1);
487
+ }
488
+ if (chain === "solana" && !wallet.solanaKey) {
489
+ error("No Solana key available.");
490
+ process.exit(1);
466
491
  }
492
+ if (process.stdout.isTTY) {
493
+ const confirmed = await prompts.confirm({ message: "This will print your private key to the terminal. Continue?" });
494
+ if (prompts.isCancel(confirmed) || !confirmed) process.exit(0);
495
+ } else warn("Warning: private key will be printed to stdout.");
496
+ if (chain === "evm") process.stdout.write(wallet.evmKey);
497
+ else process.stdout.write(base58.encode(wallet.solanaKey.slice(0, 32)));
467
498
  }
468
499
  });
469
500
 
@@ -578,7 +609,7 @@ const routes = buildRouteMap({
578
609
  });
579
610
  const app = buildApplication(routes, {
580
611
  name: "x402-proxy",
581
- versionInfo: { currentVersion: "0.2.1" },
612
+ versionInfo: { currentVersion: "0.3.1" },
582
613
  scanner: { caseStyle: "allow-kebab-for-camel" }
583
614
  });
584
615
 
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
 
@@ -106,7 +106,13 @@ function shortModel(model) {
106
106
  const parts = model.split("/");
107
107
  return parts[parts.length - 1].replace(/-\d{6,8}$/, "").replace(/-\d{4}$/, "");
108
108
  }
109
- function formatTxLine(r) {
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) {
110
116
  const time = new Date(r.t).toLocaleTimeString("en-US", {
111
117
  hour: "2-digit",
112
118
  minute: "2-digit",
@@ -118,6 +124,11 @@ function formatTxLine(r) {
118
124
  if (r.label) parts.push(r.label);
119
125
  if (r.ok && r.amount != null && r.token) parts.push(formatAmount(r.amount, r.token));
120
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
+ }
121
132
  return ` ${timeStr} ${r.ok ? "" : "✗ "}${parts.join(" · ")}`;
122
133
  }
123
134
 
@@ -128,6 +139,12 @@ function getConfigDir() {
128
139
  const xdg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
129
140
  return path.join(xdg, APP_NAME);
130
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
+ }
131
148
  function getWalletPath() {
132
149
  return path.join(getConfigDir(), "wallet.json");
133
150
  }
@@ -201,7 +218,7 @@ function warn(msg) {
201
218
  process.stderr.write(`${isTTY() ? pc.yellow(msg) : msg}\n`);
202
219
  }
203
220
  function error(msg) {
204
- process.stderr.write(`${isTTY() ? pc.red(msg) : msg}\n`);
221
+ process.stderr.write(`${isTTY() ? pc.red(`✗ ${msg}`) : `✗ ${msg}`}\n`);
205
222
  }
206
223
  function dim(msg) {
207
224
  process.stderr.write(`${isTTY() ? pc.dim(msg) : msg}\n`);
@@ -338,11 +355,23 @@ function parsesolanaKey(input) {
338
355
  }
339
356
  return base58.decode(trimmed);
340
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
+ }
341
365
  /**
342
366
  * Build a configured x402Client from resolved wallet keys.
343
367
  */
344
- async function buildX402Client(wallet) {
345
- 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);
346
375
  if (wallet.evmKey) {
347
376
  const hex = wallet.evmKey;
348
377
  const signer = toClientEvmSigner(privateKeyToAccount(hex), createPublicClient({
@@ -357,39 +386,175 @@ async function buildX402Client(wallet) {
357
386
  client.register("solana:mainnet", new ExactSvmScheme(signer));
358
387
  client.register("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", new ExactSvmScheme(signer));
359
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
+ });
360
406
  return client;
361
407
  }
362
408
 
409
+ //#endregion
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();
426
+ }
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"])]);
433
+ return {
434
+ eth: ethRes.result ? (Number(BigInt(ethRes.result)) / 0xde0b6b3a7640000).toFixed(6) : "?",
435
+ usdc: usdcRes.result ? (Number(BigInt(usdcRes.result)) / 1e6).toFixed(2) : "?"
436
+ };
437
+ }
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
+ };
450
+ }
451
+ function balanceLine(usdc, native, nativeSymbol) {
452
+ return pc.dim(` (${usdc} USDC, ${native} ${nativeSymbol})`);
453
+ }
454
+ const walletInfoCommand = buildCommand({
455
+ docs: { brief: "Show wallet addresses and balances" },
456
+ parameters: {
457
+ flags: { verbose: {
458
+ kind: "boolean",
459
+ brief: "Show transaction IDs",
460
+ default: false
461
+ } },
462
+ positional: {
463
+ kind: "tuple",
464
+ parameters: []
465
+ }
466
+ },
467
+ async func(flags) {
468
+ const wallet = resolveWallet();
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
+ }
475
+ console.log();
476
+ info("Wallet");
477
+ console.log();
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}`);
489
+ }
490
+ console.log();
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
+
363
509
  //#endregion
364
510
  //#region src/commands/status.ts
365
- function displayStatus() {
511
+ async function displayStatus() {
366
512
  const wallet = resolveWallet();
367
513
  const config = loadConfig();
514
+ const records = readHistory(getHistoryPath());
515
+ const spend = calcSpend(records);
368
516
  console.log();
369
- info("x402-proxy status");
370
- console.log();
371
- dim(` Config directory: ${getConfigDir()}`);
372
- if (config) {
373
- if (config.spendLimit) dim(` Spend limit: ${config.spendLimit} USDC`);
374
- if (config.defaultNetwork) dim(` Default network: ${config.defaultNetwork}`);
375
- }
517
+ console.log(pc.cyan(pc.bold("x402-proxy")));
518
+ console.log(pc.dim("curl for x402 paid APIs"));
376
519
  console.log();
377
520
  if (wallet.source === "none") {
378
521
  console.log(pc.yellow(" No wallet configured."));
379
- console.log(pc.dim(` Run ${pc.cyan("x402-proxy setup")} to create one.`));
522
+ console.log(pc.dim(` Run ${pc.cyan("$ npx x402-proxy setup")} to create one.`));
380
523
  } else {
381
- dim(` Wallet source: ${wallet.source}`);
382
- if (wallet.evmAddress) console.log(` EVM: ${pc.green(wallet.evmAddress)}`);
383
- if (wallet.solanaAddress) console.log(` Solana: ${pc.green(wallet.solanaAddress)}`);
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
+ }
384
543
  }
385
544
  console.log();
386
- const spend = calcSpend(readHistory(getHistoryPath()));
387
545
  if (spend.count > 0) {
388
- dim(` Transactions: ${spend.count}`);
389
- dim(` Today: ${spend.today.toFixed(4)} USDC`);
390
- dim(` Total: ${spend.total.toFixed(4)} USDC`);
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`);
391
554
  } else dim(" No payment history yet.");
392
555
  console.log();
556
+ if (config?.defaultNetwork) dim(` Network: ${config.defaultNetwork}`);
557
+ dim(` Config: ${getConfigDirShort()}`);
393
558
  }
394
559
  const statusCommand = buildCommand({
395
560
  docs: { brief: "Show configuration and wallet status" },
@@ -400,10 +565,11 @@ const statusCommand = buildCommand({
400
565
  parameters: []
401
566
  }
402
567
  },
403
- func() {
404
- displayStatus();
568
+ async func() {
569
+ await displayStatus();
570
+ console.log();
405
571
  }
406
572
  });
407
573
 
408
574
  //#endregion
409
- export { readHistory as C, formatTxLine as S, isConfigured as _, deriveEvmKeypair as a, appendHistory as b, dim as c, isTTY as d, warn as f, getWalletPath as g, getHistoryPath as h, resolveWallet as i, error as l, getConfigDir as m, statusCommand as n, deriveSolanaKeypair as o, ensureConfigDir as p, buildX402Client as r, generateMnemonic$1 as s, displayStatus as t, info as u, saveConfig as v, calcSpend as x, saveWalletFile as y };
575
+ export { appendHistory as C, readHistory as E, saveWalletFile as S, formatTxLine as T, getWalletPath as _, resolveWallet as a, loadWalletFile 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, calcSpend as w, saveConfig as x, loadConfig as y };
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { n as statusCommand, t as displayStatus } from "./status-J-RoszDZ.js";
2
+ import { n as statusCommand, t as displayStatus } from "./status-BAzavcCj.js";
3
3
 
4
4
  export { displayStatus };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402-proxy",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "curl for x402 paid APIs. Auto-pays any endpoint on Base and Solana.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -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/**",