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