x402-proxy 0.9.4 → 0.10.1
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 +38 -1
- package/README.md +7 -3
- package/dist/bin/cli.js +1067 -156
- package/dist/openclaw/plugin.d.ts +12 -56
- package/dist/openclaw/plugin.js +958 -221
- package/dist/openclaw.plugin.json +69 -4
- package/dist/{setup-Dp5fS7ob.js → setup-DtKrojW1.js} +1 -1
- package/dist/setup-EX1_teNg.js +3 -0
- package/dist/{status-BZTToWE_.js → status-B5oH1nHv.js} +2 -2
- package/dist/status-DlR8yBrK.js +3 -0
- package/dist/wallet-7XKcknNZ.js +3 -0
- package/dist/{wallet-CqUc-ZFn.js → wallet-DqGROXlC.js} +18 -28
- package/openclaw.plugin.json +69 -4
- package/package.json +13 -4
- package/skills/SKILL.md +3 -1
- package/skills/references/openclaw-plugin.md +21 -11
- package/dist/setup-B6xRV8Ue.js +0 -3
- package/dist/status-Bj3U-W2M.js +0 -3
- package/dist/wallet-4C9EL4Iv.js +0 -3
- /package/dist/{derive-EDXzwKW2.js → derive-CY0En_Y3.js} +0 -0
package/dist/bin/cli.js
CHANGED
|
@@ -1,16 +1,1063 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { n as setupCommand, t as runSetup } from "../setup-
|
|
5
|
-
import { n as statusCommand } from "../status-
|
|
2
|
+
import { _ as error, b as success, 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, x as warn, y as isTTY } from "../wallet-DqGROXlC.js";
|
|
3
|
+
import { a as getHistoryPath, c as loadConfig, i as getConfigDirShort, l as loadWalletFile, s as isConfigured, u as saveConfig } from "../derive-CY0En_Y3.js";
|
|
4
|
+
import { n as setupCommand, t as runSetup } from "../setup-DtKrojW1.js";
|
|
5
|
+
import { n as statusCommand } from "../status-B5oH1nHv.js";
|
|
6
6
|
import { dirname, join, normalize, resolve } from "node:path";
|
|
7
7
|
import { buildApplication, buildCommand, buildRouteMap, run } from "@stricli/core";
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
8
9
|
import pc from "picocolors";
|
|
10
|
+
import { once } from "node:events";
|
|
11
|
+
import http from "node:http";
|
|
12
|
+
import { decodePaymentResponseHeader, wrapFetchWithPayment } from "@x402/fetch";
|
|
9
13
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
14
|
import { homedir } from "node:os";
|
|
11
|
-
import { decodePaymentResponseHeader, wrapFetchWithPayment } from "@x402/fetch";
|
|
12
15
|
import { base58 } from "@scure/base";
|
|
16
|
+
import "@sinclair/typebox";
|
|
13
17
|
import * as prompts from "@clack/prompts";
|
|
18
|
+
//#region src/openclaw/defaults.ts
|
|
19
|
+
const DEFAULT_SURF_PROVIDER_ID = "surf";
|
|
20
|
+
const DEFAULT_SURF_BASE_URL = "/x402-proxy/v1";
|
|
21
|
+
const DEFAULT_SURF_UPSTREAM_URL = "https://surf.cascade.fyi/api/v1/inference";
|
|
22
|
+
const DEFAULT_SURF_MODELS = [
|
|
23
|
+
{
|
|
24
|
+
id: "anthropic/claude-opus-4.6",
|
|
25
|
+
name: "Claude Opus 4.6",
|
|
26
|
+
maxTokens: 2e5,
|
|
27
|
+
reasoning: true,
|
|
28
|
+
input: ["text", "image"],
|
|
29
|
+
cost: {
|
|
30
|
+
input: .015,
|
|
31
|
+
output: .075,
|
|
32
|
+
cacheRead: .0015,
|
|
33
|
+
cacheWrite: .01875
|
|
34
|
+
},
|
|
35
|
+
contextWindow: 2e5
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "anthropic/claude-sonnet-4.6",
|
|
39
|
+
name: "Claude Sonnet 4.6",
|
|
40
|
+
maxTokens: 2e5,
|
|
41
|
+
reasoning: true,
|
|
42
|
+
input: ["text", "image"],
|
|
43
|
+
cost: {
|
|
44
|
+
input: .003,
|
|
45
|
+
output: .015,
|
|
46
|
+
cacheRead: 3e-4,
|
|
47
|
+
cacheWrite: .00375
|
|
48
|
+
},
|
|
49
|
+
contextWindow: 2e5
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "x-ai/grok-4.20-beta",
|
|
53
|
+
name: "Grok 4.20 Beta",
|
|
54
|
+
maxTokens: 131072,
|
|
55
|
+
reasoning: true,
|
|
56
|
+
input: ["text"],
|
|
57
|
+
cost: {
|
|
58
|
+
input: .003,
|
|
59
|
+
output: .015,
|
|
60
|
+
cacheRead: 0,
|
|
61
|
+
cacheWrite: 0
|
|
62
|
+
},
|
|
63
|
+
contextWindow: 131072
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: "minimax/minimax-m2.5",
|
|
67
|
+
name: "MiniMax M2.5",
|
|
68
|
+
maxTokens: 1e6,
|
|
69
|
+
reasoning: false,
|
|
70
|
+
input: ["text"],
|
|
71
|
+
cost: {
|
|
72
|
+
input: .001,
|
|
73
|
+
output: .005,
|
|
74
|
+
cacheRead: 0,
|
|
75
|
+
cacheWrite: 0
|
|
76
|
+
},
|
|
77
|
+
contextWindow: 1e6
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: "moonshotai/kimi-k2.5",
|
|
81
|
+
name: "Kimi K2.5",
|
|
82
|
+
maxTokens: 131072,
|
|
83
|
+
reasoning: true,
|
|
84
|
+
input: ["text"],
|
|
85
|
+
cost: {
|
|
86
|
+
input: .002,
|
|
87
|
+
output: .008,
|
|
88
|
+
cacheRead: 0,
|
|
89
|
+
cacheWrite: 0
|
|
90
|
+
},
|
|
91
|
+
contextWindow: 131072
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "z-ai/glm-5",
|
|
95
|
+
name: "GLM-5",
|
|
96
|
+
maxTokens: 128e3,
|
|
97
|
+
reasoning: false,
|
|
98
|
+
input: ["text"],
|
|
99
|
+
cost: {
|
|
100
|
+
input: .001,
|
|
101
|
+
output: .005,
|
|
102
|
+
cacheRead: 0,
|
|
103
|
+
cacheWrite: 0
|
|
104
|
+
},
|
|
105
|
+
contextWindow: 128e3
|
|
106
|
+
}
|
|
107
|
+
];
|
|
108
|
+
function resolveProviders(config) {
|
|
109
|
+
const defaultProtocol = resolveProtocol(config.protocol);
|
|
110
|
+
const defaultMppSessionBudget = resolveMppSessionBudget(config.mppSessionBudget);
|
|
111
|
+
const raw = config.providers ?? {};
|
|
112
|
+
const entries = Object.entries(raw).length > 0 ? Object.entries(raw).map(([id, provider]) => ({
|
|
113
|
+
id,
|
|
114
|
+
baseUrl: provider.baseUrl || "/x402-proxy/v1",
|
|
115
|
+
upstreamUrl: provider.upstreamUrl || "https://surf.cascade.fyi/api/v1/inference",
|
|
116
|
+
protocol: resolveProtocol(provider.protocol, defaultProtocol),
|
|
117
|
+
mppSessionBudget: resolveMppSessionBudget(provider.mppSessionBudget, defaultMppSessionBudget),
|
|
118
|
+
models: provider.models && provider.models.length > 0 ? provider.models : DEFAULT_SURF_MODELS
|
|
119
|
+
})) : [{
|
|
120
|
+
id: DEFAULT_SURF_PROVIDER_ID,
|
|
121
|
+
baseUrl: DEFAULT_SURF_BASE_URL,
|
|
122
|
+
upstreamUrl: DEFAULT_SURF_UPSTREAM_URL,
|
|
123
|
+
protocol: defaultProtocol,
|
|
124
|
+
mppSessionBudget: defaultMppSessionBudget,
|
|
125
|
+
models: DEFAULT_SURF_MODELS
|
|
126
|
+
}];
|
|
127
|
+
return {
|
|
128
|
+
providers: entries,
|
|
129
|
+
models: entries.flatMap((provider) => provider.models.map((model) => ({
|
|
130
|
+
...model,
|
|
131
|
+
provider: provider.id
|
|
132
|
+
})))
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function resolveProtocol(value, fallback = "mpp") {
|
|
136
|
+
return value === "x402" || value === "mpp" || value === "auto" ? value : fallback;
|
|
137
|
+
}
|
|
138
|
+
function resolveMppSessionBudget(value, fallback = "0.5") {
|
|
139
|
+
return typeof value === "string" && value.trim().length > 0 ? value : fallback;
|
|
140
|
+
}
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/handler.ts
|
|
143
|
+
/**
|
|
144
|
+
* Detect which payment protocols a 402 response advertises.
|
|
145
|
+
* - x402: PAYMENT-REQUIRED or X-PAYMENT-REQUIRED header
|
|
146
|
+
* - MPP: WWW-Authenticate header with Payment scheme
|
|
147
|
+
*/
|
|
148
|
+
function detectProtocols(response) {
|
|
149
|
+
const pr = response.headers.get("PAYMENT-REQUIRED") ?? response.headers.get("X-PAYMENT-REQUIRED");
|
|
150
|
+
const wwwAuth = response.headers.get("WWW-Authenticate");
|
|
151
|
+
return {
|
|
152
|
+
x402: !!pr,
|
|
153
|
+
mpp: !!(wwwAuth && /^Payment\b/i.test(wwwAuth.trim()))
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Extract the on-chain transaction signature from an x402 payment response header.
|
|
158
|
+
*/
|
|
159
|
+
function extractTxSignature(response) {
|
|
160
|
+
const x402Header = response.headers.get("PAYMENT-RESPONSE") ?? response.headers.get("X-PAYMENT-RESPONSE");
|
|
161
|
+
if (x402Header) try {
|
|
162
|
+
return decodePaymentResponseHeader(x402Header).transaction ?? void 0;
|
|
163
|
+
} catch {}
|
|
164
|
+
const mppHeader = response.headers.get("Payment-Receipt");
|
|
165
|
+
if (mppHeader) try {
|
|
166
|
+
return JSON.parse(Buffer.from(mppHeader, "base64url").toString()).reference ?? void 0;
|
|
167
|
+
} catch {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Create an x402 proxy handler that wraps fetch with automatic payment.
|
|
173
|
+
*/
|
|
174
|
+
function createX402ProxyHandler(opts) {
|
|
175
|
+
const { client } = opts;
|
|
176
|
+
const paymentQueue = [];
|
|
177
|
+
client.onAfterPaymentCreation(async (hookCtx) => {
|
|
178
|
+
const raw = hookCtx.selectedRequirements.amount;
|
|
179
|
+
paymentQueue.push({
|
|
180
|
+
protocol: "x402",
|
|
181
|
+
network: hookCtx.selectedRequirements.network,
|
|
182
|
+
payTo: hookCtx.selectedRequirements.payTo,
|
|
183
|
+
amount: raw,
|
|
184
|
+
asset: hookCtx.selectedRequirements.asset
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
return {
|
|
188
|
+
x402Fetch: wrapFetchWithPayment(globalThis.fetch, client),
|
|
189
|
+
shiftPayment: () => paymentQueue.shift()
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const TEMPO_NETWORK = "eip155:4217";
|
|
193
|
+
/**
|
|
194
|
+
* Create an MPP proxy handler using mppx client.
|
|
195
|
+
* Dynamically imports mppx/client to keep startup fast.
|
|
196
|
+
*/
|
|
197
|
+
async function createMppProxyHandler(opts) {
|
|
198
|
+
const { Mppx, tempo } = await import("mppx/client");
|
|
199
|
+
const { privateKeyToAccount } = await import("viem/accounts");
|
|
200
|
+
const account = privateKeyToAccount(opts.evmKey);
|
|
201
|
+
const maxDeposit = opts.maxDeposit ?? "1";
|
|
202
|
+
const paymentQueue = [];
|
|
203
|
+
let lastChallengeAmount;
|
|
204
|
+
const mppx = Mppx.create({
|
|
205
|
+
methods: [tempo({
|
|
206
|
+
account,
|
|
207
|
+
maxDeposit
|
|
208
|
+
})],
|
|
209
|
+
polyfill: false,
|
|
210
|
+
onChallenge: async (challenge) => {
|
|
211
|
+
const req = challenge.request;
|
|
212
|
+
if (req.amount) lastChallengeAmount = (Number(req.amount) / 10 ** (req.decimals ?? 6)).toString();
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
let session;
|
|
216
|
+
return {
|
|
217
|
+
async fetch(input, init) {
|
|
218
|
+
const response = await mppx.fetch(typeof input === "string" ? input : input.toString(), init);
|
|
219
|
+
const receiptHeader = response.headers.get("Payment-Receipt");
|
|
220
|
+
if (receiptHeader) {
|
|
221
|
+
try {
|
|
222
|
+
const receipt = JSON.parse(Buffer.from(receiptHeader, "base64url").toString());
|
|
223
|
+
paymentQueue.push({
|
|
224
|
+
protocol: "mpp",
|
|
225
|
+
network: TEMPO_NETWORK,
|
|
226
|
+
amount: lastChallengeAmount,
|
|
227
|
+
receipt
|
|
228
|
+
});
|
|
229
|
+
} catch {
|
|
230
|
+
paymentQueue.push({
|
|
231
|
+
protocol: "mpp",
|
|
232
|
+
network: TEMPO_NETWORK,
|
|
233
|
+
amount: lastChallengeAmount
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
lastChallengeAmount = void 0;
|
|
237
|
+
}
|
|
238
|
+
return response;
|
|
239
|
+
},
|
|
240
|
+
async sse(input, init) {
|
|
241
|
+
session ??= tempo.session({
|
|
242
|
+
account,
|
|
243
|
+
maxDeposit
|
|
244
|
+
});
|
|
245
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
246
|
+
const iterable = await session.sse(url, init);
|
|
247
|
+
paymentQueue.push({
|
|
248
|
+
protocol: "mpp",
|
|
249
|
+
network: TEMPO_NETWORK,
|
|
250
|
+
intent: "session"
|
|
251
|
+
});
|
|
252
|
+
return iterable;
|
|
253
|
+
},
|
|
254
|
+
shiftPayment: () => paymentQueue.shift(),
|
|
255
|
+
async close() {
|
|
256
|
+
if (session?.opened) {
|
|
257
|
+
const receipt = await session.close();
|
|
258
|
+
if (receipt) {
|
|
259
|
+
const spentUsdc = receipt.spent ? (Number(receipt.spent) / 1e6).toString() : void 0;
|
|
260
|
+
paymentQueue.push({
|
|
261
|
+
protocol: "mpp",
|
|
262
|
+
network: TEMPO_NETWORK,
|
|
263
|
+
intent: "session",
|
|
264
|
+
amount: spentUsdc,
|
|
265
|
+
channelId: session.channelId ?? void 0,
|
|
266
|
+
receipt: {
|
|
267
|
+
method: receipt.method,
|
|
268
|
+
reference: receipt.reference,
|
|
269
|
+
status: receipt.status,
|
|
270
|
+
timestamp: receipt.timestamp,
|
|
271
|
+
acceptedCumulative: receipt.acceptedCumulative,
|
|
272
|
+
txHash: receipt.txHash
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
//#endregion
|
|
281
|
+
//#region src/openclaw/tools.ts
|
|
282
|
+
const SOL_MAINNET = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
283
|
+
const USDC_DECIMALS = 6;
|
|
284
|
+
function paymentAmount(payment) {
|
|
285
|
+
if (!payment?.amount) return void 0;
|
|
286
|
+
const parsed = Number.parseFloat(payment.amount);
|
|
287
|
+
return Number.isNaN(parsed) ? void 0 : parsed / 10 ** USDC_DECIMALS;
|
|
288
|
+
}
|
|
289
|
+
function parseMppAmount(value) {
|
|
290
|
+
if (!value) return void 0;
|
|
291
|
+
const parsed = Number(value);
|
|
292
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
293
|
+
}
|
|
294
|
+
function addressForNetwork(evmAddress, solanaAddress, network) {
|
|
295
|
+
if (network?.startsWith("eip155:")) return evmAddress ?? solanaAddress;
|
|
296
|
+
if (network?.startsWith("solana:")) return solanaAddress ?? evmAddress;
|
|
297
|
+
return solanaAddress ?? evmAddress;
|
|
298
|
+
}
|
|
299
|
+
//#endregion
|
|
300
|
+
//#region src/openclaw/route.ts
|
|
301
|
+
function createInferenceProxyRouteHandler(opts) {
|
|
302
|
+
const { providers, getX402Proxy, getWalletAddress, getWalletAddressForNetwork, getEvmKey, historyPath, allModels, logger } = opts;
|
|
303
|
+
const sortedProviders = providers.slice().sort((left, right) => right.baseUrl.length - left.baseUrl.length);
|
|
304
|
+
return async (req, res) => {
|
|
305
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
306
|
+
const provider = sortedProviders.find((entry) => entry.baseUrl === "/" || url.pathname.startsWith(entry.baseUrl));
|
|
307
|
+
if (!provider) {
|
|
308
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
309
|
+
res.end(JSON.stringify({ error: {
|
|
310
|
+
message: "Unknown inference route",
|
|
311
|
+
code: "not_found"
|
|
312
|
+
} }));
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
const walletAddress = getWalletAddress();
|
|
316
|
+
if (!walletAddress) {
|
|
317
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
318
|
+
res.end(JSON.stringify({ error: {
|
|
319
|
+
message: "Wallet not loaded yet",
|
|
320
|
+
code: "not_ready"
|
|
321
|
+
} }));
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
const pathSuffix = provider.baseUrl === "/" ? url.pathname : url.pathname.slice(provider.baseUrl.length);
|
|
325
|
+
const upstreamUrl = `${provider.upstreamUrl.replace(/\/+$/, "")}${pathSuffix.startsWith("/") ? pathSuffix : `/${pathSuffix}`}${url.search}`;
|
|
326
|
+
logger.info(`proxy: intercepting ${upstreamUrl.substring(0, 80)}`);
|
|
327
|
+
const chunks = [];
|
|
328
|
+
for await (const chunk of req) chunks.push(Buffer.from(chunk));
|
|
329
|
+
let body = Buffer.concat(chunks).toString("utf-8");
|
|
330
|
+
const HOP_BY_HOP = new Set([
|
|
331
|
+
"authorization",
|
|
332
|
+
"host",
|
|
333
|
+
"connection",
|
|
334
|
+
"content-length",
|
|
335
|
+
"transfer-encoding",
|
|
336
|
+
"keep-alive",
|
|
337
|
+
"te",
|
|
338
|
+
"upgrade"
|
|
339
|
+
]);
|
|
340
|
+
const headers = {};
|
|
341
|
+
for (const [key, val] of Object.entries(req.headers)) {
|
|
342
|
+
if (HOP_BY_HOP.has(key)) continue;
|
|
343
|
+
if (typeof val === "string") headers[key] = val;
|
|
344
|
+
}
|
|
345
|
+
const isChatCompletion = pathSuffix.includes("/chat/completions");
|
|
346
|
+
let thinkingMode;
|
|
347
|
+
if (isChatCompletion && body) try {
|
|
348
|
+
const parsed = JSON.parse(body);
|
|
349
|
+
if (parsed.reasoning_effort) thinkingMode = String(parsed.reasoning_effort);
|
|
350
|
+
if (!parsed.stream_options) {
|
|
351
|
+
parsed.stream_options = { include_usage: true };
|
|
352
|
+
body = JSON.stringify(parsed);
|
|
353
|
+
}
|
|
354
|
+
} catch {}
|
|
355
|
+
const method = req.method ?? "GET";
|
|
356
|
+
const startMs = Date.now();
|
|
357
|
+
try {
|
|
358
|
+
const requestInit = {
|
|
359
|
+
method,
|
|
360
|
+
headers,
|
|
361
|
+
body: ["GET", "HEAD"].includes(method) ? void 0 : body
|
|
362
|
+
};
|
|
363
|
+
const useMpp = provider.protocol === "mpp" || provider.protocol === "auto";
|
|
364
|
+
const wantsStreaming = isChatCompletion && /"stream"\s*:\s*true/.test(body);
|
|
365
|
+
if (useMpp) {
|
|
366
|
+
const evmKey = getEvmKey();
|
|
367
|
+
if (!evmKey) {
|
|
368
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
369
|
+
res.end(JSON.stringify({ error: {
|
|
370
|
+
message: "MPP inference requires an EVM wallet. Configure X402_PROXY_WALLET_MNEMONIC or X402_PROXY_WALLET_EVM_KEY.",
|
|
371
|
+
code: "mpp_wallet_missing"
|
|
372
|
+
} }));
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
return await handleMppRequest({
|
|
376
|
+
req,
|
|
377
|
+
res,
|
|
378
|
+
upstreamUrl,
|
|
379
|
+
requestInit,
|
|
380
|
+
walletAddress,
|
|
381
|
+
historyPath,
|
|
382
|
+
logger,
|
|
383
|
+
allModels,
|
|
384
|
+
thinkingMode,
|
|
385
|
+
wantsStreaming,
|
|
386
|
+
startMs,
|
|
387
|
+
evmKey,
|
|
388
|
+
mppSessionBudget: provider.mppSessionBudget
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
const proxy = getX402Proxy();
|
|
392
|
+
if (!proxy) {
|
|
393
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
394
|
+
res.end(JSON.stringify({ error: {
|
|
395
|
+
message: "x402 wallet not loaded yet. Configure a Solana wallet or switch the provider to mpp.",
|
|
396
|
+
code: "x402_wallet_missing"
|
|
397
|
+
} }));
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
const response = await proxy.x402Fetch(upstreamUrl, requestInit);
|
|
401
|
+
if (response.status === 402) {
|
|
402
|
+
const responseBody = await response.text();
|
|
403
|
+
logger.error(`x402: payment failed, raw response: ${responseBody}`);
|
|
404
|
+
const payment = proxy.shiftPayment();
|
|
405
|
+
const amount = paymentAmount(payment);
|
|
406
|
+
const paymentFrom = (payment?.network && getWalletAddressForNetwork?.(payment.network)) ?? walletAddress;
|
|
407
|
+
const paymentNetwork = payment?.network ?? "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
408
|
+
appendHistory(historyPath, {
|
|
409
|
+
t: Date.now(),
|
|
410
|
+
ok: false,
|
|
411
|
+
kind: "x402_inference",
|
|
412
|
+
net: paymentNetwork,
|
|
413
|
+
from: paymentFrom,
|
|
414
|
+
to: payment?.payTo,
|
|
415
|
+
amount,
|
|
416
|
+
token: amount != null ? "USDC" : void 0,
|
|
417
|
+
ms: Date.now() - startMs,
|
|
418
|
+
error: "payment_required"
|
|
419
|
+
});
|
|
420
|
+
let userMessage;
|
|
421
|
+
if (responseBody.includes("simulation") || responseBody.includes("Simulation")) userMessage = `Insufficient USDC or SOL in wallet ${walletAddress}. Fund it with USDC (SPL token) to pay for inference.`;
|
|
422
|
+
else if (responseBody.includes("insufficient") || responseBody.includes("balance")) userMessage = `Insufficient funds in wallet ${walletAddress}. Top up with USDC on Solana mainnet.`;
|
|
423
|
+
else userMessage = `x402 payment failed: ${responseBody.substring(0, 200) || "unknown error"}. Wallet: ${walletAddress}`;
|
|
424
|
+
res.writeHead(402, { "Content-Type": "application/json" });
|
|
425
|
+
res.end(JSON.stringify({ error: {
|
|
426
|
+
message: userMessage,
|
|
427
|
+
type: "x402_payment_error",
|
|
428
|
+
code: "payment_failed"
|
|
429
|
+
} }));
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
if (!response.ok && isChatCompletion) {
|
|
433
|
+
const responseBody = await response.text();
|
|
434
|
+
logger.error(`x402: upstream error ${response.status}: ${responseBody.substring(0, 300)}`);
|
|
435
|
+
const payment = proxy.shiftPayment();
|
|
436
|
+
const amount = paymentAmount(payment);
|
|
437
|
+
const paymentFrom = (payment?.network && getWalletAddressForNetwork?.(payment.network)) ?? walletAddress;
|
|
438
|
+
const paymentNetwork = payment?.network ?? "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
439
|
+
appendHistory(historyPath, {
|
|
440
|
+
t: Date.now(),
|
|
441
|
+
ok: false,
|
|
442
|
+
kind: "x402_inference",
|
|
443
|
+
net: paymentNetwork,
|
|
444
|
+
from: paymentFrom,
|
|
445
|
+
to: payment?.payTo,
|
|
446
|
+
amount,
|
|
447
|
+
token: amount != null ? "USDC" : void 0,
|
|
448
|
+
ms: Date.now() - startMs,
|
|
449
|
+
error: `upstream_${response.status}`
|
|
450
|
+
});
|
|
451
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
452
|
+
res.end(JSON.stringify({ error: {
|
|
453
|
+
message: `LLM provider temporarily unavailable (HTTP ${response.status}). Try again shortly.`,
|
|
454
|
+
type: "x402_upstream_error",
|
|
455
|
+
code: "upstream_failed"
|
|
456
|
+
} }));
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
459
|
+
logger.info(`x402: response ${response.status}`);
|
|
460
|
+
const txSig = extractTxSignature(response);
|
|
461
|
+
const payment = proxy.shiftPayment();
|
|
462
|
+
const amount = paymentAmount(payment);
|
|
463
|
+
const paymentFrom = (payment?.network && getWalletAddressForNetwork?.(payment.network)) ?? walletAddress;
|
|
464
|
+
const paymentNetwork = payment?.network ?? "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
465
|
+
const resHeaders = {};
|
|
466
|
+
for (const [key, val] of response.headers.entries()) resHeaders[key] = val;
|
|
467
|
+
res.writeHead(response.status, resHeaders);
|
|
468
|
+
if (!response.body) {
|
|
469
|
+
res.end();
|
|
470
|
+
appendHistory(historyPath, {
|
|
471
|
+
t: Date.now(),
|
|
472
|
+
ok: true,
|
|
473
|
+
kind: "x402_inference",
|
|
474
|
+
net: paymentNetwork,
|
|
475
|
+
from: paymentFrom,
|
|
476
|
+
to: payment?.payTo,
|
|
477
|
+
tx: txSig,
|
|
478
|
+
amount,
|
|
479
|
+
token: "USDC",
|
|
480
|
+
ms: Date.now() - startMs
|
|
481
|
+
});
|
|
482
|
+
return true;
|
|
483
|
+
}
|
|
484
|
+
const ct = response.headers.get("content-type") || "";
|
|
485
|
+
const sse = isChatCompletion && ct.includes("text/event-stream") ? createSseTracker() : null;
|
|
486
|
+
const reader = response.body.getReader();
|
|
487
|
+
const decoder = new TextDecoder();
|
|
488
|
+
try {
|
|
489
|
+
while (true) {
|
|
490
|
+
const { done, value } = await reader.read();
|
|
491
|
+
if (done) break;
|
|
492
|
+
res.write(value);
|
|
493
|
+
sse?.push(decoder.decode(value, { stream: true }));
|
|
494
|
+
}
|
|
495
|
+
} finally {
|
|
496
|
+
reader.releaseLock();
|
|
497
|
+
}
|
|
498
|
+
res.end();
|
|
499
|
+
const durationMs = Date.now() - startMs;
|
|
500
|
+
if (sse?.lastData) try {
|
|
501
|
+
const parsed = JSON.parse(sse.lastData);
|
|
502
|
+
const usage = parsed.usage;
|
|
503
|
+
const model = parsed.model ?? "";
|
|
504
|
+
appendHistory(historyPath, {
|
|
505
|
+
t: Date.now(),
|
|
506
|
+
ok: true,
|
|
507
|
+
kind: "x402_inference",
|
|
508
|
+
net: paymentNetwork,
|
|
509
|
+
from: paymentFrom,
|
|
510
|
+
to: payment?.payTo,
|
|
511
|
+
tx: txSig,
|
|
512
|
+
amount,
|
|
513
|
+
token: "USDC",
|
|
514
|
+
provider: allModels.find((m) => m.id === model || `${m.provider}/${m.id}` === model)?.provider,
|
|
515
|
+
model,
|
|
516
|
+
inputTokens: usage?.prompt_tokens ?? 0,
|
|
517
|
+
outputTokens: usage?.completion_tokens ?? 0,
|
|
518
|
+
reasoningTokens: usage?.completion_tokens_details?.reasoning_tokens,
|
|
519
|
+
cacheRead: usage?.prompt_tokens_details?.cached_tokens,
|
|
520
|
+
cacheWrite: usage?.prompt_tokens_details?.cache_creation_input_tokens,
|
|
521
|
+
thinking: thinkingMode,
|
|
522
|
+
ms: durationMs
|
|
523
|
+
});
|
|
524
|
+
} catch {
|
|
525
|
+
appendHistory(historyPath, {
|
|
526
|
+
t: Date.now(),
|
|
527
|
+
ok: true,
|
|
528
|
+
kind: "x402_inference",
|
|
529
|
+
net: paymentNetwork,
|
|
530
|
+
from: paymentFrom,
|
|
531
|
+
to: payment?.payTo,
|
|
532
|
+
tx: txSig,
|
|
533
|
+
amount,
|
|
534
|
+
token: "USDC",
|
|
535
|
+
ms: durationMs
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
else appendHistory(historyPath, {
|
|
539
|
+
t: Date.now(),
|
|
540
|
+
ok: true,
|
|
541
|
+
kind: "x402_inference",
|
|
542
|
+
net: paymentNetwork,
|
|
543
|
+
from: paymentFrom,
|
|
544
|
+
to: payment?.payTo,
|
|
545
|
+
tx: txSig,
|
|
546
|
+
amount,
|
|
547
|
+
token: "USDC",
|
|
548
|
+
ms: durationMs
|
|
549
|
+
});
|
|
550
|
+
return true;
|
|
551
|
+
} catch (err) {
|
|
552
|
+
const msg = String(err);
|
|
553
|
+
logger.error(`x402: fetch threw: ${msg}`);
|
|
554
|
+
getX402Proxy()?.shiftPayment();
|
|
555
|
+
appendHistory(historyPath, {
|
|
556
|
+
t: Date.now(),
|
|
557
|
+
ok: false,
|
|
558
|
+
kind: "x402_inference",
|
|
559
|
+
net: SOL_MAINNET,
|
|
560
|
+
from: walletAddress,
|
|
561
|
+
ms: Date.now() - startMs,
|
|
562
|
+
error: msg.substring(0, 200)
|
|
563
|
+
});
|
|
564
|
+
let userMessage;
|
|
565
|
+
if (msg.includes("Simulation failed") || msg.includes("simulation")) userMessage = `Insufficient USDC or SOL in wallet ${walletAddress}. Fund it with USDC and SOL to pay for inference.`;
|
|
566
|
+
else if (msg.includes("Failed to create payment")) userMessage = `x402 payment creation failed: ${msg}. Wallet: ${walletAddress}`;
|
|
567
|
+
else userMessage = `x402 request failed: ${msg}`;
|
|
568
|
+
if (!res.headersSent) {
|
|
569
|
+
res.writeHead(402, { "Content-Type": "application/json" });
|
|
570
|
+
res.end(JSON.stringify({ error: {
|
|
571
|
+
message: userMessage,
|
|
572
|
+
type: "x402_payment_error",
|
|
573
|
+
code: "payment_failed"
|
|
574
|
+
} }));
|
|
575
|
+
}
|
|
576
|
+
return true;
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
function createSseTracker() {
|
|
581
|
+
let residual = "";
|
|
582
|
+
let lastDataLine = "";
|
|
583
|
+
return {
|
|
584
|
+
push(text) {
|
|
585
|
+
const lines = (residual + text).split("\n");
|
|
586
|
+
residual = lines.pop() ?? "";
|
|
587
|
+
for (const line of lines) if (line.startsWith("data: ") && line !== "data: [DONE]") lastDataLine = line.slice(6);
|
|
588
|
+
},
|
|
589
|
+
get lastData() {
|
|
590
|
+
return lastDataLine;
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
async function closeMppSession(handler) {
|
|
595
|
+
try {
|
|
596
|
+
await handler.close();
|
|
597
|
+
} catch {}
|
|
598
|
+
}
|
|
599
|
+
async function handleMppRequest(opts) {
|
|
600
|
+
const { req, res, upstreamUrl, requestInit, walletAddress, historyPath, logger, allModels, thinkingMode, wantsStreaming, startMs, evmKey, mppSessionBudget } = opts;
|
|
601
|
+
const mpp = await createMppProxyHandler({
|
|
602
|
+
evmKey,
|
|
603
|
+
maxDeposit: mppSessionBudget
|
|
604
|
+
});
|
|
605
|
+
try {
|
|
606
|
+
if (wantsStreaming) {
|
|
607
|
+
res.writeHead(200, {
|
|
608
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
609
|
+
"Cache-Control": "no-cache, no-transform",
|
|
610
|
+
Connection: "keep-alive"
|
|
611
|
+
});
|
|
612
|
+
const sse = createSseTracker();
|
|
613
|
+
const stream = await mpp.sse(upstreamUrl, requestInit);
|
|
614
|
+
for await (const chunk of stream) {
|
|
615
|
+
const text = String(chunk);
|
|
616
|
+
res.write(text);
|
|
617
|
+
sse.push(text);
|
|
618
|
+
}
|
|
619
|
+
res.end();
|
|
620
|
+
mpp.shiftPayment();
|
|
621
|
+
const payment = mpp.shiftPayment();
|
|
622
|
+
appendInferenceHistory({
|
|
623
|
+
historyPath,
|
|
624
|
+
allModels,
|
|
625
|
+
walletAddress,
|
|
626
|
+
paymentNetwork: payment?.network ?? "eip155:4217",
|
|
627
|
+
paymentTo: void 0,
|
|
628
|
+
tx: payment?.receipt?.reference ?? payment?.channelId,
|
|
629
|
+
amount: parseMppAmount(payment?.amount),
|
|
630
|
+
thinkingMode,
|
|
631
|
+
lastDataLine: sse.lastData,
|
|
632
|
+
durationMs: Date.now() - startMs
|
|
633
|
+
});
|
|
634
|
+
return true;
|
|
635
|
+
}
|
|
636
|
+
const response = await mpp.fetch(upstreamUrl, requestInit);
|
|
637
|
+
const tx = extractTxSignature(response);
|
|
638
|
+
if (response.status === 402) {
|
|
639
|
+
const responseBody = await response.text();
|
|
640
|
+
logger.error(`mpp: payment failed, raw response: ${responseBody}`);
|
|
641
|
+
appendHistory(historyPath, {
|
|
642
|
+
t: Date.now(),
|
|
643
|
+
ok: false,
|
|
644
|
+
kind: "x402_inference",
|
|
645
|
+
net: TEMPO_NETWORK,
|
|
646
|
+
from: walletAddress,
|
|
647
|
+
ms: Date.now() - startMs,
|
|
648
|
+
error: "payment_required"
|
|
649
|
+
});
|
|
650
|
+
res.writeHead(402, { "Content-Type": "application/json" });
|
|
651
|
+
res.end(JSON.stringify({ error: {
|
|
652
|
+
message: `MPP payment failed: ${responseBody.substring(0, 200) || "unknown error"}. Wallet: ${walletAddress}`,
|
|
653
|
+
type: "mpp_payment_error",
|
|
654
|
+
code: "payment_failed"
|
|
655
|
+
} }));
|
|
656
|
+
return true;
|
|
657
|
+
}
|
|
658
|
+
const resHeaders = {};
|
|
659
|
+
for (const [key, value] of response.headers.entries()) resHeaders[key] = value;
|
|
660
|
+
res.writeHead(response.status, resHeaders);
|
|
661
|
+
const responseBody = await response.text();
|
|
662
|
+
res.end(responseBody);
|
|
663
|
+
const payment = mpp.shiftPayment();
|
|
664
|
+
appendInferenceHistory({
|
|
665
|
+
historyPath,
|
|
666
|
+
allModels,
|
|
667
|
+
walletAddress,
|
|
668
|
+
paymentNetwork: payment?.network ?? "eip155:4217",
|
|
669
|
+
paymentTo: void 0,
|
|
670
|
+
tx: tx ?? payment?.receipt?.reference,
|
|
671
|
+
amount: parseMppAmount(payment?.amount),
|
|
672
|
+
thinkingMode,
|
|
673
|
+
lastDataLine: responseBody,
|
|
674
|
+
durationMs: Date.now() - startMs
|
|
675
|
+
});
|
|
676
|
+
return true;
|
|
677
|
+
} catch (err) {
|
|
678
|
+
logger.error(`mpp: fetch threw: ${String(err)}`);
|
|
679
|
+
appendHistory(historyPath, {
|
|
680
|
+
t: Date.now(),
|
|
681
|
+
ok: false,
|
|
682
|
+
kind: "x402_inference",
|
|
683
|
+
net: TEMPO_NETWORK,
|
|
684
|
+
from: walletAddress,
|
|
685
|
+
ms: Date.now() - startMs,
|
|
686
|
+
error: String(err).substring(0, 200)
|
|
687
|
+
});
|
|
688
|
+
if (!res.headersSent) {
|
|
689
|
+
res.writeHead(402, { "Content-Type": "application/json" });
|
|
690
|
+
res.end(JSON.stringify({ error: {
|
|
691
|
+
message: `MPP request failed: ${String(err)}`,
|
|
692
|
+
type: "mpp_payment_error",
|
|
693
|
+
code: "payment_failed"
|
|
694
|
+
} }));
|
|
695
|
+
}
|
|
696
|
+
return true;
|
|
697
|
+
} finally {
|
|
698
|
+
await closeMppSession(mpp);
|
|
699
|
+
if (!res.writableEnded) res.end();
|
|
700
|
+
req.resume();
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
function appendInferenceHistory(opts) {
|
|
704
|
+
const { historyPath, allModels, walletAddress, paymentNetwork, paymentTo, tx, amount, thinkingMode, lastDataLine, durationMs } = opts;
|
|
705
|
+
try {
|
|
706
|
+
const parsed = JSON.parse(lastDataLine);
|
|
707
|
+
const usage = parsed.usage;
|
|
708
|
+
const model = parsed.model ?? "";
|
|
709
|
+
appendHistory(historyPath, {
|
|
710
|
+
t: Date.now(),
|
|
711
|
+
ok: true,
|
|
712
|
+
kind: "x402_inference",
|
|
713
|
+
net: paymentNetwork,
|
|
714
|
+
from: walletAddress,
|
|
715
|
+
to: paymentTo,
|
|
716
|
+
tx,
|
|
717
|
+
amount,
|
|
718
|
+
token: "USDC",
|
|
719
|
+
provider: allModels.find((entry) => entry.id === model || `${entry.provider}/${entry.id}` === model)?.provider,
|
|
720
|
+
model,
|
|
721
|
+
inputTokens: usage?.prompt_tokens ?? 0,
|
|
722
|
+
outputTokens: usage?.completion_tokens ?? 0,
|
|
723
|
+
reasoningTokens: usage?.completion_tokens_details?.reasoning_tokens,
|
|
724
|
+
cacheRead: usage?.prompt_tokens_details?.cached_tokens,
|
|
725
|
+
cacheWrite: usage?.prompt_tokens_details?.cache_creation_input_tokens,
|
|
726
|
+
thinking: thinkingMode,
|
|
727
|
+
ms: durationMs
|
|
728
|
+
});
|
|
729
|
+
} catch {
|
|
730
|
+
appendHistory(historyPath, {
|
|
731
|
+
t: Date.now(),
|
|
732
|
+
ok: true,
|
|
733
|
+
kind: "x402_inference",
|
|
734
|
+
net: paymentNetwork,
|
|
735
|
+
from: walletAddress,
|
|
736
|
+
to: paymentTo,
|
|
737
|
+
tx,
|
|
738
|
+
amount,
|
|
739
|
+
token: "USDC",
|
|
740
|
+
ms: durationMs
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
//#endregion
|
|
745
|
+
//#region src/commands/serve.ts
|
|
746
|
+
async function resolveWalletForServe(flags) {
|
|
747
|
+
let wallet = resolveWallet({
|
|
748
|
+
evmKey: flags.evmKey,
|
|
749
|
+
solanaKey: flags.solanaKey
|
|
750
|
+
});
|
|
751
|
+
if (wallet.source !== "none") return wallet;
|
|
752
|
+
const { runSetup } = await import("../setup-EX1_teNg.js");
|
|
753
|
+
if (isTTY()) {
|
|
754
|
+
dim(" No wallet found. Let's set one up first.\n");
|
|
755
|
+
await runSetup();
|
|
756
|
+
} else {
|
|
757
|
+
dim("No wallet found. Auto-generating...");
|
|
758
|
+
await runSetup({ nonInteractive: true });
|
|
759
|
+
}
|
|
760
|
+
wallet = resolveWallet({
|
|
761
|
+
evmKey: flags.evmKey,
|
|
762
|
+
solanaKey: flags.solanaKey
|
|
763
|
+
});
|
|
764
|
+
if (wallet.source === "none") {
|
|
765
|
+
error("Wallet setup failed. Run: $ npx x402-proxy setup");
|
|
766
|
+
process.exit(1);
|
|
767
|
+
}
|
|
768
|
+
return wallet;
|
|
769
|
+
}
|
|
770
|
+
async function detectPreferredNetwork(wallet) {
|
|
771
|
+
if (!wallet.evmAddress || !wallet.solanaAddress) return;
|
|
772
|
+
const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
|
|
773
|
+
const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
|
|
774
|
+
const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
|
|
775
|
+
if (evmUsdc > solUsdc) return "base";
|
|
776
|
+
if (solUsdc > evmUsdc) return "solana";
|
|
777
|
+
}
|
|
778
|
+
function walletAddressForNetwork(wallet, network) {
|
|
779
|
+
return addressForNetwork(wallet.evmAddress ?? null, wallet.solanaAddress ?? null, network);
|
|
780
|
+
}
|
|
781
|
+
function createRequestHandler(routeHandler) {
|
|
782
|
+
return (req, res) => {
|
|
783
|
+
routeHandler(req, res).then((handled) => {
|
|
784
|
+
if (!handled && !res.headersSent) {
|
|
785
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
786
|
+
res.end(JSON.stringify({ error: {
|
|
787
|
+
message: "Not found",
|
|
788
|
+
code: "not_found"
|
|
789
|
+
} }));
|
|
790
|
+
}
|
|
791
|
+
}).catch((err) => {
|
|
792
|
+
if (!res.headersSent) res.writeHead(500, { "Content-Type": "application/json" });
|
|
793
|
+
res.end(JSON.stringify({ error: {
|
|
794
|
+
message: err instanceof Error ? err.message : String(err),
|
|
795
|
+
code: "proxy_failed"
|
|
796
|
+
} }));
|
|
797
|
+
});
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
async function startServeServer(options = {}) {
|
|
801
|
+
const config = loadConfig();
|
|
802
|
+
const wallet = await resolveWalletForServe(options);
|
|
803
|
+
const resolvedProtocol = resolveProtocol(options.protocol ?? config?.preferredProtocol);
|
|
804
|
+
const configuredMppBudget = resolveMppSessionBudget(config?.mppSessionBudget);
|
|
805
|
+
const preferredNetwork = config?.defaultNetwork ?? await detectPreferredNetwork(wallet);
|
|
806
|
+
const upstreamUrl = options.upstreamUrl ?? "https://surf.cascade.fyi/api/v1/inference";
|
|
807
|
+
const x402Proxy = createX402ProxyHandler({ client: await buildX402Client(wallet, {
|
|
808
|
+
preferredNetwork: preferredNetwork || void 0,
|
|
809
|
+
network: options.network,
|
|
810
|
+
spendLimitDaily: config?.spendLimitDaily,
|
|
811
|
+
spendLimitPerTx: config?.spendLimitPerTx
|
|
812
|
+
}) });
|
|
813
|
+
const { providers, models } = resolveProviders({
|
|
814
|
+
protocol: resolvedProtocol,
|
|
815
|
+
mppSessionBudget: configuredMppBudget,
|
|
816
|
+
providers: { [DEFAULT_SURF_PROVIDER_ID]: {
|
|
817
|
+
baseUrl: "/",
|
|
818
|
+
upstreamUrl,
|
|
819
|
+
protocol: resolvedProtocol,
|
|
820
|
+
mppSessionBudget: configuredMppBudget
|
|
821
|
+
} }
|
|
822
|
+
});
|
|
823
|
+
const routeHandler = createInferenceProxyRouteHandler({
|
|
824
|
+
providers,
|
|
825
|
+
getX402Proxy: () => x402Proxy,
|
|
826
|
+
getWalletAddress: () => wallet.solanaAddress ?? wallet.evmAddress ?? null,
|
|
827
|
+
getWalletAddressForNetwork: (network) => walletAddressForNetwork(wallet, network),
|
|
828
|
+
getEvmKey: () => wallet.evmKey ?? null,
|
|
829
|
+
historyPath: getHistoryPath(),
|
|
830
|
+
allModels: models,
|
|
831
|
+
logger: {
|
|
832
|
+
info: (msg) => {
|
|
833
|
+
if (!options.quiet) dim(msg);
|
|
834
|
+
},
|
|
835
|
+
error: (msg) => {
|
|
836
|
+
if (!options.quiet) error(msg);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
const server = http.createServer(createRequestHandler(routeHandler));
|
|
841
|
+
server.on("clientError", (err, socket) => {
|
|
842
|
+
socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
843
|
+
if (!options.quiet) error(`client error: ${err.message}`);
|
|
844
|
+
});
|
|
845
|
+
server.listen(options.port ?? 0, "127.0.0.1");
|
|
846
|
+
await once(server, "listening");
|
|
847
|
+
const address = server.address();
|
|
848
|
+
const port = address && typeof address === "object" && typeof address.port === "number" ? address.port : 0;
|
|
849
|
+
if (!options.quiet) {
|
|
850
|
+
success(`Proxy listening on http://127.0.0.1:${port}`);
|
|
851
|
+
info(`Upstream: ${upstreamUrl}`);
|
|
852
|
+
info(`Protocol: ${resolvedProtocol}`);
|
|
853
|
+
}
|
|
854
|
+
return {
|
|
855
|
+
server,
|
|
856
|
+
port,
|
|
857
|
+
close: async () => {
|
|
858
|
+
if (!server.listening) return;
|
|
859
|
+
server.close();
|
|
860
|
+
await once(server, "close");
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
function waitForSignal() {
|
|
865
|
+
return new Promise((resolve) => {
|
|
866
|
+
const onSignal = (signal) => {
|
|
867
|
+
process.off("SIGINT", onSigInt);
|
|
868
|
+
process.off("SIGTERM", onSigTerm);
|
|
869
|
+
resolve(signal);
|
|
870
|
+
};
|
|
871
|
+
const onSigInt = () => onSignal("SIGINT");
|
|
872
|
+
const onSigTerm = () => onSignal("SIGTERM");
|
|
873
|
+
process.on("SIGINT", onSigInt);
|
|
874
|
+
process.on("SIGTERM", onSigTerm);
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
const serveCommand = buildCommand({
|
|
878
|
+
docs: {
|
|
879
|
+
brief: "Start a local paid inference proxy",
|
|
880
|
+
fullDescription: `Start a local HTTP proxy that forwards inference requests upstream and auto-pays x402 or MPP 402 challenges.
|
|
881
|
+
|
|
882
|
+
Examples:
|
|
883
|
+
$ x402-proxy serve
|
|
884
|
+
$ x402-proxy serve --port 8402
|
|
885
|
+
$ x402-proxy serve https://surf.cascade.fyi/api/v1/inference --protocol mpp`
|
|
886
|
+
},
|
|
887
|
+
parameters: {
|
|
888
|
+
flags: {
|
|
889
|
+
port: {
|
|
890
|
+
kind: "parsed",
|
|
891
|
+
brief: "Listen port (0 = ephemeral)",
|
|
892
|
+
parse: String,
|
|
893
|
+
default: "0"
|
|
894
|
+
},
|
|
895
|
+
protocol: {
|
|
896
|
+
kind: "parsed",
|
|
897
|
+
brief: "Payment protocol (x402, mpp, auto)",
|
|
898
|
+
parse: String,
|
|
899
|
+
optional: true
|
|
900
|
+
},
|
|
901
|
+
network: {
|
|
902
|
+
kind: "parsed",
|
|
903
|
+
brief: "Preferred or required network (base, solana, tempo)",
|
|
904
|
+
parse: String,
|
|
905
|
+
optional: true
|
|
906
|
+
},
|
|
907
|
+
evmKey: {
|
|
908
|
+
kind: "parsed",
|
|
909
|
+
brief: "EVM private key (hex)",
|
|
910
|
+
parse: String,
|
|
911
|
+
optional: true
|
|
912
|
+
},
|
|
913
|
+
solanaKey: {
|
|
914
|
+
kind: "parsed",
|
|
915
|
+
brief: "Solana private key (base58)",
|
|
916
|
+
parse: String,
|
|
917
|
+
optional: true
|
|
918
|
+
}
|
|
919
|
+
},
|
|
920
|
+
positional: {
|
|
921
|
+
kind: "tuple",
|
|
922
|
+
parameters: [{
|
|
923
|
+
brief: "Upstream inference URL",
|
|
924
|
+
parse: String,
|
|
925
|
+
optional: true
|
|
926
|
+
}]
|
|
927
|
+
}
|
|
928
|
+
},
|
|
929
|
+
async func(flags, upstreamUrl) {
|
|
930
|
+
const started = await startServeServer({
|
|
931
|
+
upstreamUrl,
|
|
932
|
+
port: Number(flags.port),
|
|
933
|
+
protocol: flags.protocol,
|
|
934
|
+
network: flags.network,
|
|
935
|
+
evmKey: flags.evmKey,
|
|
936
|
+
solanaKey: flags.solanaKey
|
|
937
|
+
});
|
|
938
|
+
const signal = await waitForSignal();
|
|
939
|
+
dim(`Received ${signal}, shutting down...`);
|
|
940
|
+
await started.close();
|
|
941
|
+
process.exit(signal === "SIGINT" ? 130 : 143);
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
//#endregion
|
|
945
|
+
//#region src/commands/claude.ts
|
|
946
|
+
const DEFAULT_MODEL = "minimax/minimax-m2.7";
|
|
947
|
+
function normalizeClaudeArgs(args) {
|
|
948
|
+
return args[0] === "--" ? args.slice(1) : args;
|
|
949
|
+
}
|
|
950
|
+
const claudeCommand = buildCommand({
|
|
951
|
+
docs: {
|
|
952
|
+
brief: "Run Claude Code through a paid local proxy",
|
|
953
|
+
fullDescription: `Start a local x402-proxy server and launch Claude Code with ANTHROPIC_BASE_URL pointed at it.
|
|
954
|
+
|
|
955
|
+
Examples:
|
|
956
|
+
$ x402-proxy claude
|
|
957
|
+
$ x402-proxy claude --model z-ai/glm-5
|
|
958
|
+
$ x402-proxy claude -- --print "explain this codebase"`
|
|
959
|
+
},
|
|
960
|
+
parameters: {
|
|
961
|
+
flags: {
|
|
962
|
+
model: {
|
|
963
|
+
kind: "parsed",
|
|
964
|
+
brief: "Model to use (default: minimax/minimax-m2.7)",
|
|
965
|
+
parse: String,
|
|
966
|
+
default: DEFAULT_MODEL
|
|
967
|
+
},
|
|
968
|
+
upstream: {
|
|
969
|
+
kind: "parsed",
|
|
970
|
+
brief: "Upstream inference URL",
|
|
971
|
+
parse: String,
|
|
972
|
+
optional: true
|
|
973
|
+
},
|
|
974
|
+
protocol: {
|
|
975
|
+
kind: "parsed",
|
|
976
|
+
brief: "Payment protocol (x402, mpp, auto)",
|
|
977
|
+
parse: String,
|
|
978
|
+
optional: true
|
|
979
|
+
},
|
|
980
|
+
network: {
|
|
981
|
+
kind: "parsed",
|
|
982
|
+
brief: "Preferred or required network (base, solana, tempo)",
|
|
983
|
+
parse: String,
|
|
984
|
+
optional: true
|
|
985
|
+
},
|
|
986
|
+
port: {
|
|
987
|
+
kind: "parsed",
|
|
988
|
+
brief: "Proxy listen port (0 = ephemeral)",
|
|
989
|
+
parse: String,
|
|
990
|
+
default: "0"
|
|
991
|
+
},
|
|
992
|
+
evmKey: {
|
|
993
|
+
kind: "parsed",
|
|
994
|
+
brief: "EVM private key (hex)",
|
|
995
|
+
parse: String,
|
|
996
|
+
optional: true
|
|
997
|
+
},
|
|
998
|
+
solanaKey: {
|
|
999
|
+
kind: "parsed",
|
|
1000
|
+
brief: "Solana private key (base58)",
|
|
1001
|
+
parse: String,
|
|
1002
|
+
optional: true
|
|
1003
|
+
}
|
|
1004
|
+
},
|
|
1005
|
+
positional: {
|
|
1006
|
+
kind: "array",
|
|
1007
|
+
parameter: {
|
|
1008
|
+
brief: "Arguments to forward to claude",
|
|
1009
|
+
parse: String
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
},
|
|
1013
|
+
async func(flags, ...rawClaudeArgs) {
|
|
1014
|
+
const started = await startServeServer({
|
|
1015
|
+
upstreamUrl: flags.upstream ?? "https://surf.cascade.fyi/api/v1/inference",
|
|
1016
|
+
port: Number(flags.port),
|
|
1017
|
+
protocol: flags.protocol,
|
|
1018
|
+
network: flags.network,
|
|
1019
|
+
evmKey: flags.evmKey,
|
|
1020
|
+
solanaKey: flags.solanaKey,
|
|
1021
|
+
quiet: false
|
|
1022
|
+
});
|
|
1023
|
+
const child = spawn("claude", normalizeClaudeArgs(rawClaudeArgs), {
|
|
1024
|
+
stdio: "inherit",
|
|
1025
|
+
env: {
|
|
1026
|
+
...process.env,
|
|
1027
|
+
ANTHROPIC_BASE_URL: `http://127.0.0.1:${started.port}`,
|
|
1028
|
+
ANTHROPIC_MODEL: flags.model,
|
|
1029
|
+
ANTHROPIC_CUSTOM_MODEL_OPTION: flags.model
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1032
|
+
const stopProxy = async () => {
|
|
1033
|
+
await started.close().catch(() => {});
|
|
1034
|
+
};
|
|
1035
|
+
const forwardSignal = (signal) => {
|
|
1036
|
+
dim(`Forwarding ${signal} to claude...`);
|
|
1037
|
+
child.kill(signal);
|
|
1038
|
+
};
|
|
1039
|
+
const onSigInt = () => forwardSignal("SIGINT");
|
|
1040
|
+
const onSigTerm = () => forwardSignal("SIGTERM");
|
|
1041
|
+
process.on("SIGINT", onSigInt);
|
|
1042
|
+
process.on("SIGTERM", onSigTerm);
|
|
1043
|
+
child.once("error", async (err) => {
|
|
1044
|
+
process.off("SIGINT", onSigInt);
|
|
1045
|
+
process.off("SIGTERM", onSigTerm);
|
|
1046
|
+
await stopProxy();
|
|
1047
|
+
if (err.code === "ENOENT") error("Claude Code CLI not found. Install: npm i -g @anthropic-ai/claude-code");
|
|
1048
|
+
else error(err instanceof Error ? err.message : String(err));
|
|
1049
|
+
process.exit(1);
|
|
1050
|
+
});
|
|
1051
|
+
child.once("exit", async (code, signal) => {
|
|
1052
|
+
process.off("SIGINT", onSigInt);
|
|
1053
|
+
process.off("SIGTERM", onSigTerm);
|
|
1054
|
+
await stopProxy();
|
|
1055
|
+
if (signal) process.exit(signal === "SIGINT" ? 130 : 143);
|
|
1056
|
+
process.exit(code ?? 1);
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
//#endregion
|
|
14
1061
|
//#region src/commands/config.ts
|
|
15
1062
|
const VALID_KEYS = {
|
|
16
1063
|
defaultNetwork: {
|
|
@@ -161,145 +1208,6 @@ const configUnsetCommand = buildCommand({
|
|
|
161
1208
|
}
|
|
162
1209
|
});
|
|
163
1210
|
//#endregion
|
|
164
|
-
//#region src/handler.ts
|
|
165
|
-
/**
|
|
166
|
-
* Detect which payment protocols a 402 response advertises.
|
|
167
|
-
* - x402: PAYMENT-REQUIRED or X-PAYMENT-REQUIRED header
|
|
168
|
-
* - MPP: WWW-Authenticate header with Payment scheme
|
|
169
|
-
*/
|
|
170
|
-
function detectProtocols(response) {
|
|
171
|
-
const pr = response.headers.get("PAYMENT-REQUIRED") ?? response.headers.get("X-PAYMENT-REQUIRED");
|
|
172
|
-
const wwwAuth = response.headers.get("WWW-Authenticate");
|
|
173
|
-
return {
|
|
174
|
-
x402: !!pr,
|
|
175
|
-
mpp: !!(wwwAuth && /^Payment\b/i.test(wwwAuth.trim()))
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Extract the on-chain transaction signature from an x402 payment response header.
|
|
180
|
-
*/
|
|
181
|
-
function extractTxSignature(response) {
|
|
182
|
-
const x402Header = response.headers.get("PAYMENT-RESPONSE") ?? response.headers.get("X-PAYMENT-RESPONSE");
|
|
183
|
-
if (x402Header) try {
|
|
184
|
-
return decodePaymentResponseHeader(x402Header).transaction ?? void 0;
|
|
185
|
-
} catch {}
|
|
186
|
-
const mppHeader = response.headers.get("Payment-Receipt");
|
|
187
|
-
if (mppHeader) try {
|
|
188
|
-
return JSON.parse(Buffer.from(mppHeader, "base64url").toString()).reference ?? void 0;
|
|
189
|
-
} catch {
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Create an x402 proxy handler that wraps fetch with automatic payment.
|
|
195
|
-
*/
|
|
196
|
-
function createX402ProxyHandler(opts) {
|
|
197
|
-
const { client } = opts;
|
|
198
|
-
const paymentQueue = [];
|
|
199
|
-
client.onAfterPaymentCreation(async (hookCtx) => {
|
|
200
|
-
const raw = hookCtx.selectedRequirements.amount;
|
|
201
|
-
paymentQueue.push({
|
|
202
|
-
protocol: "x402",
|
|
203
|
-
network: hookCtx.selectedRequirements.network,
|
|
204
|
-
payTo: hookCtx.selectedRequirements.payTo,
|
|
205
|
-
amount: raw,
|
|
206
|
-
asset: hookCtx.selectedRequirements.asset
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
return {
|
|
210
|
-
x402Fetch: wrapFetchWithPayment(globalThis.fetch, client),
|
|
211
|
-
shiftPayment: () => paymentQueue.shift()
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
const TEMPO_NETWORK = "eip155:4217";
|
|
215
|
-
/**
|
|
216
|
-
* Create an MPP proxy handler using mppx client.
|
|
217
|
-
* Dynamically imports mppx/client to keep startup fast.
|
|
218
|
-
*/
|
|
219
|
-
async function createMppProxyHandler(opts) {
|
|
220
|
-
const { Mppx, tempo } = await import("mppx/client");
|
|
221
|
-
const { privateKeyToAccount } = await import("viem/accounts");
|
|
222
|
-
const account = privateKeyToAccount(opts.evmKey);
|
|
223
|
-
const maxDeposit = opts.maxDeposit ?? "1";
|
|
224
|
-
const paymentQueue = [];
|
|
225
|
-
let lastChallengeAmount;
|
|
226
|
-
const mppx = Mppx.create({
|
|
227
|
-
methods: [tempo({
|
|
228
|
-
account,
|
|
229
|
-
maxDeposit
|
|
230
|
-
})],
|
|
231
|
-
polyfill: false,
|
|
232
|
-
onChallenge: async (challenge) => {
|
|
233
|
-
const req = challenge.request;
|
|
234
|
-
if (req.amount) lastChallengeAmount = (Number(req.amount) / 10 ** (req.decimals ?? 6)).toString();
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
let session;
|
|
238
|
-
return {
|
|
239
|
-
async fetch(input, init) {
|
|
240
|
-
const response = await mppx.fetch(typeof input === "string" ? input : input.toString(), init);
|
|
241
|
-
const receiptHeader = response.headers.get("Payment-Receipt");
|
|
242
|
-
if (receiptHeader) {
|
|
243
|
-
try {
|
|
244
|
-
const receipt = JSON.parse(Buffer.from(receiptHeader, "base64url").toString());
|
|
245
|
-
paymentQueue.push({
|
|
246
|
-
protocol: "mpp",
|
|
247
|
-
network: TEMPO_NETWORK,
|
|
248
|
-
amount: lastChallengeAmount,
|
|
249
|
-
receipt
|
|
250
|
-
});
|
|
251
|
-
} catch {
|
|
252
|
-
paymentQueue.push({
|
|
253
|
-
protocol: "mpp",
|
|
254
|
-
network: TEMPO_NETWORK,
|
|
255
|
-
amount: lastChallengeAmount
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
lastChallengeAmount = void 0;
|
|
259
|
-
}
|
|
260
|
-
return response;
|
|
261
|
-
},
|
|
262
|
-
async sse(input, init) {
|
|
263
|
-
session ??= tempo.session({
|
|
264
|
-
account,
|
|
265
|
-
maxDeposit
|
|
266
|
-
});
|
|
267
|
-
const url = typeof input === "string" ? input : input.toString();
|
|
268
|
-
const iterable = await session.sse(url, init);
|
|
269
|
-
paymentQueue.push({
|
|
270
|
-
protocol: "mpp",
|
|
271
|
-
network: TEMPO_NETWORK,
|
|
272
|
-
intent: "session"
|
|
273
|
-
});
|
|
274
|
-
return iterable;
|
|
275
|
-
},
|
|
276
|
-
shiftPayment: () => paymentQueue.shift(),
|
|
277
|
-
async close() {
|
|
278
|
-
if (session?.opened) {
|
|
279
|
-
const receipt = await session.close();
|
|
280
|
-
if (receipt) {
|
|
281
|
-
const spentUsdc = receipt.spent ? (Number(receipt.spent) / 1e6).toString() : void 0;
|
|
282
|
-
paymentQueue.push({
|
|
283
|
-
protocol: "mpp",
|
|
284
|
-
network: TEMPO_NETWORK,
|
|
285
|
-
intent: "session",
|
|
286
|
-
amount: spentUsdc,
|
|
287
|
-
channelId: session.channelId ?? void 0,
|
|
288
|
-
receipt: {
|
|
289
|
-
method: receipt.method,
|
|
290
|
-
reference: receipt.reference,
|
|
291
|
-
status: receipt.status,
|
|
292
|
-
timestamp: receipt.timestamp,
|
|
293
|
-
acceptedCumulative: receipt.acceptedCumulative,
|
|
294
|
-
txHash: receipt.txHash
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
//#endregion
|
|
303
1211
|
//#region src/commands/fetch.ts
|
|
304
1212
|
function isStreamingResponse(res) {
|
|
305
1213
|
return (res.headers.get("content-type") ?? "").includes("text/event-stream");
|
|
@@ -398,7 +1306,7 @@ Examples:
|
|
|
398
1306
|
};
|
|
399
1307
|
if (!url) {
|
|
400
1308
|
if (isConfigured()) {
|
|
401
|
-
const { displayStatus } = await import("../status-
|
|
1309
|
+
const { displayStatus } = await import("../status-DlR8yBrK.js");
|
|
402
1310
|
await displayStatus();
|
|
403
1311
|
console.log();
|
|
404
1312
|
console.log(pc.dim(" Commands:"));
|
|
@@ -450,7 +1358,7 @@ Examples:
|
|
|
450
1358
|
process.exit(1);
|
|
451
1359
|
}
|
|
452
1360
|
dim(" No wallet found. Let's set one up first.\n");
|
|
453
|
-
const { runSetup } = await import("../setup-
|
|
1361
|
+
const { runSetup } = await import("../setup-EX1_teNg.js");
|
|
454
1362
|
await runSetup();
|
|
455
1363
|
console.log();
|
|
456
1364
|
wallet = resolveWallet();
|
|
@@ -463,7 +1371,7 @@ Examples:
|
|
|
463
1371
|
verbose(`protocol: ${resolvedProtocol ?? "auto-detect"}, maxDeposit: ${maxDeposit}`);
|
|
464
1372
|
let preferredNetwork = config?.defaultNetwork;
|
|
465
1373
|
if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
|
|
466
|
-
const { fetchAllBalances } = await import("../wallet-
|
|
1374
|
+
const { fetchAllBalances } = await import("../wallet-7XKcknNZ.js");
|
|
467
1375
|
const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
|
|
468
1376
|
const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
|
|
469
1377
|
const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
|
|
@@ -630,7 +1538,7 @@ Examples:
|
|
|
630
1538
|
const hasSolana = accepts.some((a) => a.network.startsWith("solana:"));
|
|
631
1539
|
const hasMpp = detected.mpp;
|
|
632
1540
|
const hasOther = accepts.some((a) => !a.network.startsWith("eip155:") && !a.network.startsWith("solana:"));
|
|
633
|
-
const { fetchAllBalances } = await import("../wallet-
|
|
1541
|
+
const { fetchAllBalances } = await import("../wallet-7XKcknNZ.js");
|
|
634
1542
|
const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
|
|
635
1543
|
const evmUsdc = hasEvm && balances.evm ? Number(balances.evm.usdc) : 0;
|
|
636
1544
|
const solUsdc = hasSolana && balances.sol ? Number(balances.sol.usdc) : 0;
|
|
@@ -806,7 +1714,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
806
1714
|
});
|
|
807
1715
|
if (wallet.source === "none") {
|
|
808
1716
|
dim("No wallet found. Auto-generating...");
|
|
809
|
-
const { runSetup } = await import("../setup-
|
|
1717
|
+
const { runSetup } = await import("../setup-EX1_teNg.js");
|
|
810
1718
|
await runSetup({ nonInteractive: true });
|
|
811
1719
|
const fresh = resolveWallet({
|
|
812
1720
|
evmKey: flags.evmKey,
|
|
@@ -850,7 +1758,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
850
1758
|
async function startX402Proxy() {
|
|
851
1759
|
let preferredNetwork = config?.defaultNetwork;
|
|
852
1760
|
if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
|
|
853
|
-
const { fetchAllBalances } = await import("../wallet-
|
|
1761
|
+
const { fetchAllBalances } = await import("../wallet-7XKcknNZ.js");
|
|
854
1762
|
const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
|
|
855
1763
|
const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
|
|
856
1764
|
const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
|
|
@@ -870,7 +1778,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
870
1778
|
}
|
|
871
1779
|
const remoteClient = new Client({
|
|
872
1780
|
name: "x402-proxy",
|
|
873
|
-
version: "0.
|
|
1781
|
+
version: "0.10.1"
|
|
874
1782
|
});
|
|
875
1783
|
const x402Mcp = new x402MCPClient(remoteClient, x402PaymentClient, {
|
|
876
1784
|
autoPayment: true,
|
|
@@ -908,7 +1816,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
908
1816
|
}
|
|
909
1817
|
const localServer = new Server({
|
|
910
1818
|
name: "x402-proxy",
|
|
911
|
-
version: "0.
|
|
1819
|
+
version: "0.10.1"
|
|
912
1820
|
}, { capabilities: {
|
|
913
1821
|
tools: tools.length > 0 ? {} : void 0,
|
|
914
1822
|
resources: remoteResources.length > 0 ? {} : void 0
|
|
@@ -1003,7 +1911,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
1003
1911
|
}));
|
|
1004
1912
|
const remoteClient = new Client({
|
|
1005
1913
|
name: "x402-proxy",
|
|
1006
|
-
version: "0.
|
|
1914
|
+
version: "0.10.1"
|
|
1007
1915
|
});
|
|
1008
1916
|
await connectTransport(remoteClient);
|
|
1009
1917
|
const mppClient = McpClient.wrap(remoteClient, { methods: wrappedMethods });
|
|
@@ -1018,7 +1926,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
1018
1926
|
}
|
|
1019
1927
|
const localServer = new Server({
|
|
1020
1928
|
name: "x402-proxy",
|
|
1021
|
-
version: "0.
|
|
1929
|
+
version: "0.10.1"
|
|
1022
1930
|
}, { capabilities: {
|
|
1023
1931
|
tools: tools.length > 0 ? {} : void 0,
|
|
1024
1932
|
resources: remoteResources.length > 0 ? {} : void 0
|
|
@@ -1398,6 +2306,8 @@ const configRoutes = buildRouteMap({
|
|
|
1398
2306
|
const app = buildApplication(buildRouteMap({
|
|
1399
2307
|
routes: {
|
|
1400
2308
|
fetch: fetchCommand,
|
|
2309
|
+
serve: serveCommand,
|
|
2310
|
+
claude: claudeCommand,
|
|
1401
2311
|
mcp: buildRouteMap({
|
|
1402
2312
|
routes: {
|
|
1403
2313
|
proxy: mcpCommand,
|
|
@@ -1415,7 +2325,7 @@ const app = buildApplication(buildRouteMap({
|
|
|
1415
2325
|
docs: { brief: "curl for x402 paid APIs" }
|
|
1416
2326
|
}), {
|
|
1417
2327
|
name: "x402-proxy",
|
|
1418
|
-
versionInfo: { currentVersion: "0.
|
|
2328
|
+
versionInfo: { currentVersion: "0.10.1" },
|
|
1419
2329
|
scanner: { caseStyle: "allow-kebab-for-camel" }
|
|
1420
2330
|
});
|
|
1421
2331
|
//#endregion
|
|
@@ -1425,7 +2335,6 @@ function buildContext(process) {
|
|
|
1425
2335
|
}
|
|
1426
2336
|
//#endregion
|
|
1427
2337
|
//#region src/bin/cli.ts
|
|
1428
|
-
process.on("SIGINT", () => process.exit(130));
|
|
1429
2338
|
const rawArgs = process.argv.slice(2);
|
|
1430
2339
|
const args = [];
|
|
1431
2340
|
for (let i = 0; i < rawArgs.length; i++) {
|
|
@@ -1441,6 +2350,8 @@ for (let i = 0; i < rawArgs.length; i++) {
|
|
|
1441
2350
|
process.env.X402_PROXY_CONFIG_DIR_OVERRIDE = dir;
|
|
1442
2351
|
} else args.push(a);
|
|
1443
2352
|
}
|
|
2353
|
+
const topLevelCommand = args[0];
|
|
2354
|
+
if (topLevelCommand !== "serve" && topLevelCommand !== "claude") process.on("SIGINT", () => process.exit(130));
|
|
1444
2355
|
await run(app, args, buildContext(process));
|
|
1445
2356
|
//#endregion
|
|
1446
2357
|
export {};
|