x402-proxy 0.7.0 → 0.8.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/CHANGELOG.md CHANGED
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.0] - 2026-03-21
11
+
12
+ ### Added
13
+ - `config` command with `show`, `set`, and `unset` subcommands for managing configuration from the CLI (no more manual YAML editing)
14
+ - Setup wizard now asks about preferred payment protocol (x402/MPP) and network
15
+ - MCP proxy handles `McpError(-32042)` from dual-protocol servers that throw instead of using `isError`, automatically retrying with x402 payment
16
+ - MPP payment amounts captured from challenges and displayed in payment logs, MCP proxy output, and transaction history
17
+ - `formatAmount()` and `formatUsdcValue()` exported from library for adaptive USDC precision formatting
18
+
19
+ ### Changed
20
+ - USDC amounts displayed with adaptive precision (2-6 decimals based on magnitude) instead of fixed 4 decimals everywhere
21
+ - Zero-balance detection uses numeric comparison instead of string matching
22
+
23
+ ## [0.7.1] - 2026-03-20
24
+
25
+ ### Fixed
26
+ - Non-402 server errors (500, 503, etc.) now indicate whether payment was attempted, helping users distinguish "server down" from "payment failed"
27
+
10
28
  ## [0.7.0] - 2026-03-20
11
29
 
12
30
  ### Added
@@ -186,7 +204,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
186
204
  - `appendHistory` / `readHistory` / `calcSpend` - JSONL transaction history
187
205
  - Re-exports from `@x402/fetch`, `@x402/svm`, `@x402/evm`
188
206
 
189
- [Unreleased]: https://github.com/cascade-protocol/x402-proxy/compare/v0.7.0...HEAD
207
+ [Unreleased]: https://github.com/cascade-protocol/x402-proxy/compare/v0.8.0...HEAD
208
+ [0.8.0]: https://github.com/cascade-protocol/x402-proxy/compare/v0.7.1...v0.8.0
209
+ [0.7.1]: https://github.com/cascade-protocol/x402-proxy/compare/v0.7.0...v0.7.1
190
210
  [0.7.0]: https://github.com/cascade-protocol/x402-proxy/compare/v0.6.0...v0.7.0
191
211
  [0.6.0]: https://github.com/cascade-protocol/x402-proxy/compare/v0.5.2...v0.6.0
192
212
  [0.5.2]: https://github.com/cascade-protocol/x402-proxy/compare/v0.5.1...v0.5.2
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # x402-proxy
2
2
 
3
- `curl` for [x402](https://www.x402.org/) paid APIs. Auto-pays HTTP 402 responses with USDC on Base, Solana, and Tempo - zero crypto code on the buyer side. Supports both [x402](https://www.x402.org/) and [MPP](https://mpp.dev/) payment protocols.
3
+ `curl` for [x402](https://www.x402.org/) and [MPP](https://mpp.dev/) paid APIs. Auto-pays HTTP 402 responses with USDC on Base, Solana, and [Tempo](https://tempo.xyz/) - zero crypto code on the buyer side. Supports one-time payments (x402, MPP charge) and pay-per-token streaming (MPP sessions).
4
4
 
5
5
  ## Quick Start
6
6
 
@@ -30,7 +30,7 @@ Let your AI agent consume any paid MCP server. Configure in Claude, Cursor, or a
30
30
  }
31
31
  ```
32
32
 
33
- The proxy sits between your agent and the remote server, intercepting 402 responses, paying automatically, and forwarding the result. Your agent never touches crypto.
33
+ The proxy sits between your agent and the remote server, intercepting 402 responses, paying automatically, and forwarding the result. Supports both x402 and MPP protocols. Your agent never touches crypto.
34
34
 
35
35
  ## HTTP Requests
36
36
 
@@ -70,6 +70,9 @@ $ npx x402-proxy <url> # paid HTTP request (default command)
70
70
  $ npx x402-proxy mcp <url> # MCP stdio proxy for agents
71
71
  $ npx x402-proxy setup # onboarding wizard
72
72
  $ npx x402-proxy status # config + wallet + spend summary
73
+ $ npx x402-proxy config # show current configuration
74
+ $ npx x402-proxy config set <key> <value> # set a config value
75
+ $ npx x402-proxy config unset <key> # remove a config value
73
76
  $ npx x402-proxy wallet # show addresses and balances
74
77
  $ npx x402-proxy wallet history # payment history
75
78
  $ npx x402-proxy wallet export-key <target> # bare key/mnemonic to stdout (evm|solana|mnemonic)
package/dist/bin/cli.js CHANGED
@@ -1,14 +1,165 @@
1
1
  #!/usr/bin/env node
2
- import { _ as formatTxLine, c as resolveWallet, d as info, f as isTTY, g as displayNetwork, h as calcSpend, l as dim, m as appendHistory, o as walletInfoCommand, p as warn, s as buildX402Client, u as error, v as readHistory } from "../wallet-DxKCHa7U.js";
3
- import { a as getHistoryPath, c as loadConfig, l as loadWalletFile, s as isConfigured } from "../derive-ibF2UinV.js";
4
- import { n as setupCommand } from "../setup-hJGkO2Lo.js";
5
- import { n as statusCommand } from "../status-JNGv2Ghp.js";
2
+ import { a as getHistoryPath, c as loadConfig, i as getConfigDirShort, l as loadWalletFile, s as isConfigured, u as saveConfig } from "../derive-BR6N1ZjI.js";
3
+ import { _ as error, b as warn, c as resolveWallet, d as displayNetwork, f as formatAmount, g as dim, h as readHistory, l as appendHistory, m as formatUsdcValue, o as walletInfoCommand, p as formatTxLine, s as buildX402Client, u as calcSpend, v as info, y as isTTY } from "../wallet-CeY5DPj-.js";
4
+ import { n as setupCommand } from "../setup-Di_b5Vp9.js";
5
+ import { n as statusCommand } from "../status-S3t-XV_M.js";
6
6
  import { buildApplication, buildCommand, buildRouteMap, run } from "@stricli/core";
7
7
  import pc from "picocolors";
8
8
  import { decodePaymentResponseHeader, wrapFetchWithPayment } from "@x402/fetch";
9
9
  import { base58 } from "@scure/base";
10
10
  import * as prompts from "@clack/prompts";
11
11
 
12
+ //#region src/commands/config.ts
13
+ const VALID_KEYS = {
14
+ defaultNetwork: {
15
+ description: "Preferred network (base, solana, tempo)",
16
+ parse: (v) => {
17
+ if (![
18
+ "base",
19
+ "solana",
20
+ "tempo"
21
+ ].includes(v)) throw new Error("Must be one of: base, solana, tempo");
22
+ return v;
23
+ }
24
+ },
25
+ preferredProtocol: {
26
+ description: "Payment protocol (x402, mpp)",
27
+ parse: (v) => {
28
+ if (!["x402", "mpp"].includes(v)) throw new Error("Must be one of: x402, mpp");
29
+ return v;
30
+ }
31
+ },
32
+ mppSessionBudget: {
33
+ description: "Max USDC per MPP session (default: 1)",
34
+ parse: (v) => {
35
+ const n = Number(v);
36
+ if (Number.isNaN(n) || n <= 0) throw new Error("Must be a positive number");
37
+ return v;
38
+ }
39
+ },
40
+ spendLimitDaily: {
41
+ description: "Daily spending limit in USDC",
42
+ parse: (v) => {
43
+ const n = Number(v);
44
+ if (Number.isNaN(n) || n <= 0) throw new Error("Must be a positive number");
45
+ return n;
46
+ }
47
+ },
48
+ spendLimitPerTx: {
49
+ description: "Per-transaction spending limit in USDC",
50
+ parse: (v) => {
51
+ const n = Number(v);
52
+ if (Number.isNaN(n) || n <= 0) throw new Error("Must be a positive number");
53
+ return n;
54
+ }
55
+ }
56
+ };
57
+ function isConfigKey(k) {
58
+ return k in VALID_KEYS;
59
+ }
60
+ const configShowCommand = buildCommand({
61
+ docs: { brief: "Show current configuration" },
62
+ parameters: {
63
+ flags: {},
64
+ positional: {
65
+ kind: "tuple",
66
+ parameters: []
67
+ }
68
+ },
69
+ async func() {
70
+ const config = loadConfig();
71
+ console.log();
72
+ console.log(pc.bold("Configuration"));
73
+ dim(` ${getConfigDirShort()}/config.yaml`);
74
+ console.log();
75
+ if (!config || Object.keys(config).length === 0) {
76
+ dim(" No configuration set. Using defaults.");
77
+ console.log();
78
+ dim(" Available keys:");
79
+ for (const [key, meta] of Object.entries(VALID_KEYS)) dim(` ${pc.cyan(key)} - ${meta.description}`);
80
+ console.log();
81
+ dim(` Set with: ${pc.cyan("npx x402-proxy config set <key> <value>")}`);
82
+ console.log();
83
+ return;
84
+ }
85
+ for (const key of Object.keys(VALID_KEYS)) {
86
+ const value = config[key];
87
+ if (value !== void 0) console.log(` ${pc.cyan(key)}: ${pc.green(String(value))}`);
88
+ else dim(` ${key}: ${pc.dim("(not set)")}`);
89
+ }
90
+ console.log();
91
+ }
92
+ });
93
+ const configSetCommand = buildCommand({
94
+ docs: {
95
+ brief: "Set a configuration value",
96
+ fullDescription: `Set a configuration value.
97
+
98
+ Available keys:
99
+ ${Object.entries(VALID_KEYS).map(([k, v]) => ` ${k} - ${v.description}`).join("\n")}
100
+
101
+ To unset a value, use: npx x402-proxy config unset <key>`
102
+ },
103
+ parameters: {
104
+ flags: {},
105
+ positional: {
106
+ kind: "tuple",
107
+ parameters: [{
108
+ brief: "Configuration key",
109
+ parse: String
110
+ }, {
111
+ brief: "Value to set",
112
+ parse: String
113
+ }]
114
+ }
115
+ },
116
+ async func(_flags, key, value) {
117
+ if (!isConfigKey(key)) {
118
+ error(`Unknown config key: ${key}`);
119
+ console.error();
120
+ dim(" Available keys:");
121
+ for (const [k, m] of Object.entries(VALID_KEYS)) dim(` ${pc.cyan(k)} - ${m.description}`);
122
+ process.exit(1);
123
+ }
124
+ const meta = VALID_KEYS[key];
125
+ let parsed;
126
+ try {
127
+ parsed = meta.parse(value);
128
+ } catch (err) {
129
+ error(`Invalid value for ${key}: ${err instanceof Error ? err.message : String(err)}`);
130
+ process.exit(1);
131
+ }
132
+ const config = loadConfig() ?? {};
133
+ config[key] = parsed;
134
+ saveConfig(config);
135
+ console.log(` ${pc.cyan(key)} = ${pc.green(String(parsed))}`);
136
+ }
137
+ });
138
+ const configUnsetCommand = buildCommand({
139
+ docs: { brief: "Unset a configuration value" },
140
+ parameters: {
141
+ flags: {},
142
+ positional: {
143
+ kind: "tuple",
144
+ parameters: [{
145
+ brief: "Configuration key to remove",
146
+ parse: String
147
+ }]
148
+ }
149
+ },
150
+ async func(_flags, key) {
151
+ if (!isConfigKey(key)) {
152
+ error(`Unknown config key: ${key}`);
153
+ process.exit(1);
154
+ }
155
+ const config = loadConfig() ?? {};
156
+ delete config[key];
157
+ saveConfig(config);
158
+ dim(` ${key} unset`);
159
+ }
160
+ });
161
+
162
+ //#endregion
12
163
  //#region src/handler.ts
13
164
  /**
14
165
  * Detect which payment protocols a 402 response advertises.
@@ -70,30 +221,40 @@ async function createMppProxyHandler(opts) {
70
221
  const account = privateKeyToAccount(opts.evmKey);
71
222
  const maxDeposit = opts.maxDeposit ?? "1";
72
223
  const paymentQueue = [];
224
+ let lastChallengeAmount;
73
225
  const mppx = Mppx.create({
74
226
  methods: [tempo({
75
227
  account,
76
228
  maxDeposit
77
229
  })],
78
- polyfill: false
230
+ polyfill: false,
231
+ onChallenge: async (challenge) => {
232
+ const req = challenge.request;
233
+ if (req.amount) lastChallengeAmount = (Number(req.amount) / 10 ** (req.decimals ?? 6)).toString();
234
+ }
79
235
  });
80
236
  let session;
81
237
  return {
82
238
  async fetch(input, init) {
83
239
  const response = await mppx.fetch(typeof input === "string" ? input : input.toString(), init);
84
240
  const receiptHeader = response.headers.get("Payment-Receipt");
85
- if (receiptHeader) try {
86
- const receipt = JSON.parse(Buffer.from(receiptHeader, "base64url").toString());
87
- paymentQueue.push({
88
- protocol: "mpp",
89
- network: TEMPO_NETWORK,
90
- receipt
91
- });
92
- } catch {
93
- paymentQueue.push({
94
- protocol: "mpp",
95
- network: TEMPO_NETWORK
96
- });
241
+ if (receiptHeader) {
242
+ try {
243
+ const receipt = JSON.parse(Buffer.from(receiptHeader, "base64url").toString());
244
+ paymentQueue.push({
245
+ protocol: "mpp",
246
+ network: TEMPO_NETWORK,
247
+ amount: lastChallengeAmount,
248
+ receipt
249
+ });
250
+ } catch {
251
+ paymentQueue.push({
252
+ protocol: "mpp",
253
+ network: TEMPO_NETWORK,
254
+ amount: lastChallengeAmount
255
+ });
256
+ }
257
+ lastChallengeAmount = void 0;
97
258
  }
98
259
  return response;
99
260
  },
@@ -230,7 +391,7 @@ Examples:
230
391
  };
231
392
  if (!url) {
232
393
  if (isConfigured()) {
233
- const { displayStatus } = await import("../status-w5y-fhhe.js");
394
+ const { displayStatus } = await import("../status-CJPUbh6Z.js");
234
395
  await displayStatus();
235
396
  console.log();
236
397
  console.log(pc.dim(" Commands:"));
@@ -282,7 +443,7 @@ Examples:
282
443
  process.exit(1);
283
444
  }
284
445
  dim(" No wallet found. Let's set one up first.\n");
285
- const { runSetup } = await import("../setup-j_xQ14-4.js");
446
+ const { runSetup } = await import("../setup-CJwYRd78.js");
286
447
  await runSetup();
287
448
  console.log();
288
449
  wallet = resolveWallet();
@@ -295,7 +456,7 @@ Examples:
295
456
  verbose(`protocol: ${resolvedProtocol ?? "auto-detect"}, maxDeposit: ${maxDeposit}`);
296
457
  let preferredNetwork = config?.defaultNetwork;
297
458
  if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
298
- const { fetchAllBalances } = await import("../wallet-DjixXCHy.js");
459
+ const { fetchAllBalances } = await import("../wallet-CJBRFJw8.js");
299
460
  const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
300
461
  const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
301
462
  const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
@@ -352,7 +513,7 @@ Examples:
352
513
  usedProtocol = "mpp";
353
514
  const elapsedMs = Date.now() - startMs;
354
515
  const spentAmount = mppPayment.amount ? Number(mppPayment.amount) : void 0;
355
- if (mppPayment && isTTY()) info(` MPP session: ${spentAmount != null ? `${spentAmount.toFixed(4)} USDC ` : ""}(${displayNetwork(mppPayment.network)})`);
516
+ if (mppPayment && isTTY()) info(` MPP session: ${spentAmount != null ? `${formatAmount(spentAmount, "USDC")} ` : ""}(${displayNetwork(mppPayment.network)})`);
356
517
  if (isTTY()) dim(` Streamed (${elapsedMs}ms)`);
357
518
  if (mppPayment) {
358
519
  const record = {
@@ -431,6 +592,8 @@ Examples:
431
592
  const txSig = extractTxSignature(response);
432
593
  verbose(`protocol used: ${usedProtocol ?? "none"}`);
433
594
  for (const [k, v] of response.headers) if (/payment|auth|www|x-pay/i.test(k)) verbose(`header ${k}: ${v.slice(0, 200)}`);
595
+ if (!response.ok && response.status !== 402 && isTTY()) if (!payment) dim(" Server returned error before payment was attempted.");
596
+ else dim(" Payment was processed but server returned an error.");
434
597
  if (response.status === 402 && isTTY()) {
435
598
  const detected = detectProtocols(response);
436
599
  const prHeader = response.headers.get("PAYMENT-REQUIRED") ?? response.headers.get("X-PAYMENT-REQUIRED");
@@ -443,13 +606,13 @@ Examples:
443
606
  if (accepts.length > 0) {
444
607
  const cheapest = accepts.reduce((min, a) => Number(a.amount) < Number(min.amount) ? a : min);
445
608
  costNum = Number(cheapest.amount) / 1e6;
446
- costStr = costNum.toFixed(4);
609
+ costStr = formatUsdcValue(costNum);
447
610
  }
448
611
  const hasEvm = accepts.some((a) => a.network.startsWith("eip155:"));
449
612
  const hasSolana = accepts.some((a) => a.network.startsWith("solana:"));
450
613
  const hasMpp = detected.mpp;
451
614
  const hasOther = accepts.some((a) => !a.network.startsWith("eip155:") && !a.network.startsWith("solana:"));
452
- const { fetchAllBalances } = await import("../wallet-DjixXCHy.js");
615
+ const { fetchAllBalances } = await import("../wallet-CJBRFJw8.js");
453
616
  const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
454
617
  const evmUsdc = hasEvm && balances.evm ? Number(balances.evm.usdc) : 0;
455
618
  const solUsdc = hasSolana && balances.sol ? Number(balances.sol.usdc) : 0;
@@ -468,9 +631,9 @@ Examples:
468
631
  if (payment) dim(" Payment was signed and sent but rejected by the server.");
469
632
  else dim(" Payment was not attempted despite sufficient balance.");
470
633
  if (serverReason) dim(` Reason: ${serverReason}`);
471
- if (hasEvm && wallet.evmAddress && evmUsdc > 0) console.error(` Base: ${pc.cyan(wallet.evmAddress)} ${pc.dim(`(${evmUsdc.toFixed(4)} USDC)`)}`);
472
- if (hasMpp && wallet.evmAddress && tempoUsdc > 0) console.error(` Tempo: ${pc.cyan(wallet.evmAddress)} ${pc.dim(`(${tempoUsdc.toFixed(4)} USDC)`)}`);
473
- if (hasSolana && wallet.solanaAddress && solUsdc > 0) console.error(` Solana: ${pc.cyan(wallet.solanaAddress)} ${pc.dim(`(${solUsdc.toFixed(4)} USDC)`)}`);
634
+ if (hasEvm && wallet.evmAddress && evmUsdc > 0) console.error(` Base: ${pc.cyan(wallet.evmAddress)} ${pc.dim(`(${formatAmount(evmUsdc, "USDC")})`)}`);
635
+ if (hasMpp && wallet.evmAddress && tempoUsdc > 0) console.error(` Tempo: ${pc.cyan(wallet.evmAddress)} ${pc.dim(`(${formatAmount(tempoUsdc, "USDC")})`)}`);
636
+ if (hasSolana && wallet.solanaAddress && solUsdc > 0) console.error(` Solana: ${pc.cyan(wallet.solanaAddress)} ${pc.dim(`(${formatAmount(solUsdc, "USDC")})`)}`);
474
637
  console.error();
475
638
  dim(" This may be a temporary server-side issue. Try again in a moment.");
476
639
  console.error();
@@ -480,15 +643,15 @@ Examples:
480
643
  console.error();
481
644
  dim(" Fund your wallet with USDC:");
482
645
  if (hasEvm && wallet.evmAddress) {
483
- const balHint = evmUsdc > 0 ? pc.dim(` (${evmUsdc.toFixed(4)} USDC)`) : "";
646
+ const balHint = evmUsdc > 0 ? pc.dim(` (${formatAmount(evmUsdc, "USDC")})`) : "";
484
647
  console.error(` Base: ${pc.cyan(wallet.evmAddress)}${balHint}`);
485
648
  }
486
649
  if (hasMpp && wallet.evmAddress) {
487
- const balHint = tempoUsdc > 0 ? pc.dim(` (${tempoUsdc.toFixed(4)} USDC)`) : "";
650
+ const balHint = tempoUsdc > 0 ? pc.dim(` (${formatAmount(tempoUsdc, "USDC")})`) : "";
488
651
  console.error(` Tempo: ${pc.cyan(wallet.evmAddress)}${balHint}`);
489
652
  }
490
653
  if (hasSolana && wallet.solanaAddress) {
491
- const balHint = solUsdc > 0 ? pc.dim(` (${solUsdc.toFixed(4)} USDC)`) : "";
654
+ const balHint = solUsdc > 0 ? pc.dim(` (${formatAmount(solUsdc, "USDC")})`) : "";
492
655
  console.error(` Solana: ${pc.cyan(wallet.solanaAddress)}${balHint}`);
493
656
  }
494
657
  if (hasEvm && !wallet.evmAddress) dim(" Base: endpoint accepts EVM but no EVM wallet configured");
@@ -507,8 +670,8 @@ Examples:
507
670
  return;
508
671
  }
509
672
  if (payment && isTTY()) {
510
- if (usedProtocol === "mpp" && mppPayment) info(` Payment: MPP (${displayNetwork(mppPayment.network)})`);
511
- else if (x402Payment) info(` Payment: ${x402Payment.amount ? (Number(x402Payment.amount) / 1e6).toFixed(4) : "?"} USDC (${displayNetwork(x402Payment.network ?? "unknown")})`);
673
+ if (usedProtocol === "mpp" && mppPayment) info(` Payment:${mppPayment.amount ? ` ${formatAmount(Number(mppPayment.amount), "USDC")}` : ""} MPP (${displayNetwork(mppPayment.network)})`);
674
+ else if (x402Payment) info(` Payment: ${x402Payment.amount ? formatAmount(Number(x402Payment.amount) / 1e6, "USDC") : "? USDC"} (${displayNetwork(x402Payment.network ?? "unknown")})`);
512
675
  if (txSig) dim(` Tx: ${txSig}`);
513
676
  }
514
677
  if (isTTY()) dim(` ${response.status} ${response.statusText} (${elapsedMs}ms)`);
@@ -624,7 +787,7 @@ Add to your MCP client config (Claude, Cursor, etc.):
624
787
  const { StreamableHTTPClientTransport } = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
625
788
  const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
626
789
  const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
627
- const { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ToolListChangedNotificationSchema, ResourceListChangedNotificationSchema } = await import("@modelcontextprotocol/sdk/types.js");
790
+ const { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ToolListChangedNotificationSchema, ResourceListChangedNotificationSchema, McpError } = await import("@modelcontextprotocol/sdk/types.js");
628
791
  async function connectTransport(target) {
629
792
  try {
630
793
  const transport = new StreamableHTTPClientTransport(new URL(remoteUrl));
@@ -646,7 +809,7 @@ Add to your MCP client config (Claude, Cursor, etc.):
646
809
  async function startX402Proxy() {
647
810
  let preferredNetwork = config?.defaultNetwork;
648
811
  if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
649
- const { fetchAllBalances } = await import("../wallet-DjixXCHy.js");
812
+ const { fetchAllBalances } = await import("../wallet-CJBRFJw8.js");
650
813
  const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
651
814
  const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
652
815
  const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
@@ -660,15 +823,18 @@ Add to your MCP client config (Claude, Cursor, etc.):
660
823
  spendLimitPerTx: config?.spendLimitPerTx
661
824
  });
662
825
  const { x402MCPClient } = await import("@x402/mcp");
826
+ function warnPayment(accepts, toolName) {
827
+ const accept = accepts?.[0];
828
+ if (accept) warn(` Payment: ${accept.amount ? formatAmount(Number(accept.amount) / 1e6, "USDC") : "? USDC"} on ${displayNetwork(accept.network)} for tool "${toolName}"`);
829
+ }
663
830
  const remoteClient = new Client({
664
831
  name: "x402-proxy",
665
- version: "0.7.0"
832
+ version: "0.8.0"
666
833
  });
667
834
  const x402Mcp = new x402MCPClient(remoteClient, x402PaymentClient, {
668
835
  autoPayment: true,
669
836
  onPaymentRequested: (ctx) => {
670
- const accept = ctx.paymentRequired.accepts?.[0];
671
- if (accept) warn(` Payment: ${accept.amount ? (Number(accept.amount) / 1e6).toFixed(4) : "?"} USDC on ${displayNetwork(accept.network)} for tool "${ctx.toolName}"`);
837
+ warnPayment(ctx.paymentRequired.accepts, ctx.toolName);
672
838
  return true;
673
839
  }
674
840
  });
@@ -701,7 +867,7 @@ Add to your MCP client config (Claude, Cursor, etc.):
701
867
  }
702
868
  const localServer = new Server({
703
869
  name: "x402-proxy",
704
- version: "0.7.0"
870
+ version: "0.8.0"
705
871
  }, { capabilities: {
706
872
  tools: tools.length > 0 ? {} : void 0,
707
873
  resources: remoteResources.length > 0 ? {} : void 0
@@ -714,11 +880,28 @@ Add to your MCP client config (Claude, Cursor, etc.):
714
880
  })) }));
715
881
  localServer.setRequestHandler(CallToolRequestSchema, async (request) => {
716
882
  const { name, arguments: args } = request.params;
717
- const result = await x402Mcp.callTool(name, args ?? {});
718
- return {
719
- content: result.content,
720
- isError: result.isError
721
- };
883
+ try {
884
+ const result = await x402Mcp.callTool(name, args ?? {});
885
+ return {
886
+ content: result.content,
887
+ isError: result.isError
888
+ };
889
+ } catch (err) {
890
+ if (err instanceof McpError && err.code === -32042) {
891
+ const x402PaymentRequired = err.data?.x402;
892
+ if (x402PaymentRequired) {
893
+ const accepts = x402PaymentRequired.accepts;
894
+ warnPayment(accepts, name);
895
+ const paymentPayload = await x402PaymentClient.createPaymentPayload(x402PaymentRequired);
896
+ const result = await x402Mcp.callToolWithPayment(name, args ?? {}, paymentPayload);
897
+ return {
898
+ content: result.content,
899
+ isError: result.isError
900
+ };
901
+ }
902
+ }
903
+ throw err;
904
+ }
722
905
  });
723
906
  if (remoteResources.length > 0) {
724
907
  localServer.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: remoteResources.map((r) => ({
@@ -765,15 +948,24 @@ Add to your MCP client config (Claude, Cursor, etc.):
765
948
  const { privateKeyToAccount } = await import("viem/accounts");
766
949
  const account = privateKeyToAccount(wallet.evmKey);
767
950
  const maxDeposit = config?.mppSessionBudget ?? "1";
951
+ let lastChallengeAmount;
952
+ const wrappedMethods = tempo({
953
+ account,
954
+ maxDeposit
955
+ }).map((m) => ({
956
+ ...m,
957
+ createCredential: async (params) => {
958
+ const req = params.challenge.request;
959
+ if (req.amount) lastChallengeAmount = Number(req.amount) / 10 ** (req.decimals ?? 6);
960
+ return m.createCredential(params);
961
+ }
962
+ }));
768
963
  const remoteClient = new Client({
769
964
  name: "x402-proxy",
770
- version: "0.7.0"
965
+ version: "0.8.0"
771
966
  });
772
967
  await connectTransport(remoteClient);
773
- const mppClient = McpClient.wrap(remoteClient, { methods: [tempo({
774
- account,
775
- maxDeposit
776
- })] });
968
+ const mppClient = McpClient.wrap(remoteClient, { methods: wrappedMethods });
777
969
  let { tools } = await remoteClient.listTools();
778
970
  dim(` ${tools.length} tools available`);
779
971
  let remoteResources = [];
@@ -785,7 +977,7 @@ Add to your MCP client config (Claude, Cursor, etc.):
785
977
  }
786
978
  const localServer = new Server({
787
979
  name: "x402-proxy",
788
- version: "0.7.0"
980
+ version: "0.8.0"
789
981
  }, { capabilities: {
790
982
  tools: tools.length > 0 ? {} : void 0,
791
983
  resources: remoteResources.length > 0 ? {} : void 0
@@ -810,11 +1002,14 @@ Add to your MCP client config (Claude, Cursor, etc.):
810
1002
  net: TEMPO_NETWORK,
811
1003
  from: wallet.evmAddress ?? "unknown",
812
1004
  tx: result.receipt.reference,
1005
+ amount: lastChallengeAmount,
813
1006
  token: "USDC",
814
1007
  label: `mcp:${name}`
815
1008
  };
816
1009
  appendHistory(getHistoryPath(), record);
817
- warn(` MPP payment for tool "${name}" (Tempo)`);
1010
+ const amountStr = lastChallengeAmount !== void 0 ? formatAmount(lastChallengeAmount, "USDC") : "";
1011
+ warn(` MPP payment for tool "${name}" (Tempo)${amountStr ? ` \u00b7 ${amountStr}` : ""}`);
1012
+ lastChallengeAmount = void 0;
818
1013
  }
819
1014
  return {
820
1015
  content: result.content,
@@ -957,7 +1152,7 @@ const walletHistoryCommand = buildCommand({
957
1152
  console.log(line);
958
1153
  }
959
1154
  console.log();
960
- console.log(pc.dim(` Today: ${spend.today.toFixed(4)} USDC | Total: ${spend.total.toFixed(4)} USDC | ${spend.count} transactions`));
1155
+ console.log(pc.dim(` Today: ${formatAmount(spend.today, "USDC")} | Total: ${formatAmount(spend.total, "USDC")} | ${spend.count} transactions`));
961
1156
  console.log();
962
1157
  }
963
1158
  });
@@ -977,6 +1172,15 @@ const routes = buildRouteMap({
977
1172
  defaultCommand: "info",
978
1173
  docs: { brief: "Wallet management" }
979
1174
  }),
1175
+ config: buildRouteMap({
1176
+ routes: {
1177
+ show: configShowCommand,
1178
+ set: configSetCommand,
1179
+ unset: configUnsetCommand
1180
+ },
1181
+ defaultCommand: "show",
1182
+ docs: { brief: "Manage configuration" }
1183
+ }),
980
1184
  setup: setupCommand,
981
1185
  status: statusCommand
982
1186
  },
@@ -985,7 +1189,7 @@ const routes = buildRouteMap({
985
1189
  });
986
1190
  const app = buildApplication(routes, {
987
1191
  name: "x402-proxy",
988
- versionInfo: { currentVersion: "0.7.0" },
1192
+ versionInfo: { currentVersion: "0.8.0" },
989
1193
  scanner: { caseStyle: "allow-kebab-for-camel" }
990
1194
  });
991
1195
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from "node:fs";
3
- import path from "node:path";
4
3
  import os from "node:os";
4
+ import path from "node:path";
5
5
  import { parse, stringify } from "yaml";
6
6
  import { ed25519 } from "@noble/curves/ed25519.js";
7
7
  import { base58 } from "@scure/base";
package/dist/index.js CHANGED
@@ -67,30 +67,40 @@ async function createMppProxyHandler(opts) {
67
67
  const account = privateKeyToAccount(opts.evmKey);
68
68
  const maxDeposit = opts.maxDeposit ?? "1";
69
69
  const paymentQueue = [];
70
+ let lastChallengeAmount;
70
71
  const mppx = Mppx.create({
71
72
  methods: [tempo({
72
73
  account,
73
74
  maxDeposit
74
75
  })],
75
- polyfill: false
76
+ polyfill: false,
77
+ onChallenge: async (challenge) => {
78
+ const req = challenge.request;
79
+ if (req.amount) lastChallengeAmount = (Number(req.amount) / 10 ** (req.decimals ?? 6)).toString();
80
+ }
76
81
  });
77
82
  let session;
78
83
  return {
79
84
  async fetch(input, init) {
80
85
  const response = await mppx.fetch(typeof input === "string" ? input : input.toString(), init);
81
86
  const receiptHeader = response.headers.get("Payment-Receipt");
82
- if (receiptHeader) try {
83
- const receipt = JSON.parse(Buffer.from(receiptHeader, "base64url").toString());
84
- paymentQueue.push({
85
- protocol: "mpp",
86
- network: TEMPO_NETWORK,
87
- receipt
88
- });
89
- } catch {
90
- paymentQueue.push({
91
- protocol: "mpp",
92
- network: TEMPO_NETWORK
93
- });
87
+ if (receiptHeader) {
88
+ try {
89
+ const receipt = JSON.parse(Buffer.from(receiptHeader, "base64url").toString());
90
+ paymentQueue.push({
91
+ protocol: "mpp",
92
+ network: TEMPO_NETWORK,
93
+ amount: lastChallengeAmount,
94
+ receipt
95
+ });
96
+ } catch {
97
+ paymentQueue.push({
98
+ protocol: "mpp",
99
+ network: TEMPO_NETWORK,
100
+ amount: lastChallengeAmount
101
+ });
102
+ }
103
+ lastChallengeAmount = void 0;
94
104
  }
95
105
  return response;
96
106
  },
@@ -189,13 +199,15 @@ function calcSpend(records) {
189
199
  count
190
200
  };
191
201
  }
202
+ /** Format a USDC value with adaptive precision (no token suffix). */
203
+ function formatUsdcValue(amount) {
204
+ if (amount >= .01) return amount.toFixed(2);
205
+ if (amount >= .001) return amount.toFixed(3);
206
+ if (amount >= 1e-4) return amount.toFixed(4);
207
+ return amount.toFixed(6);
208
+ }
192
209
  function formatAmount(amount, token) {
193
- if (token === "USDC") {
194
- if (amount >= .01) return `${amount.toFixed(2)} USDC`;
195
- if (amount >= .001) return `${amount.toFixed(3)} USDC`;
196
- if (amount >= 1e-4) return `${amount.toFixed(4)} USDC`;
197
- return `${amount.toFixed(6)} USDC`;
198
- }
210
+ if (token === "USDC") return `${formatUsdcValue(amount)} USDC`;
199
211
  if (token === "SOL") return `${amount} SOL`;
200
212
  return `${amount} ${token}`;
201
213
  }
@@ -138,13 +138,15 @@ function calcSpend(records) {
138
138
  count
139
139
  };
140
140
  }
141
+ /** Format a USDC value with adaptive precision (no token suffix). */
142
+ function formatUsdcValue(amount) {
143
+ if (amount >= .01) return amount.toFixed(2);
144
+ if (amount >= .001) return amount.toFixed(3);
145
+ if (amount >= 1e-4) return amount.toFixed(4);
146
+ return amount.toFixed(6);
147
+ }
141
148
  function formatAmount(amount, token) {
142
- if (token === "USDC") {
143
- if (amount >= .01) return `${amount.toFixed(2)} USDC`;
144
- if (amount >= .001) return `${amount.toFixed(3)} USDC`;
145
- if (amount >= 1e-4) return `${amount.toFixed(4)} USDC`;
146
- return `${amount.toFixed(6)} USDC`;
147
- }
149
+ if (token === "USDC") return `${formatUsdcValue(amount)} USDC`;
148
150
  if (token === "SOL") return `${amount} SOL`;
149
151
  return `${amount} ${token}`;
150
152
  }
@@ -502,8 +504,8 @@ function createBalanceTool(ctx) {
502
504
  `USDC: ${snap.ui} USDC`,
503
505
  `Available for tools: ${available.toFixed(2)} USDC`,
504
506
  `Reserved for inference: ${INFERENCE_RESERVE.toFixed(2)} USDC`,
505
- `Spent today: ${snap.spend.today.toFixed(4)} USDC`,
506
- `Total spent: ${snap.spend.total.toFixed(4)} USDC (${snap.spend.count} txs)`,
507
+ `Spent today: ${formatAmount(snap.spend.today, "USDC")}`,
508
+ `Total spent: ${formatAmount(snap.spend.total, "USDC")} (${snap.spend.count} txs)`,
507
509
  ...tokenLines.length > 0 ? [`Tokens held: ${tokenLines.join(", ")}`] : []
508
510
  ].join("\n"));
509
511
  } catch (err) {
@@ -724,7 +726,7 @@ function createWalletCommand(ctx) {
724
726
  try {
725
727
  const snap = await getWalletSnapshot(ctx.rpcUrl, walletAddress, ctx.historyPath);
726
728
  const solscanUrl = `https://solscan.io/account/${walletAddress}`;
727
- const lines = [`x402-proxy v0.7.0`];
729
+ const lines = [`x402-proxy v0.8.0`];
728
730
  const defaultModel = ctx.allModels[0];
729
731
  if (defaultModel) lines.push("", `**Model** - ${defaultModel.name} (${defaultModel.provider})`);
730
732
  lines.push("", `**[Wallet](${solscanUrl})**`, `\`${walletAddress}\``);
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { n as setupCommand, t as runSetup } from "./setup-Di_b5Vp9.js";
3
+
4
+ export { runSetup };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { d as saveWalletFile, i as getConfigDirShort, n as deriveSolanaKeypair, o as getWalletPath, r as generateMnemonic, s as isConfigured, t as deriveEvmKeypair, u as saveConfig } from "./derive-ibF2UinV.js";
2
+ import { d as saveWalletFile, i as getConfigDirShort, n as deriveSolanaKeypair, o as getWalletPath, r as generateMnemonic, s as isConfigured, t as deriveEvmKeypair, u as saveConfig } from "./derive-BR6N1ZjI.js";
3
3
  import { buildCommand } from "@stricli/core";
4
4
  import pc from "picocolors";
5
5
  import * as prompts from "@clack/prompts";
@@ -57,7 +57,48 @@ async function runSetup(opts) {
57
57
  solana: sol.address
58
58
  }
59
59
  });
60
- saveConfig({});
60
+ const protocol = await prompts.select({
61
+ message: "Preferred payment protocol?",
62
+ options: [{
63
+ value: "x402",
64
+ label: "x402 - on-chain payments (Base, Solana)"
65
+ }, {
66
+ value: "mpp",
67
+ label: "MPP - streaming micropayments (Tempo)"
68
+ }]
69
+ });
70
+ if (prompts.isCancel(protocol)) {
71
+ prompts.cancel("Setup cancelled.");
72
+ process.exit(0);
73
+ }
74
+ const networkOptions = protocol === "mpp" ? [{
75
+ value: "tempo",
76
+ label: "Tempo"
77
+ }] : [
78
+ {
79
+ value: "auto",
80
+ label: "Auto-detect (pick chain with highest balance)"
81
+ },
82
+ {
83
+ value: "base",
84
+ label: "Base (EVM)"
85
+ },
86
+ {
87
+ value: "solana",
88
+ label: "Solana"
89
+ }
90
+ ];
91
+ const network = await prompts.select({
92
+ message: "Preferred network?",
93
+ options: networkOptions
94
+ });
95
+ if (prompts.isCancel(network)) {
96
+ prompts.cancel("Setup cancelled.");
97
+ process.exit(0);
98
+ }
99
+ const config = { preferredProtocol: protocol };
100
+ if (network !== "auto") config.defaultNetwork = network;
101
+ saveConfig(config);
61
102
  prompts.log.info(`Config directory: ${pc.dim(getConfigDirShort())}`);
62
103
  prompts.log.step("Fund your wallets to start using x402 resources:");
63
104
  prompts.log.message(` Solana (USDC): Send USDC to ${pc.cyan(sol.address)}`);
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { n as statusCommand, t as displayStatus } from "./status-JNGv2Ghp.js";
2
+ import { n as statusCommand, t as displayStatus } from "./status-S3t-XV_M.js";
3
3
 
4
4
  export { displayStatus };
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { _ as formatTxLine, c as resolveWallet, h as calcSpend, l as dim, n as fetchAllBalances, t as balanceLine, v as readHistory } from "./wallet-DxKCHa7U.js";
3
- import { a as getHistoryPath, c as loadConfig, i as getConfigDirShort } from "./derive-ibF2UinV.js";
2
+ import { a as getHistoryPath, c as loadConfig, i as getConfigDirShort } from "./derive-BR6N1ZjI.js";
3
+ import { c as resolveWallet, f as formatAmount, g as dim, h as readHistory, m as formatUsdcValue, n as fetchAllBalances, p as formatTxLine, t as balanceLine, u as calcSpend } from "./wallet-CeY5DPj-.js";
4
4
  import { buildCommand } from "@stricli/core";
5
5
  import pc from "picocolors";
6
6
 
@@ -35,7 +35,7 @@ async function displayStatus() {
35
35
  console.log();
36
36
  if (config.spendLimitDaily) {
37
37
  const pct = config.spendLimitDaily > 0 ? Math.round(spend.today / config.spendLimitDaily * 100) : 0;
38
- dim(` Daily limit: ${spend.today.toFixed(4)} / ${config.spendLimitDaily} USDC (${pct}%)`);
38
+ dim(` Daily limit: ${formatUsdcValue(spend.today)} / ${config.spendLimitDaily} USDC (${pct}%)`);
39
39
  }
40
40
  if (config.spendLimitPerTx) dim(` Per-tx limit: ${config.spendLimitPerTx} USDC`);
41
41
  }
@@ -49,7 +49,7 @@ async function displayStatus() {
49
49
  console.log(line);
50
50
  }
51
51
  console.log();
52
- dim(` Today: ${spend.today.toFixed(4)} USDC | Total: ${spend.total.toFixed(4)} USDC | ${spend.count} tx`);
52
+ dim(` Today: ${formatAmount(spend.today, "USDC")} | Total: ${formatAmount(spend.total, "USDC")} | ${spend.count} tx`);
53
53
  } else dim(" No payment history yet.");
54
54
  console.log();
55
55
  if (config?.defaultNetwork) dim(` Network: ${config.defaultNetwork}`);
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { a as fetchTempoBalances, i as fetchSolanaBalances, n as fetchAllBalances, o as walletInfoCommand, r as fetchEvmBalances, t as balanceLine } from "./wallet-DxKCHa7U.js";
2
+ import { a as fetchTempoBalances, i as fetchSolanaBalances, n as fetchAllBalances, o as walletInfoCommand, r as fetchEvmBalances, t as balanceLine } from "./wallet-CeY5DPj-.js";
3
3
 
4
4
  export { fetchAllBalances };
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { a as getHistoryPath, l as loadWalletFile, n as deriveSolanaKeypair, t as deriveEvmKeypair } from "./derive-ibF2UinV.js";
2
+ import { a as getHistoryPath, l as loadWalletFile, n as deriveSolanaKeypair, t as deriveEvmKeypair } from "./derive-BR6N1ZjI.js";
3
3
  import { buildCommand } from "@stricli/core";
4
4
  import pc from "picocolors";
5
- import { x402Client } from "@x402/fetch";
6
5
  import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
7
6
  import { dirname } from "node:path";
7
+ import { x402Client } from "@x402/fetch";
8
8
  import { ed25519 } from "@noble/curves/ed25519.js";
9
9
  import { base58 } from "@scure/base";
10
10
  import { toClientEvmSigner } from "@x402/evm";
@@ -15,6 +15,24 @@ import { privateKeyToAccount } from "viem/accounts";
15
15
  import { base } from "viem/chains";
16
16
  import { address, getAddressEncoder, getProgramDerivedAddress } from "@solana/kit";
17
17
 
18
+ //#region src/lib/output.ts
19
+ function isTTY() {
20
+ return !!process.stderr.isTTY;
21
+ }
22
+ function info(msg) {
23
+ process.stderr.write(`${isTTY() ? pc.cyan(msg) : msg}\n`);
24
+ }
25
+ function warn(msg) {
26
+ process.stderr.write(`${isTTY() ? pc.yellow(msg) : msg}\n`);
27
+ }
28
+ function error(msg) {
29
+ process.stderr.write(`${isTTY() ? pc.red(`✗ ${msg}`) : `✗ ${msg}`}\n`);
30
+ }
31
+ function dim(msg) {
32
+ process.stderr.write(`${isTTY() ? pc.dim(msg) : msg}\n`);
33
+ }
34
+
35
+ //#endregion
18
36
  //#region src/history.ts
19
37
  const HISTORY_MAX_LINES = 1e3;
20
38
  const HISTORY_KEEP_LINES = 500;
@@ -68,13 +86,15 @@ function calcSpend(records) {
68
86
  count
69
87
  };
70
88
  }
89
+ /** Format a USDC value with adaptive precision (no token suffix). */
90
+ function formatUsdcValue(amount) {
91
+ if (amount >= .01) return amount.toFixed(2);
92
+ if (amount >= .001) return amount.toFixed(3);
93
+ if (amount >= 1e-4) return amount.toFixed(4);
94
+ return amount.toFixed(6);
95
+ }
71
96
  function formatAmount(amount, token) {
72
- if (token === "USDC") {
73
- if (amount >= .01) return `${amount.toFixed(2)} USDC`;
74
- if (amount >= .001) return `${amount.toFixed(3)} USDC`;
75
- if (amount >= 1e-4) return `${amount.toFixed(4)} USDC`;
76
- return `${amount.toFixed(6)} USDC`;
77
- }
97
+ if (token === "USDC") return `${formatUsdcValue(amount)} USDC`;
78
98
  if (token === "SOL") return `${amount} SOL`;
79
99
  return `${amount} ${token}`;
80
100
  }
@@ -138,24 +158,6 @@ function formatTxLine(r, opts) {
138
158
  return ` ${timeStr} ${r.ok ? "" : "✗ "}${parts.join(" · ")}`;
139
159
  }
140
160
 
141
- //#endregion
142
- //#region src/lib/output.ts
143
- function isTTY() {
144
- return !!process.stderr.isTTY;
145
- }
146
- function info(msg) {
147
- process.stderr.write(`${isTTY() ? pc.cyan(msg) : msg}\n`);
148
- }
149
- function warn(msg) {
150
- process.stderr.write(`${isTTY() ? pc.yellow(msg) : msg}\n`);
151
- }
152
- function error(msg) {
153
- process.stderr.write(`${isTTY() ? pc.red(`✗ ${msg}`) : `✗ ${msg}`}\n`);
154
- }
155
- function dim(msg) {
156
- process.stderr.write(`${isTTY() ? pc.dim(msg) : msg}\n`);
157
- }
158
-
159
161
  //#endregion
160
162
  //#region src/lib/resolve-wallet.ts
161
163
  /**
@@ -273,10 +275,10 @@ async function buildX402Client(wallet, opts) {
273
275
  if (daily || perTx) client.registerPolicy((_version, reqs) => {
274
276
  if (daily) {
275
277
  const spend = calcSpend(readHistory(getHistoryPath()));
276
- if (spend.today >= daily) throw new Error(`Daily spend limit reached (${spend.today.toFixed(4)}/${daily} USDC)`);
278
+ if (spend.today >= daily) throw new Error(`Daily spend limit reached (${formatUsdcValue(spend.today)}/${daily} USDC)`);
277
279
  const remaining = daily - spend.today;
278
280
  reqs = reqs.filter((r) => Number(r.amount) / 1e6 <= remaining);
279
- if (reqs.length === 0) throw new Error(`Daily spend limit of ${daily} USDC would be exceeded (${spend.today.toFixed(4)} spent today)`);
281
+ if (reqs.length === 0) throw new Error(`Daily spend limit of ${daily} USDC would be exceeded (${formatUsdcValue(spend.today)} spent today)`);
280
282
  }
281
283
  if (perTx) {
282
284
  const before = reqs.length;
@@ -320,7 +322,7 @@ async function fetchEvmBalances(address) {
320
322
  }, "latest"])]);
321
323
  return {
322
324
  eth: ethRes.result ? (Number(BigInt(ethRes.result)) / 0xde0b6b3a7640000).toFixed(6) : "?",
323
- usdc: usdcRes.result ? (Number(BigInt(usdcRes.result)) / 1e6).toFixed(4) : "?"
325
+ usdc: usdcRes.result ? formatUsdcValue(Number(BigInt(usdcRes.result)) / 1e6) : "?"
324
326
  };
325
327
  }
326
328
  async function fetchTempoBalances(address) {
@@ -328,7 +330,7 @@ async function fetchTempoBalances(address) {
328
330
  to: USDC_TEMPO,
329
331
  data: `0x70a08231${address.slice(2).padStart(64, "0")}`
330
332
  }, "latest"]);
331
- return { usdc: res.result ? (Number(BigInt(res.result)) / 1e6).toFixed(4) : "?" };
333
+ return { usdc: res.result ? formatUsdcValue(Number(BigInt(res.result)) / 1e6) : "?" };
332
334
  }
333
335
  async function getUsdcAta(owner) {
334
336
  const encoder = getAddressEncoder();
@@ -349,7 +351,7 @@ async function fetchSolanaBalances(ownerAddress) {
349
351
  const usdcVal = usdcRes.result?.value;
350
352
  return {
351
353
  sol,
352
- usdc: usdcVal ? Number(usdcVal.uiAmountString).toFixed(4) : usdcVal === void 0 ? "?" : "0.0000"
354
+ usdc: usdcVal ? formatUsdcValue(Number(usdcVal.uiAmountString)) : usdcVal === void 0 ? "?" : "0"
353
355
  };
354
356
  }
355
357
  function balanceLine(usdc, native, nativeSymbol) {
@@ -404,9 +406,9 @@ const walletInfoCommand = buildCommand({
404
406
  const bal = sol ? balanceLine(sol.usdc, sol.sol, "SOL") : pc.dim(" (network error)");
405
407
  console.log(` Solana: ${pc.green(wallet.solanaAddress)}${bal}`);
406
408
  }
407
- const evmEmpty = !evm || evm.usdc === "0.0000";
408
- const solEmpty = !sol || sol.usdc === "0.0000";
409
- const tempoEmpty = !tempo || tempo.usdc === "0.0000";
409
+ const evmEmpty = !evm || Number(evm.usdc) === 0;
410
+ const solEmpty = !sol || Number(sol.usdc) === 0;
411
+ const tempoEmpty = !tempo || Number(tempo.usdc) === 0;
410
412
  if (evmEmpty && solEmpty && tempoEmpty) {
411
413
  console.log();
412
414
  dim(" Send USDC to any address above to start using paid APIs.");
@@ -422,7 +424,7 @@ const walletInfoCommand = buildCommand({
422
424
  console.log(line);
423
425
  }
424
426
  console.log();
425
- console.log(pc.dim(` Today: ${spend.today.toFixed(4)} USDC | Total: ${spend.total.toFixed(4)} USDC | ${spend.count} tx`));
427
+ console.log(pc.dim(` Today: ${formatAmount(spend.today, "USDC")} | Total: ${formatAmount(spend.total, "USDC")} | ${spend.count} tx`));
426
428
  } else dim(" No transactions yet.");
427
429
  console.log();
428
430
  console.log(pc.dim(" See also: wallet history, wallet export-key"));
@@ -431,4 +433,4 @@ const walletInfoCommand = buildCommand({
431
433
  });
432
434
 
433
435
  //#endregion
434
- export { formatTxLine as _, fetchTempoBalances as a, resolveWallet as c, info as d, isTTY as f, displayNetwork as g, calcSpend as h, fetchSolanaBalances as i, dim as l, appendHistory as m, fetchAllBalances as n, walletInfoCommand as o, warn as p, fetchEvmBalances as r, buildX402Client as s, balanceLine as t, error as u, readHistory as v };
436
+ export { error as _, fetchTempoBalances as a, warn as b, resolveWallet as c, displayNetwork as d, formatAmount as f, dim as g, readHistory as h, fetchSolanaBalances as i, appendHistory as l, formatUsdcValue as m, fetchAllBalances as n, walletInfoCommand as o, formatTxLine as p, fetchEvmBalances as r, buildX402Client as s, balanceLine as t, calcSpend as u, info as v, isTTY as y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402-proxy",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "curl for x402 paid APIs. Auto-pays any endpoint on Base, Solana, and Tempo. Also works as an OpenClaw plugin.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
package/skills/SKILL.md CHANGED
@@ -5,7 +5,7 @@ description: Use x402-proxy CLI for consuming and debugging x402 and MPP paid AP
5
5
 
6
6
  # x402-proxy
7
7
 
8
- `curl` for x402 paid APIs. Auto-pays HTTP 402 responses with USDC on Base, Solana, and Tempo (via MPP).
8
+ `curl` for x402 and MPP paid APIs. Auto-pays HTTP 402 responses with USDC on Base, Solana, and [Tempo](https://tempo.xyz/). Supports one-time payments (x402, MPP charge) and pay-per-token streaming (MPP sessions).
9
9
 
10
10
  ## Quick start
11
11
 
@@ -45,6 +45,9 @@ x402-proxy mcp <url> # MCP stdio proxy for AI agents
45
45
  x402-proxy setup # wallet onboarding wizard
46
46
  x402-proxy setup --force # re-run setup (overwrite existing wallet)
47
47
  x402-proxy status # config + wallet + daily spend summary
48
+ x402-proxy config # show current configuration
49
+ x402-proxy config set <key> <value> # set a config value
50
+ x402-proxy config unset <key> # remove a config value
48
51
  x402-proxy wallet # show addresses and USDC balances
49
52
  x402-proxy wallet history # payment log
50
53
  x402-proxy wallet history --limit 5 # last 5 payments
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env node
2
- import { n as setupCommand, t as runSetup } from "./setup-hJGkO2Lo.js";
3
-
4
- export { runSetup };