x402-proxy 0.3.2 → 0.4.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/README.md +3 -0
- package/dist/bin/cli.js +107 -30
- package/dist/{derive-Rikypf5B.js → derive-CISr_ond.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/setup-Crq9TylJ.js +4 -0
- package/dist/{setup-CvB0go56.js → setup-gla-Qyqi.js} +1 -1
- package/dist/{status-4nQP3Fpu.js → status-0rAVbrke.js} +1 -1
- package/dist/status-B7iFmj8f.js +72 -0
- package/dist/wallet-BX5vQtUz.js +4 -0
- package/dist/{status-Cp51UDnh.js → wallet-Vp9zRQzC.js} +42 -81
- package/package.json +1 -1
- package/dist/setup-DtPaKQ37.js +0 -4
package/README.md
CHANGED
|
@@ -46,6 +46,9 @@ $ npx x402-proxy --method POST \
|
|
|
46
46
|
--body '{"url":"https://x402.org"}' \
|
|
47
47
|
https://web.surf.cascade.fyi/v1/crawl
|
|
48
48
|
|
|
49
|
+
# Force a specific network
|
|
50
|
+
$ npx x402-proxy --network base https://api.example.com/data
|
|
51
|
+
|
|
49
52
|
# Pipe-safe
|
|
50
53
|
$ npx x402-proxy https://api.example.com/data | jq '.results'
|
|
51
54
|
```
|
package/dist/bin/cli.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as
|
|
3
|
-
import { c as isConfigured, i as ensureConfigDir, l as loadConfig, o as getHistoryPath, u as loadWalletFile } from "../derive-
|
|
4
|
-
import { n as setupCommand } from "../setup-
|
|
2
|
+
import { a as buildX402Client, c as error, d as warn, f as appendHistory, g as readHistory, h as formatTxLine, i as walletInfoCommand, l as info, m as displayNetwork, o as resolveWallet, p as calcSpend, s as dim, u as isTTY } from "../wallet-Vp9zRQzC.js";
|
|
3
|
+
import { c as isConfigured, i as ensureConfigDir, l as loadConfig, o as getHistoryPath, u as loadWalletFile } from "../derive-CISr_ond.js";
|
|
4
|
+
import { n as setupCommand } from "../setup-gla-Qyqi.js";
|
|
5
|
+
import { n as statusCommand } from "../status-B7iFmj8f.js";
|
|
5
6
|
import { buildApplication, buildCommand, buildRouteMap, run } from "@stricli/core";
|
|
6
7
|
import pc from "picocolors";
|
|
7
8
|
import { decodePaymentResponseHeader, wrapFetchWithPayment } from "@x402/fetch";
|
|
@@ -38,7 +39,7 @@ function createX402ProxyHandler(opts) {
|
|
|
38
39
|
paymentQueue.push({
|
|
39
40
|
network: hookCtx.selectedRequirements.network,
|
|
40
41
|
payTo: hookCtx.selectedRequirements.payTo,
|
|
41
|
-
amount: raw
|
|
42
|
+
amount: raw,
|
|
42
43
|
asset: hookCtx.selectedRequirements.asset
|
|
43
44
|
});
|
|
44
45
|
});
|
|
@@ -93,6 +94,12 @@ Examples:
|
|
|
93
94
|
parse: String,
|
|
94
95
|
optional: true
|
|
95
96
|
},
|
|
97
|
+
network: {
|
|
98
|
+
kind: "parsed",
|
|
99
|
+
brief: "Require specific network (base, solana)",
|
|
100
|
+
parse: String,
|
|
101
|
+
optional: true
|
|
102
|
+
},
|
|
96
103
|
json: {
|
|
97
104
|
kind: "boolean",
|
|
98
105
|
brief: "Force JSON output",
|
|
@@ -111,7 +118,7 @@ Examples:
|
|
|
111
118
|
async func(flags, url) {
|
|
112
119
|
if (!url) {
|
|
113
120
|
if (isConfigured()) {
|
|
114
|
-
const { displayStatus } = await import("../status-
|
|
121
|
+
const { displayStatus } = await import("../status-0rAVbrke.js");
|
|
115
122
|
await displayStatus();
|
|
116
123
|
console.log();
|
|
117
124
|
console.log(pc.dim(" Commands:"));
|
|
@@ -163,15 +170,25 @@ Examples:
|
|
|
163
170
|
process.exit(1);
|
|
164
171
|
}
|
|
165
172
|
dim(" No wallet found. Let's set one up first.\n");
|
|
166
|
-
const { runSetup } = await import("../setup-
|
|
173
|
+
const { runSetup } = await import("../setup-Crq9TylJ.js");
|
|
167
174
|
await runSetup();
|
|
168
175
|
console.log();
|
|
169
176
|
wallet = resolveWallet();
|
|
170
177
|
if (wallet.source === "none") return;
|
|
171
178
|
}
|
|
172
179
|
const config = loadConfig();
|
|
180
|
+
let preferredNetwork = config?.defaultNetwork;
|
|
181
|
+
if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
|
|
182
|
+
const { fetchEvmBalances, fetchSolanaBalances } = await import("../wallet-BX5vQtUz.js");
|
|
183
|
+
const [evmBal, solBal] = await Promise.allSettled([fetchEvmBalances(wallet.evmAddress), fetchSolanaBalances(wallet.solanaAddress)]);
|
|
184
|
+
const evmUsdc = evmBal.status === "fulfilled" ? Number(evmBal.value?.usdc ?? 0) : 0;
|
|
185
|
+
const solUsdc = solBal.status === "fulfilled" ? Number(solBal.value?.usdc ?? 0) : 0;
|
|
186
|
+
if (evmUsdc > solUsdc) preferredNetwork = "base";
|
|
187
|
+
else if (solUsdc > evmUsdc) preferredNetwork = "solana";
|
|
188
|
+
}
|
|
173
189
|
const { x402Fetch, shiftPayment } = createX402ProxyHandler({ client: await buildX402Client(wallet, {
|
|
174
|
-
preferredNetwork
|
|
190
|
+
preferredNetwork,
|
|
191
|
+
network: flags.network,
|
|
175
192
|
spendLimitDaily: config?.spendLimitDaily,
|
|
176
193
|
spendLimitPerTx: config?.spendLimitPerTx
|
|
177
194
|
}) });
|
|
@@ -204,33 +221,75 @@ Examples:
|
|
|
204
221
|
if (prHeader) try {
|
|
205
222
|
accepts = JSON.parse(Buffer.from(prHeader, "base64").toString()).accepts ?? [];
|
|
206
223
|
} catch {}
|
|
224
|
+
let costNum = 0;
|
|
225
|
+
let costStr = "?";
|
|
207
226
|
if (accepts.length > 0) {
|
|
208
227
|
const cheapest = accepts.reduce((min, a) => Number(a.amount) < Number(min.amount) ? a : min);
|
|
209
|
-
|
|
210
|
-
|
|
228
|
+
costNum = Number(cheapest.amount) / 1e6;
|
|
229
|
+
costStr = costNum.toFixed(4);
|
|
230
|
+
}
|
|
211
231
|
const hasEvm = accepts.some((a) => a.network.startsWith("eip155:"));
|
|
212
232
|
const hasSolana = accepts.some((a) => a.network.startsWith("solana:"));
|
|
213
233
|
const hasOther = accepts.some((a) => !a.network.startsWith("eip155:") && !a.network.startsWith("solana:"));
|
|
214
|
-
|
|
234
|
+
const { fetchEvmBalances, fetchSolanaBalances } = await import("../wallet-BX5vQtUz.js");
|
|
235
|
+
let evmUsdc = 0;
|
|
236
|
+
let solUsdc = 0;
|
|
237
|
+
if (hasEvm && wallet.evmAddress) try {
|
|
238
|
+
const bal = await fetchEvmBalances(wallet.evmAddress);
|
|
239
|
+
evmUsdc = Number(bal.usdc);
|
|
240
|
+
} catch {}
|
|
241
|
+
if (hasSolana && wallet.solanaAddress) try {
|
|
242
|
+
const bal = await fetchSolanaBalances(wallet.solanaAddress);
|
|
243
|
+
solUsdc = Number(bal.usdc);
|
|
244
|
+
} catch {}
|
|
245
|
+
if (hasEvm && evmUsdc >= costNum || hasSolana && solUsdc >= costNum) {
|
|
246
|
+
let serverReason;
|
|
247
|
+
try {
|
|
248
|
+
const body = await response.text();
|
|
249
|
+
if (body) {
|
|
250
|
+
const parsed = JSON.parse(body);
|
|
251
|
+
serverReason = parsed.error || parsed.message;
|
|
252
|
+
}
|
|
253
|
+
} catch {}
|
|
254
|
+
error(`Payment failed: ${costStr} USDC`);
|
|
255
|
+
console.error();
|
|
256
|
+
if (payment) dim(" Payment was signed and sent but rejected by the server.");
|
|
257
|
+
else dim(" Payment was not attempted despite sufficient balance.");
|
|
258
|
+
if (serverReason) dim(` Reason: ${serverReason}`);
|
|
259
|
+
if (hasEvm && wallet.evmAddress && evmUsdc > 0) console.error(` Base: ${pc.cyan(wallet.evmAddress)} ${pc.dim(`(${evmUsdc.toFixed(4)} USDC)`)}`);
|
|
260
|
+
if (hasSolana && wallet.solanaAddress && solUsdc > 0) console.error(` Solana: ${pc.cyan(wallet.solanaAddress)} ${pc.dim(`(${solUsdc.toFixed(4)} USDC)`)}`);
|
|
215
261
|
console.error();
|
|
216
|
-
dim("
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (
|
|
221
|
-
|
|
222
|
-
|
|
262
|
+
dim(" This may be a temporary server-side issue. Try again in a moment.");
|
|
263
|
+
console.error();
|
|
264
|
+
} else {
|
|
265
|
+
error(`Payment required: ${costStr} USDC`);
|
|
266
|
+
if (hasEvm || hasSolana) {
|
|
267
|
+
console.error();
|
|
268
|
+
dim(" Fund your wallet with USDC:");
|
|
269
|
+
if (hasEvm && wallet.evmAddress) {
|
|
270
|
+
const balHint = evmUsdc > 0 ? pc.dim(` (${evmUsdc.toFixed(4)} USDC)`) : "";
|
|
271
|
+
console.error(` Base: ${pc.cyan(wallet.evmAddress)}${balHint}`);
|
|
272
|
+
}
|
|
273
|
+
if (hasSolana && wallet.solanaAddress) {
|
|
274
|
+
const balHint = solUsdc > 0 ? pc.dim(` (${solUsdc.toFixed(4)} USDC)`) : "";
|
|
275
|
+
console.error(` Solana: ${pc.cyan(wallet.solanaAddress)}${balHint}`);
|
|
276
|
+
}
|
|
277
|
+
if (hasEvm && !wallet.evmAddress) dim(" Base: endpoint accepts EVM but no EVM wallet configured");
|
|
278
|
+
if (hasSolana && !wallet.solanaAddress) dim(" Solana: endpoint accepts Solana but no Solana wallet configured");
|
|
279
|
+
} else if (hasOther) {
|
|
280
|
+
const networks = [...new Set(accepts.map((a) => a.network))].join(", ");
|
|
281
|
+
console.error();
|
|
282
|
+
error(`This endpoint only accepts payment on unsupported networks: ${networks}`);
|
|
283
|
+
}
|
|
284
|
+
console.error();
|
|
285
|
+
dim(" Then re-run:");
|
|
286
|
+
console.error(` ${pc.cyan(`$ npx x402-proxy ${url}`)}`);
|
|
223
287
|
console.error();
|
|
224
|
-
error(`This endpoint only accepts payment on unsupported networks: ${networks}`);
|
|
225
288
|
}
|
|
226
|
-
console.error();
|
|
227
|
-
dim(" Then re-run:");
|
|
228
|
-
console.error(` ${pc.cyan(`$ npx x402-proxy ${url}`)}`);
|
|
229
|
-
console.error();
|
|
230
289
|
return;
|
|
231
290
|
}
|
|
232
291
|
if (payment && isTTY()) {
|
|
233
|
-
info(` Payment: ${payment.amount ? (Number(payment.amount) / 1e6).toFixed(4) : "?"} USDC (${payment.network ?? "unknown"})`);
|
|
292
|
+
info(` Payment: ${payment.amount ? (Number(payment.amount) / 1e6).toFixed(4) : "?"} USDC (${displayNetwork(payment.network ?? "unknown")})`);
|
|
234
293
|
if (txSig) dim(` Tx: ${txSig}`);
|
|
235
294
|
}
|
|
236
295
|
if (isTTY()) dim(` ${response.status} ${response.statusText} (${elapsedMs}ms)`);
|
|
@@ -292,6 +351,12 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
292
351
|
brief: "Solana private key (base58)",
|
|
293
352
|
parse: String,
|
|
294
353
|
optional: true
|
|
354
|
+
},
|
|
355
|
+
network: {
|
|
356
|
+
kind: "parsed",
|
|
357
|
+
brief: "Require specific network (base, solana)",
|
|
358
|
+
parse: String,
|
|
359
|
+
optional: true
|
|
295
360
|
}
|
|
296
361
|
},
|
|
297
362
|
positional: {
|
|
@@ -316,8 +381,18 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
316
381
|
if (wallet.evmAddress) dim(` EVM: ${wallet.evmAddress}`);
|
|
317
382
|
if (wallet.solanaAddress) dim(` Solana: ${wallet.solanaAddress}`);
|
|
318
383
|
const config = loadConfig();
|
|
384
|
+
let preferredNetwork = config?.defaultNetwork;
|
|
385
|
+
if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
|
|
386
|
+
const { fetchEvmBalances, fetchSolanaBalances } = await import("../wallet-BX5vQtUz.js");
|
|
387
|
+
const [evmBal, solBal] = await Promise.allSettled([fetchEvmBalances(wallet.evmAddress), fetchSolanaBalances(wallet.solanaAddress)]);
|
|
388
|
+
const evmUsdc = evmBal.status === "fulfilled" ? Number(evmBal.value?.usdc ?? 0) : 0;
|
|
389
|
+
const solUsdc = solBal.status === "fulfilled" ? Number(solBal.value?.usdc ?? 0) : 0;
|
|
390
|
+
if (evmUsdc > solUsdc) preferredNetwork = "base";
|
|
391
|
+
else if (solUsdc > evmUsdc) preferredNetwork = "solana";
|
|
392
|
+
}
|
|
319
393
|
const x402PaymentClient = await buildX402Client(wallet, {
|
|
320
|
-
preferredNetwork
|
|
394
|
+
preferredNetwork,
|
|
395
|
+
network: flags.network,
|
|
321
396
|
spendLimitDaily: config?.spendLimitDaily,
|
|
322
397
|
spendLimitPerTx: config?.spendLimitPerTx
|
|
323
398
|
});
|
|
@@ -329,26 +404,28 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
329
404
|
const { x402MCPClient } = await import("@x402/mcp");
|
|
330
405
|
const x402Mcp = new x402MCPClient(new Client({
|
|
331
406
|
name: "x402-proxy",
|
|
332
|
-
version: "0.
|
|
407
|
+
version: "0.4.0"
|
|
333
408
|
}), x402PaymentClient, {
|
|
334
409
|
autoPayment: true,
|
|
335
410
|
onPaymentRequested: (ctx) => {
|
|
336
411
|
const accept = ctx.paymentRequired.accepts?.[0];
|
|
337
|
-
if (accept) warn(` Payment: ${accept.amount} on ${accept.network} for tool "${ctx.toolName}"`);
|
|
412
|
+
if (accept) warn(` Payment: ${accept.amount ? (Number(accept.amount) / 1e6).toFixed(4) : "?"} USDC on ${displayNetwork(accept.network)} for tool "${ctx.toolName}"`);
|
|
338
413
|
return true;
|
|
339
414
|
}
|
|
340
415
|
});
|
|
341
416
|
x402Mcp.onAfterPayment(async (ctx) => {
|
|
342
417
|
ensureConfigDir();
|
|
418
|
+
const accepted = ctx.paymentPayload.accepted;
|
|
343
419
|
const tx = ctx.settleResponse?.transaction;
|
|
344
|
-
const accept = ctx.paymentPayload;
|
|
345
420
|
const record = {
|
|
346
421
|
t: Date.now(),
|
|
347
422
|
ok: true,
|
|
348
423
|
kind: "x402_payment",
|
|
349
|
-
net:
|
|
424
|
+
net: accepted?.network ?? "unknown",
|
|
350
425
|
from: wallet.evmAddress ?? wallet.solanaAddress ?? "unknown",
|
|
426
|
+
to: accepted?.payTo,
|
|
351
427
|
tx: typeof tx === "string" ? tx : void 0,
|
|
428
|
+
amount: accepted?.amount ? Number(accepted.amount) / 1e6 : void 0,
|
|
352
429
|
token: "USDC",
|
|
353
430
|
label: `mcp:${ctx.toolName}`
|
|
354
431
|
};
|
|
@@ -374,7 +451,7 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
374
451
|
dim(` ${tools.length} tools available`);
|
|
375
452
|
const localServer = new McpServer({
|
|
376
453
|
name: "x402-proxy",
|
|
377
|
-
version: "0.
|
|
454
|
+
version: "0.4.0"
|
|
378
455
|
});
|
|
379
456
|
for (const tool of tools) localServer.tool(tool.name, tool.description ?? "", tool.inputSchema?.properties ? Object.fromEntries(Object.entries(tool.inputSchema.properties).map(([k, v]) => [k, v])) : {}, async (args) => {
|
|
380
457
|
const result = await x402Mcp.callTool(tool.name, args);
|
|
@@ -531,7 +608,7 @@ const routes = buildRouteMap({
|
|
|
531
608
|
});
|
|
532
609
|
const app = buildApplication(routes, {
|
|
533
610
|
name: "x402-proxy",
|
|
534
|
-
versionInfo: { currentVersion: "0.
|
|
611
|
+
versionInfo: { currentVersion: "0.4.0" },
|
|
535
612
|
scanner: { caseStyle: "allow-kebab-for-camel" }
|
|
536
613
|
});
|
|
537
614
|
|
|
@@ -3,8 +3,8 @@ import fs from "node:fs";
|
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { parse, stringify } from "yaml";
|
|
6
|
-
import { base58 } from "@scure/base";
|
|
7
6
|
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
7
|
+
import { base58 } from "@scure/base";
|
|
8
8
|
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
9
9
|
import { hmac } from "@noble/hashes/hmac.js";
|
|
10
10
|
import { sha512 } from "@noble/hashes/sha2.js";
|
package/dist/index.js
CHANGED
|
@@ -35,7 +35,7 @@ function createX402ProxyHandler(opts) {
|
|
|
35
35
|
paymentQueue.push({
|
|
36
36
|
network: hookCtx.selectedRequirements.network,
|
|
37
37
|
payTo: hookCtx.selectedRequirements.payTo,
|
|
38
|
-
amount: raw
|
|
38
|
+
amount: raw,
|
|
39
39
|
asset: hookCtx.selectedRequirements.asset
|
|
40
40
|
});
|
|
41
41
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as getConfigDirShort, c as isConfigured, d as saveConfig, f as saveWalletFile, n as deriveSolanaKeypair, r as generateMnemonic, s as getWalletPath, t as deriveEvmKeypair } from "./derive-
|
|
2
|
+
import { a as getConfigDirShort, c as isConfigured, d as saveConfig, f as saveWalletFile, n as deriveSolanaKeypair, r as generateMnemonic, s as getWalletPath, t as deriveEvmKeypair } from "./derive-CISr_ond.js";
|
|
3
3
|
import { buildCommand } from "@stricli/core";
|
|
4
4
|
import pc from "picocolors";
|
|
5
5
|
import * as prompts from "@clack/prompts";
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { g as readHistory, h as formatTxLine, n as fetchEvmBalances, o as resolveWallet, p as calcSpend, r as fetchSolanaBalances, s as dim, t as balanceLine } from "./wallet-Vp9zRQzC.js";
|
|
3
|
+
import { a as getConfigDirShort, l as loadConfig, o as getHistoryPath } from "./derive-CISr_ond.js";
|
|
4
|
+
import { buildCommand } from "@stricli/core";
|
|
5
|
+
import pc from "picocolors";
|
|
6
|
+
|
|
7
|
+
//#region src/commands/status.ts
|
|
8
|
+
async function displayStatus() {
|
|
9
|
+
const wallet = resolveWallet();
|
|
10
|
+
const config = loadConfig();
|
|
11
|
+
const records = readHistory(getHistoryPath());
|
|
12
|
+
const spend = calcSpend(records);
|
|
13
|
+
console.log();
|
|
14
|
+
console.log(pc.cyan(pc.bold("x402-proxy")));
|
|
15
|
+
console.log(pc.dim("curl for x402 paid APIs"));
|
|
16
|
+
console.log();
|
|
17
|
+
if (wallet.source === "none") {
|
|
18
|
+
console.log(pc.yellow(" No wallet configured."));
|
|
19
|
+
console.log(pc.dim(` Run ${pc.cyan("$ npx x402-proxy setup")} to create one.`));
|
|
20
|
+
} else {
|
|
21
|
+
const [evmResult, solResult] = await Promise.allSettled([wallet.evmAddress ? fetchEvmBalances(wallet.evmAddress) : Promise.resolve(null), wallet.solanaAddress ? fetchSolanaBalances(wallet.solanaAddress) : Promise.resolve(null)]);
|
|
22
|
+
const evm = evmResult.status === "fulfilled" ? evmResult.value : null;
|
|
23
|
+
const sol = solResult.status === "fulfilled" ? solResult.value : null;
|
|
24
|
+
if (wallet.evmAddress) {
|
|
25
|
+
const bal = evm ? balanceLine(evm.usdc, evm.eth, "ETH") : pc.dim(" (network error)");
|
|
26
|
+
console.log(` Base: ${pc.green(wallet.evmAddress)}${bal}`);
|
|
27
|
+
}
|
|
28
|
+
if (wallet.solanaAddress) {
|
|
29
|
+
const bal = sol ? balanceLine(sol.usdc, sol.sol, "SOL") : pc.dim(" (network error)");
|
|
30
|
+
console.log(` Solana: ${pc.green(wallet.solanaAddress)}${bal}`);
|
|
31
|
+
}
|
|
32
|
+
if (config?.spendLimitDaily || config?.spendLimitPerTx) {
|
|
33
|
+
console.log();
|
|
34
|
+
if (config.spendLimitDaily) {
|
|
35
|
+
const pct = config.spendLimitDaily > 0 ? Math.round(spend.today / config.spendLimitDaily * 100) : 0;
|
|
36
|
+
dim(` Daily limit: ${spend.today.toFixed(4)} / ${config.spendLimitDaily} USDC (${pct}%)`);
|
|
37
|
+
}
|
|
38
|
+
if (config.spendLimitPerTx) dim(` Per-tx limit: ${config.spendLimitPerTx} USDC`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
console.log();
|
|
42
|
+
if (spend.count > 0) {
|
|
43
|
+
const recent = records.slice(-5);
|
|
44
|
+
dim(" Recent transactions:");
|
|
45
|
+
for (const r of recent) {
|
|
46
|
+
const line = formatTxLine(r).replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
47
|
+
console.log(line);
|
|
48
|
+
}
|
|
49
|
+
console.log();
|
|
50
|
+
dim(` Today: ${spend.today.toFixed(4)} USDC | Total: ${spend.total.toFixed(4)} USDC | ${spend.count} tx`);
|
|
51
|
+
} else dim(" No payment history yet.");
|
|
52
|
+
console.log();
|
|
53
|
+
if (config?.defaultNetwork) dim(` Network: ${config.defaultNetwork}`);
|
|
54
|
+
dim(` Config: ${getConfigDirShort()}`);
|
|
55
|
+
}
|
|
56
|
+
const statusCommand = buildCommand({
|
|
57
|
+
docs: { brief: "Show configuration and wallet status" },
|
|
58
|
+
parameters: {
|
|
59
|
+
flags: {},
|
|
60
|
+
positional: {
|
|
61
|
+
kind: "tuple",
|
|
62
|
+
parameters: []
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
async func() {
|
|
66
|
+
await displayStatus();
|
|
67
|
+
console.log();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
//#endregion
|
|
72
|
+
export { statusCommand as n, displayStatus as t };
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { n as deriveSolanaKeypair, o as getHistoryPath, t as deriveEvmKeypair, u as loadWalletFile } from "./derive-CISr_ond.js";
|
|
3
3
|
import { buildCommand } from "@stricli/core";
|
|
4
4
|
import pc from "picocolors";
|
|
5
5
|
import { x402Client } from "@x402/fetch";
|
|
6
6
|
import { appendFileSync, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
7
8
|
import { base58 } from "@scure/base";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
9
|
+
import { toClientEvmSigner } from "@x402/evm";
|
|
10
|
+
import { registerExactEvmScheme } from "@x402/evm/exact/client";
|
|
11
|
+
import { registerExactSvmScheme } from "@x402/svm/exact/client";
|
|
10
12
|
import { createPublicClient, http } from "viem";
|
|
11
13
|
import { privateKeyToAccount } from "viem/accounts";
|
|
12
14
|
import { base } from "viem/chains";
|
|
@@ -96,6 +98,12 @@ function shortModel(model) {
|
|
|
96
98
|
const parts = model.split("/");
|
|
97
99
|
return parts[parts.length - 1].replace(/-\d{6,8}$/, "").replace(/-\d{4}$/, "");
|
|
98
100
|
}
|
|
101
|
+
function displayNetwork(net) {
|
|
102
|
+
if (net === "eip155:8453") return "Base";
|
|
103
|
+
if (net.startsWith("eip155:")) return `EVM (${net.split(":")[1]})`;
|
|
104
|
+
if (net.startsWith("solana:")) return "Solana";
|
|
105
|
+
return net;
|
|
106
|
+
}
|
|
99
107
|
function shortNetwork(net) {
|
|
100
108
|
if (net === "eip155:8453") return "base";
|
|
101
109
|
if (net.startsWith("eip155:")) return `evm:${net.split(":")[1]}`;
|
|
@@ -157,7 +165,10 @@ function resolveWallet(opts) {
|
|
|
157
165
|
result.evmKey = hex;
|
|
158
166
|
result.evmAddress = privateKeyToAccount(hex).address;
|
|
159
167
|
}
|
|
160
|
-
if (opts.solanaKey)
|
|
168
|
+
if (opts.solanaKey) {
|
|
169
|
+
result.solanaKey = parsesolanaKey(opts.solanaKey);
|
|
170
|
+
result.solanaAddress = solanaAddressFromKey(result.solanaKey);
|
|
171
|
+
}
|
|
161
172
|
return result;
|
|
162
173
|
}
|
|
163
174
|
const envEvm = process.env.X402_PROXY_WALLET_EVM_KEY;
|
|
@@ -169,7 +180,10 @@ function resolveWallet(opts) {
|
|
|
169
180
|
result.evmKey = hex;
|
|
170
181
|
result.evmAddress = privateKeyToAccount(hex).address;
|
|
171
182
|
}
|
|
172
|
-
if (envSol)
|
|
183
|
+
if (envSol) {
|
|
184
|
+
result.solanaKey = parsesolanaKey(envSol);
|
|
185
|
+
result.solanaAddress = solanaAddressFromKey(result.solanaKey);
|
|
186
|
+
}
|
|
173
187
|
return result;
|
|
174
188
|
}
|
|
175
189
|
const envMnemonic = process.env.X402_PROXY_WALLET_MNEMONIC;
|
|
@@ -200,6 +214,10 @@ function parsesolanaKey(input) {
|
|
|
200
214
|
}
|
|
201
215
|
return base58.decode(trimmed);
|
|
202
216
|
}
|
|
217
|
+
function solanaAddressFromKey(keyBytes) {
|
|
218
|
+
if (keyBytes.length >= 64) return base58.encode(keyBytes.slice(32));
|
|
219
|
+
return base58.encode(ed25519.getPublicKey(keyBytes));
|
|
220
|
+
}
|
|
203
221
|
function networkToCaipPrefix(name) {
|
|
204
222
|
switch (name.toLowerCase()) {
|
|
205
223
|
case "base": return "eip155:8453";
|
|
@@ -219,17 +237,25 @@ async function buildX402Client(wallet, opts) {
|
|
|
219
237
|
})() : void 0);
|
|
220
238
|
if (wallet.evmKey) {
|
|
221
239
|
const hex = wallet.evmKey;
|
|
222
|
-
|
|
240
|
+
registerExactEvmScheme(client, { signer: toClientEvmSigner(privateKeyToAccount(hex), createPublicClient({
|
|
223
241
|
chain: base,
|
|
224
242
|
transport: http()
|
|
225
|
-
}));
|
|
226
|
-
client.register("eip155:8453", new ExactEvmScheme(signer));
|
|
243
|
+
})) });
|
|
227
244
|
}
|
|
228
245
|
if (wallet.solanaKey) {
|
|
229
246
|
const { createKeyPairSignerFromBytes } = await import("@solana/kit");
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
247
|
+
registerExactSvmScheme(client, { signer: await createKeyPairSignerFromBytes(wallet.solanaKey) });
|
|
248
|
+
}
|
|
249
|
+
if (opts?.network) {
|
|
250
|
+
const prefix = networkToCaipPrefix(opts.network);
|
|
251
|
+
client.registerPolicy((_version, reqs) => {
|
|
252
|
+
const filtered = reqs.filter((r) => r.network.startsWith(prefix));
|
|
253
|
+
if (filtered.length === 0) {
|
|
254
|
+
const available = [...new Set(reqs.map((r) => displayNetwork(r.network)))].join(", ");
|
|
255
|
+
throw new Error(`Network '${opts.network}' not accepted. Available: ${available}`);
|
|
256
|
+
}
|
|
257
|
+
return filtered;
|
|
258
|
+
});
|
|
233
259
|
}
|
|
234
260
|
const daily = opts?.spendLimitDaily;
|
|
235
261
|
const perTx = opts?.spendLimitPerTx;
|
|
@@ -277,7 +303,7 @@ async function fetchEvmBalances(address) {
|
|
|
277
303
|
}, "latest"])]);
|
|
278
304
|
return {
|
|
279
305
|
eth: ethRes.result ? (Number(BigInt(ethRes.result)) / 0xde0b6b3a7640000).toFixed(6) : "?",
|
|
280
|
-
usdc: usdcRes.result ? (Number(BigInt(usdcRes.result)) / 1e6).toFixed(
|
|
306
|
+
usdc: usdcRes.result ? (Number(BigInt(usdcRes.result)) / 1e6).toFixed(4) : "?"
|
|
281
307
|
};
|
|
282
308
|
}
|
|
283
309
|
async function fetchSolanaBalances(address) {
|
|
@@ -290,7 +316,7 @@ async function fetchSolanaBalances(address) {
|
|
|
290
316
|
const accounts = usdcRes.result?.value;
|
|
291
317
|
return {
|
|
292
318
|
sol,
|
|
293
|
-
usdc: accounts?.length ? Number(accounts[0].account.data.parsed.info.tokenAmount.uiAmountString).toFixed(
|
|
319
|
+
usdc: accounts?.length ? Number(accounts[0].account.data.parsed.info.tokenAmount.uiAmountString).toFixed(4) : "0.0000"
|
|
294
320
|
};
|
|
295
321
|
}
|
|
296
322
|
function balanceLine(usdc, native, nativeSymbol) {
|
|
@@ -331,8 +357,8 @@ const walletInfoCommand = buildCommand({
|
|
|
331
357
|
const bal = sol ? balanceLine(sol.usdc, sol.sol, "SOL") : pc.dim(" (network error)");
|
|
332
358
|
console.log(` Solana: ${pc.green(wallet.solanaAddress)}${bal}`);
|
|
333
359
|
}
|
|
334
|
-
const evmEmpty = !evm || evm.usdc === "0.
|
|
335
|
-
const solEmpty = !sol || sol.usdc === "0.
|
|
360
|
+
const evmEmpty = !evm || evm.usdc === "0.0000";
|
|
361
|
+
const solEmpty = !sol || sol.usdc === "0.0000";
|
|
336
362
|
if (evmEmpty && solEmpty) {
|
|
337
363
|
console.log();
|
|
338
364
|
dim(" Send USDC to either address above to start using x402 APIs.");
|
|
@@ -357,69 +383,4 @@ const walletInfoCommand = buildCommand({
|
|
|
357
383
|
});
|
|
358
384
|
|
|
359
385
|
//#endregion
|
|
360
|
-
|
|
361
|
-
async function displayStatus() {
|
|
362
|
-
const wallet = resolveWallet();
|
|
363
|
-
const config = loadConfig();
|
|
364
|
-
const records = readHistory(getHistoryPath());
|
|
365
|
-
const spend = calcSpend(records);
|
|
366
|
-
console.log();
|
|
367
|
-
console.log(pc.cyan(pc.bold("x402-proxy")));
|
|
368
|
-
console.log(pc.dim("curl for x402 paid APIs"));
|
|
369
|
-
console.log();
|
|
370
|
-
if (wallet.source === "none") {
|
|
371
|
-
console.log(pc.yellow(" No wallet configured."));
|
|
372
|
-
console.log(pc.dim(` Run ${pc.cyan("$ npx x402-proxy setup")} to create one.`));
|
|
373
|
-
} else {
|
|
374
|
-
const [evmResult, solResult] = await Promise.allSettled([wallet.evmAddress ? fetchEvmBalances(wallet.evmAddress) : Promise.resolve(null), wallet.solanaAddress ? fetchSolanaBalances(wallet.solanaAddress) : Promise.resolve(null)]);
|
|
375
|
-
const evm = evmResult.status === "fulfilled" ? evmResult.value : null;
|
|
376
|
-
const sol = solResult.status === "fulfilled" ? solResult.value : null;
|
|
377
|
-
if (wallet.evmAddress) {
|
|
378
|
-
const bal = evm ? balanceLine(evm.usdc, evm.eth, "ETH") : pc.dim(" (network error)");
|
|
379
|
-
console.log(` Base: ${pc.green(wallet.evmAddress)}${bal}`);
|
|
380
|
-
}
|
|
381
|
-
if (wallet.solanaAddress) {
|
|
382
|
-
const bal = sol ? balanceLine(sol.usdc, sol.sol, "SOL") : pc.dim(" (network error)");
|
|
383
|
-
console.log(` Solana: ${pc.green(wallet.solanaAddress)}${bal}`);
|
|
384
|
-
}
|
|
385
|
-
if (config?.spendLimitDaily || config?.spendLimitPerTx) {
|
|
386
|
-
console.log();
|
|
387
|
-
if (config.spendLimitDaily) {
|
|
388
|
-
const pct = config.spendLimitDaily > 0 ? Math.round(spend.today / config.spendLimitDaily * 100) : 0;
|
|
389
|
-
dim(` Daily limit: ${spend.today.toFixed(4)} / ${config.spendLimitDaily} USDC (${pct}%)`);
|
|
390
|
-
}
|
|
391
|
-
if (config.spendLimitPerTx) dim(` Per-tx limit: ${config.spendLimitPerTx} USDC`);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
console.log();
|
|
395
|
-
if (spend.count > 0) {
|
|
396
|
-
const recent = records.slice(-5);
|
|
397
|
-
dim(" Recent transactions:");
|
|
398
|
-
for (const r of recent) {
|
|
399
|
-
const line = formatTxLine(r).replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
400
|
-
console.log(line);
|
|
401
|
-
}
|
|
402
|
-
console.log();
|
|
403
|
-
dim(` Today: ${spend.today.toFixed(4)} USDC | Total: ${spend.total.toFixed(4)} USDC | ${spend.count} tx`);
|
|
404
|
-
} else dim(" No payment history yet.");
|
|
405
|
-
console.log();
|
|
406
|
-
if (config?.defaultNetwork) dim(` Network: ${config.defaultNetwork}`);
|
|
407
|
-
dim(` Config: ${getConfigDirShort()}`);
|
|
408
|
-
}
|
|
409
|
-
const statusCommand = buildCommand({
|
|
410
|
-
docs: { brief: "Show configuration and wallet status" },
|
|
411
|
-
parameters: {
|
|
412
|
-
flags: {},
|
|
413
|
-
positional: {
|
|
414
|
-
kind: "tuple",
|
|
415
|
-
parameters: []
|
|
416
|
-
}
|
|
417
|
-
},
|
|
418
|
-
async func() {
|
|
419
|
-
await displayStatus();
|
|
420
|
-
console.log();
|
|
421
|
-
}
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
//#endregion
|
|
425
|
-
export { resolveWallet as a, info as c, appendHistory as d, calcSpend as f, buildX402Client as i, isTTY as l, readHistory as m, statusCommand as n, dim as o, formatTxLine as p, walletInfoCommand as r, error as s, displayStatus as t, warn as u };
|
|
386
|
+
export { buildX402Client as a, error as c, warn as d, appendHistory as f, readHistory as g, formatTxLine as h, walletInfoCommand as i, info as l, displayNetwork as m, fetchEvmBalances as n, resolveWallet as o, calcSpend as p, fetchSolanaBalances as r, dim as s, balanceLine as t, isTTY as u };
|
package/package.json
CHANGED
package/dist/setup-DtPaKQ37.js
DELETED