x402-proxy 0.2.0 → 0.2.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 +43 -35
- package/dist/bin/cli.js +77 -78
- package/dist/status-DnxuVdOP.js +4 -0
- package/dist/{status-DeCY-cLR.js → status-J-RoszDZ.js} +152 -151
- package/package.json +9 -8
- package/dist/status-CPBQ0UBZ.js +0 -4
package/README.md
CHANGED
|
@@ -1,33 +1,47 @@
|
|
|
1
1
|
# x402-proxy
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`curl` for [x402](https://www.x402.org/) paid APIs. Auto-pays HTTP 402 responses with USDC on Base and Solana - zero crypto code on the buyer side.
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npx x402-proxy
|
|
9
|
-
npx x402-proxy wallet fund # see where to send USDC
|
|
10
|
-
npx x402-proxy https://example.com # make a paid request
|
|
8
|
+
npx x402-proxy https://twitter.surf.cascade.fyi/search?q=cascade_fyi
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
That's it. The endpoint returns 402, x402-proxy pays and streams the response.
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
First time? Set up a wallet:
|
|
16
14
|
|
|
17
15
|
```bash
|
|
18
|
-
x402-proxy
|
|
19
|
-
x402-proxy
|
|
20
|
-
x402-proxy setup # onboarding wizard
|
|
21
|
-
x402-proxy status # config + wallet + spend summary
|
|
22
|
-
x402-proxy wallet # show addresses
|
|
23
|
-
x402-proxy wallet history # payment history
|
|
24
|
-
x402-proxy wallet fund # funding instructions
|
|
25
|
-
x402-proxy wallet export-key <chain> # bare key to stdout (evm|solana)
|
|
16
|
+
npx x402-proxy setup # generate wallet from BIP-39 mnemonic
|
|
17
|
+
npx x402-proxy wallet fund # see where to send USDC
|
|
26
18
|
```
|
|
27
19
|
|
|
28
|
-
|
|
20
|
+
One mnemonic derives both EVM (Base) and Solana keypairs. Fund either chain and go.
|
|
21
|
+
|
|
22
|
+
## MCP Proxy
|
|
23
|
+
|
|
24
|
+
Let your AI agent consume any paid MCP server. Configure in Claude, Cursor, or any MCP client:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"paid-service": {
|
|
30
|
+
"command": "npx",
|
|
31
|
+
"args": ["x402-proxy", "mcp", "https://mcp.example.com/sse"],
|
|
32
|
+
"env": {
|
|
33
|
+
"X402_PROXY_WALLET_MNEMONIC": "your 24 words here"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The proxy sits between your agent and the remote server, intercepting 402 responses, paying automatically, and forwarding the result. Your agent never touches crypto.
|
|
41
|
+
|
|
42
|
+
## HTTP Requests
|
|
29
43
|
|
|
30
|
-
|
|
44
|
+
Works like curl. Response body streams to stdout, payment info goes to stderr.
|
|
31
45
|
|
|
32
46
|
```bash
|
|
33
47
|
# GET request
|
|
@@ -38,32 +52,26 @@ x402-proxy --method POST \
|
|
|
38
52
|
--header "Content-Type: application/json" \
|
|
39
53
|
--body '{"url":"https://x402.org"}' \
|
|
40
54
|
https://web.surf.cascade.fyi/v1/crawl
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Response body streams to stdout, payment info goes to stderr. Pipe-safe:
|
|
44
55
|
|
|
45
|
-
|
|
56
|
+
# Pipe-safe
|
|
46
57
|
x402-proxy https://api.example.com/data | jq '.results'
|
|
47
58
|
```
|
|
48
59
|
|
|
49
|
-
##
|
|
50
|
-
|
|
51
|
-
Wraps a remote MCP server with automatic x402 payment. Configure in your MCP client:
|
|
60
|
+
## Commands
|
|
52
61
|
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
62
|
+
```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 fund # funding instructions
|
|
70
|
+
x402-proxy wallet export-key <chain> # bare key to stdout (evm|solana)
|
|
65
71
|
```
|
|
66
72
|
|
|
73
|
+
All commands support `--help` for details.
|
|
74
|
+
|
|
67
75
|
## Wallet
|
|
68
76
|
|
|
69
77
|
A single BIP-39 mnemonic derives both chains:
|
package/dist/bin/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { S as
|
|
2
|
+
import { C as readHistory, S as formatTxLine, _ as isConfigured, a as deriveEvmKeypair, b as appendHistory, c as dim, d as isTTY, f as warn, g as getWalletPath, h as getHistoryPath, i as resolveWallet, l as error, m as getConfigDir, n as statusCommand, o as deriveSolanaKeypair, p as ensureConfigDir, r as buildX402Client, s as generateMnemonic, u as info, v as saveConfig, x as calcSpend, y as saveWalletFile } from "../status-J-RoszDZ.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";
|
|
@@ -56,8 +56,7 @@ const fetchCommand = buildCommand({
|
|
|
56
56
|
kind: "parsed",
|
|
57
57
|
brief: "HTTP method",
|
|
58
58
|
parse: String,
|
|
59
|
-
default: "GET"
|
|
60
|
-
optional: true
|
|
59
|
+
default: "GET"
|
|
61
60
|
},
|
|
62
61
|
body: {
|
|
63
62
|
kind: "parsed",
|
|
@@ -102,8 +101,8 @@ const fetchCommand = buildCommand({
|
|
|
102
101
|
async func(flags, url) {
|
|
103
102
|
if (!url) {
|
|
104
103
|
if (isConfigured()) {
|
|
105
|
-
const {
|
|
106
|
-
|
|
104
|
+
const { displayStatus } = await import("../status-DnxuVdOP.js");
|
|
105
|
+
displayStatus();
|
|
107
106
|
} else {
|
|
108
107
|
console.log();
|
|
109
108
|
console.log(pc.cyan("x402-proxy") + pc.dim(" - pay for any x402 resource"));
|
|
@@ -250,7 +249,7 @@ const mcpCommand = buildCommand({
|
|
|
250
249
|
autoPayment: true,
|
|
251
250
|
onPaymentRequested: (ctx) => {
|
|
252
251
|
const accept = ctx.paymentRequired.accepts?.[0];
|
|
253
|
-
if (accept) warn(` Payment: ${accept.
|
|
252
|
+
if (accept) warn(` Payment: ${accept.amount} on ${accept.network} for tool "${ctx.toolName}"`);
|
|
254
253
|
return true;
|
|
255
254
|
}
|
|
256
255
|
});
|
|
@@ -365,7 +364,7 @@ const setupCommand = buildCommand({
|
|
|
365
364
|
} else {
|
|
366
365
|
const input = await prompts.text({
|
|
367
366
|
message: "Enter your 24-word mnemonic:",
|
|
368
|
-
validate: (v) => {
|
|
367
|
+
validate: (v = "") => {
|
|
369
368
|
const words = v.trim().split(/\s+/);
|
|
370
369
|
if (words.length !== 12 && words.length !== 24) return "Mnemonic must be 12 or 24 words";
|
|
371
370
|
}
|
|
@@ -427,51 +426,44 @@ const walletInfoCommand = buildCommand({
|
|
|
427
426
|
});
|
|
428
427
|
|
|
429
428
|
//#endregion
|
|
430
|
-
//#region src/commands/wallet-
|
|
431
|
-
const
|
|
432
|
-
docs: { brief: "
|
|
429
|
+
//#region src/commands/wallet-export.ts
|
|
430
|
+
const walletExportCommand = buildCommand({
|
|
431
|
+
docs: { brief: "Export private key to stdout (pipe-safe)" },
|
|
433
432
|
parameters: {
|
|
434
|
-
flags: {
|
|
435
|
-
limit: {
|
|
436
|
-
kind: "parsed",
|
|
437
|
-
brief: "Number of entries to show",
|
|
438
|
-
parse: Number,
|
|
439
|
-
default: "20"
|
|
440
|
-
},
|
|
441
|
-
json: {
|
|
442
|
-
kind: "boolean",
|
|
443
|
-
brief: "Output raw JSONL",
|
|
444
|
-
default: false
|
|
445
|
-
}
|
|
446
|
-
},
|
|
433
|
+
flags: {},
|
|
447
434
|
positional: {
|
|
448
435
|
kind: "tuple",
|
|
449
|
-
parameters: [
|
|
436
|
+
parameters: [{
|
|
437
|
+
brief: "Chain to export: evm or solana",
|
|
438
|
+
parse: (input) => {
|
|
439
|
+
const v = input.toLowerCase();
|
|
440
|
+
if (v !== "evm" && v !== "solana") throw new Error("Must be 'evm' or 'solana'");
|
|
441
|
+
return v;
|
|
442
|
+
}
|
|
443
|
+
}]
|
|
450
444
|
}
|
|
451
445
|
},
|
|
452
|
-
func(flags) {
|
|
453
|
-
const
|
|
454
|
-
if (
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
}
|
|
458
|
-
if (flags.json) {
|
|
459
|
-
const slice = records.slice(-flags.limit);
|
|
460
|
-
for (const r of slice) process.stdout.write(`${JSON.stringify(r)}\n`);
|
|
461
|
-
return;
|
|
446
|
+
func(flags, chain) {
|
|
447
|
+
const wallet = resolveWallet();
|
|
448
|
+
if (wallet.source === "none") {
|
|
449
|
+
error("No wallet configured.");
|
|
450
|
+
process.exit(1);
|
|
462
451
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
452
|
+
if (chain === "evm") {
|
|
453
|
+
if (!wallet.evmKey) {
|
|
454
|
+
error("No EVM key available.");
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
warn("Warning: private key will be printed to stdout.");
|
|
458
|
+
process.stdout.write(wallet.evmKey);
|
|
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
466
|
}
|
|
472
|
-
console.log();
|
|
473
|
-
console.log(pc.dim(` Today: ${spend.today.toFixed(4)} USDC | Total: ${spend.total.toFixed(4)} USDC | ${spend.count} transactions`));
|
|
474
|
-
console.log();
|
|
475
467
|
}
|
|
476
468
|
});
|
|
477
469
|
|
|
@@ -514,44 +506,51 @@ const walletFundCommand = buildCommand({
|
|
|
514
506
|
});
|
|
515
507
|
|
|
516
508
|
//#endregion
|
|
517
|
-
//#region src/commands/wallet-
|
|
518
|
-
const
|
|
519
|
-
docs: { brief: "
|
|
509
|
+
//#region src/commands/wallet-history.ts
|
|
510
|
+
const walletHistoryCommand = buildCommand({
|
|
511
|
+
docs: { brief: "Show payment history" },
|
|
520
512
|
parameters: {
|
|
521
|
-
flags: {
|
|
513
|
+
flags: {
|
|
514
|
+
limit: {
|
|
515
|
+
kind: "parsed",
|
|
516
|
+
brief: "Number of entries to show",
|
|
517
|
+
parse: Number,
|
|
518
|
+
default: "20"
|
|
519
|
+
},
|
|
520
|
+
json: {
|
|
521
|
+
kind: "boolean",
|
|
522
|
+
brief: "Output raw JSONL",
|
|
523
|
+
default: false
|
|
524
|
+
}
|
|
525
|
+
},
|
|
522
526
|
positional: {
|
|
523
527
|
kind: "tuple",
|
|
524
|
-
parameters: [
|
|
525
|
-
brief: "Chain to export: evm or solana",
|
|
526
|
-
parse: (input) => {
|
|
527
|
-
const v = input.toLowerCase();
|
|
528
|
-
if (v !== "evm" && v !== "solana") throw new Error("Must be 'evm' or 'solana'");
|
|
529
|
-
return v;
|
|
530
|
-
}
|
|
531
|
-
}]
|
|
528
|
+
parameters: []
|
|
532
529
|
}
|
|
533
530
|
},
|
|
534
|
-
func(
|
|
535
|
-
const
|
|
536
|
-
if (
|
|
537
|
-
|
|
538
|
-
|
|
531
|
+
func(flags) {
|
|
532
|
+
const records = readHistory(getHistoryPath());
|
|
533
|
+
if (records.length === 0) {
|
|
534
|
+
console.log(pc.dim("No payment history yet."));
|
|
535
|
+
return;
|
|
539
536
|
}
|
|
540
|
-
if (
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
}
|
|
545
|
-
warn("Warning: private key will be printed to stdout.");
|
|
546
|
-
process.stdout.write(wallet.evmKey);
|
|
547
|
-
} else {
|
|
548
|
-
if (!wallet.solanaKey) {
|
|
549
|
-
error("No Solana key available.");
|
|
550
|
-
process.exit(1);
|
|
551
|
-
}
|
|
552
|
-
warn("Warning: private key will be printed to stdout.");
|
|
553
|
-
process.stdout.write(base58.encode(wallet.solanaKey.slice(0, 32)));
|
|
537
|
+
if (flags.json) {
|
|
538
|
+
const slice = records.slice(-flags.limit);
|
|
539
|
+
for (const r of slice) process.stdout.write(`${JSON.stringify(r)}\n`);
|
|
540
|
+
return;
|
|
554
541
|
}
|
|
542
|
+
const spend = calcSpend(records);
|
|
543
|
+
const slice = records.slice(-flags.limit);
|
|
544
|
+
console.log();
|
|
545
|
+
info("Payment History");
|
|
546
|
+
console.log();
|
|
547
|
+
for (const r of slice) {
|
|
548
|
+
const line = formatTxLine(r).replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
549
|
+
console.log(line);
|
|
550
|
+
}
|
|
551
|
+
console.log();
|
|
552
|
+
console.log(pc.dim(` Today: ${spend.today.toFixed(4)} USDC | Total: ${spend.total.toFixed(4)} USDC | ${spend.count} transactions`));
|
|
553
|
+
console.log();
|
|
555
554
|
}
|
|
556
555
|
});
|
|
557
556
|
|
|
@@ -575,11 +574,11 @@ const routes = buildRouteMap({
|
|
|
575
574
|
status: statusCommand
|
|
576
575
|
},
|
|
577
576
|
defaultCommand: "fetch",
|
|
578
|
-
docs: { brief: "
|
|
577
|
+
docs: { brief: "curl for x402 paid APIs" }
|
|
579
578
|
});
|
|
580
579
|
const app = buildApplication(routes, {
|
|
581
580
|
name: "x402-proxy",
|
|
582
|
-
versionInfo: { currentVersion: "0.2.
|
|
581
|
+
versionInfo: { currentVersion: "0.2.1" },
|
|
583
582
|
scanner: { caseStyle: "allow-kebab-for-camel" }
|
|
584
583
|
});
|
|
585
584
|
|
|
@@ -1,26 +1,127 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { buildCommand } from "@stricli/core";
|
|
3
3
|
import pc from "picocolors";
|
|
4
|
+
import { x402Client } from "@x402/fetch";
|
|
4
5
|
import fs, { appendFileSync, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
5
6
|
import os from "node:os";
|
|
6
7
|
import path from "node:path";
|
|
7
8
|
import { parse, stringify } from "yaml";
|
|
9
|
+
import { base58 } from "@scure/base";
|
|
8
10
|
import { ExactEvmScheme, toClientEvmSigner } from "@x402/evm";
|
|
9
|
-
import { x402Client } from "@x402/fetch";
|
|
10
11
|
import { ExactSvmScheme } from "@x402/svm/exact/client";
|
|
11
|
-
import { base58 } from "@scure/base";
|
|
12
12
|
import { createPublicClient, http } from "viem";
|
|
13
13
|
import { privateKeyToAccount } from "viem/accounts";
|
|
14
14
|
import { base } from "viem/chains";
|
|
15
15
|
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
16
16
|
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
17
17
|
import { hmac } from "@noble/hashes/hmac.js";
|
|
18
|
-
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
19
18
|
import { sha512 } from "@noble/hashes/sha2.js";
|
|
19
|
+
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
20
20
|
import { HDKey } from "@scure/bip32";
|
|
21
21
|
import { generateMnemonic, mnemonicToSeedSync } from "@scure/bip39";
|
|
22
22
|
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
23
23
|
|
|
24
|
+
//#region src/history.ts
|
|
25
|
+
const HISTORY_MAX_LINES = 1e3;
|
|
26
|
+
const HISTORY_KEEP_LINES = 500;
|
|
27
|
+
function appendHistory(historyPath, record) {
|
|
28
|
+
try {
|
|
29
|
+
appendFileSync(historyPath, `${JSON.stringify(record)}\n`);
|
|
30
|
+
if (existsSync(historyPath)) {
|
|
31
|
+
if (statSync(historyPath).size > HISTORY_MAX_LINES * 200) {
|
|
32
|
+
const lines = readFileSync(historyPath, "utf-8").trimEnd().split("\n");
|
|
33
|
+
if (lines.length > HISTORY_MAX_LINES) writeFileSync(historyPath, `${lines.slice(-HISTORY_KEEP_LINES).join("\n")}\n`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
} catch {}
|
|
37
|
+
}
|
|
38
|
+
function readHistory(historyPath) {
|
|
39
|
+
try {
|
|
40
|
+
if (!existsSync(historyPath)) return [];
|
|
41
|
+
const content = readFileSync(historyPath, "utf-8").trimEnd();
|
|
42
|
+
if (!content) return [];
|
|
43
|
+
return content.split("\n").flatMap((line) => {
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(line);
|
|
46
|
+
if (typeof parsed.t !== "number" || typeof parsed.kind !== "string") return [];
|
|
47
|
+
return [parsed];
|
|
48
|
+
} catch {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
} catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function calcSpend(records) {
|
|
57
|
+
const todayStart = /* @__PURE__ */ new Date();
|
|
58
|
+
todayStart.setUTCHours(0, 0, 0, 0);
|
|
59
|
+
const todayMs = todayStart.getTime();
|
|
60
|
+
let today = 0;
|
|
61
|
+
let total = 0;
|
|
62
|
+
let count = 0;
|
|
63
|
+
for (const r of records) {
|
|
64
|
+
if (!r.ok || r.amount == null) continue;
|
|
65
|
+
if (r.token !== "USDC") continue;
|
|
66
|
+
total += r.amount;
|
|
67
|
+
count++;
|
|
68
|
+
if (r.t >= todayMs) today += r.amount;
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
today,
|
|
72
|
+
total,
|
|
73
|
+
count
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function formatAmount(amount, token) {
|
|
77
|
+
if (token === "USDC") {
|
|
78
|
+
if (amount >= .01) return `${amount.toFixed(2)} USDC`;
|
|
79
|
+
if (amount >= .001) return `${amount.toFixed(3)} USDC`;
|
|
80
|
+
if (amount >= 1e-4) return `${amount.toFixed(4)} USDC`;
|
|
81
|
+
return `${amount.toFixed(6)} USDC`;
|
|
82
|
+
}
|
|
83
|
+
if (token === "SOL") return `${amount} SOL`;
|
|
84
|
+
return `${amount} ${token}`;
|
|
85
|
+
}
|
|
86
|
+
const KIND_LABELS = {
|
|
87
|
+
x402_inference: "inference",
|
|
88
|
+
x402_payment: "payment",
|
|
89
|
+
transfer: "transfer",
|
|
90
|
+
buy: "buy",
|
|
91
|
+
sell: "sell",
|
|
92
|
+
mint: "mint",
|
|
93
|
+
swap: "swap"
|
|
94
|
+
};
|
|
95
|
+
function explorerUrl(net, tx) {
|
|
96
|
+
if (net.startsWith("eip155:")) {
|
|
97
|
+
if (net.split(":")[1] === "8453") return `https://basescan.org/tx/${tx}`;
|
|
98
|
+
return `https://basescan.org/tx/${tx}`;
|
|
99
|
+
}
|
|
100
|
+
return `https://solscan.io/tx/${tx}`;
|
|
101
|
+
}
|
|
102
|
+
/** Strip provider prefix and OpenRouter date suffixes from model IDs.
|
|
103
|
+
* e.g. "minimax/minimax-m2.5-20260211" -> "minimax-m2.5"
|
|
104
|
+
* "moonshotai/kimi-k2.5-0127" -> "kimi-k2.5" */
|
|
105
|
+
function shortModel(model) {
|
|
106
|
+
const parts = model.split("/");
|
|
107
|
+
return parts[parts.length - 1].replace(/-\d{6,8}$/, "").replace(/-\d{4}$/, "");
|
|
108
|
+
}
|
|
109
|
+
function formatTxLine(r) {
|
|
110
|
+
const time = new Date(r.t).toLocaleTimeString("en-US", {
|
|
111
|
+
hour: "2-digit",
|
|
112
|
+
minute: "2-digit",
|
|
113
|
+
hour12: false,
|
|
114
|
+
timeZone: "UTC"
|
|
115
|
+
});
|
|
116
|
+
const timeStr = r.tx ? `[${time}](${explorerUrl(r.net, r.tx)})` : time;
|
|
117
|
+
const parts = [r.kind === "x402_inference" && r.model ? shortModel(r.model) : KIND_LABELS[r.kind] ?? r.kind];
|
|
118
|
+
if (r.label) parts.push(r.label);
|
|
119
|
+
if (r.ok && r.amount != null && r.token) parts.push(formatAmount(r.amount, r.token));
|
|
120
|
+
else if (r.ok && r.kind === "sell" && r.meta?.pct != null) parts.push(`${r.meta.pct}%`);
|
|
121
|
+
return ` ${timeStr} ${r.ok ? "" : "✗ "}${parts.join(" · ")}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
//#endregion
|
|
24
125
|
//#region src/lib/config.ts
|
|
25
126
|
const APP_NAME = "x402-proxy";
|
|
26
127
|
function getConfigDir() {
|
|
@@ -72,9 +173,7 @@ function loadConfig() {
|
|
|
72
173
|
return JSON.parse(stripped);
|
|
73
174
|
}
|
|
74
175
|
return JSON.parse(raw);
|
|
75
|
-
} catch {
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
176
|
+
} catch {}
|
|
78
177
|
}
|
|
79
178
|
return null;
|
|
80
179
|
}
|
|
@@ -90,6 +189,24 @@ function isConfigured() {
|
|
|
90
189
|
return loadWalletFile() !== null;
|
|
91
190
|
}
|
|
92
191
|
|
|
192
|
+
//#endregion
|
|
193
|
+
//#region src/lib/output.ts
|
|
194
|
+
function isTTY() {
|
|
195
|
+
return !!process.stderr.isTTY;
|
|
196
|
+
}
|
|
197
|
+
function info(msg) {
|
|
198
|
+
process.stderr.write(`${isTTY() ? pc.cyan(msg) : msg}\n`);
|
|
199
|
+
}
|
|
200
|
+
function warn(msg) {
|
|
201
|
+
process.stderr.write(`${isTTY() ? pc.yellow(msg) : msg}\n`);
|
|
202
|
+
}
|
|
203
|
+
function error(msg) {
|
|
204
|
+
process.stderr.write(`${isTTY() ? pc.red(msg) : msg}\n`);
|
|
205
|
+
}
|
|
206
|
+
function dim(msg) {
|
|
207
|
+
process.stderr.write(`${isTTY() ? pc.dim(msg) : msg}\n`);
|
|
208
|
+
}
|
|
209
|
+
|
|
93
210
|
//#endregion
|
|
94
211
|
//#region src/lib/derive.ts
|
|
95
212
|
/**
|
|
@@ -244,126 +361,36 @@ async function buildX402Client(wallet) {
|
|
|
244
361
|
}
|
|
245
362
|
|
|
246
363
|
//#endregion
|
|
247
|
-
//#region src/
|
|
248
|
-
function
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
process.stderr.write(`${isTTY() ? pc.red(msg) : msg}\n`);
|
|
259
|
-
}
|
|
260
|
-
function dim(msg) {
|
|
261
|
-
process.stderr.write(`${isTTY() ? pc.dim(msg) : msg}\n`);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
//#endregion
|
|
265
|
-
//#region src/history.ts
|
|
266
|
-
const HISTORY_MAX_LINES = 1e3;
|
|
267
|
-
const HISTORY_KEEP_LINES = 500;
|
|
268
|
-
function appendHistory(historyPath, record) {
|
|
269
|
-
try {
|
|
270
|
-
appendFileSync(historyPath, `${JSON.stringify(record)}\n`);
|
|
271
|
-
if (existsSync(historyPath)) {
|
|
272
|
-
if (statSync(historyPath).size > HISTORY_MAX_LINES * 200) {
|
|
273
|
-
const lines = readFileSync(historyPath, "utf-8").trimEnd().split("\n");
|
|
274
|
-
if (lines.length > HISTORY_MAX_LINES) writeFileSync(historyPath, `${lines.slice(-HISTORY_KEEP_LINES).join("\n")}\n`);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
} catch {}
|
|
278
|
-
}
|
|
279
|
-
function readHistory(historyPath) {
|
|
280
|
-
try {
|
|
281
|
-
if (!existsSync(historyPath)) return [];
|
|
282
|
-
const content = readFileSync(historyPath, "utf-8").trimEnd();
|
|
283
|
-
if (!content) return [];
|
|
284
|
-
return content.split("\n").flatMap((line) => {
|
|
285
|
-
try {
|
|
286
|
-
const parsed = JSON.parse(line);
|
|
287
|
-
if (typeof parsed.t !== "number" || typeof parsed.kind !== "string") return [];
|
|
288
|
-
return [parsed];
|
|
289
|
-
} catch {
|
|
290
|
-
return [];
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
} catch {
|
|
294
|
-
return [];
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
function calcSpend(records) {
|
|
298
|
-
const todayStart = /* @__PURE__ */ new Date();
|
|
299
|
-
todayStart.setUTCHours(0, 0, 0, 0);
|
|
300
|
-
const todayMs = todayStart.getTime();
|
|
301
|
-
let today = 0;
|
|
302
|
-
let total = 0;
|
|
303
|
-
let count = 0;
|
|
304
|
-
for (const r of records) {
|
|
305
|
-
if (!r.ok || r.amount == null) continue;
|
|
306
|
-
if (r.token !== "USDC") continue;
|
|
307
|
-
total += r.amount;
|
|
308
|
-
count++;
|
|
309
|
-
if (r.t >= todayMs) today += r.amount;
|
|
364
|
+
//#region src/commands/status.ts
|
|
365
|
+
function displayStatus() {
|
|
366
|
+
const wallet = resolveWallet();
|
|
367
|
+
const config = loadConfig();
|
|
368
|
+
console.log();
|
|
369
|
+
info("x402-proxy status");
|
|
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}`);
|
|
310
375
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if (amount >= .01) return `${amount.toFixed(2)} USDC`;
|
|
320
|
-
if (amount >= .001) return `${amount.toFixed(3)} USDC`;
|
|
321
|
-
if (amount >= 1e-4) return `${amount.toFixed(4)} USDC`;
|
|
322
|
-
return `${amount.toFixed(6)} USDC`;
|
|
376
|
+
console.log();
|
|
377
|
+
if (wallet.source === "none") {
|
|
378
|
+
console.log(pc.yellow(" No wallet configured."));
|
|
379
|
+
console.log(pc.dim(` Run ${pc.cyan("x402-proxy setup")} to create one.`));
|
|
380
|
+
} else {
|
|
381
|
+
dim(` Wallet source: ${wallet.source}`);
|
|
382
|
+
if (wallet.evmAddress) console.log(` EVM: ${pc.green(wallet.evmAddress)}`);
|
|
383
|
+
if (wallet.solanaAddress) console.log(` Solana: ${pc.green(wallet.solanaAddress)}`);
|
|
323
384
|
}
|
|
324
|
-
|
|
325
|
-
|
|
385
|
+
console.log();
|
|
386
|
+
const spend = calcSpend(readHistory(getHistoryPath()));
|
|
387
|
+
if (spend.count > 0) {
|
|
388
|
+
dim(` Transactions: ${spend.count}`);
|
|
389
|
+
dim(` Today: ${spend.today.toFixed(4)} USDC`);
|
|
390
|
+
dim(` Total: ${spend.total.toFixed(4)} USDC`);
|
|
391
|
+
} else dim(" No payment history yet.");
|
|
392
|
+
console.log();
|
|
326
393
|
}
|
|
327
|
-
const KIND_LABELS = {
|
|
328
|
-
x402_inference: "inference",
|
|
329
|
-
x402_payment: "payment",
|
|
330
|
-
transfer: "transfer",
|
|
331
|
-
buy: "buy",
|
|
332
|
-
sell: "sell",
|
|
333
|
-
mint: "mint",
|
|
334
|
-
swap: "swap"
|
|
335
|
-
};
|
|
336
|
-
function explorerUrl(net, tx) {
|
|
337
|
-
if (net.startsWith("eip155:")) {
|
|
338
|
-
if (net.split(":")[1] === "8453") return `https://basescan.org/tx/${tx}`;
|
|
339
|
-
return `https://basescan.org/tx/${tx}`;
|
|
340
|
-
}
|
|
341
|
-
return `https://solscan.io/tx/${tx}`;
|
|
342
|
-
}
|
|
343
|
-
/** Strip provider prefix and OpenRouter date suffixes from model IDs.
|
|
344
|
-
* e.g. "minimax/minimax-m2.5-20260211" -> "minimax-m2.5"
|
|
345
|
-
* "moonshotai/kimi-k2.5-0127" -> "kimi-k2.5" */
|
|
346
|
-
function shortModel(model) {
|
|
347
|
-
const parts = model.split("/");
|
|
348
|
-
return parts[parts.length - 1].replace(/-\d{6,8}$/, "").replace(/-\d{4}$/, "");
|
|
349
|
-
}
|
|
350
|
-
function formatTxLine(r) {
|
|
351
|
-
const time = new Date(r.t).toLocaleTimeString("en-US", {
|
|
352
|
-
hour: "2-digit",
|
|
353
|
-
minute: "2-digit",
|
|
354
|
-
hour12: false,
|
|
355
|
-
timeZone: "UTC"
|
|
356
|
-
});
|
|
357
|
-
const timeStr = r.tx ? `[${time}](${explorerUrl(r.net, r.tx)})` : time;
|
|
358
|
-
const parts = [r.kind === "x402_inference" && r.model ? shortModel(r.model) : KIND_LABELS[r.kind] ?? r.kind];
|
|
359
|
-
if (r.label) parts.push(r.label);
|
|
360
|
-
if (r.ok && r.amount != null && r.token) parts.push(formatAmount(r.amount, r.token));
|
|
361
|
-
else if (r.ok && r.kind === "sell" && r.meta?.pct != null) parts.push(`${r.meta.pct}%`);
|
|
362
|
-
return ` ${timeStr} ${r.ok ? "" : "✗ "}${parts.join(" · ")}`;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
//#endregion
|
|
366
|
-
//#region src/commands/status.ts
|
|
367
394
|
const statusCommand = buildCommand({
|
|
368
395
|
docs: { brief: "Show configuration and wallet status" },
|
|
369
396
|
parameters: {
|
|
@@ -374,35 +401,9 @@ const statusCommand = buildCommand({
|
|
|
374
401
|
}
|
|
375
402
|
},
|
|
376
403
|
func() {
|
|
377
|
-
|
|
378
|
-
const config = loadConfig();
|
|
379
|
-
console.log();
|
|
380
|
-
info("x402-proxy status");
|
|
381
|
-
console.log();
|
|
382
|
-
dim(` Config directory: ${getConfigDir()}`);
|
|
383
|
-
if (config) {
|
|
384
|
-
if (config.spendLimit) dim(` Spend limit: ${config.spendLimit} USDC`);
|
|
385
|
-
if (config.defaultNetwork) dim(` Default network: ${config.defaultNetwork}`);
|
|
386
|
-
}
|
|
387
|
-
console.log();
|
|
388
|
-
if (wallet.source === "none") {
|
|
389
|
-
console.log(pc.yellow(" No wallet configured."));
|
|
390
|
-
console.log(pc.dim(` Run ${pc.cyan("x402-proxy setup")} to create one.`));
|
|
391
|
-
} else {
|
|
392
|
-
dim(` Wallet source: ${wallet.source}`);
|
|
393
|
-
if (wallet.evmAddress) console.log(` EVM: ${pc.green(wallet.evmAddress)}`);
|
|
394
|
-
if (wallet.solanaAddress) console.log(` Solana: ${pc.green(wallet.solanaAddress)}`);
|
|
395
|
-
}
|
|
396
|
-
console.log();
|
|
397
|
-
const spend = calcSpend(readHistory(getHistoryPath()));
|
|
398
|
-
if (spend.count > 0) {
|
|
399
|
-
dim(` Transactions: ${spend.count}`);
|
|
400
|
-
dim(` Today: ${spend.today.toFixed(4)} USDC`);
|
|
401
|
-
dim(` Total: ${spend.total.toFixed(4)} USDC`);
|
|
402
|
-
} else dim(" No payment history yet.");
|
|
403
|
-
console.log();
|
|
404
|
+
displayStatus();
|
|
404
405
|
}
|
|
405
406
|
});
|
|
406
407
|
|
|
407
408
|
//#endregion
|
|
408
|
-
export {
|
|
409
|
+
export { readHistory as C, formatTxLine as S, isConfigured as _, deriveEvmKeypair as a, appendHistory as b, dim as c, isTTY as d, warn as f, getWalletPath as g, getHistoryPath as h, resolveWallet as i, error as l, getConfigDir as m, statusCommand as n, deriveSolanaKeypair as o, ensureConfigDir as p, buildX402Client as r, generateMnemonic$1 as s, displayStatus as t, info as u, saveConfig as v, calcSpend as x, saveWalletFile as y };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "x402-proxy",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "curl for x402 paid APIs. Auto-pays any endpoint on Base and Solana.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -54,15 +54,16 @@
|
|
|
54
54
|
],
|
|
55
55
|
"keywords": [
|
|
56
56
|
"x402",
|
|
57
|
-
"
|
|
58
|
-
"proxy",
|
|
57
|
+
"http-402",
|
|
59
58
|
"mcp",
|
|
60
|
-
"
|
|
61
|
-
"
|
|
59
|
+
"mcp-proxy",
|
|
60
|
+
"ai-agent",
|
|
61
|
+
"agentic-commerce",
|
|
62
62
|
"base",
|
|
63
|
+
"solana",
|
|
63
64
|
"usdc",
|
|
64
|
-
"
|
|
65
|
-
"
|
|
65
|
+
"coinbase",
|
|
66
|
+
"cli"
|
|
66
67
|
],
|
|
67
68
|
"license": "Apache-2.0",
|
|
68
69
|
"engines": {
|
package/dist/status-CPBQ0UBZ.js
DELETED