@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.
@@ -79,7 +79,7 @@ var auditLogger = AuditLogger.getInstance();
79
79
 
80
80
  // wallet/tokenUtils.ts
81
81
  function isNetworkErrorMessage(message) {
82
- return message.includes("NetworkError when attempting to fetch resource") || message.includes("Failed to fetch") || message.includes("Load failed");
82
+ 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");
83
83
  }
84
84
  function getBalanceInSats(balance, unit) {
85
85
  return unit === "msat" ? balance / 1e3 : balance;
@@ -369,7 +369,26 @@ var CashuSpender = class {
369
369
  }
370
370
  }
371
371
  if (token && baseUrl) {
372
- this.storageAdapter.setToken(baseUrl, token);
372
+ try {
373
+ this.storageAdapter.setToken(baseUrl, token);
374
+ } catch (error) {
375
+ if (error instanceof Error && error.message.includes("Token already exists")) {
376
+ this._log(
377
+ "DEBUG",
378
+ `[CashuSpender] _spendInternal: Token already exists for ${baseUrl}, receiving newly created token and using existing`
379
+ );
380
+ const receiveResult = await this.receiveToken(token);
381
+ if (receiveResult.success) {
382
+ this._log(
383
+ "DEBUG",
384
+ `[CashuSpender] _spendInternal: Token restored successfully, amount=${receiveResult.amount}`
385
+ );
386
+ }
387
+ token = this.storageAdapter.getToken(baseUrl);
388
+ } else {
389
+ throw error;
390
+ }
391
+ }
373
392
  }
374
393
  this._logTransaction("spend", {
375
394
  amount: spentAmount,
@@ -847,7 +866,9 @@ var BalanceManager = class {
847
866
  p2pkPubkey
848
867
  } = options;
849
868
  const adjustedAmount = Math.ceil(amount);
869
+ console.log(`[BalanceManager.createProviderToken] Starting: baseUrl=${baseUrl}, mintUrl=${mintUrl}, amount=${amount}, adjustedAmount=${adjustedAmount}, retryCount=${retryCount}`);
850
870
  if (!adjustedAmount || isNaN(adjustedAmount)) {
871
+ console.error(`[BalanceManager.createProviderToken] FAILURE: Invalid amount - amount=${amount}, adjustedAmount=${adjustedAmount}`);
851
872
  return { success: false, error: "Invalid top up amount" };
852
873
  }
853
874
  const balanceState = await this.getBalanceState();
@@ -878,6 +899,7 @@ var BalanceManager = class {
878
899
  { url: "", balance: 0 }
879
900
  ).url
880
901
  );
902
+ console.error(`[BalanceManager.createProviderToken] FAILURE: Insufficient balance - required=${adjustedAmount}, available=${totalMintBalance + targetProviderBalance}, totalMintBalance=${totalMintBalance}, targetProviderBalance=${targetProviderBalance}, refundableProviderBalance=${refundableProviderBalance}`);
881
903
  return { success: false, error: error.message };
882
904
  }
883
905
  if (targetProviderBalance >= adjustedAmount) {
@@ -919,6 +941,7 @@ var BalanceManager = class {
919
941
  maxMintUrl = mintUrl2;
920
942
  }
921
943
  }
944
+ console.error(`[BalanceManager.createProviderToken] FAILURE: No candidate mints found - requiredAmount=${requiredAmount}, totalMintBalance=${totalMintBalance}, maxBalance=${maxBalance}, maxMintUrl=${maxMintUrl}, providerMints=${JSON.stringify(providerMints)}`);
922
945
  const error = new InsufficientBalanceError(
923
946
  adjustedAmount,
924
947
  totalMintBalance,
@@ -930,11 +953,13 @@ var BalanceManager = class {
930
953
  let lastError;
931
954
  for (const candidateMint of candidates) {
932
955
  try {
956
+ console.log(`[BalanceManager.createProviderToken] Attempting mint: ${candidateMint}, amount: ${requiredAmount}`);
933
957
  const token = await this.walletAdapter.sendToken(
934
958
  candidateMint,
935
959
  requiredAmount,
936
960
  p2pkPubkey
937
961
  );
962
+ console.log(`[BalanceManager.createProviderToken] SUCCESS: Token created from mint ${candidateMint}`);
938
963
  return {
939
964
  success: true,
940
965
  token,
@@ -942,9 +967,12 @@ var BalanceManager = class {
942
967
  amountSpent: requiredAmount
943
968
  };
944
969
  } catch (error) {
970
+ const errorMsg = error instanceof Error ? error.message : String(error);
971
+ console.error(`[BalanceManager.createProviderToken] FAILURE: Mint ${candidateMint} failed with error: ${errorMsg}`);
945
972
  if (error instanceof Error) {
946
- lastError = error.message;
973
+ lastError = errorMsg;
947
974
  if (isNetworkErrorMessage(error.message)) {
975
+ console.warn(`[BalanceManager.createProviderToken] Network error from ${candidateMint}, trying next mint...`);
948
976
  continue;
949
977
  }
950
978
  }
@@ -954,6 +982,7 @@ var BalanceManager = class {
954
982
  };
955
983
  }
956
984
  }
985
+ console.error(`[BalanceManager.createProviderToken] FAILURE: All candidate mints exhausted - lastError=${lastError}, candidates=${JSON.stringify(candidates)}`);
957
986
  return {
958
987
  success: false,
959
988
  error: lastError || "All candidate mints failed while creating top up token"
@@ -1239,7 +1268,6 @@ var BalanceManager = class {
1239
1268
  });
1240
1269
  if (response.ok) {
1241
1270
  const data = await response.json();
1242
- console.log("TOKENA FASJDFAS", data);
1243
1271
  return {
1244
1272
  amount: data.balance,
1245
1273
  reserved: data.reserved ?? 0,
@@ -1612,22 +1640,101 @@ function calculateImageTokens(width, height, detail = "auto") {
1612
1640
  function isInsecureHttpUrl(url) {
1613
1641
  return url.startsWith("http://");
1614
1642
  }
1615
- var ProviderManager = class {
1643
+ var ProviderManager = class _ProviderManager {
1616
1644
  constructor(providerRegistry) {
1617
1645
  this.providerRegistry = providerRegistry;
1618
1646
  }
1619
1647
  failedProviders = /* @__PURE__ */ new Set();
1648
+ /** Track when each provider last failed (provider URL -> timestamp) */
1649
+ lastFailed = /* @__PURE__ */ new Map();
1650
+ /** Providers on cooldown: [provider_url, cooldown_started_timestamp][] */
1651
+ providersOnCoolDown = [];
1652
+ /** Cooldown duration in milliseconds (5 minutes) */
1653
+ static COOLDOWN_DURATION_MS = 5 * 60 * 1e3;
1654
+ /**
1655
+ * Clean up expired cooldown entries
1656
+ */
1657
+ cleanupExpiredCooldowns() {
1658
+ const now = Date.now();
1659
+ this.providersOnCoolDown = this.providersOnCoolDown.filter(
1660
+ ([, timestamp]) => now - timestamp < _ProviderManager.COOLDOWN_DURATION_MS
1661
+ );
1662
+ }
1663
+ /**
1664
+ * Get the cooldown duration in milliseconds
1665
+ */
1666
+ getCooldownDurationMs() {
1667
+ return _ProviderManager.COOLDOWN_DURATION_MS;
1668
+ }
1669
+ /**
1670
+ * Check if a provider is currently on cooldown
1671
+ */
1672
+ isOnCooldown(baseUrl) {
1673
+ this.cleanupExpiredCooldowns();
1674
+ return this.providersOnCoolDown.some(([url]) => url === baseUrl);
1675
+ }
1676
+ /**
1677
+ * Get all providers currently on cooldown
1678
+ */
1679
+ getProvidersOnCooldown() {
1680
+ this.cleanupExpiredCooldowns();
1681
+ return [...this.providersOnCoolDown];
1682
+ }
1620
1683
  /**
1621
1684
  * Reset the failed providers list
1622
1685
  */
1623
1686
  resetFailedProviders() {
1624
1687
  this.failedProviders.clear();
1625
1688
  }
1689
+ /**
1690
+ * Get the last failed timestamp for a provider
1691
+ */
1692
+ getLastFailed(baseUrl) {
1693
+ return this.lastFailed.get(baseUrl);
1694
+ }
1695
+ /**
1696
+ * Get all providers with their last failed timestamps
1697
+ */
1698
+ getAllLastFailed() {
1699
+ return new Map(this.lastFailed);
1700
+ }
1626
1701
  /**
1627
1702
  * Mark a provider as failed
1703
+ * If a provider fails twice within 5 minutes, it's added to cooldown
1628
1704
  */
1629
1705
  markFailed(baseUrl) {
1706
+ const now = Date.now();
1707
+ const lastFailure = this.lastFailed.get(baseUrl);
1708
+ this.lastFailed.set(baseUrl, now);
1630
1709
  this.failedProviders.add(baseUrl);
1710
+ if (lastFailure !== void 0 && now - lastFailure < _ProviderManager.COOLDOWN_DURATION_MS) {
1711
+ if (!this.isOnCooldown(baseUrl)) {
1712
+ this.providersOnCoolDown.push([baseUrl, now]);
1713
+ console.log(
1714
+ `Provider ${baseUrl} added to cooldown after second failure within 5 minutes`
1715
+ );
1716
+ }
1717
+ }
1718
+ }
1719
+ /**
1720
+ * Remove a provider from cooldown (e.g., after successful request)
1721
+ */
1722
+ removeFromCooldown(baseUrl) {
1723
+ this.providersOnCoolDown = this.providersOnCoolDown.filter(
1724
+ ([url]) => url !== baseUrl
1725
+ );
1726
+ }
1727
+ /**
1728
+ * Clear all cooldown tracking
1729
+ */
1730
+ clearCooldowns() {
1731
+ this.providersOnCoolDown = [];
1732
+ }
1733
+ /**
1734
+ * Clear all failure tracking (lastFailed timestamps)
1735
+ */
1736
+ clearFailureHistory() {
1737
+ this.lastFailed.clear();
1631
1738
  }
1632
1739
  /**
1633
1740
  * Check if a provider has failed
@@ -1656,7 +1763,7 @@ var ProviderManager = class {
1656
1763
  const allProviders = this.providerRegistry.getAllProvidersModels();
1657
1764
  const candidates = [];
1658
1765
  for (const [baseUrl, models] of Object.entries(allProviders)) {
1659
- if (baseUrl === currentBaseUrl || this.failedProviders.has(baseUrl) || disabledProviders.has(baseUrl)) {
1766
+ if (baseUrl === currentBaseUrl || this.failedProviders.has(baseUrl) || disabledProviders.has(baseUrl) || this.isOnCooldown(baseUrl)) {
1660
1767
  continue;
1661
1768
  }
1662
1769
  if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl))) {
@@ -1703,6 +1810,7 @@ var ProviderManager = class {
1703
1810
  const torMode = isTorContext();
1704
1811
  for (const [baseUrl, models] of Object.entries(allProviders)) {
1705
1812
  if (disabledProviders.has(baseUrl)) continue;
1813
+ if (this.isOnCooldown(baseUrl)) continue;
1706
1814
  if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl)))
1707
1815
  continue;
1708
1816
  const model = models.find((m) => m.id === modelId);
@@ -1726,6 +1834,7 @@ var ProviderManager = class {
1726
1834
  const results = [];
1727
1835
  for (const [baseUrl, models] of Object.entries(allModels)) {
1728
1836
  if (!includeDisabled && disabledProviders.has(baseUrl)) continue;
1837
+ if (this.isOnCooldown(baseUrl)) continue;
1729
1838
  if (torMode && !baseUrl.includes(".onion")) continue;
1730
1839
  if (!torMode && (baseUrl.includes(".onion") || isInsecureHttpUrl(baseUrl)))
1731
1840
  continue;
@@ -2139,7 +2248,6 @@ var RoutstrClient = class {
2139
2248
  body: body === void 0 || method === "GET" ? void 0 : JSON.stringify(body)
2140
2249
  });
2141
2250
  if (this.mode === "xcashu") this._log("DEBUG", "response,", response);
2142
- this._log("DEBUG", "response,", response);
2143
2251
  response.baseUrl = baseUrl;
2144
2252
  response.token = token;
2145
2253
  if (!response.ok) {
@@ -2162,7 +2270,7 @@ var RoutstrClient = class {
2162
2270
  }
2163
2271
  return response;
2164
2272
  } catch (error) {
2165
- if (this._isNetworkError(error?.message || "")) {
2273
+ if (isNetworkErrorMessage(error?.message || "")) {
2166
2274
  return await this._handleErrorResponse(
2167
2275
  params,
2168
2276
  token,
@@ -2297,7 +2405,13 @@ var RoutstrClient = class {
2297
2405
  "DEBUG",
2298
2406
  `[RoutstrClient] _handleErrorResponse: Insufficient balance, need=${required}, have=${available}`
2299
2407
  );
2300
- throw new InsufficientBalanceError(required, available, 0, "", message);
2408
+ throw new InsufficientBalanceError(
2409
+ required,
2410
+ available,
2411
+ 0,
2412
+ "",
2413
+ message
2414
+ );
2301
2415
  } else {
2302
2416
  this._log(
2303
2417
  "DEBUG",
@@ -2510,7 +2624,10 @@ var RoutstrClient = class {
2510
2624
  retryCount: 0
2511
2625
  });
2512
2626
  }
2513
- throw new FailoverError(baseUrl, Array.from(this.providerManager.getFailedProviders()));
2627
+ throw new FailoverError(
2628
+ baseUrl,
2629
+ Array.from(this.providerManager.getFailedProviders())
2630
+ );
2514
2631
  }
2515
2632
  /**
2516
2633
  * Handle post-response balance update for all modes
@@ -2660,12 +2777,6 @@ var RoutstrClient = class {
2660
2777
  const distribution = this.storageAdapter.getCachedTokenDistribution();
2661
2778
  return distribution.reduce((total, item) => total + item.amount, 0);
2662
2779
  }
2663
- /**
2664
- * Check if error message indicates a network error
2665
- */
2666
- _isNetworkError(message) {
2667
- return message.includes("NetworkError when attempting to fetch resource") || message.includes("Failed to fetch") || message.includes("Load failed");
2668
- }
2669
2780
  /**
2670
2781
  * Handle errors and notify callbacks
2671
2782
  */