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 +19 -1
- package/README.md +5 -5
- package/dist/bin/cli.js +18 -18
- package/dist/openclaw/plugin.js +100 -4
- package/dist/setup-B6xRV8Ue.js +3 -0
- package/dist/{setup-QtTFsCFs.js → setup-Dp5fS7ob.js} +1 -1
- 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/skills/SKILL.md +9 -7
- package/skills/references/openclaw-plugin.md +135 -0
- package/dist/setup-lCsiivm2.js +0 -3
- package/dist/status-Dt8be_kW.js +0 -3
- package/dist/wallet-8evCw-5Z.js +0 -3
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.
|
|
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://
|
|
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
|
-
#
|
|
56
|
-
$ npx x402-proxy https://
|
|
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://
|
|
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://
|
|
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-
|
|
4
|
-
import { n as setupCommand, t as runSetup } from "../setup-
|
|
5
|
-
import { n as statusCommand } from "../status-
|
|
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://
|
|
314
|
-
$ x402-proxy -X POST -d '{"url":"https://x402.org"}' https://
|
|
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-
|
|
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(
|
|
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-
|
|
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-
|
|
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;
|
|
@@ -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-
|
|
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-
|
|
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.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.
|
|
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.
|
|
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.
|
|
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(
|
|
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.
|
|
1418
|
+
versionInfo: { currentVersion: "0.9.4" },
|
|
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.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
|
|
1152
|
+
client.register(SOL_MAINNET, new OptimizedSvmScheme(signer, { rpcUrl }));
|
|
1057
1153
|
proxyRef = createX402ProxyHandler({ client });
|
|
1058
1154
|
const upstreamOrigin = upstreamOrigins[0];
|
|
1059
1155
|
if (upstreamOrigin) {
|
|
@@ -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(
|
|
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-
|
|
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.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://
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
package/dist/setup-lCsiivm2.js
DELETED
package/dist/status-Dt8be_kW.js
DELETED
package/dist/wallet-8evCw-5Z.js
DELETED