ethers-rpc-pool 1.1.1 → 1.1.2
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 +31 -10
- package/dist/index.cjs +8 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,6 +38,7 @@ Designed for production backends and dApps that need:
|
|
|
38
38
|
- [When To Use](#when-to-use)
|
|
39
39
|
- [Example Architecture](#example-architecture)
|
|
40
40
|
- [Roadmap](#roadmap)
|
|
41
|
+
- [Article](#article)
|
|
41
42
|
- [License](#license)
|
|
42
43
|
|
|
43
44
|
## Why ethers-rpc-pool?
|
|
@@ -351,16 +352,28 @@ Not intended for:
|
|
|
351
352
|
## Example Architecture
|
|
352
353
|
|
|
353
354
|
```
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
355
|
+
┌───────────────┐
|
|
356
|
+
│ Application │
|
|
357
|
+
└───────┬───────┘
|
|
358
|
+
│
|
|
359
|
+
┌────────▼────────┐
|
|
360
|
+
│ RPCPoolProvider │
|
|
361
|
+
├─────────────────┤
|
|
362
|
+
│ Metrics │
|
|
363
|
+
├─────────────────┤
|
|
364
|
+
│ Router │
|
|
365
|
+
└───────┬─────────┘
|
|
366
|
+
┌─────────────────┼───────────────────┐
|
|
367
|
+
▼ ▼ ▼
|
|
368
|
+
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
|
|
369
|
+
│ RPC Endpoint │ │ RPC Endpoint │ │ RPC Endpoint │
|
|
370
|
+
│ #1 │ │ #2 │ │ #3 │
|
|
371
|
+
├────────────────┤ ├────────────────┤ ├────────────────┤
|
|
372
|
+
│ In-Flight │ │ In-Flight │ │ In-Flight │
|
|
373
|
+
│ Semaphore │ │ Semaphore │ │ Semaphore │
|
|
374
|
+
├────────────────┤ ├────────────────┤ ├────────────────┤
|
|
375
|
+
│ RPS Limiter │ │ RPS Limiter │ │ RPS Limiter │
|
|
376
|
+
└────────────────┘ └────────────────┘ └────────────────┘
|
|
364
377
|
```
|
|
365
378
|
|
|
366
379
|
---
|
|
@@ -374,6 +387,14 @@ Not intended for:
|
|
|
374
387
|
|
|
375
388
|
---
|
|
376
389
|
|
|
390
|
+
## Article
|
|
391
|
+
|
|
392
|
+
Read the engineering story behind this library:
|
|
393
|
+
|
|
394
|
+
(How I solved Ethereum RPC rate limits without paying $250/month)[https://dev.to/ahiipsa/how-i-solved-ethereum-rpc-rate-limits-with-traffic-engineering-instead-of-paying-250month-30ed]
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
377
398
|
## License
|
|
378
399
|
|
|
379
400
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -399,16 +399,21 @@ var RPCPoolProvider = class extends import_ethers2.JsonRpcProvider {
|
|
|
399
399
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
400
400
|
async send(method, params) {
|
|
401
401
|
const tried = /* @__PURE__ */ new Set();
|
|
402
|
-
const
|
|
403
|
-
|
|
402
|
+
const maxAttempts = this.params.retry.attempts;
|
|
403
|
+
let attempts = 0;
|
|
404
|
+
while (attempts < maxAttempts) {
|
|
405
|
+
if (tried.size === this.router.size()) {
|
|
406
|
+
tried.clear();
|
|
407
|
+
}
|
|
404
408
|
const ep = this.router.pick();
|
|
405
409
|
if (tried.has(ep.providerId)) continue;
|
|
406
410
|
tried.add(ep.providerId);
|
|
411
|
+
attempts++;
|
|
407
412
|
try {
|
|
408
413
|
return await ep.provider.send(method, params);
|
|
409
414
|
} catch (e) {
|
|
410
415
|
if (!shouldFailover(e)) throw e;
|
|
411
|
-
if (
|
|
416
|
+
if (attempts >= maxAttempts) throw e;
|
|
412
417
|
const baseDelay = Math.min(1e3 * Math.pow(2, tried.size - 1), 5e3);
|
|
413
418
|
const jitter = Math.random() * baseDelay;
|
|
414
419
|
await new Promise((resolve) => setTimeout(resolve, jitter));
|
package/dist/index.cjs.map
CHANGED
|
@@ -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 { 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"]}
|
|
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 maxAttempts = this.params.retry.attempts;\n let attempts = 0;\n\n while (attempts < maxAttempts) {\n // All endpoints have been tried, reset for another round of attempts\n if (tried.size === this.router.size()) {\n tried.clear();\n }\n\n const ep = this.router.pick();\n if (tried.has(ep.providerId)) continue;\n tried.add(ep.providerId);\n attempts++;\n\n try {\n return await ep.provider.send(method, params);\n } catch (e: any) {\n if (!shouldFailover(e)) throw e;\n if (attempts >= maxAttempts) 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;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAAAA,iBAAmE;;;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,oBAOO;;;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,8BAAgB;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,2BAAa,GAAG;AAAA,IACrC,OAAO;AACL,qBAAe;AAAA,IACjB;AAEA,iBAAa,UAAU,QAAQ,WAAW;AAE1C,UAAM,WAAW,sBAAQ,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,cAA8B,+BAAgB;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,QAA+B;AACzC,UAAM,UAAU,uBAAQ,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,cAAc,KAAK,OAAO,MAAM;AACtC,QAAI,WAAW;AAEf,WAAO,WAAW,aAAa;AAE7B,UAAI,MAAM,SAAS,KAAK,OAAO,KAAK,GAAG;AACrC,cAAM,MAAM;AAAA,MACd;AAEA,YAAM,KAAK,KAAK,OAAO,KAAK;AAC5B,UAAI,MAAM,IAAI,GAAG,UAAU,EAAG;AAC9B,YAAM,IAAI,GAAG,UAAU;AACvB;AAEA,UAAI;AACF,eAAO,MAAM,GAAG,SAAS,KAAK,QAAQ,MAAM;AAAA,MAC9C,SAAS,GAAQ;AACf,YAAI,CAAC,eAAe,CAAC,EAAG,OAAM;AAC9B,YAAI,YAAY,YAAa,OAAM;AAGnC,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":["import_ethers"]}
|
package/dist/index.js
CHANGED
|
@@ -377,16 +377,21 @@ var RPCPoolProvider = class extends JsonRpcProvider2 {
|
|
|
377
377
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
378
378
|
async send(method, params) {
|
|
379
379
|
const tried = /* @__PURE__ */ new Set();
|
|
380
|
-
const
|
|
381
|
-
|
|
380
|
+
const maxAttempts = this.params.retry.attempts;
|
|
381
|
+
let attempts = 0;
|
|
382
|
+
while (attempts < maxAttempts) {
|
|
383
|
+
if (tried.size === this.router.size()) {
|
|
384
|
+
tried.clear();
|
|
385
|
+
}
|
|
382
386
|
const ep = this.router.pick();
|
|
383
387
|
if (tried.has(ep.providerId)) continue;
|
|
384
388
|
tried.add(ep.providerId);
|
|
389
|
+
attempts++;
|
|
385
390
|
try {
|
|
386
391
|
return await ep.provider.send(method, params);
|
|
387
392
|
} catch (e) {
|
|
388
393
|
if (!shouldFailover(e)) throw e;
|
|
389
|
-
if (
|
|
394
|
+
if (attempts >= maxAttempts) throw e;
|
|
390
395
|
const baseDelay = Math.min(1e3 * Math.pow(2, tried.size - 1), 5e3);
|
|
391
396
|
const jitter = Math.random() * baseDelay;
|
|
392
397
|
await new Promise((resolve) => setTimeout(resolve, jitter));
|
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 { 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":["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 maxAttempts = this.params.retry.attempts;\n let attempts = 0;\n\n while (attempts < maxAttempts) {\n // All endpoints have been tried, reset for another round of attempts\n if (tried.size === this.router.size()) {\n tried.clear();\n }\n\n const ep = this.router.pick();\n if (tried.has(ep.providerId)) continue;\n tried.add(ep.providerId);\n attempts++;\n\n try {\n return await ep.provider.send(method, params);\n } catch (e: any) {\n if (!shouldFailover(e)) throw e;\n if (attempts >= maxAttempts) 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":["JsonRpcProvider","Network","JsonRpcProvider","Network"]}
|