@routstr/sdk 0.2.1 → 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
@@ -1635,6 +1742,12 @@ var ProviderManager = class {
1635
1742
  hasFailed(baseUrl) {
1636
1743
  return this.failedProviders.has(baseUrl);
1637
1744
  }
1745
+ /**
1746
+ * Get a copy of the failed providers set
1747
+ */
1748
+ getFailedProviders() {
1749
+ return new Set(this.failedProviders);
1750
+ }
1638
1751
  /**
1639
1752
  * Find the next best provider for a model
1640
1753
  * @param modelId The model ID to find a provider for
@@ -1650,7 +1763,7 @@ var ProviderManager = class {
1650
1763
  const allProviders = this.providerRegistry.getAllProvidersModels();
1651
1764
  const candidates = [];
1652
1765
  for (const [baseUrl, models] of Object.entries(allProviders)) {
1653
- if (baseUrl === currentBaseUrl || this.failedProviders.has(baseUrl) || disabledProviders.has(baseUrl)) {
1766
+ if (baseUrl === currentBaseUrl || this.failedProviders.has(baseUrl) || disabledProviders.has(baseUrl) || this.isOnCooldown(baseUrl)) {
1654
1767
  continue;
1655
1768
  }
1656
1769
  if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl))) {
@@ -1697,6 +1810,7 @@ var ProviderManager = class {
1697
1810
  const torMode = isTorContext();
1698
1811
  for (const [baseUrl, models] of Object.entries(allProviders)) {
1699
1812
  if (disabledProviders.has(baseUrl)) continue;
1813
+ if (this.isOnCooldown(baseUrl)) continue;
1700
1814
  if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl)))
1701
1815
  continue;
1702
1816
  const model = models.find((m) => m.id === modelId);
@@ -1720,6 +1834,7 @@ var ProviderManager = class {
1720
1834
  const results = [];
1721
1835
  for (const [baseUrl, models] of Object.entries(allModels)) {
1722
1836
  if (!includeDisabled && disabledProviders.has(baseUrl)) continue;
1837
+ if (this.isOnCooldown(baseUrl)) continue;
1723
1838
  if (torMode && !baseUrl.includes(".onion")) continue;
1724
1839
  if (!torMode && (baseUrl.includes(".onion") || isInsecureHttpUrl(baseUrl)))
1725
1840
  continue;
@@ -2133,7 +2248,6 @@ var RoutstrClient = class {
2133
2248
  body: body === void 0 || method === "GET" ? void 0 : JSON.stringify(body)
2134
2249
  });
2135
2250
  if (this.mode === "xcashu") this._log("DEBUG", "response,", response);
2136
- this._log("DEBUG", "response,", response);
2137
2251
  response.baseUrl = baseUrl;
2138
2252
  response.token = token;
2139
2253
  if (!response.ok) {
@@ -2156,7 +2270,7 @@ var RoutstrClient = class {
2156
2270
  }
2157
2271
  return response;
2158
2272
  } catch (error) {
2159
- if (this._isNetworkError(error?.message || "")) {
2273
+ if (isNetworkErrorMessage(error?.message || "")) {
2160
2274
  return await this._handleErrorResponse(
2161
2275
  params,
2162
2276
  token,
@@ -2291,7 +2405,13 @@ var RoutstrClient = class {
2291
2405
  "DEBUG",
2292
2406
  `[RoutstrClient] _handleErrorResponse: Insufficient balance, need=${required}, have=${available}`
2293
2407
  );
2294
- throw new InsufficientBalanceError(required, available, 0, "", message);
2408
+ throw new InsufficientBalanceError(
2409
+ required,
2410
+ available,
2411
+ 0,
2412
+ "",
2413
+ message
2414
+ );
2295
2415
  } else {
2296
2416
  this._log(
2297
2417
  "DEBUG",
@@ -2504,7 +2624,10 @@ var RoutstrClient = class {
2504
2624
  retryCount: 0
2505
2625
  });
2506
2626
  }
2507
- throw new FailoverError(baseUrl, Array.from(this.providerManager));
2627
+ throw new FailoverError(
2628
+ baseUrl,
2629
+ Array.from(this.providerManager.getFailedProviders())
2630
+ );
2508
2631
  }
2509
2632
  /**
2510
2633
  * Handle post-response balance update for all modes
@@ -2654,12 +2777,6 @@ var RoutstrClient = class {
2654
2777
  const distribution = this.storageAdapter.getCachedTokenDistribution();
2655
2778
  return distribution.reduce((total, item) => total + item.amount, 0);
2656
2779
  }
2657
- /**
2658
- * Check if error message indicates a network error
2659
- */
2660
- _isNetworkError(message) {
2661
- return message.includes("NetworkError when attempting to fetch resource") || message.includes("Failed to fetch") || message.includes("Load failed");
2662
- }
2663
2780
  /**
2664
2781
  * Handle errors and notify callbacks
2665
2782
  */