ton-provider-system 0.2.1 → 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.
package/dist/index.js CHANGED
@@ -750,17 +750,43 @@ var OnFinalityProvider = class extends BaseProvider {
750
750
  if (normalized.includes("onfinality.io")) {
751
751
  try {
752
752
  const url = new URL(normalized);
753
- const baseUrl = normalized.split("?")[0];
753
+ const hasQueryParams = normalized.includes("?");
754
+ const queryString = hasQueryParams ? normalized.split("?")[1] : "";
755
+ const hasApiKeyInQuery = queryString.includes("apikey=");
754
756
  if (!url.pathname || url.pathname === "/") {
755
757
  if (this.provider.apiKey) {
756
- return baseUrl.replace(/\/?$/, "/rpc");
758
+ const baseUrl2 = normalized.split("?")[0];
759
+ if (hasApiKeyInQuery) {
760
+ return normalized.replace(/\/?$/, "/rpc");
761
+ }
762
+ if (hasQueryParams) {
763
+ return `${baseUrl2.replace(/\/?$/, "/rpc")}?${queryString}&apikey=${encodeURIComponent(this.provider.apiKey)}`;
764
+ }
765
+ return `${baseUrl2.replace(/\/?$/, "/rpc")}?apikey=${encodeURIComponent(this.provider.apiKey)}`;
757
766
  }
767
+ const baseUrl = normalized.split("?")[0];
758
768
  return baseUrl.replace(/\/?$/, "/public");
759
769
  }
760
770
  if (url.pathname === "/rpc" || url.pathname === "/public") {
761
- return baseUrl;
771
+ if (url.pathname === "/rpc") {
772
+ if (hasApiKeyInQuery) {
773
+ return normalized;
774
+ }
775
+ if (this.provider.apiKey) {
776
+ const baseUrl = normalized.split("?")[0];
777
+ if (hasQueryParams) {
778
+ return `${baseUrl}?${queryString}&apikey=${encodeURIComponent(this.provider.apiKey)}`;
779
+ }
780
+ return `${baseUrl}?apikey=${encodeURIComponent(this.provider.apiKey)}`;
781
+ }
782
+ return normalized;
783
+ }
784
+ if (url.pathname === "/public") {
785
+ return normalized.split("?")[0];
786
+ }
787
+ return normalized;
762
788
  }
763
- return baseUrl;
789
+ return normalized;
764
790
  } catch {
765
791
  if (normalized.includes("{key}")) {
766
792
  return normalized.split("?")[0].replace(/\/?$/, "/public");
@@ -768,7 +794,7 @@ var OnFinalityProvider = class extends BaseProvider {
768
794
  if (!normalized.includes("/rpc") && !normalized.includes("/public")) {
769
795
  return normalized.split("?")[0] + "/public";
770
796
  }
771
- return normalized.split("?")[0];
797
+ return normalized;
772
798
  }
773
799
  }
774
800
  return normalized;
@@ -777,9 +803,6 @@ var OnFinalityProvider = class extends BaseProvider {
777
803
  const headers = {
778
804
  "Content-Type": "application/json"
779
805
  };
780
- if (this.provider.apiKey) {
781
- headers["apikey"] = this.provider.apiKey;
782
- }
783
806
  return headers;
784
807
  }
785
808
  parseResponse(data) {
@@ -1083,7 +1106,8 @@ var HealthChecker = class {
1083
1106
  } catch (error) {
1084
1107
  if (provider.type === "onfinality" && normalizedEndpoint.includes("/rpc") && provider.apiKey && error.message?.includes("backend error")) {
1085
1108
  this.logger.debug(`OnFinality /rpc failed, retrying with /public endpoint`);
1086
- const publicEndpoint = normalizedEndpoint.replace("/rpc", "/public");
1109
+ const baseUrl = normalizedEndpoint.split("?")[0];
1110
+ const publicEndpoint = baseUrl.replace("/rpc", "/public");
1087
1111
  const publicProvider = { ...provider, apiKey: void 0 };
1088
1112
  const publicProviderImpl = createProvider(publicProvider);
1089
1113
  info = await this.callGetMasterchainInfo(publicEndpoint, publicProvider, publicProviderImpl);
@@ -1130,17 +1154,26 @@ var HealthChecker = class {
1130
1154
  const endTime = performance.now();
1131
1155
  const latencyMs = Math.round(endTime - startTime);
1132
1156
  const errorMsg = error.message || String(error) || "Unknown error";
1157
+ const errorMsgLower = errorMsg.toLowerCase();
1133
1158
  const isCorsError = this.isCorsError(error, errorMsg);
1134
- const is429 = errorMsg.includes("429") || errorMsg.toLowerCase().includes("rate limit");
1135
- const is404 = errorMsg.includes("404") || errorMsg.toLowerCase().includes("not found");
1136
- const is503 = errorMsg.includes("503") || errorMsg.toLowerCase().includes("service unavailable");
1137
- const is502 = errorMsg.includes("502") || errorMsg.toLowerCase().includes("bad gateway");
1138
- const isTimeout = error.name === "AbortError" || errorMsg.includes("timeout");
1139
- const isOnFinalityBackendError = provider.type === "onfinality" && (errorMsg.includes("Backend error") || errorMsg.includes("backend error"));
1159
+ const responseStatus = error?.response?.status || error?.status || error?.statusCode || null;
1160
+ const statusMatch = errorMsg.match(/\b(\d{3})\b/);
1161
+ const statusFromMsg = statusMatch ? parseInt(statusMatch[1], 10) : null;
1162
+ const httpStatus = responseStatus || statusFromMsg;
1163
+ const is429 = httpStatus === 429 || errorMsgLower.includes("429") || errorMsgLower.includes("rate limit") || errorMsgLower.includes("too many requests");
1164
+ const is404 = httpStatus === 404 || errorMsgLower.includes("404") || errorMsgLower.includes("not found");
1165
+ const is401 = httpStatus === 401 || errorMsgLower.includes("401") || errorMsgLower.includes("unauthorized") || errorMsgLower.includes("invalid api key") || errorMsgLower.includes("authentication failed");
1166
+ const is403 = httpStatus === 403 || errorMsgLower.includes("403") || errorMsgLower.includes("forbidden");
1167
+ const is503 = httpStatus === 503 || errorMsgLower.includes("503") || errorMsgLower.includes("service unavailable");
1168
+ const is502 = httpStatus === 502 || errorMsgLower.includes("502") || errorMsgLower.includes("bad gateway");
1169
+ const isTimeout = error.name === "AbortError" || errorMsgLower.includes("timeout") || errorMsgLower.includes("timed out") || errorMsgLower.includes("aborted");
1170
+ const isOnFinalityBackendError = provider.type === "onfinality" && (errorMsgLower.includes("backend error") || errorMsgLower.includes("backend error"));
1140
1171
  let status = "offline";
1141
1172
  if (is429) {
1142
1173
  status = "degraded";
1143
- } else if (is404 || is503 || is502 || isOnFinalityBackendError) {
1174
+ } else if (is404 || is401 || is403) {
1175
+ status = "offline";
1176
+ } else if (is503 || is502 || isOnFinalityBackendError) {
1144
1177
  status = "offline";
1145
1178
  } else if (isTimeout) {
1146
1179
  status = "offline";
@@ -1168,8 +1201,9 @@ var HealthChecker = class {
1168
1201
  *
1169
1202
  * @param batchSize - Number of providers to test in parallel (default: 2)
1170
1203
  * @param batchDelayMs - Delay between batches in milliseconds (default: 500 to avoid rate limits)
1204
+ * If not provided, calculates delay based on lowest RPS in batch
1171
1205
  */
1172
- async testProviders(providers, batchSize = 2, batchDelayMs = 500) {
1206
+ async testProviders(providers, batchSize = 2, batchDelayMs) {
1173
1207
  const results = [];
1174
1208
  for (let i = 0; i < providers.length; i += batchSize) {
1175
1209
  const batch = providers.slice(i, i + batchSize);
@@ -1177,8 +1211,15 @@ var HealthChecker = class {
1177
1211
  batch.map((p) => this.testProvider(p))
1178
1212
  );
1179
1213
  results.push(...batchResults);
1180
- if (i + batchSize < providers.length && batchDelayMs > 0) {
1181
- await this.sleep(batchDelayMs);
1214
+ if (i + batchSize < providers.length) {
1215
+ let delay = batchDelayMs;
1216
+ if (delay === void 0) {
1217
+ const minRps = Math.min(...batch.map((p) => p.rps || 1));
1218
+ delay = Math.max(500, Math.ceil(1e3 / minRps * 1.5));
1219
+ }
1220
+ if (delay > 0) {
1221
+ await this.sleep(delay);
1222
+ }
1182
1223
  }
1183
1224
  }
1184
1225
  return results;
@@ -1567,24 +1608,36 @@ var TokenBucketRateLimiter = class {
1567
1608
  const startTime = Date.now();
1568
1609
  if (this.processing) {
1569
1610
  const acquired = await new Promise((resolve) => {
1611
+ let timeoutInterval = null;
1612
+ let resolved = false;
1613
+ const cleanup = () => {
1614
+ if (timeoutInterval !== null) {
1615
+ clearInterval(timeoutInterval);
1616
+ timeoutInterval = null;
1617
+ }
1618
+ };
1570
1619
  const checkTimeout = () => {
1571
1620
  if (Date.now() - startTime > timeoutMs) {
1572
1621
  const idx = this.requestQueue.indexOf(resolveCallback);
1573
1622
  if (idx >= 0) {
1574
1623
  this.requestQueue.splice(idx, 1);
1575
1624
  }
1576
- resolve(false);
1625
+ if (!resolved) {
1626
+ resolved = true;
1627
+ cleanup();
1628
+ resolve(false);
1629
+ }
1577
1630
  }
1578
1631
  };
1579
- const resolveCallback = () => resolve(true);
1580
- this.requestQueue.push(resolveCallback);
1581
- const timeoutInterval = setInterval(checkTimeout, 1e3);
1582
- const cleanup = () => clearInterval(timeoutInterval);
1583
- Promise.resolve().then(() => {
1584
- if (this.requestQueue.includes(resolveCallback)) ; else {
1632
+ const resolveCallback = () => {
1633
+ if (!resolved) {
1634
+ resolved = true;
1585
1635
  cleanup();
1636
+ resolve(true);
1586
1637
  }
1587
- });
1638
+ };
1639
+ this.requestQueue.push(resolveCallback);
1640
+ timeoutInterval = setInterval(checkTimeout, 1e3);
1588
1641
  });
1589
1642
  if (!acquired) {
1590
1643
  return false;
@@ -1592,24 +1645,25 @@ var TokenBucketRateLimiter = class {
1592
1645
  }
1593
1646
  this.processing = true;
1594
1647
  try {
1595
- this.refill();
1596
1648
  if (this.currentBackoff > 0) {
1597
1649
  this.logger.debug(`Applying backoff: ${this.currentBackoff}ms`);
1598
1650
  await sleep(this.currentBackoff);
1599
1651
  this.lastRefill = Date.now();
1600
- this.currentBackoff = 0;
1601
1652
  }
1653
+ this.refill();
1602
1654
  while (this.tokens <= 0) {
1603
1655
  if (Date.now() - startTime > timeoutMs) {
1604
1656
  return false;
1605
1657
  }
1606
- await sleep(Math.min(100, this.config.minDelayMs));
1658
+ const waitTime = Math.min(100, this.config.minDelayMs);
1659
+ await sleep(waitTime);
1607
1660
  this.refill();
1608
1661
  }
1609
1662
  this.tokens--;
1610
1663
  const timeSinceLastRefill = Date.now() - this.lastRefill;
1611
1664
  if (timeSinceLastRefill < this.config.minDelayMs) {
1612
- await sleep(this.config.minDelayMs - timeSinceLastRefill);
1665
+ const remainingDelay = this.config.minDelayMs - timeSinceLastRefill;
1666
+ await sleep(remainingDelay);
1613
1667
  }
1614
1668
  this.lastRefill = Date.now();
1615
1669
  return true;
@@ -1627,10 +1681,21 @@ var TokenBucketRateLimiter = class {
1627
1681
  release() {
1628
1682
  }
1629
1683
  /**
1630
- * Report a successful request (resets backoff)
1684
+ * Report a successful request (gradually reduces backoff)
1685
+ *
1686
+ * Instead of immediately clearing backoff, we gradually reduce it to prevent
1687
+ * immediately hitting the rate limit again after a single success.
1631
1688
  */
1632
1689
  reportSuccess() {
1633
- this.currentBackoff = 0;
1690
+ if (this.currentBackoff > 0) {
1691
+ this.currentBackoff = Math.max(
1692
+ this.config.minDelayMs,
1693
+ Math.floor(this.currentBackoff / 2)
1694
+ );
1695
+ if (this.currentBackoff <= this.config.minDelayMs) {
1696
+ this.currentBackoff = 0;
1697
+ }
1698
+ }
1634
1699
  this.consecutiveErrors = 0;
1635
1700
  }
1636
1701
  /**
@@ -1821,7 +1886,9 @@ var DEFAULT_CONFIG2 = {
1821
1886
  latencyWeight: 0.4,
1822
1887
  priorityWeight: 0.3,
1823
1888
  freshnessWeight: 0.3,
1824
- minStatus: ["available", "degraded"]
1889
+ minStatus: ["available", "degraded"],
1890
+ retryCooldownMs: 3e4
1891
+ // 30 seconds
1825
1892
  };
1826
1893
  var ProviderSelector = class {
1827
1894
  constructor(registry, healthChecker, config, logger, adapter = "node") {
@@ -1915,14 +1982,19 @@ var ProviderSelector = class {
1915
1982
  return defaultProvider;
1916
1983
  }
1917
1984
  if (health.success === false && health.lastTested) {
1918
- const timeSinceFailure = Date.now() - health.lastTested.getTime();
1919
- const cooldownMs = 3e4;
1920
- if (timeSinceFailure > cooldownMs) {
1921
- this.logger.warn(
1922
- `No healthy providers for ${network}, retrying failed default after cooldown: ${defaultProvider.id}`
1985
+ if (this.isRetryableError(health.error)) {
1986
+ const timeSinceFailure = Date.now() - health.lastTested.getTime();
1987
+ if (timeSinceFailure > this.config.retryCooldownMs) {
1988
+ this.logger.warn(
1989
+ `No healthy providers for ${network}, retrying failed default after cooldown: ${defaultProvider.id}`
1990
+ );
1991
+ this.activeProviderByNetwork.set(network, defaultProvider.id);
1992
+ return defaultProvider;
1993
+ }
1994
+ } else {
1995
+ this.logger.debug(
1996
+ `Skipping provider ${defaultProvider.id} with permanent error: ${health.error}`
1923
1997
  );
1924
- this.activeProviderByNetwork.set(network, defaultProvider.id);
1925
- return defaultProvider;
1926
1998
  }
1927
1999
  }
1928
2000
  }
@@ -1936,14 +2008,19 @@ var ProviderSelector = class {
1936
2008
  return provider;
1937
2009
  }
1938
2010
  if (health.success === false && health.lastTested) {
1939
- const timeSinceFailure = Date.now() - health.lastTested.getTime();
1940
- const cooldownMs = 3e4;
1941
- if (timeSinceFailure > cooldownMs) {
1942
- this.logger.warn(
1943
- `No healthy providers for ${network}, retrying failed provider after cooldown: ${provider.id}`
2011
+ if (this.isRetryableError(health.error)) {
2012
+ const timeSinceFailure = Date.now() - health.lastTested.getTime();
2013
+ if (timeSinceFailure > this.config.retryCooldownMs) {
2014
+ this.logger.warn(
2015
+ `No healthy providers for ${network}, retrying failed provider after cooldown: ${provider.id}`
2016
+ );
2017
+ this.activeProviderByNetwork.set(network, provider.id);
2018
+ return provider;
2019
+ }
2020
+ } else {
2021
+ this.logger.debug(
2022
+ `Skipping provider ${provider.id} with permanent error: ${health.error}`
1944
2023
  );
1945
- this.activeProviderByNetwork.set(network, provider.id);
1946
- return provider;
1947
2024
  }
1948
2025
  }
1949
2026
  }
@@ -1958,6 +2035,12 @@ var ProviderSelector = class {
1958
2035
  this.logger.debug(
1959
2036
  `Best provider for ${network}: ${best.id} (score: ${scored[0].score.toFixed(2)})`
1960
2037
  );
2038
+ } else if (bestHealth && bestHealth.success === false) {
2039
+ this.bestProviderByNetwork.delete(network);
2040
+ this.activeProviderByNetwork.set(network, best.id);
2041
+ this.logger.debug(
2042
+ `Best provider for ${network}: ${best.id} (score: ${scored[0].score.toFixed(2)}, failed - not cached)`
2043
+ );
1961
2044
  } else {
1962
2045
  this.activeProviderByNetwork.set(network, best.id);
1963
2046
  this.logger.debug(
@@ -2008,11 +2091,12 @@ var ProviderSelector = class {
2008
2091
  return 0.01 * (1 / (provider.priority + 1));
2009
2092
  }
2010
2093
  if (health.success === false) {
2011
- if (health.lastTested) {
2012
- const timeSinceFailure = Date.now() - health.lastTested.getTime();
2013
- const cooldownMs = 3e4;
2014
- if (timeSinceFailure > cooldownMs) {
2015
- return 1e-3 * (1 / (provider.priority + 1));
2094
+ if (this.isRetryableError(health.error)) {
2095
+ if (health.lastTested) {
2096
+ const timeSinceFailure = Date.now() - health.lastTested.getTime();
2097
+ if (timeSinceFailure > this.config.retryCooldownMs) {
2098
+ return 1e-3 * (1 / (provider.priority + 1));
2099
+ }
2016
2100
  }
2017
2101
  }
2018
2102
  return 0;
@@ -2207,6 +2291,34 @@ var ProviderSelector = class {
2207
2291
  return true;
2208
2292
  });
2209
2293
  }
2294
+ /**
2295
+ * Check if an error is retryable (temporary) or permanent
2296
+ *
2297
+ * Permanent errors (never retry):
2298
+ * - 404 (Not Found) - endpoint doesn't exist
2299
+ * - 401 (Unauthorized) - invalid API key
2300
+ * - Invalid API key errors
2301
+ *
2302
+ * Retryable errors (can retry after cooldown):
2303
+ * - 503 (Service Unavailable) - temporary server issue
2304
+ * - 502 (Bad Gateway) - temporary gateway issue
2305
+ * - Timeout errors - network issues
2306
+ * - Network errors - connection issues
2307
+ * - 429 (Rate Limit) - temporary rate limit (handled separately)
2308
+ */
2309
+ isRetryableError(error) {
2310
+ if (!error) {
2311
+ return true;
2312
+ }
2313
+ const errorLower = error.toLowerCase();
2314
+ if (errorLower.includes("404") || errorLower.includes("not found") || errorLower.includes("401") || errorLower.includes("unauthorized") || errorLower.includes("invalid api key") || errorLower.includes("api key is invalid") || errorLower.includes("authentication failed") || errorLower.includes("forbidden")) {
2315
+ return false;
2316
+ }
2317
+ if (errorLower.includes("503") || errorLower.includes("service unavailable") || errorLower.includes("502") || errorLower.includes("bad gateway") || errorLower.includes("timeout") || errorLower.includes("network error") || errorLower.includes("connection") || errorLower.includes("econnrefused") || errorLower.includes("enotfound")) {
2318
+ return true;
2319
+ }
2320
+ return true;
2321
+ }
2210
2322
  };
2211
2323
  function createSelector(registry, healthChecker, config, logger, adapter = "node") {
2212
2324
  return new ProviderSelector(registry, healthChecker, config, logger, adapter);
@@ -2630,10 +2742,17 @@ var _ProviderManager = class _ProviderManager {
2630
2742
  }
2631
2743
  const acquired = await this.rateLimiter.acquire(provider.id, timeoutMs);
2632
2744
  if (!acquired) {
2633
- this.options.logger.warn(`Rate limit timeout for ${provider.id}`);
2745
+ const timeoutError = new Error(`Rate limit token acquisition timeout for ${provider.id} after ${timeoutMs || 6e4}ms`);
2746
+ this.options.logger.warn(timeoutError.message);
2747
+ this.reportError(timeoutError);
2634
2748
  const next = this.selector.getNextProvider(this.network, [provider.id]);
2635
2749
  if (next) {
2636
- return normalizeV2Endpoint(next.endpointV2, next);
2750
+ const nextTimeout = timeoutMs ? Math.min(timeoutMs, 1e4) : 1e4;
2751
+ const nextAcquired = await this.rateLimiter.acquire(next.id, nextTimeout);
2752
+ if (nextAcquired) {
2753
+ return normalizeV2Endpoint(next.endpointV2, next);
2754
+ }
2755
+ this.options.logger.warn(`Rate limit timeout for next provider ${next.id}, using fallback`);
2637
2756
  }
2638
2757
  return this.getFallbackEndpoint();
2639
2758
  }
@@ -2683,26 +2802,48 @@ var _ProviderManager = class _ProviderManager {
2683
2802
  if (!provider) {
2684
2803
  provider = this.selector.getBestProvider(this.network);
2685
2804
  }
2686
- if (!provider) return;
2805
+ if (!provider) {
2806
+ this.options.logger.warn(`Cannot report error: no provider available for ${this.network}`);
2807
+ return;
2808
+ }
2687
2809
  const errorMsg = error instanceof Error ? error.message : String(error);
2688
2810
  const errorMsgLower = errorMsg.toLowerCase();
2689
- const is429 = errorMsgLower.includes("429") || errorMsgLower.includes("rate limit");
2690
- const is503 = errorMsgLower.includes("503") || errorMsgLower.includes("service unavailable");
2691
- const is502 = errorMsgLower.includes("502") || errorMsgLower.includes("bad gateway");
2692
- const is404 = errorMsgLower.includes("404") || errorMsgLower.includes("not found");
2693
- const isTimeout = errorMsgLower.includes("timeout") || errorMsgLower.includes("abort");
2811
+ const enhancedErrorMsg = `Provider ${provider.id} (${provider.name}): ${errorMsg}`;
2812
+ const responseStatus = error instanceof Error && error?.response?.status || error instanceof Error && error?.status || error instanceof Error && error?.statusCode || null;
2813
+ const statusMatch = errorMsg.match(/\b(\d{3})\b/);
2814
+ const statusFromMsg = statusMatch ? parseInt(statusMatch[1], 10) : null;
2815
+ const httpStatus = responseStatus || statusFromMsg;
2816
+ const is429 = httpStatus === 429 || errorMsgLower.includes("429") || errorMsgLower.includes("rate limit") || errorMsgLower.includes("too many requests");
2817
+ const is503 = httpStatus === 503 || errorMsgLower.includes("503") || errorMsgLower.includes("service unavailable");
2818
+ const is502 = httpStatus === 502 || errorMsgLower.includes("502") || errorMsgLower.includes("bad gateway");
2819
+ const is404 = httpStatus === 404 || errorMsgLower.includes("404") || errorMsgLower.includes("not found");
2820
+ const is401 = httpStatus === 401 || errorMsgLower.includes("401") || errorMsgLower.includes("unauthorized") || errorMsgLower.includes("invalid api key") || errorMsgLower.includes("authentication failed");
2821
+ const isTimeout = error instanceof Error && error.name === "AbortError" || errorMsgLower.includes("timeout") || errorMsgLower.includes("timed out") || errorMsgLower.includes("abort");
2694
2822
  if (isRateLimitError(error) || is429) {
2695
2823
  this.rateLimiter.reportRateLimitError(provider.id);
2696
- this.healthChecker.markDegraded(provider.id, this.network, errorMsg);
2697
- } else if (is503 || is502 || is404 || isTimeout) {
2824
+ this.healthChecker.markDegraded(provider.id, this.network, enhancedErrorMsg);
2825
+ this.options.logger.warn(`${enhancedErrorMsg} - Rate limit detected, switching to next provider`);
2826
+ } else if (is404 || is401) {
2827
+ this.rateLimiter.reportError(provider.id);
2828
+ this.healthChecker.markOffline(provider.id, this.network, enhancedErrorMsg);
2829
+ this.options.logger.error(`${enhancedErrorMsg} - Permanent error (${is404 ? "404" : "401"}), provider marked offline`);
2830
+ } else if (is503 || is502 || isTimeout) {
2698
2831
  this.rateLimiter.reportError(provider.id);
2699
- this.healthChecker.markOffline(provider.id, this.network, errorMsg);
2832
+ this.healthChecker.markOffline(provider.id, this.network, enhancedErrorMsg);
2833
+ const errorType = is503 ? "503" : is502 ? "502" : "timeout";
2834
+ this.options.logger.warn(`${enhancedErrorMsg} - Server error (${errorType}), switching to next provider`);
2700
2835
  } else {
2701
2836
  this.rateLimiter.reportError(provider.id);
2702
- this.healthChecker.markDegraded(provider.id, this.network, errorMsg);
2837
+ this.healthChecker.markDegraded(provider.id, this.network, enhancedErrorMsg);
2838
+ this.options.logger.warn(`${enhancedErrorMsg} - Unknown error, switching to next provider`);
2839
+ }
2840
+ const nextProvider = this.selector.handleProviderFailure(provider.id, this.network);
2841
+ if (nextProvider) {
2842
+ this.options.logger.info(`Failover: switched from ${provider.id} to ${nextProvider.id}`);
2843
+ } else {
2844
+ this.options.logger.warn(`Failover: no alternative provider available for ${this.network}`);
2703
2845
  }
2704
2846
  this.selector.clearCache(this.network);
2705
- this.selector.handleProviderFailure(provider.id, this.network);
2706
2847
  this.notifyListeners();
2707
2848
  }
2708
2849
  // ========================================================================
@@ -3253,9 +3394,11 @@ var BrowserAdapter = class {
3253
3394
  // ========================================================================
3254
3395
  /**
3255
3396
  * Make a JSON-RPC call to the TON API
3397
+ *
3398
+ * Note: Uses rate limiting to prevent 429 errors.
3256
3399
  */
3257
3400
  async jsonRpc(method, params = {}, timeoutMs = 1e4) {
3258
- const endpoint = await this.manager.getEndpoint();
3401
+ const endpoint = await this.manager.getEndpointWithRateLimit(timeoutMs);
3259
3402
  const controller = new AbortController();
3260
3403
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
3261
3404
  try {
@@ -3294,9 +3437,11 @@ var BrowserAdapter = class {
3294
3437
  // ========================================================================
3295
3438
  /**
3296
3439
  * Get address state
3440
+ *
3441
+ * Note: Uses rate limiting to prevent 429 errors.
3297
3442
  */
3298
3443
  async getAddressState(address, timeoutMs = 1e4) {
3299
- const endpoint = await this.manager.getEndpoint();
3444
+ const endpoint = await this.manager.getEndpointWithRateLimit(timeoutMs);
3300
3445
  const baseV2 = toV2Base(endpoint);
3301
3446
  const url = `${baseV2}/getAddressState?address=${encodeURIComponent(address)}`;
3302
3447
  const controller = new AbortController();
@@ -3326,9 +3471,11 @@ var BrowserAdapter = class {
3326
3471
  }
3327
3472
  /**
3328
3473
  * Get address balance
3474
+ *
3475
+ * Note: Uses rate limiting to prevent 429 errors.
3329
3476
  */
3330
3477
  async getAddressBalance(address, timeoutMs = 1e4) {
3331
- const endpoint = await this.manager.getEndpoint();
3478
+ const endpoint = await this.manager.getEndpointWithRateLimit(timeoutMs);
3332
3479
  const baseV2 = toV2Base(endpoint);
3333
3480
  const url = `${baseV2}/getAddressBalance?address=${encodeURIComponent(address)}`;
3334
3481
  const controller = new AbortController();
@@ -3358,9 +3505,11 @@ var BrowserAdapter = class {
3358
3505
  }
3359
3506
  /**
3360
3507
  * Get address information
3508
+ *
3509
+ * Note: Uses rate limiting to prevent 429 errors.
3361
3510
  */
3362
3511
  async getAddressInfo(address, timeoutMs = 1e4) {
3363
- const endpoint = await this.manager.getEndpoint();
3512
+ const endpoint = await this.manager.getEndpointWithRateLimit(timeoutMs);
3364
3513
  const baseV2 = toV2Base(endpoint);
3365
3514
  const url = `${baseV2}/getAddressInformation?address=${encodeURIComponent(address)}`;
3366
3515
  const controller = new AbortController();
@@ -3388,9 +3537,11 @@ var BrowserAdapter = class {
3388
3537
  }
3389
3538
  /**
3390
3539
  * Run get method
3540
+ *
3541
+ * Note: Uses rate limiting to prevent 429 errors.
3391
3542
  */
3392
3543
  async runGetMethod(address, method, stack = [], timeoutMs = 15e3) {
3393
- const endpoint = await this.manager.getEndpoint();
3544
+ const endpoint = await this.manager.getEndpointWithRateLimit(timeoutMs);
3394
3545
  const baseV2 = toV2Base(endpoint);
3395
3546
  const url = `${baseV2}/runGetMethod`;
3396
3547
  const controller = new AbortController();