@blockrun/clawrouter 0.3.2 → 0.3.3
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/README.md +291 -217
- package/dist/index.d.ts +215 -3
- package/dist/index.js +655 -82
- package/dist/index.js.map +1 -1
- package/package.json +10 -3
package/dist/index.js
CHANGED
|
@@ -95,40 +95,260 @@ var envKeyAuth = {
|
|
|
95
95
|
// src/models.ts
|
|
96
96
|
var BLOCKRUN_MODELS = [
|
|
97
97
|
// Smart routing meta-model — proxy replaces with actual model
|
|
98
|
-
{
|
|
98
|
+
{
|
|
99
|
+
id: "blockrun/auto",
|
|
100
|
+
name: "BlockRun Smart Router",
|
|
101
|
+
inputPrice: 0,
|
|
102
|
+
outputPrice: 0,
|
|
103
|
+
contextWindow: 105e4,
|
|
104
|
+
maxOutput: 128e3
|
|
105
|
+
},
|
|
99
106
|
// OpenAI GPT-5 Family
|
|
100
|
-
{
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
{
|
|
108
|
+
id: "openai/gpt-5.2",
|
|
109
|
+
name: "GPT-5.2",
|
|
110
|
+
inputPrice: 1.75,
|
|
111
|
+
outputPrice: 14,
|
|
112
|
+
contextWindow: 4e5,
|
|
113
|
+
maxOutput: 128e3,
|
|
114
|
+
reasoning: true,
|
|
115
|
+
vision: true
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "openai/gpt-5-mini",
|
|
119
|
+
name: "GPT-5 Mini",
|
|
120
|
+
inputPrice: 0.25,
|
|
121
|
+
outputPrice: 2,
|
|
122
|
+
contextWindow: 2e5,
|
|
123
|
+
maxOutput: 65536
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: "openai/gpt-5-nano",
|
|
127
|
+
name: "GPT-5 Nano",
|
|
128
|
+
inputPrice: 0.05,
|
|
129
|
+
outputPrice: 0.4,
|
|
130
|
+
contextWindow: 128e3,
|
|
131
|
+
maxOutput: 32768
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: "openai/gpt-5.2-pro",
|
|
135
|
+
name: "GPT-5.2 Pro",
|
|
136
|
+
inputPrice: 21,
|
|
137
|
+
outputPrice: 168,
|
|
138
|
+
contextWindow: 4e5,
|
|
139
|
+
maxOutput: 128e3,
|
|
140
|
+
reasoning: true
|
|
141
|
+
},
|
|
104
142
|
// OpenAI GPT-4 Family
|
|
105
|
-
{
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
143
|
+
{
|
|
144
|
+
id: "openai/gpt-4.1",
|
|
145
|
+
name: "GPT-4.1",
|
|
146
|
+
inputPrice: 2,
|
|
147
|
+
outputPrice: 8,
|
|
148
|
+
contextWindow: 128e3,
|
|
149
|
+
maxOutput: 16384,
|
|
150
|
+
vision: true
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: "openai/gpt-4.1-mini",
|
|
154
|
+
name: "GPT-4.1 Mini",
|
|
155
|
+
inputPrice: 0.4,
|
|
156
|
+
outputPrice: 1.6,
|
|
157
|
+
contextWindow: 128e3,
|
|
158
|
+
maxOutput: 16384
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: "openai/gpt-4.1-nano",
|
|
162
|
+
name: "GPT-4.1 Nano",
|
|
163
|
+
inputPrice: 0.1,
|
|
164
|
+
outputPrice: 0.4,
|
|
165
|
+
contextWindow: 128e3,
|
|
166
|
+
maxOutput: 16384
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: "openai/gpt-4o",
|
|
170
|
+
name: "GPT-4o",
|
|
171
|
+
inputPrice: 2.5,
|
|
172
|
+
outputPrice: 10,
|
|
173
|
+
contextWindow: 128e3,
|
|
174
|
+
maxOutput: 16384,
|
|
175
|
+
vision: true
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: "openai/gpt-4o-mini",
|
|
179
|
+
name: "GPT-4o Mini",
|
|
180
|
+
inputPrice: 0.15,
|
|
181
|
+
outputPrice: 0.6,
|
|
182
|
+
contextWindow: 128e3,
|
|
183
|
+
maxOutput: 16384
|
|
184
|
+
},
|
|
110
185
|
// OpenAI O-series (Reasoning)
|
|
111
|
-
{
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
186
|
+
{
|
|
187
|
+
id: "openai/o1",
|
|
188
|
+
name: "o1",
|
|
189
|
+
inputPrice: 15,
|
|
190
|
+
outputPrice: 60,
|
|
191
|
+
contextWindow: 2e5,
|
|
192
|
+
maxOutput: 1e5,
|
|
193
|
+
reasoning: true
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: "openai/o1-mini",
|
|
197
|
+
name: "o1-mini",
|
|
198
|
+
inputPrice: 1.1,
|
|
199
|
+
outputPrice: 4.4,
|
|
200
|
+
contextWindow: 128e3,
|
|
201
|
+
maxOutput: 65536,
|
|
202
|
+
reasoning: true
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
id: "openai/o3",
|
|
206
|
+
name: "o3",
|
|
207
|
+
inputPrice: 2,
|
|
208
|
+
outputPrice: 8,
|
|
209
|
+
contextWindow: 2e5,
|
|
210
|
+
maxOutput: 1e5,
|
|
211
|
+
reasoning: true
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: "openai/o3-mini",
|
|
215
|
+
name: "o3-mini",
|
|
216
|
+
inputPrice: 1.1,
|
|
217
|
+
outputPrice: 4.4,
|
|
218
|
+
contextWindow: 128e3,
|
|
219
|
+
maxOutput: 65536,
|
|
220
|
+
reasoning: true
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: "openai/o4-mini",
|
|
224
|
+
name: "o4-mini",
|
|
225
|
+
inputPrice: 1.1,
|
|
226
|
+
outputPrice: 4.4,
|
|
227
|
+
contextWindow: 128e3,
|
|
228
|
+
maxOutput: 65536,
|
|
229
|
+
reasoning: true
|
|
230
|
+
},
|
|
116
231
|
// Anthropic
|
|
117
|
-
{
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
232
|
+
{
|
|
233
|
+
id: "anthropic/claude-haiku-4.5",
|
|
234
|
+
name: "Claude Haiku 4.5",
|
|
235
|
+
inputPrice: 1,
|
|
236
|
+
outputPrice: 5,
|
|
237
|
+
contextWindow: 2e5,
|
|
238
|
+
maxOutput: 8192
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: "anthropic/claude-sonnet-4",
|
|
242
|
+
name: "Claude Sonnet 4",
|
|
243
|
+
inputPrice: 3,
|
|
244
|
+
outputPrice: 15,
|
|
245
|
+
contextWindow: 2e5,
|
|
246
|
+
maxOutput: 64e3,
|
|
247
|
+
reasoning: true
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
id: "anthropic/claude-opus-4",
|
|
251
|
+
name: "Claude Opus 4",
|
|
252
|
+
inputPrice: 15,
|
|
253
|
+
outputPrice: 75,
|
|
254
|
+
contextWindow: 2e5,
|
|
255
|
+
maxOutput: 32e3,
|
|
256
|
+
reasoning: true
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
id: "anthropic/claude-opus-4.5",
|
|
260
|
+
name: "Claude Opus 4.5",
|
|
261
|
+
inputPrice: 5,
|
|
262
|
+
outputPrice: 25,
|
|
263
|
+
contextWindow: 2e5,
|
|
264
|
+
maxOutput: 32e3,
|
|
265
|
+
reasoning: true
|
|
266
|
+
},
|
|
121
267
|
// Google
|
|
122
|
-
{
|
|
123
|
-
|
|
124
|
-
|
|
268
|
+
{
|
|
269
|
+
id: "google/gemini-3-pro-preview",
|
|
270
|
+
name: "Gemini 3 Pro Preview",
|
|
271
|
+
inputPrice: 2,
|
|
272
|
+
outputPrice: 12,
|
|
273
|
+
contextWindow: 105e4,
|
|
274
|
+
maxOutput: 65536,
|
|
275
|
+
reasoning: true,
|
|
276
|
+
vision: true
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
id: "google/gemini-2.5-pro",
|
|
280
|
+
name: "Gemini 2.5 Pro",
|
|
281
|
+
inputPrice: 1.25,
|
|
282
|
+
outputPrice: 10,
|
|
283
|
+
contextWindow: 105e4,
|
|
284
|
+
maxOutput: 65536,
|
|
285
|
+
reasoning: true,
|
|
286
|
+
vision: true
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
id: "google/gemini-2.5-flash",
|
|
290
|
+
name: "Gemini 2.5 Flash",
|
|
291
|
+
inputPrice: 0.15,
|
|
292
|
+
outputPrice: 0.6,
|
|
293
|
+
contextWindow: 1e6,
|
|
294
|
+
maxOutput: 65536
|
|
295
|
+
},
|
|
125
296
|
// DeepSeek
|
|
126
|
-
{
|
|
127
|
-
|
|
297
|
+
{
|
|
298
|
+
id: "deepseek/deepseek-chat",
|
|
299
|
+
name: "DeepSeek V3.2 Chat",
|
|
300
|
+
inputPrice: 0.28,
|
|
301
|
+
outputPrice: 0.42,
|
|
302
|
+
contextWindow: 128e3,
|
|
303
|
+
maxOutput: 8192
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
id: "deepseek/deepseek-reasoner",
|
|
307
|
+
name: "DeepSeek V3.2 Reasoner",
|
|
308
|
+
inputPrice: 0.28,
|
|
309
|
+
outputPrice: 0.42,
|
|
310
|
+
contextWindow: 128e3,
|
|
311
|
+
maxOutput: 8192,
|
|
312
|
+
reasoning: true
|
|
313
|
+
},
|
|
314
|
+
// Moonshot / Kimi
|
|
315
|
+
{
|
|
316
|
+
id: "moonshot/kimi-k2.5",
|
|
317
|
+
name: "Kimi K2.5",
|
|
318
|
+
inputPrice: 0.5,
|
|
319
|
+
outputPrice: 2.4,
|
|
320
|
+
contextWindow: 262144,
|
|
321
|
+
maxOutput: 8192,
|
|
322
|
+
reasoning: true,
|
|
323
|
+
vision: true
|
|
324
|
+
},
|
|
128
325
|
// xAI / Grok
|
|
129
|
-
{
|
|
130
|
-
|
|
131
|
-
|
|
326
|
+
{
|
|
327
|
+
id: "xai/grok-3",
|
|
328
|
+
name: "Grok 3",
|
|
329
|
+
inputPrice: 3,
|
|
330
|
+
outputPrice: 15,
|
|
331
|
+
contextWindow: 131072,
|
|
332
|
+
maxOutput: 16384,
|
|
333
|
+
reasoning: true
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
id: "xai/grok-3-fast",
|
|
337
|
+
name: "Grok 3 Fast",
|
|
338
|
+
inputPrice: 5,
|
|
339
|
+
outputPrice: 25,
|
|
340
|
+
contextWindow: 131072,
|
|
341
|
+
maxOutput: 16384,
|
|
342
|
+
reasoning: true
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
id: "xai/grok-3-mini",
|
|
346
|
+
name: "Grok 3 Mini",
|
|
347
|
+
inputPrice: 0.3,
|
|
348
|
+
outputPrice: 0.5,
|
|
349
|
+
contextWindow: 131072,
|
|
350
|
+
maxOutput: 16384
|
|
351
|
+
}
|
|
132
352
|
];
|
|
133
353
|
function toOpenClawModel(m) {
|
|
134
354
|
return {
|
|
@@ -393,10 +613,18 @@ function scoreTokenCount(estimatedTokens, thresholds) {
|
|
|
393
613
|
function scoreKeywordMatch(text, keywords, name, signalLabel, thresholds, scores) {
|
|
394
614
|
const matches = keywords.filter((kw) => text.includes(kw.toLowerCase()));
|
|
395
615
|
if (matches.length >= thresholds.high) {
|
|
396
|
-
return {
|
|
616
|
+
return {
|
|
617
|
+
name,
|
|
618
|
+
score: scores.high,
|
|
619
|
+
signal: `${signalLabel} (${matches.slice(0, 3).join(", ")})`
|
|
620
|
+
};
|
|
397
621
|
}
|
|
398
622
|
if (matches.length >= thresholds.low) {
|
|
399
|
-
return {
|
|
623
|
+
return {
|
|
624
|
+
name,
|
|
625
|
+
score: scores.low,
|
|
626
|
+
signal: `${signalLabel} (${matches.slice(0, 3).join(", ")})`
|
|
627
|
+
};
|
|
400
628
|
}
|
|
401
629
|
return { name, score: scores.none, signal: null };
|
|
402
630
|
}
|
|
@@ -519,9 +747,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
|
|
|
519
747
|
const w = weights[d.name] ?? 0;
|
|
520
748
|
weightedScore += d.score * w;
|
|
521
749
|
}
|
|
522
|
-
const reasoningMatches = config.reasoningKeywords.filter(
|
|
523
|
-
(kw) => text.includes(kw.toLowerCase())
|
|
524
|
-
);
|
|
750
|
+
const reasoningMatches = config.reasoningKeywords.filter((kw) => text.includes(kw.toLowerCase()));
|
|
525
751
|
if (reasoningMatches.length >= 2) {
|
|
526
752
|
const confidence2 = calibrateConfidence(
|
|
527
753
|
Math.max(weightedScore, 0.3),
|
|
@@ -543,10 +769,7 @@ function classifyByRules(prompt, systemPrompt, estimatedTokens, config) {
|
|
|
543
769
|
distanceFromBoundary = simpleMedium - weightedScore;
|
|
544
770
|
} else if (weightedScore < mediumComplex) {
|
|
545
771
|
tier = "MEDIUM";
|
|
546
|
-
distanceFromBoundary = Math.min(
|
|
547
|
-
weightedScore - simpleMedium,
|
|
548
|
-
mediumComplex - weightedScore
|
|
549
|
-
);
|
|
772
|
+
distanceFromBoundary = Math.min(weightedScore - simpleMedium, mediumComplex - weightedScore);
|
|
550
773
|
} else if (weightedScore < complexReasoning) {
|
|
551
774
|
tier = "COMPLEX";
|
|
552
775
|
distanceFromBoundary = Math.min(
|
|
@@ -651,15 +874,7 @@ var DEFAULT_ROUTING_CONFIG = {
|
|
|
651
874
|
"database",
|
|
652
875
|
"infrastructure"
|
|
653
876
|
],
|
|
654
|
-
creativeKeywords: [
|
|
655
|
-
"story",
|
|
656
|
-
"poem",
|
|
657
|
-
"compose",
|
|
658
|
-
"brainstorm",
|
|
659
|
-
"creative",
|
|
660
|
-
"imagine",
|
|
661
|
-
"write a"
|
|
662
|
-
],
|
|
877
|
+
creativeKeywords: ["story", "poem", "compose", "brainstorm", "creative", "imagine", "write a"],
|
|
663
878
|
// New dimension keyword lists
|
|
664
879
|
imperativeVerbs: [
|
|
665
880
|
"build",
|
|
@@ -802,15 +1017,10 @@ function route(prompt, systemPrompt, maxOutputTokens, options) {
|
|
|
802
1017
|
);
|
|
803
1018
|
}
|
|
804
1019
|
const hasStructuredOutput = systemPrompt ? /json|structured|schema/i.test(systemPrompt) : false;
|
|
805
|
-
const ruleResult = classifyByRules(
|
|
806
|
-
prompt,
|
|
807
|
-
systemPrompt,
|
|
808
|
-
estimatedTokens,
|
|
809
|
-
config.scoring
|
|
810
|
-
);
|
|
1020
|
+
const ruleResult = classifyByRules(prompt, systemPrompt, estimatedTokens, config.scoring);
|
|
811
1021
|
let tier;
|
|
812
1022
|
let confidence;
|
|
813
|
-
|
|
1023
|
+
const method = "rules";
|
|
814
1024
|
let reasoning = `score=${ruleResult.score} | ${ruleResult.signals.join(", ")}`;
|
|
815
1025
|
if (ruleResult.tier !== null) {
|
|
816
1026
|
tier = ruleResult.tier;
|
|
@@ -891,14 +1101,16 @@ var RequestDeduplicator = class {
|
|
|
891
1101
|
const entry = this.inflight.get(key);
|
|
892
1102
|
if (!entry) return void 0;
|
|
893
1103
|
const promise = new Promise((resolve) => {
|
|
894
|
-
entry.waiters.push(
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
1104
|
+
entry.waiters.push(
|
|
1105
|
+
new Promise((r) => {
|
|
1106
|
+
const orig = entry.resolve;
|
|
1107
|
+
entry.resolve = (result) => {
|
|
1108
|
+
orig(result);
|
|
1109
|
+
resolve(result);
|
|
1110
|
+
r(result);
|
|
1111
|
+
};
|
|
1112
|
+
})
|
|
1113
|
+
);
|
|
902
1114
|
});
|
|
903
1115
|
return promise;
|
|
904
1116
|
}
|
|
@@ -937,11 +1149,182 @@ var RequestDeduplicator = class {
|
|
|
937
1149
|
}
|
|
938
1150
|
};
|
|
939
1151
|
|
|
1152
|
+
// src/balance.ts
|
|
1153
|
+
import { createPublicClient, http, erc20Abi } from "viem";
|
|
1154
|
+
import { base } from "viem/chains";
|
|
1155
|
+
|
|
1156
|
+
// src/errors.ts
|
|
1157
|
+
var InsufficientFundsError = class extends Error {
|
|
1158
|
+
code = "INSUFFICIENT_FUNDS";
|
|
1159
|
+
currentBalanceUSD;
|
|
1160
|
+
requiredUSD;
|
|
1161
|
+
walletAddress;
|
|
1162
|
+
constructor(opts) {
|
|
1163
|
+
super(
|
|
1164
|
+
`Insufficient USDC balance. Current: ${opts.currentBalanceUSD}, Required: ${opts.requiredUSD}. Fund wallet: ${opts.walletAddress}`
|
|
1165
|
+
);
|
|
1166
|
+
this.name = "InsufficientFundsError";
|
|
1167
|
+
this.currentBalanceUSD = opts.currentBalanceUSD;
|
|
1168
|
+
this.requiredUSD = opts.requiredUSD;
|
|
1169
|
+
this.walletAddress = opts.walletAddress;
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1172
|
+
var EmptyWalletError = class extends Error {
|
|
1173
|
+
code = "EMPTY_WALLET";
|
|
1174
|
+
walletAddress;
|
|
1175
|
+
constructor(walletAddress) {
|
|
1176
|
+
super(`No USDC balance. Fund wallet to use ClawRouter: ${walletAddress}`);
|
|
1177
|
+
this.name = "EmptyWalletError";
|
|
1178
|
+
this.walletAddress = walletAddress;
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
function isInsufficientFundsError(error) {
|
|
1182
|
+
return error instanceof Error && error.code === "INSUFFICIENT_FUNDS";
|
|
1183
|
+
}
|
|
1184
|
+
function isEmptyWalletError(error) {
|
|
1185
|
+
return error instanceof Error && error.code === "EMPTY_WALLET";
|
|
1186
|
+
}
|
|
1187
|
+
function isBalanceError(error) {
|
|
1188
|
+
return isInsufficientFundsError(error) || isEmptyWalletError(error);
|
|
1189
|
+
}
|
|
1190
|
+
var RpcError = class extends Error {
|
|
1191
|
+
code = "RPC_ERROR";
|
|
1192
|
+
originalError;
|
|
1193
|
+
constructor(message, originalError) {
|
|
1194
|
+
super(`RPC error: ${message}. Check network connectivity.`);
|
|
1195
|
+
this.name = "RpcError";
|
|
1196
|
+
this.originalError = originalError;
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
function isRpcError(error) {
|
|
1200
|
+
return error instanceof Error && error.code === "RPC_ERROR";
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// src/balance.ts
|
|
1204
|
+
var USDC_BASE2 = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
1205
|
+
var CACHE_TTL_MS = 3e4;
|
|
1206
|
+
var BALANCE_THRESHOLDS = {
|
|
1207
|
+
/** Low balance warning threshold: $1.00 */
|
|
1208
|
+
LOW_BALANCE_MICROS: 1000000n,
|
|
1209
|
+
/** Effectively zero threshold: $0.0001 (covers dust/rounding) */
|
|
1210
|
+
ZERO_THRESHOLD: 100n
|
|
1211
|
+
};
|
|
1212
|
+
var BalanceMonitor = class {
|
|
1213
|
+
client;
|
|
1214
|
+
walletAddress;
|
|
1215
|
+
/** Cached balance (null = not yet fetched) */
|
|
1216
|
+
cachedBalance = null;
|
|
1217
|
+
/** Timestamp when cache was last updated */
|
|
1218
|
+
cachedAt = 0;
|
|
1219
|
+
constructor(walletAddress) {
|
|
1220
|
+
this.walletAddress = walletAddress;
|
|
1221
|
+
this.client = createPublicClient({
|
|
1222
|
+
chain: base,
|
|
1223
|
+
transport: http()
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Check current USDC balance.
|
|
1228
|
+
* Uses cache if valid, otherwise fetches from RPC.
|
|
1229
|
+
*/
|
|
1230
|
+
async checkBalance() {
|
|
1231
|
+
const now = Date.now();
|
|
1232
|
+
if (this.cachedBalance !== null && now - this.cachedAt < CACHE_TTL_MS) {
|
|
1233
|
+
return this.buildInfo(this.cachedBalance);
|
|
1234
|
+
}
|
|
1235
|
+
const balance = await this.fetchBalance();
|
|
1236
|
+
this.cachedBalance = balance;
|
|
1237
|
+
this.cachedAt = now;
|
|
1238
|
+
return this.buildInfo(balance);
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Check if balance is sufficient for an estimated cost.
|
|
1242
|
+
*
|
|
1243
|
+
* @param estimatedCostMicros - Estimated cost in USDC smallest unit (6 decimals)
|
|
1244
|
+
*/
|
|
1245
|
+
async checkSufficient(estimatedCostMicros) {
|
|
1246
|
+
const info = await this.checkBalance();
|
|
1247
|
+
if (info.balance >= estimatedCostMicros) {
|
|
1248
|
+
return { sufficient: true, info };
|
|
1249
|
+
}
|
|
1250
|
+
const shortfall = estimatedCostMicros - info.balance;
|
|
1251
|
+
return {
|
|
1252
|
+
sufficient: false,
|
|
1253
|
+
info,
|
|
1254
|
+
shortfall: this.formatUSDC(shortfall)
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* Optimistically deduct estimated cost from cached balance.
|
|
1259
|
+
* Call this after a successful payment to keep cache accurate.
|
|
1260
|
+
*
|
|
1261
|
+
* @param amountMicros - Amount to deduct in USDC smallest unit
|
|
1262
|
+
*/
|
|
1263
|
+
deductEstimated(amountMicros) {
|
|
1264
|
+
if (this.cachedBalance !== null && this.cachedBalance >= amountMicros) {
|
|
1265
|
+
this.cachedBalance -= amountMicros;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* Invalidate cache, forcing next checkBalance() to fetch from RPC.
|
|
1270
|
+
* Call this after a payment failure to get accurate balance.
|
|
1271
|
+
*/
|
|
1272
|
+
invalidate() {
|
|
1273
|
+
this.cachedBalance = null;
|
|
1274
|
+
this.cachedAt = 0;
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Force refresh balance from RPC (ignores cache).
|
|
1278
|
+
*/
|
|
1279
|
+
async refresh() {
|
|
1280
|
+
this.invalidate();
|
|
1281
|
+
return this.checkBalance();
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Format USDC amount (in micros) as "$X.XX".
|
|
1285
|
+
*/
|
|
1286
|
+
formatUSDC(amountMicros) {
|
|
1287
|
+
const dollars = Number(amountMicros) / 1e6;
|
|
1288
|
+
return `$${dollars.toFixed(2)}`;
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Get the wallet address being monitored.
|
|
1292
|
+
*/
|
|
1293
|
+
getWalletAddress() {
|
|
1294
|
+
return this.walletAddress;
|
|
1295
|
+
}
|
|
1296
|
+
/** Fetch balance from RPC */
|
|
1297
|
+
async fetchBalance() {
|
|
1298
|
+
try {
|
|
1299
|
+
const balance = await this.client.readContract({
|
|
1300
|
+
address: USDC_BASE2,
|
|
1301
|
+
abi: erc20Abi,
|
|
1302
|
+
functionName: "balanceOf",
|
|
1303
|
+
args: [this.walletAddress]
|
|
1304
|
+
});
|
|
1305
|
+
return balance;
|
|
1306
|
+
} catch (error) {
|
|
1307
|
+
throw new RpcError(error instanceof Error ? error.message : "Unknown error", error);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
/** Build BalanceInfo from raw balance */
|
|
1311
|
+
buildInfo(balance) {
|
|
1312
|
+
return {
|
|
1313
|
+
balance,
|
|
1314
|
+
balanceUSD: this.formatUSDC(balance),
|
|
1315
|
+
isLow: balance < BALANCE_THRESHOLDS.LOW_BALANCE_MICROS,
|
|
1316
|
+
isEmpty: balance < BALANCE_THRESHOLDS.ZERO_THRESHOLD,
|
|
1317
|
+
walletAddress: this.walletAddress
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
};
|
|
1321
|
+
|
|
940
1322
|
// src/proxy.ts
|
|
941
1323
|
var BLOCKRUN_API = "https://blockrun.ai/api";
|
|
942
1324
|
var AUTO_MODEL = "blockrun/auto";
|
|
943
1325
|
var USER_AGENT = "clawrouter/0.3.2";
|
|
944
1326
|
var HEARTBEAT_INTERVAL_MS = 2e3;
|
|
1327
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 18e4;
|
|
945
1328
|
function buildModelPricing() {
|
|
946
1329
|
const map = /* @__PURE__ */ new Map();
|
|
947
1330
|
for (const m of BLOCKRUN_MODELS) {
|
|
@@ -973,7 +1356,8 @@ function estimateAmount(modelId, bodyLength, maxTokens) {
|
|
|
973
1356
|
async function startProxy(options) {
|
|
974
1357
|
const apiBase = options.apiBase ?? BLOCKRUN_API;
|
|
975
1358
|
const account = privateKeyToAccount3(options.walletKey);
|
|
976
|
-
const { fetch: payFetch
|
|
1359
|
+
const { fetch: payFetch } = createPaymentFetch(options.walletKey);
|
|
1360
|
+
const balanceMonitor = new BalanceMonitor(account.address);
|
|
977
1361
|
const routingConfig = mergeRoutingConfig(options.routingConfig);
|
|
978
1362
|
const modelPricing = buildModelPricing();
|
|
979
1363
|
const routerOpts = {
|
|
@@ -982,9 +1366,25 @@ async function startProxy(options) {
|
|
|
982
1366
|
};
|
|
983
1367
|
const deduplicator = new RequestDeduplicator();
|
|
984
1368
|
const server = createServer(async (req, res) => {
|
|
985
|
-
if (req.url === "/health") {
|
|
1369
|
+
if (req.url === "/health" || req.url?.startsWith("/health?")) {
|
|
1370
|
+
const url = new URL(req.url, "http://localhost");
|
|
1371
|
+
const full = url.searchParams.get("full") === "true";
|
|
1372
|
+
const response = {
|
|
1373
|
+
status: "ok",
|
|
1374
|
+
wallet: account.address
|
|
1375
|
+
};
|
|
1376
|
+
if (full) {
|
|
1377
|
+
try {
|
|
1378
|
+
const balanceInfo = await balanceMonitor.checkBalance();
|
|
1379
|
+
response.balance = balanceInfo.balanceUSD;
|
|
1380
|
+
response.isLow = balanceInfo.isLow;
|
|
1381
|
+
response.isEmpty = balanceInfo.isEmpty;
|
|
1382
|
+
} catch {
|
|
1383
|
+
response.balanceError = "Could not fetch balance";
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
986
1386
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
987
|
-
res.end(JSON.stringify(
|
|
1387
|
+
res.end(JSON.stringify(response));
|
|
988
1388
|
return;
|
|
989
1389
|
}
|
|
990
1390
|
if (!req.url?.startsWith("/v1")) {
|
|
@@ -993,19 +1393,32 @@ async function startProxy(options) {
|
|
|
993
1393
|
return;
|
|
994
1394
|
}
|
|
995
1395
|
try {
|
|
996
|
-
await proxyRequest(
|
|
1396
|
+
await proxyRequest(
|
|
1397
|
+
req,
|
|
1398
|
+
res,
|
|
1399
|
+
apiBase,
|
|
1400
|
+
payFetch,
|
|
1401
|
+
options,
|
|
1402
|
+
routerOpts,
|
|
1403
|
+
deduplicator,
|
|
1404
|
+
balanceMonitor
|
|
1405
|
+
);
|
|
997
1406
|
} catch (err) {
|
|
998
1407
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
999
1408
|
options.onError?.(error);
|
|
1000
1409
|
if (!res.headersSent) {
|
|
1001
1410
|
res.writeHead(502, { "Content-Type": "application/json" });
|
|
1002
|
-
res.end(
|
|
1003
|
-
|
|
1004
|
-
|
|
1411
|
+
res.end(
|
|
1412
|
+
JSON.stringify({
|
|
1413
|
+
error: { message: `Proxy error: ${error.message}`, type: "proxy_error" }
|
|
1414
|
+
})
|
|
1415
|
+
);
|
|
1005
1416
|
} else if (!res.writableEnded) {
|
|
1006
|
-
res.write(
|
|
1417
|
+
res.write(
|
|
1418
|
+
`data: ${JSON.stringify({ error: { message: error.message, type: "proxy_error" } })}
|
|
1007
1419
|
|
|
1008
|
-
`
|
|
1420
|
+
`
|
|
1421
|
+
);
|
|
1009
1422
|
res.write("data: [DONE]\n\n");
|
|
1010
1423
|
res.end();
|
|
1011
1424
|
}
|
|
@@ -1022,6 +1435,8 @@ async function startProxy(options) {
|
|
|
1022
1435
|
resolve({
|
|
1023
1436
|
port,
|
|
1024
1437
|
baseUrl,
|
|
1438
|
+
walletAddress: account.address,
|
|
1439
|
+
balanceMonitor,
|
|
1025
1440
|
close: () => new Promise((res, rej) => {
|
|
1026
1441
|
server.close((err) => err ? rej(err) : res());
|
|
1027
1442
|
})
|
|
@@ -1029,7 +1444,7 @@ async function startProxy(options) {
|
|
|
1029
1444
|
});
|
|
1030
1445
|
});
|
|
1031
1446
|
}
|
|
1032
|
-
async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, deduplicator) {
|
|
1447
|
+
async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, deduplicator, balanceMonitor) {
|
|
1033
1448
|
const startTime = Date.now();
|
|
1034
1449
|
const upstreamUrl = `${apiBase}${req.url}`;
|
|
1035
1450
|
const bodyChunks = [];
|
|
@@ -1086,13 +1501,51 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
1086
1501
|
return;
|
|
1087
1502
|
}
|
|
1088
1503
|
deduplicator.markInflight(dedupKey);
|
|
1504
|
+
let estimatedCostMicros;
|
|
1505
|
+
if (modelId) {
|
|
1506
|
+
const estimated = estimateAmount(modelId, body.length, maxTokens);
|
|
1507
|
+
if (estimated) {
|
|
1508
|
+
estimatedCostMicros = BigInt(estimated);
|
|
1509
|
+
const sufficiency = await balanceMonitor.checkSufficient(estimatedCostMicros);
|
|
1510
|
+
if (sufficiency.info.isEmpty) {
|
|
1511
|
+
deduplicator.removeInflight(dedupKey);
|
|
1512
|
+
const error = new EmptyWalletError(sufficiency.info.walletAddress);
|
|
1513
|
+
options.onInsufficientFunds?.({
|
|
1514
|
+
balanceUSD: sufficiency.info.balanceUSD,
|
|
1515
|
+
requiredUSD: balanceMonitor.formatUSDC(estimatedCostMicros),
|
|
1516
|
+
walletAddress: sufficiency.info.walletAddress
|
|
1517
|
+
});
|
|
1518
|
+
throw error;
|
|
1519
|
+
}
|
|
1520
|
+
if (!sufficiency.sufficient) {
|
|
1521
|
+
deduplicator.removeInflight(dedupKey);
|
|
1522
|
+
const error = new InsufficientFundsError({
|
|
1523
|
+
currentBalanceUSD: sufficiency.info.balanceUSD,
|
|
1524
|
+
requiredUSD: balanceMonitor.formatUSDC(estimatedCostMicros),
|
|
1525
|
+
walletAddress: sufficiency.info.walletAddress
|
|
1526
|
+
});
|
|
1527
|
+
options.onInsufficientFunds?.({
|
|
1528
|
+
balanceUSD: sufficiency.info.balanceUSD,
|
|
1529
|
+
requiredUSD: balanceMonitor.formatUSDC(estimatedCostMicros),
|
|
1530
|
+
walletAddress: sufficiency.info.walletAddress
|
|
1531
|
+
});
|
|
1532
|
+
throw error;
|
|
1533
|
+
}
|
|
1534
|
+
if (sufficiency.info.isLow) {
|
|
1535
|
+
options.onLowBalance?.({
|
|
1536
|
+
balanceUSD: sufficiency.info.balanceUSD,
|
|
1537
|
+
walletAddress: sufficiency.info.walletAddress
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1089
1542
|
let heartbeatInterval;
|
|
1090
1543
|
let headersSentEarly = false;
|
|
1091
1544
|
if (isStreaming) {
|
|
1092
1545
|
res.writeHead(200, {
|
|
1093
1546
|
"content-type": "text/event-stream",
|
|
1094
1547
|
"cache-control": "no-cache",
|
|
1095
|
-
|
|
1548
|
+
connection: "keep-alive"
|
|
1096
1549
|
});
|
|
1097
1550
|
headersSentEarly = true;
|
|
1098
1551
|
res.write(": heartbeat\n\n");
|
|
@@ -1104,7 +1557,8 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
1104
1557
|
}
|
|
1105
1558
|
const headers = {};
|
|
1106
1559
|
for (const [key, value] of Object.entries(req.headers)) {
|
|
1107
|
-
if (key === "host" || key === "connection" || key === "transfer-encoding" || key === "content-length")
|
|
1560
|
+
if (key === "host" || key === "connection" || key === "transfer-encoding" || key === "content-length")
|
|
1561
|
+
continue;
|
|
1108
1562
|
if (typeof value === "string") {
|
|
1109
1563
|
headers[key] = value;
|
|
1110
1564
|
}
|
|
@@ -1114,18 +1568,34 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
1114
1568
|
}
|
|
1115
1569
|
headers["user-agent"] = USER_AGENT;
|
|
1116
1570
|
let preAuth;
|
|
1117
|
-
if (
|
|
1118
|
-
|
|
1119
|
-
if (estimated) {
|
|
1120
|
-
preAuth = { estimatedAmount: estimated };
|
|
1121
|
-
}
|
|
1571
|
+
if (estimatedCostMicros !== void 0) {
|
|
1572
|
+
preAuth = { estimatedAmount: estimatedCostMicros.toString() };
|
|
1122
1573
|
}
|
|
1574
|
+
let completed = false;
|
|
1575
|
+
res.on("close", () => {
|
|
1576
|
+
if (heartbeatInterval) {
|
|
1577
|
+
clearInterval(heartbeatInterval);
|
|
1578
|
+
heartbeatInterval = void 0;
|
|
1579
|
+
}
|
|
1580
|
+
if (!completed) {
|
|
1581
|
+
deduplicator.removeInflight(dedupKey);
|
|
1582
|
+
}
|
|
1583
|
+
});
|
|
1584
|
+
const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
1585
|
+
const controller = new AbortController();
|
|
1586
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
1123
1587
|
try {
|
|
1124
|
-
const upstream = await payFetch(
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1588
|
+
const upstream = await payFetch(
|
|
1589
|
+
upstreamUrl,
|
|
1590
|
+
{
|
|
1591
|
+
method: req.method ?? "POST",
|
|
1592
|
+
headers,
|
|
1593
|
+
body: body.length > 0 ? body : void 0,
|
|
1594
|
+
signal: controller.signal
|
|
1595
|
+
},
|
|
1596
|
+
preAuth
|
|
1597
|
+
);
|
|
1598
|
+
clearTimeout(timeoutId);
|
|
1129
1599
|
if (heartbeatInterval) {
|
|
1130
1600
|
clearInterval(heartbeatInterval);
|
|
1131
1601
|
heartbeatInterval = void 0;
|
|
@@ -1198,11 +1668,21 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
1198
1668
|
completedAt: Date.now()
|
|
1199
1669
|
});
|
|
1200
1670
|
}
|
|
1671
|
+
if (estimatedCostMicros !== void 0) {
|
|
1672
|
+
balanceMonitor.deductEstimated(estimatedCostMicros);
|
|
1673
|
+
}
|
|
1674
|
+
completed = true;
|
|
1201
1675
|
} catch (err) {
|
|
1676
|
+
clearTimeout(timeoutId);
|
|
1202
1677
|
if (heartbeatInterval) {
|
|
1203
1678
|
clearInterval(heartbeatInterval);
|
|
1679
|
+
heartbeatInterval = void 0;
|
|
1204
1680
|
}
|
|
1205
1681
|
deduplicator.removeInflight(dedupKey);
|
|
1682
|
+
balanceMonitor.invalidate();
|
|
1683
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
1684
|
+
throw new Error(`Request timed out after ${timeoutMs}ms`);
|
|
1685
|
+
}
|
|
1206
1686
|
throw err;
|
|
1207
1687
|
}
|
|
1208
1688
|
if (routingDecision) {
|
|
@@ -1217,6 +1697,62 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
1217
1697
|
}
|
|
1218
1698
|
}
|
|
1219
1699
|
|
|
1700
|
+
// src/retry.ts
|
|
1701
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
1702
|
+
maxRetries: 2,
|
|
1703
|
+
baseDelayMs: 500,
|
|
1704
|
+
retryableCodes: [429, 502, 503, 504]
|
|
1705
|
+
};
|
|
1706
|
+
function sleep(ms) {
|
|
1707
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1708
|
+
}
|
|
1709
|
+
async function fetchWithRetry(fetchFn, url, init, config) {
|
|
1710
|
+
const cfg = {
|
|
1711
|
+
...DEFAULT_RETRY_CONFIG,
|
|
1712
|
+
...config
|
|
1713
|
+
};
|
|
1714
|
+
let lastError;
|
|
1715
|
+
let lastResponse;
|
|
1716
|
+
for (let attempt = 0; attempt <= cfg.maxRetries; attempt++) {
|
|
1717
|
+
try {
|
|
1718
|
+
const response = await fetchFn(url, init);
|
|
1719
|
+
if (!cfg.retryableCodes.includes(response.status)) {
|
|
1720
|
+
return response;
|
|
1721
|
+
}
|
|
1722
|
+
lastResponse = response;
|
|
1723
|
+
const retryAfter = response.headers.get("retry-after");
|
|
1724
|
+
let delay;
|
|
1725
|
+
if (retryAfter) {
|
|
1726
|
+
const seconds = parseInt(retryAfter, 10);
|
|
1727
|
+
delay = isNaN(seconds) ? cfg.baseDelayMs * Math.pow(2, attempt) : seconds * 1e3;
|
|
1728
|
+
} else {
|
|
1729
|
+
delay = cfg.baseDelayMs * Math.pow(2, attempt);
|
|
1730
|
+
}
|
|
1731
|
+
if (attempt < cfg.maxRetries) {
|
|
1732
|
+
await sleep(delay);
|
|
1733
|
+
}
|
|
1734
|
+
} catch (err) {
|
|
1735
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
1736
|
+
if (attempt < cfg.maxRetries) {
|
|
1737
|
+
const delay = cfg.baseDelayMs * Math.pow(2, attempt);
|
|
1738
|
+
await sleep(delay);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
if (lastResponse) {
|
|
1743
|
+
return lastResponse;
|
|
1744
|
+
}
|
|
1745
|
+
throw lastError ?? new Error("Max retries exceeded");
|
|
1746
|
+
}
|
|
1747
|
+
function isRetryable(errorOrResponse, config) {
|
|
1748
|
+
const retryableCodes = config?.retryableCodes ?? DEFAULT_RETRY_CONFIG.retryableCodes;
|
|
1749
|
+
if (errorOrResponse instanceof Response) {
|
|
1750
|
+
return retryableCodes.includes(errorOrResponse.status);
|
|
1751
|
+
}
|
|
1752
|
+
const message = errorOrResponse.message.toLowerCase();
|
|
1753
|
+
return message.includes("network") || message.includes("timeout") || message.includes("econnreset") || message.includes("econnrefused") || message.includes("socket hang up");
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1220
1756
|
// src/index.ts
|
|
1221
1757
|
async function startProxyInBackground(api) {
|
|
1222
1758
|
const { key: walletKey, address, source } = await resolveOrGenerateWalletKey();
|
|
@@ -1228,6 +1764,23 @@ async function startProxyInBackground(api) {
|
|
|
1228
1764
|
} else {
|
|
1229
1765
|
api.logger.info(`Using wallet from BLOCKRUN_WALLET_KEY: ${address}`);
|
|
1230
1766
|
}
|
|
1767
|
+
const startupMonitor = new BalanceMonitor(address);
|
|
1768
|
+
try {
|
|
1769
|
+
const startupBalance = await startupMonitor.checkBalance();
|
|
1770
|
+
if (startupBalance.isEmpty) {
|
|
1771
|
+
api.logger.warn(`[!] No USDC balance. Fund wallet to use ClawRouter: ${address}`);
|
|
1772
|
+
} else if (startupBalance.isLow) {
|
|
1773
|
+
api.logger.warn(
|
|
1774
|
+
`[!] Low balance: ${startupBalance.balanceUSD} remaining. Fund wallet: ${address}`
|
|
1775
|
+
);
|
|
1776
|
+
} else {
|
|
1777
|
+
api.logger.info(`Wallet balance: ${startupBalance.balanceUSD}`);
|
|
1778
|
+
}
|
|
1779
|
+
} catch (err) {
|
|
1780
|
+
api.logger.warn(
|
|
1781
|
+
`Could not check wallet balance: ${err instanceof Error ? err.message : String(err)}`
|
|
1782
|
+
);
|
|
1783
|
+
}
|
|
1231
1784
|
const routingConfig = api.pluginConfig?.routing;
|
|
1232
1785
|
const proxy = await startProxy({
|
|
1233
1786
|
walletKey,
|
|
@@ -1242,6 +1795,14 @@ async function startProxyInBackground(api) {
|
|
|
1242
1795
|
const cost = decision.costEstimate.toFixed(4);
|
|
1243
1796
|
const saved = (decision.savings * 100).toFixed(0);
|
|
1244
1797
|
api.logger.info(`${decision.model} $${cost} (saved ${saved}%)`);
|
|
1798
|
+
},
|
|
1799
|
+
onLowBalance: (info) => {
|
|
1800
|
+
api.logger.warn(`[!] Low balance: ${info.balanceUSD}. Fund wallet: ${info.walletAddress}`);
|
|
1801
|
+
},
|
|
1802
|
+
onInsufficientFunds: (info) => {
|
|
1803
|
+
api.logger.error(
|
|
1804
|
+
`[!] Insufficient funds. Balance: ${info.balanceUSD}, Needed: ${info.requiredUSD}. Fund wallet: ${info.walletAddress}`
|
|
1805
|
+
);
|
|
1245
1806
|
}
|
|
1246
1807
|
});
|
|
1247
1808
|
setActiveProxy(proxy);
|
|
@@ -1264,15 +1825,27 @@ var plugin = {
|
|
|
1264
1825
|
};
|
|
1265
1826
|
var index_default = plugin;
|
|
1266
1827
|
export {
|
|
1828
|
+
BALANCE_THRESHOLDS,
|
|
1267
1829
|
BLOCKRUN_MODELS,
|
|
1830
|
+
BalanceMonitor,
|
|
1831
|
+
DEFAULT_RETRY_CONFIG,
|
|
1268
1832
|
DEFAULT_ROUTING_CONFIG,
|
|
1833
|
+
EmptyWalletError,
|
|
1834
|
+
InsufficientFundsError,
|
|
1269
1835
|
OPENCLAW_MODELS,
|
|
1270
1836
|
PaymentCache,
|
|
1271
1837
|
RequestDeduplicator,
|
|
1838
|
+
RpcError,
|
|
1272
1839
|
blockrunProvider,
|
|
1273
1840
|
buildProviderModels,
|
|
1274
1841
|
createPaymentFetch,
|
|
1275
1842
|
index_default as default,
|
|
1843
|
+
fetchWithRetry,
|
|
1844
|
+
isBalanceError,
|
|
1845
|
+
isEmptyWalletError,
|
|
1846
|
+
isInsufficientFundsError,
|
|
1847
|
+
isRetryable,
|
|
1848
|
+
isRpcError,
|
|
1276
1849
|
logUsage,
|
|
1277
1850
|
route,
|
|
1278
1851
|
startProxy
|