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 CHANGED
@@ -324,7 +324,13 @@ var DEFAULT_PROVIDERS = {
324
324
  },
325
325
  rps: 1,
326
326
  // Without API key
327
- priority: 100,
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: 50,
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
- testnet: ["orbs_testnet", "toncenter_testnet"],
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
- const normalizedEndpoint = providerImpl.normalizeEndpoint(endpoint);
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
- try {
1108
- info = await this.callGetMasterchainInfo(normalizedEndpoint, provider, providerImpl);
1109
- } catch (error) {
1110
- if (provider.type === "onfinality" && normalizedEndpoint.includes("/rpc") && provider.apiKey && error.message?.includes("backend error")) {
1111
- this.logger.debug(`OnFinality /rpc failed, retrying with /public endpoint`);
1112
- const baseUrl = normalizedEndpoint.split("?")[0];
1113
- const publicEndpoint = baseUrl.replace("/rpc", "/public");
1114
- const publicProvider = { ...provider, apiKey: void 0 };
1115
- const publicProviderImpl = createProvider(publicProvider);
1116
- info = await this.callGetMasterchainInfo(publicEndpoint, publicProvider, publicProviderImpl);
1117
- } else {
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 blocksBehind = Math.max(0, (this.highestSeqno.get(provider.network) || seqno) - seqno);
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 || "(none)"}`);
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;