@routstr/sdk 0.2.2 → 0.2.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.
@@ -24,15 +24,58 @@ interface ModelProviderPrice {
24
24
  declare class ProviderManager {
25
25
  private providerRegistry;
26
26
  private failedProviders;
27
+ /** Track when each provider last failed (provider URL -> timestamp) */
28
+ private lastFailed;
29
+ /** Providers on cooldown: [provider_url, cooldown_started_timestamp][] */
30
+ private providersOnCoolDown;
31
+ /** Cooldown duration in milliseconds (5 minutes) */
32
+ private static readonly COOLDOWN_DURATION_MS;
27
33
  constructor(providerRegistry: ProviderRegistry);
34
+ /**
35
+ * Clean up expired cooldown entries
36
+ */
37
+ private cleanupExpiredCooldowns;
38
+ /**
39
+ * Get the cooldown duration in milliseconds
40
+ */
41
+ getCooldownDurationMs(): number;
42
+ /**
43
+ * Check if a provider is currently on cooldown
44
+ */
45
+ isOnCooldown(baseUrl: string): boolean;
46
+ /**
47
+ * Get all providers currently on cooldown
48
+ */
49
+ getProvidersOnCooldown(): [string, number][];
28
50
  /**
29
51
  * Reset the failed providers list
30
52
  */
31
53
  resetFailedProviders(): void;
54
+ /**
55
+ * Get the last failed timestamp for a provider
56
+ */
57
+ getLastFailed(baseUrl: string): number | undefined;
58
+ /**
59
+ * Get all providers with their last failed timestamps
60
+ */
61
+ getAllLastFailed(): Map<string, number>;
32
62
  /**
33
63
  * Mark a provider as failed
64
+ * If a provider fails twice within 5 minutes, it's added to cooldown
34
65
  */
35
66
  markFailed(baseUrl: string): void;
67
+ /**
68
+ * Remove a provider from cooldown (e.g., after successful request)
69
+ */
70
+ removeFromCooldown(baseUrl: string): void;
71
+ /**
72
+ * Clear all cooldown tracking
73
+ */
74
+ clearCooldowns(): void;
75
+ /**
76
+ * Clear all failure tracking (lastFailed timestamps)
77
+ */
78
+ clearFailureHistory(): void;
36
79
  /**
37
80
  * Check if a provider has failed
38
81
  */
@@ -210,10 +253,6 @@ declare class RoutstrClient {
210
253
  * Get pending cashu token amount
211
254
  */
212
255
  private _getPendingCashuTokenAmount;
213
- /**
214
- * Check if error message indicates a network error
215
- */
216
- private _isNetworkError;
217
256
  /**
218
257
  * Handle errors and notify callbacks
219
258
  */
@@ -24,15 +24,58 @@ interface ModelProviderPrice {
24
24
  declare class ProviderManager {
25
25
  private providerRegistry;
26
26
  private failedProviders;
27
+ /** Track when each provider last failed (provider URL -> timestamp) */
28
+ private lastFailed;
29
+ /** Providers on cooldown: [provider_url, cooldown_started_timestamp][] */
30
+ private providersOnCoolDown;
31
+ /** Cooldown duration in milliseconds (5 minutes) */
32
+ private static readonly COOLDOWN_DURATION_MS;
27
33
  constructor(providerRegistry: ProviderRegistry);
34
+ /**
35
+ * Clean up expired cooldown entries
36
+ */
37
+ private cleanupExpiredCooldowns;
38
+ /**
39
+ * Get the cooldown duration in milliseconds
40
+ */
41
+ getCooldownDurationMs(): number;
42
+ /**
43
+ * Check if a provider is currently on cooldown
44
+ */
45
+ isOnCooldown(baseUrl: string): boolean;
46
+ /**
47
+ * Get all providers currently on cooldown
48
+ */
49
+ getProvidersOnCooldown(): [string, number][];
28
50
  /**
29
51
  * Reset the failed providers list
30
52
  */
31
53
  resetFailedProviders(): void;
54
+ /**
55
+ * Get the last failed timestamp for a provider
56
+ */
57
+ getLastFailed(baseUrl: string): number | undefined;
58
+ /**
59
+ * Get all providers with their last failed timestamps
60
+ */
61
+ getAllLastFailed(): Map<string, number>;
32
62
  /**
33
63
  * Mark a provider as failed
64
+ * If a provider fails twice within 5 minutes, it's added to cooldown
34
65
  */
35
66
  markFailed(baseUrl: string): void;
67
+ /**
68
+ * Remove a provider from cooldown (e.g., after successful request)
69
+ */
70
+ removeFromCooldown(baseUrl: string): void;
71
+ /**
72
+ * Clear all cooldown tracking
73
+ */
74
+ clearCooldowns(): void;
75
+ /**
76
+ * Clear all failure tracking (lastFailed timestamps)
77
+ */
78
+ clearFailureHistory(): void;
36
79
  /**
37
80
  * Check if a provider has failed
38
81
  */
@@ -210,10 +253,6 @@ declare class RoutstrClient {
210
253
  * Get pending cashu token amount
211
254
  */
212
255
  private _getPendingCashuTokenAmount;
213
- /**
214
- * Check if error message indicates a network error
215
- */
216
- private _isNetworkError;
217
256
  /**
218
257
  * Handle errors and notify callbacks
219
258
  */
@@ -81,7 +81,7 @@ var auditLogger = AuditLogger.getInstance();
81
81
 
82
82
  // wallet/tokenUtils.ts
83
83
  function isNetworkErrorMessage(message) {
84
- return message.includes("NetworkError when attempting to fetch resource") || message.includes("Failed to fetch") || message.includes("Load failed");
84
+ return message.includes("NetworkError when attempting to fetch resource") || message.includes("Failed to fetch") || message.includes("Load failed") || message.includes("ERR_TLS_CERT_ALTNAME_INVALID") || message.includes("ERR_TLS_CERT_NOT_YET_VALID") || message.includes("ERR_TLS_CERT_EXPIRED") || message.includes("UNABLE_TO_VERIFY_LEAF_SIGNATURE") || message.includes("SELF_SIGNED_CERT_IN_CHAIN");
85
85
  }
86
86
  function getBalanceInSats(balance, unit) {
87
87
  return unit === "msat" ? balance / 1e3 : balance;
@@ -371,7 +371,26 @@ var CashuSpender = class {
371
371
  }
372
372
  }
373
373
  if (token && baseUrl) {
374
- this.storageAdapter.setToken(baseUrl, token);
374
+ try {
375
+ this.storageAdapter.setToken(baseUrl, token);
376
+ } catch (error) {
377
+ if (error instanceof Error && error.message.includes("Token already exists")) {
378
+ this._log(
379
+ "DEBUG",
380
+ `[CashuSpender] _spendInternal: Token already exists for ${baseUrl}, receiving newly created token and using existing`
381
+ );
382
+ const receiveResult = await this.receiveToken(token);
383
+ if (receiveResult.success) {
384
+ this._log(
385
+ "DEBUG",
386
+ `[CashuSpender] _spendInternal: Token restored successfully, amount=${receiveResult.amount}`
387
+ );
388
+ }
389
+ token = this.storageAdapter.getToken(baseUrl);
390
+ } else {
391
+ throw error;
392
+ }
393
+ }
375
394
  }
376
395
  this._logTransaction("spend", {
377
396
  amount: spentAmount,
@@ -849,7 +868,9 @@ var BalanceManager = class {
849
868
  p2pkPubkey
850
869
  } = options;
851
870
  const adjustedAmount = Math.ceil(amount);
871
+ console.log(`[BalanceManager.createProviderToken] Starting: baseUrl=${baseUrl}, mintUrl=${mintUrl}, amount=${amount}, adjustedAmount=${adjustedAmount}, retryCount=${retryCount}`);
852
872
  if (!adjustedAmount || isNaN(adjustedAmount)) {
873
+ console.error(`[BalanceManager.createProviderToken] FAILURE: Invalid amount - amount=${amount}, adjustedAmount=${adjustedAmount}`);
853
874
  return { success: false, error: "Invalid top up amount" };
854
875
  }
855
876
  const balanceState = await this.getBalanceState();
@@ -880,6 +901,7 @@ var BalanceManager = class {
880
901
  { url: "", balance: 0 }
881
902
  ).url
882
903
  );
904
+ console.error(`[BalanceManager.createProviderToken] FAILURE: Insufficient balance - required=${adjustedAmount}, available=${totalMintBalance + targetProviderBalance}, totalMintBalance=${totalMintBalance}, targetProviderBalance=${targetProviderBalance}, refundableProviderBalance=${refundableProviderBalance}`);
883
905
  return { success: false, error: error.message };
884
906
  }
885
907
  if (targetProviderBalance >= adjustedAmount) {
@@ -921,6 +943,7 @@ var BalanceManager = class {
921
943
  maxMintUrl = mintUrl2;
922
944
  }
923
945
  }
946
+ console.error(`[BalanceManager.createProviderToken] FAILURE: No candidate mints found - requiredAmount=${requiredAmount}, totalMintBalance=${totalMintBalance}, maxBalance=${maxBalance}, maxMintUrl=${maxMintUrl}, providerMints=${JSON.stringify(providerMints)}`);
924
947
  const error = new InsufficientBalanceError(
925
948
  adjustedAmount,
926
949
  totalMintBalance,
@@ -932,11 +955,13 @@ var BalanceManager = class {
932
955
  let lastError;
933
956
  for (const candidateMint of candidates) {
934
957
  try {
958
+ console.log(`[BalanceManager.createProviderToken] Attempting mint: ${candidateMint}, amount: ${requiredAmount}`);
935
959
  const token = await this.walletAdapter.sendToken(
936
960
  candidateMint,
937
961
  requiredAmount,
938
962
  p2pkPubkey
939
963
  );
964
+ console.log(`[BalanceManager.createProviderToken] SUCCESS: Token created from mint ${candidateMint}`);
940
965
  return {
941
966
  success: true,
942
967
  token,
@@ -944,9 +969,12 @@ var BalanceManager = class {
944
969
  amountSpent: requiredAmount
945
970
  };
946
971
  } catch (error) {
972
+ const errorMsg = error instanceof Error ? error.message : String(error);
973
+ console.error(`[BalanceManager.createProviderToken] FAILURE: Mint ${candidateMint} failed with error: ${errorMsg}`);
947
974
  if (error instanceof Error) {
948
- lastError = error.message;
975
+ lastError = errorMsg;
949
976
  if (isNetworkErrorMessage(error.message)) {
977
+ console.warn(`[BalanceManager.createProviderToken] Network error from ${candidateMint}, trying next mint...`);
950
978
  continue;
951
979
  }
952
980
  }
@@ -956,6 +984,7 @@ var BalanceManager = class {
956
984
  };
957
985
  }
958
986
  }
987
+ console.error(`[BalanceManager.createProviderToken] FAILURE: All candidate mints exhausted - lastError=${lastError}, candidates=${JSON.stringify(candidates)}`);
959
988
  return {
960
989
  success: false,
961
990
  error: lastError || "All candidate mints failed while creating top up token"
@@ -1241,7 +1270,6 @@ var BalanceManager = class {
1241
1270
  });
1242
1271
  if (response.ok) {
1243
1272
  const data = await response.json();
1244
- console.log("TOKENA FASJDFAS", data);
1245
1273
  return {
1246
1274
  amount: data.balance,
1247
1275
  reserved: data.reserved ?? 0,
@@ -1614,22 +1642,101 @@ function calculateImageTokens(width, height, detail = "auto") {
1614
1642
  function isInsecureHttpUrl(url) {
1615
1643
  return url.startsWith("http://");
1616
1644
  }
1617
- var ProviderManager = class {
1645
+ var ProviderManager = class _ProviderManager {
1618
1646
  constructor(providerRegistry) {
1619
1647
  this.providerRegistry = providerRegistry;
1620
1648
  }
1621
1649
  failedProviders = /* @__PURE__ */ new Set();
1650
+ /** Track when each provider last failed (provider URL -> timestamp) */
1651
+ lastFailed = /* @__PURE__ */ new Map();
1652
+ /** Providers on cooldown: [provider_url, cooldown_started_timestamp][] */
1653
+ providersOnCoolDown = [];
1654
+ /** Cooldown duration in milliseconds (5 minutes) */
1655
+ static COOLDOWN_DURATION_MS = 5 * 60 * 1e3;
1656
+ /**
1657
+ * Clean up expired cooldown entries
1658
+ */
1659
+ cleanupExpiredCooldowns() {
1660
+ const now = Date.now();
1661
+ this.providersOnCoolDown = this.providersOnCoolDown.filter(
1662
+ ([, timestamp]) => now - timestamp < _ProviderManager.COOLDOWN_DURATION_MS
1663
+ );
1664
+ }
1665
+ /**
1666
+ * Get the cooldown duration in milliseconds
1667
+ */
1668
+ getCooldownDurationMs() {
1669
+ return _ProviderManager.COOLDOWN_DURATION_MS;
1670
+ }
1671
+ /**
1672
+ * Check if a provider is currently on cooldown
1673
+ */
1674
+ isOnCooldown(baseUrl) {
1675
+ this.cleanupExpiredCooldowns();
1676
+ return this.providersOnCoolDown.some(([url]) => url === baseUrl);
1677
+ }
1678
+ /**
1679
+ * Get all providers currently on cooldown
1680
+ */
1681
+ getProvidersOnCooldown() {
1682
+ this.cleanupExpiredCooldowns();
1683
+ return [...this.providersOnCoolDown];
1684
+ }
1622
1685
  /**
1623
1686
  * Reset the failed providers list
1624
1687
  */
1625
1688
  resetFailedProviders() {
1626
1689
  this.failedProviders.clear();
1627
1690
  }
1691
+ /**
1692
+ * Get the last failed timestamp for a provider
1693
+ */
1694
+ getLastFailed(baseUrl) {
1695
+ return this.lastFailed.get(baseUrl);
1696
+ }
1697
+ /**
1698
+ * Get all providers with their last failed timestamps
1699
+ */
1700
+ getAllLastFailed() {
1701
+ return new Map(this.lastFailed);
1702
+ }
1628
1703
  /**
1629
1704
  * Mark a provider as failed
1705
+ * If a provider fails twice within 5 minutes, it's added to cooldown
1630
1706
  */
1631
1707
  markFailed(baseUrl) {
1708
+ const now = Date.now();
1709
+ const lastFailure = this.lastFailed.get(baseUrl);
1710
+ this.lastFailed.set(baseUrl, now);
1632
1711
  this.failedProviders.add(baseUrl);
1712
+ if (lastFailure !== void 0 && now - lastFailure < _ProviderManager.COOLDOWN_DURATION_MS) {
1713
+ if (!this.isOnCooldown(baseUrl)) {
1714
+ this.providersOnCoolDown.push([baseUrl, now]);
1715
+ console.log(
1716
+ `Provider ${baseUrl} added to cooldown after second failure within 5 minutes`
1717
+ );
1718
+ }
1719
+ }
1720
+ }
1721
+ /**
1722
+ * Remove a provider from cooldown (e.g., after successful request)
1723
+ */
1724
+ removeFromCooldown(baseUrl) {
1725
+ this.providersOnCoolDown = this.providersOnCoolDown.filter(
1726
+ ([url]) => url !== baseUrl
1727
+ );
1728
+ }
1729
+ /**
1730
+ * Clear all cooldown tracking
1731
+ */
1732
+ clearCooldowns() {
1733
+ this.providersOnCoolDown = [];
1734
+ }
1735
+ /**
1736
+ * Clear all failure tracking (lastFailed timestamps)
1737
+ */
1738
+ clearFailureHistory() {
1739
+ this.lastFailed.clear();
1633
1740
  }
1634
1741
  /**
1635
1742
  * Check if a provider has failed
@@ -1658,7 +1765,7 @@ var ProviderManager = class {
1658
1765
  const allProviders = this.providerRegistry.getAllProvidersModels();
1659
1766
  const candidates = [];
1660
1767
  for (const [baseUrl, models] of Object.entries(allProviders)) {
1661
- if (baseUrl === currentBaseUrl || this.failedProviders.has(baseUrl) || disabledProviders.has(baseUrl)) {
1768
+ if (baseUrl === currentBaseUrl || this.failedProviders.has(baseUrl) || disabledProviders.has(baseUrl) || this.isOnCooldown(baseUrl)) {
1662
1769
  continue;
1663
1770
  }
1664
1771
  if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl))) {
@@ -1705,6 +1812,7 @@ var ProviderManager = class {
1705
1812
  const torMode = isTorContext();
1706
1813
  for (const [baseUrl, models] of Object.entries(allProviders)) {
1707
1814
  if (disabledProviders.has(baseUrl)) continue;
1815
+ if (this.isOnCooldown(baseUrl)) continue;
1708
1816
  if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl)))
1709
1817
  continue;
1710
1818
  const model = models.find((m) => m.id === modelId);
@@ -1728,6 +1836,7 @@ var ProviderManager = class {
1728
1836
  const results = [];
1729
1837
  for (const [baseUrl, models] of Object.entries(allModels)) {
1730
1838
  if (!includeDisabled && disabledProviders.has(baseUrl)) continue;
1839
+ if (this.isOnCooldown(baseUrl)) continue;
1731
1840
  if (torMode && !baseUrl.includes(".onion")) continue;
1732
1841
  if (!torMode && (baseUrl.includes(".onion") || isInsecureHttpUrl(baseUrl)))
1733
1842
  continue;
@@ -2141,7 +2250,6 @@ var RoutstrClient = class {
2141
2250
  body: body === void 0 || method === "GET" ? void 0 : JSON.stringify(body)
2142
2251
  });
2143
2252
  if (this.mode === "xcashu") this._log("DEBUG", "response,", response);
2144
- this._log("DEBUG", "response,", response);
2145
2253
  response.baseUrl = baseUrl;
2146
2254
  response.token = token;
2147
2255
  if (!response.ok) {
@@ -2164,7 +2272,7 @@ var RoutstrClient = class {
2164
2272
  }
2165
2273
  return response;
2166
2274
  } catch (error) {
2167
- if (this._isNetworkError(error?.message || "")) {
2275
+ if (isNetworkErrorMessage(error?.message || "")) {
2168
2276
  return await this._handleErrorResponse(
2169
2277
  params,
2170
2278
  token,
@@ -2299,7 +2407,13 @@ var RoutstrClient = class {
2299
2407
  "DEBUG",
2300
2408
  `[RoutstrClient] _handleErrorResponse: Insufficient balance, need=${required}, have=${available}`
2301
2409
  );
2302
- throw new InsufficientBalanceError(required, available, 0, "", message);
2410
+ throw new InsufficientBalanceError(
2411
+ required,
2412
+ available,
2413
+ 0,
2414
+ "",
2415
+ message
2416
+ );
2303
2417
  } else {
2304
2418
  this._log(
2305
2419
  "DEBUG",
@@ -2512,7 +2626,10 @@ var RoutstrClient = class {
2512
2626
  retryCount: 0
2513
2627
  });
2514
2628
  }
2515
- throw new FailoverError(baseUrl, Array.from(this.providerManager.getFailedProviders()));
2629
+ throw new FailoverError(
2630
+ baseUrl,
2631
+ Array.from(this.providerManager.getFailedProviders())
2632
+ );
2516
2633
  }
2517
2634
  /**
2518
2635
  * Handle post-response balance update for all modes
@@ -2662,12 +2779,6 @@ var RoutstrClient = class {
2662
2779
  const distribution = this.storageAdapter.getCachedTokenDistribution();
2663
2780
  return distribution.reduce((total, item) => total + item.amount, 0);
2664
2781
  }
2665
- /**
2666
- * Check if error message indicates a network error
2667
- */
2668
- _isNetworkError(message) {
2669
- return message.includes("NetworkError when attempting to fetch resource") || message.includes("Failed to fetch") || message.includes("Load failed");
2670
- }
2671
2782
  /**
2672
2783
  * Handle errors and notify callbacks
2673
2784
  */