x402-proxy 0.9.2 → 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 +13 -1
- package/dist/bin/cli.js +11 -11
- package/dist/openclaw/plugin.js +100 -4
- package/dist/{status-ZRhJKaXq.js → status-BZTToWE_.js} +1 -1
- package/dist/status-Bj3U-W2M.js +3 -0
- package/dist/wallet-4C9EL4Iv.js +3 -0
- package/dist/{wallet-Bs8cBOv7.js → wallet-CqUc-ZFn.js} +100 -3
- package/package.json +5 -1
- package/dist/status-Dt8be_kW.js +0 -3
- package/dist/wallet-8evCw-5Z.js +0 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ 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
|
+
|
|
10
21
|
## [0.9.2] - 2026-03-26
|
|
11
22
|
|
|
12
23
|
### Changed
|
|
@@ -284,7 +295,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
284
295
|
- `appendHistory` / `readHistory` / `calcSpend` - JSONL transaction history
|
|
285
296
|
- Re-exports from `@x402/fetch`, `@x402/svm`, `@x402/evm`
|
|
286
297
|
|
|
287
|
-
[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
|
|
288
300
|
[0.9.2]: https://github.com/cascade-protocol/x402-proxy/compare/v0.9.1...v0.9.2
|
|
289
301
|
[0.9.1]: https://github.com/cascade-protocol/x402-proxy/compare/v0.9.0...v0.9.1
|
|
290
302
|
[0.9.0]: https://github.com/cascade-protocol/x402-proxy/compare/v0.8.6...v0.9.0
|
package/dist/bin/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
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-
|
|
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
4
|
import { n as setupCommand, t as runSetup } from "../setup-QtTFsCFs.js";
|
|
5
|
-
import { n as statusCommand } from "../status-
|
|
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";
|
|
@@ -398,7 +398,7 @@ Examples:
|
|
|
398
398
|
};
|
|
399
399
|
if (!url) {
|
|
400
400
|
if (isConfigured()) {
|
|
401
|
-
const { displayStatus } = await import("../status-
|
|
401
|
+
const { displayStatus } = await import("../status-Bj3U-W2M.js");
|
|
402
402
|
await displayStatus();
|
|
403
403
|
console.log();
|
|
404
404
|
console.log(pc.dim(" Commands:"));
|
|
@@ -463,7 +463,7 @@ Examples:
|
|
|
463
463
|
verbose(`protocol: ${resolvedProtocol ?? "auto-detect"}, maxDeposit: ${maxDeposit}`);
|
|
464
464
|
let preferredNetwork = config?.defaultNetwork;
|
|
465
465
|
if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
|
|
466
|
-
const { fetchAllBalances } = await import("../wallet-
|
|
466
|
+
const { fetchAllBalances } = await import("../wallet-4C9EL4Iv.js");
|
|
467
467
|
const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
|
|
468
468
|
const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
|
|
469
469
|
const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
|
|
@@ -630,7 +630,7 @@ Examples:
|
|
|
630
630
|
const hasSolana = accepts.some((a) => a.network.startsWith("solana:"));
|
|
631
631
|
const hasMpp = detected.mpp;
|
|
632
632
|
const hasOther = accepts.some((a) => !a.network.startsWith("eip155:") && !a.network.startsWith("solana:"));
|
|
633
|
-
const { fetchAllBalances } = await import("../wallet-
|
|
633
|
+
const { fetchAllBalances } = await import("../wallet-4C9EL4Iv.js");
|
|
634
634
|
const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
|
|
635
635
|
const evmUsdc = hasEvm && balances.evm ? Number(balances.evm.usdc) : 0;
|
|
636
636
|
const solUsdc = hasSolana && balances.sol ? Number(balances.sol.usdc) : 0;
|
|
@@ -850,7 +850,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
850
850
|
async function startX402Proxy() {
|
|
851
851
|
let preferredNetwork = config?.defaultNetwork;
|
|
852
852
|
if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
|
|
853
|
-
const { fetchAllBalances } = await import("../wallet-
|
|
853
|
+
const { fetchAllBalances } = await import("../wallet-4C9EL4Iv.js");
|
|
854
854
|
const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
|
|
855
855
|
const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
|
|
856
856
|
const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
|
|
@@ -870,7 +870,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
870
870
|
}
|
|
871
871
|
const remoteClient = new Client({
|
|
872
872
|
name: "x402-proxy",
|
|
873
|
-
version: "0.9.
|
|
873
|
+
version: "0.9.3"
|
|
874
874
|
});
|
|
875
875
|
const x402Mcp = new x402MCPClient(remoteClient, x402PaymentClient, {
|
|
876
876
|
autoPayment: true,
|
|
@@ -908,7 +908,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
908
908
|
}
|
|
909
909
|
const localServer = new Server({
|
|
910
910
|
name: "x402-proxy",
|
|
911
|
-
version: "0.9.
|
|
911
|
+
version: "0.9.3"
|
|
912
912
|
}, { capabilities: {
|
|
913
913
|
tools: tools.length > 0 ? {} : void 0,
|
|
914
914
|
resources: remoteResources.length > 0 ? {} : void 0
|
|
@@ -1003,7 +1003,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
1003
1003
|
}));
|
|
1004
1004
|
const remoteClient = new Client({
|
|
1005
1005
|
name: "x402-proxy",
|
|
1006
|
-
version: "0.9.
|
|
1006
|
+
version: "0.9.3"
|
|
1007
1007
|
});
|
|
1008
1008
|
await connectTransport(remoteClient);
|
|
1009
1009
|
const mppClient = McpClient.wrap(remoteClient, { methods: wrappedMethods });
|
|
@@ -1018,7 +1018,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
1018
1018
|
}
|
|
1019
1019
|
const localServer = new Server({
|
|
1020
1020
|
name: "x402-proxy",
|
|
1021
|
-
version: "0.9.
|
|
1021
|
+
version: "0.9.3"
|
|
1022
1022
|
}, { capabilities: {
|
|
1023
1023
|
tools: tools.length > 0 ? {} : void 0,
|
|
1024
1024
|
resources: remoteResources.length > 0 ? {} : void 0
|
|
@@ -1415,7 +1415,7 @@ const app = buildApplication(buildRouteMap({
|
|
|
1415
1415
|
docs: { brief: "curl for x402 paid APIs" }
|
|
1416
1416
|
}), {
|
|
1417
1417
|
name: "x402-proxy",
|
|
1418
|
-
versionInfo: { currentVersion: "0.9.
|
|
1418
|
+
versionInfo: { currentVersion: "0.9.3" },
|
|
1419
1419
|
scanner: { caseStyle: "allow-kebab-for-camel" }
|
|
1420
1420
|
});
|
|
1421
1421
|
//#endregion
|
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";
|
|
@@ -81,6 +83,100 @@ function loadWalletFile() {
|
|
|
81
83
|
return null;
|
|
82
84
|
}
|
|
83
85
|
}
|
|
86
|
+
//#endregion
|
|
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
|
+
};
|
|
84
180
|
function appendHistory(historyPath, record) {
|
|
85
181
|
try {
|
|
86
182
|
mkdirSync(dirname(historyPath), { recursive: true });
|
|
@@ -713,7 +809,7 @@ function createWalletCommand(ctx) {
|
|
|
713
809
|
try {
|
|
714
810
|
const snap = await getWalletSnapshot(ctx.rpcUrl, walletAddress, ctx.historyPath);
|
|
715
811
|
const solscanUrl = `https://solscan.io/account/${walletAddress}`;
|
|
716
|
-
const lines = [`x402-proxy v0.9.
|
|
812
|
+
const lines = [`x402-proxy v0.9.3`];
|
|
717
813
|
const defaultModel = ctx.allModels[0];
|
|
718
814
|
if (defaultModel) lines.push("", `**Model** - ${defaultModel.name} (${defaultModel.provider})`);
|
|
719
815
|
lines.push("", `**[Wallet](${solscanUrl})**`, `\`${walletAddress}\``);
|
|
@@ -1053,7 +1149,7 @@ function register(api) {
|
|
|
1053
1149
|
return;
|
|
1054
1150
|
}
|
|
1055
1151
|
const client = new x402Client();
|
|
1056
|
-
client.register(SOL_MAINNET, new
|
|
1152
|
+
client.register(SOL_MAINNET, new OptimizedSvmScheme(signer, { rpcUrl }));
|
|
1057
1153
|
proxyRef = createX402ProxyHandler({ client });
|
|
1058
1154
|
const upstreamOrigin = upstreamOrigins[0];
|
|
1059
1155
|
if (upstreamOrigin) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
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-
|
|
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
6
|
//#region src/commands/status.ts
|
|
@@ -9,11 +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";
|
|
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";
|
|
17
19
|
//#region src/lib/output.ts
|
|
18
20
|
function isTTY() {
|
|
19
21
|
return !!process.stderr.isTTY;
|
|
@@ -152,6 +154,100 @@ function formatTxLine(r, opts) {
|
|
|
152
154
|
return ` ${timeStr} ${r.ok ? "" : "✗ "}${parts.join(" · ")}`;
|
|
153
155
|
}
|
|
154
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
|
+
};
|
|
250
|
+
//#endregion
|
|
155
251
|
//#region src/lib/resolve-wallet.ts
|
|
156
252
|
/**
|
|
157
253
|
* Resolve wallet keys following the priority cascade:
|
|
@@ -282,7 +378,8 @@ async function buildX402Client(wallet, opts) {
|
|
|
282
378
|
}
|
|
283
379
|
if (wallet.solanaKey) {
|
|
284
380
|
const { createKeyPairSignerFromBytes } = await import("@solana/kit");
|
|
285
|
-
|
|
381
|
+
const signer = await createKeyPairSignerFromBytes(wallet.solanaKey);
|
|
382
|
+
client.register("solana:*", new OptimizedSvmScheme(signer));
|
|
286
383
|
}
|
|
287
384
|
client.registerPolicy(createAddressValidationPolicy());
|
|
288
385
|
if (opts?.network) client.registerPolicy(createNetworkFilter(opts.network));
|
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,
|
|
@@ -35,8 +35,12 @@
|
|
|
35
35
|
"@scure/bip32": "^2.0.1",
|
|
36
36
|
"@scure/bip39": "^2.0.1",
|
|
37
37
|
"@sinclair/typebox": "^0.34.48",
|
|
38
|
+
"@solana-program/compute-budget": "^0.15.0",
|
|
39
|
+
"@solana-program/token": "^0.12.0",
|
|
40
|
+
"@solana-program/token-2022": "^0.9.0",
|
|
38
41
|
"@solana/kit": "^6.5.0",
|
|
39
42
|
"@stricli/core": "^1.2.6",
|
|
43
|
+
"@x402/core": "^2.8.0",
|
|
40
44
|
"@x402/evm": "^2.8.0",
|
|
41
45
|
"@x402/fetch": "^2.8.0",
|
|
42
46
|
"@x402/mcp": "^2.8.0",
|
package/dist/status-Dt8be_kW.js
DELETED
package/dist/wallet-8evCw-5Z.js
DELETED