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