ton-provider-system 0.2.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +292 -203
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -1
- package/dist/index.d.ts +26 -1
- package/dist/index.js +292 -204
- package/dist/index.js.map +1 -1
- package/package.json +17 -19
package/dist/index.cjs
CHANGED
|
@@ -1038,6 +1038,238 @@ function createProvider(provider) {
|
|
|
1038
1038
|
}
|
|
1039
1039
|
}
|
|
1040
1040
|
|
|
1041
|
+
// src/utils/endpoint.ts
|
|
1042
|
+
function normalizeV2Endpoint(endpoint, provider) {
|
|
1043
|
+
if (provider) {
|
|
1044
|
+
const providerImpl = createProvider(provider);
|
|
1045
|
+
return providerImpl.normalizeEndpoint(endpoint);
|
|
1046
|
+
}
|
|
1047
|
+
return normalizeV2EndpointFallback(endpoint);
|
|
1048
|
+
}
|
|
1049
|
+
function normalizeV2EndpointFallback(endpoint) {
|
|
1050
|
+
let normalized = endpoint.trim();
|
|
1051
|
+
if (normalized.endsWith("/")) {
|
|
1052
|
+
normalized = normalized.slice(0, -1);
|
|
1053
|
+
}
|
|
1054
|
+
if (normalized.toLowerCase().endsWith("/jsonrpc")) {
|
|
1055
|
+
return normalized;
|
|
1056
|
+
}
|
|
1057
|
+
if (normalized.includes("gateway.tatum.io")) {
|
|
1058
|
+
try {
|
|
1059
|
+
const url = new URL(normalized);
|
|
1060
|
+
if (!url.pathname || url.pathname === "/") {
|
|
1061
|
+
return normalized + "/jsonRPC";
|
|
1062
|
+
}
|
|
1063
|
+
if (!url.pathname.toLowerCase().endsWith("/jsonrpc")) {
|
|
1064
|
+
return normalized + "/jsonRPC";
|
|
1065
|
+
}
|
|
1066
|
+
} catch {
|
|
1067
|
+
return normalized + "/jsonRPC";
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
if (normalized.includes("onfinality.io")) {
|
|
1071
|
+
try {
|
|
1072
|
+
const url = new URL(normalized);
|
|
1073
|
+
const baseUrl = normalized.split("?")[0];
|
|
1074
|
+
if (!url.pathname || url.pathname === "/") {
|
|
1075
|
+
const apikey = url.searchParams.get("apikey");
|
|
1076
|
+
if (apikey && apikey !== "{key}" && apikey.length > 0) {
|
|
1077
|
+
return baseUrl.replace(/\/?$/, "/rpc");
|
|
1078
|
+
}
|
|
1079
|
+
return baseUrl.replace(/\/?$/, "/public");
|
|
1080
|
+
}
|
|
1081
|
+
if (url.pathname === "/rpc" || url.pathname === "/public") {
|
|
1082
|
+
return baseUrl;
|
|
1083
|
+
}
|
|
1084
|
+
return baseUrl;
|
|
1085
|
+
} catch {
|
|
1086
|
+
if (normalized.includes("{key}")) {
|
|
1087
|
+
return normalized.split("?")[0].replace(/\/?$/, "/public");
|
|
1088
|
+
}
|
|
1089
|
+
if (!normalized.includes("/rpc") && !normalized.includes("/public")) {
|
|
1090
|
+
return normalized.split("?")[0] + "/public";
|
|
1091
|
+
}
|
|
1092
|
+
return normalized.split("?")[0];
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
if (normalized.endsWith("/api/v2")) {
|
|
1096
|
+
return normalized + "/jsonRPC";
|
|
1097
|
+
}
|
|
1098
|
+
if (normalized.endsWith("/api/v3")) {
|
|
1099
|
+
return normalized.replace("/api/v3", "/api/v2/jsonRPC");
|
|
1100
|
+
}
|
|
1101
|
+
if (normalized.includes("quiknode.pro") || normalized.includes("getblock.io")) {
|
|
1102
|
+
try {
|
|
1103
|
+
const url = new URL(normalized);
|
|
1104
|
+
if (!url.pathname || url.pathname === "/") {
|
|
1105
|
+
return normalized + "/jsonRPC";
|
|
1106
|
+
}
|
|
1107
|
+
if (!url.pathname.toLowerCase().endsWith("/jsonrpc")) {
|
|
1108
|
+
return normalized + "/jsonRPC";
|
|
1109
|
+
}
|
|
1110
|
+
} catch {
|
|
1111
|
+
return normalized + "/jsonRPC";
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
try {
|
|
1115
|
+
const url = new URL(normalized);
|
|
1116
|
+
if (!url.pathname || url.pathname === "/") {
|
|
1117
|
+
return normalized + "/jsonRPC";
|
|
1118
|
+
}
|
|
1119
|
+
} catch {
|
|
1120
|
+
}
|
|
1121
|
+
return normalized;
|
|
1122
|
+
}
|
|
1123
|
+
function toV2Base(endpoint) {
|
|
1124
|
+
let normalized = endpoint.trim();
|
|
1125
|
+
if (normalized.endsWith("/")) {
|
|
1126
|
+
normalized = normalized.slice(0, -1);
|
|
1127
|
+
}
|
|
1128
|
+
if (normalized.toLowerCase().endsWith("/jsonrpc")) {
|
|
1129
|
+
normalized = normalized.slice(0, -8);
|
|
1130
|
+
}
|
|
1131
|
+
normalized = normalized.replace(/\/api\/v3\b/, "/api/v2");
|
|
1132
|
+
if (!normalized.endsWith("/api/v2")) {
|
|
1133
|
+
if (normalized.includes("/api/v2")) {
|
|
1134
|
+
const idx = normalized.indexOf("/api/v2");
|
|
1135
|
+
normalized = normalized.slice(0, idx + 7);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
return normalized;
|
|
1139
|
+
}
|
|
1140
|
+
function toV3Base(endpoint) {
|
|
1141
|
+
const normalized = toV2Base(endpoint);
|
|
1142
|
+
return normalized.replace("/api/v2", "/api/v3");
|
|
1143
|
+
}
|
|
1144
|
+
function getBaseUrl(endpoint) {
|
|
1145
|
+
try {
|
|
1146
|
+
const url = new URL(endpoint);
|
|
1147
|
+
return `${url.protocol}//${url.host}`;
|
|
1148
|
+
} catch {
|
|
1149
|
+
return endpoint;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
function isChainstackUrl(url) {
|
|
1153
|
+
try {
|
|
1154
|
+
const parsed = new URL(url.trim());
|
|
1155
|
+
return parsed.hostname.includes("chainstack.com");
|
|
1156
|
+
} catch {
|
|
1157
|
+
return false;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
function isQuickNodeUrl(url) {
|
|
1161
|
+
try {
|
|
1162
|
+
const parsed = new URL(url.trim());
|
|
1163
|
+
return parsed.hostname.includes("quiknode.pro");
|
|
1164
|
+
} catch {
|
|
1165
|
+
return false;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
function isTonCenterUrl(url) {
|
|
1169
|
+
try {
|
|
1170
|
+
const parsed = new URL(url.trim());
|
|
1171
|
+
return parsed.hostname.includes("toncenter.com");
|
|
1172
|
+
} catch {
|
|
1173
|
+
return false;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
function isOrbsUrl(url) {
|
|
1177
|
+
try {
|
|
1178
|
+
const parsed = new URL(url.trim());
|
|
1179
|
+
return parsed.hostname.includes("orbs.network") || parsed.hostname.includes("ton-access");
|
|
1180
|
+
} catch {
|
|
1181
|
+
return false;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
function buildRestUrl(baseEndpoint, method) {
|
|
1185
|
+
const base = toV2Base(baseEndpoint);
|
|
1186
|
+
return `${base}/${method}`;
|
|
1187
|
+
}
|
|
1188
|
+
function buildGetAddressStateUrl(baseEndpoint, address) {
|
|
1189
|
+
const base = toV2Base(baseEndpoint);
|
|
1190
|
+
return `${base}/getAddressState?address=${encodeURIComponent(address)}`;
|
|
1191
|
+
}
|
|
1192
|
+
function buildGetAddressBalanceUrl(baseEndpoint, address) {
|
|
1193
|
+
const base = toV2Base(baseEndpoint);
|
|
1194
|
+
return `${base}/getAddressBalance?address=${encodeURIComponent(address)}`;
|
|
1195
|
+
}
|
|
1196
|
+
function buildGetAddressInfoUrl(baseEndpoint, address) {
|
|
1197
|
+
const base = toV2Base(baseEndpoint);
|
|
1198
|
+
return `${base}/getAddressInformation?address=${encodeURIComponent(address)}`;
|
|
1199
|
+
}
|
|
1200
|
+
function detectNetworkFromEndpoint(endpoint) {
|
|
1201
|
+
const lower = endpoint.toLowerCase();
|
|
1202
|
+
if (lower.includes("testnet") || lower.includes("test") || lower.includes("sandbox")) {
|
|
1203
|
+
return "testnet";
|
|
1204
|
+
}
|
|
1205
|
+
if (lower.includes("mainnet") || lower.includes("main") || // TonCenter mainnet doesn't have 'mainnet' in URL
|
|
1206
|
+
lower.includes("toncenter.com") && !lower.includes("testnet")) {
|
|
1207
|
+
return "mainnet";
|
|
1208
|
+
}
|
|
1209
|
+
return null;
|
|
1210
|
+
}
|
|
1211
|
+
function isValidHttpUrl(str) {
|
|
1212
|
+
try {
|
|
1213
|
+
const url = new URL(str);
|
|
1214
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
1215
|
+
} catch {
|
|
1216
|
+
return false;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
function isValidWsUrl(str) {
|
|
1220
|
+
try {
|
|
1221
|
+
const url = new URL(str);
|
|
1222
|
+
return url.protocol === "ws:" || url.protocol === "wss:";
|
|
1223
|
+
} catch {
|
|
1224
|
+
return false;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
function isCredentialLike(segment) {
|
|
1228
|
+
return /^[0-9a-f]{16,}$/i.test(segment) || // hex API keys (e.g. Chainstack/GetBlock)
|
|
1229
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(segment) || // UUID
|
|
1230
|
+
/^[A-Za-z0-9_-]{20,}$/.test(segment);
|
|
1231
|
+
}
|
|
1232
|
+
function redactUrl(url) {
|
|
1233
|
+
const MASK = "***";
|
|
1234
|
+
const SENSITIVE_PARAM = /^(apikey|api[-_]?key|key|token|secret|password)$/i;
|
|
1235
|
+
const STRUCTURAL = /* @__PURE__ */ new Set([
|
|
1236
|
+
"api",
|
|
1237
|
+
"v1",
|
|
1238
|
+
"v2",
|
|
1239
|
+
"v3",
|
|
1240
|
+
"v4",
|
|
1241
|
+
"jsonrpc",
|
|
1242
|
+
"rpc",
|
|
1243
|
+
"public",
|
|
1244
|
+
"ws",
|
|
1245
|
+
"testnet",
|
|
1246
|
+
"mainnet",
|
|
1247
|
+
"toncenter-api-v2"
|
|
1248
|
+
]);
|
|
1249
|
+
try {
|
|
1250
|
+
const u = new URL(url);
|
|
1251
|
+
for (const name of Array.from(u.searchParams.keys())) {
|
|
1252
|
+
if (SENSITIVE_PARAM.test(name)) {
|
|
1253
|
+
u.searchParams.set(name, MASK);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
u.pathname = u.pathname.split("/").map(
|
|
1257
|
+
(seg) => seg && !STRUCTURAL.has(seg.toLowerCase()) && isCredentialLike(seg) ? MASK : seg
|
|
1258
|
+
).join("/");
|
|
1259
|
+
const hostParts = u.hostname.split(".");
|
|
1260
|
+
if (hostParts.length > 2 && isCredentialLike(hostParts[0])) {
|
|
1261
|
+
hostParts[0] = MASK;
|
|
1262
|
+
u.hostname = hostParts.join(".");
|
|
1263
|
+
}
|
|
1264
|
+
return u.toString();
|
|
1265
|
+
} catch {
|
|
1266
|
+
return url.replace(
|
|
1267
|
+
/\b(apikey|api[-_]?key|key|token|secret|password)=[^&\s]+/gi,
|
|
1268
|
+
`$1=${MASK}`
|
|
1269
|
+
);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1041
1273
|
// src/core/healthChecker.ts
|
|
1042
1274
|
var consoleLogger2 = {
|
|
1043
1275
|
debug: (msg, data) => console.debug(`[HealthChecker] ${msg}`, data || ""),
|
|
@@ -1054,6 +1286,8 @@ var HealthChecker = class {
|
|
|
1054
1286
|
constructor(config, logger, rateLimiter) {
|
|
1055
1287
|
this.results = /* @__PURE__ */ new Map();
|
|
1056
1288
|
this.highestSeqno = /* @__PURE__ */ new Map();
|
|
1289
|
+
/** Rolling window of recent valid seqnos per network (for outlier-robust blocksBehind). */
|
|
1290
|
+
this.seqnoSamples = /* @__PURE__ */ new Map();
|
|
1057
1291
|
this.rateLimiter = null;
|
|
1058
1292
|
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
1059
1293
|
this.logger = logger || consoleLogger2;
|
|
@@ -1099,22 +1333,36 @@ var HealthChecker = class {
|
|
|
1099
1333
|
if (!validation.valid) {
|
|
1100
1334
|
throw new Error(validation.error || "Provider configuration invalid");
|
|
1101
1335
|
}
|
|
1102
|
-
|
|
1336
|
+
let normalizedEndpoint = providerImpl.normalizeEndpoint(endpoint);
|
|
1103
1337
|
if (provider.type === "onfinality") {
|
|
1104
|
-
this.logger.debug(`OnFinality endpoint: ${endpoint} -> ${normalizedEndpoint}, API key: ${provider.apiKey ? "set" : "not set"}`);
|
|
1338
|
+
this.logger.debug(`OnFinality endpoint: ${redactUrl(endpoint)} -> ${redactUrl(normalizedEndpoint)}, API key: ${provider.apiKey ? "set" : "not set"}`);
|
|
1105
1339
|
}
|
|
1340
|
+
const orbsRetries = provider.isDynamic && provider.type === "orbs" ? 2 : 0;
|
|
1106
1341
|
let info;
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1342
|
+
for (let attempt = 0; ; attempt++) {
|
|
1343
|
+
try {
|
|
1344
|
+
info = await this.callGetMasterchainInfo(normalizedEndpoint, provider, providerImpl);
|
|
1345
|
+
break;
|
|
1346
|
+
} catch (error) {
|
|
1347
|
+
if (provider.type === "onfinality" && normalizedEndpoint.includes("/rpc") && provider.apiKey && error.message?.includes("backend error")) {
|
|
1348
|
+
this.logger.debug(`OnFinality /rpc failed, retrying with /public endpoint`);
|
|
1349
|
+
const baseUrl = normalizedEndpoint.split("?")[0];
|
|
1350
|
+
const publicEndpoint = baseUrl.replace("/rpc", "/public");
|
|
1351
|
+
const publicProvider = { ...provider, apiKey: void 0 };
|
|
1352
|
+
const publicProviderImpl = createProvider(publicProvider);
|
|
1353
|
+
info = await this.callGetMasterchainInfo(publicEndpoint, publicProvider, publicProviderImpl);
|
|
1354
|
+
break;
|
|
1355
|
+
}
|
|
1356
|
+
if (attempt < orbsRetries && this.isNonJsonResponseError(error)) {
|
|
1357
|
+
this.logger.debug(
|
|
1358
|
+
`Orbs gateway returned a non-JSON response; re-discovering endpoint (retry ${attempt + 1}/${orbsRetries})`
|
|
1359
|
+
);
|
|
1360
|
+
const freshEndpoint = await this.getEndpoint(provider);
|
|
1361
|
+
if (freshEndpoint) {
|
|
1362
|
+
normalizedEndpoint = providerImpl.normalizeEndpoint(freshEndpoint);
|
|
1363
|
+
continue;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1118
1366
|
throw error;
|
|
1119
1367
|
}
|
|
1120
1368
|
}
|
|
@@ -1129,7 +1377,8 @@ var HealthChecker = class {
|
|
|
1129
1377
|
if (seqno > currentHighest) {
|
|
1130
1378
|
this.highestSeqno.set(provider.network, seqno);
|
|
1131
1379
|
}
|
|
1132
|
-
const
|
|
1380
|
+
const baselineSeqno = this.recordSeqnoAndGetBaseline(provider.network, seqno);
|
|
1381
|
+
const blocksBehind = Math.max(0, baselineSeqno - seqno);
|
|
1133
1382
|
let status = "available";
|
|
1134
1383
|
if (blocksBehind > this.config.maxBlocksBehind) {
|
|
1135
1384
|
status = "stale";
|
|
@@ -1273,6 +1522,7 @@ var HealthChecker = class {
|
|
|
1273
1522
|
clearResults() {
|
|
1274
1523
|
this.results.clear();
|
|
1275
1524
|
this.highestSeqno.clear();
|
|
1525
|
+
this.seqnoSamples.clear();
|
|
1276
1526
|
}
|
|
1277
1527
|
/**
|
|
1278
1528
|
* Mark a provider as degraded (e.g., on 429 error)
|
|
@@ -1342,6 +1592,31 @@ var HealthChecker = class {
|
|
|
1342
1592
|
getResultKey(providerId, network) {
|
|
1343
1593
|
return `${providerId}-${network}`;
|
|
1344
1594
|
}
|
|
1595
|
+
/**
|
|
1596
|
+
* Record a valid seqno sample and return an outlier-robust freshness baseline
|
|
1597
|
+
* (median of recent samples for the network). Using the median instead of the
|
|
1598
|
+
* raw maximum prevents a single provider that reports an inflated seqno from
|
|
1599
|
+
* poisoning `blocksBehind` for every other (healthy) provider. See P2-2.
|
|
1600
|
+
*/
|
|
1601
|
+
recordSeqnoAndGetBaseline(network, seqno) {
|
|
1602
|
+
const samples = this.seqnoSamples.get(network) ?? [];
|
|
1603
|
+
samples.push(seqno);
|
|
1604
|
+
if (samples.length > 20) {
|
|
1605
|
+
samples.shift();
|
|
1606
|
+
}
|
|
1607
|
+
this.seqnoSamples.set(network, samples);
|
|
1608
|
+
const sorted = [...samples].sort((a, b) => a - b);
|
|
1609
|
+
const mid = Math.floor((sorted.length - 1) / 2);
|
|
1610
|
+
return sorted[mid];
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Detect a "non-JSON response" error (e.g. an HTML error page returned by a
|
|
1614
|
+
* misbehaving gateway), used to decide whether to re-discover an Orbs endpoint.
|
|
1615
|
+
*/
|
|
1616
|
+
isNonJsonResponseError(error) {
|
|
1617
|
+
const msg = (error?.message || String(error) || "").toLowerCase();
|
|
1618
|
+
return msg.includes("invalid response type") || msg.includes("expected json") || msg.includes("got text/html");
|
|
1619
|
+
}
|
|
1345
1620
|
/**
|
|
1346
1621
|
* Get endpoint URL for a provider (handles dynamic providers like Orbs)
|
|
1347
1622
|
*/
|
|
@@ -2183,7 +2458,7 @@ var ProviderSelector = class {
|
|
|
2183
2458
|
*/
|
|
2184
2459
|
setCustomEndpoint(endpoint) {
|
|
2185
2460
|
this.customEndpoint = endpoint?.trim() || null;
|
|
2186
|
-
this.logger.info(`Custom endpoint: ${this.customEndpoint
|
|
2461
|
+
this.logger.info(`Custom endpoint: ${this.customEndpoint ? redactUrl(this.customEndpoint) : "(none)"}`);
|
|
2187
2462
|
}
|
|
2188
2463
|
/**
|
|
2189
2464
|
* Get custom endpoint
|
|
@@ -2327,193 +2602,6 @@ function createSelector(registry, healthChecker, config, logger, adapter = "node
|
|
|
2327
2602
|
return new ProviderSelector(registry, healthChecker, config, logger, adapter);
|
|
2328
2603
|
}
|
|
2329
2604
|
|
|
2330
|
-
// src/utils/endpoint.ts
|
|
2331
|
-
function normalizeV2Endpoint(endpoint, provider) {
|
|
2332
|
-
if (provider) {
|
|
2333
|
-
const providerImpl = createProvider(provider);
|
|
2334
|
-
return providerImpl.normalizeEndpoint(endpoint);
|
|
2335
|
-
}
|
|
2336
|
-
return normalizeV2EndpointFallback(endpoint);
|
|
2337
|
-
}
|
|
2338
|
-
function normalizeV2EndpointFallback(endpoint) {
|
|
2339
|
-
let normalized = endpoint.trim();
|
|
2340
|
-
if (normalized.endsWith("/")) {
|
|
2341
|
-
normalized = normalized.slice(0, -1);
|
|
2342
|
-
}
|
|
2343
|
-
if (normalized.toLowerCase().endsWith("/jsonrpc")) {
|
|
2344
|
-
return normalized;
|
|
2345
|
-
}
|
|
2346
|
-
if (normalized.includes("gateway.tatum.io")) {
|
|
2347
|
-
try {
|
|
2348
|
-
const url = new URL(normalized);
|
|
2349
|
-
if (!url.pathname || url.pathname === "/") {
|
|
2350
|
-
return normalized + "/jsonRPC";
|
|
2351
|
-
}
|
|
2352
|
-
if (!url.pathname.toLowerCase().endsWith("/jsonrpc")) {
|
|
2353
|
-
return normalized + "/jsonRPC";
|
|
2354
|
-
}
|
|
2355
|
-
} catch {
|
|
2356
|
-
return normalized + "/jsonRPC";
|
|
2357
|
-
}
|
|
2358
|
-
}
|
|
2359
|
-
if (normalized.includes("onfinality.io")) {
|
|
2360
|
-
try {
|
|
2361
|
-
const url = new URL(normalized);
|
|
2362
|
-
const baseUrl = normalized.split("?")[0];
|
|
2363
|
-
if (!url.pathname || url.pathname === "/") {
|
|
2364
|
-
const apikey = url.searchParams.get("apikey");
|
|
2365
|
-
if (apikey && apikey !== "{key}" && apikey.length > 0) {
|
|
2366
|
-
return baseUrl.replace(/\/?$/, "/rpc");
|
|
2367
|
-
}
|
|
2368
|
-
return baseUrl.replace(/\/?$/, "/public");
|
|
2369
|
-
}
|
|
2370
|
-
if (url.pathname === "/rpc" || url.pathname === "/public") {
|
|
2371
|
-
return baseUrl;
|
|
2372
|
-
}
|
|
2373
|
-
return baseUrl;
|
|
2374
|
-
} catch {
|
|
2375
|
-
if (normalized.includes("{key}")) {
|
|
2376
|
-
return normalized.split("?")[0].replace(/\/?$/, "/public");
|
|
2377
|
-
}
|
|
2378
|
-
if (!normalized.includes("/rpc") && !normalized.includes("/public")) {
|
|
2379
|
-
return normalized.split("?")[0] + "/public";
|
|
2380
|
-
}
|
|
2381
|
-
return normalized.split("?")[0];
|
|
2382
|
-
}
|
|
2383
|
-
}
|
|
2384
|
-
if (normalized.endsWith("/api/v2")) {
|
|
2385
|
-
return normalized + "/jsonRPC";
|
|
2386
|
-
}
|
|
2387
|
-
if (normalized.endsWith("/api/v3")) {
|
|
2388
|
-
return normalized.replace("/api/v3", "/api/v2/jsonRPC");
|
|
2389
|
-
}
|
|
2390
|
-
if (normalized.includes("quiknode.pro") || normalized.includes("getblock.io")) {
|
|
2391
|
-
try {
|
|
2392
|
-
const url = new URL(normalized);
|
|
2393
|
-
if (!url.pathname || url.pathname === "/") {
|
|
2394
|
-
return normalized + "/jsonRPC";
|
|
2395
|
-
}
|
|
2396
|
-
if (!url.pathname.toLowerCase().endsWith("/jsonrpc")) {
|
|
2397
|
-
return normalized + "/jsonRPC";
|
|
2398
|
-
}
|
|
2399
|
-
} catch {
|
|
2400
|
-
return normalized + "/jsonRPC";
|
|
2401
|
-
}
|
|
2402
|
-
}
|
|
2403
|
-
try {
|
|
2404
|
-
const url = new URL(normalized);
|
|
2405
|
-
if (!url.pathname || url.pathname === "/") {
|
|
2406
|
-
return normalized + "/jsonRPC";
|
|
2407
|
-
}
|
|
2408
|
-
} catch {
|
|
2409
|
-
}
|
|
2410
|
-
return normalized;
|
|
2411
|
-
}
|
|
2412
|
-
function toV2Base(endpoint) {
|
|
2413
|
-
let normalized = endpoint.trim();
|
|
2414
|
-
if (normalized.endsWith("/")) {
|
|
2415
|
-
normalized = normalized.slice(0, -1);
|
|
2416
|
-
}
|
|
2417
|
-
if (normalized.toLowerCase().endsWith("/jsonrpc")) {
|
|
2418
|
-
normalized = normalized.slice(0, -8);
|
|
2419
|
-
}
|
|
2420
|
-
normalized = normalized.replace(/\/api\/v3\b/, "/api/v2");
|
|
2421
|
-
if (!normalized.endsWith("/api/v2")) {
|
|
2422
|
-
if (normalized.includes("/api/v2")) {
|
|
2423
|
-
const idx = normalized.indexOf("/api/v2");
|
|
2424
|
-
normalized = normalized.slice(0, idx + 7);
|
|
2425
|
-
}
|
|
2426
|
-
}
|
|
2427
|
-
return normalized;
|
|
2428
|
-
}
|
|
2429
|
-
function toV3Base(endpoint) {
|
|
2430
|
-
const normalized = toV2Base(endpoint);
|
|
2431
|
-
return normalized.replace("/api/v2", "/api/v3");
|
|
2432
|
-
}
|
|
2433
|
-
function getBaseUrl(endpoint) {
|
|
2434
|
-
try {
|
|
2435
|
-
const url = new URL(endpoint);
|
|
2436
|
-
return `${url.protocol}//${url.host}`;
|
|
2437
|
-
} catch {
|
|
2438
|
-
return endpoint;
|
|
2439
|
-
}
|
|
2440
|
-
}
|
|
2441
|
-
function isChainstackUrl(url) {
|
|
2442
|
-
try {
|
|
2443
|
-
const parsed = new URL(url.trim());
|
|
2444
|
-
return parsed.hostname.includes("chainstack.com");
|
|
2445
|
-
} catch {
|
|
2446
|
-
return false;
|
|
2447
|
-
}
|
|
2448
|
-
}
|
|
2449
|
-
function isQuickNodeUrl(url) {
|
|
2450
|
-
try {
|
|
2451
|
-
const parsed = new URL(url.trim());
|
|
2452
|
-
return parsed.hostname.includes("quiknode.pro");
|
|
2453
|
-
} catch {
|
|
2454
|
-
return false;
|
|
2455
|
-
}
|
|
2456
|
-
}
|
|
2457
|
-
function isTonCenterUrl(url) {
|
|
2458
|
-
try {
|
|
2459
|
-
const parsed = new URL(url.trim());
|
|
2460
|
-
return parsed.hostname.includes("toncenter.com");
|
|
2461
|
-
} catch {
|
|
2462
|
-
return false;
|
|
2463
|
-
}
|
|
2464
|
-
}
|
|
2465
|
-
function isOrbsUrl(url) {
|
|
2466
|
-
try {
|
|
2467
|
-
const parsed = new URL(url.trim());
|
|
2468
|
-
return parsed.hostname.includes("orbs.network") || parsed.hostname.includes("ton-access");
|
|
2469
|
-
} catch {
|
|
2470
|
-
return false;
|
|
2471
|
-
}
|
|
2472
|
-
}
|
|
2473
|
-
function buildRestUrl(baseEndpoint, method) {
|
|
2474
|
-
const base = toV2Base(baseEndpoint);
|
|
2475
|
-
return `${base}/${method}`;
|
|
2476
|
-
}
|
|
2477
|
-
function buildGetAddressStateUrl(baseEndpoint, address) {
|
|
2478
|
-
const base = toV2Base(baseEndpoint);
|
|
2479
|
-
return `${base}/getAddressState?address=${encodeURIComponent(address)}`;
|
|
2480
|
-
}
|
|
2481
|
-
function buildGetAddressBalanceUrl(baseEndpoint, address) {
|
|
2482
|
-
const base = toV2Base(baseEndpoint);
|
|
2483
|
-
return `${base}/getAddressBalance?address=${encodeURIComponent(address)}`;
|
|
2484
|
-
}
|
|
2485
|
-
function buildGetAddressInfoUrl(baseEndpoint, address) {
|
|
2486
|
-
const base = toV2Base(baseEndpoint);
|
|
2487
|
-
return `${base}/getAddressInformation?address=${encodeURIComponent(address)}`;
|
|
2488
|
-
}
|
|
2489
|
-
function detectNetworkFromEndpoint(endpoint) {
|
|
2490
|
-
const lower = endpoint.toLowerCase();
|
|
2491
|
-
if (lower.includes("testnet") || lower.includes("test") || lower.includes("sandbox")) {
|
|
2492
|
-
return "testnet";
|
|
2493
|
-
}
|
|
2494
|
-
if (lower.includes("mainnet") || lower.includes("main") || // TonCenter mainnet doesn't have 'mainnet' in URL
|
|
2495
|
-
lower.includes("toncenter.com") && !lower.includes("testnet")) {
|
|
2496
|
-
return "mainnet";
|
|
2497
|
-
}
|
|
2498
|
-
return null;
|
|
2499
|
-
}
|
|
2500
|
-
function isValidHttpUrl(str) {
|
|
2501
|
-
try {
|
|
2502
|
-
const url = new URL(str);
|
|
2503
|
-
return url.protocol === "http:" || url.protocol === "https:";
|
|
2504
|
-
} catch {
|
|
2505
|
-
return false;
|
|
2506
|
-
}
|
|
2507
|
-
}
|
|
2508
|
-
function isValidWsUrl(str) {
|
|
2509
|
-
try {
|
|
2510
|
-
const url = new URL(str);
|
|
2511
|
-
return url.protocol === "ws:" || url.protocol === "wss:";
|
|
2512
|
-
} catch {
|
|
2513
|
-
return false;
|
|
2514
|
-
}
|
|
2515
|
-
}
|
|
2516
|
-
|
|
2517
2605
|
// src/core/manager.ts
|
|
2518
2606
|
var consoleLogger5 = {
|
|
2519
2607
|
debug: (msg, data) => console.debug(`[ProviderManager] ${msg}`, data || ""),
|
|
@@ -3113,7 +3201,7 @@ var NodeAdapter = class {
|
|
|
3113
3201
|
network,
|
|
3114
3202
|
createdAt: Date.now()
|
|
3115
3203
|
};
|
|
3116
|
-
this.logger.debug(`Created TonClient for ${network}`, { endpoint });
|
|
3204
|
+
this.logger.debug(`Created TonClient for ${network}`, { endpoint: redactUrl(endpoint) });
|
|
3117
3205
|
return client;
|
|
3118
3206
|
}
|
|
3119
3207
|
/**
|
|
@@ -3735,6 +3823,7 @@ exports.mergeWithDefaults = mergeWithDefaults;
|
|
|
3735
3823
|
exports.normalizeV2Endpoint = normalizeV2Endpoint;
|
|
3736
3824
|
exports.parseProviderConfig = parseProviderConfig;
|
|
3737
3825
|
exports.parseRpcConfig = parseRpcConfig;
|
|
3826
|
+
exports.redactUrl = redactUrl;
|
|
3738
3827
|
exports.resetNodeAdapter = resetNodeAdapter;
|
|
3739
3828
|
exports.resolveAllProviders = resolveAllProviders;
|
|
3740
3829
|
exports.resolveEndpoints = resolveEndpoints;
|