envio 3.15.0 → 3.16.0-subgraph

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.
Files changed (186) hide show
  1. package/README.md +40 -34
  2. package/evm.schema.json +11 -2
  3. package/fuel.schema.json +10 -1
  4. package/index.d.ts +6 -0
  5. package/licenses/README.md +1 -1
  6. package/package.json +10 -9
  7. package/src/Batch.res.mjs +5 -5
  8. package/src/BatchProcessing.res +1 -0
  9. package/src/BatchProcessing.res.mjs +1 -0
  10. package/src/Bin.res +7 -0
  11. package/src/Bin.res.mjs +8 -0
  12. package/src/ChainFetching.res +450 -16
  13. package/src/ChainFetching.res.mjs +403 -32
  14. package/src/ChainState.res +124 -7
  15. package/src/ChainState.res.mjs +128 -67
  16. package/src/ChainState.resi +26 -0
  17. package/src/Clock.res +57 -0
  18. package/src/Clock.res.mjs +72 -0
  19. package/src/Config.res +31 -3
  20. package/src/Config.res.mjs +15 -4
  21. package/src/ContractRegisterContext.res +3 -6
  22. package/src/ContractRegisterContext.res.mjs +4 -2
  23. package/src/Core.res +95 -36
  24. package/src/Core.res.mjs +90 -1
  25. package/src/CrossChainState.res +12 -2
  26. package/src/CrossChainState.res.mjs +3 -3
  27. package/src/Ecosystem.res +7 -4
  28. package/src/Ecosystem.res.mjs +8 -8
  29. package/src/EffectCacheLookup.res +98 -0
  30. package/src/EffectCacheLookup.res.mjs +116 -0
  31. package/src/EffectState.res +10 -2
  32. package/src/EffectState.res.mjs +8 -4
  33. package/src/EffectState.resi +3 -1
  34. package/src/EntityId.res +2 -7
  35. package/src/EntityId.res.mjs +1 -8
  36. package/src/EntityTables.res +4 -1
  37. package/src/EntityTables.res.mjs +1 -1
  38. package/src/Env.res +20 -0
  39. package/src/Env.res.mjs +16 -0
  40. package/src/EventProcessing.res +38 -1
  41. package/src/EventProcessing.res.mjs +28 -1
  42. package/src/FetchState.res +408 -73
  43. package/src/FetchState.res.mjs +361 -91
  44. package/src/FinalizeBackfill.res +2 -4
  45. package/src/FinalizeBackfill.res.mjs +3 -4
  46. package/src/HandlerLoader.res +39 -15
  47. package/src/HandlerLoader.res.mjs +27 -8
  48. package/src/HandlerRegister.res +4 -0
  49. package/src/HandlerRegister.res.mjs +5 -2
  50. package/src/HandlerRegister.resi +2 -0
  51. package/src/InMemoryTable.res +17 -16
  52. package/src/InMemoryTable.res.mjs +14 -15
  53. package/src/IndexerState.res +28 -4
  54. package/src/IndexerState.res.mjs +19 -6
  55. package/src/IndexerState.resi +4 -0
  56. package/src/Internal.res +61 -49
  57. package/src/Internal.res.mjs +36 -80
  58. package/src/LoadLayer.res +54 -15
  59. package/src/LoadLayer.res.mjs +75 -45
  60. package/src/LoadLayer.resi +26 -0
  61. package/src/Main.res +14 -1
  62. package/src/Main.res.mjs +4 -3
  63. package/src/Persistence.res +3 -23
  64. package/src/Persistence.res.mjs +3 -3
  65. package/src/PreloadLookahead.res +202 -0
  66. package/src/PreloadLookahead.res.mjs +218 -0
  67. package/src/Rollback.res +0 -1
  68. package/src/Rollback.res.mjs +1 -1
  69. package/src/RunScope.res +3 -0
  70. package/src/SafeCheckpointTracking.res +4 -4
  71. package/src/SafeCheckpointTracking.res.mjs +1 -1
  72. package/src/SimulateItems.res +42 -26
  73. package/src/SimulateItems.res.mjs +40 -16
  74. package/src/StagedRows.res +106 -0
  75. package/src/StagedRows.res.mjs +102 -0
  76. package/src/Staging.res +32 -14
  77. package/src/Staging.res.mjs +17 -20
  78. package/src/Storage.res +303 -948
  79. package/src/Storage.res.mjs +229 -708
  80. package/src/SubgraphLog.res +19 -0
  81. package/src/SubgraphLog.res.mjs +26 -0
  82. package/src/Supervisor.res +75 -56
  83. package/src/Supervisor.res.mjs +55 -26
  84. package/src/SyncWrites.res +107 -0
  85. package/src/SyncWrites.res.mjs +98 -0
  86. package/src/TestIndexer.res +22 -27
  87. package/src/TestIndexer.res.mjs +20 -17
  88. package/src/Throttler.res +15 -8
  89. package/src/Throttler.res.mjs +9 -6
  90. package/src/Throttler.resi +1 -1
  91. package/src/UserContext.res +419 -46
  92. package/src/UserContext.res.mjs +318 -30
  93. package/src/Utils.res +17 -6
  94. package/src/Utils.res.mjs +12 -6
  95. package/src/Writing.res +0 -1
  96. package/src/Writing.res.mjs +1 -1
  97. package/src/bindings/ClickHouse.res +0 -322
  98. package/src/bindings/ClickHouse.res.mjs +0 -338
  99. package/src/bindings/StorageClient.res +108 -174
  100. package/src/bindings/StorageClient.res.mjs +19 -8
  101. package/src/bindings/Vitest.res +0 -26
  102. package/src/bindings/Vitest.res.mjs +0 -3
  103. package/src/db/Table.res +24 -150
  104. package/src/db/Table.res.mjs +38 -272
  105. package/src/sources/AddressSet.res +2 -0
  106. package/src/sources/AddressStore.res +3 -0
  107. package/src/sources/CallSource.res +274 -0
  108. package/src/sources/CallSource.res.mjs +196 -0
  109. package/src/sources/ChainSources.res +2 -0
  110. package/src/sources/ChainSources.res.mjs +2 -2
  111. package/src/sources/EvmChain.res +10 -1
  112. package/src/sources/EvmChain.res.mjs +6 -3
  113. package/src/sources/EvmHyperSyncSource.res +2 -2
  114. package/src/sources/EvmHyperSyncSource.res.mjs +1 -1
  115. package/src/sources/EvmRpcClient.res +13 -3
  116. package/src/sources/EvmRpcClient.res.mjs +4 -3
  117. package/src/sources/EvmRpcWs.res +4 -3
  118. package/src/sources/EvmRpcWs.res.mjs +2 -2
  119. package/src/sources/HeightFeed.res +31 -10
  120. package/src/sources/HeightFeed.res.mjs +10 -6
  121. package/src/sources/HeightStream.res +32 -17
  122. package/src/sources/HeightStream.res.mjs +10 -10
  123. package/src/sources/HyperSync.res +2 -1
  124. package/src/sources/HyperSync.res.mjs +2 -1
  125. package/src/sources/HyperSyncSSE.res +4 -6
  126. package/src/sources/HyperSyncSSE.res.mjs +2 -2
  127. package/src/sources/RpcSource.res +10 -1
  128. package/src/sources/RpcSource.res.mjs +4 -3
  129. package/src/sources/Source.res +3 -0
  130. package/src/sources/SourceManager.res +84 -77
  131. package/src/sources/SourceManager.res.mjs +17 -20
  132. package/src/sources/SourceManager.resi +3 -4
  133. package/src/sources/StartBlockResolver.res +1 -0
  134. package/src/sources/StartBlockResolver.res.mjs +3 -2
  135. package/src/sources/SvmHyperSyncSource.res +2 -2
  136. package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
  137. package/src/subgraph/abi-types.ts +84 -0
  138. package/src/subgraph/aggregations.ts +745 -0
  139. package/src/subgraph/as-package.ts +143 -0
  140. package/src/subgraph/assemblyscript-regex.ts +67 -0
  141. package/src/subgraph/assemblyscript.ts +731 -0
  142. package/src/subgraph/blocks.ts +398 -0
  143. package/src/subgraph/calls-source.ts +702 -0
  144. package/src/subgraph/calls.ts +258 -0
  145. package/src/subgraph/conformance.ts +106 -0
  146. package/src/subgraph/errors.ts +85 -0
  147. package/src/subgraph/graft.ts +73 -0
  148. package/src/subgraph/graph-cli-worker.mjs +35 -0
  149. package/src/subgraph/graph-cli.ts +900 -0
  150. package/src/subgraph/graph-node-decimal.ts +199 -0
  151. package/src/subgraph/graph-ts-enums.ts +28 -0
  152. package/src/subgraph/graph-ts-types/VERSION +2 -0
  153. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
  154. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
  155. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
  156. package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
  157. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
  158. package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
  159. package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
  160. package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
  161. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  162. package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
  163. package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
  164. package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
  165. package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
  166. package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
  167. package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
  168. package/src/subgraph/graph-ts-types/index.d.ts +102 -0
  169. package/src/subgraph/graph-ts.ts +4339 -0
  170. package/src/subgraph/hosts.ts +554 -0
  171. package/src/subgraph/log.ts +28 -0
  172. package/src/subgraph/receipts.ts +184 -0
  173. package/src/subgraph/register-slots.ts +93 -0
  174. package/src/subgraph/rollup.ts +109 -0
  175. package/src/subgraph/rpc.ts +337 -0
  176. package/src/subgraph/runtime.ts +2059 -0
  177. package/src/subgraph/scope.ts +176 -0
  178. package/src/subgraph/stats.ts +42 -0
  179. package/src/subgraph/template-instances.ts +66 -0
  180. package/src/subgraph/toolchain.ts +327 -0
  181. package/src/subgraph/transient.ts +176 -0
  182. package/svm.schema.json +11 -2
  183. package/src/PgWriting.res +0 -36
  184. package/src/PgWriting.res.mjs +0 -34
  185. package/src/db/IndexDefinition.res +0 -47
  186. package/src/db/IndexDefinition.res.mjs +0 -67
@@ -0,0 +1,337 @@
1
+ /**
2
+ * The one client every RPC read of the subgraph runtime goes through: contract
3
+ * calls, `getBalance`/`hasCode` and block timestamps.
4
+ *
5
+ * Preload runs a batch's handlers at once, and with them every read they make.
6
+ * Unbounded, that is one open connection per read — thousands on a factory's
7
+ * backfill, until the endpoint stops answering and the batch never completes.
8
+ * So requests beyond a fixed number wait for one in flight to finish; each is
9
+ * short, and a queue drains far faster than a refused connection retries.
10
+ */
11
+
12
+ import { HttpStatusError, isRateLimit, isRevert, requestTimeoutMs, withDeadline } from "./transient.ts";
13
+
14
+ /**
15
+ * Requests in flight at once, per set of endpoints. A handler's calls run one
16
+ * after another (a decimals call, then a name, then a price), so a batch's
17
+ * throughput is this many divided by the endpoint's latency: at 16 and a
18
+ * second a call — a shared or distant endpoint — a batch of five thousand
19
+ * events took minutes to commit. `ENVIO_SUBGRAPH_RPC_CONCURRENCY` overrides it.
20
+ */
21
+ const DEFAULT_MAX_IN_FLIGHT = 48;
22
+
23
+ function maxInFlightFromEnv(): number {
24
+ const value = Number(process.env.ENVIO_SUBGRAPH_RPC_CONCURRENCY);
25
+ return Number.isInteger(value) && value > 0 ? value : DEFAULT_MAX_IN_FLIGHT;
26
+ }
27
+
28
+ /**
29
+ * Most requests one HTTP request carries as a JSON-RPC batch. Requests that
30
+ * queue while every connection is busy leave together, so a backfill's calls
31
+ * take a round trip per batch rather than per call. `ENVIO_SUBGRAPH_RPC_BATCH`
32
+ * overrides it; 1 sends every request on its own.
33
+ */
34
+ const DEFAULT_MAX_BATCH = 32;
35
+
36
+ function maxBatchFromEnv(): number {
37
+ const value = Number(process.env.ENVIO_SUBGRAPH_RPC_BATCH);
38
+ return Number.isInteger(value) && value > 0 ? value : DEFAULT_MAX_BATCH;
39
+ }
40
+
41
+ /**
42
+ * Runs at most `maxInFlight` requests at once, queueing the rest. A slot is
43
+ * held at most the request deadline: one that never settles would otherwise
44
+ * keep it for good, and a handful of those stall every call behind them with
45
+ * no connection doing anything.
46
+ */
47
+ export function gate(maxInFlight = maxInFlightFromEnv()) {
48
+ let inFlight = 0;
49
+ const queued: (() => void)[] = [];
50
+ return async <T>(request: () => Promise<T>): Promise<T> => {
51
+ if (inFlight < maxInFlight) inFlight++;
52
+ // A finishing request hands its slot straight over.
53
+ else await new Promise<void>((resolve) => queued.push(resolve));
54
+ try {
55
+ return await withDeadline("The RPC request", request);
56
+ } finally {
57
+ const next = queued.shift();
58
+ if (next) next();
59
+ else inFlight--;
60
+ }
61
+ };
62
+ }
63
+
64
+ type Body = { method: string; params?: unknown };
65
+ type RpcErrorBody = { code: number; message: string; data?: unknown };
66
+ type Answer = { result?: unknown; error?: RpcErrorBody };
67
+
68
+ /** A JSON-RPC error the endpoint answered with: the request's own outcome. */
69
+ export class RpcError extends Error {
70
+ readonly code: number;
71
+ readonly data: unknown;
72
+ constructor(
73
+ readonly url: string,
74
+ method: string,
75
+ error: RpcErrorBody,
76
+ ) {
77
+ super(`${method} on ${url} failed: ${error.message}`);
78
+ this.code = error.code;
79
+ this.data = error.data;
80
+ }
81
+ }
82
+
83
+ /** One HTTP request to the endpoint, bounded by the request deadline. */
84
+ async function post(url: string, body: unknown): Promise<unknown> {
85
+ const response = await fetch(url, {
86
+ method: "POST",
87
+ headers: { "content-type": "application/json" },
88
+ body: JSON.stringify(body),
89
+ signal: AbortSignal.timeout(requestTimeoutMs()),
90
+ });
91
+ if (!response.ok) {
92
+ const text = await response.text().catch(() => "");
93
+ throw new HttpStatusError(url, response.status, text.slice(0, 200));
94
+ }
95
+ return response.json();
96
+ }
97
+ type Waiting = { body: Body; resolve: (answer: Answer) => void; reject: (error: unknown) => void; missing: number };
98
+
99
+ /**
100
+ * An endpoint's own words for "this batch is too big" (dRPC's free plan:
101
+ * "Batch of more than 3 requests are not allowed"), said per item.
102
+ */
103
+ function isBatchLimit(error: Answer["error"]): boolean {
104
+ const message = String(error?.message ?? "");
105
+ return /batch/i.test(message) && /more than|too (many|large|big)|exceed|limit|not allowed|maximum|size/i.test(message);
106
+ }
107
+
108
+ /** The size such a message allows, when it says. */
109
+ function allowedBatch(error: Answer["error"]): number | undefined {
110
+ const match = /more than (\d+)|(?:maximum|max|limit)[^\d]{0,20}(\d+)/i.exec(String(error?.message ?? ""));
111
+ const value = Number(match?.[1] ?? match?.[2]);
112
+ return Number.isInteger(value) && value > 0 ? value : undefined;
113
+ }
114
+
115
+ /** Times a batch item may come back without its answer before it fails on its own. */
116
+ const MAX_MISSING = 3;
117
+
118
+ /**
119
+ * One endpoint, `maxInFlight` HTTP requests at a time, each carrying up to the
120
+ * batch size the endpoint takes. Every request keeps exactly the answer it
121
+ * would get sent alone: items are matched to their answers by id, a JSON-RPC
122
+ * error stays that item's error (a revert reaches only the call that
123
+ * reverted), and HTTP failures fail every item they carried, to be asked
124
+ * again by `untilAnswered`. An endpoint that refuses a batch — HTTP 4xx, an
125
+ * error in place of the array, or a "batch too large" answer per item — gets
126
+ * smaller ones from then on, down to plain single requests.
127
+ *
128
+ * A public endpoint answers 429 past the few requests it serves at once, and
129
+ * at 48 in flight most of every wave was refused, each refused call backing
130
+ * off alone for longer and longer. So the requests in flight, and the calls a
131
+ * batch carries, are halved when the endpoint says "slow down" — once per
132
+ * wave: refusals of requests sent before the last cut don't cut again — and
133
+ * grow back by one per full round of answers (a batch: per answered batch), up
134
+ * to `maxInFlight` and the batch size the endpoint takes.
135
+ */
136
+ function batchingTransport(url: string, maxInFlight: number, maxBatch: number): (body: Body) => Promise<unknown> {
137
+ // What the endpoint takes in one batch, and what is sent for now.
138
+ let batchCap = maxBatch;
139
+ let batchSize = maxBatch;
140
+ let limit = maxInFlight;
141
+ let wave = 0;
142
+ let answeredSinceGrowth = 0;
143
+ let inFlight = 0;
144
+ let pumpScheduled = false;
145
+ let nextId = 1;
146
+ const queue: Waiting[] = [];
147
+
148
+ const shrink = (to?: number) => {
149
+ batchCap = Math.max(1, Math.min(to ?? Math.floor(batchCap / 2), batchCap - 1));
150
+ batchSize = Math.min(batchSize, batchCap);
151
+ };
152
+
153
+ const slowDown = (sentIn: number) => {
154
+ if (sentIn !== wave) return;
155
+ wave++;
156
+ limit = Math.max(1, Math.floor(limit / 2));
157
+ batchSize = Math.max(1, Math.floor(batchSize / 2));
158
+ answeredSinceGrowth = 0;
159
+ };
160
+
161
+ const answered = () => {
162
+ if (limit < maxInFlight && ++answeredSinceGrowth >= limit) {
163
+ limit++;
164
+ answeredSinceGrowth = 0;
165
+ }
166
+ };
167
+
168
+ const send = async (items: Waiting[]) => {
169
+ const sentIn = wave;
170
+ if (items.length === 1) {
171
+ const [item] = items;
172
+ try {
173
+ const answer = (await post(url, { jsonrpc: "2.0", id: nextId++, ...item.body })) as Answer;
174
+ if (answer.error && isRateLimit(answer.error)) slowDown(sentIn);
175
+ else answered();
176
+ item.resolve(answer);
177
+ } catch (error) {
178
+ if (isRateLimit(error)) slowDown(sentIn);
179
+ item.reject(error);
180
+ }
181
+ return;
182
+ }
183
+ const ids = items.map(() => nextId++);
184
+ let data: unknown;
185
+ try {
186
+ data = await post(
187
+ url,
188
+ items.map((item, i) => ({ jsonrpc: "2.0", id: ids[i], ...item.body })),
189
+ );
190
+ } catch (error) {
191
+ if (isRateLimit(error)) slowDown(sentIn);
192
+ const status = (error as { status?: unknown }).status;
193
+ if (typeof status === "number" && status >= 400 && status < 500 && status !== 408 && status !== 429) {
194
+ shrink();
195
+ queue.unshift(...items);
196
+ return;
197
+ }
198
+ for (const item of items) item.reject(error);
199
+ return;
200
+ }
201
+ if (!Array.isArray(data)) {
202
+ // An error in place of the array: the endpoint takes no batch this big.
203
+ shrink();
204
+ queue.unshift(...items);
205
+ return;
206
+ }
207
+ answered();
208
+ if (batchSize < batchCap) batchSize++;
209
+ const byId = new Map<unknown, Answer>();
210
+ for (const answer of data) if (answer && typeof answer === "object") byId.set((answer as any).id, answer as Answer);
211
+ const again: Waiting[] = [];
212
+ items.forEach((item, i) => {
213
+ const answer = byId.get(ids[i]);
214
+ if (answer === undefined) {
215
+ if (++item.missing >= MAX_MISSING) {
216
+ item.reject(new Error(`${url} answered a batch without the answer to ${item.body.method}; asking again.`));
217
+ } else again.push(item);
218
+ return;
219
+ }
220
+ if (answer.error && isBatchLimit(answer.error)) {
221
+ shrink(allowedBatch(answer.error));
222
+ again.push(item);
223
+ return;
224
+ }
225
+ if (answer.error && isRateLimit(answer.error)) slowDown(sentIn);
226
+ item.resolve(answer);
227
+ });
228
+ if (again.length > 0) queue.unshift(...again);
229
+ };
230
+
231
+ const schedule = () => {
232
+ if (!pumpScheduled) {
233
+ pumpScheduled = true;
234
+ setImmediate(pump);
235
+ }
236
+ };
237
+
238
+ const pump = () => {
239
+ pumpScheduled = false;
240
+ while (inFlight < limit && queue.length > 0) {
241
+ const items = queue.splice(0, batchSize);
242
+ inFlight++;
243
+ void send(items).finally(() => {
244
+ inFlight--;
245
+ pump();
246
+ });
247
+ }
248
+ };
249
+
250
+ return async (body: Body) => {
251
+ const answer = await new Promise<Answer>((resolve, reject) => {
252
+ queue.push({ body, resolve, reject, missing: 0 });
253
+ // Requests made in the same turn — preload's calls, a batch's
254
+ // timestamps — leave together.
255
+ schedule();
256
+ });
257
+ if (answer.error) throw new RpcError(url, body.method, answer.error);
258
+ return answer.result;
259
+ };
260
+ }
261
+
262
+ const toHex = (value: number | bigint) => `0x${BigInt(value).toString(16)}`;
263
+
264
+ /** The reads the runtime makes, over the endpoints in the order given. */
265
+ export type RpcClient = {
266
+ request(body: Body): Promise<any>;
267
+ /** The call's return data. */
268
+ call(args: { to: string; data: string; blockNumber: number | bigint }): Promise<string>;
269
+ getBalance(args: { address: string; blockNumber: number | bigint }): Promise<bigint>;
270
+ getCode(args: { address: string; blockNumber: number | bigint }): Promise<string>;
271
+ /** The block as the endpoint spells it, its numbers hex. */
272
+ getBlock(args: { blockNumber: number | bigint }): Promise<Record<string, any>>;
273
+ getTransactionReceipt(args: { hash: string }): Promise<Record<string, any>>;
274
+ };
275
+
276
+ function makeClient(transports: ((body: Body) => Promise<unknown>)[]): RpcClient {
277
+ // The next endpoint is asked whatever the first answered, except a revert:
278
+ // every node gives that one the same.
279
+ const request = async (body: Body): Promise<any> => {
280
+ let failure: unknown;
281
+ for (const transport of transports) {
282
+ try {
283
+ return await transport(body);
284
+ } catch (error) {
285
+ if (error instanceof RpcError && isRevert(error)) throw error;
286
+ failure = error;
287
+ }
288
+ }
289
+ throw failure;
290
+ };
291
+ return {
292
+ request,
293
+ call: ({ to, data, blockNumber }) =>
294
+ request({ method: "eth_call", params: [{ to, data }, toHex(blockNumber)] }).then(
295
+ (result) => (result ?? "0x") as string,
296
+ ),
297
+ getBalance: ({ address, blockNumber }) =>
298
+ request({ method: "eth_getBalance", params: [address, toHex(blockNumber)] }).then((result) =>
299
+ BigInt(result),
300
+ ),
301
+ getCode: ({ address, blockNumber }) =>
302
+ request({ method: "eth_getCode", params: [address, toHex(blockNumber)] }).then(
303
+ (result) => (result ?? "0x") as string,
304
+ ),
305
+ getBlock: async ({ blockNumber }) => {
306
+ const block = await request({ method: "eth_getBlockByNumber", params: [toHex(blockNumber), false] });
307
+ if (!block) throw new Error(`Block at number ${blockNumber} could not be found.`);
308
+ return block;
309
+ },
310
+ getTransactionReceipt: async ({ hash }) => {
311
+ const receipt = await request({ method: "eth_getTransactionReceipt", params: [hash] });
312
+ if (!receipt) throw new Error(`The receipt of transaction ${hash} could not be found.`);
313
+ return receipt;
314
+ },
315
+ };
316
+ }
317
+
318
+ const clients = new Map<string, RpcClient>();
319
+
320
+ export function rpcClient(rpcUrls: string[]): RpcClient {
321
+ const key = rpcUrls.join("|");
322
+ let client = clients.get(key);
323
+ if (!client) {
324
+ const maxInFlight = maxInFlightFromEnv();
325
+ const maxBatch = maxBatchFromEnv();
326
+ // Retrying is `untilAnswered`'s job, with a backoff that outlasts a rate
327
+ // limit.
328
+ client = makeClient(rpcUrls.map((url) => batchingTransport(url, maxInFlight, maxBatch)));
329
+ clients.set(key, client);
330
+ }
331
+ return client;
332
+ }
333
+
334
+ /** Reset between test indexers, which each bring their own endpoints. */
335
+ export function resetRpcClients() {
336
+ clients.clear();
337
+ }