@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.
package/dist/index.js CHANGED
@@ -728,7 +728,7 @@ var auditLogger = AuditLogger.getInstance();
728
728
 
729
729
  // wallet/tokenUtils.ts
730
730
  function isNetworkErrorMessage(message) {
731
- return message.includes("NetworkError when attempting to fetch resource") || message.includes("Failed to fetch") || message.includes("Load failed");
731
+ 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");
732
732
  }
733
733
  function getBalanceInSats(balance, unit) {
734
734
  return unit === "msat" ? balance / 1e3 : balance;
@@ -1018,7 +1018,26 @@ var CashuSpender = class {
1018
1018
  }
1019
1019
  }
1020
1020
  if (token && baseUrl) {
1021
- this.storageAdapter.setToken(baseUrl, token);
1021
+ try {
1022
+ this.storageAdapter.setToken(baseUrl, token);
1023
+ } catch (error) {
1024
+ if (error instanceof Error && error.message.includes("Token already exists")) {
1025
+ this._log(
1026
+ "DEBUG",
1027
+ `[CashuSpender] _spendInternal: Token already exists for ${baseUrl}, receiving newly created token and using existing`
1028
+ );
1029
+ const receiveResult = await this.receiveToken(token);
1030
+ if (receiveResult.success) {
1031
+ this._log(
1032
+ "DEBUG",
1033
+ `[CashuSpender] _spendInternal: Token restored successfully, amount=${receiveResult.amount}`
1034
+ );
1035
+ }
1036
+ token = this.storageAdapter.getToken(baseUrl);
1037
+ } else {
1038
+ throw error;
1039
+ }
1040
+ }
1022
1041
  }
1023
1042
  this._logTransaction("spend", {
1024
1043
  amount: spentAmount,
@@ -1496,7 +1515,9 @@ var BalanceManager = class {
1496
1515
  p2pkPubkey
1497
1516
  } = options;
1498
1517
  const adjustedAmount = Math.ceil(amount);
1518
+ console.log(`[BalanceManager.createProviderToken] Starting: baseUrl=${baseUrl}, mintUrl=${mintUrl}, amount=${amount}, adjustedAmount=${adjustedAmount}, retryCount=${retryCount}`);
1499
1519
  if (!adjustedAmount || isNaN(adjustedAmount)) {
1520
+ console.error(`[BalanceManager.createProviderToken] FAILURE: Invalid amount - amount=${amount}, adjustedAmount=${adjustedAmount}`);
1500
1521
  return { success: false, error: "Invalid top up amount" };
1501
1522
  }
1502
1523
  const balanceState = await this.getBalanceState();
@@ -1527,6 +1548,7 @@ var BalanceManager = class {
1527
1548
  { url: "", balance: 0 }
1528
1549
  ).url
1529
1550
  );
1551
+ console.error(`[BalanceManager.createProviderToken] FAILURE: Insufficient balance - required=${adjustedAmount}, available=${totalMintBalance + targetProviderBalance}, totalMintBalance=${totalMintBalance}, targetProviderBalance=${targetProviderBalance}, refundableProviderBalance=${refundableProviderBalance}`);
1530
1552
  return { success: false, error: error.message };
1531
1553
  }
1532
1554
  if (targetProviderBalance >= adjustedAmount) {
@@ -1568,6 +1590,7 @@ var BalanceManager = class {
1568
1590
  maxMintUrl = mintUrl2;
1569
1591
  }
1570
1592
  }
1593
+ console.error(`[BalanceManager.createProviderToken] FAILURE: No candidate mints found - requiredAmount=${requiredAmount}, totalMintBalance=${totalMintBalance}, maxBalance=${maxBalance}, maxMintUrl=${maxMintUrl}, providerMints=${JSON.stringify(providerMints)}`);
1571
1594
  const error = new InsufficientBalanceError(
1572
1595
  adjustedAmount,
1573
1596
  totalMintBalance,
@@ -1579,11 +1602,13 @@ var BalanceManager = class {
1579
1602
  let lastError;
1580
1603
  for (const candidateMint of candidates) {
1581
1604
  try {
1605
+ console.log(`[BalanceManager.createProviderToken] Attempting mint: ${candidateMint}, amount: ${requiredAmount}`);
1582
1606
  const token = await this.walletAdapter.sendToken(
1583
1607
  candidateMint,
1584
1608
  requiredAmount,
1585
1609
  p2pkPubkey
1586
1610
  );
1611
+ console.log(`[BalanceManager.createProviderToken] SUCCESS: Token created from mint ${candidateMint}`);
1587
1612
  return {
1588
1613
  success: true,
1589
1614
  token,
@@ -1591,9 +1616,12 @@ var BalanceManager = class {
1591
1616
  amountSpent: requiredAmount
1592
1617
  };
1593
1618
  } catch (error) {
1619
+ const errorMsg = error instanceof Error ? error.message : String(error);
1620
+ console.error(`[BalanceManager.createProviderToken] FAILURE: Mint ${candidateMint} failed with error: ${errorMsg}`);
1594
1621
  if (error instanceof Error) {
1595
- lastError = error.message;
1622
+ lastError = errorMsg;
1596
1623
  if (isNetworkErrorMessage(error.message)) {
1624
+ console.warn(`[BalanceManager.createProviderToken] Network error from ${candidateMint}, trying next mint...`);
1597
1625
  continue;
1598
1626
  }
1599
1627
  }
@@ -1603,6 +1631,7 @@ var BalanceManager = class {
1603
1631
  };
1604
1632
  }
1605
1633
  }
1634
+ console.error(`[BalanceManager.createProviderToken] FAILURE: All candidate mints exhausted - lastError=${lastError}, candidates=${JSON.stringify(candidates)}`);
1606
1635
  return {
1607
1636
  success: false,
1608
1637
  error: lastError || "All candidate mints failed while creating top up token"
@@ -1888,7 +1917,6 @@ var BalanceManager = class {
1888
1917
  });
1889
1918
  if (response.ok) {
1890
1919
  const data = await response.json();
1891
- console.log("TOKENA FASJDFAS", data);
1892
1920
  return {
1893
1921
  amount: data.balance,
1894
1922
  reserved: data.reserved ?? 0,
@@ -2317,22 +2345,101 @@ function calculateImageTokens(width, height, detail = "auto") {
2317
2345
  function isInsecureHttpUrl(url) {
2318
2346
  return url.startsWith("http://");
2319
2347
  }
2320
- var ProviderManager = class {
2348
+ var ProviderManager = class _ProviderManager {
2321
2349
  constructor(providerRegistry) {
2322
2350
  this.providerRegistry = providerRegistry;
2323
2351
  }
2324
2352
  failedProviders = /* @__PURE__ */ new Set();
2353
+ /** Track when each provider last failed (provider URL -> timestamp) */
2354
+ lastFailed = /* @__PURE__ */ new Map();
2355
+ /** Providers on cooldown: [provider_url, cooldown_started_timestamp][] */
2356
+ providersOnCoolDown = [];
2357
+ /** Cooldown duration in milliseconds (5 minutes) */
2358
+ static COOLDOWN_DURATION_MS = 5 * 60 * 1e3;
2359
+ /**
2360
+ * Clean up expired cooldown entries
2361
+ */
2362
+ cleanupExpiredCooldowns() {
2363
+ const now = Date.now();
2364
+ this.providersOnCoolDown = this.providersOnCoolDown.filter(
2365
+ ([, timestamp]) => now - timestamp < _ProviderManager.COOLDOWN_DURATION_MS
2366
+ );
2367
+ }
2368
+ /**
2369
+ * Get the cooldown duration in milliseconds
2370
+ */
2371
+ getCooldownDurationMs() {
2372
+ return _ProviderManager.COOLDOWN_DURATION_MS;
2373
+ }
2374
+ /**
2375
+ * Check if a provider is currently on cooldown
2376
+ */
2377
+ isOnCooldown(baseUrl) {
2378
+ this.cleanupExpiredCooldowns();
2379
+ return this.providersOnCoolDown.some(([url]) => url === baseUrl);
2380
+ }
2381
+ /**
2382
+ * Get all providers currently on cooldown
2383
+ */
2384
+ getProvidersOnCooldown() {
2385
+ this.cleanupExpiredCooldowns();
2386
+ return [...this.providersOnCoolDown];
2387
+ }
2325
2388
  /**
2326
2389
  * Reset the failed providers list
2327
2390
  */
2328
2391
  resetFailedProviders() {
2329
2392
  this.failedProviders.clear();
2330
2393
  }
2394
+ /**
2395
+ * Get the last failed timestamp for a provider
2396
+ */
2397
+ getLastFailed(baseUrl) {
2398
+ return this.lastFailed.get(baseUrl);
2399
+ }
2400
+ /**
2401
+ * Get all providers with their last failed timestamps
2402
+ */
2403
+ getAllLastFailed() {
2404
+ return new Map(this.lastFailed);
2405
+ }
2331
2406
  /**
2332
2407
  * Mark a provider as failed
2408
+ * If a provider fails twice within 5 minutes, it's added to cooldown
2333
2409
  */
2334
2410
  markFailed(baseUrl) {
2411
+ const now = Date.now();
2412
+ const lastFailure = this.lastFailed.get(baseUrl);
2413
+ this.lastFailed.set(baseUrl, now);
2335
2414
  this.failedProviders.add(baseUrl);
2415
+ if (lastFailure !== void 0 && now - lastFailure < _ProviderManager.COOLDOWN_DURATION_MS) {
2416
+ if (!this.isOnCooldown(baseUrl)) {
2417
+ this.providersOnCoolDown.push([baseUrl, now]);
2418
+ console.log(
2419
+ `Provider ${baseUrl} added to cooldown after second failure within 5 minutes`
2420
+ );
2421
+ }
2422
+ }
2423
+ }
2424
+ /**
2425
+ * Remove a provider from cooldown (e.g., after successful request)
2426
+ */
2427
+ removeFromCooldown(baseUrl) {
2428
+ this.providersOnCoolDown = this.providersOnCoolDown.filter(
2429
+ ([url]) => url !== baseUrl
2430
+ );
2431
+ }
2432
+ /**
2433
+ * Clear all cooldown tracking
2434
+ */
2435
+ clearCooldowns() {
2436
+ this.providersOnCoolDown = [];
2437
+ }
2438
+ /**
2439
+ * Clear all failure tracking (lastFailed timestamps)
2440
+ */
2441
+ clearFailureHistory() {
2442
+ this.lastFailed.clear();
2336
2443
  }
2337
2444
  /**
2338
2445
  * Check if a provider has failed
@@ -2361,7 +2468,7 @@ var ProviderManager = class {
2361
2468
  const allProviders = this.providerRegistry.getAllProvidersModels();
2362
2469
  const candidates = [];
2363
2470
  for (const [baseUrl, models] of Object.entries(allProviders)) {
2364
- if (baseUrl === currentBaseUrl || this.failedProviders.has(baseUrl) || disabledProviders.has(baseUrl)) {
2471
+ if (baseUrl === currentBaseUrl || this.failedProviders.has(baseUrl) || disabledProviders.has(baseUrl) || this.isOnCooldown(baseUrl)) {
2365
2472
  continue;
2366
2473
  }
2367
2474
  if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl))) {
@@ -2408,6 +2515,7 @@ var ProviderManager = class {
2408
2515
  const torMode = isTorContext();
2409
2516
  for (const [baseUrl, models] of Object.entries(allProviders)) {
2410
2517
  if (disabledProviders.has(baseUrl)) continue;
2518
+ if (this.isOnCooldown(baseUrl)) continue;
2411
2519
  if (!torMode && (isOnionUrl(baseUrl) || isInsecureHttpUrl(baseUrl)))
2412
2520
  continue;
2413
2521
  const model = models.find((m) => m.id === modelId);
@@ -2431,6 +2539,7 @@ var ProviderManager = class {
2431
2539
  const results = [];
2432
2540
  for (const [baseUrl, models] of Object.entries(allModels)) {
2433
2541
  if (!includeDisabled && disabledProviders.has(baseUrl)) continue;
2542
+ if (this.isOnCooldown(baseUrl)) continue;
2434
2543
  if (torMode && !baseUrl.includes(".onion")) continue;
2435
2544
  if (!torMode && (baseUrl.includes(".onion") || isInsecureHttpUrl(baseUrl)))
2436
2545
  continue;
@@ -2844,7 +2953,6 @@ var RoutstrClient = class {
2844
2953
  body: body === void 0 || method === "GET" ? void 0 : JSON.stringify(body)
2845
2954
  });
2846
2955
  if (this.mode === "xcashu") this._log("DEBUG", "response,", response);
2847
- this._log("DEBUG", "response,", response);
2848
2956
  response.baseUrl = baseUrl;
2849
2957
  response.token = token;
2850
2958
  if (!response.ok) {
@@ -2867,7 +2975,7 @@ var RoutstrClient = class {
2867
2975
  }
2868
2976
  return response;
2869
2977
  } catch (error) {
2870
- if (this._isNetworkError(error?.message || "")) {
2978
+ if (isNetworkErrorMessage(error?.message || "")) {
2871
2979
  return await this._handleErrorResponse(
2872
2980
  params,
2873
2981
  token,
@@ -3002,7 +3110,13 @@ var RoutstrClient = class {
3002
3110
  "DEBUG",
3003
3111
  `[RoutstrClient] _handleErrorResponse: Insufficient balance, need=${required}, have=${available}`
3004
3112
  );
3005
- throw new InsufficientBalanceError(required, available, 0, "", message);
3113
+ throw new InsufficientBalanceError(
3114
+ required,
3115
+ available,
3116
+ 0,
3117
+ "",
3118
+ message
3119
+ );
3006
3120
  } else {
3007
3121
  this._log(
3008
3122
  "DEBUG",
@@ -3215,7 +3329,10 @@ var RoutstrClient = class {
3215
3329
  retryCount: 0
3216
3330
  });
3217
3331
  }
3218
- throw new FailoverError(baseUrl, Array.from(this.providerManager.getFailedProviders()));
3332
+ throw new FailoverError(
3333
+ baseUrl,
3334
+ Array.from(this.providerManager.getFailedProviders())
3335
+ );
3219
3336
  }
3220
3337
  /**
3221
3338
  * Handle post-response balance update for all modes
@@ -3365,12 +3482,6 @@ var RoutstrClient = class {
3365
3482
  const distribution = this.storageAdapter.getCachedTokenDistribution();
3366
3483
  return distribution.reduce((total, item) => total + item.amount, 0);
3367
3484
  }
3368
- /**
3369
- * Check if error message indicates a network error
3370
- */
3371
- _isNetworkError(message) {
3372
- return message.includes("NetworkError when attempting to fetch resource") || message.includes("Failed to fetch") || message.includes("Load failed");
3373
- }
3374
3485
  /**
3375
3486
  * Handle errors and notify callbacks
3376
3487
  */