x402-proxy 0.10.3 → 0.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -1
- package/dist/bin/cli.js +69 -44
- 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 +27 -2
- package/dist/openclaw/plugin.js +62 -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,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.10.4] - 2026-04-01
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- MPP sessions now reuse a single handler across requests instead of creating a new one per request - eliminates redundant escrow deposits and wasted USDC
|
|
14
|
+
- MPP sessions properly settle on process shutdown (`serve` command and OpenClaw plugin both call `close()` on SIGTERM/stop)
|
|
15
|
+
- MPP channelId persisted to `~/.config/x402-proxy/session.json` for tracking; cleared on session close
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- `--debug` global CLI flag - sets `X402_PROXY_DEBUG=1` for verbose stderr logging of MPP SSE lifecycle, channelId, and proxy routing
|
|
19
|
+
- Debug trace points in inference proxy: upstream routing, SSE start/end, usage stats, errors
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- Upgraded `mppx` from ^0.4.9 to ^0.5.1
|
|
23
|
+
- 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
|
|
24
|
+
- Inference proxy no longer accepts `getEvmKey` option - receives pre-built `getMppHandler` instead
|
|
25
|
+
|
|
10
26
|
## [0.10.3] - 2026-04-01
|
|
11
27
|
|
|
12
28
|
### Fixed
|
|
@@ -351,7 +367,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
351
367
|
- `appendHistory` / `readHistory` / `calcSpend` - JSONL transaction history
|
|
352
368
|
- Re-exports from `@x402/fetch`, `@x402/svm`, `@x402/evm`
|
|
353
369
|
|
|
354
|
-
[Unreleased]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.
|
|
370
|
+
[Unreleased]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.4...HEAD
|
|
371
|
+
[0.10.4]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.3...v0.10.4
|
|
372
|
+
[0.10.3]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.2...v0.10.3
|
|
355
373
|
[0.10.2]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.1...v0.10.2
|
|
356
374
|
[0.10.1]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.0...v0.10.1
|
|
357
375
|
[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,20 @@ 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 headers = new Headers(init?.headers);
|
|
220
|
+
headers.set("X-Payer-Address", payerAddress);
|
|
221
|
+
return {
|
|
222
|
+
...init,
|
|
223
|
+
headers
|
|
224
|
+
};
|
|
225
|
+
}
|
|
215
226
|
let session;
|
|
227
|
+
let persistedChannelId;
|
|
216
228
|
return {
|
|
217
229
|
async fetch(input, init) {
|
|
218
|
-
const response = await mppx.fetch(typeof input === "string" ? input : input.toString(), init);
|
|
230
|
+
const response = await mppx.fetch(typeof input === "string" ? input : input.toString(), injectPayerHeader(init));
|
|
219
231
|
const receiptHeader = response.headers.get("Payment-Receipt");
|
|
220
232
|
if (receiptHeader) {
|
|
221
233
|
try {
|
|
@@ -243,7 +255,17 @@ async function createMppProxyHandler(opts) {
|
|
|
243
255
|
maxDeposit
|
|
244
256
|
});
|
|
245
257
|
const url = typeof input === "string" ? input : input.toString();
|
|
246
|
-
const iterable = await session.sse(url, init);
|
|
258
|
+
const iterable = await session.sse(url, injectPayerHeader(init));
|
|
259
|
+
if (session.channelId && session.channelId !== persistedChannelId) {
|
|
260
|
+
persistedChannelId = session.channelId;
|
|
261
|
+
if (debug) process.stderr.write(`[x402-proxy] channelId: ${persistedChannelId}\n`);
|
|
262
|
+
try {
|
|
263
|
+
saveSession({
|
|
264
|
+
channelId: session.channelId,
|
|
265
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
266
|
+
});
|
|
267
|
+
} catch {}
|
|
268
|
+
}
|
|
247
269
|
paymentQueue.push({
|
|
248
270
|
protocol: "mpp",
|
|
249
271
|
network: TEMPO_NETWORK,
|
|
@@ -255,6 +277,9 @@ async function createMppProxyHandler(opts) {
|
|
|
255
277
|
async close() {
|
|
256
278
|
if (session?.opened) {
|
|
257
279
|
const receipt = await session.close();
|
|
280
|
+
try {
|
|
281
|
+
clearSession();
|
|
282
|
+
} catch {}
|
|
258
283
|
if (receipt) {
|
|
259
284
|
const spentUsdc = receipt.spent ? (Number(receipt.spent) / 1e6).toString() : void 0;
|
|
260
285
|
paymentQueue.push({
|
|
@@ -298,8 +323,12 @@ function addressForNetwork(evmAddress, solanaAddress, network) {
|
|
|
298
323
|
}
|
|
299
324
|
//#endregion
|
|
300
325
|
//#region src/openclaw/route.ts
|
|
326
|
+
const debug = process.env.X402_PROXY_DEBUG === "1";
|
|
327
|
+
function dbg(msg) {
|
|
328
|
+
if (debug) process.stderr.write(`[x402-proxy] ${msg}\n`);
|
|
329
|
+
}
|
|
301
330
|
function createInferenceProxyRouteHandler(opts) {
|
|
302
|
-
const { providers, getX402Proxy, getWalletAddress, getWalletAddressForNetwork,
|
|
331
|
+
const { providers, getX402Proxy, getMppHandler, getWalletAddress, getWalletAddressForNetwork, historyPath, allModels, logger } = opts;
|
|
303
332
|
const sortedProviders = providers.slice().sort((left, right) => right.baseUrl.length - left.baseUrl.length);
|
|
304
333
|
return async (req, res) => {
|
|
305
334
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
@@ -323,6 +352,7 @@ function createInferenceProxyRouteHandler(opts) {
|
|
|
323
352
|
}
|
|
324
353
|
const pathSuffix = provider.baseUrl === "/" ? url.pathname : url.pathname.slice(provider.baseUrl.length);
|
|
325
354
|
const upstreamUrl = `${provider.upstreamUrl.replace(/\/+$/, "")}${pathSuffix.startsWith("/") ? pathSuffix : `/${pathSuffix}`}${url.search}`;
|
|
355
|
+
dbg(`${req.method} ${url.pathname} -> ${upstreamUrl}`);
|
|
326
356
|
logger.info(`proxy: intercepting ${upstreamUrl.substring(0, 80)}`);
|
|
327
357
|
const chunks = [];
|
|
328
358
|
for await (const chunk of req) chunks.push(Buffer.from(chunk));
|
|
@@ -369,8 +399,8 @@ function createInferenceProxyRouteHandler(opts) {
|
|
|
369
399
|
const useMpp = provider.protocol === "mpp" || provider.protocol === "auto";
|
|
370
400
|
const wantsStreaming = isLlmEndpoint && /"stream"\s*:\s*true/.test(body);
|
|
371
401
|
if (useMpp) {
|
|
372
|
-
const
|
|
373
|
-
if (!
|
|
402
|
+
const mpp = getMppHandler();
|
|
403
|
+
if (!mpp) {
|
|
374
404
|
res.writeHead(503, { "Content-Type": "application/json" });
|
|
375
405
|
res.end(JSON.stringify({ error: {
|
|
376
406
|
message: "MPP inference requires an EVM wallet. Configure X402_PROXY_WALLET_MNEMONIC or X402_PROXY_WALLET_EVM_KEY.",
|
|
@@ -379,7 +409,6 @@ function createInferenceProxyRouteHandler(opts) {
|
|
|
379
409
|
return true;
|
|
380
410
|
}
|
|
381
411
|
return await handleMppRequest({
|
|
382
|
-
req,
|
|
383
412
|
res,
|
|
384
413
|
upstreamUrl,
|
|
385
414
|
requestInit,
|
|
@@ -391,8 +420,7 @@ function createInferenceProxyRouteHandler(opts) {
|
|
|
391
420
|
wantsStreaming,
|
|
392
421
|
isMessagesApi,
|
|
393
422
|
startMs,
|
|
394
|
-
|
|
395
|
-
mppSessionBudget: provider.mppSessionBudget
|
|
423
|
+
mpp
|
|
396
424
|
});
|
|
397
425
|
}
|
|
398
426
|
const proxy = getX402Proxy();
|
|
@@ -629,17 +657,8 @@ function createSseTracker() {
|
|
|
629
657
|
}
|
|
630
658
|
};
|
|
631
659
|
}
|
|
632
|
-
async function closeMppSession(handler) {
|
|
633
|
-
try {
|
|
634
|
-
await handler.close();
|
|
635
|
-
} catch {}
|
|
636
|
-
}
|
|
637
660
|
async function handleMppRequest(opts) {
|
|
638
|
-
const {
|
|
639
|
-
const mpp = await createMppProxyHandler({
|
|
640
|
-
evmKey,
|
|
641
|
-
maxDeposit: mppSessionBudget
|
|
642
|
-
});
|
|
661
|
+
const { res, upstreamUrl, requestInit, walletAddress, historyPath, logger, allModels, thinkingMode, wantsStreaming, isMessagesApi, startMs, mpp } = opts;
|
|
643
662
|
try {
|
|
644
663
|
if (wantsStreaming) {
|
|
645
664
|
res.writeHead(200, {
|
|
@@ -648,7 +667,9 @@ async function handleMppRequest(opts) {
|
|
|
648
667
|
Connection: "keep-alive"
|
|
649
668
|
});
|
|
650
669
|
const sse = createSseTracker();
|
|
670
|
+
dbg(`mpp.sse() calling ${upstreamUrl}`);
|
|
651
671
|
const stream = await mpp.sse(upstreamUrl, requestInit);
|
|
672
|
+
dbg("mpp.sse() resolved, iterating stream");
|
|
652
673
|
if (isMessagesApi) for await (const chunk of stream) {
|
|
653
674
|
const text = String(chunk);
|
|
654
675
|
let eventType = "unknown";
|
|
@@ -666,17 +687,15 @@ async function handleMppRequest(opts) {
|
|
|
666
687
|
}
|
|
667
688
|
res.write("data: [DONE]\n\n");
|
|
668
689
|
}
|
|
690
|
+
dbg(`stream done, ${sse.result ? `${sse.result.model} ${sse.result.inputTokens}+${sse.result.outputTokens}t` : "no usage"}`);
|
|
669
691
|
res.end();
|
|
670
692
|
mpp.shiftPayment();
|
|
671
|
-
const payment = mpp.shiftPayment();
|
|
672
693
|
appendInferenceHistory({
|
|
673
694
|
historyPath,
|
|
674
695
|
allModels,
|
|
675
696
|
walletAddress,
|
|
676
|
-
paymentNetwork:
|
|
697
|
+
paymentNetwork: TEMPO_NETWORK,
|
|
677
698
|
paymentTo: void 0,
|
|
678
|
-
tx: payment?.receipt?.reference ?? payment?.channelId,
|
|
679
|
-
amount: parseMppAmount(payment?.amount),
|
|
680
699
|
thinkingMode,
|
|
681
700
|
usage: sse.result,
|
|
682
701
|
durationMs: Date.now() - startMs
|
|
@@ -722,6 +741,7 @@ async function handleMppRequest(opts) {
|
|
|
722
741
|
});
|
|
723
742
|
return true;
|
|
724
743
|
} catch (err) {
|
|
744
|
+
dbg(`mpp error: ${String(err)}`);
|
|
725
745
|
logger.error(`mpp: fetch threw: ${String(err)}`);
|
|
726
746
|
appendHistory(historyPath, {
|
|
727
747
|
t: Date.now(),
|
|
@@ -733,11 +753,8 @@ async function handleMppRequest(opts) {
|
|
|
733
753
|
error: String(err).substring(0, 200)
|
|
734
754
|
});
|
|
735
755
|
if (!res.headersSent) writeErrorResponse(res, 402, `MPP request failed: ${String(err)}`, "mpp_payment_error", "payment_failed", isMessagesApi);
|
|
756
|
+
else if (!res.writableEnded) res.end();
|
|
736
757
|
return true;
|
|
737
|
-
} finally {
|
|
738
|
-
await closeMppSession(mpp);
|
|
739
|
-
if (!res.writableEnded) res.end();
|
|
740
|
-
req.resume();
|
|
741
758
|
}
|
|
742
759
|
}
|
|
743
760
|
function appendInferenceHistory(opts) {
|
|
@@ -783,7 +800,7 @@ async function resolveWalletForServe(flags) {
|
|
|
783
800
|
solanaKey: flags.solanaKey
|
|
784
801
|
});
|
|
785
802
|
if (wallet.source !== "none") return wallet;
|
|
786
|
-
const { runSetup } = await import("../setup-
|
|
803
|
+
const { runSetup } = await import("../setup-DTIxPe58.js");
|
|
787
804
|
if (isTTY()) {
|
|
788
805
|
dim(" No wallet found. Let's set one up first.\n");
|
|
789
806
|
await runSetup();
|
|
@@ -844,6 +861,10 @@ async function startServeServer(options = {}) {
|
|
|
844
861
|
spendLimitDaily: config?.spendLimitDaily,
|
|
845
862
|
spendLimitPerTx: config?.spendLimitPerTx
|
|
846
863
|
}) });
|
|
864
|
+
const mppHandler = wallet.evmKey ? await createMppProxyHandler({
|
|
865
|
+
evmKey: wallet.evmKey,
|
|
866
|
+
maxDeposit: configuredMppBudget
|
|
867
|
+
}) : null;
|
|
847
868
|
const { providers, models } = resolveProviders({
|
|
848
869
|
protocol: resolvedProtocol,
|
|
849
870
|
mppSessionBudget: configuredMppBudget,
|
|
@@ -857,9 +878,9 @@ async function startServeServer(options = {}) {
|
|
|
857
878
|
const routeHandler = createInferenceProxyRouteHandler({
|
|
858
879
|
providers,
|
|
859
880
|
getX402Proxy: () => x402Proxy,
|
|
881
|
+
getMppHandler: () => mppHandler,
|
|
860
882
|
getWalletAddress: () => wallet.solanaAddress ?? wallet.evmAddress ?? null,
|
|
861
883
|
getWalletAddressForNetwork: (network) => walletAddressForNetwork(wallet, network),
|
|
862
|
-
getEvmKey: () => wallet.evmKey ?? null,
|
|
863
884
|
historyPath: getHistoryPath(),
|
|
864
885
|
allModels: models,
|
|
865
886
|
logger: {
|
|
@@ -889,6 +910,9 @@ async function startServeServer(options = {}) {
|
|
|
889
910
|
server,
|
|
890
911
|
port,
|
|
891
912
|
close: async () => {
|
|
913
|
+
if (mppHandler) try {
|
|
914
|
+
await mppHandler.close();
|
|
915
|
+
} catch {}
|
|
892
916
|
if (!server.listening) return;
|
|
893
917
|
server.close();
|
|
894
918
|
await once(server, "close");
|
|
@@ -1340,7 +1364,7 @@ Examples:
|
|
|
1340
1364
|
};
|
|
1341
1365
|
if (!url) {
|
|
1342
1366
|
if (isConfigured()) {
|
|
1343
|
-
const { displayStatus } = await import("../status-
|
|
1367
|
+
const { displayStatus } = await import("../status-wknbavnl.js");
|
|
1344
1368
|
await displayStatus();
|
|
1345
1369
|
console.log();
|
|
1346
1370
|
console.log(pc.dim(" Commands:"));
|
|
@@ -1392,7 +1416,7 @@ Examples:
|
|
|
1392
1416
|
process.exit(1);
|
|
1393
1417
|
}
|
|
1394
1418
|
dim(" No wallet found. Let's set one up first.\n");
|
|
1395
|
-
const { runSetup } = await import("../setup-
|
|
1419
|
+
const { runSetup } = await import("../setup-DTIxPe58.js");
|
|
1396
1420
|
await runSetup();
|
|
1397
1421
|
console.log();
|
|
1398
1422
|
wallet = resolveWallet();
|
|
@@ -1405,7 +1429,7 @@ Examples:
|
|
|
1405
1429
|
verbose(`protocol: ${resolvedProtocol ?? "auto-detect"}, maxDeposit: ${maxDeposit}`);
|
|
1406
1430
|
let preferredNetwork = config?.defaultNetwork;
|
|
1407
1431
|
if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
|
|
1408
|
-
const { fetchAllBalances } = await import("../wallet-
|
|
1432
|
+
const { fetchAllBalances } = await import("../wallet-C9UlV7qi.js");
|
|
1409
1433
|
const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
|
|
1410
1434
|
const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
|
|
1411
1435
|
const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
|
|
@@ -1572,7 +1596,7 @@ Examples:
|
|
|
1572
1596
|
const hasSolana = accepts.some((a) => a.network.startsWith("solana:"));
|
|
1573
1597
|
const hasMpp = detected.mpp;
|
|
1574
1598
|
const hasOther = accepts.some((a) => !a.network.startsWith("eip155:") && !a.network.startsWith("solana:"));
|
|
1575
|
-
const { fetchAllBalances } = await import("../wallet-
|
|
1599
|
+
const { fetchAllBalances } = await import("../wallet-C9UlV7qi.js");
|
|
1576
1600
|
const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
|
|
1577
1601
|
const evmUsdc = hasEvm && balances.evm ? Number(balances.evm.usdc) : 0;
|
|
1578
1602
|
const solUsdc = hasSolana && balances.sol ? Number(balances.sol.usdc) : 0;
|
|
@@ -1748,7 +1772,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
1748
1772
|
});
|
|
1749
1773
|
if (wallet.source === "none") {
|
|
1750
1774
|
dim("No wallet found. Auto-generating...");
|
|
1751
|
-
const { runSetup } = await import("../setup-
|
|
1775
|
+
const { runSetup } = await import("../setup-DTIxPe58.js");
|
|
1752
1776
|
await runSetup({ nonInteractive: true });
|
|
1753
1777
|
const fresh = resolveWallet({
|
|
1754
1778
|
evmKey: flags.evmKey,
|
|
@@ -1792,7 +1816,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
1792
1816
|
async function startX402Proxy() {
|
|
1793
1817
|
let preferredNetwork = config?.defaultNetwork;
|
|
1794
1818
|
if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
|
|
1795
|
-
const { fetchAllBalances } = await import("../wallet-
|
|
1819
|
+
const { fetchAllBalances } = await import("../wallet-C9UlV7qi.js");
|
|
1796
1820
|
const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
|
|
1797
1821
|
const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
|
|
1798
1822
|
const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
|
|
@@ -1812,7 +1836,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
1812
1836
|
}
|
|
1813
1837
|
const remoteClient = new Client({
|
|
1814
1838
|
name: "x402-proxy",
|
|
1815
|
-
version: "0.10.
|
|
1839
|
+
version: "0.10.4"
|
|
1816
1840
|
});
|
|
1817
1841
|
const x402Mcp = new x402MCPClient(remoteClient, x402PaymentClient, {
|
|
1818
1842
|
autoPayment: true,
|
|
@@ -1850,7 +1874,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
1850
1874
|
}
|
|
1851
1875
|
const localServer = new Server({
|
|
1852
1876
|
name: "x402-proxy",
|
|
1853
|
-
version: "0.10.
|
|
1877
|
+
version: "0.10.4"
|
|
1854
1878
|
}, { capabilities: {
|
|
1855
1879
|
tools: tools.length > 0 ? {} : void 0,
|
|
1856
1880
|
resources: remoteResources.length > 0 ? {} : void 0
|
|
@@ -1945,7 +1969,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
1945
1969
|
}));
|
|
1946
1970
|
const remoteClient = new Client({
|
|
1947
1971
|
name: "x402-proxy",
|
|
1948
|
-
version: "0.10.
|
|
1972
|
+
version: "0.10.4"
|
|
1949
1973
|
});
|
|
1950
1974
|
await connectTransport(remoteClient);
|
|
1951
1975
|
const mppClient = McpClient.wrap(remoteClient, { methods: wrappedMethods });
|
|
@@ -1960,7 +1984,7 @@ Wallet is auto-generated on first run. No env vars needed.`
|
|
|
1960
1984
|
}
|
|
1961
1985
|
const localServer = new Server({
|
|
1962
1986
|
name: "x402-proxy",
|
|
1963
|
-
version: "0.10.
|
|
1987
|
+
version: "0.10.4"
|
|
1964
1988
|
}, { capabilities: {
|
|
1965
1989
|
tools: tools.length > 0 ? {} : void 0,
|
|
1966
1990
|
resources: remoteResources.length > 0 ? {} : void 0
|
|
@@ -2359,7 +2383,7 @@ const app = buildApplication(buildRouteMap({
|
|
|
2359
2383
|
docs: { brief: "curl for x402 paid APIs" }
|
|
2360
2384
|
}), {
|
|
2361
2385
|
name: "x402-proxy",
|
|
2362
|
-
versionInfo: { currentVersion: "0.10.
|
|
2386
|
+
versionInfo: { currentVersion: "0.10.4" },
|
|
2363
2387
|
scanner: { caseStyle: "allow-kebab-for-camel" }
|
|
2364
2388
|
});
|
|
2365
2389
|
//#endregion
|
|
@@ -2382,7 +2406,8 @@ for (let i = 0; i < rawArgs.length; i++) {
|
|
|
2382
2406
|
const dir = resolve(a.slice(13));
|
|
2383
2407
|
process.env.XDG_CONFIG_HOME = dir;
|
|
2384
2408
|
process.env.X402_PROXY_CONFIG_DIR_OVERRIDE = dir;
|
|
2385
|
-
} else
|
|
2409
|
+
} else if (a === "--debug") process.env.X402_PROXY_DEBUG = "1";
|
|
2410
|
+
else args.push(a);
|
|
2386
2411
|
}
|
|
2387
2412
|
const topLevelCommand = args[0];
|
|
2388
2413
|
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,20 @@ 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 headers = new Headers(init?.headers);
|
|
86
|
+
headers.set("X-Payer-Address", payerAddress);
|
|
87
|
+
return {
|
|
88
|
+
...init,
|
|
89
|
+
headers
|
|
90
|
+
};
|
|
91
|
+
}
|
|
81
92
|
let session;
|
|
93
|
+
let persistedChannelId;
|
|
82
94
|
return {
|
|
83
95
|
async fetch(input, init) {
|
|
84
|
-
const response = await mppx.fetch(typeof input === "string" ? input : input.toString(), init);
|
|
96
|
+
const response = await mppx.fetch(typeof input === "string" ? input : input.toString(), injectPayerHeader(init));
|
|
85
97
|
const receiptHeader = response.headers.get("Payment-Receipt");
|
|
86
98
|
if (receiptHeader) {
|
|
87
99
|
try {
|
|
@@ -109,7 +121,17 @@ async function createMppProxyHandler(opts) {
|
|
|
109
121
|
maxDeposit
|
|
110
122
|
});
|
|
111
123
|
const url = typeof input === "string" ? input : input.toString();
|
|
112
|
-
const iterable = await session.sse(url, init);
|
|
124
|
+
const iterable = await session.sse(url, injectPayerHeader(init));
|
|
125
|
+
if (session.channelId && session.channelId !== persistedChannelId) {
|
|
126
|
+
persistedChannelId = session.channelId;
|
|
127
|
+
if (debug) process.stderr.write(`[x402-proxy] channelId: ${persistedChannelId}\n`);
|
|
128
|
+
try {
|
|
129
|
+
saveSession({
|
|
130
|
+
channelId: session.channelId,
|
|
131
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
132
|
+
});
|
|
133
|
+
} catch {}
|
|
134
|
+
}
|
|
113
135
|
paymentQueue.push({
|
|
114
136
|
protocol: "mpp",
|
|
115
137
|
network: TEMPO_NETWORK,
|
|
@@ -121,6 +143,9 @@ async function createMppProxyHandler(opts) {
|
|
|
121
143
|
async close() {
|
|
122
144
|
if (session?.opened) {
|
|
123
145
|
const receipt = await session.close();
|
|
146
|
+
try {
|
|
147
|
+
clearSession();
|
|
148
|
+
} catch {}
|
|
124
149
|
if (receipt) {
|
|
125
150
|
const spentUsdc = receipt.spent ? (Number(receipt.spent) / 1e6).toString() : void 0;
|
|
126
151
|
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,20 @@ 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 headers = new Headers(init?.headers);
|
|
91
|
+
headers.set("X-Payer-Address", payerAddress);
|
|
92
|
+
return {
|
|
93
|
+
...init,
|
|
94
|
+
headers
|
|
95
|
+
};
|
|
96
|
+
}
|
|
86
97
|
let session;
|
|
98
|
+
let persistedChannelId;
|
|
87
99
|
return {
|
|
88
100
|
async fetch(input, init) {
|
|
89
|
-
const response = await mppx.fetch(typeof input === "string" ? input : input.toString(), init);
|
|
101
|
+
const response = await mppx.fetch(typeof input === "string" ? input : input.toString(), injectPayerHeader(init));
|
|
90
102
|
const receiptHeader = response.headers.get("Payment-Receipt");
|
|
91
103
|
if (receiptHeader) {
|
|
92
104
|
try {
|
|
@@ -114,7 +126,17 @@ async function createMppProxyHandler(opts) {
|
|
|
114
126
|
maxDeposit
|
|
115
127
|
});
|
|
116
128
|
const url = typeof input === "string" ? input : input.toString();
|
|
117
|
-
const iterable = await session.sse(url, init);
|
|
129
|
+
const iterable = await session.sse(url, injectPayerHeader(init));
|
|
130
|
+
if (session.channelId && session.channelId !== persistedChannelId) {
|
|
131
|
+
persistedChannelId = session.channelId;
|
|
132
|
+
if (debug) process.stderr.write(`[x402-proxy] channelId: ${persistedChannelId}\n`);
|
|
133
|
+
try {
|
|
134
|
+
saveSession({
|
|
135
|
+
channelId: session.channelId,
|
|
136
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
137
|
+
});
|
|
138
|
+
} catch {}
|
|
139
|
+
}
|
|
118
140
|
paymentQueue.push({
|
|
119
141
|
protocol: "mpp",
|
|
120
142
|
network: TEMPO_NETWORK,
|
|
@@ -126,6 +148,9 @@ async function createMppProxyHandler(opts) {
|
|
|
126
148
|
async close() {
|
|
127
149
|
if (session?.opened) {
|
|
128
150
|
const receipt = await session.close();
|
|
151
|
+
try {
|
|
152
|
+
clearSession();
|
|
153
|
+
} catch {}
|
|
129
154
|
if (receipt) {
|
|
130
155
|
const spentUsdc = receipt.spent ? (Number(receipt.spent) / 1e6).toString() : void 0;
|
|
131
156
|
paymentQueue.push({
|
|
@@ -149,30 +174,6 @@ async function createMppProxyHandler(opts) {
|
|
|
149
174
|
};
|
|
150
175
|
}
|
|
151
176
|
//#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
177
|
//#region src/lib/optimized-svm-scheme.ts
|
|
177
178
|
const MEMO_PROGRAM_ADDRESS = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr";
|
|
178
179
|
const COMPUTE_UNIT_LIMIT = 2e4;
|
|
@@ -1150,7 +1151,7 @@ function createWalletCommand(ctx) {
|
|
|
1150
1151
|
if (parts[0]?.toLowerCase() === "send") return { text: "Use `/x_send <amount|all> <address>` for transfers." };
|
|
1151
1152
|
try {
|
|
1152
1153
|
const snap = await getWalletSnapshot(ctx.rpcUrl, solanaWallet, evmWallet, ctx.historyPath);
|
|
1153
|
-
const lines = [`x402-proxy v0.10.
|
|
1154
|
+
const lines = [`x402-proxy v0.10.4`];
|
|
1154
1155
|
const defaultModel = ctx.allModels[0];
|
|
1155
1156
|
if (defaultModel) lines.push("", `**Model** - ${defaultModel.name} (${defaultModel.provider})`);
|
|
1156
1157
|
lines.push("", `**Protocol** - ${ctx.getDefaultRequestProtocol()}`);
|
|
@@ -1366,8 +1367,12 @@ function resolveMppSessionBudget(value, fallback = "0.5") {
|
|
|
1366
1367
|
}
|
|
1367
1368
|
//#endregion
|
|
1368
1369
|
//#region src/openclaw/route.ts
|
|
1370
|
+
const debug = process.env.X402_PROXY_DEBUG === "1";
|
|
1371
|
+
function dbg(msg) {
|
|
1372
|
+
if (debug) process.stderr.write(`[x402-proxy] ${msg}\n`);
|
|
1373
|
+
}
|
|
1369
1374
|
function createInferenceProxyRouteHandler(opts) {
|
|
1370
|
-
const { providers, getX402Proxy, getWalletAddress, getWalletAddressForNetwork,
|
|
1375
|
+
const { providers, getX402Proxy, getMppHandler, getWalletAddress, getWalletAddressForNetwork, historyPath, allModels, logger } = opts;
|
|
1371
1376
|
const sortedProviders = providers.slice().sort((left, right) => right.baseUrl.length - left.baseUrl.length);
|
|
1372
1377
|
return async (req, res) => {
|
|
1373
1378
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
@@ -1391,6 +1396,7 @@ function createInferenceProxyRouteHandler(opts) {
|
|
|
1391
1396
|
}
|
|
1392
1397
|
const pathSuffix = provider.baseUrl === "/" ? url.pathname : url.pathname.slice(provider.baseUrl.length);
|
|
1393
1398
|
const upstreamUrl = `${provider.upstreamUrl.replace(/\/+$/, "")}${pathSuffix.startsWith("/") ? pathSuffix : `/${pathSuffix}`}${url.search}`;
|
|
1399
|
+
dbg(`${req.method} ${url.pathname} -> ${upstreamUrl}`);
|
|
1394
1400
|
logger.info(`proxy: intercepting ${upstreamUrl.substring(0, 80)}`);
|
|
1395
1401
|
const chunks = [];
|
|
1396
1402
|
for await (const chunk of req) chunks.push(Buffer.from(chunk));
|
|
@@ -1437,8 +1443,8 @@ function createInferenceProxyRouteHandler(opts) {
|
|
|
1437
1443
|
const useMpp = provider.protocol === "mpp" || provider.protocol === "auto";
|
|
1438
1444
|
const wantsStreaming = isLlmEndpoint && /"stream"\s*:\s*true/.test(body);
|
|
1439
1445
|
if (useMpp) {
|
|
1440
|
-
const
|
|
1441
|
-
if (!
|
|
1446
|
+
const mpp = getMppHandler();
|
|
1447
|
+
if (!mpp) {
|
|
1442
1448
|
res.writeHead(503, { "Content-Type": "application/json" });
|
|
1443
1449
|
res.end(JSON.stringify({ error: {
|
|
1444
1450
|
message: "MPP inference requires an EVM wallet. Configure X402_PROXY_WALLET_MNEMONIC or X402_PROXY_WALLET_EVM_KEY.",
|
|
@@ -1447,7 +1453,6 @@ function createInferenceProxyRouteHandler(opts) {
|
|
|
1447
1453
|
return true;
|
|
1448
1454
|
}
|
|
1449
1455
|
return await handleMppRequest({
|
|
1450
|
-
req,
|
|
1451
1456
|
res,
|
|
1452
1457
|
upstreamUrl,
|
|
1453
1458
|
requestInit,
|
|
@@ -1459,8 +1464,7 @@ function createInferenceProxyRouteHandler(opts) {
|
|
|
1459
1464
|
wantsStreaming,
|
|
1460
1465
|
isMessagesApi,
|
|
1461
1466
|
startMs,
|
|
1462
|
-
|
|
1463
|
-
mppSessionBudget: provider.mppSessionBudget
|
|
1467
|
+
mpp
|
|
1464
1468
|
});
|
|
1465
1469
|
}
|
|
1466
1470
|
const proxy = getX402Proxy();
|
|
@@ -1697,17 +1701,8 @@ function createSseTracker() {
|
|
|
1697
1701
|
}
|
|
1698
1702
|
};
|
|
1699
1703
|
}
|
|
1700
|
-
async function closeMppSession(handler) {
|
|
1701
|
-
try {
|
|
1702
|
-
await handler.close();
|
|
1703
|
-
} catch {}
|
|
1704
|
-
}
|
|
1705
1704
|
async function handleMppRequest(opts) {
|
|
1706
|
-
const {
|
|
1707
|
-
const mpp = await createMppProxyHandler({
|
|
1708
|
-
evmKey,
|
|
1709
|
-
maxDeposit: mppSessionBudget
|
|
1710
|
-
});
|
|
1705
|
+
const { res, upstreamUrl, requestInit, walletAddress, historyPath, logger, allModels, thinkingMode, wantsStreaming, isMessagesApi, startMs, mpp } = opts;
|
|
1711
1706
|
try {
|
|
1712
1707
|
if (wantsStreaming) {
|
|
1713
1708
|
res.writeHead(200, {
|
|
@@ -1716,7 +1711,9 @@ async function handleMppRequest(opts) {
|
|
|
1716
1711
|
Connection: "keep-alive"
|
|
1717
1712
|
});
|
|
1718
1713
|
const sse = createSseTracker();
|
|
1714
|
+
dbg(`mpp.sse() calling ${upstreamUrl}`);
|
|
1719
1715
|
const stream = await mpp.sse(upstreamUrl, requestInit);
|
|
1716
|
+
dbg("mpp.sse() resolved, iterating stream");
|
|
1720
1717
|
if (isMessagesApi) for await (const chunk of stream) {
|
|
1721
1718
|
const text = String(chunk);
|
|
1722
1719
|
let eventType = "unknown";
|
|
@@ -1734,17 +1731,15 @@ async function handleMppRequest(opts) {
|
|
|
1734
1731
|
}
|
|
1735
1732
|
res.write("data: [DONE]\n\n");
|
|
1736
1733
|
}
|
|
1734
|
+
dbg(`stream done, ${sse.result ? `${sse.result.model} ${sse.result.inputTokens}+${sse.result.outputTokens}t` : "no usage"}`);
|
|
1737
1735
|
res.end();
|
|
1738
1736
|
mpp.shiftPayment();
|
|
1739
|
-
const payment = mpp.shiftPayment();
|
|
1740
1737
|
appendInferenceHistory({
|
|
1741
1738
|
historyPath,
|
|
1742
1739
|
allModels,
|
|
1743
1740
|
walletAddress,
|
|
1744
|
-
paymentNetwork:
|
|
1741
|
+
paymentNetwork: TEMPO_NETWORK,
|
|
1745
1742
|
paymentTo: void 0,
|
|
1746
|
-
tx: payment?.receipt?.reference ?? payment?.channelId,
|
|
1747
|
-
amount: parseMppAmount(payment?.amount),
|
|
1748
1743
|
thinkingMode,
|
|
1749
1744
|
usage: sse.result,
|
|
1750
1745
|
durationMs: Date.now() - startMs
|
|
@@ -1790,6 +1785,7 @@ async function handleMppRequest(opts) {
|
|
|
1790
1785
|
});
|
|
1791
1786
|
return true;
|
|
1792
1787
|
} catch (err) {
|
|
1788
|
+
dbg(`mpp error: ${String(err)}`);
|
|
1793
1789
|
logger.error(`mpp: fetch threw: ${String(err)}`);
|
|
1794
1790
|
appendHistory(historyPath, {
|
|
1795
1791
|
t: Date.now(),
|
|
@@ -1801,11 +1797,8 @@ async function handleMppRequest(opts) {
|
|
|
1801
1797
|
error: String(err).substring(0, 200)
|
|
1802
1798
|
});
|
|
1803
1799
|
if (!res.headersSent) writeErrorResponse(res, 402, `MPP request failed: ${String(err)}`, "mpp_payment_error", "payment_failed", isMessagesApi);
|
|
1800
|
+
else if (!res.writableEnded) res.end();
|
|
1804
1801
|
return true;
|
|
1805
|
-
} finally {
|
|
1806
|
-
await closeMppSession(mpp);
|
|
1807
|
-
if (!res.writableEnded) res.end();
|
|
1808
|
-
req.resume();
|
|
1809
1802
|
}
|
|
1810
1803
|
}
|
|
1811
1804
|
function appendInferenceHistory(opts) {
|
|
@@ -1871,6 +1864,7 @@ function register(api) {
|
|
|
1871
1864
|
let evmWalletAddress = null;
|
|
1872
1865
|
let signerRef = null;
|
|
1873
1866
|
let proxyRef = null;
|
|
1867
|
+
let mppHandlerRef = null;
|
|
1874
1868
|
let evmKeyRef = null;
|
|
1875
1869
|
let walletLoadPromise = null;
|
|
1876
1870
|
const historyPath = getHistoryPath();
|
|
@@ -1878,9 +1872,9 @@ function register(api) {
|
|
|
1878
1872
|
const handler = createInferenceProxyRouteHandler({
|
|
1879
1873
|
providers,
|
|
1880
1874
|
getX402Proxy: () => proxyRef,
|
|
1875
|
+
getMppHandler: () => mppHandlerRef,
|
|
1881
1876
|
getWalletAddress: () => solanaWalletAddress ?? evmWalletAddress,
|
|
1882
1877
|
getWalletAddressForNetwork: (network) => addressForNetwork(evmWalletAddress, solanaWalletAddress, network),
|
|
1883
|
-
getEvmKey: () => evmKeyRef,
|
|
1884
1878
|
historyPath,
|
|
1885
1879
|
allModels,
|
|
1886
1880
|
logger: api.logger
|
|
@@ -1921,6 +1915,13 @@ function register(api) {
|
|
|
1921
1915
|
client.register(SOL_MAINNET, new OptimizedSvmScheme(signerRef, { rpcUrl }));
|
|
1922
1916
|
proxyRef = createX402ProxyHandler({ client });
|
|
1923
1917
|
} else proxyRef = null;
|
|
1918
|
+
if (evmKeyRef) {
|
|
1919
|
+
const maxBudget = Math.max(...providers.map((p) => Number(p.mppSessionBudget) || .5)).toString();
|
|
1920
|
+
mppHandlerRef = await createMppProxyHandler({
|
|
1921
|
+
evmKey: evmKeyRef,
|
|
1922
|
+
maxDeposit: maxBudget
|
|
1923
|
+
});
|
|
1924
|
+
} else mppHandlerRef = null;
|
|
1924
1925
|
api.logger.info(`wallets: solana=${solanaWalletAddress ?? "missing"} evm=${evmWalletAddress ?? "missing"}`);
|
|
1925
1926
|
} catch (err) {
|
|
1926
1927
|
api.logger.error(`wallet load failed: ${err}`);
|
|
@@ -1934,7 +1935,11 @@ function register(api) {
|
|
|
1934
1935
|
async start() {
|
|
1935
1936
|
await ensureWalletLoaded();
|
|
1936
1937
|
},
|
|
1937
|
-
async stop() {
|
|
1938
|
+
async stop() {
|
|
1939
|
+
if (mppHandlerRef) try {
|
|
1940
|
+
await mppHandlerRef.close();
|
|
1941
|
+
} catch {}
|
|
1942
|
+
}
|
|
1938
1943
|
});
|
|
1939
1944
|
const toolCtx = {
|
|
1940
1945
|
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.4",
|
|
4
4
|
"description": "curl for x402 paid APIs. Auto-pays any endpoint on Base, Solana, and Tempo. Also works as an OpenClaw plugin.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -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