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