x402-proxy 0.9.2 → 0.9.4

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 CHANGED
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.9.4] - 2026-03-27
11
+
12
+ ### Fixed
13
+ - All example URLs migrated from legacy individual service subdomains (`twitter.surf.cascade.fyi`, `web.surf.cascade.fyi`, `inference.surf.cascade.fyi`) to unified `surf.cascade.fyi/api/v1/` endpoints across CLI help text, README, SKILL.md, and OpenClaw plugin docs
14
+
15
+ ## [0.9.3] - 2026-03-26
16
+
17
+ ### Fixed
18
+ - 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)
19
+ - Hardcoded USDC mint metadata (Token Program address, 6 decimals) to skip `fetchMint` RPC call entirely for USDC payments
20
+ - 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)
21
+ - Custom RPC URL (via config or OpenClaw plugin) is tried first, with public RPCs as fallback
22
+
23
+ ### Changed
24
+ - Added `@solana-program/compute-budget`, `@solana-program/token`, `@solana-program/token-2022`, `@x402/core` as direct dependencies (previously transitive via `@x402/svm`)
25
+
10
26
  ## [0.9.2] - 2026-03-26
11
27
 
12
28
  ### Changed
@@ -284,7 +300,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
284
300
  - `appendHistory` / `readHistory` / `calcSpend` - JSONL transaction history
285
301
  - Re-exports from `@x402/fetch`, `@x402/svm`, `@x402/evm`
286
302
 
287
- [Unreleased]: https://github.com/cascade-protocol/x402-proxy/compare/v0.9.2...HEAD
303
+ [Unreleased]: https://github.com/cascade-protocol/x402-proxy/compare/v0.9.4...HEAD
304
+ [0.9.4]: https://github.com/cascade-protocol/x402-proxy/compare/v0.9.3...v0.9.4
305
+ [0.9.3]: https://github.com/cascade-protocol/x402-proxy/compare/v0.9.2...v0.9.3
288
306
  [0.9.2]: https://github.com/cascade-protocol/x402-proxy/compare/v0.9.1...v0.9.2
289
307
  [0.9.1]: https://github.com/cascade-protocol/x402-proxy/compare/v0.9.0...v0.9.1
290
308
  [0.9.0]: https://github.com/cascade-protocol/x402-proxy/compare/v0.8.6...v0.9.0
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  ## Quick Start
6
6
 
7
7
  ```bash
8
- npx x402-proxy https://twitter.surf.cascade.fyi/users/cascade_fyi
8
+ npx x402-proxy -X POST -d '{"ref":"CoinbaseDev"}' https://surf.cascade.fyi/api/v1/twitter/user
9
9
  ```
10
10
 
11
11
  That's it. The endpoint returns 402, x402-proxy pays and streams the response.
@@ -52,14 +52,14 @@ openclaw mcp set surf '{"command":"npx","args":["-y","x402-proxy","mcp","https:/
52
52
  Works like curl. Response body streams to stdout, payment info goes to stderr.
53
53
 
54
54
  ```bash
55
- # GET request
56
- $ npx x402-proxy https://twitter.surf.cascade.fyi/users/cascade_fyi
55
+ # POST request
56
+ $ npx x402-proxy -X POST -d '{"ref":"CoinbaseDev"}' https://surf.cascade.fyi/api/v1/twitter/user
57
57
 
58
58
  # POST with body and headers (curl-style short flags: -X, -H, -d)
59
59
  $ npx x402-proxy -X POST \
60
60
  -H "Content-Type: application/json" \
61
61
  -d '{"url":"https://x402.org"}' \
62
- https://web.surf.cascade.fyi/v1/crawl
62
+ https://surf.cascade.fyi/api/v1/web/crawl
63
63
 
64
64
  # Force a specific network
65
65
  $ npx x402-proxy --network base https://api.example.com/data
@@ -71,7 +71,7 @@ $ npx x402-proxy --verbose https://api.example.com/data
71
71
  $ npx x402-proxy --protocol mpp \
72
72
  -X POST -H "Content-Type: application/json" \
73
73
  -d '{"model":"minimax/minimax-m2.5","stream":true,"messages":[{"role":"user","content":"Hello"}]}' \
74
- https://inference.surf.cascade.fyi/v1/chat/completions
74
+ https://surf.cascade.fyi/api/v1/inference/completions
75
75
 
76
76
  # Pipe-safe
77
77
  $ npx x402-proxy https://api.example.com/data | jq '.results'
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-Bs8cBOv7.js";
4
- import { n as setupCommand, t as runSetup } from "../setup-QtTFsCFs.js";
5
- import { n as statusCommand } from "../status-ZRhJKaXq.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-Dp5fS7ob.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";
@@ -310,8 +310,8 @@ const fetchCommand = buildCommand({
310
310
  fullDescription: `Make a paid HTTP request. Payment is automatic when the server returns 402.
311
311
 
312
312
  Examples:
313
- $ x402-proxy https://twitter.surf.cascade.fyi/users/cascade_fyi
314
- $ x402-proxy -X POST -d '{"url":"https://x402.org"}' https://web.surf.cascade.fyi/v1/crawl
313
+ $ x402-proxy -X POST -d '{"ref":"CoinbaseDev"}' https://surf.cascade.fyi/api/v1/twitter/user
314
+ $ x402-proxy -X POST -d '{"url":"https://x402.org"}' https://surf.cascade.fyi/api/v1/web/crawl
315
315
  $ x402-proxy https://api.example.com/data | jq '.results'`
316
316
  },
317
317
  parameters: {
@@ -398,7 +398,7 @@ Examples:
398
398
  };
399
399
  if (!url) {
400
400
  if (isConfigured()) {
401
- const { displayStatus } = await import("../status-Dt8be_kW.js");
401
+ const { displayStatus } = await import("../status-Bj3U-W2M.js");
402
402
  await displayStatus();
403
403
  console.log();
404
404
  console.log(pc.dim(" Commands:"));
@@ -408,7 +408,7 @@ Examples:
408
408
  console.log(` ${pc.cyan("$ npx x402-proxy wallet")} Addresses and balances`);
409
409
  console.log(` ${pc.cyan("$ npx x402-proxy wallet history")} Full payment history`);
410
410
  console.log();
411
- console.log(pc.dim(" try: ") + pc.cyan("$ npx x402-proxy https://twitter.surf.cascade.fyi/users/cascade_fyi"));
411
+ console.log(pc.dim(" try: ") + pc.cyan(`$ npx x402-proxy -X POST -d '{"ref":"CoinbaseDev"}' https://surf.cascade.fyi/api/v1/twitter/user`));
412
412
  console.log();
413
413
  console.log(pc.dim(" https://github.com/cascade-protocol/x402-proxy"));
414
414
  console.log();
@@ -450,7 +450,7 @@ Examples:
450
450
  process.exit(1);
451
451
  }
452
452
  dim(" No wallet found. Let's set one up first.\n");
453
- const { runSetup } = await import("../setup-lCsiivm2.js");
453
+ const { runSetup } = await import("../setup-B6xRV8Ue.js");
454
454
  await runSetup();
455
455
  console.log();
456
456
  wallet = resolveWallet();
@@ -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-8evCw-5Z.js");
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-8evCw-5Z.js");
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;
@@ -806,7 +806,7 @@ Wallet is auto-generated on first run. No env vars needed.`
806
806
  });
807
807
  if (wallet.source === "none") {
808
808
  dim("No wallet found. Auto-generating...");
809
- const { runSetup } = await import("../setup-lCsiivm2.js");
809
+ const { runSetup } = await import("../setup-B6xRV8Ue.js");
810
810
  await runSetup({ nonInteractive: true });
811
811
  const fresh = resolveWallet({
812
812
  evmKey: flags.evmKey,
@@ -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-8evCw-5Z.js");
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.2"
873
+ version: "0.9.4"
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.2"
911
+ version: "0.9.4"
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.2"
1006
+ version: "0.9.4"
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.2"
1021
+ version: "0.9.4"
1022
1022
  }, { capabilities: {
1023
1023
  tools: tools.length > 0 ? {} : void 0,
1024
1024
  resources: remoteResources.length > 0 ? {} : void 0
@@ -1272,7 +1272,7 @@ const mcpAddCommand = buildCommand({
1272
1272
  }
1273
1273
  }
1274
1274
  prompts.log.step("Try your first request:");
1275
- prompts.log.message(` ${pc.cyan("$ npx x402-proxy https://twitter.surf.cascade.fyi/users/cascade_fyi")}`);
1275
+ prompts.log.message(` ${pc.cyan(`$ npx x402-proxy -X POST -d '{"ref":"CoinbaseDev"}' https://surf.cascade.fyi/api/v1/twitter/user`)}`);
1276
1276
  prompts.log.message(` ${pc.dim("Run")} ${pc.cyan("npx x402-proxy")} ${pc.dim("to see your wallet and balance")}`);
1277
1277
  prompts.outro(pc.green(`MCP server ${pc.bold(serverName)} is ready to use!`));
1278
1278
  }
@@ -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.2" },
1418
+ versionInfo: { currentVersion: "0.9.4" },
1419
1419
  scanner: { caseStyle: "allow-kebab-for-camel" }
1420
1420
  });
1421
1421
  //#endregion
@@ -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.2`];
812
+ const lines = [`x402-proxy v0.9.4`];
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 ExactSvmScheme(signer, { rpcUrl }));
1152
+ client.register(SOL_MAINNET, new OptimizedSvmScheme(signer, { rpcUrl }));
1057
1153
  proxyRef = createX402ProxyHandler({ client });
1058
1154
  const upstreamOrigin = upstreamOrigins[0];
1059
1155
  if (upstreamOrigin) {
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { t as runSetup } from "./setup-Dp5fS7ob.js";
3
+ export { runSetup };
@@ -143,7 +143,7 @@ async function runSetup(opts) {
143
143
  prompts.log.message(` Solana (USDC): Send USDC to ${pc.cyan(sol.address)}`);
144
144
  prompts.log.message(` Base (USDC): Send USDC to ${pc.cyan(evm.address)}`);
145
145
  prompts.log.step("Try your first request:");
146
- prompts.log.message(` ${pc.cyan("$ npx x402-proxy https://twitter.surf.cascade.fyi/users/cascade_fyi")}`);
146
+ prompts.log.message(` ${pc.cyan(`$ npx x402-proxy -X POST -d '{"ref":"CoinbaseDev"}' https://surf.cascade.fyi/api/v1/twitter/user`)}`);
147
147
  prompts.outro(pc.green("Setup complete!"));
148
148
  }
149
149
  const setupCommand = buildCommand({
@@ -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-Bs8cBOv7.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
6
  //#region src/commands/status.ts
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { t as displayStatus } from "./status-BZTToWE_.js";
3
+ export { displayStatus };
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { n as fetchAllBalances } from "./wallet-CqUc-ZFn.js";
3
+ export { fetchAllBalances };
@@ -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
- registerExactSvmScheme(client, { signer: await createKeyPairSignerFromBytes(wallet.solanaKey) });
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.2",
3
+ "version": "0.9.4",
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/skills/SKILL.md CHANGED
@@ -10,7 +10,7 @@ description: Use x402-proxy CLI for consuming and debugging x402 and MPP paid AP
10
10
  ## Quick start
11
11
 
12
12
  ```bash
13
- npx x402-proxy https://twitter.surf.cascade.fyi/users/cascade_fyi
13
+ npx x402-proxy -X POST -d '{"ref":"CoinbaseDev"}' https://surf.cascade.fyi/api/v1/twitter/user
14
14
  ```
15
15
 
16
16
  First run auto-creates a wallet. No setup needed.
@@ -165,14 +165,16 @@ stdout = response body, stderr = payment info. Pipes, redirects, and `jq` all wo
165
165
 
166
166
  ## OpenClaw Plugin
167
167
 
168
- x402-proxy ships as an [OpenClaw](https://openclaw.dev) plugin. Install it to give your OpenClaw gateway automatic x402 payment capabilities:
168
+ x402-proxy ships as an [OpenClaw](https://openclaw.dev) plugin for automatic x402 payments, wallet management, and pay-per-use inference proxying. For full installation, provider/model configuration, and troubleshooting, read `references/openclaw-plugin.md`.
169
169
 
170
- - `x_balance` tool - check wallet SOL/USDC balances
171
- - `x_payment` tool - call any x402-enabled endpoint with automatic payment
172
- - `/x_wallet` command - wallet status, send USDC, transaction history
173
- - HTTP route proxy (`/x402/*`) - proxies requests to upstream x402 endpoints with payment
170
+ Quick install:
174
171
 
175
- Configure in your OpenClaw plugin settings with `providers` (upstream x402 endpoints and models) and optionally `keypairPath` or use the standard `X402_PROXY_WALLET_MNEMONIC` env var.
172
+ ```bash
173
+ openclaw plugins install x402-proxy
174
+ npx x402-proxy setup # creates wallet if needed
175
+ ```
176
+
177
+ Registers: `x_balance` tool, `x_payment` tool, `/x_wallet` command, `/x402/*` HTTP route for inference proxying.
176
178
 
177
179
  ## Library API
178
180
 
@@ -0,0 +1,135 @@
1
+ # OpenClaw Plugin Setup
2
+
3
+ x402-proxy ships as an [OpenClaw](https://openclaw.dev) plugin. Gives your gateway automatic x402 payment, wallet management, and pay-per-use inference proxying via Solana USDC.
4
+
5
+ ## What it registers
6
+
7
+ - **`x_balance` tool** - check wallet SOL/USDC balances, daily spend, available funds
8
+ - **`x_payment` tool** - call any x402-enabled endpoint with automatic payment (params: `url`, `method`, `params`, `headers`)
9
+ - **`/x_wallet` command** - wallet status dashboard, `send <amount|all> <address>`, `history [page]`
10
+ - **HTTP route `/x402/*`** - proxies requests to upstream inference endpoints with payment, tracks token usage and cost
11
+
12
+ ## Step 1: Install the plugin
13
+
14
+ ```bash
15
+ openclaw plugins install x402-proxy
16
+ ```
17
+
18
+ This downloads from npm, validates `openclaw.plugin.json`, and installs to `~/.openclaw/extensions/x402-proxy/`.
19
+
20
+ ## Step 2: Configure wallet
21
+
22
+ The plugin resolves a Solana wallet using the same cascade as the CLI:
23
+
24
+ 1. `keypairPath` in plugin config (solana-keygen JSON file)
25
+ 2. `X402_PROXY_WALLET_SOLANA_KEY` env var (base58 or JSON array)
26
+ 3. `X402_PROXY_WALLET_MNEMONIC` env var (BIP-39, derives both Solana and EVM)
27
+ 4. `~/.config/x402-proxy/wallet.json` (auto-created by `npx x402-proxy setup`)
28
+
29
+ Easiest path - run setup first, then the plugin picks up the wallet automatically:
30
+
31
+ ```bash
32
+ npx x402-proxy setup
33
+ ```
34
+
35
+ Or set an explicit keypair in plugin config (step 3).
36
+
37
+ ## Step 3: Configure providers and models
38
+
39
+ Add the plugin config to your `openclaw.json` (or via `openclaw config edit`):
40
+
41
+ ```json
42
+ {
43
+ "plugins": {
44
+ "entries": {
45
+ "x402-proxy": {
46
+ "config": {
47
+ "providers": {
48
+ "surf-inference": {
49
+ "baseUrl": "/x402/v1",
50
+ "upstreamUrl": "https://surf.cascade.fyi/api/v1/inference",
51
+ "models": [
52
+ { "id": "anthropic/claude-opus-4.6", "name": "Claude Opus 4.6", "maxTokens": 200000, "reasoning": true, "input": ["text", "image"], "cost": { "input": 0.015, "output": 0.075, "cacheRead": 0.0015, "cacheWrite": 0.01875 }, "contextWindow": 200000 },
53
+ { "id": "anthropic/claude-sonnet-4.6", "name": "Claude Sonnet 4.6", "maxTokens": 200000, "reasoning": true, "input": ["text", "image"], "cost": { "input": 0.003, "output": 0.015, "cacheRead": 0.0003, "cacheWrite": 0.00375 }, "contextWindow": 200000 },
54
+ { "id": "anthropic/claude-opus-4.5", "name": "Claude Opus 4.5", "maxTokens": 200000, "reasoning": true, "input": ["text", "image"], "cost": { "input": 0.015, "output": 0.075, "cacheRead": 0.0015, "cacheWrite": 0.01875 }, "contextWindow": 200000 },
55
+ { "id": "anthropic/claude-sonnet-4.5", "name": "Claude Sonnet 4.5", "maxTokens": 200000, "reasoning": true, "input": ["text", "image"], "cost": { "input": 0.003, "output": 0.015, "cacheRead": 0.0003, "cacheWrite": 0.00375 }, "contextWindow": 200000 },
56
+ { "id": "x-ai/grok-4.20-beta", "name": "Grok 4.20 Beta", "maxTokens": 131072, "reasoning": true, "input": ["text"], "cost": { "input": 0.003, "output": 0.015, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 131072 },
57
+ { "id": "x-ai/grok-4.20-multi-agent-beta", "name": "Grok 4.20 Multi-Agent", "maxTokens": 131072, "reasoning": true, "input": ["text"], "cost": { "input": 0.003, "output": 0.015, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 131072 },
58
+ { "id": "x-ai/grok-4.1-fast", "name": "Grok 4.1 Fast", "maxTokens": 131072, "reasoning": false, "input": ["text"], "cost": { "input": 0.001, "output": 0.005, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 131072 },
59
+ { "id": "x-ai/grok-4.20-beta:online", "name": "Grok 4.20 Beta (Online)", "maxTokens": 131072, "reasoning": true, "input": ["text"], "cost": { "input": 0.005, "output": 0.025, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 131072 },
60
+ { "id": "x-ai/grok-4.20-multi-agent-beta:online", "name": "Grok 4.20 Multi-Agent (Online)", "maxTokens": 131072, "reasoning": true, "input": ["text"], "cost": { "input": 0.005, "output": 0.025, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 131072 },
61
+ { "id": "x-ai/grok-4.1-fast:online", "name": "Grok 4.1 Fast (Online)", "maxTokens": 131072, "reasoning": false, "input": ["text"], "cost": { "input": 0.003, "output": 0.015, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 131072 },
62
+ { "id": "minimax/minimax-m2.7", "name": "MiniMax M2.7", "maxTokens": 1000000, "reasoning": false, "input": ["text"], "cost": { "input": 0.001, "output": 0.005, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 1000000 },
63
+ { "id": "minimax/minimax-m2.5", "name": "MiniMax M2.5", "maxTokens": 1000000, "reasoning": false, "input": ["text"], "cost": { "input": 0.001, "output": 0.005, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 1000000 },
64
+ { "id": "moonshotai/kimi-k2.5", "name": "Kimi K2.5", "maxTokens": 131072, "reasoning": true, "input": ["text"], "cost": { "input": 0.002, "output": 0.008, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 131072 },
65
+ { "id": "z-ai/glm-5", "name": "GLM-5", "maxTokens": 128000, "reasoning": false, "input": ["text"], "cost": { "input": 0.001, "output": 0.005, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 128000 },
66
+ { "id": "qwen/qwen-2.5-7b-instruct", "name": "Qwen 2.5 7B Instruct", "maxTokens": 32768, "reasoning": false, "input": ["text"], "cost": { "input": 0.0003, "output": 0.001, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 32768 }
67
+ ]
68
+ }
69
+ }
70
+ }
71
+ }
72
+ }
73
+ }
74
+ }
75
+ ```
76
+
77
+ ### Config fields
78
+
79
+ | Field | Description |
80
+ |-------|-------------|
81
+ | `providers.<name>.baseUrl` | Route path registered in OpenClaw (e.g., `/x402/v1`) |
82
+ | `providers.<name>.upstreamUrl` | Actual upstream endpoint (e.g., `https://surf.cascade.fyi/api/v1/inference`) |
83
+ | `providers.<name>.models[]` | Model catalog array |
84
+ | `keypairPath` | Optional path to solana-keygen JSON file (overrides wallet resolution) |
85
+ | `rpcUrl` | Solana RPC URL (defaults to mainnet public endpoints with failover) |
86
+ | `dashboardUrl` | URL linked from `/x_wallet` dashboard |
87
+
88
+ ### Model entry fields
89
+
90
+ | Field | Type | Description |
91
+ |-------|------|-------------|
92
+ | `id` | string | Model identifier (e.g., `anthropic/claude-opus-4.6`) |
93
+ | `name` | string | Display name |
94
+ | `maxTokens` | number | Max context length |
95
+ | `reasoning` | boolean | Supports extended thinking |
96
+ | `input` | string[] | Input modalities: `["text"]` or `["text", "image"]` |
97
+ | `cost.input` | number | USDC per 1K input tokens |
98
+ | `cost.output` | number | USDC per 1K output tokens |
99
+ | `cost.cacheRead` | number | USDC per 1K cached read tokens |
100
+ | `cost.cacheWrite` | number | USDC per 1K cache write tokens |
101
+ | `contextWindow` | number | Full context window size |
102
+
103
+ ## Step 4: Restart gateway and verify
104
+
105
+ ```bash
106
+ openclaw gateway restart
107
+ openclaw models # verify models appear
108
+ ```
109
+
110
+ ## How it works
111
+
112
+ 1. Plugin boots, loads wallet via the resolution cascade
113
+ 2. Registers each provider from config into OpenClaw's model catalog (API type: `openai-completions`, no auth required)
114
+ 3. HTTP route `/x402/*` intercepts inference requests, strips prefix, proxies to `upstreamUrl`
115
+ 4. On 402 response, auto-signs a Solana USDC payment and retries
116
+ 5. SSE streaming responses are parsed for token usage and logged to `~/.config/x402-proxy/history.jsonl`
117
+ 6. Tools and command are available to all agents on the gateway
118
+
119
+ ## Fetching latest models
120
+
121
+ The model list on `surf.cascade.fyi` changes over time. Fetch the current catalog:
122
+
123
+ ```bash
124
+ npx x402-proxy --protocol mpp --network solana \
125
+ https://surf.cascade.fyi/api/v1/inference/models
126
+ ```
127
+
128
+ Then update the `models` array in your plugin config accordingly.
129
+
130
+ ## Troubleshooting
131
+
132
+ - **Models don't appear in `openclaw models`** - the plugin uses a `catalog` hook (not `models` field). Make sure you're on x402-proxy >= 0.8.5.
133
+ - **"no wallet found" in logs** - run `npx x402-proxy setup` or set `X402_PROXY_WALLET_MNEMONIC` env var before starting the gateway.
134
+ - **402 errors on inference** - check wallet has USDC balance: use `x_balance` tool or `npx x402-proxy wallet`.
135
+ - **Gateway cold start slow** - normal on small VMs (~72s). The `x402-wallet` service eagerly loads the wallet during boot.
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- import { t as runSetup } from "./setup-QtTFsCFs.js";
3
- export { runSetup };
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- import { t as displayStatus } from "./status-ZRhJKaXq.js";
3
- export { displayStatus };
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- import { n as fetchAllBalances } from "./wallet-Bs8cBOv7.js";
3
- export { fetchAllBalances };