x402-proxy 0.3.2 → 0.4.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/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-DrC0uxCS.js} +1 -1
- package/dist/status-DxW72Hx6.js +72 -0
- package/dist/{status-Cp51UDnh.js → wallet-BHywTzdN.js} +50 -87
- package/dist/wallet-DFhJWn3o.js +4 -0
- 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-BHywTzdN.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-DxW72Hx6.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-DrC0uxCS.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-DFhJWn3o.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-DFhJWn3o.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-DFhJWn3o.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.1"
|
|
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.1"
|
|
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.1" },
|
|
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-BHywTzdN.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";
|
|
@@ -207,30 +225,40 @@ function networkToCaipPrefix(name) {
|
|
|
207
225
|
default: return name;
|
|
208
226
|
}
|
|
209
227
|
}
|
|
228
|
+
function createNetworkFilter(network) {
|
|
229
|
+
const prefix = networkToCaipPrefix(network);
|
|
230
|
+
return (_version, reqs) => {
|
|
231
|
+
const filtered = reqs.filter((r) => r.network.startsWith(prefix));
|
|
232
|
+
if (filtered.length === 0) {
|
|
233
|
+
const available = [...new Set(reqs.map((r) => displayNetwork(r.network)))].join(", ");
|
|
234
|
+
throw new Error(`Network '${network}' not accepted. Available: ${available}`);
|
|
235
|
+
}
|
|
236
|
+
return filtered;
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function createNetworkPreference(network) {
|
|
240
|
+
const prefix = networkToCaipPrefix(network);
|
|
241
|
+
return (_version, accepts) => {
|
|
242
|
+
return accepts.find((r) => r.network.startsWith(prefix)) || accepts[0];
|
|
243
|
+
};
|
|
244
|
+
}
|
|
210
245
|
/**
|
|
211
246
|
* Build a configured x402Client from resolved wallet keys.
|
|
212
247
|
*/
|
|
213
248
|
async function buildX402Client(wallet, opts) {
|
|
214
|
-
const client = new x402Client(opts?.preferredNetwork ? (
|
|
215
|
-
const prefix = networkToCaipPrefix(opts.preferredNetwork);
|
|
216
|
-
return (_version, accepts) => {
|
|
217
|
-
return accepts.find((r) => r.network.startsWith(prefix)) || accepts[0];
|
|
218
|
-
};
|
|
219
|
-
})() : void 0);
|
|
249
|
+
const client = new x402Client(opts?.preferredNetwork ? createNetworkPreference(opts.preferredNetwork) : void 0);
|
|
220
250
|
if (wallet.evmKey) {
|
|
221
251
|
const hex = wallet.evmKey;
|
|
222
|
-
|
|
252
|
+
registerExactEvmScheme(client, { signer: toClientEvmSigner(privateKeyToAccount(hex), createPublicClient({
|
|
223
253
|
chain: base,
|
|
224
254
|
transport: http()
|
|
225
|
-
}));
|
|
226
|
-
client.register("eip155:8453", new ExactEvmScheme(signer));
|
|
255
|
+
})) });
|
|
227
256
|
}
|
|
228
257
|
if (wallet.solanaKey) {
|
|
229
258
|
const { createKeyPairSignerFromBytes } = await import("@solana/kit");
|
|
230
|
-
|
|
231
|
-
client.register("solana:mainnet", new ExactSvmScheme(signer));
|
|
232
|
-
client.register("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", new ExactSvmScheme(signer));
|
|
259
|
+
registerExactSvmScheme(client, { signer: await createKeyPairSignerFromBytes(wallet.solanaKey) });
|
|
233
260
|
}
|
|
261
|
+
if (opts?.network) client.registerPolicy(createNetworkFilter(opts.network));
|
|
234
262
|
const daily = opts?.spendLimitDaily;
|
|
235
263
|
const perTx = opts?.spendLimitPerTx;
|
|
236
264
|
if (daily || perTx) client.registerPolicy((_version, reqs) => {
|
|
@@ -277,7 +305,7 @@ async function fetchEvmBalances(address) {
|
|
|
277
305
|
}, "latest"])]);
|
|
278
306
|
return {
|
|
279
307
|
eth: ethRes.result ? (Number(BigInt(ethRes.result)) / 0xde0b6b3a7640000).toFixed(6) : "?",
|
|
280
|
-
usdc: usdcRes.result ? (Number(BigInt(usdcRes.result)) / 1e6).toFixed(
|
|
308
|
+
usdc: usdcRes.result ? (Number(BigInt(usdcRes.result)) / 1e6).toFixed(4) : "?"
|
|
281
309
|
};
|
|
282
310
|
}
|
|
283
311
|
async function fetchSolanaBalances(address) {
|
|
@@ -290,7 +318,7 @@ async function fetchSolanaBalances(address) {
|
|
|
290
318
|
const accounts = usdcRes.result?.value;
|
|
291
319
|
return {
|
|
292
320
|
sol,
|
|
293
|
-
usdc: accounts?.length ? Number(accounts[0].account.data.parsed.info.tokenAmount.uiAmountString).toFixed(
|
|
321
|
+
usdc: accounts?.length ? Number(accounts[0].account.data.parsed.info.tokenAmount.uiAmountString).toFixed(4) : "0.0000"
|
|
294
322
|
};
|
|
295
323
|
}
|
|
296
324
|
function balanceLine(usdc, native, nativeSymbol) {
|
|
@@ -331,8 +359,8 @@ const walletInfoCommand = buildCommand({
|
|
|
331
359
|
const bal = sol ? balanceLine(sol.usdc, sol.sol, "SOL") : pc.dim(" (network error)");
|
|
332
360
|
console.log(` Solana: ${pc.green(wallet.solanaAddress)}${bal}`);
|
|
333
361
|
}
|
|
334
|
-
const evmEmpty = !evm || evm.usdc === "0.
|
|
335
|
-
const solEmpty = !sol || sol.usdc === "0.
|
|
362
|
+
const evmEmpty = !evm || evm.usdc === "0.0000";
|
|
363
|
+
const solEmpty = !sol || sol.usdc === "0.0000";
|
|
336
364
|
if (evmEmpty && solEmpty) {
|
|
337
365
|
console.log();
|
|
338
366
|
dim(" Send USDC to either address above to start using x402 APIs.");
|
|
@@ -357,69 +385,4 @@ const walletInfoCommand = buildCommand({
|
|
|
357
385
|
});
|
|
358
386
|
|
|
359
387
|
//#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 };
|
|
388
|
+
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