ethers-rpc-pool 1.0.7 → 1.1.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/README.md CHANGED
@@ -15,6 +15,31 @@ Designed for production backends and dApps that need:
15
15
 
16
16
  ---
17
17
 
18
+ ## Table of Contents
19
+
20
+ - [Why ethers-rpc-pool](#why-ethers-rpc-pool)
21
+ - [Features](#features)
22
+ - [Requirements](#requirements)
23
+ - [Installation](#installation)
24
+ - [Quick Start](#quick-start)
25
+ - [Configuration](#configuration)
26
+ - - [Interfaces](#interfaces)
27
+ - - [RPCPoolProvider Options](#rpcpoolprovider-options)
28
+ - - [JsonRpcProvider Options](#jsonrpcprovider-options)
29
+ - [How It Works](#how-it-works)
30
+ - - [Routing](#1-routing)
31
+ - - [Concurrency Control](#2-concurrency-control)
32
+ - - [Rate Limiting](#3-rate-limiting)
33
+ - - [Retry Strategy](#4-retry-strategy)
34
+ - [Instrumentation & Metrics](#instrumentation--metrics)
35
+ - [Production Considerations](#production-considerations)
36
+ - - [Recommended Settings](#recommended-settings)
37
+ - - [Known Limitations](#known-limitations)
38
+ - [When To Use](#when-to-use)
39
+ - [Example Architecture](#example-architecture)
40
+ - [Roadmap](#roadmap)
41
+ - [License](#license)
42
+
18
43
  ## Why ethers-rpc-pool?
19
44
 
20
45
  Most production apps rely on a single RPC provider. This creates:
@@ -67,13 +92,18 @@ import { RPCPoolProvider } from 'ethers-rpc-pool';
67
92
 
68
93
  const poolProvider = new RPCPoolProvider({
69
94
  chainId: 1,
70
- urls: ['http://rpc1.invalid', 'http://rpc2.invalid'],
71
- perUrl: { inFlight: 1, timeout: 3000, rps: 2, rpsBurst: 5 },
72
- retry: { attempts: 2 },
95
+ rpc: [
96
+ { url: 'https://eth.drpc.org' },
97
+ { url: 'https://eth1.lava.build' },
98
+ { url: 'https://rpc.mevblocker.io' },
99
+ { url: 'https://eth.blockrazor.xyz' },
100
+ { url: 'https://public-eth.nownodes.io' },
101
+ ],
102
+ defaultRpcOptions: { inFlight: 1, timeout: 3000, rps: 2, rpsBurst: 5 },
103
+ retry: { attempts: 3 },
73
104
  });
74
105
 
75
106
  // Use it like a regular `JsonRpcProvider`:
76
-
77
107
  const blockNumber = await poolProvider.getBlockNumber();
78
108
  const balance = await poolProvider.getBalance('0x...');
79
109
  ```
@@ -82,15 +112,37 @@ const balance = await poolProvider.getBalance('0x...');
82
112
 
83
113
  ## Configuration
84
114
 
85
- ### RPCPoolProviderParams
115
+ ### Interfaces
86
116
 
87
117
  ```ts
88
- interface RPCPoolProviderParams {
89
- chainId: number;
90
- urls: string[];
91
- perUrl: {
92
- inFlight: number;
93
- };
118
+ interface RPCParameters {
119
+ inFlight?: number;
120
+ timeout?: number;
121
+ rps?: number;
122
+ rpsBurst?: number;
123
+
124
+ // Optional instrumentation hook for provider-level events
125
+ stats?: Stats;
126
+ onEvent?: (e: RpcEvent) => void;
127
+ providerId: string;
128
+
129
+ // Optional JsonRpcProvider options for compatibility
130
+ // https://docs.ethers.org/v6/api/providers/jsonrpc/#JsonRpcApiProviderOptions
131
+ batchStallTime?: number;
132
+ batchMaxSize?: number;
133
+ batchMaxCount?: number;
134
+ staticNetwork?: null | boolean | Network;
135
+ polling?: boolean;
136
+ cacheTimeout?: number;
137
+ pollingInterval?: number;
138
+ }
139
+ ```
140
+
141
+ ```ts
142
+ interface PoolProviderParameters {
143
+ network: number;
144
+ rpc: RpcProviderOptions[];
145
+ defaultRpcOptions?: RpcProviderOptions;
94
146
  retry: {
95
147
  attempts: number;
96
148
  };
@@ -100,18 +152,25 @@ interface RPCPoolProviderParams {
100
152
  }
101
153
  ```
102
154
 
103
- ### Options Explained
155
+ ### RPCPoolProvider Options
104
156
 
105
- | Option | Description |
106
- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
107
- | `chainId` | Target chain ID |
108
- | `urls` | List of RPC endpoints |
109
- | `perUrl.inFlight` | Max concurrent requests per endpoint |
110
- | `perUrl.timeout` | Timeout in ms for each request to this URL, default 10s |
111
- | `perUrl.rps` | Maximum number of requests per second allowed for a single RPC endpoint. Enforced using a token bucket rate limiter. |
112
- | `perUrl.rpsBurst` | aximum burst capacity for the rate limiter. Allows short spikes above the sustained rate by accumulating tokens during idle periods. |
113
- | `retry.attempts` | Maximum number of unique endpoints to try |
114
- | `hooks.onEvent` | Optional instrumentation hook |
157
+ | Option | Description |
158
+ | ------------------- | ----------------------------------------- |
159
+ | `network` | Target chain ID |
160
+ | `rpc` | List of RPC endpoints |
161
+ | `retry.attempts` | Maximum number of unique endpoints to try |
162
+ | `defaultRpcOptions` | Default options for all RPC endpoints |
163
+ | `hooks.onEvent` | Optional instrumentation hook |
164
+
165
+ ### JsonRpcProvider Options
166
+
167
+ | Option | Description |
168
+ | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
169
+ | `inFlight` | Max concurrent requests per endpoint |
170
+ | `timeout` | Timeout in ms for each request to this URL, default 10s |
171
+ | `rps` | Maximum number of requests per second allowed for a single RPC endpoint. Enforced using a token bucket rate limiter. |
172
+ | `rpsBurst` | Maximum burst capacity for the rate limiter. Allows short spikes above the sustained rate by accumulating tokens during idle periods. |
173
+ | ... | Also allows customization of [ethers.JsonRpcApiProviderOptions](https://docs.ethers.org/v6/api/providers/jsonrpc/#JsonRpcApiProviderOptions) |
115
174
 
116
175
  ---
117
176
 
@@ -125,10 +184,8 @@ Requests are routed through an internal `Router`, which selects an available end
125
184
 
126
185
  Each endpoint has its own semaphore limiter:
127
186
 
128
- ```ts
129
- perUrl: {
130
- inFlight: number;
131
- }
187
+ ```
188
+ inFlight: number;
132
189
  ```
133
190
 
134
191
  This prevents:
@@ -137,7 +194,34 @@ This prevents:
137
194
  - Triggering provider-side throttling
138
195
  - Self-induced retry storms
139
196
 
140
- ### 3. Retry Strategy
197
+ ### 3. Rate Limiting
198
+
199
+ Each RPC endpoint uses a token bucket rate limiter to control request throughput.
200
+
201
+ ```
202
+ rps: number;
203
+ rpsBurst: number;
204
+ ```
205
+
206
+ Where:
207
+
208
+ - `rps` defines the sustained request rate
209
+ - `rpsBurst` defines how many requests may temporarily exceed that rate (**maximum burst capacity**)
210
+
211
+ This helps:
212
+
213
+ - Prevent 429 rate limit errors
214
+ - Smooth traffic spikes
215
+ - Protect RPC providers
216
+ - Improve overall system stability
217
+
218
+ Unused capacity accumulates as tokens and may be consumed during short traffic bursts.
219
+
220
+ ### 4. Retry Strategy
221
+
222
+ ```
223
+ retry.attempts: number
224
+ ```
141
225
 
142
226
  If a retryable error occurs:
143
227
 
@@ -237,13 +321,12 @@ Useful for:
237
321
 
238
322
  - `inFlight`: 1–2 depending on rpc provider limits
239
323
  - `retry.attempts`: 2–3
240
- - Use at least 23 independent RPC providers
324
+ - Use at least 35 independent RPC providers
241
325
 
242
326
  ### Known Limitations
243
327
 
244
328
  - Basic circuit breaker/cooldown
245
329
  - No sticky session/blockTag consistency yet
246
- - No built-in JSON-RPC batching
247
330
  - Archive/debug/trace methods depend on underlying RPC support
248
331
 
249
332
  ---
@@ -287,7 +370,6 @@ Not intended for:
287
370
  - Circuit breaker + health scoring
288
371
  - Sticky session / blockTag consistency
289
372
  - Adaptive latency-based routing
290
- - JSON-RPC batch support
291
373
  - Singleflight request deduplication
292
374
 
293
375
  ---
package/dist/index.cjs CHANGED
@@ -230,29 +230,33 @@ var RpsLimiter = class {
230
230
  };
231
231
 
232
232
  // src/InstrumentedProvider.ts
233
- var InstrumentedStaticJsonRpcProvider = class extends import_ethers.JsonRpcProvider {
233
+ var InstrumentedJsonRpcProvider = class extends import_ethers.JsonRpcProvider {
234
234
  providerId;
235
235
  chainId;
236
- params;
236
+ options;
237
237
  inFlightLimiter;
238
238
  rpsLimiter;
239
239
  stats;
240
240
  fetchRequest;
241
241
  lastCooldownMs = 0;
242
- constructor(params) {
243
- const { url, providerId, chainId } = params;
244
- const network = import_ethers.Network.from(chainId);
245
- const fetchRequest = new import_ethers.FetchRequest(url);
246
- fetchRequest.timeout = params.timeout || 1e4;
247
- super(fetchRequest, chainId, { staticNetwork: network, batchMaxCount: 1 });
242
+ constructor(url, network, options) {
243
+ let fetchRequest;
244
+ if (typeof url == "string") {
245
+ fetchRequest = new import_ethers.FetchRequest(url);
246
+ } else {
247
+ fetchRequest = url;
248
+ }
249
+ fetchRequest.timeout = options.timeout || 1e4;
250
+ const _network = import_ethers.Network.from(network);
251
+ super(fetchRequest, _network, { staticNetwork: true, ...options });
248
252
  this.fetchRequest = fetchRequest;
249
- this.providerId = providerId;
250
- this.chainId = chainId;
251
- this.params = params;
252
- const { rps = 10, rpsBurst, inFlight = 1 } = params;
253
+ this.providerId = options.providerId;
254
+ this.chainId = _network.chainId;
255
+ this.options = options;
256
+ const { rps = 10, rpsBurst, inFlight = 1 } = options;
253
257
  this.inFlightLimiter = new Semaphore(inFlight);
254
258
  this.rpsLimiter = new RpsLimiter(rps, rpsBurst || rps);
255
- this.stats = params.stats;
259
+ this.stats = options.stats;
256
260
  }
257
261
  async _send(payload) {
258
262
  await this.rpsLimiter.take(1);
@@ -272,7 +276,7 @@ var InstrumentedStaticJsonRpcProvider = class extends import_ethers.JsonRpcProvi
272
276
  this.stats.bumpProviderTotal(this.providerId);
273
277
  for (const p of payloads) {
274
278
  this.stats.bumpPerMethod(p.method);
275
- this.params.onEvent?.({
279
+ this.options.onEvent?.({
276
280
  type: "request",
277
281
  chainId: this.chainId,
278
282
  providerId: this.providerId,
@@ -284,7 +288,7 @@ var InstrumentedStaticJsonRpcProvider = class extends import_ethers.JsonRpcProvi
284
288
  const res = await super._send(payload);
285
289
  const endedAt = Date.now();
286
290
  for (const p of payloads) {
287
- this.params.onEvent?.({
291
+ this.options.onEvent?.({
288
292
  type: "response",
289
293
  chainId: this.chainId,
290
294
  providerId: this.providerId,
@@ -325,7 +329,7 @@ var InstrumentedStaticJsonRpcProvider = class extends import_ethers.JsonRpcProvi
325
329
  this.stats.setCooldown(this.providerId, cooldownMs);
326
330
  }
327
331
  for (const p of payloads) {
328
- this.params.onEvent?.({
332
+ this.options.onEvent?.({
329
333
  type: "error",
330
334
  chainId: this.chainId,
331
335
  providerId: this.providerId,
@@ -374,22 +378,18 @@ var RPCPoolProvider = class extends import_ethers2.JsonRpcProvider {
374
378
  params;
375
379
  stats;
376
380
  constructor(params) {
377
- const network = import_ethers2.Network.from(params.chainId);
381
+ const network = import_ethers2.Network.from(params.network);
378
382
  super("http://localhost", network, { staticNetwork: network });
379
383
  this.params = params;
380
384
  this.stats = new Stats();
381
- const endpoints = this.params.urls.map((url, i) => {
382
- const providerId = `rpc#${i + 1}-chainId:${this.params.chainId}-${url}`;
383
- const { inFlight = 1, rps = 1, rpsBurst, timeout = 1e4 } = this.params.perUrl;
384
- const provider = new InstrumentedStaticJsonRpcProvider({
385
- url,
386
- chainId: this.params.chainId,
385
+ const endpoints = this.params.rpc.map((options, i) => {
386
+ const url = typeof options.url === "string" ? options.url : options.url.url;
387
+ const providerId = `rpc#${i + 1}-chainId:${this.params.network}-${url}`;
388
+ const provider = new InstrumentedJsonRpcProvider(options.url, this.params.network, {
387
389
  providerId,
388
390
  stats: this.stats,
389
- inFlight,
390
- rps,
391
- rpsBurst,
392
- timeout,
391
+ ...this.params.defaultRpcOptions,
392
+ ...options,
393
393
  onEvent: this.params.hooks?.onEvent
394
394
  });
395
395
  return { providerId, url, provider };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/RpcPoolProvider.ts","../src/Stats.ts","../src/utils.ts","../src/InstrumentedProvider.ts","../src/Semaphore.ts","../src/RpsLimiter.ts","../src/Router.ts"],"sourcesContent":["export { RPCPoolProvider, RPCPoolProviderParams } from './RpcPoolProvider';\nexport type { RpcEvent } from './utils';\nexport type { RpcStatsSnapshot } from './Stats';\n","import { JsonRpcProvider, Network } from 'ethers';\nimport { Stats } from './Stats';\nimport { Endpoint, RpcEvent, shouldFailover } from './utils';\nimport { InstrumentedStaticJsonRpcProvider } from './InstrumentedProvider';\nimport { Router } from './Router';\n\nexport interface RPCPoolProviderParams {\n chainId: number;\n urls: string[];\n perUrl: { inFlight: number; timeout?: number; rps?: number; rpsBurst?: number };\n retry: { attempts: number };\n hooks?: {\n onEvent(e: RpcEvent): void;\n };\n}\n\n// TODO\n// -- circuit breaker + health checks\n// -- sticky “session”\n\nexport class RPCPoolProvider extends JsonRpcProvider {\n readonly router: Router;\n readonly params: RPCPoolProviderParams;\n readonly stats: Stats;\n\n constructor(params: RPCPoolProviderParams) {\n const network = Network.from(params.chainId);\n super('http://localhost', network, { staticNetwork: network });\n\n this.params = params;\n\n this.stats = new Stats();\n\n const endpoints: Endpoint[] = this.params.urls.map((url, i) => {\n const providerId = `rpc#${i + 1}-chainId:${this.params.chainId}-${url}`;\n\n const { inFlight = 1, rps = 1, rpsBurst, timeout = 10_000 } = this.params.perUrl;\n\n const provider = new InstrumentedStaticJsonRpcProvider({\n url,\n chainId: this.params.chainId,\n providerId,\n stats: this.stats,\n inFlight,\n rps,\n rpsBurst,\n timeout,\n onEvent: this.params.hooks?.onEvent,\n });\n\n return { providerId, url, provider };\n });\n\n this.router = new Router(endpoints, this.stats);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async send(method: string, params: any): Promise<any> {\n const tried = new Set<string>();\n const maxUniqueTries = Math.min(this.params.retry.attempts, this.router.size());\n\n while (tried.size < maxUniqueTries) {\n const ep = this.router.pick();\n if (tried.has(ep.providerId)) continue;\n tried.add(ep.providerId);\n\n try {\n return await ep.provider.send(method, params);\n } catch (e: any) {\n if (!shouldFailover(e)) throw e;\n if (tried.size >= maxUniqueTries) throw e;\n\n // Add exponential backoff with jitter before retry\n const baseDelay = Math.min(1000 * Math.pow(2, tried.size - 1), 5000);\n const jitter = Math.random() * baseDelay;\n await new Promise((resolve) => setTimeout(resolve, jitter));\n }\n }\n\n throw new Error('No RPC available');\n }\n\n getStats(): Stats {\n return this.stats;\n }\n}\n","export interface RpcStatsSnapshot {\n total: number;\n inFlight: number;\n perMethodTotal: Record<string, number>;\n rateLimitedTotal: number;\n perProviderRateLimited: Record<string, number>;\n timeoutTotal: number;\n perProviderTimeout: Record<string, number>;\n perProviderTotal: Record<string, number>;\n providerCooldownUntil: Record<string, number>;\n perProviderInFlight: Record<string, number>;\n perProviderError: Record<string, number>;\n}\n\nexport class Stats {\n private _total = 0;\n private _inFlight = 0;\n\n private _perMethod: Record<string, number> = {};\n\n private _rateLimitedTotal = 0;\n private _timeoutTotal = 0;\n\n private _perProviderInFlight: Record<string, number> = {};\n private _perProviderTotal: Record<string, number> = {};\n private _perProviderTimeout: Record<string, number> = {};\n private _perProviderRateLimited: Record<string, number> = {};\n private _perProviderError: Record<string, number> = {};\n\n private _providerCooldownUntil: Record<string, number> = {};\n\n private _bump(map: Record<string, number>, key: string) {\n map[key] = (map[key] || 0) + 1;\n }\n\n private _decrease(map: Record<string, number>, key: string) {\n map[key] = Math.max((map[key] || 0) - 1, 0);\n }\n\n private _bumpTotal() {\n this._total++;\n }\n\n private _bumpInFlight() {\n this._inFlight++;\n }\n\n private _bumpRateLimitedTotal() {\n this._rateLimitedTotal++;\n }\n\n private _bumpTimeoutTotal() {\n this._timeoutTotal++;\n }\n\n bumpInFlightPerProvider(id: string) {\n this._bumpInFlight();\n this._bump(this._perProviderInFlight, id);\n }\n\n decreaseInFlightPerProvider(id: string) {\n this.decreaseInFlight();\n this._decrease(this._perProviderInFlight, id);\n }\n\n decreaseInFlight() {\n this._inFlight = Math.max(this._inFlight - 1, 0);\n }\n\n bumpPerMethod(method: string) {\n this._bump(this._perMethod, method);\n }\n\n bumpRateLimitedPerProvider(id: string) {\n this._bumpRateLimitedTotal();\n this._bump(this._perProviderRateLimited, id);\n }\n\n bumpTimeoutPerProvider(id: string) {\n this._bumpTimeoutTotal();\n this._bump(this._perProviderTimeout, id);\n }\n\n bumpProviderTotal(id: string) {\n this._bumpTotal();\n this._perProviderTotal[id] = (this._perProviderTotal[id] || 0) + 1;\n }\n bumpServerErrorPerProvider(id: string) {\n this._bump(this._perProviderError, id);\n }\n\n timeoutRatio(id: string) {\n const t = this._perProviderTimeout[id] || 0;\n const n = this._perProviderTotal[id] || 0;\n return n ? t / n : 0;\n }\n\n isInCooldown(id: string) {\n return (this._providerCooldownUntil[id] || 0) > Date.now();\n }\n\n setCooldown(id: string, ms: number) {\n this._providerCooldownUntil[id] = Date.now() + ms;\n }\n\n snapshot(): Readonly<RpcStatsSnapshot> {\n return {\n total: this._total,\n inFlight: this._inFlight,\n perMethodTotal: { ...this._perMethod },\n rateLimitedTotal: this._rateLimitedTotal,\n timeoutTotal: this._timeoutTotal,\n perProviderInFlight: { ...this._perProviderInFlight },\n perProviderRateLimited: { ...this._perProviderRateLimited },\n perProviderTimeout: { ...this._perProviderTimeout },\n perProviderError: { ...this._perProviderError },\n perProviderTotal: { ...this._perProviderTotal },\n providerCooldownUntil: { ...this._providerCooldownUntil },\n };\n }\n}\n","import { InstrumentedStaticJsonRpcProvider } from './InstrumentedProvider';\n\nexport interface Endpoint {\n providerId: string;\n url: string;\n provider: InstrumentedStaticJsonRpcProvider;\n}\n\nexport type RpcEvent =\n | {\n type: 'request';\n chainId: number;\n providerId: string;\n method: string;\n startedAt: number;\n }\n | {\n type: 'response';\n chainId: number;\n providerId: string;\n method: string;\n startedAt: number;\n endedAt: number;\n ms: number;\n }\n | {\n type: 'error';\n chainId: number;\n providerId: string;\n method: string;\n startedAt: number;\n endedAt: number;\n ms: number;\n isRateLimit: boolean;\n isTimeout: boolean;\n status?: number;\n code?: string;\n message: string;\n };\n\nexport function getHttpStatus(e: any): number | undefined {\n return (\n e?.status ??\n e?.response?.status ??\n e?.response?.statusCode ??\n e?.error?.status ??\n e?.error?.response?.status ??\n e?.body?.statusCode // sometimes present\n );\n}\n\nexport function isRateLimitError(e: any): boolean {\n const status = getHttpStatus(e);\n if (status === 429 || status === 402) return true;\n\n const msg = String(e?.message || e);\n if (/error code:\\s*1015/i.test(msg)) return true; // Cloudflare\n return /rate limit|too many requests|429|quota|throttl/i.test(msg);\n}\n\nexport function isServerError(e: any): boolean {\n const status = getHttpStatus(e);\n return status !== undefined && status >= 500;\n}\n\nexport function getRetryAfterMs(e: any): number | null {\n const ra =\n e?.response?.headers?.get?.('retry-after') ??\n e?.response?.headers?.['retry-after'] ??\n e?.headers?.['retry-after'];\n const n = Number(ra);\n return Number.isFinite(n) ? n * 1000 : null;\n}\n\nexport function isTimeoutError(e: any): boolean {\n // ethers v5\n if (e?.code === 'TIMEOUT') return true;\n\n const status = getHttpStatus(e);\n // some RPCs / proxies return 504 on timeout\n if (status === 504) return true;\n\n const msg = String(e?.message || e);\n\n // node-fetch / undici / axios / nginx / generic\n return /timeout|timed out|ETIMEDOUT|ESOCKETTIMEDOUT|ECONNABORTED|504 Gateway/i.test(msg);\n}\n\nexport function shouldFailover(e: any): boolean {\n const to = isTimeoutError(e);\n const rl = isRateLimitError(e);\n const se = isServerError(e);\n\n // failover on timeouts and rate limits, but not on logical errors (e.g. invalid params)\n return to || rl || se;\n}\n","import { JsonRpcProvider, Network, JsonRpcPayload, FetchRequest } from 'ethers';\nimport { Semaphore } from './Semaphore';\nimport { Stats } from './Stats';\nimport {\n getHttpStatus,\n getRetryAfterMs,\n isRateLimitError,\n isServerError,\n isTimeoutError,\n RpcEvent,\n} from './utils';\nimport { RpsLimiter } from './RpsLimiter';\n\ninterface ProviderParams {\n url: string;\n chainId: number;\n providerId: string;\n stats: Stats;\n inFlight?: number;\n timeout?: number;\n rps?: number;\n rpsBurst?: number;\n onEvent?: (e: RpcEvent) => void;\n}\n/**\n * Instrumented StaticJsonRpcProvider.\n * Tracks requests, inFlight count, rate limits, and per-method / per-provider metrics.\n */\nexport class InstrumentedStaticJsonRpcProvider extends JsonRpcProvider {\n readonly providerId: string;\n readonly chainId: number;\n readonly params: ProviderParams;\n\n readonly inFlightLimiter: Semaphore;\n readonly rpsLimiter: RpsLimiter;\n readonly stats: Stats;\n readonly fetchRequest: FetchRequest;\n\n private lastCooldownMs: number = 0;\n\n constructor(params: ProviderParams) {\n const { url, providerId, chainId } = params;\n\n const network = Network.from(chainId);\n const fetchRequest = new FetchRequest(url);\n\n fetchRequest.timeout = params.timeout || 10_000;\n\n super(fetchRequest, chainId, { staticNetwork: network, batchMaxCount: 1 });\n this.fetchRequest = fetchRequest;\n this.providerId = providerId;\n this.chainId = chainId;\n this.params = params;\n\n const { rps = 10, rpsBurst, inFlight = 1 } = params;\n\n this.inFlightLimiter = new Semaphore(inFlight);\n this.rpsLimiter = new RpsLimiter(rps, rpsBurst || rps);\n this.stats = params.stats;\n }\n\n override async _send(payload: JsonRpcPayload | JsonRpcPayload[]): Promise<any> {\n await this.rpsLimiter.take(1);\n\n const release = this.inFlightLimiter ? await this.inFlightLimiter.acquire() : undefined;\n\n try {\n return await this._sendInstrumented(payload);\n } finally {\n release?.();\n }\n }\n\n // ethers v5 calls send(method, params)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private async _sendInstrumented(payload: JsonRpcPayload | JsonRpcPayload[]): Promise<any> {\n const startedAt = Date.now();\n const payloads = Array.isArray(payload) ? payload : [payload];\n\n this.stats.bumpInFlightPerProvider(this.providerId);\n this.stats.bumpProviderTotal(this.providerId);\n\n for (const p of payloads) {\n this.stats.bumpPerMethod(p.method);\n this.params.onEvent?.({\n type: 'request',\n chainId: this.chainId,\n providerId: this.providerId,\n method: p.method,\n startedAt,\n });\n }\n\n try {\n const res = await super._send(payload);\n\n const endedAt = Date.now();\n\n for (const p of payloads) {\n this.params.onEvent?.({\n type: 'response',\n chainId: this.chainId,\n providerId: this.providerId,\n method: p.method,\n startedAt,\n endedAt,\n ms: endedAt - startedAt,\n });\n }\n\n this.lastCooldownMs = 0;\n\n return res;\n } catch (e: any) {\n const endedAt = Date.now();\n const rl = isRateLimitError(e);\n if (rl) {\n this.stats.bumpRateLimitedPerProvider(this.providerId);\n const cooldownMs = 10_000;\n const raMs = getRetryAfterMs(e) ?? cooldownMs;\n this.stats.setCooldown(this.providerId, raMs);\n }\n\n const isTimeout = isTimeoutError(e);\n if (isTimeout && !rl) {\n this.stats.bumpTimeoutPerProvider(this.providerId);\n\n const n = this.stats.snapshot().perProviderTotal[this.providerId] || 0;\n const ratio = this.stats.timeoutRatio(this.providerId);\n\n // thresholds: do not ban on a single timeout, only after enough data\n if (n >= 50 && ratio >= 0.2) {\n const cooldownMs = ratio >= 0.5 ? 600_000 : 60_000;\n const raMs = getRetryAfterMs(e) ?? cooldownMs;\n this.lastCooldownMs = raMs;\n this.stats.setCooldown(this.providerId, raMs + Math.floor(Math.random() * 1000));\n }\n }\n\n const isError = isServerError(e);\n if (isError && !rl && !isTimeout) {\n this.stats.bumpServerErrorPerProvider(this.providerId);\n const cooldownMs = (this.lastCooldownMs * 2 || 10_000) + Math.floor(Math.random() * 1000);\n this.lastCooldownMs = cooldownMs;\n this.stats.setCooldown(this.providerId, cooldownMs);\n }\n\n for (const p of payloads) {\n this.params.onEvent?.({\n type: 'error',\n chainId: this.chainId,\n providerId: this.providerId,\n method: p.method,\n startedAt,\n endedAt,\n ms: endedAt - startedAt,\n isRateLimit: rl,\n isTimeout: isTimeout,\n status: getHttpStatus(e),\n code: e?.code,\n message: String(e?.message || e),\n });\n }\n\n throw e;\n } finally {\n this.stats.decreaseInFlightPerProvider(this.providerId);\n }\n }\n}\n","export class Semaphore {\n private inUse = 0;\n private queue: Array<() => void> = [];\n\n constructor(private readonly max: number) {\n if (!Number.isFinite(max) || max <= 0) {\n throw new Error(`Semaphore max must be a positive number, got: ${max}`);\n }\n }\n\n async acquire(): Promise<() => void> {\n if (this.inUse < this.max) {\n this.inUse++;\n let released = false;\n return () => {\n if (released) return;\n released = true;\n this.release();\n };\n }\n\n return new Promise<() => void>((resolve) => {\n this.queue.push(() => {\n this.inUse++;\n let released = false;\n resolve(() => {\n if (released) return;\n released = true;\n this.release();\n });\n });\n });\n }\n\n private release() {\n this.inUse = Math.max(0, this.inUse - 1);\n const next = this.queue.shift();\n if (next) next();\n }\n}\n","export class RpsLimiter {\n // Current number of tokens in the bucket (can be fractional)\n private tokens: number;\n\n // Time of last token refill, in ms\n private lastRefill = Date.now();\n\n constructor(\n // rps: how many tokens we add per second\n private readonly rps: number,\n // burst: maximum bucket capacity.\n // Default: >=1 and approximately equal to rps (to allow a small burst)\n private readonly burst: number = Math.max(1, Math.ceil(rps)),\n ) {\n // At start, the bucket is full: can make burst requests immediately\n this.tokens = burst;\n }\n\n // Refill tokens according to elapsed time\n private refill(now: number) {\n // rps<=0 means \"limit is disabled\"\n if (this.rps <= 0) return;\n\n const elapsed = now - this.lastRefill; // ms since last refill\n if (elapsed <= 0) return;\n\n // How many tokens to add:\n // elapsed/1000 = seconds, multiply by rps\n const add = (elapsed / 1000) * this.rps;\n\n // Add tokens, but don't exceed burst (bucket capacity)\n this.tokens = Math.min(this.burst, this.tokens + add);\n\n // Remember that we refilled tokens at time now\n this.lastRefill = now;\n }\n\n // Take count tokens (usually 1 request = 1 token).\n // If not enough tokens — wait and try again.\n async take(count = 1): Promise<void> {\n if (!this.rps || this.rps <= 0) return;\n\n while (true) {\n const now = Date.now();\n\n // Before attempting — refill tokens\n this.refill(now);\n\n // If enough tokens — \"pay\" for the request and exit\n if (this.tokens >= count) {\n this.tokens -= count;\n return;\n }\n\n // Not enough tokens: calculate how long to wait\n const need = count - this.tokens;\n\n // How many ms needed to accumulate need tokens:\n // need / rps = seconds, *1000 = ms\n const waitMs = Math.ceil((need / this.rps) * 1000);\n\n // Wait in chunks (not all waitMs at once), to:\n // - not sleep too long if time/state changed\n // - be more resilient to timer drift\n await new Promise((r) => setTimeout(r, Math.min(waitMs, 50)));\n }\n }\n}\n","import { Endpoint } from './utils';\nimport { Stats } from './Stats';\n\nexport class Router {\n private rr = 0;\n\n constructor(\n private readonly endpoints: Endpoint[],\n private readonly stats: Stats,\n ) {}\n\n size(): number {\n return this.endpoints.length;\n }\n\n pick(): Endpoint {\n const n = this.endpoints.length;\n for (let k = 0; k < n; k++) {\n const i = ((this.rr++ % n) + n) % n;\n const ep = this.endpoints[i];\n\n if (!this.stats.isInCooldown(ep.providerId)) return ep;\n }\n // if all are in cooldown, return the next one in round-robin order\n return this.endpoints[((this.rr++ % n) + n) % n];\n }\n}\n"],"mappings":"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","names":["import_ethers"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/RpcPoolProvider.ts","../src/Stats.ts","../src/utils.ts","../src/InstrumentedProvider.ts","../src/Semaphore.ts","../src/RpsLimiter.ts","../src/Router.ts"],"sourcesContent":["export { RPCPoolProvider, RPCPoolProviderParams } from './RpcPoolProvider';\nexport type { RpcEvent } from './utils';\nexport type { RpcStatsSnapshot } from './Stats';\n","import { FetchRequest, JsonRpcProvider, Network, Networkish } from 'ethers';\nimport { Stats } from './Stats';\nimport { Endpoint, RpcEvent, shouldFailover } from './utils';\nimport {\n InstrumentedJsonRpcProvider,\n InstrumentedJsonRpcProviderOptions,\n} from './InstrumentedProvider';\nimport { Router } from './Router';\n\ninterface RPCPoolProviderOptions extends Partial<InstrumentedJsonRpcProviderOptions> {\n url: string | FetchRequest;\n network?: Networkish;\n}\n\nexport interface RPCPoolProviderParams {\n network: Networkish;\n rpc: RPCPoolProviderOptions[];\n defaultRpcOptions: { inFlight: number; timeout?: number; rps?: number; rpsBurst?: number };\n retry: { attempts: number };\n hooks?: {\n onEvent(e: RpcEvent): void;\n };\n}\n\n// TODO\n// -- circuit breaker + health checks\n// -- sticky “session”\n\nexport class RPCPoolProvider extends JsonRpcProvider {\n readonly router: Router;\n readonly params: RPCPoolProviderParams;\n readonly stats: Stats;\n\n constructor(params: RPCPoolProviderParams) {\n const network = Network.from(params.network);\n super('http://localhost', network, { staticNetwork: network });\n\n this.params = params;\n\n this.stats = new Stats();\n\n const endpoints: Endpoint[] = this.params.rpc.map((options, i) => {\n const url = typeof options.url === 'string' ? options.url : options.url.url;\n const providerId = `rpc#${i + 1}-chainId:${this.params.network}-${url}`;\n\n const provider = new InstrumentedJsonRpcProvider(options.url, this.params.network, {\n providerId,\n stats: this.stats,\n ...this.params.defaultRpcOptions,\n ...options,\n onEvent: this.params.hooks?.onEvent,\n });\n\n return { providerId, url, provider };\n });\n\n this.router = new Router(endpoints, this.stats);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async send(method: string, params: any): Promise<any> {\n const tried = new Set<string>();\n const maxUniqueTries = Math.min(this.params.retry.attempts, this.router.size());\n\n while (tried.size < maxUniqueTries) {\n const ep = this.router.pick();\n if (tried.has(ep.providerId)) continue;\n tried.add(ep.providerId);\n\n try {\n return await ep.provider.send(method, params);\n } catch (e: any) {\n if (!shouldFailover(e)) throw e;\n if (tried.size >= maxUniqueTries) throw e;\n\n // Add exponential backoff with jitter before retry\n const baseDelay = Math.min(1000 * Math.pow(2, tried.size - 1), 5000);\n const jitter = Math.random() * baseDelay;\n await new Promise((resolve) => setTimeout(resolve, jitter));\n }\n }\n\n throw new Error('No RPC available');\n }\n\n getStats(): Stats {\n return this.stats;\n }\n}\n","export interface RpcStatsSnapshot {\n total: number;\n inFlight: number;\n perMethodTotal: Record<string, number>;\n rateLimitedTotal: number;\n perProviderRateLimited: Record<string, number>;\n timeoutTotal: number;\n perProviderTimeout: Record<string, number>;\n perProviderTotal: Record<string, number>;\n providerCooldownUntil: Record<string, number>;\n perProviderInFlight: Record<string, number>;\n perProviderError: Record<string, number>;\n}\n\nexport class Stats {\n private _total = 0;\n private _inFlight = 0;\n\n private _perMethod: Record<string, number> = {};\n\n private _rateLimitedTotal = 0;\n private _timeoutTotal = 0;\n\n private _perProviderInFlight: Record<string, number> = {};\n private _perProviderTotal: Record<string, number> = {};\n private _perProviderTimeout: Record<string, number> = {};\n private _perProviderRateLimited: Record<string, number> = {};\n private _perProviderError: Record<string, number> = {};\n\n private _providerCooldownUntil: Record<string, number> = {};\n\n private _bump(map: Record<string, number>, key: string) {\n map[key] = (map[key] || 0) + 1;\n }\n\n private _decrease(map: Record<string, number>, key: string) {\n map[key] = Math.max((map[key] || 0) - 1, 0);\n }\n\n private _bumpTotal() {\n this._total++;\n }\n\n private _bumpInFlight() {\n this._inFlight++;\n }\n\n private _bumpRateLimitedTotal() {\n this._rateLimitedTotal++;\n }\n\n private _bumpTimeoutTotal() {\n this._timeoutTotal++;\n }\n\n bumpInFlightPerProvider(id: string) {\n this._bumpInFlight();\n this._bump(this._perProviderInFlight, id);\n }\n\n decreaseInFlightPerProvider(id: string) {\n this.decreaseInFlight();\n this._decrease(this._perProviderInFlight, id);\n }\n\n decreaseInFlight() {\n this._inFlight = Math.max(this._inFlight - 1, 0);\n }\n\n bumpPerMethod(method: string) {\n this._bump(this._perMethod, method);\n }\n\n bumpRateLimitedPerProvider(id: string) {\n this._bumpRateLimitedTotal();\n this._bump(this._perProviderRateLimited, id);\n }\n\n bumpTimeoutPerProvider(id: string) {\n this._bumpTimeoutTotal();\n this._bump(this._perProviderTimeout, id);\n }\n\n bumpProviderTotal(id: string) {\n this._bumpTotal();\n this._perProviderTotal[id] = (this._perProviderTotal[id] || 0) + 1;\n }\n bumpServerErrorPerProvider(id: string) {\n this._bump(this._perProviderError, id);\n }\n\n timeoutRatio(id: string) {\n const t = this._perProviderTimeout[id] || 0;\n const n = this._perProviderTotal[id] || 0;\n return n ? t / n : 0;\n }\n\n isInCooldown(id: string) {\n return (this._providerCooldownUntil[id] || 0) > Date.now();\n }\n\n setCooldown(id: string, ms: number) {\n this._providerCooldownUntil[id] = Date.now() + ms;\n }\n\n snapshot(): Readonly<RpcStatsSnapshot> {\n return {\n total: this._total,\n inFlight: this._inFlight,\n perMethodTotal: { ...this._perMethod },\n rateLimitedTotal: this._rateLimitedTotal,\n timeoutTotal: this._timeoutTotal,\n perProviderInFlight: { ...this._perProviderInFlight },\n perProviderRateLimited: { ...this._perProviderRateLimited },\n perProviderTimeout: { ...this._perProviderTimeout },\n perProviderError: { ...this._perProviderError },\n perProviderTotal: { ...this._perProviderTotal },\n providerCooldownUntil: { ...this._providerCooldownUntil },\n };\n }\n}\n","import { InstrumentedJsonRpcProvider } from './InstrumentedProvider';\n\nexport interface Endpoint {\n providerId: string;\n url: string;\n provider: InstrumentedJsonRpcProvider;\n}\n\nexport type RpcEvent =\n | {\n type: 'request';\n chainId: bigint;\n providerId: string;\n method: string;\n startedAt: number;\n }\n | {\n type: 'response';\n chainId: bigint;\n providerId: string;\n method: string;\n startedAt: number;\n endedAt: number;\n ms: number;\n }\n | {\n type: 'error';\n chainId: bigint;\n providerId: string;\n method: string;\n startedAt: number;\n endedAt: number;\n ms: number;\n isRateLimit: boolean;\n isTimeout: boolean;\n status?: number;\n code?: string;\n message: string;\n };\n\nexport function getHttpStatus(e: any): number | undefined {\n return (\n e?.status ??\n e?.response?.status ??\n e?.response?.statusCode ??\n e?.error?.status ??\n e?.error?.response?.status ??\n e?.body?.statusCode // sometimes present\n );\n}\n\nexport function isRateLimitError(e: any): boolean {\n const status = getHttpStatus(e);\n if (status === 429 || status === 402) return true;\n\n const msg = String(e?.message || e);\n if (/error code:\\s*1015/i.test(msg)) return true; // Cloudflare\n return /rate limit|too many requests|429|quota|throttl/i.test(msg);\n}\n\nexport function isServerError(e: any): boolean {\n const status = getHttpStatus(e);\n return status !== undefined && status >= 500;\n}\n\nexport function getRetryAfterMs(e: any): number | null {\n const ra =\n e?.response?.headers?.get?.('retry-after') ??\n e?.response?.headers?.['retry-after'] ??\n e?.headers?.['retry-after'];\n const n = Number(ra);\n return Number.isFinite(n) ? n * 1000 : null;\n}\n\nexport function isTimeoutError(e: any): boolean {\n // ethers v5\n if (e?.code === 'TIMEOUT') return true;\n\n const status = getHttpStatus(e);\n // some RPCs / proxies return 504 on timeout\n if (status === 504) return true;\n\n const msg = String(e?.message || e);\n\n // node-fetch / undici / axios / nginx / generic\n return /timeout|timed out|ETIMEDOUT|ESOCKETTIMEDOUT|ECONNABORTED|504 Gateway/i.test(msg);\n}\n\nexport function shouldFailover(e: any): boolean {\n const to = isTimeoutError(e);\n const rl = isRateLimitError(e);\n const se = isServerError(e);\n\n // failover on timeouts and rate limits, but not on logical errors (e.g. invalid params)\n return to || rl || se;\n}\n","import {\n JsonRpcProvider,\n Network,\n JsonRpcPayload,\n FetchRequest,\n JsonRpcApiProviderOptions,\n Networkish,\n} from 'ethers';\nimport { Semaphore } from './Semaphore';\nimport { Stats } from './Stats';\nimport {\n getHttpStatus,\n getRetryAfterMs,\n isRateLimitError,\n isServerError,\n isTimeoutError,\n RpcEvent,\n} from './utils';\nimport { RpsLimiter } from './RpsLimiter';\n\nexport interface InstrumentedJsonRpcProviderOptions extends JsonRpcApiProviderOptions {\n providerId: string;\n stats: Stats;\n inFlight?: number;\n timeout?: number;\n rps?: number;\n rpsBurst?: number;\n onEvent?: (e: RpcEvent) => void;\n}\n/**\n * Instrumented JsonRpcProvider.\n * Tracks requests, inFlight count, rate limits, and per-method / per-provider metrics.\n */\nexport class InstrumentedJsonRpcProvider extends JsonRpcProvider {\n readonly providerId: string;\n readonly chainId: bigint;\n readonly options: InstrumentedJsonRpcProviderOptions;\n\n readonly inFlightLimiter: Semaphore;\n readonly rpsLimiter: RpsLimiter;\n readonly stats: Stats;\n readonly fetchRequest: FetchRequest;\n\n private lastCooldownMs: number = 0;\n\n constructor(\n url: string | FetchRequest,\n network: Networkish,\n options: InstrumentedJsonRpcProviderOptions,\n ) {\n let fetchRequest: FetchRequest;\n\n if (typeof url == 'string') {\n fetchRequest = new FetchRequest(url);\n } else {\n fetchRequest = url;\n }\n\n fetchRequest.timeout = options.timeout || 10_000;\n\n const _network = Network.from(network);\n super(fetchRequest, _network, { staticNetwork: true, ...options });\n this.fetchRequest = fetchRequest;\n this.providerId = options.providerId;\n this.chainId = _network.chainId;\n this.options = options;\n\n const { rps = 10, rpsBurst, inFlight = 1 } = options;\n\n this.inFlightLimiter = new Semaphore(inFlight);\n this.rpsLimiter = new RpsLimiter(rps, rpsBurst || rps);\n this.stats = options.stats;\n }\n\n override async _send(payload: JsonRpcPayload | JsonRpcPayload[]): Promise<any> {\n await this.rpsLimiter.take(1);\n\n const release = this.inFlightLimiter ? await this.inFlightLimiter.acquire() : undefined;\n\n try {\n return await this._sendInstrumented(payload);\n } finally {\n release?.();\n }\n }\n\n // ethers v5 calls send(method, params)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private async _sendInstrumented(payload: JsonRpcPayload | JsonRpcPayload[]): Promise<any> {\n const startedAt = Date.now();\n const payloads = Array.isArray(payload) ? payload : [payload];\n\n this.stats.bumpInFlightPerProvider(this.providerId);\n this.stats.bumpProviderTotal(this.providerId);\n\n for (const p of payloads) {\n this.stats.bumpPerMethod(p.method);\n this.options.onEvent?.({\n type: 'request',\n chainId: this.chainId,\n providerId: this.providerId,\n method: p.method,\n startedAt,\n });\n }\n\n try {\n const res = await super._send(payload);\n\n const endedAt = Date.now();\n\n for (const p of payloads) {\n this.options.onEvent?.({\n type: 'response',\n chainId: this.chainId,\n providerId: this.providerId,\n method: p.method,\n startedAt,\n endedAt,\n ms: endedAt - startedAt,\n });\n }\n\n this.lastCooldownMs = 0;\n\n return res;\n } catch (e: any) {\n const endedAt = Date.now();\n const rl = isRateLimitError(e);\n if (rl) {\n this.stats.bumpRateLimitedPerProvider(this.providerId);\n const cooldownMs = 10_000;\n const raMs = getRetryAfterMs(e) ?? cooldownMs;\n this.stats.setCooldown(this.providerId, raMs);\n }\n\n const isTimeout = isTimeoutError(e);\n if (isTimeout && !rl) {\n this.stats.bumpTimeoutPerProvider(this.providerId);\n\n const n = this.stats.snapshot().perProviderTotal[this.providerId] || 0;\n const ratio = this.stats.timeoutRatio(this.providerId);\n\n // thresholds: do not ban on a single timeout, only after enough data\n if (n >= 50 && ratio >= 0.2) {\n const cooldownMs = ratio >= 0.5 ? 600_000 : 60_000;\n const raMs = getRetryAfterMs(e) ?? cooldownMs;\n this.lastCooldownMs = raMs;\n this.stats.setCooldown(this.providerId, raMs + Math.floor(Math.random() * 1000));\n }\n }\n\n const isError = isServerError(e);\n if (isError && !rl && !isTimeout) {\n this.stats.bumpServerErrorPerProvider(this.providerId);\n const cooldownMs = (this.lastCooldownMs * 2 || 10_000) + Math.floor(Math.random() * 1000);\n this.lastCooldownMs = cooldownMs;\n this.stats.setCooldown(this.providerId, cooldownMs);\n }\n\n for (const p of payloads) {\n this.options.onEvent?.({\n type: 'error',\n chainId: this.chainId,\n providerId: this.providerId,\n method: p.method,\n startedAt,\n endedAt,\n ms: endedAt - startedAt,\n isRateLimit: rl,\n isTimeout: isTimeout,\n status: getHttpStatus(e),\n code: e?.code,\n message: String(e?.message || e),\n });\n }\n\n throw e;\n } finally {\n this.stats.decreaseInFlightPerProvider(this.providerId);\n }\n }\n}\n","export class Semaphore {\n private inUse = 0;\n private queue: Array<() => void> = [];\n\n constructor(private readonly max: number) {\n if (!Number.isFinite(max) || max <= 0) {\n throw new Error(`Semaphore max must be a positive number, got: ${max}`);\n }\n }\n\n async acquire(): Promise<() => void> {\n if (this.inUse < this.max) {\n this.inUse++;\n let released = false;\n return () => {\n if (released) return;\n released = true;\n this.release();\n };\n }\n\n return new Promise<() => void>((resolve) => {\n this.queue.push(() => {\n this.inUse++;\n let released = false;\n resolve(() => {\n if (released) return;\n released = true;\n this.release();\n });\n });\n });\n }\n\n private release() {\n this.inUse = Math.max(0, this.inUse - 1);\n const next = this.queue.shift();\n if (next) next();\n }\n}\n","export class RpsLimiter {\n // Current number of tokens in the bucket (can be fractional)\n private tokens: number;\n\n // Time of last token refill, in ms\n private lastRefill = Date.now();\n\n constructor(\n // rps: how many tokens we add per second\n private readonly rps: number,\n // burst: maximum bucket capacity.\n // Default: >=1 and approximately equal to rps (to allow a small burst)\n private readonly burst: number = Math.max(1, Math.ceil(rps)),\n ) {\n // At start, the bucket is full: can make burst requests immediately\n this.tokens = burst;\n }\n\n // Refill tokens according to elapsed time\n private refill(now: number) {\n // rps<=0 means \"limit is disabled\"\n if (this.rps <= 0) return;\n\n const elapsed = now - this.lastRefill; // ms since last refill\n if (elapsed <= 0) return;\n\n // How many tokens to add:\n // elapsed/1000 = seconds, multiply by rps\n const add = (elapsed / 1000) * this.rps;\n\n // Add tokens, but don't exceed burst (bucket capacity)\n this.tokens = Math.min(this.burst, this.tokens + add);\n\n // Remember that we refilled tokens at time now\n this.lastRefill = now;\n }\n\n // Take count tokens (usually 1 request = 1 token).\n // If not enough tokens — wait and try again.\n async take(count = 1): Promise<void> {\n if (!this.rps || this.rps <= 0) return;\n\n while (true) {\n const now = Date.now();\n\n // Before attempting — refill tokens\n this.refill(now);\n\n // If enough tokens — \"pay\" for the request and exit\n if (this.tokens >= count) {\n this.tokens -= count;\n return;\n }\n\n // Not enough tokens: calculate how long to wait\n const need = count - this.tokens;\n\n // How many ms needed to accumulate need tokens:\n // need / rps = seconds, *1000 = ms\n const waitMs = Math.ceil((need / this.rps) * 1000);\n\n // Wait in chunks (not all waitMs at once), to:\n // - not sleep too long if time/state changed\n // - be more resilient to timer drift\n await new Promise((r) => setTimeout(r, Math.min(waitMs, 50)));\n }\n }\n}\n","import { Endpoint } from './utils';\nimport { Stats } from './Stats';\n\nexport class Router {\n private rr = 0;\n\n constructor(\n private readonly endpoints: Endpoint[],\n private readonly stats: Stats,\n ) {}\n\n size(): number {\n return this.endpoints.length;\n }\n\n pick(): Endpoint {\n const n = this.endpoints.length;\n for (let k = 0; k < n; k++) {\n const i = ((this.rr++ % n) + n) % n;\n const ep = this.endpoints[i];\n\n if (!this.stats.isInCooldown(ep.providerId)) return ep;\n }\n // if all are in cooldown, return the next one in round-robin order\n return this.endpoints[((this.rr++ % n) + n) % n];\n }\n}\n"],"mappings":"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","names":["import_ethers"]}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { JsonRpcProvider, FetchRequest, JsonRpcPayload } from 'ethers';
1
+ import { JsonRpcProvider, JsonRpcApiProviderOptions, FetchRequest, Networkish, JsonRpcPayload } from 'ethers';
2
2
 
3
3
  interface RpcStatsSnapshot {
4
4
  total: number;
@@ -64,9 +64,7 @@ declare class RpsLimiter {
64
64
  take(count?: number): Promise<void>;
65
65
  }
66
66
 
67
- interface ProviderParams {
68
- url: string;
69
- chainId: number;
67
+ interface InstrumentedJsonRpcProviderOptions extends JsonRpcApiProviderOptions {
70
68
  providerId: string;
71
69
  stats: Stats;
72
70
  inFlight?: number;
@@ -76,19 +74,19 @@ interface ProviderParams {
76
74
  onEvent?: (e: RpcEvent) => void;
77
75
  }
78
76
  /**
79
- * Instrumented StaticJsonRpcProvider.
77
+ * Instrumented JsonRpcProvider.
80
78
  * Tracks requests, inFlight count, rate limits, and per-method / per-provider metrics.
81
79
  */
82
- declare class InstrumentedStaticJsonRpcProvider extends JsonRpcProvider {
80
+ declare class InstrumentedJsonRpcProvider extends JsonRpcProvider {
83
81
  readonly providerId: string;
84
- readonly chainId: number;
85
- readonly params: ProviderParams;
82
+ readonly chainId: bigint;
83
+ readonly options: InstrumentedJsonRpcProviderOptions;
86
84
  readonly inFlightLimiter: Semaphore;
87
85
  readonly rpsLimiter: RpsLimiter;
88
86
  readonly stats: Stats;
89
87
  readonly fetchRequest: FetchRequest;
90
88
  private lastCooldownMs;
91
- constructor(params: ProviderParams);
89
+ constructor(url: string | FetchRequest, network: Networkish, options: InstrumentedJsonRpcProviderOptions);
92
90
  _send(payload: JsonRpcPayload | JsonRpcPayload[]): Promise<any>;
93
91
  private _sendInstrumented;
94
92
  }
@@ -96,17 +94,17 @@ declare class InstrumentedStaticJsonRpcProvider extends JsonRpcProvider {
96
94
  interface Endpoint {
97
95
  providerId: string;
98
96
  url: string;
99
- provider: InstrumentedStaticJsonRpcProvider;
97
+ provider: InstrumentedJsonRpcProvider;
100
98
  }
101
99
  type RpcEvent = {
102
100
  type: 'request';
103
- chainId: number;
101
+ chainId: bigint;
104
102
  providerId: string;
105
103
  method: string;
106
104
  startedAt: number;
107
105
  } | {
108
106
  type: 'response';
109
- chainId: number;
107
+ chainId: bigint;
110
108
  providerId: string;
111
109
  method: string;
112
110
  startedAt: number;
@@ -114,7 +112,7 @@ type RpcEvent = {
114
112
  ms: number;
115
113
  } | {
116
114
  type: 'error';
117
- chainId: number;
115
+ chainId: bigint;
118
116
  providerId: string;
119
117
  method: string;
120
118
  startedAt: number;
@@ -136,10 +134,14 @@ declare class Router {
136
134
  pick(): Endpoint;
137
135
  }
138
136
 
137
+ interface RPCPoolProviderOptions extends Partial<InstrumentedJsonRpcProviderOptions> {
138
+ url: string | FetchRequest;
139
+ network?: Networkish;
140
+ }
139
141
  interface RPCPoolProviderParams {
140
- chainId: number;
141
- urls: string[];
142
- perUrl: {
142
+ network: Networkish;
143
+ rpc: RPCPoolProviderOptions[];
144
+ defaultRpcOptions: {
143
145
  inFlight: number;
144
146
  timeout?: number;
145
147
  rps?: number;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { JsonRpcProvider, FetchRequest, JsonRpcPayload } from 'ethers';
1
+ import { JsonRpcProvider, JsonRpcApiProviderOptions, FetchRequest, Networkish, JsonRpcPayload } from 'ethers';
2
2
 
3
3
  interface RpcStatsSnapshot {
4
4
  total: number;
@@ -64,9 +64,7 @@ declare class RpsLimiter {
64
64
  take(count?: number): Promise<void>;
65
65
  }
66
66
 
67
- interface ProviderParams {
68
- url: string;
69
- chainId: number;
67
+ interface InstrumentedJsonRpcProviderOptions extends JsonRpcApiProviderOptions {
70
68
  providerId: string;
71
69
  stats: Stats;
72
70
  inFlight?: number;
@@ -76,19 +74,19 @@ interface ProviderParams {
76
74
  onEvent?: (e: RpcEvent) => void;
77
75
  }
78
76
  /**
79
- * Instrumented StaticJsonRpcProvider.
77
+ * Instrumented JsonRpcProvider.
80
78
  * Tracks requests, inFlight count, rate limits, and per-method / per-provider metrics.
81
79
  */
82
- declare class InstrumentedStaticJsonRpcProvider extends JsonRpcProvider {
80
+ declare class InstrumentedJsonRpcProvider extends JsonRpcProvider {
83
81
  readonly providerId: string;
84
- readonly chainId: number;
85
- readonly params: ProviderParams;
82
+ readonly chainId: bigint;
83
+ readonly options: InstrumentedJsonRpcProviderOptions;
86
84
  readonly inFlightLimiter: Semaphore;
87
85
  readonly rpsLimiter: RpsLimiter;
88
86
  readonly stats: Stats;
89
87
  readonly fetchRequest: FetchRequest;
90
88
  private lastCooldownMs;
91
- constructor(params: ProviderParams);
89
+ constructor(url: string | FetchRequest, network: Networkish, options: InstrumentedJsonRpcProviderOptions);
92
90
  _send(payload: JsonRpcPayload | JsonRpcPayload[]): Promise<any>;
93
91
  private _sendInstrumented;
94
92
  }
@@ -96,17 +94,17 @@ declare class InstrumentedStaticJsonRpcProvider extends JsonRpcProvider {
96
94
  interface Endpoint {
97
95
  providerId: string;
98
96
  url: string;
99
- provider: InstrumentedStaticJsonRpcProvider;
97
+ provider: InstrumentedJsonRpcProvider;
100
98
  }
101
99
  type RpcEvent = {
102
100
  type: 'request';
103
- chainId: number;
101
+ chainId: bigint;
104
102
  providerId: string;
105
103
  method: string;
106
104
  startedAt: number;
107
105
  } | {
108
106
  type: 'response';
109
- chainId: number;
107
+ chainId: bigint;
110
108
  providerId: string;
111
109
  method: string;
112
110
  startedAt: number;
@@ -114,7 +112,7 @@ type RpcEvent = {
114
112
  ms: number;
115
113
  } | {
116
114
  type: 'error';
117
- chainId: number;
115
+ chainId: bigint;
118
116
  providerId: string;
119
117
  method: string;
120
118
  startedAt: number;
@@ -136,10 +134,14 @@ declare class Router {
136
134
  pick(): Endpoint;
137
135
  }
138
136
 
137
+ interface RPCPoolProviderOptions extends Partial<InstrumentedJsonRpcProviderOptions> {
138
+ url: string | FetchRequest;
139
+ network?: Networkish;
140
+ }
139
141
  interface RPCPoolProviderParams {
140
- chainId: number;
141
- urls: string[];
142
- perUrl: {
142
+ network: Networkish;
143
+ rpc: RPCPoolProviderOptions[];
144
+ defaultRpcOptions: {
143
145
  inFlight: number;
144
146
  timeout?: number;
145
147
  rps?: number;
package/dist/index.js CHANGED
@@ -124,7 +124,11 @@ function shouldFailover(e) {
124
124
  }
125
125
 
126
126
  // src/InstrumentedProvider.ts
127
- import { JsonRpcProvider, Network, FetchRequest } from "ethers";
127
+ import {
128
+ JsonRpcProvider,
129
+ Network,
130
+ FetchRequest
131
+ } from "ethers";
128
132
 
129
133
  // src/Semaphore.ts
130
134
  var Semaphore = class {
@@ -204,29 +208,33 @@ var RpsLimiter = class {
204
208
  };
205
209
 
206
210
  // src/InstrumentedProvider.ts
207
- var InstrumentedStaticJsonRpcProvider = class extends JsonRpcProvider {
211
+ var InstrumentedJsonRpcProvider = class extends JsonRpcProvider {
208
212
  providerId;
209
213
  chainId;
210
- params;
214
+ options;
211
215
  inFlightLimiter;
212
216
  rpsLimiter;
213
217
  stats;
214
218
  fetchRequest;
215
219
  lastCooldownMs = 0;
216
- constructor(params) {
217
- const { url, providerId, chainId } = params;
218
- const network = Network.from(chainId);
219
- const fetchRequest = new FetchRequest(url);
220
- fetchRequest.timeout = params.timeout || 1e4;
221
- super(fetchRequest, chainId, { staticNetwork: network, batchMaxCount: 1 });
220
+ constructor(url, network, options) {
221
+ let fetchRequest;
222
+ if (typeof url == "string") {
223
+ fetchRequest = new FetchRequest(url);
224
+ } else {
225
+ fetchRequest = url;
226
+ }
227
+ fetchRequest.timeout = options.timeout || 1e4;
228
+ const _network = Network.from(network);
229
+ super(fetchRequest, _network, { staticNetwork: true, ...options });
222
230
  this.fetchRequest = fetchRequest;
223
- this.providerId = providerId;
224
- this.chainId = chainId;
225
- this.params = params;
226
- const { rps = 10, rpsBurst, inFlight = 1 } = params;
231
+ this.providerId = options.providerId;
232
+ this.chainId = _network.chainId;
233
+ this.options = options;
234
+ const { rps = 10, rpsBurst, inFlight = 1 } = options;
227
235
  this.inFlightLimiter = new Semaphore(inFlight);
228
236
  this.rpsLimiter = new RpsLimiter(rps, rpsBurst || rps);
229
- this.stats = params.stats;
237
+ this.stats = options.stats;
230
238
  }
231
239
  async _send(payload) {
232
240
  await this.rpsLimiter.take(1);
@@ -246,7 +254,7 @@ var InstrumentedStaticJsonRpcProvider = class extends JsonRpcProvider {
246
254
  this.stats.bumpProviderTotal(this.providerId);
247
255
  for (const p of payloads) {
248
256
  this.stats.bumpPerMethod(p.method);
249
- this.params.onEvent?.({
257
+ this.options.onEvent?.({
250
258
  type: "request",
251
259
  chainId: this.chainId,
252
260
  providerId: this.providerId,
@@ -258,7 +266,7 @@ var InstrumentedStaticJsonRpcProvider = class extends JsonRpcProvider {
258
266
  const res = await super._send(payload);
259
267
  const endedAt = Date.now();
260
268
  for (const p of payloads) {
261
- this.params.onEvent?.({
269
+ this.options.onEvent?.({
262
270
  type: "response",
263
271
  chainId: this.chainId,
264
272
  providerId: this.providerId,
@@ -299,7 +307,7 @@ var InstrumentedStaticJsonRpcProvider = class extends JsonRpcProvider {
299
307
  this.stats.setCooldown(this.providerId, cooldownMs);
300
308
  }
301
309
  for (const p of payloads) {
302
- this.params.onEvent?.({
310
+ this.options.onEvent?.({
303
311
  type: "error",
304
312
  chainId: this.chainId,
305
313
  providerId: this.providerId,
@@ -348,22 +356,18 @@ var RPCPoolProvider = class extends JsonRpcProvider2 {
348
356
  params;
349
357
  stats;
350
358
  constructor(params) {
351
- const network = Network2.from(params.chainId);
359
+ const network = Network2.from(params.network);
352
360
  super("http://localhost", network, { staticNetwork: network });
353
361
  this.params = params;
354
362
  this.stats = new Stats();
355
- const endpoints = this.params.urls.map((url, i) => {
356
- const providerId = `rpc#${i + 1}-chainId:${this.params.chainId}-${url}`;
357
- const { inFlight = 1, rps = 1, rpsBurst, timeout = 1e4 } = this.params.perUrl;
358
- const provider = new InstrumentedStaticJsonRpcProvider({
359
- url,
360
- chainId: this.params.chainId,
363
+ const endpoints = this.params.rpc.map((options, i) => {
364
+ const url = typeof options.url === "string" ? options.url : options.url.url;
365
+ const providerId = `rpc#${i + 1}-chainId:${this.params.network}-${url}`;
366
+ const provider = new InstrumentedJsonRpcProvider(options.url, this.params.network, {
361
367
  providerId,
362
368
  stats: this.stats,
363
- inFlight,
364
- rps,
365
- rpsBurst,
366
- timeout,
369
+ ...this.params.defaultRpcOptions,
370
+ ...options,
367
371
  onEvent: this.params.hooks?.onEvent
368
372
  });
369
373
  return { providerId, url, provider };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/RpcPoolProvider.ts","../src/Stats.ts","../src/utils.ts","../src/InstrumentedProvider.ts","../src/Semaphore.ts","../src/RpsLimiter.ts","../src/Router.ts"],"sourcesContent":["import { JsonRpcProvider, Network } from 'ethers';\nimport { Stats } from './Stats';\nimport { Endpoint, RpcEvent, shouldFailover } from './utils';\nimport { InstrumentedStaticJsonRpcProvider } from './InstrumentedProvider';\nimport { Router } from './Router';\n\nexport interface RPCPoolProviderParams {\n chainId: number;\n urls: string[];\n perUrl: { inFlight: number; timeout?: number; rps?: number; rpsBurst?: number };\n retry: { attempts: number };\n hooks?: {\n onEvent(e: RpcEvent): void;\n };\n}\n\n// TODO\n// -- circuit breaker + health checks\n// -- sticky “session”\n\nexport class RPCPoolProvider extends JsonRpcProvider {\n readonly router: Router;\n readonly params: RPCPoolProviderParams;\n readonly stats: Stats;\n\n constructor(params: RPCPoolProviderParams) {\n const network = Network.from(params.chainId);\n super('http://localhost', network, { staticNetwork: network });\n\n this.params = params;\n\n this.stats = new Stats();\n\n const endpoints: Endpoint[] = this.params.urls.map((url, i) => {\n const providerId = `rpc#${i + 1}-chainId:${this.params.chainId}-${url}`;\n\n const { inFlight = 1, rps = 1, rpsBurst, timeout = 10_000 } = this.params.perUrl;\n\n const provider = new InstrumentedStaticJsonRpcProvider({\n url,\n chainId: this.params.chainId,\n providerId,\n stats: this.stats,\n inFlight,\n rps,\n rpsBurst,\n timeout,\n onEvent: this.params.hooks?.onEvent,\n });\n\n return { providerId, url, provider };\n });\n\n this.router = new Router(endpoints, this.stats);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async send(method: string, params: any): Promise<any> {\n const tried = new Set<string>();\n const maxUniqueTries = Math.min(this.params.retry.attempts, this.router.size());\n\n while (tried.size < maxUniqueTries) {\n const ep = this.router.pick();\n if (tried.has(ep.providerId)) continue;\n tried.add(ep.providerId);\n\n try {\n return await ep.provider.send(method, params);\n } catch (e: any) {\n if (!shouldFailover(e)) throw e;\n if (tried.size >= maxUniqueTries) throw e;\n\n // Add exponential backoff with jitter before retry\n const baseDelay = Math.min(1000 * Math.pow(2, tried.size - 1), 5000);\n const jitter = Math.random() * baseDelay;\n await new Promise((resolve) => setTimeout(resolve, jitter));\n }\n }\n\n throw new Error('No RPC available');\n }\n\n getStats(): Stats {\n return this.stats;\n }\n}\n","export interface RpcStatsSnapshot {\n total: number;\n inFlight: number;\n perMethodTotal: Record<string, number>;\n rateLimitedTotal: number;\n perProviderRateLimited: Record<string, number>;\n timeoutTotal: number;\n perProviderTimeout: Record<string, number>;\n perProviderTotal: Record<string, number>;\n providerCooldownUntil: Record<string, number>;\n perProviderInFlight: Record<string, number>;\n perProviderError: Record<string, number>;\n}\n\nexport class Stats {\n private _total = 0;\n private _inFlight = 0;\n\n private _perMethod: Record<string, number> = {};\n\n private _rateLimitedTotal = 0;\n private _timeoutTotal = 0;\n\n private _perProviderInFlight: Record<string, number> = {};\n private _perProviderTotal: Record<string, number> = {};\n private _perProviderTimeout: Record<string, number> = {};\n private _perProviderRateLimited: Record<string, number> = {};\n private _perProviderError: Record<string, number> = {};\n\n private _providerCooldownUntil: Record<string, number> = {};\n\n private _bump(map: Record<string, number>, key: string) {\n map[key] = (map[key] || 0) + 1;\n }\n\n private _decrease(map: Record<string, number>, key: string) {\n map[key] = Math.max((map[key] || 0) - 1, 0);\n }\n\n private _bumpTotal() {\n this._total++;\n }\n\n private _bumpInFlight() {\n this._inFlight++;\n }\n\n private _bumpRateLimitedTotal() {\n this._rateLimitedTotal++;\n }\n\n private _bumpTimeoutTotal() {\n this._timeoutTotal++;\n }\n\n bumpInFlightPerProvider(id: string) {\n this._bumpInFlight();\n this._bump(this._perProviderInFlight, id);\n }\n\n decreaseInFlightPerProvider(id: string) {\n this.decreaseInFlight();\n this._decrease(this._perProviderInFlight, id);\n }\n\n decreaseInFlight() {\n this._inFlight = Math.max(this._inFlight - 1, 0);\n }\n\n bumpPerMethod(method: string) {\n this._bump(this._perMethod, method);\n }\n\n bumpRateLimitedPerProvider(id: string) {\n this._bumpRateLimitedTotal();\n this._bump(this._perProviderRateLimited, id);\n }\n\n bumpTimeoutPerProvider(id: string) {\n this._bumpTimeoutTotal();\n this._bump(this._perProviderTimeout, id);\n }\n\n bumpProviderTotal(id: string) {\n this._bumpTotal();\n this._perProviderTotal[id] = (this._perProviderTotal[id] || 0) + 1;\n }\n bumpServerErrorPerProvider(id: string) {\n this._bump(this._perProviderError, id);\n }\n\n timeoutRatio(id: string) {\n const t = this._perProviderTimeout[id] || 0;\n const n = this._perProviderTotal[id] || 0;\n return n ? t / n : 0;\n }\n\n isInCooldown(id: string) {\n return (this._providerCooldownUntil[id] || 0) > Date.now();\n }\n\n setCooldown(id: string, ms: number) {\n this._providerCooldownUntil[id] = Date.now() + ms;\n }\n\n snapshot(): Readonly<RpcStatsSnapshot> {\n return {\n total: this._total,\n inFlight: this._inFlight,\n perMethodTotal: { ...this._perMethod },\n rateLimitedTotal: this._rateLimitedTotal,\n timeoutTotal: this._timeoutTotal,\n perProviderInFlight: { ...this._perProviderInFlight },\n perProviderRateLimited: { ...this._perProviderRateLimited },\n perProviderTimeout: { ...this._perProviderTimeout },\n perProviderError: { ...this._perProviderError },\n perProviderTotal: { ...this._perProviderTotal },\n providerCooldownUntil: { ...this._providerCooldownUntil },\n };\n }\n}\n","import { InstrumentedStaticJsonRpcProvider } from './InstrumentedProvider';\n\nexport interface Endpoint {\n providerId: string;\n url: string;\n provider: InstrumentedStaticJsonRpcProvider;\n}\n\nexport type RpcEvent =\n | {\n type: 'request';\n chainId: number;\n providerId: string;\n method: string;\n startedAt: number;\n }\n | {\n type: 'response';\n chainId: number;\n providerId: string;\n method: string;\n startedAt: number;\n endedAt: number;\n ms: number;\n }\n | {\n type: 'error';\n chainId: number;\n providerId: string;\n method: string;\n startedAt: number;\n endedAt: number;\n ms: number;\n isRateLimit: boolean;\n isTimeout: boolean;\n status?: number;\n code?: string;\n message: string;\n };\n\nexport function getHttpStatus(e: any): number | undefined {\n return (\n e?.status ??\n e?.response?.status ??\n e?.response?.statusCode ??\n e?.error?.status ??\n e?.error?.response?.status ??\n e?.body?.statusCode // sometimes present\n );\n}\n\nexport function isRateLimitError(e: any): boolean {\n const status = getHttpStatus(e);\n if (status === 429 || status === 402) return true;\n\n const msg = String(e?.message || e);\n if (/error code:\\s*1015/i.test(msg)) return true; // Cloudflare\n return /rate limit|too many requests|429|quota|throttl/i.test(msg);\n}\n\nexport function isServerError(e: any): boolean {\n const status = getHttpStatus(e);\n return status !== undefined && status >= 500;\n}\n\nexport function getRetryAfterMs(e: any): number | null {\n const ra =\n e?.response?.headers?.get?.('retry-after') ??\n e?.response?.headers?.['retry-after'] ??\n e?.headers?.['retry-after'];\n const n = Number(ra);\n return Number.isFinite(n) ? n * 1000 : null;\n}\n\nexport function isTimeoutError(e: any): boolean {\n // ethers v5\n if (e?.code === 'TIMEOUT') return true;\n\n const status = getHttpStatus(e);\n // some RPCs / proxies return 504 on timeout\n if (status === 504) return true;\n\n const msg = String(e?.message || e);\n\n // node-fetch / undici / axios / nginx / generic\n return /timeout|timed out|ETIMEDOUT|ESOCKETTIMEDOUT|ECONNABORTED|504 Gateway/i.test(msg);\n}\n\nexport function shouldFailover(e: any): boolean {\n const to = isTimeoutError(e);\n const rl = isRateLimitError(e);\n const se = isServerError(e);\n\n // failover on timeouts and rate limits, but not on logical errors (e.g. invalid params)\n return to || rl || se;\n}\n","import { JsonRpcProvider, Network, JsonRpcPayload, FetchRequest } from 'ethers';\nimport { Semaphore } from './Semaphore';\nimport { Stats } from './Stats';\nimport {\n getHttpStatus,\n getRetryAfterMs,\n isRateLimitError,\n isServerError,\n isTimeoutError,\n RpcEvent,\n} from './utils';\nimport { RpsLimiter } from './RpsLimiter';\n\ninterface ProviderParams {\n url: string;\n chainId: number;\n providerId: string;\n stats: Stats;\n inFlight?: number;\n timeout?: number;\n rps?: number;\n rpsBurst?: number;\n onEvent?: (e: RpcEvent) => void;\n}\n/**\n * Instrumented StaticJsonRpcProvider.\n * Tracks requests, inFlight count, rate limits, and per-method / per-provider metrics.\n */\nexport class InstrumentedStaticJsonRpcProvider extends JsonRpcProvider {\n readonly providerId: string;\n readonly chainId: number;\n readonly params: ProviderParams;\n\n readonly inFlightLimiter: Semaphore;\n readonly rpsLimiter: RpsLimiter;\n readonly stats: Stats;\n readonly fetchRequest: FetchRequest;\n\n private lastCooldownMs: number = 0;\n\n constructor(params: ProviderParams) {\n const { url, providerId, chainId } = params;\n\n const network = Network.from(chainId);\n const fetchRequest = new FetchRequest(url);\n\n fetchRequest.timeout = params.timeout || 10_000;\n\n super(fetchRequest, chainId, { staticNetwork: network, batchMaxCount: 1 });\n this.fetchRequest = fetchRequest;\n this.providerId = providerId;\n this.chainId = chainId;\n this.params = params;\n\n const { rps = 10, rpsBurst, inFlight = 1 } = params;\n\n this.inFlightLimiter = new Semaphore(inFlight);\n this.rpsLimiter = new RpsLimiter(rps, rpsBurst || rps);\n this.stats = params.stats;\n }\n\n override async _send(payload: JsonRpcPayload | JsonRpcPayload[]): Promise<any> {\n await this.rpsLimiter.take(1);\n\n const release = this.inFlightLimiter ? await this.inFlightLimiter.acquire() : undefined;\n\n try {\n return await this._sendInstrumented(payload);\n } finally {\n release?.();\n }\n }\n\n // ethers v5 calls send(method, params)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private async _sendInstrumented(payload: JsonRpcPayload | JsonRpcPayload[]): Promise<any> {\n const startedAt = Date.now();\n const payloads = Array.isArray(payload) ? payload : [payload];\n\n this.stats.bumpInFlightPerProvider(this.providerId);\n this.stats.bumpProviderTotal(this.providerId);\n\n for (const p of payloads) {\n this.stats.bumpPerMethod(p.method);\n this.params.onEvent?.({\n type: 'request',\n chainId: this.chainId,\n providerId: this.providerId,\n method: p.method,\n startedAt,\n });\n }\n\n try {\n const res = await super._send(payload);\n\n const endedAt = Date.now();\n\n for (const p of payloads) {\n this.params.onEvent?.({\n type: 'response',\n chainId: this.chainId,\n providerId: this.providerId,\n method: p.method,\n startedAt,\n endedAt,\n ms: endedAt - startedAt,\n });\n }\n\n this.lastCooldownMs = 0;\n\n return res;\n } catch (e: any) {\n const endedAt = Date.now();\n const rl = isRateLimitError(e);\n if (rl) {\n this.stats.bumpRateLimitedPerProvider(this.providerId);\n const cooldownMs = 10_000;\n const raMs = getRetryAfterMs(e) ?? cooldownMs;\n this.stats.setCooldown(this.providerId, raMs);\n }\n\n const isTimeout = isTimeoutError(e);\n if (isTimeout && !rl) {\n this.stats.bumpTimeoutPerProvider(this.providerId);\n\n const n = this.stats.snapshot().perProviderTotal[this.providerId] || 0;\n const ratio = this.stats.timeoutRatio(this.providerId);\n\n // thresholds: do not ban on a single timeout, only after enough data\n if (n >= 50 && ratio >= 0.2) {\n const cooldownMs = ratio >= 0.5 ? 600_000 : 60_000;\n const raMs = getRetryAfterMs(e) ?? cooldownMs;\n this.lastCooldownMs = raMs;\n this.stats.setCooldown(this.providerId, raMs + Math.floor(Math.random() * 1000));\n }\n }\n\n const isError = isServerError(e);\n if (isError && !rl && !isTimeout) {\n this.stats.bumpServerErrorPerProvider(this.providerId);\n const cooldownMs = (this.lastCooldownMs * 2 || 10_000) + Math.floor(Math.random() * 1000);\n this.lastCooldownMs = cooldownMs;\n this.stats.setCooldown(this.providerId, cooldownMs);\n }\n\n for (const p of payloads) {\n this.params.onEvent?.({\n type: 'error',\n chainId: this.chainId,\n providerId: this.providerId,\n method: p.method,\n startedAt,\n endedAt,\n ms: endedAt - startedAt,\n isRateLimit: rl,\n isTimeout: isTimeout,\n status: getHttpStatus(e),\n code: e?.code,\n message: String(e?.message || e),\n });\n }\n\n throw e;\n } finally {\n this.stats.decreaseInFlightPerProvider(this.providerId);\n }\n }\n}\n","export class Semaphore {\n private inUse = 0;\n private queue: Array<() => void> = [];\n\n constructor(private readonly max: number) {\n if (!Number.isFinite(max) || max <= 0) {\n throw new Error(`Semaphore max must be a positive number, got: ${max}`);\n }\n }\n\n async acquire(): Promise<() => void> {\n if (this.inUse < this.max) {\n this.inUse++;\n let released = false;\n return () => {\n if (released) return;\n released = true;\n this.release();\n };\n }\n\n return new Promise<() => void>((resolve) => {\n this.queue.push(() => {\n this.inUse++;\n let released = false;\n resolve(() => {\n if (released) return;\n released = true;\n this.release();\n });\n });\n });\n }\n\n private release() {\n this.inUse = Math.max(0, this.inUse - 1);\n const next = this.queue.shift();\n if (next) next();\n }\n}\n","export class RpsLimiter {\n // Current number of tokens in the bucket (can be fractional)\n private tokens: number;\n\n // Time of last token refill, in ms\n private lastRefill = Date.now();\n\n constructor(\n // rps: how many tokens we add per second\n private readonly rps: number,\n // burst: maximum bucket capacity.\n // Default: >=1 and approximately equal to rps (to allow a small burst)\n private readonly burst: number = Math.max(1, Math.ceil(rps)),\n ) {\n // At start, the bucket is full: can make burst requests immediately\n this.tokens = burst;\n }\n\n // Refill tokens according to elapsed time\n private refill(now: number) {\n // rps<=0 means \"limit is disabled\"\n if (this.rps <= 0) return;\n\n const elapsed = now - this.lastRefill; // ms since last refill\n if (elapsed <= 0) return;\n\n // How many tokens to add:\n // elapsed/1000 = seconds, multiply by rps\n const add = (elapsed / 1000) * this.rps;\n\n // Add tokens, but don't exceed burst (bucket capacity)\n this.tokens = Math.min(this.burst, this.tokens + add);\n\n // Remember that we refilled tokens at time now\n this.lastRefill = now;\n }\n\n // Take count tokens (usually 1 request = 1 token).\n // If not enough tokens — wait and try again.\n async take(count = 1): Promise<void> {\n if (!this.rps || this.rps <= 0) return;\n\n while (true) {\n const now = Date.now();\n\n // Before attempting — refill tokens\n this.refill(now);\n\n // If enough tokens — \"pay\" for the request and exit\n if (this.tokens >= count) {\n this.tokens -= count;\n return;\n }\n\n // Not enough tokens: calculate how long to wait\n const need = count - this.tokens;\n\n // How many ms needed to accumulate need tokens:\n // need / rps = seconds, *1000 = ms\n const waitMs = Math.ceil((need / this.rps) * 1000);\n\n // Wait in chunks (not all waitMs at once), to:\n // - not sleep too long if time/state changed\n // - be more resilient to timer drift\n await new Promise((r) => setTimeout(r, Math.min(waitMs, 50)));\n }\n }\n}\n","import { Endpoint } from './utils';\nimport { Stats } from './Stats';\n\nexport class Router {\n private rr = 0;\n\n constructor(\n private readonly endpoints: Endpoint[],\n private readonly stats: Stats,\n ) {}\n\n size(): number {\n return this.endpoints.length;\n }\n\n pick(): Endpoint {\n const n = this.endpoints.length;\n for (let k = 0; k < n; k++) {\n const i = ((this.rr++ % n) + n) % n;\n const ep = this.endpoints[i];\n\n if (!this.stats.isInCooldown(ep.providerId)) return ep;\n }\n // if all are in cooldown, return the next one in round-robin order\n return this.endpoints[((this.rr++ % n) + n) % n];\n }\n}\n"],"mappings":"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","names":["JsonRpcProvider","Network","JsonRpcProvider","Network"]}
1
+ {"version":3,"sources":["../src/RpcPoolProvider.ts","../src/Stats.ts","../src/utils.ts","../src/InstrumentedProvider.ts","../src/Semaphore.ts","../src/RpsLimiter.ts","../src/Router.ts"],"sourcesContent":["import { FetchRequest, JsonRpcProvider, Network, Networkish } from 'ethers';\nimport { Stats } from './Stats';\nimport { Endpoint, RpcEvent, shouldFailover } from './utils';\nimport {\n InstrumentedJsonRpcProvider,\n InstrumentedJsonRpcProviderOptions,\n} from './InstrumentedProvider';\nimport { Router } from './Router';\n\ninterface RPCPoolProviderOptions extends Partial<InstrumentedJsonRpcProviderOptions> {\n url: string | FetchRequest;\n network?: Networkish;\n}\n\nexport interface RPCPoolProviderParams {\n network: Networkish;\n rpc: RPCPoolProviderOptions[];\n defaultRpcOptions: { inFlight: number; timeout?: number; rps?: number; rpsBurst?: number };\n retry: { attempts: number };\n hooks?: {\n onEvent(e: RpcEvent): void;\n };\n}\n\n// TODO\n// -- circuit breaker + health checks\n// -- sticky “session”\n\nexport class RPCPoolProvider extends JsonRpcProvider {\n readonly router: Router;\n readonly params: RPCPoolProviderParams;\n readonly stats: Stats;\n\n constructor(params: RPCPoolProviderParams) {\n const network = Network.from(params.network);\n super('http://localhost', network, { staticNetwork: network });\n\n this.params = params;\n\n this.stats = new Stats();\n\n const endpoints: Endpoint[] = this.params.rpc.map((options, i) => {\n const url = typeof options.url === 'string' ? options.url : options.url.url;\n const providerId = `rpc#${i + 1}-chainId:${this.params.network}-${url}`;\n\n const provider = new InstrumentedJsonRpcProvider(options.url, this.params.network, {\n providerId,\n stats: this.stats,\n ...this.params.defaultRpcOptions,\n ...options,\n onEvent: this.params.hooks?.onEvent,\n });\n\n return { providerId, url, provider };\n });\n\n this.router = new Router(endpoints, this.stats);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async send(method: string, params: any): Promise<any> {\n const tried = new Set<string>();\n const maxUniqueTries = Math.min(this.params.retry.attempts, this.router.size());\n\n while (tried.size < maxUniqueTries) {\n const ep = this.router.pick();\n if (tried.has(ep.providerId)) continue;\n tried.add(ep.providerId);\n\n try {\n return await ep.provider.send(method, params);\n } catch (e: any) {\n if (!shouldFailover(e)) throw e;\n if (tried.size >= maxUniqueTries) throw e;\n\n // Add exponential backoff with jitter before retry\n const baseDelay = Math.min(1000 * Math.pow(2, tried.size - 1), 5000);\n const jitter = Math.random() * baseDelay;\n await new Promise((resolve) => setTimeout(resolve, jitter));\n }\n }\n\n throw new Error('No RPC available');\n }\n\n getStats(): Stats {\n return this.stats;\n }\n}\n","export interface RpcStatsSnapshot {\n total: number;\n inFlight: number;\n perMethodTotal: Record<string, number>;\n rateLimitedTotal: number;\n perProviderRateLimited: Record<string, number>;\n timeoutTotal: number;\n perProviderTimeout: Record<string, number>;\n perProviderTotal: Record<string, number>;\n providerCooldownUntil: Record<string, number>;\n perProviderInFlight: Record<string, number>;\n perProviderError: Record<string, number>;\n}\n\nexport class Stats {\n private _total = 0;\n private _inFlight = 0;\n\n private _perMethod: Record<string, number> = {};\n\n private _rateLimitedTotal = 0;\n private _timeoutTotal = 0;\n\n private _perProviderInFlight: Record<string, number> = {};\n private _perProviderTotal: Record<string, number> = {};\n private _perProviderTimeout: Record<string, number> = {};\n private _perProviderRateLimited: Record<string, number> = {};\n private _perProviderError: Record<string, number> = {};\n\n private _providerCooldownUntil: Record<string, number> = {};\n\n private _bump(map: Record<string, number>, key: string) {\n map[key] = (map[key] || 0) + 1;\n }\n\n private _decrease(map: Record<string, number>, key: string) {\n map[key] = Math.max((map[key] || 0) - 1, 0);\n }\n\n private _bumpTotal() {\n this._total++;\n }\n\n private _bumpInFlight() {\n this._inFlight++;\n }\n\n private _bumpRateLimitedTotal() {\n this._rateLimitedTotal++;\n }\n\n private _bumpTimeoutTotal() {\n this._timeoutTotal++;\n }\n\n bumpInFlightPerProvider(id: string) {\n this._bumpInFlight();\n this._bump(this._perProviderInFlight, id);\n }\n\n decreaseInFlightPerProvider(id: string) {\n this.decreaseInFlight();\n this._decrease(this._perProviderInFlight, id);\n }\n\n decreaseInFlight() {\n this._inFlight = Math.max(this._inFlight - 1, 0);\n }\n\n bumpPerMethod(method: string) {\n this._bump(this._perMethod, method);\n }\n\n bumpRateLimitedPerProvider(id: string) {\n this._bumpRateLimitedTotal();\n this._bump(this._perProviderRateLimited, id);\n }\n\n bumpTimeoutPerProvider(id: string) {\n this._bumpTimeoutTotal();\n this._bump(this._perProviderTimeout, id);\n }\n\n bumpProviderTotal(id: string) {\n this._bumpTotal();\n this._perProviderTotal[id] = (this._perProviderTotal[id] || 0) + 1;\n }\n bumpServerErrorPerProvider(id: string) {\n this._bump(this._perProviderError, id);\n }\n\n timeoutRatio(id: string) {\n const t = this._perProviderTimeout[id] || 0;\n const n = this._perProviderTotal[id] || 0;\n return n ? t / n : 0;\n }\n\n isInCooldown(id: string) {\n return (this._providerCooldownUntil[id] || 0) > Date.now();\n }\n\n setCooldown(id: string, ms: number) {\n this._providerCooldownUntil[id] = Date.now() + ms;\n }\n\n snapshot(): Readonly<RpcStatsSnapshot> {\n return {\n total: this._total,\n inFlight: this._inFlight,\n perMethodTotal: { ...this._perMethod },\n rateLimitedTotal: this._rateLimitedTotal,\n timeoutTotal: this._timeoutTotal,\n perProviderInFlight: { ...this._perProviderInFlight },\n perProviderRateLimited: { ...this._perProviderRateLimited },\n perProviderTimeout: { ...this._perProviderTimeout },\n perProviderError: { ...this._perProviderError },\n perProviderTotal: { ...this._perProviderTotal },\n providerCooldownUntil: { ...this._providerCooldownUntil },\n };\n }\n}\n","import { InstrumentedJsonRpcProvider } from './InstrumentedProvider';\n\nexport interface Endpoint {\n providerId: string;\n url: string;\n provider: InstrumentedJsonRpcProvider;\n}\n\nexport type RpcEvent =\n | {\n type: 'request';\n chainId: bigint;\n providerId: string;\n method: string;\n startedAt: number;\n }\n | {\n type: 'response';\n chainId: bigint;\n providerId: string;\n method: string;\n startedAt: number;\n endedAt: number;\n ms: number;\n }\n | {\n type: 'error';\n chainId: bigint;\n providerId: string;\n method: string;\n startedAt: number;\n endedAt: number;\n ms: number;\n isRateLimit: boolean;\n isTimeout: boolean;\n status?: number;\n code?: string;\n message: string;\n };\n\nexport function getHttpStatus(e: any): number | undefined {\n return (\n e?.status ??\n e?.response?.status ??\n e?.response?.statusCode ??\n e?.error?.status ??\n e?.error?.response?.status ??\n e?.body?.statusCode // sometimes present\n );\n}\n\nexport function isRateLimitError(e: any): boolean {\n const status = getHttpStatus(e);\n if (status === 429 || status === 402) return true;\n\n const msg = String(e?.message || e);\n if (/error code:\\s*1015/i.test(msg)) return true; // Cloudflare\n return /rate limit|too many requests|429|quota|throttl/i.test(msg);\n}\n\nexport function isServerError(e: any): boolean {\n const status = getHttpStatus(e);\n return status !== undefined && status >= 500;\n}\n\nexport function getRetryAfterMs(e: any): number | null {\n const ra =\n e?.response?.headers?.get?.('retry-after') ??\n e?.response?.headers?.['retry-after'] ??\n e?.headers?.['retry-after'];\n const n = Number(ra);\n return Number.isFinite(n) ? n * 1000 : null;\n}\n\nexport function isTimeoutError(e: any): boolean {\n // ethers v5\n if (e?.code === 'TIMEOUT') return true;\n\n const status = getHttpStatus(e);\n // some RPCs / proxies return 504 on timeout\n if (status === 504) return true;\n\n const msg = String(e?.message || e);\n\n // node-fetch / undici / axios / nginx / generic\n return /timeout|timed out|ETIMEDOUT|ESOCKETTIMEDOUT|ECONNABORTED|504 Gateway/i.test(msg);\n}\n\nexport function shouldFailover(e: any): boolean {\n const to = isTimeoutError(e);\n const rl = isRateLimitError(e);\n const se = isServerError(e);\n\n // failover on timeouts and rate limits, but not on logical errors (e.g. invalid params)\n return to || rl || se;\n}\n","import {\n JsonRpcProvider,\n Network,\n JsonRpcPayload,\n FetchRequest,\n JsonRpcApiProviderOptions,\n Networkish,\n} from 'ethers';\nimport { Semaphore } from './Semaphore';\nimport { Stats } from './Stats';\nimport {\n getHttpStatus,\n getRetryAfterMs,\n isRateLimitError,\n isServerError,\n isTimeoutError,\n RpcEvent,\n} from './utils';\nimport { RpsLimiter } from './RpsLimiter';\n\nexport interface InstrumentedJsonRpcProviderOptions extends JsonRpcApiProviderOptions {\n providerId: string;\n stats: Stats;\n inFlight?: number;\n timeout?: number;\n rps?: number;\n rpsBurst?: number;\n onEvent?: (e: RpcEvent) => void;\n}\n/**\n * Instrumented JsonRpcProvider.\n * Tracks requests, inFlight count, rate limits, and per-method / per-provider metrics.\n */\nexport class InstrumentedJsonRpcProvider extends JsonRpcProvider {\n readonly providerId: string;\n readonly chainId: bigint;\n readonly options: InstrumentedJsonRpcProviderOptions;\n\n readonly inFlightLimiter: Semaphore;\n readonly rpsLimiter: RpsLimiter;\n readonly stats: Stats;\n readonly fetchRequest: FetchRequest;\n\n private lastCooldownMs: number = 0;\n\n constructor(\n url: string | FetchRequest,\n network: Networkish,\n options: InstrumentedJsonRpcProviderOptions,\n ) {\n let fetchRequest: FetchRequest;\n\n if (typeof url == 'string') {\n fetchRequest = new FetchRequest(url);\n } else {\n fetchRequest = url;\n }\n\n fetchRequest.timeout = options.timeout || 10_000;\n\n const _network = Network.from(network);\n super(fetchRequest, _network, { staticNetwork: true, ...options });\n this.fetchRequest = fetchRequest;\n this.providerId = options.providerId;\n this.chainId = _network.chainId;\n this.options = options;\n\n const { rps = 10, rpsBurst, inFlight = 1 } = options;\n\n this.inFlightLimiter = new Semaphore(inFlight);\n this.rpsLimiter = new RpsLimiter(rps, rpsBurst || rps);\n this.stats = options.stats;\n }\n\n override async _send(payload: JsonRpcPayload | JsonRpcPayload[]): Promise<any> {\n await this.rpsLimiter.take(1);\n\n const release = this.inFlightLimiter ? await this.inFlightLimiter.acquire() : undefined;\n\n try {\n return await this._sendInstrumented(payload);\n } finally {\n release?.();\n }\n }\n\n // ethers v5 calls send(method, params)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private async _sendInstrumented(payload: JsonRpcPayload | JsonRpcPayload[]): Promise<any> {\n const startedAt = Date.now();\n const payloads = Array.isArray(payload) ? payload : [payload];\n\n this.stats.bumpInFlightPerProvider(this.providerId);\n this.stats.bumpProviderTotal(this.providerId);\n\n for (const p of payloads) {\n this.stats.bumpPerMethod(p.method);\n this.options.onEvent?.({\n type: 'request',\n chainId: this.chainId,\n providerId: this.providerId,\n method: p.method,\n startedAt,\n });\n }\n\n try {\n const res = await super._send(payload);\n\n const endedAt = Date.now();\n\n for (const p of payloads) {\n this.options.onEvent?.({\n type: 'response',\n chainId: this.chainId,\n providerId: this.providerId,\n method: p.method,\n startedAt,\n endedAt,\n ms: endedAt - startedAt,\n });\n }\n\n this.lastCooldownMs = 0;\n\n return res;\n } catch (e: any) {\n const endedAt = Date.now();\n const rl = isRateLimitError(e);\n if (rl) {\n this.stats.bumpRateLimitedPerProvider(this.providerId);\n const cooldownMs = 10_000;\n const raMs = getRetryAfterMs(e) ?? cooldownMs;\n this.stats.setCooldown(this.providerId, raMs);\n }\n\n const isTimeout = isTimeoutError(e);\n if (isTimeout && !rl) {\n this.stats.bumpTimeoutPerProvider(this.providerId);\n\n const n = this.stats.snapshot().perProviderTotal[this.providerId] || 0;\n const ratio = this.stats.timeoutRatio(this.providerId);\n\n // thresholds: do not ban on a single timeout, only after enough data\n if (n >= 50 && ratio >= 0.2) {\n const cooldownMs = ratio >= 0.5 ? 600_000 : 60_000;\n const raMs = getRetryAfterMs(e) ?? cooldownMs;\n this.lastCooldownMs = raMs;\n this.stats.setCooldown(this.providerId, raMs + Math.floor(Math.random() * 1000));\n }\n }\n\n const isError = isServerError(e);\n if (isError && !rl && !isTimeout) {\n this.stats.bumpServerErrorPerProvider(this.providerId);\n const cooldownMs = (this.lastCooldownMs * 2 || 10_000) + Math.floor(Math.random() * 1000);\n this.lastCooldownMs = cooldownMs;\n this.stats.setCooldown(this.providerId, cooldownMs);\n }\n\n for (const p of payloads) {\n this.options.onEvent?.({\n type: 'error',\n chainId: this.chainId,\n providerId: this.providerId,\n method: p.method,\n startedAt,\n endedAt,\n ms: endedAt - startedAt,\n isRateLimit: rl,\n isTimeout: isTimeout,\n status: getHttpStatus(e),\n code: e?.code,\n message: String(e?.message || e),\n });\n }\n\n throw e;\n } finally {\n this.stats.decreaseInFlightPerProvider(this.providerId);\n }\n }\n}\n","export class Semaphore {\n private inUse = 0;\n private queue: Array<() => void> = [];\n\n constructor(private readonly max: number) {\n if (!Number.isFinite(max) || max <= 0) {\n throw new Error(`Semaphore max must be a positive number, got: ${max}`);\n }\n }\n\n async acquire(): Promise<() => void> {\n if (this.inUse < this.max) {\n this.inUse++;\n let released = false;\n return () => {\n if (released) return;\n released = true;\n this.release();\n };\n }\n\n return new Promise<() => void>((resolve) => {\n this.queue.push(() => {\n this.inUse++;\n let released = false;\n resolve(() => {\n if (released) return;\n released = true;\n this.release();\n });\n });\n });\n }\n\n private release() {\n this.inUse = Math.max(0, this.inUse - 1);\n const next = this.queue.shift();\n if (next) next();\n }\n}\n","export class RpsLimiter {\n // Current number of tokens in the bucket (can be fractional)\n private tokens: number;\n\n // Time of last token refill, in ms\n private lastRefill = Date.now();\n\n constructor(\n // rps: how many tokens we add per second\n private readonly rps: number,\n // burst: maximum bucket capacity.\n // Default: >=1 and approximately equal to rps (to allow a small burst)\n private readonly burst: number = Math.max(1, Math.ceil(rps)),\n ) {\n // At start, the bucket is full: can make burst requests immediately\n this.tokens = burst;\n }\n\n // Refill tokens according to elapsed time\n private refill(now: number) {\n // rps<=0 means \"limit is disabled\"\n if (this.rps <= 0) return;\n\n const elapsed = now - this.lastRefill; // ms since last refill\n if (elapsed <= 0) return;\n\n // How many tokens to add:\n // elapsed/1000 = seconds, multiply by rps\n const add = (elapsed / 1000) * this.rps;\n\n // Add tokens, but don't exceed burst (bucket capacity)\n this.tokens = Math.min(this.burst, this.tokens + add);\n\n // Remember that we refilled tokens at time now\n this.lastRefill = now;\n }\n\n // Take count tokens (usually 1 request = 1 token).\n // If not enough tokens — wait and try again.\n async take(count = 1): Promise<void> {\n if (!this.rps || this.rps <= 0) return;\n\n while (true) {\n const now = Date.now();\n\n // Before attempting — refill tokens\n this.refill(now);\n\n // If enough tokens — \"pay\" for the request and exit\n if (this.tokens >= count) {\n this.tokens -= count;\n return;\n }\n\n // Not enough tokens: calculate how long to wait\n const need = count - this.tokens;\n\n // How many ms needed to accumulate need tokens:\n // need / rps = seconds, *1000 = ms\n const waitMs = Math.ceil((need / this.rps) * 1000);\n\n // Wait in chunks (not all waitMs at once), to:\n // - not sleep too long if time/state changed\n // - be more resilient to timer drift\n await new Promise((r) => setTimeout(r, Math.min(waitMs, 50)));\n }\n }\n}\n","import { Endpoint } from './utils';\nimport { Stats } from './Stats';\n\nexport class Router {\n private rr = 0;\n\n constructor(\n private readonly endpoints: Endpoint[],\n private readonly stats: Stats,\n ) {}\n\n size(): number {\n return this.endpoints.length;\n }\n\n pick(): Endpoint {\n const n = this.endpoints.length;\n for (let k = 0; k < n; k++) {\n const i = ((this.rr++ % n) + n) % n;\n const ep = this.endpoints[i];\n\n if (!this.stats.isInCooldown(ep.providerId)) return ep;\n }\n // if all are in cooldown, return the next one in round-robin order\n return this.endpoints[((this.rr++ % n) + n) % n];\n }\n}\n"],"mappings":";AAAA,SAAuB,mBAAAA,kBAAiB,WAAAC,gBAA2B;;;ACc5D,IAAM,QAAN,MAAY;AAAA,EACT,SAAS;AAAA,EACT,YAAY;AAAA,EAEZ,aAAqC,CAAC;AAAA,EAEtC,oBAAoB;AAAA,EACpB,gBAAgB;AAAA,EAEhB,uBAA+C,CAAC;AAAA,EAChD,oBAA4C,CAAC;AAAA,EAC7C,sBAA8C,CAAC;AAAA,EAC/C,0BAAkD,CAAC;AAAA,EACnD,oBAA4C,CAAC;AAAA,EAE7C,yBAAiD,CAAC;AAAA,EAElD,MAAM,KAA6B,KAAa;AACtD,QAAI,GAAG,KAAK,IAAI,GAAG,KAAK,KAAK;AAAA,EAC/B;AAAA,EAEQ,UAAU,KAA6B,KAAa;AAC1D,QAAI,GAAG,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,KAAK,GAAG,CAAC;AAAA,EAC5C;AAAA,EAEQ,aAAa;AACnB,SAAK;AAAA,EACP;AAAA,EAEQ,gBAAgB;AACtB,SAAK;AAAA,EACP;AAAA,EAEQ,wBAAwB;AAC9B,SAAK;AAAA,EACP;AAAA,EAEQ,oBAAoB;AAC1B,SAAK;AAAA,EACP;AAAA,EAEA,wBAAwB,IAAY;AAClC,SAAK,cAAc;AACnB,SAAK,MAAM,KAAK,sBAAsB,EAAE;AAAA,EAC1C;AAAA,EAEA,4BAA4B,IAAY;AACtC,SAAK,iBAAiB;AACtB,SAAK,UAAU,KAAK,sBAAsB,EAAE;AAAA,EAC9C;AAAA,EAEA,mBAAmB;AACjB,SAAK,YAAY,KAAK,IAAI,KAAK,YAAY,GAAG,CAAC;AAAA,EACjD;AAAA,EAEA,cAAc,QAAgB;AAC5B,SAAK,MAAM,KAAK,YAAY,MAAM;AAAA,EACpC;AAAA,EAEA,2BAA2B,IAAY;AACrC,SAAK,sBAAsB;AAC3B,SAAK,MAAM,KAAK,yBAAyB,EAAE;AAAA,EAC7C;AAAA,EAEA,uBAAuB,IAAY;AACjC,SAAK,kBAAkB;AACvB,SAAK,MAAM,KAAK,qBAAqB,EAAE;AAAA,EACzC;AAAA,EAEA,kBAAkB,IAAY;AAC5B,SAAK,WAAW;AAChB,SAAK,kBAAkB,EAAE,KAAK,KAAK,kBAAkB,EAAE,KAAK,KAAK;AAAA,EACnE;AAAA,EACA,2BAA2B,IAAY;AACrC,SAAK,MAAM,KAAK,mBAAmB,EAAE;AAAA,EACvC;AAAA,EAEA,aAAa,IAAY;AACvB,UAAM,IAAI,KAAK,oBAAoB,EAAE,KAAK;AAC1C,UAAM,IAAI,KAAK,kBAAkB,EAAE,KAAK;AACxC,WAAO,IAAI,IAAI,IAAI;AAAA,EACrB;AAAA,EAEA,aAAa,IAAY;AACvB,YAAQ,KAAK,uBAAuB,EAAE,KAAK,KAAK,KAAK,IAAI;AAAA,EAC3D;AAAA,EAEA,YAAY,IAAY,IAAY;AAClC,SAAK,uBAAuB,EAAE,IAAI,KAAK,IAAI,IAAI;AAAA,EACjD;AAAA,EAEA,WAAuC;AACrC,WAAO;AAAA,MACL,OAAO,KAAK;AAAA,MACZ,UAAU,KAAK;AAAA,MACf,gBAAgB,EAAE,GAAG,KAAK,WAAW;AAAA,MACrC,kBAAkB,KAAK;AAAA,MACvB,cAAc,KAAK;AAAA,MACnB,qBAAqB,EAAE,GAAG,KAAK,qBAAqB;AAAA,MACpD,wBAAwB,EAAE,GAAG,KAAK,wBAAwB;AAAA,MAC1D,oBAAoB,EAAE,GAAG,KAAK,oBAAoB;AAAA,MAClD,kBAAkB,EAAE,GAAG,KAAK,kBAAkB;AAAA,MAC9C,kBAAkB,EAAE,GAAG,KAAK,kBAAkB;AAAA,MAC9C,uBAAuB,EAAE,GAAG,KAAK,uBAAuB;AAAA,IAC1D;AAAA,EACF;AACF;;;AChFO,SAAS,cAAc,GAA4B;AACxD,SACE,GAAG,UACH,GAAG,UAAU,UACb,GAAG,UAAU,cACb,GAAG,OAAO,UACV,GAAG,OAAO,UAAU,UACpB,GAAG,MAAM;AAEb;AAEO,SAAS,iBAAiB,GAAiB;AAChD,QAAM,SAAS,cAAc,CAAC;AAC9B,MAAI,WAAW,OAAO,WAAW,IAAK,QAAO;AAE7C,QAAM,MAAM,OAAO,GAAG,WAAW,CAAC;AAClC,MAAI,sBAAsB,KAAK,GAAG,EAAG,QAAO;AAC5C,SAAO,kDAAkD,KAAK,GAAG;AACnE;AAEO,SAAS,cAAc,GAAiB;AAC7C,QAAM,SAAS,cAAc,CAAC;AAC9B,SAAO,WAAW,UAAa,UAAU;AAC3C;AAEO,SAAS,gBAAgB,GAAuB;AACrD,QAAM,KACJ,GAAG,UAAU,SAAS,MAAM,aAAa,KACzC,GAAG,UAAU,UAAU,aAAa,KACpC,GAAG,UAAU,aAAa;AAC5B,QAAM,IAAI,OAAO,EAAE;AACnB,SAAO,OAAO,SAAS,CAAC,IAAI,IAAI,MAAO;AACzC;AAEO,SAAS,eAAe,GAAiB;AAE9C,MAAI,GAAG,SAAS,UAAW,QAAO;AAElC,QAAM,SAAS,cAAc,CAAC;AAE9B,MAAI,WAAW,IAAK,QAAO;AAE3B,QAAM,MAAM,OAAO,GAAG,WAAW,CAAC;AAGlC,SAAO,wEAAwE,KAAK,GAAG;AACzF;AAEO,SAAS,eAAe,GAAiB;AAC9C,QAAM,KAAK,eAAe,CAAC;AAC3B,QAAM,KAAK,iBAAiB,CAAC;AAC7B,QAAM,KAAK,cAAc,CAAC;AAG1B,SAAO,MAAM,MAAM;AACrB;;;AC/FA;AAAA,EACE;AAAA,EACA;AAAA,EAEA;AAAA,OAGK;;;ACPA,IAAM,YAAN,MAAgB;AAAA,EAIrB,YAA6B,KAAa;AAAb;AAC3B,QAAI,CAAC,OAAO,SAAS,GAAG,KAAK,OAAO,GAAG;AACrC,YAAM,IAAI,MAAM,iDAAiD,GAAG,EAAE;AAAA,IACxE;AAAA,EACF;AAAA,EAPQ,QAAQ;AAAA,EACR,QAA2B,CAAC;AAAA,EAQpC,MAAM,UAA+B;AACnC,QAAI,KAAK,QAAQ,KAAK,KAAK;AACzB,WAAK;AACL,UAAI,WAAW;AACf,aAAO,MAAM;AACX,YAAI,SAAU;AACd,mBAAW;AACX,aAAK,QAAQ;AAAA,MACf;AAAA,IACF;AAEA,WAAO,IAAI,QAAoB,CAAC,YAAY;AAC1C,WAAK,MAAM,KAAK,MAAM;AACpB,aAAK;AACL,YAAI,WAAW;AACf,gBAAQ,MAAM;AACZ,cAAI,SAAU;AACd,qBAAW;AACX,eAAK,QAAQ;AAAA,QACf,CAAC;AAAA,MACH,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEQ,UAAU;AAChB,SAAK,QAAQ,KAAK,IAAI,GAAG,KAAK,QAAQ,CAAC;AACvC,UAAM,OAAO,KAAK,MAAM,MAAM;AAC9B,QAAI,KAAM,MAAK;AAAA,EACjB;AACF;;;ACvCO,IAAM,aAAN,MAAiB;AAAA,EAOtB,YAEmB,KAGA,QAAgB,KAAK,IAAI,GAAG,KAAK,KAAK,GAAG,CAAC,GAC3D;AAJiB;AAGA;AAGjB,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAdQ;AAAA;AAAA,EAGA,aAAa,KAAK,IAAI;AAAA;AAAA,EActB,OAAO,KAAa;AAE1B,QAAI,KAAK,OAAO,EAAG;AAEnB,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,WAAW,EAAG;AAIlB,UAAM,MAAO,UAAU,MAAQ,KAAK;AAGpC,SAAK,SAAS,KAAK,IAAI,KAAK,OAAO,KAAK,SAAS,GAAG;AAGpD,SAAK,aAAa;AAAA,EACpB;AAAA;AAAA;AAAA,EAIA,MAAM,KAAK,QAAQ,GAAkB;AACnC,QAAI,CAAC,KAAK,OAAO,KAAK,OAAO,EAAG;AAEhC,WAAO,MAAM;AACX,YAAM,MAAM,KAAK,IAAI;AAGrB,WAAK,OAAO,GAAG;AAGf,UAAI,KAAK,UAAU,OAAO;AACxB,aAAK,UAAU;AACf;AAAA,MACF;AAGA,YAAM,OAAO,QAAQ,KAAK;AAI1B,YAAM,SAAS,KAAK,KAAM,OAAO,KAAK,MAAO,GAAI;AAKjD,YAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,KAAK,IAAI,QAAQ,EAAE,CAAC,CAAC;AAAA,IAC9D;AAAA,EACF;AACF;;;AFlCO,IAAM,8BAAN,cAA0C,gBAAgB;AAAA,EACtD;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAED,iBAAyB;AAAA,EAEjC,YACE,KACA,SACA,SACA;AACA,QAAI;AAEJ,QAAI,OAAO,OAAO,UAAU;AAC1B,qBAAe,IAAI,aAAa,GAAG;AAAA,IACrC,OAAO;AACL,qBAAe;AAAA,IACjB;AAEA,iBAAa,UAAU,QAAQ,WAAW;AAE1C,UAAM,WAAW,QAAQ,KAAK,OAAO;AACrC,UAAM,cAAc,UAAU,EAAE,eAAe,MAAM,GAAG,QAAQ,CAAC;AACjE,SAAK,eAAe;AACpB,SAAK,aAAa,QAAQ;AAC1B,SAAK,UAAU,SAAS;AACxB,SAAK,UAAU;AAEf,UAAM,EAAE,MAAM,IAAI,UAAU,WAAW,EAAE,IAAI;AAE7C,SAAK,kBAAkB,IAAI,UAAU,QAAQ;AAC7C,SAAK,aAAa,IAAI,WAAW,KAAK,YAAY,GAAG;AACrD,SAAK,QAAQ,QAAQ;AAAA,EACvB;AAAA,EAEA,MAAe,MAAM,SAA0D;AAC7E,UAAM,KAAK,WAAW,KAAK,CAAC;AAE5B,UAAM,UAAU,KAAK,kBAAkB,MAAM,KAAK,gBAAgB,QAAQ,IAAI;AAE9E,QAAI;AACF,aAAO,MAAM,KAAK,kBAAkB,OAAO;AAAA,IAC7C,UAAE;AACA,gBAAU;AAAA,IACZ;AAAA,EACF;AAAA;AAAA;AAAA,EAIA,MAAc,kBAAkB,SAA0D;AACxF,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,WAAW,MAAM,QAAQ,OAAO,IAAI,UAAU,CAAC,OAAO;AAE5D,SAAK,MAAM,wBAAwB,KAAK,UAAU;AAClD,SAAK,MAAM,kBAAkB,KAAK,UAAU;AAE5C,eAAW,KAAK,UAAU;AACxB,WAAK,MAAM,cAAc,EAAE,MAAM;AACjC,WAAK,QAAQ,UAAU;AAAA,QACrB,MAAM;AAAA,QACN,SAAS,KAAK;AAAA,QACd,YAAY,KAAK;AAAA,QACjB,QAAQ,EAAE;AAAA,QACV;AAAA,MACF,CAAC;AAAA,IACH;AAEA,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,MAAM,OAAO;AAErC,YAAM,UAAU,KAAK,IAAI;AAEzB,iBAAW,KAAK,UAAU;AACxB,aAAK,QAAQ,UAAU;AAAA,UACrB,MAAM;AAAA,UACN,SAAS,KAAK;AAAA,UACd,YAAY,KAAK;AAAA,UACjB,QAAQ,EAAE;AAAA,UACV;AAAA,UACA;AAAA,UACA,IAAI,UAAU;AAAA,QAChB,CAAC;AAAA,MACH;AAEA,WAAK,iBAAiB;AAEtB,aAAO;AAAA,IACT,SAAS,GAAQ;AACf,YAAM,UAAU,KAAK,IAAI;AACzB,YAAM,KAAK,iBAAiB,CAAC;AAC7B,UAAI,IAAI;AACN,aAAK,MAAM,2BAA2B,KAAK,UAAU;AACrD,cAAM,aAAa;AACnB,cAAM,OAAO,gBAAgB,CAAC,KAAK;AACnC,aAAK,MAAM,YAAY,KAAK,YAAY,IAAI;AAAA,MAC9C;AAEA,YAAM,YAAY,eAAe,CAAC;AAClC,UAAI,aAAa,CAAC,IAAI;AACpB,aAAK,MAAM,uBAAuB,KAAK,UAAU;AAEjD,cAAM,IAAI,KAAK,MAAM,SAAS,EAAE,iBAAiB,KAAK,UAAU,KAAK;AACrE,cAAM,QAAQ,KAAK,MAAM,aAAa,KAAK,UAAU;AAGrD,YAAI,KAAK,MAAM,SAAS,KAAK;AAC3B,gBAAM,aAAa,SAAS,MAAM,MAAU;AAC5C,gBAAM,OAAO,gBAAgB,CAAC,KAAK;AACnC,eAAK,iBAAiB;AACtB,eAAK,MAAM,YAAY,KAAK,YAAY,OAAO,KAAK,MAAM,KAAK,OAAO,IAAI,GAAI,CAAC;AAAA,QACjF;AAAA,MACF;AAEA,YAAM,UAAU,cAAc,CAAC;AAC/B,UAAI,WAAW,CAAC,MAAM,CAAC,WAAW;AAChC,aAAK,MAAM,2BAA2B,KAAK,UAAU;AACrD,cAAM,cAAc,KAAK,iBAAiB,KAAK,OAAU,KAAK,MAAM,KAAK,OAAO,IAAI,GAAI;AACxF,aAAK,iBAAiB;AACtB,aAAK,MAAM,YAAY,KAAK,YAAY,UAAU;AAAA,MACpD;AAEA,iBAAW,KAAK,UAAU;AACxB,aAAK,QAAQ,UAAU;AAAA,UACrB,MAAM;AAAA,UACN,SAAS,KAAK;AAAA,UACd,YAAY,KAAK;AAAA,UACjB,QAAQ,EAAE;AAAA,UACV;AAAA,UACA;AAAA,UACA,IAAI,UAAU;AAAA,UACd,aAAa;AAAA,UACb;AAAA,UACA,QAAQ,cAAc,CAAC;AAAA,UACvB,MAAM,GAAG;AAAA,UACT,SAAS,OAAO,GAAG,WAAW,CAAC;AAAA,QACjC,CAAC;AAAA,MACH;AAEA,YAAM;AAAA,IACR,UAAE;AACA,WAAK,MAAM,4BAA4B,KAAK,UAAU;AAAA,IACxD;AAAA,EACF;AACF;;;AGnLO,IAAM,SAAN,MAAa;AAAA,EAGlB,YACmB,WACA,OACjB;AAFiB;AACA;AAAA,EAChB;AAAA,EALK,KAAK;AAAA,EAOb,OAAe;AACb,WAAO,KAAK,UAAU;AAAA,EACxB;AAAA,EAEA,OAAiB;AACf,UAAM,IAAI,KAAK,UAAU;AACzB,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,YAAM,KAAM,KAAK,OAAO,IAAK,KAAK;AAClC,YAAM,KAAK,KAAK,UAAU,CAAC;AAE3B,UAAI,CAAC,KAAK,MAAM,aAAa,GAAG,UAAU,EAAG,QAAO;AAAA,IACtD;AAEA,WAAO,KAAK,WAAY,KAAK,OAAO,IAAK,KAAK,CAAC;AAAA,EACjD;AACF;;;ANEO,IAAM,kBAAN,cAA8BC,iBAAgB;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,QAA+B;AACzC,UAAM,UAAUC,SAAQ,KAAK,OAAO,OAAO;AAC3C,UAAM,oBAAoB,SAAS,EAAE,eAAe,QAAQ,CAAC;AAE7D,SAAK,SAAS;AAEd,SAAK,QAAQ,IAAI,MAAM;AAEvB,UAAM,YAAwB,KAAK,OAAO,IAAI,IAAI,CAAC,SAAS,MAAM;AAChE,YAAM,MAAM,OAAO,QAAQ,QAAQ,WAAW,QAAQ,MAAM,QAAQ,IAAI;AACxE,YAAM,aAAa,OAAO,IAAI,CAAC,YAAY,KAAK,OAAO,OAAO,IAAI,GAAG;AAErE,YAAM,WAAW,IAAI,4BAA4B,QAAQ,KAAK,KAAK,OAAO,SAAS;AAAA,QACjF;AAAA,QACA,OAAO,KAAK;AAAA,QACZ,GAAG,KAAK,OAAO;AAAA,QACf,GAAG;AAAA,QACH,SAAS,KAAK,OAAO,OAAO;AAAA,MAC9B,CAAC;AAED,aAAO,EAAE,YAAY,KAAK,SAAS;AAAA,IACrC,CAAC;AAED,SAAK,SAAS,IAAI,OAAO,WAAW,KAAK,KAAK;AAAA,EAChD;AAAA;AAAA,EAGA,MAAM,KAAK,QAAgB,QAA2B;AACpD,UAAM,QAAQ,oBAAI,IAAY;AAC9B,UAAM,iBAAiB,KAAK,IAAI,KAAK,OAAO,MAAM,UAAU,KAAK,OAAO,KAAK,CAAC;AAE9E,WAAO,MAAM,OAAO,gBAAgB;AAClC,YAAM,KAAK,KAAK,OAAO,KAAK;AAC5B,UAAI,MAAM,IAAI,GAAG,UAAU,EAAG;AAC9B,YAAM,IAAI,GAAG,UAAU;AAEvB,UAAI;AACF,eAAO,MAAM,GAAG,SAAS,KAAK,QAAQ,MAAM;AAAA,MAC9C,SAAS,GAAQ;AACf,YAAI,CAAC,eAAe,CAAC,EAAG,OAAM;AAC9B,YAAI,MAAM,QAAQ,eAAgB,OAAM;AAGxC,cAAM,YAAY,KAAK,IAAI,MAAO,KAAK,IAAI,GAAG,MAAM,OAAO,CAAC,GAAG,GAAI;AACnE,cAAM,SAAS,KAAK,OAAO,IAAI;AAC/B,cAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,MAAM,CAAC;AAAA,MAC5D;AAAA,IACF;AAEA,UAAM,IAAI,MAAM,kBAAkB;AAAA,EACpC;AAAA,EAEA,WAAkB;AAChB,WAAO,KAAK;AAAA,EACd;AACF;","names":["JsonRpcProvider","Network","JsonRpcProvider","Network"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ethers-rpc-pool",
3
- "version": "1.0.7",
3
+ "version": "1.1.1",
4
4
  "description": "EVM RPC multiplexer for ethers.js with load balancing, rate limiting, failover and consistency controls.",
5
5
  "license": "MIT",
6
6
  "keywords": [