@blockrun/clawrouter 0.11.12 → 0.11.14

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/dist/index.js CHANGED
@@ -1,3 +1,168 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/solana-balance.ts
12
+ var solana_balance_exports = {};
13
+ __export(solana_balance_exports, {
14
+ SolanaBalanceMonitor: () => SolanaBalanceMonitor
15
+ });
16
+ import { address as solAddress, createSolanaRpc } from "@solana/kit";
17
+ var SOLANA_USDC_MINT, SOLANA_DEFAULT_RPC, BALANCE_TIMEOUT_MS, CACHE_TTL_MS2, SolanaBalanceMonitor;
18
+ var init_solana_balance = __esm({
19
+ "src/solana-balance.ts"() {
20
+ "use strict";
21
+ SOLANA_USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
22
+ SOLANA_DEFAULT_RPC = "https://api.mainnet-beta.solana.com";
23
+ BALANCE_TIMEOUT_MS = 1e4;
24
+ CACHE_TTL_MS2 = 3e4;
25
+ SolanaBalanceMonitor = class {
26
+ rpc;
27
+ walletAddress;
28
+ cachedBalance = null;
29
+ cachedAt = 0;
30
+ constructor(walletAddress, rpcUrl) {
31
+ this.walletAddress = walletAddress;
32
+ const url = rpcUrl || process["env"].CLAWROUTER_SOLANA_RPC_URL || SOLANA_DEFAULT_RPC;
33
+ this.rpc = createSolanaRpc(url);
34
+ }
35
+ async checkBalance() {
36
+ const now = Date.now();
37
+ if (this.cachedBalance !== null && now - this.cachedAt < CACHE_TTL_MS2) {
38
+ return this.buildInfo(this.cachedBalance);
39
+ }
40
+ const balance = await this.fetchBalance();
41
+ this.cachedBalance = balance;
42
+ this.cachedAt = now;
43
+ return this.buildInfo(balance);
44
+ }
45
+ deductEstimated(amountMicros) {
46
+ if (this.cachedBalance !== null && this.cachedBalance >= amountMicros) {
47
+ this.cachedBalance -= amountMicros;
48
+ }
49
+ }
50
+ invalidate() {
51
+ this.cachedBalance = null;
52
+ this.cachedAt = 0;
53
+ }
54
+ async refresh() {
55
+ this.invalidate();
56
+ return this.checkBalance();
57
+ }
58
+ /**
59
+ * Check if balance is sufficient for an estimated cost.
60
+ */
61
+ async checkSufficient(estimatedCostMicros) {
62
+ const info = await this.checkBalance();
63
+ if (info.balance >= estimatedCostMicros) {
64
+ return { sufficient: true, info };
65
+ }
66
+ const shortfall = estimatedCostMicros - info.balance;
67
+ return {
68
+ sufficient: false,
69
+ info,
70
+ shortfall: this.formatUSDC(shortfall)
71
+ };
72
+ }
73
+ /**
74
+ * Format USDC amount (in micros) as "$X.XX".
75
+ */
76
+ formatUSDC(amountMicros) {
77
+ const dollars = Number(amountMicros) / 1e6;
78
+ return `$${dollars.toFixed(2)}`;
79
+ }
80
+ getWalletAddress() {
81
+ return this.walletAddress;
82
+ }
83
+ async fetchBalance() {
84
+ const owner = solAddress(this.walletAddress);
85
+ const mint = solAddress(SOLANA_USDC_MINT);
86
+ const controller = new AbortController();
87
+ const timer = setTimeout(() => controller.abort(), BALANCE_TIMEOUT_MS);
88
+ try {
89
+ const response = await this.rpc.getTokenAccountsByOwner(owner, { mint }, { encoding: "jsonParsed" }).send({ abortSignal: controller.signal });
90
+ if (response.value.length === 0) return 0n;
91
+ let total = 0n;
92
+ for (const account of response.value) {
93
+ const parsed = account.account.data;
94
+ total += BigInt(parsed.parsed.info.tokenAmount.amount);
95
+ }
96
+ return total;
97
+ } catch (err) {
98
+ throw new Error(`Failed to fetch Solana USDC balance: ${err instanceof Error ? err.message : String(err)}`);
99
+ } finally {
100
+ clearTimeout(timer);
101
+ }
102
+ }
103
+ buildInfo(balance) {
104
+ const dollars = Number(balance) / 1e6;
105
+ return {
106
+ balance,
107
+ balanceUSD: `$${dollars.toFixed(2)}`,
108
+ isLow: balance < 1000000n,
109
+ isEmpty: balance < 100n,
110
+ walletAddress: this.walletAddress
111
+ };
112
+ }
113
+ };
114
+ }
115
+ });
116
+
117
+ // src/wallet.ts
118
+ var wallet_exports = {};
119
+ __export(wallet_exports, {
120
+ deriveAllKeys: () => deriveAllKeys,
121
+ deriveEvmKey: () => deriveEvmKey,
122
+ deriveSolanaKeyBytes: () => deriveSolanaKeyBytes,
123
+ generateWalletMnemonic: () => generateWalletMnemonic,
124
+ isValidMnemonic: () => isValidMnemonic
125
+ });
126
+ import { HDKey } from "@scure/bip32";
127
+ import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from "@scure/bip39";
128
+ import { wordlist as english } from "@scure/bip39/wordlists/english";
129
+ import { privateKeyToAccount } from "viem/accounts";
130
+ function generateWalletMnemonic() {
131
+ return generateMnemonic(english, 256);
132
+ }
133
+ function isValidMnemonic(mnemonic) {
134
+ return validateMnemonic(mnemonic, english);
135
+ }
136
+ function deriveEvmKey(mnemonic) {
137
+ const seed = mnemonicToSeedSync(mnemonic);
138
+ const hdKey = HDKey.fromMasterSeed(seed);
139
+ const derived = hdKey.derive(ETH_DERIVATION_PATH);
140
+ if (!derived.privateKey) throw new Error("Failed to derive EVM private key");
141
+ const hex = `0x${Buffer.from(derived.privateKey).toString("hex")}`;
142
+ const account = privateKeyToAccount(hex);
143
+ return { privateKey: hex, address: account.address };
144
+ }
145
+ function deriveSolanaKeyBytes(mnemonic) {
146
+ const seed = mnemonicToSeedSync(mnemonic);
147
+ const hdKey = HDKey.fromMasterSeed(seed);
148
+ const derived = hdKey.derive(SOLANA_DERIVATION_PATH);
149
+ if (!derived.privateKey) throw new Error("Failed to derive Solana private key");
150
+ return new Uint8Array(derived.privateKey);
151
+ }
152
+ function deriveAllKeys(mnemonic) {
153
+ const { privateKey: evmPrivateKey, address: evmAddress } = deriveEvmKey(mnemonic);
154
+ const solanaPrivateKeyBytes = deriveSolanaKeyBytes(mnemonic);
155
+ return { mnemonic, evmPrivateKey, evmAddress, solanaPrivateKeyBytes };
156
+ }
157
+ var ETH_DERIVATION_PATH, SOLANA_DERIVATION_PATH;
158
+ var init_wallet = __esm({
159
+ "src/wallet.ts"() {
160
+ "use strict";
161
+ ETH_DERIVATION_PATH = "m/44'/60'/0'/0/0";
162
+ SOLANA_DERIVATION_PATH = "m/44'/501'/0'/0'";
163
+ }
164
+ });
165
+
1
166
  // src/models.ts
2
167
  var MODEL_ALIASES = {
3
168
  // Claude - use newest versions (4.6)
@@ -644,271 +809,72 @@ var blockrunProvider = {
644
809
  // src/proxy.ts
645
810
  import { createServer } from "http";
646
811
  import { finished } from "stream";
647
- import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
812
+ import { createPublicClient as createPublicClient2, http as http2 } from "viem";
813
+ import { base as base2 } from "viem/chains";
814
+ import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
815
+ import { x402Client } from "@x402/fetch";
648
816
 
649
- // src/x402.ts
650
- import { signTypedData, privateKeyToAccount } from "viem/accounts";
651
-
652
- // src/payment-cache.ts
817
+ // src/payment-preauth.ts
818
+ import { x402HTTPClient } from "@x402/fetch";
653
819
  var DEFAULT_TTL_MS = 36e5;
654
- var PaymentCache = class {
655
- cache = /* @__PURE__ */ new Map();
656
- ttlMs;
657
- constructor(ttlMs = DEFAULT_TTL_MS) {
658
- this.ttlMs = ttlMs;
659
- }
660
- /** Get cached payment params for an endpoint path. */
661
- get(endpointPath) {
662
- const entry = this.cache.get(endpointPath);
663
- if (!entry) return void 0;
664
- if (Date.now() - entry.cachedAt > this.ttlMs) {
665
- this.cache.delete(endpointPath);
666
- return void 0;
667
- }
668
- return entry;
669
- }
670
- /** Cache payment params from a 402 response. */
671
- set(endpointPath, params) {
672
- this.cache.set(endpointPath, { ...params, cachedAt: Date.now() });
673
- }
674
- /** Invalidate cache for an endpoint (e.g., if payTo changed). */
675
- invalidate(endpointPath) {
676
- this.cache.delete(endpointPath);
677
- }
678
- };
679
-
680
- // src/x402.ts
681
- var BASE_CHAIN_ID = 8453;
682
- var BASE_SEPOLIA_CHAIN_ID = 84532;
683
- var DEFAULT_TOKEN_NAME = "USD Coin";
684
- var DEFAULT_TOKEN_VERSION = "2";
685
- var DEFAULT_NETWORK = "eip155:8453";
686
- var DEFAULT_MAX_TIMEOUT_SECONDS = 300;
687
- var TRANSFER_TYPES = {
688
- TransferWithAuthorization: [
689
- { name: "from", type: "address" },
690
- { name: "to", type: "address" },
691
- { name: "value", type: "uint256" },
692
- { name: "validAfter", type: "uint256" },
693
- { name: "validBefore", type: "uint256" },
694
- { name: "nonce", type: "bytes32" }
695
- ]
696
- };
697
- function createNonce() {
698
- const bytes = new Uint8Array(32);
699
- crypto.getRandomValues(bytes);
700
- return `0x${Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("")}`;
701
- }
702
- function decodeBase64Json(value) {
703
- const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
704
- const padding = (4 - normalized.length % 4) % 4;
705
- const padded = normalized + "=".repeat(padding);
706
- const decoded = Buffer.from(padded, "base64").toString("utf8");
707
- return JSON.parse(decoded);
708
- }
709
- function encodeBase64Json(value) {
710
- return Buffer.from(JSON.stringify(value), "utf8").toString("base64");
711
- }
712
- function parsePaymentRequired(headerValue) {
713
- return decodeBase64Json(headerValue);
714
- }
715
- function normalizeNetwork(network) {
716
- if (!network || network.trim().length === 0) {
717
- return DEFAULT_NETWORK;
718
- }
719
- return network.trim().toLowerCase();
720
- }
721
- function resolveChainId(network) {
722
- const eip155Match = network.match(/^eip155:(\d+)$/i);
723
- if (eip155Match) {
724
- const parsed = Number.parseInt(eip155Match[1], 10);
725
- if (Number.isFinite(parsed) && parsed > 0) {
726
- return parsed;
727
- }
728
- }
729
- if (network === "base") return BASE_CHAIN_ID;
730
- if (network === "base-sepolia") return BASE_SEPOLIA_CHAIN_ID;
731
- return BASE_CHAIN_ID;
732
- }
733
- function parseHexAddress(value) {
734
- if (!value) return void 0;
735
- const direct = value.match(/^0x[a-fA-F0-9]{40}$/);
736
- if (direct) {
737
- return direct[0];
738
- }
739
- const caipSuffix = value.match(/0x[a-fA-F0-9]{40}$/);
740
- if (caipSuffix) {
741
- return caipSuffix[0];
742
- }
743
- return void 0;
744
- }
745
- function requireHexAddress(value, field) {
746
- const parsed = parseHexAddress(value);
747
- if (!parsed) {
748
- throw new Error(`Invalid ${field} in payment requirements: ${String(value)}`);
749
- }
750
- return parsed;
751
- }
752
- function setPaymentHeaders(headers, payload) {
753
- headers.set("payment-signature", payload);
754
- headers.set("x-payment", payload);
755
- }
756
- async function createPaymentPayload(privateKey, fromAddress, option, amount, requestUrl, resource) {
757
- const network = normalizeNetwork(option.network);
758
- const chainId = resolveChainId(network);
759
- const recipient = requireHexAddress(option.payTo, "payTo");
760
- const verifyingContract = requireHexAddress(option.asset, "asset");
761
- const maxTimeoutSeconds = typeof option.maxTimeoutSeconds === "number" && option.maxTimeoutSeconds > 0 ? Math.floor(option.maxTimeoutSeconds) : DEFAULT_MAX_TIMEOUT_SECONDS;
762
- const now = Math.floor(Date.now() / 1e3);
763
- const validAfter = now - 600;
764
- const validBefore = now + maxTimeoutSeconds;
765
- const nonce = createNonce();
766
- const signature = await signTypedData({
767
- privateKey,
768
- domain: {
769
- name: option.extra?.name || DEFAULT_TOKEN_NAME,
770
- version: option.extra?.version || DEFAULT_TOKEN_VERSION,
771
- chainId,
772
- verifyingContract
773
- },
774
- types: TRANSFER_TYPES,
775
- primaryType: "TransferWithAuthorization",
776
- message: {
777
- from: fromAddress,
778
- to: recipient,
779
- value: BigInt(amount),
780
- validAfter: BigInt(validAfter),
781
- validBefore: BigInt(validBefore),
782
- nonce
783
- }
784
- });
785
- const paymentData = {
786
- x402Version: 2,
787
- resource: {
788
- url: resource?.url || requestUrl,
789
- description: resource?.description || "BlockRun AI API call",
790
- mimeType: "application/json"
791
- },
792
- accepted: {
793
- scheme: option.scheme,
794
- network,
795
- amount,
796
- asset: option.asset,
797
- payTo: option.payTo,
798
- maxTimeoutSeconds: option.maxTimeoutSeconds,
799
- extra: option.extra
800
- },
801
- payload: {
802
- signature,
803
- authorization: {
804
- from: fromAddress,
805
- to: recipient,
806
- value: amount,
807
- validAfter: validAfter.toString(),
808
- validBefore: validBefore.toString(),
809
- nonce
810
- }
811
- },
812
- extensions: {}
813
- };
814
- return encodeBase64Json(paymentData);
815
- }
816
- function createPaymentFetch(privateKey) {
817
- const account = privateKeyToAccount(privateKey);
818
- const walletAddress = account.address;
819
- const paymentCache = new PaymentCache();
820
- const payFetch = async (input, init, preAuth) => {
821
- const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
822
- const endpointPath = new URL(url).pathname;
823
- const cached = paymentCache.get(endpointPath);
824
- if (cached && preAuth?.estimatedAmount) {
825
- const paymentPayload = await createPaymentPayload(
826
- privateKey,
827
- walletAddress,
828
- {
829
- scheme: cached.scheme,
830
- network: cached.network,
831
- asset: cached.asset,
832
- payTo: cached.payTo,
833
- maxTimeoutSeconds: cached.maxTimeoutSeconds,
834
- extra: cached.extra
835
- },
836
- preAuth.estimatedAmount,
837
- url,
838
- {
839
- url: cached.resourceUrl,
840
- description: cached.resourceDescription
820
+ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, options) {
821
+ const httpClient = new x402HTTPClient(client);
822
+ const cache = /* @__PURE__ */ new Map();
823
+ return async (input, init) => {
824
+ const request = new Request(input, init);
825
+ const urlPath = new URL(request.url).pathname;
826
+ const cached = !options?.skipPreAuth ? cache.get(urlPath) : void 0;
827
+ if (cached && Date.now() - cached.cachedAt < ttlMs) {
828
+ try {
829
+ const payload2 = await client.createPaymentPayload(cached.paymentRequired);
830
+ const headers = httpClient.encodePaymentSignatureHeader(payload2);
831
+ const preAuthRequest = request.clone();
832
+ for (const [key, value] of Object.entries(headers)) {
833
+ preAuthRequest.headers.set(key, value);
841
834
  }
842
- );
843
- const preAuthHeaders = new Headers(init?.headers);
844
- setPaymentHeaders(preAuthHeaders, paymentPayload);
845
- const response2 = await fetch(input, { ...init, headers: preAuthHeaders });
846
- if (response2.status !== 402) {
847
- return response2;
848
- }
849
- const paymentHeader2 = response2.headers.get("x-payment-required");
850
- if (paymentHeader2) {
851
- return handle402(input, init, url, endpointPath, paymentHeader2);
852
- }
853
- paymentCache.invalidate(endpointPath);
854
- const cleanResponse = await fetch(input, init);
855
- if (cleanResponse.status !== 402) {
856
- return cleanResponse;
857
- }
858
- const cleanHeader = cleanResponse.headers.get("x-payment-required");
859
- if (!cleanHeader) {
860
- throw new Error("402 response missing x-payment-required header");
835
+ const response2 = await baseFetch(preAuthRequest);
836
+ if (response2.status !== 402) {
837
+ return response2;
838
+ }
839
+ cache.delete(urlPath);
840
+ } catch {
841
+ cache.delete(urlPath);
861
842
  }
862
- return handle402(input, init, url, endpointPath, cleanHeader);
863
843
  }
864
- const response = await fetch(input, init);
844
+ const clonedRequest = request.clone();
845
+ const response = await baseFetch(request);
865
846
  if (response.status !== 402) {
866
847
  return response;
867
848
  }
868
- const paymentHeader = response.headers.get("x-payment-required");
869
- if (!paymentHeader) {
870
- throw new Error("402 response missing x-payment-required header");
849
+ let paymentRequired;
850
+ try {
851
+ const getHeader = (name) => response.headers.get(name);
852
+ let body;
853
+ try {
854
+ const responseText = await response.text();
855
+ if (responseText) body = JSON.parse(responseText);
856
+ } catch {
857
+ }
858
+ paymentRequired = httpClient.getPaymentRequiredResponse(getHeader, body);
859
+ cache.set(urlPath, { paymentRequired, cachedAt: Date.now() });
860
+ } catch (error) {
861
+ throw new Error(
862
+ `Failed to parse payment requirements: ${error instanceof Error ? error.message : "Unknown error"}`
863
+ );
864
+ }
865
+ const payload = await client.createPaymentPayload(paymentRequired);
866
+ const paymentHeaders = httpClient.encodePaymentSignatureHeader(payload);
867
+ for (const [key, value] of Object.entries(paymentHeaders)) {
868
+ clonedRequest.headers.set(key, value);
871
869
  }
872
- return handle402(input, init, url, endpointPath, paymentHeader);
870
+ return baseFetch(clonedRequest);
873
871
  };
874
- async function handle402(input, init, url, endpointPath, paymentHeader) {
875
- const paymentRequired = parsePaymentRequired(paymentHeader);
876
- const option = paymentRequired.accepts?.[0];
877
- if (!option) {
878
- throw new Error("No payment options in 402 response");
879
- }
880
- const amount = option.amount || option.maxAmountRequired;
881
- if (!amount) {
882
- throw new Error("No amount in payment requirements");
883
- }
884
- paymentCache.set(endpointPath, {
885
- payTo: option.payTo,
886
- asset: option.asset,
887
- scheme: option.scheme,
888
- network: option.network,
889
- extra: option.extra,
890
- maxTimeoutSeconds: option.maxTimeoutSeconds,
891
- resourceUrl: paymentRequired.resource?.url,
892
- resourceDescription: paymentRequired.resource?.description
893
- });
894
- const paymentPayload = await createPaymentPayload(
895
- privateKey,
896
- walletAddress,
897
- option,
898
- amount,
899
- url,
900
- paymentRequired.resource
901
- );
902
- const retryHeaders = new Headers(init?.headers);
903
- setPaymentHeaders(retryHeaders, paymentPayload);
904
- return fetch(input, {
905
- ...init,
906
- headers: retryHeaders
907
- });
908
- }
909
- return { fetch: payFetch, cache: paymentCache };
910
872
  }
911
873
 
874
+ // src/proxy.ts
875
+ import { registerExactEvmScheme } from "@x402/evm/exact/client";
876
+ import { toClientEvmSigner } from "@x402/evm";
877
+
912
878
  // src/router/rules.ts
913
879
  function scoreTokenCount(estimatedTokens, thresholds) {
914
880
  if (estimatedTokens < thresholds.simple) {
@@ -2435,7 +2401,7 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
2435
2401
  const ruleResult = classifyByRules(prompt, systemPrompt, estimatedTokens, config.scoring);
2436
2402
  const { routingProfile } = options;
2437
2403
  let tierConfigs;
2438
- let profileSuffix = "";
2404
+ let profileSuffix;
2439
2405
  if (routingProfile === "eco" && config.ecoTiers) {
2440
2406
  tierConfigs = config.ecoTiers;
2441
2407
  profileSuffix = " | eco";
@@ -3252,6 +3218,199 @@ var BalanceMonitor = class {
3252
3218
  }
3253
3219
  };
3254
3220
 
3221
+ // src/proxy.ts
3222
+ init_solana_balance();
3223
+
3224
+ // src/auth.ts
3225
+ import { writeFile, mkdir as mkdir2 } from "fs/promises";
3226
+ init_wallet();
3227
+ import { join as join4 } from "path";
3228
+ import { homedir as homedir3 } from "os";
3229
+ import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
3230
+ var WALLET_DIR = join4(homedir3(), ".openclaw", "blockrun");
3231
+ var WALLET_FILE = join4(WALLET_DIR, "wallet.key");
3232
+ var MNEMONIC_FILE = join4(WALLET_DIR, "mnemonic");
3233
+ var CHAIN_FILE = join4(WALLET_DIR, "payment-chain");
3234
+ async function loadSavedWallet() {
3235
+ try {
3236
+ const key = (await readTextFile(WALLET_FILE)).trim();
3237
+ if (key.startsWith("0x") && key.length === 66) {
3238
+ console.log(`[ClawRouter] \u2713 Loaded existing wallet from ${WALLET_FILE}`);
3239
+ return key;
3240
+ }
3241
+ console.error(`[ClawRouter] \u2717 CRITICAL: Wallet file exists but has invalid format!`);
3242
+ console.error(`[ClawRouter] File: ${WALLET_FILE}`);
3243
+ console.error(`[ClawRouter] Expected: 0x followed by 64 hex characters (66 chars total)`);
3244
+ console.error(
3245
+ `[ClawRouter] To fix: restore your backup key or set BLOCKRUN_WALLET_KEY env var`
3246
+ );
3247
+ throw new Error(
3248
+ `Wallet file at ${WALLET_FILE} is corrupted or has wrong format. Refusing to auto-generate new wallet to protect existing funds. Restore your backup key or set BLOCKRUN_WALLET_KEY environment variable.`
3249
+ );
3250
+ } catch (err) {
3251
+ if (err.code !== "ENOENT") {
3252
+ if (err instanceof Error && err.message.includes("Refusing to auto-generate")) {
3253
+ throw err;
3254
+ }
3255
+ console.error(
3256
+ `[ClawRouter] \u2717 Failed to read wallet file: ${err instanceof Error ? err.message : String(err)}`
3257
+ );
3258
+ throw new Error(
3259
+ `Cannot read wallet file at ${WALLET_FILE}: ${err instanceof Error ? err.message : String(err)}. Refusing to auto-generate new wallet to protect existing funds. Fix file permissions or set BLOCKRUN_WALLET_KEY environment variable.`,
3260
+ { cause: err }
3261
+ );
3262
+ }
3263
+ }
3264
+ return void 0;
3265
+ }
3266
+ async function loadMnemonic() {
3267
+ try {
3268
+ const mnemonic = (await readTextFile(MNEMONIC_FILE)).trim();
3269
+ if (mnemonic && isValidMnemonic(mnemonic)) {
3270
+ return mnemonic;
3271
+ }
3272
+ console.warn(`[ClawRouter] \u26A0 Mnemonic file exists but has invalid format \u2014 ignoring`);
3273
+ return void 0;
3274
+ } catch (err) {
3275
+ if (err.code !== "ENOENT") {
3276
+ console.warn(`[ClawRouter] \u26A0 Cannot read mnemonic file \u2014 ignoring`);
3277
+ }
3278
+ }
3279
+ return void 0;
3280
+ }
3281
+ async function saveMnemonic(mnemonic) {
3282
+ await mkdir2(WALLET_DIR, { recursive: true });
3283
+ await writeFile(MNEMONIC_FILE, mnemonic + "\n", { mode: 384 });
3284
+ }
3285
+ async function generateAndSaveWallet() {
3286
+ const existingMnemonic = await loadMnemonic();
3287
+ if (existingMnemonic) {
3288
+ throw new Error(
3289
+ `Mnemonic file exists at ${MNEMONIC_FILE} but wallet.key is missing. This means a Solana wallet was derived from this mnemonic. Refusing to generate a new wallet to protect Solana funds. Restore your EVM key with: export BLOCKRUN_WALLET_KEY=<your_key>`
3290
+ );
3291
+ }
3292
+ const mnemonic = generateWalletMnemonic();
3293
+ const derived = deriveAllKeys(mnemonic);
3294
+ await mkdir2(WALLET_DIR, { recursive: true });
3295
+ await writeFile(WALLET_FILE, derived.evmPrivateKey + "\n", { mode: 384 });
3296
+ await writeFile(MNEMONIC_FILE, mnemonic + "\n", { mode: 384 });
3297
+ try {
3298
+ const verification = (await readTextFile(WALLET_FILE)).trim();
3299
+ if (verification !== derived.evmPrivateKey) {
3300
+ throw new Error("Wallet file verification failed - content mismatch");
3301
+ }
3302
+ console.log(`[ClawRouter] Wallet saved and verified at ${WALLET_FILE}`);
3303
+ } catch (err) {
3304
+ throw new Error(
3305
+ `Failed to verify wallet file after creation: ${err instanceof Error ? err.message : String(err)}`,
3306
+ { cause: err }
3307
+ );
3308
+ }
3309
+ console.log(`[ClawRouter]`);
3310
+ console.log(`[ClawRouter] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
3311
+ console.log(`[ClawRouter] NEW WALLET GENERATED \u2014 BACK UP YOUR KEY NOW`);
3312
+ console.log(`[ClawRouter] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
3313
+ console.log(`[ClawRouter] EVM Address : ${derived.evmAddress}`);
3314
+ console.log(`[ClawRouter] Key file : ${WALLET_FILE}`);
3315
+ console.log(`[ClawRouter] Mnemonic : ${MNEMONIC_FILE}`);
3316
+ console.log(`[ClawRouter]`);
3317
+ console.log(`[ClawRouter] Both EVM (Base) and Solana wallets are ready.`);
3318
+ console.log(`[ClawRouter] To back up, run in OpenClaw:`);
3319
+ console.log(`[ClawRouter] /wallet export`);
3320
+ console.log(`[ClawRouter]`);
3321
+ console.log(`[ClawRouter] To restore on another machine:`);
3322
+ console.log(`[ClawRouter] export BLOCKRUN_WALLET_KEY=<your_key>`);
3323
+ console.log(`[ClawRouter] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
3324
+ console.log(`[ClawRouter]`);
3325
+ return {
3326
+ key: derived.evmPrivateKey,
3327
+ address: derived.evmAddress,
3328
+ mnemonic,
3329
+ solanaPrivateKeyBytes: derived.solanaPrivateKeyBytes
3330
+ };
3331
+ }
3332
+ async function resolveOrGenerateWalletKey() {
3333
+ const saved = await loadSavedWallet();
3334
+ if (saved) {
3335
+ const account = privateKeyToAccount2(saved);
3336
+ const mnemonic = await loadMnemonic();
3337
+ if (mnemonic) {
3338
+ const solanaKeyBytes = deriveSolanaKeyBytes(mnemonic);
3339
+ return {
3340
+ key: saved,
3341
+ address: account.address,
3342
+ source: "saved",
3343
+ mnemonic,
3344
+ solanaPrivateKeyBytes: solanaKeyBytes
3345
+ };
3346
+ }
3347
+ return { key: saved, address: account.address, source: "saved" };
3348
+ }
3349
+ const envKey = process["env"].BLOCKRUN_WALLET_KEY;
3350
+ if (typeof envKey === "string" && envKey.startsWith("0x") && envKey.length === 66) {
3351
+ const account = privateKeyToAccount2(envKey);
3352
+ const mnemonic = await loadMnemonic();
3353
+ if (mnemonic) {
3354
+ const solanaKeyBytes = deriveSolanaKeyBytes(mnemonic);
3355
+ return {
3356
+ key: envKey,
3357
+ address: account.address,
3358
+ source: "env",
3359
+ mnemonic,
3360
+ solanaPrivateKeyBytes: solanaKeyBytes
3361
+ };
3362
+ }
3363
+ return { key: envKey, address: account.address, source: "env" };
3364
+ }
3365
+ const result = await generateAndSaveWallet();
3366
+ return {
3367
+ key: result.key,
3368
+ address: result.address,
3369
+ source: "generated",
3370
+ mnemonic: result.mnemonic,
3371
+ solanaPrivateKeyBytes: result.solanaPrivateKeyBytes
3372
+ };
3373
+ }
3374
+ async function setupSolana() {
3375
+ const existing = await loadMnemonic();
3376
+ if (existing) {
3377
+ throw new Error(
3378
+ "Solana wallet already set up. Mnemonic file exists at " + MNEMONIC_FILE
3379
+ );
3380
+ }
3381
+ const savedKey = await loadSavedWallet();
3382
+ if (!savedKey) {
3383
+ throw new Error(
3384
+ "No EVM wallet found. Run ClawRouter first to generate a wallet before setting up Solana."
3385
+ );
3386
+ }
3387
+ const mnemonic = generateWalletMnemonic();
3388
+ const solanaKeyBytes = deriveSolanaKeyBytes(mnemonic);
3389
+ await saveMnemonic(mnemonic);
3390
+ console.log(`[ClawRouter] Solana wallet set up successfully.`);
3391
+ console.log(`[ClawRouter] Mnemonic saved to ${MNEMONIC_FILE}`);
3392
+ console.log(`[ClawRouter] Existing EVM wallet unchanged.`);
3393
+ return { mnemonic, solanaPrivateKeyBytes: solanaKeyBytes };
3394
+ }
3395
+ async function savePaymentChain(chain) {
3396
+ await mkdir2(WALLET_DIR, { recursive: true });
3397
+ await writeFile(CHAIN_FILE, chain + "\n", { mode: 384 });
3398
+ }
3399
+ async function loadPaymentChain() {
3400
+ try {
3401
+ const content = (await readTextFile(CHAIN_FILE)).trim();
3402
+ if (content === "solana") return "solana";
3403
+ return "base";
3404
+ } catch {
3405
+ return "base";
3406
+ }
3407
+ }
3408
+ async function resolvePaymentChain() {
3409
+ if (process["env"].CLAWROUTER_PAYMENT_CHAIN === "solana") return "solana";
3410
+ if (process["env"].CLAWROUTER_PAYMENT_CHAIN === "base") return "base";
3411
+ return loadPaymentChain();
3412
+ }
3413
+
3255
3414
  // src/compression/types.ts
3256
3415
  var DEFAULT_COMPRESSION_CONFIG = {
3257
3416
  enabled: true,
@@ -3281,7 +3440,7 @@ var DEFAULT_COMPRESSION_CONFIG = {
3281
3440
  };
3282
3441
 
3283
3442
  // src/compression/layers/deduplication.ts
3284
- import crypto2 from "crypto";
3443
+ import crypto from "crypto";
3285
3444
  function hashMessage(message) {
3286
3445
  let contentStr = "";
3287
3446
  if (typeof message.content === "string") {
@@ -3301,7 +3460,7 @@ function hashMessage(message) {
3301
3460
  );
3302
3461
  }
3303
3462
  const content = parts.join("|");
3304
- return crypto2.createHash("md5").update(content).digest("hex");
3463
+ return crypto.createHash("md5").update(content).digest("hex");
3305
3464
  }
3306
3465
  function deduplicateMessages(messages) {
3307
3466
  const seen = /* @__PURE__ */ new Set();
@@ -3454,7 +3613,7 @@ function generateCodebookHeader(usedCodes, pathMap = {}) {
3454
3613
  parts.push(`[Dict: ${codeEntries}]`);
3455
3614
  }
3456
3615
  if (Object.keys(pathMap).length > 0) {
3457
- const pathEntries = Object.entries(pathMap).map(([code, path]) => `${code}=${path}`).join(", ");
3616
+ const pathEntries = Object.entries(pathMap).map(([code, path2]) => `${code}=${path2}`).join(", ");
3458
3617
  parts.push(`[Paths: ${pathEntries}]`);
3459
3618
  }
3460
3619
  return parts.join("\n");
@@ -3528,8 +3687,8 @@ function extractPaths(messages) {
3528
3687
  }
3529
3688
  function findFrequentPrefixes(paths) {
3530
3689
  const prefixCounts = /* @__PURE__ */ new Map();
3531
- for (const path of paths) {
3532
- const parts = path.split("/").filter(Boolean);
3690
+ for (const path2 of paths) {
3691
+ const parts = path2.split("/").filter(Boolean);
3533
3692
  for (let i = 2; i < parts.length; i++) {
3534
3693
  const prefix = "/" + parts.slice(0, i).join("/") + "/";
3535
3694
  prefixCounts.set(prefix, (prefixCounts.get(prefix) || 0) + 1);
@@ -4398,6 +4557,7 @@ ${lines.join("\n")}`;
4398
4557
 
4399
4558
  // src/proxy.ts
4400
4559
  var BLOCKRUN_API = "https://blockrun.ai/api";
4560
+ var BLOCKRUN_SOLANA_API = "https://sol.blockrun.ai/api";
4401
4561
  var AUTO_MODEL = "blockrun/auto";
4402
4562
  var ROUTING_PROFILES = /* @__PURE__ */ new Set([
4403
4563
  "blockrun/free",
@@ -4526,7 +4686,7 @@ async function checkExistingProxy(port) {
4526
4686
  if (response.ok) {
4527
4687
  const data = await response.json();
4528
4688
  if (data.status === "ok" && data.wallet) {
4529
- return data.wallet;
4689
+ return { wallet: data.wallet, paymentChain: data.paymentChain };
4530
4690
  }
4531
4691
  }
4532
4692
  return void 0;
@@ -4956,31 +5116,78 @@ async function uploadDataUriToHost(dataUri) {
4956
5116
  throw new Error(`catbox.moe upload failed: ${result}`);
4957
5117
  }
4958
5118
  async function startProxy(options) {
4959
- const apiBase = options.apiBase ?? BLOCKRUN_API;
5119
+ const walletKey = typeof options.wallet === "string" ? options.wallet : options.wallet.key;
5120
+ const solanaPrivateKeyBytes = typeof options.wallet === "string" ? void 0 : options.wallet.solanaPrivateKeyBytes;
5121
+ const paymentChain = options.paymentChain ?? await resolvePaymentChain();
5122
+ const apiBase = options.apiBase ?? (paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
5123
+ if (paymentChain === "solana" && !solanaPrivateKeyBytes) {
5124
+ console.warn(`[ClawRouter] Payment chain is Solana but no Solana keys provided. Using Base (EVM).`);
5125
+ } else if (paymentChain === "solana") {
5126
+ console.log(`[ClawRouter] Payment chain: Solana (${BLOCKRUN_SOLANA_API})`);
5127
+ }
4960
5128
  const listenPort = options.port ?? getProxyPort();
4961
- const existingWallet = await checkExistingProxy(listenPort);
4962
- if (existingWallet) {
4963
- const account2 = privateKeyToAccount2(options.walletKey);
4964
- const balanceMonitor2 = new BalanceMonitor(account2.address);
5129
+ const existingProxy = await checkExistingProxy(listenPort);
5130
+ if (existingProxy) {
5131
+ const account2 = privateKeyToAccount3(walletKey);
4965
5132
  const baseUrl2 = `http://127.0.0.1:${listenPort}`;
4966
- if (existingWallet !== account2.address) {
5133
+ if (existingProxy.wallet !== account2.address) {
4967
5134
  console.warn(
4968
- `[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingWallet}, but current config uses ${account2.address}. Reusing existing proxy.`
5135
+ `[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingProxy.wallet}, but current config uses ${account2.address}. Reusing existing proxy.`
5136
+ );
5137
+ }
5138
+ if (existingProxy.paymentChain) {
5139
+ if (existingProxy.paymentChain !== paymentChain) {
5140
+ throw new Error(
5141
+ `Existing proxy on port ${listenPort} is using ${existingProxy.paymentChain} but ${paymentChain} was requested. Stop the existing proxy first or use a different port.`
5142
+ );
5143
+ }
5144
+ } else if (paymentChain !== "base") {
5145
+ console.warn(`[ClawRouter] Existing proxy on port ${listenPort} does not report paymentChain (pre-v0.11 instance). Assuming Base.`);
5146
+ throw new Error(
5147
+ `Existing proxy on port ${listenPort} is a pre-v0.11 instance (assumed Base) but ${paymentChain} was requested. Stop the existing proxy first or use a different port.`
4969
5148
  );
4970
5149
  }
5150
+ let reuseSolanaAddress;
5151
+ if (solanaPrivateKeyBytes) {
5152
+ const { createKeyPairSignerFromPrivateKeyBytes } = await import("@solana/kit");
5153
+ const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes(solanaPrivateKeyBytes);
5154
+ reuseSolanaAddress = solanaSigner.address;
5155
+ }
5156
+ const balanceMonitor2 = paymentChain === "solana" && reuseSolanaAddress ? new SolanaBalanceMonitor(reuseSolanaAddress) : new BalanceMonitor(account2.address);
4971
5157
  options.onReady?.(listenPort);
4972
5158
  return {
4973
5159
  port: listenPort,
4974
5160
  baseUrl: baseUrl2,
4975
- walletAddress: existingWallet,
5161
+ walletAddress: existingProxy.wallet,
5162
+ solanaAddress: reuseSolanaAddress,
4976
5163
  balanceMonitor: balanceMonitor2,
4977
5164
  close: async () => {
4978
5165
  }
4979
5166
  };
4980
5167
  }
4981
- const account = privateKeyToAccount2(options.walletKey);
4982
- const { fetch: payFetch } = createPaymentFetch(options.walletKey);
4983
- const balanceMonitor = new BalanceMonitor(account.address);
5168
+ const account = privateKeyToAccount3(walletKey);
5169
+ const evmPublicClient = createPublicClient2({ chain: base2, transport: http2() });
5170
+ const evmSigner = toClientEvmSigner(account, evmPublicClient);
5171
+ const x402 = new x402Client();
5172
+ registerExactEvmScheme(x402, { signer: evmSigner });
5173
+ let solanaAddress;
5174
+ if (solanaPrivateKeyBytes) {
5175
+ const { registerExactSvmScheme } = await import("@x402/svm/exact/client");
5176
+ const { createKeyPairSignerFromPrivateKeyBytes } = await import("@solana/kit");
5177
+ const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes(solanaPrivateKeyBytes);
5178
+ solanaAddress = solanaSigner.address;
5179
+ registerExactSvmScheme(x402, { signer: solanaSigner });
5180
+ console.log(`[ClawRouter] Solana x402 scheme registered: ${solanaAddress}`);
5181
+ }
5182
+ x402.onAfterPaymentCreation(async (context) => {
5183
+ const network = context.selectedRequirements.network;
5184
+ const chain = network.startsWith("eip155") ? "Base (EVM)" : network.startsWith("solana") ? "Solana" : network;
5185
+ console.log(`[ClawRouter] Payment signed on ${chain} (${network})`);
5186
+ });
5187
+ const payFetch = createPayFetchWithPreAuth(fetch, x402, void 0, {
5188
+ skipPreAuth: paymentChain === "solana"
5189
+ });
5190
+ const balanceMonitor = paymentChain === "solana" && solanaAddress ? new SolanaBalanceMonitor(solanaAddress) : new BalanceMonitor(account.address);
4984
5191
  const routingConfig = mergeRoutingConfig(options.routingConfig);
4985
5192
  const modelPricing = buildModelPricing();
4986
5193
  const routerOpts = {
@@ -5014,8 +5221,12 @@ async function startProxy(options) {
5014
5221
  const full = url.searchParams.get("full") === "true";
5015
5222
  const response = {
5016
5223
  status: "ok",
5017
- wallet: account.address
5224
+ wallet: account.address,
5225
+ paymentChain
5018
5226
  };
5227
+ if (solanaAddress) {
5228
+ response.solana = solanaAddress;
5229
+ }
5019
5230
  if (full) {
5020
5231
  try {
5021
5232
  const balanceInfo = await balanceMonitor.checkBalance();
@@ -5127,10 +5338,10 @@ async function startProxy(options) {
5127
5338
  const onError = async (err) => {
5128
5339
  server.removeListener("error", onError);
5129
5340
  if (err.code === "EADDRINUSE") {
5130
- const existingWallet2 = await checkExistingProxy(listenPort);
5131
- if (existingWallet2) {
5341
+ const existingProxy2 = await checkExistingProxy(listenPort);
5342
+ if (existingProxy2) {
5132
5343
  console.log(`[ClawRouter] Existing proxy detected on port ${listenPort}, reusing`);
5133
- rejectAttempt({ code: "REUSE_EXISTING", wallet: existingWallet2 });
5344
+ rejectAttempt({ code: "REUSE_EXISTING", wallet: existingProxy2.wallet, existingChain: existingProxy2.paymentChain });
5134
5345
  return;
5135
5346
  }
5136
5347
  if (attempt < PORT_RETRY_ATTEMPTS) {
@@ -5163,6 +5374,11 @@ async function startProxy(options) {
5163
5374
  } catch (err) {
5164
5375
  const error = err;
5165
5376
  if (error.code === "REUSE_EXISTING" && error.wallet) {
5377
+ if (error.existingChain && error.existingChain !== paymentChain) {
5378
+ throw new Error(
5379
+ `Existing proxy on port ${listenPort} is using ${error.existingChain} but ${paymentChain} was requested. Stop the existing proxy first or use a different port.`
5380
+ );
5381
+ }
5166
5382
  const baseUrl2 = `http://127.0.0.1:${listenPort}`;
5167
5383
  options.onReady?.(listenPort);
5168
5384
  return {
@@ -5220,6 +5436,7 @@ async function startProxy(options) {
5220
5436
  port,
5221
5437
  baseUrl,
5222
5438
  walletAddress: account.address,
5439
+ solanaAddress,
5223
5440
  balanceMonitor,
5224
5441
  close: () => new Promise((res, rej) => {
5225
5442
  const timeout = setTimeout(() => {
@@ -5266,8 +5483,6 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
5266
5483
  requestBody = Buffer.from(JSON.stringify(parsed));
5267
5484
  } catch {
5268
5485
  }
5269
- const estimated = estimateAmount(modelId, requestBody.length, maxTokens);
5270
- const preAuth = estimated ? { estimatedAmount: estimated } : void 0;
5271
5486
  try {
5272
5487
  const response = await payFetch(
5273
5488
  upstreamUrl,
@@ -5276,8 +5491,7 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
5276
5491
  headers,
5277
5492
  body: requestBody.length > 0 ? new Uint8Array(requestBody) : void 0,
5278
5493
  signal
5279
- },
5280
- preAuth
5494
+ }
5281
5495
  );
5282
5496
  if (response.status !== 200) {
5283
5497
  const errorBody = await response.text();
@@ -5913,6 +6127,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5913
6127
  }
5914
6128
  deduplicator.markInflight(dedupKey);
5915
6129
  let estimatedCostMicros;
6130
+ let balanceFallbackNotice;
5916
6131
  const isFreeModel = modelId === FREE_MODEL;
5917
6132
  if (modelId && !options.skipBalanceCheck && !isFreeModel) {
5918
6133
  const estimated = estimateAmount(modelId, body.length, maxTokens);
@@ -5923,12 +6138,17 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5923
6138
  if (sufficiency.info.isEmpty || !sufficiency.sufficient) {
5924
6139
  const originalModel = modelId;
5925
6140
  console.log(
5926
- `[ClawRouter] Wallet ${sufficiency.info.isEmpty ? "empty" : "insufficient"} ($${sufficiency.info.balanceUSD}), falling back to free model: ${FREE_MODEL} (requested: ${originalModel})`
6141
+ `[ClawRouter] Wallet ${sufficiency.info.isEmpty ? "empty" : "insufficient"} (${sufficiency.info.balanceUSD}), falling back to free model: ${FREE_MODEL} (requested: ${originalModel})`
5927
6142
  );
5928
6143
  modelId = FREE_MODEL;
5929
6144
  const parsed = JSON.parse(body.toString());
5930
6145
  parsed.model = FREE_MODEL;
5931
6146
  body = Buffer.from(JSON.stringify(parsed));
6147
+ balanceFallbackNotice = sufficiency.info.isEmpty ? `> **\u26A0\uFE0F Wallet empty** \u2014 using free model. Fund your wallet to use ${originalModel}.
6148
+
6149
+ ` : `> **\u26A0\uFE0F Insufficient balance** (${sufficiency.info.balanceUSD}) \u2014 using free model instead of ${originalModel}.
6150
+
6151
+ `;
5932
6152
  options.onLowBalance?.({
5933
6153
  balanceUSD: sufficiency.info.balanceUSD,
5934
6154
  walletAddress: sufficiency.info.walletAddress
@@ -6220,6 +6440,18 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6220
6440
  `;
6221
6441
  safeWrite(res, roleData);
6222
6442
  responseChunks.push(Buffer.from(roleData));
6443
+ if (balanceFallbackNotice) {
6444
+ const noticeChunk = {
6445
+ ...baseChunk,
6446
+ choices: [{ index, delta: { content: balanceFallbackNotice }, logprobs: null, finish_reason: null }]
6447
+ };
6448
+ const noticeData = `data: ${JSON.stringify(noticeChunk)}
6449
+
6450
+ `;
6451
+ safeWrite(res, noticeData);
6452
+ responseChunks.push(Buffer.from(noticeData));
6453
+ balanceFallbackNotice = void 0;
6454
+ }
6223
6455
  if (content) {
6224
6456
  const contentChunk = {
6225
6457
  ...baseChunk,
@@ -6304,23 +6536,36 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6304
6536
  responseHeaders["x-clawrouter-agentic-score"] = routingDecision.agenticScore.toFixed(2);
6305
6537
  }
6306
6538
  }
6307
- res.writeHead(upstream.status, responseHeaders);
6539
+ const bodyParts = [];
6308
6540
  if (upstream.body) {
6309
6541
  const reader = upstream.body.getReader();
6310
6542
  try {
6311
6543
  while (true) {
6312
6544
  const { done, value } = await reader.read();
6313
6545
  if (done) break;
6314
- const chunk = Buffer.from(value);
6315
- safeWrite(res, chunk);
6316
- responseChunks.push(chunk);
6546
+ bodyParts.push(Buffer.from(value));
6317
6547
  }
6318
6548
  } finally {
6319
6549
  reader.releaseLock();
6320
6550
  }
6321
6551
  }
6552
+ let responseBody = Buffer.concat(bodyParts);
6553
+ if (balanceFallbackNotice && responseBody.length > 0) {
6554
+ try {
6555
+ const parsed = JSON.parse(responseBody.toString());
6556
+ if (parsed.choices?.[0]?.message?.content !== void 0) {
6557
+ parsed.choices[0].message.content = balanceFallbackNotice + parsed.choices[0].message.content;
6558
+ responseBody = Buffer.from(JSON.stringify(parsed));
6559
+ }
6560
+ } catch {
6561
+ }
6562
+ balanceFallbackNotice = void 0;
6563
+ }
6564
+ responseHeaders["content-length"] = String(responseBody.length);
6565
+ res.writeHead(upstream.status, responseHeaders);
6566
+ safeWrite(res, responseBody);
6567
+ responseChunks.push(responseBody);
6322
6568
  res.end();
6323
- const responseBody = Buffer.concat(responseChunks);
6324
6569
  deduplicator.complete(dedupKey, {
6325
6570
  status: upstream.status,
6326
6571
  headers: responseHeaders,
@@ -6372,7 +6617,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6372
6617
  deduplicator.removeInflight(dedupKey);
6373
6618
  balanceMonitor.invalidate();
6374
6619
  if (err instanceof Error && err.name === "AbortError") {
6375
- throw new Error(`Request timed out after ${timeoutMs}ms`);
6620
+ throw new Error(`Request timed out after ${timeoutMs}ms`, { cause: err });
6376
6621
  }
6377
6622
  throw err;
6378
6623
  }
@@ -6403,102 +6648,17 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6403
6648
  }
6404
6649
  }
6405
6650
 
6406
- // src/auth.ts
6407
- import { writeFile, mkdir as mkdir2 } from "fs/promises";
6408
- import { join as join4 } from "path";
6409
- import { homedir as homedir3 } from "os";
6410
- import { generatePrivateKey, privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
6411
- var WALLET_DIR = join4(homedir3(), ".openclaw", "blockrun");
6412
- var WALLET_FILE = join4(WALLET_DIR, "wallet.key");
6413
- async function loadSavedWallet() {
6414
- try {
6415
- const key = (await readTextFile(WALLET_FILE)).trim();
6416
- if (key.startsWith("0x") && key.length === 66) {
6417
- console.log(`[ClawRouter] \u2713 Loaded existing wallet from ${WALLET_FILE}`);
6418
- return key;
6419
- }
6420
- console.error(`[ClawRouter] \u2717 CRITICAL: Wallet file exists but has invalid format!`);
6421
- console.error(`[ClawRouter] File: ${WALLET_FILE}`);
6422
- console.error(`[ClawRouter] Expected: 0x followed by 64 hex characters (66 chars total)`);
6423
- console.error(
6424
- `[ClawRouter] To fix: restore your backup key or set BLOCKRUN_WALLET_KEY env var`
6425
- );
6426
- throw new Error(
6427
- `Wallet file at ${WALLET_FILE} is corrupted or has wrong format. Refusing to auto-generate new wallet to protect existing funds. Restore your backup key or set BLOCKRUN_WALLET_KEY environment variable.`
6428
- );
6429
- } catch (err) {
6430
- if (err.code !== "ENOENT") {
6431
- if (err instanceof Error && err.message.includes("Refusing to auto-generate")) {
6432
- throw err;
6433
- }
6434
- console.error(
6435
- `[ClawRouter] \u2717 Failed to read wallet file: ${err instanceof Error ? err.message : String(err)}`
6436
- );
6437
- throw new Error(
6438
- `Cannot read wallet file at ${WALLET_FILE}: ${err instanceof Error ? err.message : String(err)}. Refusing to auto-generate new wallet to protect existing funds. Fix file permissions or set BLOCKRUN_WALLET_KEY environment variable.`
6439
- );
6440
- }
6441
- }
6442
- return void 0;
6443
- }
6444
- async function generateAndSaveWallet() {
6445
- const key = generatePrivateKey();
6446
- const account = privateKeyToAccount3(key);
6447
- await mkdir2(WALLET_DIR, { recursive: true });
6448
- await writeFile(WALLET_FILE, key + "\n", { mode: 384 });
6449
- try {
6450
- const verification = (await readTextFile(WALLET_FILE)).trim();
6451
- if (verification !== key) {
6452
- throw new Error("Wallet file verification failed - content mismatch");
6453
- }
6454
- console.log(`[ClawRouter] \u2713 Wallet saved and verified at ${WALLET_FILE}`);
6455
- } catch (err) {
6456
- throw new Error(
6457
- `Failed to verify wallet file after creation: ${err instanceof Error ? err.message : String(err)}`
6458
- );
6459
- }
6460
- console.log(`[ClawRouter]`);
6461
- console.log(`[ClawRouter] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
6462
- console.log(`[ClawRouter] NEW WALLET GENERATED \u2014 BACK UP YOUR KEY NOW`);
6463
- console.log(`[ClawRouter] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
6464
- console.log(`[ClawRouter] Address : ${account.address}`);
6465
- console.log(`[ClawRouter] Key file: ${WALLET_FILE}`);
6466
- console.log(`[ClawRouter]`);
6467
- console.log(`[ClawRouter] To back up, run in OpenClaw:`);
6468
- console.log(`[ClawRouter] /wallet export`);
6469
- console.log(`[ClawRouter]`);
6470
- console.log(`[ClawRouter] To restore on another machine:`);
6471
- console.log(`[ClawRouter] export BLOCKRUN_WALLET_KEY=<your_key>`);
6472
- console.log(`[ClawRouter] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
6473
- console.log(`[ClawRouter]`);
6474
- return { key, address: account.address };
6475
- }
6476
- async function resolveOrGenerateWalletKey() {
6477
- const saved = await loadSavedWallet();
6478
- if (saved) {
6479
- const account = privateKeyToAccount3(saved);
6480
- return { key: saved, address: account.address, source: "saved" };
6481
- }
6482
- const envKey = process["env"].BLOCKRUN_WALLET_KEY;
6483
- if (typeof envKey === "string" && envKey.startsWith("0x") && envKey.length === 66) {
6484
- const account = privateKeyToAccount3(envKey);
6485
- return { key: envKey, address: account.address, source: "env" };
6486
- }
6487
- const { key, address } = await generateAndSaveWallet();
6488
- return { key, address, source: "generated" };
6489
- }
6490
-
6491
6651
  // src/index.ts
6492
6652
  import {
6493
- writeFileSync,
6494
- existsSync,
6653
+ writeFileSync as writeFileSync2,
6654
+ existsSync as existsSync2,
6495
6655
  readdirSync,
6496
- mkdirSync,
6656
+ mkdirSync as mkdirSync2,
6497
6657
  copyFileSync,
6498
6658
  renameSync
6499
6659
  } from "fs";
6500
- import { homedir as homedir4 } from "os";
6501
- import { join as join5 } from "path";
6660
+ import { homedir as homedir5 } from "os";
6661
+ import { join as join6 } from "path";
6502
6662
  import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
6503
6663
 
6504
6664
  // src/partners/registry.ts
@@ -6596,6 +6756,260 @@ function buildPartnerTools(proxyBaseUrl) {
6596
6756
  return PARTNER_SERVICES.map((service) => buildTool(service, proxyBaseUrl));
6597
6757
  }
6598
6758
 
6759
+ // src/index.ts
6760
+ init_solana_balance();
6761
+
6762
+ // src/spend-control.ts
6763
+ import * as fs from "fs";
6764
+ import * as path from "path";
6765
+ import { homedir as homedir4 } from "os";
6766
+ var WALLET_DIR2 = path.join(homedir4(), ".openclaw", "blockrun");
6767
+ var HOUR_MS = 60 * 60 * 1e3;
6768
+ var DAY_MS = 24 * HOUR_MS;
6769
+ var FileSpendControlStorage = class {
6770
+ spendingFile;
6771
+ constructor() {
6772
+ this.spendingFile = path.join(WALLET_DIR2, "spending.json");
6773
+ }
6774
+ load() {
6775
+ try {
6776
+ if (fs.existsSync(this.spendingFile)) {
6777
+ const data = JSON.parse(readTextFileSync(this.spendingFile));
6778
+ const rawLimits = data.limits ?? {};
6779
+ const rawHistory = data.history ?? [];
6780
+ const limits = {};
6781
+ for (const key of ["perRequest", "hourly", "daily", "session"]) {
6782
+ const val = rawLimits[key];
6783
+ if (typeof val === "number" && val > 0 && Number.isFinite(val)) {
6784
+ limits[key] = val;
6785
+ }
6786
+ }
6787
+ const history = [];
6788
+ if (Array.isArray(rawHistory)) {
6789
+ for (const r of rawHistory) {
6790
+ if (typeof r?.timestamp === "number" && typeof r?.amount === "number" && Number.isFinite(r.timestamp) && Number.isFinite(r.amount) && r.amount >= 0) {
6791
+ history.push({
6792
+ timestamp: r.timestamp,
6793
+ amount: r.amount,
6794
+ model: typeof r.model === "string" ? r.model : void 0,
6795
+ action: typeof r.action === "string" ? r.action : void 0
6796
+ });
6797
+ }
6798
+ }
6799
+ }
6800
+ return { limits, history };
6801
+ }
6802
+ } catch (err) {
6803
+ console.error(`[ClawRouter] Failed to load spending data, starting fresh: ${err}`);
6804
+ }
6805
+ return null;
6806
+ }
6807
+ save(data) {
6808
+ try {
6809
+ if (!fs.existsSync(WALLET_DIR2)) {
6810
+ fs.mkdirSync(WALLET_DIR2, { recursive: true, mode: 448 });
6811
+ }
6812
+ fs.writeFileSync(this.spendingFile, JSON.stringify(data, null, 2), {
6813
+ mode: 384
6814
+ });
6815
+ } catch (err) {
6816
+ console.error(`[ClawRouter] Failed to save spending data: ${err}`);
6817
+ }
6818
+ }
6819
+ };
6820
+ var InMemorySpendControlStorage = class {
6821
+ data = null;
6822
+ load() {
6823
+ return this.data ? {
6824
+ limits: { ...this.data.limits },
6825
+ history: this.data.history.map((r) => ({ ...r }))
6826
+ } : null;
6827
+ }
6828
+ save(data) {
6829
+ this.data = {
6830
+ limits: { ...data.limits },
6831
+ history: data.history.map((r) => ({ ...r }))
6832
+ };
6833
+ }
6834
+ };
6835
+ var SpendControl = class {
6836
+ limits = {};
6837
+ history = [];
6838
+ sessionSpent = 0;
6839
+ sessionCalls = 0;
6840
+ storage;
6841
+ now;
6842
+ constructor(options) {
6843
+ this.storage = options?.storage ?? new FileSpendControlStorage();
6844
+ this.now = options?.now ?? (() => Date.now());
6845
+ this.load();
6846
+ }
6847
+ setLimit(window, amount) {
6848
+ if (!Number.isFinite(amount) || amount <= 0) {
6849
+ throw new Error("Limit must be a finite positive number");
6850
+ }
6851
+ this.limits[window] = amount;
6852
+ this.save();
6853
+ }
6854
+ clearLimit(window) {
6855
+ delete this.limits[window];
6856
+ this.save();
6857
+ }
6858
+ getLimits() {
6859
+ return { ...this.limits };
6860
+ }
6861
+ check(estimatedCost) {
6862
+ const now = this.now();
6863
+ if (this.limits.perRequest !== void 0) {
6864
+ if (estimatedCost > this.limits.perRequest) {
6865
+ return {
6866
+ allowed: false,
6867
+ blockedBy: "perRequest",
6868
+ remaining: this.limits.perRequest,
6869
+ reason: `Per-request limit exceeded: $${estimatedCost.toFixed(4)} > $${this.limits.perRequest.toFixed(2)} max`
6870
+ };
6871
+ }
6872
+ }
6873
+ if (this.limits.hourly !== void 0) {
6874
+ const hourlySpent = this.getSpendingInWindow(now - HOUR_MS, now);
6875
+ const remaining = this.limits.hourly - hourlySpent;
6876
+ if (estimatedCost > remaining) {
6877
+ const oldestInWindow = this.history.find((r) => r.timestamp >= now - HOUR_MS);
6878
+ const resetIn = oldestInWindow ? Math.ceil((oldestInWindow.timestamp + HOUR_MS - now) / 1e3) : 0;
6879
+ return {
6880
+ allowed: false,
6881
+ blockedBy: "hourly",
6882
+ remaining,
6883
+ reason: `Hourly limit exceeded: $${(hourlySpent + estimatedCost).toFixed(2)} > $${this.limits.hourly.toFixed(2)} max`,
6884
+ resetIn
6885
+ };
6886
+ }
6887
+ }
6888
+ if (this.limits.daily !== void 0) {
6889
+ const dailySpent = this.getSpendingInWindow(now - DAY_MS, now);
6890
+ const remaining = this.limits.daily - dailySpent;
6891
+ if (estimatedCost > remaining) {
6892
+ const oldestInWindow = this.history.find((r) => r.timestamp >= now - DAY_MS);
6893
+ const resetIn = oldestInWindow ? Math.ceil((oldestInWindow.timestamp + DAY_MS - now) / 1e3) : 0;
6894
+ return {
6895
+ allowed: false,
6896
+ blockedBy: "daily",
6897
+ remaining,
6898
+ reason: `Daily limit exceeded: $${(dailySpent + estimatedCost).toFixed(2)} > $${this.limits.daily.toFixed(2)} max`,
6899
+ resetIn
6900
+ };
6901
+ }
6902
+ }
6903
+ if (this.limits.session !== void 0) {
6904
+ const remaining = this.limits.session - this.sessionSpent;
6905
+ if (estimatedCost > remaining) {
6906
+ return {
6907
+ allowed: false,
6908
+ blockedBy: "session",
6909
+ remaining,
6910
+ reason: `Session limit exceeded: $${(this.sessionSpent + estimatedCost).toFixed(2)} > $${this.limits.session.toFixed(2)} max`
6911
+ };
6912
+ }
6913
+ }
6914
+ return { allowed: true };
6915
+ }
6916
+ record(amount, metadata) {
6917
+ if (!Number.isFinite(amount) || amount < 0) {
6918
+ throw new Error("Record amount must be a non-negative finite number");
6919
+ }
6920
+ const record = {
6921
+ timestamp: this.now(),
6922
+ amount,
6923
+ model: metadata?.model,
6924
+ action: metadata?.action
6925
+ };
6926
+ this.history.push(record);
6927
+ this.sessionSpent += amount;
6928
+ this.sessionCalls += 1;
6929
+ this.cleanup();
6930
+ this.save();
6931
+ }
6932
+ getSpendingInWindow(from, to) {
6933
+ return this.history.filter((r) => r.timestamp >= from && r.timestamp <= to).reduce((sum, r) => sum + r.amount, 0);
6934
+ }
6935
+ getSpending(window) {
6936
+ const now = this.now();
6937
+ switch (window) {
6938
+ case "hourly":
6939
+ return this.getSpendingInWindow(now - HOUR_MS, now);
6940
+ case "daily":
6941
+ return this.getSpendingInWindow(now - DAY_MS, now);
6942
+ case "session":
6943
+ return this.sessionSpent;
6944
+ }
6945
+ }
6946
+ getRemaining(window) {
6947
+ const limit = this.limits[window];
6948
+ if (limit === void 0) return null;
6949
+ return Math.max(0, limit - this.getSpending(window));
6950
+ }
6951
+ getStatus() {
6952
+ const now = this.now();
6953
+ const hourlySpent = this.getSpendingInWindow(now - HOUR_MS, now);
6954
+ const dailySpent = this.getSpendingInWindow(now - DAY_MS, now);
6955
+ return {
6956
+ limits: { ...this.limits },
6957
+ spending: {
6958
+ hourly: hourlySpent,
6959
+ daily: dailySpent,
6960
+ session: this.sessionSpent
6961
+ },
6962
+ remaining: {
6963
+ hourly: this.limits.hourly !== void 0 ? this.limits.hourly - hourlySpent : null,
6964
+ daily: this.limits.daily !== void 0 ? this.limits.daily - dailySpent : null,
6965
+ session: this.limits.session !== void 0 ? this.limits.session - this.sessionSpent : null
6966
+ },
6967
+ calls: this.sessionCalls
6968
+ };
6969
+ }
6970
+ getHistory(limit) {
6971
+ const records = [...this.history].reverse();
6972
+ return limit ? records.slice(0, limit) : records;
6973
+ }
6974
+ resetSession() {
6975
+ this.sessionSpent = 0;
6976
+ this.sessionCalls = 0;
6977
+ }
6978
+ cleanup() {
6979
+ const cutoff = this.now() - DAY_MS;
6980
+ this.history = this.history.filter((r) => r.timestamp >= cutoff);
6981
+ }
6982
+ save() {
6983
+ this.storage.save({
6984
+ limits: { ...this.limits },
6985
+ history: [...this.history]
6986
+ });
6987
+ }
6988
+ load() {
6989
+ const data = this.storage.load();
6990
+ if (data) {
6991
+ this.limits = data.limits;
6992
+ this.history = data.history;
6993
+ this.cleanup();
6994
+ }
6995
+ }
6996
+ };
6997
+ function formatDuration(seconds) {
6998
+ if (seconds < 60) {
6999
+ return `${seconds}s`;
7000
+ } else if (seconds < 3600) {
7001
+ const mins = Math.ceil(seconds / 60);
7002
+ return `${mins} min`;
7003
+ } else {
7004
+ const hours = Math.floor(seconds / 3600);
7005
+ const mins = Math.ceil(seconds % 3600 / 60);
7006
+ return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
7007
+ }
7008
+ }
7009
+
7010
+ // src/index.ts
7011
+ init_wallet();
7012
+
6599
7013
  // src/retry.ts
6600
7014
  var DEFAULT_RETRY_CONFIG = {
6601
7015
  maxRetries: 2,
@@ -6674,13 +7088,13 @@ function isGatewayMode() {
6674
7088
  return args.includes("gateway");
6675
7089
  }
6676
7090
  function injectModelsConfig(logger) {
6677
- const configDir = join5(homedir4(), ".openclaw");
6678
- const configPath = join5(configDir, "openclaw.json");
7091
+ const configDir = join6(homedir5(), ".openclaw");
7092
+ const configPath = join6(configDir, "openclaw.json");
6679
7093
  let config = {};
6680
7094
  let needsWrite = false;
6681
- if (!existsSync(configDir)) {
7095
+ if (!existsSync2(configDir)) {
6682
7096
  try {
6683
- mkdirSync(configDir, { recursive: true });
7097
+ mkdirSync2(configDir, { recursive: true });
6684
7098
  logger.info("Created OpenClaw config directory");
6685
7099
  } catch (err) {
6686
7100
  logger.info(
@@ -6689,7 +7103,7 @@ function injectModelsConfig(logger) {
6689
7103
  return;
6690
7104
  }
6691
7105
  }
6692
- if (existsSync(configPath)) {
7106
+ if (existsSync2(configPath)) {
6693
7107
  try {
6694
7108
  const content = readTextFileSync(configPath).trim();
6695
7109
  if (content) {
@@ -6834,7 +7248,7 @@ function injectModelsConfig(logger) {
6834
7248
  if (needsWrite) {
6835
7249
  try {
6836
7250
  const tmpPath = `${configPath}.tmp.${process.pid}`;
6837
- writeFileSync(tmpPath, JSON.stringify(config, null, 2));
7251
+ writeFileSync2(tmpPath, JSON.stringify(config, null, 2));
6838
7252
  renameSync(tmpPath, configPath);
6839
7253
  logger.info("Smart routing enabled (blockrun/auto)");
6840
7254
  } catch (err) {
@@ -6843,10 +7257,10 @@ function injectModelsConfig(logger) {
6843
7257
  }
6844
7258
  }
6845
7259
  function injectAuthProfile(logger) {
6846
- const agentsDir = join5(homedir4(), ".openclaw", "agents");
6847
- if (!existsSync(agentsDir)) {
7260
+ const agentsDir = join6(homedir5(), ".openclaw", "agents");
7261
+ if (!existsSync2(agentsDir)) {
6848
7262
  try {
6849
- mkdirSync(agentsDir, { recursive: true });
7263
+ mkdirSync2(agentsDir, { recursive: true });
6850
7264
  } catch (err) {
6851
7265
  logger.info(
6852
7266
  `Could not create agents dir: ${err instanceof Error ? err.message : String(err)}`
@@ -6860,11 +7274,11 @@ function injectAuthProfile(logger) {
6860
7274
  agents = ["main", ...agents];
6861
7275
  }
6862
7276
  for (const agentId of agents) {
6863
- const authDir = join5(agentsDir, agentId, "agent");
6864
- const authPath = join5(authDir, "auth-profiles.json");
6865
- if (!existsSync(authDir)) {
7277
+ const authDir = join6(agentsDir, agentId, "agent");
7278
+ const authPath = join6(authDir, "auth-profiles.json");
7279
+ if (!existsSync2(authDir)) {
6866
7280
  try {
6867
- mkdirSync(authDir, { recursive: true });
7281
+ mkdirSync2(authDir, { recursive: true });
6868
7282
  } catch {
6869
7283
  continue;
6870
7284
  }
@@ -6873,7 +7287,7 @@ function injectAuthProfile(logger) {
6873
7287
  version: 1,
6874
7288
  profiles: {}
6875
7289
  };
6876
- if (existsSync(authPath)) {
7290
+ if (existsSync2(authPath)) {
6877
7291
  try {
6878
7292
  const existing = JSON.parse(readTextFileSync(authPath));
6879
7293
  if (existing.version && existing.profiles) {
@@ -6892,7 +7306,7 @@ function injectAuthProfile(logger) {
6892
7306
  key: "x402-proxy-handles-auth"
6893
7307
  };
6894
7308
  try {
6895
- writeFileSync(authPath, JSON.stringify(store, null, 2));
7309
+ writeFileSync2(authPath, JSON.stringify(store, null, 2));
6896
7310
  logger.info(`Injected BlockRun auth profile for agent: ${agentId}`);
6897
7311
  } catch (err) {
6898
7312
  logger.info(
@@ -6906,22 +7320,22 @@ function injectAuthProfile(logger) {
6906
7320
  }
6907
7321
  var activeProxyHandle = null;
6908
7322
  async function startProxyInBackground(api) {
6909
- const { key: walletKey, address, source } = await resolveOrGenerateWalletKey();
6910
- if (source === "generated") {
7323
+ const wallet = await resolveOrGenerateWalletKey();
7324
+ if (wallet.source === "generated") {
6911
7325
  api.logger.warn(`\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
6912
7326
  api.logger.warn(` NEW WALLET GENERATED \u2014 BACK UP YOUR KEY NOW!`);
6913
- api.logger.warn(` Address : ${address}`);
7327
+ api.logger.warn(` Address : ${wallet.address}`);
6914
7328
  api.logger.warn(` Run /wallet export to get your private key`);
6915
7329
  api.logger.warn(` Losing this key = losing your USDC funds`);
6916
7330
  api.logger.warn(`\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
6917
- } else if (source === "saved") {
6918
- api.logger.info(`Using saved wallet: ${address}`);
7331
+ } else if (wallet.source === "saved") {
7332
+ api.logger.info(`Using saved wallet: ${wallet.address}`);
6919
7333
  } else {
6920
- api.logger.info(`Using wallet from BLOCKRUN_WALLET_KEY: ${address}`);
7334
+ api.logger.info(`Using wallet from BLOCKRUN_WALLET_KEY: ${wallet.address}`);
6921
7335
  }
6922
7336
  const routingConfig = api.pluginConfig?.routing;
6923
7337
  const proxy = await startProxy({
6924
- walletKey,
7338
+ wallet,
6925
7339
  routingConfig,
6926
7340
  onReady: (port) => {
6927
7341
  api.logger.info(`BlockRun x402 proxy listening on port ${port}`);
@@ -6949,18 +7363,18 @@ async function startProxyInBackground(api) {
6949
7363
  activeProxyHandle = proxy;
6950
7364
  api.logger.info(`ClawRouter ready \u2014 smart routing enabled`);
6951
7365
  api.logger.info(`Pricing: Simple ~$0.001 | Code ~$0.01 | Complex ~$0.05 | Free: $0`);
6952
- const startupMonitor = new BalanceMonitor(address);
6953
- startupMonitor.checkBalance().then((balance) => {
7366
+ const displayAddress = proxy.solanaAddress ?? wallet.address;
7367
+ proxy.balanceMonitor.checkBalance().then((balance) => {
6954
7368
  if (balance.isEmpty) {
6955
- api.logger.info(`Wallet: ${address} | Balance: $0.00`);
7369
+ api.logger.info(`Wallet: ${displayAddress} | Balance: $0.00`);
6956
7370
  api.logger.info(`Using FREE model. Fund wallet for premium models.`);
6957
7371
  } else if (balance.isLow) {
6958
- api.logger.info(`Wallet: ${address} | Balance: ${balance.balanceUSD} (low)`);
7372
+ api.logger.info(`Wallet: ${displayAddress} | Balance: ${balance.balanceUSD} (low)`);
6959
7373
  } else {
6960
- api.logger.info(`Wallet: ${address} | Balance: ${balance.balanceUSD}`);
7374
+ api.logger.info(`Wallet: ${displayAddress} | Balance: ${balance.balanceUSD}`);
6961
7375
  }
6962
7376
  }).catch(() => {
6963
- api.logger.info(`Wallet: ${address} | Balance: (checking...)`);
7377
+ api.logger.info(`Wallet: ${displayAddress} | Balance: (checking...)`);
6964
7378
  });
6965
7379
  }
6966
7380
  async function createStatsCommand() {
@@ -6998,7 +7412,7 @@ async function createWalletCommand() {
6998
7412
  let walletKey;
6999
7413
  let address;
7000
7414
  try {
7001
- if (existsSync(WALLET_FILE)) {
7415
+ if (existsSync2(WALLET_FILE)) {
7002
7416
  walletKey = readTextFileSync(WALLET_FILE).trim();
7003
7417
  if (walletKey.startsWith("0x") && walletKey.length === 66) {
7004
7418
  const account = privateKeyToAccount4(walletKey);
@@ -7016,48 +7430,176 @@ Run \`openclaw plugins install @blockrun/clawrouter\` to generate a wallet.`,
7016
7430
  };
7017
7431
  }
7018
7432
  if (subcommand === "export") {
7019
- return {
7020
- text: [
7021
- "\u{1F510} **ClawRouter Wallet Export**",
7022
- "",
7023
- "\u26A0\uFE0F **SECURITY WARNING**: Your private key controls your wallet funds.",
7024
- "Never share this key. Anyone with this key can spend your USDC.",
7025
- "",
7026
- `**Address:** \`${address}\``,
7027
- "",
7028
- `**Private Key:**`,
7029
- `\`${walletKey}\``,
7030
- "",
7031
- "**To restore on a new machine:**",
7032
- "1. Set the environment variable before running OpenClaw:",
7033
- ` \`export BLOCKRUN_WALLET_KEY=${walletKey}\``,
7034
- "2. Or save to file:",
7035
- ` \`mkdir -p ~/.openclaw/blockrun && echo "${walletKey}" > ~/.openclaw/blockrun/wallet.key && chmod 600 ~/.openclaw/blockrun/wallet.key\``
7036
- ].join("\n")
7037
- };
7433
+ const lines = [
7434
+ "**ClawRouter Wallet Export**",
7435
+ "",
7436
+ "**SECURITY WARNING**: Your private key and mnemonic control your wallet funds.",
7437
+ "Never share these. Anyone with them can spend your USDC.",
7438
+ "",
7439
+ "**EVM (Base):**",
7440
+ ` Address: \`${address}\``,
7441
+ ` Private Key: \`${walletKey}\``
7442
+ ];
7443
+ let hasMnemonic = false;
7444
+ try {
7445
+ if (existsSync2(MNEMONIC_FILE)) {
7446
+ const mnemonic = readTextFileSync(MNEMONIC_FILE).trim();
7447
+ if (mnemonic) {
7448
+ hasMnemonic = true;
7449
+ const { deriveSolanaKeyBytes: deriveSolanaKeyBytes2 } = await Promise.resolve().then(() => (init_wallet(), wallet_exports));
7450
+ const solKeyBytes = deriveSolanaKeyBytes2(mnemonic);
7451
+ const { createKeyPairSignerFromPrivateKeyBytes } = await import("@solana/kit");
7452
+ const signer = await createKeyPairSignerFromPrivateKeyBytes(solKeyBytes);
7453
+ lines.push(
7454
+ "",
7455
+ "**Solana:**",
7456
+ ` Address: \`${signer.address}\``,
7457
+ ` (Derived from mnemonic below)`,
7458
+ "",
7459
+ "**Mnemonic (24 words):**",
7460
+ `\`${mnemonic}\``,
7461
+ "",
7462
+ "CRITICAL: Back up this mnemonic. It is the ONLY way to recover your Solana wallet."
7463
+ );
7464
+ }
7465
+ }
7466
+ } catch {
7467
+ }
7468
+ lines.push(
7469
+ "",
7470
+ "**To restore on a new machine:**",
7471
+ "1. Set the environment variable before running OpenClaw:",
7472
+ ` \`export BLOCKRUN_WALLET_KEY=${walletKey}\``,
7473
+ "2. Or save to file:",
7474
+ ` \`mkdir -p ~/.openclaw/blockrun && echo "${walletKey}" > ~/.openclaw/blockrun/wallet.key && chmod 600 ~/.openclaw/blockrun/wallet.key\``
7475
+ );
7476
+ if (hasMnemonic) {
7477
+ lines.push(
7478
+ "3. Restore the mnemonic for Solana:",
7479
+ ` \`echo "<your mnemonic>" > ~/.openclaw/blockrun/mnemonic && chmod 600 ~/.openclaw/blockrun/mnemonic\``
7480
+ );
7481
+ }
7482
+ return { text: lines.join("\n") };
7483
+ }
7484
+ if (subcommand === "solana") {
7485
+ try {
7486
+ let solanaAddr;
7487
+ if (existsSync2(MNEMONIC_FILE)) {
7488
+ const existingMnemonic = readTextFileSync(MNEMONIC_FILE).trim();
7489
+ if (existingMnemonic) {
7490
+ await savePaymentChain("solana");
7491
+ const { deriveSolanaKeyBytes: deriveSolanaKeyBytes2 } = await Promise.resolve().then(() => (init_wallet(), wallet_exports));
7492
+ const solKeyBytes = deriveSolanaKeyBytes2(existingMnemonic);
7493
+ const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await import("@solana/kit");
7494
+ const signer2 = await createKeyPairSignerFromPrivateKeyBytes2(solKeyBytes);
7495
+ solanaAddr = signer2.address;
7496
+ return {
7497
+ text: [
7498
+ "Payment chain set to Solana. Restart the gateway to apply.",
7499
+ "",
7500
+ `**Solana Address:** \`${solanaAddr}\``,
7501
+ `**Fund with USDC on Solana:** https://solscan.io/account/${solanaAddr}`
7502
+ ].join("\n")
7503
+ };
7504
+ }
7505
+ }
7506
+ const { solanaPrivateKeyBytes } = await setupSolana();
7507
+ await savePaymentChain("solana");
7508
+ const { createKeyPairSignerFromPrivateKeyBytes } = await import("@solana/kit");
7509
+ const signer = await createKeyPairSignerFromPrivateKeyBytes(solanaPrivateKeyBytes);
7510
+ return {
7511
+ text: [
7512
+ "**Solana Wallet Set Up**",
7513
+ "",
7514
+ `**Solana Address:** \`${signer.address}\``,
7515
+ `**Mnemonic File:** \`${MNEMONIC_FILE}\``,
7516
+ "",
7517
+ "Your existing EVM wallet is unchanged.",
7518
+ "Payment chain set to Solana. Restart the gateway to apply.",
7519
+ "",
7520
+ `**Fund with USDC on Solana:** https://solscan.io/account/${signer.address}`
7521
+ ].join("\n")
7522
+ };
7523
+ } catch (err) {
7524
+ return {
7525
+ text: `Failed to set up Solana: ${err instanceof Error ? err.message : String(err)}`,
7526
+ isError: true
7527
+ };
7528
+ }
7038
7529
  }
7039
- let balanceText = "Balance: (checking...)";
7530
+ if (subcommand === "base") {
7531
+ try {
7532
+ await savePaymentChain("base");
7533
+ return {
7534
+ text: "Payment chain set to Base (EVM). Restart the gateway to apply."
7535
+ };
7536
+ } catch (err) {
7537
+ return {
7538
+ text: `Failed to set payment chain: ${err instanceof Error ? err.message : String(err)}`,
7539
+ isError: true
7540
+ };
7541
+ }
7542
+ }
7543
+ let evmBalanceText = "Balance: (checking...)";
7040
7544
  try {
7041
7545
  const monitor = new BalanceMonitor(address);
7042
7546
  const balance = await monitor.checkBalance();
7043
- balanceText = `Balance: ${balance.balanceUSD}`;
7547
+ evmBalanceText = `Balance: ${balance.balanceUSD}`;
7548
+ } catch {
7549
+ evmBalanceText = "Balance: (could not check)";
7550
+ }
7551
+ let solanaSection = "";
7552
+ try {
7553
+ if (existsSync2(MNEMONIC_FILE)) {
7554
+ const { deriveSolanaKeyBytes: deriveSolanaKeyBytes2 } = await Promise.resolve().then(() => (init_wallet(), wallet_exports));
7555
+ const mnemonic = readTextFileSync(MNEMONIC_FILE).trim();
7556
+ if (mnemonic) {
7557
+ const solKeyBytes = deriveSolanaKeyBytes2(mnemonic);
7558
+ const { createKeyPairSignerFromPrivateKeyBytes } = await import("@solana/kit");
7559
+ const signer = await createKeyPairSignerFromPrivateKeyBytes(solKeyBytes);
7560
+ const solAddr = signer.address;
7561
+ let solBalanceText = "Balance: (checking...)";
7562
+ try {
7563
+ const { SolanaBalanceMonitor: SolanaBalanceMonitor2 } = await Promise.resolve().then(() => (init_solana_balance(), solana_balance_exports));
7564
+ const solMonitor = new SolanaBalanceMonitor2(solAddr);
7565
+ const solBalance = await solMonitor.checkBalance();
7566
+ solBalanceText = `Balance: ${solBalance.balanceUSD}`;
7567
+ } catch {
7568
+ solBalanceText = "Balance: (could not check)";
7569
+ }
7570
+ solanaSection = [
7571
+ "",
7572
+ "**Solana:**",
7573
+ ` Address: \`${solAddr}\``,
7574
+ ` ${solBalanceText}`,
7575
+ ` Fund: https://solscan.io/account/${solAddr}`
7576
+ ].join("\n");
7577
+ }
7578
+ }
7044
7579
  } catch {
7045
- balanceText = "Balance: (could not check)";
7046
7580
  }
7581
+ const currentChain = await resolvePaymentChain();
7047
7582
  return {
7048
7583
  text: [
7049
- "\u{1F99E} **ClawRouter Wallet**",
7584
+ "**ClawRouter Wallet**",
7585
+ "",
7586
+ `**Payment Chain:** ${currentChain === "solana" ? "Solana" : "Base (EVM)"}`,
7587
+ "",
7588
+ "**Base (EVM):**",
7589
+ ` Address: \`${address}\``,
7590
+ ` ${evmBalanceText}`,
7591
+ ` Fund: https://basescan.org/address/${address}`,
7592
+ solanaSection,
7050
7593
  "",
7051
- `**Address:** \`${address}\``,
7052
- `**${balanceText}**`,
7053
7594
  `**Key File:** \`${WALLET_FILE}\``,
7054
7595
  "",
7055
7596
  "**Commands:**",
7056
7597
  "\u2022 `/wallet` - Show this status",
7057
7598
  "\u2022 `/wallet export` - Export private key for backup",
7058
- "",
7059
- `**Fund with USDC on Base:** https://basescan.org/address/${address}`
7060
- ].join("\n")
7599
+ !solanaSection ? "\u2022 `/wallet solana` - Enable Solana payments" : "",
7600
+ solanaSection ? "\u2022 `/wallet base` - Switch to Base (EVM)" : "",
7601
+ solanaSection ? "\u2022 `/wallet solana` - Switch to Solana" : ""
7602
+ ].filter(Boolean).join("\n")
7061
7603
  };
7062
7604
  }
7063
7605
  };
@@ -7212,23 +7754,30 @@ export {
7212
7754
  DEFAULT_ROUTING_CONFIG,
7213
7755
  DEFAULT_SESSION_CONFIG,
7214
7756
  EmptyWalletError,
7757
+ FileSpendControlStorage,
7758
+ InMemorySpendControlStorage,
7215
7759
  InsufficientFundsError,
7216
7760
  MODEL_ALIASES,
7217
7761
  OPENCLAW_MODELS,
7218
7762
  PARTNER_SERVICES,
7219
- PaymentCache,
7220
7763
  RequestDeduplicator,
7221
7764
  ResponseCache,
7222
7765
  RpcError,
7223
7766
  SessionStore,
7767
+ SolanaBalanceMonitor,
7768
+ SpendControl,
7224
7769
  blockrunProvider,
7225
7770
  buildPartnerTools,
7226
7771
  buildProviderModels,
7227
7772
  calculateModelCost,
7228
- createPaymentFetch,
7229
7773
  index_default as default,
7774
+ deriveAllKeys,
7775
+ deriveEvmKey,
7776
+ deriveSolanaKeyBytes,
7230
7777
  fetchWithRetry,
7778
+ formatDuration,
7231
7779
  formatStatsAscii,
7780
+ generateWalletMnemonic,
7232
7781
  getAgenticModels,
7233
7782
  getFallbackChain,
7234
7783
  getFallbackChainFiltered,
@@ -7244,9 +7793,14 @@ export {
7244
7793
  isInsufficientFundsError,
7245
7794
  isRetryable,
7246
7795
  isRpcError,
7796
+ isValidMnemonic,
7797
+ loadPaymentChain,
7247
7798
  logUsage,
7248
7799
  resolveModelAlias,
7800
+ resolvePaymentChain,
7249
7801
  route,
7802
+ savePaymentChain,
7803
+ setupSolana,
7250
7804
  startProxy
7251
7805
  };
7252
7806
  //# sourceMappingURL=index.js.map