@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/cli.js CHANGED
@@ -3,271 +3,72 @@
3
3
  // src/proxy.ts
4
4
  import { createServer } from "http";
5
5
  import { finished } from "stream";
6
- import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
7
-
8
- // src/x402.ts
9
- import { signTypedData, privateKeyToAccount } from "viem/accounts";
6
+ import { createPublicClient as createPublicClient2, http as http2 } from "viem";
7
+ import { base as base2 } from "viem/chains";
8
+ import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
9
+ import { x402Client } from "@x402/fetch";
10
10
 
11
- // src/payment-cache.ts
11
+ // src/payment-preauth.ts
12
+ import { x402HTTPClient } from "@x402/fetch";
12
13
  var DEFAULT_TTL_MS = 36e5;
13
- var PaymentCache = class {
14
- cache = /* @__PURE__ */ new Map();
15
- ttlMs;
16
- constructor(ttlMs = DEFAULT_TTL_MS) {
17
- this.ttlMs = ttlMs;
18
- }
19
- /** Get cached payment params for an endpoint path. */
20
- get(endpointPath) {
21
- const entry = this.cache.get(endpointPath);
22
- if (!entry) return void 0;
23
- if (Date.now() - entry.cachedAt > this.ttlMs) {
24
- this.cache.delete(endpointPath);
25
- return void 0;
26
- }
27
- return entry;
28
- }
29
- /** Cache payment params from a 402 response. */
30
- set(endpointPath, params) {
31
- this.cache.set(endpointPath, { ...params, cachedAt: Date.now() });
32
- }
33
- /** Invalidate cache for an endpoint (e.g., if payTo changed). */
34
- invalidate(endpointPath) {
35
- this.cache.delete(endpointPath);
36
- }
37
- };
38
-
39
- // src/x402.ts
40
- var BASE_CHAIN_ID = 8453;
41
- var BASE_SEPOLIA_CHAIN_ID = 84532;
42
- var DEFAULT_TOKEN_NAME = "USD Coin";
43
- var DEFAULT_TOKEN_VERSION = "2";
44
- var DEFAULT_NETWORK = "eip155:8453";
45
- var DEFAULT_MAX_TIMEOUT_SECONDS = 300;
46
- var TRANSFER_TYPES = {
47
- TransferWithAuthorization: [
48
- { name: "from", type: "address" },
49
- { name: "to", type: "address" },
50
- { name: "value", type: "uint256" },
51
- { name: "validAfter", type: "uint256" },
52
- { name: "validBefore", type: "uint256" },
53
- { name: "nonce", type: "bytes32" }
54
- ]
55
- };
56
- function createNonce() {
57
- const bytes = new Uint8Array(32);
58
- crypto.getRandomValues(bytes);
59
- return `0x${Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("")}`;
60
- }
61
- function decodeBase64Json(value) {
62
- const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
63
- const padding = (4 - normalized.length % 4) % 4;
64
- const padded = normalized + "=".repeat(padding);
65
- const decoded = Buffer.from(padded, "base64").toString("utf8");
66
- return JSON.parse(decoded);
67
- }
68
- function encodeBase64Json(value) {
69
- return Buffer.from(JSON.stringify(value), "utf8").toString("base64");
70
- }
71
- function parsePaymentRequired(headerValue) {
72
- return decodeBase64Json(headerValue);
73
- }
74
- function normalizeNetwork(network) {
75
- if (!network || network.trim().length === 0) {
76
- return DEFAULT_NETWORK;
77
- }
78
- return network.trim().toLowerCase();
79
- }
80
- function resolveChainId(network) {
81
- const eip155Match = network.match(/^eip155:(\d+)$/i);
82
- if (eip155Match) {
83
- const parsed = Number.parseInt(eip155Match[1], 10);
84
- if (Number.isFinite(parsed) && parsed > 0) {
85
- return parsed;
86
- }
87
- }
88
- if (network === "base") return BASE_CHAIN_ID;
89
- if (network === "base-sepolia") return BASE_SEPOLIA_CHAIN_ID;
90
- return BASE_CHAIN_ID;
91
- }
92
- function parseHexAddress(value) {
93
- if (!value) return void 0;
94
- const direct = value.match(/^0x[a-fA-F0-9]{40}$/);
95
- if (direct) {
96
- return direct[0];
97
- }
98
- const caipSuffix = value.match(/0x[a-fA-F0-9]{40}$/);
99
- if (caipSuffix) {
100
- return caipSuffix[0];
101
- }
102
- return void 0;
103
- }
104
- function requireHexAddress(value, field) {
105
- const parsed = parseHexAddress(value);
106
- if (!parsed) {
107
- throw new Error(`Invalid ${field} in payment requirements: ${String(value)}`);
108
- }
109
- return parsed;
110
- }
111
- function setPaymentHeaders(headers, payload) {
112
- headers.set("payment-signature", payload);
113
- headers.set("x-payment", payload);
114
- }
115
- async function createPaymentPayload(privateKey, fromAddress, option, amount, requestUrl, resource) {
116
- const network = normalizeNetwork(option.network);
117
- const chainId = resolveChainId(network);
118
- const recipient = requireHexAddress(option.payTo, "payTo");
119
- const verifyingContract = requireHexAddress(option.asset, "asset");
120
- const maxTimeoutSeconds = typeof option.maxTimeoutSeconds === "number" && option.maxTimeoutSeconds > 0 ? Math.floor(option.maxTimeoutSeconds) : DEFAULT_MAX_TIMEOUT_SECONDS;
121
- const now = Math.floor(Date.now() / 1e3);
122
- const validAfter = now - 600;
123
- const validBefore = now + maxTimeoutSeconds;
124
- const nonce = createNonce();
125
- const signature = await signTypedData({
126
- privateKey,
127
- domain: {
128
- name: option.extra?.name || DEFAULT_TOKEN_NAME,
129
- version: option.extra?.version || DEFAULT_TOKEN_VERSION,
130
- chainId,
131
- verifyingContract
132
- },
133
- types: TRANSFER_TYPES,
134
- primaryType: "TransferWithAuthorization",
135
- message: {
136
- from: fromAddress,
137
- to: recipient,
138
- value: BigInt(amount),
139
- validAfter: BigInt(validAfter),
140
- validBefore: BigInt(validBefore),
141
- nonce
142
- }
143
- });
144
- const paymentData = {
145
- x402Version: 2,
146
- resource: {
147
- url: resource?.url || requestUrl,
148
- description: resource?.description || "BlockRun AI API call",
149
- mimeType: "application/json"
150
- },
151
- accepted: {
152
- scheme: option.scheme,
153
- network,
154
- amount,
155
- asset: option.asset,
156
- payTo: option.payTo,
157
- maxTimeoutSeconds: option.maxTimeoutSeconds,
158
- extra: option.extra
159
- },
160
- payload: {
161
- signature,
162
- authorization: {
163
- from: fromAddress,
164
- to: recipient,
165
- value: amount,
166
- validAfter: validAfter.toString(),
167
- validBefore: validBefore.toString(),
168
- nonce
169
- }
170
- },
171
- extensions: {}
172
- };
173
- return encodeBase64Json(paymentData);
174
- }
175
- function createPaymentFetch(privateKey) {
176
- const account = privateKeyToAccount(privateKey);
177
- const walletAddress = account.address;
178
- const paymentCache = new PaymentCache();
179
- const payFetch = async (input, init, preAuth) => {
180
- const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
181
- const endpointPath = new URL(url).pathname;
182
- const cached = paymentCache.get(endpointPath);
183
- if (cached && preAuth?.estimatedAmount) {
184
- const paymentPayload = await createPaymentPayload(
185
- privateKey,
186
- walletAddress,
187
- {
188
- scheme: cached.scheme,
189
- network: cached.network,
190
- asset: cached.asset,
191
- payTo: cached.payTo,
192
- maxTimeoutSeconds: cached.maxTimeoutSeconds,
193
- extra: cached.extra
194
- },
195
- preAuth.estimatedAmount,
196
- url,
197
- {
198
- url: cached.resourceUrl,
199
- description: cached.resourceDescription
14
+ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, options) {
15
+ const httpClient = new x402HTTPClient(client);
16
+ const cache = /* @__PURE__ */ new Map();
17
+ return async (input, init) => {
18
+ const request = new Request(input, init);
19
+ const urlPath = new URL(request.url).pathname;
20
+ const cached = !options?.skipPreAuth ? cache.get(urlPath) : void 0;
21
+ if (cached && Date.now() - cached.cachedAt < ttlMs) {
22
+ try {
23
+ const payload2 = await client.createPaymentPayload(cached.paymentRequired);
24
+ const headers = httpClient.encodePaymentSignatureHeader(payload2);
25
+ const preAuthRequest = request.clone();
26
+ for (const [key, value] of Object.entries(headers)) {
27
+ preAuthRequest.headers.set(key, value);
200
28
  }
201
- );
202
- const preAuthHeaders = new Headers(init?.headers);
203
- setPaymentHeaders(preAuthHeaders, paymentPayload);
204
- const response2 = await fetch(input, { ...init, headers: preAuthHeaders });
205
- if (response2.status !== 402) {
206
- return response2;
207
- }
208
- const paymentHeader2 = response2.headers.get("x-payment-required");
209
- if (paymentHeader2) {
210
- return handle402(input, init, url, endpointPath, paymentHeader2);
211
- }
212
- paymentCache.invalidate(endpointPath);
213
- const cleanResponse = await fetch(input, init);
214
- if (cleanResponse.status !== 402) {
215
- return cleanResponse;
216
- }
217
- const cleanHeader = cleanResponse.headers.get("x-payment-required");
218
- if (!cleanHeader) {
219
- throw new Error("402 response missing x-payment-required header");
29
+ const response2 = await baseFetch(preAuthRequest);
30
+ if (response2.status !== 402) {
31
+ return response2;
32
+ }
33
+ cache.delete(urlPath);
34
+ } catch {
35
+ cache.delete(urlPath);
220
36
  }
221
- return handle402(input, init, url, endpointPath, cleanHeader);
222
37
  }
223
- const response = await fetch(input, init);
38
+ const clonedRequest = request.clone();
39
+ const response = await baseFetch(request);
224
40
  if (response.status !== 402) {
225
41
  return response;
226
42
  }
227
- const paymentHeader = response.headers.get("x-payment-required");
228
- if (!paymentHeader) {
229
- throw new Error("402 response missing x-payment-required header");
43
+ let paymentRequired;
44
+ try {
45
+ const getHeader = (name) => response.headers.get(name);
46
+ let body;
47
+ try {
48
+ const responseText = await response.text();
49
+ if (responseText) body = JSON.parse(responseText);
50
+ } catch {
51
+ }
52
+ paymentRequired = httpClient.getPaymentRequiredResponse(getHeader, body);
53
+ cache.set(urlPath, { paymentRequired, cachedAt: Date.now() });
54
+ } catch (error) {
55
+ throw new Error(
56
+ `Failed to parse payment requirements: ${error instanceof Error ? error.message : "Unknown error"}`
57
+ );
58
+ }
59
+ const payload = await client.createPaymentPayload(paymentRequired);
60
+ const paymentHeaders = httpClient.encodePaymentSignatureHeader(payload);
61
+ for (const [key, value] of Object.entries(paymentHeaders)) {
62
+ clonedRequest.headers.set(key, value);
230
63
  }
231
- return handle402(input, init, url, endpointPath, paymentHeader);
64
+ return baseFetch(clonedRequest);
232
65
  };
233
- async function handle402(input, init, url, endpointPath, paymentHeader) {
234
- const paymentRequired = parsePaymentRequired(paymentHeader);
235
- const option = paymentRequired.accepts?.[0];
236
- if (!option) {
237
- throw new Error("No payment options in 402 response");
238
- }
239
- const amount = option.amount || option.maxAmountRequired;
240
- if (!amount) {
241
- throw new Error("No amount in payment requirements");
242
- }
243
- paymentCache.set(endpointPath, {
244
- payTo: option.payTo,
245
- asset: option.asset,
246
- scheme: option.scheme,
247
- network: option.network,
248
- extra: option.extra,
249
- maxTimeoutSeconds: option.maxTimeoutSeconds,
250
- resourceUrl: paymentRequired.resource?.url,
251
- resourceDescription: paymentRequired.resource?.description
252
- });
253
- const paymentPayload = await createPaymentPayload(
254
- privateKey,
255
- walletAddress,
256
- option,
257
- amount,
258
- url,
259
- paymentRequired.resource
260
- );
261
- const retryHeaders = new Headers(init?.headers);
262
- setPaymentHeaders(retryHeaders, paymentPayload);
263
- return fetch(input, {
264
- ...init,
265
- headers: retryHeaders
266
- });
267
- }
268
- return { fetch: payFetch, cache: paymentCache };
269
66
  }
270
67
 
68
+ // src/proxy.ts
69
+ import { registerExactEvmScheme } from "@x402/evm/exact/client";
70
+ import { toClientEvmSigner } from "@x402/evm";
71
+
271
72
  // src/router/rules.ts
272
73
  function scoreTokenCount(estimatedTokens, thresholds) {
273
74
  if (estimatedTokens < thresholds.simple) {
@@ -1794,7 +1595,7 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
1794
1595
  const ruleResult = classifyByRules(prompt, systemPrompt, estimatedTokens, config.scoring);
1795
1596
  const { routingProfile } = options;
1796
1597
  let tierConfigs;
1797
- let profileSuffix = "";
1598
+ let profileSuffix;
1798
1599
  if (routingProfile === "eco" && config.ecoTiers) {
1799
1600
  tierConfigs = config.ecoTiers;
1800
1601
  profileSuffix = " | eco";
@@ -3102,6 +2903,299 @@ var BalanceMonitor = class {
3102
2903
  }
3103
2904
  };
3104
2905
 
2906
+ // src/solana-balance.ts
2907
+ import { address as solAddress, createSolanaRpc } from "@solana/kit";
2908
+ var SOLANA_USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
2909
+ var SOLANA_DEFAULT_RPC = "https://api.mainnet-beta.solana.com";
2910
+ var BALANCE_TIMEOUT_MS = 1e4;
2911
+ var CACHE_TTL_MS2 = 3e4;
2912
+ var SolanaBalanceMonitor = class {
2913
+ rpc;
2914
+ walletAddress;
2915
+ cachedBalance = null;
2916
+ cachedAt = 0;
2917
+ constructor(walletAddress, rpcUrl) {
2918
+ this.walletAddress = walletAddress;
2919
+ const url = rpcUrl || process["env"].CLAWROUTER_SOLANA_RPC_URL || SOLANA_DEFAULT_RPC;
2920
+ this.rpc = createSolanaRpc(url);
2921
+ }
2922
+ async checkBalance() {
2923
+ const now = Date.now();
2924
+ if (this.cachedBalance !== null && now - this.cachedAt < CACHE_TTL_MS2) {
2925
+ return this.buildInfo(this.cachedBalance);
2926
+ }
2927
+ const balance = await this.fetchBalance();
2928
+ this.cachedBalance = balance;
2929
+ this.cachedAt = now;
2930
+ return this.buildInfo(balance);
2931
+ }
2932
+ deductEstimated(amountMicros) {
2933
+ if (this.cachedBalance !== null && this.cachedBalance >= amountMicros) {
2934
+ this.cachedBalance -= amountMicros;
2935
+ }
2936
+ }
2937
+ invalidate() {
2938
+ this.cachedBalance = null;
2939
+ this.cachedAt = 0;
2940
+ }
2941
+ async refresh() {
2942
+ this.invalidate();
2943
+ return this.checkBalance();
2944
+ }
2945
+ /**
2946
+ * Check if balance is sufficient for an estimated cost.
2947
+ */
2948
+ async checkSufficient(estimatedCostMicros) {
2949
+ const info = await this.checkBalance();
2950
+ if (info.balance >= estimatedCostMicros) {
2951
+ return { sufficient: true, info };
2952
+ }
2953
+ const shortfall = estimatedCostMicros - info.balance;
2954
+ return {
2955
+ sufficient: false,
2956
+ info,
2957
+ shortfall: this.formatUSDC(shortfall)
2958
+ };
2959
+ }
2960
+ /**
2961
+ * Format USDC amount (in micros) as "$X.XX".
2962
+ */
2963
+ formatUSDC(amountMicros) {
2964
+ const dollars = Number(amountMicros) / 1e6;
2965
+ return `$${dollars.toFixed(2)}`;
2966
+ }
2967
+ getWalletAddress() {
2968
+ return this.walletAddress;
2969
+ }
2970
+ async fetchBalance() {
2971
+ const owner = solAddress(this.walletAddress);
2972
+ const mint = solAddress(SOLANA_USDC_MINT);
2973
+ const controller = new AbortController();
2974
+ const timer = setTimeout(() => controller.abort(), BALANCE_TIMEOUT_MS);
2975
+ try {
2976
+ const response = await this.rpc.getTokenAccountsByOwner(owner, { mint }, { encoding: "jsonParsed" }).send({ abortSignal: controller.signal });
2977
+ if (response.value.length === 0) return 0n;
2978
+ let total = 0n;
2979
+ for (const account of response.value) {
2980
+ const parsed = account.account.data;
2981
+ total += BigInt(parsed.parsed.info.tokenAmount.amount);
2982
+ }
2983
+ return total;
2984
+ } catch (err) {
2985
+ throw new Error(`Failed to fetch Solana USDC balance: ${err instanceof Error ? err.message : String(err)}`);
2986
+ } finally {
2987
+ clearTimeout(timer);
2988
+ }
2989
+ }
2990
+ buildInfo(balance) {
2991
+ const dollars = Number(balance) / 1e6;
2992
+ return {
2993
+ balance,
2994
+ balanceUSD: `$${dollars.toFixed(2)}`,
2995
+ isLow: balance < 1000000n,
2996
+ isEmpty: balance < 100n,
2997
+ walletAddress: this.walletAddress
2998
+ };
2999
+ }
3000
+ };
3001
+
3002
+ // src/auth.ts
3003
+ import { writeFile, mkdir as mkdir2 } from "fs/promises";
3004
+ import { join as join4 } from "path";
3005
+ import { homedir as homedir3 } from "os";
3006
+ import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
3007
+
3008
+ // src/wallet.ts
3009
+ import { HDKey } from "@scure/bip32";
3010
+ import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from "@scure/bip39";
3011
+ import { wordlist as english } from "@scure/bip39/wordlists/english";
3012
+ import { privateKeyToAccount } from "viem/accounts";
3013
+ var ETH_DERIVATION_PATH = "m/44'/60'/0'/0/0";
3014
+ var SOLANA_DERIVATION_PATH = "m/44'/501'/0'/0'";
3015
+ function generateWalletMnemonic() {
3016
+ return generateMnemonic(english, 256);
3017
+ }
3018
+ function isValidMnemonic(mnemonic) {
3019
+ return validateMnemonic(mnemonic, english);
3020
+ }
3021
+ function deriveEvmKey(mnemonic) {
3022
+ const seed = mnemonicToSeedSync(mnemonic);
3023
+ const hdKey = HDKey.fromMasterSeed(seed);
3024
+ const derived = hdKey.derive(ETH_DERIVATION_PATH);
3025
+ if (!derived.privateKey) throw new Error("Failed to derive EVM private key");
3026
+ const hex = `0x${Buffer.from(derived.privateKey).toString("hex")}`;
3027
+ const account = privateKeyToAccount(hex);
3028
+ return { privateKey: hex, address: account.address };
3029
+ }
3030
+ function deriveSolanaKeyBytes(mnemonic) {
3031
+ const seed = mnemonicToSeedSync(mnemonic);
3032
+ const hdKey = HDKey.fromMasterSeed(seed);
3033
+ const derived = hdKey.derive(SOLANA_DERIVATION_PATH);
3034
+ if (!derived.privateKey) throw new Error("Failed to derive Solana private key");
3035
+ return new Uint8Array(derived.privateKey);
3036
+ }
3037
+ function deriveAllKeys(mnemonic) {
3038
+ const { privateKey: evmPrivateKey, address: evmAddress } = deriveEvmKey(mnemonic);
3039
+ const solanaPrivateKeyBytes = deriveSolanaKeyBytes(mnemonic);
3040
+ return { mnemonic, evmPrivateKey, evmAddress, solanaPrivateKeyBytes };
3041
+ }
3042
+
3043
+ // src/auth.ts
3044
+ var WALLET_DIR = join4(homedir3(), ".openclaw", "blockrun");
3045
+ var WALLET_FILE = join4(WALLET_DIR, "wallet.key");
3046
+ var MNEMONIC_FILE = join4(WALLET_DIR, "mnemonic");
3047
+ var CHAIN_FILE = join4(WALLET_DIR, "payment-chain");
3048
+ async function loadSavedWallet() {
3049
+ try {
3050
+ const key = (await readTextFile(WALLET_FILE)).trim();
3051
+ if (key.startsWith("0x") && key.length === 66) {
3052
+ console.log(`[ClawRouter] \u2713 Loaded existing wallet from ${WALLET_FILE}`);
3053
+ return key;
3054
+ }
3055
+ console.error(`[ClawRouter] \u2717 CRITICAL: Wallet file exists but has invalid format!`);
3056
+ console.error(`[ClawRouter] File: ${WALLET_FILE}`);
3057
+ console.error(`[ClawRouter] Expected: 0x followed by 64 hex characters (66 chars total)`);
3058
+ console.error(
3059
+ `[ClawRouter] To fix: restore your backup key or set BLOCKRUN_WALLET_KEY env var`
3060
+ );
3061
+ throw new Error(
3062
+ `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.`
3063
+ );
3064
+ } catch (err) {
3065
+ if (err.code !== "ENOENT") {
3066
+ if (err instanceof Error && err.message.includes("Refusing to auto-generate")) {
3067
+ throw err;
3068
+ }
3069
+ console.error(
3070
+ `[ClawRouter] \u2717 Failed to read wallet file: ${err instanceof Error ? err.message : String(err)}`
3071
+ );
3072
+ throw new Error(
3073
+ `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.`,
3074
+ { cause: err }
3075
+ );
3076
+ }
3077
+ }
3078
+ return void 0;
3079
+ }
3080
+ async function loadMnemonic() {
3081
+ try {
3082
+ const mnemonic = (await readTextFile(MNEMONIC_FILE)).trim();
3083
+ if (mnemonic && isValidMnemonic(mnemonic)) {
3084
+ return mnemonic;
3085
+ }
3086
+ console.warn(`[ClawRouter] \u26A0 Mnemonic file exists but has invalid format \u2014 ignoring`);
3087
+ return void 0;
3088
+ } catch (err) {
3089
+ if (err.code !== "ENOENT") {
3090
+ console.warn(`[ClawRouter] \u26A0 Cannot read mnemonic file \u2014 ignoring`);
3091
+ }
3092
+ }
3093
+ return void 0;
3094
+ }
3095
+ async function generateAndSaveWallet() {
3096
+ const existingMnemonic = await loadMnemonic();
3097
+ if (existingMnemonic) {
3098
+ throw new Error(
3099
+ `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>`
3100
+ );
3101
+ }
3102
+ const mnemonic = generateWalletMnemonic();
3103
+ const derived = deriveAllKeys(mnemonic);
3104
+ await mkdir2(WALLET_DIR, { recursive: true });
3105
+ await writeFile(WALLET_FILE, derived.evmPrivateKey + "\n", { mode: 384 });
3106
+ await writeFile(MNEMONIC_FILE, mnemonic + "\n", { mode: 384 });
3107
+ try {
3108
+ const verification = (await readTextFile(WALLET_FILE)).trim();
3109
+ if (verification !== derived.evmPrivateKey) {
3110
+ throw new Error("Wallet file verification failed - content mismatch");
3111
+ }
3112
+ console.log(`[ClawRouter] Wallet saved and verified at ${WALLET_FILE}`);
3113
+ } catch (err) {
3114
+ throw new Error(
3115
+ `Failed to verify wallet file after creation: ${err instanceof Error ? err.message : String(err)}`,
3116
+ { cause: err }
3117
+ );
3118
+ }
3119
+ console.log(`[ClawRouter]`);
3120
+ 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`);
3121
+ console.log(`[ClawRouter] NEW WALLET GENERATED \u2014 BACK UP YOUR KEY NOW`);
3122
+ 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`);
3123
+ console.log(`[ClawRouter] EVM Address : ${derived.evmAddress}`);
3124
+ console.log(`[ClawRouter] Key file : ${WALLET_FILE}`);
3125
+ console.log(`[ClawRouter] Mnemonic : ${MNEMONIC_FILE}`);
3126
+ console.log(`[ClawRouter]`);
3127
+ console.log(`[ClawRouter] Both EVM (Base) and Solana wallets are ready.`);
3128
+ console.log(`[ClawRouter] To back up, run in OpenClaw:`);
3129
+ console.log(`[ClawRouter] /wallet export`);
3130
+ console.log(`[ClawRouter]`);
3131
+ console.log(`[ClawRouter] To restore on another machine:`);
3132
+ console.log(`[ClawRouter] export BLOCKRUN_WALLET_KEY=<your_key>`);
3133
+ 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`);
3134
+ console.log(`[ClawRouter]`);
3135
+ return {
3136
+ key: derived.evmPrivateKey,
3137
+ address: derived.evmAddress,
3138
+ mnemonic,
3139
+ solanaPrivateKeyBytes: derived.solanaPrivateKeyBytes
3140
+ };
3141
+ }
3142
+ async function resolveOrGenerateWalletKey() {
3143
+ const saved = await loadSavedWallet();
3144
+ if (saved) {
3145
+ const account = privateKeyToAccount2(saved);
3146
+ const mnemonic = await loadMnemonic();
3147
+ if (mnemonic) {
3148
+ const solanaKeyBytes = deriveSolanaKeyBytes(mnemonic);
3149
+ return {
3150
+ key: saved,
3151
+ address: account.address,
3152
+ source: "saved",
3153
+ mnemonic,
3154
+ solanaPrivateKeyBytes: solanaKeyBytes
3155
+ };
3156
+ }
3157
+ return { key: saved, address: account.address, source: "saved" };
3158
+ }
3159
+ const envKey = process["env"].BLOCKRUN_WALLET_KEY;
3160
+ if (typeof envKey === "string" && envKey.startsWith("0x") && envKey.length === 66) {
3161
+ const account = privateKeyToAccount2(envKey);
3162
+ const mnemonic = await loadMnemonic();
3163
+ if (mnemonic) {
3164
+ const solanaKeyBytes = deriveSolanaKeyBytes(mnemonic);
3165
+ return {
3166
+ key: envKey,
3167
+ address: account.address,
3168
+ source: "env",
3169
+ mnemonic,
3170
+ solanaPrivateKeyBytes: solanaKeyBytes
3171
+ };
3172
+ }
3173
+ return { key: envKey, address: account.address, source: "env" };
3174
+ }
3175
+ const result = await generateAndSaveWallet();
3176
+ return {
3177
+ key: result.key,
3178
+ address: result.address,
3179
+ source: "generated",
3180
+ mnemonic: result.mnemonic,
3181
+ solanaPrivateKeyBytes: result.solanaPrivateKeyBytes
3182
+ };
3183
+ }
3184
+ async function loadPaymentChain() {
3185
+ try {
3186
+ const content = (await readTextFile(CHAIN_FILE)).trim();
3187
+ if (content === "solana") return "solana";
3188
+ return "base";
3189
+ } catch {
3190
+ return "base";
3191
+ }
3192
+ }
3193
+ async function resolvePaymentChain() {
3194
+ if (process["env"].CLAWROUTER_PAYMENT_CHAIN === "solana") return "solana";
3195
+ if (process["env"].CLAWROUTER_PAYMENT_CHAIN === "base") return "base";
3196
+ return loadPaymentChain();
3197
+ }
3198
+
3105
3199
  // src/compression/types.ts
3106
3200
  var DEFAULT_COMPRESSION_CONFIG = {
3107
3201
  enabled: true,
@@ -3131,7 +3225,7 @@ var DEFAULT_COMPRESSION_CONFIG = {
3131
3225
  };
3132
3226
 
3133
3227
  // src/compression/layers/deduplication.ts
3134
- import crypto2 from "crypto";
3228
+ import crypto from "crypto";
3135
3229
  function hashMessage(message) {
3136
3230
  let contentStr = "";
3137
3231
  if (typeof message.content === "string") {
@@ -3151,7 +3245,7 @@ function hashMessage(message) {
3151
3245
  );
3152
3246
  }
3153
3247
  const content = parts.join("|");
3154
- return crypto2.createHash("md5").update(content).digest("hex");
3248
+ return crypto.createHash("md5").update(content).digest("hex");
3155
3249
  }
3156
3250
  function deduplicateMessages(messages) {
3157
3251
  const seen = /* @__PURE__ */ new Set();
@@ -4248,6 +4342,7 @@ ${lines.join("\n")}`;
4248
4342
 
4249
4343
  // src/proxy.ts
4250
4344
  var BLOCKRUN_API = "https://blockrun.ai/api";
4345
+ var BLOCKRUN_SOLANA_API = "https://sol.blockrun.ai/api";
4251
4346
  var AUTO_MODEL = "blockrun/auto";
4252
4347
  var ROUTING_PROFILES = /* @__PURE__ */ new Set([
4253
4348
  "blockrun/free",
@@ -4376,7 +4471,7 @@ async function checkExistingProxy(port) {
4376
4471
  if (response.ok) {
4377
4472
  const data = await response.json();
4378
4473
  if (data.status === "ok" && data.wallet) {
4379
- return data.wallet;
4474
+ return { wallet: data.wallet, paymentChain: data.paymentChain };
4380
4475
  }
4381
4476
  }
4382
4477
  return void 0;
@@ -4806,31 +4901,78 @@ async function uploadDataUriToHost(dataUri) {
4806
4901
  throw new Error(`catbox.moe upload failed: ${result}`);
4807
4902
  }
4808
4903
  async function startProxy(options) {
4809
- const apiBase = options.apiBase ?? BLOCKRUN_API;
4904
+ const walletKey = typeof options.wallet === "string" ? options.wallet : options.wallet.key;
4905
+ const solanaPrivateKeyBytes = typeof options.wallet === "string" ? void 0 : options.wallet.solanaPrivateKeyBytes;
4906
+ const paymentChain = options.paymentChain ?? await resolvePaymentChain();
4907
+ const apiBase = options.apiBase ?? (paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
4908
+ if (paymentChain === "solana" && !solanaPrivateKeyBytes) {
4909
+ console.warn(`[ClawRouter] Payment chain is Solana but no Solana keys provided. Using Base (EVM).`);
4910
+ } else if (paymentChain === "solana") {
4911
+ console.log(`[ClawRouter] Payment chain: Solana (${BLOCKRUN_SOLANA_API})`);
4912
+ }
4810
4913
  const listenPort = options.port ?? getProxyPort();
4811
- const existingWallet = await checkExistingProxy(listenPort);
4812
- if (existingWallet) {
4813
- const account2 = privateKeyToAccount2(options.walletKey);
4814
- const balanceMonitor2 = new BalanceMonitor(account2.address);
4914
+ const existingProxy = await checkExistingProxy(listenPort);
4915
+ if (existingProxy) {
4916
+ const account2 = privateKeyToAccount3(walletKey);
4815
4917
  const baseUrl2 = `http://127.0.0.1:${listenPort}`;
4816
- if (existingWallet !== account2.address) {
4918
+ if (existingProxy.wallet !== account2.address) {
4817
4919
  console.warn(
4818
- `[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingWallet}, but current config uses ${account2.address}. Reusing existing proxy.`
4920
+ `[ClawRouter] Existing proxy on port ${listenPort} uses wallet ${existingProxy.wallet}, but current config uses ${account2.address}. Reusing existing proxy.`
4819
4921
  );
4820
4922
  }
4923
+ if (existingProxy.paymentChain) {
4924
+ if (existingProxy.paymentChain !== paymentChain) {
4925
+ throw new Error(
4926
+ `Existing proxy on port ${listenPort} is using ${existingProxy.paymentChain} but ${paymentChain} was requested. Stop the existing proxy first or use a different port.`
4927
+ );
4928
+ }
4929
+ } else if (paymentChain !== "base") {
4930
+ console.warn(`[ClawRouter] Existing proxy on port ${listenPort} does not report paymentChain (pre-v0.11 instance). Assuming Base.`);
4931
+ throw new Error(
4932
+ `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.`
4933
+ );
4934
+ }
4935
+ let reuseSolanaAddress;
4936
+ if (solanaPrivateKeyBytes) {
4937
+ const { createKeyPairSignerFromPrivateKeyBytes } = await import("@solana/kit");
4938
+ const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes(solanaPrivateKeyBytes);
4939
+ reuseSolanaAddress = solanaSigner.address;
4940
+ }
4941
+ const balanceMonitor2 = paymentChain === "solana" && reuseSolanaAddress ? new SolanaBalanceMonitor(reuseSolanaAddress) : new BalanceMonitor(account2.address);
4821
4942
  options.onReady?.(listenPort);
4822
4943
  return {
4823
4944
  port: listenPort,
4824
4945
  baseUrl: baseUrl2,
4825
- walletAddress: existingWallet,
4946
+ walletAddress: existingProxy.wallet,
4947
+ solanaAddress: reuseSolanaAddress,
4826
4948
  balanceMonitor: balanceMonitor2,
4827
4949
  close: async () => {
4828
4950
  }
4829
4951
  };
4830
4952
  }
4831
- const account = privateKeyToAccount2(options.walletKey);
4832
- const { fetch: payFetch } = createPaymentFetch(options.walletKey);
4833
- const balanceMonitor = new BalanceMonitor(account.address);
4953
+ const account = privateKeyToAccount3(walletKey);
4954
+ const evmPublicClient = createPublicClient2({ chain: base2, transport: http2() });
4955
+ const evmSigner = toClientEvmSigner(account, evmPublicClient);
4956
+ const x402 = new x402Client();
4957
+ registerExactEvmScheme(x402, { signer: evmSigner });
4958
+ let solanaAddress;
4959
+ if (solanaPrivateKeyBytes) {
4960
+ const { registerExactSvmScheme } = await import("@x402/svm/exact/client");
4961
+ const { createKeyPairSignerFromPrivateKeyBytes } = await import("@solana/kit");
4962
+ const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes(solanaPrivateKeyBytes);
4963
+ solanaAddress = solanaSigner.address;
4964
+ registerExactSvmScheme(x402, { signer: solanaSigner });
4965
+ console.log(`[ClawRouter] Solana x402 scheme registered: ${solanaAddress}`);
4966
+ }
4967
+ x402.onAfterPaymentCreation(async (context) => {
4968
+ const network = context.selectedRequirements.network;
4969
+ const chain = network.startsWith("eip155") ? "Base (EVM)" : network.startsWith("solana") ? "Solana" : network;
4970
+ console.log(`[ClawRouter] Payment signed on ${chain} (${network})`);
4971
+ });
4972
+ const payFetch = createPayFetchWithPreAuth(fetch, x402, void 0, {
4973
+ skipPreAuth: paymentChain === "solana"
4974
+ });
4975
+ const balanceMonitor = paymentChain === "solana" && solanaAddress ? new SolanaBalanceMonitor(solanaAddress) : new BalanceMonitor(account.address);
4834
4976
  const routingConfig = mergeRoutingConfig(options.routingConfig);
4835
4977
  const modelPricing = buildModelPricing();
4836
4978
  const routerOpts = {
@@ -4864,8 +5006,12 @@ async function startProxy(options) {
4864
5006
  const full = url.searchParams.get("full") === "true";
4865
5007
  const response = {
4866
5008
  status: "ok",
4867
- wallet: account.address
5009
+ wallet: account.address,
5010
+ paymentChain
4868
5011
  };
5012
+ if (solanaAddress) {
5013
+ response.solana = solanaAddress;
5014
+ }
4869
5015
  if (full) {
4870
5016
  try {
4871
5017
  const balanceInfo = await balanceMonitor.checkBalance();
@@ -4977,10 +5123,10 @@ async function startProxy(options) {
4977
5123
  const onError = async (err) => {
4978
5124
  server.removeListener("error", onError);
4979
5125
  if (err.code === "EADDRINUSE") {
4980
- const existingWallet2 = await checkExistingProxy(listenPort);
4981
- if (existingWallet2) {
5126
+ const existingProxy2 = await checkExistingProxy(listenPort);
5127
+ if (existingProxy2) {
4982
5128
  console.log(`[ClawRouter] Existing proxy detected on port ${listenPort}, reusing`);
4983
- rejectAttempt({ code: "REUSE_EXISTING", wallet: existingWallet2 });
5129
+ rejectAttempt({ code: "REUSE_EXISTING", wallet: existingProxy2.wallet, existingChain: existingProxy2.paymentChain });
4984
5130
  return;
4985
5131
  }
4986
5132
  if (attempt < PORT_RETRY_ATTEMPTS) {
@@ -5013,6 +5159,11 @@ async function startProxy(options) {
5013
5159
  } catch (err) {
5014
5160
  const error = err;
5015
5161
  if (error.code === "REUSE_EXISTING" && error.wallet) {
5162
+ if (error.existingChain && error.existingChain !== paymentChain) {
5163
+ throw new Error(
5164
+ `Existing proxy on port ${listenPort} is using ${error.existingChain} but ${paymentChain} was requested. Stop the existing proxy first or use a different port.`
5165
+ );
5166
+ }
5016
5167
  const baseUrl2 = `http://127.0.0.1:${listenPort}`;
5017
5168
  options.onReady?.(listenPort);
5018
5169
  return {
@@ -5070,6 +5221,7 @@ async function startProxy(options) {
5070
5221
  port,
5071
5222
  baseUrl,
5072
5223
  walletAddress: account.address,
5224
+ solanaAddress,
5073
5225
  balanceMonitor,
5074
5226
  close: () => new Promise((res, rej) => {
5075
5227
  const timeout = setTimeout(() => {
@@ -5116,8 +5268,6 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
5116
5268
  requestBody = Buffer.from(JSON.stringify(parsed));
5117
5269
  } catch {
5118
5270
  }
5119
- const estimated = estimateAmount(modelId, requestBody.length, maxTokens);
5120
- const preAuth = estimated ? { estimatedAmount: estimated } : void 0;
5121
5271
  try {
5122
5272
  const response = await payFetch(
5123
5273
  upstreamUrl,
@@ -5126,8 +5276,7 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
5126
5276
  headers,
5127
5277
  body: requestBody.length > 0 ? new Uint8Array(requestBody) : void 0,
5128
5278
  signal
5129
- },
5130
- preAuth
5279
+ }
5131
5280
  );
5132
5281
  if (response.status !== 200) {
5133
5282
  const errorBody = await response.text();
@@ -5763,6 +5912,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5763
5912
  }
5764
5913
  deduplicator.markInflight(dedupKey);
5765
5914
  let estimatedCostMicros;
5915
+ let balanceFallbackNotice;
5766
5916
  const isFreeModel = modelId === FREE_MODEL;
5767
5917
  if (modelId && !options.skipBalanceCheck && !isFreeModel) {
5768
5918
  const estimated = estimateAmount(modelId, body.length, maxTokens);
@@ -5773,12 +5923,17 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5773
5923
  if (sufficiency.info.isEmpty || !sufficiency.sufficient) {
5774
5924
  const originalModel = modelId;
5775
5925
  console.log(
5776
- `[ClawRouter] Wallet ${sufficiency.info.isEmpty ? "empty" : "insufficient"} ($${sufficiency.info.balanceUSD}), falling back to free model: ${FREE_MODEL} (requested: ${originalModel})`
5926
+ `[ClawRouter] Wallet ${sufficiency.info.isEmpty ? "empty" : "insufficient"} (${sufficiency.info.balanceUSD}), falling back to free model: ${FREE_MODEL} (requested: ${originalModel})`
5777
5927
  );
5778
5928
  modelId = FREE_MODEL;
5779
5929
  const parsed = JSON.parse(body.toString());
5780
5930
  parsed.model = FREE_MODEL;
5781
5931
  body = Buffer.from(JSON.stringify(parsed));
5932
+ balanceFallbackNotice = sufficiency.info.isEmpty ? `> **\u26A0\uFE0F Wallet empty** \u2014 using free model. Fund your wallet to use ${originalModel}.
5933
+
5934
+ ` : `> **\u26A0\uFE0F Insufficient balance** (${sufficiency.info.balanceUSD}) \u2014 using free model instead of ${originalModel}.
5935
+
5936
+ `;
5782
5937
  options.onLowBalance?.({
5783
5938
  balanceUSD: sufficiency.info.balanceUSD,
5784
5939
  walletAddress: sufficiency.info.walletAddress
@@ -6070,6 +6225,18 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6070
6225
  `;
6071
6226
  safeWrite(res, roleData);
6072
6227
  responseChunks.push(Buffer.from(roleData));
6228
+ if (balanceFallbackNotice) {
6229
+ const noticeChunk = {
6230
+ ...baseChunk,
6231
+ choices: [{ index, delta: { content: balanceFallbackNotice }, logprobs: null, finish_reason: null }]
6232
+ };
6233
+ const noticeData = `data: ${JSON.stringify(noticeChunk)}
6234
+
6235
+ `;
6236
+ safeWrite(res, noticeData);
6237
+ responseChunks.push(Buffer.from(noticeData));
6238
+ balanceFallbackNotice = void 0;
6239
+ }
6073
6240
  if (content) {
6074
6241
  const contentChunk = {
6075
6242
  ...baseChunk,
@@ -6154,23 +6321,36 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6154
6321
  responseHeaders["x-clawrouter-agentic-score"] = routingDecision.agenticScore.toFixed(2);
6155
6322
  }
6156
6323
  }
6157
- res.writeHead(upstream.status, responseHeaders);
6324
+ const bodyParts = [];
6158
6325
  if (upstream.body) {
6159
6326
  const reader = upstream.body.getReader();
6160
6327
  try {
6161
6328
  while (true) {
6162
6329
  const { done, value } = await reader.read();
6163
6330
  if (done) break;
6164
- const chunk = Buffer.from(value);
6165
- safeWrite(res, chunk);
6166
- responseChunks.push(chunk);
6331
+ bodyParts.push(Buffer.from(value));
6167
6332
  }
6168
6333
  } finally {
6169
6334
  reader.releaseLock();
6170
6335
  }
6171
6336
  }
6337
+ let responseBody = Buffer.concat(bodyParts);
6338
+ if (balanceFallbackNotice && responseBody.length > 0) {
6339
+ try {
6340
+ const parsed = JSON.parse(responseBody.toString());
6341
+ if (parsed.choices?.[0]?.message?.content !== void 0) {
6342
+ parsed.choices[0].message.content = balanceFallbackNotice + parsed.choices[0].message.content;
6343
+ responseBody = Buffer.from(JSON.stringify(parsed));
6344
+ }
6345
+ } catch {
6346
+ }
6347
+ balanceFallbackNotice = void 0;
6348
+ }
6349
+ responseHeaders["content-length"] = String(responseBody.length);
6350
+ res.writeHead(upstream.status, responseHeaders);
6351
+ safeWrite(res, responseBody);
6352
+ responseChunks.push(responseBody);
6172
6353
  res.end();
6173
- const responseBody = Buffer.concat(responseChunks);
6174
6354
  deduplicator.complete(dedupKey, {
6175
6355
  status: upstream.status,
6176
6356
  headers: responseHeaders,
@@ -6222,7 +6402,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6222
6402
  deduplicator.removeInflight(dedupKey);
6223
6403
  balanceMonitor.invalidate();
6224
6404
  if (err instanceof Error && err.name === "AbortError") {
6225
- throw new Error(`Request timed out after ${timeoutMs}ms`);
6405
+ throw new Error(`Request timed out after ${timeoutMs}ms`, { cause: err });
6226
6406
  }
6227
6407
  throw err;
6228
6408
  }
@@ -6253,91 +6433,6 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
6253
6433
  }
6254
6434
  }
6255
6435
 
6256
- // src/auth.ts
6257
- import { writeFile, mkdir as mkdir2 } from "fs/promises";
6258
- import { join as join4 } from "path";
6259
- import { homedir as homedir3 } from "os";
6260
- import { generatePrivateKey, privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
6261
- var WALLET_DIR = join4(homedir3(), ".openclaw", "blockrun");
6262
- var WALLET_FILE = join4(WALLET_DIR, "wallet.key");
6263
- async function loadSavedWallet() {
6264
- try {
6265
- const key = (await readTextFile(WALLET_FILE)).trim();
6266
- if (key.startsWith("0x") && key.length === 66) {
6267
- console.log(`[ClawRouter] \u2713 Loaded existing wallet from ${WALLET_FILE}`);
6268
- return key;
6269
- }
6270
- console.error(`[ClawRouter] \u2717 CRITICAL: Wallet file exists but has invalid format!`);
6271
- console.error(`[ClawRouter] File: ${WALLET_FILE}`);
6272
- console.error(`[ClawRouter] Expected: 0x followed by 64 hex characters (66 chars total)`);
6273
- console.error(
6274
- `[ClawRouter] To fix: restore your backup key or set BLOCKRUN_WALLET_KEY env var`
6275
- );
6276
- throw new Error(
6277
- `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.`
6278
- );
6279
- } catch (err) {
6280
- if (err.code !== "ENOENT") {
6281
- if (err instanceof Error && err.message.includes("Refusing to auto-generate")) {
6282
- throw err;
6283
- }
6284
- console.error(
6285
- `[ClawRouter] \u2717 Failed to read wallet file: ${err instanceof Error ? err.message : String(err)}`
6286
- );
6287
- throw new Error(
6288
- `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.`
6289
- );
6290
- }
6291
- }
6292
- return void 0;
6293
- }
6294
- async function generateAndSaveWallet() {
6295
- const key = generatePrivateKey();
6296
- const account = privateKeyToAccount3(key);
6297
- await mkdir2(WALLET_DIR, { recursive: true });
6298
- await writeFile(WALLET_FILE, key + "\n", { mode: 384 });
6299
- try {
6300
- const verification = (await readTextFile(WALLET_FILE)).trim();
6301
- if (verification !== key) {
6302
- throw new Error("Wallet file verification failed - content mismatch");
6303
- }
6304
- console.log(`[ClawRouter] \u2713 Wallet saved and verified at ${WALLET_FILE}`);
6305
- } catch (err) {
6306
- throw new Error(
6307
- `Failed to verify wallet file after creation: ${err instanceof Error ? err.message : String(err)}`
6308
- );
6309
- }
6310
- console.log(`[ClawRouter]`);
6311
- 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`);
6312
- console.log(`[ClawRouter] NEW WALLET GENERATED \u2014 BACK UP YOUR KEY NOW`);
6313
- 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`);
6314
- console.log(`[ClawRouter] Address : ${account.address}`);
6315
- console.log(`[ClawRouter] Key file: ${WALLET_FILE}`);
6316
- console.log(`[ClawRouter]`);
6317
- console.log(`[ClawRouter] To back up, run in OpenClaw:`);
6318
- console.log(`[ClawRouter] /wallet export`);
6319
- console.log(`[ClawRouter]`);
6320
- console.log(`[ClawRouter] To restore on another machine:`);
6321
- console.log(`[ClawRouter] export BLOCKRUN_WALLET_KEY=<your_key>`);
6322
- 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`);
6323
- console.log(`[ClawRouter]`);
6324
- return { key, address: account.address };
6325
- }
6326
- async function resolveOrGenerateWalletKey() {
6327
- const saved = await loadSavedWallet();
6328
- if (saved) {
6329
- const account = privateKeyToAccount3(saved);
6330
- return { key: saved, address: account.address, source: "saved" };
6331
- }
6332
- const envKey = process["env"].BLOCKRUN_WALLET_KEY;
6333
- if (typeof envKey === "string" && envKey.startsWith("0x") && envKey.length === 66) {
6334
- const account = privateKeyToAccount3(envKey);
6335
- return { key: envKey, address: account.address, source: "env" };
6336
- }
6337
- const { key, address } = await generateAndSaveWallet();
6338
- return { key, address, source: "generated" };
6339
- }
6340
-
6341
6436
  // src/report.ts
6342
6437
  async function generateReport(period, json = false) {
6343
6438
  const days = period === "daily" ? 1 : period === "weekly" ? 7 : 30;
@@ -6379,6 +6474,12 @@ function capitalize(str) {
6379
6474
 
6380
6475
  // src/doctor.ts
6381
6476
  import { platform, arch, freemem, totalmem } from "os";
6477
+ import { createPublicClient as createPublicClient3, http as http3 } from "viem";
6478
+ import { base as base3 } from "viem/chains";
6479
+ import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
6480
+ import { wrapFetchWithPayment, x402Client as x402Client2 } from "@x402/fetch";
6481
+ import { registerExactEvmScheme as registerExactEvmScheme2 } from "@x402/evm/exact/client";
6482
+ import { toClientEvmSigner as toClientEvmSigner2 } from "@x402/evm";
6382
6483
  function formatBytes(bytes) {
6383
6484
  const gb = bytes / (1024 * 1024 * 1024);
6384
6485
  return `${gb.toFixed(1)}GB`;
@@ -6463,7 +6564,6 @@ async function collectNetworkInfo() {
6463
6564
  blockrunLatency = Date.now() - start;
6464
6565
  blockrunReachable = response.ok || response.status === 402;
6465
6566
  } catch {
6466
- blockrunReachable = false;
6467
6567
  }
6468
6568
  let proxyRunning = false;
6469
6569
  try {
@@ -6473,7 +6573,6 @@ async function collectNetworkInfo() {
6473
6573
  });
6474
6574
  proxyRunning = response.ok;
6475
6575
  } catch {
6476
- proxyRunning = false;
6477
6576
  }
6478
6577
  return {
6479
6578
  blockrunApi: { reachable: blockrunReachable, latencyMs: blockrunLatency },
@@ -6592,7 +6691,12 @@ async function analyzeWithAI(diagnostics, userQuestion, model = "sonnet") {
6592
6691
  `);
6593
6692
  try {
6594
6693
  const { key } = await resolveOrGenerateWalletKey();
6595
- const { fetch: paymentFetch } = createPaymentFetch(key);
6694
+ const account = privateKeyToAccount4(key);
6695
+ const publicClient = createPublicClient3({ chain: base3, transport: http3() });
6696
+ const evmSigner = toClientEvmSigner2(account, publicClient);
6697
+ const x402 = new x402Client2();
6698
+ registerExactEvmScheme2(x402, { signer: evmSigner });
6699
+ const paymentFetch = wrapFetchWithPayment(fetch, x402);
6596
6700
  const response = await paymentFetch(
6597
6701
  "https://blockrun.ai/api/v1/chat/completions",
6598
6702
  {
@@ -6627,8 +6731,7 @@ Please analyze and help me fix any issues.`
6627
6731
  ],
6628
6732
  max_tokens: 1e3
6629
6733
  })
6630
- },
6631
- void 0
6734
+ }
6632
6735
  );
6633
6736
  if (!response.ok) {
6634
6737
  const text = await response.text();
@@ -6864,16 +6967,16 @@ ClawRouter Partner APIs (v${VERSION})
6864
6967
  console.log(report);
6865
6968
  process.exit(0);
6866
6969
  }
6867
- const { key: walletKey, address, source } = await resolveOrGenerateWalletKey();
6868
- if (source === "generated") {
6869
- console.log(`[ClawRouter] Generated new wallet: ${address}`);
6870
- } else if (source === "saved") {
6871
- console.log(`[ClawRouter] Using saved wallet: ${address}`);
6970
+ const wallet = await resolveOrGenerateWalletKey();
6971
+ if (wallet.source === "generated") {
6972
+ console.log(`[ClawRouter] Generated new wallet: ${wallet.address}`);
6973
+ } else if (wallet.source === "saved") {
6974
+ console.log(`[ClawRouter] Using saved wallet: ${wallet.address}`);
6872
6975
  } else {
6873
- console.log(`[ClawRouter] Using wallet from BLOCKRUN_WALLET_KEY: ${address}`);
6976
+ console.log(`[ClawRouter] Using wallet from BLOCKRUN_WALLET_KEY: ${wallet.address}`);
6874
6977
  }
6875
6978
  const proxy = await startProxy({
6876
- walletKey,
6979
+ wallet,
6877
6980
  port: args.port,
6878
6981
  onReady: (port) => {
6879
6982
  console.log(`[ClawRouter] Proxy listening on http://127.0.0.1:${port}`);
@@ -6897,19 +7000,19 @@ ClawRouter Partner APIs (v${VERSION})
6897
7000
  console.error(`[ClawRouter] Need help? Run: npx @blockrun/clawrouter doctor`);
6898
7001
  }
6899
7002
  });
6900
- const monitor = new BalanceMonitor(address);
7003
+ const monitor = new BalanceMonitor(wallet.address);
6901
7004
  try {
6902
7005
  const balance = await monitor.checkBalance();
6903
7006
  if (balance.isEmpty) {
6904
7007
  console.log(`[ClawRouter] Wallet balance: $0.00 (using FREE model)`);
6905
- console.log(`[ClawRouter] Fund wallet for premium models: ${address}`);
7008
+ console.log(`[ClawRouter] Fund wallet for premium models: ${wallet.address}`);
6906
7009
  } else if (balance.isLow) {
6907
7010
  console.log(`[ClawRouter] Wallet balance: ${balance.balanceUSD} (low)`);
6908
7011
  } else {
6909
7012
  console.log(`[ClawRouter] Wallet balance: ${balance.balanceUSD}`);
6910
7013
  }
6911
7014
  } catch {
6912
- console.log(`[ClawRouter] Wallet: ${address} (balance check pending)`);
7015
+ console.log(`[ClawRouter] Wallet: ${wallet.address} (balance check pending)`);
6913
7016
  }
6914
7017
  console.log(`[ClawRouter] Ready - Ctrl+C to stop`);
6915
7018
  const shutdown = async (signal) => {