x402-proxy 0.5.2 → 0.6.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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from "node:fs";
3
- import os from "node:os";
4
3
  import path from "node:path";
4
+ import os from "node:os";
5
5
  import { parse, stringify } from "yaml";
6
6
  import { ed25519 } from "@noble/curves/ed25519.js";
7
7
  import { base58 } from "@scure/base";
@@ -158,4 +158,4 @@ function checksumAddress(addr) {
158
158
  }
159
159
 
160
160
  //#endregion
161
- export { getConfigDirShort as a, isConfigured as c, saveConfig as d, saveWalletFile as f, ensureConfigDir as i, loadConfig as l, deriveSolanaKeypair as n, getHistoryPath as o, generateMnemonic$1 as r, getWalletPath as s, deriveEvmKeypair as t, loadWalletFile as u };
161
+ export { getHistoryPath as a, loadConfig as c, saveWalletFile as d, getConfigDirShort as i, loadWalletFile as l, deriveSolanaKeypair as n, getWalletPath as o, generateMnemonic$1 as r, isConfigured as s, deriveEvmKeypair as t, saveConfig as u };
package/dist/index.d.ts CHANGED
@@ -5,11 +5,27 @@ import { KeyPairSigner } from "@solana/kit";
5
5
 
6
6
  //#region src/handler.d.ts
7
7
  type PaymentInfo = {
8
+ protocol: "x402";
8
9
  network: string | undefined;
9
10
  payTo: string | undefined; /** Raw amount in base units as returned by x402 (e.g. "50000" for 0.05 USDC) */
10
11
  amount: string | undefined;
11
12
  asset: string | undefined;
12
13
  };
14
+ type MppPaymentInfo = {
15
+ protocol: "mpp";
16
+ network: string;
17
+ amount?: string;
18
+ intent?: string;
19
+ channelId?: string;
20
+ receipt?: {
21
+ method: string;
22
+ reference: string;
23
+ status: string;
24
+ timestamp: string;
25
+ acceptedCumulative?: string;
26
+ txHash?: string;
27
+ };
28
+ };
13
29
  type X402ProxyOptions = {
14
30
  client: x402Client$1;
15
31
  };
@@ -17,20 +33,39 @@ type X402ProxyHandler = {
17
33
  /** Wrapped fetch with x402 payment handling */x402Fetch: (input: string | URL | Request, init?: RequestInit) => Promise<Response>; /** Shift the latest payment info from the queue (call after x402Fetch) */
18
34
  shiftPayment: () => PaymentInfo | undefined;
19
35
  };
36
+ type MppProxyHandler = {
37
+ /** Payment-aware fetch that handles 402 + WWW-Authenticate automatically */fetch: (input: string | URL, init?: RequestInit) => Promise<Response>; /** SSE streaming with mid-stream voucher cycling */
38
+ sse: (input: string | URL, init?: RequestInit) => Promise<AsyncIterable<string>>; /** Shift the latest payment info from the queue */
39
+ shiftPayment: () => MppPaymentInfo | undefined; /** Settle any active session channel */
40
+ close: () => Promise<void>;
41
+ };
42
+ type DetectedProtocols = {
43
+ x402: boolean;
44
+ mpp: boolean;
45
+ };
46
+ /**
47
+ * Detect which payment protocols a 402 response advertises.
48
+ * - x402: PAYMENT-REQUIRED or X-PAYMENT-REQUIRED header
49
+ * - MPP: WWW-Authenticate header with Payment scheme
50
+ */
51
+ declare function detectProtocols(response: Response): DetectedProtocols;
20
52
  /**
21
53
  * Extract the on-chain transaction signature from an x402 payment response header.
22
54
  */
23
55
  declare function extractTxSignature(response: Response): string | undefined;
24
56
  /**
25
57
  * Create an x402 proxy handler that wraps fetch with automatic payment.
26
- *
27
- * Chain-agnostic: accepts a pre-configured x402Client with any registered
28
- * schemes (SVM, EVM, etc). The handler captures payment info via the
29
- * onAfterPaymentCreation hook. Callers use `x402Fetch` for requests that
30
- * may require x402 payment, and `shiftPayment` to retrieve captured
31
- * payment info after each call.
32
58
  */
33
59
  declare function createX402ProxyHandler(opts: X402ProxyOptions): X402ProxyHandler;
60
+ declare const TEMPO_NETWORK = "eip155:4217";
61
+ /**
62
+ * Create an MPP proxy handler using mppx client.
63
+ * Dynamically imports mppx/client to keep startup fast.
64
+ */
65
+ declare function createMppProxyHandler(opts: {
66
+ evmKey: string;
67
+ maxDeposit?: string;
68
+ }): Promise<MppProxyHandler>;
34
69
  //#endregion
35
70
  //#region src/history.d.ts
36
71
  declare const HISTORY_MAX_LINES = 1000;
@@ -38,7 +73,7 @@ declare const HISTORY_KEEP_LINES = 500;
38
73
  type TxRecord = {
39
74
  t: number;
40
75
  ok: boolean;
41
- kind: "x402_inference" | "x402_payment" | "transfer" | "buy" | "sell" | "mint" | "swap";
76
+ kind: "x402_inference" | "x402_payment" | "mpp_payment" | "transfer" | "buy" | "sell" | "mint" | "swap";
42
77
  net: string;
43
78
  from: string;
44
79
  to?: string;
@@ -82,4 +117,4 @@ declare function loadSvmWallet(keypairPath: string): Promise<KeyPairSigner>;
82
117
  */
83
118
  declare function loadEvmWallet(keyPath: string): ClientEvmSigner$1;
84
119
  //#endregion
85
- export { type ClientEvmSigner, ExactEvmScheme, ExactSvmScheme, HISTORY_KEEP_LINES, HISTORY_MAX_LINES, type PaymentInfo, type TxRecord, type X402ProxyHandler, type X402ProxyOptions, appendHistory, calcSpend, createX402ProxyHandler, explorerUrl, extractTxSignature, formatTxLine, loadEvmWallet, loadSvmWallet, readHistory, toClientEvmSigner, x402Client };
120
+ export { type ClientEvmSigner, type DetectedProtocols, ExactEvmScheme, ExactSvmScheme, HISTORY_KEEP_LINES, HISTORY_MAX_LINES, type MppPaymentInfo, type MppProxyHandler, type PaymentInfo, TEMPO_NETWORK, type TxRecord, type X402ProxyHandler, type X402ProxyOptions, appendHistory, calcSpend, createMppProxyHandler, createX402ProxyHandler, detectProtocols, explorerUrl, extractTxSignature, formatTxLine, loadEvmWallet, loadSvmWallet, readHistory, toClientEvmSigner, x402Client };
package/dist/index.js CHANGED
@@ -1,31 +1,42 @@
1
1
  import { ExactEvmScheme, toClientEvmSigner, toClientEvmSigner as toClientEvmSigner$1 } from "@x402/evm";
2
2
  import { decodePaymentResponseHeader, wrapFetchWithPayment, x402Client } from "@x402/fetch";
3
3
  import { ExactSvmScheme } from "@x402/svm/exact/client";
4
- import { appendFileSync, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
4
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
5
+ import { dirname } from "node:path";
5
6
  import { createKeyPairSignerFromBytes } from "@solana/kit";
6
7
  import { privateKeyToAccount } from "viem/accounts";
7
8
 
8
9
  //#region src/handler.ts
9
10
  /**
11
+ * Detect which payment protocols a 402 response advertises.
12
+ * - x402: PAYMENT-REQUIRED or X-PAYMENT-REQUIRED header
13
+ * - MPP: WWW-Authenticate header with Payment scheme
14
+ */
15
+ function detectProtocols(response) {
16
+ const pr = response.headers.get("PAYMENT-REQUIRED") ?? response.headers.get("X-PAYMENT-REQUIRED");
17
+ const wwwAuth = response.headers.get("WWW-Authenticate");
18
+ return {
19
+ x402: !!pr,
20
+ mpp: !!(wwwAuth && /^Payment\b/i.test(wwwAuth.trim()))
21
+ };
22
+ }
23
+ /**
10
24
  * Extract the on-chain transaction signature from an x402 payment response header.
11
25
  */
12
26
  function extractTxSignature(response) {
13
- const header = response.headers.get("PAYMENT-RESPONSE") ?? response.headers.get("X-PAYMENT-RESPONSE");
14
- if (!header) return void 0;
15
- try {
16
- return decodePaymentResponseHeader(header).transaction ?? void 0;
27
+ const x402Header = response.headers.get("PAYMENT-RESPONSE") ?? response.headers.get("X-PAYMENT-RESPONSE");
28
+ if (x402Header) try {
29
+ return decodePaymentResponseHeader(x402Header).transaction ?? void 0;
30
+ } catch {}
31
+ const mppHeader = response.headers.get("Payment-Receipt");
32
+ if (mppHeader) try {
33
+ return JSON.parse(Buffer.from(mppHeader, "base64url").toString()).reference ?? void 0;
17
34
  } catch {
18
35
  return;
19
36
  }
20
37
  }
21
38
  /**
22
39
  * Create an x402 proxy handler that wraps fetch with automatic payment.
23
- *
24
- * Chain-agnostic: accepts a pre-configured x402Client with any registered
25
- * schemes (SVM, EVM, etc). The handler captures payment info via the
26
- * onAfterPaymentCreation hook. Callers use `x402Fetch` for requests that
27
- * may require x402 payment, and `shiftPayment` to retrieve captured
28
- * payment info after each call.
29
40
  */
30
41
  function createX402ProxyHandler(opts) {
31
42
  const { client } = opts;
@@ -33,6 +44,7 @@ function createX402ProxyHandler(opts) {
33
44
  client.onAfterPaymentCreation(async (hookCtx) => {
34
45
  const raw = hookCtx.selectedRequirements.amount;
35
46
  paymentQueue.push({
47
+ protocol: "x402",
36
48
  network: hookCtx.selectedRequirements.network,
37
49
  payTo: hookCtx.selectedRequirements.payTo,
38
50
  amount: raw,
@@ -44,6 +56,80 @@ function createX402ProxyHandler(opts) {
44
56
  shiftPayment: () => paymentQueue.shift()
45
57
  };
46
58
  }
59
+ const TEMPO_NETWORK = "eip155:4217";
60
+ /**
61
+ * Create an MPP proxy handler using mppx client.
62
+ * Dynamically imports mppx/client to keep startup fast.
63
+ */
64
+ async function createMppProxyHandler(opts) {
65
+ const { Mppx, tempo } = await import("mppx/client");
66
+ const { privateKeyToAccount } = await import("viem/accounts");
67
+ const account = privateKeyToAccount(opts.evmKey);
68
+ const maxDeposit = opts.maxDeposit ?? "1";
69
+ const paymentQueue = [];
70
+ const mppx = Mppx.create({
71
+ methods: [tempo({
72
+ account,
73
+ maxDeposit
74
+ })],
75
+ polyfill: false
76
+ });
77
+ let session;
78
+ return {
79
+ async fetch(input, init) {
80
+ const response = await mppx.fetch(typeof input === "string" ? input : input.toString(), init);
81
+ 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
+ });
94
+ }
95
+ return response;
96
+ },
97
+ async sse(input, init) {
98
+ session ??= tempo.session({
99
+ account,
100
+ maxDeposit
101
+ });
102
+ const url = typeof input === "string" ? input : input.toString();
103
+ const iterable = await session.sse(url, init);
104
+ paymentQueue.push({
105
+ protocol: "mpp",
106
+ network: TEMPO_NETWORK,
107
+ intent: "session"
108
+ });
109
+ return iterable;
110
+ },
111
+ shiftPayment: () => paymentQueue.shift(),
112
+ async close() {
113
+ if (session?.opened) {
114
+ const receipt = await session.close();
115
+ if (receipt) paymentQueue.push({
116
+ protocol: "mpp",
117
+ network: TEMPO_NETWORK,
118
+ intent: "session",
119
+ channelId: session.channelId ?? void 0,
120
+ receipt: {
121
+ method: receipt.method,
122
+ reference: receipt.reference,
123
+ status: receipt.status,
124
+ timestamp: receipt.timestamp,
125
+ acceptedCumulative: receipt.acceptedCumulative,
126
+ txHash: receipt.txHash
127
+ }
128
+ });
129
+ }
130
+ }
131
+ };
132
+ }
47
133
 
48
134
  //#endregion
49
135
  //#region src/history.ts
@@ -51,6 +137,7 @@ const HISTORY_MAX_LINES = 1e3;
51
137
  const HISTORY_KEEP_LINES = 500;
52
138
  function appendHistory(historyPath, record) {
53
139
  try {
140
+ mkdirSync(dirname(historyPath), { recursive: true });
54
141
  appendFileSync(historyPath, `${JSON.stringify(record)}\n`);
55
142
  if (existsSync(historyPath)) {
56
143
  if (statSync(historyPath).size > HISTORY_MAX_LINES * 200) {
@@ -111,6 +198,7 @@ function formatAmount(amount, token) {
111
198
  const KIND_LABELS = {
112
199
  x402_inference: "inference",
113
200
  x402_payment: "payment",
201
+ mpp_payment: "mpp payment",
114
202
  transfer: "transfer",
115
203
  buy: "buy",
116
204
  sell: "sell",
@@ -119,7 +207,9 @@ const KIND_LABELS = {
119
207
  };
120
208
  function explorerUrl(net, tx) {
121
209
  if (net.startsWith("eip155:")) {
122
- if (net.split(":")[1] === "8453") return `https://basescan.org/tx/${tx}`;
210
+ const chainId = net.split(":")[1];
211
+ if (chainId === "4217") return `https://explore.mainnet.tempo.xyz/tx/${tx}`;
212
+ if (chainId === "8453") return `https://basescan.org/tx/${tx}`;
123
213
  return `https://basescan.org/tx/${tx}`;
124
214
  }
125
215
  return `https://solscan.io/tx/${tx}`;
@@ -133,6 +223,7 @@ function shortModel(model) {
133
223
  }
134
224
  function shortNetwork(net) {
135
225
  if (net === "eip155:8453") return "base";
226
+ if (net === "eip155:4217") return "tempo";
136
227
  if (net.startsWith("eip155:")) return `evm:${net.split(":")[1]}`;
137
228
  if (net.startsWith("solana:")) return "sol";
138
229
  return net;
@@ -178,4 +269,4 @@ function loadEvmWallet(keyPath) {
178
269
  }
179
270
 
180
271
  //#endregion
181
- export { ExactEvmScheme, ExactSvmScheme, HISTORY_KEEP_LINES, HISTORY_MAX_LINES, appendHistory, calcSpend, createX402ProxyHandler, explorerUrl, extractTxSignature, formatTxLine, loadEvmWallet, loadSvmWallet, readHistory, toClientEvmSigner, x402Client };
272
+ export { ExactEvmScheme, ExactSvmScheme, HISTORY_KEEP_LINES, HISTORY_MAX_LINES, TEMPO_NETWORK, appendHistory, calcSpend, createMppProxyHandler, createX402ProxyHandler, detectProtocols, explorerUrl, extractTxSignature, formatTxLine, loadEvmWallet, loadSvmWallet, readHistory, toClientEvmSigner, x402Client };
@@ -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-CISr_ond.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-ibF2UinV.js";
3
3
  import { buildCommand } from "@stricli/core";
4
4
  import pc from "picocolors";
5
5
  import * as prompts from "@clack/prompts";
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { n as setupCommand, t as runSetup } from "./setup-hJGkO2Lo.js";
3
+
4
+ export { runSetup };
@@ -1,6 +1,6 @@
1
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-BMYYtAP6.js";
3
- import { a as getConfigDirShort, l as loadConfig, o as getHistoryPath } from "./derive-CISr_ond.js";
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";
4
4
  import { buildCommand } from "@stricli/core";
5
5
  import pc from "picocolors";
6
6
 
@@ -18,13 +18,15 @@ async function displayStatus() {
18
18
  console.log(pc.yellow(" No wallet configured."));
19
19
  console.log(pc.dim(` Run ${pc.cyan("$ npx x402-proxy setup")} to create one.`));
20
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;
21
+ const { evm, sol, tempo } = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
24
22
  if (wallet.evmAddress) {
25
23
  const bal = evm ? balanceLine(evm.usdc, evm.eth, "ETH") : pc.dim(" (network error)");
26
24
  console.log(` Base: ${pc.green(wallet.evmAddress)}${bal}`);
27
25
  }
26
+ if (wallet.evmAddress) {
27
+ const bal = tempo ? pc.dim(` (${tempo.usdc} USDC)`) : pc.dim(" (network error)");
28
+ console.log(` Tempo: ${pc.green(wallet.evmAddress)}${bal}`);
29
+ }
28
30
  if (wallet.solanaAddress) {
29
31
  const bal = sol ? balanceLine(sol.usdc, sol.sol, "SOL") : pc.dim(" (network error)");
30
32
  console.log(` Solana: ${pc.green(wallet.solanaAddress)}${bal}`);
@@ -50,7 +52,11 @@ async function displayStatus() {
50
52
  dim(` Today: ${spend.today.toFixed(4)} USDC | Total: ${spend.total.toFixed(4)} USDC | ${spend.count} tx`);
51
53
  } else dim(" No payment history yet.");
52
54
  console.log();
53
- if (config?.defaultNetwork) dim(` Network: ${config.defaultNetwork}`);
55
+ if (config?.defaultNetwork) dim(` Network: ${config.defaultNetwork}`);
56
+ if (config?.preferredProtocol) {
57
+ const budget = config.mppSessionBudget ?? "1";
58
+ dim(` Protocol: ${config.preferredProtocol}${config.preferredProtocol === "mpp" ? ` (sessions up to ${budget} USDC)` : ""}`);
59
+ }
54
60
  dim(` Config: ${getConfigDirShort()}`);
55
61
  }
56
62
  const statusCommand = buildCommand({
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { n as statusCommand, t as displayStatus } from "./status-BoH_1kIH.js";
2
+ import { n as statusCommand, t as displayStatus } from "./status-JNGv2Ghp.js";
3
3
 
4
4
  export { displayStatus };
@@ -0,0 +1,4 @@
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";
3
+
4
+ export { fetchAllBalances };
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { n as deriveSolanaKeypair, o as getHistoryPath, t as deriveEvmKeypair, u as loadWalletFile } from "./derive-CISr_ond.js";
2
+ import { a as getHistoryPath, l as loadWalletFile, n as deriveSolanaKeypair, t as deriveEvmKeypair } from "./derive-ibF2UinV.js";
3
3
  import { buildCommand } from "@stricli/core";
4
4
  import pc from "picocolors";
5
5
  import { x402Client } from "@x402/fetch";
6
- import { appendFileSync, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
6
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
7
+ import { dirname } from "node:path";
7
8
  import { ed25519 } from "@noble/curves/ed25519.js";
8
9
  import { base58 } from "@scure/base";
9
10
  import { toClientEvmSigner } from "@x402/evm";
@@ -19,6 +20,7 @@ const HISTORY_MAX_LINES = 1e3;
19
20
  const HISTORY_KEEP_LINES = 500;
20
21
  function appendHistory(historyPath, record) {
21
22
  try {
23
+ mkdirSync(dirname(historyPath), { recursive: true });
22
24
  appendFileSync(historyPath, `${JSON.stringify(record)}\n`);
23
25
  if (existsSync(historyPath)) {
24
26
  if (statSync(historyPath).size > HISTORY_MAX_LINES * 200) {
@@ -79,6 +81,7 @@ function formatAmount(amount, token) {
79
81
  const KIND_LABELS = {
80
82
  x402_inference: "inference",
81
83
  x402_payment: "payment",
84
+ mpp_payment: "mpp payment",
82
85
  transfer: "transfer",
83
86
  buy: "buy",
84
87
  sell: "sell",
@@ -87,7 +90,9 @@ const KIND_LABELS = {
87
90
  };
88
91
  function explorerUrl(net, tx) {
89
92
  if (net.startsWith("eip155:")) {
90
- if (net.split(":")[1] === "8453") return `https://basescan.org/tx/${tx}`;
93
+ const chainId = net.split(":")[1];
94
+ if (chainId === "4217") return `https://explore.mainnet.tempo.xyz/tx/${tx}`;
95
+ if (chainId === "8453") return `https://basescan.org/tx/${tx}`;
91
96
  return `https://basescan.org/tx/${tx}`;
92
97
  }
93
98
  return `https://solscan.io/tx/${tx}`;
@@ -101,12 +106,14 @@ function shortModel(model) {
101
106
  }
102
107
  function displayNetwork(net) {
103
108
  if (net === "eip155:8453") return "Base";
109
+ if (net === "eip155:4217") return "Tempo";
104
110
  if (net.startsWith("eip155:")) return `EVM (${net.split(":")[1]})`;
105
111
  if (net.startsWith("solana:")) return "Solana";
106
112
  return net;
107
113
  }
108
114
  function shortNetwork(net) {
109
115
  if (net === "eip155:8453") return "base";
116
+ if (net === "eip155:4217") return "tempo";
110
117
  if (net.startsWith("eip155:")) return `evm:${net.split(":")[1]}`;
111
118
  if (net.startsWith("solana:")) return "sol";
112
119
  return net;
@@ -222,6 +229,7 @@ function solanaAddressFromKey(keyBytes) {
222
229
  function networkToCaipPrefix(name) {
223
230
  switch (name.toLowerCase()) {
224
231
  case "base": return "eip155:8453";
232
+ case "tempo": return "eip155:4217";
225
233
  case "solana": return "solana:";
226
234
  default: return name;
227
235
  }
@@ -284,12 +292,14 @@ async function buildX402Client(wallet, opts) {
284
292
  //#region src/commands/wallet.ts
285
293
  const BASE_RPC = "https://mainnet.base.org";
286
294
  const SOLANA_RPC = "https://api.mainnet-beta.solana.com";
295
+ const TEMPO_RPC = "https://rpc.presto.tempo.xyz";
287
296
  const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
297
+ const USDC_TEMPO = "0x20C000000000000000000000b9537d11c60E8b50";
288
298
  const USDC_SOLANA_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
289
299
  const TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
290
300
  const ATA_PROGRAM = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
291
301
  async function rpcCall(url, method, params) {
292
- return (await fetch(url, {
302
+ const res = await fetch(url, {
293
303
  method: "POST",
294
304
  headers: { "Content-Type": "application/json" },
295
305
  body: JSON.stringify({
@@ -298,7 +308,9 @@ async function rpcCall(url, method, params) {
298
308
  params,
299
309
  id: 1
300
310
  })
301
- })).json();
311
+ });
312
+ if (!res.ok) throw new Error(`RPC ${method} failed: ${res.status} ${res.statusText}`);
313
+ return res.json();
302
314
  }
303
315
  async function fetchEvmBalances(address) {
304
316
  const usdcData = `0x70a08231${address.slice(2).padStart(64, "0")}`;
@@ -311,6 +323,13 @@ async function fetchEvmBalances(address) {
311
323
  usdc: usdcRes.result ? (Number(BigInt(usdcRes.result)) / 1e6).toFixed(4) : "?"
312
324
  };
313
325
  }
326
+ async function fetchTempoBalances(address) {
327
+ const res = await rpcCall(TEMPO_RPC, "eth_call", [{
328
+ to: USDC_TEMPO,
329
+ data: `0x70a08231${address.slice(2).padStart(64, "0")}`
330
+ }, "latest"]);
331
+ return { usdc: res.result ? (Number(BigInt(res.result)) / 1e6).toFixed(4) : "?" };
332
+ }
314
333
  async function getUsdcAta(owner) {
315
334
  const encoder = getAddressEncoder();
316
335
  const [ata] = await getProgramDerivedAddress({
@@ -336,6 +355,18 @@ async function fetchSolanaBalances(ownerAddress) {
336
355
  function balanceLine(usdc, native, nativeSymbol) {
337
356
  return pc.dim(` (${usdc} USDC, ${native} ${nativeSymbol})`);
338
357
  }
358
+ async function fetchAllBalances(evmAddress, solanaAddress) {
359
+ const [evmResult, solResult, tempoResult] = await Promise.allSettled([
360
+ evmAddress ? fetchEvmBalances(evmAddress) : Promise.resolve(null),
361
+ solanaAddress ? fetchSolanaBalances(solanaAddress) : Promise.resolve(null),
362
+ evmAddress ? fetchTempoBalances(evmAddress) : Promise.resolve(null)
363
+ ]);
364
+ return {
365
+ evm: evmResult.status === "fulfilled" ? evmResult.value : null,
366
+ sol: solResult.status === "fulfilled" ? solResult.value : null,
367
+ tempo: tempoResult.status === "fulfilled" ? tempoResult.value : null
368
+ };
369
+ }
339
370
  const walletInfoCommand = buildCommand({
340
371
  docs: { brief: "Show wallet addresses and balances" },
341
372
  parameters: {
@@ -360,22 +391,25 @@ const walletInfoCommand = buildCommand({
360
391
  info("Wallet");
361
392
  console.log();
362
393
  console.log(pc.dim(` Source: ${wallet.source}`));
363
- const [evmResult, solResult] = await Promise.allSettled([wallet.evmAddress ? fetchEvmBalances(wallet.evmAddress) : Promise.resolve(null), wallet.solanaAddress ? fetchSolanaBalances(wallet.solanaAddress) : Promise.resolve(null)]);
364
- const evm = evmResult.status === "fulfilled" ? evmResult.value : null;
365
- const sol = solResult.status === "fulfilled" ? solResult.value : null;
394
+ const { evm, sol, tempo } = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
366
395
  if (wallet.evmAddress) {
367
396
  const bal = evm ? balanceLine(evm.usdc, evm.eth, "ETH") : pc.dim(" (network error)");
368
397
  console.log(` Base: ${pc.green(wallet.evmAddress)}${bal}`);
369
398
  }
399
+ if (wallet.evmAddress) {
400
+ const bal = tempo ? pc.dim(` (${tempo.usdc} USDC)`) : pc.dim(" (network error)");
401
+ console.log(` Tempo: ${pc.green(wallet.evmAddress)}${bal}`);
402
+ }
370
403
  if (wallet.solanaAddress) {
371
404
  const bal = sol ? balanceLine(sol.usdc, sol.sol, "SOL") : pc.dim(" (network error)");
372
405
  console.log(` Solana: ${pc.green(wallet.solanaAddress)}${bal}`);
373
406
  }
374
407
  const evmEmpty = !evm || evm.usdc === "0.0000";
375
408
  const solEmpty = !sol || sol.usdc === "0.0000";
376
- if (evmEmpty && solEmpty) {
409
+ const tempoEmpty = !tempo || tempo.usdc === "0.0000";
410
+ if (evmEmpty && solEmpty && tempoEmpty) {
377
411
  console.log();
378
- dim(" Send USDC to either address above to start using x402 APIs.");
412
+ dim(" Send USDC to any address above to start using paid APIs.");
379
413
  }
380
414
  console.log();
381
415
  const records = readHistory(getHistoryPath());
@@ -397,4 +431,4 @@ const walletInfoCommand = buildCommand({
397
431
  });
398
432
 
399
433
  //#endregion
400
- 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 };
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 };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "x402-proxy",
3
- "version": "0.5.2",
4
- "description": "curl for x402 paid APIs. Auto-pays any endpoint on Base and Solana.",
3
+ "version": "0.6.0",
4
+ "description": "curl for x402 paid APIs. Auto-pays any endpoint on Base, Solana, and Tempo.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "main": "./dist/index.js",
@@ -29,6 +29,7 @@
29
29
  "@x402/evm": "^2.6.0",
30
30
  "@x402/fetch": "^2.6.0",
31
31
  "@x402/mcp": "^2.6.0",
32
+ "mppx": "^0.4.7",
32
33
  "@x402/svm": "^2.6.0",
33
34
  "ethers": "^6.0.0",
34
35
  "picocolors": "^1.1.1",
@@ -51,6 +52,7 @@
51
52
  ],
52
53
  "keywords": [
53
54
  "x402",
55
+ "mpp",
54
56
  "http-402",
55
57
  "mcp",
56
58
  "mcp-proxy",
@@ -58,6 +60,7 @@
58
60
  "agentic-commerce",
59
61
  "base",
60
62
  "solana",
63
+ "tempo",
61
64
  "usdc",
62
65
  "coinbase",
63
66
  "cli"
package/skills/SKILL.md CHANGED
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: x402-proxy
3
- description: Use x402-proxy CLI for consuming and debugging x402 paid APIs. Use this skill when testing x402 endpoints, configuring MCP payment proxies for AI agents, managing x402 wallets, or scripting paid HTTP requests. Triggers on x402-proxy, npx x402-proxy, x402 endpoint testing, paid API debugging, MCP payment proxy, x402 wallet management, or any mention of auto-paying HTTP 402 responses.
3
+ description: Use x402-proxy CLI for consuming and debugging x402 and MPP paid APIs. Use this skill when testing x402/MPP endpoints, configuring MCP payment proxies for AI agents, managing wallets, or scripting paid HTTP requests. Triggers on x402-proxy, npx x402-proxy, x402 endpoint testing, MPP streaming payments, paid API debugging, MCP payment proxy, wallet management, or any mention of auto-paying HTTP 402 responses.
4
4
  ---
5
5
 
6
6
  # x402-proxy
7
7
 
8
- `curl` for x402 paid APIs. Auto-pays HTTP 402 responses with USDC on Base and Solana.
8
+ `curl` for x402 paid APIs. Auto-pays HTTP 402 responses with USDC on Base, Solana, and Tempo (via MPP).
9
9
 
10
10
  ## Quick start
11
11
 
@@ -60,7 +60,8 @@ x402-proxy wallet export-key mnemonic # bare mnemonic to stdout
60
60
  --method, -X <METHOD> HTTP method (default: GET)
61
61
  --header, -H <KEY:VALUE> Add request header (repeatable)
62
62
  --body, -d <DATA> Request body (string or @file)
63
- --network <base|solana> Force payment on this chain
63
+ --network <NETWORK> Force payment chain (base, solana, tempo)
64
+ --protocol <PROTOCOL> Payment protocol (x402, mpp)
64
65
  ```
65
66
 
66
67
  ## MCP proxy for AI agents
@@ -108,9 +109,11 @@ Lives at `~/.config/x402-proxy/` (or `$XDG_CONFIG_HOME/x402-proxy/`):
108
109
 
109
110
  ```yaml
110
111
  # config.yaml
111
- defaultNetwork: base # or "solana"
112
- spendLimitDaily: 10 # USDC daily cap
113
- spendLimitPerTx: 1 # USDC per-request cap
112
+ defaultNetwork: base # or "solana"
113
+ preferredProtocol: x402 # or "mpp" for Tempo/MPP endpoints
114
+ mppSessionBudget: "1" # max USDC deposit for MPP streaming sessions
115
+ spendLimitDaily: 10 # USDC daily cap
116
+ spendLimitPerTx: 1 # USDC per-request cap
114
117
  ```
115
118
 
116
119
  Also supports JSONC and JSON config files. Wallet stored in `wallet.json` (mode 0600), payments logged to `history.jsonl`.
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env node
2
- import { n as setupCommand, t as runSetup } from "./setup-gla-Qyqi.js";
3
-
4
- export { runSetup };
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env node
2
- import { i as walletInfoCommand, n as fetchEvmBalances, r as fetchSolanaBalances, t as balanceLine } from "./wallet-BMYYtAP6.js";
3
-
4
- export { fetchEvmBalances, fetchSolanaBalances };