ton-provider-system 0.5.0 → 0.6.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 +110 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -1
- package/dist/index.d.ts +44 -1
- package/dist/index.js +110 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/rpc.json +8 -8
package/dist/index.cjs
CHANGED
|
@@ -3069,6 +3069,22 @@ var _ProviderManager = class _ProviderManager {
|
|
|
3069
3069
|
if (!this.initialized || !this.network) return [];
|
|
3070
3070
|
return this.selector.getAvailableProviders(this.network).filter((p) => p.servesGetTransactions !== false);
|
|
3071
3071
|
}
|
|
3072
|
+
/**
|
|
3073
|
+
* Get broadcast-capable providers for the current network, in selector score
|
|
3074
|
+
* order (best first).
|
|
3075
|
+
*
|
|
3076
|
+
* Unlike getTransactionCapableProviders, this applies NO capability filter:
|
|
3077
|
+
* `sendBoc` (forwarding an already-signed external message) works on EVERY
|
|
3078
|
+
* provider type — including the liteserver-proxy providers (Chainstack/Orbs)
|
|
3079
|
+
* that 403 only on the v2 `getTransactions` shape. The list is the plain
|
|
3080
|
+
* score-ordered available set, so a broadcast walks best→worst and a provider
|
|
3081
|
+
* that 429s/5xxs on sendBoc is skipped in favour of the next one.
|
|
3082
|
+
* Returns [] when no provider is currently selectable.
|
|
3083
|
+
*/
|
|
3084
|
+
getBroadcastCapableProviders() {
|
|
3085
|
+
if (!this.initialized || !this.network) return [];
|
|
3086
|
+
return this.selector.getAvailableProviders(this.network);
|
|
3087
|
+
}
|
|
3072
3088
|
/**
|
|
3073
3089
|
* Get provider health results for current network
|
|
3074
3090
|
*/
|
|
@@ -3457,30 +3473,106 @@ var NodeAdapter = class {
|
|
|
3457
3473
|
}
|
|
3458
3474
|
}
|
|
3459
3475
|
/**
|
|
3460
|
-
*
|
|
3476
|
+
* Broadcast an already-signed external message BOC, with provider failover.
|
|
3477
|
+
*
|
|
3478
|
+
* Unlike a single-endpoint `client.sendFile`/raw POST, this walks the network's
|
|
3479
|
+
* score-ordered providers (best first; see ProviderManager.getBroadcastCapableProviders)
|
|
3480
|
+
* and fails over to the next provider when one returns a TRANSIENT failure — a
|
|
3481
|
+
* 429 (rate limit), a 5xx (gateway/server error, e.g. the testnet chainstack
|
|
3482
|
+
* free plan 500ing on sendBoc), a timeout, or a network error. This closes the
|
|
3483
|
+
* broadcast half of the failover story: the getTransactions capability flag only
|
|
3484
|
+
* diverted reads, so a demoted-but-still-reachable provider could still 500 a
|
|
3485
|
+
* broadcast — now the broadcast itself moves on to a healthy provider.
|
|
3486
|
+
*
|
|
3487
|
+
* Safety: re-broadcasting is idempotent at the TON level — the external message
|
|
3488
|
+
* has a deterministic hash, so forwarding the SAME signed BOC to another provider
|
|
3489
|
+
* after a transient failure is safe (the network dedups; a duplicate is a no-op).
|
|
3490
|
+
* A 4xx that means "this BOC is invalid" (400/413/422) is therefore NOT failed
|
|
3491
|
+
* over: it is the payload, not the provider — surfaced immediately, without
|
|
3492
|
+
* poisoning the provider's health or spraying a bad BOC across the fleet.
|
|
3461
3493
|
*/
|
|
3462
3494
|
async sendBoc(boc, timeoutMs = 3e4) {
|
|
3463
|
-
const
|
|
3495
|
+
const network = this.manager.getNetwork();
|
|
3496
|
+
if (!network) {
|
|
3497
|
+
throw new Error("ProviderManager not initialized");
|
|
3498
|
+
}
|
|
3499
|
+
const bocBase64 = typeof boc === "string" ? boc : boc.toString("base64");
|
|
3500
|
+
const candidates = this.manager.getBroadcastCapableProviders();
|
|
3501
|
+
if (candidates.length === 0) {
|
|
3502
|
+
const endpoint = await this.manager.getEndpoint();
|
|
3503
|
+
try {
|
|
3504
|
+
await this.sendBocToEndpoint(endpoint, bocBase64, timeoutMs);
|
|
3505
|
+
this.manager.reportSuccess();
|
|
3506
|
+
} catch (error) {
|
|
3507
|
+
if (!this.isInvalidPayloadError(error)) {
|
|
3508
|
+
this.manager.reportError(error);
|
|
3509
|
+
}
|
|
3510
|
+
throw error;
|
|
3511
|
+
}
|
|
3512
|
+
return;
|
|
3513
|
+
}
|
|
3514
|
+
const rateLimiter = this.manager.getRateLimiter();
|
|
3515
|
+
let lastError;
|
|
3516
|
+
for (const provider of candidates) {
|
|
3517
|
+
if (rateLimiter) {
|
|
3518
|
+
await rateLimiter.acquire(provider.id, timeoutMs);
|
|
3519
|
+
}
|
|
3520
|
+
const endpoint = normalizeV2Endpoint(provider.endpointV2, provider);
|
|
3521
|
+
try {
|
|
3522
|
+
await this.sendBocToEndpoint(endpoint, bocBase64, timeoutMs);
|
|
3523
|
+
this.manager.reportSuccess(provider.id);
|
|
3524
|
+
return;
|
|
3525
|
+
} catch (error) {
|
|
3526
|
+
if (this.isInvalidPayloadError(error)) {
|
|
3527
|
+
throw error;
|
|
3528
|
+
}
|
|
3529
|
+
lastError = error;
|
|
3530
|
+
this.manager.reportError(error, provider.id);
|
|
3531
|
+
this.logger.warn(
|
|
3532
|
+
`sendBoc failed on ${provider.id}, failing over`,
|
|
3533
|
+
{ error: error?.message || String(error) }
|
|
3534
|
+
);
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
throw lastError || new Error(`sendBoc: all providers failed for ${network}`);
|
|
3538
|
+
}
|
|
3539
|
+
/**
|
|
3540
|
+
* POST a base64 BOC to one provider's REST `/sendBoc`. Throws on a non-2xx
|
|
3541
|
+
* response with the HTTP `status` attached (so the caller can classify
|
|
3542
|
+
* transient vs invalid-payload), and on an `ok:false` JSON body.
|
|
3543
|
+
*/
|
|
3544
|
+
async sendBocToEndpoint(endpoint, bocBase64, timeoutMs) {
|
|
3464
3545
|
const baseV2 = toV2Base(endpoint);
|
|
3465
3546
|
const url = `${baseV2}/sendBoc`;
|
|
3466
|
-
const
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
{
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3547
|
+
const response = await fetchWithTimeout(
|
|
3548
|
+
url,
|
|
3549
|
+
{
|
|
3550
|
+
method: "POST",
|
|
3551
|
+
headers: { "Content-Type": "application/json" },
|
|
3552
|
+
body: JSON.stringify({ boc: bocBase64 })
|
|
3553
|
+
},
|
|
3554
|
+
timeoutMs
|
|
3555
|
+
);
|
|
3556
|
+
if (!response.ok) {
|
|
3557
|
+
const err = new Error(
|
|
3558
|
+
`HTTP ${response.status} ${response.statusText} on sendBoc`
|
|
3476
3559
|
);
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
this.manager.reportSuccess();
|
|
3480
|
-
} catch (error) {
|
|
3481
|
-
this.manager.reportError(error);
|
|
3482
|
-
throw error;
|
|
3560
|
+
err.status = response.status;
|
|
3561
|
+
throw err;
|
|
3483
3562
|
}
|
|
3563
|
+
const json = await response.json();
|
|
3564
|
+
this.unwrapResponse(json);
|
|
3565
|
+
}
|
|
3566
|
+
/**
|
|
3567
|
+
* True only for a 4xx that means the BOC payload itself is invalid (400 Bad
|
|
3568
|
+
* Request, 413 Payload Too Large, 422 Unprocessable) — these must NOT fail over
|
|
3569
|
+
* (the next provider would reject the same BOC identically). Auth/availability
|
|
3570
|
+
* 4xx (401/403/404), 429, 5xx, timeouts and network errors are all transient
|
|
3571
|
+
* for an idempotent broadcast and DO fail over.
|
|
3572
|
+
*/
|
|
3573
|
+
isInvalidPayloadError(error) {
|
|
3574
|
+
const status = error?.status;
|
|
3575
|
+
return typeof status === "number" && (status === 400 || status === 413 || status === 422);
|
|
3484
3576
|
}
|
|
3485
3577
|
/**
|
|
3486
3578
|
* Check if contract is deployed
|