x402-proxy 0.7.1 → 0.8.1
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 +26 -1
- package/README.md +9 -7
- package/dist/bin/cli.js +265 -53
- 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 +2 -2
- package/skills/SKILL.md +3 -0
- package/dist/setup-j_xQ14-4.js +0 -4
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.8.1] - 2026-03-24
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Curl-style short flags: `-X` (method), `-H` (header), `-d` (body) for the `fetch` command
|
|
14
|
+
- `-H` preprocessing in CLI entry point to work around Stricli reserving `-H` for `--help-all`
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- SSE streaming resilience: swallow Node.js "terminated" errors when server closes connection after final event, so payment logging still completes
|
|
18
|
+
- Bumped `mppx` to ^0.4.9 (fixes 204-safe SSE receipt wrapping and idempotent voucher replay)
|
|
19
|
+
|
|
20
|
+
## [0.8.0] - 2026-03-21
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- `config` command with `show`, `set`, and `unset` subcommands for managing configuration from the CLI (no more manual YAML editing)
|
|
24
|
+
- Setup wizard now asks about preferred payment protocol (x402/MPP) and network
|
|
25
|
+
- MCP proxy handles `McpError(-32042)` from dual-protocol servers that throw instead of using `isError`, automatically retrying with x402 payment
|
|
26
|
+
- MPP payment amounts captured from challenges and displayed in payment logs, MCP proxy output, and transaction history
|
|
27
|
+
- `formatAmount()` and `formatUsdcValue()` exported from library for adaptive USDC precision formatting
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
- USDC amounts displayed with adaptive precision (2-6 decimals based on magnitude) instead of fixed 4 decimals everywhere
|
|
31
|
+
- Zero-balance detection uses numeric comparison instead of string matching
|
|
32
|
+
|
|
10
33
|
## [0.7.1] - 2026-03-20
|
|
11
34
|
|
|
12
35
|
### Fixed
|
|
@@ -191,7 +214,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
191
214
|
- `appendHistory` / `readHistory` / `calcSpend` - JSONL transaction history
|
|
192
215
|
- Re-exports from `@x402/fetch`, `@x402/svm`, `@x402/evm`
|
|
193
216
|
|
|
194
|
-
[Unreleased]: https://github.com/cascade-protocol/x402-proxy/compare/v0.
|
|
217
|
+
[Unreleased]: https://github.com/cascade-protocol/x402-proxy/compare/v0.8.1...HEAD
|
|
218
|
+
[0.8.1]: https://github.com/cascade-protocol/x402-proxy/compare/v0.8.0...v0.8.1
|
|
219
|
+
[0.8.0]: https://github.com/cascade-protocol/x402-proxy/compare/v0.7.1...v0.8.0
|
|
195
220
|
[0.7.1]: https://github.com/cascade-protocol/x402-proxy/compare/v0.7.0...v0.7.1
|
|
196
221
|
[0.7.0]: https://github.com/cascade-protocol/x402-proxy/compare/v0.6.0...v0.7.0
|
|
197
222
|
[0.6.0]: https://github.com/cascade-protocol/x402-proxy/compare/v0.5.2...v0.6.0
|
package/README.md
CHANGED
|
@@ -40,10 +40,10 @@ Works like curl. Response body streams to stdout, payment info goes to stderr.
|
|
|
40
40
|
# GET request
|
|
41
41
|
$ npx x402-proxy https://twitter.surf.cascade.fyi/users/cascade_fyi
|
|
42
42
|
|
|
43
|
-
# POST with body and headers
|
|
44
|
-
$ npx x402-proxy
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
# POST with body and headers (curl-style short flags: -X, -H, -d)
|
|
44
|
+
$ npx x402-proxy -X POST \
|
|
45
|
+
-H "Content-Type: application/json" \
|
|
46
|
+
-d '{"url":"https://x402.org"}' \
|
|
47
47
|
https://web.surf.cascade.fyi/v1/crawl
|
|
48
48
|
|
|
49
49
|
# Force a specific network
|
|
@@ -54,9 +54,8 @@ $ npx x402-proxy --verbose https://api.example.com/data
|
|
|
54
54
|
|
|
55
55
|
# Use MPP protocol for streaming payments
|
|
56
56
|
$ npx x402-proxy --protocol mpp \
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
--body '{"model":"minimax/minimax-m2.5","stream":true,"messages":[{"role":"user","content":"Hello"}]}' \
|
|
57
|
+
-X POST -H "Content-Type: application/json" \
|
|
58
|
+
-d '{"model":"minimax/minimax-m2.5","stream":true,"messages":[{"role":"user","content":"Hello"}]}' \
|
|
60
59
|
https://inference.surf.cascade.fyi/v1/chat/completions
|
|
61
60
|
|
|
62
61
|
# Pipe-safe
|
|
@@ -70,6 +69,9 @@ $ npx x402-proxy <url> # paid HTTP request (default command)
|
|
|
70
69
|
$ npx x402-proxy mcp <url> # MCP stdio proxy for agents
|
|
71
70
|
$ npx x402-proxy setup # onboarding wizard
|
|
72
71
|
$ npx x402-proxy status # config + wallet + spend summary
|
|
72
|
+
$ npx x402-proxy config # show current configuration
|
|
73
|
+
$ npx x402-proxy config set <key> <value> # set a config value
|
|
74
|
+
$ npx x402-proxy config unset <key> # remove a config value
|
|
73
75
|
$ npx x402-proxy wallet # show addresses and balances
|
|
74
76
|
$ npx x402-proxy wallet history # payment history
|
|
75
77
|
$ 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
|
},
|
|
@@ -206,6 +367,10 @@ Examples:
|
|
|
206
367
|
default: false
|
|
207
368
|
}
|
|
208
369
|
},
|
|
370
|
+
aliases: {
|
|
371
|
+
X: "method",
|
|
372
|
+
d: "body"
|
|
373
|
+
},
|
|
209
374
|
positional: {
|
|
210
375
|
kind: "tuple",
|
|
211
376
|
parameters: [{
|
|
@@ -230,7 +395,7 @@ Examples:
|
|
|
230
395
|
};
|
|
231
396
|
if (!url) {
|
|
232
397
|
if (isConfigured()) {
|
|
233
|
-
const { displayStatus } = await import("../status-
|
|
398
|
+
const { displayStatus } = await import("../status-CJPUbh6Z.js");
|
|
234
399
|
await displayStatus();
|
|
235
400
|
console.log();
|
|
236
401
|
console.log(pc.dim(" Commands:"));
|
|
@@ -282,7 +447,7 @@ Examples:
|
|
|
282
447
|
process.exit(1);
|
|
283
448
|
}
|
|
284
449
|
dim(" No wallet found. Let's set one up first.\n");
|
|
285
|
-
const { runSetup } = await import("../setup-
|
|
450
|
+
const { runSetup } = await import("../setup-CJwYRd78.js");
|
|
286
451
|
await runSetup();
|
|
287
452
|
console.log();
|
|
288
453
|
wallet = resolveWallet();
|
|
@@ -295,7 +460,7 @@ Examples:
|
|
|
295
460
|
verbose(`protocol: ${resolvedProtocol ?? "auto-detect"}, maxDeposit: ${maxDeposit}`);
|
|
296
461
|
let preferredNetwork = config?.defaultNetwork;
|
|
297
462
|
if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
|
|
298
|
-
const { fetchAllBalances } = await import("../wallet-
|
|
463
|
+
const { fetchAllBalances } = await import("../wallet-CJBRFJw8.js");
|
|
299
464
|
const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
|
|
300
465
|
const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
|
|
301
466
|
const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
|
|
@@ -336,7 +501,13 @@ Examples:
|
|
|
336
501
|
verbose("opening SSE session...");
|
|
337
502
|
const tokens = await mppHandler.sse(parsedUrl.toString(), init);
|
|
338
503
|
verbose("SSE stream opened, reading tokens...");
|
|
339
|
-
|
|
504
|
+
try {
|
|
505
|
+
for await (const token of tokens) process.stdout.write(token);
|
|
506
|
+
} catch (streamErr) {
|
|
507
|
+
const msg = streamErr instanceof Error ? streamErr.message : String(streamErr);
|
|
508
|
+
verbose(`SSE stream error: ${msg}`);
|
|
509
|
+
if (!msg.includes("terminated")) throw streamErr;
|
|
510
|
+
}
|
|
340
511
|
verbose("SSE stream complete");
|
|
341
512
|
} finally {
|
|
342
513
|
await closeMppSession(mppHandler);
|
|
@@ -352,7 +523,7 @@ Examples:
|
|
|
352
523
|
usedProtocol = "mpp";
|
|
353
524
|
const elapsedMs = Date.now() - startMs;
|
|
354
525
|
const spentAmount = mppPayment.amount ? Number(mppPayment.amount) : void 0;
|
|
355
|
-
if (mppPayment && isTTY()) info(` MPP session: ${spentAmount != null ? `${spentAmount
|
|
526
|
+
if (mppPayment && isTTY()) info(` MPP session: ${spentAmount != null ? `${formatAmount(spentAmount, "USDC")} ` : ""}(${displayNetwork(mppPayment.network)})`);
|
|
356
527
|
if (isTTY()) dim(` Streamed (${elapsedMs}ms)`);
|
|
357
528
|
if (mppPayment) {
|
|
358
529
|
const record = {
|
|
@@ -445,13 +616,13 @@ Examples:
|
|
|
445
616
|
if (accepts.length > 0) {
|
|
446
617
|
const cheapest = accepts.reduce((min, a) => Number(a.amount) < Number(min.amount) ? a : min);
|
|
447
618
|
costNum = Number(cheapest.amount) / 1e6;
|
|
448
|
-
costStr = costNum
|
|
619
|
+
costStr = formatUsdcValue(costNum);
|
|
449
620
|
}
|
|
450
621
|
const hasEvm = accepts.some((a) => a.network.startsWith("eip155:"));
|
|
451
622
|
const hasSolana = accepts.some((a) => a.network.startsWith("solana:"));
|
|
452
623
|
const hasMpp = detected.mpp;
|
|
453
624
|
const hasOther = accepts.some((a) => !a.network.startsWith("eip155:") && !a.network.startsWith("solana:"));
|
|
454
|
-
const { fetchAllBalances } = await import("../wallet-
|
|
625
|
+
const { fetchAllBalances } = await import("../wallet-CJBRFJw8.js");
|
|
455
626
|
const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
|
|
456
627
|
const evmUsdc = hasEvm && balances.evm ? Number(balances.evm.usdc) : 0;
|
|
457
628
|
const solUsdc = hasSolana && balances.sol ? Number(balances.sol.usdc) : 0;
|
|
@@ -470,9 +641,9 @@ Examples:
|
|
|
470
641
|
if (payment) dim(" Payment was signed and sent but rejected by the server.");
|
|
471
642
|
else dim(" Payment was not attempted despite sufficient balance.");
|
|
472
643
|
if (serverReason) dim(` Reason: ${serverReason}`);
|
|
473
|
-
if (hasEvm && wallet.evmAddress && evmUsdc > 0) console.error(` Base: ${pc.cyan(wallet.evmAddress)} ${pc.dim(`(${evmUsdc
|
|
474
|
-
if (hasMpp && wallet.evmAddress && tempoUsdc > 0) console.error(` Tempo: ${pc.cyan(wallet.evmAddress)} ${pc.dim(`(${tempoUsdc
|
|
475
|
-
if (hasSolana && wallet.solanaAddress && solUsdc > 0) console.error(` Solana: ${pc.cyan(wallet.solanaAddress)} ${pc.dim(`(${solUsdc
|
|
644
|
+
if (hasEvm && wallet.evmAddress && evmUsdc > 0) console.error(` Base: ${pc.cyan(wallet.evmAddress)} ${pc.dim(`(${formatAmount(evmUsdc, "USDC")})`)}`);
|
|
645
|
+
if (hasMpp && wallet.evmAddress && tempoUsdc > 0) console.error(` Tempo: ${pc.cyan(wallet.evmAddress)} ${pc.dim(`(${formatAmount(tempoUsdc, "USDC")})`)}`);
|
|
646
|
+
if (hasSolana && wallet.solanaAddress && solUsdc > 0) console.error(` Solana: ${pc.cyan(wallet.solanaAddress)} ${pc.dim(`(${formatAmount(solUsdc, "USDC")})`)}`);
|
|
476
647
|
console.error();
|
|
477
648
|
dim(" This may be a temporary server-side issue. Try again in a moment.");
|
|
478
649
|
console.error();
|
|
@@ -482,15 +653,15 @@ Examples:
|
|
|
482
653
|
console.error();
|
|
483
654
|
dim(" Fund your wallet with USDC:");
|
|
484
655
|
if (hasEvm && wallet.evmAddress) {
|
|
485
|
-
const balHint = evmUsdc > 0 ? pc.dim(` (${evmUsdc
|
|
656
|
+
const balHint = evmUsdc > 0 ? pc.dim(` (${formatAmount(evmUsdc, "USDC")})`) : "";
|
|
486
657
|
console.error(` Base: ${pc.cyan(wallet.evmAddress)}${balHint}`);
|
|
487
658
|
}
|
|
488
659
|
if (hasMpp && wallet.evmAddress) {
|
|
489
|
-
const balHint = tempoUsdc > 0 ? pc.dim(` (${tempoUsdc
|
|
660
|
+
const balHint = tempoUsdc > 0 ? pc.dim(` (${formatAmount(tempoUsdc, "USDC")})`) : "";
|
|
490
661
|
console.error(` Tempo: ${pc.cyan(wallet.evmAddress)}${balHint}`);
|
|
491
662
|
}
|
|
492
663
|
if (hasSolana && wallet.solanaAddress) {
|
|
493
|
-
const balHint = solUsdc > 0 ? pc.dim(` (${solUsdc
|
|
664
|
+
const balHint = solUsdc > 0 ? pc.dim(` (${formatAmount(solUsdc, "USDC")})`) : "";
|
|
494
665
|
console.error(` Solana: ${pc.cyan(wallet.solanaAddress)}${balHint}`);
|
|
495
666
|
}
|
|
496
667
|
if (hasEvm && !wallet.evmAddress) dim(" Base: endpoint accepts EVM but no EVM wallet configured");
|
|
@@ -509,8 +680,8 @@ Examples:
|
|
|
509
680
|
return;
|
|
510
681
|
}
|
|
511
682
|
if (payment && isTTY()) {
|
|
512
|
-
if (usedProtocol === "mpp" && mppPayment) info(` Payment: MPP (${displayNetwork(mppPayment.network)})`);
|
|
513
|
-
else if (x402Payment) info(` Payment: ${x402Payment.amount ? (Number(x402Payment.amount) / 1e6)
|
|
683
|
+
if (usedProtocol === "mpp" && mppPayment) info(` Payment:${mppPayment.amount ? ` ${formatAmount(Number(mppPayment.amount), "USDC")}` : ""} MPP (${displayNetwork(mppPayment.network)})`);
|
|
684
|
+
else if (x402Payment) info(` Payment: ${x402Payment.amount ? formatAmount(Number(x402Payment.amount) / 1e6, "USDC") : "? USDC"} (${displayNetwork(x402Payment.network ?? "unknown")})`);
|
|
514
685
|
if (txSig) dim(` Tx: ${txSig}`);
|
|
515
686
|
}
|
|
516
687
|
if (isTTY()) dim(` ${response.status} ${response.statusText} (${elapsedMs}ms)`);
|
|
@@ -626,7 +797,7 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
626
797
|
const { StreamableHTTPClientTransport } = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
|
627
798
|
const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
|
|
628
799
|
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
629
|
-
const { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ToolListChangedNotificationSchema, ResourceListChangedNotificationSchema } = await import("@modelcontextprotocol/sdk/types.js");
|
|
800
|
+
const { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ToolListChangedNotificationSchema, ResourceListChangedNotificationSchema, McpError } = await import("@modelcontextprotocol/sdk/types.js");
|
|
630
801
|
async function connectTransport(target) {
|
|
631
802
|
try {
|
|
632
803
|
const transport = new StreamableHTTPClientTransport(new URL(remoteUrl));
|
|
@@ -648,7 +819,7 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
648
819
|
async function startX402Proxy() {
|
|
649
820
|
let preferredNetwork = config?.defaultNetwork;
|
|
650
821
|
if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
|
|
651
|
-
const { fetchAllBalances } = await import("../wallet-
|
|
822
|
+
const { fetchAllBalances } = await import("../wallet-CJBRFJw8.js");
|
|
652
823
|
const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
|
|
653
824
|
const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
|
|
654
825
|
const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
|
|
@@ -662,15 +833,18 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
662
833
|
spendLimitPerTx: config?.spendLimitPerTx
|
|
663
834
|
});
|
|
664
835
|
const { x402MCPClient } = await import("@x402/mcp");
|
|
836
|
+
function warnPayment(accepts, toolName) {
|
|
837
|
+
const accept = accepts?.[0];
|
|
838
|
+
if (accept) warn(` Payment: ${accept.amount ? formatAmount(Number(accept.amount) / 1e6, "USDC") : "? USDC"} on ${displayNetwork(accept.network)} for tool "${toolName}"`);
|
|
839
|
+
}
|
|
665
840
|
const remoteClient = new Client({
|
|
666
841
|
name: "x402-proxy",
|
|
667
|
-
version: "0.
|
|
842
|
+
version: "0.8.1"
|
|
668
843
|
});
|
|
669
844
|
const x402Mcp = new x402MCPClient(remoteClient, x402PaymentClient, {
|
|
670
845
|
autoPayment: true,
|
|
671
846
|
onPaymentRequested: (ctx) => {
|
|
672
|
-
|
|
673
|
-
if (accept) warn(` Payment: ${accept.amount ? (Number(accept.amount) / 1e6).toFixed(4) : "?"} USDC on ${displayNetwork(accept.network)} for tool "${ctx.toolName}"`);
|
|
847
|
+
warnPayment(ctx.paymentRequired.accepts, ctx.toolName);
|
|
674
848
|
return true;
|
|
675
849
|
}
|
|
676
850
|
});
|
|
@@ -703,7 +877,7 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
703
877
|
}
|
|
704
878
|
const localServer = new Server({
|
|
705
879
|
name: "x402-proxy",
|
|
706
|
-
version: "0.
|
|
880
|
+
version: "0.8.1"
|
|
707
881
|
}, { capabilities: {
|
|
708
882
|
tools: tools.length > 0 ? {} : void 0,
|
|
709
883
|
resources: remoteResources.length > 0 ? {} : void 0
|
|
@@ -716,11 +890,28 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
716
890
|
})) }));
|
|
717
891
|
localServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
718
892
|
const { name, arguments: args } = request.params;
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
893
|
+
try {
|
|
894
|
+
const result = await x402Mcp.callTool(name, args ?? {});
|
|
895
|
+
return {
|
|
896
|
+
content: result.content,
|
|
897
|
+
isError: result.isError
|
|
898
|
+
};
|
|
899
|
+
} catch (err) {
|
|
900
|
+
if (err instanceof McpError && err.code === -32042) {
|
|
901
|
+
const x402PaymentRequired = err.data?.x402;
|
|
902
|
+
if (x402PaymentRequired) {
|
|
903
|
+
const accepts = x402PaymentRequired.accepts;
|
|
904
|
+
warnPayment(accepts, name);
|
|
905
|
+
const paymentPayload = await x402PaymentClient.createPaymentPayload(x402PaymentRequired);
|
|
906
|
+
const result = await x402Mcp.callToolWithPayment(name, args ?? {}, paymentPayload);
|
|
907
|
+
return {
|
|
908
|
+
content: result.content,
|
|
909
|
+
isError: result.isError
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
throw err;
|
|
914
|
+
}
|
|
724
915
|
});
|
|
725
916
|
if (remoteResources.length > 0) {
|
|
726
917
|
localServer.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: remoteResources.map((r) => ({
|
|
@@ -767,15 +958,24 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
767
958
|
const { privateKeyToAccount } = await import("viem/accounts");
|
|
768
959
|
const account = privateKeyToAccount(wallet.evmKey);
|
|
769
960
|
const maxDeposit = config?.mppSessionBudget ?? "1";
|
|
961
|
+
let lastChallengeAmount;
|
|
962
|
+
const wrappedMethods = tempo({
|
|
963
|
+
account,
|
|
964
|
+
maxDeposit
|
|
965
|
+
}).map((m) => ({
|
|
966
|
+
...m,
|
|
967
|
+
createCredential: async (params) => {
|
|
968
|
+
const req = params.challenge.request;
|
|
969
|
+
if (req.amount) lastChallengeAmount = Number(req.amount) / 10 ** (req.decimals ?? 6);
|
|
970
|
+
return m.createCredential(params);
|
|
971
|
+
}
|
|
972
|
+
}));
|
|
770
973
|
const remoteClient = new Client({
|
|
771
974
|
name: "x402-proxy",
|
|
772
|
-
version: "0.
|
|
975
|
+
version: "0.8.1"
|
|
773
976
|
});
|
|
774
977
|
await connectTransport(remoteClient);
|
|
775
|
-
const mppClient = McpClient.wrap(remoteClient, { methods:
|
|
776
|
-
account,
|
|
777
|
-
maxDeposit
|
|
778
|
-
})] });
|
|
978
|
+
const mppClient = McpClient.wrap(remoteClient, { methods: wrappedMethods });
|
|
779
979
|
let { tools } = await remoteClient.listTools();
|
|
780
980
|
dim(` ${tools.length} tools available`);
|
|
781
981
|
let remoteResources = [];
|
|
@@ -787,7 +987,7 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
787
987
|
}
|
|
788
988
|
const localServer = new Server({
|
|
789
989
|
name: "x402-proxy",
|
|
790
|
-
version: "0.
|
|
990
|
+
version: "0.8.1"
|
|
791
991
|
}, { capabilities: {
|
|
792
992
|
tools: tools.length > 0 ? {} : void 0,
|
|
793
993
|
resources: remoteResources.length > 0 ? {} : void 0
|
|
@@ -812,11 +1012,14 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
812
1012
|
net: TEMPO_NETWORK,
|
|
813
1013
|
from: wallet.evmAddress ?? "unknown",
|
|
814
1014
|
tx: result.receipt.reference,
|
|
1015
|
+
amount: lastChallengeAmount,
|
|
815
1016
|
token: "USDC",
|
|
816
1017
|
label: `mcp:${name}`
|
|
817
1018
|
};
|
|
818
1019
|
appendHistory(getHistoryPath(), record);
|
|
819
|
-
|
|
1020
|
+
const amountStr = lastChallengeAmount !== void 0 ? formatAmount(lastChallengeAmount, "USDC") : "";
|
|
1021
|
+
warn(` MPP payment for tool "${name}" (Tempo)${amountStr ? ` \u00b7 ${amountStr}` : ""}`);
|
|
1022
|
+
lastChallengeAmount = void 0;
|
|
820
1023
|
}
|
|
821
1024
|
return {
|
|
822
1025
|
content: result.content,
|
|
@@ -959,7 +1162,7 @@ const walletHistoryCommand = buildCommand({
|
|
|
959
1162
|
console.log(line);
|
|
960
1163
|
}
|
|
961
1164
|
console.log();
|
|
962
|
-
console.log(pc.dim(` Today: ${spend.today
|
|
1165
|
+
console.log(pc.dim(` Today: ${formatAmount(spend.today, "USDC")} | Total: ${formatAmount(spend.total, "USDC")} | ${spend.count} transactions`));
|
|
963
1166
|
console.log();
|
|
964
1167
|
}
|
|
965
1168
|
});
|
|
@@ -979,6 +1182,15 @@ const routes = buildRouteMap({
|
|
|
979
1182
|
defaultCommand: "info",
|
|
980
1183
|
docs: { brief: "Wallet management" }
|
|
981
1184
|
}),
|
|
1185
|
+
config: buildRouteMap({
|
|
1186
|
+
routes: {
|
|
1187
|
+
show: configShowCommand,
|
|
1188
|
+
set: configSetCommand,
|
|
1189
|
+
unset: configUnsetCommand
|
|
1190
|
+
},
|
|
1191
|
+
defaultCommand: "show",
|
|
1192
|
+
docs: { brief: "Manage configuration" }
|
|
1193
|
+
}),
|
|
982
1194
|
setup: setupCommand,
|
|
983
1195
|
status: statusCommand
|
|
984
1196
|
},
|
|
@@ -987,7 +1199,7 @@ const routes = buildRouteMap({
|
|
|
987
1199
|
});
|
|
988
1200
|
const app = buildApplication(routes, {
|
|
989
1201
|
name: "x402-proxy",
|
|
990
|
-
versionInfo: { currentVersion: "0.
|
|
1202
|
+
versionInfo: { currentVersion: "0.8.1" },
|
|
991
1203
|
scanner: { caseStyle: "allow-kebab-for-camel" }
|
|
992
1204
|
});
|
|
993
1205
|
|
|
@@ -999,7 +1211,7 @@ function buildContext(process) {
|
|
|
999
1211
|
|
|
1000
1212
|
//#endregion
|
|
1001
1213
|
//#region src/bin/cli.ts
|
|
1002
|
-
await run(app, process.argv.slice(2), buildContext(process));
|
|
1214
|
+
await run(app, process.argv.slice(2).map((a) => a === "-H" ? "--header" : a), buildContext(process));
|
|
1003
1215
|
|
|
1004
1216
|
//#endregion
|
|
1005
1217
|
export { };
|
|
@@ -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.1`];
|
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "x402-proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "curl for x402 paid APIs. Auto-pays any endpoint on Base, Solana, and Tempo. Also works as an OpenClaw plugin.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@x402/evm": "^2.6.0",
|
|
40
40
|
"@x402/fetch": "^2.6.0",
|
|
41
41
|
"@x402/mcp": "^2.6.0",
|
|
42
|
-
"mppx": "^0.4.
|
|
42
|
+
"mppx": "^0.4.9",
|
|
43
43
|
"@x402/svm": "^2.6.0",
|
|
44
44
|
"ethers": "^6.0.0",
|
|
45
45
|
"picocolors": "^1.1.1",
|
package/skills/SKILL.md
CHANGED
|
@@ -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