x402-proxy 0.10.3 → 0.10.5
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 +27 -1
- package/dist/bin/cli.js +74 -45
- package/dist/config-B_upkJeK.js +66 -0
- package/dist/config-Be35NM5s.js +3 -0
- package/dist/config-D9wIR3xc.js +91 -0
- package/dist/config-J1m-CWXT.js +27 -0
- package/dist/{derive-CY0En_Y3.js → derive-CL6e8K0Z.js} +1 -78
- package/dist/index.js +29 -2
- package/dist/openclaw/plugin.js +63 -57
- package/dist/{setup-DtKrojW1.js → setup-CNyMLnM-.js} +2 -1
- package/dist/setup-DTIxPe58.js +3 -0
- package/dist/{status-B5oH1nHv.js → status-Dc1dB0ZG.js} +2 -2
- package/dist/status-wknbavnl.js +3 -0
- package/dist/wallet-C9UlV7qi.js +3 -0
- package/dist/{wallet-DqGROXlC.js → wallet-LUl2l8WM.js} +2 -1
- package/package.json +2 -2
- package/dist/setup-EX1_teNg.js +0 -3
- package/dist/status-DlR8yBrK.js +0 -3
- package/dist/wallet-7XKcknNZ.js +0 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.10.5] - 2026-04-01
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- `--debug` flag now works with inference proxy - route.ts debug check was captured at import time (before `cli.ts` sets the env var), so `dbg()` never fired; now evaluates lazily per call
|
|
14
|
+
- `--debug` on `claude` command now shows proxy logs (sets `quiet: false`)
|
|
15
|
+
- MPP `X-Payer-Address` header injection preserves headers as plain objects instead of converting to `Headers` instance, avoiding mppx headers-spreading bug that silently drops `Content-Type` on SSE requests
|
|
16
|
+
|
|
17
|
+
## [0.10.4] - 2026-04-01
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- MPP sessions now reuse a single handler across requests instead of creating a new one per request - eliminates redundant escrow deposits and wasted USDC
|
|
21
|
+
- MPP sessions properly settle on process shutdown (`serve` command and OpenClaw plugin both call `close()` on SIGTERM/stop)
|
|
22
|
+
- MPP channelId persisted to `~/.config/x402-proxy/session.json` for tracking; cleared on session close
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- `--debug` global CLI flag - sets `X402_PROXY_DEBUG=1` for verbose stderr logging of MPP SSE lifecycle, channelId, and proxy routing
|
|
26
|
+
- Debug trace points in inference proxy: upstream routing, SSE start/end, usage stats, errors
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
- Upgraded `mppx` from ^0.4.9 to ^0.5.1
|
|
30
|
+
- MPP requests now send `X-Payer-Address` header so the server can include the payer's existing `channelId` in 402 challenges, enabling cross-restart session recovery
|
|
31
|
+
- Inference proxy no longer accepts `getEvmKey` option - receives pre-built `getMppHandler` instead
|
|
32
|
+
|
|
10
33
|
## [0.10.3] - 2026-04-01
|
|
11
34
|
|
|
12
35
|
### Fixed
|
|
@@ -351,7 +374,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
351
374
|
- `appendHistory` / `readHistory` / `calcSpend` - JSONL transaction history
|
|
352
375
|
- Re-exports from `@x402/fetch`, `@x402/svm`, `@x402/evm`
|
|
353
376
|
|
|
354
|
-
[Unreleased]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.
|
|
377
|
+
[Unreleased]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.5...HEAD
|
|
378
|
+
[0.10.5]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.4...v0.10.5
|
|
379
|
+
[0.10.4]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.3...v0.10.4
|
|
380
|
+
[0.10.3]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.2...v0.10.3
|
|
355
381
|
[0.10.2]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.1...v0.10.2
|
|
356
382
|
[0.10.1]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.0...v0.10.1
|
|
357
383
|
[0.10.0]: https://github.com/cascade-protocol/x402-proxy/compare/v0.9.4...v0.10.0
|
package/dist/bin/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
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-
|
|
3
|
-
import { a as getHistoryPath, c as
|
|
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-LUl2l8WM.js";
|
|
3
|
+
import { a as getHistoryPath, c as isConfigured, d as saveConfig, i as getConfigDirShort, l as loadConfig, u as loadWalletFile } from "../config-D9wIR3xc.js";
|
|
4
|
+
import { n as setupCommand, t as runSetup } from "../setup-CNyMLnM-.js";
|
|
5
|
+
import { n as statusCommand } from "../status-Dc1dB0ZG.js";
|
|
6
6
|
import { dirname, join, normalize, resolve } from "node:path";
|
|
7
7
|
import { buildApplication, buildCommand, buildRouteMap, run } from "@stricli/core";
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
@@ -197,10 +197,12 @@ const TEMPO_NETWORK = "eip155:4217";
|
|
|
197
197
|
async function createMppProxyHandler(opts) {
|
|
198
198
|
const { Mppx, tempo } = await import("mppx/client");
|
|
199
199
|
const { privateKeyToAccount } = await import("viem/accounts");
|
|
200
|
+
const { saveSession, clearSession } = await import("../config-Be35NM5s.js");
|
|
200
201
|
const account = privateKeyToAccount(opts.evmKey);
|
|
201
202
|
const maxDeposit = opts.maxDeposit ?? "1";
|
|
202
203
|
const paymentQueue = [];
|
|
203
204
|
let lastChallengeAmount;
|
|
205
|
+
const debug = process.env.X402_PROXY_DEBUG === "1";
|
|
204
206
|
const mppx = Mppx.create({
|
|
205
207
|
methods: [tempo({
|
|
206
208
|
account,
|
|
@@ -212,10 +214,22 @@ async function createMppProxyHandler(opts) {
|
|
|
212
214
|
if (req.amount) lastChallengeAmount = (Number(req.amount) / 10 ** (req.decimals ?? 6)).toString();
|
|
213
215
|
}
|
|
214
216
|
});
|
|
217
|
+
const payerAddress = account.address;
|
|
218
|
+
function injectPayerHeader(init) {
|
|
219
|
+
const existing = init?.headers instanceof Headers ? Object.fromEntries(init.headers.entries()) : init?.headers ?? {};
|
|
220
|
+
return {
|
|
221
|
+
...init,
|
|
222
|
+
headers: {
|
|
223
|
+
...existing,
|
|
224
|
+
"X-Payer-Address": payerAddress
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
215
228
|
let session;
|
|
229
|
+
let persistedChannelId;
|
|
216
230
|
return {
|
|
217
231
|
async fetch(input, init) {
|
|
218
|
-
const response = await mppx.fetch(typeof input === "string" ? input : input.toString(), init);
|
|
232
|
+
const response = await mppx.fetch(typeof input === "string" ? input : input.toString(), injectPayerHeader(init));
|
|
219
233
|
const receiptHeader = response.headers.get("Payment-Receipt");
|
|
220
234
|
if (receiptHeader) {
|
|
221
235
|
try {
|
|
@@ -243,7 +257,17 @@ async function createMppProxyHandler(opts) {
|
|
|
243
257
|
maxDeposit
|
|
244
258
|
});
|
|
245
259
|
const url = typeof input === "string" ? input : input.toString();
|
|
246
|
-
const iterable = await session.sse(url, init);
|
|
260
|
+
const iterable = await session.sse(url, injectPayerHeader(init));
|
|
261
|
+
if (session.channelId && session.channelId !== persistedChannelId) {
|
|
262
|
+
persistedChannelId = session.channelId;
|
|
263
|
+
if (debug) process.stderr.write(`[x402-proxy] channelId: ${persistedChannelId}\n`);
|
|
264
|
+
try {
|
|
265
|
+
saveSession({
|
|
266
|
+
channelId: session.channelId,
|
|
267
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
268
|
+
});
|
|
269
|
+
} catch {}
|
|
270
|
+
}
|
|
247
271
|
paymentQueue.push({
|
|
248
272
|
protocol: "mpp",
|
|
249
273
|
network: TEMPO_NETWORK,
|
|
@@ -255,6 +279,9 @@ async function createMppProxyHandler(opts) {
|
|
|
255
279
|
async close() {
|
|
256
280
|
if (session?.opened) {
|
|
257
281
|
const receipt = await session.close();
|
|
282
|
+
try {
|
|
283
|
+
clearSession();
|
|
284
|
+
} catch {}
|
|
258
285
|
if (receipt) {
|
|
259
286
|
const spentUsdc = receipt.spent ? (Number(receipt.spent) / 1e6).toString() : void 0;
|
|
260
287
|
paymentQueue.push({
|
|
@@ -298,8 +325,11 @@ function addressForNetwork(evmAddress, solanaAddress, network) {
|
|
|
298
325
|
}
|
|
299
326
|
//#endregion
|
|
300
327
|
//#region src/openclaw/route.ts
|
|
328
|
+
function dbg(msg) {
|
|
329
|
+
if (process.env.X402_PROXY_DEBUG === "1") process.stderr.write(`[x402-proxy] ${msg}\n`);
|
|
330
|
+
}
|
|
301
331
|
function createInferenceProxyRouteHandler(opts) {
|
|
302
|
-
const { providers, getX402Proxy, getWalletAddress, getWalletAddressForNetwork,
|
|
332
|
+
const { providers, getX402Proxy, getMppHandler, getWalletAddress, getWalletAddressForNetwork, historyPath, allModels, logger } = opts;
|
|
303
333
|
const sortedProviders = providers.slice().sort((left, right) => right.baseUrl.length - left.baseUrl.length);
|
|
304
334
|
return async (req, res) => {
|
|
305
335
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
@@ -323,6 +353,7 @@ function createInferenceProxyRouteHandler(opts) {
|
|
|
323
353
|
}
|
|
324
354
|
const pathSuffix = provider.baseUrl === "/" ? url.pathname : url.pathname.slice(provider.baseUrl.length);
|
|
325
355
|
const upstreamUrl = `${provider.upstreamUrl.replace(/\/+$/, "")}${pathSuffix.startsWith("/") ? pathSuffix : `/${pathSuffix}`}${url.search}`;
|
|
356
|
+
dbg(`${req.method} ${url.pathname} -> ${upstreamUrl}`);
|
|
326
357
|
logger.info(`proxy: intercepting ${upstreamUrl.substring(0, 80)}`);
|
|
327
358
|
const chunks = [];
|
|
328
359
|
for await (const chunk of req) chunks.push(Buffer.from(chunk));
|
|
@@ -369,8 +400,8 @@ function createInferenceProxyRouteHandler(opts) {
|
|
|
369
400
|
const useMpp = provider.protocol === "mpp" || provider.protocol === "auto";
|
|
370
401
|
const wantsStreaming = isLlmEndpoint && /"stream"\s*:\s*true/.test(body);
|
|
371
402
|
if (useMpp) {
|
|
372
|
-
const
|
|
373
|
-
if (!
|
|
403
|
+
const mpp = getMppHandler();
|
|
404
|
+
if (!mpp) {
|
|
374
405
|
res.writeHead(503, { "Content-Type": "application/json" });
|
|
375
406
|
res.end(JSON.stringify({ error: {
|
|
376
407
|
message: "MPP inference requires an EVM wallet. Configure X402_PROXY_WALLET_MNEMONIC or X402_PROXY_WALLET_EVM_KEY.",
|
|
@@ -379,7 +410,6 @@ function createInferenceProxyRouteHandler(opts) {
|
|
|
379
410
|
return true;
|
|
380
411
|
}
|
|
381
412
|
return await handleMppRequest({
|
|
382
|
-
req,
|
|
383
413
|
res,
|
|
384
414
|
upstreamUrl,
|
|
385
415
|
requestInit,
|
|
@@ -391,8 +421,7 @@ function createInferenceProxyRouteHandler(opts) {
|
|
|
391
421
|
wantsStreaming,
|
|
392
422
|
isMessagesApi,
|
|
393
423
|
startMs,
|
|
394
|
-
|
|
395
|
-
mppSessionBudget: provider.mppSessionBudget
|
|
424
|
+
mpp
|
|
396
425
|
});
|
|
397
426
|
}
|
|
398
427
|
const proxy = getX402Proxy();
|
|
@@ -629,17 +658,8 @@ function createSseTracker() {
|
|
|
629
658
|
}
|
|
630
659
|
};
|
|
631
660
|
}
|
|
632
|
-
async function closeMppSession(handler) {
|
|
633
|
-
try {
|
|
634
|
-
await handler.close();
|
|
635
|
-
} catch {}
|
|
636
|
-
}
|
|
637
661
|
async function handleMppRequest(opts) {
|
|
638
|
-
const {
|
|
639
|
-
const mpp = await createMppProxyHandler({
|
|
640
|
-
evmKey,
|
|
641
|
-
maxDeposit: mppSessionBudget
|
|
642
|
-
});
|
|
662
|
+
const { res, upstreamUrl, requestInit, walletAddress, historyPath, logger, allModels, thinkingMode, wantsStreaming, isMessagesApi, startMs, mpp } = opts;
|
|
643
663
|
try {
|
|
644
664
|
if (wantsStreaming) {
|
|
645
665
|
res.writeHead(200, {
|
|
@@ -648,7 +668,9 @@ async function handleMppRequest(opts) {
|
|
|
648
668
|
Connection: "keep-alive"
|
|
649
669
|
});
|
|
650
670
|
const sse = createSseTracker();
|
|
671
|
+
dbg(`mpp.sse() calling ${upstreamUrl}`);
|
|
651
672
|
const stream = await mpp.sse(upstreamUrl, requestInit);
|
|
673
|
+
dbg("mpp.sse() resolved, iterating stream");
|
|
652
674
|
if (isMessagesApi) for await (const chunk of stream) {
|
|
653
675
|
const text = String(chunk);
|
|
654
676
|
let eventType = "unknown";
|
|
@@ -666,17 +688,15 @@ async function handleMppRequest(opts) {
|
|
|
666
688
|
}
|
|
667
689
|
res.write("data: [DONE]\n\n");
|
|
668
690
|
}
|
|
691
|
+
dbg(`stream done, ${sse.result ? `${sse.result.model} ${sse.result.inputTokens}+${sse.result.outputTokens}t` : "no usage"}`);
|
|
669
692
|
res.end();
|
|
670
693
|
mpp.shiftPayment();
|
|
671
|
-
const payment = mpp.shiftPayment();
|
|
672
694
|
appendInferenceHistory({
|
|
673
695
|
historyPath,
|
|
674
696
|
allModels,
|
|
675
697
|
walletAddress,
|
|
676
|
-
paymentNetwork:
|
|
698
|
+
paymentNetwork: TEMPO_NETWORK,
|
|
677
699
|
paymentTo: void 0,
|
|
678
|
-
tx: payment?.receipt?.reference ?? payment?.channelId,
|
|
679
|
-
amount: parseMppAmount(payment?.amount),
|
|
680
700
|
thinkingMode,
|
|
681
701
|
usage: sse.result,
|
|
682
702
|
durationMs: Date.now() - startMs
|
|
@@ -722,6 +742,7 @@ async function handleMppRequest(opts) {
|
|
|
722
742
|
});
|
|
723
743
|
return true;
|
|
724
744
|
} catch (err) {
|
|
745
|
+
dbg(`mpp error: ${String(err)}`);
|
|
725
746
|
logger.error(`mpp: fetch threw: ${String(err)}`);
|
|
726
747
|
appendHistory(historyPath, {
|
|
727
748
|
t: Date.now(),
|
|
@@ -733,11 +754,8 @@ async function handleMppRequest(opts) {
|
|
|
733
754
|
error: String(err).substring(0, 200)
|
|
734
755
|
});
|
|
735
756
|
if (!res.headersSent) writeErrorResponse(res, 402, `MPP request failed: ${String(err)}`, "mpp_payment_error", "payment_failed", isMessagesApi);
|
|
757
|
+
else if (!res.writableEnded) res.end();
|
|
736
758
|
return true;
|
|
737
|
-
} finally {
|
|
738
|
-
await closeMppSession(mpp);
|
|
739
|
-
if (!res.writableEnded) res.end();
|
|
740
|
-
req.resume();
|
|
741
759
|
}
|
|
742
760
|
}
|
|
743
761
|
function appendInferenceHistory(opts) {
|
|
@@ -783,7 +801,7 @@ async function resolveWalletForServe(flags) {
|
|
|
783
801
|
solanaKey: flags.solanaKey
|
|
784
802
|
});
|
|
785
803
|
if (wallet.source !== "none") return wallet;
|
|
786
|
-
const { runSetup } = await import("../setup-
|
|
804
|
+
const { runSetup } = await import("../setup-DTIxPe58.js");
|
|
787
805
|
if (isTTY()) {
|
|
788
806
|
dim(" No wallet found. Let's set one up first.\n");
|
|
789
807
|
await runSetup();
|
|
@@ -832,6 +850,7 @@ function createRequestHandler(routeHandler) {
|
|
|
832
850
|
};
|
|
833
851
|
}
|
|
834
852
|
async function startServeServer(options = {}) {
|
|
853
|
+
if (options.debug) options.quiet = false;
|
|
835
854
|
const config = loadConfig();
|
|
836
855
|
const wallet = await resolveWalletForServe(options);
|
|
837
856
|
const resolvedProtocol = resolveProtocol(options.protocol ?? config?.preferredProtocol);
|
|
@@ -844,6 +863,10 @@ async function startServeServer(options = {}) {
|
|
|
844
863
|
spendLimitDaily: config?.spendLimitDaily,
|
|
845
864
|
spendLimitPerTx: config?.spendLimitPerTx
|
|
846
865
|
}) });
|
|
866
|
+
const mppHandler = wallet.evmKey ? await createMppProxyHandler({
|
|
867
|
+
evmKey: wallet.evmKey,
|
|
868
|
+
maxDeposit: configuredMppBudget
|
|
869
|
+
}) : null;
|
|
847
870
|
const { providers, models } = resolveProviders({
|
|
848
871
|
protocol: resolvedProtocol,
|
|
849
872
|
mppSessionBudget: configuredMppBudget,
|
|
@@ -857,9 +880,9 @@ async function startServeServer(options = {}) {
|
|
|
857
880
|
const routeHandler = createInferenceProxyRouteHandler({
|
|
858
881
|
providers,
|
|
859
882
|
getX402Proxy: () => x402Proxy,
|
|
883
|
+
getMppHandler: () => mppHandler,
|
|
860
884
|
getWalletAddress: () => wallet.solanaAddress ?? wallet.evmAddress ?? null,
|
|
861
885
|
getWalletAddressForNetwork: (network) => walletAddressForNetwork(wallet, network),
|
|
862
|
-
getEvmKey: () => wallet.evmKey ?? null,
|
|
863
886
|
historyPath: getHistoryPath(),
|
|
864
887
|
allModels: models,
|
|
865
888
|
logger: {
|
|
@@ -889,6 +912,9 @@ async function startServeServer(options = {}) {
|
|
|
889
912
|
server,
|
|
890
913
|
port,
|
|
891
914
|
close: async () => {
|
|
915
|
+
if (mppHandler) try {
|
|
916
|
+
await mppHandler.close();
|
|
917
|
+
} catch {}
|
|
892
918
|
if (!server.listening) return;
|
|
893
919
|
server.close();
|
|
894
920
|
await once(server, "close");
|
|
@@ -1045,6 +1071,7 @@ Examples:
|
|
|
1045
1071
|
}
|
|
1046
1072
|
},
|
|
1047
1073
|
async func(flags, ...rawClaudeArgs) {
|
|
1074
|
+
const debug = process.env.X402_PROXY_DEBUG === "1";
|
|
1048
1075
|
const started = await startServeServer({
|
|
1049
1076
|
upstreamUrl: flags.upstream ?? "https://surf.cascade.fyi/api/v1/inference",
|
|
1050
1077
|
port: Number(flags.port),
|
|
@@ -1052,7 +1079,8 @@ Examples:
|
|
|
1052
1079
|
network: flags.network,
|
|
1053
1080
|
evmKey: flags.evmKey,
|
|
1054
1081
|
solanaKey: flags.solanaKey,
|
|
1055
|
-
quiet:
|
|
1082
|
+
quiet: !debug,
|
|
1083
|
+
debug
|
|
1056
1084
|
});
|
|
1057
1085
|
const child = spawn("claude", normalizeClaudeArgs(rawClaudeArgs), {
|
|
1058
1086
|
stdio: "inherit",
|
|
@@ -1340,7 +1368,7 @@ Examples:
|
|
|
1340
1368
|
};
|
|
1341
1369
|
if (!url) {
|
|
1342
1370
|
if (isConfigured()) {
|
|
1343
|
-
const { displayStatus } = await import("../status-
|
|
1371
|
+
const { displayStatus } = await import("../status-wknbavnl.js");
|
|
1344
1372
|
await displayStatus();
|
|
1345
1373
|
console.log();
|
|
1346
1374
|
console.log(pc.dim(" Commands:"));
|
|
@@ -1392,7 +1420,7 @@ Examples:
|
|
|
1392
1420
|
process.exit(1);
|
|
1393
1421
|
}
|
|
1394
1422
|
dim(" No wallet found. Let's set one up first.\n");
|
|
1395
|
-
const { runSetup } = await import("../setup-
|
|
1423
|
+
const { runSetup } = await import("../setup-DTIxPe58.js");
|
|
1396
1424
|
await runSetup();
|
|
1397
1425
|
console.log();
|
|
1398
1426
|
wallet = resolveWallet();
|
|
@@ -1405,7 +1433,7 @@ Examples:
|
|
|
1405
1433
|
verbose(`protocol: ${resolvedProtocol ?? "auto-detect"}, maxDeposit: ${maxDeposit}`);
|
|
1406
1434
|
let preferredNetwork = config?.defaultNetwork;
|
|
1407
1435
|
if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
|
|
1408
|
-
const { fetchAllBalances } = await import("../wallet-
|
|
1436
|
+
const { fetchAllBalances } = await import("../wallet-C9UlV7qi.js");
|
|
1409
1437
|
const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
|
|
1410
1438
|
const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
|
|
1411
1439
|
const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
|
|
@@ -1572,7 +1600,7 @@ Examples:
|
|
|
1572
1600
|
const hasSolana = accepts.some((a) => a.network.startsWith("solana:"));
|
|
1573
1601
|
const hasMpp = detected.mpp;
|
|
1574
1602
|
const hasOther = accepts.some((a) => !a.network.startsWith("eip155:") && !a.network.startsWith("solana:"));
|
|
1575
|
-
const { fetchAllBalances } = await import("../wallet-
|
|
1603
|
+
const { fetchAllBalances } = await import("../wallet-C9UlV7qi.js");
|
|
1576
1604
|
const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
|
|
1577
1605
|
const evmUsdc = hasEvm && balances.evm ? Number(balances.evm.usdc) : 0;
|
|
1578
1606
|
const solUsdc = hasSolana && balances.sol ? Number(balances.sol.usdc) : 0;
|
|
@@ -1748,7 +1776,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
1748
1776
|
});
|
|
1749
1777
|
if (wallet.source === "none") {
|
|
1750
1778
|
dim("No wallet found. Auto-generating...");
|
|
1751
|
-
const { runSetup } = await import("../setup-
|
|
1779
|
+
const { runSetup } = await import("../setup-DTIxPe58.js");
|
|
1752
1780
|
await runSetup({ nonInteractive: true });
|
|
1753
1781
|
const fresh = resolveWallet({
|
|
1754
1782
|
evmKey: flags.evmKey,
|
|
@@ -1792,7 +1820,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
1792
1820
|
async function startX402Proxy() {
|
|
1793
1821
|
let preferredNetwork = config?.defaultNetwork;
|
|
1794
1822
|
if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
|
|
1795
|
-
const { fetchAllBalances } = await import("../wallet-
|
|
1823
|
+
const { fetchAllBalances } = await import("../wallet-C9UlV7qi.js");
|
|
1796
1824
|
const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
|
|
1797
1825
|
const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
|
|
1798
1826
|
const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
|
|
@@ -1812,7 +1840,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
1812
1840
|
}
|
|
1813
1841
|
const remoteClient = new Client({
|
|
1814
1842
|
name: "x402-proxy",
|
|
1815
|
-
version: "0.10.
|
|
1843
|
+
version: "0.10.5"
|
|
1816
1844
|
});
|
|
1817
1845
|
const x402Mcp = new x402MCPClient(remoteClient, x402PaymentClient, {
|
|
1818
1846
|
autoPayment: true,
|
|
@@ -1850,7 +1878,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
1850
1878
|
}
|
|
1851
1879
|
const localServer = new Server({
|
|
1852
1880
|
name: "x402-proxy",
|
|
1853
|
-
version: "0.10.
|
|
1881
|
+
version: "0.10.5"
|
|
1854
1882
|
}, { capabilities: {
|
|
1855
1883
|
tools: tools.length > 0 ? {} : void 0,
|
|
1856
1884
|
resources: remoteResources.length > 0 ? {} : void 0
|
|
@@ -1945,7 +1973,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
1945
1973
|
}));
|
|
1946
1974
|
const remoteClient = new Client({
|
|
1947
1975
|
name: "x402-proxy",
|
|
1948
|
-
version: "0.10.
|
|
1976
|
+
version: "0.10.5"
|
|
1949
1977
|
});
|
|
1950
1978
|
await connectTransport(remoteClient);
|
|
1951
1979
|
const mppClient = McpClient.wrap(remoteClient, { methods: wrappedMethods });
|
|
@@ -1960,7 +1988,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
1960
1988
|
}
|
|
1961
1989
|
const localServer = new Server({
|
|
1962
1990
|
name: "x402-proxy",
|
|
1963
|
-
version: "0.10.
|
|
1991
|
+
version: "0.10.5"
|
|
1964
1992
|
}, { capabilities: {
|
|
1965
1993
|
tools: tools.length > 0 ? {} : void 0,
|
|
1966
1994
|
resources: remoteResources.length > 0 ? {} : void 0
|
|
@@ -2359,7 +2387,7 @@ const app = buildApplication(buildRouteMap({
|
|
|
2359
2387
|
docs: { brief: "curl for x402 paid APIs" }
|
|
2360
2388
|
}), {
|
|
2361
2389
|
name: "x402-proxy",
|
|
2362
|
-
versionInfo: { currentVersion: "0.10.
|
|
2390
|
+
versionInfo: { currentVersion: "0.10.5" },
|
|
2363
2391
|
scanner: { caseStyle: "allow-kebab-for-camel" }
|
|
2364
2392
|
});
|
|
2365
2393
|
//#endregion
|
|
@@ -2382,7 +2410,8 @@ for (let i = 0; i < rawArgs.length; i++) {
|
|
|
2382
2410
|
const dir = resolve(a.slice(13));
|
|
2383
2411
|
process.env.XDG_CONFIG_HOME = dir;
|
|
2384
2412
|
process.env.X402_PROXY_CONFIG_DIR_OVERRIDE = dir;
|
|
2385
|
-
} else
|
|
2413
|
+
} else if (a === "--debug") process.env.X402_PROXY_DEBUG = "1";
|
|
2414
|
+
else args.push(a);
|
|
2386
2415
|
}
|
|
2387
2416
|
const topLevelCommand = args[0];
|
|
2388
2417
|
if (topLevelCommand !== "serve" && topLevelCommand !== "claude") process.on("SIGINT", () => process.exit(130));
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import "yaml";
|
|
5
|
+
//#region \0rolldown/runtime.js
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __exportAll = (all, no_symbols) => {
|
|
8
|
+
let target = {};
|
|
9
|
+
for (var name in all) __defProp(target, name, {
|
|
10
|
+
get: all[name],
|
|
11
|
+
enumerable: true
|
|
12
|
+
});
|
|
13
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
14
|
+
return target;
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/lib/config.ts
|
|
18
|
+
var config_exports = /* @__PURE__ */ __exportAll({
|
|
19
|
+
clearSession: () => clearSession,
|
|
20
|
+
ensureConfigDir: () => ensureConfigDir,
|
|
21
|
+
getConfigDir: () => getConfigDir,
|
|
22
|
+
getHistoryPath: () => getHistoryPath,
|
|
23
|
+
getSessionPath: () => getSessionPath,
|
|
24
|
+
getWalletPath: () => getWalletPath,
|
|
25
|
+
loadWalletFile: () => loadWalletFile,
|
|
26
|
+
saveSession: () => saveSession
|
|
27
|
+
});
|
|
28
|
+
const APP_NAME = "x402-proxy";
|
|
29
|
+
function getConfigDir() {
|
|
30
|
+
const xdg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
31
|
+
return path.join(xdg, APP_NAME);
|
|
32
|
+
}
|
|
33
|
+
function getWalletPath() {
|
|
34
|
+
return path.join(getConfigDir(), "wallet.json");
|
|
35
|
+
}
|
|
36
|
+
function getHistoryPath() {
|
|
37
|
+
return path.join(getConfigDir(), "history.jsonl");
|
|
38
|
+
}
|
|
39
|
+
function ensureConfigDir() {
|
|
40
|
+
fs.mkdirSync(getConfigDir(), { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
function loadWalletFile() {
|
|
43
|
+
const p = getWalletPath();
|
|
44
|
+
if (!fs.existsSync(p)) return null;
|
|
45
|
+
try {
|
|
46
|
+
const data = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
47
|
+
if (data.version === 1 && typeof data.mnemonic === "string") return data;
|
|
48
|
+
return null;
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function getSessionPath() {
|
|
54
|
+
return path.join(getConfigDir(), "session.json");
|
|
55
|
+
}
|
|
56
|
+
function saveSession(session) {
|
|
57
|
+
ensureConfigDir();
|
|
58
|
+
fs.writeFileSync(getSessionPath(), JSON.stringify(session, null, 2), "utf-8");
|
|
59
|
+
}
|
|
60
|
+
function clearSession() {
|
|
61
|
+
try {
|
|
62
|
+
fs.unlinkSync(getSessionPath());
|
|
63
|
+
} catch {}
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { getHistoryPath as n, loadWalletFile as r, config_exports as t };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import { parse, stringify } from "yaml";
|
|
6
|
+
//#region src/lib/config.ts
|
|
7
|
+
const APP_NAME = "x402-proxy";
|
|
8
|
+
function getConfigDir() {
|
|
9
|
+
const xdg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
10
|
+
return path.join(xdg, APP_NAME);
|
|
11
|
+
}
|
|
12
|
+
/** Config dir with $HOME replaced by ~ for display */
|
|
13
|
+
function getConfigDirShort() {
|
|
14
|
+
const dir = getConfigDir();
|
|
15
|
+
const home = os.homedir();
|
|
16
|
+
return dir.startsWith(home) ? `~${dir.slice(home.length)}` : dir;
|
|
17
|
+
}
|
|
18
|
+
function getWalletPath() {
|
|
19
|
+
return path.join(getConfigDir(), "wallet.json");
|
|
20
|
+
}
|
|
21
|
+
function getHistoryPath() {
|
|
22
|
+
return path.join(getConfigDir(), "history.jsonl");
|
|
23
|
+
}
|
|
24
|
+
function ensureConfigDir() {
|
|
25
|
+
fs.mkdirSync(getConfigDir(), { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
function loadWalletFile() {
|
|
28
|
+
const p = getWalletPath();
|
|
29
|
+
if (!fs.existsSync(p)) return null;
|
|
30
|
+
try {
|
|
31
|
+
const data = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
32
|
+
if (data.version === 1 && typeof data.mnemonic === "string") return data;
|
|
33
|
+
return null;
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function saveWalletFile(wallet) {
|
|
39
|
+
ensureConfigDir();
|
|
40
|
+
fs.writeFileSync(getWalletPath(), JSON.stringify(wallet, null, 2), {
|
|
41
|
+
mode: 384,
|
|
42
|
+
encoding: "utf-8"
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function loadConfig() {
|
|
46
|
+
const dir = getConfigDir();
|
|
47
|
+
for (const name of [
|
|
48
|
+
"config.yaml",
|
|
49
|
+
"config.yml",
|
|
50
|
+
"config.jsonc",
|
|
51
|
+
"config.json"
|
|
52
|
+
]) {
|
|
53
|
+
const p = path.join(dir, name);
|
|
54
|
+
if (!fs.existsSync(p)) continue;
|
|
55
|
+
try {
|
|
56
|
+
const raw = fs.readFileSync(p, "utf-8");
|
|
57
|
+
if (name.endsWith(".yaml") || name.endsWith(".yml")) return parse(raw);
|
|
58
|
+
if (name.endsWith(".jsonc")) {
|
|
59
|
+
const stripped = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
60
|
+
return JSON.parse(stripped);
|
|
61
|
+
}
|
|
62
|
+
return JSON.parse(raw);
|
|
63
|
+
} catch {}
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
function saveConfig(config) {
|
|
68
|
+
ensureConfigDir();
|
|
69
|
+
const p = path.join(getConfigDir(), "config.yaml");
|
|
70
|
+
fs.writeFileSync(p, stringify(config), "utf-8");
|
|
71
|
+
}
|
|
72
|
+
function getSessionPath() {
|
|
73
|
+
return path.join(getConfigDir(), "session.json");
|
|
74
|
+
}
|
|
75
|
+
function saveSession(session) {
|
|
76
|
+
ensureConfigDir();
|
|
77
|
+
fs.writeFileSync(getSessionPath(), JSON.stringify(session, null, 2), "utf-8");
|
|
78
|
+
}
|
|
79
|
+
function clearSession() {
|
|
80
|
+
try {
|
|
81
|
+
fs.unlinkSync(getSessionPath());
|
|
82
|
+
} catch {}
|
|
83
|
+
}
|
|
84
|
+
function isConfigured() {
|
|
85
|
+
if (process.env.X402_PROXY_WALLET_MNEMONIC) return true;
|
|
86
|
+
if (process.env.X402_PROXY_WALLET_EVM_KEY) return true;
|
|
87
|
+
if (process.env.X402_PROXY_WALLET_SOLANA_KEY) return true;
|
|
88
|
+
return loadWalletFile() !== null;
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
export { getHistoryPath as a, isConfigured as c, saveConfig as d, saveSession as f, getConfigDirShort as i, loadConfig as l, ensureConfigDir as n, getSessionPath as o, saveWalletFile as p, getConfigDir as r, getWalletPath as s, clearSession as t, loadWalletFile as u };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import "yaml";
|
|
5
|
+
//#region src/lib/config.ts
|
|
6
|
+
const APP_NAME = "x402-proxy";
|
|
7
|
+
function getConfigDir() {
|
|
8
|
+
const xdg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
9
|
+
return path.join(xdg, APP_NAME);
|
|
10
|
+
}
|
|
11
|
+
function ensureConfigDir() {
|
|
12
|
+
fs.mkdirSync(getConfigDir(), { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
function getSessionPath() {
|
|
15
|
+
return path.join(getConfigDir(), "session.json");
|
|
16
|
+
}
|
|
17
|
+
function saveSession(session) {
|
|
18
|
+
ensureConfigDir();
|
|
19
|
+
fs.writeFileSync(getSessionPath(), JSON.stringify(session, null, 2), "utf-8");
|
|
20
|
+
}
|
|
21
|
+
function clearSession() {
|
|
22
|
+
try {
|
|
23
|
+
fs.unlinkSync(getSessionPath());
|
|
24
|
+
} catch {}
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { clearSession, saveSession };
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import { parse, stringify } from "yaml";
|
|
6
2
|
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
7
3
|
import { base58 } from "@scure/base";
|
|
8
4
|
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
@@ -12,79 +8,6 @@ import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
|
12
8
|
import { HDKey } from "@scure/bip32";
|
|
13
9
|
import { generateMnemonic, mnemonicToSeedSync } from "@scure/bip39";
|
|
14
10
|
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
15
|
-
//#region src/lib/config.ts
|
|
16
|
-
const APP_NAME = "x402-proxy";
|
|
17
|
-
function getConfigDir() {
|
|
18
|
-
const xdg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
19
|
-
return path.join(xdg, APP_NAME);
|
|
20
|
-
}
|
|
21
|
-
/** Config dir with $HOME replaced by ~ for display */
|
|
22
|
-
function getConfigDirShort() {
|
|
23
|
-
const dir = getConfigDir();
|
|
24
|
-
const home = os.homedir();
|
|
25
|
-
return dir.startsWith(home) ? `~${dir.slice(home.length)}` : dir;
|
|
26
|
-
}
|
|
27
|
-
function getWalletPath() {
|
|
28
|
-
return path.join(getConfigDir(), "wallet.json");
|
|
29
|
-
}
|
|
30
|
-
function getHistoryPath() {
|
|
31
|
-
return path.join(getConfigDir(), "history.jsonl");
|
|
32
|
-
}
|
|
33
|
-
function ensureConfigDir() {
|
|
34
|
-
fs.mkdirSync(getConfigDir(), { recursive: true });
|
|
35
|
-
}
|
|
36
|
-
function loadWalletFile() {
|
|
37
|
-
const p = getWalletPath();
|
|
38
|
-
if (!fs.existsSync(p)) return null;
|
|
39
|
-
try {
|
|
40
|
-
const data = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
41
|
-
if (data.version === 1 && typeof data.mnemonic === "string") return data;
|
|
42
|
-
return null;
|
|
43
|
-
} catch {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
function saveWalletFile(wallet) {
|
|
48
|
-
ensureConfigDir();
|
|
49
|
-
fs.writeFileSync(getWalletPath(), JSON.stringify(wallet, null, 2), {
|
|
50
|
-
mode: 384,
|
|
51
|
-
encoding: "utf-8"
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
function loadConfig() {
|
|
55
|
-
const dir = getConfigDir();
|
|
56
|
-
for (const name of [
|
|
57
|
-
"config.yaml",
|
|
58
|
-
"config.yml",
|
|
59
|
-
"config.jsonc",
|
|
60
|
-
"config.json"
|
|
61
|
-
]) {
|
|
62
|
-
const p = path.join(dir, name);
|
|
63
|
-
if (!fs.existsSync(p)) continue;
|
|
64
|
-
try {
|
|
65
|
-
const raw = fs.readFileSync(p, "utf-8");
|
|
66
|
-
if (name.endsWith(".yaml") || name.endsWith(".yml")) return parse(raw);
|
|
67
|
-
if (name.endsWith(".jsonc")) {
|
|
68
|
-
const stripped = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
69
|
-
return JSON.parse(stripped);
|
|
70
|
-
}
|
|
71
|
-
return JSON.parse(raw);
|
|
72
|
-
} catch {}
|
|
73
|
-
}
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
function saveConfig(config) {
|
|
77
|
-
ensureConfigDir();
|
|
78
|
-
const p = path.join(getConfigDir(), "config.yaml");
|
|
79
|
-
fs.writeFileSync(p, stringify(config), "utf-8");
|
|
80
|
-
}
|
|
81
|
-
function isConfigured() {
|
|
82
|
-
if (process.env.X402_PROXY_WALLET_MNEMONIC) return true;
|
|
83
|
-
if (process.env.X402_PROXY_WALLET_EVM_KEY) return true;
|
|
84
|
-
if (process.env.X402_PROXY_WALLET_SOLANA_KEY) return true;
|
|
85
|
-
return loadWalletFile() !== null;
|
|
86
|
-
}
|
|
87
|
-
//#endregion
|
|
88
11
|
//#region src/lib/derive.ts
|
|
89
12
|
/**
|
|
90
13
|
* Wallet derivation from BIP-39 mnemonic.
|
|
@@ -155,4 +78,4 @@ function checksumAddress(addr) {
|
|
|
155
78
|
return out;
|
|
156
79
|
}
|
|
157
80
|
//#endregion
|
|
158
|
-
export {
|
|
81
|
+
export { deriveSolanaKeypair as n, generateMnemonic$1 as r, deriveEvmKeypair as t };
|
package/dist/index.js
CHANGED
|
@@ -63,10 +63,12 @@ const TEMPO_NETWORK = "eip155:4217";
|
|
|
63
63
|
async function createMppProxyHandler(opts) {
|
|
64
64
|
const { Mppx, tempo } = await import("mppx/client");
|
|
65
65
|
const { privateKeyToAccount } = await import("viem/accounts");
|
|
66
|
+
const { saveSession, clearSession } = await import("./config-J1m-CWXT.js");
|
|
66
67
|
const account = privateKeyToAccount(opts.evmKey);
|
|
67
68
|
const maxDeposit = opts.maxDeposit ?? "1";
|
|
68
69
|
const paymentQueue = [];
|
|
69
70
|
let lastChallengeAmount;
|
|
71
|
+
const debug = process.env.X402_PROXY_DEBUG === "1";
|
|
70
72
|
const mppx = Mppx.create({
|
|
71
73
|
methods: [tempo({
|
|
72
74
|
account,
|
|
@@ -78,10 +80,22 @@ async function createMppProxyHandler(opts) {
|
|
|
78
80
|
if (req.amount) lastChallengeAmount = (Number(req.amount) / 10 ** (req.decimals ?? 6)).toString();
|
|
79
81
|
}
|
|
80
82
|
});
|
|
83
|
+
const payerAddress = account.address;
|
|
84
|
+
function injectPayerHeader(init) {
|
|
85
|
+
const existing = init?.headers instanceof Headers ? Object.fromEntries(init.headers.entries()) : init?.headers ?? {};
|
|
86
|
+
return {
|
|
87
|
+
...init,
|
|
88
|
+
headers: {
|
|
89
|
+
...existing,
|
|
90
|
+
"X-Payer-Address": payerAddress
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
81
94
|
let session;
|
|
95
|
+
let persistedChannelId;
|
|
82
96
|
return {
|
|
83
97
|
async fetch(input, init) {
|
|
84
|
-
const response = await mppx.fetch(typeof input === "string" ? input : input.toString(), init);
|
|
98
|
+
const response = await mppx.fetch(typeof input === "string" ? input : input.toString(), injectPayerHeader(init));
|
|
85
99
|
const receiptHeader = response.headers.get("Payment-Receipt");
|
|
86
100
|
if (receiptHeader) {
|
|
87
101
|
try {
|
|
@@ -109,7 +123,17 @@ async function createMppProxyHandler(opts) {
|
|
|
109
123
|
maxDeposit
|
|
110
124
|
});
|
|
111
125
|
const url = typeof input === "string" ? input : input.toString();
|
|
112
|
-
const iterable = await session.sse(url, init);
|
|
126
|
+
const iterable = await session.sse(url, injectPayerHeader(init));
|
|
127
|
+
if (session.channelId && session.channelId !== persistedChannelId) {
|
|
128
|
+
persistedChannelId = session.channelId;
|
|
129
|
+
if (debug) process.stderr.write(`[x402-proxy] channelId: ${persistedChannelId}\n`);
|
|
130
|
+
try {
|
|
131
|
+
saveSession({
|
|
132
|
+
channelId: session.channelId,
|
|
133
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
134
|
+
});
|
|
135
|
+
} catch {}
|
|
136
|
+
}
|
|
113
137
|
paymentQueue.push({
|
|
114
138
|
protocol: "mpp",
|
|
115
139
|
network: TEMPO_NETWORK,
|
|
@@ -121,6 +145,9 @@ async function createMppProxyHandler(opts) {
|
|
|
121
145
|
async close() {
|
|
122
146
|
if (session?.opened) {
|
|
123
147
|
const receipt = await session.close();
|
|
148
|
+
try {
|
|
149
|
+
clearSession();
|
|
150
|
+
} catch {}
|
|
124
151
|
if (receipt) {
|
|
125
152
|
const spentUsdc = receipt.spent ? (Number(receipt.spent) / 1e6).toString() : void 0;
|
|
126
153
|
paymentQueue.push({
|
package/dist/openclaw/plugin.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { n as getHistoryPath, r as loadWalletFile } from "../config-B_upkJeK.js";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
3
4
|
import { SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR, address, appendTransactionMessageInstructions, createDefaultRpcTransport, createKeyPairSignerFromBytes, createSolanaRpc, createSolanaRpcFromTransport, createTransactionMessage, getAddressEncoder, getBase64EncodedWireTransaction, getProgramDerivedAddress, isSolanaError, mainnet, partiallySignTransactionMessageWithSigners, pipe, setTransactionMessageComputeUnitLimit, setTransactionMessageComputeUnitPrice, setTransactionMessageFeePayer, setTransactionMessageLifetimeUsingBlockhash } from "@solana/kit";
|
|
4
5
|
import { decodePaymentResponseHeader, wrapFetchWithPayment, x402Client } from "@x402/fetch";
|
|
5
6
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
6
|
-
import
|
|
7
|
-
import "yaml";
|
|
7
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { TOKEN_PROGRAM_ADDRESS, findAssociatedTokenPda, getTransferCheckedInstruction } from "@solana-program/token";
|
|
9
9
|
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
10
10
|
import { base58 } from "@scure/base";
|
|
@@ -68,10 +68,12 @@ const TEMPO_NETWORK = "eip155:4217";
|
|
|
68
68
|
async function createMppProxyHandler(opts) {
|
|
69
69
|
const { Mppx, tempo } = await import("mppx/client");
|
|
70
70
|
const { privateKeyToAccount } = await import("viem/accounts");
|
|
71
|
+
const { saveSession, clearSession } = await import("../config-B_upkJeK.js").then((n) => n.t);
|
|
71
72
|
const account = privateKeyToAccount(opts.evmKey);
|
|
72
73
|
const maxDeposit = opts.maxDeposit ?? "1";
|
|
73
74
|
const paymentQueue = [];
|
|
74
75
|
let lastChallengeAmount;
|
|
76
|
+
const debug = process.env.X402_PROXY_DEBUG === "1";
|
|
75
77
|
const mppx = Mppx.create({
|
|
76
78
|
methods: [tempo({
|
|
77
79
|
account,
|
|
@@ -83,10 +85,22 @@ async function createMppProxyHandler(opts) {
|
|
|
83
85
|
if (req.amount) lastChallengeAmount = (Number(req.amount) / 10 ** (req.decimals ?? 6)).toString();
|
|
84
86
|
}
|
|
85
87
|
});
|
|
88
|
+
const payerAddress = account.address;
|
|
89
|
+
function injectPayerHeader(init) {
|
|
90
|
+
const existing = init?.headers instanceof Headers ? Object.fromEntries(init.headers.entries()) : init?.headers ?? {};
|
|
91
|
+
return {
|
|
92
|
+
...init,
|
|
93
|
+
headers: {
|
|
94
|
+
...existing,
|
|
95
|
+
"X-Payer-Address": payerAddress
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
86
99
|
let session;
|
|
100
|
+
let persistedChannelId;
|
|
87
101
|
return {
|
|
88
102
|
async fetch(input, init) {
|
|
89
|
-
const response = await mppx.fetch(typeof input === "string" ? input : input.toString(), init);
|
|
103
|
+
const response = await mppx.fetch(typeof input === "string" ? input : input.toString(), injectPayerHeader(init));
|
|
90
104
|
const receiptHeader = response.headers.get("Payment-Receipt");
|
|
91
105
|
if (receiptHeader) {
|
|
92
106
|
try {
|
|
@@ -114,7 +128,17 @@ async function createMppProxyHandler(opts) {
|
|
|
114
128
|
maxDeposit
|
|
115
129
|
});
|
|
116
130
|
const url = typeof input === "string" ? input : input.toString();
|
|
117
|
-
const iterable = await session.sse(url, init);
|
|
131
|
+
const iterable = await session.sse(url, injectPayerHeader(init));
|
|
132
|
+
if (session.channelId && session.channelId !== persistedChannelId) {
|
|
133
|
+
persistedChannelId = session.channelId;
|
|
134
|
+
if (debug) process.stderr.write(`[x402-proxy] channelId: ${persistedChannelId}\n`);
|
|
135
|
+
try {
|
|
136
|
+
saveSession({
|
|
137
|
+
channelId: session.channelId,
|
|
138
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
139
|
+
});
|
|
140
|
+
} catch {}
|
|
141
|
+
}
|
|
118
142
|
paymentQueue.push({
|
|
119
143
|
protocol: "mpp",
|
|
120
144
|
network: TEMPO_NETWORK,
|
|
@@ -126,6 +150,9 @@ async function createMppProxyHandler(opts) {
|
|
|
126
150
|
async close() {
|
|
127
151
|
if (session?.opened) {
|
|
128
152
|
const receipt = await session.close();
|
|
153
|
+
try {
|
|
154
|
+
clearSession();
|
|
155
|
+
} catch {}
|
|
129
156
|
if (receipt) {
|
|
130
157
|
const spentUsdc = receipt.spent ? (Number(receipt.spent) / 1e6).toString() : void 0;
|
|
131
158
|
paymentQueue.push({
|
|
@@ -149,30 +176,6 @@ async function createMppProxyHandler(opts) {
|
|
|
149
176
|
};
|
|
150
177
|
}
|
|
151
178
|
//#endregion
|
|
152
|
-
//#region src/lib/config.ts
|
|
153
|
-
const APP_NAME = "x402-proxy";
|
|
154
|
-
function getConfigDir() {
|
|
155
|
-
const xdg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
156
|
-
return path.join(xdg, APP_NAME);
|
|
157
|
-
}
|
|
158
|
-
function getWalletPath() {
|
|
159
|
-
return path.join(getConfigDir(), "wallet.json");
|
|
160
|
-
}
|
|
161
|
-
function getHistoryPath() {
|
|
162
|
-
return path.join(getConfigDir(), "history.jsonl");
|
|
163
|
-
}
|
|
164
|
-
function loadWalletFile() {
|
|
165
|
-
const p = getWalletPath();
|
|
166
|
-
if (!fs.existsSync(p)) return null;
|
|
167
|
-
try {
|
|
168
|
-
const data = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
169
|
-
if (data.version === 1 && typeof data.mnemonic === "string") return data;
|
|
170
|
-
return null;
|
|
171
|
-
} catch {
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
//#endregion
|
|
176
179
|
//#region src/lib/optimized-svm-scheme.ts
|
|
177
180
|
const MEMO_PROGRAM_ADDRESS = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr";
|
|
178
181
|
const COMPUTE_UNIT_LIMIT = 2e4;
|
|
@@ -1150,7 +1153,7 @@ function createWalletCommand(ctx) {
|
|
|
1150
1153
|
if (parts[0]?.toLowerCase() === "send") return { text: "Use `/x_send <amount|all> <address>` for transfers." };
|
|
1151
1154
|
try {
|
|
1152
1155
|
const snap = await getWalletSnapshot(ctx.rpcUrl, solanaWallet, evmWallet, ctx.historyPath);
|
|
1153
|
-
const lines = [`x402-proxy v0.10.
|
|
1156
|
+
const lines = [`x402-proxy v0.10.5`];
|
|
1154
1157
|
const defaultModel = ctx.allModels[0];
|
|
1155
1158
|
if (defaultModel) lines.push("", `**Model** - ${defaultModel.name} (${defaultModel.provider})`);
|
|
1156
1159
|
lines.push("", `**Protocol** - ${ctx.getDefaultRequestProtocol()}`);
|
|
@@ -1366,8 +1369,11 @@ function resolveMppSessionBudget(value, fallback = "0.5") {
|
|
|
1366
1369
|
}
|
|
1367
1370
|
//#endregion
|
|
1368
1371
|
//#region src/openclaw/route.ts
|
|
1372
|
+
function dbg(msg) {
|
|
1373
|
+
if (process.env.X402_PROXY_DEBUG === "1") process.stderr.write(`[x402-proxy] ${msg}\n`);
|
|
1374
|
+
}
|
|
1369
1375
|
function createInferenceProxyRouteHandler(opts) {
|
|
1370
|
-
const { providers, getX402Proxy, getWalletAddress, getWalletAddressForNetwork,
|
|
1376
|
+
const { providers, getX402Proxy, getMppHandler, getWalletAddress, getWalletAddressForNetwork, historyPath, allModels, logger } = opts;
|
|
1371
1377
|
const sortedProviders = providers.slice().sort((left, right) => right.baseUrl.length - left.baseUrl.length);
|
|
1372
1378
|
return async (req, res) => {
|
|
1373
1379
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
@@ -1391,6 +1397,7 @@ function createInferenceProxyRouteHandler(opts) {
|
|
|
1391
1397
|
}
|
|
1392
1398
|
const pathSuffix = provider.baseUrl === "/" ? url.pathname : url.pathname.slice(provider.baseUrl.length);
|
|
1393
1399
|
const upstreamUrl = `${provider.upstreamUrl.replace(/\/+$/, "")}${pathSuffix.startsWith("/") ? pathSuffix : `/${pathSuffix}`}${url.search}`;
|
|
1400
|
+
dbg(`${req.method} ${url.pathname} -> ${upstreamUrl}`);
|
|
1394
1401
|
logger.info(`proxy: intercepting ${upstreamUrl.substring(0, 80)}`);
|
|
1395
1402
|
const chunks = [];
|
|
1396
1403
|
for await (const chunk of req) chunks.push(Buffer.from(chunk));
|
|
@@ -1437,8 +1444,8 @@ function createInferenceProxyRouteHandler(opts) {
|
|
|
1437
1444
|
const useMpp = provider.protocol === "mpp" || provider.protocol === "auto";
|
|
1438
1445
|
const wantsStreaming = isLlmEndpoint && /"stream"\s*:\s*true/.test(body);
|
|
1439
1446
|
if (useMpp) {
|
|
1440
|
-
const
|
|
1441
|
-
if (!
|
|
1447
|
+
const mpp = getMppHandler();
|
|
1448
|
+
if (!mpp) {
|
|
1442
1449
|
res.writeHead(503, { "Content-Type": "application/json" });
|
|
1443
1450
|
res.end(JSON.stringify({ error: {
|
|
1444
1451
|
message: "MPP inference requires an EVM wallet. Configure X402_PROXY_WALLET_MNEMONIC or X402_PROXY_WALLET_EVM_KEY.",
|
|
@@ -1447,7 +1454,6 @@ function createInferenceProxyRouteHandler(opts) {
|
|
|
1447
1454
|
return true;
|
|
1448
1455
|
}
|
|
1449
1456
|
return await handleMppRequest({
|
|
1450
|
-
req,
|
|
1451
1457
|
res,
|
|
1452
1458
|
upstreamUrl,
|
|
1453
1459
|
requestInit,
|
|
@@ -1459,8 +1465,7 @@ function createInferenceProxyRouteHandler(opts) {
|
|
|
1459
1465
|
wantsStreaming,
|
|
1460
1466
|
isMessagesApi,
|
|
1461
1467
|
startMs,
|
|
1462
|
-
|
|
1463
|
-
mppSessionBudget: provider.mppSessionBudget
|
|
1468
|
+
mpp
|
|
1464
1469
|
});
|
|
1465
1470
|
}
|
|
1466
1471
|
const proxy = getX402Proxy();
|
|
@@ -1697,17 +1702,8 @@ function createSseTracker() {
|
|
|
1697
1702
|
}
|
|
1698
1703
|
};
|
|
1699
1704
|
}
|
|
1700
|
-
async function closeMppSession(handler) {
|
|
1701
|
-
try {
|
|
1702
|
-
await handler.close();
|
|
1703
|
-
} catch {}
|
|
1704
|
-
}
|
|
1705
1705
|
async function handleMppRequest(opts) {
|
|
1706
|
-
const {
|
|
1707
|
-
const mpp = await createMppProxyHandler({
|
|
1708
|
-
evmKey,
|
|
1709
|
-
maxDeposit: mppSessionBudget
|
|
1710
|
-
});
|
|
1706
|
+
const { res, upstreamUrl, requestInit, walletAddress, historyPath, logger, allModels, thinkingMode, wantsStreaming, isMessagesApi, startMs, mpp } = opts;
|
|
1711
1707
|
try {
|
|
1712
1708
|
if (wantsStreaming) {
|
|
1713
1709
|
res.writeHead(200, {
|
|
@@ -1716,7 +1712,9 @@ async function handleMppRequest(opts) {
|
|
|
1716
1712
|
Connection: "keep-alive"
|
|
1717
1713
|
});
|
|
1718
1714
|
const sse = createSseTracker();
|
|
1715
|
+
dbg(`mpp.sse() calling ${upstreamUrl}`);
|
|
1719
1716
|
const stream = await mpp.sse(upstreamUrl, requestInit);
|
|
1717
|
+
dbg("mpp.sse() resolved, iterating stream");
|
|
1720
1718
|
if (isMessagesApi) for await (const chunk of stream) {
|
|
1721
1719
|
const text = String(chunk);
|
|
1722
1720
|
let eventType = "unknown";
|
|
@@ -1734,17 +1732,15 @@ async function handleMppRequest(opts) {
|
|
|
1734
1732
|
}
|
|
1735
1733
|
res.write("data: [DONE]\n\n");
|
|
1736
1734
|
}
|
|
1735
|
+
dbg(`stream done, ${sse.result ? `${sse.result.model} ${sse.result.inputTokens}+${sse.result.outputTokens}t` : "no usage"}`);
|
|
1737
1736
|
res.end();
|
|
1738
1737
|
mpp.shiftPayment();
|
|
1739
|
-
const payment = mpp.shiftPayment();
|
|
1740
1738
|
appendInferenceHistory({
|
|
1741
1739
|
historyPath,
|
|
1742
1740
|
allModels,
|
|
1743
1741
|
walletAddress,
|
|
1744
|
-
paymentNetwork:
|
|
1742
|
+
paymentNetwork: TEMPO_NETWORK,
|
|
1745
1743
|
paymentTo: void 0,
|
|
1746
|
-
tx: payment?.receipt?.reference ?? payment?.channelId,
|
|
1747
|
-
amount: parseMppAmount(payment?.amount),
|
|
1748
1744
|
thinkingMode,
|
|
1749
1745
|
usage: sse.result,
|
|
1750
1746
|
durationMs: Date.now() - startMs
|
|
@@ -1790,6 +1786,7 @@ async function handleMppRequest(opts) {
|
|
|
1790
1786
|
});
|
|
1791
1787
|
return true;
|
|
1792
1788
|
} catch (err) {
|
|
1789
|
+
dbg(`mpp error: ${String(err)}`);
|
|
1793
1790
|
logger.error(`mpp: fetch threw: ${String(err)}`);
|
|
1794
1791
|
appendHistory(historyPath, {
|
|
1795
1792
|
t: Date.now(),
|
|
@@ -1801,11 +1798,8 @@ async function handleMppRequest(opts) {
|
|
|
1801
1798
|
error: String(err).substring(0, 200)
|
|
1802
1799
|
});
|
|
1803
1800
|
if (!res.headersSent) writeErrorResponse(res, 402, `MPP request failed: ${String(err)}`, "mpp_payment_error", "payment_failed", isMessagesApi);
|
|
1801
|
+
else if (!res.writableEnded) res.end();
|
|
1804
1802
|
return true;
|
|
1805
|
-
} finally {
|
|
1806
|
-
await closeMppSession(mpp);
|
|
1807
|
-
if (!res.writableEnded) res.end();
|
|
1808
|
-
req.resume();
|
|
1809
1803
|
}
|
|
1810
1804
|
}
|
|
1811
1805
|
function appendInferenceHistory(opts) {
|
|
@@ -1871,6 +1865,7 @@ function register(api) {
|
|
|
1871
1865
|
let evmWalletAddress = null;
|
|
1872
1866
|
let signerRef = null;
|
|
1873
1867
|
let proxyRef = null;
|
|
1868
|
+
let mppHandlerRef = null;
|
|
1874
1869
|
let evmKeyRef = null;
|
|
1875
1870
|
let walletLoadPromise = null;
|
|
1876
1871
|
const historyPath = getHistoryPath();
|
|
@@ -1878,9 +1873,9 @@ function register(api) {
|
|
|
1878
1873
|
const handler = createInferenceProxyRouteHandler({
|
|
1879
1874
|
providers,
|
|
1880
1875
|
getX402Proxy: () => proxyRef,
|
|
1876
|
+
getMppHandler: () => mppHandlerRef,
|
|
1881
1877
|
getWalletAddress: () => solanaWalletAddress ?? evmWalletAddress,
|
|
1882
1878
|
getWalletAddressForNetwork: (network) => addressForNetwork(evmWalletAddress, solanaWalletAddress, network),
|
|
1883
|
-
getEvmKey: () => evmKeyRef,
|
|
1884
1879
|
historyPath,
|
|
1885
1880
|
allModels,
|
|
1886
1881
|
logger: api.logger
|
|
@@ -1921,6 +1916,13 @@ function register(api) {
|
|
|
1921
1916
|
client.register(SOL_MAINNET, new OptimizedSvmScheme(signerRef, { rpcUrl }));
|
|
1922
1917
|
proxyRef = createX402ProxyHandler({ client });
|
|
1923
1918
|
} else proxyRef = null;
|
|
1919
|
+
if (evmKeyRef) {
|
|
1920
|
+
const maxBudget = Math.max(...providers.map((p) => Number(p.mppSessionBudget) || .5)).toString();
|
|
1921
|
+
mppHandlerRef = await createMppProxyHandler({
|
|
1922
|
+
evmKey: evmKeyRef,
|
|
1923
|
+
maxDeposit: maxBudget
|
|
1924
|
+
});
|
|
1925
|
+
} else mppHandlerRef = null;
|
|
1924
1926
|
api.logger.info(`wallets: solana=${solanaWalletAddress ?? "missing"} evm=${evmWalletAddress ?? "missing"}`);
|
|
1925
1927
|
} catch (err) {
|
|
1926
1928
|
api.logger.error(`wallet load failed: ${err}`);
|
|
@@ -1934,7 +1936,11 @@ function register(api) {
|
|
|
1934
1936
|
async start() {
|
|
1935
1937
|
await ensureWalletLoaded();
|
|
1936
1938
|
},
|
|
1937
|
-
async stop() {
|
|
1939
|
+
async stop() {
|
|
1940
|
+
if (mppHandlerRef) try {
|
|
1941
|
+
await mppHandlerRef.close();
|
|
1942
|
+
} catch {}
|
|
1943
|
+
}
|
|
1938
1944
|
});
|
|
1939
1945
|
const toolCtx = {
|
|
1940
1946
|
ensureReady: ensureWalletLoaded,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { c as isConfigured, d as saveConfig, i as getConfigDirShort, p as saveWalletFile, s as getWalletPath, u as loadWalletFile } from "./config-D9wIR3xc.js";
|
|
3
|
+
import { n as deriveSolanaKeypair, r as generateMnemonic, t as deriveEvmKeypair } from "./derive-CL6e8K0Z.js";
|
|
3
4
|
import { buildCommand } from "@stricli/core";
|
|
4
5
|
import pc from "picocolors";
|
|
5
6
|
import * as prompts from "@clack/prompts";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
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 { a as getHistoryPath,
|
|
2
|
+
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-LUl2l8WM.js";
|
|
3
|
+
import { a as getHistoryPath, i as getConfigDirShort, l as loadConfig } from "./config-D9wIR3xc.js";
|
|
4
4
|
import { buildCommand } from "@stricli/core";
|
|
5
5
|
import pc from "picocolors";
|
|
6
6
|
//#region src/commands/status.ts
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as getHistoryPath,
|
|
2
|
+
import { a as getHistoryPath, u as loadWalletFile } from "./config-D9wIR3xc.js";
|
|
3
|
+
import { n as deriveSolanaKeypair, t as deriveEvmKeypair } from "./derive-CL6e8K0Z.js";
|
|
3
4
|
import { dirname } from "node:path";
|
|
4
5
|
import { buildCommand } from "@stricli/core";
|
|
5
6
|
import pc from "picocolors";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "x402-proxy",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.5",
|
|
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,
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"@x402/mcp": "^2.8.0",
|
|
56
56
|
"@x402/svm": "^2.8.0",
|
|
57
57
|
"ethers": "^6.16.0",
|
|
58
|
-
"mppx": "^0.
|
|
58
|
+
"mppx": "^0.5.1",
|
|
59
59
|
"picocolors": "^1.1.1",
|
|
60
60
|
"viem": "^2.47.6",
|
|
61
61
|
"yaml": "^2.8.3"
|
package/dist/setup-EX1_teNg.js
DELETED
package/dist/status-DlR8yBrK.js
DELETED
package/dist/wallet-7XKcknNZ.js
DELETED