x402-proxy 0.9.1 → 0.9.3
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/CHANGELOG.md +21 -1
- package/dist/bin/cli.js +65 -80
- package/dist/{derive-CAYmX-Yz.js → derive-EDXzwKW2.js} +1 -4
- package/dist/index.js +3 -7
- package/dist/openclaw/plugin.js +102 -22
- package/dist/{setup-B7YJa7s6.js → setup-QtTFsCFs.js} +2 -4
- package/dist/setup-lCsiivm2.js +3 -0
- package/dist/{status-D3f5IVf6.js → status-BZTToWE_.js} +3 -5
- package/dist/status-Bj3U-W2M.js +3 -0
- package/dist/wallet-4C9EL4Iv.js +3 -0
- package/dist/{wallet-DZsXptY7.js → wallet-CqUc-ZFn.js} +104 -16
- package/package.json +20 -16
- package/dist/setup-xhtYsp7D.js +0 -4
- package/dist/status-BkURZYDA.js +0 -4
- package/dist/wallet-BM0ngyqk.js +0 -4
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.9.3] - 2026-03-26
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Solana RPC 429 rate-limit failures on concurrent payments - replaced upstream `ExactSvmScheme` (creates new RPC client per call) with `OptimizedSvmScheme` that shares a single RPC client with `@solana/kit` request coalescing (identical calls in the same microtask merge into one network request)
|
|
14
|
+
- Hardcoded USDC mint metadata (Token Program address, 6 decimals) to skip `fetchMint` RPC call entirely for USDC payments
|
|
15
|
+
- Added RPC failover transport: on 429 from one endpoint, immediately tries the next instead of failing. Two public mainnet RPCs: `api.mainnet.solana.com` (Solana Labs, 100 req/10s) and `public.rpc.solanavibestation.com` (community, 5 RPS)
|
|
16
|
+
- Custom RPC URL (via config or OpenClaw plugin) is tried first, with public RPCs as fallback
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- Added `@solana-program/compute-budget`, `@solana-program/token`, `@solana-program/token-2022`, `@x402/core` as direct dependencies (previously transitive via `@x402/svm`)
|
|
20
|
+
|
|
21
|
+
## [0.9.2] - 2026-03-26
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- Upgraded all dependencies to latest: `@x402/*` 2.6.0 -> 2.8.0, `@solana/kit` 6.3.0 -> 6.5.0, `viem` 2.47.6, `@modelcontextprotocol/sdk` 1.28.0, `vitest` 4.1.1, `tsdown` 0.21.5, `@biomejs/biome` 2.4.9, `turbo` 2.8.20, `openclaw` 2026.3.24
|
|
25
|
+
- `pnpm check` now runs tests alongside build, type-check, and biome
|
|
26
|
+
- Biome config scoped to supported file types only (`*.ts`, `*.json`, `*.jsonc`)
|
|
27
|
+
|
|
10
28
|
## [0.9.1] - 2026-03-26
|
|
11
29
|
|
|
12
30
|
### Added
|
|
@@ -277,7 +295,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
277
295
|
- `appendHistory` / `readHistory` / `calcSpend` - JSONL transaction history
|
|
278
296
|
- Re-exports from `@x402/fetch`, `@x402/svm`, `@x402/evm`
|
|
279
297
|
|
|
280
|
-
[Unreleased]: https://github.com/cascade-protocol/x402-proxy/compare/v0.9.
|
|
298
|
+
[Unreleased]: https://github.com/cascade-protocol/x402-proxy/compare/v0.9.3...HEAD
|
|
299
|
+
[0.9.3]: https://github.com/cascade-protocol/x402-proxy/compare/v0.9.2...v0.9.3
|
|
300
|
+
[0.9.2]: https://github.com/cascade-protocol/x402-proxy/compare/v0.9.1...v0.9.2
|
|
281
301
|
[0.9.1]: https://github.com/cascade-protocol/x402-proxy/compare/v0.9.0...v0.9.1
|
|
282
302
|
[0.9.0]: https://github.com/cascade-protocol/x402-proxy/compare/v0.8.6...v0.9.0
|
|
283
303
|
[0.8.6]: https://github.com/cascade-protocol/x402-proxy/compare/v0.8.5...v0.8.6
|
package/dist/bin/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as getHistoryPath, c as loadConfig, i as getConfigDirShort, l as loadWalletFile, s as isConfigured, u as saveConfig } from "../derive-
|
|
3
|
-
import { _ as error, b as warn, c as resolveWallet, d as displayNetwork, f as formatAmount, g as dim, h as readHistory, l as appendHistory, m as formatUsdcValue, n as fetchAllBalances, o as walletInfoCommand, p as formatTxLine, s as buildX402Client, u as calcSpend, v as info, y as isTTY } from "../wallet-
|
|
4
|
-
import { n as setupCommand, t as runSetup } from "../setup-
|
|
5
|
-
import { n as statusCommand } from "../status-
|
|
2
|
+
import { a as getHistoryPath, c as loadConfig, i as getConfigDirShort, l as loadWalletFile, s as isConfigured, u as saveConfig } from "../derive-EDXzwKW2.js";
|
|
3
|
+
import { _ as error, b as warn, c as resolveWallet, d as displayNetwork, f as formatAmount, g as dim, h as readHistory, l as appendHistory, m as formatUsdcValue, n as fetchAllBalances, o as walletInfoCommand, p as formatTxLine, s as buildX402Client, u as calcSpend, v as info, y as isTTY } from "../wallet-CqUc-ZFn.js";
|
|
4
|
+
import { n as setupCommand, t as runSetup } from "../setup-QtTFsCFs.js";
|
|
5
|
+
import { n as statusCommand } from "../status-BZTToWE_.js";
|
|
6
6
|
import { dirname, join, normalize, resolve } from "node:path";
|
|
7
7
|
import { buildApplication, buildCommand, buildRouteMap, run } from "@stricli/core";
|
|
8
8
|
import pc from "picocolors";
|
|
@@ -11,7 +11,6 @@ import { homedir } from "node:os";
|
|
|
11
11
|
import { decodePaymentResponseHeader, wrapFetchWithPayment } from "@x402/fetch";
|
|
12
12
|
import { base58 } from "@scure/base";
|
|
13
13
|
import * as prompts from "@clack/prompts";
|
|
14
|
-
|
|
15
14
|
//#region src/commands/config.ts
|
|
16
15
|
const VALID_KEYS = {
|
|
17
16
|
defaultNetwork: {
|
|
@@ -161,7 +160,6 @@ const configUnsetCommand = buildCommand({
|
|
|
161
160
|
dim(` ${key} unset`);
|
|
162
161
|
}
|
|
163
162
|
});
|
|
164
|
-
|
|
165
163
|
//#endregion
|
|
166
164
|
//#region src/handler.ts
|
|
167
165
|
/**
|
|
@@ -301,7 +299,6 @@ async function createMppProxyHandler(opts) {
|
|
|
301
299
|
}
|
|
302
300
|
};
|
|
303
301
|
}
|
|
304
|
-
|
|
305
302
|
//#endregion
|
|
306
303
|
//#region src/commands/fetch.ts
|
|
307
304
|
function isStreamingResponse(res) {
|
|
@@ -401,7 +398,7 @@ Examples:
|
|
|
401
398
|
};
|
|
402
399
|
if (!url) {
|
|
403
400
|
if (isConfigured()) {
|
|
404
|
-
const { displayStatus } = await import("../status-
|
|
401
|
+
const { displayStatus } = await import("../status-Bj3U-W2M.js");
|
|
405
402
|
await displayStatus();
|
|
406
403
|
console.log();
|
|
407
404
|
console.log(pc.dim(" Commands:"));
|
|
@@ -453,7 +450,7 @@ Examples:
|
|
|
453
450
|
process.exit(1);
|
|
454
451
|
}
|
|
455
452
|
dim(" No wallet found. Let's set one up first.\n");
|
|
456
|
-
const { runSetup } = await import("../setup-
|
|
453
|
+
const { runSetup } = await import("../setup-lCsiivm2.js");
|
|
457
454
|
await runSetup();
|
|
458
455
|
console.log();
|
|
459
456
|
wallet = resolveWallet();
|
|
@@ -466,7 +463,7 @@ Examples:
|
|
|
466
463
|
verbose(`protocol: ${resolvedProtocol ?? "auto-detect"}, maxDeposit: ${maxDeposit}`);
|
|
467
464
|
let preferredNetwork = config?.defaultNetwork;
|
|
468
465
|
if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
|
|
469
|
-
const { fetchAllBalances } = await import("../wallet-
|
|
466
|
+
const { fetchAllBalances } = await import("../wallet-4C9EL4Iv.js");
|
|
470
467
|
const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
|
|
471
468
|
const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
|
|
472
469
|
const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
|
|
@@ -527,7 +524,7 @@ Examples:
|
|
|
527
524
|
verbose(closeReceipt ? `close receipt: amount=${closeReceipt.amount ?? "none"}, channelId=${closeReceipt.channelId ?? "none"}, txHash=${closeReceipt.receipt?.txHash ?? "none"}` : "no close receipt (session close may have failed)");
|
|
528
525
|
mppPayment = closeReceipt ?? {
|
|
529
526
|
protocol: "mpp",
|
|
530
|
-
network:
|
|
527
|
+
network: "eip155:4217",
|
|
531
528
|
intent: "session"
|
|
532
529
|
};
|
|
533
530
|
usedProtocol = "mpp";
|
|
@@ -633,7 +630,7 @@ Examples:
|
|
|
633
630
|
const hasSolana = accepts.some((a) => a.network.startsWith("solana:"));
|
|
634
631
|
const hasMpp = detected.mpp;
|
|
635
632
|
const hasOther = accepts.some((a) => !a.network.startsWith("eip155:") && !a.network.startsWith("solana:"));
|
|
636
|
-
const { fetchAllBalances } = await import("../wallet-
|
|
633
|
+
const { fetchAllBalances } = await import("../wallet-4C9EL4Iv.js");
|
|
637
634
|
const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
|
|
638
635
|
const evmUsdc = hasEvm && balances.evm ? Number(balances.evm.usdc) : 0;
|
|
639
636
|
const solUsdc = hasSolana && balances.sol ? Number(balances.sol.usdc) : 0;
|
|
@@ -754,7 +751,6 @@ Examples:
|
|
|
754
751
|
}
|
|
755
752
|
}
|
|
756
753
|
});
|
|
757
|
-
|
|
758
754
|
//#endregion
|
|
759
755
|
//#region src/commands/mcp.ts
|
|
760
756
|
const mcpCommand = buildCommand({
|
|
@@ -810,7 +806,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
810
806
|
});
|
|
811
807
|
if (wallet.source === "none") {
|
|
812
808
|
dim("No wallet found. Auto-generating...");
|
|
813
|
-
const { runSetup } = await import("../setup-
|
|
809
|
+
const { runSetup } = await import("../setup-lCsiivm2.js");
|
|
814
810
|
await runSetup({ nonInteractive: true });
|
|
815
811
|
const fresh = resolveWallet({
|
|
816
812
|
evmKey: flags.evmKey,
|
|
@@ -854,7 +850,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
854
850
|
async function startX402Proxy() {
|
|
855
851
|
let preferredNetwork = config?.defaultNetwork;
|
|
856
852
|
if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
|
|
857
|
-
const { fetchAllBalances } = await import("../wallet-
|
|
853
|
+
const { fetchAllBalances } = await import("../wallet-4C9EL4Iv.js");
|
|
858
854
|
const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
|
|
859
855
|
const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
|
|
860
856
|
const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
|
|
@@ -874,7 +870,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
874
870
|
}
|
|
875
871
|
const remoteClient = new Client({
|
|
876
872
|
name: "x402-proxy",
|
|
877
|
-
version: "0.9.
|
|
873
|
+
version: "0.9.3"
|
|
878
874
|
});
|
|
879
875
|
const x402Mcp = new x402MCPClient(remoteClient, x402PaymentClient, {
|
|
880
876
|
autoPayment: true,
|
|
@@ -912,7 +908,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
912
908
|
}
|
|
913
909
|
const localServer = new Server({
|
|
914
910
|
name: "x402-proxy",
|
|
915
|
-
version: "0.9.
|
|
911
|
+
version: "0.9.3"
|
|
916
912
|
}, { capabilities: {
|
|
917
913
|
tools: tools.length > 0 ? {} : void 0,
|
|
918
914
|
resources: remoteResources.length > 0 ? {} : void 0
|
|
@@ -1007,7 +1003,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
1007
1003
|
}));
|
|
1008
1004
|
const remoteClient = new Client({
|
|
1009
1005
|
name: "x402-proxy",
|
|
1010
|
-
version: "0.9.
|
|
1006
|
+
version: "0.9.3"
|
|
1011
1007
|
});
|
|
1012
1008
|
await connectTransport(remoteClient);
|
|
1013
1009
|
const mppClient = McpClient.wrap(remoteClient, { methods: wrappedMethods });
|
|
@@ -1022,7 +1018,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
1022
1018
|
}
|
|
1023
1019
|
const localServer = new Server({
|
|
1024
1020
|
name: "x402-proxy",
|
|
1025
|
-
version: "0.9.
|
|
1021
|
+
version: "0.9.3"
|
|
1026
1022
|
}, { capabilities: {
|
|
1027
1023
|
tools: tools.length > 0 ? {} : void 0,
|
|
1028
1024
|
resources: remoteResources.length > 0 ? {} : void 0
|
|
@@ -1098,7 +1094,6 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
1098
1094
|
}
|
|
1099
1095
|
}
|
|
1100
1096
|
});
|
|
1101
|
-
|
|
1102
1097
|
//#endregion
|
|
1103
1098
|
//#region src/commands/mcp-add.ts
|
|
1104
1099
|
function resolvePlatformPath(raw) {
|
|
@@ -1282,7 +1277,6 @@ const mcpAddCommand = buildCommand({
|
|
|
1282
1277
|
prompts.outro(pc.green(`MCP server ${pc.bold(serverName)} is ready to use!`));
|
|
1283
1278
|
}
|
|
1284
1279
|
});
|
|
1285
|
-
|
|
1286
1280
|
//#endregion
|
|
1287
1281
|
//#region src/commands/wallet-export.ts
|
|
1288
1282
|
const walletExportCommand = buildCommand({
|
|
@@ -1336,62 +1330,57 @@ const walletExportCommand = buildCommand({
|
|
|
1336
1330
|
else process.stdout.write(base58.encode(wallet.solanaKey.slice(0, 32)));
|
|
1337
1331
|
}
|
|
1338
1332
|
});
|
|
1339
|
-
|
|
1340
|
-
//#endregion
|
|
1341
|
-
//#region src/commands/wallet-history.ts
|
|
1342
|
-
const walletHistoryCommand = buildCommand({
|
|
1343
|
-
docs: { brief: "Show payment history" },
|
|
1344
|
-
parameters: {
|
|
1345
|
-
flags: {
|
|
1346
|
-
limit: {
|
|
1347
|
-
kind: "parsed",
|
|
1348
|
-
brief: "Number of entries to show",
|
|
1349
|
-
parse: Number,
|
|
1350
|
-
default: "20"
|
|
1351
|
-
},
|
|
1352
|
-
json: {
|
|
1353
|
-
kind: "boolean",
|
|
1354
|
-
brief: "Output raw JSONL",
|
|
1355
|
-
default: false
|
|
1356
|
-
}
|
|
1357
|
-
},
|
|
1358
|
-
positional: {
|
|
1359
|
-
kind: "tuple",
|
|
1360
|
-
parameters: []
|
|
1361
|
-
}
|
|
1362
|
-
},
|
|
1363
|
-
func(flags) {
|
|
1364
|
-
const records = readHistory(getHistoryPath());
|
|
1365
|
-
if (records.length === 0) {
|
|
1366
|
-
console.log(pc.dim("No payment history yet."));
|
|
1367
|
-
return;
|
|
1368
|
-
}
|
|
1369
|
-
if (flags.json) {
|
|
1370
|
-
const slice = records.slice(-flags.limit);
|
|
1371
|
-
for (const r of slice) process.stdout.write(`${JSON.stringify(r)}\n`);
|
|
1372
|
-
return;
|
|
1373
|
-
}
|
|
1374
|
-
const spend = calcSpend(records);
|
|
1375
|
-
const slice = records.slice(-flags.limit);
|
|
1376
|
-
console.log();
|
|
1377
|
-
info("Payment History");
|
|
1378
|
-
console.log();
|
|
1379
|
-
for (const r of slice) {
|
|
1380
|
-
const line = formatTxLine(r).replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
1381
|
-
console.log(line);
|
|
1382
|
-
}
|
|
1383
|
-
console.log();
|
|
1384
|
-
console.log(pc.dim(` Today: ${formatAmount(spend.today, "USDC")} | Total: ${formatAmount(spend.total, "USDC")} | ${spend.count} transactions`));
|
|
1385
|
-
console.log();
|
|
1386
|
-
}
|
|
1387
|
-
});
|
|
1388
|
-
|
|
1389
1333
|
//#endregion
|
|
1390
1334
|
//#region src/app.ts
|
|
1391
1335
|
const walletRoutes = buildRouteMap({
|
|
1392
1336
|
routes: {
|
|
1393
1337
|
info: walletInfoCommand,
|
|
1394
|
-
history:
|
|
1338
|
+
history: buildCommand({
|
|
1339
|
+
docs: { brief: "Show payment history" },
|
|
1340
|
+
parameters: {
|
|
1341
|
+
flags: {
|
|
1342
|
+
limit: {
|
|
1343
|
+
kind: "parsed",
|
|
1344
|
+
brief: "Number of entries to show",
|
|
1345
|
+
parse: Number,
|
|
1346
|
+
default: "20"
|
|
1347
|
+
},
|
|
1348
|
+
json: {
|
|
1349
|
+
kind: "boolean",
|
|
1350
|
+
brief: "Output raw JSONL",
|
|
1351
|
+
default: false
|
|
1352
|
+
}
|
|
1353
|
+
},
|
|
1354
|
+
positional: {
|
|
1355
|
+
kind: "tuple",
|
|
1356
|
+
parameters: []
|
|
1357
|
+
}
|
|
1358
|
+
},
|
|
1359
|
+
func(flags) {
|
|
1360
|
+
const records = readHistory(getHistoryPath());
|
|
1361
|
+
if (records.length === 0) {
|
|
1362
|
+
console.log(pc.dim("No payment history yet."));
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
if (flags.json) {
|
|
1366
|
+
const slice = records.slice(-flags.limit);
|
|
1367
|
+
for (const r of slice) process.stdout.write(`${JSON.stringify(r)}\n`);
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
const spend = calcSpend(records);
|
|
1371
|
+
const slice = records.slice(-flags.limit);
|
|
1372
|
+
console.log();
|
|
1373
|
+
info("Payment History");
|
|
1374
|
+
console.log();
|
|
1375
|
+
for (const r of slice) {
|
|
1376
|
+
const line = formatTxLine(r).replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
1377
|
+
console.log(line);
|
|
1378
|
+
}
|
|
1379
|
+
console.log();
|
|
1380
|
+
console.log(pc.dim(` Today: ${formatAmount(spend.today, "USDC")} | Total: ${formatAmount(spend.total, "USDC")} | ${spend.count} transactions`));
|
|
1381
|
+
console.log();
|
|
1382
|
+
}
|
|
1383
|
+
}),
|
|
1395
1384
|
"export-key": walletExportCommand
|
|
1396
1385
|
},
|
|
1397
1386
|
defaultCommand: "info",
|
|
@@ -1406,7 +1395,7 @@ const configRoutes = buildRouteMap({
|
|
|
1406
1395
|
defaultCommand: "show",
|
|
1407
1396
|
docs: { brief: "Manage configuration" }
|
|
1408
1397
|
});
|
|
1409
|
-
const
|
|
1398
|
+
const app = buildApplication(buildRouteMap({
|
|
1410
1399
|
routes: {
|
|
1411
1400
|
fetch: fetchCommand,
|
|
1412
1401
|
mcp: buildRouteMap({
|
|
@@ -1424,19 +1413,16 @@ const routes = buildRouteMap({
|
|
|
1424
1413
|
},
|
|
1425
1414
|
defaultCommand: "fetch",
|
|
1426
1415
|
docs: { brief: "curl for x402 paid APIs" }
|
|
1427
|
-
})
|
|
1428
|
-
const app = buildApplication(routes, {
|
|
1416
|
+
}), {
|
|
1429
1417
|
name: "x402-proxy",
|
|
1430
|
-
versionInfo: { currentVersion: "0.9.
|
|
1418
|
+
versionInfo: { currentVersion: "0.9.3" },
|
|
1431
1419
|
scanner: { caseStyle: "allow-kebab-for-camel" }
|
|
1432
1420
|
});
|
|
1433
|
-
|
|
1434
1421
|
//#endregion
|
|
1435
1422
|
//#region src/context.ts
|
|
1436
1423
|
function buildContext(process) {
|
|
1437
1424
|
return { process };
|
|
1438
1425
|
}
|
|
1439
|
-
|
|
1440
1426
|
//#endregion
|
|
1441
1427
|
//#region src/bin/cli.ts
|
|
1442
1428
|
process.on("SIGINT", () => process.exit(130));
|
|
@@ -1456,6 +1442,5 @@ for (let i = 0; i < rawArgs.length; i++) {
|
|
|
1456
1442
|
} else args.push(a);
|
|
1457
1443
|
}
|
|
1458
1444
|
await run(app, args, buildContext(process));
|
|
1459
|
-
|
|
1460
1445
|
//#endregion
|
|
1461
|
-
export {
|
|
1446
|
+
export {};
|
|
@@ -12,7 +12,6 @@ import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
|
12
12
|
import { HDKey } from "@scure/bip32";
|
|
13
13
|
import { generateMnemonic, mnemonicToSeedSync } from "@scure/bip39";
|
|
14
14
|
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
15
|
-
|
|
16
15
|
//#region src/lib/config.ts
|
|
17
16
|
const APP_NAME = "x402-proxy";
|
|
18
17
|
function getConfigDir() {
|
|
@@ -85,7 +84,6 @@ function isConfigured() {
|
|
|
85
84
|
if (process.env.X402_PROXY_WALLET_SOLANA_KEY) return true;
|
|
86
85
|
return loadWalletFile() !== null;
|
|
87
86
|
}
|
|
88
|
-
|
|
89
87
|
//#endregion
|
|
90
88
|
//#region src/lib/derive.ts
|
|
91
89
|
/**
|
|
@@ -156,6 +154,5 @@ function checksumAddress(addr) {
|
|
|
156
154
|
for (let i = 0; i < 40; i++) out += Number.parseInt(hash[i], 16) >= 8 ? addr[i].toUpperCase() : addr[i];
|
|
157
155
|
return out;
|
|
158
156
|
}
|
|
159
|
-
|
|
160
157
|
//#endregion
|
|
161
|
-
export { getHistoryPath as a, loadConfig as c, saveWalletFile as d, getConfigDirShort as i, loadWalletFile as l, deriveSolanaKeypair as n, getWalletPath as o, generateMnemonic$1 as r, isConfigured as s, deriveEvmKeypair as t, saveConfig as u };
|
|
158
|
+
export { getHistoryPath as a, loadConfig as c, saveWalletFile as d, getConfigDirShort as i, loadWalletFile as l, deriveSolanaKeypair as n, getWalletPath as o, generateMnemonic$1 as r, isConfigured as s, deriveEvmKeypair as t, saveConfig as u };
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,6 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFil
|
|
|
5
5
|
import { dirname } from "node:path";
|
|
6
6
|
import { createKeyPairSignerFromBytes } from "@solana/kit";
|
|
7
7
|
import { privateKeyToAccount } from "viem/accounts";
|
|
8
|
-
|
|
9
8
|
//#region src/handler.ts
|
|
10
9
|
/**
|
|
11
10
|
* Detect which payment protocols a 402 response advertises.
|
|
@@ -144,7 +143,6 @@ async function createMppProxyHandler(opts) {
|
|
|
144
143
|
}
|
|
145
144
|
};
|
|
146
145
|
}
|
|
147
|
-
|
|
148
146
|
//#endregion
|
|
149
147
|
//#region src/history.ts
|
|
150
148
|
const HISTORY_MAX_LINES = 1e3;
|
|
@@ -154,9 +152,9 @@ function appendHistory(historyPath, record) {
|
|
|
154
152
|
mkdirSync(dirname(historyPath), { recursive: true });
|
|
155
153
|
appendFileSync(historyPath, `${JSON.stringify(record)}\n`);
|
|
156
154
|
if (existsSync(historyPath)) {
|
|
157
|
-
if (statSync(historyPath).size >
|
|
155
|
+
if (statSync(historyPath).size > 1e3 * 200) {
|
|
158
156
|
const lines = readFileSync(historyPath, "utf-8").trimEnd().split("\n");
|
|
159
|
-
if (lines.length >
|
|
157
|
+
if (lines.length > 1e3) writeFileSync(historyPath, `${lines.slice(-500).join("\n")}\n`);
|
|
160
158
|
}
|
|
161
159
|
}
|
|
162
160
|
} catch {}
|
|
@@ -263,7 +261,6 @@ function formatTxLine(r, opts) {
|
|
|
263
261
|
}
|
|
264
262
|
return ` ${timeStr} ${r.ok ? "" : "✗ "}${parts.join(" · ")}`;
|
|
265
263
|
}
|
|
266
|
-
|
|
267
264
|
//#endregion
|
|
268
265
|
//#region src/wallet.ts
|
|
269
266
|
/**
|
|
@@ -283,6 +280,5 @@ function loadEvmWallet(keyPath) {
|
|
|
283
280
|
if (!hex.startsWith("0x")) hex = `0x${hex}`;
|
|
284
281
|
return toClientEvmSigner$1(privateKeyToAccount(hex));
|
|
285
282
|
}
|
|
286
|
-
|
|
287
283
|
//#endregion
|
|
288
|
-
export { ExactEvmScheme, ExactSvmScheme, HISTORY_KEEP_LINES, HISTORY_MAX_LINES, TEMPO_NETWORK, appendHistory, calcSpend, createMppProxyHandler, createX402ProxyHandler, detectProtocols, explorerUrl, extractTxSignature, formatTxLine, loadEvmWallet, loadSvmWallet, readHistory, toClientEvmSigner, x402Client };
|
|
284
|
+
export { ExactEvmScheme, ExactSvmScheme, HISTORY_KEEP_LINES, HISTORY_MAX_LINES, TEMPO_NETWORK, appendHistory, calcSpend, createMppProxyHandler, createX402ProxyHandler, detectProtocols, explorerUrl, extractTxSignature, formatTxLine, loadEvmWallet, loadSvmWallet, readHistory, toClientEvmSigner, x402Client };
|
package/dist/openclaw/plugin.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import os, { homedir } from "node:os";
|
|
2
2
|
import path, { dirname, join } from "node:path";
|
|
3
|
-
import { address, appendTransactionMessageInstructions, createKeyPairSignerFromBytes, createSolanaRpc, createTransactionMessage, getAddressEncoder, getBase64EncodedWireTransaction, getProgramDerivedAddress, partiallySignTransactionMessageWithSigners, pipe, setTransactionMessageFeePayer, setTransactionMessageLifetimeUsingBlockhash } from "@solana/kit";
|
|
3
|
+
import { SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR, address, appendTransactionMessageInstructions, createDefaultRpcTransport, createKeyPairSignerFromBytes, createSolanaRpc, createSolanaRpcFromTransport, createTransactionMessage, getAddressEncoder, getBase64EncodedWireTransaction, getProgramDerivedAddress, isSolanaError, mainnet, partiallySignTransactionMessageWithSigners, pipe, prependTransactionMessageInstruction, setTransactionMessageFeePayer, setTransactionMessageLifetimeUsingBlockhash } from "@solana/kit";
|
|
4
4
|
import { decodePaymentResponseHeader, wrapFetchWithPayment, x402Client } from "@x402/fetch";
|
|
5
|
-
import { ExactSvmScheme } from "@x402/svm/exact/client";
|
|
6
5
|
import fs, { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
7
6
|
import "yaml";
|
|
7
|
+
import { getSetComputeUnitLimitInstruction, setTransactionMessageComputeUnitPrice } from "@solana-program/compute-budget";
|
|
8
|
+
import { TOKEN_PROGRAM_ADDRESS } from "@solana-program/token";
|
|
9
|
+
import { TOKEN_2022_PROGRAM_ADDRESS, fetchMint, findAssociatedTokenPda, getTransferCheckedInstruction } from "@solana-program/token-2022";
|
|
8
10
|
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
9
11
|
import { base58 } from "@scure/base";
|
|
10
12
|
import "@x402/evm";
|
|
@@ -20,7 +22,6 @@ import { HDKey } from "@scure/bip32";
|
|
|
20
22
|
import { mnemonicToSeedSync } from "@scure/bip39";
|
|
21
23
|
import "@scure/bip39/wordlists/english.js";
|
|
22
24
|
import { Type } from "@sinclair/typebox";
|
|
23
|
-
|
|
24
25
|
//#region src/handler.ts
|
|
25
26
|
/**
|
|
26
27
|
* Extract the on-chain transaction signature from an x402 payment response header.
|
|
@@ -58,7 +59,6 @@ function createX402ProxyHandler(opts) {
|
|
|
58
59
|
shiftPayment: () => paymentQueue.shift()
|
|
59
60
|
};
|
|
60
61
|
}
|
|
61
|
-
|
|
62
62
|
//#endregion
|
|
63
63
|
//#region src/lib/config.ts
|
|
64
64
|
const APP_NAME = "x402-proxy";
|
|
@@ -83,19 +83,108 @@ function loadWalletFile() {
|
|
|
83
83
|
return null;
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
-
|
|
87
86
|
//#endregion
|
|
88
|
-
//#region src/
|
|
89
|
-
const
|
|
90
|
-
const
|
|
87
|
+
//#region src/lib/optimized-svm-scheme.ts
|
|
88
|
+
const MEMO_PROGRAM_ADDRESS = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr";
|
|
89
|
+
const COMPUTE_UNIT_LIMIT = 2e4;
|
|
90
|
+
const COMPUTE_UNIT_PRICE_MICROLAMPORTS = 1;
|
|
91
|
+
const USDC_MINT$1 = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
92
|
+
const USDC_DECIMALS$1 = 6;
|
|
93
|
+
const MAINNET_RPC_URLS = ["https://api.mainnet.solana.com", "https://public.rpc.solanavibestation.com"];
|
|
94
|
+
/**
|
|
95
|
+
* Create a failover transport that tries each RPC in order.
|
|
96
|
+
* On 429 from one endpoint, immediately tries the next instead of waiting.
|
|
97
|
+
* Each transport gets its own coalescing via createDefaultRpcTransport.
|
|
98
|
+
*/
|
|
99
|
+
function createFailoverTransport(urls) {
|
|
100
|
+
const transports = urls.map((url) => createDefaultRpcTransport({ url }));
|
|
101
|
+
const failover = (async (config) => {
|
|
102
|
+
let lastError;
|
|
103
|
+
for (const transport of transports) try {
|
|
104
|
+
return await transport(config);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
lastError = e;
|
|
107
|
+
if (isSolanaError(e, SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR) && e.context.statusCode === 429) continue;
|
|
108
|
+
throw e;
|
|
109
|
+
}
|
|
110
|
+
throw lastError;
|
|
111
|
+
});
|
|
112
|
+
return failover;
|
|
113
|
+
}
|
|
114
|
+
function createRpcClient(customRpcUrl) {
|
|
115
|
+
return createSolanaRpcFromTransport(createFailoverTransport((customRpcUrl ? [customRpcUrl, ...MAINNET_RPC_URLS] : MAINNET_RPC_URLS).map((u) => mainnet(u))));
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Optimized ExactSvmScheme that replaces upstream @x402/svm to prevent
|
|
119
|
+
* RPC rate-limit failures on parallel payments.
|
|
120
|
+
*
|
|
121
|
+
* Two optimizations over upstream:
|
|
122
|
+
* 1. Shared RPC client - @solana/kit's built-in request coalescing
|
|
123
|
+
* merges identical getLatestBlockhash calls in the same tick into 1.
|
|
124
|
+
* 2. Hardcoded USDC - skips fetchMint RPC call for USDC (immutable data).
|
|
125
|
+
*/
|
|
126
|
+
var OptimizedSvmScheme = class {
|
|
127
|
+
scheme = "exact";
|
|
128
|
+
rpc;
|
|
129
|
+
constructor(signer, config) {
|
|
130
|
+
this.signer = signer;
|
|
131
|
+
this.rpc = createRpcClient(config?.rpcUrl);
|
|
132
|
+
}
|
|
133
|
+
async createPaymentPayload(x402Version, paymentRequirements) {
|
|
134
|
+
const rpc = this.rpc;
|
|
135
|
+
const asset = paymentRequirements.asset;
|
|
136
|
+
let tokenProgramAddress;
|
|
137
|
+
let decimals;
|
|
138
|
+
if (asset === USDC_MINT$1) {
|
|
139
|
+
tokenProgramAddress = TOKEN_PROGRAM_ADDRESS;
|
|
140
|
+
decimals = USDC_DECIMALS$1;
|
|
141
|
+
} else {
|
|
142
|
+
const tokenMint = await fetchMint(rpc, asset);
|
|
143
|
+
tokenProgramAddress = tokenMint.programAddress;
|
|
144
|
+
if (tokenProgramAddress !== TOKEN_PROGRAM_ADDRESS && tokenProgramAddress !== TOKEN_2022_PROGRAM_ADDRESS) throw new Error("Asset was not created by a known token program");
|
|
145
|
+
decimals = tokenMint.data.decimals;
|
|
146
|
+
}
|
|
147
|
+
const [sourceATA] = await findAssociatedTokenPda({
|
|
148
|
+
mint: asset,
|
|
149
|
+
owner: this.signer.address,
|
|
150
|
+
tokenProgram: tokenProgramAddress
|
|
151
|
+
});
|
|
152
|
+
const [destinationATA] = await findAssociatedTokenPda({
|
|
153
|
+
mint: asset,
|
|
154
|
+
owner: paymentRequirements.payTo,
|
|
155
|
+
tokenProgram: tokenProgramAddress
|
|
156
|
+
});
|
|
157
|
+
const transferIx = getTransferCheckedInstruction({
|
|
158
|
+
source: sourceATA,
|
|
159
|
+
mint: asset,
|
|
160
|
+
destination: destinationATA,
|
|
161
|
+
authority: this.signer,
|
|
162
|
+
amount: BigInt(paymentRequirements.amount),
|
|
163
|
+
decimals
|
|
164
|
+
}, { programAddress: tokenProgramAddress });
|
|
165
|
+
const feePayer = paymentRequirements.extra?.feePayer;
|
|
166
|
+
if (!feePayer) throw new Error("feePayer is required in paymentRequirements.extra for SVM transactions");
|
|
167
|
+
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
|
|
168
|
+
const nonce = crypto.getRandomValues(new Uint8Array(16));
|
|
169
|
+
const memoIx = {
|
|
170
|
+
programAddress: MEMO_PROGRAM_ADDRESS,
|
|
171
|
+
accounts: [],
|
|
172
|
+
data: new TextEncoder().encode(Array.from(nonce).map((b) => b.toString(16).padStart(2, "0")).join(""))
|
|
173
|
+
};
|
|
174
|
+
return {
|
|
175
|
+
x402Version,
|
|
176
|
+
payload: { transaction: getBase64EncodedWireTransaction(await partiallySignTransactionMessageWithSigners(pipe(createTransactionMessage({ version: 0 }), (tx) => setTransactionMessageComputeUnitPrice(COMPUTE_UNIT_PRICE_MICROLAMPORTS, tx), (tx) => setTransactionMessageFeePayer(feePayer, tx), (tx) => prependTransactionMessageInstruction(getSetComputeUnitLimitInstruction({ units: COMPUTE_UNIT_LIMIT }), tx), (tx) => appendTransactionMessageInstructions([transferIx, memoIx], tx), (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx)))) }
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
};
|
|
91
180
|
function appendHistory(historyPath, record) {
|
|
92
181
|
try {
|
|
93
182
|
mkdirSync(dirname(historyPath), { recursive: true });
|
|
94
183
|
appendFileSync(historyPath, `${JSON.stringify(record)}\n`);
|
|
95
184
|
if (existsSync(historyPath)) {
|
|
96
|
-
if (statSync(historyPath).size >
|
|
185
|
+
if (statSync(historyPath).size > 1e3 * 200) {
|
|
97
186
|
const lines = readFileSync(historyPath, "utf-8").trimEnd().split("\n");
|
|
98
|
-
if (lines.length >
|
|
187
|
+
if (lines.length > 1e3) writeFileSync(historyPath, `${lines.slice(-500).join("\n")}\n`);
|
|
99
188
|
}
|
|
100
189
|
}
|
|
101
190
|
} catch {}
|
|
@@ -202,7 +291,6 @@ function formatTxLine(r, opts) {
|
|
|
202
291
|
}
|
|
203
292
|
return ` ${timeStr} ${r.ok ? "" : "✗ "}${parts.join(" · ")}`;
|
|
204
293
|
}
|
|
205
|
-
|
|
206
294
|
//#endregion
|
|
207
295
|
//#region src/lib/derive.ts
|
|
208
296
|
/**
|
|
@@ -270,7 +358,6 @@ function checksumAddress(addr) {
|
|
|
270
358
|
for (let i = 0; i < 40; i++) out += Number.parseInt(hash[i], 16) >= 8 ? addr[i].toUpperCase() : addr[i];
|
|
271
359
|
return out;
|
|
272
360
|
}
|
|
273
|
-
|
|
274
361
|
//#endregion
|
|
275
362
|
//#region src/lib/resolve-wallet.ts
|
|
276
363
|
/**
|
|
@@ -341,7 +428,6 @@ function solanaAddressFromKey(keyBytes) {
|
|
|
341
428
|
if (keyBytes.length >= 64) return base58.encode(keyBytes.slice(32));
|
|
342
429
|
return base58.encode(ed25519.getPublicKey(keyBytes));
|
|
343
430
|
}
|
|
344
|
-
|
|
345
431
|
//#endregion
|
|
346
432
|
//#region src/wallet.ts
|
|
347
433
|
/**
|
|
@@ -352,7 +438,6 @@ async function loadSvmWallet(keypairPath) {
|
|
|
352
438
|
const data = JSON.parse(readFileSync(keypairPath, "utf-8"));
|
|
353
439
|
return createKeyPairSignerFromBytes(new Uint8Array(data));
|
|
354
440
|
}
|
|
355
|
-
|
|
356
441
|
//#endregion
|
|
357
442
|
//#region src/openclaw/solana.ts
|
|
358
443
|
const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
@@ -444,7 +529,6 @@ async function transferUsdc(signer, rpcUrl, dest, amountRaw) {
|
|
|
444
529
|
const encoded = getBase64EncodedWireTransaction(await partiallySignTransactionMessageWithSigners(pipe(createTransactionMessage({ version: 0 }), (m) => setTransactionMessageFeePayer(signer.address, m), (m) => appendTransactionMessageInstructions([transferIx], m), (m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m))));
|
|
445
530
|
return await rpc.sendTransaction(encoded, { encoding: "base64" }).send();
|
|
446
531
|
}
|
|
447
|
-
|
|
448
532
|
//#endregion
|
|
449
533
|
//#region src/openclaw/tools.ts
|
|
450
534
|
const SOL_MAINNET = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
@@ -606,7 +690,6 @@ function createPaymentTool(ctx) {
|
|
|
606
690
|
}
|
|
607
691
|
};
|
|
608
692
|
}
|
|
609
|
-
|
|
610
693
|
//#endregion
|
|
611
694
|
//#region src/openclaw/commands.ts
|
|
612
695
|
const HISTORY_PAGE_SIZE = 5;
|
|
@@ -726,7 +809,7 @@ function createWalletCommand(ctx) {
|
|
|
726
809
|
try {
|
|
727
810
|
const snap = await getWalletSnapshot(ctx.rpcUrl, walletAddress, ctx.historyPath);
|
|
728
811
|
const solscanUrl = `https://solscan.io/account/${walletAddress}`;
|
|
729
|
-
const lines = [`x402-proxy v0.9.
|
|
812
|
+
const lines = [`x402-proxy v0.9.3`];
|
|
730
813
|
const defaultModel = ctx.allModels[0];
|
|
731
814
|
if (defaultModel) lines.push("", `**Model** - ${defaultModel.name} (${defaultModel.provider})`);
|
|
732
815
|
lines.push("", `**[Wallet](${solscanUrl})**`, `\`${walletAddress}\``);
|
|
@@ -759,7 +842,6 @@ function createWalletCommand(ctx) {
|
|
|
759
842
|
}
|
|
760
843
|
};
|
|
761
844
|
}
|
|
762
|
-
|
|
763
845
|
//#endregion
|
|
764
846
|
//#region src/openclaw/route.ts
|
|
765
847
|
function createX402RouteHandler(opts) {
|
|
@@ -992,7 +1074,6 @@ function createX402RouteHandler(opts) {
|
|
|
992
1074
|
}
|
|
993
1075
|
};
|
|
994
1076
|
}
|
|
995
|
-
|
|
996
1077
|
//#endregion
|
|
997
1078
|
//#region src/openclaw/plugin.ts
|
|
998
1079
|
function parseProviders(config) {
|
|
@@ -1068,7 +1149,7 @@ function register(api) {
|
|
|
1068
1149
|
return;
|
|
1069
1150
|
}
|
|
1070
1151
|
const client = new x402Client();
|
|
1071
|
-
client.register(SOL_MAINNET, new
|
|
1152
|
+
client.register(SOL_MAINNET, new OptimizedSvmScheme(signer, { rpcUrl }));
|
|
1072
1153
|
proxyRef = createX402ProxyHandler({ client });
|
|
1073
1154
|
const upstreamOrigin = upstreamOrigins[0];
|
|
1074
1155
|
if (upstreamOrigin) {
|
|
@@ -1120,6 +1201,5 @@ function register(api) {
|
|
|
1120
1201
|
};
|
|
1121
1202
|
api.registerCommand(createWalletCommand(cmdCtx));
|
|
1122
1203
|
}
|
|
1123
|
-
|
|
1124
1204
|
//#endregion
|
|
1125
|
-
export { register };
|
|
1205
|
+
export { register };
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { d as saveWalletFile, i as getConfigDirShort, l as loadWalletFile, n as deriveSolanaKeypair, o as getWalletPath, r as generateMnemonic, s as isConfigured, t as deriveEvmKeypair, u as saveConfig } from "./derive-
|
|
2
|
+
import { d as saveWalletFile, i as getConfigDirShort, l as loadWalletFile, n as deriveSolanaKeypair, o as getWalletPath, r as generateMnemonic, s as isConfigured, t as deriveEvmKeypair, u as saveConfig } from "./derive-EDXzwKW2.js";
|
|
3
3
|
import { buildCommand } from "@stricli/core";
|
|
4
4
|
import pc from "picocolors";
|
|
5
5
|
import * as prompts from "@clack/prompts";
|
|
6
|
-
|
|
7
6
|
//#region src/commands/setup.ts
|
|
8
7
|
async function runSetup(opts) {
|
|
9
8
|
const nonInteractive = opts?.nonInteractive ?? false;
|
|
@@ -193,6 +192,5 @@ If a wallet already exists, --non-interactive outputs the existing addresses.`
|
|
|
193
192
|
});
|
|
194
193
|
}
|
|
195
194
|
});
|
|
196
|
-
|
|
197
195
|
//#endregion
|
|
198
|
-
export { setupCommand as n, runSetup as t };
|
|
196
|
+
export { setupCommand as n, runSetup as t };
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as getHistoryPath, c as loadConfig, i as getConfigDirShort } from "./derive-
|
|
3
|
-
import { c as resolveWallet, f as formatAmount, g as dim, h as readHistory, m as formatUsdcValue, n as fetchAllBalances, p as formatTxLine, t as balanceLine, u as calcSpend } from "./wallet-
|
|
2
|
+
import { a as getHistoryPath, c as loadConfig, i as getConfigDirShort } from "./derive-EDXzwKW2.js";
|
|
3
|
+
import { c as resolveWallet, f as formatAmount, g as dim, h as readHistory, m as formatUsdcValue, n as fetchAllBalances, p as formatTxLine, t as balanceLine, u as calcSpend } from "./wallet-CqUc-ZFn.js";
|
|
4
4
|
import { buildCommand } from "@stricli/core";
|
|
5
5
|
import pc from "picocolors";
|
|
6
|
-
|
|
7
6
|
//#region src/commands/status.ts
|
|
8
7
|
async function displayStatus() {
|
|
9
8
|
const wallet = resolveWallet();
|
|
@@ -73,6 +72,5 @@ const statusCommand = buildCommand({
|
|
|
73
72
|
console.log();
|
|
74
73
|
}
|
|
75
74
|
});
|
|
76
|
-
|
|
77
75
|
//#endregion
|
|
78
|
-
export { statusCommand as n, displayStatus as t };
|
|
76
|
+
export { statusCommand as n, displayStatus as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as getHistoryPath, l as loadWalletFile, n as deriveSolanaKeypair, t as deriveEvmKeypair } from "./derive-
|
|
2
|
+
import { a as getHistoryPath, l as loadWalletFile, n as deriveSolanaKeypair, t as deriveEvmKeypair } from "./derive-EDXzwKW2.js";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
4
|
import { buildCommand } from "@stricli/core";
|
|
5
5
|
import pc from "picocolors";
|
|
@@ -9,12 +9,13 @@ import { ed25519 } from "@noble/curves/ed25519.js";
|
|
|
9
9
|
import { base58 } from "@scure/base";
|
|
10
10
|
import { toClientEvmSigner } from "@x402/evm";
|
|
11
11
|
import { registerExactEvmScheme } from "@x402/evm/exact/client";
|
|
12
|
-
import { registerExactSvmScheme } from "@x402/svm/exact/client";
|
|
13
12
|
import { createPublicClient, http } from "viem";
|
|
14
13
|
import { privateKeyToAccount } from "viem/accounts";
|
|
15
14
|
import { base } from "viem/chains";
|
|
16
|
-
import { address, getAddressEncoder, getProgramDerivedAddress } from "@solana/kit";
|
|
17
|
-
|
|
15
|
+
import { SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR, address, appendTransactionMessageInstructions, createDefaultRpcTransport, createSolanaRpcFromTransport, createTransactionMessage, getAddressEncoder, getBase64EncodedWireTransaction, getProgramDerivedAddress, isSolanaError, mainnet, partiallySignTransactionMessageWithSigners, pipe, prependTransactionMessageInstruction, setTransactionMessageFeePayer, setTransactionMessageLifetimeUsingBlockhash } from "@solana/kit";
|
|
16
|
+
import { getSetComputeUnitLimitInstruction, setTransactionMessageComputeUnitPrice } from "@solana-program/compute-budget";
|
|
17
|
+
import { TOKEN_PROGRAM_ADDRESS } from "@solana-program/token";
|
|
18
|
+
import { TOKEN_2022_PROGRAM_ADDRESS, fetchMint, findAssociatedTokenPda, getTransferCheckedInstruction } from "@solana-program/token-2022";
|
|
18
19
|
//#region src/lib/output.ts
|
|
19
20
|
function isTTY() {
|
|
20
21
|
return !!process.stderr.isTTY;
|
|
@@ -31,19 +32,14 @@ function error(msg) {
|
|
|
31
32
|
function dim(msg) {
|
|
32
33
|
process.stderr.write(`${isTTY() ? pc.dim(msg) : msg}\n`);
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
-
//#endregion
|
|
36
|
-
//#region src/history.ts
|
|
37
|
-
const HISTORY_MAX_LINES = 1e3;
|
|
38
|
-
const HISTORY_KEEP_LINES = 500;
|
|
39
35
|
function appendHistory(historyPath, record) {
|
|
40
36
|
try {
|
|
41
37
|
mkdirSync(dirname(historyPath), { recursive: true });
|
|
42
38
|
appendFileSync(historyPath, `${JSON.stringify(record)}\n`);
|
|
43
39
|
if (existsSync(historyPath)) {
|
|
44
|
-
if (statSync(historyPath).size >
|
|
40
|
+
if (statSync(historyPath).size > 1e3 * 200) {
|
|
45
41
|
const lines = readFileSync(historyPath, "utf-8").trimEnd().split("\n");
|
|
46
|
-
if (lines.length >
|
|
42
|
+
if (lines.length > 1e3) writeFileSync(historyPath, `${lines.slice(-500).join("\n")}\n`);
|
|
47
43
|
}
|
|
48
44
|
}
|
|
49
45
|
} catch {}
|
|
@@ -157,7 +153,100 @@ function formatTxLine(r, opts) {
|
|
|
157
153
|
}
|
|
158
154
|
return ` ${timeStr} ${r.ok ? "" : "✗ "}${parts.join(" · ")}`;
|
|
159
155
|
}
|
|
160
|
-
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region src/lib/optimized-svm-scheme.ts
|
|
158
|
+
const MEMO_PROGRAM_ADDRESS = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr";
|
|
159
|
+
const COMPUTE_UNIT_LIMIT = 2e4;
|
|
160
|
+
const COMPUTE_UNIT_PRICE_MICROLAMPORTS = 1;
|
|
161
|
+
const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
162
|
+
const USDC_DECIMALS = 6;
|
|
163
|
+
const MAINNET_RPC_URLS = ["https://api.mainnet.solana.com", "https://public.rpc.solanavibestation.com"];
|
|
164
|
+
/**
|
|
165
|
+
* Create a failover transport that tries each RPC in order.
|
|
166
|
+
* On 429 from one endpoint, immediately tries the next instead of waiting.
|
|
167
|
+
* Each transport gets its own coalescing via createDefaultRpcTransport.
|
|
168
|
+
*/
|
|
169
|
+
function createFailoverTransport(urls) {
|
|
170
|
+
const transports = urls.map((url) => createDefaultRpcTransport({ url }));
|
|
171
|
+
const failover = (async (config) => {
|
|
172
|
+
let lastError;
|
|
173
|
+
for (const transport of transports) try {
|
|
174
|
+
return await transport(config);
|
|
175
|
+
} catch (e) {
|
|
176
|
+
lastError = e;
|
|
177
|
+
if (isSolanaError(e, SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR) && e.context.statusCode === 429) continue;
|
|
178
|
+
throw e;
|
|
179
|
+
}
|
|
180
|
+
throw lastError;
|
|
181
|
+
});
|
|
182
|
+
return failover;
|
|
183
|
+
}
|
|
184
|
+
function createRpcClient(customRpcUrl) {
|
|
185
|
+
return createSolanaRpcFromTransport(createFailoverTransport((customRpcUrl ? [customRpcUrl, ...MAINNET_RPC_URLS] : MAINNET_RPC_URLS).map((u) => mainnet(u))));
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Optimized ExactSvmScheme that replaces upstream @x402/svm to prevent
|
|
189
|
+
* RPC rate-limit failures on parallel payments.
|
|
190
|
+
*
|
|
191
|
+
* Two optimizations over upstream:
|
|
192
|
+
* 1. Shared RPC client - @solana/kit's built-in request coalescing
|
|
193
|
+
* merges identical getLatestBlockhash calls in the same tick into 1.
|
|
194
|
+
* 2. Hardcoded USDC - skips fetchMint RPC call for USDC (immutable data).
|
|
195
|
+
*/
|
|
196
|
+
var OptimizedSvmScheme = class {
|
|
197
|
+
scheme = "exact";
|
|
198
|
+
rpc;
|
|
199
|
+
constructor(signer, config) {
|
|
200
|
+
this.signer = signer;
|
|
201
|
+
this.rpc = createRpcClient(config?.rpcUrl);
|
|
202
|
+
}
|
|
203
|
+
async createPaymentPayload(x402Version, paymentRequirements) {
|
|
204
|
+
const rpc = this.rpc;
|
|
205
|
+
const asset = paymentRequirements.asset;
|
|
206
|
+
let tokenProgramAddress;
|
|
207
|
+
let decimals;
|
|
208
|
+
if (asset === USDC_MINT) {
|
|
209
|
+
tokenProgramAddress = TOKEN_PROGRAM_ADDRESS;
|
|
210
|
+
decimals = USDC_DECIMALS;
|
|
211
|
+
} else {
|
|
212
|
+
const tokenMint = await fetchMint(rpc, asset);
|
|
213
|
+
tokenProgramAddress = tokenMint.programAddress;
|
|
214
|
+
if (tokenProgramAddress !== TOKEN_PROGRAM_ADDRESS && tokenProgramAddress !== TOKEN_2022_PROGRAM_ADDRESS) throw new Error("Asset was not created by a known token program");
|
|
215
|
+
decimals = tokenMint.data.decimals;
|
|
216
|
+
}
|
|
217
|
+
const [sourceATA] = await findAssociatedTokenPda({
|
|
218
|
+
mint: asset,
|
|
219
|
+
owner: this.signer.address,
|
|
220
|
+
tokenProgram: tokenProgramAddress
|
|
221
|
+
});
|
|
222
|
+
const [destinationATA] = await findAssociatedTokenPda({
|
|
223
|
+
mint: asset,
|
|
224
|
+
owner: paymentRequirements.payTo,
|
|
225
|
+
tokenProgram: tokenProgramAddress
|
|
226
|
+
});
|
|
227
|
+
const transferIx = getTransferCheckedInstruction({
|
|
228
|
+
source: sourceATA,
|
|
229
|
+
mint: asset,
|
|
230
|
+
destination: destinationATA,
|
|
231
|
+
authority: this.signer,
|
|
232
|
+
amount: BigInt(paymentRequirements.amount),
|
|
233
|
+
decimals
|
|
234
|
+
}, { programAddress: tokenProgramAddress });
|
|
235
|
+
const feePayer = paymentRequirements.extra?.feePayer;
|
|
236
|
+
if (!feePayer) throw new Error("feePayer is required in paymentRequirements.extra for SVM transactions");
|
|
237
|
+
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
|
|
238
|
+
const nonce = crypto.getRandomValues(new Uint8Array(16));
|
|
239
|
+
const memoIx = {
|
|
240
|
+
programAddress: MEMO_PROGRAM_ADDRESS,
|
|
241
|
+
accounts: [],
|
|
242
|
+
data: new TextEncoder().encode(Array.from(nonce).map((b) => b.toString(16).padStart(2, "0")).join(""))
|
|
243
|
+
};
|
|
244
|
+
return {
|
|
245
|
+
x402Version,
|
|
246
|
+
payload: { transaction: getBase64EncodedWireTransaction(await partiallySignTransactionMessageWithSigners(pipe(createTransactionMessage({ version: 0 }), (tx) => setTransactionMessageComputeUnitPrice(COMPUTE_UNIT_PRICE_MICROLAMPORTS, tx), (tx) => setTransactionMessageFeePayer(feePayer, tx), (tx) => prependTransactionMessageInstruction(getSetComputeUnitLimitInstruction({ units: COMPUTE_UNIT_LIMIT }), tx), (tx) => appendTransactionMessageInstructions([transferIx, memoIx], tx), (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx)))) }
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
};
|
|
161
250
|
//#endregion
|
|
162
251
|
//#region src/lib/resolve-wallet.ts
|
|
163
252
|
/**
|
|
@@ -289,7 +378,8 @@ async function buildX402Client(wallet, opts) {
|
|
|
289
378
|
}
|
|
290
379
|
if (wallet.solanaKey) {
|
|
291
380
|
const { createKeyPairSignerFromBytes } = await import("@solana/kit");
|
|
292
|
-
|
|
381
|
+
const signer = await createKeyPairSignerFromBytes(wallet.solanaKey);
|
|
382
|
+
client.register("solana:*", new OptimizedSvmScheme(signer));
|
|
293
383
|
}
|
|
294
384
|
client.registerPolicy(createAddressValidationPolicy());
|
|
295
385
|
if (opts?.network) client.registerPolicy(createNetworkFilter(opts.network));
|
|
@@ -312,7 +402,6 @@ async function buildX402Client(wallet, opts) {
|
|
|
312
402
|
});
|
|
313
403
|
return client;
|
|
314
404
|
}
|
|
315
|
-
|
|
316
405
|
//#endregion
|
|
317
406
|
//#region src/commands/wallet.ts
|
|
318
407
|
const BASE_RPC = "https://mainnet.base.org";
|
|
@@ -454,6 +543,5 @@ const walletInfoCommand = buildCommand({
|
|
|
454
543
|
console.log();
|
|
455
544
|
}
|
|
456
545
|
});
|
|
457
|
-
|
|
458
546
|
//#endregion
|
|
459
|
-
export { error as _, fetchTempoBalances as a, warn as b, resolveWallet as c, displayNetwork as d, formatAmount as f, dim as g, readHistory as h, fetchSolanaBalances as i, appendHistory as l, formatUsdcValue as m, fetchAllBalances as n, walletInfoCommand as o, formatTxLine as p, fetchEvmBalances as r, buildX402Client as s, balanceLine as t, calcSpend as u, info as v, isTTY as y };
|
|
547
|
+
export { error as _, fetchTempoBalances as a, warn as b, resolveWallet as c, displayNetwork as d, formatAmount as f, dim as g, readHistory as h, fetchSolanaBalances as i, appendHistory as l, formatUsdcValue as m, fetchAllBalances as n, walletInfoCommand as o, formatTxLine as p, fetchEvmBalances as r, buildX402Client as s, balanceLine as t, calcSpend as u, info as v, isTTY as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "x402-proxy",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"description": "curl for x402 paid APIs. Auto-pays any endpoint on Base, Solana, and Tempo. Also works as an OpenClaw plugin.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -28,24 +28,28 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@clack/prompts": "^1.1.0",
|
|
30
30
|
"@getmcp/generators": "^0.10.1",
|
|
31
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
32
32
|
"@noble/curves": "^2.0.1",
|
|
33
33
|
"@noble/hashes": "^2.0.1",
|
|
34
34
|
"@scure/base": "^2.0.0",
|
|
35
35
|
"@scure/bip32": "^2.0.1",
|
|
36
36
|
"@scure/bip39": "^2.0.1",
|
|
37
37
|
"@sinclair/typebox": "^0.34.48",
|
|
38
|
-
"@solana/
|
|
38
|
+
"@solana-program/compute-budget": "^0.15.0",
|
|
39
|
+
"@solana-program/token": "^0.12.0",
|
|
40
|
+
"@solana-program/token-2022": "^0.9.0",
|
|
41
|
+
"@solana/kit": "^6.5.0",
|
|
39
42
|
"@stricli/core": "^1.2.6",
|
|
40
|
-
"@x402/
|
|
41
|
-
"@x402/
|
|
42
|
-
"@x402/
|
|
43
|
-
"@x402/
|
|
44
|
-
"
|
|
43
|
+
"@x402/core": "^2.8.0",
|
|
44
|
+
"@x402/evm": "^2.8.0",
|
|
45
|
+
"@x402/fetch": "^2.8.0",
|
|
46
|
+
"@x402/mcp": "^2.8.0",
|
|
47
|
+
"@x402/svm": "^2.8.0",
|
|
48
|
+
"ethers": "^6.16.0",
|
|
45
49
|
"mppx": "^0.4.9",
|
|
46
50
|
"picocolors": "^1.1.1",
|
|
47
|
-
"viem": "^2.
|
|
48
|
-
"yaml": "^2.8.
|
|
51
|
+
"viem": "^2.47.6",
|
|
52
|
+
"yaml": "^2.8.3"
|
|
49
53
|
},
|
|
50
54
|
"peerDependencies": {
|
|
51
55
|
"openclaw": ">=2026.3.8"
|
|
@@ -56,12 +60,12 @@
|
|
|
56
60
|
}
|
|
57
61
|
},
|
|
58
62
|
"devDependencies": {
|
|
59
|
-
"@types/node": "^
|
|
60
|
-
"openclaw": "
|
|
61
|
-
"publint": "^0.3.
|
|
62
|
-
"tsdown": "^0.
|
|
63
|
-
"typescript": "^5.9.
|
|
64
|
-
"vitest": "^4.
|
|
63
|
+
"@types/node": "^25.5.0",
|
|
64
|
+
"openclaw": "^2026.3.24",
|
|
65
|
+
"publint": "^0.3.18",
|
|
66
|
+
"tsdown": "^0.21.5",
|
|
67
|
+
"typescript": "^5.9.3",
|
|
68
|
+
"vitest": "^4.1.1"
|
|
65
69
|
},
|
|
66
70
|
"files": [
|
|
67
71
|
"dist/**",
|
package/dist/setup-xhtYsp7D.js
DELETED
package/dist/status-BkURZYDA.js
DELETED
package/dist/wallet-BM0ngyqk.js
DELETED