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.
- package/CHANGELOG.md +22 -1
- package/README.md +11 -2
- package/dist/bin/cli.js +472 -167
- package/dist/{derive-CISr_ond.js → derive-ibF2UinV.js} +2 -2
- package/dist/index.d.ts +43 -8
- package/dist/index.js +104 -13
- package/dist/{setup-gla-Qyqi.js → setup-hJGkO2Lo.js} +1 -1
- package/dist/setup-j_xQ14-4.js +4 -0
- package/dist/{status-BoH_1kIH.js → status-JNGv2Ghp.js} +12 -6
- package/dist/{status-BVIU3-b6.js → status-w5y-fhhe.js} +1 -1
- package/dist/wallet-DjixXCHy.js +4 -0
- package/dist/{wallet-BMYYtAP6.js → wallet-DxKCHa7U.js} +45 -11
- package/package.json +5 -2
- package/skills/SKILL.md +9 -6
- package/dist/setup-Crq9TylJ.js +0 -4
- package/dist/wallet-BoY0fgX7.js +0 -4
|
@@ -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 {
|
|
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
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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 {
|
|
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";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { a as
|
|
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
|
|
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:
|
|
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,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
})
|
|
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
|
|
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
|
-
|
|
409
|
+
const tempoEmpty = !tempo || tempo.usdc === "0.0000";
|
|
410
|
+
if (evmEmpty && solEmpty && tempoEmpty) {
|
|
377
411
|
console.log();
|
|
378
|
-
dim(" Send USDC to
|
|
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 {
|
|
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.
|
|
4
|
-
"description": "curl for x402 paid APIs. Auto-pays any endpoint on Base and
|
|
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
|
|
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
|
|
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 <
|
|
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
|
|
112
|
-
|
|
113
|
-
|
|
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`.
|
package/dist/setup-Crq9TylJ.js
DELETED
package/dist/wallet-BoY0fgX7.js
DELETED