ton-provider-system 0.2.4 → 0.3.1
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 +308 -206
- 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 +308 -207
- package/dist/index.js.map +1 -1
- package/package.json +17 -19
- package/rpc.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -324,7 +324,13 @@ var DEFAULT_PROVIDERS = {
|
|
|
324
324
|
},
|
|
325
325
|
rps: 1,
|
|
326
326
|
// Without API key
|
|
327
|
-
|
|
327
|
+
// Preferred on testnet: Toncenter serves the full v2 surface incl.
|
|
328
|
+
// getTransactions (curl-proven 200). Orbs only proxies liteserver
|
|
329
|
+
// get-methods/state and 403s on getTransactions, so it must NOT be the
|
|
330
|
+
// primary testnet provider (it stays a fallback for get-method reads).
|
|
331
|
+
// Selection is score-based and priority is the lever (lower = better),
|
|
332
|
+
// so this gives Toncenter the testnet edge over Orbs (priority 90).
|
|
333
|
+
priority: 10,
|
|
328
334
|
enabled: true,
|
|
329
335
|
description: "Official TON Center public endpoint"
|
|
330
336
|
},
|
|
@@ -336,7 +342,9 @@ var DEFAULT_PROVIDERS = {
|
|
|
336
342
|
v2: "https://ton-testnet.orbs.network/api/v2"
|
|
337
343
|
},
|
|
338
344
|
rps: 10,
|
|
339
|
-
priority:
|
|
345
|
+
// Demoted below toncenter_testnet (priority 10): Orbs is the decentralised
|
|
346
|
+
// fallback for non-transaction (get-method/state) testnet reads only.
|
|
347
|
+
priority: 90,
|
|
340
348
|
enabled: true,
|
|
341
349
|
isDynamic: true,
|
|
342
350
|
description: "Decentralized gateway - no API key needed"
|
|
@@ -373,7 +381,12 @@ function createDefaultConfig() {
|
|
|
373
381
|
version: "1.0",
|
|
374
382
|
providers: { ...DEFAULT_PROVIDERS },
|
|
375
383
|
defaults: {
|
|
376
|
-
|
|
384
|
+
// Testnet: Toncenter first (transactions-capable), Orbs as fallback.
|
|
385
|
+
// This default order only governs the no-healthy-providers fallback
|
|
386
|
+
// path; the primary, score-based selection is driven by priority
|
|
387
|
+
// (see toncenter_testnet/orbs_testnet above). Mainnet order is left
|
|
388
|
+
// unchanged (Orbs stays primary there — no regression).
|
|
389
|
+
testnet: ["toncenter_testnet", "orbs_testnet"],
|
|
377
390
|
mainnet: ["orbs_mainnet", "toncenter_mainnet"]
|
|
378
391
|
}
|
|
379
392
|
};
|
|
@@ -1038,6 +1051,238 @@ function createProvider(provider) {
|
|
|
1038
1051
|
}
|
|
1039
1052
|
}
|
|
1040
1053
|
|
|
1054
|
+
// src/utils/endpoint.ts
|
|
1055
|
+
function normalizeV2Endpoint(endpoint, provider) {
|
|
1056
|
+
if (provider) {
|
|
1057
|
+
const providerImpl = createProvider(provider);
|
|
1058
|
+
return providerImpl.normalizeEndpoint(endpoint);
|
|
1059
|
+
}
|
|
1060
|
+
return normalizeV2EndpointFallback(endpoint);
|
|
1061
|
+
}
|
|
1062
|
+
function normalizeV2EndpointFallback(endpoint) {
|
|
1063
|
+
let normalized = endpoint.trim();
|
|
1064
|
+
if (normalized.endsWith("/")) {
|
|
1065
|
+
normalized = normalized.slice(0, -1);
|
|
1066
|
+
}
|
|
1067
|
+
if (normalized.toLowerCase().endsWith("/jsonrpc")) {
|
|
1068
|
+
return normalized;
|
|
1069
|
+
}
|
|
1070
|
+
if (normalized.includes("gateway.tatum.io")) {
|
|
1071
|
+
try {
|
|
1072
|
+
const url = new URL(normalized);
|
|
1073
|
+
if (!url.pathname || url.pathname === "/") {
|
|
1074
|
+
return normalized + "/jsonRPC";
|
|
1075
|
+
}
|
|
1076
|
+
if (!url.pathname.toLowerCase().endsWith("/jsonrpc")) {
|
|
1077
|
+
return normalized + "/jsonRPC";
|
|
1078
|
+
}
|
|
1079
|
+
} catch {
|
|
1080
|
+
return normalized + "/jsonRPC";
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
if (normalized.includes("onfinality.io")) {
|
|
1084
|
+
try {
|
|
1085
|
+
const url = new URL(normalized);
|
|
1086
|
+
const baseUrl = normalized.split("?")[0];
|
|
1087
|
+
if (!url.pathname || url.pathname === "/") {
|
|
1088
|
+
const apikey = url.searchParams.get("apikey");
|
|
1089
|
+
if (apikey && apikey !== "{key}" && apikey.length > 0) {
|
|
1090
|
+
return baseUrl.replace(/\/?$/, "/rpc");
|
|
1091
|
+
}
|
|
1092
|
+
return baseUrl.replace(/\/?$/, "/public");
|
|
1093
|
+
}
|
|
1094
|
+
if (url.pathname === "/rpc" || url.pathname === "/public") {
|
|
1095
|
+
return baseUrl;
|
|
1096
|
+
}
|
|
1097
|
+
return baseUrl;
|
|
1098
|
+
} catch {
|
|
1099
|
+
if (normalized.includes("{key}")) {
|
|
1100
|
+
return normalized.split("?")[0].replace(/\/?$/, "/public");
|
|
1101
|
+
}
|
|
1102
|
+
if (!normalized.includes("/rpc") && !normalized.includes("/public")) {
|
|
1103
|
+
return normalized.split("?")[0] + "/public";
|
|
1104
|
+
}
|
|
1105
|
+
return normalized.split("?")[0];
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
if (normalized.endsWith("/api/v2")) {
|
|
1109
|
+
return normalized + "/jsonRPC";
|
|
1110
|
+
}
|
|
1111
|
+
if (normalized.endsWith("/api/v3")) {
|
|
1112
|
+
return normalized.replace("/api/v3", "/api/v2/jsonRPC");
|
|
1113
|
+
}
|
|
1114
|
+
if (normalized.includes("quiknode.pro") || normalized.includes("getblock.io")) {
|
|
1115
|
+
try {
|
|
1116
|
+
const url = new URL(normalized);
|
|
1117
|
+
if (!url.pathname || url.pathname === "/") {
|
|
1118
|
+
return normalized + "/jsonRPC";
|
|
1119
|
+
}
|
|
1120
|
+
if (!url.pathname.toLowerCase().endsWith("/jsonrpc")) {
|
|
1121
|
+
return normalized + "/jsonRPC";
|
|
1122
|
+
}
|
|
1123
|
+
} catch {
|
|
1124
|
+
return normalized + "/jsonRPC";
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
try {
|
|
1128
|
+
const url = new URL(normalized);
|
|
1129
|
+
if (!url.pathname || url.pathname === "/") {
|
|
1130
|
+
return normalized + "/jsonRPC";
|
|
1131
|
+
}
|
|
1132
|
+
} catch {
|
|
1133
|
+
}
|
|
1134
|
+
return normalized;
|
|
1135
|
+
}
|
|
1136
|
+
function toV2Base(endpoint) {
|
|
1137
|
+
let normalized = endpoint.trim();
|
|
1138
|
+
if (normalized.endsWith("/")) {
|
|
1139
|
+
normalized = normalized.slice(0, -1);
|
|
1140
|
+
}
|
|
1141
|
+
if (normalized.toLowerCase().endsWith("/jsonrpc")) {
|
|
1142
|
+
normalized = normalized.slice(0, -8);
|
|
1143
|
+
}
|
|
1144
|
+
normalized = normalized.replace(/\/api\/v3\b/, "/api/v2");
|
|
1145
|
+
if (!normalized.endsWith("/api/v2")) {
|
|
1146
|
+
if (normalized.includes("/api/v2")) {
|
|
1147
|
+
const idx = normalized.indexOf("/api/v2");
|
|
1148
|
+
normalized = normalized.slice(0, idx + 7);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
return normalized;
|
|
1152
|
+
}
|
|
1153
|
+
function toV3Base(endpoint) {
|
|
1154
|
+
const normalized = toV2Base(endpoint);
|
|
1155
|
+
return normalized.replace("/api/v2", "/api/v3");
|
|
1156
|
+
}
|
|
1157
|
+
function getBaseUrl(endpoint) {
|
|
1158
|
+
try {
|
|
1159
|
+
const url = new URL(endpoint);
|
|
1160
|
+
return `${url.protocol}//${url.host}`;
|
|
1161
|
+
} catch {
|
|
1162
|
+
return endpoint;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
function isChainstackUrl(url) {
|
|
1166
|
+
try {
|
|
1167
|
+
const parsed = new URL(url.trim());
|
|
1168
|
+
return parsed.hostname.includes("chainstack.com");
|
|
1169
|
+
} catch {
|
|
1170
|
+
return false;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
function isQuickNodeUrl(url) {
|
|
1174
|
+
try {
|
|
1175
|
+
const parsed = new URL(url.trim());
|
|
1176
|
+
return parsed.hostname.includes("quiknode.pro");
|
|
1177
|
+
} catch {
|
|
1178
|
+
return false;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
function isTonCenterUrl(url) {
|
|
1182
|
+
try {
|
|
1183
|
+
const parsed = new URL(url.trim());
|
|
1184
|
+
return parsed.hostname.includes("toncenter.com");
|
|
1185
|
+
} catch {
|
|
1186
|
+
return false;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
function isOrbsUrl(url) {
|
|
1190
|
+
try {
|
|
1191
|
+
const parsed = new URL(url.trim());
|
|
1192
|
+
return parsed.hostname.includes("orbs.network") || parsed.hostname.includes("ton-access");
|
|
1193
|
+
} catch {
|
|
1194
|
+
return false;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
function buildRestUrl(baseEndpoint, method) {
|
|
1198
|
+
const base = toV2Base(baseEndpoint);
|
|
1199
|
+
return `${base}/${method}`;
|
|
1200
|
+
}
|
|
1201
|
+
function buildGetAddressStateUrl(baseEndpoint, address) {
|
|
1202
|
+
const base = toV2Base(baseEndpoint);
|
|
1203
|
+
return `${base}/getAddressState?address=${encodeURIComponent(address)}`;
|
|
1204
|
+
}
|
|
1205
|
+
function buildGetAddressBalanceUrl(baseEndpoint, address) {
|
|
1206
|
+
const base = toV2Base(baseEndpoint);
|
|
1207
|
+
return `${base}/getAddressBalance?address=${encodeURIComponent(address)}`;
|
|
1208
|
+
}
|
|
1209
|
+
function buildGetAddressInfoUrl(baseEndpoint, address) {
|
|
1210
|
+
const base = toV2Base(baseEndpoint);
|
|
1211
|
+
return `${base}/getAddressInformation?address=${encodeURIComponent(address)}`;
|
|
1212
|
+
}
|
|
1213
|
+
function detectNetworkFromEndpoint(endpoint) {
|
|
1214
|
+
const lower = endpoint.toLowerCase();
|
|
1215
|
+
if (lower.includes("testnet") || lower.includes("test") || lower.includes("sandbox")) {
|
|
1216
|
+
return "testnet";
|
|
1217
|
+
}
|
|
1218
|
+
if (lower.includes("mainnet") || lower.includes("main") || // TonCenter mainnet doesn't have 'mainnet' in URL
|
|
1219
|
+
lower.includes("toncenter.com") && !lower.includes("testnet")) {
|
|
1220
|
+
return "mainnet";
|
|
1221
|
+
}
|
|
1222
|
+
return null;
|
|
1223
|
+
}
|
|
1224
|
+
function isValidHttpUrl(str) {
|
|
1225
|
+
try {
|
|
1226
|
+
const url = new URL(str);
|
|
1227
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
1228
|
+
} catch {
|
|
1229
|
+
return false;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
function isValidWsUrl(str) {
|
|
1233
|
+
try {
|
|
1234
|
+
const url = new URL(str);
|
|
1235
|
+
return url.protocol === "ws:" || url.protocol === "wss:";
|
|
1236
|
+
} catch {
|
|
1237
|
+
return false;
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
function isCredentialLike(segment) {
|
|
1241
|
+
return /^[0-9a-f]{16,}$/i.test(segment) || // hex API keys (e.g. Chainstack/GetBlock)
|
|
1242
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(segment) || // UUID
|
|
1243
|
+
/^[A-Za-z0-9_-]{20,}$/.test(segment);
|
|
1244
|
+
}
|
|
1245
|
+
function redactUrl(url) {
|
|
1246
|
+
const MASK = "***";
|
|
1247
|
+
const SENSITIVE_PARAM = /^(apikey|api[-_]?key|key|token|secret|password)$/i;
|
|
1248
|
+
const STRUCTURAL = /* @__PURE__ */ new Set([
|
|
1249
|
+
"api",
|
|
1250
|
+
"v1",
|
|
1251
|
+
"v2",
|
|
1252
|
+
"v3",
|
|
1253
|
+
"v4",
|
|
1254
|
+
"jsonrpc",
|
|
1255
|
+
"rpc",
|
|
1256
|
+
"public",
|
|
1257
|
+
"ws",
|
|
1258
|
+
"testnet",
|
|
1259
|
+
"mainnet",
|
|
1260
|
+
"toncenter-api-v2"
|
|
1261
|
+
]);
|
|
1262
|
+
try {
|
|
1263
|
+
const u = new URL(url);
|
|
1264
|
+
for (const name of Array.from(u.searchParams.keys())) {
|
|
1265
|
+
if (SENSITIVE_PARAM.test(name)) {
|
|
1266
|
+
u.searchParams.set(name, MASK);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
u.pathname = u.pathname.split("/").map(
|
|
1270
|
+
(seg) => seg && !STRUCTURAL.has(seg.toLowerCase()) && isCredentialLike(seg) ? MASK : seg
|
|
1271
|
+
).join("/");
|
|
1272
|
+
const hostParts = u.hostname.split(".");
|
|
1273
|
+
if (hostParts.length > 2 && isCredentialLike(hostParts[0])) {
|
|
1274
|
+
hostParts[0] = MASK;
|
|
1275
|
+
u.hostname = hostParts.join(".");
|
|
1276
|
+
}
|
|
1277
|
+
return u.toString();
|
|
1278
|
+
} catch {
|
|
1279
|
+
return url.replace(
|
|
1280
|
+
/\b(apikey|api[-_]?key|key|token|secret|password)=[^&\s]+/gi,
|
|
1281
|
+
`$1=${MASK}`
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1041
1286
|
// src/core/healthChecker.ts
|
|
1042
1287
|
var consoleLogger2 = {
|
|
1043
1288
|
debug: (msg, data) => console.debug(`[HealthChecker] ${msg}`, data || ""),
|
|
@@ -1054,6 +1299,8 @@ var HealthChecker = class {
|
|
|
1054
1299
|
constructor(config, logger, rateLimiter) {
|
|
1055
1300
|
this.results = /* @__PURE__ */ new Map();
|
|
1056
1301
|
this.highestSeqno = /* @__PURE__ */ new Map();
|
|
1302
|
+
/** Rolling window of recent valid seqnos per network (for outlier-robust blocksBehind). */
|
|
1303
|
+
this.seqnoSamples = /* @__PURE__ */ new Map();
|
|
1057
1304
|
this.rateLimiter = null;
|
|
1058
1305
|
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
1059
1306
|
this.logger = logger || consoleLogger2;
|
|
@@ -1099,22 +1346,36 @@ var HealthChecker = class {
|
|
|
1099
1346
|
if (!validation.valid) {
|
|
1100
1347
|
throw new Error(validation.error || "Provider configuration invalid");
|
|
1101
1348
|
}
|
|
1102
|
-
|
|
1349
|
+
let normalizedEndpoint = providerImpl.normalizeEndpoint(endpoint);
|
|
1103
1350
|
if (provider.type === "onfinality") {
|
|
1104
|
-
this.logger.debug(`OnFinality endpoint: ${endpoint} -> ${normalizedEndpoint}, API key: ${provider.apiKey ? "set" : "not set"}`);
|
|
1351
|
+
this.logger.debug(`OnFinality endpoint: ${redactUrl(endpoint)} -> ${redactUrl(normalizedEndpoint)}, API key: ${provider.apiKey ? "set" : "not set"}`);
|
|
1105
1352
|
}
|
|
1353
|
+
const orbsRetries = provider.isDynamic && provider.type === "orbs" ? 2 : 0;
|
|
1106
1354
|
let info;
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1355
|
+
for (let attempt = 0; ; attempt++) {
|
|
1356
|
+
try {
|
|
1357
|
+
info = await this.callGetMasterchainInfo(normalizedEndpoint, provider, providerImpl);
|
|
1358
|
+
break;
|
|
1359
|
+
} catch (error) {
|
|
1360
|
+
if (provider.type === "onfinality" && normalizedEndpoint.includes("/rpc") && provider.apiKey && error.message?.includes("backend error")) {
|
|
1361
|
+
this.logger.debug(`OnFinality /rpc failed, retrying with /public endpoint`);
|
|
1362
|
+
const baseUrl = normalizedEndpoint.split("?")[0];
|
|
1363
|
+
const publicEndpoint = baseUrl.replace("/rpc", "/public");
|
|
1364
|
+
const publicProvider = { ...provider, apiKey: void 0 };
|
|
1365
|
+
const publicProviderImpl = createProvider(publicProvider);
|
|
1366
|
+
info = await this.callGetMasterchainInfo(publicEndpoint, publicProvider, publicProviderImpl);
|
|
1367
|
+
break;
|
|
1368
|
+
}
|
|
1369
|
+
if (attempt < orbsRetries && this.isNonJsonResponseError(error)) {
|
|
1370
|
+
this.logger.debug(
|
|
1371
|
+
`Orbs gateway returned a non-JSON response; re-discovering endpoint (retry ${attempt + 1}/${orbsRetries})`
|
|
1372
|
+
);
|
|
1373
|
+
const freshEndpoint = await this.getEndpoint(provider);
|
|
1374
|
+
if (freshEndpoint) {
|
|
1375
|
+
normalizedEndpoint = providerImpl.normalizeEndpoint(freshEndpoint);
|
|
1376
|
+
continue;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1118
1379
|
throw error;
|
|
1119
1380
|
}
|
|
1120
1381
|
}
|
|
@@ -1129,7 +1390,8 @@ var HealthChecker = class {
|
|
|
1129
1390
|
if (seqno > currentHighest) {
|
|
1130
1391
|
this.highestSeqno.set(provider.network, seqno);
|
|
1131
1392
|
}
|
|
1132
|
-
const
|
|
1393
|
+
const baselineSeqno = this.recordSeqnoAndGetBaseline(provider.network, seqno);
|
|
1394
|
+
const blocksBehind = Math.max(0, baselineSeqno - seqno);
|
|
1133
1395
|
let status = "available";
|
|
1134
1396
|
if (blocksBehind > this.config.maxBlocksBehind) {
|
|
1135
1397
|
status = "stale";
|
|
@@ -1273,6 +1535,7 @@ var HealthChecker = class {
|
|
|
1273
1535
|
clearResults() {
|
|
1274
1536
|
this.results.clear();
|
|
1275
1537
|
this.highestSeqno.clear();
|
|
1538
|
+
this.seqnoSamples.clear();
|
|
1276
1539
|
}
|
|
1277
1540
|
/**
|
|
1278
1541
|
* Mark a provider as degraded (e.g., on 429 error)
|
|
@@ -1342,6 +1605,31 @@ var HealthChecker = class {
|
|
|
1342
1605
|
getResultKey(providerId, network) {
|
|
1343
1606
|
return `${providerId}-${network}`;
|
|
1344
1607
|
}
|
|
1608
|
+
/**
|
|
1609
|
+
* Record a valid seqno sample and return an outlier-robust freshness baseline
|
|
1610
|
+
* (median of recent samples for the network). Using the median instead of the
|
|
1611
|
+
* raw maximum prevents a single provider that reports an inflated seqno from
|
|
1612
|
+
* poisoning `blocksBehind` for every other (healthy) provider. See P2-2.
|
|
1613
|
+
*/
|
|
1614
|
+
recordSeqnoAndGetBaseline(network, seqno) {
|
|
1615
|
+
const samples = this.seqnoSamples.get(network) ?? [];
|
|
1616
|
+
samples.push(seqno);
|
|
1617
|
+
if (samples.length > 20) {
|
|
1618
|
+
samples.shift();
|
|
1619
|
+
}
|
|
1620
|
+
this.seqnoSamples.set(network, samples);
|
|
1621
|
+
const sorted = [...samples].sort((a, b) => a - b);
|
|
1622
|
+
const mid = Math.floor((sorted.length - 1) / 2);
|
|
1623
|
+
return sorted[mid];
|
|
1624
|
+
}
|
|
1625
|
+
/**
|
|
1626
|
+
* Detect a "non-JSON response" error (e.g. an HTML error page returned by a
|
|
1627
|
+
* misbehaving gateway), used to decide whether to re-discover an Orbs endpoint.
|
|
1628
|
+
*/
|
|
1629
|
+
isNonJsonResponseError(error) {
|
|
1630
|
+
const msg = (error?.message || String(error) || "").toLowerCase();
|
|
1631
|
+
return msg.includes("invalid response type") || msg.includes("expected json") || msg.includes("got text/html");
|
|
1632
|
+
}
|
|
1345
1633
|
/**
|
|
1346
1634
|
* Get endpoint URL for a provider (handles dynamic providers like Orbs)
|
|
1347
1635
|
*/
|
|
@@ -2183,7 +2471,7 @@ var ProviderSelector = class {
|
|
|
2183
2471
|
*/
|
|
2184
2472
|
setCustomEndpoint(endpoint) {
|
|
2185
2473
|
this.customEndpoint = endpoint?.trim() || null;
|
|
2186
|
-
this.logger.info(`Custom endpoint: ${this.customEndpoint
|
|
2474
|
+
this.logger.info(`Custom endpoint: ${this.customEndpoint ? redactUrl(this.customEndpoint) : "(none)"}`);
|
|
2187
2475
|
}
|
|
2188
2476
|
/**
|
|
2189
2477
|
* Get custom endpoint
|
|
@@ -2327,193 +2615,6 @@ function createSelector(registry, healthChecker, config, logger, adapter = "node
|
|
|
2327
2615
|
return new ProviderSelector(registry, healthChecker, config, logger, adapter);
|
|
2328
2616
|
}
|
|
2329
2617
|
|
|
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
2618
|
// src/core/manager.ts
|
|
2518
2619
|
var consoleLogger5 = {
|
|
2519
2620
|
debug: (msg, data) => console.debug(`[ProviderManager] ${msg}`, data || ""),
|
|
@@ -3113,7 +3214,7 @@ var NodeAdapter = class {
|
|
|
3113
3214
|
network,
|
|
3114
3215
|
createdAt: Date.now()
|
|
3115
3216
|
};
|
|
3116
|
-
this.logger.debug(`Created TonClient for ${network}`, { endpoint });
|
|
3217
|
+
this.logger.debug(`Created TonClient for ${network}`, { endpoint: redactUrl(endpoint) });
|
|
3117
3218
|
return client;
|
|
3118
3219
|
}
|
|
3119
3220
|
/**
|
|
@@ -3735,6 +3836,7 @@ exports.mergeWithDefaults = mergeWithDefaults;
|
|
|
3735
3836
|
exports.normalizeV2Endpoint = normalizeV2Endpoint;
|
|
3736
3837
|
exports.parseProviderConfig = parseProviderConfig;
|
|
3737
3838
|
exports.parseRpcConfig = parseRpcConfig;
|
|
3839
|
+
exports.redactUrl = redactUrl;
|
|
3738
3840
|
exports.resetNodeAdapter = resetNodeAdapter;
|
|
3739
3841
|
exports.resolveAllProviders = resolveAllProviders;
|
|
3740
3842
|
exports.resolveEndpoints = resolveEndpoints;
|