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 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.2...HEAD
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-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,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, getEvmKey, historyPath, allModels, logger } = opts;
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 evmKey = getEvmKey();
373
- if (!evmKey) {
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
- evmKey,
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 { 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
- });
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: payment?.network ?? "eip155:4217",
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-EX1_teNg.js");
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-DlR8yBrK.js");
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-EX1_teNg.js");
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-7XKcknNZ.js");
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-7XKcknNZ.js");
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-EX1_teNg.js");
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-7XKcknNZ.js");
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.3"
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.3"
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.3"
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.3"
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.3" },
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 args.push(a);
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,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,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({
@@ -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,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.3`];
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, getEvmKey, historyPath, allModels, logger } = opts;
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 evmKey = getEvmKey();
1441
- if (!evmKey) {
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
- evmKey,
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 { 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
- });
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: payment?.network ?? "eip155:4217",
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 { 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.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.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 };