x402-proxy 0.3.0 → 0.3.2
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 +14 -21
- package/dist/bin/cli.js +68 -133
- package/dist/derive-Rikypf5B.js +161 -0
- package/dist/setup-CvB0go56.js +88 -0
- package/dist/setup-DtPaKQ37.js +4 -0
- package/dist/{status-BFDDA6oN.js → status-4nQP3Fpu.js} +1 -1
- package/dist/{status-DRSZjSq9.js → status-Cp51UDnh.js} +11 -161
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -10,14 +10,7 @@ npx x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi
|
|
|
10
10
|
|
|
11
11
|
That's it. The endpoint returns 402, x402-proxy pays and streams the response.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npx x402-proxy setup # generate wallet from BIP-39 mnemonic
|
|
17
|
-
npx x402-proxy wallet fund # see where to send USDC
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
One mnemonic derives both EVM (Base) and Solana keypairs. Fund either chain and go.
|
|
13
|
+
No wallet? It'll walk you through setup automatically. One mnemonic derives both EVM (Base) and Solana keypairs. Fund either chain and go.
|
|
21
14
|
|
|
22
15
|
## MCP Proxy
|
|
23
16
|
|
|
@@ -45,29 +38,28 @@ Works like curl. Response body streams to stdout, payment info goes to stderr.
|
|
|
45
38
|
|
|
46
39
|
```bash
|
|
47
40
|
# GET request
|
|
48
|
-
x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi
|
|
41
|
+
$ npx x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi
|
|
49
42
|
|
|
50
43
|
# POST with body and headers
|
|
51
|
-
x402-proxy --method POST \
|
|
44
|
+
$ npx x402-proxy --method POST \
|
|
52
45
|
--header "Content-Type: application/json" \
|
|
53
46
|
--body '{"url":"https://x402.org"}' \
|
|
54
47
|
https://web.surf.cascade.fyi/v1/crawl
|
|
55
48
|
|
|
56
49
|
# Pipe-safe
|
|
57
|
-
x402-proxy https://api.example.com/data | jq '.results'
|
|
50
|
+
$ npx x402-proxy https://api.example.com/data | jq '.results'
|
|
58
51
|
```
|
|
59
52
|
|
|
60
53
|
## Commands
|
|
61
54
|
|
|
62
55
|
```bash
|
|
63
|
-
x402-proxy <url> # paid HTTP request (default command)
|
|
64
|
-
x402-proxy mcp <url> # MCP stdio proxy for agents
|
|
65
|
-
x402-proxy setup # onboarding wizard
|
|
66
|
-
x402-proxy status # config + wallet + spend summary
|
|
67
|
-
x402-proxy wallet # show addresses
|
|
68
|
-
x402-proxy wallet history # payment history
|
|
69
|
-
x402-proxy wallet
|
|
70
|
-
x402-proxy wallet export-key <chain> # bare key to stdout (evm|solana)
|
|
56
|
+
$ npx x402-proxy <url> # paid HTTP request (default command)
|
|
57
|
+
$ npx x402-proxy mcp <url> # MCP stdio proxy for agents
|
|
58
|
+
$ npx x402-proxy setup # onboarding wizard
|
|
59
|
+
$ npx x402-proxy status # config + wallet + spend summary
|
|
60
|
+
$ npx x402-proxy wallet # show addresses and balances
|
|
61
|
+
$ npx x402-proxy wallet history # payment history
|
|
62
|
+
$ npx x402-proxy wallet export-key <target> # bare key/mnemonic to stdout (evm|solana|mnemonic)
|
|
71
63
|
```
|
|
72
64
|
|
|
73
65
|
All commands support `--help` for details.
|
|
@@ -83,8 +75,9 @@ Config stored at `$XDG_CONFIG_HOME/x402-proxy/` (default `~/.config/x402-proxy/`
|
|
|
83
75
|
### Export keys for other tools
|
|
84
76
|
|
|
85
77
|
```bash
|
|
86
|
-
# Pipe-safe - outputs bare key to stdout
|
|
87
|
-
MY_KEY=$(npx x402-proxy wallet export-key evm)
|
|
78
|
+
# Pipe-safe - outputs bare key/mnemonic to stdout
|
|
79
|
+
$ MY_KEY=$(npx x402-proxy wallet export-key evm)
|
|
80
|
+
$ MY_MNEMONIC=$(npx x402-proxy wallet export-key mnemonic)
|
|
88
81
|
```
|
|
89
82
|
|
|
90
83
|
## Env Vars
|
package/dist/bin/cli.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { a as resolveWallet, c as info, d as appendHistory, f as calcSpend, i as buildX402Client, l as isTTY, m as readHistory, n as statusCommand, o as dim, p as formatTxLine, r as walletInfoCommand, s as error, u as warn } from "../status-Cp51UDnh.js";
|
|
3
|
+
import { c as isConfigured, i as ensureConfigDir, l as loadConfig, o as getHistoryPath, u as loadWalletFile } from "../derive-Rikypf5B.js";
|
|
4
|
+
import { n as setupCommand } from "../setup-CvB0go56.js";
|
|
3
5
|
import { buildApplication, buildCommand, buildRouteMap, run } from "@stricli/core";
|
|
4
6
|
import pc from "picocolors";
|
|
5
7
|
import { decodePaymentResponseHeader, wrapFetchWithPayment } from "@x402/fetch";
|
|
@@ -109,7 +111,7 @@ Examples:
|
|
|
109
111
|
async func(flags, url) {
|
|
110
112
|
if (!url) {
|
|
111
113
|
if (isConfigured()) {
|
|
112
|
-
const { displayStatus } = await import("../status-
|
|
114
|
+
const { displayStatus } = await import("../status-4nQP3Fpu.js");
|
|
113
115
|
await displayStatus();
|
|
114
116
|
console.log();
|
|
115
117
|
console.log(pc.dim(" Commands:"));
|
|
@@ -118,7 +120,6 @@ Examples:
|
|
|
118
120
|
console.log(` ${pc.cyan("$ npx x402-proxy setup")} Reconfigure wallet`);
|
|
119
121
|
console.log(` ${pc.cyan("$ npx x402-proxy wallet")} Addresses and balances`);
|
|
120
122
|
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
123
|
console.log();
|
|
123
124
|
console.log(pc.dim(" try: ") + pc.cyan("$ npx x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi"));
|
|
124
125
|
console.log();
|
|
@@ -135,7 +136,6 @@ Examples:
|
|
|
135
136
|
console.log(` ${pc.cyan("$ npx x402-proxy mcp <url>")} MCP proxy for AI agents`);
|
|
136
137
|
console.log(` ${pc.cyan("$ npx x402-proxy wallet")} Addresses and balances`);
|
|
137
138
|
console.log(` ${pc.cyan("$ npx x402-proxy wallet history")} Payment history`);
|
|
138
|
-
console.log(` ${pc.cyan("$ npx x402-proxy wallet fund")} Funding instructions`);
|
|
139
139
|
console.log(` ${pc.cyan("$ npx x402-proxy --help")} All options`);
|
|
140
140
|
console.log();
|
|
141
141
|
console.log(pc.dim(" try: ") + pc.cyan("$ npx x402-proxy setup"));
|
|
@@ -152,14 +152,22 @@ Examples:
|
|
|
152
152
|
error(`Invalid URL: ${url}`);
|
|
153
153
|
process.exit(1);
|
|
154
154
|
}
|
|
155
|
-
|
|
155
|
+
let wallet = resolveWallet({
|
|
156
156
|
evmKey: flags.evmKey,
|
|
157
157
|
solanaKey: flags.solanaKey
|
|
158
158
|
});
|
|
159
159
|
if (wallet.source === "none") {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
160
|
+
if (!isTTY()) {
|
|
161
|
+
error("No wallet configured.");
|
|
162
|
+
console.error(pc.dim(`Run:\n ${pc.cyan("$ npx x402-proxy setup")}\n\nOr set X402_PROXY_WALLET_MNEMONIC`));
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
dim(" No wallet found. Let's set one up first.\n");
|
|
166
|
+
const { runSetup } = await import("../setup-DtPaKQ37.js");
|
|
167
|
+
await runSetup();
|
|
168
|
+
console.log();
|
|
169
|
+
wallet = resolveWallet();
|
|
170
|
+
if (wallet.source === "none") return;
|
|
163
171
|
}
|
|
164
172
|
const config = loadConfig();
|
|
165
173
|
const { x402Fetch, shiftPayment } = createX402ProxyHandler({ client: await buildX402Client(wallet, {
|
|
@@ -190,8 +198,39 @@ Examples:
|
|
|
190
198
|
const elapsedMs = Date.now() - startMs;
|
|
191
199
|
const payment = shiftPayment();
|
|
192
200
|
const txSig = extractTxSignature(response);
|
|
201
|
+
if (response.status === 402 && isTTY()) {
|
|
202
|
+
const prHeader = response.headers.get("PAYMENT-REQUIRED") ?? response.headers.get("X-PAYMENT-REQUIRED");
|
|
203
|
+
let accepts = [];
|
|
204
|
+
if (prHeader) try {
|
|
205
|
+
accepts = JSON.parse(Buffer.from(prHeader, "base64").toString()).accepts ?? [];
|
|
206
|
+
} catch {}
|
|
207
|
+
if (accepts.length > 0) {
|
|
208
|
+
const cheapest = accepts.reduce((min, a) => Number(a.amount) < Number(min.amount) ? a : min);
|
|
209
|
+
error(`Payment required: ${(Number(cheapest.amount) / 1e6).toFixed(4)} USDC`);
|
|
210
|
+
} else error("Payment required");
|
|
211
|
+
const hasEvm = accepts.some((a) => a.network.startsWith("eip155:"));
|
|
212
|
+
const hasSolana = accepts.some((a) => a.network.startsWith("solana:"));
|
|
213
|
+
const hasOther = accepts.some((a) => !a.network.startsWith("eip155:") && !a.network.startsWith("solana:"));
|
|
214
|
+
if (hasEvm || hasSolana) {
|
|
215
|
+
console.error();
|
|
216
|
+
dim(" Fund your wallet with USDC:");
|
|
217
|
+
if (hasEvm && wallet.evmAddress) console.error(` Base: ${pc.cyan(wallet.evmAddress)}`);
|
|
218
|
+
if (hasSolana && wallet.solanaAddress) console.error(` Solana: ${pc.cyan(wallet.solanaAddress)}`);
|
|
219
|
+
if (hasEvm && !wallet.evmAddress) dim(" Base: endpoint accepts EVM but no EVM wallet configured");
|
|
220
|
+
if (hasSolana && !wallet.solanaAddress) dim(" Solana: endpoint accepts Solana but no Solana wallet configured");
|
|
221
|
+
} else if (hasOther) {
|
|
222
|
+
const networks = [...new Set(accepts.map((a) => a.network))].join(", ");
|
|
223
|
+
console.error();
|
|
224
|
+
error(`This endpoint only accepts payment on unsupported networks: ${networks}`);
|
|
225
|
+
}
|
|
226
|
+
console.error();
|
|
227
|
+
dim(" Then re-run:");
|
|
228
|
+
console.error(` ${pc.cyan(`$ npx x402-proxy ${url}`)}`);
|
|
229
|
+
console.error();
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
193
232
|
if (payment && isTTY()) {
|
|
194
|
-
info(` Payment: ${payment.amount
|
|
233
|
+
info(` Payment: ${payment.amount ? (Number(payment.amount) / 1e6).toFixed(4) : "?"} USDC (${payment.network ?? "unknown"})`);
|
|
195
234
|
if (txSig) dim(` Tx: ${txSig}`);
|
|
196
235
|
}
|
|
197
236
|
if (isTTY()) dim(` ${response.status} ${response.statusText} (${elapsedMs}ms)`);
|
|
@@ -269,7 +308,7 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
269
308
|
solanaKey: flags.solanaKey
|
|
270
309
|
});
|
|
271
310
|
if (wallet.source === "none") {
|
|
272
|
-
error("No wallet configured
|
|
311
|
+
error("No wallet configured.\nRun:\n $ npx x402-proxy setup\n\nOr set X402_PROXY_WALLET_MNEMONIC");
|
|
273
312
|
process.exit(1);
|
|
274
313
|
}
|
|
275
314
|
warn("Note: MCP proxy is alpha - please report issues.");
|
|
@@ -290,7 +329,7 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
290
329
|
const { x402MCPClient } = await import("@x402/mcp");
|
|
291
330
|
const x402Mcp = new x402MCPClient(new Client({
|
|
292
331
|
name: "x402-proxy",
|
|
293
|
-
version: "0.3.
|
|
332
|
+
version: "0.3.2"
|
|
294
333
|
}), x402PaymentClient, {
|
|
295
334
|
autoPayment: true,
|
|
296
335
|
onPaymentRequested: (ctx) => {
|
|
@@ -335,7 +374,7 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
335
374
|
dim(` ${tools.length} tools available`);
|
|
336
375
|
const localServer = new McpServer({
|
|
337
376
|
name: "x402-proxy",
|
|
338
|
-
version: "0.3.
|
|
377
|
+
version: "0.3.2"
|
|
339
378
|
});
|
|
340
379
|
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) => {
|
|
341
380
|
const result = await x402Mcp.callTool(tool.name, args);
|
|
@@ -366,103 +405,38 @@ Add to your MCP client config (Claude, Cursor, etc.):
|
|
|
366
405
|
}
|
|
367
406
|
});
|
|
368
407
|
|
|
369
|
-
//#endregion
|
|
370
|
-
//#region src/commands/setup.ts
|
|
371
|
-
const setupCommand = buildCommand({
|
|
372
|
-
docs: { brief: "Set up x402-proxy with a new wallet" },
|
|
373
|
-
parameters: {
|
|
374
|
-
flags: { force: {
|
|
375
|
-
kind: "boolean",
|
|
376
|
-
brief: "Overwrite existing configuration",
|
|
377
|
-
default: false
|
|
378
|
-
} },
|
|
379
|
-
positional: {
|
|
380
|
-
kind: "tuple",
|
|
381
|
-
parameters: []
|
|
382
|
-
}
|
|
383
|
-
},
|
|
384
|
-
async func(flags) {
|
|
385
|
-
if (isConfigured() && !flags.force) {
|
|
386
|
-
prompts.log.warn(`Already configured. Wallet at ${pc.dim(getWalletPath())}\nUse ${pc.cyan("x402-proxy setup --force")} to reconfigure.`);
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
prompts.intro(pc.cyan("x402-proxy setup"));
|
|
390
|
-
prompts.log.info("This will generate a single BIP-39 mnemonic that derives wallets for both Solana and EVM chains.");
|
|
391
|
-
const action = await prompts.select({
|
|
392
|
-
message: "How would you like to set up your wallet?",
|
|
393
|
-
options: [{
|
|
394
|
-
value: "generate",
|
|
395
|
-
label: "Generate a new mnemonic"
|
|
396
|
-
}, {
|
|
397
|
-
value: "import",
|
|
398
|
-
label: "Import an existing mnemonic"
|
|
399
|
-
}]
|
|
400
|
-
});
|
|
401
|
-
if (prompts.isCancel(action)) {
|
|
402
|
-
prompts.cancel("Setup cancelled.");
|
|
403
|
-
process.exit(0);
|
|
404
|
-
}
|
|
405
|
-
let mnemonic;
|
|
406
|
-
if (action === "generate") {
|
|
407
|
-
mnemonic = generateMnemonic();
|
|
408
|
-
prompts.log.warn("Write down your mnemonic and store it safely. It will NOT be shown again.");
|
|
409
|
-
prompts.log.message(pc.bold(mnemonic));
|
|
410
|
-
} else {
|
|
411
|
-
const input = await prompts.text({
|
|
412
|
-
message: "Enter your 24-word mnemonic:",
|
|
413
|
-
validate: (v = "") => {
|
|
414
|
-
const words = v.trim().split(/\s+/);
|
|
415
|
-
if (words.length !== 12 && words.length !== 24) return "Mnemonic must be 12 or 24 words";
|
|
416
|
-
}
|
|
417
|
-
});
|
|
418
|
-
if (prompts.isCancel(input)) {
|
|
419
|
-
prompts.cancel("Setup cancelled.");
|
|
420
|
-
process.exit(0);
|
|
421
|
-
}
|
|
422
|
-
mnemonic = input.trim();
|
|
423
|
-
}
|
|
424
|
-
const evm = deriveEvmKeypair(mnemonic);
|
|
425
|
-
const sol = deriveSolanaKeypair(mnemonic);
|
|
426
|
-
prompts.log.success(`Base address: ${pc.green(evm.address)}`);
|
|
427
|
-
prompts.log.success(`Solana address: ${pc.green(sol.address)}`);
|
|
428
|
-
saveWalletFile({
|
|
429
|
-
version: 1,
|
|
430
|
-
mnemonic,
|
|
431
|
-
addresses: {
|
|
432
|
-
evm: evm.address,
|
|
433
|
-
solana: sol.address
|
|
434
|
-
}
|
|
435
|
-
});
|
|
436
|
-
saveConfig({});
|
|
437
|
-
prompts.log.info(`Config directory: ${pc.dim(getConfigDirShort())}`);
|
|
438
|
-
prompts.log.step("Fund your wallets to start using x402 resources:");
|
|
439
|
-
prompts.log.message(` Solana (USDC): Send USDC to ${pc.cyan(sol.address)}`);
|
|
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")}`);
|
|
443
|
-
prompts.outro(pc.green("Setup complete!"));
|
|
444
|
-
}
|
|
445
|
-
});
|
|
446
|
-
|
|
447
408
|
//#endregion
|
|
448
409
|
//#region src/commands/wallet-export.ts
|
|
449
410
|
const walletExportCommand = buildCommand({
|
|
450
|
-
docs: { brief: "Export private key to stdout (pipe-safe)" },
|
|
411
|
+
docs: { brief: "Export private key or mnemonic to stdout (pipe-safe)" },
|
|
451
412
|
parameters: {
|
|
452
413
|
flags: {},
|
|
453
414
|
positional: {
|
|
454
415
|
kind: "tuple",
|
|
455
416
|
parameters: [{
|
|
456
|
-
brief: "
|
|
417
|
+
brief: "What to export: evm, solana, or mnemonic",
|
|
457
418
|
parse: (input) => {
|
|
458
419
|
const v = input.toLowerCase();
|
|
459
|
-
if (v !== "evm" && v !== "solana") throw new Error("Must be 'evm' or '
|
|
420
|
+
if (v !== "evm" && v !== "solana" && v !== "mnemonic") throw new Error("Must be 'evm', 'solana', or 'mnemonic'");
|
|
460
421
|
return v;
|
|
461
422
|
}
|
|
462
423
|
}]
|
|
463
424
|
}
|
|
464
425
|
},
|
|
465
426
|
async func(flags, chain) {
|
|
427
|
+
if (chain === "mnemonic") {
|
|
428
|
+
const mnemonic = process.env.X402_PROXY_WALLET_MNEMONIC || loadWalletFile()?.mnemonic;
|
|
429
|
+
if (!mnemonic) {
|
|
430
|
+
error("No mnemonic available. Wallet may have been configured with individual keys.");
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
if (process.stdout.isTTY) {
|
|
434
|
+
const confirmed = await prompts.confirm({ message: "This will print your mnemonic to the terminal. Continue?" });
|
|
435
|
+
if (prompts.isCancel(confirmed) || !confirmed) process.exit(0);
|
|
436
|
+
} else warn("Warning: mnemonic will be printed to stdout.");
|
|
437
|
+
process.stdout.write(mnemonic);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
466
440
|
const wallet = resolveWallet();
|
|
467
441
|
if (wallet.source === "none") {
|
|
468
442
|
error("No wallet configured.");
|
|
@@ -485,44 +459,6 @@ const walletExportCommand = buildCommand({
|
|
|
485
459
|
}
|
|
486
460
|
});
|
|
487
461
|
|
|
488
|
-
//#endregion
|
|
489
|
-
//#region src/commands/wallet-fund.ts
|
|
490
|
-
const walletFundCommand = buildCommand({
|
|
491
|
-
docs: { brief: "Show wallet funding instructions" },
|
|
492
|
-
parameters: {
|
|
493
|
-
flags: {},
|
|
494
|
-
positional: {
|
|
495
|
-
kind: "tuple",
|
|
496
|
-
parameters: []
|
|
497
|
-
}
|
|
498
|
-
},
|
|
499
|
-
func() {
|
|
500
|
-
const wallet = resolveWallet();
|
|
501
|
-
if (wallet.source === "none") {
|
|
502
|
-
console.log(pc.yellow("No wallet configured."));
|
|
503
|
-
console.log(pc.dim(`Run ${pc.cyan("x402-proxy setup")} to create one.`));
|
|
504
|
-
process.exit(1);
|
|
505
|
-
}
|
|
506
|
-
console.log();
|
|
507
|
-
info("Funding Instructions");
|
|
508
|
-
console.log();
|
|
509
|
-
if (wallet.solanaAddress) {
|
|
510
|
-
console.log(pc.bold(" Solana (USDC):"));
|
|
511
|
-
console.log(` Send USDC to: ${pc.green(wallet.solanaAddress)}`);
|
|
512
|
-
console.log(pc.dim(" Network: Solana Mainnet"));
|
|
513
|
-
console.log();
|
|
514
|
-
}
|
|
515
|
-
if (wallet.evmAddress) {
|
|
516
|
-
console.log(pc.bold(" Base (USDC):"));
|
|
517
|
-
console.log(` Send USDC to: ${pc.green(wallet.evmAddress)}`);
|
|
518
|
-
console.log(pc.dim(" Network: Base (Chain ID 8453)"));
|
|
519
|
-
console.log();
|
|
520
|
-
}
|
|
521
|
-
console.log(pc.dim(" Tip: Most x402 services accept USDC on Base or Solana."));
|
|
522
|
-
console.log();
|
|
523
|
-
}
|
|
524
|
-
});
|
|
525
|
-
|
|
526
462
|
//#endregion
|
|
527
463
|
//#region src/commands/wallet-history.ts
|
|
528
464
|
const walletHistoryCommand = buildCommand({
|
|
@@ -582,7 +518,6 @@ const routes = buildRouteMap({
|
|
|
582
518
|
routes: {
|
|
583
519
|
info: walletInfoCommand,
|
|
584
520
|
history: walletHistoryCommand,
|
|
585
|
-
fund: walletFundCommand,
|
|
586
521
|
"export-key": walletExportCommand
|
|
587
522
|
},
|
|
588
523
|
defaultCommand: "info",
|
|
@@ -596,7 +531,7 @@ const routes = buildRouteMap({
|
|
|
596
531
|
});
|
|
597
532
|
const app = buildApplication(routes, {
|
|
598
533
|
name: "x402-proxy",
|
|
599
|
-
versionInfo: { currentVersion: "0.3.
|
|
534
|
+
versionInfo: { currentVersion: "0.3.2" },
|
|
600
535
|
scanner: { caseStyle: "allow-kebab-for-camel" }
|
|
601
536
|
});
|
|
602
537
|
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { parse, stringify } from "yaml";
|
|
6
|
+
import { base58 } from "@scure/base";
|
|
7
|
+
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
8
|
+
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
9
|
+
import { hmac } from "@noble/hashes/hmac.js";
|
|
10
|
+
import { sha512 } from "@noble/hashes/sha2.js";
|
|
11
|
+
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
12
|
+
import { HDKey } from "@scure/bip32";
|
|
13
|
+
import { generateMnemonic, mnemonicToSeedSync } from "@scure/bip39";
|
|
14
|
+
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
15
|
+
|
|
16
|
+
//#region src/lib/config.ts
|
|
17
|
+
const APP_NAME = "x402-proxy";
|
|
18
|
+
function getConfigDir() {
|
|
19
|
+
const xdg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
20
|
+
return path.join(xdg, APP_NAME);
|
|
21
|
+
}
|
|
22
|
+
/** Config dir with $HOME replaced by ~ for display */
|
|
23
|
+
function getConfigDirShort() {
|
|
24
|
+
const dir = getConfigDir();
|
|
25
|
+
const home = os.homedir();
|
|
26
|
+
return dir.startsWith(home) ? `~${dir.slice(home.length)}` : dir;
|
|
27
|
+
}
|
|
28
|
+
function getWalletPath() {
|
|
29
|
+
return path.join(getConfigDir(), "wallet.json");
|
|
30
|
+
}
|
|
31
|
+
function getHistoryPath() {
|
|
32
|
+
return path.join(getConfigDir(), "history.jsonl");
|
|
33
|
+
}
|
|
34
|
+
function ensureConfigDir() {
|
|
35
|
+
fs.mkdirSync(getConfigDir(), { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
function loadWalletFile() {
|
|
38
|
+
const p = getWalletPath();
|
|
39
|
+
if (!fs.existsSync(p)) return null;
|
|
40
|
+
try {
|
|
41
|
+
const data = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
42
|
+
if (data.version === 1 && typeof data.mnemonic === "string") return data;
|
|
43
|
+
return null;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function saveWalletFile(wallet) {
|
|
49
|
+
ensureConfigDir();
|
|
50
|
+
fs.writeFileSync(getWalletPath(), JSON.stringify(wallet, null, 2), {
|
|
51
|
+
mode: 384,
|
|
52
|
+
encoding: "utf-8"
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function loadConfig() {
|
|
56
|
+
const dir = getConfigDir();
|
|
57
|
+
for (const name of [
|
|
58
|
+
"config.yaml",
|
|
59
|
+
"config.yml",
|
|
60
|
+
"config.jsonc",
|
|
61
|
+
"config.json"
|
|
62
|
+
]) {
|
|
63
|
+
const p = path.join(dir, name);
|
|
64
|
+
if (!fs.existsSync(p)) continue;
|
|
65
|
+
try {
|
|
66
|
+
const raw = fs.readFileSync(p, "utf-8");
|
|
67
|
+
if (name.endsWith(".yaml") || name.endsWith(".yml")) return parse(raw);
|
|
68
|
+
if (name.endsWith(".jsonc")) {
|
|
69
|
+
const stripped = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
70
|
+
return JSON.parse(stripped);
|
|
71
|
+
}
|
|
72
|
+
return JSON.parse(raw);
|
|
73
|
+
} catch {}
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
function saveConfig(config) {
|
|
78
|
+
ensureConfigDir();
|
|
79
|
+
const p = path.join(getConfigDir(), "config.yaml");
|
|
80
|
+
fs.writeFileSync(p, stringify(config), "utf-8");
|
|
81
|
+
}
|
|
82
|
+
function isConfigured() {
|
|
83
|
+
if (process.env.X402_PROXY_WALLET_MNEMONIC) return true;
|
|
84
|
+
if (process.env.X402_PROXY_WALLET_EVM_KEY) return true;
|
|
85
|
+
if (process.env.X402_PROXY_WALLET_SOLANA_KEY) return true;
|
|
86
|
+
return loadWalletFile() !== null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/lib/derive.ts
|
|
91
|
+
/**
|
|
92
|
+
* Wallet derivation from BIP-39 mnemonic.
|
|
93
|
+
*
|
|
94
|
+
* A single 24-word mnemonic is the root secret. Both Solana and EVM keypairs
|
|
95
|
+
* are deterministically derived from it.
|
|
96
|
+
*
|
|
97
|
+
* Solana: SLIP-10 Ed25519 at m/44'/501'/0'/0'
|
|
98
|
+
* EVM: BIP-32 secp256k1 at m/44'/60'/0'/0/0
|
|
99
|
+
*
|
|
100
|
+
* Ported from agentbox/packages/openclaw-x402/src/wallet.ts
|
|
101
|
+
*/
|
|
102
|
+
function generateMnemonic$1() {
|
|
103
|
+
return generateMnemonic(wordlist, 256);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* SLIP-10 Ed25519 derivation at m/44'/501'/0'/0' (Phantom/Backpack compatible).
|
|
107
|
+
*/
|
|
108
|
+
const enc = new TextEncoder();
|
|
109
|
+
function deriveSolanaKeypair(mnemonic) {
|
|
110
|
+
const seed = mnemonicToSeedSync(mnemonic);
|
|
111
|
+
let I = hmac(sha512, enc.encode("ed25519 seed"), seed);
|
|
112
|
+
let key = I.slice(0, 32);
|
|
113
|
+
let chainCode = I.slice(32);
|
|
114
|
+
for (const index of [
|
|
115
|
+
2147483692,
|
|
116
|
+
2147484149,
|
|
117
|
+
2147483648,
|
|
118
|
+
2147483648
|
|
119
|
+
]) {
|
|
120
|
+
const data = new Uint8Array(37);
|
|
121
|
+
data[0] = 0;
|
|
122
|
+
data.set(key, 1);
|
|
123
|
+
data[33] = index >>> 24 & 255;
|
|
124
|
+
data[34] = index >>> 16 & 255;
|
|
125
|
+
data[35] = index >>> 8 & 255;
|
|
126
|
+
data[36] = index & 255;
|
|
127
|
+
I = hmac(sha512, chainCode, data);
|
|
128
|
+
key = I.slice(0, 32);
|
|
129
|
+
chainCode = I.slice(32);
|
|
130
|
+
}
|
|
131
|
+
const secretKey = new Uint8Array(key);
|
|
132
|
+
const publicKey = ed25519.getPublicKey(secretKey);
|
|
133
|
+
return {
|
|
134
|
+
secretKey,
|
|
135
|
+
publicKey,
|
|
136
|
+
address: base58.encode(publicKey)
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* BIP-32 secp256k1 derivation at m/44'/60'/0'/0/0.
|
|
141
|
+
*/
|
|
142
|
+
function deriveEvmKeypair(mnemonic) {
|
|
143
|
+
const seed = mnemonicToSeedSync(mnemonic);
|
|
144
|
+
const derived = HDKey.fromMasterSeed(seed).derive("m/44'/60'/0'/0/0");
|
|
145
|
+
if (!derived.privateKey) throw new Error("Failed to derive EVM private key");
|
|
146
|
+
const privateKey = `0x${Buffer.from(derived.privateKey).toString("hex")}`;
|
|
147
|
+
const hash = keccak_256(secp256k1.getPublicKey(derived.privateKey, false).slice(1));
|
|
148
|
+
return {
|
|
149
|
+
privateKey,
|
|
150
|
+
address: checksumAddress(Buffer.from(hash.slice(-20)).toString("hex"))
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function checksumAddress(addr) {
|
|
154
|
+
const hash = Buffer.from(keccak_256(enc.encode(addr))).toString("hex");
|
|
155
|
+
let out = "0x";
|
|
156
|
+
for (let i = 0; i < 40; i++) out += Number.parseInt(hash[i], 16) >= 8 ? addr[i].toUpperCase() : addr[i];
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
//#endregion
|
|
161
|
+
export { getConfigDirShort as a, isConfigured as c, saveConfig as d, saveWalletFile as f, ensureConfigDir as i, loadConfig as l, deriveSolanaKeypair as n, getHistoryPath as o, generateMnemonic$1 as r, getWalletPath as s, deriveEvmKeypair as t, loadWalletFile as u };
|
|
@@ -0,0 +1,88 @@
|
|
|
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-Rikypf5B.js";
|
|
3
|
+
import { buildCommand } from "@stricli/core";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
import * as prompts from "@clack/prompts";
|
|
6
|
+
|
|
7
|
+
//#region src/commands/setup.ts
|
|
8
|
+
async function runSetup(opts) {
|
|
9
|
+
if (isConfigured() && !opts?.force) {
|
|
10
|
+
prompts.log.warn(`Already configured. Wallet at ${pc.dim(getWalletPath())}\nTo reconfigure, run:\n ${pc.cyan("$ npx x402-proxy setup --force")}`);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
prompts.intro(pc.cyan("x402-proxy setup"));
|
|
14
|
+
prompts.log.info("This will generate a single BIP-39 mnemonic that derives wallets for both Solana and EVM chains.");
|
|
15
|
+
const action = await prompts.select({
|
|
16
|
+
message: "How would you like to set up your wallet?",
|
|
17
|
+
options: [{
|
|
18
|
+
value: "generate",
|
|
19
|
+
label: "Generate a new mnemonic"
|
|
20
|
+
}, {
|
|
21
|
+
value: "import",
|
|
22
|
+
label: "Import an existing mnemonic"
|
|
23
|
+
}]
|
|
24
|
+
});
|
|
25
|
+
if (prompts.isCancel(action)) {
|
|
26
|
+
prompts.cancel("Setup cancelled.");
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
let mnemonic;
|
|
30
|
+
if (action === "generate") {
|
|
31
|
+
mnemonic = generateMnemonic();
|
|
32
|
+
prompts.log.warn("Write down your mnemonic and store it safely. It will NOT be shown again.");
|
|
33
|
+
prompts.log.message(pc.bold(mnemonic));
|
|
34
|
+
} else {
|
|
35
|
+
const input = await prompts.text({
|
|
36
|
+
message: "Enter your 24-word mnemonic:",
|
|
37
|
+
validate: (v = "") => {
|
|
38
|
+
const words = v.trim().split(/\s+/);
|
|
39
|
+
if (words.length !== 12 && words.length !== 24) return "Mnemonic must be 12 or 24 words";
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
if (prompts.isCancel(input)) {
|
|
43
|
+
prompts.cancel("Setup cancelled.");
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
mnemonic = input.trim();
|
|
47
|
+
}
|
|
48
|
+
const evm = deriveEvmKeypair(mnemonic);
|
|
49
|
+
const sol = deriveSolanaKeypair(mnemonic);
|
|
50
|
+
prompts.log.success(`Base address: ${pc.green(evm.address)}`);
|
|
51
|
+
prompts.log.success(`Solana address: ${pc.green(sol.address)}`);
|
|
52
|
+
saveWalletFile({
|
|
53
|
+
version: 1,
|
|
54
|
+
mnemonic,
|
|
55
|
+
addresses: {
|
|
56
|
+
evm: evm.address,
|
|
57
|
+
solana: sol.address
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
saveConfig({});
|
|
61
|
+
prompts.log.info(`Config directory: ${pc.dim(getConfigDirShort())}`);
|
|
62
|
+
prompts.log.step("Fund your wallets to start using x402 resources:");
|
|
63
|
+
prompts.log.message(` Solana (USDC): Send USDC to ${pc.cyan(sol.address)}`);
|
|
64
|
+
prompts.log.message(` Base (USDC): Send USDC to ${pc.cyan(evm.address)}`);
|
|
65
|
+
prompts.log.step("Try your first request:");
|
|
66
|
+
prompts.log.message(` ${pc.cyan("$ npx x402-proxy https://twitter.surf.cascade.fyi/user/cascade_fyi")}`);
|
|
67
|
+
prompts.outro(pc.green("Setup complete!"));
|
|
68
|
+
}
|
|
69
|
+
const setupCommand = buildCommand({
|
|
70
|
+
docs: { brief: "Set up x402-proxy with a new wallet" },
|
|
71
|
+
parameters: {
|
|
72
|
+
flags: { force: {
|
|
73
|
+
kind: "boolean",
|
|
74
|
+
brief: "Overwrite existing configuration",
|
|
75
|
+
default: false
|
|
76
|
+
} },
|
|
77
|
+
positional: {
|
|
78
|
+
kind: "tuple",
|
|
79
|
+
parameters: []
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
async func(flags) {
|
|
83
|
+
await runSetup({ force: flags.force });
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
//#endregion
|
|
88
|
+
export { setupCommand as n, runSetup as t };
|
|
@@ -1,25 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { a as getConfigDirShort, l as loadConfig, n as deriveSolanaKeypair, o as getHistoryPath, t as deriveEvmKeypair, u as loadWalletFile } from "./derive-Rikypf5B.js";
|
|
2
3
|
import { buildCommand } from "@stricli/core";
|
|
3
4
|
import pc from "picocolors";
|
|
4
5
|
import { x402Client } from "@x402/fetch";
|
|
5
|
-
import
|
|
6
|
-
import os from "node:os";
|
|
7
|
-
import path from "node:path";
|
|
8
|
-
import { parse, stringify } from "yaml";
|
|
6
|
+
import { appendFileSync, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
9
7
|
import { base58 } from "@scure/base";
|
|
10
8
|
import { ExactEvmScheme, toClientEvmSigner } from "@x402/evm";
|
|
11
9
|
import { ExactSvmScheme } from "@x402/svm/exact/client";
|
|
12
10
|
import { createPublicClient, http } from "viem";
|
|
13
11
|
import { privateKeyToAccount } from "viem/accounts";
|
|
14
12
|
import { base } from "viem/chains";
|
|
15
|
-
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
16
|
-
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
17
|
-
import { hmac } from "@noble/hashes/hmac.js";
|
|
18
|
-
import { sha512 } from "@noble/hashes/sha2.js";
|
|
19
|
-
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
20
|
-
import { HDKey } from "@scure/bip32";
|
|
21
|
-
import { generateMnemonic, mnemonicToSeedSync } from "@scure/bip39";
|
|
22
|
-
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
23
13
|
|
|
24
14
|
//#region src/history.ts
|
|
25
15
|
const HISTORY_MAX_LINES = 1e3;
|
|
@@ -132,80 +122,6 @@ function formatTxLine(r, opts) {
|
|
|
132
122
|
return ` ${timeStr} ${r.ok ? "" : "✗ "}${parts.join(" · ")}`;
|
|
133
123
|
}
|
|
134
124
|
|
|
135
|
-
//#endregion
|
|
136
|
-
//#region src/lib/config.ts
|
|
137
|
-
const APP_NAME = "x402-proxy";
|
|
138
|
-
function getConfigDir() {
|
|
139
|
-
const xdg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
140
|
-
return path.join(xdg, APP_NAME);
|
|
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
|
-
}
|
|
148
|
-
function getWalletPath() {
|
|
149
|
-
return path.join(getConfigDir(), "wallet.json");
|
|
150
|
-
}
|
|
151
|
-
function getHistoryPath() {
|
|
152
|
-
return path.join(getConfigDir(), "history.jsonl");
|
|
153
|
-
}
|
|
154
|
-
function ensureConfigDir() {
|
|
155
|
-
fs.mkdirSync(getConfigDir(), { recursive: true });
|
|
156
|
-
}
|
|
157
|
-
function loadWalletFile() {
|
|
158
|
-
const p = getWalletPath();
|
|
159
|
-
if (!fs.existsSync(p)) return null;
|
|
160
|
-
try {
|
|
161
|
-
const data = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
162
|
-
if (data.version === 1 && typeof data.mnemonic === "string") return data;
|
|
163
|
-
return null;
|
|
164
|
-
} catch {
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
function saveWalletFile(wallet) {
|
|
169
|
-
ensureConfigDir();
|
|
170
|
-
fs.writeFileSync(getWalletPath(), JSON.stringify(wallet, null, 2), {
|
|
171
|
-
mode: 384,
|
|
172
|
-
encoding: "utf-8"
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
function loadConfig() {
|
|
176
|
-
const dir = getConfigDir();
|
|
177
|
-
for (const name of [
|
|
178
|
-
"config.yaml",
|
|
179
|
-
"config.yml",
|
|
180
|
-
"config.jsonc",
|
|
181
|
-
"config.json"
|
|
182
|
-
]) {
|
|
183
|
-
const p = path.join(dir, name);
|
|
184
|
-
if (!fs.existsSync(p)) continue;
|
|
185
|
-
try {
|
|
186
|
-
const raw = fs.readFileSync(p, "utf-8");
|
|
187
|
-
if (name.endsWith(".yaml") || name.endsWith(".yml")) return parse(raw);
|
|
188
|
-
if (name.endsWith(".jsonc")) {
|
|
189
|
-
const stripped = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
190
|
-
return JSON.parse(stripped);
|
|
191
|
-
}
|
|
192
|
-
return JSON.parse(raw);
|
|
193
|
-
} catch {}
|
|
194
|
-
}
|
|
195
|
-
return null;
|
|
196
|
-
}
|
|
197
|
-
function saveConfig(config) {
|
|
198
|
-
ensureConfigDir();
|
|
199
|
-
const p = path.join(getConfigDir(), "config.yaml");
|
|
200
|
-
fs.writeFileSync(p, stringify(config), "utf-8");
|
|
201
|
-
}
|
|
202
|
-
function isConfigured() {
|
|
203
|
-
if (process.env.X402_PROXY_WALLET_MNEMONIC) return true;
|
|
204
|
-
if (process.env.X402_PROXY_WALLET_EVM_KEY) return true;
|
|
205
|
-
if (process.env.X402_PROXY_WALLET_SOLANA_KEY) return true;
|
|
206
|
-
return loadWalletFile() !== null;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
125
|
//#endregion
|
|
210
126
|
//#region src/lib/output.ts
|
|
211
127
|
function isTTY() {
|
|
@@ -224,77 +140,6 @@ function dim(msg) {
|
|
|
224
140
|
process.stderr.write(`${isTTY() ? pc.dim(msg) : msg}\n`);
|
|
225
141
|
}
|
|
226
142
|
|
|
227
|
-
//#endregion
|
|
228
|
-
//#region src/lib/derive.ts
|
|
229
|
-
/**
|
|
230
|
-
* Wallet derivation from BIP-39 mnemonic.
|
|
231
|
-
*
|
|
232
|
-
* A single 24-word mnemonic is the root secret. Both Solana and EVM keypairs
|
|
233
|
-
* are deterministically derived from it.
|
|
234
|
-
*
|
|
235
|
-
* Solana: SLIP-10 Ed25519 at m/44'/501'/0'/0'
|
|
236
|
-
* EVM: BIP-32 secp256k1 at m/44'/60'/0'/0/0
|
|
237
|
-
*
|
|
238
|
-
* Ported from agentbox/packages/openclaw-x402/src/wallet.ts
|
|
239
|
-
*/
|
|
240
|
-
function generateMnemonic$1() {
|
|
241
|
-
return generateMnemonic(wordlist, 256);
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* SLIP-10 Ed25519 derivation at m/44'/501'/0'/0' (Phantom/Backpack compatible).
|
|
245
|
-
*/
|
|
246
|
-
const enc = new TextEncoder();
|
|
247
|
-
function deriveSolanaKeypair(mnemonic) {
|
|
248
|
-
const seed = mnemonicToSeedSync(mnemonic);
|
|
249
|
-
let I = hmac(sha512, enc.encode("ed25519 seed"), seed);
|
|
250
|
-
let key = I.slice(0, 32);
|
|
251
|
-
let chainCode = I.slice(32);
|
|
252
|
-
for (const index of [
|
|
253
|
-
2147483692,
|
|
254
|
-
2147484149,
|
|
255
|
-
2147483648,
|
|
256
|
-
2147483648
|
|
257
|
-
]) {
|
|
258
|
-
const data = new Uint8Array(37);
|
|
259
|
-
data[0] = 0;
|
|
260
|
-
data.set(key, 1);
|
|
261
|
-
data[33] = index >>> 24 & 255;
|
|
262
|
-
data[34] = index >>> 16 & 255;
|
|
263
|
-
data[35] = index >>> 8 & 255;
|
|
264
|
-
data[36] = index & 255;
|
|
265
|
-
I = hmac(sha512, chainCode, data);
|
|
266
|
-
key = I.slice(0, 32);
|
|
267
|
-
chainCode = I.slice(32);
|
|
268
|
-
}
|
|
269
|
-
const secretKey = new Uint8Array(key);
|
|
270
|
-
const publicKey = ed25519.getPublicKey(secretKey);
|
|
271
|
-
return {
|
|
272
|
-
secretKey,
|
|
273
|
-
publicKey,
|
|
274
|
-
address: base58.encode(publicKey)
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
/**
|
|
278
|
-
* BIP-32 secp256k1 derivation at m/44'/60'/0'/0/0.
|
|
279
|
-
*/
|
|
280
|
-
function deriveEvmKeypair(mnemonic) {
|
|
281
|
-
const seed = mnemonicToSeedSync(mnemonic);
|
|
282
|
-
const derived = HDKey.fromMasterSeed(seed).derive("m/44'/60'/0'/0/0");
|
|
283
|
-
if (!derived.privateKey) throw new Error("Failed to derive EVM private key");
|
|
284
|
-
const privateKey = `0x${Buffer.from(derived.privateKey).toString("hex")}`;
|
|
285
|
-
const hash = keccak_256(secp256k1.getPublicKey(derived.privateKey, false).slice(1));
|
|
286
|
-
return {
|
|
287
|
-
privateKey,
|
|
288
|
-
address: checksumAddress(Buffer.from(hash.slice(-20)).toString("hex"))
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
function checksumAddress(addr) {
|
|
292
|
-
const hash = Buffer.from(keccak_256(enc.encode(addr))).toString("hex");
|
|
293
|
-
let out = "0x";
|
|
294
|
-
for (let i = 0; i < 40; i++) out += Number.parseInt(hash[i], 16) >= 8 ? addr[i].toUpperCase() : addr[i];
|
|
295
|
-
return out;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
143
|
//#endregion
|
|
299
144
|
//#region src/lib/resolve-wallet.ts
|
|
300
145
|
/**
|
|
@@ -468,8 +313,7 @@ const walletInfoCommand = buildCommand({
|
|
|
468
313
|
const wallet = resolveWallet();
|
|
469
314
|
if (wallet.source === "none") {
|
|
470
315
|
console.log(pc.yellow("No wallet configured."));
|
|
471
|
-
console.log(pc.dim(
|
|
472
|
-
console.log(pc.dim(`Or set ${pc.cyan("X402_PROXY_WALLET_MNEMONIC")} environment variable.`));
|
|
316
|
+
console.log(pc.dim(`\nRun:\n ${pc.cyan("$ npx x402-proxy setup")}\n\nOr set ${pc.cyan("X402_PROXY_WALLET_MNEMONIC")} environment variable.`));
|
|
473
317
|
process.exit(1);
|
|
474
318
|
}
|
|
475
319
|
console.log();
|
|
@@ -487,6 +331,12 @@ const walletInfoCommand = buildCommand({
|
|
|
487
331
|
const bal = sol ? balanceLine(sol.usdc, sol.sol, "SOL") : pc.dim(" (network error)");
|
|
488
332
|
console.log(` Solana: ${pc.green(wallet.solanaAddress)}${bal}`);
|
|
489
333
|
}
|
|
334
|
+
const evmEmpty = !evm || evm.usdc === "0.00";
|
|
335
|
+
const solEmpty = !sol || sol.usdc === "0.00";
|
|
336
|
+
if (evmEmpty && solEmpty) {
|
|
337
|
+
console.log();
|
|
338
|
+
dim(" Send USDC to either address above to start using x402 APIs.");
|
|
339
|
+
}
|
|
490
340
|
console.log();
|
|
491
341
|
const records = readHistory(getHistoryPath());
|
|
492
342
|
if (records.length > 0) {
|
|
@@ -501,7 +351,7 @@ const walletInfoCommand = buildCommand({
|
|
|
501
351
|
console.log(pc.dim(` Today: ${spend.today.toFixed(4)} USDC | Total: ${spend.total.toFixed(4)} USDC | ${spend.count} tx`));
|
|
502
352
|
} else dim(" No transactions yet.");
|
|
503
353
|
console.log();
|
|
504
|
-
console.log(pc.dim(" See also: wallet history, wallet
|
|
354
|
+
console.log(pc.dim(" See also: wallet history, wallet export-key"));
|
|
505
355
|
console.log();
|
|
506
356
|
}
|
|
507
357
|
});
|
|
@@ -572,4 +422,4 @@ const statusCommand = buildCommand({
|
|
|
572
422
|
});
|
|
573
423
|
|
|
574
424
|
//#endregion
|
|
575
|
-
export {
|
|
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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "x402-proxy",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "curl for x402 paid APIs. Auto-pays any endpoint on Base and Solana.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
"@types/node": "^22.0.0",
|
|
42
42
|
"publint": "^0.3.17",
|
|
43
43
|
"tsdown": "^0.20.3",
|
|
44
|
-
"typescript": "^5.9.0"
|
|
44
|
+
"typescript": "^5.9.0",
|
|
45
|
+
"vitest": "^4.0.18"
|
|
45
46
|
},
|
|
46
47
|
"files": [
|
|
47
48
|
"dist/**",
|
|
@@ -78,6 +79,7 @@
|
|
|
78
79
|
"build": "rm -rf dist && tsdown --publint",
|
|
79
80
|
"dev": "tsdown --watch",
|
|
80
81
|
"type-check": "tsc --noEmit",
|
|
82
|
+
"test": "vitest run",
|
|
81
83
|
"check": "pnpm type-check && biome check --write"
|
|
82
84
|
}
|
|
83
85
|
}
|