x402-proxy 0.2.1 → 0.3.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 +2 -2
- package/dist/bin/cli.js +82 -64
- package/dist/index.d.ts +3 -1
- package/dist/index.js +12 -1
- package/dist/{status-DnxuVdOP.js → status-BFDDA6oN.js} +1 -1
- package/dist/{status-J-RoszDZ.js → status-DRSZjSq9.js} +189 -23
- package/package.json +5 -9
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npx x402-proxy https://twitter.surf.cascade.fyi/
|
|
8
|
+
npx x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
That's it. The endpoint returns 402, x402-proxy pays and streams the response.
|
|
@@ -45,7 +45,7 @@ Works like curl. Response body streams to stdout, payment info goes to stderr.
|
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
47
|
# GET request
|
|
48
|
-
x402-proxy https://twitter.surf.cascade.fyi/
|
|
48
|
+
x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi
|
|
49
49
|
|
|
50
50
|
# POST with body and headers
|
|
51
51
|
x402-proxy --method POST \
|
package/dist/bin/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as
|
|
2
|
+
import { C as calcSpend, S as appendHistory, T as readHistory, _ as getWalletPath, a as resolveWallet, b as saveConfig, c as generateMnemonic, d as info, f as isTTY, g as getHistoryPath, h as getConfigDirShort, i as buildX402Client, l as dim, m as ensureConfigDir, n as statusCommand, o as deriveEvmKeypair, p as warn, r as walletInfoCommand, s as deriveSolanaKeypair, u as error, v as isConfigured, w as formatTxLine, x as saveWalletFile, y as loadConfig } from "../status-DRSZjSq9.js";
|
|
3
3
|
import { buildApplication, buildCommand, buildRouteMap, run } from "@stricli/core";
|
|
4
4
|
import pc from "picocolors";
|
|
5
5
|
import { decodePaymentResponseHeader, wrapFetchWithPayment } from "@x402/fetch";
|
|
@@ -49,7 +49,15 @@ function createX402ProxyHandler(opts) {
|
|
|
49
49
|
//#endregion
|
|
50
50
|
//#region src/commands/fetch.ts
|
|
51
51
|
const fetchCommand = buildCommand({
|
|
52
|
-
docs: {
|
|
52
|
+
docs: {
|
|
53
|
+
brief: "Make a paid HTTP request (default command)",
|
|
54
|
+
fullDescription: `Make a paid HTTP request. Payment is automatic when the server returns 402.
|
|
55
|
+
|
|
56
|
+
Examples:
|
|
57
|
+
$ x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi
|
|
58
|
+
$ x402-proxy -X POST -d '{"url":"https://x402.org"}' https://web.surf.cascade.fyi/v1/crawl
|
|
59
|
+
$ x402-proxy https://api.example.com/data | jq '.results'`
|
|
60
|
+
},
|
|
53
61
|
parameters: {
|
|
54
62
|
flags: {
|
|
55
63
|
method: {
|
|
@@ -101,18 +109,38 @@ const fetchCommand = buildCommand({
|
|
|
101
109
|
async func(flags, url) {
|
|
102
110
|
if (!url) {
|
|
103
111
|
if (isConfigured()) {
|
|
104
|
-
const { displayStatus } = await import("../status-
|
|
105
|
-
displayStatus();
|
|
112
|
+
const { displayStatus } = await import("../status-BFDDA6oN.js");
|
|
113
|
+
await displayStatus();
|
|
114
|
+
console.log();
|
|
115
|
+
console.log(pc.dim(" Commands:"));
|
|
116
|
+
console.log(` ${pc.cyan("$ npx x402-proxy <url>")} Fetch a paid API`);
|
|
117
|
+
console.log(` ${pc.cyan("$ npx x402-proxy mcp <url>")} MCP proxy for AI agents`);
|
|
118
|
+
console.log(` ${pc.cyan("$ npx x402-proxy setup")} Reconfigure wallet`);
|
|
119
|
+
console.log(` ${pc.cyan("$ npx x402-proxy wallet")} Addresses and balances`);
|
|
120
|
+
console.log(` ${pc.cyan("$ npx x402-proxy wallet history")} Full payment history`);
|
|
121
|
+
console.log(` ${pc.cyan("$ npx x402-proxy wallet fund")} Funding instructions`);
|
|
122
|
+
console.log();
|
|
123
|
+
console.log(pc.dim(" try: ") + pc.cyan("$ npx x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi"));
|
|
124
|
+
console.log();
|
|
125
|
+
console.log(pc.dim(" https://github.com/cascade-protocol/x402-proxy"));
|
|
126
|
+
console.log();
|
|
106
127
|
} else {
|
|
107
128
|
console.log();
|
|
108
|
-
console.log(pc.cyan(
|
|
129
|
+
console.log(pc.cyan(pc.bold("x402-proxy")));
|
|
130
|
+
console.log(pc.dim("curl for x402 paid APIs"));
|
|
131
|
+
console.log();
|
|
132
|
+
console.log(pc.dim(" Commands:"));
|
|
133
|
+
console.log(` ${pc.cyan("$ npx x402-proxy setup")} Create a wallet`);
|
|
134
|
+
console.log(` ${pc.cyan("$ npx x402-proxy <url>")} Fetch a paid API`);
|
|
135
|
+
console.log(` ${pc.cyan("$ npx x402-proxy mcp <url>")} MCP proxy for AI agents`);
|
|
136
|
+
console.log(` ${pc.cyan("$ npx x402-proxy wallet")} Addresses and balances`);
|
|
137
|
+
console.log(` ${pc.cyan("$ npx x402-proxy wallet history")} Payment history`);
|
|
138
|
+
console.log(` ${pc.cyan("$ npx x402-proxy wallet fund")} Funding instructions`);
|
|
139
|
+
console.log(` ${pc.cyan("$ npx x402-proxy --help")} All options`);
|
|
140
|
+
console.log();
|
|
141
|
+
console.log(pc.dim(" try: ") + pc.cyan("$ npx x402-proxy setup"));
|
|
109
142
|
console.log();
|
|
110
|
-
console.log(pc.dim("
|
|
111
|
-
console.log(` ${pc.cyan("x402-proxy setup")} Create a wallet`);
|
|
112
|
-
console.log(` ${pc.cyan("x402-proxy <url>")} Make a paid request`);
|
|
113
|
-
console.log(` ${pc.cyan("x402-proxy mcp <url>")} MCP proxy for agents`);
|
|
114
|
-
console.log(` ${pc.cyan("x402-proxy wallet")} Wallet info`);
|
|
115
|
-
console.log(` ${pc.cyan("x402-proxy --help")} All commands`);
|
|
143
|
+
console.log(pc.dim(" https://github.com/cascade-protocol/x402-proxy"));
|
|
116
144
|
console.log();
|
|
117
145
|
}
|
|
118
146
|
return;
|
|
@@ -133,7 +161,12 @@ const fetchCommand = buildCommand({
|
|
|
133
161
|
console.error(pc.dim(`Run ${pc.cyan("x402-proxy setup")} or set X402_PROXY_WALLET_MNEMONIC`));
|
|
134
162
|
process.exit(1);
|
|
135
163
|
}
|
|
136
|
-
const
|
|
164
|
+
const config = loadConfig();
|
|
165
|
+
const { x402Fetch, shiftPayment } = createX402ProxyHandler({ client: await buildX402Client(wallet, {
|
|
166
|
+
preferredNetwork: config?.defaultNetwork,
|
|
167
|
+
spendLimitDaily: config?.spendLimitDaily,
|
|
168
|
+
spendLimitPerTx: config?.spendLimitPerTx
|
|
169
|
+
}) });
|
|
137
170
|
const headers = new Headers();
|
|
138
171
|
if (flags.header) for (const h of flags.header) {
|
|
139
172
|
const idx = h.indexOf(":");
|
|
@@ -198,7 +231,15 @@ const fetchCommand = buildCommand({
|
|
|
198
231
|
//#endregion
|
|
199
232
|
//#region src/commands/mcp.ts
|
|
200
233
|
const mcpCommand = buildCommand({
|
|
201
|
-
docs: {
|
|
234
|
+
docs: {
|
|
235
|
+
brief: "Start MCP stdio proxy with x402 payment (alpha)",
|
|
236
|
+
fullDescription: `Start an MCP stdio proxy with automatic x402 payment for AI agents.
|
|
237
|
+
|
|
238
|
+
Add to your MCP client config (Claude, Cursor, etc.):
|
|
239
|
+
"command": "npx",
|
|
240
|
+
"args": ["x402-proxy", "mcp", "https://mcp.example.com/sse"],
|
|
241
|
+
"env": { "X402_PROXY_WALLET_MNEMONIC": "your 24 words" }`
|
|
242
|
+
},
|
|
202
243
|
parameters: {
|
|
203
244
|
flags: {
|
|
204
245
|
evmKey: {
|
|
@@ -235,7 +276,12 @@ const mcpCommand = buildCommand({
|
|
|
235
276
|
dim(`x402-proxy MCP proxy -> ${remoteUrl}`);
|
|
236
277
|
if (wallet.evmAddress) dim(` EVM: ${wallet.evmAddress}`);
|
|
237
278
|
if (wallet.solanaAddress) dim(` Solana: ${wallet.solanaAddress}`);
|
|
238
|
-
const
|
|
279
|
+
const config = loadConfig();
|
|
280
|
+
const x402PaymentClient = await buildX402Client(wallet, {
|
|
281
|
+
preferredNetwork: config?.defaultNetwork,
|
|
282
|
+
spendLimitDaily: config?.spendLimitDaily,
|
|
283
|
+
spendLimitPerTx: config?.spendLimitPerTx
|
|
284
|
+
});
|
|
239
285
|
const { Client } = await import("@modelcontextprotocol/sdk/client/index.js");
|
|
240
286
|
const { SSEClientTransport } = await import("@modelcontextprotocol/sdk/client/sse.js");
|
|
241
287
|
const { StreamableHTTPClientTransport } = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
|
@@ -244,7 +290,7 @@ const mcpCommand = buildCommand({
|
|
|
244
290
|
const { x402MCPClient } = await import("@x402/mcp");
|
|
245
291
|
const x402Mcp = new x402MCPClient(new Client({
|
|
246
292
|
name: "x402-proxy",
|
|
247
|
-
version: "0.
|
|
293
|
+
version: "0.3.0"
|
|
248
294
|
}), x402PaymentClient, {
|
|
249
295
|
autoPayment: true,
|
|
250
296
|
onPaymentRequested: (ctx) => {
|
|
@@ -289,7 +335,7 @@ const mcpCommand = buildCommand({
|
|
|
289
335
|
dim(` ${tools.length} tools available`);
|
|
290
336
|
const localServer = new McpServer({
|
|
291
337
|
name: "x402-proxy",
|
|
292
|
-
version: "0.
|
|
338
|
+
version: "0.3.0"
|
|
293
339
|
});
|
|
294
340
|
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) => {
|
|
295
341
|
const result = await x402Mcp.callTool(tool.name, args);
|
|
@@ -377,7 +423,7 @@ const setupCommand = buildCommand({
|
|
|
377
423
|
}
|
|
378
424
|
const evm = deriveEvmKeypair(mnemonic);
|
|
379
425
|
const sol = deriveSolanaKeypair(mnemonic);
|
|
380
|
-
prompts.log.success(`
|
|
426
|
+
prompts.log.success(`Base address: ${pc.green(evm.address)}`);
|
|
381
427
|
prompts.log.success(`Solana address: ${pc.green(sol.address)}`);
|
|
382
428
|
saveWalletFile({
|
|
383
429
|
version: 1,
|
|
@@ -388,43 +434,16 @@ const setupCommand = buildCommand({
|
|
|
388
434
|
}
|
|
389
435
|
});
|
|
390
436
|
saveConfig({});
|
|
391
|
-
prompts.log.info(`Config directory: ${pc.dim(
|
|
437
|
+
prompts.log.info(`Config directory: ${pc.dim(getConfigDirShort())}`);
|
|
392
438
|
prompts.log.step("Fund your wallets to start using x402 resources:");
|
|
393
439
|
prompts.log.message(` Solana (USDC): Send USDC to ${pc.cyan(sol.address)}`);
|
|
394
|
-
prompts.log.message(`
|
|
440
|
+
prompts.log.message(` Base (USDC): Send USDC to ${pc.cyan(evm.address)}`);
|
|
441
|
+
prompts.log.step("Try your first request:");
|
|
442
|
+
prompts.log.message(` ${pc.cyan("$ npx x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi")}`);
|
|
395
443
|
prompts.outro(pc.green("Setup complete!"));
|
|
396
444
|
}
|
|
397
445
|
});
|
|
398
446
|
|
|
399
|
-
//#endregion
|
|
400
|
-
//#region src/commands/wallet.ts
|
|
401
|
-
const walletInfoCommand = buildCommand({
|
|
402
|
-
docs: { brief: "Show wallet addresses" },
|
|
403
|
-
parameters: {
|
|
404
|
-
flags: {},
|
|
405
|
-
positional: {
|
|
406
|
-
kind: "tuple",
|
|
407
|
-
parameters: []
|
|
408
|
-
}
|
|
409
|
-
},
|
|
410
|
-
func() {
|
|
411
|
-
const wallet = resolveWallet();
|
|
412
|
-
if (wallet.source === "none") {
|
|
413
|
-
console.log(pc.yellow("No wallet configured."));
|
|
414
|
-
console.log(pc.dim(`Run ${pc.cyan("x402-proxy setup")} to create one.`));
|
|
415
|
-
console.log(pc.dim(`Or set ${pc.cyan("X402_PROXY_WALLET_MNEMONIC")} environment variable.`));
|
|
416
|
-
process.exit(1);
|
|
417
|
-
}
|
|
418
|
-
console.log();
|
|
419
|
-
info("Wallet");
|
|
420
|
-
console.log();
|
|
421
|
-
console.log(pc.dim(` Source: ${wallet.source}`));
|
|
422
|
-
if (wallet.evmAddress) console.log(` EVM: ${pc.green(wallet.evmAddress)}`);
|
|
423
|
-
if (wallet.solanaAddress) console.log(` Solana: ${pc.green(wallet.solanaAddress)}`);
|
|
424
|
-
console.log();
|
|
425
|
-
}
|
|
426
|
-
});
|
|
427
|
-
|
|
428
447
|
//#endregion
|
|
429
448
|
//#region src/commands/wallet-export.ts
|
|
430
449
|
const walletExportCommand = buildCommand({
|
|
@@ -443,27 +462,26 @@ const walletExportCommand = buildCommand({
|
|
|
443
462
|
}]
|
|
444
463
|
}
|
|
445
464
|
},
|
|
446
|
-
func(flags, chain) {
|
|
465
|
+
async func(flags, chain) {
|
|
447
466
|
const wallet = resolveWallet();
|
|
448
467
|
if (wallet.source === "none") {
|
|
449
468
|
error("No wallet configured.");
|
|
450
469
|
process.exit(1);
|
|
451
470
|
}
|
|
452
|
-
if (chain === "evm") {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
process.
|
|
459
|
-
} else {
|
|
460
|
-
if (!wallet.solanaKey) {
|
|
461
|
-
error("No Solana key available.");
|
|
462
|
-
process.exit(1);
|
|
463
|
-
}
|
|
464
|
-
warn("Warning: private key will be printed to stdout.");
|
|
465
|
-
process.stdout.write(base58.encode(wallet.solanaKey.slice(0, 32)));
|
|
471
|
+
if (chain === "evm" && !wallet.evmKey) {
|
|
472
|
+
error("No EVM key available.");
|
|
473
|
+
process.exit(1);
|
|
474
|
+
}
|
|
475
|
+
if (chain === "solana" && !wallet.solanaKey) {
|
|
476
|
+
error("No Solana key available.");
|
|
477
|
+
process.exit(1);
|
|
466
478
|
}
|
|
479
|
+
if (process.stdout.isTTY) {
|
|
480
|
+
const confirmed = await prompts.confirm({ message: "This will print your private key to the terminal. Continue?" });
|
|
481
|
+
if (prompts.isCancel(confirmed) || !confirmed) process.exit(0);
|
|
482
|
+
} else warn("Warning: private key will be printed to stdout.");
|
|
483
|
+
if (chain === "evm") process.stdout.write(wallet.evmKey);
|
|
484
|
+
else process.stdout.write(base58.encode(wallet.solanaKey.slice(0, 32)));
|
|
467
485
|
}
|
|
468
486
|
});
|
|
469
487
|
|
|
@@ -578,7 +596,7 @@ const routes = buildRouteMap({
|
|
|
578
596
|
});
|
|
579
597
|
const app = buildApplication(routes, {
|
|
580
598
|
name: "x402-proxy",
|
|
581
|
-
versionInfo: { currentVersion: "0.
|
|
599
|
+
versionInfo: { currentVersion: "0.3.0" },
|
|
582
600
|
scanner: { caseStyle: "allow-kebab-for-camel" }
|
|
583
601
|
});
|
|
584
602
|
|
package/dist/index.d.ts
CHANGED
|
@@ -66,7 +66,9 @@ declare function calcSpend(records: TxRecord[]): {
|
|
|
66
66
|
count: number;
|
|
67
67
|
};
|
|
68
68
|
declare function explorerUrl(net: string, tx: string): string;
|
|
69
|
-
declare function formatTxLine(r: TxRecord
|
|
69
|
+
declare function formatTxLine(r: TxRecord, opts?: {
|
|
70
|
+
verbose?: boolean;
|
|
71
|
+
}): string;
|
|
70
72
|
//#endregion
|
|
71
73
|
//#region src/wallet.d.ts
|
|
72
74
|
/**
|
package/dist/index.js
CHANGED
|
@@ -131,7 +131,13 @@ function shortModel(model) {
|
|
|
131
131
|
const parts = model.split("/");
|
|
132
132
|
return parts[parts.length - 1].replace(/-\d{6,8}$/, "").replace(/-\d{4}$/, "");
|
|
133
133
|
}
|
|
134
|
-
function
|
|
134
|
+
function shortNetwork(net) {
|
|
135
|
+
if (net === "eip155:8453") return "base";
|
|
136
|
+
if (net.startsWith("eip155:")) return `evm:${net.split(":")[1]}`;
|
|
137
|
+
if (net.startsWith("solana:")) return "sol";
|
|
138
|
+
return net;
|
|
139
|
+
}
|
|
140
|
+
function formatTxLine(r, opts) {
|
|
135
141
|
const time = new Date(r.t).toLocaleTimeString("en-US", {
|
|
136
142
|
hour: "2-digit",
|
|
137
143
|
minute: "2-digit",
|
|
@@ -143,6 +149,11 @@ function formatTxLine(r) {
|
|
|
143
149
|
if (r.label) parts.push(r.label);
|
|
144
150
|
if (r.ok && r.amount != null && r.token) parts.push(formatAmount(r.amount, r.token));
|
|
145
151
|
else if (r.ok && r.kind === "sell" && r.meta?.pct != null) parts.push(`${r.meta.pct}%`);
|
|
152
|
+
parts.push(shortNetwork(r.net));
|
|
153
|
+
if (opts?.verbose && r.tx) {
|
|
154
|
+
const short = r.tx.length > 20 ? `${r.tx.slice(0, 10)}...${r.tx.slice(-6)}` : r.tx;
|
|
155
|
+
parts.push(short);
|
|
156
|
+
}
|
|
146
157
|
return ` ${timeStr} ${r.ok ? "" : "✗ "}${parts.join(" · ")}`;
|
|
147
158
|
}
|
|
148
159
|
|
|
@@ -106,7 +106,13 @@ function shortModel(model) {
|
|
|
106
106
|
const parts = model.split("/");
|
|
107
107
|
return parts[parts.length - 1].replace(/-\d{6,8}$/, "").replace(/-\d{4}$/, "");
|
|
108
108
|
}
|
|
109
|
-
function
|
|
109
|
+
function shortNetwork(net) {
|
|
110
|
+
if (net === "eip155:8453") return "base";
|
|
111
|
+
if (net.startsWith("eip155:")) return `evm:${net.split(":")[1]}`;
|
|
112
|
+
if (net.startsWith("solana:")) return "sol";
|
|
113
|
+
return net;
|
|
114
|
+
}
|
|
115
|
+
function formatTxLine(r, opts) {
|
|
110
116
|
const time = new Date(r.t).toLocaleTimeString("en-US", {
|
|
111
117
|
hour: "2-digit",
|
|
112
118
|
minute: "2-digit",
|
|
@@ -118,6 +124,11 @@ function formatTxLine(r) {
|
|
|
118
124
|
if (r.label) parts.push(r.label);
|
|
119
125
|
if (r.ok && r.amount != null && r.token) parts.push(formatAmount(r.amount, r.token));
|
|
120
126
|
else if (r.ok && r.kind === "sell" && r.meta?.pct != null) parts.push(`${r.meta.pct}%`);
|
|
127
|
+
parts.push(shortNetwork(r.net));
|
|
128
|
+
if (opts?.verbose && r.tx) {
|
|
129
|
+
const short = r.tx.length > 20 ? `${r.tx.slice(0, 10)}...${r.tx.slice(-6)}` : r.tx;
|
|
130
|
+
parts.push(short);
|
|
131
|
+
}
|
|
121
132
|
return ` ${timeStr} ${r.ok ? "" : "✗ "}${parts.join(" · ")}`;
|
|
122
133
|
}
|
|
123
134
|
|
|
@@ -128,6 +139,12 @@ function getConfigDir() {
|
|
|
128
139
|
const xdg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
129
140
|
return path.join(xdg, APP_NAME);
|
|
130
141
|
}
|
|
142
|
+
/** Config dir with $HOME replaced by ~ for display */
|
|
143
|
+
function getConfigDirShort() {
|
|
144
|
+
const dir = getConfigDir();
|
|
145
|
+
const home = os.homedir();
|
|
146
|
+
return dir.startsWith(home) ? `~${dir.slice(home.length)}` : dir;
|
|
147
|
+
}
|
|
131
148
|
function getWalletPath() {
|
|
132
149
|
return path.join(getConfigDir(), "wallet.json");
|
|
133
150
|
}
|
|
@@ -201,7 +218,7 @@ function warn(msg) {
|
|
|
201
218
|
process.stderr.write(`${isTTY() ? pc.yellow(msg) : msg}\n`);
|
|
202
219
|
}
|
|
203
220
|
function error(msg) {
|
|
204
|
-
process.stderr.write(`${isTTY() ? pc.red(msg) : msg}\n`);
|
|
221
|
+
process.stderr.write(`${isTTY() ? pc.red(`✗ ${msg}`) : `✗ ${msg}`}\n`);
|
|
205
222
|
}
|
|
206
223
|
function dim(msg) {
|
|
207
224
|
process.stderr.write(`${isTTY() ? pc.dim(msg) : msg}\n`);
|
|
@@ -338,11 +355,23 @@ function parsesolanaKey(input) {
|
|
|
338
355
|
}
|
|
339
356
|
return base58.decode(trimmed);
|
|
340
357
|
}
|
|
358
|
+
function networkToCaipPrefix(name) {
|
|
359
|
+
switch (name.toLowerCase()) {
|
|
360
|
+
case "base": return "eip155:8453";
|
|
361
|
+
case "solana": return "solana:";
|
|
362
|
+
default: return name;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
341
365
|
/**
|
|
342
366
|
* Build a configured x402Client from resolved wallet keys.
|
|
343
367
|
*/
|
|
344
|
-
async function buildX402Client(wallet) {
|
|
345
|
-
const client = new x402Client()
|
|
368
|
+
async function buildX402Client(wallet, opts) {
|
|
369
|
+
const client = new x402Client(opts?.preferredNetwork ? (() => {
|
|
370
|
+
const prefix = networkToCaipPrefix(opts.preferredNetwork);
|
|
371
|
+
return (_version, accepts) => {
|
|
372
|
+
return accepts.find((r) => r.network.startsWith(prefix)) || accepts[0];
|
|
373
|
+
};
|
|
374
|
+
})() : void 0);
|
|
346
375
|
if (wallet.evmKey) {
|
|
347
376
|
const hex = wallet.evmKey;
|
|
348
377
|
const signer = toClientEvmSigner(privateKeyToAccount(hex), createPublicClient({
|
|
@@ -357,39 +386,175 @@ async function buildX402Client(wallet) {
|
|
|
357
386
|
client.register("solana:mainnet", new ExactSvmScheme(signer));
|
|
358
387
|
client.register("solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", new ExactSvmScheme(signer));
|
|
359
388
|
}
|
|
389
|
+
const daily = opts?.spendLimitDaily;
|
|
390
|
+
const perTx = opts?.spendLimitPerTx;
|
|
391
|
+
if (daily || perTx) client.registerPolicy((_version, reqs) => {
|
|
392
|
+
if (daily) {
|
|
393
|
+
const spend = calcSpend(readHistory(getHistoryPath()));
|
|
394
|
+
if (spend.today >= daily) throw new Error(`Daily spend limit reached (${spend.today.toFixed(4)}/${daily} USDC)`);
|
|
395
|
+
const remaining = daily - spend.today;
|
|
396
|
+
reqs = reqs.filter((r) => Number(r.amount) / 1e6 <= remaining);
|
|
397
|
+
if (reqs.length === 0) throw new Error(`Daily spend limit of ${daily} USDC would be exceeded (${spend.today.toFixed(4)} spent today)`);
|
|
398
|
+
}
|
|
399
|
+
if (perTx) {
|
|
400
|
+
const before = reqs.length;
|
|
401
|
+
reqs = reqs.filter((r) => Number(r.amount) / 1e6 <= perTx);
|
|
402
|
+
if (reqs.length === 0 && before > 0) throw new Error(`Payment exceeds per-transaction limit of ${perTx} USDC`);
|
|
403
|
+
}
|
|
404
|
+
return reqs;
|
|
405
|
+
});
|
|
360
406
|
return client;
|
|
361
407
|
}
|
|
362
408
|
|
|
409
|
+
//#endregion
|
|
410
|
+
//#region src/commands/wallet.ts
|
|
411
|
+
const BASE_RPC = "https://mainnet.base.org";
|
|
412
|
+
const SOLANA_RPC = "https://api.mainnet-beta.solana.com";
|
|
413
|
+
const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
414
|
+
const USDC_SOLANA_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
415
|
+
async function rpcCall(url, method, params) {
|
|
416
|
+
return (await fetch(url, {
|
|
417
|
+
method: "POST",
|
|
418
|
+
headers: { "Content-Type": "application/json" },
|
|
419
|
+
body: JSON.stringify({
|
|
420
|
+
jsonrpc: "2.0",
|
|
421
|
+
method,
|
|
422
|
+
params,
|
|
423
|
+
id: 1
|
|
424
|
+
})
|
|
425
|
+
})).json();
|
|
426
|
+
}
|
|
427
|
+
async function fetchEvmBalances(address) {
|
|
428
|
+
const usdcData = `0x70a08231${address.slice(2).padStart(64, "0")}`;
|
|
429
|
+
const [ethRes, usdcRes] = await Promise.all([rpcCall(BASE_RPC, "eth_getBalance", [address, "latest"]), rpcCall(BASE_RPC, "eth_call", [{
|
|
430
|
+
to: USDC_BASE,
|
|
431
|
+
data: usdcData
|
|
432
|
+
}, "latest"])]);
|
|
433
|
+
return {
|
|
434
|
+
eth: ethRes.result ? (Number(BigInt(ethRes.result)) / 0xde0b6b3a7640000).toFixed(6) : "?",
|
|
435
|
+
usdc: usdcRes.result ? (Number(BigInt(usdcRes.result)) / 1e6).toFixed(2) : "?"
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
async function fetchSolanaBalances(address) {
|
|
439
|
+
const [solRes, usdcRes] = await Promise.all([rpcCall(SOLANA_RPC, "getBalance", [address]), rpcCall(SOLANA_RPC, "getTokenAccountsByOwner", [
|
|
440
|
+
address,
|
|
441
|
+
{ mint: USDC_SOLANA_MINT },
|
|
442
|
+
{ encoding: "jsonParsed" }
|
|
443
|
+
])]);
|
|
444
|
+
const sol = solRes.result?.value != null ? (solRes.result.value / 1e9).toFixed(6) : "?";
|
|
445
|
+
const accounts = usdcRes.result?.value;
|
|
446
|
+
return {
|
|
447
|
+
sol,
|
|
448
|
+
usdc: accounts?.length ? Number(accounts[0].account.data.parsed.info.tokenAmount.uiAmountString).toFixed(2) : "0.00"
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
function balanceLine(usdc, native, nativeSymbol) {
|
|
452
|
+
return pc.dim(` (${usdc} USDC, ${native} ${nativeSymbol})`);
|
|
453
|
+
}
|
|
454
|
+
const walletInfoCommand = buildCommand({
|
|
455
|
+
docs: { brief: "Show wallet addresses and balances" },
|
|
456
|
+
parameters: {
|
|
457
|
+
flags: { verbose: {
|
|
458
|
+
kind: "boolean",
|
|
459
|
+
brief: "Show transaction IDs",
|
|
460
|
+
default: false
|
|
461
|
+
} },
|
|
462
|
+
positional: {
|
|
463
|
+
kind: "tuple",
|
|
464
|
+
parameters: []
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
async func(flags) {
|
|
468
|
+
const wallet = resolveWallet();
|
|
469
|
+
if (wallet.source === "none") {
|
|
470
|
+
console.log(pc.yellow("No wallet configured."));
|
|
471
|
+
console.log(pc.dim(`Run ${pc.cyan("x402-proxy setup")} to create one.`));
|
|
472
|
+
console.log(pc.dim(`Or set ${pc.cyan("X402_PROXY_WALLET_MNEMONIC")} environment variable.`));
|
|
473
|
+
process.exit(1);
|
|
474
|
+
}
|
|
475
|
+
console.log();
|
|
476
|
+
info("Wallet");
|
|
477
|
+
console.log();
|
|
478
|
+
console.log(pc.dim(` Source: ${wallet.source}`));
|
|
479
|
+
const [evmResult, solResult] = await Promise.allSettled([wallet.evmAddress ? fetchEvmBalances(wallet.evmAddress) : Promise.resolve(null), wallet.solanaAddress ? fetchSolanaBalances(wallet.solanaAddress) : Promise.resolve(null)]);
|
|
480
|
+
const evm = evmResult.status === "fulfilled" ? evmResult.value : null;
|
|
481
|
+
const sol = solResult.status === "fulfilled" ? solResult.value : null;
|
|
482
|
+
if (wallet.evmAddress) {
|
|
483
|
+
const bal = evm ? balanceLine(evm.usdc, evm.eth, "ETH") : pc.dim(" (network error)");
|
|
484
|
+
console.log(` Base: ${pc.green(wallet.evmAddress)}${bal}`);
|
|
485
|
+
}
|
|
486
|
+
if (wallet.solanaAddress) {
|
|
487
|
+
const bal = sol ? balanceLine(sol.usdc, sol.sol, "SOL") : pc.dim(" (network error)");
|
|
488
|
+
console.log(` Solana: ${pc.green(wallet.solanaAddress)}${bal}`);
|
|
489
|
+
}
|
|
490
|
+
console.log();
|
|
491
|
+
const records = readHistory(getHistoryPath());
|
|
492
|
+
if (records.length > 0) {
|
|
493
|
+
const spend = calcSpend(records);
|
|
494
|
+
const recent = records.slice(-10);
|
|
495
|
+
dim(" Recent transactions:");
|
|
496
|
+
for (const r of recent) {
|
|
497
|
+
const line = formatTxLine(r, { verbose: flags.verbose }).replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
498
|
+
console.log(line);
|
|
499
|
+
}
|
|
500
|
+
console.log();
|
|
501
|
+
console.log(pc.dim(` Today: ${spend.today.toFixed(4)} USDC | Total: ${spend.total.toFixed(4)} USDC | ${spend.count} tx`));
|
|
502
|
+
} else dim(" No transactions yet.");
|
|
503
|
+
console.log();
|
|
504
|
+
console.log(pc.dim(" See also: wallet history, wallet fund, wallet export-key"));
|
|
505
|
+
console.log();
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
|
|
363
509
|
//#endregion
|
|
364
510
|
//#region src/commands/status.ts
|
|
365
|
-
function displayStatus() {
|
|
511
|
+
async function displayStatus() {
|
|
366
512
|
const wallet = resolveWallet();
|
|
367
513
|
const config = loadConfig();
|
|
514
|
+
const records = readHistory(getHistoryPath());
|
|
515
|
+
const spend = calcSpend(records);
|
|
368
516
|
console.log();
|
|
369
|
-
|
|
370
|
-
console.log();
|
|
371
|
-
dim(` Config directory: ${getConfigDir()}`);
|
|
372
|
-
if (config) {
|
|
373
|
-
if (config.spendLimit) dim(` Spend limit: ${config.spendLimit} USDC`);
|
|
374
|
-
if (config.defaultNetwork) dim(` Default network: ${config.defaultNetwork}`);
|
|
375
|
-
}
|
|
517
|
+
console.log(pc.cyan(pc.bold("x402-proxy")));
|
|
518
|
+
console.log(pc.dim("curl for x402 paid APIs"));
|
|
376
519
|
console.log();
|
|
377
520
|
if (wallet.source === "none") {
|
|
378
521
|
console.log(pc.yellow(" No wallet configured."));
|
|
379
|
-
console.log(pc.dim(` Run ${pc.cyan("x402-proxy setup")} to create one.`));
|
|
522
|
+
console.log(pc.dim(` Run ${pc.cyan("$ npx x402-proxy setup")} to create one.`));
|
|
380
523
|
} else {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
524
|
+
const [evmResult, solResult] = await Promise.allSettled([wallet.evmAddress ? fetchEvmBalances(wallet.evmAddress) : Promise.resolve(null), wallet.solanaAddress ? fetchSolanaBalances(wallet.solanaAddress) : Promise.resolve(null)]);
|
|
525
|
+
const evm = evmResult.status === "fulfilled" ? evmResult.value : null;
|
|
526
|
+
const sol = solResult.status === "fulfilled" ? solResult.value : null;
|
|
527
|
+
if (wallet.evmAddress) {
|
|
528
|
+
const bal = evm ? balanceLine(evm.usdc, evm.eth, "ETH") : pc.dim(" (network error)");
|
|
529
|
+
console.log(` Base: ${pc.green(wallet.evmAddress)}${bal}`);
|
|
530
|
+
}
|
|
531
|
+
if (wallet.solanaAddress) {
|
|
532
|
+
const bal = sol ? balanceLine(sol.usdc, sol.sol, "SOL") : pc.dim(" (network error)");
|
|
533
|
+
console.log(` Solana: ${pc.green(wallet.solanaAddress)}${bal}`);
|
|
534
|
+
}
|
|
535
|
+
if (config?.spendLimitDaily || config?.spendLimitPerTx) {
|
|
536
|
+
console.log();
|
|
537
|
+
if (config.spendLimitDaily) {
|
|
538
|
+
const pct = config.spendLimitDaily > 0 ? Math.round(spend.today / config.spendLimitDaily * 100) : 0;
|
|
539
|
+
dim(` Daily limit: ${spend.today.toFixed(4)} / ${config.spendLimitDaily} USDC (${pct}%)`);
|
|
540
|
+
}
|
|
541
|
+
if (config.spendLimitPerTx) dim(` Per-tx limit: ${config.spendLimitPerTx} USDC`);
|
|
542
|
+
}
|
|
384
543
|
}
|
|
385
544
|
console.log();
|
|
386
|
-
const spend = calcSpend(readHistory(getHistoryPath()));
|
|
387
545
|
if (spend.count > 0) {
|
|
388
|
-
|
|
389
|
-
dim(
|
|
390
|
-
|
|
546
|
+
const recent = records.slice(-5);
|
|
547
|
+
dim(" Recent transactions:");
|
|
548
|
+
for (const r of recent) {
|
|
549
|
+
const line = formatTxLine(r).replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
550
|
+
console.log(line);
|
|
551
|
+
}
|
|
552
|
+
console.log();
|
|
553
|
+
dim(` Today: ${spend.today.toFixed(4)} USDC | Total: ${spend.total.toFixed(4)} USDC | ${spend.count} tx`);
|
|
391
554
|
} else dim(" No payment history yet.");
|
|
392
555
|
console.log();
|
|
556
|
+
if (config?.defaultNetwork) dim(` Network: ${config.defaultNetwork}`);
|
|
557
|
+
dim(` Config: ${getConfigDirShort()}`);
|
|
393
558
|
}
|
|
394
559
|
const statusCommand = buildCommand({
|
|
395
560
|
docs: { brief: "Show configuration and wallet status" },
|
|
@@ -400,10 +565,11 @@ const statusCommand = buildCommand({
|
|
|
400
565
|
parameters: []
|
|
401
566
|
}
|
|
402
567
|
},
|
|
403
|
-
func() {
|
|
404
|
-
displayStatus();
|
|
568
|
+
async func() {
|
|
569
|
+
await displayStatus();
|
|
570
|
+
console.log();
|
|
405
571
|
}
|
|
406
572
|
});
|
|
407
573
|
|
|
408
574
|
//#endregion
|
|
409
|
-
export {
|
|
575
|
+
export { calcSpend as C, appendHistory as S, readHistory as T, getWalletPath as _, resolveWallet as a, saveConfig as b, generateMnemonic$1 as c, info as d, isTTY as f, getHistoryPath as g, getConfigDirShort as h, buildX402Client as i, dim as l, ensureConfigDir as m, statusCommand as n, deriveEvmKeypair as o, warn as p, walletInfoCommand as r, deriveSolanaKeypair as s, displayStatus as t, error as u, isConfigured as v, formatTxLine as w, saveWalletFile as x, loadConfig as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "x402-proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "curl for x402 paid APIs. Auto-pays any endpoint on Base and Solana.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -26,26 +26,22 @@
|
|
|
26
26
|
"@scure/base": "^2.0.0",
|
|
27
27
|
"@scure/bip32": "^2.0.1",
|
|
28
28
|
"@scure/bip39": "^2.0.1",
|
|
29
|
+
"@solana/kit": "^6.0.0",
|
|
29
30
|
"@stricli/core": "^1.2.6",
|
|
30
31
|
"@x402/evm": "^2.6.0",
|
|
31
32
|
"@x402/fetch": "^2.6.0",
|
|
32
33
|
"@x402/mcp": "^2.6.0",
|
|
33
34
|
"@x402/svm": "^2.6.0",
|
|
35
|
+
"ethers": "^6.0.0",
|
|
34
36
|
"picocolors": "^1.1.1",
|
|
37
|
+
"viem": "^2.0.0",
|
|
35
38
|
"yaml": "^2.8.2"
|
|
36
39
|
},
|
|
37
|
-
"peerDependencies": {
|
|
38
|
-
"@solana/kit": "^6.0.0",
|
|
39
|
-
"ethers": "^6.0.0",
|
|
40
|
-
"viem": "^2.0.0"
|
|
41
|
-
},
|
|
42
40
|
"devDependencies": {
|
|
43
|
-
"@solana/kit": "^6.0.0",
|
|
44
41
|
"@types/node": "^22.0.0",
|
|
45
42
|
"publint": "^0.3.17",
|
|
46
43
|
"tsdown": "^0.20.3",
|
|
47
|
-
"typescript": "^5.9.0"
|
|
48
|
-
"viem": "^2.0.0"
|
|
44
|
+
"typescript": "^5.9.0"
|
|
49
45
|
},
|
|
50
46
|
"files": [
|
|
51
47
|
"dist/**",
|