ton-provider-system 0.3.1 → 0.5.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 CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  var zod = require('zod');
4
4
  var ton = require('@ton/ton');
5
+ var core = require('@ton/core');
5
6
 
6
7
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
7
8
  // src/types.ts
@@ -71,6 +72,7 @@ var ProviderConfigSchema = zod.z.object({
71
72
  enabled: zod.z.boolean().default(true),
72
73
  isDynamic: zod.z.boolean().optional().default(false),
73
74
  browserCompatible: zod.z.boolean().optional(),
75
+ servesGetTransactions: zod.z.boolean().optional(),
74
76
  description: zod.z.string().optional()
75
77
  });
76
78
  var NetworkDefaultsSchema = zod.z.object({
@@ -219,7 +221,10 @@ function resolveProvider(id, config) {
219
221
  rps: config.rps,
220
222
  priority: config.priority,
221
223
  isDynamic: config.isDynamic || false,
222
- browserCompatible: config.browserCompatible !== void 0 ? config.browserCompatible : true
224
+ browserCompatible: config.browserCompatible !== void 0 ? config.browserCompatible : true,
225
+ // Default true: a provider is assumed to serve getTransactions unless the
226
+ // config explicitly opts out (e.g. Chainstack/Orbs testnet liteserver proxies).
227
+ servesGetTransactions: config.servesGetTransactions !== false
223
228
  };
224
229
  }
225
230
  function resolveAllProviders(config) {
@@ -2553,8 +2558,10 @@ var ProviderSelector = class {
2553
2558
  rps: 10,
2554
2559
  priority: 0,
2555
2560
  isDynamic: false,
2556
- browserCompatible: true
2561
+ browserCompatible: true,
2557
2562
  // Custom endpoints are assumed compatible
2563
+ servesGetTransactions: true
2564
+ // Custom endpoints are assumed fully capable
2558
2565
  };
2559
2566
  }
2560
2567
  /**
@@ -2885,26 +2892,39 @@ var _ProviderManager = class _ProviderManager {
2885
2892
  // ========================================================================
2886
2893
  /**
2887
2894
  * Report a successful request
2895
+ *
2896
+ * @param providerId - Optionally attribute the success to a SPECIFIC provider
2897
+ * (used by callers that drive their own candidate list, e.g.
2898
+ * NodeAdapter.getTransactions, where the provider used is not necessarily the
2899
+ * selector's current best). Defaults to the current best provider.
2888
2900
  */
2889
- reportSuccess() {
2901
+ reportSuccess(providerId) {
2890
2902
  if (!this.initialized || !this.network) return;
2891
- const provider = this.selector.getBestProvider(this.network);
2892
- if (provider) {
2893
- this.rateLimiter.reportSuccess(provider.id);
2903
+ const id = providerId ?? this.selector.getBestProvider(this.network)?.id;
2904
+ if (id) {
2905
+ this.rateLimiter.reportSuccess(id);
2894
2906
  }
2895
2907
  }
2896
2908
  /**
2897
2909
  * Report an error (triggers provider switch if needed)
2910
+ *
2911
+ * @param providerId - Optionally attribute the error to a SPECIFIC provider
2912
+ * (used by callers that drive their own candidate list, e.g.
2913
+ * NodeAdapter.getTransactions). Defaults to the current active/best provider.
2898
2914
  */
2899
- reportError(error) {
2915
+ reportError(error, providerId) {
2900
2916
  if (!this.initialized || !this.network) return;
2901
- const activeProviderId = this.selector.getActiveProviderId(this.network);
2902
2917
  let provider = null;
2903
- if (activeProviderId) {
2904
- provider = this.registry.getProvider(activeProviderId) || null;
2905
- }
2906
- if (!provider) {
2907
- provider = this.selector.getBestProvider(this.network);
2918
+ if (providerId) {
2919
+ provider = this.registry.getProvider(providerId) || null;
2920
+ } else {
2921
+ const activeProviderId = this.selector.getActiveProviderId(this.network);
2922
+ if (activeProviderId) {
2923
+ provider = this.registry.getProvider(activeProviderId) || null;
2924
+ }
2925
+ if (!provider) {
2926
+ provider = this.selector.getBestProvider(this.network);
2927
+ }
2908
2928
  }
2909
2929
  if (!provider) {
2910
2930
  this.options.logger.warn(`Cannot report error: no provider available for ${this.network}`);
@@ -3034,6 +3054,21 @@ var _ProviderManager = class _ProviderManager {
3034
3054
  }
3035
3055
  return providers;
3036
3056
  }
3057
+ /**
3058
+ * Get transaction-capable providers for the current network, in selector
3059
+ * score order (best first).
3060
+ *
3061
+ * Read-only: enumerates the score-ordered available providers and excludes any
3062
+ * flagged `servesGetTransactions: false` (Chainstack/Orbs testnet liteserver
3063
+ * proxies, which 403 on v2 getTransactions). Used by
3064
+ * NodeAdapter.getTransactions to build its candidate set WITHOUT poisoning the
3065
+ * incapable providers' global health — so the get-method path keeps using them.
3066
+ * Returns [] when no capable provider is currently selectable.
3067
+ */
3068
+ getTransactionCapableProviders() {
3069
+ if (!this.initialized || !this.network) return [];
3070
+ return this.selector.getAvailableProviders(this.network).filter((p) => p.servesGetTransactions !== false);
3071
+ }
3037
3072
  /**
3038
3073
  * Get provider health results for current network
3039
3074
  */
@@ -3249,6 +3284,76 @@ var NodeAdapter = class {
3249
3284
  };
3250
3285
  }
3251
3286
  // ========================================================================
3287
+ // Failover-aware high-level reads
3288
+ // ========================================================================
3289
+ /**
3290
+ * Get account transactions with capability-aware provider failover.
3291
+ *
3292
+ * Unlike `getClient().getTransactions(...)`, this builds a candidate set of the
3293
+ * network's *transaction-capable* providers (score-ordered, best first) and
3294
+ * loops over THAT set. Providers known not to serve the v2 `getTransactions`
3295
+ * shape — flagged `servesGetTransactions: false` in config (Chainstack/Orbs
3296
+ * testnet, which pass the `getMasterchainInfo` health probe but 403 on
3297
+ * transaction reads) — are excluded UP FRONT. They are never called and never
3298
+ * `reportError`-ed here, so their global health stays intact and the fast
3299
+ * get-method path keeps using them (a wasted 403 would otherwise evict them).
3300
+ *
3301
+ * Semantics (per capable candidate, in score order):
3302
+ * 1. Acquire a rate-limit token for THAT provider, bind a short-lived
3303
+ * `TonClient` to its endpoint, and call `getTransactions`.
3304
+ * 2. On success → `reportSuccess(provider.id)` and return.
3305
+ * 3. On a GENUINE error → `reportError(error, provider.id)` (marks THAT
3306
+ * provider success:false + clears the selection cache) and try the next
3307
+ * capable candidate.
3308
+ * 4. When every capable candidate has been tried, re-throw the last error so
3309
+ * the caller's retry/dead-letter logic still triggers.
3310
+ * 5. If there is NO capable provider at all, throw a clear error.
3311
+ *
3312
+ * Returns the same `Transaction[]` `TonClient.getTransactions` returns, so a
3313
+ * consumer can swap `client.getTransactions(addr, opts)` for
3314
+ * `adapter.getTransactions(addr, opts)` with no shape change.
3315
+ */
3316
+ async getTransactions(address, opts = {}, timeoutMs = 15e3) {
3317
+ const network = this.manager.getNetwork();
3318
+ if (!network) {
3319
+ throw new Error("ProviderManager not initialized");
3320
+ }
3321
+ const addr = typeof address === "string" ? core.Address.parse(address) : address;
3322
+ const reqOpts = { limit: opts.limit ?? 20, ...opts };
3323
+ const candidates = this.manager.getTransactionCapableProviders();
3324
+ if (candidates.length === 0) {
3325
+ throw new Error(
3326
+ `getTransactions: no transaction-capable provider for ${network}`
3327
+ );
3328
+ }
3329
+ const rateLimiter = this.manager.getRateLimiter();
3330
+ let lastError;
3331
+ for (const provider of candidates) {
3332
+ if (rateLimiter) {
3333
+ await rateLimiter.acquire(provider.id, timeoutMs);
3334
+ }
3335
+ const endpoint = normalizeV2Endpoint(provider.endpointV2, provider);
3336
+ const client = new ton.TonClient({ endpoint, apiKey: provider.apiKey });
3337
+ try {
3338
+ const result = await withTimeout(
3339
+ client.getTransactions(addr, reqOpts),
3340
+ timeoutMs,
3341
+ `getTransactions(${provider.id})`
3342
+ );
3343
+ this.manager.reportSuccess(provider.id);
3344
+ return result;
3345
+ } catch (error) {
3346
+ lastError = error;
3347
+ this.logger.warn(
3348
+ `getTransactions failed on ${provider.id}, failing over`,
3349
+ { error: error?.message || String(error) }
3350
+ );
3351
+ this.manager.reportError(error, provider.id);
3352
+ }
3353
+ }
3354
+ throw lastError || new Error(`getTransactions: all transaction-capable providers failed for ${network}`);
3355
+ }
3356
+ // ========================================================================
3252
3357
  // Direct REST API Methods
3253
3358
  // ========================================================================
3254
3359
  /**