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