x402-proxy-openclaw 0.10.9
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 +453 -0
- package/LICENSE +201 -0
- package/README.md +35 -0
- package/dist/commands/wallet.js +149 -0
- package/dist/commands.js +218 -0
- package/dist/defaults.js +129 -0
- package/dist/handler.js +319 -0
- package/dist/history.js +124 -0
- package/dist/lib/config.js +47 -0
- package/dist/lib/debug-log.js +29 -0
- package/dist/lib/derive.js +77 -0
- package/dist/lib/env.js +9 -0
- package/dist/lib/optimized-svm-scheme.js +86 -0
- package/dist/lib/output.js +13 -0
- package/dist/lib/wallet-resolution.js +76 -0
- package/dist/openclaw/plugin.d.ts +15 -0
- package/dist/openclaw/plugin.js +156 -0
- package/dist/openclaw.plugin.json +112 -0
- package/dist/route.js +671 -0
- package/dist/solana.js +93 -0
- package/dist/tools.js +269 -0
- package/dist/wallet.js +15 -0
- package/openclaw.plugin.json +112 -0
- package/package.json +93 -0
- package/skills/SKILL.md +183 -0
- package/skills/references/library.md +85 -0
- package/skills/references/openclaw-plugin.md +145 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
//#region packages/x402-proxy/src/lib/output.ts
|
|
3
|
+
function isTTY() {
|
|
4
|
+
return !!process.stderr.isTTY;
|
|
5
|
+
}
|
|
6
|
+
function info(msg) {
|
|
7
|
+
process.stderr.write(`${isTTY() ? pc.cyan(msg) : msg}\n`);
|
|
8
|
+
}
|
|
9
|
+
function dim(msg) {
|
|
10
|
+
process.stderr.write(`${isTTY() ? pc.dim(msg) : msg}\n`);
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { dim, info };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { loadWalletFile } from "./config.js";
|
|
2
|
+
import { deriveEvmKeypair, deriveSolanaKeypair } from "./derive.js";
|
|
3
|
+
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
4
|
+
import { base58 } from "@scure/base";
|
|
5
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
6
|
+
//#region packages/x402-proxy/src/lib/wallet-resolution.ts
|
|
7
|
+
/**
|
|
8
|
+
* Resolve wallet keys following the priority cascade:
|
|
9
|
+
* 1. Flags (--evm-key / --solana-key as raw key strings)
|
|
10
|
+
* 2. X402_PROXY_WALLET_EVM_KEY / X402_PROXY_WALLET_SOLANA_KEY env vars
|
|
11
|
+
* 3. X402_PROXY_WALLET_MNEMONIC env var (derives both)
|
|
12
|
+
* 4. ~/.config/x402-proxy/wallet.json (mnemonic file)
|
|
13
|
+
*/
|
|
14
|
+
function resolveWallet(opts) {
|
|
15
|
+
if (opts?.evmKey || opts?.solanaKey) {
|
|
16
|
+
const result = { source: "flag" };
|
|
17
|
+
if (opts.evmKey) {
|
|
18
|
+
const hex = opts.evmKey.startsWith("0x") ? opts.evmKey : `0x${opts.evmKey}`;
|
|
19
|
+
result.evmKey = hex;
|
|
20
|
+
result.evmAddress = privateKeyToAccount(hex).address;
|
|
21
|
+
}
|
|
22
|
+
if (opts.solanaKey) {
|
|
23
|
+
result.solanaKey = parseSolanaKey(opts.solanaKey);
|
|
24
|
+
result.solanaAddress = solanaAddressFromKey(result.solanaKey);
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
const envEvm = process.env.X402_PROXY_WALLET_EVM_KEY;
|
|
29
|
+
const envSol = process.env.X402_PROXY_WALLET_SOLANA_KEY;
|
|
30
|
+
if (envEvm || envSol) {
|
|
31
|
+
const result = { source: "env" };
|
|
32
|
+
if (envEvm) {
|
|
33
|
+
const hex = envEvm.startsWith("0x") ? envEvm : `0x${envEvm}`;
|
|
34
|
+
result.evmKey = hex;
|
|
35
|
+
result.evmAddress = privateKeyToAccount(hex).address;
|
|
36
|
+
}
|
|
37
|
+
if (envSol) {
|
|
38
|
+
result.solanaKey = parseSolanaKey(envSol);
|
|
39
|
+
result.solanaAddress = solanaAddressFromKey(result.solanaKey);
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
const envMnemonic = process.env.X402_PROXY_WALLET_MNEMONIC;
|
|
44
|
+
if (envMnemonic) return resolveFromMnemonic(envMnemonic, "mnemonic-env");
|
|
45
|
+
const walletFile = loadWalletFile();
|
|
46
|
+
if (walletFile) return resolveFromMnemonic(walletFile.mnemonic, "wallet-file");
|
|
47
|
+
return { source: "none" };
|
|
48
|
+
}
|
|
49
|
+
function resolveFromMnemonic(mnemonic, source) {
|
|
50
|
+
const evm = deriveEvmKeypair(mnemonic);
|
|
51
|
+
const sol = deriveSolanaKeypair(mnemonic);
|
|
52
|
+
const solanaKey = new Uint8Array(64);
|
|
53
|
+
solanaKey.set(sol.secretKey, 0);
|
|
54
|
+
solanaKey.set(sol.publicKey, 32);
|
|
55
|
+
return {
|
|
56
|
+
evmKey: evm.privateKey,
|
|
57
|
+
evmAddress: evm.address,
|
|
58
|
+
solanaKey,
|
|
59
|
+
solanaAddress: sol.address,
|
|
60
|
+
source
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function parseSolanaKey(input) {
|
|
64
|
+
const trimmed = input.trim();
|
|
65
|
+
if (trimmed.startsWith("[")) {
|
|
66
|
+
const arr = JSON.parse(trimmed);
|
|
67
|
+
return new Uint8Array(arr);
|
|
68
|
+
}
|
|
69
|
+
return base58.decode(trimmed);
|
|
70
|
+
}
|
|
71
|
+
function solanaAddressFromKey(keyBytes) {
|
|
72
|
+
if (keyBytes.length >= 64) return base58.encode(keyBytes.slice(32));
|
|
73
|
+
return base58.encode(ed25519.getPublicKey(keyBytes));
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
export { resolveWallet };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
|
|
2
|
+
import * as openclaw_plugin_sdk0 from "openclaw/plugin-sdk";
|
|
3
|
+
import * as openclaw_plugin_sdk_core0 from "openclaw/plugin-sdk/core";
|
|
4
|
+
|
|
5
|
+
//#region packages/x402-proxy/src/openclaw/plugin.d.ts
|
|
6
|
+
declare function register(api: OpenClawPluginApi): void;
|
|
7
|
+
declare const _default: {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
configSchema: openclaw_plugin_sdk0.OpenClawPluginConfigSchema;
|
|
12
|
+
register: NonNullable<openclaw_plugin_sdk_core0.OpenClawPluginDefinition["register"]>;
|
|
13
|
+
} & Pick<openclaw_plugin_sdk_core0.OpenClawPluginDefinition, "kind">;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { _default as default, register };
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { createMppProxyHandler, createX402ProxyHandler } from "../handler.js";
|
|
2
|
+
import { getHistoryPath } from "../lib/config.js";
|
|
3
|
+
import { OptimizedSvmScheme } from "../lib/optimized-svm-scheme.js";
|
|
4
|
+
import { resolveWallet } from "../lib/wallet-resolution.js";
|
|
5
|
+
import { loadSvmWallet } from "../wallet.js";
|
|
6
|
+
import { SOL_MAINNET, addressForNetwork, createRequestTool, createWalletTool } from "../tools.js";
|
|
7
|
+
import { createSendCommand, createWalletCommand } from "../commands.js";
|
|
8
|
+
import { resolveProviders, routePrefixForBaseUrl } from "../defaults.js";
|
|
9
|
+
import { createInferenceProxyRouteHandler } from "../route.js";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { createKeyPairSignerFromBytes } from "@solana/kit";
|
|
13
|
+
import { x402Client } from "@x402/fetch";
|
|
14
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
15
|
+
//#region packages/x402-proxy/src/openclaw/plugin.ts
|
|
16
|
+
function register(api) {
|
|
17
|
+
const config = api.pluginConfig ?? {};
|
|
18
|
+
const explicitKeypairPath = config.keypairPath;
|
|
19
|
+
const rpcUrl = config.rpcUrl || "https://api.mainnet-beta.solana.com";
|
|
20
|
+
const dashboardUrl = config.dashboardUrl || "";
|
|
21
|
+
const { providers, models: allModels } = resolveProviders(config);
|
|
22
|
+
const defaultProvider = providers[0];
|
|
23
|
+
for (const provider of providers) api.registerProvider({
|
|
24
|
+
id: provider.id,
|
|
25
|
+
label: provider.id,
|
|
26
|
+
auth: [],
|
|
27
|
+
catalog: {
|
|
28
|
+
order: "simple",
|
|
29
|
+
run: async () => ({ provider: {
|
|
30
|
+
baseUrl: provider.baseUrl,
|
|
31
|
+
api: "openai-completions",
|
|
32
|
+
authHeader: false,
|
|
33
|
+
models: provider.models
|
|
34
|
+
} })
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
api.logger.info(`x402-proxy: ${providers.map((provider) => `${provider.id}:${provider.protocol}`).join(", ")} - ${allModels.length} models`);
|
|
38
|
+
let solanaWalletAddress = null;
|
|
39
|
+
let evmWalletAddress = null;
|
|
40
|
+
let signerRef = null;
|
|
41
|
+
let proxyRef = null;
|
|
42
|
+
let mppHandlerRef = null;
|
|
43
|
+
let evmKeyRef = null;
|
|
44
|
+
let walletLoadPromise = null;
|
|
45
|
+
const historyPath = getHistoryPath();
|
|
46
|
+
const routePrefixes = [...new Set(providers.map((provider) => routePrefixForBaseUrl(provider.baseUrl)))];
|
|
47
|
+
const handler = createInferenceProxyRouteHandler({
|
|
48
|
+
providers,
|
|
49
|
+
getX402Proxy: () => proxyRef,
|
|
50
|
+
getMppHandler: () => mppHandlerRef,
|
|
51
|
+
getWalletAddress: () => solanaWalletAddress ?? evmWalletAddress,
|
|
52
|
+
getWalletAddressForNetwork: (network) => addressForNetwork(evmWalletAddress, solanaWalletAddress, network),
|
|
53
|
+
historyPath,
|
|
54
|
+
allModels,
|
|
55
|
+
logger: api.logger
|
|
56
|
+
});
|
|
57
|
+
for (const path of routePrefixes) api.registerHttpRoute({
|
|
58
|
+
path,
|
|
59
|
+
match: "prefix",
|
|
60
|
+
auth: "plugin",
|
|
61
|
+
handler
|
|
62
|
+
});
|
|
63
|
+
api.logger.info(`proxy: HTTP routes ${routePrefixes.join(", ")} registered for ${providers.map((provider) => provider.upstreamUrl).join(", ")}`);
|
|
64
|
+
async function ensureWalletLoaded() {
|
|
65
|
+
if (walletLoadPromise) {
|
|
66
|
+
await walletLoadPromise;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
walletLoadPromise = (async () => {
|
|
70
|
+
try {
|
|
71
|
+
const resolution = resolveWallet();
|
|
72
|
+
evmKeyRef = resolution.evmKey ?? null;
|
|
73
|
+
evmWalletAddress = resolution.evmAddress ?? null;
|
|
74
|
+
if (explicitKeypairPath) {
|
|
75
|
+
signerRef = await loadSvmWallet(explicitKeypairPath.startsWith("~/") ? join(homedir(), explicitKeypairPath.slice(2)) : explicitKeypairPath);
|
|
76
|
+
solanaWalletAddress = signerRef.address;
|
|
77
|
+
} else if (resolution.solanaKey && resolution.solanaAddress) {
|
|
78
|
+
signerRef = await createKeyPairSignerFromBytes(resolution.solanaKey);
|
|
79
|
+
solanaWalletAddress = resolution.solanaAddress;
|
|
80
|
+
} else {
|
|
81
|
+
signerRef = null;
|
|
82
|
+
solanaWalletAddress = null;
|
|
83
|
+
}
|
|
84
|
+
if (!solanaWalletAddress && !evmWalletAddress) {
|
|
85
|
+
api.logger.error("x402-proxy: no wallet found. Run `x402-proxy setup` to create one, or set X402_PROXY_WALLET_MNEMONIC / X402_PROXY_WALLET_EVM_KEY.");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (signerRef) {
|
|
89
|
+
const client = new x402Client();
|
|
90
|
+
client.register(SOL_MAINNET, new OptimizedSvmScheme(signerRef, { rpcUrl }));
|
|
91
|
+
proxyRef = createX402ProxyHandler({ client });
|
|
92
|
+
} else proxyRef = null;
|
|
93
|
+
if (evmKeyRef) {
|
|
94
|
+
const maxBudget = Math.max(...providers.map((p) => Number(p.mppSessionBudget) || .5)).toString();
|
|
95
|
+
mppHandlerRef = await createMppProxyHandler({
|
|
96
|
+
evmKey: evmKeyRef,
|
|
97
|
+
maxDeposit: maxBudget
|
|
98
|
+
});
|
|
99
|
+
} else mppHandlerRef = null;
|
|
100
|
+
api.logger.info(`wallets: solana=${solanaWalletAddress ?? "missing"} evm=${evmWalletAddress ?? "missing"}`);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
api.logger.error(`wallet load failed: ${err}`);
|
|
103
|
+
}
|
|
104
|
+
})();
|
|
105
|
+
await walletLoadPromise;
|
|
106
|
+
}
|
|
107
|
+
ensureWalletLoaded();
|
|
108
|
+
api.registerService({
|
|
109
|
+
id: "x402-wallet",
|
|
110
|
+
async start() {
|
|
111
|
+
await ensureWalletLoaded();
|
|
112
|
+
},
|
|
113
|
+
async stop() {
|
|
114
|
+
if (mppHandlerRef) try {
|
|
115
|
+
await mppHandlerRef.close();
|
|
116
|
+
} catch {}
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
const toolCtx = {
|
|
120
|
+
ensureReady: ensureWalletLoaded,
|
|
121
|
+
getSolanaWalletAddress: () => solanaWalletAddress,
|
|
122
|
+
getEvmWalletAddress: () => evmWalletAddress,
|
|
123
|
+
getSigner: () => signerRef,
|
|
124
|
+
getX402Proxy: () => proxyRef,
|
|
125
|
+
getEvmKey: () => evmKeyRef,
|
|
126
|
+
getDefaultRequestProtocol: () => defaultProvider?.protocol ?? "mpp",
|
|
127
|
+
getDefaultMppSessionBudget: () => defaultProvider?.mppSessionBudget ?? "0.5",
|
|
128
|
+
rpcUrl,
|
|
129
|
+
historyPath,
|
|
130
|
+
allModels
|
|
131
|
+
};
|
|
132
|
+
api.registerTool(createWalletTool(toolCtx), { names: ["x_balance"] });
|
|
133
|
+
api.registerTool(createRequestTool(toolCtx), { names: ["x_payment"] });
|
|
134
|
+
const cmdCtx = {
|
|
135
|
+
ensureReady: ensureWalletLoaded,
|
|
136
|
+
getSolanaWalletAddress: () => solanaWalletAddress,
|
|
137
|
+
getEvmWalletAddress: () => evmWalletAddress,
|
|
138
|
+
getSigner: () => signerRef,
|
|
139
|
+
getDefaultRequestProtocol: () => defaultProvider?.protocol ?? "mpp",
|
|
140
|
+
getDefaultMppSessionBudget: () => defaultProvider?.mppSessionBudget ?? "0.5",
|
|
141
|
+
rpcUrl,
|
|
142
|
+
dashboardUrl,
|
|
143
|
+
historyPath,
|
|
144
|
+
allModels
|
|
145
|
+
};
|
|
146
|
+
api.registerCommand(createWalletCommand(cmdCtx));
|
|
147
|
+
api.registerCommand(createSendCommand(cmdCtx));
|
|
148
|
+
}
|
|
149
|
+
var plugin_default = definePluginEntry({
|
|
150
|
+
id: "x402-proxy",
|
|
151
|
+
name: "mpp/x402 Payments Proxy",
|
|
152
|
+
description: "x402 and MPP payments, wallet tools, and paid inference proxying",
|
|
153
|
+
register
|
|
154
|
+
});
|
|
155
|
+
//#endregion
|
|
156
|
+
export { plugin_default as default, register };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "x402-proxy",
|
|
3
|
+
"name": "mpp/x402 Payments Proxy",
|
|
4
|
+
"description": "x402 and MPP payments, wallet tools, and paid inference proxying",
|
|
5
|
+
"version": "0.10.9",
|
|
6
|
+
"providers": [
|
|
7
|
+
"surf"
|
|
8
|
+
],
|
|
9
|
+
"autoEnableWhenConfiguredProviders": [
|
|
10
|
+
"surf"
|
|
11
|
+
],
|
|
12
|
+
"skills": [
|
|
13
|
+
"skills"
|
|
14
|
+
],
|
|
15
|
+
"contracts": {
|
|
16
|
+
"tools": [
|
|
17
|
+
"x_wallet",
|
|
18
|
+
"x_balance",
|
|
19
|
+
"x_request",
|
|
20
|
+
"x_payment"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"configSchema": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"additionalProperties": false,
|
|
26
|
+
"properties": {
|
|
27
|
+
"providers": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"description": "Provider catalog keyed by name. Defaults to the built-in surf provider when omitted.",
|
|
30
|
+
"additionalProperties": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"additionalProperties": false,
|
|
33
|
+
"properties": {
|
|
34
|
+
"baseUrl": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Gateway base path for this provider, typically under /x402-proxy"
|
|
37
|
+
},
|
|
38
|
+
"upstreamUrl": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"description": "Upstream inference origin to proxy to"
|
|
41
|
+
},
|
|
42
|
+
"protocol": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"enum": [
|
|
45
|
+
"x402",
|
|
46
|
+
"mpp",
|
|
47
|
+
"auto"
|
|
48
|
+
],
|
|
49
|
+
"description": "Payment protocol for this provider. Defaults to mpp."
|
|
50
|
+
},
|
|
51
|
+
"mppSessionBudget": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": "Maximum USDC budget to lock for MPP sessions for this provider (default: 0.5)"
|
|
54
|
+
},
|
|
55
|
+
"models": {
|
|
56
|
+
"type": "array",
|
|
57
|
+
"description": "Static OpenClaw model catalog entries for this provider"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"protocol": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"enum": [
|
|
65
|
+
"x402",
|
|
66
|
+
"mpp",
|
|
67
|
+
"auto"
|
|
68
|
+
],
|
|
69
|
+
"description": "Default payment protocol fallback for providers that do not override it."
|
|
70
|
+
},
|
|
71
|
+
"mppSessionBudget": {
|
|
72
|
+
"type": "string",
|
|
73
|
+
"description": "Default maximum USDC budget to lock for MPP sessions (default: 0.5)"
|
|
74
|
+
},
|
|
75
|
+
"keypairPath": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"description": "Optional path to Solana keypair JSON file (overrides x402-proxy wallet resolution for Solana/x402 surfaces)"
|
|
78
|
+
},
|
|
79
|
+
"rpcUrl": {
|
|
80
|
+
"type": "string",
|
|
81
|
+
"description": "Solana RPC URL"
|
|
82
|
+
},
|
|
83
|
+
"dashboardUrl": {
|
|
84
|
+
"type": "string",
|
|
85
|
+
"description": "URL to link from /x_wallet dashboard"
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"required": []
|
|
89
|
+
},
|
|
90
|
+
"uiHints": {
|
|
91
|
+
"protocol": {
|
|
92
|
+
"label": "Default protocol",
|
|
93
|
+
"help": "Used only when a provider entry does not set its own protocol."
|
|
94
|
+
},
|
|
95
|
+
"mppSessionBudget": {
|
|
96
|
+
"label": "Default MPP budget",
|
|
97
|
+
"placeholder": "0.5"
|
|
98
|
+
},
|
|
99
|
+
"keypairPath": {
|
|
100
|
+
"label": "Solana keypair path",
|
|
101
|
+
"advanced": true
|
|
102
|
+
},
|
|
103
|
+
"rpcUrl": {
|
|
104
|
+
"label": "Solana RPC URL",
|
|
105
|
+
"advanced": true
|
|
106
|
+
},
|
|
107
|
+
"dashboardUrl": {
|
|
108
|
+
"label": "Dashboard URL",
|
|
109
|
+
"advanced": true
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|