ethers-rpc-pool 1.0.6 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -17
- package/dist/index.cjs +28 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -16
- package/dist/index.d.ts +18 -16
- package/dist/index.js +33 -47
- package/dist/index.js.map +1 -1
- package/package.json +1 -3
package/README.md
CHANGED
|
@@ -67,8 +67,14 @@ import { RPCPoolProvider } from 'ethers-rpc-pool';
|
|
|
67
67
|
|
|
68
68
|
const poolProvider = new RPCPoolProvider({
|
|
69
69
|
chainId: 1,
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
rpc: [
|
|
71
|
+
{ url: 'http://rpc1.example' },
|
|
72
|
+
{ url: 'http://rpc2.example' },
|
|
73
|
+
{ url: 'http://rpc3.example' },
|
|
74
|
+
{ url: 'http://rpc4.example' },
|
|
75
|
+
{ url: 'http://rpc5.example' },
|
|
76
|
+
],
|
|
77
|
+
defaultRpcOptions: { inFlight: 1, timeout: 3000, rps: 2, rpsBurst: 5 },
|
|
72
78
|
retry: { attempts: 2 },
|
|
73
79
|
});
|
|
74
80
|
|
|
@@ -86,8 +92,18 @@ const balance = await poolProvider.getBalance('0x...');
|
|
|
86
92
|
|
|
87
93
|
```ts
|
|
88
94
|
interface RPCPoolProviderParams {
|
|
89
|
-
|
|
90
|
-
|
|
95
|
+
network: number;
|
|
96
|
+
rpc: {
|
|
97
|
+
url: string;
|
|
98
|
+
timeout?: number;
|
|
99
|
+
rps?: number;
|
|
100
|
+
rpsBurst?: number;
|
|
101
|
+
}[];
|
|
102
|
+
defaultRpcOptions?: {
|
|
103
|
+
timeout?: number;
|
|
104
|
+
rps?: number;
|
|
105
|
+
rpsBurst?: number;
|
|
106
|
+
};
|
|
91
107
|
perUrl: {
|
|
92
108
|
inFlight: number;
|
|
93
109
|
};
|
|
@@ -102,16 +118,16 @@ interface RPCPoolProviderParams {
|
|
|
102
118
|
|
|
103
119
|
### Options Explained
|
|
104
120
|
|
|
105
|
-
| Option
|
|
106
|
-
|
|
|
107
|
-
| `
|
|
108
|
-
| `
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `retry.attempts`
|
|
114
|
-
| `hooks.onEvent`
|
|
121
|
+
| Option | Description |
|
|
122
|
+
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
123
|
+
| `network` | Target chain ID |
|
|
124
|
+
| `rpc` | List of RPC endpoints |
|
|
125
|
+
| `defaultRpcOptions.inFlight` | Max concurrent requests per endpoint |
|
|
126
|
+
| `defaultRpcOptions.timeout` | Timeout in ms for each request to this URL, default 10s |
|
|
127
|
+
| `defaultRpcOptions.rps` | Maximum number of requests per second allowed for a single RPC endpoint. Enforced using a token bucket rate limiter. |
|
|
128
|
+
| `defaultRpcOptions.rpsBurst` | Maximum burst capacity for the rate limiter. Allows short spikes above the sustained rate by accumulating tokens during idle periods. |
|
|
129
|
+
| `retry.attempts` | Maximum number of unique endpoints to try |
|
|
130
|
+
| `hooks.onEvent` | Optional instrumentation hook |
|
|
115
131
|
|
|
116
132
|
---
|
|
117
133
|
|
|
@@ -126,9 +142,7 @@ Requests are routed through an internal `Router`, which selects an available end
|
|
|
126
142
|
Each endpoint has its own semaphore limiter:
|
|
127
143
|
|
|
128
144
|
```ts
|
|
129
|
-
|
|
130
|
-
inFlight: number;
|
|
131
|
-
}
|
|
145
|
+
inFlight: number;
|
|
132
146
|
```
|
|
133
147
|
|
|
134
148
|
This prevents:
|
package/dist/index.cjs
CHANGED
|
@@ -142,20 +142,6 @@ function isTimeoutError(e) {
|
|
|
142
142
|
const msg = String(e?.message || e);
|
|
143
143
|
return /timeout|timed out|ETIMEDOUT|ESOCKETTIMEDOUT|ECONNABORTED|504 Gateway/i.test(msg);
|
|
144
144
|
}
|
|
145
|
-
function withTimeout(p, ms, meta) {
|
|
146
|
-
let t;
|
|
147
|
-
const timeout = new Promise((_, reject) => {
|
|
148
|
-
t = setTimeout(() => {
|
|
149
|
-
const err = new Error(
|
|
150
|
-
`RPC timeout after ${ms}ms` + (meta?.method ? ` method=${meta.method}` : "") + (meta?.providerId ? ` provider=${meta.providerId}` : "") + (meta?.chainId != null ? ` chainId=${meta.chainId}` : "")
|
|
151
|
-
);
|
|
152
|
-
err.code = "TIMEOUT";
|
|
153
|
-
err.timeout = ms;
|
|
154
|
-
reject(err);
|
|
155
|
-
}, ms);
|
|
156
|
-
});
|
|
157
|
-
return Promise.race([p, timeout]).finally(() => t && clearTimeout(t));
|
|
158
|
-
}
|
|
159
145
|
function shouldFailover(e) {
|
|
160
146
|
const to = isTimeoutError(e);
|
|
161
147
|
const rl = isRateLimitError(e);
|
|
@@ -244,29 +230,33 @@ var RpsLimiter = class {
|
|
|
244
230
|
};
|
|
245
231
|
|
|
246
232
|
// src/InstrumentedProvider.ts
|
|
247
|
-
var
|
|
233
|
+
var InstrumentedJsonRpcProvider = class extends import_ethers.JsonRpcProvider {
|
|
248
234
|
providerId;
|
|
249
235
|
chainId;
|
|
250
|
-
|
|
236
|
+
options;
|
|
251
237
|
inFlightLimiter;
|
|
252
238
|
rpsLimiter;
|
|
253
239
|
stats;
|
|
254
240
|
fetchRequest;
|
|
255
241
|
lastCooldownMs = 0;
|
|
256
|
-
constructor(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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 });
|
|
262
252
|
this.fetchRequest = fetchRequest;
|
|
263
|
-
this.providerId = providerId;
|
|
264
|
-
this.chainId = chainId;
|
|
265
|
-
this.
|
|
266
|
-
const { rps = 10, rpsBurst, inFlight = 1 } =
|
|
253
|
+
this.providerId = options.providerId;
|
|
254
|
+
this.chainId = _network.chainId;
|
|
255
|
+
this.options = options;
|
|
256
|
+
const { rps = 10, rpsBurst, inFlight = 1 } = options;
|
|
267
257
|
this.inFlightLimiter = new Semaphore(inFlight);
|
|
268
258
|
this.rpsLimiter = new RpsLimiter(rps, rpsBurst || rps);
|
|
269
|
-
this.stats =
|
|
259
|
+
this.stats = options.stats;
|
|
270
260
|
}
|
|
271
261
|
async _send(payload) {
|
|
272
262
|
await this.rpsLimiter.take(1);
|
|
@@ -286,7 +276,7 @@ var InstrumentedStaticJsonRpcProvider = class extends import_ethers.JsonRpcProvi
|
|
|
286
276
|
this.stats.bumpProviderTotal(this.providerId);
|
|
287
277
|
for (const p of payloads) {
|
|
288
278
|
this.stats.bumpPerMethod(p.method);
|
|
289
|
-
this.
|
|
279
|
+
this.options.onEvent?.({
|
|
290
280
|
type: "request",
|
|
291
281
|
chainId: this.chainId,
|
|
292
282
|
providerId: this.providerId,
|
|
@@ -295,14 +285,10 @@ var InstrumentedStaticJsonRpcProvider = class extends import_ethers.JsonRpcProvi
|
|
|
295
285
|
});
|
|
296
286
|
}
|
|
297
287
|
try {
|
|
298
|
-
const
|
|
299
|
-
const res = await withTimeout(base, this.params.timeout || 1e4, {
|
|
300
|
-
chainId: this.chainId,
|
|
301
|
-
providerId: this.providerId
|
|
302
|
-
});
|
|
288
|
+
const res = await super._send(payload);
|
|
303
289
|
const endedAt = Date.now();
|
|
304
290
|
for (const p of payloads) {
|
|
305
|
-
this.
|
|
291
|
+
this.options.onEvent?.({
|
|
306
292
|
type: "response",
|
|
307
293
|
chainId: this.chainId,
|
|
308
294
|
providerId: this.providerId,
|
|
@@ -343,7 +329,7 @@ var InstrumentedStaticJsonRpcProvider = class extends import_ethers.JsonRpcProvi
|
|
|
343
329
|
this.stats.setCooldown(this.providerId, cooldownMs);
|
|
344
330
|
}
|
|
345
331
|
for (const p of payloads) {
|
|
346
|
-
this.
|
|
332
|
+
this.options.onEvent?.({
|
|
347
333
|
type: "error",
|
|
348
334
|
chainId: this.chainId,
|
|
349
335
|
providerId: this.providerId,
|
|
@@ -392,22 +378,18 @@ var RPCPoolProvider = class extends import_ethers2.JsonRpcProvider {
|
|
|
392
378
|
params;
|
|
393
379
|
stats;
|
|
394
380
|
constructor(params) {
|
|
395
|
-
const network = import_ethers2.Network.from(params.
|
|
381
|
+
const network = import_ethers2.Network.from(params.network);
|
|
396
382
|
super("http://localhost", network, { staticNetwork: network });
|
|
397
383
|
this.params = params;
|
|
398
384
|
this.stats = new Stats();
|
|
399
|
-
const endpoints = this.params.
|
|
400
|
-
const
|
|
401
|
-
const
|
|
402
|
-
const provider = new
|
|
403
|
-
url,
|
|
404
|
-
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, {
|
|
405
389
|
providerId,
|
|
406
390
|
stats: this.stats,
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
rpsBurst,
|
|
410
|
-
timeout,
|
|
391
|
+
...this.params.defaultRpcOptions,
|
|
392
|
+
...options,
|
|
411
393
|
onEvent: this.params.hooks?.onEvent
|
|
412
394
|
});
|
|
413
395
|
return { providerId, url, provider };
|
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 { 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\n/**\n * Wraps a promise with a timeout (useful when an RPC hangs without emitting TIMEOUT).\n * Important: this does not cancel the network request, but you get a controlled error\n * and FallbackProvider can switch to another RPC.\n */\nexport function withTimeout<T>(\n p: Promise<T>,\n ms: number,\n meta?: { chainId?: number; providerId?: string; method?: string },\n): Promise<T> {\n let t: NodeJS.Timeout | undefined;\n\n const timeout = new Promise<T>((_, reject) => {\n t = setTimeout(() => {\n const err: any = new Error(\n `RPC timeout after ${ms}ms` +\n (meta?.method ? ` method=${meta.method}` : '') +\n (meta?.providerId ? ` provider=${meta.providerId}` : '') +\n (meta?.chainId != null ? ` chainId=${meta.chainId}` : ''),\n );\n err.code = 'TIMEOUT';\n err.timeout = ms;\n reject(err);\n }, ms);\n });\n\n return Promise.race([p, timeout]).finally(() => t && clearTimeout(t));\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 withTimeout,\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 });\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 base = super._send(payload);\n const res = await withTimeout(base, this.params.timeout || 10_000, {\n chainId: this.chainId,\n providerId: this.providerId,\n });\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
|
|
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
|
|
77
|
+
* Instrumented JsonRpcProvider.
|
|
80
78
|
* Tracks requests, inFlight count, rate limits, and per-method / per-provider metrics.
|
|
81
79
|
*/
|
|
82
|
-
declare class
|
|
80
|
+
declare class InstrumentedJsonRpcProvider extends JsonRpcProvider {
|
|
83
81
|
readonly providerId: string;
|
|
84
|
-
readonly chainId:
|
|
85
|
-
readonly
|
|
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(
|
|
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:
|
|
97
|
+
provider: InstrumentedJsonRpcProvider;
|
|
100
98
|
}
|
|
101
99
|
type RpcEvent = {
|
|
102
100
|
type: 'request';
|
|
103
|
-
chainId:
|
|
101
|
+
chainId: bigint;
|
|
104
102
|
providerId: string;
|
|
105
103
|
method: string;
|
|
106
104
|
startedAt: number;
|
|
107
105
|
} | {
|
|
108
106
|
type: 'response';
|
|
109
|
-
chainId:
|
|
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:
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
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
|
|
77
|
+
* Instrumented JsonRpcProvider.
|
|
80
78
|
* Tracks requests, inFlight count, rate limits, and per-method / per-provider metrics.
|
|
81
79
|
*/
|
|
82
|
-
declare class
|
|
80
|
+
declare class InstrumentedJsonRpcProvider extends JsonRpcProvider {
|
|
83
81
|
readonly providerId: string;
|
|
84
|
-
readonly chainId:
|
|
85
|
-
readonly
|
|
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(
|
|
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:
|
|
97
|
+
provider: InstrumentedJsonRpcProvider;
|
|
100
98
|
}
|
|
101
99
|
type RpcEvent = {
|
|
102
100
|
type: 'request';
|
|
103
|
-
chainId:
|
|
101
|
+
chainId: bigint;
|
|
104
102
|
providerId: string;
|
|
105
103
|
method: string;
|
|
106
104
|
startedAt: number;
|
|
107
105
|
} | {
|
|
108
106
|
type: 'response';
|
|
109
|
-
chainId:
|
|
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:
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
@@ -116,20 +116,6 @@ function isTimeoutError(e) {
|
|
|
116
116
|
const msg = String(e?.message || e);
|
|
117
117
|
return /timeout|timed out|ETIMEDOUT|ESOCKETTIMEDOUT|ECONNABORTED|504 Gateway/i.test(msg);
|
|
118
118
|
}
|
|
119
|
-
function withTimeout(p, ms, meta) {
|
|
120
|
-
let t;
|
|
121
|
-
const timeout = new Promise((_, reject) => {
|
|
122
|
-
t = setTimeout(() => {
|
|
123
|
-
const err = new Error(
|
|
124
|
-
`RPC timeout after ${ms}ms` + (meta?.method ? ` method=${meta.method}` : "") + (meta?.providerId ? ` provider=${meta.providerId}` : "") + (meta?.chainId != null ? ` chainId=${meta.chainId}` : "")
|
|
125
|
-
);
|
|
126
|
-
err.code = "TIMEOUT";
|
|
127
|
-
err.timeout = ms;
|
|
128
|
-
reject(err);
|
|
129
|
-
}, ms);
|
|
130
|
-
});
|
|
131
|
-
return Promise.race([p, timeout]).finally(() => t && clearTimeout(t));
|
|
132
|
-
}
|
|
133
119
|
function shouldFailover(e) {
|
|
134
120
|
const to = isTimeoutError(e);
|
|
135
121
|
const rl = isRateLimitError(e);
|
|
@@ -138,7 +124,11 @@ function shouldFailover(e) {
|
|
|
138
124
|
}
|
|
139
125
|
|
|
140
126
|
// src/InstrumentedProvider.ts
|
|
141
|
-
import {
|
|
127
|
+
import {
|
|
128
|
+
JsonRpcProvider,
|
|
129
|
+
Network,
|
|
130
|
+
FetchRequest
|
|
131
|
+
} from "ethers";
|
|
142
132
|
|
|
143
133
|
// src/Semaphore.ts
|
|
144
134
|
var Semaphore = class {
|
|
@@ -218,29 +208,33 @@ var RpsLimiter = class {
|
|
|
218
208
|
};
|
|
219
209
|
|
|
220
210
|
// src/InstrumentedProvider.ts
|
|
221
|
-
var
|
|
211
|
+
var InstrumentedJsonRpcProvider = class extends JsonRpcProvider {
|
|
222
212
|
providerId;
|
|
223
213
|
chainId;
|
|
224
|
-
|
|
214
|
+
options;
|
|
225
215
|
inFlightLimiter;
|
|
226
216
|
rpsLimiter;
|
|
227
217
|
stats;
|
|
228
218
|
fetchRequest;
|
|
229
219
|
lastCooldownMs = 0;
|
|
230
|
-
constructor(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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 });
|
|
236
230
|
this.fetchRequest = fetchRequest;
|
|
237
|
-
this.providerId = providerId;
|
|
238
|
-
this.chainId = chainId;
|
|
239
|
-
this.
|
|
240
|
-
const { rps = 10, rpsBurst, inFlight = 1 } =
|
|
231
|
+
this.providerId = options.providerId;
|
|
232
|
+
this.chainId = _network.chainId;
|
|
233
|
+
this.options = options;
|
|
234
|
+
const { rps = 10, rpsBurst, inFlight = 1 } = options;
|
|
241
235
|
this.inFlightLimiter = new Semaphore(inFlight);
|
|
242
236
|
this.rpsLimiter = new RpsLimiter(rps, rpsBurst || rps);
|
|
243
|
-
this.stats =
|
|
237
|
+
this.stats = options.stats;
|
|
244
238
|
}
|
|
245
239
|
async _send(payload) {
|
|
246
240
|
await this.rpsLimiter.take(1);
|
|
@@ -260,7 +254,7 @@ var InstrumentedStaticJsonRpcProvider = class extends JsonRpcProvider {
|
|
|
260
254
|
this.stats.bumpProviderTotal(this.providerId);
|
|
261
255
|
for (const p of payloads) {
|
|
262
256
|
this.stats.bumpPerMethod(p.method);
|
|
263
|
-
this.
|
|
257
|
+
this.options.onEvent?.({
|
|
264
258
|
type: "request",
|
|
265
259
|
chainId: this.chainId,
|
|
266
260
|
providerId: this.providerId,
|
|
@@ -269,14 +263,10 @@ var InstrumentedStaticJsonRpcProvider = class extends JsonRpcProvider {
|
|
|
269
263
|
});
|
|
270
264
|
}
|
|
271
265
|
try {
|
|
272
|
-
const
|
|
273
|
-
const res = await withTimeout(base, this.params.timeout || 1e4, {
|
|
274
|
-
chainId: this.chainId,
|
|
275
|
-
providerId: this.providerId
|
|
276
|
-
});
|
|
266
|
+
const res = await super._send(payload);
|
|
277
267
|
const endedAt = Date.now();
|
|
278
268
|
for (const p of payloads) {
|
|
279
|
-
this.
|
|
269
|
+
this.options.onEvent?.({
|
|
280
270
|
type: "response",
|
|
281
271
|
chainId: this.chainId,
|
|
282
272
|
providerId: this.providerId,
|
|
@@ -317,7 +307,7 @@ var InstrumentedStaticJsonRpcProvider = class extends JsonRpcProvider {
|
|
|
317
307
|
this.stats.setCooldown(this.providerId, cooldownMs);
|
|
318
308
|
}
|
|
319
309
|
for (const p of payloads) {
|
|
320
|
-
this.
|
|
310
|
+
this.options.onEvent?.({
|
|
321
311
|
type: "error",
|
|
322
312
|
chainId: this.chainId,
|
|
323
313
|
providerId: this.providerId,
|
|
@@ -366,22 +356,18 @@ var RPCPoolProvider = class extends JsonRpcProvider2 {
|
|
|
366
356
|
params;
|
|
367
357
|
stats;
|
|
368
358
|
constructor(params) {
|
|
369
|
-
const network = Network2.from(params.
|
|
359
|
+
const network = Network2.from(params.network);
|
|
370
360
|
super("http://localhost", network, { staticNetwork: network });
|
|
371
361
|
this.params = params;
|
|
372
362
|
this.stats = new Stats();
|
|
373
|
-
const endpoints = this.params.
|
|
374
|
-
const
|
|
375
|
-
const
|
|
376
|
-
const provider = new
|
|
377
|
-
url,
|
|
378
|
-
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, {
|
|
379
367
|
providerId,
|
|
380
368
|
stats: this.stats,
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
rpsBurst,
|
|
384
|
-
timeout,
|
|
369
|
+
...this.params.defaultRpcOptions,
|
|
370
|
+
...options,
|
|
385
371
|
onEvent: this.params.hooks?.onEvent
|
|
386
372
|
});
|
|
387
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\n/**\n * Wraps a promise with a timeout (useful when an RPC hangs without emitting TIMEOUT).\n * Important: this does not cancel the network request, but you get a controlled error\n * and FallbackProvider can switch to another RPC.\n */\nexport function withTimeout<T>(\n p: Promise<T>,\n ms: number,\n meta?: { chainId?: number; providerId?: string; method?: string },\n): Promise<T> {\n let t: NodeJS.Timeout | undefined;\n\n const timeout = new Promise<T>((_, reject) => {\n t = setTimeout(() => {\n const err: any = new Error(\n `RPC timeout after ${ms}ms` +\n (meta?.method ? ` method=${meta.method}` : '') +\n (meta?.providerId ? ` provider=${meta.providerId}` : '') +\n (meta?.chainId != null ? ` chainId=${meta.chainId}` : ''),\n );\n err.code = 'TIMEOUT';\n err.timeout = ms;\n reject(err);\n }, ms);\n });\n\n return Promise.race([p, timeout]).finally(() => t && clearTimeout(t));\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 withTimeout,\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 });\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 base = super._send(payload);\n const res = await withTimeout(base, this.params.timeout || 10_000, {\n chainId: this.chainId,\n providerId: this.providerId,\n });\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
|
|
3
|
+
"version": "1.1.0",
|
|
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": [
|
|
@@ -57,10 +57,8 @@
|
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/node": "^25.2.3",
|
|
60
|
-
"express": "^5.2.1",
|
|
61
60
|
"husky": "^9.1.7",
|
|
62
61
|
"lint-staged": "^16.2.7",
|
|
63
|
-
"msw": "^2.12.10",
|
|
64
62
|
"prettier": "^3.8.1",
|
|
65
63
|
"tsup": "^8.5.1",
|
|
66
64
|
"tsx": "^4.21.0",
|