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 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.2...HEAD
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-DqGROXlC.js";
3
- import { a as getHistoryPath, c as loadConfig, i as getConfigDirShort, l as loadWalletFile, s as isConfigured, u as saveConfig } from "../derive-CY0En_Y3.js";
4
- import { n as setupCommand, t as runSetup } from "../setup-DtKrojW1.js";
5
- import { n as statusCommand } from "../status-B5oH1nHv.js";
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, getEvmKey, historyPath, allModels, logger } = opts;
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 evmKey = getEvmKey();
373
- if (!evmKey) {
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
- evmKey,
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 { req, res, upstreamUrl, requestInit, walletAddress, historyPath, logger, allModels, thinkingMode, wantsStreaming, isMessagesApi, startMs, evmKey, mppSessionBudget } = opts;
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: payment?.network ?? "eip155:4217",
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-EX1_teNg.js");
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: true
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-DlR8yBrK.js");
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-EX1_teNg.js");
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-7XKcknNZ.js");
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-7XKcknNZ.js");
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-EX1_teNg.js");
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-7XKcknNZ.js");
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.3"
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.3"
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.3"
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.3"
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.3" },
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 args.push(a);
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,3 @@
1
+ #!/usr/bin/env node
2
+ import { f as saveSession, t as clearSession } from "./config-D9wIR3xc.js";
3
+ export { clearSession, saveSession };
@@ -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 { getHistoryPath as a, loadConfig as c, saveWalletFile as d, getConfigDirShort as i, loadWalletFile as l, deriveSolanaKeypair as n, getWalletPath as o, generateMnemonic$1 as r, isConfigured as s, deriveEvmKeypair as t, saveConfig as u };
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({
@@ -1,10 +1,10 @@
1
- import os, { homedir } from "node:os";
2
- import path, { dirname, join } from "node:path";
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 fs, { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
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.3`];
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, getEvmKey, historyPath, allModels, logger } = opts;
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 evmKey = getEvmKey();
1441
- if (!evmKey) {
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
- evmKey,
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 { req, res, upstreamUrl, requestInit, walletAddress, historyPath, logger, allModels, thinkingMode, wantsStreaming, isMessagesApi, startMs, evmKey, mppSessionBudget } = opts;
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: payment?.network ?? "eip155:4217",
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 { d as saveWalletFile, i as getConfigDirShort, l as loadWalletFile, n as deriveSolanaKeypair, o as getWalletPath, r as generateMnemonic, s as isConfigured, t as deriveEvmKeypair, u as saveConfig } from "./derive-CY0En_Y3.js";
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";
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { t as runSetup } from "./setup-CNyMLnM-.js";
3
+ export { runSetup };
@@ -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-DqGROXlC.js";
3
- import { a as getHistoryPath, c as loadConfig, i as getConfigDirShort } from "./derive-CY0En_Y3.js";
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
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { t as displayStatus } from "./status-Dc1dB0ZG.js";
3
+ export { displayStatus };
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { n as fetchAllBalances } from "./wallet-LUl2l8WM.js";
3
+ export { fetchAllBalances };
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { a as getHistoryPath, l as loadWalletFile, n as deriveSolanaKeypair, t as deriveEvmKeypair } from "./derive-CY0En_Y3.js";
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",
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.4.9",
58
+ "mppx": "^0.5.1",
59
59
  "picocolors": "^1.1.1",
60
60
  "viem": "^2.47.6",
61
61
  "yaml": "^2.8.3"
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- import { t as runSetup } from "./setup-DtKrojW1.js";
3
- export { runSetup };
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- import { t as displayStatus } from "./status-B5oH1nHv.js";
3
- export { displayStatus };
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- import { n as fetchAllBalances } from "./wallet-DqGROXlC.js";
3
- export { fetchAllBalances };