ponder 0.14.13 → 0.15.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.
Files changed (237) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/esm/bin/commands/createViews.js +28 -11
  3. package/dist/esm/bin/commands/createViews.js.map +1 -1
  4. package/dist/esm/bin/commands/dev.js +42 -22
  5. package/dist/esm/bin/commands/dev.js.map +1 -1
  6. package/dist/esm/bin/commands/prune.js +3 -0
  7. package/dist/esm/bin/commands/prune.js.map +1 -1
  8. package/dist/esm/bin/commands/serve.js +4 -1
  9. package/dist/esm/bin/commands/serve.js.map +1 -1
  10. package/dist/esm/bin/commands/start.js +18 -6
  11. package/dist/esm/bin/commands/start.js.map +1 -1
  12. package/dist/esm/bin/isolatedController.js +200 -0
  13. package/dist/esm/bin/isolatedController.js.map +1 -0
  14. package/dist/esm/bin/isolatedWorker.js +146 -0
  15. package/dist/esm/bin/isolatedWorker.js.map +1 -0
  16. package/dist/esm/build/config.js +322 -402
  17. package/dist/esm/build/config.js.map +1 -1
  18. package/dist/esm/build/index.js +8 -11
  19. package/dist/esm/build/index.js.map +1 -1
  20. package/dist/esm/build/pre.js +1 -4
  21. package/dist/esm/build/pre.js.map +1 -1
  22. package/dist/esm/build/schema.js +25 -3
  23. package/dist/esm/build/schema.js.map +1 -1
  24. package/dist/esm/client/index.js +306 -42
  25. package/dist/esm/client/index.js.map +1 -1
  26. package/dist/esm/database/actions.js +264 -104
  27. package/dist/esm/database/actions.js.map +1 -1
  28. package/dist/esm/database/index.js +39 -33
  29. package/dist/esm/database/index.js.map +1 -1
  30. package/dist/esm/database/queryBuilder.js +1 -0
  31. package/dist/esm/database/queryBuilder.js.map +1 -1
  32. package/dist/esm/drizzle/index.js +11 -7
  33. package/dist/esm/drizzle/index.js.map +1 -1
  34. package/dist/esm/drizzle/onchain.js +18 -0
  35. package/dist/esm/drizzle/onchain.js.map +1 -1
  36. package/dist/esm/indexing/client.js +32 -25
  37. package/dist/esm/indexing/client.js.map +1 -1
  38. package/dist/esm/indexing/index.js +110 -178
  39. package/dist/esm/indexing/index.js.map +1 -1
  40. package/dist/esm/indexing/profile.js +1 -1
  41. package/dist/esm/indexing/profile.js.map +1 -1
  42. package/dist/esm/indexing-store/cache.js +196 -274
  43. package/dist/esm/indexing-store/cache.js.map +1 -1
  44. package/dist/esm/indexing-store/historical.js +17 -13
  45. package/dist/esm/indexing-store/historical.js.map +1 -1
  46. package/dist/esm/indexing-store/index.js +10 -1
  47. package/dist/esm/indexing-store/index.js.map +1 -1
  48. package/dist/esm/indexing-store/profile.js +3 -3
  49. package/dist/esm/indexing-store/profile.js.map +1 -1
  50. package/dist/esm/indexing-store/realtime.js +27 -2
  51. package/dist/esm/indexing-store/realtime.js.map +1 -1
  52. package/dist/esm/internal/errors.js +28 -0
  53. package/dist/esm/internal/errors.js.map +1 -1
  54. package/dist/esm/internal/metrics.js +279 -82
  55. package/dist/esm/internal/metrics.js.map +1 -1
  56. package/dist/esm/internal/options.js +1 -0
  57. package/dist/esm/internal/options.js.map +1 -1
  58. package/dist/esm/internal/telemetry.js +1 -1
  59. package/dist/esm/internal/telemetry.js.map +1 -1
  60. package/dist/esm/rpc/http.js +130 -0
  61. package/dist/esm/rpc/http.js.map +1 -0
  62. package/dist/esm/rpc/index.js +38 -7
  63. package/dist/esm/rpc/index.js.map +1 -1
  64. package/dist/esm/runtime/events.js +179 -212
  65. package/dist/esm/runtime/events.js.map +1 -1
  66. package/dist/esm/runtime/filter.js +71 -0
  67. package/dist/esm/runtime/filter.js.map +1 -1
  68. package/dist/esm/runtime/fragments.js +78 -73
  69. package/dist/esm/runtime/fragments.js.map +1 -1
  70. package/dist/esm/runtime/historical.js +306 -130
  71. package/dist/esm/runtime/historical.js.map +1 -1
  72. package/dist/esm/runtime/index.js +183 -58
  73. package/dist/esm/runtime/index.js.map +1 -1
  74. package/dist/esm/runtime/isolated.js +462 -0
  75. package/dist/esm/runtime/isolated.js.map +1 -0
  76. package/dist/esm/runtime/multichain.js +80 -73
  77. package/dist/esm/runtime/multichain.js.map +1 -1
  78. package/dist/esm/runtime/omnichain.js +82 -75
  79. package/dist/esm/runtime/omnichain.js.map +1 -1
  80. package/dist/esm/runtime/realtime.js +198 -66
  81. package/dist/esm/runtime/realtime.js.map +1 -1
  82. package/dist/esm/sync-historical/index.js +416 -457
  83. package/dist/esm/sync-historical/index.js.map +1 -1
  84. package/dist/esm/sync-realtime/bloom.js +3 -3
  85. package/dist/esm/sync-realtime/bloom.js.map +1 -1
  86. package/dist/esm/sync-realtime/index.js +27 -46
  87. package/dist/esm/sync-realtime/index.js.map +1 -1
  88. package/dist/esm/sync-store/index.js +112 -63
  89. package/dist/esm/sync-store/index.js.map +1 -1
  90. package/dist/esm/utils/abi.js +20 -32
  91. package/dist/esm/utils/abi.js.map +1 -1
  92. package/dist/esm/utils/chunk.js +8 -0
  93. package/dist/esm/utils/chunk.js.map +1 -0
  94. package/dist/esm/utils/promiseAllSettledWithThrow.js +19 -0
  95. package/dist/esm/utils/promiseAllSettledWithThrow.js.map +1 -0
  96. package/dist/esm/{client/parse.js → utils/sql-parse.js} +94 -80
  97. package/dist/esm/utils/sql-parse.js.map +1 -0
  98. package/dist/types/bin/commands/createViews.d.ts.map +1 -1
  99. package/dist/types/bin/commands/dev.d.ts.map +1 -1
  100. package/dist/types/bin/commands/prune.d.ts.map +1 -1
  101. package/dist/types/bin/commands/serve.d.ts.map +1 -1
  102. package/dist/types/bin/commands/start.d.ts.map +1 -1
  103. package/dist/types/bin/isolatedController.d.ts +13 -0
  104. package/dist/types/bin/isolatedController.d.ts.map +1 -0
  105. package/dist/types/bin/isolatedWorker.d.ts +9 -0
  106. package/dist/types/bin/isolatedWorker.d.ts.map +1 -0
  107. package/dist/types/build/config.d.ts +29 -11
  108. package/dist/types/build/config.d.ts.map +1 -1
  109. package/dist/types/build/index.d.ts +3 -2
  110. package/dist/types/build/index.d.ts.map +1 -1
  111. package/dist/types/build/pre.d.ts +1 -1
  112. package/dist/types/build/pre.d.ts.map +1 -1
  113. package/dist/types/build/schema.d.ts +5 -3
  114. package/dist/types/build/schema.d.ts.map +1 -1
  115. package/dist/types/client/index.d.ts +1 -1
  116. package/dist/types/client/index.d.ts.map +1 -1
  117. package/dist/types/config/index.d.ts +3 -3
  118. package/dist/types/config/index.d.ts.map +1 -1
  119. package/dist/types/database/actions.d.ts +53 -7
  120. package/dist/types/database/actions.d.ts.map +1 -1
  121. package/dist/types/database/index.d.ts +21 -21
  122. package/dist/types/database/index.d.ts.map +1 -1
  123. package/dist/types/database/queryBuilder.d.ts.map +1 -1
  124. package/dist/types/drizzle/index.d.ts +4 -5
  125. package/dist/types/drizzle/index.d.ts.map +1 -1
  126. package/dist/types/drizzle/onchain.d.ts +6 -0
  127. package/dist/types/drizzle/onchain.d.ts.map +1 -1
  128. package/dist/types/indexing/client.d.ts.map +1 -1
  129. package/dist/types/indexing/index.d.ts +2 -5
  130. package/dist/types/indexing/index.d.ts.map +1 -1
  131. package/dist/types/indexing-store/cache.d.ts +3 -2
  132. package/dist/types/indexing-store/cache.d.ts.map +1 -1
  133. package/dist/types/indexing-store/historical.d.ts +2 -1
  134. package/dist/types/indexing-store/historical.d.ts.map +1 -1
  135. package/dist/types/indexing-store/index.d.ts +1 -0
  136. package/dist/types/indexing-store/index.d.ts.map +1 -1
  137. package/dist/types/indexing-store/realtime.d.ts +2 -1
  138. package/dist/types/indexing-store/realtime.d.ts.map +1 -1
  139. package/dist/types/internal/errors.d.ts +5 -0
  140. package/dist/types/internal/errors.d.ts.map +1 -1
  141. package/dist/types/internal/metrics.d.ts +21 -0
  142. package/dist/types/internal/metrics.d.ts.map +1 -1
  143. package/dist/types/internal/options.d.ts +2 -0
  144. package/dist/types/internal/options.d.ts.map +1 -1
  145. package/dist/types/internal/types.d.ts +66 -58
  146. package/dist/types/internal/types.d.ts.map +1 -1
  147. package/dist/types/rpc/http.d.ts +17 -0
  148. package/dist/types/rpc/http.d.ts.map +1 -0
  149. package/dist/types/rpc/index.d.ts.map +1 -1
  150. package/dist/types/runtime/events.d.ts +4 -4
  151. package/dist/types/runtime/events.d.ts.map +1 -1
  152. package/dist/types/runtime/filter.d.ts +5 -1
  153. package/dist/types/runtime/filter.d.ts.map +1 -1
  154. package/dist/types/runtime/fragments.d.ts +4 -3
  155. package/dist/types/runtime/fragments.d.ts.map +1 -1
  156. package/dist/types/runtime/historical.d.ts +29 -13
  157. package/dist/types/runtime/historical.d.ts.map +1 -1
  158. package/dist/types/runtime/index.d.ts +49 -6
  159. package/dist/types/runtime/index.d.ts.map +1 -1
  160. package/dist/types/runtime/init.d.ts +5 -5
  161. package/dist/types/runtime/init.d.ts.map +1 -1
  162. package/dist/types/runtime/isolated.d.ts +14 -0
  163. package/dist/types/runtime/isolated.d.ts.map +1 -0
  164. package/dist/types/runtime/multichain.d.ts.map +1 -1
  165. package/dist/types/runtime/omnichain.d.ts.map +1 -1
  166. package/dist/types/runtime/realtime.d.ts +21 -10
  167. package/dist/types/runtime/realtime.d.ts.map +1 -1
  168. package/dist/types/sync-historical/index.d.ts +18 -8
  169. package/dist/types/sync-historical/index.d.ts.map +1 -1
  170. package/dist/types/sync-realtime/bloom.d.ts.map +1 -1
  171. package/dist/types/sync-realtime/index.d.ts +2 -2
  172. package/dist/types/sync-realtime/index.d.ts.map +1 -1
  173. package/dist/types/sync-store/index.d.ts +9 -9
  174. package/dist/types/sync-store/index.d.ts.map +1 -1
  175. package/dist/types/utils/abi.d.ts +3 -34
  176. package/dist/types/utils/abi.d.ts.map +1 -1
  177. package/dist/types/utils/chunk.d.ts +2 -0
  178. package/dist/types/utils/chunk.d.ts.map +1 -0
  179. package/dist/types/utils/promiseAllSettledWithThrow.d.ts +8 -0
  180. package/dist/types/utils/promiseAllSettledWithThrow.d.ts.map +1 -0
  181. package/dist/types/utils/sql-parse.d.ts +21 -0
  182. package/dist/types/utils/sql-parse.d.ts.map +1 -0
  183. package/package.json +2 -2
  184. package/src/bin/commands/createViews.ts +35 -15
  185. package/src/bin/commands/dev.ts +43 -21
  186. package/src/bin/commands/prune.ts +6 -0
  187. package/src/bin/commands/serve.ts +4 -1
  188. package/src/bin/commands/start.ts +20 -5
  189. package/src/bin/isolatedController.ts +300 -0
  190. package/src/bin/isolatedWorker.ts +192 -0
  191. package/src/build/config.ts +570 -632
  192. package/src/build/index.ts +14 -14
  193. package/src/build/pre.ts +1 -4
  194. package/src/build/schema.ts +49 -4
  195. package/src/client/index.ts +386 -48
  196. package/src/config/index.ts +3 -3
  197. package/src/database/actions.ts +469 -120
  198. package/src/database/index.ts +85 -58
  199. package/src/database/queryBuilder.ts +1 -0
  200. package/src/drizzle/index.ts +15 -7
  201. package/src/drizzle/onchain.ts +19 -0
  202. package/src/indexing/client.ts +38 -25
  203. package/src/indexing/index.ts +137 -230
  204. package/src/indexing/profile.ts +1 -1
  205. package/src/indexing-store/cache.ts +285 -414
  206. package/src/indexing-store/historical.ts +20 -10
  207. package/src/indexing-store/index.ts +16 -0
  208. package/src/indexing-store/profile.ts +3 -3
  209. package/src/indexing-store/realtime.ts +28 -0
  210. package/src/internal/errors.ts +26 -0
  211. package/src/internal/metrics.ts +341 -111
  212. package/src/internal/options.ts +4 -0
  213. package/src/internal/telemetry.ts +1 -1
  214. package/src/internal/types.ts +70 -87
  215. package/src/rpc/http.ts +164 -0
  216. package/src/rpc/index.ts +39 -7
  217. package/src/runtime/events.ts +195 -240
  218. package/src/runtime/filter.ts +85 -1
  219. package/src/runtime/fragments.ts +109 -113
  220. package/src/runtime/historical.ts +467 -189
  221. package/src/runtime/index.ts +337 -69
  222. package/src/runtime/init.ts +5 -5
  223. package/src/runtime/isolated.ts +768 -0
  224. package/src/runtime/multichain.ts +137 -102
  225. package/src/runtime/omnichain.ts +138 -106
  226. package/src/runtime/realtime.ts +322 -123
  227. package/src/sync-historical/index.ts +556 -692
  228. package/src/sync-realtime/bloom.ts +7 -3
  229. package/src/sync-realtime/index.ts +31 -46
  230. package/src/sync-store/index.ts +189 -95
  231. package/src/utils/abi.ts +33 -90
  232. package/src/utils/chunk.ts +7 -0
  233. package/src/utils/promiseAllSettledWithThrow.ts +27 -0
  234. package/src/{client/parse.ts → utils/sql-parse.ts} +100 -90
  235. package/dist/esm/client/parse.js.map +0 -1
  236. package/dist/types/client/parse.d.ts +0 -14
  237. package/dist/types/client/parse.d.ts.map +0 -1
@@ -1,17 +1,22 @@
1
+ import type { Factory } from "@/config/address.js";
1
2
  import type { Config } from "@/config/index.js";
2
3
  import type { Common } from "@/internal/common.js";
3
4
  import { BuildError } from "@/internal/errors.js";
4
5
  import type {
5
- AccountSource,
6
- BlockSource,
6
+ BlockFilter,
7
7
  Chain,
8
- ContractSource,
8
+ Contract,
9
+ EventCallback,
10
+ FilterAddress,
9
11
  IndexingBuild,
10
12
  IndexingFunctions,
11
13
  LightBlock,
12
- RawIndexingFunctions,
13
- Source,
14
+ LogFilter,
15
+ SetupCallback,
14
16
  SyncBlock,
17
+ TraceFilter,
18
+ TransactionFilter,
19
+ TransferFilter,
15
20
  } from "@/internal/types.js";
16
21
  import { _eth_getBlockByNumber } from "@/rpc/actions.js";
17
22
  import { type Rpc, createRpc } from "@/rpc/index.js";
@@ -23,12 +28,23 @@ import {
23
28
  defaultTransactionReceiptInclude,
24
29
  defaultTransferFilterInclude,
25
30
  } from "@/runtime/filter.js";
26
- import { buildAbiEvents, buildAbiFunctions, buildTopics } from "@/utils/abi.js";
31
+ import { buildTopics, toSafeName } from "@/utils/abi.js";
27
32
  import { hyperliquidEvm, chains as viemChains } from "@/utils/chains.js";
28
33
  import { dedupe } from "@/utils/dedupe.js";
29
34
  import { getFinalityBlockCount } from "@/utils/finality.js";
30
35
  import { toLowerCase } from "@/utils/lowercase.js";
31
- import { BlockNotFoundError, type Hex, type LogTopic, hexToNumber } from "viem";
36
+ import {
37
+ type Abi,
38
+ type AbiEvent,
39
+ type AbiFunction,
40
+ type Address,
41
+ BlockNotFoundError,
42
+ type Hex,
43
+ type LogTopic,
44
+ hexToNumber,
45
+ toEventSelector,
46
+ toFunctionSelector,
47
+ } from "viem";
32
48
  import { buildLogFactory } from "./factory.js";
33
49
 
34
50
  const flattenSources = <
@@ -63,19 +79,27 @@ const flattenSources = <
63
79
  export async function buildIndexingFunctions({
64
80
  common,
65
81
  config,
66
- rawIndexingFunctions,
82
+ indexingFunctions,
67
83
  configBuild: { chains, rpcs },
68
84
  }: {
69
85
  common: Common;
70
86
  config: Config;
71
- rawIndexingFunctions: RawIndexingFunctions;
87
+ indexingFunctions: IndexingFunctions;
72
88
  configBuild: Pick<IndexingBuild, "chains" | "rpcs">;
73
89
  }): Promise<{
74
90
  chains: Chain[];
75
91
  rpcs: Rpc[];
76
92
  finalizedBlocks: LightBlock[];
77
- sources: Source[];
78
- indexingFunctions: IndexingFunctions;
93
+ eventCallbacks: EventCallback[][];
94
+ setupCallbacks: SetupCallback[][];
95
+ contracts: {
96
+ [name: string]: {
97
+ abi: Abi;
98
+ address?: Address | readonly Address[];
99
+ startBlock?: number;
100
+ endBlock?: number;
101
+ };
102
+ }[];
79
103
  logs: ({ level: "warn" | "info" | "debug"; msg: string } & Record<
80
104
  string,
81
105
  unknown
@@ -179,10 +203,15 @@ export async function buildIndexingFunctions({
179
203
  }
180
204
 
181
205
  // Validate and build indexing functions
182
- let indexingFunctionCount = 0;
183
- const indexingFunctions: IndexingFunctions = {};
206
+ if (indexingFunctions.length === 0) {
207
+ throw new Error(
208
+ "Validation failed: Found 0 registered indexing functions.",
209
+ );
210
+ }
211
+
212
+ const eventNames = new Set<string>();
184
213
 
185
- for (const { name: eventName, fn } of rawIndexingFunctions) {
214
+ for (const { name: eventName } of indexingFunctions) {
186
215
  const eventNameComponents = eventName.includes(".")
187
216
  ? eventName.split(".")
188
217
  : eventName.split(":");
@@ -220,12 +249,14 @@ export async function buildIndexingFunctions({
220
249
  );
221
250
  }
222
251
 
223
- if (eventName in indexingFunctions) {
252
+ if (eventNames.has(eventName)) {
224
253
  throw new Error(
225
254
  `Validation failed: Multiple indexing functions registered for event '${eventName}'.`,
226
255
  );
227
256
  }
228
257
 
258
+ eventNames.add(eventName);
259
+
229
260
  // Validate that the indexing function uses a sourceName that is present in the config.
230
261
  const matchedSourceName = Object.keys({
231
262
  ...(config.contracts ?? {}),
@@ -242,13 +273,6 @@ export async function buildIndexingFunctions({
242
273
  .join(", ")}].`,
243
274
  );
244
275
  }
245
-
246
- indexingFunctions[eventName] = fn;
247
- indexingFunctionCount += 1;
248
- }
249
-
250
- if (indexingFunctionCount === 0) {
251
- logs.push({ level: "warn", msg: "No registered indexing functions" });
252
276
  }
253
277
 
254
278
  // common validation for all sources
@@ -345,659 +369,571 @@ export async function buildIndexingFunctions({
345
369
  }
346
370
  }
347
371
 
348
- const contractSources: ContractSource[] = (
349
- await Promise.all(
350
- flattenSources(config.contracts ?? {}).map(
351
- async (source): Promise<ContractSource[]> => {
352
- const chain = chains.find((n) => n.name === source.chain)!;
353
-
354
- // Get indexing function that were registered for this contract
355
- const registeredLogEvents: string[] = [];
356
- const registeredCallTraceEvents: string[] = [];
357
- for (const eventName of Object.keys(indexingFunctions)) {
358
- // log event
359
- if (eventName.includes(":")) {
360
- const [logContractName, logEventName] = eventName.split(":") as [
361
- string,
362
- string,
363
- ];
364
- if (logContractName === source.name && logEventName !== "setup") {
365
- registeredLogEvents.push(logEventName);
366
- }
367
- }
372
+ const perChainEventCallbacks: Map<number, EventCallback[]> = new Map();
373
+ const perChainSetupCallbacks: Map<number, SetupCallback[]> = new Map();
374
+ const perChainContracts: Map<
375
+ number,
376
+ {
377
+ [name: string]: Contract;
378
+ }
379
+ > = new Map();
380
+ for (const chain of chains) {
381
+ perChainEventCallbacks.set(chain.id, []);
382
+ perChainSetupCallbacks.set(chain.id, []);
383
+ perChainContracts.set(chain.id, {});
384
+ }
368
385
 
369
- // trace event
370
- if (eventName.includes(".")) {
371
- const [functionContractName, functionName] = eventName.split(
372
- ".",
373
- ) as [string, string];
374
- if (functionContractName === source.name) {
375
- registeredCallTraceEvents.push(functionName);
376
- }
377
- }
378
- }
386
+ for (const source of flattenSources(config.contracts ?? {})) {
387
+ const chain = chains.find((n) => n.name === source.chain)!;
379
388
 
380
- // Note: This can probably throw for invalid ABIs. Consider adding explicit ABI validation before this line.
381
- const abiEvents = buildAbiEvents({ abi: source.abi });
382
- const abiFunctions = buildAbiFunctions({ abi: source.abi });
383
-
384
- const registeredEventSelectors: Hex[] = [];
385
- // Validate that the registered log events exist in the abi
386
- for (const logEvent of registeredLogEvents) {
387
- const abiEvent = abiEvents.bySafeName[logEvent];
388
- if (abiEvent === undefined) {
389
- throw new Error(
390
- `Validation failed: Event name for event '${logEvent}' not found in the contract ABI. Got '${logEvent}', expected one of [${Object.keys(
391
- abiEvents.bySafeName,
392
- )
393
- .map((eventName) => `'${eventName}'`)
394
- .join(", ")}].`,
395
- );
396
- }
389
+ const fromBlock = await resolveBlockNumber(source.startBlock, chain);
390
+ const toBlock = await resolveBlockNumber(source.endBlock, chain);
397
391
 
398
- registeredEventSelectors.push(abiEvent.selector);
399
- }
400
-
401
- const registeredFunctionSelectors: Hex[] = [];
402
- for (const _function of registeredCallTraceEvents) {
403
- const abiFunction = abiFunctions.bySafeName[_function];
404
- if (abiFunction === undefined) {
405
- throw new Error(
406
- `Validation failed: Function name for function '${_function}' not found in the contract ABI. Got '${_function}', expected one of [${Object.keys(
407
- abiFunctions.bySafeName,
408
- )
409
- .map((eventName) => `'${eventName}'`)
410
- .join(", ")}].`,
411
- );
412
- }
392
+ if (indexingFunctions.some((f) => f.name === `${source.name}:setup`)) {
393
+ perChainSetupCallbacks.get(chain.id)!.push({
394
+ name: `${source.name}:setup`,
395
+ fn: indexingFunctions.find((f) => f.name === `${source.name}:setup`)!
396
+ .fn,
397
+ chain,
398
+ block: fromBlock,
399
+ });
400
+ }
413
401
 
414
- registeredFunctionSelectors.push(abiFunction.selector);
415
- }
402
+ let address: FilterAddress;
416
403
 
417
- const topicsArray: {
418
- topic0: LogTopic;
419
- topic1: LogTopic;
420
- topic2: LogTopic;
421
- topic3: LogTopic;
422
- }[] = [];
423
-
424
- if (source.filter !== undefined) {
425
- const eventFilters = Array.isArray(source.filter)
426
- ? source.filter
427
- : [source.filter];
428
-
429
- for (const filter of eventFilters) {
430
- const abiEvent = abiEvents.bySafeName[filter.event];
431
- if (!abiEvent) {
432
- throw new Error(
433
- `Validation failed: Invalid filter for contract '${
434
- source.name
435
- }'. Got event name '${filter.event}', expected one of [${Object.keys(
436
- abiEvents.bySafeName,
437
- )
438
- .map((n) => `'${n}'`)
439
- .join(", ")}].`,
440
- );
441
- }
442
- }
404
+ const resolvedAddress = source?.address;
405
+ if (
406
+ typeof resolvedAddress === "object" &&
407
+ Array.isArray(resolvedAddress) === false
408
+ ) {
409
+ const factoryAddress = resolvedAddress as Factory;
410
+ const factoryFromBlock =
411
+ (await resolveBlockNumber(factoryAddress.startBlock, chain)) ??
412
+ fromBlock;
413
+
414
+ const factoryToBlock =
415
+ (await resolveBlockNumber(factoryAddress.endBlock, chain)) ?? toBlock;
416
+
417
+ // Note that this can throw.
418
+ const logFactory = buildLogFactory({
419
+ chainId: chain.id,
420
+ ...factoryAddress,
421
+ fromBlock: factoryFromBlock,
422
+ toBlock: factoryToBlock,
423
+ });
443
424
 
444
- topicsArray.push(...buildTopics(source.abi, eventFilters));
425
+ perChainContracts.get(chain.id)![source.name] = {
426
+ abi: source.abi,
427
+ address: undefined,
428
+ startBlock: fromBlock,
429
+ endBlock: toBlock,
430
+ };
445
431
 
446
- // event selectors that have a filter
447
- const filteredEventSelectors: Hex[] = topicsArray.map(
448
- (t) => t.topic0 as Hex,
432
+ address = logFactory;
433
+ } else {
434
+ if (resolvedAddress !== undefined) {
435
+ for (const address of Array.isArray(resolvedAddress)
436
+ ? resolvedAddress
437
+ : [resolvedAddress as Address]) {
438
+ if (!address!.startsWith("0x"))
439
+ throw new Error(
440
+ `Validation failed: Invalid prefix for address '${address}'. Got '${address!.slice(
441
+ 0,
442
+ 2,
443
+ )}', expected '0x'.`,
449
444
  );
450
- // event selectors that are registered but don't have a filter
451
- const excludedRegisteredEventSelectors =
452
- registeredEventSelectors.filter(
453
- (s) => filteredEventSelectors.includes(s) === false,
454
- );
455
-
456
- for (const selector of filteredEventSelectors) {
457
- if (registeredEventSelectors.includes(selector) === false) {
458
- throw new Error(
459
- `Validation failed: Event selector '${abiEvents.bySelector[selector]?.safeName}' is used in a filter but does not have a corresponding indexing function.`,
460
- );
461
- }
462
- }
463
-
464
- if (excludedRegisteredEventSelectors.length > 0) {
465
- topicsArray.push({
466
- topic0: excludedRegisteredEventSelectors,
467
- topic1: null,
468
- topic2: null,
469
- topic3: null,
470
- });
471
- }
472
- } else {
473
- topicsArray.push({
474
- topic0: registeredEventSelectors,
475
- topic1: null,
476
- topic2: null,
477
- topic3: null,
478
- });
479
- }
445
+ if (address!.length !== 42)
446
+ throw new Error(
447
+ `Validation failed: Invalid length for address '${address}'. Got ${address!.length}, expected 42 characters.`,
448
+ );
449
+ }
450
+ }
480
451
 
481
- const fromBlock = await resolveBlockNumber(source.startBlock, chain);
482
- const toBlock = await resolveBlockNumber(source.endBlock, chain);
452
+ const validatedAddress = Array.isArray(resolvedAddress)
453
+ ? dedupe(resolvedAddress).map((r) => toLowerCase(r))
454
+ : resolvedAddress !== undefined
455
+ ? toLowerCase(resolvedAddress as Address)
456
+ : undefined;
483
457
 
484
- const contractMetadata = {
485
- type: "contract",
486
- abi: source.abi,
487
- abiEvents,
488
- abiFunctions,
489
- name: source.name,
490
- chain,
491
- } as const;
492
-
493
- const resolvedAddress = source?.address;
494
-
495
- if (
496
- typeof resolvedAddress === "object" &&
497
- !Array.isArray(resolvedAddress)
498
- ) {
499
- const factoryFromBlock =
500
- (await resolveBlockNumber(resolvedAddress.startBlock, chain)) ??
501
- fromBlock;
502
-
503
- const factoryToBlock =
504
- (await resolveBlockNumber(resolvedAddress.endBlock, chain)) ??
505
- toBlock;
506
-
507
- // Note that this can throw.
508
- const logFactory = buildLogFactory({
509
- chainId: chain.id,
510
- ...resolvedAddress,
511
- fromBlock: factoryFromBlock,
512
- toBlock: factoryToBlock,
513
- });
514
-
515
- const logSources = topicsArray.map(
516
- (topics) =>
517
- ({
518
- ...contractMetadata,
519
- filter: {
520
- type: "log",
521
- chainId: chain.id,
522
- address: logFactory,
523
- topic0: topics.topic0,
524
- topic1: topics.topic1,
525
- topic2: topics.topic2,
526
- topic3: topics.topic3,
527
- fromBlock,
528
- toBlock,
529
- hasTransactionReceipt:
530
- source.includeTransactionReceipts ?? false,
531
- include: defaultLogFilterInclude.concat(
532
- source.includeTransactionReceipts
533
- ? defaultTransactionReceiptInclude.map(
534
- (value) => `transactionReceipt.${value}` as const,
535
- )
536
- : [],
537
- ),
538
- },
539
- }) satisfies ContractSource,
540
- );
458
+ perChainContracts.get(chain.id)![source.name] = {
459
+ abi: source.abi,
460
+ address: validatedAddress,
461
+ startBlock: fromBlock,
462
+ endBlock: toBlock,
463
+ };
541
464
 
542
- if (source.includeCallTraces) {
543
- return [
544
- ...logSources,
545
- {
546
- ...contractMetadata,
547
- filter: {
548
- type: "trace",
549
- chainId: chain.id,
550
- fromAddress: undefined,
551
- toAddress: logFactory,
552
- callType: "CALL",
553
- functionSelector: registeredFunctionSelectors,
554
- includeReverted: false,
555
- fromBlock,
556
- toBlock,
557
- hasTransactionReceipt:
558
- source.includeTransactionReceipts ?? false,
559
- include: defaultTraceFilterInclude.concat(
560
- source.includeTransactionReceipts
561
- ? defaultTransactionReceiptInclude.map(
562
- (value) => `transactionReceipt.${value}` as const,
563
- )
564
- : [],
565
- ),
566
- },
567
- } satisfies ContractSource,
568
- ];
569
- }
465
+ address = validatedAddress;
466
+ }
570
467
 
571
- return logSources;
572
- } else if (resolvedAddress !== undefined) {
573
- for (const address of Array.isArray(resolvedAddress)
574
- ? resolvedAddress
575
- : [resolvedAddress]) {
576
- if (!address!.startsWith("0x"))
577
- throw new Error(
578
- `Validation failed: Invalid prefix for address '${address}'. Got '${address!.slice(
579
- 0,
580
- 2,
581
- )}', expected '0x'.`,
582
- );
583
- if (address!.length !== 42)
584
- throw new Error(
585
- `Validation failed: Invalid length for address '${address}'. Got ${address!.length}, expected 42 characters.`,
586
- );
587
- }
588
- }
468
+ const filteredEventSelectors = new Map<
469
+ Hex,
470
+ ReturnType<typeof buildTopics>[number]
471
+ >();
472
+
473
+ if (source.filter) {
474
+ const eventFilters = Array.isArray(source.filter)
475
+ ? source.filter
476
+ : [source.filter];
477
+
478
+ for (const filter of eventFilters) {
479
+ const abiEvent = source.abi.find(
480
+ (item): item is AbiEvent =>
481
+ item.type === "event" &&
482
+ toSafeName({ abi: source.abi, item }) === filter.event,
483
+ );
484
+ if (!abiEvent) {
485
+ throw new Error(
486
+ `Validation failed: Invalid filter for contract '${
487
+ source.name
488
+ }'. Got event name '${filter.event}', expected one of [${source.abi
489
+ .filter((item): item is AbiEvent => item.type === "event")
490
+ .map((item) => `'${toSafeName({ abi: source.abi, item })}'`)
491
+ .join(", ")}].`,
492
+ );
493
+ }
494
+ }
495
+ const topics = buildTopics(source.abi, eventFilters);
496
+
497
+ for (const { topic0, topic1, topic2, topic3 } of topics) {
498
+ const abiItem = source.abi.find(
499
+ (item): item is AbiEvent =>
500
+ item.type === "event" && toEventSelector(item) === topic0,
501
+ )!;
502
+ const indexingFunction = indexingFunctions.find(
503
+ (f) => f.name === `${source.name}:${abiItem.name}`,
504
+ );
589
505
 
590
- const validatedAddress = Array.isArray(resolvedAddress)
591
- ? dedupe(resolvedAddress).map((r) => toLowerCase(r))
592
- : resolvedAddress !== undefined
593
- ? toLowerCase(resolvedAddress)
594
- : undefined;
595
-
596
- const logSources = topicsArray.map(
597
- (topics) =>
598
- ({
599
- ...contractMetadata,
600
- filter: {
601
- type: "log",
602
- chainId: chain.id,
603
- address: validatedAddress,
604
- topic0: topics.topic0,
605
- topic1: topics.topic1,
606
- topic2: topics.topic2,
607
- topic3: topics.topic3,
608
- fromBlock,
609
- toBlock,
610
- hasTransactionReceipt:
611
- source.includeTransactionReceipts ?? false,
612
- include: defaultLogFilterInclude.concat(
613
- source.includeTransactionReceipts
614
- ? defaultTransactionReceiptInclude.map(
615
- (value) => `transactionReceipt.${value}` as const,
616
- )
617
- : [],
618
- ),
619
- },
620
- }) satisfies ContractSource,
506
+ if (indexingFunction === undefined) {
507
+ throw new Error(
508
+ `Validation failed: Event selector '${toSafeName({ abi: source.abi, item: abiItem })}' is used in a filter but does not have a corresponding indexing function.`,
621
509
  );
510
+ }
622
511
 
623
- if (source.includeCallTraces) {
624
- return [
625
- ...logSources,
626
- {
627
- ...contractMetadata,
628
- filter: {
629
- type: "trace",
630
- chainId: chain.id,
631
- fromAddress: undefined,
632
- toAddress: Array.isArray(validatedAddress)
633
- ? validatedAddress
634
- : validatedAddress === undefined
635
- ? undefined
636
- : [validatedAddress],
637
- callType: "CALL",
638
- functionSelector: registeredFunctionSelectors,
639
- includeReverted: false,
640
- fromBlock,
641
- toBlock,
642
- hasTransactionReceipt:
643
- source.includeTransactionReceipts ?? false,
644
- include: defaultTraceFilterInclude.concat(
645
- source.includeTransactionReceipts
646
- ? defaultTransactionReceiptInclude.map(
647
- (value) => `transactionReceipt.${value}` as const,
648
- )
649
- : [],
650
- ),
651
- },
652
- } satisfies ContractSource,
653
- ];
654
- } else return logSources;
655
- },
656
- ),
657
- )
658
- )
659
- .flat() // Remove sources with no registered indexing functions
660
- .filter((source) => {
661
- const hasNoRegisteredIndexingFunctions =
662
- source.filter.type === "trace"
663
- ? Array.isArray(source.filter.functionSelector) &&
664
- source.filter.functionSelector.length === 0
665
- : Array.isArray(source.filter.topic0) &&
666
- source.filter.topic0?.length === 0;
667
- if (hasNoRegisteredIndexingFunctions) {
668
- logs.push({
669
- level: "warn",
670
- msg: "No registered indexing functions",
671
- chain: source.chain.name,
672
- chain_id: source.chain.id,
673
- name: source.name,
674
- type: source.filter.type,
675
- });
512
+ filteredEventSelectors.set(topic0, { topic0, topic1, topic2, topic3 });
676
513
  }
677
- return hasNoRegisteredIndexingFunctions === false;
678
- });
514
+ }
679
515
 
680
- const accountSources: AccountSource[] = (
681
- await Promise.all(
682
- flattenSources(config.accounts ?? {}).map(
683
- async (source): Promise<AccountSource[]> => {
684
- const chain = chains.find((n) => n.name === source.chain)!;
516
+ const registeredLogEvents: string[] = [];
517
+ const registeredCallTraceEvents: string[] = [];
518
+ for (const { name: eventName } of indexingFunctions) {
519
+ // log event
520
+ if (eventName.includes(":")) {
521
+ const [logContractName, logEventName] = eventName.split(":") as [
522
+ string,
523
+ string,
524
+ ];
525
+ if (logContractName === source.name && logEventName !== "setup") {
526
+ registeredLogEvents.push(logEventName);
527
+ }
528
+ }
685
529
 
686
- const fromBlock = await resolveBlockNumber(source.startBlock, chain);
687
- const toBlock = await resolveBlockNumber(source.endBlock, chain);
530
+ // trace event
531
+ if (eventName.includes(".")) {
532
+ const [functionContractName, functionName] = eventName.split(".") as [
533
+ string,
534
+ string,
535
+ ];
688
536
 
689
- const resolvedAddress = source?.address;
537
+ if (source.includeCallTraces !== true) {
538
+ continue;
539
+ }
540
+ if (functionContractName === source.name) {
541
+ registeredCallTraceEvents.push(functionName);
542
+ }
543
+ }
544
+ }
690
545
 
691
- if (resolvedAddress === undefined) {
692
- throw new Error(
693
- `Validation failed: Account '${source.name}' must specify an 'address'.`,
694
- );
695
- }
546
+ for (const logEventName of registeredLogEvents) {
547
+ const abiEvent = source.abi.find(
548
+ (item): item is AbiEvent =>
549
+ item.type === "event" &&
550
+ toSafeName({ abi: source.abi, item }) === logEventName,
551
+ );
552
+ if (abiEvent === undefined) {
553
+ throw new Error(
554
+ `Validation failed: Event name for event '${logEventName}' not found in the contract ABI. Got '${logEventName}', expected one of [${source.abi
555
+ .filter((item): item is AbiEvent => item.type === "event")
556
+ .map((item) => `'${toSafeName({ abi: source.abi, item })}'`)
557
+ .join(", ")}].`,
558
+ );
559
+ }
696
560
 
697
- if (
698
- typeof resolvedAddress === "object" &&
699
- !Array.isArray(resolvedAddress)
700
- ) {
701
- const factoryFromBlock =
702
- (await resolveBlockNumber(resolvedAddress.startBlock, chain)) ??
703
- fromBlock;
704
-
705
- const factoryToBlock =
706
- (await resolveBlockNumber(resolvedAddress.endBlock, chain)) ??
707
- toBlock;
708
-
709
- // Note that this can throw.
710
- const logFactory = buildLogFactory({
711
- chainId: chain.id,
712
- ...resolvedAddress,
713
- fromBlock: factoryFromBlock,
714
- toBlock: factoryToBlock,
715
- });
716
-
717
- return [
718
- {
719
- type: "account",
720
- name: source.name,
721
- chain,
722
- filter: {
723
- type: "transaction",
724
- chainId: chain.id,
725
- fromAddress: undefined,
726
- toAddress: logFactory,
727
- includeReverted: false,
728
- fromBlock,
729
- toBlock,
730
- hasTransactionReceipt: true,
731
- include: defaultTransactionFilterInclude,
732
- },
733
- } satisfies AccountSource,
734
- {
735
- type: "account",
736
- name: source.name,
737
- chain,
738
- filter: {
739
- type: "transaction",
740
- chainId: chain.id,
741
- fromAddress: logFactory,
742
- toAddress: undefined,
743
- includeReverted: false,
744
- fromBlock,
745
- toBlock,
746
- hasTransactionReceipt: true,
747
- include: defaultTransactionFilterInclude,
748
- },
749
- } satisfies AccountSource,
750
- {
751
- type: "account",
752
- name: source.name,
753
- chain,
754
- filter: {
755
- type: "transfer",
756
- chainId: chain.id,
757
- fromAddress: undefined,
758
- toAddress: logFactory,
759
- includeReverted: false,
760
- fromBlock,
761
- toBlock,
762
- hasTransactionReceipt:
763
- source.includeTransactionReceipts ?? false,
764
- include: defaultTransferFilterInclude.concat(
765
- source.includeTransactionReceipts
766
- ? defaultTransactionReceiptInclude.map(
767
- (value) => `transactionReceipt.${value}` as const,
768
- )
769
- : [],
770
- ),
771
- },
772
- } satisfies AccountSource,
773
- {
774
- type: "account",
775
- name: source.name,
776
- chain,
777
- filter: {
778
- type: "transfer",
779
- chainId: chain.id,
780
- fromAddress: logFactory,
781
- toAddress: undefined,
782
- includeReverted: false,
783
- fromBlock,
784
- toBlock,
785
- hasTransactionReceipt:
786
- source.includeTransactionReceipts ?? false,
787
- include: defaultTransferFilterInclude.concat(
788
- source.includeTransactionReceipts
789
- ? defaultTransactionReceiptInclude.map(
790
- (value) => `transactionReceipt.${value}` as const,
791
- )
792
- : [],
793
- ),
794
- },
795
- } satisfies AccountSource,
796
- ];
797
- }
561
+ const eventName = `${source.name}:${logEventName}`;
798
562
 
799
- for (const address of Array.isArray(resolvedAddress)
800
- ? resolvedAddress
801
- : [resolvedAddress]) {
802
- if (!address!.startsWith("0x"))
803
- throw new Error(
804
- `Validation failed: Invalid prefix for address '${address}'. Got '${address!.slice(
805
- 0,
806
- 2,
807
- )}', expected '0x'.`,
808
- );
809
- if (address!.length !== 42)
810
- throw new Error(
811
- `Validation failed: Invalid length for address '${address}'. Got ${address!.length}, expected 42 characters.`,
812
- );
813
- }
563
+ const indexingFunction = indexingFunctions.find(
564
+ (f) => f.name === eventName,
565
+ )!;
814
566
 
815
- const validatedAddress = Array.isArray(resolvedAddress)
816
- ? dedupe(resolvedAddress).map((r) => toLowerCase(r))
817
- : resolvedAddress !== undefined
818
- ? toLowerCase(resolvedAddress)
819
- : undefined;
567
+ let topic1: LogTopic;
568
+ let topic2: LogTopic;
569
+ let topic3: LogTopic;
820
570
 
821
- return [
822
- {
823
- type: "account",
824
- name: source.name,
825
- chain,
826
- filter: {
827
- type: "transaction",
828
- chainId: chain.id,
829
- fromAddress: undefined,
830
- toAddress: validatedAddress,
831
- includeReverted: false,
832
- fromBlock,
833
- toBlock,
834
- hasTransactionReceipt: true,
835
- include: defaultTransactionFilterInclude,
836
- },
837
- } satisfies AccountSource,
838
- {
839
- type: "account",
840
- name: source.name,
841
- chain,
842
- filter: {
843
- type: "transaction",
844
- chainId: chain.id,
845
- fromAddress: validatedAddress,
846
- toAddress: undefined,
847
- includeReverted: false,
848
- fromBlock,
849
- toBlock,
850
- hasTransactionReceipt: true,
851
- include: defaultTransactionFilterInclude,
852
- },
853
- } satisfies AccountSource,
854
- {
855
- type: "account",
856
- name: source.name,
857
- chain,
858
- filter: {
859
- type: "transfer",
860
- chainId: chain.id,
861
- fromAddress: undefined,
862
- toAddress: validatedAddress,
863
- includeReverted: false,
864
- fromBlock,
865
- toBlock,
866
- hasTransactionReceipt:
867
- source.includeTransactionReceipts ?? false,
868
- include: defaultTransferFilterInclude.concat(
869
- source.includeTransactionReceipts
870
- ? defaultTransactionReceiptInclude.map(
871
- (value) => `transactionReceipt.${value}` as const,
872
- )
873
- : [],
874
- ),
875
- },
876
- } satisfies AccountSource,
877
- {
878
- type: "account",
879
- name: source.name,
880
- chain,
881
- filter: {
882
- type: "transfer",
883
- chainId: chain.id,
884
- fromAddress: validatedAddress,
885
- toAddress: undefined,
886
- includeReverted: false,
887
- fromBlock,
888
- toBlock,
889
- hasTransactionReceipt:
890
- source.includeTransactionReceipts ?? false,
891
- include: defaultTransferFilterInclude.concat(
892
- source.includeTransactionReceipts
893
- ? defaultTransactionReceiptInclude.map(
894
- (value) => `transactionReceipt.${value}` as const,
895
- )
896
- : [],
897
- ),
898
- },
899
- } satisfies AccountSource,
900
- ];
571
+ const eventSelector = toEventSelector(abiEvent);
572
+
573
+ if (filteredEventSelectors.has(eventSelector)) {
574
+ topic1 = filteredEventSelectors.get(eventSelector)!.topic1;
575
+ topic2 = filteredEventSelectors.get(eventSelector)!.topic2;
576
+ topic3 = filteredEventSelectors.get(eventSelector)!.topic3;
577
+ } else {
578
+ topic1 = null;
579
+ topic2 = null;
580
+ topic3 = null;
581
+ }
582
+
583
+ const filter = {
584
+ type: "log",
585
+ chainId: chain.id,
586
+ sourceId: source.name,
587
+ address,
588
+ topic0: eventSelector,
589
+ topic1,
590
+ topic2,
591
+ topic3,
592
+ fromBlock,
593
+ toBlock,
594
+ hasTransactionReceipt: source.includeTransactionReceipts ?? false,
595
+ include: defaultLogFilterInclude.concat(
596
+ source.includeTransactionReceipts
597
+ ? defaultTransactionReceiptInclude.map(
598
+ (value) => `transactionReceipt.${value}` as const,
599
+ )
600
+ : [],
601
+ ),
602
+ } satisfies LogFilter;
603
+
604
+ const eventCallback = {
605
+ filter,
606
+ name: eventName,
607
+ fn: indexingFunction.fn,
608
+ chain,
609
+ type: "contract",
610
+ abiItem: abiEvent,
611
+ metadata: {
612
+ safeName: logEventName,
613
+ abi: source.abi,
614
+ },
615
+ } satisfies EventCallback;
616
+
617
+ perChainEventCallbacks.get(chain.id)!.push(eventCallback);
618
+ }
619
+
620
+ for (const functionEventName of registeredCallTraceEvents) {
621
+ const abiFunction = source.abi.find(
622
+ (item): item is AbiFunction =>
623
+ item.type === "function" &&
624
+ toSafeName({ abi: source.abi, item }) === functionEventName,
625
+ );
626
+ if (abiFunction === undefined) {
627
+ throw new Error(
628
+ `Validation failed: Function name for function '${functionEventName}' not found in the contract ABI. Got '${functionEventName}', expected one of [${source.abi
629
+ .filter((item): item is AbiFunction => item.type === "function")
630
+ .map((item) => `'${toSafeName({ abi: source.abi, item })}'`)
631
+ .join(", ")}].`,
632
+ );
633
+ }
634
+
635
+ const eventName = `${source.name}.${functionEventName}`;
636
+
637
+ const indexingFunction = indexingFunctions.find(
638
+ (f) => f.name === eventName,
639
+ )!;
640
+
641
+ const filter = {
642
+ type: "trace",
643
+ chainId: chain.id,
644
+ sourceId: source.name,
645
+ fromAddress: undefined,
646
+ toAddress: address,
647
+ callType: "CALL",
648
+ functionSelector: toFunctionSelector(abiFunction),
649
+ includeReverted: false,
650
+ fromBlock,
651
+ toBlock,
652
+ hasTransactionReceipt: source.includeTransactionReceipts ?? false,
653
+ include: defaultTraceFilterInclude.concat(
654
+ source.includeTransactionReceipts
655
+ ? defaultTransactionReceiptInclude.map(
656
+ (value) => `transactionReceipt.${value}` as const,
657
+ )
658
+ : [],
659
+ ),
660
+ } satisfies TraceFilter;
661
+
662
+ const eventCallback = {
663
+ filter,
664
+ name: eventName,
665
+ fn: indexingFunction.fn,
666
+ chain,
667
+ type: "contract",
668
+ abiItem: abiFunction,
669
+ metadata: {
670
+ safeName: functionEventName,
671
+ abi: source.abi,
901
672
  },
902
- ),
903
- )
904
- )
905
- .flat()
906
- .filter((source) => {
673
+ } satisfies EventCallback;
674
+
675
+ perChainEventCallbacks.get(chain.id)!.push(eventCallback);
676
+ }
677
+
678
+ if (
679
+ registeredLogEvents.length === 0 &&
680
+ registeredCallTraceEvents.length === 0
681
+ ) {
682
+ logs.push({
683
+ level: "warn",
684
+ msg: "No registered indexing functions",
685
+ name: source.name,
686
+ type: "contract",
687
+ });
688
+ }
689
+ }
690
+
691
+ for (const source of flattenSources(config.accounts ?? {})) {
692
+ const chain = chains.find((n) => n.name === source.chain)!;
693
+
694
+ const fromBlock = await resolveBlockNumber(source.startBlock, chain);
695
+ const toBlock = await resolveBlockNumber(source.endBlock, chain);
696
+
697
+ const resolvedAddress = source?.address;
698
+ if (resolvedAddress === undefined) {
699
+ throw new Error(
700
+ `Validation failed: Account '${source.name}' must specify an 'address'.`,
701
+ );
702
+ }
703
+
704
+ let address: FilterAddress;
705
+
706
+ if (
707
+ typeof resolvedAddress === "object" &&
708
+ !Array.isArray(resolvedAddress)
709
+ ) {
710
+ const factoryFromBlock =
711
+ (await resolveBlockNumber(resolvedAddress.startBlock, chain)) ??
712
+ fromBlock;
713
+
714
+ const factoryToBlock =
715
+ (await resolveBlockNumber(resolvedAddress.endBlock, chain)) ?? toBlock;
716
+
717
+ // Note that this can throw.
718
+ const logFactory = buildLogFactory({
719
+ chainId: chain.id,
720
+ ...resolvedAddress,
721
+ fromBlock: factoryFromBlock,
722
+ toBlock: factoryToBlock,
723
+ });
724
+
725
+ address = logFactory;
726
+ } else {
727
+ for (const address of Array.isArray(resolvedAddress)
728
+ ? resolvedAddress
729
+ : [resolvedAddress]) {
730
+ if (!address!.startsWith("0x"))
731
+ throw new Error(
732
+ `Validation failed: Invalid prefix for address '${address}'. Got '${address!.slice(
733
+ 0,
734
+ 2,
735
+ )}', expected '0x'.`,
736
+ );
737
+ if (address!.length !== 42)
738
+ throw new Error(
739
+ `Validation failed: Invalid length for address '${address}'. Got ${address!.length}, expected 42 characters.`,
740
+ );
741
+ }
742
+
743
+ const validatedAddress = Array.isArray(resolvedAddress)
744
+ ? dedupe(resolvedAddress).map((r) => toLowerCase(r))
745
+ : resolvedAddress !== undefined
746
+ ? toLowerCase(resolvedAddress)
747
+ : undefined;
748
+
749
+ address = validatedAddress;
750
+ }
751
+
752
+ const filters = [
753
+ {
754
+ type: "transaction",
755
+ chainId: chain.id,
756
+ sourceId: source.name,
757
+ fromAddress: undefined,
758
+ toAddress: address,
759
+ includeReverted: false,
760
+ fromBlock,
761
+ toBlock,
762
+ hasTransactionReceipt: true,
763
+ include: defaultTransactionFilterInclude,
764
+ },
765
+ {
766
+ type: "transaction",
767
+ chainId: chain.id,
768
+ sourceId: source.name,
769
+ fromAddress: address,
770
+ toAddress: undefined,
771
+ includeReverted: false,
772
+ fromBlock,
773
+ toBlock,
774
+ hasTransactionReceipt: true,
775
+ include: defaultTransactionFilterInclude,
776
+ },
777
+ {
778
+ type: "transfer",
779
+ chainId: chain.id,
780
+ sourceId: source.name,
781
+ fromAddress: undefined,
782
+ toAddress: address,
783
+ includeReverted: false,
784
+ fromBlock,
785
+ toBlock,
786
+ hasTransactionReceipt: source.includeTransactionReceipts ?? false,
787
+ include: defaultTransferFilterInclude.concat(
788
+ source.includeTransactionReceipts
789
+ ? defaultTransactionReceiptInclude.map(
790
+ (value) => `transactionReceipt.${value}` as const,
791
+ )
792
+ : [],
793
+ ),
794
+ },
795
+ {
796
+ type: "transfer",
797
+ chainId: chain.id,
798
+ sourceId: source.name,
799
+ fromAddress: address,
800
+ toAddress: undefined,
801
+ includeReverted: false,
802
+ fromBlock,
803
+ toBlock,
804
+ hasTransactionReceipt: source.includeTransactionReceipts ?? false,
805
+ include: defaultTransferFilterInclude.concat(
806
+ source.includeTransactionReceipts
807
+ ? defaultTransactionReceiptInclude.map(
808
+ (value) => `transactionReceipt.${value}` as const,
809
+ )
810
+ : [],
811
+ ),
812
+ },
813
+ ] satisfies [
814
+ TransactionFilter,
815
+ TransactionFilter,
816
+ TransferFilter,
817
+ TransferFilter,
818
+ ];
819
+
820
+ let hasRegisteredIndexingFunction = false;
821
+
822
+ for (const filter of filters) {
907
823
  const eventName =
908
- source.filter.type === "transaction"
909
- ? source.filter.fromAddress === undefined
824
+ filter.type === "transaction"
825
+ ? filter.fromAddress === undefined
910
826
  ? `${source.name}:transaction:to`
911
827
  : `${source.name}:transaction:from`
912
- : source.filter.fromAddress === undefined
828
+ : filter.fromAddress === undefined
913
829
  ? `${source.name}:transfer:to`
914
830
  : `${source.name}:transfer:from`;
915
831
 
916
- const hasRegisteredIndexingFunction =
917
- indexingFunctions[eventName] !== undefined;
918
- if (!hasRegisteredIndexingFunction) {
919
- logs.push({
920
- level: "debug",
921
- msg: "No registered indexing functions",
922
- chain: source.chain.name,
923
- chain_id: source.chain.id,
832
+ const indexingFunction = indexingFunctions.find(
833
+ (f) => f.name === eventName,
834
+ );
835
+
836
+ if (indexingFunction) {
837
+ hasRegisteredIndexingFunction = true;
838
+
839
+ const eventCallback = {
840
+ filter,
924
841
  name: eventName,
925
- type: source.filter.type,
926
- });
842
+ fn: indexingFunction.fn,
843
+ chain,
844
+ type: "account",
845
+ direction: filter.fromAddress === undefined ? "to" : "from",
846
+ } satisfies EventCallback;
847
+
848
+ perChainEventCallbacks.get(chain.id)!.push(eventCallback);
927
849
  }
928
- return hasRegisteredIndexingFunction;
929
- });
850
+ }
930
851
 
931
- const blockSources: BlockSource[] = (
932
- await Promise.all(
933
- flattenSources(config.blocks ?? {}).map(async (source) => {
934
- const chain = chains.find((n) => n.name === source.chain)!;
852
+ if (hasRegisteredIndexingFunction === false) {
853
+ logs.push({
854
+ level: "warn",
855
+ msg: "No registered indexing functions",
856
+ name: source.name,
857
+ type: "account",
858
+ });
859
+ }
860
+ }
935
861
 
936
- const intervalMaybeNan = source.interval ?? 1;
937
- const interval = Number.isNaN(intervalMaybeNan) ? 0 : intervalMaybeNan;
862
+ for (const source of flattenSources(config.blocks ?? {})) {
863
+ const chain = chains.find((n) => n.name === source.chain)!;
938
864
 
939
- if (!Number.isInteger(interval) || interval === 0) {
940
- throw new Error(
941
- `Validation failed: Invalid interval for block interval '${source.name}'. Got ${interval}, expected a non-zero integer.`,
942
- );
943
- }
865
+ const intervalMaybeNan = source.interval ?? 1;
866
+ const interval = Number.isNaN(intervalMaybeNan) ? 0 : intervalMaybeNan;
944
867
 
945
- const fromBlock = await resolveBlockNumber(source.startBlock, chain);
946
- const toBlock = await resolveBlockNumber(source.endBlock, chain);
868
+ if (!Number.isInteger(interval) || interval === 0) {
869
+ throw new Error(
870
+ `Validation failed: Invalid interval for block interval '${source.name}'. Got ${interval}, expected a non-zero integer.`,
871
+ );
872
+ }
947
873
 
948
- return {
949
- type: "block",
950
- name: source.name,
951
- chain,
952
- filter: {
953
- type: "block",
954
- chainId: chain.id,
955
- interval: interval,
956
- offset: (fromBlock ?? 0) % interval,
957
- fromBlock,
958
- toBlock,
959
- hasTransactionReceipt: false,
960
- include: defaultBlockFilterInclude,
961
- },
962
- } satisfies BlockSource;
963
- }),
964
- )
965
- )
966
- .flat()
967
- .filter((source) => {
968
- const hasRegisteredIndexingFunction =
969
- indexingFunctions[`${source.name}:block`] !== undefined;
970
- if (!hasRegisteredIndexingFunction) {
971
- logs.push({
972
- level: "warn",
973
- msg: "No registered indexing functions",
974
- chain: source.chain.name,
975
- chain_id: source.chain.id,
976
- name: source.name,
977
- type: "block",
978
- });
979
- }
980
- return hasRegisteredIndexingFunction;
981
- });
874
+ const fromBlock = await resolveBlockNumber(source.startBlock, chain);
875
+ const toBlock = await resolveBlockNumber(source.endBlock, chain);
876
+
877
+ const eventName = `${source.name}:block`;
878
+
879
+ const indexingFunction = indexingFunctions.find(
880
+ (f) => f.name === eventName,
881
+ );
982
882
 
983
- const sources = [...contractSources, ...accountSources, ...blockSources];
883
+ if (indexingFunction) {
884
+ const filter = {
885
+ type: "block",
886
+ chainId: chain.id,
887
+ sourceId: source.name,
888
+ interval: interval,
889
+ offset: (fromBlock ?? 0) % interval,
890
+ fromBlock,
891
+ toBlock,
892
+ hasTransactionReceipt: false,
893
+ include: defaultBlockFilterInclude,
894
+ } satisfies BlockFilter;
895
+
896
+ const eventCallback = {
897
+ filter,
898
+ name: eventName,
899
+ fn: indexingFunction.fn,
900
+ chain,
901
+ type: "block",
902
+ } satisfies EventCallback;
903
+
904
+ perChainEventCallbacks.get(chain.id)!.push(eventCallback);
905
+ } else {
906
+ logs.push({
907
+ level: "warn",
908
+ msg: "No registered indexing functions",
909
+ name: source.name,
910
+ type: "block",
911
+ });
912
+ }
913
+ }
984
914
 
985
915
  // Filter out any chains that don't have any sources registered.
986
916
  const chainsWithSources: Chain[] = [];
987
917
  const rpcsWithSources: Rpc[] = [];
988
918
  const finalizedBlocksWithSources: LightBlock[] = [];
919
+ const eventCallbacksWithSources: EventCallback[][] = [];
920
+ const setupCallbacksWithSources: SetupCallback[][] = [];
921
+ const contractsWithSources: { [name: string]: Contract }[] = [];
989
922
 
990
923
  for (let i = 0; i < chains.length; i++) {
991
924
  const chain = chains[i]!;
992
925
  const rpc = rpcs[i]!;
993
- const hasSources = sources.some(
994
- (source) => source.chain.name === chain.name,
995
- );
926
+ const hasIndexingFunctions =
927
+ perChainEventCallbacks.get(chain.id)!.length > 0 ||
928
+ perChainSetupCallbacks.get(chain.id)!.length > 0;
996
929
 
997
- if (hasSources) {
930
+ if (hasIndexingFunctions) {
998
931
  chainsWithSources.push(chain);
999
932
  rpcsWithSources.push(rpc);
1000
933
  finalizedBlocksWithSources.push(finalizedBlocks[i]!);
934
+ eventCallbacksWithSources.push(perChainEventCallbacks.get(chain.id)!);
935
+ setupCallbacksWithSources.push(perChainSetupCallbacks.get(chain.id)!);
936
+ contractsWithSources.push(perChainContracts.get(chain.id)!);
1001
937
  } else {
1002
938
  logs.push({
1003
939
  level: "warn",
@@ -1008,9 +944,9 @@ export async function buildIndexingFunctions({
1008
944
  }
1009
945
  }
1010
946
 
1011
- if (Object.keys(indexingFunctions).length === 0) {
947
+ if (chainsWithSources.length === 0) {
1012
948
  throw new Error(
1013
- "Validation failed: Found 0 registered indexing functions.",
949
+ "Validation failed: Found 0 chains with registered indexing functions.",
1014
950
  );
1015
951
  }
1016
952
 
@@ -1018,8 +954,9 @@ export async function buildIndexingFunctions({
1018
954
  chains: chainsWithSources,
1019
955
  rpcs: rpcsWithSources,
1020
956
  finalizedBlocks: finalizedBlocksWithSources,
1021
- sources,
1022
- indexingFunctions,
957
+ eventCallbacks: eventCallbacksWithSources,
958
+ setupCallbacks: setupCallbacksWithSources,
959
+ contracts: contractsWithSources,
1023
960
  logs,
1024
961
  };
1025
962
  }
@@ -1141,29 +1078,30 @@ export function buildConfig({
1141
1078
  export async function safeBuildIndexingFunctions({
1142
1079
  common,
1143
1080
  config,
1144
- rawIndexingFunctions,
1081
+ indexingFunctions,
1145
1082
  configBuild,
1146
1083
  }: {
1147
1084
  common: Common;
1148
1085
  config: Config;
1149
- rawIndexingFunctions: RawIndexingFunctions;
1086
+ indexingFunctions: IndexingFunctions;
1150
1087
  configBuild: Pick<IndexingBuild, "chains" | "rpcs">;
1151
1088
  }) {
1152
1089
  try {
1153
1090
  const result = await buildIndexingFunctions({
1154
1091
  common,
1155
1092
  config,
1156
- rawIndexingFunctions,
1093
+ indexingFunctions,
1157
1094
  configBuild,
1158
1095
  });
1159
1096
 
1160
1097
  return {
1161
1098
  status: "success",
1162
- sources: result.sources,
1163
1099
  chains: result.chains,
1164
1100
  rpcs: result.rpcs,
1165
1101
  finalizedBlocks: result.finalizedBlocks,
1166
- indexingFunctions: result.indexingFunctions,
1102
+ eventCallbacks: result.eventCallbacks,
1103
+ setupCallbacks: result.setupCallbacks,
1104
+ contracts: result.contracts,
1167
1105
  logs: result.logs,
1168
1106
  } as const;
1169
1107
  } catch (_error) {