@routstr/sdk 0.2.6 → 0.2.7

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.mjs CHANGED
@@ -466,7 +466,7 @@ var ModelManager = class _ModelManager {
466
466
  }
467
467
  }
468
468
  const DEFAULT_RELAYS = [
469
- "wss://relay.primal.net",
469
+ "wss://relay.damus.io",
470
470
  "wss://nos.lol",
471
471
  "wss://relay.routstr.com"
472
472
  ];
@@ -1081,8 +1081,9 @@ var CashuSpender = class {
1081
1081
  return null;
1082
1082
  }
1083
1083
  /**
1084
- * Refund all xcashu tokens from storage and increment tryCounts on failure.
1085
- * Reuses receiveToken from BalanceManager/CashuSpender for receiving refunds.
1084
+ * Refund all xcashu tokens from storage by calling the provider's refund endpoint.
1085
+ * The xcashu token acts as an API key to claim the refund, and the response contains
1086
+ * the actual refunded Cashu token which is then received into the wallet.
1086
1087
  * @param mintUrl - The mint URL for receiving tokens
1087
1088
  * @param excludeBaseUrls - Base URLs to exclude from refund (optional)
1088
1089
  * @returns Results for each xcashu token refund attempt
@@ -1095,7 +1096,20 @@ var CashuSpender = class {
1095
1096
  if (excludedUrls.has(baseUrl)) continue;
1096
1097
  for (const xcashuToken of tokens) {
1097
1098
  try {
1098
- const receiveResult = await this.receiveToken(xcashuToken.token);
1099
+ if (!this.balanceManager) {
1100
+ throw new Error("BalanceManager not available for xcashu refund");
1101
+ }
1102
+ const fetchResult = await this.balanceManager.fetchRefundToken(
1103
+ baseUrl,
1104
+ xcashuToken.token,
1105
+ true
1106
+ );
1107
+ if (!fetchResult.success || !fetchResult.token) {
1108
+ throw new Error(
1109
+ fetchResult.error || "Failed to fetch refund token from provider"
1110
+ );
1111
+ }
1112
+ const receiveResult = await this.receiveToken(fetchResult.token);
1099
1113
  if (receiveResult.success) {
1100
1114
  this.storageAdapter.removeXcashuToken(baseUrl, xcashuToken.token);
1101
1115
  results.push({
@@ -1110,7 +1124,10 @@ var CashuSpender = class {
1110
1124
  } else {
1111
1125
  const currentTryCount = xcashuToken.tryCount ?? 0;
1112
1126
  const newTryCount = currentTryCount + 1;
1113
- this.storageAdapter.updateXcashuTokenTryCount(xcashuToken.token, newTryCount);
1127
+ this.storageAdapter.updateXcashuTokenTryCount(
1128
+ xcashuToken.token,
1129
+ newTryCount
1130
+ );
1114
1131
  results.push({
1115
1132
  baseUrl,
1116
1133
  token: xcashuToken.token,
@@ -1119,13 +1136,16 @@ var CashuSpender = class {
1119
1136
  });
1120
1137
  this._log(
1121
1138
  "DEBUG",
1122
- `[CashuSpender] refundXcashuTokens: Failed to refund xcashu token for ${baseUrl}, incremented tryCount to ${newTryCount}`
1139
+ `[CashuSpender] refundXcashuTokens: Failed to receive refund token for ${baseUrl}, incremented tryCount to ${newTryCount}`
1123
1140
  );
1124
1141
  }
1125
1142
  } catch (error) {
1126
1143
  const currentTryCount = xcashuToken.tryCount ?? 0;
1127
1144
  const newTryCount = currentTryCount + 1;
1128
- this.storageAdapter.updateXcashuTokenTryCount(xcashuToken.token, newTryCount);
1145
+ this.storageAdapter.updateXcashuTokenTryCount(
1146
+ xcashuToken.token,
1147
+ newTryCount
1148
+ );
1129
1149
  const errorMessage = error instanceof Error ? error.message : String(error);
1130
1150
  results.push({
1131
1151
  baseUrl,
@@ -1162,7 +1182,10 @@ var CashuSpender = class {
1162
1182
  if (refundResult.success) {
1163
1183
  this.storageAdapter.removeApiKey(apiKeyEntry.baseUrl);
1164
1184
  } else {
1165
- this.storageAdapter.updateApiKeyBalance(apiKeyEntry.baseUrl, apiKeyEntry.amount);
1185
+ this.storageAdapter.updateApiKeyBalance(
1186
+ apiKeyEntry.baseUrl,
1187
+ apiKeyEntry.amount
1188
+ );
1166
1189
  }
1167
1190
  results.push({
1168
1191
  baseUrl: apiKeyEntry.baseUrl,
@@ -1300,7 +1323,7 @@ var BalanceManager = class {
1300
1323
  }
1301
1324
  let fetchResult;
1302
1325
  try {
1303
- fetchResult = await this._fetchRefundTokenWithApiKey(baseUrl, apiKey);
1326
+ fetchResult = await this.fetchRefundToken(baseUrl, apiKey);
1304
1327
  if (!fetchResult.success) {
1305
1328
  return {
1306
1329
  success: false,
@@ -1336,9 +1359,9 @@ var BalanceManager = class {
1336
1359
  }
1337
1360
  }
1338
1361
  /**
1339
- * Fetch refund token from provider API using API key authentication
1362
+ * Fetch refund token from provider API using API key (or xcashu token) authentication
1340
1363
  */
1341
- async _fetchRefundTokenWithApiKey(baseUrl, apiKey) {
1364
+ async fetchRefundToken(baseUrl, apiKeyOrToken, xCashu = false) {
1342
1365
  if (!baseUrl) {
1343
1366
  return {
1344
1367
  success: false,
@@ -1352,12 +1375,17 @@ var BalanceManager = class {
1352
1375
  controller.abort();
1353
1376
  }, 6e4);
1354
1377
  try {
1378
+ const headers = {
1379
+ "Content-Type": "application/json"
1380
+ };
1381
+ if (xCashu) {
1382
+ headers["X-Cashu"] = apiKeyOrToken;
1383
+ } else {
1384
+ headers["Authorization"] = `Bearer ${apiKeyOrToken}`;
1385
+ }
1355
1386
  const response = await fetch(url, {
1356
1387
  method: "POST",
1357
- headers: {
1358
- Authorization: `Bearer ${apiKey}`,
1359
- "Content-Type": "application/json"
1360
- },
1388
+ headers,
1361
1389
  signal: controller.signal
1362
1390
  });
1363
1391
  clearTimeout(timeoutId);
@@ -1378,10 +1406,7 @@ var BalanceManager = class {
1378
1406
  };
1379
1407
  } catch (error) {
1380
1408
  clearTimeout(timeoutId);
1381
- console.error(
1382
- "[BalanceManager._fetchRefundTokenWithApiKey] Fetch error",
1383
- error
1384
- );
1409
+ console.error("[BalanceManager.fetchRefundToken] Fetch error", error);
1385
1410
  if (error instanceof Error) {
1386
1411
  if (error.name === "AbortError") {
1387
1412
  return {
@@ -1428,11 +1453,7 @@ var BalanceManager = class {
1428
1453
  };
1429
1454
  }
1430
1455
  cashuToken = tokenResult.token;
1431
- const topUpResult = await this._postTopUp(
1432
- baseUrl,
1433
- apiKey,
1434
- cashuToken
1435
- );
1456
+ const topUpResult = await this._postTopUp(baseUrl, apiKey, cashuToken);
1436
1457
  requestId = topUpResult.requestId;
1437
1458
  console.log(topUpResult);
1438
1459
  if (!topUpResult.success) {
@@ -1798,7 +1819,7 @@ var BalanceManager = class {
1798
1819
  console.log(response.status);
1799
1820
  const data = await response.json();
1800
1821
  console.log("FAILED ", data);
1801
- const isInvalidApiKey = response.status === 401 && data?.code === "invalid_api_key" && data?.message?.includes("proofs already spent");
1822
+ const isInvalidApiKey = response.status === 401 && data?.detail?.error?.code === "invalid_api_key" && data?.detail?.error?.message?.includes("proofs already spent");
1802
1823
  return {
1803
1824
  amount: -1,
1804
1825
  reserved: data.reserved ?? 0,
@@ -2296,8 +2317,13 @@ function isInsecureHttpUrl(url) {
2296
2317
  return url.startsWith("http://");
2297
2318
  }
2298
2319
  var ProviderManager = class _ProviderManager {
2299
- constructor(providerRegistry) {
2320
+ constructor(providerRegistry, store) {
2300
2321
  this.providerRegistry = providerRegistry;
2322
+ this.instanceId = `pm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
2323
+ if (store) {
2324
+ this.store = store;
2325
+ this.hydrateFromStore();
2326
+ }
2301
2327
  }
2302
2328
  failedProviders = /* @__PURE__ */ new Set();
2303
2329
  /** Track when each provider last failed (provider URL -> timestamp) */
@@ -2306,14 +2332,57 @@ var ProviderManager = class _ProviderManager {
2306
2332
  providersOnCoolDown = [];
2307
2333
  /** Cooldown duration in milliseconds (5 minutes) */
2308
2334
  static COOLDOWN_DURATION_MS = 5 * 60 * 1e3;
2335
+ /** Optional persistent store for failure tracking */
2336
+ store = null;
2337
+ /** Instance ID for debugging */
2338
+ instanceId;
2339
+ /**
2340
+ * Hydrate in-memory state from persistent store
2341
+ */
2342
+ hydrateFromStore() {
2343
+ if (!this.store) return;
2344
+ const state = this.store.getState();
2345
+ this.failedProviders = new Set(state.failedProviders);
2346
+ this.lastFailed = new Map(Object.entries(state.lastFailed));
2347
+ const now = Date.now();
2348
+ this.providersOnCoolDown = state.providersOnCooldown.filter(
2349
+ (entry) => now - entry.timestamp < _ProviderManager.COOLDOWN_DURATION_MS
2350
+ ).map((entry) => [entry.baseUrl, entry.timestamp]);
2351
+ console.log(`[ProviderManager:${this.instanceId}] Hydrated from store:`);
2352
+ console.log(` failedProviders: ${this.failedProviders.size}`);
2353
+ console.log(` lastFailed: ${this.lastFailed.size}`);
2354
+ console.log(` providersOnCooldown: ${this.providersOnCoolDown.length}`);
2355
+ }
2356
+ /**
2357
+ * Get instance ID for debugging
2358
+ */
2359
+ getInstanceId() {
2360
+ return this.instanceId;
2361
+ }
2309
2362
  /**
2310
2363
  * Clean up expired cooldown entries
2311
2364
  */
2312
2365
  cleanupExpiredCooldowns() {
2313
2366
  const now = Date.now();
2367
+ const before = this.providersOnCoolDown.length;
2314
2368
  this.providersOnCoolDown = this.providersOnCoolDown.filter(
2315
- ([, timestamp]) => now - timestamp < _ProviderManager.COOLDOWN_DURATION_MS
2369
+ ([url, timestamp]) => {
2370
+ const age = now - timestamp;
2371
+ const isExpired = age >= _ProviderManager.COOLDOWN_DURATION_MS;
2372
+ if (isExpired) {
2373
+ console.log(
2374
+ `[cleanupExpiredCooldowns:${this.instanceId}] Removing expired cooldown for ${url} (age: ${age}ms, cooldown: ${_ProviderManager.COOLDOWN_DURATION_MS}ms)`
2375
+ );
2376
+ }
2377
+ return !isExpired;
2378
+ }
2316
2379
  );
2380
+ const after = this.providersOnCoolDown.length;
2381
+ if (before !== after) {
2382
+ console.log(
2383
+ `[cleanupExpiredCooldowns:${this.instanceId}] Cleaned up ${before - after} expired cooldown(s), ${after} remaining`
2384
+ );
2385
+ }
2317
2386
  }
2318
2387
  /**
2319
2388
  * Get the cooldown duration in milliseconds
@@ -2326,7 +2395,8 @@ var ProviderManager = class _ProviderManager {
2326
2395
  */
2327
2396
  isOnCooldown(baseUrl) {
2328
2397
  this.cleanupExpiredCooldowns();
2329
- return this.providersOnCoolDown.some(([url]) => url === baseUrl);
2398
+ const result = this.providersOnCoolDown.some(([url]) => url === baseUrl);
2399
+ return result;
2330
2400
  }
2331
2401
  /**
2332
2402
  * Get all providers currently on cooldown
@@ -2340,6 +2410,9 @@ var ProviderManager = class _ProviderManager {
2340
2410
  */
2341
2411
  resetFailedProviders() {
2342
2412
  this.failedProviders.clear();
2413
+ if (this.store) {
2414
+ this.store.getState().setFailedProviders([]);
2415
+ }
2343
2416
  }
2344
2417
  /**
2345
2418
  * Get the last failed timestamp for a provider
@@ -2360,13 +2433,62 @@ var ProviderManager = class _ProviderManager {
2360
2433
  markFailed(baseUrl) {
2361
2434
  const now = Date.now();
2362
2435
  const lastFailure = this.lastFailed.get(baseUrl);
2436
+ console.log(`[markFailed:${this.instanceId}] baseUrl: ${baseUrl}`);
2437
+ console.log(
2438
+ `[markFailed:${this.instanceId}] lastFailure from map: ${lastFailure}`
2439
+ );
2440
+ console.log(
2441
+ `[markFailed:${this.instanceId}] current timestamp (now): ${now}`
2442
+ );
2443
+ console.log(
2444
+ `[markFailed:${this.instanceId}] COOLDOWN_DURATION_MS: ${_ProviderManager.COOLDOWN_DURATION_MS}`
2445
+ );
2446
+ if (lastFailure !== void 0) {
2447
+ const timeSinceLastFailure = now - lastFailure;
2448
+ console.log(
2449
+ `[markFailed:${this.instanceId}] timeSinceLastFailure: ${timeSinceLastFailure}ms`
2450
+ );
2451
+ console.log(
2452
+ `[markFailed:${this.instanceId}] isWithinCooldownWindow: ${timeSinceLastFailure < _ProviderManager.COOLDOWN_DURATION_MS}`
2453
+ );
2454
+ }
2363
2455
  this.lastFailed.set(baseUrl, now);
2364
2456
  this.failedProviders.add(baseUrl);
2457
+ if (this.store) {
2458
+ this.store.getState().setLastFailedTimestamp(baseUrl, now);
2459
+ this.store.getState().addFailedProvider(baseUrl);
2460
+ }
2461
+ console.log(
2462
+ `[markFailed:${this.instanceId}] Updated lastFailed map for ${baseUrl} to ${now}`
2463
+ );
2464
+ console.log(
2465
+ `[markFailed:${this.instanceId}] failedProviders set size: ${this.failedProviders.size}`
2466
+ );
2365
2467
  if (lastFailure !== void 0 && now - lastFailure < _ProviderManager.COOLDOWN_DURATION_MS) {
2468
+ console.log(
2469
+ `[markFailed:${this.instanceId}] Second failure detected within cooldown window for ${baseUrl}`
2470
+ );
2366
2471
  if (!this.isOnCooldown(baseUrl)) {
2367
2472
  this.providersOnCoolDown.push([baseUrl, now]);
2473
+ if (this.store) {
2474
+ this.store.getState().addProviderOnCooldown(baseUrl, now);
2475
+ }
2368
2476
  console.log(
2369
- `Provider ${baseUrl} added to cooldown after second failure within 5 minutes`
2477
+ `[markFailed:${this.instanceId}] Provider ${baseUrl} added to cooldown after second failure within 5 minutes`
2478
+ );
2479
+ } else {
2480
+ console.log(
2481
+ `[markFailed:${this.instanceId}] Provider ${baseUrl} is already on cooldown`
2482
+ );
2483
+ }
2484
+ } else {
2485
+ if (lastFailure === void 0) {
2486
+ console.log(
2487
+ `[markFailed:${this.instanceId}] First failure for ${baseUrl} - not adding to cooldown yet`
2488
+ );
2489
+ } else {
2490
+ console.log(
2491
+ `[markFailed:${this.instanceId}] Failure outside cooldown window for ${baseUrl} (timeSinceLastFailure: ${now - lastFailure}ms)`
2370
2492
  );
2371
2493
  }
2372
2494
  }
@@ -2378,18 +2500,27 @@ var ProviderManager = class _ProviderManager {
2378
2500
  this.providersOnCoolDown = this.providersOnCoolDown.filter(
2379
2501
  ([url]) => url !== baseUrl
2380
2502
  );
2503
+ if (this.store) {
2504
+ this.store.getState().removeProviderFromCooldown(baseUrl);
2505
+ }
2381
2506
  }
2382
2507
  /**
2383
2508
  * Clear all cooldown tracking
2384
2509
  */
2385
2510
  clearCooldowns() {
2386
2511
  this.providersOnCoolDown = [];
2512
+ if (this.store) {
2513
+ this.store.getState().clearProvidersOnCooldown();
2514
+ }
2387
2515
  }
2388
2516
  /**
2389
2517
  * Clear all failure tracking (lastFailed timestamps)
2390
2518
  */
2391
2519
  clearFailureHistory() {
2392
2520
  this.lastFailed.clear();
2521
+ if (this.store) {
2522
+ this.store.getState().setLastFailed({});
2523
+ }
2393
2524
  }
2394
2525
  /**
2395
2526
  * Check if a provider has failed
@@ -2958,7 +3089,10 @@ var SDK_STORAGE_KEYS = {
2958
3089
  LAST_ROUTSTR21_MODELS_UPDATE: "lastRoutstr21ModelsUpdate",
2959
3090
  CACHED_RECEIVE_TOKENS: "cached_receive_tokens",
2960
3091
  USAGE_TRACKING: "usage_tracking",
2961
- CLIENT_IDS: "client_ids"
3092
+ CLIENT_IDS: "client_ids",
3093
+ FAILED_PROVIDERS: "failed_providers",
3094
+ LAST_FAILED: "last_failed",
3095
+ PROVIDERS_ON_COOLDOWN: "providers_on_cooldown"
2962
3096
  };
2963
3097
 
2964
3098
  // storage/usageTracking/indexedDB.ts
@@ -3605,6 +3739,9 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
3605
3739
  lastRoutstr21ModelsUpdate: null,
3606
3740
  cachedReceiveTokens: [],
3607
3741
  clientIds: [],
3742
+ failedProviders: [],
3743
+ lastFailed: {},
3744
+ providersOnCooldown: [],
3608
3745
  setModelsFromAllProviders: (value) => {
3609
3746
  const normalized = {};
3610
3747
  for (const [baseUrl, models] of Object.entries(value)) {
@@ -3744,6 +3881,71 @@ var createEmptyStore = (driver) => createStore((set, get) => ({
3744
3881
  void driver.setItem(SDK_STORAGE_KEYS.CLIENT_IDS, normalized);
3745
3882
  return { clientIds: normalized };
3746
3883
  });
3884
+ },
3885
+ // ========== Failure Tracking ==========
3886
+ setFailedProviders: (value) => {
3887
+ const normalized = value.map((url) => normalizeBaseUrl5(url));
3888
+ void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, normalized);
3889
+ set({ failedProviders: normalized });
3890
+ },
3891
+ addFailedProvider: (baseUrl) => {
3892
+ const normalized = normalizeBaseUrl5(baseUrl);
3893
+ const current = get().failedProviders;
3894
+ if (!current.includes(normalized)) {
3895
+ const updated = [...current, normalized];
3896
+ void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, updated);
3897
+ set({ failedProviders: updated });
3898
+ }
3899
+ },
3900
+ removeFailedProvider: (baseUrl) => {
3901
+ const normalized = normalizeBaseUrl5(baseUrl);
3902
+ const current = get().failedProviders;
3903
+ const updated = current.filter((url) => url !== normalized);
3904
+ void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, updated);
3905
+ set({ failedProviders: updated });
3906
+ },
3907
+ setLastFailed: (value) => {
3908
+ const normalized = {};
3909
+ for (const [baseUrl, timestamp] of Object.entries(value)) {
3910
+ normalized[normalizeBaseUrl5(baseUrl)] = timestamp;
3911
+ }
3912
+ void driver.setItem(SDK_STORAGE_KEYS.LAST_FAILED, normalized);
3913
+ set({ lastFailed: normalized });
3914
+ },
3915
+ setLastFailedTimestamp: (baseUrl, timestamp) => {
3916
+ const normalized = normalizeBaseUrl5(baseUrl);
3917
+ const current = get().lastFailed;
3918
+ const updated = { ...current, [normalized]: timestamp };
3919
+ void driver.setItem(SDK_STORAGE_KEYS.LAST_FAILED, updated);
3920
+ set({ lastFailed: updated });
3921
+ },
3922
+ setProvidersOnCooldown: (value) => {
3923
+ const normalized = value.map((entry) => ({
3924
+ baseUrl: normalizeBaseUrl5(entry.baseUrl),
3925
+ timestamp: entry.timestamp
3926
+ }));
3927
+ void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, normalized);
3928
+ set({ providersOnCooldown: normalized });
3929
+ },
3930
+ addProviderOnCooldown: (baseUrl, timestamp) => {
3931
+ const normalized = normalizeBaseUrl5(baseUrl);
3932
+ const current = get().providersOnCooldown;
3933
+ if (!current.some((entry) => entry.baseUrl === normalized)) {
3934
+ const updated = [...current, { baseUrl: normalized, timestamp }];
3935
+ void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, updated);
3936
+ set({ providersOnCooldown: updated });
3937
+ }
3938
+ },
3939
+ removeProviderFromCooldown: (baseUrl) => {
3940
+ const normalized = normalizeBaseUrl5(baseUrl);
3941
+ const current = get().providersOnCooldown;
3942
+ const updated = current.filter((entry) => entry.baseUrl !== normalized);
3943
+ void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, updated);
3944
+ set({ providersOnCooldown: updated });
3945
+ },
3946
+ clearProvidersOnCooldown: () => {
3947
+ void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, []);
3948
+ set({ providersOnCooldown: [] });
3747
3949
  }
3748
3950
  }));
3749
3951
  var hydrateStoreFromDriver = async (store, driver) => {
@@ -3762,7 +3964,10 @@ var hydrateStoreFromDriver = async (store, driver) => {
3762
3964
  rawRoutstr21Models,
3763
3965
  rawLastRoutstr21ModelsUpdate,
3764
3966
  rawCachedReceiveTokens,
3765
- rawClientIds
3967
+ rawClientIds,
3968
+ rawFailedProviders,
3969
+ rawLastFailed,
3970
+ rawProvidersOnCooldown
3766
3971
  ] = await Promise.all([
3767
3972
  driver.getItem(
3768
3973
  SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
@@ -3793,7 +3998,10 @@ var hydrateStoreFromDriver = async (store, driver) => {
3793
3998
  null
3794
3999
  ),
3795
4000
  driver.getItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, []),
3796
- driver.getItem(SDK_STORAGE_KEYS.CLIENT_IDS, [])
4001
+ driver.getItem(SDK_STORAGE_KEYS.CLIENT_IDS, []),
4002
+ driver.getItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, []),
4003
+ driver.getItem(SDK_STORAGE_KEYS.LAST_FAILED, {}),
4004
+ driver.getItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, [])
3797
4005
  ]);
3798
4006
  const modelsFromAllProviders = Object.fromEntries(
3799
4007
  Object.entries(rawModels).map(([baseUrl, models]) => [
@@ -3861,6 +4069,17 @@ var hydrateStoreFromDriver = async (store, driver) => {
3861
4069
  createdAt: entry.createdAt ?? Date.now(),
3862
4070
  lastUsed: entry.lastUsed ?? null
3863
4071
  }));
4072
+ const failedProviders = rawFailedProviders.map((url) => normalizeBaseUrl5(url));
4073
+ const lastFailed = Object.fromEntries(
4074
+ Object.entries(rawLastFailed).map(([baseUrl, timestamp]) => [
4075
+ normalizeBaseUrl5(baseUrl),
4076
+ timestamp
4077
+ ])
4078
+ );
4079
+ const providersOnCooldown = rawProvidersOnCooldown.map((entry) => ({
4080
+ baseUrl: normalizeBaseUrl5(entry.baseUrl),
4081
+ timestamp: entry.timestamp
4082
+ }));
3864
4083
  store.setState({
3865
4084
  modelsFromAllProviders,
3866
4085
  lastUsedModel,
@@ -3876,7 +4095,10 @@ var hydrateStoreFromDriver = async (store, driver) => {
3876
4095
  routstr21Models,
3877
4096
  lastRoutstr21ModelsUpdate,
3878
4097
  cachedReceiveTokens,
3879
- clientIds
4098
+ clientIds,
4099
+ failedProviders,
4100
+ lastFailed,
4101
+ providersOnCooldown
3880
4102
  });
3881
4103
  };
3882
4104
  var createSdkStore = ({
@@ -4292,11 +4514,11 @@ var RoutstrClient = class {
4292
4514
  this.balanceManager
4293
4515
  );
4294
4516
  this.streamProcessor = new StreamProcessor();
4295
- this.providerManager = new ProviderManager(providerRegistry);
4296
4517
  this.alertLevel = alertLevel;
4297
4518
  this.mode = mode;
4298
4519
  this.usageTrackingDriver = options.usageTrackingDriver;
4299
4520
  this.sdkStore = options.sdkStore;
4521
+ this.providerManager = options.providerManager ?? new ProviderManager(providerRegistry, this.sdkStore);
4300
4522
  }
4301
4523
  cashuSpender;
4302
4524
  balanceManager;
@@ -4826,6 +5048,10 @@ var RoutstrClient = class {
4826
5048
  const currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
4827
5049
  const shortfall = Math.max(0, params.requiredSats - currentBalance);
4828
5050
  topupAmount = shortfall > 0 ? shortfall : params.requiredSats;
5051
+ this._log(
5052
+ "DEBUG",
5053
+ `The shortfall is: ${shortfall}. requiredSats: ${params.requiredSats}. Current Balance: ${currentBalance} `
5054
+ );
4829
5055
  } catch (e) {
4830
5056
  this._log(
4831
5057
  "WARN",
@@ -4955,6 +5181,20 @@ var RoutstrClient = class {
4955
5181
  tryNextProvider = true;
4956
5182
  }
4957
5183
  }
5184
+ if (status === 401 && this.mode === "apikeys") {
5185
+ this._log(
5186
+ "DEBUG",
5187
+ `[RoutstrClient] _handleErrorResponse: Checking balance for ${baseUrl}, key preview=${token}`
5188
+ );
5189
+ const latestBalanceInfo = await this.balanceManager.getTokenBalance(
5190
+ token,
5191
+ baseUrl
5192
+ );
5193
+ if (latestBalanceInfo.isInvalidApiKey) {
5194
+ this.storageAdapter.removeApiKey(baseUrl);
5195
+ tryNextProvider = true;
5196
+ }
5197
+ }
4958
5198
  if ((status === 401 || status === 403 || status === 413 || status === 400 || status === 500 || status === 502 || status === 503 || status === 504 || status === 521) && !tryNextProvider) {
4959
5199
  this._log(
4960
5200
  "DEBUG",
@@ -4965,13 +5205,13 @@ var RoutstrClient = class {
4965
5205
  "DEBUG",
4966
5206
  `[RoutstrClient] _handleErrorResponse: Attempting API key refund for ${baseUrl}, key preview=${token}`
4967
5207
  );
4968
- const initialBalance = await this.balanceManager.getTokenBalance(
5208
+ const latestBalanceInfo = await this.balanceManager.getTokenBalance(
4969
5209
  token,
4970
5210
  baseUrl
4971
5211
  );
4972
5212
  this._log(
4973
5213
  "DEBUG",
4974
- `[RoutstrClient] _handleErrorResponse: Initial API key balance: ${initialBalance.amount}`
5214
+ `[RoutstrClient] _handleErrorResponse: Initial API key balance: ${latestBalanceInfo.amount}`
4975
5215
  );
4976
5216
  const refundResult = await this.balanceManager.refundApiKey({
4977
5217
  mintUrl,
@@ -4983,7 +5223,7 @@ var RoutstrClient = class {
4983
5223
  "DEBUG",
4984
5224
  `[RoutstrClient] _handleErrorResponse: API key refund result: success=${refundResult.success}, message=${refundResult.message}`
4985
5225
  );
4986
- if (!refundResult.success && initialBalance.amount > 0) {
5226
+ if (!refundResult.success && latestBalanceInfo.amount > 0) {
4987
5227
  throw new ProviderError(
4988
5228
  baseUrl,
4989
5229
  status,
@@ -5123,7 +5363,6 @@ var RoutstrClient = class {
5123
5363
  try {
5124
5364
  const xcashuResults = await this.cashuSpender.refundXcashuTokens(mintUrl);
5125
5365
  this._log("DEBUG", "Refund xcashu tokens results:", xcashuResults);
5126
- const results = await this.cashuSpender.refundProviders(mintUrl);
5127
5366
  } catch (error) {
5128
5367
  this._log("ERROR", "Failed to refund providers:", error);
5129
5368
  }
@@ -5464,7 +5703,8 @@ async function resolveRouteRequestContext(options) {
5464
5703
  debugLevel,
5465
5704
  mode = "apikeys",
5466
5705
  usageTrackingDriver,
5467
- sdkStore
5706
+ sdkStore,
5707
+ providerManager: providedProviderManager
5468
5708
  } = options;
5469
5709
  let modelManager;
5470
5710
  let providers;
@@ -5484,7 +5724,7 @@ async function resolveRouteRequestContext(options) {
5484
5724
  }
5485
5725
  await modelManager.fetchModels(providers, forceRefresh);
5486
5726
  }
5487
- const providerManager = new ProviderManager(providerRegistry);
5727
+ const providerManager = providedProviderManager ?? new ProviderManager(providerRegistry, sdkStore);
5488
5728
  let baseUrl;
5489
5729
  let selectedModel;
5490
5730
  if (forcedProvider) {
@@ -5529,7 +5769,7 @@ async function resolveRouteRequestContext(options) {
5529
5769
  providerRegistry,
5530
5770
  "min",
5531
5771
  mode,
5532
- { usageTrackingDriver, sdkStore }
5772
+ { usageTrackingDriver, sdkStore, providerManager }
5533
5773
  );
5534
5774
  if (debugLevel) {
5535
5775
  client.setDebugLevel(debugLevel);