ton-provider-system 0.1.0 → 0.1.2

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
@@ -3,6 +3,7 @@
3
3
  var zod = require('zod');
4
4
  var ton = require('@ton/ton');
5
5
 
6
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
6
7
  // src/types.ts
7
8
  var TimeoutError = class extends Error {
8
9
  constructor(operation, timeoutMs, message) {
@@ -199,7 +200,10 @@ function resolveProvider(id, config) {
199
200
  console.warn(`[ConfigParser] Provider ${id} has no valid endpoints after resolution`);
200
201
  return null;
201
202
  }
202
- const apiKey = config.apiKeyEnvVar ? getEnvVar(config.apiKeyEnvVar) : void 0;
203
+ let apiKey = config.apiKeyEnvVar ? getEnvVar(config.apiKeyEnvVar) : void 0;
204
+ if (!apiKey && config.keyEnvVar && config.type === "onfinality") {
205
+ apiKey = getEnvVar(config.keyEnvVar);
206
+ }
203
207
  return {
204
208
  id,
205
209
  name: config.name,
@@ -250,13 +254,24 @@ function getDefaultProvidersForNetwork(config, network) {
250
254
  async function loadBuiltinConfig() {
251
255
  const fs = await import('fs').then((m) => m.promises);
252
256
  const path = await import('path');
257
+ const { fileURLToPath } = await import('url');
258
+ const getDirname = () => {
259
+ try {
260
+ if ((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))) {
261
+ return path.dirname(fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
262
+ }
263
+ } catch {
264
+ }
265
+ return process.cwd();
266
+ };
267
+ const dirname = getDirname();
253
268
  const possiblePaths = [
254
269
  // When running from project root (e.g., ts-node scripts/...)
255
270
  path.resolve(process.cwd(), "provider_system", RPC_CONFIG_FILENAME),
256
271
  // When running from provider_system folder
257
272
  path.resolve(process.cwd(), RPC_CONFIG_FILENAME),
258
- // Relative to this file (CommonJS style)
259
- path.resolve(__dirname, "..", RPC_CONFIG_FILENAME)
273
+ // Relative to this file (ESM style)
274
+ path.resolve(dirname, "..", RPC_CONFIG_FILENAME)
260
275
  ];
261
276
  for (const configPath of possiblePaths) {
262
277
  try {
@@ -549,12 +564,63 @@ function normalizeV2Endpoint(endpoint) {
549
564
  if (normalized.toLowerCase().endsWith("/jsonrpc")) {
550
565
  return normalized;
551
566
  }
567
+ if (normalized.includes("gateway.tatum.io")) {
568
+ try {
569
+ const url = new URL(normalized);
570
+ if (!url.pathname || url.pathname === "/") {
571
+ return normalized + "/jsonRPC";
572
+ }
573
+ if (!url.pathname.toLowerCase().endsWith("/jsonrpc")) {
574
+ return normalized + "/jsonRPC";
575
+ }
576
+ } catch {
577
+ return normalized + "/jsonRPC";
578
+ }
579
+ }
580
+ if (normalized.includes("onfinality.io")) {
581
+ try {
582
+ const url = new URL(normalized);
583
+ const baseUrl = normalized.split("?")[0];
584
+ if (!url.pathname || url.pathname === "/") {
585
+ const apikey = url.searchParams.get("apikey");
586
+ if (apikey && apikey !== "{key}" && apikey.length > 0) {
587
+ return baseUrl.replace(/\/?$/, "/rpc");
588
+ }
589
+ return baseUrl.replace(/\/?$/, "/public");
590
+ }
591
+ if (url.pathname === "/rpc" || url.pathname === "/public") {
592
+ return baseUrl;
593
+ }
594
+ return baseUrl;
595
+ } catch {
596
+ if (normalized.includes("{key}")) {
597
+ return normalized.split("?")[0].replace(/\/?$/, "/public");
598
+ }
599
+ if (!normalized.includes("/rpc") && !normalized.includes("/public")) {
600
+ return normalized.split("?")[0] + "/public";
601
+ }
602
+ return normalized.split("?")[0];
603
+ }
604
+ }
552
605
  if (normalized.endsWith("/api/v2")) {
553
606
  return normalized + "/jsonRPC";
554
607
  }
555
608
  if (normalized.endsWith("/api/v3")) {
556
609
  return normalized.replace("/api/v3", "/api/v2/jsonRPC");
557
610
  }
611
+ if (normalized.includes("quiknode.pro") || normalized.includes("getblock.io")) {
612
+ try {
613
+ const url = new URL(normalized);
614
+ if (!url.pathname || url.pathname === "/") {
615
+ return normalized + "/jsonRPC";
616
+ }
617
+ if (!url.pathname.toLowerCase().endsWith("/jsonrpc")) {
618
+ return normalized + "/jsonRPC";
619
+ }
620
+ } catch {
621
+ return normalized + "/jsonRPC";
622
+ }
623
+ }
558
624
  try {
559
625
  const url = new URL(normalized);
560
626
  if (!url.pathname || url.pathname === "/") {
@@ -582,7 +648,7 @@ function toV2Base(endpoint) {
582
648
  return normalized;
583
649
  }
584
650
  function toV3Base(endpoint) {
585
- let normalized = toV2Base(endpoint);
651
+ const normalized = toV2Base(endpoint);
586
652
  return normalized.replace("/api/v2", "/api/v3");
587
653
  }
588
654
  function getBaseUrl(endpoint) {
@@ -682,11 +748,19 @@ var DEFAULT_CONFIG = {
682
748
  degradedLatencyMs: 3e3
683
749
  };
684
750
  var HealthChecker = class {
685
- constructor(config, logger) {
751
+ constructor(config, logger, rateLimiter) {
686
752
  this.results = /* @__PURE__ */ new Map();
687
753
  this.highestSeqno = /* @__PURE__ */ new Map();
754
+ this.rateLimiter = null;
688
755
  this.config = { ...DEFAULT_CONFIG, ...config };
689
756
  this.logger = logger || consoleLogger2;
757
+ this.rateLimiter = rateLimiter || null;
758
+ }
759
+ /**
760
+ * Set rate limiter (can be set after construction)
761
+ */
762
+ setRateLimiter(rateLimiter) {
763
+ this.rateLimiter = rateLimiter;
690
764
  }
691
765
  /**
692
766
  * Test a single provider's health
@@ -706,15 +780,42 @@ var HealthChecker = class {
706
780
  };
707
781
  this.results.set(key, testingResult);
708
782
  try {
783
+ if (this.rateLimiter) {
784
+ const acquired = await this.rateLimiter.acquire(provider.id, this.config.timeoutMs);
785
+ if (!acquired) {
786
+ throw new Error("Rate limit timeout - unable to acquire token for health check");
787
+ }
788
+ }
709
789
  const endpoint = await this.getEndpoint(provider);
710
790
  if (!endpoint) {
711
791
  throw new Error("No valid endpoint available");
712
792
  }
713
- const normalizedEndpoint = normalizeV2Endpoint(endpoint);
714
- const info = await this.callGetMasterchainInfo(normalizedEndpoint);
793
+ if (provider.type === "tatum" && !provider.apiKey) {
794
+ throw new Error("Tatum provider requires API key (set TATUM_API_KEY_TESTNET or TATUM_API_KEY_MAINNET)");
795
+ }
796
+ let normalizedEndpoint = this.normalizeEndpointForProvider(provider, endpoint);
797
+ if (provider.type === "onfinality") {
798
+ this.logger.debug(`OnFinality endpoint: ${endpoint} -> ${normalizedEndpoint}, API key: ${provider.apiKey ? "set" : "not set"}`);
799
+ }
800
+ let info;
801
+ try {
802
+ info = await this.callGetMasterchainInfo(normalizedEndpoint, provider);
803
+ } catch (error) {
804
+ if (provider.type === "onfinality" && normalizedEndpoint.includes("/rpc") && provider.apiKey && error.message?.includes("backend error")) {
805
+ this.logger.debug(`OnFinality /rpc failed, retrying with /public endpoint`);
806
+ const publicEndpoint = normalizedEndpoint.replace("/rpc", "/public");
807
+ info = await this.callGetMasterchainInfo(publicEndpoint, { ...provider, apiKey: void 0 });
808
+ } else {
809
+ throw error;
810
+ }
811
+ }
715
812
  const endTime = performance.now();
716
813
  const latencyMs = Math.round(endTime - startTime);
717
- const seqno = info.last?.seqno || 0;
814
+ const infoWithLast = info;
815
+ const seqno = infoWithLast.last?.seqno;
816
+ if (!seqno || seqno <= 0 || !Number.isInteger(seqno)) {
817
+ throw new Error("Invalid seqno in response (must be positive integer)");
818
+ }
718
819
  const currentHighest = this.highestSeqno.get(provider.network) || 0;
719
820
  if (seqno > currentHighest) {
720
821
  this.highestSeqno.set(provider.network, seqno);
@@ -748,11 +849,14 @@ var HealthChecker = class {
748
849
  const errorMsg = error.message || String(error) || "Unknown error";
749
850
  const is429 = errorMsg.includes("429") || errorMsg.toLowerCase().includes("rate limit");
750
851
  const is404 = errorMsg.includes("404") || errorMsg.toLowerCase().includes("not found");
852
+ const is503 = errorMsg.includes("503") || errorMsg.toLowerCase().includes("service unavailable");
853
+ const is502 = errorMsg.includes("502") || errorMsg.toLowerCase().includes("bad gateway");
751
854
  const isTimeout = error.name === "AbortError" || errorMsg.includes("timeout");
855
+ const isOnFinalityBackendError = provider.type === "onfinality" && (errorMsg.includes("Backend error") || errorMsg.includes("backend error"));
752
856
  let status = "offline";
753
857
  if (is429) {
754
858
  status = "degraded";
755
- } else if (is404) {
859
+ } else if (is404 || is503 || is502 || isOnFinalityBackendError) {
756
860
  status = "offline";
757
861
  } else if (isTimeout) {
758
862
  status = "offline";
@@ -775,8 +879,11 @@ var HealthChecker = class {
775
879
  }
776
880
  /**
777
881
  * Test multiple providers in parallel with staggered batches
882
+ *
883
+ * @param batchSize - Number of providers to test in parallel (default: 2)
884
+ * @param batchDelayMs - Delay between batches in milliseconds (default: 500 to avoid rate limits)
778
885
  */
779
- async testProviders(providers, batchSize = 2, batchDelayMs = 300) {
886
+ async testProviders(providers, batchSize = 2, batchDelayMs = 500) {
780
887
  const results = [];
781
888
  for (let i = 0; i < providers.length; i += batchSize) {
782
889
  const batch = providers.slice(i, i + batchSize);
@@ -890,15 +997,37 @@ var HealthChecker = class {
890
997
  return provider.endpointV2 || provider.endpointV3 || null;
891
998
  }
892
999
  /**
893
- * Call getMasterchainInfo API
1000
+ * Normalize endpoint for provider-specific requirements
1001
+ *
1002
+ * Note: normalizeV2Endpoint now handles all provider-specific cases correctly,
1003
+ * including Tatum (/jsonRPC), OnFinality (/public or /rpc), QuickNode, and GetBlock.
1004
+ */
1005
+ normalizeEndpointForProvider(provider, endpoint) {
1006
+ if (provider.type === "tatum" && endpoint.includes("api.tatum.io/v3/blockchain/node")) {
1007
+ const network = provider.network === "testnet" ? "testnet" : "mainnet";
1008
+ endpoint = `https://ton-${network}.gateway.tatum.io`;
1009
+ }
1010
+ return normalizeV2Endpoint(endpoint);
1011
+ }
1012
+ /**
1013
+ * Call getMasterchainInfo API with provider-specific handling
894
1014
  */
895
- async callGetMasterchainInfo(endpoint) {
1015
+ async callGetMasterchainInfo(endpoint, provider) {
896
1016
  const controller = new AbortController();
897
1017
  const timeoutId = setTimeout(() => controller.abort(), this.config.timeoutMs);
1018
+ const headers = {
1019
+ "Content-Type": "application/json"
1020
+ };
1021
+ if (provider.type === "tatum" && provider.apiKey) {
1022
+ headers["x-api-key"] = provider.apiKey;
1023
+ }
1024
+ if (provider.type === "onfinality" && provider.apiKey) {
1025
+ headers["apikey"] = provider.apiKey;
1026
+ }
898
1027
  try {
899
1028
  const response = await fetch(endpoint, {
900
1029
  method: "POST",
901
- headers: { "Content-Type": "application/json" },
1030
+ headers,
902
1031
  body: JSON.stringify({
903
1032
  id: "1",
904
1033
  jsonrpc: "2.0",
@@ -908,20 +1037,63 @@ var HealthChecker = class {
908
1037
  signal: controller.signal
909
1038
  });
910
1039
  clearTimeout(timeoutId);
1040
+ const contentType = response.headers.get("content-type") || "";
1041
+ let text = null;
1042
+ let data;
1043
+ if (!contentType.includes("application/json")) {
1044
+ text = await response.text();
1045
+ this.logger.debug(`${provider.type} non-JSON response (${contentType}): ${text.substring(0, 200)}`);
1046
+ if (provider.type === "onfinality" && text.includes("Backend error")) {
1047
+ throw new Error(`OnFinality backend error: ${text}`);
1048
+ }
1049
+ throw new Error(`Invalid response type: expected JSON, got ${contentType}. Response: ${text.substring(0, 100)}`);
1050
+ }
911
1051
  if (!response.ok) {
912
- throw new Error(`HTTP ${response.status}`);
1052
+ try {
1053
+ data = await response.json();
1054
+ const errorObj = data;
1055
+ const errorMsg = typeof errorObj.error === "string" ? errorObj.error : errorObj.error?.message || `HTTP ${response.status}`;
1056
+ throw new Error(errorMsg);
1057
+ } catch {
1058
+ throw new Error(`HTTP ${response.status}`);
1059
+ }
913
1060
  }
914
- const data = await response.json();
915
- if (data && typeof data === "object" && "ok" in data) {
916
- if (!data.ok) {
917
- throw new Error(data.error || "API returned ok=false");
1061
+ data = await response.json();
1062
+ let info;
1063
+ if (data && typeof data === "object") {
1064
+ const dataObj = data;
1065
+ if ("ok" in dataObj) {
1066
+ if (!dataObj.ok) {
1067
+ const error = dataObj.error;
1068
+ throw new Error(error || "API returned ok=false");
1069
+ }
1070
+ const result = dataObj.result;
1071
+ info = result || dataObj;
1072
+ } else if ("result" in dataObj) {
1073
+ info = dataObj.result;
1074
+ } else if ("last" in dataObj || "@type" in dataObj) {
1075
+ info = dataObj;
1076
+ } else if ("error" in dataObj) {
1077
+ const errorObj = dataObj.error;
1078
+ const errorMsg = typeof errorObj === "string" ? errorObj : errorObj?.message || errorObj?.code || String(errorObj);
1079
+ throw new Error(`API error: ${errorMsg}`);
1080
+ } else {
1081
+ throw new Error(`Unknown response format from ${provider.type}`);
918
1082
  }
919
- return data.result || data;
1083
+ } else {
1084
+ throw new Error(`Invalid response type: ${typeof data}`);
920
1085
  }
921
- if (data.result) {
922
- return data.result;
1086
+ if (!info || typeof info !== "object") {
1087
+ this.logger.debug(`Invalid response structure from ${provider.type}: ${JSON.stringify(data)}`);
1088
+ throw new Error("Invalid response structure");
923
1089
  }
924
- return data;
1090
+ const infoObj = info;
1091
+ const seqno = infoObj.last?.seqno;
1092
+ if (seqno === void 0 || seqno === null || seqno <= 0 || !Number.isInteger(seqno)) {
1093
+ this.logger.debug(`Invalid seqno from ${provider.type}:`, { seqno, info });
1094
+ throw new Error(`Invalid seqno: ${seqno} (must be positive integer)`);
1095
+ }
1096
+ return info;
925
1097
  } catch (error) {
926
1098
  clearTimeout(timeoutId);
927
1099
  throw error;
@@ -931,8 +1103,8 @@ var HealthChecker = class {
931
1103
  return new Promise((resolve) => setTimeout(resolve, ms));
932
1104
  }
933
1105
  };
934
- function createHealthChecker(config, logger) {
935
- return new HealthChecker(config, logger);
1106
+ function createHealthChecker(config, logger, rateLimiter) {
1107
+ return new HealthChecker(config, logger, rateLimiter);
936
1108
  }
937
1109
 
938
1110
  // src/utils/timeout.ts
@@ -1404,7 +1576,7 @@ var ProviderSelector = class {
1404
1576
  if (cachedBestId) {
1405
1577
  const cached = this.registry.getProvider(cachedBestId);
1406
1578
  const health = this.healthChecker.getResult(cachedBestId, network);
1407
- if (cached && health && this.config.minStatus.includes(health.status)) {
1579
+ if (cached && health && health.success !== false && this.config.minStatus.includes(health.status)) {
1408
1580
  return cached;
1409
1581
  }
1410
1582
  }
@@ -1425,17 +1597,39 @@ var ProviderSelector = class {
1425
1597
  })).filter((item) => item.score > 0).sort((a, b) => b.score - a.score);
1426
1598
  if (scored.length === 0) {
1427
1599
  const defaults = this.registry.getDefaultOrderForNetwork(network);
1428
- if (defaults.length > 0) {
1429
- this.logger.warn(`No healthy providers for ${network}, using first default`);
1430
- return defaults[0];
1600
+ for (const defaultProvider of defaults) {
1601
+ const health = this.healthChecker.getResult(defaultProvider.id, network);
1602
+ if (!health || health.status === "untested" || health.success === true) {
1603
+ this.logger.warn(
1604
+ `No healthy providers for ${network}, using default: ${defaultProvider.id}`
1605
+ );
1606
+ return defaultProvider;
1607
+ }
1608
+ }
1609
+ for (const provider of providers) {
1610
+ const health = this.healthChecker.getResult(provider.id, network);
1611
+ if (!health || health.status === "untested") {
1612
+ this.logger.warn(
1613
+ `No tested healthy providers for ${network}, using untested: ${provider.id}`
1614
+ );
1615
+ return provider;
1616
+ }
1431
1617
  }
1432
- return providers[0];
1618
+ this.logger.error(`No available providers for ${network} (all tested and failed)`);
1619
+ return null;
1433
1620
  }
1434
1621
  const best = scored[0].provider;
1435
- this.bestProviderByNetwork.set(network, best.id);
1436
- this.logger.debug(
1437
- `Best provider for ${network}: ${best.id} (score: ${scored[0].score.toFixed(2)})`
1438
- );
1622
+ const bestHealth = this.healthChecker.getResult(best.id, network);
1623
+ if (bestHealth && bestHealth.success === true) {
1624
+ this.bestProviderByNetwork.set(network, best.id);
1625
+ this.logger.debug(
1626
+ `Best provider for ${network}: ${best.id} (score: ${scored[0].score.toFixed(2)})`
1627
+ );
1628
+ } else {
1629
+ this.logger.debug(
1630
+ `Best provider for ${network}: ${best.id} (score: ${scored[0].score.toFixed(2)}, untested)`
1631
+ );
1632
+ }
1439
1633
  return best;
1440
1634
  }
1441
1635
  /**
@@ -1471,7 +1665,7 @@ var ProviderSelector = class {
1471
1665
  scoreProvider(provider, network) {
1472
1666
  const health = this.healthChecker.getResult(provider.id, network);
1473
1667
  if (!health || health.status === "untested") {
1474
- return 0.1 * (1 / (provider.priority + 1));
1668
+ return 0.01 * (1 / (provider.priority + 1));
1475
1669
  }
1476
1670
  if (health.success === false) {
1477
1671
  return 0;
@@ -1701,28 +1895,29 @@ var _ProviderManager = class _ProviderManager {
1701
1895
  const config = await loadConfig();
1702
1896
  const mergedConfig = mergeWithDefaults(config);
1703
1897
  this.registry = new ProviderRegistry(mergedConfig, this.options.logger);
1898
+ this.rateLimiter = createRateLimiterManager(this.options.logger);
1899
+ for (const provider of this.registry.getAllProviders()) {
1900
+ const config2 = getRateLimitForType(provider.type);
1901
+ this.rateLimiter.setConfig(provider.id, {
1902
+ ...config2,
1903
+ rps: provider.rps,
1904
+ minDelayMs: Math.ceil(1e3 / provider.rps)
1905
+ });
1906
+ }
1704
1907
  this.healthChecker = createHealthChecker(
1705
1908
  {
1706
1909
  timeoutMs: this.options.requestTimeoutMs,
1707
1910
  maxBlocksBehind: this.options.maxBlocksBehind
1708
1911
  },
1709
- this.options.logger
1912
+ this.options.logger,
1913
+ this.rateLimiter
1710
1914
  );
1711
- this.rateLimiter = createRateLimiterManager(this.options.logger);
1712
1915
  this.selector = createSelector(
1713
1916
  this.registry,
1714
1917
  this.healthChecker,
1715
1918
  void 0,
1716
1919
  this.options.logger
1717
1920
  );
1718
- for (const provider of this.registry.getAllProviders()) {
1719
- const config2 = getRateLimitForType(provider.type);
1720
- this.rateLimiter.setConfig(provider.id, {
1721
- ...config2,
1722
- rps: provider.rps,
1723
- minDelayMs: Math.ceil(1e3 / provider.rps)
1724
- });
1725
- }
1726
1921
  this.initialized = true;
1727
1922
  this.notifyListeners();
1728
1923
  if (testProviders) {
@@ -2112,6 +2307,17 @@ var NodeAdapter = class {
2112
2307
  * Get TonClient instance
2113
2308
  *
2114
2309
  * Creates a new client if endpoint changed, otherwise returns cached.
2310
+ *
2311
+ * NOTE: Direct TonClient calls bypass rate limiting. For rate-limited operations,
2312
+ * use adapter methods (getAddressState, runGetMethod, etc.) or wrap your calls
2313
+ * with rate limit token acquisition.
2314
+ *
2315
+ * Example with rate limiting:
2316
+ * ```typescript
2317
+ * const endpoint = await adapter.manager.getEndpointWithRateLimit();
2318
+ * // Make your TonClient call
2319
+ * adapter.manager.reportSuccess(); // or reportError() on failure
2320
+ * ```
2115
2321
  */
2116
2322
  async getClient() {
2117
2323
  const endpoint = await this.manager.getEndpoint();
@@ -2137,6 +2343,19 @@ var NodeAdapter = class {
2137
2343
  this.logger.debug(`Created TonClient for ${network}`, { endpoint });
2138
2344
  return client;
2139
2345
  }
2346
+ /**
2347
+ * Get TonClient with rate limiting applied
2348
+ *
2349
+ * Acquires a rate limit token before returning the client.
2350
+ * Use this when you need to ensure rate limiting is respected.
2351
+ *
2352
+ * Note: This only acquires ONE token. For multiple operations,
2353
+ * you should acquire tokens before each operation or use adapter methods.
2354
+ */
2355
+ async getClientWithRateLimit(timeoutMs) {
2356
+ await this.manager.getEndpointWithRateLimit(timeoutMs);
2357
+ return this.getClient();
2358
+ }
2140
2359
  /**
2141
2360
  * Reset client cache (forces new client creation)
2142
2361
  */
@@ -2322,6 +2541,22 @@ async function getTonClient(manager) {
2322
2541
  const adapter = new NodeAdapter(manager);
2323
2542
  return adapter.getClient();
2324
2543
  }
2544
+ async function getTonClientWithRateLimit(manager) {
2545
+ const adapter = new NodeAdapter(manager);
2546
+ const client = await adapter.getClient();
2547
+ const withRateLimit = async (fn) => {
2548
+ await manager.getEndpointWithRateLimit();
2549
+ try {
2550
+ const result = await fn();
2551
+ manager.reportSuccess();
2552
+ return result;
2553
+ } catch (error) {
2554
+ manager.reportError(error);
2555
+ throw error;
2556
+ }
2557
+ };
2558
+ return { client, withRateLimit };
2559
+ }
2325
2560
  async function getTonClientForNetwork(network, configPath) {
2326
2561
  const manager = ProviderManager.getInstance({ configPath });
2327
2562
  if (!manager.isInitialized() || manager.getNetwork() !== network) {
@@ -2660,6 +2895,7 @@ exports.getProvidersForNetwork = getProvidersForNetwork;
2660
2895
  exports.getRateLimitForType = getRateLimitForType;
2661
2896
  exports.getTonClient = getTonClient;
2662
2897
  exports.getTonClientForNetwork = getTonClientForNetwork;
2898
+ exports.getTonClientWithRateLimit = getTonClientWithRateLimit;
2663
2899
  exports.isApiVersion = isApiVersion;
2664
2900
  exports.isChainstackUrl = isChainstackUrl;
2665
2901
  exports.isNetwork = isNetwork;