ton-provider-system 0.2.2 → 0.3.0

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
@@ -1035,6 +1035,238 @@ function createProvider(provider) {
1035
1035
  }
1036
1036
  }
1037
1037
 
1038
+ // src/utils/endpoint.ts
1039
+ function normalizeV2Endpoint(endpoint, provider) {
1040
+ if (provider) {
1041
+ const providerImpl = createProvider(provider);
1042
+ return providerImpl.normalizeEndpoint(endpoint);
1043
+ }
1044
+ return normalizeV2EndpointFallback(endpoint);
1045
+ }
1046
+ function normalizeV2EndpointFallback(endpoint) {
1047
+ let normalized = endpoint.trim();
1048
+ if (normalized.endsWith("/")) {
1049
+ normalized = normalized.slice(0, -1);
1050
+ }
1051
+ if (normalized.toLowerCase().endsWith("/jsonrpc")) {
1052
+ return normalized;
1053
+ }
1054
+ if (normalized.includes("gateway.tatum.io")) {
1055
+ try {
1056
+ const url = new URL(normalized);
1057
+ if (!url.pathname || url.pathname === "/") {
1058
+ return normalized + "/jsonRPC";
1059
+ }
1060
+ if (!url.pathname.toLowerCase().endsWith("/jsonrpc")) {
1061
+ return normalized + "/jsonRPC";
1062
+ }
1063
+ } catch {
1064
+ return normalized + "/jsonRPC";
1065
+ }
1066
+ }
1067
+ if (normalized.includes("onfinality.io")) {
1068
+ try {
1069
+ const url = new URL(normalized);
1070
+ const baseUrl = normalized.split("?")[0];
1071
+ if (!url.pathname || url.pathname === "/") {
1072
+ const apikey = url.searchParams.get("apikey");
1073
+ if (apikey && apikey !== "{key}" && apikey.length > 0) {
1074
+ return baseUrl.replace(/\/?$/, "/rpc");
1075
+ }
1076
+ return baseUrl.replace(/\/?$/, "/public");
1077
+ }
1078
+ if (url.pathname === "/rpc" || url.pathname === "/public") {
1079
+ return baseUrl;
1080
+ }
1081
+ return baseUrl;
1082
+ } catch {
1083
+ if (normalized.includes("{key}")) {
1084
+ return normalized.split("?")[0].replace(/\/?$/, "/public");
1085
+ }
1086
+ if (!normalized.includes("/rpc") && !normalized.includes("/public")) {
1087
+ return normalized.split("?")[0] + "/public";
1088
+ }
1089
+ return normalized.split("?")[0];
1090
+ }
1091
+ }
1092
+ if (normalized.endsWith("/api/v2")) {
1093
+ return normalized + "/jsonRPC";
1094
+ }
1095
+ if (normalized.endsWith("/api/v3")) {
1096
+ return normalized.replace("/api/v3", "/api/v2/jsonRPC");
1097
+ }
1098
+ if (normalized.includes("quiknode.pro") || normalized.includes("getblock.io")) {
1099
+ try {
1100
+ const url = new URL(normalized);
1101
+ if (!url.pathname || url.pathname === "/") {
1102
+ return normalized + "/jsonRPC";
1103
+ }
1104
+ if (!url.pathname.toLowerCase().endsWith("/jsonrpc")) {
1105
+ return normalized + "/jsonRPC";
1106
+ }
1107
+ } catch {
1108
+ return normalized + "/jsonRPC";
1109
+ }
1110
+ }
1111
+ try {
1112
+ const url = new URL(normalized);
1113
+ if (!url.pathname || url.pathname === "/") {
1114
+ return normalized + "/jsonRPC";
1115
+ }
1116
+ } catch {
1117
+ }
1118
+ return normalized;
1119
+ }
1120
+ function toV2Base(endpoint) {
1121
+ let normalized = endpoint.trim();
1122
+ if (normalized.endsWith("/")) {
1123
+ normalized = normalized.slice(0, -1);
1124
+ }
1125
+ if (normalized.toLowerCase().endsWith("/jsonrpc")) {
1126
+ normalized = normalized.slice(0, -8);
1127
+ }
1128
+ normalized = normalized.replace(/\/api\/v3\b/, "/api/v2");
1129
+ if (!normalized.endsWith("/api/v2")) {
1130
+ if (normalized.includes("/api/v2")) {
1131
+ const idx = normalized.indexOf("/api/v2");
1132
+ normalized = normalized.slice(0, idx + 7);
1133
+ }
1134
+ }
1135
+ return normalized;
1136
+ }
1137
+ function toV3Base(endpoint) {
1138
+ const normalized = toV2Base(endpoint);
1139
+ return normalized.replace("/api/v2", "/api/v3");
1140
+ }
1141
+ function getBaseUrl(endpoint) {
1142
+ try {
1143
+ const url = new URL(endpoint);
1144
+ return `${url.protocol}//${url.host}`;
1145
+ } catch {
1146
+ return endpoint;
1147
+ }
1148
+ }
1149
+ function isChainstackUrl(url) {
1150
+ try {
1151
+ const parsed = new URL(url.trim());
1152
+ return parsed.hostname.includes("chainstack.com");
1153
+ } catch {
1154
+ return false;
1155
+ }
1156
+ }
1157
+ function isQuickNodeUrl(url) {
1158
+ try {
1159
+ const parsed = new URL(url.trim());
1160
+ return parsed.hostname.includes("quiknode.pro");
1161
+ } catch {
1162
+ return false;
1163
+ }
1164
+ }
1165
+ function isTonCenterUrl(url) {
1166
+ try {
1167
+ const parsed = new URL(url.trim());
1168
+ return parsed.hostname.includes("toncenter.com");
1169
+ } catch {
1170
+ return false;
1171
+ }
1172
+ }
1173
+ function isOrbsUrl(url) {
1174
+ try {
1175
+ const parsed = new URL(url.trim());
1176
+ return parsed.hostname.includes("orbs.network") || parsed.hostname.includes("ton-access");
1177
+ } catch {
1178
+ return false;
1179
+ }
1180
+ }
1181
+ function buildRestUrl(baseEndpoint, method) {
1182
+ const base = toV2Base(baseEndpoint);
1183
+ return `${base}/${method}`;
1184
+ }
1185
+ function buildGetAddressStateUrl(baseEndpoint, address) {
1186
+ const base = toV2Base(baseEndpoint);
1187
+ return `${base}/getAddressState?address=${encodeURIComponent(address)}`;
1188
+ }
1189
+ function buildGetAddressBalanceUrl(baseEndpoint, address) {
1190
+ const base = toV2Base(baseEndpoint);
1191
+ return `${base}/getAddressBalance?address=${encodeURIComponent(address)}`;
1192
+ }
1193
+ function buildGetAddressInfoUrl(baseEndpoint, address) {
1194
+ const base = toV2Base(baseEndpoint);
1195
+ return `${base}/getAddressInformation?address=${encodeURIComponent(address)}`;
1196
+ }
1197
+ function detectNetworkFromEndpoint(endpoint) {
1198
+ const lower = endpoint.toLowerCase();
1199
+ if (lower.includes("testnet") || lower.includes("test") || lower.includes("sandbox")) {
1200
+ return "testnet";
1201
+ }
1202
+ if (lower.includes("mainnet") || lower.includes("main") || // TonCenter mainnet doesn't have 'mainnet' in URL
1203
+ lower.includes("toncenter.com") && !lower.includes("testnet")) {
1204
+ return "mainnet";
1205
+ }
1206
+ return null;
1207
+ }
1208
+ function isValidHttpUrl(str) {
1209
+ try {
1210
+ const url = new URL(str);
1211
+ return url.protocol === "http:" || url.protocol === "https:";
1212
+ } catch {
1213
+ return false;
1214
+ }
1215
+ }
1216
+ function isValidWsUrl(str) {
1217
+ try {
1218
+ const url = new URL(str);
1219
+ return url.protocol === "ws:" || url.protocol === "wss:";
1220
+ } catch {
1221
+ return false;
1222
+ }
1223
+ }
1224
+ function isCredentialLike(segment) {
1225
+ return /^[0-9a-f]{16,}$/i.test(segment) || // hex API keys (e.g. Chainstack/GetBlock)
1226
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(segment) || // UUID
1227
+ /^[A-Za-z0-9_-]{20,}$/.test(segment);
1228
+ }
1229
+ function redactUrl(url) {
1230
+ const MASK = "***";
1231
+ const SENSITIVE_PARAM = /^(apikey|api[-_]?key|key|token|secret|password)$/i;
1232
+ const STRUCTURAL = /* @__PURE__ */ new Set([
1233
+ "api",
1234
+ "v1",
1235
+ "v2",
1236
+ "v3",
1237
+ "v4",
1238
+ "jsonrpc",
1239
+ "rpc",
1240
+ "public",
1241
+ "ws",
1242
+ "testnet",
1243
+ "mainnet",
1244
+ "toncenter-api-v2"
1245
+ ]);
1246
+ try {
1247
+ const u = new URL(url);
1248
+ for (const name of Array.from(u.searchParams.keys())) {
1249
+ if (SENSITIVE_PARAM.test(name)) {
1250
+ u.searchParams.set(name, MASK);
1251
+ }
1252
+ }
1253
+ u.pathname = u.pathname.split("/").map(
1254
+ (seg) => seg && !STRUCTURAL.has(seg.toLowerCase()) && isCredentialLike(seg) ? MASK : seg
1255
+ ).join("/");
1256
+ const hostParts = u.hostname.split(".");
1257
+ if (hostParts.length > 2 && isCredentialLike(hostParts[0])) {
1258
+ hostParts[0] = MASK;
1259
+ u.hostname = hostParts.join(".");
1260
+ }
1261
+ return u.toString();
1262
+ } catch {
1263
+ return url.replace(
1264
+ /\b(apikey|api[-_]?key|key|token|secret|password)=[^&\s]+/gi,
1265
+ `$1=${MASK}`
1266
+ );
1267
+ }
1268
+ }
1269
+
1038
1270
  // src/core/healthChecker.ts
1039
1271
  var consoleLogger2 = {
1040
1272
  debug: (msg, data) => console.debug(`[HealthChecker] ${msg}`, data || ""),
@@ -1051,6 +1283,8 @@ var HealthChecker = class {
1051
1283
  constructor(config, logger, rateLimiter) {
1052
1284
  this.results = /* @__PURE__ */ new Map();
1053
1285
  this.highestSeqno = /* @__PURE__ */ new Map();
1286
+ /** Rolling window of recent valid seqnos per network (for outlier-robust blocksBehind). */
1287
+ this.seqnoSamples = /* @__PURE__ */ new Map();
1054
1288
  this.rateLimiter = null;
1055
1289
  this.config = { ...DEFAULT_CONFIG, ...config };
1056
1290
  this.logger = logger || consoleLogger2;
@@ -1096,22 +1330,36 @@ var HealthChecker = class {
1096
1330
  if (!validation.valid) {
1097
1331
  throw new Error(validation.error || "Provider configuration invalid");
1098
1332
  }
1099
- const normalizedEndpoint = providerImpl.normalizeEndpoint(endpoint);
1333
+ let normalizedEndpoint = providerImpl.normalizeEndpoint(endpoint);
1100
1334
  if (provider.type === "onfinality") {
1101
- this.logger.debug(`OnFinality endpoint: ${endpoint} -> ${normalizedEndpoint}, API key: ${provider.apiKey ? "set" : "not set"}`);
1335
+ this.logger.debug(`OnFinality endpoint: ${redactUrl(endpoint)} -> ${redactUrl(normalizedEndpoint)}, API key: ${provider.apiKey ? "set" : "not set"}`);
1102
1336
  }
1337
+ const orbsRetries = provider.isDynamic && provider.type === "orbs" ? 2 : 0;
1103
1338
  let info;
1104
- try {
1105
- info = await this.callGetMasterchainInfo(normalizedEndpoint, provider, providerImpl);
1106
- } catch (error) {
1107
- if (provider.type === "onfinality" && normalizedEndpoint.includes("/rpc") && provider.apiKey && error.message?.includes("backend error")) {
1108
- this.logger.debug(`OnFinality /rpc failed, retrying with /public endpoint`);
1109
- const baseUrl = normalizedEndpoint.split("?")[0];
1110
- const publicEndpoint = baseUrl.replace("/rpc", "/public");
1111
- const publicProvider = { ...provider, apiKey: void 0 };
1112
- const publicProviderImpl = createProvider(publicProvider);
1113
- info = await this.callGetMasterchainInfo(publicEndpoint, publicProvider, publicProviderImpl);
1114
- } else {
1339
+ for (let attempt = 0; ; attempt++) {
1340
+ try {
1341
+ info = await this.callGetMasterchainInfo(normalizedEndpoint, provider, providerImpl);
1342
+ break;
1343
+ } catch (error) {
1344
+ if (provider.type === "onfinality" && normalizedEndpoint.includes("/rpc") && provider.apiKey && error.message?.includes("backend error")) {
1345
+ this.logger.debug(`OnFinality /rpc failed, retrying with /public endpoint`);
1346
+ const baseUrl = normalizedEndpoint.split("?")[0];
1347
+ const publicEndpoint = baseUrl.replace("/rpc", "/public");
1348
+ const publicProvider = { ...provider, apiKey: void 0 };
1349
+ const publicProviderImpl = createProvider(publicProvider);
1350
+ info = await this.callGetMasterchainInfo(publicEndpoint, publicProvider, publicProviderImpl);
1351
+ break;
1352
+ }
1353
+ if (attempt < orbsRetries && this.isNonJsonResponseError(error)) {
1354
+ this.logger.debug(
1355
+ `Orbs gateway returned a non-JSON response; re-discovering endpoint (retry ${attempt + 1}/${orbsRetries})`
1356
+ );
1357
+ const freshEndpoint = await this.getEndpoint(provider);
1358
+ if (freshEndpoint) {
1359
+ normalizedEndpoint = providerImpl.normalizeEndpoint(freshEndpoint);
1360
+ continue;
1361
+ }
1362
+ }
1115
1363
  throw error;
1116
1364
  }
1117
1365
  }
@@ -1126,7 +1374,8 @@ var HealthChecker = class {
1126
1374
  if (seqno > currentHighest) {
1127
1375
  this.highestSeqno.set(provider.network, seqno);
1128
1376
  }
1129
- const blocksBehind = Math.max(0, (this.highestSeqno.get(provider.network) || seqno) - seqno);
1377
+ const baselineSeqno = this.recordSeqnoAndGetBaseline(provider.network, seqno);
1378
+ const blocksBehind = Math.max(0, baselineSeqno - seqno);
1130
1379
  let status = "available";
1131
1380
  if (blocksBehind > this.config.maxBlocksBehind) {
1132
1381
  status = "stale";
@@ -1154,17 +1403,26 @@ var HealthChecker = class {
1154
1403
  const endTime = performance.now();
1155
1404
  const latencyMs = Math.round(endTime - startTime);
1156
1405
  const errorMsg = error.message || String(error) || "Unknown error";
1406
+ const errorMsgLower = errorMsg.toLowerCase();
1157
1407
  const isCorsError = this.isCorsError(error, errorMsg);
1158
- const is429 = errorMsg.includes("429") || errorMsg.toLowerCase().includes("rate limit");
1159
- const is404 = errorMsg.includes("404") || errorMsg.toLowerCase().includes("not found");
1160
- const is503 = errorMsg.includes("503") || errorMsg.toLowerCase().includes("service unavailable");
1161
- const is502 = errorMsg.includes("502") || errorMsg.toLowerCase().includes("bad gateway");
1162
- const isTimeout = error.name === "AbortError" || errorMsg.includes("timeout");
1163
- const isOnFinalityBackendError = provider.type === "onfinality" && (errorMsg.includes("Backend error") || errorMsg.includes("backend error"));
1408
+ const responseStatus = error?.response?.status || error?.status || error?.statusCode || null;
1409
+ const statusMatch = errorMsg.match(/\b(\d{3})\b/);
1410
+ const statusFromMsg = statusMatch ? parseInt(statusMatch[1], 10) : null;
1411
+ const httpStatus = responseStatus || statusFromMsg;
1412
+ const is429 = httpStatus === 429 || errorMsgLower.includes("429") || errorMsgLower.includes("rate limit") || errorMsgLower.includes("too many requests");
1413
+ const is404 = httpStatus === 404 || errorMsgLower.includes("404") || errorMsgLower.includes("not found");
1414
+ const is401 = httpStatus === 401 || errorMsgLower.includes("401") || errorMsgLower.includes("unauthorized") || errorMsgLower.includes("invalid api key") || errorMsgLower.includes("authentication failed");
1415
+ const is403 = httpStatus === 403 || errorMsgLower.includes("403") || errorMsgLower.includes("forbidden");
1416
+ const is503 = httpStatus === 503 || errorMsgLower.includes("503") || errorMsgLower.includes("service unavailable");
1417
+ const is502 = httpStatus === 502 || errorMsgLower.includes("502") || errorMsgLower.includes("bad gateway");
1418
+ const isTimeout = error.name === "AbortError" || errorMsgLower.includes("timeout") || errorMsgLower.includes("timed out") || errorMsgLower.includes("aborted");
1419
+ const isOnFinalityBackendError = provider.type === "onfinality" && (errorMsgLower.includes("backend error") || errorMsgLower.includes("backend error"));
1164
1420
  let status = "offline";
1165
1421
  if (is429) {
1166
1422
  status = "degraded";
1167
- } else if (is404 || is503 || is502 || isOnFinalityBackendError) {
1423
+ } else if (is404 || is401 || is403) {
1424
+ status = "offline";
1425
+ } else if (is503 || is502 || isOnFinalityBackendError) {
1168
1426
  status = "offline";
1169
1427
  } else if (isTimeout) {
1170
1428
  status = "offline";
@@ -1192,8 +1450,9 @@ var HealthChecker = class {
1192
1450
  *
1193
1451
  * @param batchSize - Number of providers to test in parallel (default: 2)
1194
1452
  * @param batchDelayMs - Delay between batches in milliseconds (default: 500 to avoid rate limits)
1453
+ * If not provided, calculates delay based on lowest RPS in batch
1195
1454
  */
1196
- async testProviders(providers, batchSize = 2, batchDelayMs = 500) {
1455
+ async testProviders(providers, batchSize = 2, batchDelayMs) {
1197
1456
  const results = [];
1198
1457
  for (let i = 0; i < providers.length; i += batchSize) {
1199
1458
  const batch = providers.slice(i, i + batchSize);
@@ -1201,8 +1460,15 @@ var HealthChecker = class {
1201
1460
  batch.map((p) => this.testProvider(p))
1202
1461
  );
1203
1462
  results.push(...batchResults);
1204
- if (i + batchSize < providers.length && batchDelayMs > 0) {
1205
- await this.sleep(batchDelayMs);
1463
+ if (i + batchSize < providers.length) {
1464
+ let delay = batchDelayMs;
1465
+ if (delay === void 0) {
1466
+ const minRps = Math.min(...batch.map((p) => p.rps || 1));
1467
+ delay = Math.max(500, Math.ceil(1e3 / minRps * 1.5));
1468
+ }
1469
+ if (delay > 0) {
1470
+ await this.sleep(delay);
1471
+ }
1206
1472
  }
1207
1473
  }
1208
1474
  return results;
@@ -1253,6 +1519,7 @@ var HealthChecker = class {
1253
1519
  clearResults() {
1254
1520
  this.results.clear();
1255
1521
  this.highestSeqno.clear();
1522
+ this.seqnoSamples.clear();
1256
1523
  }
1257
1524
  /**
1258
1525
  * Mark a provider as degraded (e.g., on 429 error)
@@ -1322,6 +1589,31 @@ var HealthChecker = class {
1322
1589
  getResultKey(providerId, network) {
1323
1590
  return `${providerId}-${network}`;
1324
1591
  }
1592
+ /**
1593
+ * Record a valid seqno sample and return an outlier-robust freshness baseline
1594
+ * (median of recent samples for the network). Using the median instead of the
1595
+ * raw maximum prevents a single provider that reports an inflated seqno from
1596
+ * poisoning `blocksBehind` for every other (healthy) provider. See P2-2.
1597
+ */
1598
+ recordSeqnoAndGetBaseline(network, seqno) {
1599
+ const samples = this.seqnoSamples.get(network) ?? [];
1600
+ samples.push(seqno);
1601
+ if (samples.length > 20) {
1602
+ samples.shift();
1603
+ }
1604
+ this.seqnoSamples.set(network, samples);
1605
+ const sorted = [...samples].sort((a, b) => a - b);
1606
+ const mid = Math.floor((sorted.length - 1) / 2);
1607
+ return sorted[mid];
1608
+ }
1609
+ /**
1610
+ * Detect a "non-JSON response" error (e.g. an HTML error page returned by a
1611
+ * misbehaving gateway), used to decide whether to re-discover an Orbs endpoint.
1612
+ */
1613
+ isNonJsonResponseError(error) {
1614
+ const msg = (error?.message || String(error) || "").toLowerCase();
1615
+ return msg.includes("invalid response type") || msg.includes("expected json") || msg.includes("got text/html");
1616
+ }
1325
1617
  /**
1326
1618
  * Get endpoint URL for a provider (handles dynamic providers like Orbs)
1327
1619
  */
@@ -1591,24 +1883,36 @@ var TokenBucketRateLimiter = class {
1591
1883
  const startTime = Date.now();
1592
1884
  if (this.processing) {
1593
1885
  const acquired = await new Promise((resolve) => {
1886
+ let timeoutInterval = null;
1887
+ let resolved = false;
1888
+ const cleanup = () => {
1889
+ if (timeoutInterval !== null) {
1890
+ clearInterval(timeoutInterval);
1891
+ timeoutInterval = null;
1892
+ }
1893
+ };
1594
1894
  const checkTimeout = () => {
1595
1895
  if (Date.now() - startTime > timeoutMs) {
1596
1896
  const idx = this.requestQueue.indexOf(resolveCallback);
1597
1897
  if (idx >= 0) {
1598
1898
  this.requestQueue.splice(idx, 1);
1599
1899
  }
1600
- resolve(false);
1900
+ if (!resolved) {
1901
+ resolved = true;
1902
+ cleanup();
1903
+ resolve(false);
1904
+ }
1601
1905
  }
1602
1906
  };
1603
- const resolveCallback = () => resolve(true);
1604
- this.requestQueue.push(resolveCallback);
1605
- const timeoutInterval = setInterval(checkTimeout, 1e3);
1606
- const cleanup = () => clearInterval(timeoutInterval);
1607
- Promise.resolve().then(() => {
1608
- if (this.requestQueue.includes(resolveCallback)) ; else {
1907
+ const resolveCallback = () => {
1908
+ if (!resolved) {
1909
+ resolved = true;
1609
1910
  cleanup();
1911
+ resolve(true);
1610
1912
  }
1611
- });
1913
+ };
1914
+ this.requestQueue.push(resolveCallback);
1915
+ timeoutInterval = setInterval(checkTimeout, 1e3);
1612
1916
  });
1613
1917
  if (!acquired) {
1614
1918
  return false;
@@ -1616,24 +1920,25 @@ var TokenBucketRateLimiter = class {
1616
1920
  }
1617
1921
  this.processing = true;
1618
1922
  try {
1619
- this.refill();
1620
1923
  if (this.currentBackoff > 0) {
1621
1924
  this.logger.debug(`Applying backoff: ${this.currentBackoff}ms`);
1622
1925
  await sleep(this.currentBackoff);
1623
1926
  this.lastRefill = Date.now();
1624
- this.currentBackoff = 0;
1625
1927
  }
1928
+ this.refill();
1626
1929
  while (this.tokens <= 0) {
1627
1930
  if (Date.now() - startTime > timeoutMs) {
1628
1931
  return false;
1629
1932
  }
1630
- await sleep(Math.min(100, this.config.minDelayMs));
1933
+ const waitTime = Math.min(100, this.config.minDelayMs);
1934
+ await sleep(waitTime);
1631
1935
  this.refill();
1632
1936
  }
1633
1937
  this.tokens--;
1634
1938
  const timeSinceLastRefill = Date.now() - this.lastRefill;
1635
1939
  if (timeSinceLastRefill < this.config.minDelayMs) {
1636
- await sleep(this.config.minDelayMs - timeSinceLastRefill);
1940
+ const remainingDelay = this.config.minDelayMs - timeSinceLastRefill;
1941
+ await sleep(remainingDelay);
1637
1942
  }
1638
1943
  this.lastRefill = Date.now();
1639
1944
  return true;
@@ -1651,10 +1956,21 @@ var TokenBucketRateLimiter = class {
1651
1956
  release() {
1652
1957
  }
1653
1958
  /**
1654
- * Report a successful request (resets backoff)
1959
+ * Report a successful request (gradually reduces backoff)
1960
+ *
1961
+ * Instead of immediately clearing backoff, we gradually reduce it to prevent
1962
+ * immediately hitting the rate limit again after a single success.
1655
1963
  */
1656
1964
  reportSuccess() {
1657
- this.currentBackoff = 0;
1965
+ if (this.currentBackoff > 0) {
1966
+ this.currentBackoff = Math.max(
1967
+ this.config.minDelayMs,
1968
+ Math.floor(this.currentBackoff / 2)
1969
+ );
1970
+ if (this.currentBackoff <= this.config.minDelayMs) {
1971
+ this.currentBackoff = 0;
1972
+ }
1973
+ }
1658
1974
  this.consecutiveErrors = 0;
1659
1975
  }
1660
1976
  /**
@@ -1845,7 +2161,9 @@ var DEFAULT_CONFIG2 = {
1845
2161
  latencyWeight: 0.4,
1846
2162
  priorityWeight: 0.3,
1847
2163
  freshnessWeight: 0.3,
1848
- minStatus: ["available", "degraded"]
2164
+ minStatus: ["available", "degraded"],
2165
+ retryCooldownMs: 3e4
2166
+ // 30 seconds
1849
2167
  };
1850
2168
  var ProviderSelector = class {
1851
2169
  constructor(registry, healthChecker, config, logger, adapter = "node") {
@@ -1939,14 +2257,19 @@ var ProviderSelector = class {
1939
2257
  return defaultProvider;
1940
2258
  }
1941
2259
  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 default after cooldown: ${defaultProvider.id}`
2260
+ if (this.isRetryableError(health.error)) {
2261
+ const timeSinceFailure = Date.now() - health.lastTested.getTime();
2262
+ if (timeSinceFailure > this.config.retryCooldownMs) {
2263
+ this.logger.warn(
2264
+ `No healthy providers for ${network}, retrying failed default after cooldown: ${defaultProvider.id}`
2265
+ );
2266
+ this.activeProviderByNetwork.set(network, defaultProvider.id);
2267
+ return defaultProvider;
2268
+ }
2269
+ } else {
2270
+ this.logger.debug(
2271
+ `Skipping provider ${defaultProvider.id} with permanent error: ${health.error}`
1947
2272
  );
1948
- this.activeProviderByNetwork.set(network, defaultProvider.id);
1949
- return defaultProvider;
1950
2273
  }
1951
2274
  }
1952
2275
  }
@@ -1960,14 +2283,19 @@ var ProviderSelector = class {
1960
2283
  return provider;
1961
2284
  }
1962
2285
  if (health.success === false && health.lastTested) {
1963
- const timeSinceFailure = Date.now() - health.lastTested.getTime();
1964
- const cooldownMs = 3e4;
1965
- if (timeSinceFailure > cooldownMs) {
1966
- this.logger.warn(
1967
- `No healthy providers for ${network}, retrying failed provider after cooldown: ${provider.id}`
2286
+ if (this.isRetryableError(health.error)) {
2287
+ const timeSinceFailure = Date.now() - health.lastTested.getTime();
2288
+ if (timeSinceFailure > this.config.retryCooldownMs) {
2289
+ this.logger.warn(
2290
+ `No healthy providers for ${network}, retrying failed provider after cooldown: ${provider.id}`
2291
+ );
2292
+ this.activeProviderByNetwork.set(network, provider.id);
2293
+ return provider;
2294
+ }
2295
+ } else {
2296
+ this.logger.debug(
2297
+ `Skipping provider ${provider.id} with permanent error: ${health.error}`
1968
2298
  );
1969
- this.activeProviderByNetwork.set(network, provider.id);
1970
- return provider;
1971
2299
  }
1972
2300
  }
1973
2301
  }
@@ -1982,6 +2310,12 @@ var ProviderSelector = class {
1982
2310
  this.logger.debug(
1983
2311
  `Best provider for ${network}: ${best.id} (score: ${scored[0].score.toFixed(2)})`
1984
2312
  );
2313
+ } else if (bestHealth && bestHealth.success === false) {
2314
+ this.bestProviderByNetwork.delete(network);
2315
+ this.activeProviderByNetwork.set(network, best.id);
2316
+ this.logger.debug(
2317
+ `Best provider for ${network}: ${best.id} (score: ${scored[0].score.toFixed(2)}, failed - not cached)`
2318
+ );
1985
2319
  } else {
1986
2320
  this.activeProviderByNetwork.set(network, best.id);
1987
2321
  this.logger.debug(
@@ -2032,11 +2366,12 @@ var ProviderSelector = class {
2032
2366
  return 0.01 * (1 / (provider.priority + 1));
2033
2367
  }
2034
2368
  if (health.success === false) {
2035
- if (health.lastTested) {
2036
- const timeSinceFailure = Date.now() - health.lastTested.getTime();
2037
- const cooldownMs = 3e4;
2038
- if (timeSinceFailure > cooldownMs) {
2039
- return 1e-3 * (1 / (provider.priority + 1));
2369
+ if (this.isRetryableError(health.error)) {
2370
+ if (health.lastTested) {
2371
+ const timeSinceFailure = Date.now() - health.lastTested.getTime();
2372
+ if (timeSinceFailure > this.config.retryCooldownMs) {
2373
+ return 1e-3 * (1 / (provider.priority + 1));
2374
+ }
2040
2375
  }
2041
2376
  }
2042
2377
  return 0;
@@ -2120,7 +2455,7 @@ var ProviderSelector = class {
2120
2455
  */
2121
2456
  setCustomEndpoint(endpoint) {
2122
2457
  this.customEndpoint = endpoint?.trim() || null;
2123
- this.logger.info(`Custom endpoint: ${this.customEndpoint || "(none)"}`);
2458
+ this.logger.info(`Custom endpoint: ${this.customEndpoint ? redactUrl(this.customEndpoint) : "(none)"}`);
2124
2459
  }
2125
2460
  /**
2126
2461
  * Get custom endpoint
@@ -2231,196 +2566,37 @@ var ProviderSelector = class {
2231
2566
  return true;
2232
2567
  });
2233
2568
  }
2234
- };
2235
- function createSelector(registry, healthChecker, config, logger, adapter = "node") {
2236
- return new ProviderSelector(registry, healthChecker, config, logger, adapter);
2237
- }
2238
-
2239
- // src/utils/endpoint.ts
2240
- function normalizeV2Endpoint(endpoint, provider) {
2241
- if (provider) {
2242
- const providerImpl = createProvider(provider);
2243
- return providerImpl.normalizeEndpoint(endpoint);
2244
- }
2245
- return normalizeV2EndpointFallback(endpoint);
2246
- }
2247
- function normalizeV2EndpointFallback(endpoint) {
2248
- let normalized = endpoint.trim();
2249
- if (normalized.endsWith("/")) {
2250
- normalized = normalized.slice(0, -1);
2251
- }
2252
- if (normalized.toLowerCase().endsWith("/jsonrpc")) {
2253
- return normalized;
2254
- }
2255
- if (normalized.includes("gateway.tatum.io")) {
2256
- try {
2257
- const url = new URL(normalized);
2258
- if (!url.pathname || url.pathname === "/") {
2259
- return normalized + "/jsonRPC";
2260
- }
2261
- if (!url.pathname.toLowerCase().endsWith("/jsonrpc")) {
2262
- return normalized + "/jsonRPC";
2263
- }
2264
- } catch {
2265
- return normalized + "/jsonRPC";
2266
- }
2267
- }
2268
- if (normalized.includes("onfinality.io")) {
2269
- try {
2270
- const url = new URL(normalized);
2271
- const baseUrl = normalized.split("?")[0];
2272
- if (!url.pathname || url.pathname === "/") {
2273
- const apikey = url.searchParams.get("apikey");
2274
- if (apikey && apikey !== "{key}" && apikey.length > 0) {
2275
- return baseUrl.replace(/\/?$/, "/rpc");
2276
- }
2277
- return baseUrl.replace(/\/?$/, "/public");
2278
- }
2279
- if (url.pathname === "/rpc" || url.pathname === "/public") {
2280
- return baseUrl;
2281
- }
2282
- return baseUrl;
2283
- } catch {
2284
- if (normalized.includes("{key}")) {
2285
- return normalized.split("?")[0].replace(/\/?$/, "/public");
2286
- }
2287
- if (!normalized.includes("/rpc") && !normalized.includes("/public")) {
2288
- return normalized.split("?")[0] + "/public";
2289
- }
2290
- return normalized.split("?")[0];
2291
- }
2292
- }
2293
- if (normalized.endsWith("/api/v2")) {
2294
- return normalized + "/jsonRPC";
2295
- }
2296
- if (normalized.endsWith("/api/v3")) {
2297
- return normalized.replace("/api/v3", "/api/v2/jsonRPC");
2298
- }
2299
- if (normalized.includes("quiknode.pro") || normalized.includes("getblock.io")) {
2300
- try {
2301
- const url = new URL(normalized);
2302
- if (!url.pathname || url.pathname === "/") {
2303
- return normalized + "/jsonRPC";
2304
- }
2305
- if (!url.pathname.toLowerCase().endsWith("/jsonrpc")) {
2306
- return normalized + "/jsonRPC";
2307
- }
2308
- } catch {
2309
- return normalized + "/jsonRPC";
2569
+ /**
2570
+ * Check if an error is retryable (temporary) or permanent
2571
+ *
2572
+ * Permanent errors (never retry):
2573
+ * - 404 (Not Found) - endpoint doesn't exist
2574
+ * - 401 (Unauthorized) - invalid API key
2575
+ * - Invalid API key errors
2576
+ *
2577
+ * Retryable errors (can retry after cooldown):
2578
+ * - 503 (Service Unavailable) - temporary server issue
2579
+ * - 502 (Bad Gateway) - temporary gateway issue
2580
+ * - Timeout errors - network issues
2581
+ * - Network errors - connection issues
2582
+ * - 429 (Rate Limit) - temporary rate limit (handled separately)
2583
+ */
2584
+ isRetryableError(error) {
2585
+ if (!error) {
2586
+ return true;
2310
2587
  }
2311
- }
2312
- try {
2313
- const url = new URL(normalized);
2314
- if (!url.pathname || url.pathname === "/") {
2315
- return normalized + "/jsonRPC";
2588
+ const errorLower = error.toLowerCase();
2589
+ 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")) {
2590
+ return false;
2316
2591
  }
2317
- } catch {
2318
- }
2319
- return normalized;
2320
- }
2321
- function toV2Base(endpoint) {
2322
- let normalized = endpoint.trim();
2323
- if (normalized.endsWith("/")) {
2324
- normalized = normalized.slice(0, -1);
2325
- }
2326
- if (normalized.toLowerCase().endsWith("/jsonrpc")) {
2327
- normalized = normalized.slice(0, -8);
2328
- }
2329
- normalized = normalized.replace(/\/api\/v3\b/, "/api/v2");
2330
- if (!normalized.endsWith("/api/v2")) {
2331
- if (normalized.includes("/api/v2")) {
2332
- const idx = normalized.indexOf("/api/v2");
2333
- normalized = normalized.slice(0, idx + 7);
2592
+ 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")) {
2593
+ return true;
2334
2594
  }
2595
+ return true;
2335
2596
  }
2336
- return normalized;
2337
- }
2338
- function toV3Base(endpoint) {
2339
- const normalized = toV2Base(endpoint);
2340
- return normalized.replace("/api/v2", "/api/v3");
2341
- }
2342
- function getBaseUrl(endpoint) {
2343
- try {
2344
- const url = new URL(endpoint);
2345
- return `${url.protocol}//${url.host}`;
2346
- } catch {
2347
- return endpoint;
2348
- }
2349
- }
2350
- function isChainstackUrl(url) {
2351
- try {
2352
- const parsed = new URL(url.trim());
2353
- return parsed.hostname.includes("chainstack.com");
2354
- } catch {
2355
- return false;
2356
- }
2357
- }
2358
- function isQuickNodeUrl(url) {
2359
- try {
2360
- const parsed = new URL(url.trim());
2361
- return parsed.hostname.includes("quiknode.pro");
2362
- } catch {
2363
- return false;
2364
- }
2365
- }
2366
- function isTonCenterUrl(url) {
2367
- try {
2368
- const parsed = new URL(url.trim());
2369
- return parsed.hostname.includes("toncenter.com");
2370
- } catch {
2371
- return false;
2372
- }
2373
- }
2374
- function isOrbsUrl(url) {
2375
- try {
2376
- const parsed = new URL(url.trim());
2377
- return parsed.hostname.includes("orbs.network") || parsed.hostname.includes("ton-access");
2378
- } catch {
2379
- return false;
2380
- }
2381
- }
2382
- function buildRestUrl(baseEndpoint, method) {
2383
- const base = toV2Base(baseEndpoint);
2384
- return `${base}/${method}`;
2385
- }
2386
- function buildGetAddressStateUrl(baseEndpoint, address) {
2387
- const base = toV2Base(baseEndpoint);
2388
- return `${base}/getAddressState?address=${encodeURIComponent(address)}`;
2389
- }
2390
- function buildGetAddressBalanceUrl(baseEndpoint, address) {
2391
- const base = toV2Base(baseEndpoint);
2392
- return `${base}/getAddressBalance?address=${encodeURIComponent(address)}`;
2393
- }
2394
- function buildGetAddressInfoUrl(baseEndpoint, address) {
2395
- const base = toV2Base(baseEndpoint);
2396
- return `${base}/getAddressInformation?address=${encodeURIComponent(address)}`;
2397
- }
2398
- function detectNetworkFromEndpoint(endpoint) {
2399
- const lower = endpoint.toLowerCase();
2400
- if (lower.includes("testnet") || lower.includes("test") || lower.includes("sandbox")) {
2401
- return "testnet";
2402
- }
2403
- if (lower.includes("mainnet") || lower.includes("main") || // TonCenter mainnet doesn't have 'mainnet' in URL
2404
- lower.includes("toncenter.com") && !lower.includes("testnet")) {
2405
- return "mainnet";
2406
- }
2407
- return null;
2408
- }
2409
- function isValidHttpUrl(str) {
2410
- try {
2411
- const url = new URL(str);
2412
- return url.protocol === "http:" || url.protocol === "https:";
2413
- } catch {
2414
- return false;
2415
- }
2416
- }
2417
- function isValidWsUrl(str) {
2418
- try {
2419
- const url = new URL(str);
2420
- return url.protocol === "ws:" || url.protocol === "wss:";
2421
- } catch {
2422
- return false;
2423
- }
2597
+ };
2598
+ function createSelector(registry, healthChecker, config, logger, adapter = "node") {
2599
+ return new ProviderSelector(registry, healthChecker, config, logger, adapter);
2424
2600
  }
2425
2601
 
2426
2602
  // src/core/manager.ts
@@ -2654,10 +2830,17 @@ var _ProviderManager = class _ProviderManager {
2654
2830
  }
2655
2831
  const acquired = await this.rateLimiter.acquire(provider.id, timeoutMs);
2656
2832
  if (!acquired) {
2657
- this.options.logger.warn(`Rate limit timeout for ${provider.id}`);
2833
+ const timeoutError = new Error(`Rate limit token acquisition timeout for ${provider.id} after ${timeoutMs || 6e4}ms`);
2834
+ this.options.logger.warn(timeoutError.message);
2835
+ this.reportError(timeoutError);
2658
2836
  const next = this.selector.getNextProvider(this.network, [provider.id]);
2659
2837
  if (next) {
2660
- return normalizeV2Endpoint(next.endpointV2, next);
2838
+ const nextTimeout = timeoutMs ? Math.min(timeoutMs, 1e4) : 1e4;
2839
+ const nextAcquired = await this.rateLimiter.acquire(next.id, nextTimeout);
2840
+ if (nextAcquired) {
2841
+ return normalizeV2Endpoint(next.endpointV2, next);
2842
+ }
2843
+ this.options.logger.warn(`Rate limit timeout for next provider ${next.id}, using fallback`);
2661
2844
  }
2662
2845
  return this.getFallbackEndpoint();
2663
2846
  }
@@ -2707,26 +2890,48 @@ var _ProviderManager = class _ProviderManager {
2707
2890
  if (!provider) {
2708
2891
  provider = this.selector.getBestProvider(this.network);
2709
2892
  }
2710
- if (!provider) return;
2893
+ if (!provider) {
2894
+ this.options.logger.warn(`Cannot report error: no provider available for ${this.network}`);
2895
+ return;
2896
+ }
2711
2897
  const errorMsg = error instanceof Error ? error.message : String(error);
2712
2898
  const errorMsgLower = errorMsg.toLowerCase();
2713
- const is429 = errorMsgLower.includes("429") || errorMsgLower.includes("rate limit");
2714
- const is503 = errorMsgLower.includes("503") || errorMsgLower.includes("service unavailable");
2715
- const is502 = errorMsgLower.includes("502") || errorMsgLower.includes("bad gateway");
2716
- const is404 = errorMsgLower.includes("404") || errorMsgLower.includes("not found");
2717
- const isTimeout = errorMsgLower.includes("timeout") || errorMsgLower.includes("abort");
2899
+ const enhancedErrorMsg = `Provider ${provider.id} (${provider.name}): ${errorMsg}`;
2900
+ const responseStatus = error instanceof Error && error?.response?.status || error instanceof Error && error?.status || error instanceof Error && error?.statusCode || null;
2901
+ const statusMatch = errorMsg.match(/\b(\d{3})\b/);
2902
+ const statusFromMsg = statusMatch ? parseInt(statusMatch[1], 10) : null;
2903
+ const httpStatus = responseStatus || statusFromMsg;
2904
+ const is429 = httpStatus === 429 || errorMsgLower.includes("429") || errorMsgLower.includes("rate limit") || errorMsgLower.includes("too many requests");
2905
+ const is503 = httpStatus === 503 || errorMsgLower.includes("503") || errorMsgLower.includes("service unavailable");
2906
+ const is502 = httpStatus === 502 || errorMsgLower.includes("502") || errorMsgLower.includes("bad gateway");
2907
+ const is404 = httpStatus === 404 || errorMsgLower.includes("404") || errorMsgLower.includes("not found");
2908
+ const is401 = httpStatus === 401 || errorMsgLower.includes("401") || errorMsgLower.includes("unauthorized") || errorMsgLower.includes("invalid api key") || errorMsgLower.includes("authentication failed");
2909
+ const isTimeout = error instanceof Error && error.name === "AbortError" || errorMsgLower.includes("timeout") || errorMsgLower.includes("timed out") || errorMsgLower.includes("abort");
2718
2910
  if (isRateLimitError(error) || is429) {
2719
2911
  this.rateLimiter.reportRateLimitError(provider.id);
2720
- this.healthChecker.markDegraded(provider.id, this.network, errorMsg);
2721
- } else if (is503 || is502 || is404 || isTimeout) {
2912
+ this.healthChecker.markDegraded(provider.id, this.network, enhancedErrorMsg);
2913
+ this.options.logger.warn(`${enhancedErrorMsg} - Rate limit detected, switching to next provider`);
2914
+ } else if (is404 || is401) {
2915
+ this.rateLimiter.reportError(provider.id);
2916
+ this.healthChecker.markOffline(provider.id, this.network, enhancedErrorMsg);
2917
+ this.options.logger.error(`${enhancedErrorMsg} - Permanent error (${is404 ? "404" : "401"}), provider marked offline`);
2918
+ } else if (is503 || is502 || isTimeout) {
2722
2919
  this.rateLimiter.reportError(provider.id);
2723
- this.healthChecker.markOffline(provider.id, this.network, errorMsg);
2920
+ this.healthChecker.markOffline(provider.id, this.network, enhancedErrorMsg);
2921
+ const errorType = is503 ? "503" : is502 ? "502" : "timeout";
2922
+ this.options.logger.warn(`${enhancedErrorMsg} - Server error (${errorType}), switching to next provider`);
2724
2923
  } else {
2725
2924
  this.rateLimiter.reportError(provider.id);
2726
- this.healthChecker.markDegraded(provider.id, this.network, errorMsg);
2925
+ this.healthChecker.markDegraded(provider.id, this.network, enhancedErrorMsg);
2926
+ this.options.logger.warn(`${enhancedErrorMsg} - Unknown error, switching to next provider`);
2927
+ }
2928
+ const nextProvider = this.selector.handleProviderFailure(provider.id, this.network);
2929
+ if (nextProvider) {
2930
+ this.options.logger.info(`Failover: switched from ${provider.id} to ${nextProvider.id}`);
2931
+ } else {
2932
+ this.options.logger.warn(`Failover: no alternative provider available for ${this.network}`);
2727
2933
  }
2728
2934
  this.selector.clearCache(this.network);
2729
- this.selector.handleProviderFailure(provider.id, this.network);
2730
2935
  this.notifyListeners();
2731
2936
  }
2732
2937
  // ========================================================================
@@ -2993,7 +3198,7 @@ var NodeAdapter = class {
2993
3198
  network,
2994
3199
  createdAt: Date.now()
2995
3200
  };
2996
- this.logger.debug(`Created TonClient for ${network}`, { endpoint });
3201
+ this.logger.debug(`Created TonClient for ${network}`, { endpoint: redactUrl(endpoint) });
2997
3202
  return client;
2998
3203
  }
2999
3204
  /**
@@ -3277,9 +3482,11 @@ var BrowserAdapter = class {
3277
3482
  // ========================================================================
3278
3483
  /**
3279
3484
  * Make a JSON-RPC call to the TON API
3485
+ *
3486
+ * Note: Uses rate limiting to prevent 429 errors.
3280
3487
  */
3281
3488
  async jsonRpc(method, params = {}, timeoutMs = 1e4) {
3282
- const endpoint = await this.manager.getEndpoint();
3489
+ const endpoint = await this.manager.getEndpointWithRateLimit(timeoutMs);
3283
3490
  const controller = new AbortController();
3284
3491
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
3285
3492
  try {
@@ -3318,9 +3525,11 @@ var BrowserAdapter = class {
3318
3525
  // ========================================================================
3319
3526
  /**
3320
3527
  * Get address state
3528
+ *
3529
+ * Note: Uses rate limiting to prevent 429 errors.
3321
3530
  */
3322
3531
  async getAddressState(address, timeoutMs = 1e4) {
3323
- const endpoint = await this.manager.getEndpoint();
3532
+ const endpoint = await this.manager.getEndpointWithRateLimit(timeoutMs);
3324
3533
  const baseV2 = toV2Base(endpoint);
3325
3534
  const url = `${baseV2}/getAddressState?address=${encodeURIComponent(address)}`;
3326
3535
  const controller = new AbortController();
@@ -3350,9 +3559,11 @@ var BrowserAdapter = class {
3350
3559
  }
3351
3560
  /**
3352
3561
  * Get address balance
3562
+ *
3563
+ * Note: Uses rate limiting to prevent 429 errors.
3353
3564
  */
3354
3565
  async getAddressBalance(address, timeoutMs = 1e4) {
3355
- const endpoint = await this.manager.getEndpoint();
3566
+ const endpoint = await this.manager.getEndpointWithRateLimit(timeoutMs);
3356
3567
  const baseV2 = toV2Base(endpoint);
3357
3568
  const url = `${baseV2}/getAddressBalance?address=${encodeURIComponent(address)}`;
3358
3569
  const controller = new AbortController();
@@ -3382,9 +3593,11 @@ var BrowserAdapter = class {
3382
3593
  }
3383
3594
  /**
3384
3595
  * Get address information
3596
+ *
3597
+ * Note: Uses rate limiting to prevent 429 errors.
3385
3598
  */
3386
3599
  async getAddressInfo(address, timeoutMs = 1e4) {
3387
- const endpoint = await this.manager.getEndpoint();
3600
+ const endpoint = await this.manager.getEndpointWithRateLimit(timeoutMs);
3388
3601
  const baseV2 = toV2Base(endpoint);
3389
3602
  const url = `${baseV2}/getAddressInformation?address=${encodeURIComponent(address)}`;
3390
3603
  const controller = new AbortController();
@@ -3412,9 +3625,11 @@ var BrowserAdapter = class {
3412
3625
  }
3413
3626
  /**
3414
3627
  * Run get method
3628
+ *
3629
+ * Note: Uses rate limiting to prevent 429 errors.
3415
3630
  */
3416
3631
  async runGetMethod(address, method, stack = [], timeoutMs = 15e3) {
3417
- const endpoint = await this.manager.getEndpoint();
3632
+ const endpoint = await this.manager.getEndpointWithRateLimit(timeoutMs);
3418
3633
  const baseV2 = toV2Base(endpoint);
3419
3634
  const url = `${baseV2}/runGetMethod`;
3420
3635
  const controller = new AbortController();
@@ -3520,6 +3735,6 @@ async function createBrowserAdapterForNetwork(network, configPath, logger) {
3520
3735
  return new BrowserAdapter(manager, logger);
3521
3736
  }
3522
3737
 
3523
- export { ApiVersionSchema, BaseProvider, BrowserAdapter, CHAINSTACK_RATE_LIMIT, ChainstackProvider, ConfigError, DEFAULT_CONTRACT_TIMEOUT_MS, DEFAULT_HEALTH_CHECK_TIMEOUT_MS, DEFAULT_PROVIDERS, DEFAULT_PROVIDER_TIMEOUT_MS, DEFAULT_RATE_LIMIT, GenericProvider, GetBlockProvider, HealthChecker, NetworkSchema, NodeAdapter, ORBS_RATE_LIMIT, OnFinalityProvider, OrbsProvider, ProviderConfigSchema, ProviderError, ProviderManager, ProviderRegistry, ProviderSelector, ProviderTypeSchema, QUICKNODE_RATE_LIMIT, QuickNodeProvider, RateLimitError, RateLimiterManager, RpcConfigSchema, TatumProvider, TimeoutError, TokenBucketRateLimiter, TonCenterProvider, buildGetAddressBalanceUrl, buildGetAddressInfoUrl, buildGetAddressStateUrl, buildRestUrl, createBrowserAdapter, createBrowserAdapterForNetwork, createDefaultConfig, createDefaultRegistry, createEmptyConfig, createHealthChecker, createNodeAdapter, createProvider, createProviderManager, createRateLimiter, createRateLimiterManager, createRegistry, createRegistryFromData, createRegistryFromFile, createSelector, createTimeoutController, detectNetworkFromEndpoint, fetchWithTimeout, getBaseUrl, getDefaultProvidersForNetwork, getEnvVar, getProviderManager, getProvidersForNetwork, getRateLimitForType, getTonClient, getTonClientForNetwork, getTonClientWithRateLimit, isApiVersion, isChainstackUrl, isNetwork, isOrbsUrl, isProviderType, isQuickNodeUrl, isRateLimitError, isTimeoutError, isTonCenterUrl, isValidHttpUrl, isValidWsUrl, loadBuiltinConfig, loadConfig, loadConfigFromData, loadConfigFromUrl, mergeConfigs, mergeWithDefaults, normalizeV2Endpoint, parseProviderConfig, parseRpcConfig, resetNodeAdapter, resolveAllProviders, resolveEndpoints, resolveKeyPlaceholder, resolveProvider, sleep, toV2Base, toV3Base, withRetry, withTimeout, withTimeoutAndRetry, withTimeoutFn };
3738
+ export { ApiVersionSchema, BaseProvider, BrowserAdapter, CHAINSTACK_RATE_LIMIT, ChainstackProvider, ConfigError, DEFAULT_CONTRACT_TIMEOUT_MS, DEFAULT_HEALTH_CHECK_TIMEOUT_MS, DEFAULT_PROVIDERS, DEFAULT_PROVIDER_TIMEOUT_MS, DEFAULT_RATE_LIMIT, GenericProvider, GetBlockProvider, HealthChecker, NetworkSchema, NodeAdapter, ORBS_RATE_LIMIT, OnFinalityProvider, OrbsProvider, ProviderConfigSchema, ProviderError, ProviderManager, ProviderRegistry, ProviderSelector, ProviderTypeSchema, QUICKNODE_RATE_LIMIT, QuickNodeProvider, RateLimitError, RateLimiterManager, RpcConfigSchema, TatumProvider, TimeoutError, TokenBucketRateLimiter, TonCenterProvider, buildGetAddressBalanceUrl, buildGetAddressInfoUrl, buildGetAddressStateUrl, buildRestUrl, createBrowserAdapter, createBrowserAdapterForNetwork, createDefaultConfig, createDefaultRegistry, createEmptyConfig, createHealthChecker, createNodeAdapter, createProvider, createProviderManager, createRateLimiter, createRateLimiterManager, createRegistry, createRegistryFromData, createRegistryFromFile, createSelector, createTimeoutController, detectNetworkFromEndpoint, fetchWithTimeout, getBaseUrl, getDefaultProvidersForNetwork, getEnvVar, getProviderManager, getProvidersForNetwork, getRateLimitForType, getTonClient, getTonClientForNetwork, getTonClientWithRateLimit, isApiVersion, isChainstackUrl, isNetwork, isOrbsUrl, isProviderType, isQuickNodeUrl, isRateLimitError, isTimeoutError, isTonCenterUrl, isValidHttpUrl, isValidWsUrl, loadBuiltinConfig, loadConfig, loadConfigFromData, loadConfigFromUrl, mergeConfigs, mergeWithDefaults, normalizeV2Endpoint, parseProviderConfig, parseRpcConfig, redactUrl, resetNodeAdapter, resolveAllProviders, resolveEndpoints, resolveKeyPlaceholder, resolveProvider, sleep, toV2Base, toV3Base, withRetry, withTimeout, withTimeoutAndRetry, withTimeoutFn };
3524
3739
  //# sourceMappingURL=index.js.map
3525
3740
  //# sourceMappingURL=index.js.map