x402-proxy 0.3.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -46,6 +46,9 @@ $ npx x402-proxy --method POST \
46
46
  --body '{"url":"https://x402.org"}' \
47
47
  https://web.surf.cascade.fyi/v1/crawl
48
48
 
49
+ # Force a specific network
50
+ $ npx x402-proxy --network base https://api.example.com/data
51
+
49
52
  # Pipe-safe
50
53
  $ npx x402-proxy https://api.example.com/data | jq '.results'
51
54
  ```
package/dist/bin/cli.js CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { a as resolveWallet, c as info, d as appendHistory, f as calcSpend, i as buildX402Client, l as isTTY, m as readHistory, n as statusCommand, o as dim, p as formatTxLine, r as walletInfoCommand, s as error, u as warn } from "../status-Cp51UDnh.js";
3
- import { c as isConfigured, i as ensureConfigDir, l as loadConfig, o as getHistoryPath, u as loadWalletFile } from "../derive-Rikypf5B.js";
4
- import { n as setupCommand } from "../setup-CvB0go56.js";
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-BHywTzdN.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-DxW72Hx6.js";
5
6
  import { buildApplication, buildCommand, buildRouteMap, run } from "@stricli/core";
6
7
  import pc from "picocolors";
7
8
  import { decodePaymentResponseHeader, wrapFetchWithPayment } from "@x402/fetch";
@@ -38,7 +39,7 @@ function createX402ProxyHandler(opts) {
38
39
  paymentQueue.push({
39
40
  network: hookCtx.selectedRequirements.network,
40
41
  payTo: hookCtx.selectedRequirements.payTo,
41
- amount: raw?.startsWith("debug.") ? raw.slice(6) : raw,
42
+ amount: raw,
42
43
  asset: hookCtx.selectedRequirements.asset
43
44
  });
44
45
  });
@@ -93,6 +94,12 @@ Examples:
93
94
  parse: String,
94
95
  optional: true
95
96
  },
97
+ network: {
98
+ kind: "parsed",
99
+ brief: "Require specific network (base, solana)",
100
+ parse: String,
101
+ optional: true
102
+ },
96
103
  json: {
97
104
  kind: "boolean",
98
105
  brief: "Force JSON output",
@@ -111,7 +118,7 @@ Examples:
111
118
  async func(flags, url) {
112
119
  if (!url) {
113
120
  if (isConfigured()) {
114
- const { displayStatus } = await import("../status-4nQP3Fpu.js");
121
+ const { displayStatus } = await import("../status-DrC0uxCS.js");
115
122
  await displayStatus();
116
123
  console.log();
117
124
  console.log(pc.dim(" Commands:"));
@@ -163,15 +170,25 @@ Examples:
163
170
  process.exit(1);
164
171
  }
165
172
  dim(" No wallet found. Let's set one up first.\n");
166
- const { runSetup } = await import("../setup-DtPaKQ37.js");
173
+ const { runSetup } = await import("../setup-Crq9TylJ.js");
167
174
  await runSetup();
168
175
  console.log();
169
176
  wallet = resolveWallet();
170
177
  if (wallet.source === "none") return;
171
178
  }
172
179
  const config = loadConfig();
180
+ let preferredNetwork = config?.defaultNetwork;
181
+ if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
182
+ const { fetchEvmBalances, fetchSolanaBalances } = await import("../wallet-DFhJWn3o.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
+ }
173
189
  const { x402Fetch, shiftPayment } = createX402ProxyHandler({ client: await buildX402Client(wallet, {
174
- preferredNetwork: config?.defaultNetwork,
190
+ preferredNetwork,
191
+ network: flags.network,
175
192
  spendLimitDaily: config?.spendLimitDaily,
176
193
  spendLimitPerTx: config?.spendLimitPerTx
177
194
  }) });
@@ -204,33 +221,75 @@ Examples:
204
221
  if (prHeader) try {
205
222
  accepts = JSON.parse(Buffer.from(prHeader, "base64").toString()).accepts ?? [];
206
223
  } catch {}
224
+ let costNum = 0;
225
+ let costStr = "?";
207
226
  if (accepts.length > 0) {
208
227
  const cheapest = accepts.reduce((min, a) => Number(a.amount) < Number(min.amount) ? a : min);
209
- error(`Payment required: ${(Number(cheapest.amount) / 1e6).toFixed(4)} USDC`);
210
- } else error("Payment required");
228
+ costNum = Number(cheapest.amount) / 1e6;
229
+ costStr = costNum.toFixed(4);
230
+ }
211
231
  const hasEvm = accepts.some((a) => a.network.startsWith("eip155:"));
212
232
  const hasSolana = accepts.some((a) => a.network.startsWith("solana:"));
213
233
  const hasOther = accepts.some((a) => !a.network.startsWith("eip155:") && !a.network.startsWith("solana:"));
214
- if (hasEvm || hasSolana) {
234
+ const { fetchEvmBalances, fetchSolanaBalances } = await import("../wallet-DFhJWn3o.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)`)}`);
215
261
  console.error();
216
- dim(" Fund your wallet with USDC:");
217
- if (hasEvm && wallet.evmAddress) console.error(` Base: ${pc.cyan(wallet.evmAddress)}`);
218
- if (hasSolana && wallet.solanaAddress) console.error(` Solana: ${pc.cyan(wallet.solanaAddress)}`);
219
- if (hasEvm && !wallet.evmAddress) dim(" Base: endpoint accepts EVM but no EVM wallet configured");
220
- if (hasSolana && !wallet.solanaAddress) dim(" Solana: endpoint accepts Solana but no Solana wallet configured");
221
- } else if (hasOther) {
222
- const networks = [...new Set(accepts.map((a) => a.network))].join(", ");
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}`)}`);
223
287
  console.error();
224
- error(`This endpoint only accepts payment on unsupported networks: ${networks}`);
225
288
  }
226
- console.error();
227
- dim(" Then re-run:");
228
- console.error(` ${pc.cyan(`$ npx x402-proxy ${url}`)}`);
229
- console.error();
230
289
  return;
231
290
  }
232
291
  if (payment && isTTY()) {
233
- info(` Payment: ${payment.amount ? (Number(payment.amount) / 1e6).toFixed(4) : "?"} USDC (${payment.network ?? "unknown"})`);
292
+ info(` Payment: ${payment.amount ? (Number(payment.amount) / 1e6).toFixed(4) : "?"} USDC (${displayNetwork(payment.network ?? "unknown")})`);
234
293
  if (txSig) dim(` Tx: ${txSig}`);
235
294
  }
236
295
  if (isTTY()) dim(` ${response.status} ${response.statusText} (${elapsedMs}ms)`);
@@ -292,6 +351,12 @@ Add to your MCP client config (Claude, Cursor, etc.):
292
351
  brief: "Solana private key (base58)",
293
352
  parse: String,
294
353
  optional: true
354
+ },
355
+ network: {
356
+ kind: "parsed",
357
+ brief: "Require specific network (base, solana)",
358
+ parse: String,
359
+ optional: true
295
360
  }
296
361
  },
297
362
  positional: {
@@ -316,8 +381,18 @@ Add to your MCP client config (Claude, Cursor, etc.):
316
381
  if (wallet.evmAddress) dim(` EVM: ${wallet.evmAddress}`);
317
382
  if (wallet.solanaAddress) dim(` Solana: ${wallet.solanaAddress}`);
318
383
  const config = loadConfig();
384
+ let preferredNetwork = config?.defaultNetwork;
385
+ if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
386
+ const { fetchEvmBalances, fetchSolanaBalances } = await import("../wallet-DFhJWn3o.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
+ }
319
393
  const x402PaymentClient = await buildX402Client(wallet, {
320
- preferredNetwork: config?.defaultNetwork,
394
+ preferredNetwork,
395
+ network: flags.network,
321
396
  spendLimitDaily: config?.spendLimitDaily,
322
397
  spendLimitPerTx: config?.spendLimitPerTx
323
398
  });
@@ -329,26 +404,28 @@ Add to your MCP client config (Claude, Cursor, etc.):
329
404
  const { x402MCPClient } = await import("@x402/mcp");
330
405
  const x402Mcp = new x402MCPClient(new Client({
331
406
  name: "x402-proxy",
332
- version: "0.3.2"
407
+ version: "0.4.1"
333
408
  }), x402PaymentClient, {
334
409
  autoPayment: true,
335
410
  onPaymentRequested: (ctx) => {
336
411
  const accept = ctx.paymentRequired.accepts?.[0];
337
- 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}"`);
338
413
  return true;
339
414
  }
340
415
  });
341
416
  x402Mcp.onAfterPayment(async (ctx) => {
342
417
  ensureConfigDir();
418
+ const accepted = ctx.paymentPayload.accepted;
343
419
  const tx = ctx.settleResponse?.transaction;
344
- const accept = ctx.paymentPayload;
345
420
  const record = {
346
421
  t: Date.now(),
347
422
  ok: true,
348
423
  kind: "x402_payment",
349
- net: accept.network ?? "unknown",
424
+ net: accepted?.network ?? "unknown",
350
425
  from: wallet.evmAddress ?? wallet.solanaAddress ?? "unknown",
426
+ to: accepted?.payTo,
351
427
  tx: typeof tx === "string" ? tx : void 0,
428
+ amount: accepted?.amount ? Number(accepted.amount) / 1e6 : void 0,
352
429
  token: "USDC",
353
430
  label: `mcp:${ctx.toolName}`
354
431
  };
@@ -374,7 +451,7 @@ Add to your MCP client config (Claude, Cursor, etc.):
374
451
  dim(` ${tools.length} tools available`);
375
452
  const localServer = new McpServer({
376
453
  name: "x402-proxy",
377
- version: "0.3.2"
454
+ version: "0.4.1"
378
455
  });
379
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) => {
380
457
  const result = await x402Mcp.callTool(tool.name, args);
@@ -531,7 +608,7 @@ const routes = buildRouteMap({
531
608
  });
532
609
  const app = buildApplication(routes, {
533
610
  name: "x402-proxy",
534
- versionInfo: { currentVersion: "0.3.2" },
611
+ versionInfo: { currentVersion: "0.4.1" },
535
612
  scanner: { caseStyle: "allow-kebab-for-camel" }
536
613
  });
537
614
 
@@ -3,8 +3,8 @@ import fs from "node:fs";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { parse, stringify } from "yaml";
6
- import { base58 } from "@scure/base";
7
6
  import { ed25519 } from "@noble/curves/ed25519.js";
7
+ import { base58 } from "@scure/base";
8
8
  import { secp256k1 } from "@noble/curves/secp256k1.js";
9
9
  import { hmac } from "@noble/hashes/hmac.js";
10
10
  import { sha512 } from "@noble/hashes/sha2.js";
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?.startsWith("debug.") ? raw.slice(6) : raw,
38
+ amount: raw,
39
39
  asset: hookCtx.selectedRequirements.asset
40
40
  });
41
41
  });
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { n as setupCommand, t as runSetup } from "./setup-gla-Qyqi.js";
3
+
4
+ export { runSetup };
@@ -1,5 +1,5 @@
1
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-Rikypf5B.js";
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
3
  import { buildCommand } from "@stricli/core";
4
4
  import pc from "picocolors";
5
5
  import * as prompts from "@clack/prompts";
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { n as statusCommand, t as displayStatus } from "./status-Cp51UDnh.js";
2
+ import { n as statusCommand, t as displayStatus } from "./status-DxW72Hx6.js";
3
3
 
4
4
  export { displayStatus };
@@ -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-BHywTzdN.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,12 +1,14 @@
1
1
  #!/usr/bin/env node
2
- import { a as getConfigDirShort, l as loadConfig, n as deriveSolanaKeypair, o as getHistoryPath, t as deriveEvmKeypair, u as loadWalletFile } from "./derive-Rikypf5B.js";
2
+ import { n as deriveSolanaKeypair, o as getHistoryPath, t as deriveEvmKeypair, u as loadWalletFile } from "./derive-CISr_ond.js";
3
3
  import { buildCommand } from "@stricli/core";
4
4
  import pc from "picocolors";
5
5
  import { x402Client } from "@x402/fetch";
6
6
  import { appendFileSync, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
7
+ import { ed25519 } from "@noble/curves/ed25519.js";
7
8
  import { base58 } from "@scure/base";
8
- import { ExactEvmScheme, toClientEvmSigner } from "@x402/evm";
9
- import { ExactSvmScheme } from "@x402/svm/exact/client";
9
+ import { toClientEvmSigner } from "@x402/evm";
10
+ import { registerExactEvmScheme } from "@x402/evm/exact/client";
11
+ import { registerExactSvmScheme } from "@x402/svm/exact/client";
10
12
  import { createPublicClient, http } from "viem";
11
13
  import { privateKeyToAccount } from "viem/accounts";
12
14
  import { base } from "viem/chains";
@@ -96,6 +98,12 @@ function shortModel(model) {
96
98
  const parts = model.split("/");
97
99
  return parts[parts.length - 1].replace(/-\d{6,8}$/, "").replace(/-\d{4}$/, "");
98
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
+ }
99
107
  function shortNetwork(net) {
100
108
  if (net === "eip155:8453") return "base";
101
109
  if (net.startsWith("eip155:")) return `evm:${net.split(":")[1]}`;
@@ -157,7 +165,10 @@ function resolveWallet(opts) {
157
165
  result.evmKey = hex;
158
166
  result.evmAddress = privateKeyToAccount(hex).address;
159
167
  }
160
- if (opts.solanaKey) result.solanaKey = parsesolanaKey(opts.solanaKey);
168
+ if (opts.solanaKey) {
169
+ result.solanaKey = parsesolanaKey(opts.solanaKey);
170
+ result.solanaAddress = solanaAddressFromKey(result.solanaKey);
171
+ }
161
172
  return result;
162
173
  }
163
174
  const envEvm = process.env.X402_PROXY_WALLET_EVM_KEY;
@@ -169,7 +180,10 @@ function resolveWallet(opts) {
169
180
  result.evmKey = hex;
170
181
  result.evmAddress = privateKeyToAccount(hex).address;
171
182
  }
172
- if (envSol) result.solanaKey = parsesolanaKey(envSol);
183
+ if (envSol) {
184
+ result.solanaKey = parsesolanaKey(envSol);
185
+ result.solanaAddress = solanaAddressFromKey(result.solanaKey);
186
+ }
173
187
  return result;
174
188
  }
175
189
  const envMnemonic = process.env.X402_PROXY_WALLET_MNEMONIC;
@@ -200,6 +214,10 @@ function parsesolanaKey(input) {
200
214
  }
201
215
  return base58.decode(trimmed);
202
216
  }
217
+ function solanaAddressFromKey(keyBytes) {
218
+ if (keyBytes.length >= 64) return base58.encode(keyBytes.slice(32));
219
+ return base58.encode(ed25519.getPublicKey(keyBytes));
220
+ }
203
221
  function networkToCaipPrefix(name) {
204
222
  switch (name.toLowerCase()) {
205
223
  case "base": return "eip155:8453";
@@ -207,30 +225,40 @@ function networkToCaipPrefix(name) {
207
225
  default: return name;
208
226
  }
209
227
  }
228
+ function createNetworkFilter(network) {
229
+ const prefix = networkToCaipPrefix(network);
230
+ return (_version, reqs) => {
231
+ const filtered = reqs.filter((r) => r.network.startsWith(prefix));
232
+ if (filtered.length === 0) {
233
+ const available = [...new Set(reqs.map((r) => displayNetwork(r.network)))].join(", ");
234
+ throw new Error(`Network '${network}' not accepted. Available: ${available}`);
235
+ }
236
+ return filtered;
237
+ };
238
+ }
239
+ function createNetworkPreference(network) {
240
+ const prefix = networkToCaipPrefix(network);
241
+ return (_version, accepts) => {
242
+ return accepts.find((r) => r.network.startsWith(prefix)) || accepts[0];
243
+ };
244
+ }
210
245
  /**
211
246
  * Build a configured x402Client from resolved wallet keys.
212
247
  */
213
248
  async function buildX402Client(wallet, opts) {
214
- const client = new x402Client(opts?.preferredNetwork ? (() => {
215
- const prefix = networkToCaipPrefix(opts.preferredNetwork);
216
- return (_version, accepts) => {
217
- return accepts.find((r) => r.network.startsWith(prefix)) || accepts[0];
218
- };
219
- })() : void 0);
249
+ const client = new x402Client(opts?.preferredNetwork ? createNetworkPreference(opts.preferredNetwork) : void 0);
220
250
  if (wallet.evmKey) {
221
251
  const hex = wallet.evmKey;
222
- const signer = toClientEvmSigner(privateKeyToAccount(hex), createPublicClient({
252
+ registerExactEvmScheme(client, { signer: toClientEvmSigner(privateKeyToAccount(hex), createPublicClient({
223
253
  chain: base,
224
254
  transport: http()
225
- }));
226
- client.register("eip155:8453", new ExactEvmScheme(signer));
255
+ })) });
227
256
  }
228
257
  if (wallet.solanaKey) {
229
258
  const { createKeyPairSignerFromBytes } = await import("@solana/kit");
230
- const signer = await createKeyPairSignerFromBytes(wallet.solanaKey);
231
- client.register("solana:mainnet", new ExactSvmScheme(signer));
232
- client.register("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", new ExactSvmScheme(signer));
259
+ registerExactSvmScheme(client, { signer: await createKeyPairSignerFromBytes(wallet.solanaKey) });
233
260
  }
261
+ if (opts?.network) client.registerPolicy(createNetworkFilter(opts.network));
234
262
  const daily = opts?.spendLimitDaily;
235
263
  const perTx = opts?.spendLimitPerTx;
236
264
  if (daily || perTx) client.registerPolicy((_version, reqs) => {
@@ -277,7 +305,7 @@ async function fetchEvmBalances(address) {
277
305
  }, "latest"])]);
278
306
  return {
279
307
  eth: ethRes.result ? (Number(BigInt(ethRes.result)) / 0xde0b6b3a7640000).toFixed(6) : "?",
280
- usdc: usdcRes.result ? (Number(BigInt(usdcRes.result)) / 1e6).toFixed(2) : "?"
308
+ usdc: usdcRes.result ? (Number(BigInt(usdcRes.result)) / 1e6).toFixed(4) : "?"
281
309
  };
282
310
  }
283
311
  async function fetchSolanaBalances(address) {
@@ -290,7 +318,7 @@ async function fetchSolanaBalances(address) {
290
318
  const accounts = usdcRes.result?.value;
291
319
  return {
292
320
  sol,
293
- usdc: accounts?.length ? Number(accounts[0].account.data.parsed.info.tokenAmount.uiAmountString).toFixed(2) : "0.00"
321
+ usdc: accounts?.length ? Number(accounts[0].account.data.parsed.info.tokenAmount.uiAmountString).toFixed(4) : "0.0000"
294
322
  };
295
323
  }
296
324
  function balanceLine(usdc, native, nativeSymbol) {
@@ -331,8 +359,8 @@ const walletInfoCommand = buildCommand({
331
359
  const bal = sol ? balanceLine(sol.usdc, sol.sol, "SOL") : pc.dim(" (network error)");
332
360
  console.log(` Solana: ${pc.green(wallet.solanaAddress)}${bal}`);
333
361
  }
334
- const evmEmpty = !evm || evm.usdc === "0.00";
335
- const solEmpty = !sol || sol.usdc === "0.00";
362
+ const evmEmpty = !evm || evm.usdc === "0.0000";
363
+ const solEmpty = !sol || sol.usdc === "0.0000";
336
364
  if (evmEmpty && solEmpty) {
337
365
  console.log();
338
366
  dim(" Send USDC to either address above to start using x402 APIs.");
@@ -357,69 +385,4 @@ const walletInfoCommand = buildCommand({
357
385
  });
358
386
 
359
387
  //#endregion
360
- //#region src/commands/status.ts
361
- async function displayStatus() {
362
- const wallet = resolveWallet();
363
- const config = loadConfig();
364
- const records = readHistory(getHistoryPath());
365
- const spend = calcSpend(records);
366
- console.log();
367
- console.log(pc.cyan(pc.bold("x402-proxy")));
368
- console.log(pc.dim("curl for x402 paid APIs"));
369
- console.log();
370
- if (wallet.source === "none") {
371
- console.log(pc.yellow(" No wallet configured."));
372
- console.log(pc.dim(` Run ${pc.cyan("$ npx x402-proxy setup")} to create one.`));
373
- } else {
374
- const [evmResult, solResult] = await Promise.allSettled([wallet.evmAddress ? fetchEvmBalances(wallet.evmAddress) : Promise.resolve(null), wallet.solanaAddress ? fetchSolanaBalances(wallet.solanaAddress) : Promise.resolve(null)]);
375
- const evm = evmResult.status === "fulfilled" ? evmResult.value : null;
376
- const sol = solResult.status === "fulfilled" ? solResult.value : null;
377
- if (wallet.evmAddress) {
378
- const bal = evm ? balanceLine(evm.usdc, evm.eth, "ETH") : pc.dim(" (network error)");
379
- console.log(` Base: ${pc.green(wallet.evmAddress)}${bal}`);
380
- }
381
- if (wallet.solanaAddress) {
382
- const bal = sol ? balanceLine(sol.usdc, sol.sol, "SOL") : pc.dim(" (network error)");
383
- console.log(` Solana: ${pc.green(wallet.solanaAddress)}${bal}`);
384
- }
385
- if (config?.spendLimitDaily || config?.spendLimitPerTx) {
386
- console.log();
387
- if (config.spendLimitDaily) {
388
- const pct = config.spendLimitDaily > 0 ? Math.round(spend.today / config.spendLimitDaily * 100) : 0;
389
- dim(` Daily limit: ${spend.today.toFixed(4)} / ${config.spendLimitDaily} USDC (${pct}%)`);
390
- }
391
- if (config.spendLimitPerTx) dim(` Per-tx limit: ${config.spendLimitPerTx} USDC`);
392
- }
393
- }
394
- console.log();
395
- if (spend.count > 0) {
396
- const recent = records.slice(-5);
397
- dim(" Recent transactions:");
398
- for (const r of recent) {
399
- const line = formatTxLine(r).replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
400
- console.log(line);
401
- }
402
- console.log();
403
- dim(` Today: ${spend.today.toFixed(4)} USDC | Total: ${spend.total.toFixed(4)} USDC | ${spend.count} tx`);
404
- } else dim(" No payment history yet.");
405
- console.log();
406
- if (config?.defaultNetwork) dim(` Network: ${config.defaultNetwork}`);
407
- dim(` Config: ${getConfigDirShort()}`);
408
- }
409
- const statusCommand = buildCommand({
410
- docs: { brief: "Show configuration and wallet status" },
411
- parameters: {
412
- flags: {},
413
- positional: {
414
- kind: "tuple",
415
- parameters: []
416
- }
417
- },
418
- async func() {
419
- await displayStatus();
420
- console.log();
421
- }
422
- });
423
-
424
- //#endregion
425
- export { resolveWallet as a, info as c, appendHistory as d, calcSpend as f, buildX402Client as i, isTTY as l, readHistory as m, statusCommand as n, dim as o, formatTxLine as p, walletInfoCommand as r, error as s, displayStatus as t, warn as u };
388
+ 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 };
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { i as walletInfoCommand, n as fetchEvmBalances, r as fetchSolanaBalances, t as balanceLine } from "./wallet-BHywTzdN.js";
3
+
4
+ export { fetchEvmBalances, fetchSolanaBalances };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402-proxy",
3
- "version": "0.3.2",
3
+ "version": "0.4.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,
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env node
2
- import { n as setupCommand, t as runSetup } from "./setup-CvB0go56.js";
3
-
4
- export { runSetup };