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