@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.
@@ -1,6 +1,6 @@
1
1
  import { M as Model, a as Message, T as TransactionHistory, l as StreamingResult } from '../types-BYj_8c5c.mjs';
2
2
  import { P as ProviderRegistry, W as WalletAdapter, S as StorageAdapter, a as StreamingCallbacks } from '../interfaces-C5fLD3jB.mjs';
3
- import { U as UsageTrackingDriver, S as SdkStore } from '../store-C5lnyX8k.mjs';
3
+ import { S as SdkStore, U as UsageTrackingDriver } from '../store-DGeLPv9E.mjs';
4
4
  import { ServerResponse } from 'http';
5
5
  import { CashuSpender, BalanceManager } from '../wallet/index.mjs';
6
6
  import { Transform } from 'stream';
@@ -35,7 +35,19 @@ declare class ProviderManager {
35
35
  private providersOnCoolDown;
36
36
  /** Cooldown duration in milliseconds (5 minutes) */
37
37
  private static readonly COOLDOWN_DURATION_MS;
38
- constructor(providerRegistry: ProviderRegistry);
38
+ /** Optional persistent store for failure tracking */
39
+ private store;
40
+ /** Instance ID for debugging */
41
+ private readonly instanceId;
42
+ constructor(providerRegistry: ProviderRegistry, store?: SdkStore);
43
+ /**
44
+ * Hydrate in-memory state from persistent store
45
+ */
46
+ private hydrateFromStore;
47
+ /**
48
+ * Get instance ID for debugging
49
+ */
50
+ getInstanceId(): string;
39
51
  /**
40
52
  * Clean up expired cooldown entries
41
53
  */
@@ -184,6 +196,8 @@ interface RouteRequestToNodeResponseParams extends RouteRequestParams {
184
196
  interface RoutstrClientConfig {
185
197
  usageTrackingDriver?: UsageTrackingDriver;
186
198
  sdkStore?: SdkStore;
199
+ /** Optional: shared ProviderManager instance for consistent failure tracking across requests */
200
+ providerManager?: ProviderManager;
187
201
  }
188
202
  declare class RoutstrClient {
189
203
  private walletAdapter;
@@ -1,6 +1,6 @@
1
1
  import { M as Model, a as Message, T as TransactionHistory, l as StreamingResult } from '../types-BYj_8c5c.js';
2
2
  import { P as ProviderRegistry, W as WalletAdapter, S as StorageAdapter, a as StreamingCallbacks } from '../interfaces-B62Rw-dd.js';
3
- import { U as UsageTrackingDriver, S as SdkStore } from '../store-BJlwiDX5.js';
3
+ import { S as SdkStore, U as UsageTrackingDriver } from '../store-h7m23ffq.js';
4
4
  import { ServerResponse } from 'http';
5
5
  import { CashuSpender, BalanceManager } from '../wallet/index.js';
6
6
  import { Transform } from 'stream';
@@ -35,7 +35,19 @@ declare class ProviderManager {
35
35
  private providersOnCoolDown;
36
36
  /** Cooldown duration in milliseconds (5 minutes) */
37
37
  private static readonly COOLDOWN_DURATION_MS;
38
- constructor(providerRegistry: ProviderRegistry);
38
+ /** Optional persistent store for failure tracking */
39
+ private store;
40
+ /** Instance ID for debugging */
41
+ private readonly instanceId;
42
+ constructor(providerRegistry: ProviderRegistry, store?: SdkStore);
43
+ /**
44
+ * Hydrate in-memory state from persistent store
45
+ */
46
+ private hydrateFromStore;
47
+ /**
48
+ * Get instance ID for debugging
49
+ */
50
+ getInstanceId(): string;
39
51
  /**
40
52
  * Clean up expired cooldown entries
41
53
  */
@@ -184,6 +196,8 @@ interface RouteRequestToNodeResponseParams extends RouteRequestParams {
184
196
  interface RoutstrClientConfig {
185
197
  usageTrackingDriver?: UsageTrackingDriver;
186
198
  sdkStore?: SdkStore;
199
+ /** Optional: shared ProviderManager instance for consistent failure tracking across requests */
200
+ providerManager?: ProviderManager;
187
201
  }
188
202
  declare class RoutstrClient {
189
203
  private walletAdapter;
@@ -449,8 +449,9 @@ var CashuSpender = class {
449
449
  return null;
450
450
  }
451
451
  /**
452
- * Refund all xcashu tokens from storage and increment tryCounts on failure.
453
- * Reuses receiveToken from BalanceManager/CashuSpender for receiving refunds.
452
+ * Refund all xcashu tokens from storage by calling the provider's refund endpoint.
453
+ * The xcashu token acts as an API key to claim the refund, and the response contains
454
+ * the actual refunded Cashu token which is then received into the wallet.
454
455
  * @param mintUrl - The mint URL for receiving tokens
455
456
  * @param excludeBaseUrls - Base URLs to exclude from refund (optional)
456
457
  * @returns Results for each xcashu token refund attempt
@@ -463,7 +464,20 @@ var CashuSpender = class {
463
464
  if (excludedUrls.has(baseUrl)) continue;
464
465
  for (const xcashuToken of tokens) {
465
466
  try {
466
- const receiveResult = await this.receiveToken(xcashuToken.token);
467
+ if (!this.balanceManager) {
468
+ throw new Error("BalanceManager not available for xcashu refund");
469
+ }
470
+ const fetchResult = await this.balanceManager.fetchRefundToken(
471
+ baseUrl,
472
+ xcashuToken.token,
473
+ true
474
+ );
475
+ if (!fetchResult.success || !fetchResult.token) {
476
+ throw new Error(
477
+ fetchResult.error || "Failed to fetch refund token from provider"
478
+ );
479
+ }
480
+ const receiveResult = await this.receiveToken(fetchResult.token);
467
481
  if (receiveResult.success) {
468
482
  this.storageAdapter.removeXcashuToken(baseUrl, xcashuToken.token);
469
483
  results.push({
@@ -478,7 +492,10 @@ var CashuSpender = class {
478
492
  } else {
479
493
  const currentTryCount = xcashuToken.tryCount ?? 0;
480
494
  const newTryCount = currentTryCount + 1;
481
- this.storageAdapter.updateXcashuTokenTryCount(xcashuToken.token, newTryCount);
495
+ this.storageAdapter.updateXcashuTokenTryCount(
496
+ xcashuToken.token,
497
+ newTryCount
498
+ );
482
499
  results.push({
483
500
  baseUrl,
484
501
  token: xcashuToken.token,
@@ -487,13 +504,16 @@ var CashuSpender = class {
487
504
  });
488
505
  this._log(
489
506
  "DEBUG",
490
- `[CashuSpender] refundXcashuTokens: Failed to refund xcashu token for ${baseUrl}, incremented tryCount to ${newTryCount}`
507
+ `[CashuSpender] refundXcashuTokens: Failed to receive refund token for ${baseUrl}, incremented tryCount to ${newTryCount}`
491
508
  );
492
509
  }
493
510
  } catch (error) {
494
511
  const currentTryCount = xcashuToken.tryCount ?? 0;
495
512
  const newTryCount = currentTryCount + 1;
496
- this.storageAdapter.updateXcashuTokenTryCount(xcashuToken.token, newTryCount);
513
+ this.storageAdapter.updateXcashuTokenTryCount(
514
+ xcashuToken.token,
515
+ newTryCount
516
+ );
497
517
  const errorMessage = error instanceof Error ? error.message : String(error);
498
518
  results.push({
499
519
  baseUrl,
@@ -530,7 +550,10 @@ var CashuSpender = class {
530
550
  if (refundResult.success) {
531
551
  this.storageAdapter.removeApiKey(apiKeyEntry.baseUrl);
532
552
  } else {
533
- this.storageAdapter.updateApiKeyBalance(apiKeyEntry.baseUrl, apiKeyEntry.amount);
553
+ this.storageAdapter.updateApiKeyBalance(
554
+ apiKeyEntry.baseUrl,
555
+ apiKeyEntry.amount
556
+ );
534
557
  }
535
558
  results.push({
536
559
  baseUrl: apiKeyEntry.baseUrl,
@@ -668,7 +691,7 @@ var BalanceManager = class {
668
691
  }
669
692
  let fetchResult;
670
693
  try {
671
- fetchResult = await this._fetchRefundTokenWithApiKey(baseUrl, apiKey);
694
+ fetchResult = await this.fetchRefundToken(baseUrl, apiKey);
672
695
  if (!fetchResult.success) {
673
696
  return {
674
697
  success: false,
@@ -704,9 +727,9 @@ var BalanceManager = class {
704
727
  }
705
728
  }
706
729
  /**
707
- * Fetch refund token from provider API using API key authentication
730
+ * Fetch refund token from provider API using API key (or xcashu token) authentication
708
731
  */
709
- async _fetchRefundTokenWithApiKey(baseUrl, apiKey) {
732
+ async fetchRefundToken(baseUrl, apiKeyOrToken, xCashu = false) {
710
733
  if (!baseUrl) {
711
734
  return {
712
735
  success: false,
@@ -720,12 +743,17 @@ var BalanceManager = class {
720
743
  controller.abort();
721
744
  }, 6e4);
722
745
  try {
746
+ const headers = {
747
+ "Content-Type": "application/json"
748
+ };
749
+ if (xCashu) {
750
+ headers["X-Cashu"] = apiKeyOrToken;
751
+ } else {
752
+ headers["Authorization"] = `Bearer ${apiKeyOrToken}`;
753
+ }
723
754
  const response = await fetch(url, {
724
755
  method: "POST",
725
- headers: {
726
- Authorization: `Bearer ${apiKey}`,
727
- "Content-Type": "application/json"
728
- },
756
+ headers,
729
757
  signal: controller.signal
730
758
  });
731
759
  clearTimeout(timeoutId);
@@ -746,10 +774,7 @@ var BalanceManager = class {
746
774
  };
747
775
  } catch (error) {
748
776
  clearTimeout(timeoutId);
749
- console.error(
750
- "[BalanceManager._fetchRefundTokenWithApiKey] Fetch error",
751
- error
752
- );
777
+ console.error("[BalanceManager.fetchRefundToken] Fetch error", error);
753
778
  if (error instanceof Error) {
754
779
  if (error.name === "AbortError") {
755
780
  return {
@@ -796,11 +821,7 @@ var BalanceManager = class {
796
821
  };
797
822
  }
798
823
  cashuToken = tokenResult.token;
799
- const topUpResult = await this._postTopUp(
800
- baseUrl,
801
- apiKey,
802
- cashuToken
803
- );
824
+ const topUpResult = await this._postTopUp(baseUrl, apiKey, cashuToken);
804
825
  requestId = topUpResult.requestId;
805
826
  console.log(topUpResult);
806
827
  if (!topUpResult.success) {
@@ -1166,7 +1187,7 @@ var BalanceManager = class {
1166
1187
  console.log(response.status);
1167
1188
  const data = await response.json();
1168
1189
  console.log("FAILED ", data);
1169
- const isInvalidApiKey = response.status === 401 && data?.code === "invalid_api_key" && data?.message?.includes("proofs already spent");
1190
+ const isInvalidApiKey = response.status === 401 && data?.detail?.error?.code === "invalid_api_key" && data?.detail?.error?.message?.includes("proofs already spent");
1170
1191
  return {
1171
1192
  amount: -1,
1172
1193
  reserved: data.reserved ?? 0,
@@ -1608,8 +1629,13 @@ function isInsecureHttpUrl(url) {
1608
1629
  return url.startsWith("http://");
1609
1630
  }
1610
1631
  var ProviderManager = class _ProviderManager {
1611
- constructor(providerRegistry) {
1632
+ constructor(providerRegistry, store) {
1612
1633
  this.providerRegistry = providerRegistry;
1634
+ this.instanceId = `pm_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
1635
+ if (store) {
1636
+ this.store = store;
1637
+ this.hydrateFromStore();
1638
+ }
1613
1639
  }
1614
1640
  failedProviders = /* @__PURE__ */ new Set();
1615
1641
  /** Track when each provider last failed (provider URL -> timestamp) */
@@ -1618,14 +1644,57 @@ var ProviderManager = class _ProviderManager {
1618
1644
  providersOnCoolDown = [];
1619
1645
  /** Cooldown duration in milliseconds (5 minutes) */
1620
1646
  static COOLDOWN_DURATION_MS = 5 * 60 * 1e3;
1647
+ /** Optional persistent store for failure tracking */
1648
+ store = null;
1649
+ /** Instance ID for debugging */
1650
+ instanceId;
1651
+ /**
1652
+ * Hydrate in-memory state from persistent store
1653
+ */
1654
+ hydrateFromStore() {
1655
+ if (!this.store) return;
1656
+ const state = this.store.getState();
1657
+ this.failedProviders = new Set(state.failedProviders);
1658
+ this.lastFailed = new Map(Object.entries(state.lastFailed));
1659
+ const now = Date.now();
1660
+ this.providersOnCoolDown = state.providersOnCooldown.filter(
1661
+ (entry) => now - entry.timestamp < _ProviderManager.COOLDOWN_DURATION_MS
1662
+ ).map((entry) => [entry.baseUrl, entry.timestamp]);
1663
+ console.log(`[ProviderManager:${this.instanceId}] Hydrated from store:`);
1664
+ console.log(` failedProviders: ${this.failedProviders.size}`);
1665
+ console.log(` lastFailed: ${this.lastFailed.size}`);
1666
+ console.log(` providersOnCooldown: ${this.providersOnCoolDown.length}`);
1667
+ }
1668
+ /**
1669
+ * Get instance ID for debugging
1670
+ */
1671
+ getInstanceId() {
1672
+ return this.instanceId;
1673
+ }
1621
1674
  /**
1622
1675
  * Clean up expired cooldown entries
1623
1676
  */
1624
1677
  cleanupExpiredCooldowns() {
1625
1678
  const now = Date.now();
1679
+ const before = this.providersOnCoolDown.length;
1626
1680
  this.providersOnCoolDown = this.providersOnCoolDown.filter(
1627
- ([, timestamp]) => now - timestamp < _ProviderManager.COOLDOWN_DURATION_MS
1681
+ ([url, timestamp]) => {
1682
+ const age = now - timestamp;
1683
+ const isExpired = age >= _ProviderManager.COOLDOWN_DURATION_MS;
1684
+ if (isExpired) {
1685
+ console.log(
1686
+ `[cleanupExpiredCooldowns:${this.instanceId}] Removing expired cooldown for ${url} (age: ${age}ms, cooldown: ${_ProviderManager.COOLDOWN_DURATION_MS}ms)`
1687
+ );
1688
+ }
1689
+ return !isExpired;
1690
+ }
1628
1691
  );
1692
+ const after = this.providersOnCoolDown.length;
1693
+ if (before !== after) {
1694
+ console.log(
1695
+ `[cleanupExpiredCooldowns:${this.instanceId}] Cleaned up ${before - after} expired cooldown(s), ${after} remaining`
1696
+ );
1697
+ }
1629
1698
  }
1630
1699
  /**
1631
1700
  * Get the cooldown duration in milliseconds
@@ -1638,7 +1707,8 @@ var ProviderManager = class _ProviderManager {
1638
1707
  */
1639
1708
  isOnCooldown(baseUrl) {
1640
1709
  this.cleanupExpiredCooldowns();
1641
- return this.providersOnCoolDown.some(([url]) => url === baseUrl);
1710
+ const result = this.providersOnCoolDown.some(([url]) => url === baseUrl);
1711
+ return result;
1642
1712
  }
1643
1713
  /**
1644
1714
  * Get all providers currently on cooldown
@@ -1652,6 +1722,9 @@ var ProviderManager = class _ProviderManager {
1652
1722
  */
1653
1723
  resetFailedProviders() {
1654
1724
  this.failedProviders.clear();
1725
+ if (this.store) {
1726
+ this.store.getState().setFailedProviders([]);
1727
+ }
1655
1728
  }
1656
1729
  /**
1657
1730
  * Get the last failed timestamp for a provider
@@ -1672,13 +1745,62 @@ var ProviderManager = class _ProviderManager {
1672
1745
  markFailed(baseUrl) {
1673
1746
  const now = Date.now();
1674
1747
  const lastFailure = this.lastFailed.get(baseUrl);
1748
+ console.log(`[markFailed:${this.instanceId}] baseUrl: ${baseUrl}`);
1749
+ console.log(
1750
+ `[markFailed:${this.instanceId}] lastFailure from map: ${lastFailure}`
1751
+ );
1752
+ console.log(
1753
+ `[markFailed:${this.instanceId}] current timestamp (now): ${now}`
1754
+ );
1755
+ console.log(
1756
+ `[markFailed:${this.instanceId}] COOLDOWN_DURATION_MS: ${_ProviderManager.COOLDOWN_DURATION_MS}`
1757
+ );
1758
+ if (lastFailure !== void 0) {
1759
+ const timeSinceLastFailure = now - lastFailure;
1760
+ console.log(
1761
+ `[markFailed:${this.instanceId}] timeSinceLastFailure: ${timeSinceLastFailure}ms`
1762
+ );
1763
+ console.log(
1764
+ `[markFailed:${this.instanceId}] isWithinCooldownWindow: ${timeSinceLastFailure < _ProviderManager.COOLDOWN_DURATION_MS}`
1765
+ );
1766
+ }
1675
1767
  this.lastFailed.set(baseUrl, now);
1676
1768
  this.failedProviders.add(baseUrl);
1769
+ if (this.store) {
1770
+ this.store.getState().setLastFailedTimestamp(baseUrl, now);
1771
+ this.store.getState().addFailedProvider(baseUrl);
1772
+ }
1773
+ console.log(
1774
+ `[markFailed:${this.instanceId}] Updated lastFailed map for ${baseUrl} to ${now}`
1775
+ );
1776
+ console.log(
1777
+ `[markFailed:${this.instanceId}] failedProviders set size: ${this.failedProviders.size}`
1778
+ );
1677
1779
  if (lastFailure !== void 0 && now - lastFailure < _ProviderManager.COOLDOWN_DURATION_MS) {
1780
+ console.log(
1781
+ `[markFailed:${this.instanceId}] Second failure detected within cooldown window for ${baseUrl}`
1782
+ );
1678
1783
  if (!this.isOnCooldown(baseUrl)) {
1679
1784
  this.providersOnCoolDown.push([baseUrl, now]);
1785
+ if (this.store) {
1786
+ this.store.getState().addProviderOnCooldown(baseUrl, now);
1787
+ }
1788
+ console.log(
1789
+ `[markFailed:${this.instanceId}] Provider ${baseUrl} added to cooldown after second failure within 5 minutes`
1790
+ );
1791
+ } else {
1792
+ console.log(
1793
+ `[markFailed:${this.instanceId}] Provider ${baseUrl} is already on cooldown`
1794
+ );
1795
+ }
1796
+ } else {
1797
+ if (lastFailure === void 0) {
1680
1798
  console.log(
1681
- `Provider ${baseUrl} added to cooldown after second failure within 5 minutes`
1799
+ `[markFailed:${this.instanceId}] First failure for ${baseUrl} - not adding to cooldown yet`
1800
+ );
1801
+ } else {
1802
+ console.log(
1803
+ `[markFailed:${this.instanceId}] Failure outside cooldown window for ${baseUrl} (timeSinceLastFailure: ${now - lastFailure}ms)`
1682
1804
  );
1683
1805
  }
1684
1806
  }
@@ -1690,18 +1812,27 @@ var ProviderManager = class _ProviderManager {
1690
1812
  this.providersOnCoolDown = this.providersOnCoolDown.filter(
1691
1813
  ([url]) => url !== baseUrl
1692
1814
  );
1815
+ if (this.store) {
1816
+ this.store.getState().removeProviderFromCooldown(baseUrl);
1817
+ }
1693
1818
  }
1694
1819
  /**
1695
1820
  * Clear all cooldown tracking
1696
1821
  */
1697
1822
  clearCooldowns() {
1698
1823
  this.providersOnCoolDown = [];
1824
+ if (this.store) {
1825
+ this.store.getState().clearProvidersOnCooldown();
1826
+ }
1699
1827
  }
1700
1828
  /**
1701
1829
  * Clear all failure tracking (lastFailed timestamps)
1702
1830
  */
1703
1831
  clearFailureHistory() {
1704
1832
  this.lastFailed.clear();
1833
+ if (this.store) {
1834
+ this.store.getState().setLastFailed({});
1835
+ }
1705
1836
  }
1706
1837
  /**
1707
1838
  * Check if a provider has failed
@@ -2123,7 +2254,10 @@ var SDK_STORAGE_KEYS = {
2123
2254
  LAST_ROUTSTR21_MODELS_UPDATE: "lastRoutstr21ModelsUpdate",
2124
2255
  CACHED_RECEIVE_TOKENS: "cached_receive_tokens",
2125
2256
  USAGE_TRACKING: "usage_tracking",
2126
- CLIENT_IDS: "client_ids"
2257
+ CLIENT_IDS: "client_ids",
2258
+ FAILED_PROVIDERS: "failed_providers",
2259
+ LAST_FAILED: "last_failed",
2260
+ PROVIDERS_ON_COOLDOWN: "providers_on_cooldown"
2127
2261
  };
2128
2262
 
2129
2263
  // storage/usageTracking/indexedDB.ts
@@ -2770,6 +2904,9 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
2770
2904
  lastRoutstr21ModelsUpdate: null,
2771
2905
  cachedReceiveTokens: [],
2772
2906
  clientIds: [],
2907
+ failedProviders: [],
2908
+ lastFailed: {},
2909
+ providersOnCooldown: [],
2773
2910
  setModelsFromAllProviders: (value) => {
2774
2911
  const normalized = {};
2775
2912
  for (const [baseUrl, models] of Object.entries(value)) {
@@ -2909,6 +3046,71 @@ var createEmptyStore = (driver) => vanilla.createStore((set, get) => ({
2909
3046
  void driver.setItem(SDK_STORAGE_KEYS.CLIENT_IDS, normalized);
2910
3047
  return { clientIds: normalized };
2911
3048
  });
3049
+ },
3050
+ // ========== Failure Tracking ==========
3051
+ setFailedProviders: (value) => {
3052
+ const normalized = value.map((url) => normalizeBaseUrl5(url));
3053
+ void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, normalized);
3054
+ set({ failedProviders: normalized });
3055
+ },
3056
+ addFailedProvider: (baseUrl) => {
3057
+ const normalized = normalizeBaseUrl5(baseUrl);
3058
+ const current = get().failedProviders;
3059
+ if (!current.includes(normalized)) {
3060
+ const updated = [...current, normalized];
3061
+ void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, updated);
3062
+ set({ failedProviders: updated });
3063
+ }
3064
+ },
3065
+ removeFailedProvider: (baseUrl) => {
3066
+ const normalized = normalizeBaseUrl5(baseUrl);
3067
+ const current = get().failedProviders;
3068
+ const updated = current.filter((url) => url !== normalized);
3069
+ void driver.setItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, updated);
3070
+ set({ failedProviders: updated });
3071
+ },
3072
+ setLastFailed: (value) => {
3073
+ const normalized = {};
3074
+ for (const [baseUrl, timestamp] of Object.entries(value)) {
3075
+ normalized[normalizeBaseUrl5(baseUrl)] = timestamp;
3076
+ }
3077
+ void driver.setItem(SDK_STORAGE_KEYS.LAST_FAILED, normalized);
3078
+ set({ lastFailed: normalized });
3079
+ },
3080
+ setLastFailedTimestamp: (baseUrl, timestamp) => {
3081
+ const normalized = normalizeBaseUrl5(baseUrl);
3082
+ const current = get().lastFailed;
3083
+ const updated = { ...current, [normalized]: timestamp };
3084
+ void driver.setItem(SDK_STORAGE_KEYS.LAST_FAILED, updated);
3085
+ set({ lastFailed: updated });
3086
+ },
3087
+ setProvidersOnCooldown: (value) => {
3088
+ const normalized = value.map((entry) => ({
3089
+ baseUrl: normalizeBaseUrl5(entry.baseUrl),
3090
+ timestamp: entry.timestamp
3091
+ }));
3092
+ void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, normalized);
3093
+ set({ providersOnCooldown: normalized });
3094
+ },
3095
+ addProviderOnCooldown: (baseUrl, timestamp) => {
3096
+ const normalized = normalizeBaseUrl5(baseUrl);
3097
+ const current = get().providersOnCooldown;
3098
+ if (!current.some((entry) => entry.baseUrl === normalized)) {
3099
+ const updated = [...current, { baseUrl: normalized, timestamp }];
3100
+ void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, updated);
3101
+ set({ providersOnCooldown: updated });
3102
+ }
3103
+ },
3104
+ removeProviderFromCooldown: (baseUrl) => {
3105
+ const normalized = normalizeBaseUrl5(baseUrl);
3106
+ const current = get().providersOnCooldown;
3107
+ const updated = current.filter((entry) => entry.baseUrl !== normalized);
3108
+ void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, updated);
3109
+ set({ providersOnCooldown: updated });
3110
+ },
3111
+ clearProvidersOnCooldown: () => {
3112
+ void driver.setItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, []);
3113
+ set({ providersOnCooldown: [] });
2912
3114
  }
2913
3115
  }));
2914
3116
  var hydrateStoreFromDriver = async (store, driver) => {
@@ -2927,7 +3129,10 @@ var hydrateStoreFromDriver = async (store, driver) => {
2927
3129
  rawRoutstr21Models,
2928
3130
  rawLastRoutstr21ModelsUpdate,
2929
3131
  rawCachedReceiveTokens,
2930
- rawClientIds
3132
+ rawClientIds,
3133
+ rawFailedProviders,
3134
+ rawLastFailed,
3135
+ rawProvidersOnCooldown
2931
3136
  ] = await Promise.all([
2932
3137
  driver.getItem(
2933
3138
  SDK_STORAGE_KEYS.MODELS_FROM_ALL_PROVIDERS,
@@ -2958,7 +3163,10 @@ var hydrateStoreFromDriver = async (store, driver) => {
2958
3163
  null
2959
3164
  ),
2960
3165
  driver.getItem(SDK_STORAGE_KEYS.CACHED_RECEIVE_TOKENS, []),
2961
- driver.getItem(SDK_STORAGE_KEYS.CLIENT_IDS, [])
3166
+ driver.getItem(SDK_STORAGE_KEYS.CLIENT_IDS, []),
3167
+ driver.getItem(SDK_STORAGE_KEYS.FAILED_PROVIDERS, []),
3168
+ driver.getItem(SDK_STORAGE_KEYS.LAST_FAILED, {}),
3169
+ driver.getItem(SDK_STORAGE_KEYS.PROVIDERS_ON_COOLDOWN, [])
2962
3170
  ]);
2963
3171
  const modelsFromAllProviders = Object.fromEntries(
2964
3172
  Object.entries(rawModels).map(([baseUrl, models]) => [
@@ -3026,6 +3234,17 @@ var hydrateStoreFromDriver = async (store, driver) => {
3026
3234
  createdAt: entry.createdAt ?? Date.now(),
3027
3235
  lastUsed: entry.lastUsed ?? null
3028
3236
  }));
3237
+ const failedProviders = rawFailedProviders.map((url) => normalizeBaseUrl5(url));
3238
+ const lastFailed = Object.fromEntries(
3239
+ Object.entries(rawLastFailed).map(([baseUrl, timestamp]) => [
3240
+ normalizeBaseUrl5(baseUrl),
3241
+ timestamp
3242
+ ])
3243
+ );
3244
+ const providersOnCooldown = rawProvidersOnCooldown.map((entry) => ({
3245
+ baseUrl: normalizeBaseUrl5(entry.baseUrl),
3246
+ timestamp: entry.timestamp
3247
+ }));
3029
3248
  store.setState({
3030
3249
  modelsFromAllProviders,
3031
3250
  lastUsedModel,
@@ -3041,7 +3260,10 @@ var hydrateStoreFromDriver = async (store, driver) => {
3041
3260
  routstr21Models,
3042
3261
  lastRoutstr21ModelsUpdate,
3043
3262
  cachedReceiveTokens,
3044
- clientIds
3263
+ clientIds,
3264
+ failedProviders,
3265
+ lastFailed,
3266
+ providersOnCooldown
3045
3267
  });
3046
3268
  };
3047
3269
  var createSdkStore = ({
@@ -3207,11 +3429,11 @@ var RoutstrClient = class {
3207
3429
  this.balanceManager
3208
3430
  );
3209
3431
  this.streamProcessor = new StreamProcessor();
3210
- this.providerManager = new ProviderManager(providerRegistry);
3211
3432
  this.alertLevel = alertLevel;
3212
3433
  this.mode = mode;
3213
3434
  this.usageTrackingDriver = options.usageTrackingDriver;
3214
3435
  this.sdkStore = options.sdkStore;
3436
+ this.providerManager = options.providerManager ?? new ProviderManager(providerRegistry, this.sdkStore);
3215
3437
  }
3216
3438
  cashuSpender;
3217
3439
  balanceManager;
@@ -3741,6 +3963,10 @@ var RoutstrClient = class {
3741
3963
  const currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1e3 : currentBalanceInfo.amount;
3742
3964
  const shortfall = Math.max(0, params.requiredSats - currentBalance);
3743
3965
  topupAmount = shortfall > 0 ? shortfall : params.requiredSats;
3966
+ this._log(
3967
+ "DEBUG",
3968
+ `The shortfall is: ${shortfall}. requiredSats: ${params.requiredSats}. Current Balance: ${currentBalance} `
3969
+ );
3744
3970
  } catch (e) {
3745
3971
  this._log(
3746
3972
  "WARN",
@@ -3870,6 +4096,20 @@ var RoutstrClient = class {
3870
4096
  tryNextProvider = true;
3871
4097
  }
3872
4098
  }
4099
+ if (status === 401 && this.mode === "apikeys") {
4100
+ this._log(
4101
+ "DEBUG",
4102
+ `[RoutstrClient] _handleErrorResponse: Checking balance for ${baseUrl}, key preview=${token}`
4103
+ );
4104
+ const latestBalanceInfo = await this.balanceManager.getTokenBalance(
4105
+ token,
4106
+ baseUrl
4107
+ );
4108
+ if (latestBalanceInfo.isInvalidApiKey) {
4109
+ this.storageAdapter.removeApiKey(baseUrl);
4110
+ tryNextProvider = true;
4111
+ }
4112
+ }
3873
4113
  if ((status === 401 || status === 403 || status === 413 || status === 400 || status === 500 || status === 502 || status === 503 || status === 504 || status === 521) && !tryNextProvider) {
3874
4114
  this._log(
3875
4115
  "DEBUG",
@@ -3880,13 +4120,13 @@ var RoutstrClient = class {
3880
4120
  "DEBUG",
3881
4121
  `[RoutstrClient] _handleErrorResponse: Attempting API key refund for ${baseUrl}, key preview=${token}`
3882
4122
  );
3883
- const initialBalance = await this.balanceManager.getTokenBalance(
4123
+ const latestBalanceInfo = await this.balanceManager.getTokenBalance(
3884
4124
  token,
3885
4125
  baseUrl
3886
4126
  );
3887
4127
  this._log(
3888
4128
  "DEBUG",
3889
- `[RoutstrClient] _handleErrorResponse: Initial API key balance: ${initialBalance.amount}`
4129
+ `[RoutstrClient] _handleErrorResponse: Initial API key balance: ${latestBalanceInfo.amount}`
3890
4130
  );
3891
4131
  const refundResult = await this.balanceManager.refundApiKey({
3892
4132
  mintUrl,
@@ -3898,7 +4138,7 @@ var RoutstrClient = class {
3898
4138
  "DEBUG",
3899
4139
  `[RoutstrClient] _handleErrorResponse: API key refund result: success=${refundResult.success}, message=${refundResult.message}`
3900
4140
  );
3901
- if (!refundResult.success && initialBalance.amount > 0) {
4141
+ if (!refundResult.success && latestBalanceInfo.amount > 0) {
3902
4142
  throw new ProviderError(
3903
4143
  baseUrl,
3904
4144
  status,
@@ -4038,7 +4278,6 @@ var RoutstrClient = class {
4038
4278
  try {
4039
4279
  const xcashuResults = await this.cashuSpender.refundXcashuTokens(mintUrl);
4040
4280
  this._log("DEBUG", "Refund xcashu tokens results:", xcashuResults);
4041
- const results = await this.cashuSpender.refundProviders(mintUrl);
4042
4281
  } catch (error) {
4043
4282
  this._log("ERROR", "Failed to refund providers:", error);
4044
4283
  }