@routstr/sdk 0.2.2 → 0.2.4

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,13 @@ var BalanceManager = class {
849
868
  p2pkPubkey
850
869
  } = options;
851
870
  const adjustedAmount = Math.ceil(amount);
871
+ console.log(
872
+ `[BalanceManager.createProviderToken] Starting: baseUrl=${baseUrl}, mintUrl=${mintUrl}, amount=${amount}, adjustedAmount=${adjustedAmount}, retryCount=${retryCount}`
873
+ );
852
874
  if (!adjustedAmount || isNaN(adjustedAmount)) {
875
+ console.error(
876
+ `[BalanceManager.createProviderToken] FAILURE: Invalid amount - amount=${amount}, adjustedAmount=${adjustedAmount}`
877
+ );
853
878
  return { success: false, error: "Invalid top up amount" };
854
879
  }
855
880
  const balanceState = await this.getBalanceState();
@@ -880,14 +905,11 @@ var BalanceManager = class {
880
905
  { url: "", balance: 0 }
881
906
  ).url
882
907
  );
908
+ console.error(
909
+ `[BalanceManager.createProviderToken] FAILURE: Insufficient balance - required=${adjustedAmount}, available=${totalMintBalance + targetProviderBalance}, totalMintBalance=${totalMintBalance}, targetProviderBalance=${targetProviderBalance}, refundableProviderBalance=${refundableProviderBalance}`
910
+ );
883
911
  return { success: false, error: error.message };
884
912
  }
885
- if (targetProviderBalance >= adjustedAmount) {
886
- return {
887
- success: true,
888
- amountSpent: 0
889
- };
890
- }
891
913
  const providerMints = baseUrl && this.providerRegistry ? this.providerRegistry.getProviderMints(baseUrl) : [];
892
914
  let requiredAmount = adjustedAmount;
893
915
  const supportedMintsOnly = providerMints.length > 0;
@@ -921,6 +943,9 @@ var BalanceManager = class {
921
943
  maxMintUrl = mintUrl2;
922
944
  }
923
945
  }
946
+ console.error(
947
+ `[BalanceManager.createProviderToken] FAILURE: No candidate mints found - requiredAmount=${requiredAmount}, totalMintBalance=${totalMintBalance}, maxBalance=${maxBalance}, maxMintUrl=${maxMintUrl}, providerMints=${JSON.stringify(providerMints)}`
948
+ );
924
949
  const error = new InsufficientBalanceError(
925
950
  adjustedAmount,
926
951
  totalMintBalance,
@@ -932,11 +957,17 @@ var BalanceManager = class {
932
957
  let lastError;
933
958
  for (const candidateMint of candidates) {
934
959
  try {
960
+ console.log(
961
+ `[BalanceManager.createProviderToken] Attempting mint: ${candidateMint}, amount: ${requiredAmount}`
962
+ );
935
963
  const token = await this.walletAdapter.sendToken(
936
964
  candidateMint,
937
965
  requiredAmount,
938
966
  p2pkPubkey
939
967
  );
968
+ console.log(
969
+ `[BalanceManager.createProviderToken] SUCCESS: Token created from mint ${candidateMint}`
970
+ );
940
971
  return {
941
972
  success: true,
942
973
  token,
@@ -944,9 +975,16 @@ var BalanceManager = class {
944
975
  amountSpent: requiredAmount
945
976
  };
946
977
  } catch (error) {
978
+ const errorMsg = error instanceof Error ? error.message : String(error);
979
+ console.error(
980
+ `[BalanceManager.createProviderToken] FAILURE: Mint ${candidateMint} failed with error: ${errorMsg}`
981
+ );
947
982
  if (error instanceof Error) {
948
- lastError = error.message;
983
+ lastError = errorMsg;
949
984
  if (isNetworkErrorMessage(error.message)) {
985
+ console.warn(
986
+ `[BalanceManager.createProviderToken] Network error from ${candidateMint}, trying next mint...`
987
+ );
950
988
  continue;
951
989
  }
952
990
  }
@@ -956,6 +994,9 @@ var BalanceManager = class {
956
994
  };
957
995
  }
958
996
  }
997
+ console.error(
998
+ `[BalanceManager.createProviderToken] FAILURE: All candidate mints exhausted - lastError=${lastError}, candidates=${JSON.stringify(candidates)}`
999
+ );
959
1000
  return {
960
1001
  success: false,
961
1002
  error: lastError || "All candidate mints failed while creating top up token"
@@ -1241,7 +1282,6 @@ var BalanceManager = class {
1241
1282
  });
1242
1283
  if (response.ok) {
1243
1284
  const data = await response.json();
1244
- console.log("TOKENA FASJDFAS", data);
1245
1285
  return {
1246
1286
  amount: data.balance,
1247
1287
  reserved: data.reserved ?? 0,
@@ -1614,22 +1654,101 @@ function calculateImageTokens(width, height, detail = "auto") {
1614
1654
  function isInsecureHttpUrl(url) {
1615
1655
  return url.startsWith("http://");
1616
1656
  }
1617
- var ProviderManager = class {
1657
+ var ProviderManager = class _ProviderManager {
1618
1658
  constructor(providerRegistry) {
1619
1659
  this.providerRegistry = providerRegistry;
1620
1660
  }
1621
1661
  failedProviders = /* @__PURE__ */ new Set();
1662
+ /** Track when each provider last failed (provider URL -> timestamp) */
1663
+ lastFailed = /* @__PURE__ */ new Map();
1664
+ /** Providers on cooldown: [provider_url, cooldown_started_timestamp][] */
1665
+ providersOnCoolDown = [];
1666
+ /** Cooldown duration in milliseconds (5 minutes) */
1667
+ static COOLDOWN_DURATION_MS = 5 * 60 * 1e3;
1668
+ /**
1669
+ * Clean up expired cooldown entries
1670
+ */
1671
+ cleanupExpiredCooldowns() {
1672
+ const now = Date.now();
1673
+ this.providersOnCoolDown = this.providersOnCoolDown.filter(
1674
+ ([, timestamp]) => now - timestamp < _ProviderManager.COOLDOWN_DURATION_MS
1675
+ );
1676
+ }
1677
+ /**
1678
+ * Get the cooldown duration in milliseconds
1679
+ */
1680
+ getCooldownDurationMs() {
1681
+ return _ProviderManager.COOLDOWN_DURATION_MS;
1682
+ }
1683
+ /**
1684
+ * Check if a provider is currently on cooldown
1685
+ */
1686
+ isOnCooldown(baseUrl) {
1687
+ this.cleanupExpiredCooldowns();
1688
+ return this.providersOnCoolDown.some(([url]) => url === baseUrl);
1689
+ }
1690
+ /**
1691
+ * Get all providers currently on cooldown
1692
+ */
1693
+ getProvidersOnCooldown() {
1694
+ this.cleanupExpiredCooldowns();
1695
+ return [...this.providersOnCoolDown];
1696
+ }
1622
1697
  /**
1623
1698
  * Reset the failed providers list
1624
1699
  */
1625
1700
  resetFailedProviders() {
1626
1701
  this.failedProviders.clear();
1627
1702
  }
1703
+ /**
1704
+ * Get the last failed timestamp for a provider
1705
+ */
1706
+ getLastFailed(baseUrl) {
1707
+ return this.lastFailed.get(baseUrl);
1708
+ }
1709
+ /**
1710
+ * Get all providers with their last failed timestamps
1711
+ */
1712
+ getAllLastFailed() {
1713
+ return new Map(this.lastFailed);
1714
+ }
1628
1715
  /**
1629
1716
  * Mark a provider as failed
1717
+ * If a provider fails twice within 5 minutes, it's added to cooldown
1630
1718
  */
1631
1719
  markFailed(baseUrl) {
1720
+ const now = Date.now();
1721
+ const lastFailure = this.lastFailed.get(baseUrl);
1722
+ this.lastFailed.set(baseUrl, now);
1632
1723
  this.failedProviders.add(baseUrl);
1724
+ if (lastFailure !== void 0 && now - lastFailure < _ProviderManager.COOLDOWN_DURATION_MS) {
1725
+ if (!this.isOnCooldown(baseUrl)) {
1726
+ this.providersOnCoolDown.push([baseUrl, now]);
1727
+ console.log(
1728
+ `Provider ${baseUrl} added to cooldown after second failure within 5 minutes`
1729
+ );
1730
+ }
1731
+ }
1732
+ }
1733
+ /**
1734
+ * Remove a provider from cooldown (e.g., after successful request)
1735
+ */
1736
+ removeFromCooldown(baseUrl) {
1737
+ this.providersOnCoolDown = this.providersOnCoolDown.filter(
1738
+ ([url]) => url !== baseUrl
1739
+ );
1740
+ }
1741
+ /**
1742
+ * Clear all cooldown tracking
1743
+ */
1744
+ clearCooldowns() {
1745
+ this.providersOnCoolDown = [];
1746
+ }
1747
+ /**
1748
+ * Clear all failure tracking (lastFailed timestamps)
1749
+ */
1750
+ clearFailureHistory() {
1751
+ this.lastFailed.clear();
1633
1752
  }
1634
1753
  /**
1635
1754
  * Check if a provider has failed
@@ -1658,7 +1777,7 @@ var ProviderManager = class {
1658
1777
  const allProviders = this.providerRegistry.getAllProvidersModels();
1659
1778
  const candidates = [];
1660
1779
  for (const [baseUrl, models] of Object.entries(allProviders)) {
1661
- if (baseUrl === currentBaseUrl || this.failedProviders.has(baseUrl) || disabledProviders.has(baseUrl)) {
1780
+ if (baseUrl === currentBaseUrl || this.failedProviders.has(baseUrl) || disabledProviders.has(baseUrl) || this.isOnCooldown(baseUrl)) {
1662
1781
  continue;
1663
1782
  }
1664
1783
  if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl))) {
@@ -1705,6 +1824,7 @@ var ProviderManager = class {
1705
1824
  const torMode = isTorContext();
1706
1825
  for (const [baseUrl, models] of Object.entries(allProviders)) {
1707
1826
  if (disabledProviders.has(baseUrl)) continue;
1827
+ if (this.isOnCooldown(baseUrl)) continue;
1708
1828
  if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl)))
1709
1829
  continue;
1710
1830
  const model = models.find((m) => m.id === modelId);
@@ -1728,6 +1848,7 @@ var ProviderManager = class {
1728
1848
  const results = [];
1729
1849
  for (const [baseUrl, models] of Object.entries(allModels)) {
1730
1850
  if (!includeDisabled && disabledProviders.has(baseUrl)) continue;
1851
+ if (this.isOnCooldown(baseUrl)) continue;
1731
1852
  if (torMode && !baseUrl.includes(".onion")) continue;
1732
1853
  if (!torMode && (baseUrl.includes(".onion") || isInsecureHttpUrl(baseUrl)))
1733
1854
  continue;
@@ -2141,7 +2262,6 @@ var RoutstrClient = class {
2141
2262
  body: body === void 0 || method === "GET" ? void 0 : JSON.stringify(body)
2142
2263
  });
2143
2264
  if (this.mode === "xcashu") this._log("DEBUG", "response,", response);
2144
- this._log("DEBUG", "response,", response);
2145
2265
  response.baseUrl = baseUrl;
2146
2266
  response.token = token;
2147
2267
  if (!response.ok) {
@@ -2164,7 +2284,7 @@ var RoutstrClient = class {
2164
2284
  }
2165
2285
  return response;
2166
2286
  } catch (error) {
2167
- if (this._isNetworkError(error?.message || "")) {
2287
+ if (isNetworkErrorMessage(error?.message || "")) {
2168
2288
  return await this._handleErrorResponse(
2169
2289
  params,
2170
2290
  token,
@@ -2299,7 +2419,13 @@ var RoutstrClient = class {
2299
2419
  "DEBUG",
2300
2420
  `[RoutstrClient] _handleErrorResponse: Insufficient balance, need=${required}, have=${available}`
2301
2421
  );
2302
- throw new InsufficientBalanceError(required, available, 0, "", message);
2422
+ throw new InsufficientBalanceError(
2423
+ required,
2424
+ available,
2425
+ 0,
2426
+ "",
2427
+ message
2428
+ );
2303
2429
  } else {
2304
2430
  this._log(
2305
2431
  "DEBUG",
@@ -2512,7 +2638,10 @@ var RoutstrClient = class {
2512
2638
  retryCount: 0
2513
2639
  });
2514
2640
  }
2515
- throw new FailoverError(baseUrl, Array.from(this.providerManager.getFailedProviders()));
2641
+ throw new FailoverError(
2642
+ baseUrl,
2643
+ Array.from(this.providerManager.getFailedProviders())
2644
+ );
2516
2645
  }
2517
2646
  /**
2518
2647
  * Handle post-response balance update for all modes
@@ -2662,12 +2791,6 @@ var RoutstrClient = class {
2662
2791
  const distribution = this.storageAdapter.getCachedTokenDistribution();
2663
2792
  return distribution.reduce((total, item) => total + item.amount, 0);
2664
2793
  }
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
2794
  /**
2672
2795
  * Handle errors and notify callbacks
2673
2796
  */