ton-provider-system 0.5.0 → 0.7.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 +130 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -3
- package/dist/index.d.ts +50 -3
- package/dist/index.js +130 -40
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/rpc.json +5 -51
package/dist/index.cjs
CHANGED
|
@@ -329,30 +329,28 @@ var DEFAULT_PROVIDERS = {
|
|
|
329
329
|
},
|
|
330
330
|
rps: 1,
|
|
331
331
|
// Without API key
|
|
332
|
-
//
|
|
333
|
-
// getTransactions (curl-proven 200).
|
|
334
|
-
//
|
|
335
|
-
|
|
336
|
-
// Selection is score-based and priority is the lever (lower = better),
|
|
337
|
-
// so this gives Toncenter the testnet edge over Orbs (priority 90).
|
|
338
|
-
priority: 10,
|
|
332
|
+
// Primary testnet provider: Toncenter serves the full v2 surface incl.
|
|
333
|
+
// getTransactions (curl-proven 200). Selection is score-based and priority
|
|
334
|
+
// is the lever (lower = better), so priority 1 keeps it ahead of Tatum.
|
|
335
|
+
priority: 1,
|
|
339
336
|
enabled: true,
|
|
340
337
|
description: "Official TON Center public endpoint"
|
|
341
338
|
},
|
|
342
|
-
|
|
343
|
-
name: "
|
|
344
|
-
type: "
|
|
339
|
+
tatum_testnet: {
|
|
340
|
+
name: "Tatum Testnet",
|
|
341
|
+
type: "tatum",
|
|
345
342
|
network: "testnet",
|
|
346
343
|
endpoints: {
|
|
347
|
-
v2: "https://ton-testnet.
|
|
344
|
+
v2: "https://ton-testnet.gateway.tatum.io"
|
|
348
345
|
},
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
//
|
|
352
|
-
|
|
346
|
+
apiKeyEnvVar: "TATUM_API_KEY_TESTNET",
|
|
347
|
+
rps: 3,
|
|
348
|
+
// Secondary testnet provider (priority 2): also serves the full workload
|
|
349
|
+
// (getTransactions/sendBoc 200), so it backs up Toncenter on failover.
|
|
350
|
+
priority: 2,
|
|
353
351
|
enabled: true,
|
|
354
|
-
|
|
355
|
-
description: "
|
|
352
|
+
browserCompatible: false,
|
|
353
|
+
description: "Tatum testnet gateway - requires TATUM_API_KEY_TESTNET"
|
|
356
354
|
},
|
|
357
355
|
toncenter_mainnet: {
|
|
358
356
|
name: "TON Center Mainnet",
|
|
@@ -386,12 +384,12 @@ function createDefaultConfig() {
|
|
|
386
384
|
version: "1.0",
|
|
387
385
|
providers: { ...DEFAULT_PROVIDERS },
|
|
388
386
|
defaults: {
|
|
389
|
-
// Testnet:
|
|
390
|
-
// This default order
|
|
391
|
-
// path; the primary, score-based selection is driven by priority
|
|
392
|
-
// (see toncenter_testnet/
|
|
387
|
+
// Testnet: only the transaction-capable providers — Toncenter first,
|
|
388
|
+
// Tatum as failover. This default order governs the no-healthy-providers
|
|
389
|
+
// fallback path; the primary, score-based selection is driven by priority
|
|
390
|
+
// (see toncenter_testnet/tatum_testnet above). Mainnet order is left
|
|
393
391
|
// unchanged (Orbs stays primary there — no regression).
|
|
394
|
-
testnet: ["toncenter_testnet", "
|
|
392
|
+
testnet: ["toncenter_testnet", "tatum_testnet"],
|
|
395
393
|
mainnet: ["orbs_mainnet", "toncenter_mainnet"]
|
|
396
394
|
}
|
|
397
395
|
};
|
|
@@ -3069,6 +3067,22 @@ var _ProviderManager = class _ProviderManager {
|
|
|
3069
3067
|
if (!this.initialized || !this.network) return [];
|
|
3070
3068
|
return this.selector.getAvailableProviders(this.network).filter((p) => p.servesGetTransactions !== false);
|
|
3071
3069
|
}
|
|
3070
|
+
/**
|
|
3071
|
+
* Get broadcast-capable providers for the current network, in selector score
|
|
3072
|
+
* order (best first).
|
|
3073
|
+
*
|
|
3074
|
+
* Unlike getTransactionCapableProviders, this applies NO capability filter:
|
|
3075
|
+
* `sendBoc` (forwarding an already-signed external message) works on EVERY
|
|
3076
|
+
* provider type — including the liteserver-proxy providers (Chainstack/Orbs)
|
|
3077
|
+
* that 403 only on the v2 `getTransactions` shape. The list is the plain
|
|
3078
|
+
* score-ordered available set, so a broadcast walks best→worst and a provider
|
|
3079
|
+
* that 429s/5xxs on sendBoc is skipped in favour of the next one.
|
|
3080
|
+
* Returns [] when no provider is currently selectable.
|
|
3081
|
+
*/
|
|
3082
|
+
getBroadcastCapableProviders() {
|
|
3083
|
+
if (!this.initialized || !this.network) return [];
|
|
3084
|
+
return this.selector.getAvailableProviders(this.network);
|
|
3085
|
+
}
|
|
3072
3086
|
/**
|
|
3073
3087
|
* Get provider health results for current network
|
|
3074
3088
|
*/
|
|
@@ -3457,30 +3471,106 @@ var NodeAdapter = class {
|
|
|
3457
3471
|
}
|
|
3458
3472
|
}
|
|
3459
3473
|
/**
|
|
3460
|
-
*
|
|
3474
|
+
* Broadcast an already-signed external message BOC, with provider failover.
|
|
3475
|
+
*
|
|
3476
|
+
* Unlike a single-endpoint `client.sendFile`/raw POST, this walks the network's
|
|
3477
|
+
* score-ordered providers (best first; see ProviderManager.getBroadcastCapableProviders)
|
|
3478
|
+
* and fails over to the next provider when one returns a TRANSIENT failure — a
|
|
3479
|
+
* 429 (rate limit), a 5xx (gateway/server error, e.g. the testnet chainstack
|
|
3480
|
+
* free plan 500ing on sendBoc), a timeout, or a network error. This closes the
|
|
3481
|
+
* broadcast half of the failover story: the getTransactions capability flag only
|
|
3482
|
+
* diverted reads, so a demoted-but-still-reachable provider could still 500 a
|
|
3483
|
+
* broadcast — now the broadcast itself moves on to a healthy provider.
|
|
3484
|
+
*
|
|
3485
|
+
* Safety: re-broadcasting is idempotent at the TON level — the external message
|
|
3486
|
+
* has a deterministic hash, so forwarding the SAME signed BOC to another provider
|
|
3487
|
+
* after a transient failure is safe (the network dedups; a duplicate is a no-op).
|
|
3488
|
+
* A 4xx that means "this BOC is invalid" (400/413/422) is therefore NOT failed
|
|
3489
|
+
* over: it is the payload, not the provider — surfaced immediately, without
|
|
3490
|
+
* poisoning the provider's health or spraying a bad BOC across the fleet.
|
|
3461
3491
|
*/
|
|
3462
3492
|
async sendBoc(boc, timeoutMs = 3e4) {
|
|
3463
|
-
const
|
|
3493
|
+
const network = this.manager.getNetwork();
|
|
3494
|
+
if (!network) {
|
|
3495
|
+
throw new Error("ProviderManager not initialized");
|
|
3496
|
+
}
|
|
3497
|
+
const bocBase64 = typeof boc === "string" ? boc : boc.toString("base64");
|
|
3498
|
+
const candidates = this.manager.getBroadcastCapableProviders();
|
|
3499
|
+
if (candidates.length === 0) {
|
|
3500
|
+
const endpoint = await this.manager.getEndpoint();
|
|
3501
|
+
try {
|
|
3502
|
+
await this.sendBocToEndpoint(endpoint, bocBase64, timeoutMs);
|
|
3503
|
+
this.manager.reportSuccess();
|
|
3504
|
+
} catch (error) {
|
|
3505
|
+
if (!this.isInvalidPayloadError(error)) {
|
|
3506
|
+
this.manager.reportError(error);
|
|
3507
|
+
}
|
|
3508
|
+
throw error;
|
|
3509
|
+
}
|
|
3510
|
+
return;
|
|
3511
|
+
}
|
|
3512
|
+
const rateLimiter = this.manager.getRateLimiter();
|
|
3513
|
+
let lastError;
|
|
3514
|
+
for (const provider of candidates) {
|
|
3515
|
+
if (rateLimiter) {
|
|
3516
|
+
await rateLimiter.acquire(provider.id, timeoutMs);
|
|
3517
|
+
}
|
|
3518
|
+
const endpoint = normalizeV2Endpoint(provider.endpointV2, provider);
|
|
3519
|
+
try {
|
|
3520
|
+
await this.sendBocToEndpoint(endpoint, bocBase64, timeoutMs);
|
|
3521
|
+
this.manager.reportSuccess(provider.id);
|
|
3522
|
+
return;
|
|
3523
|
+
} catch (error) {
|
|
3524
|
+
if (this.isInvalidPayloadError(error)) {
|
|
3525
|
+
throw error;
|
|
3526
|
+
}
|
|
3527
|
+
lastError = error;
|
|
3528
|
+
this.manager.reportError(error, provider.id);
|
|
3529
|
+
this.logger.warn(
|
|
3530
|
+
`sendBoc failed on ${provider.id}, failing over`,
|
|
3531
|
+
{ error: error?.message || String(error) }
|
|
3532
|
+
);
|
|
3533
|
+
}
|
|
3534
|
+
}
|
|
3535
|
+
throw lastError || new Error(`sendBoc: all providers failed for ${network}`);
|
|
3536
|
+
}
|
|
3537
|
+
/**
|
|
3538
|
+
* POST a base64 BOC to one provider's REST `/sendBoc`. Throws on a non-2xx
|
|
3539
|
+
* response with the HTTP `status` attached (so the caller can classify
|
|
3540
|
+
* transient vs invalid-payload), and on an `ok:false` JSON body.
|
|
3541
|
+
*/
|
|
3542
|
+
async sendBocToEndpoint(endpoint, bocBase64, timeoutMs) {
|
|
3464
3543
|
const baseV2 = toV2Base(endpoint);
|
|
3465
3544
|
const url = `${baseV2}/sendBoc`;
|
|
3466
|
-
const
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
{
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3545
|
+
const response = await fetchWithTimeout(
|
|
3546
|
+
url,
|
|
3547
|
+
{
|
|
3548
|
+
method: "POST",
|
|
3549
|
+
headers: { "Content-Type": "application/json" },
|
|
3550
|
+
body: JSON.stringify({ boc: bocBase64 })
|
|
3551
|
+
},
|
|
3552
|
+
timeoutMs
|
|
3553
|
+
);
|
|
3554
|
+
if (!response.ok) {
|
|
3555
|
+
const err = new Error(
|
|
3556
|
+
`HTTP ${response.status} ${response.statusText} on sendBoc`
|
|
3476
3557
|
);
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
this.manager.reportSuccess();
|
|
3480
|
-
} catch (error) {
|
|
3481
|
-
this.manager.reportError(error);
|
|
3482
|
-
throw error;
|
|
3558
|
+
err.status = response.status;
|
|
3559
|
+
throw err;
|
|
3483
3560
|
}
|
|
3561
|
+
const json = await response.json();
|
|
3562
|
+
this.unwrapResponse(json);
|
|
3563
|
+
}
|
|
3564
|
+
/**
|
|
3565
|
+
* True only for a 4xx that means the BOC payload itself is invalid (400 Bad
|
|
3566
|
+
* Request, 413 Payload Too Large, 422 Unprocessable) — these must NOT fail over
|
|
3567
|
+
* (the next provider would reject the same BOC identically). Auth/availability
|
|
3568
|
+
* 4xx (401/403/404), 429, 5xx, timeouts and network errors are all transient
|
|
3569
|
+
* for an idempotent broadcast and DO fail over.
|
|
3570
|
+
*/
|
|
3571
|
+
isInvalidPayloadError(error) {
|
|
3572
|
+
const status = error?.status;
|
|
3573
|
+
return typeof status === "number" && (status === 400 || status === 413 || status === 422);
|
|
3484
3574
|
}
|
|
3485
3575
|
/**
|
|
3486
3576
|
* Check if contract is deployed
|