ton-provider-system 0.4.0 → 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
@@ -72,6 +72,7 @@ var ProviderConfigSchema = zod.z.object({
72
72
  enabled: zod.z.boolean().default(true),
73
73
  isDynamic: zod.z.boolean().optional().default(false),
74
74
  browserCompatible: zod.z.boolean().optional(),
75
+ servesGetTransactions: zod.z.boolean().optional(),
75
76
  description: zod.z.string().optional()
76
77
  });
77
78
  var NetworkDefaultsSchema = zod.z.object({
@@ -220,7 +221,10 @@ function resolveProvider(id, config) {
220
221
  rps: config.rps,
221
222
  priority: config.priority,
222
223
  isDynamic: config.isDynamic || false,
223
- 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
224
228
  };
225
229
  }
226
230
  function resolveAllProviders(config) {
@@ -2554,8 +2558,10 @@ var ProviderSelector = class {
2554
2558
  rps: 10,
2555
2559
  priority: 0,
2556
2560
  isDynamic: false,
2557
- browserCompatible: true
2561
+ browserCompatible: true,
2558
2562
  // Custom endpoints are assumed compatible
2563
+ servesGetTransactions: true
2564
+ // Custom endpoints are assumed fully capable
2559
2565
  };
2560
2566
  }
2561
2567
  /**
@@ -2886,26 +2892,39 @@ var _ProviderManager = class _ProviderManager {
2886
2892
  // ========================================================================
2887
2893
  /**
2888
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.
2889
2900
  */
2890
- reportSuccess() {
2901
+ reportSuccess(providerId) {
2891
2902
  if (!this.initialized || !this.network) return;
2892
- const provider = this.selector.getBestProvider(this.network);
2893
- if (provider) {
2894
- this.rateLimiter.reportSuccess(provider.id);
2903
+ const id = providerId ?? this.selector.getBestProvider(this.network)?.id;
2904
+ if (id) {
2905
+ this.rateLimiter.reportSuccess(id);
2895
2906
  }
2896
2907
  }
2897
2908
  /**
2898
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.
2899
2914
  */
2900
- reportError(error) {
2915
+ reportError(error, providerId) {
2901
2916
  if (!this.initialized || !this.network) return;
2902
- const activeProviderId = this.selector.getActiveProviderId(this.network);
2903
2917
  let provider = null;
2904
- if (activeProviderId) {
2905
- provider = this.registry.getProvider(activeProviderId) || null;
2906
- }
2907
- if (!provider) {
2908
- 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
+ }
2909
2928
  }
2910
2929
  if (!provider) {
2911
2930
  this.options.logger.warn(`Cannot report error: no provider available for ${this.network}`);
@@ -3035,6 +3054,21 @@ var _ProviderManager = class _ProviderManager {
3035
3054
  }
3036
3055
  return providers;
3037
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
+ }
3038
3072
  /**
3039
3073
  * Get provider health results for current network
3040
3074
  */
@@ -3253,24 +3287,27 @@ var NodeAdapter = class {
3253
3287
  // Failover-aware high-level reads
3254
3288
  // ========================================================================
3255
3289
  /**
3256
- * Get account transactions with provider failover.
3290
+ * Get account transactions with capability-aware provider failover.
3257
3291
  *
3258
- * Unlike `getClient().getTransactions(...)`, this loops across the network's
3259
- * providers: a provider that passes the `getMasterchainInfo` health probe but
3260
- * fails the v2 `getTransactions` call (e.g. Chainstack/Orbs testnet, which
3261
- * 403 on transaction reads) is no longer able to permanently break this path.
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).
3262
3300
  *
3263
- * Semantics:
3264
- * 1. Pick the current best provider (the selector's scored top).
3265
- * 2. Acquire a rate-limit token for THAT provider, bind a short-lived
3301
+ * Semantics (per capable candidate, in score order):
3302
+ * 1. Acquire a rate-limit token for THAT provider, bind a short-lived
3266
3303
  * `TonClient` to its endpoint, and call `getTransactions`.
3267
- * 3. On success → `reportSuccess()` and return.
3268
- * 4. On error → `reportError()` (marks the provider success:false, scoring 0
3269
- * within its cooldown, and clears the selection cache) so the next pick is
3270
- * a DIFFERENT, still-eligible provider, then retry.
3271
- * 5. When every candidate has been tried, re-throw the last error so the
3272
- * caller's retry/dead-letter logic still triggers — but only after a
3273
- * genuine failover across all providers.
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.
3274
3311
  *
3275
3312
  * Returns the same `Transaction[]` `TonClient.getTransactions` returns, so a
3276
3313
  * consumer can swap `client.getTransactions(addr, opts)` for
@@ -3283,28 +3320,27 @@ var NodeAdapter = class {
3283
3320
  }
3284
3321
  const addr = typeof address === "string" ? core.Address.parse(address) : address;
3285
3322
  const reqOpts = { limit: opts.limit ?? 20, ...opts };
3286
- const maxAttempts = Math.max(1, this.manager.getProviders().length);
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
+ }
3287
3329
  const rateLimiter = this.manager.getRateLimiter();
3288
- const tried = /* @__PURE__ */ new Set();
3289
3330
  let lastError;
3290
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
3291
- const provider = this.manager.getActiveProvider();
3292
- if (!provider) break;
3293
- if (tried.has(provider.id)) break;
3294
- tried.add(provider.id);
3331
+ for (const provider of candidates) {
3295
3332
  if (rateLimiter) {
3296
3333
  await rateLimiter.acquire(provider.id, timeoutMs);
3297
3334
  }
3298
- const endpoint = await this.manager.getEndpoint();
3299
- const apiKey = this.manager.getActiveProvider()?.apiKey;
3300
- const client = new ton.TonClient({ endpoint, apiKey });
3335
+ const endpoint = normalizeV2Endpoint(provider.endpointV2, provider);
3336
+ const client = new ton.TonClient({ endpoint, apiKey: provider.apiKey });
3301
3337
  try {
3302
3338
  const result = await withTimeout(
3303
3339
  client.getTransactions(addr, reqOpts),
3304
3340
  timeoutMs,
3305
3341
  `getTransactions(${provider.id})`
3306
3342
  );
3307
- this.manager.reportSuccess();
3343
+ this.manager.reportSuccess(provider.id);
3308
3344
  return result;
3309
3345
  } catch (error) {
3310
3346
  lastError = error;
@@ -3312,10 +3348,10 @@ var NodeAdapter = class {
3312
3348
  `getTransactions failed on ${provider.id}, failing over`,
3313
3349
  { error: error?.message || String(error) }
3314
3350
  );
3315
- this.manager.reportError(error);
3351
+ this.manager.reportError(error, provider.id);
3316
3352
  }
3317
3353
  }
3318
- throw lastError || new Error(`getTransactions: no available provider for ${network}`);
3354
+ throw lastError || new Error(`getTransactions: all transaction-capable providers failed for ${network}`);
3319
3355
  }
3320
3356
  // ========================================================================
3321
3357
  // Direct REST API Methods