ton-provider-system 0.1.13 → 0.2.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
@@ -557,184 +557,462 @@ function createRegistryFromData(data, logger) {
557
557
  return new ProviderRegistry(mergedConfig, logger);
558
558
  }
559
559
 
560
- // src/utils/endpoint.ts
561
- function normalizeV2Endpoint(endpoint) {
562
- let normalized = endpoint.trim();
563
- if (normalized.endsWith("/")) {
564
- normalized = normalized.slice(0, -1);
560
+ // src/providers/base.ts
561
+ var BaseProvider = class {
562
+ constructor(provider) {
563
+ this.provider = provider;
565
564
  }
566
- if (normalized.toLowerCase().endsWith("/jsonrpc")) {
567
- return normalized;
565
+ /**
566
+ * Get the provider instance
567
+ */
568
+ getProvider() {
569
+ return this.provider;
568
570
  }
569
- if (normalized.includes("gateway.tatum.io")) {
570
- try {
571
- const url = new URL(normalized);
572
- if (!url.pathname || url.pathname === "/") {
573
- return normalized + "/jsonRPC";
571
+ /**
572
+ * Build a JSON-RPC request body.
573
+ * Most providers use standard JSON-RPC 2.0, but some may need modifications.
574
+ */
575
+ buildRequest(method, params = {}) {
576
+ return {
577
+ id: "1",
578
+ jsonrpc: "2.0",
579
+ method,
580
+ params
581
+ };
582
+ }
583
+ /**
584
+ * Parse response from provider.
585
+ * Handles provider-specific response formats.
586
+ */
587
+ parseResponse(data) {
588
+ if (data && typeof data === "object") {
589
+ const dataObj = data;
590
+ if ("ok" in dataObj) {
591
+ if (!dataObj.ok) {
592
+ const error = dataObj.error;
593
+ throw new Error(error || "API returned ok=false");
594
+ }
595
+ const result = dataObj.result;
596
+ return result || dataObj;
574
597
  }
575
- if (!url.pathname.toLowerCase().endsWith("/jsonrpc")) {
576
- return normalized + "/jsonRPC";
598
+ if ("result" in dataObj) {
599
+ return dataObj.result;
577
600
  }
578
- } catch {
601
+ if ("last" in dataObj || "@type" in dataObj) {
602
+ return dataObj;
603
+ }
604
+ if ("error" in dataObj) {
605
+ const errorObj = dataObj.error;
606
+ const errorMsg = typeof errorObj === "string" ? errorObj : errorObj?.message || errorObj?.code || String(errorObj);
607
+ throw new Error(`API error: ${errorMsg}`);
608
+ }
609
+ }
610
+ return data;
611
+ }
612
+ /**
613
+ * Parse masterchain info from response.
614
+ * Validates the response structure and extracts seqno.
615
+ */
616
+ parseMasterchainInfo(data) {
617
+ const info = this.parseResponse(data);
618
+ if (!info || typeof info !== "object") {
619
+ throw new Error("Invalid response structure");
620
+ }
621
+ const infoObj = info;
622
+ const seqno = infoObj.last?.seqno;
623
+ if (seqno === void 0 || seqno === null || seqno <= 0 || !Number.isInteger(seqno)) {
624
+ throw new Error(`Invalid seqno: ${seqno} (must be positive integer)`);
625
+ }
626
+ return info;
627
+ }
628
+ /**
629
+ * Validate provider configuration.
630
+ * Checks if required API keys are present, etc.
631
+ */
632
+ validateConfig() {
633
+ return { valid: true };
634
+ }
635
+ /**
636
+ * Get the normalized endpoint for this provider.
637
+ */
638
+ getNormalizedEndpoint() {
639
+ return this.normalizeEndpoint(this.provider.endpointV2);
640
+ }
641
+ /**
642
+ * Check if this provider requires an API key.
643
+ */
644
+ requiresApiKey() {
645
+ return false;
646
+ }
647
+ /**
648
+ * Check if API key is present (if required).
649
+ */
650
+ hasApiKey() {
651
+ return !!this.provider.apiKey;
652
+ }
653
+ };
654
+
655
+ // src/providers/chainstack.ts
656
+ var ChainstackProvider = class extends BaseProvider {
657
+ constructor(provider) {
658
+ super(provider);
659
+ }
660
+ normalizeEndpoint(endpoint) {
661
+ let normalized = endpoint.trim();
662
+ if (normalized.endsWith("/")) {
663
+ normalized = normalized.slice(0, -1);
664
+ }
665
+ if (normalized.toLowerCase().endsWith("/jsonrpc")) {
666
+ return normalized;
667
+ }
668
+ if (normalized.endsWith("/api/v2")) {
579
669
  return normalized + "/jsonRPC";
580
670
  }
671
+ if (normalized.endsWith("/api/v3")) {
672
+ return normalized.replace("/api/v3", "/api/v2/jsonRPC");
673
+ }
674
+ return normalized;
581
675
  }
582
- if (normalized.includes("onfinality.io")) {
583
- try {
584
- const url = new URL(normalized);
585
- const baseUrl = normalized.split("?")[0];
586
- if (!url.pathname || url.pathname === "/") {
587
- const apikey = url.searchParams.get("apikey");
588
- if (apikey && apikey !== "{key}" && apikey.length > 0) {
589
- return baseUrl.replace(/\/?$/, "/rpc");
676
+ buildHeaders() {
677
+ return {
678
+ "Content-Type": "application/json"
679
+ };
680
+ }
681
+ validateConfig() {
682
+ if (this.provider.endpointV2.includes("{key}")) {
683
+ return {
684
+ valid: false,
685
+ error: "Chainstack API key not resolved in endpoint URL"
686
+ };
687
+ }
688
+ return { valid: true };
689
+ }
690
+ };
691
+
692
+ // src/providers/tatum.ts
693
+ var TatumProvider = class extends BaseProvider {
694
+ constructor(provider) {
695
+ super(provider);
696
+ }
697
+ normalizeEndpoint(endpoint) {
698
+ let normalized = endpoint.trim();
699
+ if (normalized.endsWith("/")) {
700
+ normalized = normalized.slice(0, -1);
701
+ }
702
+ if (normalized.toLowerCase().endsWith("/jsonrpc")) {
703
+ return normalized;
704
+ }
705
+ if (normalized.includes("gateway.tatum.io")) {
706
+ try {
707
+ const url = new URL(normalized);
708
+ if (!url.pathname || url.pathname === "/") {
709
+ return normalized + "/jsonRPC";
590
710
  }
591
- return baseUrl.replace(/\/?$/, "/public");
711
+ if (!url.pathname.toLowerCase().endsWith("/jsonrpc")) {
712
+ return normalized + "/jsonRPC";
713
+ }
714
+ } catch {
715
+ return normalized + "/jsonRPC";
592
716
  }
593
- if (url.pathname === "/rpc" || url.pathname === "/public") {
717
+ }
718
+ return normalized;
719
+ }
720
+ buildHeaders() {
721
+ const headers = {
722
+ "Content-Type": "application/json"
723
+ };
724
+ if (this.provider.apiKey) {
725
+ headers["x-api-key"] = this.provider.apiKey;
726
+ }
727
+ return headers;
728
+ }
729
+ requiresApiKey() {
730
+ return true;
731
+ }
732
+ validateConfig() {
733
+ if (!this.provider.apiKey) {
734
+ return {
735
+ valid: false,
736
+ error: "Tatum provider requires API key (set TATUM_API_KEY_TESTNET or TATUM_API_KEY_MAINNET)"
737
+ };
738
+ }
739
+ return { valid: true };
740
+ }
741
+ };
742
+
743
+ // src/providers/onfinality.ts
744
+ var OnFinalityProvider = class extends BaseProvider {
745
+ constructor(provider) {
746
+ super(provider);
747
+ }
748
+ normalizeEndpoint(endpoint) {
749
+ let normalized = endpoint.trim();
750
+ if (normalized.endsWith("/")) {
751
+ normalized = normalized.slice(0, -1);
752
+ }
753
+ if (normalized.includes("onfinality.io")) {
754
+ try {
755
+ const url = new URL(normalized);
756
+ const baseUrl = normalized.split("?")[0];
757
+ if (!url.pathname || url.pathname === "/") {
758
+ if (this.provider.apiKey) {
759
+ return baseUrl.replace(/\/?$/, "/rpc");
760
+ }
761
+ return baseUrl.replace(/\/?$/, "/public");
762
+ }
763
+ if (url.pathname === "/rpc" || url.pathname === "/public") {
764
+ return baseUrl;
765
+ }
594
766
  return baseUrl;
767
+ } catch {
768
+ if (normalized.includes("{key}")) {
769
+ return normalized.split("?")[0].replace(/\/?$/, "/public");
770
+ }
771
+ if (!normalized.includes("/rpc") && !normalized.includes("/public")) {
772
+ return normalized.split("?")[0] + "/public";
773
+ }
774
+ return normalized.split("?")[0];
595
775
  }
596
- return baseUrl;
597
- } catch {
598
- if (normalized.includes("{key}")) {
599
- return normalized.split("?")[0].replace(/\/?$/, "/public");
600
- }
601
- if (!normalized.includes("/rpc") && !normalized.includes("/public")) {
602
- return normalized.split("?")[0] + "/public";
603
- }
604
- return normalized.split("?")[0];
605
776
  }
777
+ return normalized;
606
778
  }
607
- if (normalized.endsWith("/api/v2")) {
608
- return normalized + "/jsonRPC";
779
+ buildHeaders() {
780
+ const headers = {
781
+ "Content-Type": "application/json"
782
+ };
783
+ if (this.provider.apiKey) {
784
+ headers["apikey"] = this.provider.apiKey;
785
+ }
786
+ return headers;
609
787
  }
610
- if (normalized.endsWith("/api/v3")) {
611
- return normalized.replace("/api/v3", "/api/v2/jsonRPC");
788
+ parseResponse(data) {
789
+ if (typeof data === "string") {
790
+ if (data.includes("Backend error") || data.includes("backend error")) {
791
+ throw new Error(`OnFinality backend error: ${data}`);
792
+ }
793
+ }
794
+ return super.parseResponse(data);
612
795
  }
613
- if (normalized.includes("quiknode.pro") || normalized.includes("getblock.io")) {
614
- try {
615
- const url = new URL(normalized);
616
- if (!url.pathname || url.pathname === "/") {
796
+ };
797
+
798
+ // src/providers/quicknode.ts
799
+ var QuickNodeProvider = class extends BaseProvider {
800
+ constructor(provider) {
801
+ super(provider);
802
+ }
803
+ normalizeEndpoint(endpoint) {
804
+ let normalized = endpoint.trim();
805
+ if (normalized.endsWith("/")) {
806
+ normalized = normalized.slice(0, -1);
807
+ }
808
+ if (normalized.toLowerCase().endsWith("/jsonrpc")) {
809
+ return normalized;
810
+ }
811
+ if (normalized.includes("quiknode.pro")) {
812
+ try {
813
+ const url = new URL(normalized);
814
+ if (!url.pathname || url.pathname === "/") {
815
+ return normalized + "/jsonRPC";
816
+ }
817
+ if (!url.pathname.toLowerCase().endsWith("/jsonrpc")) {
818
+ return normalized + "/jsonRPC";
819
+ }
820
+ } catch {
617
821
  return normalized + "/jsonRPC";
618
822
  }
619
- if (!url.pathname.toLowerCase().endsWith("/jsonrpc")) {
823
+ }
824
+ return normalized;
825
+ }
826
+ buildHeaders() {
827
+ return {
828
+ "Content-Type": "application/json"
829
+ };
830
+ }
831
+ validateConfig() {
832
+ if (this.provider.endpointV2.includes("{key}")) {
833
+ return {
834
+ valid: false,
835
+ error: "QuickNode API key not resolved in endpoint URL"
836
+ };
837
+ }
838
+ return { valid: true };
839
+ }
840
+ };
841
+
842
+ // src/providers/getblock.ts
843
+ var GetBlockProvider = class extends BaseProvider {
844
+ constructor(provider) {
845
+ super(provider);
846
+ }
847
+ normalizeEndpoint(endpoint) {
848
+ let normalized = endpoint.trim();
849
+ if (normalized.endsWith("/")) {
850
+ normalized = normalized.slice(0, -1);
851
+ }
852
+ if (normalized.toLowerCase().endsWith("/jsonrpc")) {
853
+ return normalized;
854
+ }
855
+ if (normalized.includes("getblock.io")) {
856
+ try {
857
+ const url = new URL(normalized);
858
+ if (!url.pathname || url.pathname === "/") {
859
+ return normalized + "/jsonRPC";
860
+ }
861
+ if (!url.pathname.toLowerCase().endsWith("/jsonrpc")) {
862
+ return normalized + "/jsonRPC";
863
+ }
864
+ } catch {
620
865
  return normalized + "/jsonRPC";
621
866
  }
622
- } catch {
623
- return normalized + "/jsonRPC";
624
867
  }
868
+ return normalized;
625
869
  }
626
- try {
627
- const url = new URL(normalized);
628
- if (!url.pathname || url.pathname === "/") {
629
- return normalized + "/jsonRPC";
870
+ buildHeaders() {
871
+ const headers = {
872
+ "Content-Type": "application/json"
873
+ };
874
+ if (this.provider.apiKey) {
875
+ headers["x-api-key"] = this.provider.apiKey;
630
876
  }
631
- } catch {
632
- }
633
- return normalized;
634
- }
635
- function toV2Base(endpoint) {
636
- let normalized = endpoint.trim();
637
- if (normalized.endsWith("/")) {
638
- normalized = normalized.slice(0, -1);
877
+ return headers;
639
878
  }
640
- if (normalized.toLowerCase().endsWith("/jsonrpc")) {
641
- normalized = normalized.slice(0, -8);
879
+ requiresApiKey() {
880
+ return true;
642
881
  }
643
- normalized = normalized.replace(/\/api\/v3\b/, "/api/v2");
644
- if (!normalized.endsWith("/api/v2")) {
645
- if (normalized.includes("/api/v2")) {
646
- const idx = normalized.indexOf("/api/v2");
647
- normalized = normalized.slice(0, idx + 7);
882
+ validateConfig() {
883
+ if (!this.provider.apiKey) {
884
+ return {
885
+ valid: false,
886
+ error: "GetBlock provider requires API key"
887
+ };
648
888
  }
889
+ return { valid: true };
649
890
  }
650
- return normalized;
651
- }
652
- function toV3Base(endpoint) {
653
- const normalized = toV2Base(endpoint);
654
- return normalized.replace("/api/v2", "/api/v3");
655
- }
656
- function getBaseUrl(endpoint) {
657
- try {
658
- const url = new URL(endpoint);
659
- return `${url.protocol}//${url.host}`;
660
- } catch {
661
- return endpoint;
891
+ };
892
+
893
+ // src/providers/toncenter.ts
894
+ var TonCenterProvider = class extends BaseProvider {
895
+ constructor(provider) {
896
+ super(provider);
662
897
  }
663
- }
664
- function isChainstackUrl(url) {
665
- try {
666
- const parsed = new URL(url.trim());
667
- return parsed.hostname.includes("chainstack.com");
668
- } catch {
669
- return false;
898
+ normalizeEndpoint(endpoint) {
899
+ let normalized = endpoint.trim();
900
+ if (normalized.endsWith("/")) {
901
+ normalized = normalized.slice(0, -1);
902
+ }
903
+ if (normalized.toLowerCase().endsWith("/jsonrpc")) {
904
+ return normalized;
905
+ }
906
+ if (normalized.endsWith("/api/v2")) {
907
+ return normalized + "/jsonRPC";
908
+ }
909
+ if (normalized.endsWith("/api/v3")) {
910
+ return normalized.replace("/api/v3", "/api/v2/jsonRPC");
911
+ }
912
+ return normalized;
670
913
  }
671
- }
672
- function isQuickNodeUrl(url) {
673
- try {
674
- const parsed = new URL(url.trim());
675
- return parsed.hostname.includes("quiknode.pro");
676
- } catch {
677
- return false;
914
+ buildHeaders() {
915
+ return {
916
+ "Content-Type": "application/json"
917
+ };
678
918
  }
679
- }
680
- function isTonCenterUrl(url) {
681
- try {
682
- const parsed = new URL(url.trim());
683
- return parsed.hostname.includes("toncenter.com");
684
- } catch {
919
+ // TON Center API key is optional (1 RPS without key, 10 RPS with key)
920
+ requiresApiKey() {
685
921
  return false;
686
922
  }
687
- }
688
- function isOrbsUrl(url) {
689
- try {
690
- const parsed = new URL(url.trim());
691
- return parsed.hostname.includes("orbs.network") || parsed.hostname.includes("ton-access");
692
- } catch {
693
- return false;
923
+ };
924
+
925
+ // src/providers/orbs.ts
926
+ var OrbsProvider = class extends BaseProvider {
927
+ constructor(provider) {
928
+ super(provider);
694
929
  }
695
- }
696
- function buildRestUrl(baseEndpoint, method) {
697
- const base = toV2Base(baseEndpoint);
698
- return `${base}/${method}`;
699
- }
700
- function buildGetAddressStateUrl(baseEndpoint, address) {
701
- const base = toV2Base(baseEndpoint);
702
- return `${base}/getAddressState?address=${encodeURIComponent(address)}`;
703
- }
704
- function buildGetAddressBalanceUrl(baseEndpoint, address) {
705
- const base = toV2Base(baseEndpoint);
706
- return `${base}/getAddressBalance?address=${encodeURIComponent(address)}`;
707
- }
708
- function buildGetAddressInfoUrl(baseEndpoint, address) {
709
- const base = toV2Base(baseEndpoint);
710
- return `${base}/getAddressInformation?address=${encodeURIComponent(address)}`;
711
- }
712
- function detectNetworkFromEndpoint(endpoint) {
713
- const lower = endpoint.toLowerCase();
714
- if (lower.includes("testnet") || lower.includes("test") || lower.includes("sandbox")) {
715
- return "testnet";
930
+ normalizeEndpoint(endpoint) {
931
+ let normalized = endpoint.trim();
932
+ if (normalized.endsWith("/")) {
933
+ normalized = normalized.slice(0, -1);
934
+ }
935
+ if (normalized.endsWith("/api/v2")) {
936
+ return normalized;
937
+ }
938
+ return normalized;
716
939
  }
717
- if (lower.includes("mainnet") || lower.includes("main") || // TonCenter mainnet doesn't have 'mainnet' in URL
718
- lower.includes("toncenter.com") && !lower.includes("testnet")) {
719
- return "mainnet";
940
+ buildHeaders() {
941
+ return {
942
+ "Content-Type": "application/json"
943
+ };
720
944
  }
721
- return null;
722
- }
723
- function isValidHttpUrl(str) {
724
- try {
725
- const url = new URL(str);
726
- return url.protocol === "http:" || url.protocol === "https:";
727
- } catch {
728
- return false;
945
+ /**
946
+ * Get dynamic endpoint from Orbs TON Access.
947
+ * This should be called before making requests.
948
+ */
949
+ async getDynamicEndpoint() {
950
+ if (!this.provider.isDynamic) {
951
+ return this.normalizeEndpoint(this.provider.endpointV2);
952
+ }
953
+ try {
954
+ const { getHttpEndpoint } = await import('@orbs-network/ton-access');
955
+ const endpoint = await getHttpEndpoint({ network: this.provider.network });
956
+ return this.normalizeEndpoint(endpoint);
957
+ } catch (error) {
958
+ return this.normalizeEndpoint(this.provider.endpointV2);
959
+ }
729
960
  }
730
- }
731
- function isValidWsUrl(str) {
732
- try {
733
- const url = new URL(str);
734
- return url.protocol === "ws:" || url.protocol === "wss:";
735
- } catch {
961
+ requiresApiKey() {
736
962
  return false;
737
963
  }
964
+ };
965
+
966
+ // src/providers/index.ts
967
+ var GenericProvider = class extends BaseProvider {
968
+ normalizeEndpoint(endpoint) {
969
+ let normalized = endpoint.trim();
970
+ if (normalized.endsWith("/")) {
971
+ normalized = normalized.slice(0, -1);
972
+ }
973
+ if (normalized.toLowerCase().endsWith("/jsonrpc")) {
974
+ return normalized;
975
+ }
976
+ try {
977
+ const url = new URL(normalized);
978
+ if (!url.pathname || url.pathname === "/") {
979
+ return normalized + "/jsonRPC";
980
+ }
981
+ } catch {
982
+ }
983
+ return normalized;
984
+ }
985
+ buildHeaders() {
986
+ const headers = {
987
+ "Content-Type": "application/json"
988
+ };
989
+ if (this.provider.apiKey) {
990
+ headers["x-api-key"] = this.provider.apiKey;
991
+ }
992
+ return headers;
993
+ }
994
+ };
995
+ function createProvider(provider) {
996
+ switch (provider.type) {
997
+ case "chainstack":
998
+ return new ChainstackProvider(provider);
999
+ case "tatum":
1000
+ return new TatumProvider(provider);
1001
+ case "onfinality":
1002
+ return new OnFinalityProvider(provider);
1003
+ case "quicknode":
1004
+ return new QuickNodeProvider(provider);
1005
+ case "getblock":
1006
+ return new GetBlockProvider(provider);
1007
+ case "toncenter":
1008
+ return new TonCenterProvider(provider);
1009
+ case "orbs":
1010
+ return new OrbsProvider(provider);
1011
+ case "custom":
1012
+ return new GenericProvider(provider);
1013
+ default:
1014
+ return new GenericProvider(provider);
1015
+ }
738
1016
  }
739
1017
 
740
1018
  // src/core/healthChecker.ts
@@ -793,21 +1071,25 @@ var HealthChecker = class {
793
1071
  if (!endpoint) {
794
1072
  throw new Error("No valid endpoint available");
795
1073
  }
796
- if (provider.type === "tatum" && !provider.apiKey) {
797
- throw new Error("Tatum provider requires API key (set TATUM_API_KEY_TESTNET or TATUM_API_KEY_MAINNET)");
1074
+ const providerImpl = createProvider(provider);
1075
+ const validation = providerImpl.validateConfig();
1076
+ if (!validation.valid) {
1077
+ throw new Error(validation.error || "Provider configuration invalid");
798
1078
  }
799
- let normalizedEndpoint = this.normalizeEndpointForProvider(provider, endpoint);
1079
+ const normalizedEndpoint = providerImpl.normalizeEndpoint(endpoint);
800
1080
  if (provider.type === "onfinality") {
801
1081
  this.logger.debug(`OnFinality endpoint: ${endpoint} -> ${normalizedEndpoint}, API key: ${provider.apiKey ? "set" : "not set"}`);
802
1082
  }
803
1083
  let info;
804
1084
  try {
805
- info = await this.callGetMasterchainInfo(normalizedEndpoint, provider);
1085
+ info = await this.callGetMasterchainInfo(normalizedEndpoint, provider, providerImpl);
806
1086
  } catch (error) {
807
1087
  if (provider.type === "onfinality" && normalizedEndpoint.includes("/rpc") && provider.apiKey && error.message?.includes("backend error")) {
808
1088
  this.logger.debug(`OnFinality /rpc failed, retrying with /public endpoint`);
809
1089
  const publicEndpoint = normalizedEndpoint.replace("/rpc", "/public");
810
- info = await this.callGetMasterchainInfo(publicEndpoint, { ...provider, apiKey: void 0 });
1090
+ const publicProvider = { ...provider, apiKey: void 0 };
1091
+ const publicProviderImpl = createProvider(publicProvider);
1092
+ info = await this.callGetMasterchainInfo(publicEndpoint, publicProvider, publicProviderImpl);
811
1093
  } else {
812
1094
  throw error;
813
1095
  }
@@ -1032,44 +1314,19 @@ var HealthChecker = class {
1032
1314
  }
1033
1315
  return provider.endpointV2 || provider.endpointV3 || null;
1034
1316
  }
1035
- /**
1036
- * Normalize endpoint for provider-specific requirements
1037
- *
1038
- * Note: normalizeV2Endpoint now handles all provider-specific cases correctly,
1039
- * including Tatum (/jsonRPC), OnFinality (/public or /rpc), QuickNode, and GetBlock.
1040
- */
1041
- normalizeEndpointForProvider(provider, endpoint) {
1042
- if (provider.type === "tatum" && endpoint.includes("api.tatum.io/v3/blockchain/node")) {
1043
- const network = provider.network === "testnet" ? "testnet" : "mainnet";
1044
- endpoint = `https://ton-${network}.gateway.tatum.io`;
1045
- }
1046
- return normalizeV2Endpoint(endpoint);
1047
- }
1048
1317
  /**
1049
1318
  * Call getMasterchainInfo API with provider-specific handling
1050
1319
  */
1051
- async callGetMasterchainInfo(endpoint, provider) {
1320
+ async callGetMasterchainInfo(endpoint, provider, providerImpl) {
1052
1321
  const controller = new AbortController();
1053
1322
  const timeoutId = setTimeout(() => controller.abort(), this.config.timeoutMs);
1054
- const headers = {
1055
- "Content-Type": "application/json"
1056
- };
1057
- if (provider.type === "tatum" && provider.apiKey) {
1058
- headers["x-api-key"] = provider.apiKey;
1059
- }
1060
- if (provider.type === "onfinality" && provider.apiKey) {
1061
- headers["apikey"] = provider.apiKey;
1062
- }
1323
+ const headers = providerImpl.buildHeaders();
1324
+ const requestBody = providerImpl.buildRequest("getMasterchainInfo", {});
1063
1325
  try {
1064
1326
  const response = await fetch(endpoint, {
1065
1327
  method: "POST",
1066
1328
  headers,
1067
- body: JSON.stringify({
1068
- id: "1",
1069
- jsonrpc: "2.0",
1070
- method: "getMasterchainInfo",
1071
- params: {}
1072
- }),
1329
+ body: JSON.stringify(requestBody),
1073
1330
  signal: controller.signal
1074
1331
  });
1075
1332
  clearTimeout(timeoutId);
@@ -1095,40 +1352,7 @@ var HealthChecker = class {
1095
1352
  }
1096
1353
  }
1097
1354
  data = await response.json();
1098
- let info;
1099
- if (data && typeof data === "object") {
1100
- const dataObj = data;
1101
- if ("ok" in dataObj) {
1102
- if (!dataObj.ok) {
1103
- const error = dataObj.error;
1104
- throw new Error(error || "API returned ok=false");
1105
- }
1106
- const result = dataObj.result;
1107
- info = result || dataObj;
1108
- } else if ("result" in dataObj) {
1109
- info = dataObj.result;
1110
- } else if ("last" in dataObj || "@type" in dataObj) {
1111
- info = dataObj;
1112
- } else if ("error" in dataObj) {
1113
- const errorObj = dataObj.error;
1114
- const errorMsg = typeof errorObj === "string" ? errorObj : errorObj?.message || errorObj?.code || String(errorObj);
1115
- throw new Error(`API error: ${errorMsg}`);
1116
- } else {
1117
- throw new Error(`Unknown response format from ${provider.type}`);
1118
- }
1119
- } else {
1120
- throw new Error(`Invalid response type: ${typeof data}`);
1121
- }
1122
- if (!info || typeof info !== "object") {
1123
- this.logger.debug(`Invalid response structure from ${provider.type}: ${JSON.stringify(data)}`);
1124
- throw new Error("Invalid response structure");
1125
- }
1126
- const infoObj = info;
1127
- const seqno = infoObj.last?.seqno;
1128
- if (seqno === void 0 || seqno === null || seqno <= 0 || !Number.isInteger(seqno)) {
1129
- this.logger.debug(`Invalid seqno from ${provider.type}:`, { seqno, info });
1130
- throw new Error(`Invalid seqno: ${seqno} (must be positive integer)`);
1131
- }
1355
+ const info = providerImpl.parseMasterchainInfo(data);
1132
1356
  return info;
1133
1357
  } catch (error) {
1134
1358
  clearTimeout(timeoutId);
@@ -1988,6 +2212,193 @@ function createSelector(registry, healthChecker, config, logger, adapter = "node
1988
2212
  return new ProviderSelector(registry, healthChecker, config, logger, adapter);
1989
2213
  }
1990
2214
 
2215
+ // src/utils/endpoint.ts
2216
+ function normalizeV2Endpoint(endpoint, provider) {
2217
+ if (provider) {
2218
+ const providerImpl = createProvider(provider);
2219
+ return providerImpl.normalizeEndpoint(endpoint);
2220
+ }
2221
+ return normalizeV2EndpointFallback(endpoint);
2222
+ }
2223
+ function normalizeV2EndpointFallback(endpoint) {
2224
+ let normalized = endpoint.trim();
2225
+ if (normalized.endsWith("/")) {
2226
+ normalized = normalized.slice(0, -1);
2227
+ }
2228
+ if (normalized.toLowerCase().endsWith("/jsonrpc")) {
2229
+ return normalized;
2230
+ }
2231
+ if (normalized.includes("gateway.tatum.io")) {
2232
+ try {
2233
+ const url = new URL(normalized);
2234
+ if (!url.pathname || url.pathname === "/") {
2235
+ return normalized + "/jsonRPC";
2236
+ }
2237
+ if (!url.pathname.toLowerCase().endsWith("/jsonrpc")) {
2238
+ return normalized + "/jsonRPC";
2239
+ }
2240
+ } catch {
2241
+ return normalized + "/jsonRPC";
2242
+ }
2243
+ }
2244
+ if (normalized.includes("onfinality.io")) {
2245
+ try {
2246
+ const url = new URL(normalized);
2247
+ const baseUrl = normalized.split("?")[0];
2248
+ if (!url.pathname || url.pathname === "/") {
2249
+ const apikey = url.searchParams.get("apikey");
2250
+ if (apikey && apikey !== "{key}" && apikey.length > 0) {
2251
+ return baseUrl.replace(/\/?$/, "/rpc");
2252
+ }
2253
+ return baseUrl.replace(/\/?$/, "/public");
2254
+ }
2255
+ if (url.pathname === "/rpc" || url.pathname === "/public") {
2256
+ return baseUrl;
2257
+ }
2258
+ return baseUrl;
2259
+ } catch {
2260
+ if (normalized.includes("{key}")) {
2261
+ return normalized.split("?")[0].replace(/\/?$/, "/public");
2262
+ }
2263
+ if (!normalized.includes("/rpc") && !normalized.includes("/public")) {
2264
+ return normalized.split("?")[0] + "/public";
2265
+ }
2266
+ return normalized.split("?")[0];
2267
+ }
2268
+ }
2269
+ if (normalized.endsWith("/api/v2")) {
2270
+ return normalized + "/jsonRPC";
2271
+ }
2272
+ if (normalized.endsWith("/api/v3")) {
2273
+ return normalized.replace("/api/v3", "/api/v2/jsonRPC");
2274
+ }
2275
+ if (normalized.includes("quiknode.pro") || normalized.includes("getblock.io")) {
2276
+ try {
2277
+ const url = new URL(normalized);
2278
+ if (!url.pathname || url.pathname === "/") {
2279
+ return normalized + "/jsonRPC";
2280
+ }
2281
+ if (!url.pathname.toLowerCase().endsWith("/jsonrpc")) {
2282
+ return normalized + "/jsonRPC";
2283
+ }
2284
+ } catch {
2285
+ return normalized + "/jsonRPC";
2286
+ }
2287
+ }
2288
+ try {
2289
+ const url = new URL(normalized);
2290
+ if (!url.pathname || url.pathname === "/") {
2291
+ return normalized + "/jsonRPC";
2292
+ }
2293
+ } catch {
2294
+ }
2295
+ return normalized;
2296
+ }
2297
+ function toV2Base(endpoint) {
2298
+ let normalized = endpoint.trim();
2299
+ if (normalized.endsWith("/")) {
2300
+ normalized = normalized.slice(0, -1);
2301
+ }
2302
+ if (normalized.toLowerCase().endsWith("/jsonrpc")) {
2303
+ normalized = normalized.slice(0, -8);
2304
+ }
2305
+ normalized = normalized.replace(/\/api\/v3\b/, "/api/v2");
2306
+ if (!normalized.endsWith("/api/v2")) {
2307
+ if (normalized.includes("/api/v2")) {
2308
+ const idx = normalized.indexOf("/api/v2");
2309
+ normalized = normalized.slice(0, idx + 7);
2310
+ }
2311
+ }
2312
+ return normalized;
2313
+ }
2314
+ function toV3Base(endpoint) {
2315
+ const normalized = toV2Base(endpoint);
2316
+ return normalized.replace("/api/v2", "/api/v3");
2317
+ }
2318
+ function getBaseUrl(endpoint) {
2319
+ try {
2320
+ const url = new URL(endpoint);
2321
+ return `${url.protocol}//${url.host}`;
2322
+ } catch {
2323
+ return endpoint;
2324
+ }
2325
+ }
2326
+ function isChainstackUrl(url) {
2327
+ try {
2328
+ const parsed = new URL(url.trim());
2329
+ return parsed.hostname.includes("chainstack.com");
2330
+ } catch {
2331
+ return false;
2332
+ }
2333
+ }
2334
+ function isQuickNodeUrl(url) {
2335
+ try {
2336
+ const parsed = new URL(url.trim());
2337
+ return parsed.hostname.includes("quiknode.pro");
2338
+ } catch {
2339
+ return false;
2340
+ }
2341
+ }
2342
+ function isTonCenterUrl(url) {
2343
+ try {
2344
+ const parsed = new URL(url.trim());
2345
+ return parsed.hostname.includes("toncenter.com");
2346
+ } catch {
2347
+ return false;
2348
+ }
2349
+ }
2350
+ function isOrbsUrl(url) {
2351
+ try {
2352
+ const parsed = new URL(url.trim());
2353
+ return parsed.hostname.includes("orbs.network") || parsed.hostname.includes("ton-access");
2354
+ } catch {
2355
+ return false;
2356
+ }
2357
+ }
2358
+ function buildRestUrl(baseEndpoint, method) {
2359
+ const base = toV2Base(baseEndpoint);
2360
+ return `${base}/${method}`;
2361
+ }
2362
+ function buildGetAddressStateUrl(baseEndpoint, address) {
2363
+ const base = toV2Base(baseEndpoint);
2364
+ return `${base}/getAddressState?address=${encodeURIComponent(address)}`;
2365
+ }
2366
+ function buildGetAddressBalanceUrl(baseEndpoint, address) {
2367
+ const base = toV2Base(baseEndpoint);
2368
+ return `${base}/getAddressBalance?address=${encodeURIComponent(address)}`;
2369
+ }
2370
+ function buildGetAddressInfoUrl(baseEndpoint, address) {
2371
+ const base = toV2Base(baseEndpoint);
2372
+ return `${base}/getAddressInformation?address=${encodeURIComponent(address)}`;
2373
+ }
2374
+ function detectNetworkFromEndpoint(endpoint) {
2375
+ const lower = endpoint.toLowerCase();
2376
+ if (lower.includes("testnet") || lower.includes("test") || lower.includes("sandbox")) {
2377
+ return "testnet";
2378
+ }
2379
+ if (lower.includes("mainnet") || lower.includes("main") || // TonCenter mainnet doesn't have 'mainnet' in URL
2380
+ lower.includes("toncenter.com") && !lower.includes("testnet")) {
2381
+ return "mainnet";
2382
+ }
2383
+ return null;
2384
+ }
2385
+ function isValidHttpUrl(str) {
2386
+ try {
2387
+ const url = new URL(str);
2388
+ return url.protocol === "http:" || url.protocol === "https:";
2389
+ } catch {
2390
+ return false;
2391
+ }
2392
+ }
2393
+ function isValidWsUrl(str) {
2394
+ try {
2395
+ const url = new URL(str);
2396
+ return url.protocol === "ws:" || url.protocol === "wss:";
2397
+ } catch {
2398
+ return false;
2399
+ }
2400
+ }
2401
+
1991
2402
  // src/core/manager.ts
1992
2403
  var consoleLogger5 = {
1993
2404
  debug: (msg, data) => console.debug(`[ProviderManager] ${msg}`, data || ""),
@@ -2186,17 +2597,20 @@ var _ProviderManager = class _ProviderManager {
2186
2597
  this.options.logger.warn("No providers available, using fallback");
2187
2598
  return this.getFallbackEndpoint();
2188
2599
  }
2189
- this.selector.getActiveProviderId(this.network) || this.selector.getBestProvider(this.network);
2600
+ const activeProviderId = this.selector.getActiveProviderId(this.network);
2601
+ if (!activeProviderId) {
2602
+ this.selector.getBestProvider(this.network);
2603
+ }
2190
2604
  if (provider.isDynamic && provider.type === "orbs") {
2191
2605
  try {
2192
2606
  const { getHttpEndpoint } = await import('@orbs-network/ton-access');
2193
2607
  const endpoint = await getHttpEndpoint({ network: this.network });
2194
- return normalizeV2Endpoint(endpoint);
2608
+ return normalizeV2Endpoint(endpoint, provider);
2195
2609
  } catch (error) {
2196
2610
  this.options.logger.warn(`Failed to get Orbs endpoint: ${error.message}`);
2197
2611
  }
2198
2612
  }
2199
- return normalizeV2Endpoint(provider.endpointV2);
2613
+ return normalizeV2Endpoint(provider.endpointV2, provider);
2200
2614
  }
2201
2615
  /**
2202
2616
  * Get endpoint with rate limiting
@@ -2219,11 +2633,11 @@ var _ProviderManager = class _ProviderManager {
2219
2633
  this.options.logger.warn(`Rate limit timeout for ${provider.id}`);
2220
2634
  const next = this.selector.getNextProvider(this.network, [provider.id]);
2221
2635
  if (next) {
2222
- return normalizeV2Endpoint(next.endpointV2);
2636
+ return normalizeV2Endpoint(next.endpointV2, next);
2223
2637
  }
2224
2638
  return this.getFallbackEndpoint();
2225
2639
  }
2226
- return normalizeV2Endpoint(provider.endpointV2);
2640
+ return normalizeV2Endpoint(provider.endpointV2, provider);
2227
2641
  }
2228
2642
  /**
2229
2643
  * Get current active provider
@@ -3083,18 +3497,24 @@ async function createBrowserAdapterForNetwork(network, configPath, logger) {
3083
3497
  }
3084
3498
 
3085
3499
  exports.ApiVersionSchema = ApiVersionSchema;
3500
+ exports.BaseProvider = BaseProvider;
3086
3501
  exports.BrowserAdapter = BrowserAdapter;
3087
3502
  exports.CHAINSTACK_RATE_LIMIT = CHAINSTACK_RATE_LIMIT;
3503
+ exports.ChainstackProvider = ChainstackProvider;
3088
3504
  exports.ConfigError = ConfigError;
3089
3505
  exports.DEFAULT_CONTRACT_TIMEOUT_MS = DEFAULT_CONTRACT_TIMEOUT_MS;
3090
3506
  exports.DEFAULT_HEALTH_CHECK_TIMEOUT_MS = DEFAULT_HEALTH_CHECK_TIMEOUT_MS;
3091
3507
  exports.DEFAULT_PROVIDERS = DEFAULT_PROVIDERS;
3092
3508
  exports.DEFAULT_PROVIDER_TIMEOUT_MS = DEFAULT_PROVIDER_TIMEOUT_MS;
3093
3509
  exports.DEFAULT_RATE_LIMIT = DEFAULT_RATE_LIMIT;
3510
+ exports.GenericProvider = GenericProvider;
3511
+ exports.GetBlockProvider = GetBlockProvider;
3094
3512
  exports.HealthChecker = HealthChecker;
3095
3513
  exports.NetworkSchema = NetworkSchema;
3096
3514
  exports.NodeAdapter = NodeAdapter;
3097
3515
  exports.ORBS_RATE_LIMIT = ORBS_RATE_LIMIT;
3516
+ exports.OnFinalityProvider = OnFinalityProvider;
3517
+ exports.OrbsProvider = OrbsProvider;
3098
3518
  exports.ProviderConfigSchema = ProviderConfigSchema;
3099
3519
  exports.ProviderError = ProviderError;
3100
3520
  exports.ProviderManager = ProviderManager;
@@ -3102,11 +3522,14 @@ exports.ProviderRegistry = ProviderRegistry;
3102
3522
  exports.ProviderSelector = ProviderSelector;
3103
3523
  exports.ProviderTypeSchema = ProviderTypeSchema;
3104
3524
  exports.QUICKNODE_RATE_LIMIT = QUICKNODE_RATE_LIMIT;
3525
+ exports.QuickNodeProvider = QuickNodeProvider;
3105
3526
  exports.RateLimitError = RateLimitError;
3106
3527
  exports.RateLimiterManager = RateLimiterManager;
3107
3528
  exports.RpcConfigSchema = RpcConfigSchema;
3529
+ exports.TatumProvider = TatumProvider;
3108
3530
  exports.TimeoutError = TimeoutError;
3109
3531
  exports.TokenBucketRateLimiter = TokenBucketRateLimiter;
3532
+ exports.TonCenterProvider = TonCenterProvider;
3110
3533
  exports.buildGetAddressBalanceUrl = buildGetAddressBalanceUrl;
3111
3534
  exports.buildGetAddressInfoUrl = buildGetAddressInfoUrl;
3112
3535
  exports.buildGetAddressStateUrl = buildGetAddressStateUrl;
@@ -3118,6 +3541,7 @@ exports.createDefaultRegistry = createDefaultRegistry;
3118
3541
  exports.createEmptyConfig = createEmptyConfig;
3119
3542
  exports.createHealthChecker = createHealthChecker;
3120
3543
  exports.createNodeAdapter = createNodeAdapter;
3544
+ exports.createProvider = createProvider;
3121
3545
  exports.createProviderManager = createProviderManager;
3122
3546
  exports.createRateLimiter = createRateLimiter;
3123
3547
  exports.createRateLimiterManager = createRateLimiterManager;