x402-proxy 0.5.2 → 0.7.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/dist/bin/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
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-BMYYtAP6.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-BoH_1kIH.js";
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";
6
6
  import { buildApplication, buildCommand, buildRouteMap, run } from "@stricli/core";
7
7
  import pc from "picocolors";
8
8
  import { decodePaymentResponseHeader, wrapFetchWithPayment } from "@x402/fetch";
@@ -11,25 +11,35 @@ import * as prompts from "@clack/prompts";
11
11
 
12
12
  //#region src/handler.ts
13
13
  /**
14
+ * Detect which payment protocols a 402 response advertises.
15
+ * - x402: PAYMENT-REQUIRED or X-PAYMENT-REQUIRED header
16
+ * - MPP: WWW-Authenticate header with Payment scheme
17
+ */
18
+ function detectProtocols(response) {
19
+ const pr = response.headers.get("PAYMENT-REQUIRED") ?? response.headers.get("X-PAYMENT-REQUIRED");
20
+ const wwwAuth = response.headers.get("WWW-Authenticate");
21
+ return {
22
+ x402: !!pr,
23
+ mpp: !!(wwwAuth && /^Payment\b/i.test(wwwAuth.trim()))
24
+ };
25
+ }
26
+ /**
14
27
  * Extract the on-chain transaction signature from an x402 payment response header.
15
28
  */
16
29
  function extractTxSignature(response) {
17
- const header = response.headers.get("PAYMENT-RESPONSE") ?? response.headers.get("X-PAYMENT-RESPONSE");
18
- if (!header) return void 0;
19
- try {
20
- return decodePaymentResponseHeader(header).transaction ?? void 0;
30
+ const x402Header = response.headers.get("PAYMENT-RESPONSE") ?? response.headers.get("X-PAYMENT-RESPONSE");
31
+ if (x402Header) try {
32
+ return decodePaymentResponseHeader(x402Header).transaction ?? void 0;
33
+ } catch {}
34
+ const mppHeader = response.headers.get("Payment-Receipt");
35
+ if (mppHeader) try {
36
+ return JSON.parse(Buffer.from(mppHeader, "base64url").toString()).reference ?? void 0;
21
37
  } catch {
22
38
  return;
23
39
  }
24
40
  }
25
41
  /**
26
42
  * Create an x402 proxy handler that wraps fetch with automatic payment.
27
- *
28
- * Chain-agnostic: accepts a pre-configured x402Client with any registered
29
- * schemes (SVM, EVM, etc). The handler captures payment info via the
30
- * onAfterPaymentCreation hook. Callers use `x402Fetch` for requests that
31
- * may require x402 payment, and `shiftPayment` to retrieve captured
32
- * payment info after each call.
33
43
  */
34
44
  function createX402ProxyHandler(opts) {
35
45
  const { client } = opts;
@@ -37,6 +47,7 @@ function createX402ProxyHandler(opts) {
37
47
  client.onAfterPaymentCreation(async (hookCtx) => {
38
48
  const raw = hookCtx.selectedRequirements.amount;
39
49
  paymentQueue.push({
50
+ protocol: "x402",
40
51
  network: hookCtx.selectedRequirements.network,
41
52
  payTo: hookCtx.selectedRequirements.payTo,
42
53
  amount: raw,
@@ -48,6 +59,84 @@ function createX402ProxyHandler(opts) {
48
59
  shiftPayment: () => paymentQueue.shift()
49
60
  };
50
61
  }
62
+ const TEMPO_NETWORK = "eip155:4217";
63
+ /**
64
+ * Create an MPP proxy handler using mppx client.
65
+ * Dynamically imports mppx/client to keep startup fast.
66
+ */
67
+ async function createMppProxyHandler(opts) {
68
+ const { Mppx, tempo } = await import("mppx/client");
69
+ const { privateKeyToAccount } = await import("viem/accounts");
70
+ const account = privateKeyToAccount(opts.evmKey);
71
+ const maxDeposit = opts.maxDeposit ?? "1";
72
+ const paymentQueue = [];
73
+ const mppx = Mppx.create({
74
+ methods: [tempo({
75
+ account,
76
+ maxDeposit
77
+ })],
78
+ polyfill: false
79
+ });
80
+ let session;
81
+ return {
82
+ async fetch(input, init) {
83
+ const response = await mppx.fetch(typeof input === "string" ? input : input.toString(), init);
84
+ 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
+ });
97
+ }
98
+ return response;
99
+ },
100
+ async sse(input, init) {
101
+ session ??= tempo.session({
102
+ account,
103
+ maxDeposit
104
+ });
105
+ const url = typeof input === "string" ? input : input.toString();
106
+ const iterable = await session.sse(url, init);
107
+ paymentQueue.push({
108
+ protocol: "mpp",
109
+ network: TEMPO_NETWORK,
110
+ intent: "session"
111
+ });
112
+ return iterable;
113
+ },
114
+ shiftPayment: () => paymentQueue.shift(),
115
+ async close() {
116
+ if (session?.opened) {
117
+ const receipt = await session.close();
118
+ if (receipt) {
119
+ const spentUsdc = receipt.spent ? (Number(receipt.spent) / 1e6).toString() : void 0;
120
+ paymentQueue.push({
121
+ protocol: "mpp",
122
+ network: TEMPO_NETWORK,
123
+ intent: "session",
124
+ amount: spentUsdc,
125
+ channelId: session.channelId ?? void 0,
126
+ receipt: {
127
+ method: receipt.method,
128
+ reference: receipt.reference,
129
+ status: receipt.status,
130
+ timestamp: receipt.timestamp,
131
+ acceptedCumulative: receipt.acceptedCumulative,
132
+ txHash: receipt.txHash
133
+ }
134
+ });
135
+ }
136
+ }
137
+ }
138
+ };
139
+ }
51
140
 
52
141
  //#endregion
53
142
  //#region src/commands/fetch.ts
@@ -96,7 +185,13 @@ Examples:
96
185
  },
97
186
  network: {
98
187
  kind: "parsed",
99
- brief: "Require specific network (base, solana)",
188
+ brief: "Require specific network (base, solana, tempo)",
189
+ parse: String,
190
+ optional: true
191
+ },
192
+ protocol: {
193
+ kind: "parsed",
194
+ brief: "Payment protocol (x402, mpp)",
100
195
  parse: String,
101
196
  optional: true
102
197
  },
@@ -104,6 +199,11 @@ Examples:
104
199
  kind: "boolean",
105
200
  brief: "Force JSON output",
106
201
  default: false
202
+ },
203
+ verbose: {
204
+ kind: "boolean",
205
+ brief: "Show debug details (protocol negotiation, headers, payment flow)",
206
+ default: false
107
207
  }
108
208
  },
109
209
  positional: {
@@ -116,9 +216,21 @@ Examples:
116
216
  }
117
217
  },
118
218
  async func(flags, url) {
219
+ const verbose = (msg) => {
220
+ if (flags.verbose) dim(` [verbose] ${msg}`);
221
+ };
222
+ const closeMppSession = async (handler) => {
223
+ verbose("closing MPP session...");
224
+ try {
225
+ await handler.close();
226
+ verbose("session closed successfully");
227
+ } catch (closeErr) {
228
+ verbose(`session close failed: ${closeErr instanceof Error ? closeErr.message : String(closeErr)}`);
229
+ }
230
+ };
119
231
  if (!url) {
120
232
  if (isConfigured()) {
121
- const { displayStatus } = await import("../status-BVIU3-b6.js");
233
+ const { displayStatus } = await import("../status-w5y-fhhe.js");
122
234
  await displayStatus();
123
235
  console.log();
124
236
  console.log(pc.dim(" Commands:"));
@@ -170,28 +282,26 @@ Examples:
170
282
  process.exit(1);
171
283
  }
172
284
  dim(" No wallet found. Let's set one up first.\n");
173
- const { runSetup } = await import("../setup-Crq9TylJ.js");
285
+ const { runSetup } = await import("../setup-j_xQ14-4.js");
174
286
  await runSetup();
175
287
  console.log();
176
288
  wallet = resolveWallet();
177
289
  if (wallet.source === "none") return;
178
290
  }
179
291
  const config = loadConfig();
292
+ const resolvedProtocol = flags.protocol ?? config?.preferredProtocol;
293
+ const maxDeposit = config?.mppSessionBudget ?? "1";
294
+ verbose(`wallet source: ${wallet.source}`);
295
+ verbose(`protocol: ${resolvedProtocol ?? "auto-detect"}, maxDeposit: ${maxDeposit}`);
180
296
  let preferredNetwork = config?.defaultNetwork;
181
297
  if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
182
- const { fetchEvmBalances, fetchSolanaBalances } = await import("../wallet-BoY0fgX7.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;
298
+ const { fetchAllBalances } = await import("../wallet-DjixXCHy.js");
299
+ const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
300
+ const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
301
+ const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
186
302
  if (evmUsdc > solUsdc) preferredNetwork = "base";
187
303
  else if (solUsdc > evmUsdc) preferredNetwork = "solana";
188
304
  }
189
- const { x402Fetch, shiftPayment } = createX402ProxyHandler({ client: await buildX402Client(wallet, {
190
- preferredNetwork,
191
- network: flags.network,
192
- spendLimitDaily: config?.spendLimitDaily,
193
- spendLimitPerTx: config?.spendLimitPerTx
194
- }) });
195
305
  const headers = new Headers();
196
306
  if (flags.header) for (const h of flags.header) {
197
307
  const idx = h.indexOf(":");
@@ -200,22 +310,129 @@ Examples:
200
310
  const method = flags.method || "GET";
201
311
  const init = {
202
312
  method,
203
- headers
313
+ headers: Object.fromEntries(headers.entries())
204
314
  };
205
315
  if (flags.body) init.body = flags.body;
206
316
  if (isTTY()) dim(` ${method} ${parsedUrl.toString()}`);
207
317
  const startMs = Date.now();
208
318
  let response;
319
+ let x402Payment;
320
+ let mppPayment;
321
+ let usedProtocol;
209
322
  try {
210
- response = await x402Fetch(parsedUrl.toString(), init);
323
+ if (resolvedProtocol === "mpp") {
324
+ if (!wallet.evmKey) {
325
+ error("MPP requires an EVM wallet. Configure one with: npx x402-proxy setup");
326
+ process.exit(1);
327
+ }
328
+ const mppHandler = await createMppProxyHandler({
329
+ evmKey: wallet.evmKey,
330
+ maxDeposit
331
+ });
332
+ const isStreamingRequest = flags.body != null && /"stream"\s*:\s*true/.test(flags.body);
333
+ verbose(`mpp handler created, streaming: ${isStreamingRequest}`);
334
+ if (isStreamingRequest) {
335
+ try {
336
+ verbose("opening SSE session...");
337
+ const tokens = await mppHandler.sse(parsedUrl.toString(), init);
338
+ verbose("SSE stream opened, reading tokens...");
339
+ for await (const token of tokens) process.stdout.write(token);
340
+ verbose("SSE stream complete");
341
+ } finally {
342
+ await closeMppSession(mppHandler);
343
+ }
344
+ mppHandler.shiftPayment();
345
+ const closeReceipt = mppHandler.shiftPayment();
346
+ verbose(closeReceipt ? `close receipt: amount=${closeReceipt.amount ?? "none"}, channelId=${closeReceipt.channelId ?? "none"}, txHash=${closeReceipt.receipt?.txHash ?? "none"}` : "no close receipt (session close may have failed)");
347
+ mppPayment = closeReceipt ?? {
348
+ protocol: "mpp",
349
+ network: TEMPO_NETWORK,
350
+ intent: "session"
351
+ };
352
+ usedProtocol = "mpp";
353
+ const elapsedMs = Date.now() - startMs;
354
+ 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)})`);
356
+ if (isTTY()) dim(` Streamed (${elapsedMs}ms)`);
357
+ if (mppPayment) {
358
+ const record = {
359
+ t: Date.now(),
360
+ ok: true,
361
+ kind: "mpp_payment",
362
+ net: mppPayment.network,
363
+ from: wallet.evmAddress ?? "unknown",
364
+ tx: mppPayment.receipt?.reference ?? mppPayment.channelId,
365
+ amount: spentAmount,
366
+ token: "USDC",
367
+ ms: elapsedMs,
368
+ label: parsedUrl.hostname
369
+ };
370
+ appendHistory(getHistoryPath(), record);
371
+ }
372
+ if (isTTY()) process.stdout.write("\n");
373
+ return;
374
+ }
375
+ verbose("sending non-streaming MPP request...");
376
+ try {
377
+ response = await mppHandler.fetch(parsedUrl.toString(), init);
378
+ verbose(`response: ${response.status} ${response.statusText}`);
379
+ } finally {
380
+ await closeMppSession(mppHandler);
381
+ }
382
+ mppPayment = mppHandler.shiftPayment();
383
+ usedProtocol = "mpp";
384
+ } else if (resolvedProtocol === "x402") {
385
+ const handler = createX402ProxyHandler({ client: await buildX402Client(wallet, {
386
+ preferredNetwork,
387
+ network: flags.network,
388
+ spendLimitDaily: config?.spendLimitDaily,
389
+ spendLimitPerTx: config?.spendLimitPerTx
390
+ }) });
391
+ response = await handler.x402Fetch(parsedUrl.toString(), init);
392
+ x402Payment = handler.shiftPayment();
393
+ usedProtocol = "x402";
394
+ } else {
395
+ const handler = createX402ProxyHandler({ client: await buildX402Client(wallet, {
396
+ preferredNetwork,
397
+ network: flags.network,
398
+ spendLimitDaily: config?.spendLimitDaily,
399
+ spendLimitPerTx: config?.spendLimitPerTx
400
+ }) });
401
+ response = await handler.x402Fetch(parsedUrl.toString(), init);
402
+ x402Payment = handler.shiftPayment();
403
+ usedProtocol = "x402";
404
+ if (response.status === 402 && wallet.evmKey) {
405
+ const detected = detectProtocols(response);
406
+ verbose(`auto-detect: x402=${detected.x402}, mpp=${detected.mpp}`);
407
+ if (detected.mpp) {
408
+ verbose("falling through to MPP...");
409
+ const mppHandler = await createMppProxyHandler({
410
+ evmKey: wallet.evmKey,
411
+ maxDeposit
412
+ });
413
+ try {
414
+ response = await mppHandler.fetch(parsedUrl.toString(), init);
415
+ verbose(`MPP response: ${response.status} ${response.statusText}`);
416
+ } finally {
417
+ await closeMppSession(mppHandler);
418
+ }
419
+ mppPayment = mppHandler.shiftPayment();
420
+ x402Payment = void 0;
421
+ usedProtocol = "mpp";
422
+ }
423
+ }
424
+ }
211
425
  } catch (err) {
212
426
  error(`Request failed: ${err instanceof Error ? err.message : String(err)}`);
213
427
  process.exit(1);
214
428
  }
215
429
  const elapsedMs = Date.now() - startMs;
216
- const payment = shiftPayment();
430
+ const payment = x402Payment ?? mppPayment;
217
431
  const txSig = extractTxSignature(response);
432
+ verbose(`protocol used: ${usedProtocol ?? "none"}`);
433
+ for (const [k, v] of response.headers) if (/payment|auth|www|x-pay/i.test(k)) verbose(`header ${k}: ${v.slice(0, 200)}`);
218
434
  if (response.status === 402 && isTTY()) {
435
+ const detected = detectProtocols(response);
219
436
  const prHeader = response.headers.get("PAYMENT-REQUIRED") ?? response.headers.get("X-PAYMENT-REQUIRED");
220
437
  let accepts = [];
221
438
  if (prHeader) try {
@@ -230,19 +447,14 @@ Examples:
230
447
  }
231
448
  const hasEvm = accepts.some((a) => a.network.startsWith("eip155:"));
232
449
  const hasSolana = accepts.some((a) => a.network.startsWith("solana:"));
450
+ const hasMpp = detected.mpp;
233
451
  const hasOther = accepts.some((a) => !a.network.startsWith("eip155:") && !a.network.startsWith("solana:"));
234
- const { fetchEvmBalances, fetchSolanaBalances } = await import("../wallet-BoY0fgX7.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) {
452
+ const { fetchAllBalances } = await import("../wallet-DjixXCHy.js");
453
+ const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
454
+ const evmUsdc = hasEvm && balances.evm ? Number(balances.evm.usdc) : 0;
455
+ const solUsdc = hasSolana && balances.sol ? Number(balances.sol.usdc) : 0;
456
+ const tempoUsdc = hasMpp && balances.tempo ? Number(balances.tempo.usdc) : 0;
457
+ if (hasEvm && evmUsdc >= costNum || hasSolana && solUsdc >= costNum || hasMpp && tempoUsdc >= costNum) {
246
458
  let serverReason;
247
459
  try {
248
460
  const body = await response.text();
@@ -257,24 +469,30 @@ Examples:
257
469
  else dim(" Payment was not attempted despite sufficient balance.");
258
470
  if (serverReason) dim(` Reason: ${serverReason}`);
259
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)`)}`);
260
473
  if (hasSolana && wallet.solanaAddress && solUsdc > 0) console.error(` Solana: ${pc.cyan(wallet.solanaAddress)} ${pc.dim(`(${solUsdc.toFixed(4)} USDC)`)}`);
261
474
  console.error();
262
475
  dim(" This may be a temporary server-side issue. Try again in a moment.");
263
476
  console.error();
264
477
  } else {
265
478
  error(`Payment required: ${costStr} USDC`);
266
- if (hasEvm || hasSolana) {
479
+ if (hasEvm || hasSolana || hasMpp) {
267
480
  console.error();
268
481
  dim(" Fund your wallet with USDC:");
269
482
  if (hasEvm && wallet.evmAddress) {
270
483
  const balHint = evmUsdc > 0 ? pc.dim(` (${evmUsdc.toFixed(4)} USDC)`) : "";
271
484
  console.error(` Base: ${pc.cyan(wallet.evmAddress)}${balHint}`);
272
485
  }
486
+ if (hasMpp && wallet.evmAddress) {
487
+ const balHint = tempoUsdc > 0 ? pc.dim(` (${tempoUsdc.toFixed(4)} USDC)`) : "";
488
+ console.error(` Tempo: ${pc.cyan(wallet.evmAddress)}${balHint}`);
489
+ }
273
490
  if (hasSolana && wallet.solanaAddress) {
274
491
  const balHint = solUsdc > 0 ? pc.dim(` (${solUsdc.toFixed(4)} USDC)`) : "";
275
492
  console.error(` Solana: ${pc.cyan(wallet.solanaAddress)}${balHint}`);
276
493
  }
277
494
  if (hasEvm && !wallet.evmAddress) dim(" Base: endpoint accepts EVM but no EVM wallet configured");
495
+ if (hasMpp && !wallet.evmAddress) dim(" Tempo: endpoint accepts MPP but no EVM wallet configured");
278
496
  if (hasSolana && !wallet.solanaAddress) dim(" Solana: endpoint accepts Solana but no Solana wallet configured");
279
497
  } else if (hasOther) {
280
498
  const networks = [...new Set(accepts.map((a) => a.network))].join(", ");
@@ -289,21 +507,35 @@ Examples:
289
507
  return;
290
508
  }
291
509
  if (payment && isTTY()) {
292
- info(` Payment: ${payment.amount ? (Number(payment.amount) / 1e6).toFixed(4) : "?"} USDC (${displayNetwork(payment.network ?? "unknown")})`);
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")})`);
293
512
  if (txSig) dim(` Tx: ${txSig}`);
294
513
  }
295
514
  if (isTTY()) dim(` ${response.status} ${response.statusText} (${elapsedMs}ms)`);
296
- if (payment) {
297
- ensureConfigDir();
515
+ if (x402Payment) {
298
516
  const record = {
299
517
  t: Date.now(),
300
518
  ok: response.ok,
301
519
  kind: "x402_payment",
302
- net: payment.network ?? "unknown",
520
+ net: x402Payment.network ?? "unknown",
303
521
  from: wallet.evmAddress ?? wallet.solanaAddress ?? "unknown",
304
- to: payment.payTo,
522
+ to: x402Payment.payTo,
305
523
  tx: txSig,
306
- amount: payment.amount ? Number(payment.amount) / 1e6 : void 0,
524
+ amount: x402Payment.amount ? Number(x402Payment.amount) / 1e6 : void 0,
525
+ token: "USDC",
526
+ ms: elapsedMs,
527
+ label: parsedUrl.hostname
528
+ };
529
+ appendHistory(getHistoryPath(), record);
530
+ } else if (mppPayment) {
531
+ const record = {
532
+ t: Date.now(),
533
+ ok: response.ok,
534
+ kind: "mpp_payment",
535
+ net: mppPayment.network,
536
+ from: wallet.evmAddress ?? "unknown",
537
+ tx: mppPayment.receipt?.reference ?? txSig,
538
+ amount: mppPayment.amount ? Number(mppPayment.amount) : void 0,
307
539
  token: "USDC",
308
540
  ms: elapsedMs,
309
541
  label: parsedUrl.hostname
@@ -330,8 +562,8 @@ Examples:
330
562
  //#region src/commands/mcp.ts
331
563
  const mcpCommand = buildCommand({
332
564
  docs: {
333
- brief: "Start MCP stdio proxy with x402 payment",
334
- fullDescription: `Start an MCP stdio proxy with automatic x402 payment for AI agents.
565
+ brief: "Start MCP stdio proxy with automatic payment",
566
+ fullDescription: `Start an MCP stdio proxy with automatic payment (x402 or MPP) for AI agents.
335
567
 
336
568
  Add to your MCP client config (Claude, Cursor, etc.):
337
569
  "command": "npx",
@@ -354,7 +586,13 @@ Add to your MCP client config (Claude, Cursor, etc.):
354
586
  },
355
587
  network: {
356
588
  kind: "parsed",
357
- brief: "Require specific network (base, solana)",
589
+ brief: "Require specific network (base, solana, tempo)",
590
+ parse: String,
591
+ optional: true
592
+ },
593
+ protocol: {
594
+ kind: "parsed",
595
+ brief: "Payment protocol (x402, mpp)",
358
596
  parse: String,
359
597
  optional: true
360
598
  }
@@ -380,132 +618,244 @@ Add to your MCP client config (Claude, Cursor, etc.):
380
618
  if (wallet.evmAddress) dim(` EVM: ${wallet.evmAddress}`);
381
619
  if (wallet.solanaAddress) dim(` Solana: ${wallet.solanaAddress}`);
382
620
  const config = loadConfig();
383
- let preferredNetwork = config?.defaultNetwork;
384
- if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
385
- const { fetchEvmBalances, fetchSolanaBalances } = await import("../wallet-BoY0fgX7.js");
386
- const [evmBal, solBal] = await Promise.allSettled([fetchEvmBalances(wallet.evmAddress), fetchSolanaBalances(wallet.solanaAddress)]);
387
- const evmUsdc = evmBal.status === "fulfilled" ? Number(evmBal.value?.usdc ?? 0) : 0;
388
- const solUsdc = solBal.status === "fulfilled" ? Number(solBal.value?.usdc ?? 0) : 0;
389
- if (evmUsdc > solUsdc) preferredNetwork = "base";
390
- else if (solUsdc > evmUsdc) preferredNetwork = "solana";
391
- }
392
- const x402PaymentClient = await buildX402Client(wallet, {
393
- preferredNetwork,
394
- network: flags.network,
395
- spendLimitDaily: config?.spendLimitDaily,
396
- spendLimitPerTx: config?.spendLimitPerTx
397
- });
621
+ const resolvedProtocol = flags.protocol ?? config?.preferredProtocol ?? "x402";
398
622
  const { Client } = await import("@modelcontextprotocol/sdk/client/index.js");
399
623
  const { SSEClientTransport } = await import("@modelcontextprotocol/sdk/client/sse.js");
400
624
  const { StreamableHTTPClientTransport } = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
401
625
  const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
402
626
  const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
403
- const { x402MCPClient } = await import("@x402/mcp");
404
- const remoteClient = new Client({
405
- name: "x402-proxy",
406
- version: "0.5.2"
407
- });
408
- const x402Mcp = new x402MCPClient(remoteClient, x402PaymentClient, {
409
- autoPayment: true,
410
- onPaymentRequested: (ctx) => {
411
- const accept = ctx.paymentRequired.accepts?.[0];
412
- if (accept) warn(` Payment: ${accept.amount ? (Number(accept.amount) / 1e6).toFixed(4) : "?"} USDC on ${displayNetwork(accept.network)} for tool "${ctx.toolName}"`);
413
- return true;
627
+ const { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ToolListChangedNotificationSchema, ResourceListChangedNotificationSchema } = await import("@modelcontextprotocol/sdk/types.js");
628
+ async function connectTransport(target) {
629
+ try {
630
+ const transport = new StreamableHTTPClientTransport(new URL(remoteUrl));
631
+ await target.connect(transport);
632
+ dim(" Connected via StreamableHTTP");
633
+ return;
634
+ } catch {}
635
+ try {
636
+ const transport = new SSEClientTransport(new URL(remoteUrl));
637
+ await target.connect(transport);
638
+ dim(" Connected via SSE");
639
+ } catch (err) {
640
+ error(`Failed to connect to ${remoteUrl}: ${err instanceof Error ? err.message : String(err)}`);
641
+ process.exit(1);
414
642
  }
415
- });
416
- x402Mcp.onAfterPayment(async (ctx) => {
417
- ensureConfigDir();
418
- const accepted = ctx.paymentPayload.accepted;
419
- const tx = ctx.settleResponse?.transaction;
420
- const record = {
421
- t: Date.now(),
422
- ok: true,
423
- kind: "x402_payment",
424
- net: accepted?.network ?? "unknown",
425
- from: wallet.evmAddress ?? wallet.solanaAddress ?? "unknown",
426
- to: accepted?.payTo,
427
- tx: typeof tx === "string" ? tx : void 0,
428
- amount: accepted?.amount ? Number(accepted.amount) / 1e6 : void 0,
429
- token: "USDC",
430
- label: `mcp:${ctx.toolName}`
431
- };
432
- appendHistory(getHistoryPath(), record);
433
- });
434
- let connected = false;
435
- try {
436
- const transport = new StreamableHTTPClientTransport(new URL(remoteUrl));
437
- await x402Mcp.connect(transport);
438
- connected = true;
439
- dim(" Connected via StreamableHTTP");
440
- } catch {}
441
- if (!connected) try {
442
- const transport = new SSEClientTransport(new URL(remoteUrl));
443
- await x402Mcp.connect(transport);
444
- connected = true;
445
- dim(" Connected via SSE");
446
- } catch (err) {
447
- error(`Failed to connect to ${remoteUrl}: ${err instanceof Error ? err.message : String(err)}`);
448
- process.exit(1);
449
643
  }
450
- let { tools } = await x402Mcp.listTools();
451
- dim(` ${tools.length} tools available`);
452
- let remoteResources = [];
453
- try {
454
- remoteResources = (await x402Mcp.listResources()).resources;
455
- if (remoteResources.length > 0) dim(` ${remoteResources.length} resources available`);
456
- } catch {
457
- dim(" Resources not available from remote");
458
- }
459
- const localServer = new Server({
460
- name: "x402-proxy",
461
- version: "0.5.2"
462
- }, { capabilities: {
463
- tools: tools.length > 0 ? {} : void 0,
464
- resources: remoteResources.length > 0 ? {} : void 0
465
- } });
466
- const { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ToolListChangedNotificationSchema, ResourceListChangedNotificationSchema } = await import("@modelcontextprotocol/sdk/types.js");
467
- localServer.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: tools.map((t) => ({
468
- name: t.name,
469
- description: t.description,
470
- inputSchema: t.inputSchema,
471
- annotations: t.annotations
472
- })) }));
473
- localServer.setRequestHandler(CallToolRequestSchema, async (request) => {
474
- const { name, arguments: args } = request.params;
475
- const result = await x402Mcp.callTool(name, args ?? {});
476
- return {
477
- content: result.content,
478
- isError: result.isError
644
+ if (resolvedProtocol === "mpp") await startMppProxy();
645
+ else await startX402Proxy();
646
+ async function startX402Proxy() {
647
+ let preferredNetwork = config?.defaultNetwork;
648
+ if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
649
+ const { fetchAllBalances } = await import("../wallet-DjixXCHy.js");
650
+ const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
651
+ const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
652
+ const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
653
+ if (evmUsdc > solUsdc) preferredNetwork = "base";
654
+ else if (solUsdc > evmUsdc) preferredNetwork = "solana";
655
+ }
656
+ const x402PaymentClient = await buildX402Client(wallet, {
657
+ preferredNetwork,
658
+ network: flags.network,
659
+ spendLimitDaily: config?.spendLimitDaily,
660
+ spendLimitPerTx: config?.spendLimitPerTx
661
+ });
662
+ const { x402MCPClient } = await import("@x402/mcp");
663
+ const remoteClient = new Client({
664
+ name: "x402-proxy",
665
+ version: "0.7.0"
666
+ });
667
+ const x402Mcp = new x402MCPClient(remoteClient, x402PaymentClient, {
668
+ autoPayment: true,
669
+ 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}"`);
672
+ return true;
673
+ }
674
+ });
675
+ x402Mcp.onAfterPayment(async (ctx) => {
676
+ const accepted = ctx.paymentPayload.accepted;
677
+ const tx = ctx.settleResponse?.transaction;
678
+ const record = {
679
+ t: Date.now(),
680
+ ok: true,
681
+ kind: "x402_payment",
682
+ net: accepted?.network ?? "unknown",
683
+ from: wallet.evmAddress ?? wallet.solanaAddress ?? "unknown",
684
+ to: accepted?.payTo,
685
+ tx: typeof tx === "string" ? tx : void 0,
686
+ amount: accepted?.amount ? Number(accepted.amount) / 1e6 : void 0,
687
+ token: "USDC",
688
+ label: `mcp:${ctx.toolName}`
689
+ };
690
+ appendHistory(getHistoryPath(), record);
691
+ });
692
+ await connectTransport(x402Mcp);
693
+ let { tools } = await x402Mcp.listTools();
694
+ dim(` ${tools.length} tools available`);
695
+ let remoteResources = [];
696
+ try {
697
+ remoteResources = (await x402Mcp.listResources()).resources;
698
+ if (remoteResources.length > 0) dim(` ${remoteResources.length} resources available`);
699
+ } catch {
700
+ dim(" Resources not available from remote");
701
+ }
702
+ const localServer = new Server({
703
+ name: "x402-proxy",
704
+ version: "0.7.0"
705
+ }, { capabilities: {
706
+ tools: tools.length > 0 ? {} : void 0,
707
+ resources: remoteResources.length > 0 ? {} : void 0
708
+ } });
709
+ localServer.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: tools.map((t) => ({
710
+ name: t.name,
711
+ description: t.description,
712
+ inputSchema: t.inputSchema,
713
+ annotations: t.annotations
714
+ })) }));
715
+ localServer.setRequestHandler(CallToolRequestSchema, async (request) => {
716
+ 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
+ };
722
+ });
723
+ if (remoteResources.length > 0) {
724
+ localServer.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: remoteResources.map((r) => ({
725
+ name: r.name,
726
+ uri: r.uri,
727
+ description: r.description,
728
+ mimeType: r.mimeType
729
+ })) }));
730
+ localServer.setRequestHandler(ReadResourceRequestSchema, async (request) => {
731
+ return { contents: (await x402Mcp.readResource({ uri: request.params.uri })).contents.map((c) => ({ ...c })) };
732
+ });
733
+ }
734
+ remoteClient.setNotificationHandler(ToolListChangedNotificationSchema, async () => {
735
+ tools = (await x402Mcp.listTools()).tools;
736
+ dim(` Tools updated: ${tools.length} available`);
737
+ await localServer.notification({ method: "notifications/tools/list_changed" });
738
+ });
739
+ if (remoteResources.length > 0) remoteClient.setNotificationHandler(ResourceListChangedNotificationSchema, async () => {
740
+ remoteResources = (await x402Mcp.listResources()).resources;
741
+ dim(` Resources updated: ${remoteResources.length} available`);
742
+ await localServer.notification({ method: "notifications/resources/list_changed" });
743
+ });
744
+ const stdioTransport = new StdioServerTransport();
745
+ await localServer.connect(stdioTransport);
746
+ dim(" MCP proxy running (stdio, x402)");
747
+ let closing = false;
748
+ const cleanup = async () => {
749
+ if (closing) return;
750
+ closing = true;
751
+ await x402Mcp.close();
752
+ process.exit(0);
479
753
  };
480
- });
481
- if (remoteResources.length > 0) {
482
- localServer.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: remoteResources.map((r) => ({
483
- name: r.name,
484
- uri: r.uri,
485
- description: r.description,
486
- mimeType: r.mimeType
754
+ process.stdin.on("end", cleanup);
755
+ process.on("SIGINT", cleanup);
756
+ process.on("SIGTERM", cleanup);
757
+ }
758
+ async function startMppProxy() {
759
+ if (!wallet.evmKey) {
760
+ error("MPP requires an EVM wallet. Configure one with: npx x402-proxy setup");
761
+ process.exit(1);
762
+ }
763
+ const { tempo } = await import("mppx/client");
764
+ const { McpClient } = await import("mppx/mcp-sdk/client");
765
+ const { privateKeyToAccount } = await import("viem/accounts");
766
+ const account = privateKeyToAccount(wallet.evmKey);
767
+ const maxDeposit = config?.mppSessionBudget ?? "1";
768
+ const remoteClient = new Client({
769
+ name: "x402-proxy",
770
+ version: "0.7.0"
771
+ });
772
+ await connectTransport(remoteClient);
773
+ const mppClient = McpClient.wrap(remoteClient, { methods: [tempo({
774
+ account,
775
+ maxDeposit
776
+ })] });
777
+ let { tools } = await remoteClient.listTools();
778
+ dim(` ${tools.length} tools available`);
779
+ let remoteResources = [];
780
+ try {
781
+ remoteResources = (await remoteClient.listResources()).resources;
782
+ if (remoteResources.length > 0) dim(` ${remoteResources.length} resources available`);
783
+ } catch {
784
+ dim(" Resources not available from remote");
785
+ }
786
+ const localServer = new Server({
787
+ name: "x402-proxy",
788
+ version: "0.7.0"
789
+ }, { capabilities: {
790
+ tools: tools.length > 0 ? {} : void 0,
791
+ resources: remoteResources.length > 0 ? {} : void 0
792
+ } });
793
+ localServer.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: tools.map((t) => ({
794
+ name: t.name,
795
+ description: t.description,
796
+ inputSchema: t.inputSchema,
797
+ annotations: t.annotations
487
798
  })) }));
488
- localServer.setRequestHandler(ReadResourceRequestSchema, async (request) => {
489
- return { contents: (await x402Mcp.readResource({ uri: request.params.uri })).contents.map((c) => ({ ...c })) };
799
+ localServer.setRequestHandler(CallToolRequestSchema, async (request) => {
800
+ const { name, arguments: args } = request.params;
801
+ const result = await mppClient.callTool({
802
+ name,
803
+ arguments: args ?? {}
804
+ });
805
+ if (result.receipt) {
806
+ const record = {
807
+ t: Date.now(),
808
+ ok: true,
809
+ kind: "mpp_payment",
810
+ net: TEMPO_NETWORK,
811
+ from: wallet.evmAddress ?? "unknown",
812
+ tx: result.receipt.reference,
813
+ token: "USDC",
814
+ label: `mcp:${name}`
815
+ };
816
+ appendHistory(getHistoryPath(), record);
817
+ warn(` MPP payment for tool "${name}" (Tempo)`);
818
+ }
819
+ return {
820
+ content: result.content,
821
+ isError: result.isError
822
+ };
490
823
  });
824
+ if (remoteResources.length > 0) {
825
+ localServer.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: remoteResources.map((r) => ({
826
+ name: r.name,
827
+ uri: r.uri,
828
+ description: r.description,
829
+ mimeType: r.mimeType
830
+ })) }));
831
+ localServer.setRequestHandler(ReadResourceRequestSchema, async (request) => {
832
+ return { contents: (await remoteClient.readResource({ uri: request.params.uri })).contents.map((c) => ({ ...c })) };
833
+ });
834
+ }
835
+ remoteClient.setNotificationHandler(ToolListChangedNotificationSchema, async () => {
836
+ tools = (await remoteClient.listTools()).tools;
837
+ dim(` Tools updated: ${tools.length} available`);
838
+ await localServer.notification({ method: "notifications/tools/list_changed" });
839
+ });
840
+ if (remoteResources.length > 0) remoteClient.setNotificationHandler(ResourceListChangedNotificationSchema, async () => {
841
+ remoteResources = (await remoteClient.listResources()).resources;
842
+ dim(` Resources updated: ${remoteResources.length} available`);
843
+ await localServer.notification({ method: "notifications/resources/list_changed" });
844
+ });
845
+ const stdioTransport = new StdioServerTransport();
846
+ await localServer.connect(stdioTransport);
847
+ dim(" MCP proxy running (stdio, mpp)");
848
+ let closing = false;
849
+ const cleanup = async () => {
850
+ if (closing) return;
851
+ closing = true;
852
+ await remoteClient.close();
853
+ process.exit(0);
854
+ };
855
+ process.stdin.on("end", cleanup);
856
+ process.on("SIGINT", cleanup);
857
+ process.on("SIGTERM", cleanup);
491
858
  }
492
- remoteClient.setNotificationHandler(ToolListChangedNotificationSchema, async () => {
493
- tools = (await x402Mcp.listTools()).tools;
494
- dim(` Tools updated: ${tools.length} available`);
495
- await localServer.notification({ method: "notifications/tools/list_changed" });
496
- });
497
- if (remoteResources.length > 0) remoteClient.setNotificationHandler(ResourceListChangedNotificationSchema, async () => {
498
- remoteResources = (await x402Mcp.listResources()).resources;
499
- dim(` Resources updated: ${remoteResources.length} available`);
500
- await localServer.notification({ method: "notifications/resources/list_changed" });
501
- });
502
- const stdioTransport = new StdioServerTransport();
503
- await localServer.connect(stdioTransport);
504
- dim(" MCP proxy running (stdio)");
505
- process.stdin.on("end", async () => {
506
- await x402Mcp.close();
507
- process.exit(0);
508
- });
509
859
  }
510
860
  });
511
861
 
@@ -635,7 +985,7 @@ const routes = buildRouteMap({
635
985
  });
636
986
  const app = buildApplication(routes, {
637
987
  name: "x402-proxy",
638
- versionInfo: { currentVersion: "0.5.2" },
988
+ versionInfo: { currentVersion: "0.7.0" },
639
989
  scanner: { caseStyle: "allow-kebab-for-camel" }
640
990
  });
641
991