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