envio 3.3.0-alpha.10 → 3.3.0-alpha.12

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 (60) hide show
  1. package/licenses/CLA.md +35 -0
  2. package/licenses/EULA.md +67 -0
  3. package/licenses/LICENSE.md +45 -0
  4. package/licenses/README.md +35 -0
  5. package/package.json +9 -8
  6. package/src/Batch.res.mjs +1 -1
  7. package/src/ChainFetching.res +30 -18
  8. package/src/ChainFetching.res.mjs +23 -13
  9. package/src/ChainState.res +45 -30
  10. package/src/ChainState.res.mjs +22 -8
  11. package/src/ChainState.resi +5 -0
  12. package/src/Config.res +9 -5
  13. package/src/Config.res.mjs +2 -2
  14. package/src/Core.res +20 -0
  15. package/src/Core.res.mjs +12 -0
  16. package/src/CrossChainState.res +64 -29
  17. package/src/CrossChainState.res.mjs +17 -8
  18. package/src/EventConfigBuilder.res +21 -46
  19. package/src/EventConfigBuilder.res.mjs +18 -25
  20. package/src/EventProcessing.res +8 -5
  21. package/src/EventProcessing.res.mjs +2 -1
  22. package/src/FetchState.res +428 -322
  23. package/src/FetchState.res.mjs +316 -233
  24. package/src/HandlerRegister.res +3 -1
  25. package/src/HandlerRegister.res.mjs +3 -2
  26. package/src/Internal.res +11 -2
  27. package/src/LogSelection.res +0 -62
  28. package/src/LogSelection.res.mjs +0 -74
  29. package/src/RawEvent.res +2 -2
  30. package/src/Rollback.res +32 -13
  31. package/src/Rollback.res.mjs +24 -11
  32. package/src/SimulateDeadInputTracker.res +1 -1
  33. package/src/SimulateItems.res +38 -6
  34. package/src/SimulateItems.res.mjs +28 -9
  35. package/src/TestIndexer.res +2 -2
  36. package/src/TestIndexer.res.mjs +2 -2
  37. package/src/TopicFilter.res +1 -25
  38. package/src/TopicFilter.res.mjs +0 -74
  39. package/src/bindings/Viem.res +0 -5
  40. package/src/sources/EventRouter.res +0 -21
  41. package/src/sources/EventRouter.res.mjs +0 -12
  42. package/src/sources/EvmChain.res +2 -27
  43. package/src/sources/EvmChain.res.mjs +2 -23
  44. package/src/sources/EvmRpcClient.res +12 -15
  45. package/src/sources/EvmRpcClient.res.mjs +3 -3
  46. package/src/sources/HyperSync.res +9 -38
  47. package/src/sources/HyperSync.res.mjs +16 -28
  48. package/src/sources/HyperSync.resi +2 -2
  49. package/src/sources/HyperSyncClient.res +88 -11
  50. package/src/sources/HyperSyncClient.res.mjs +39 -6
  51. package/src/sources/HyperSyncSource.res +18 -199
  52. package/src/sources/HyperSyncSource.res.mjs +9 -128
  53. package/src/sources/Rpc.res +0 -32
  54. package/src/sources/Rpc.res.mjs +1 -46
  55. package/src/sources/RpcSource.res +29 -175
  56. package/src/sources/RpcSource.res.mjs +32 -110
  57. package/src/sources/SimulateSource.res +37 -19
  58. package/src/sources/SimulateSource.res.mjs +27 -10
  59. package/src/sources/SvmHyperSyncSource.res +13 -3
  60. package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
@@ -97,10 +97,6 @@ let getErrorMessage = (exn: exn): option<string> =>
97
97
  | _ => None
98
98
  }
99
99
 
100
- type logSelection = {
101
- addresses: option<array<Address.t>>,
102
- topicQuery: Rpc.GetLogs.topicQuery,
103
- }
104
100
 
105
101
  // `EvmRpcClient.getNextPage` throws a napi error whose message is a JSON
106
102
  // payload describing the retry decision:
@@ -161,122 +157,6 @@ let parseGetNextPageRetryError = (exn: exn): option<(
161
157
  | _ => None
162
158
  }
163
159
 
164
- type selectionConfig = {
165
- getLogSelectionsOrThrow: (
166
- ~addressesByContractName: dict<array<Address.t>>,
167
- ) => array<logSelection>,
168
- }
169
-
170
- let getSelectionConfig = (selection: FetchState.selection) => {
171
- let evmOnEventRegistrations =
172
- selection.onEventRegistrations->(
173
- Utils.magic: array<Internal.onEventRegistration> => array<Internal.evmOnEventRegistration>
174
- )
175
-
176
- if evmOnEventRegistrations->Utils.Array.isEmpty {
177
- throw(
178
- Source.GetItemsError(
179
- UnsupportedSelection({
180
- message: "Invalid events configuration for the partition. Nothing to fetch. Please, report to the Envio team.",
181
- }),
182
- ),
183
- )
184
- }
185
-
186
- // eth_getLogs takes one address list and one topic selection per request, so
187
- // fan out to one request per selection. Each address-bound event is grouped by
188
- // its contract and later scoped to that contract's own addresses — pooling all
189
- // contracts' addresses would let one contract's query fetch a sibling's logs,
190
- // which route back by address and bypass the sibling's filter (routing never
191
- // re-applies it). Pure-wildcard events carry no address constraint, so they're
192
- // pooled and resolved once.
193
- let noAddressTopicSelections = []
194
- let byContract = Dict.make()
195
- let wildcardByContract = Dict.make()
196
- let contractNames = Utils.Set.make()
197
-
198
- evmOnEventRegistrations->Array.forEach(reg => {
199
- let contractName = reg.eventConfig.contractName
200
- let {isWildcard, dependsOnAddresses, resolvedWhere} = reg
201
- if dependsOnAddresses {
202
- contractNames->Utils.Set.add(contractName)->ignore
203
- (isWildcard ? wildcardByContract : byContract)->Utils.Dict.pushMany(
204
- contractName,
205
- resolvedWhere.topicSelections,
206
- )
207
- } else {
208
- noAddressTopicSelections
209
- ->Array.pushMany(
210
- resolvedWhere.topicSelections->LogSelection.materializeTopicSelections(~addresses=[]),
211
- )
212
- ->ignore
213
- }
214
- })
215
-
216
- // `compressTopicSelections` folds the filter-less events into a single topic0
217
- // OR-set, keeping the common case at one request.
218
- let toLogSelections = (~addresses, topicSelections): array<logSelection> =>
219
- topicSelections
220
- ->LogSelection.compressTopicSelections
221
- ->Array.map(topicSelection => {
222
- addresses,
223
- topicQuery: topicSelection->Rpc.GetLogs.mapTopicQuery,
224
- })
225
-
226
- // Address-independent, so resolve once (the wildcard partition reuses this).
227
- let noAddressLogSelections = toLogSelections(~addresses=None, noAddressTopicSelections)
228
-
229
- let getLogSelectionsOrThrow = if contractNames->Utils.Set.size === 0 {
230
- (~addressesByContractName as _) => noAddressLogSelections
231
- } else {
232
- (~addressesByContractName): array<logSelection> => {
233
- let logSelections = noAddressLogSelections->Array.copy
234
- contractNames->Utils.Set.forEach(contractName => {
235
- switch addressesByContractName->Utils.Dict.dangerouslyGetNonOption(contractName) {
236
- | None
237
- | Some([]) => ()
238
- | Some(addresses) =>
239
- // Non-wildcard filters, scoped to this contract's addresses.
240
- switch byContract->Utils.Dict.dangerouslyGetNonOption(contractName) {
241
- | Some(topicSelections) =>
242
- logSelections
243
- ->Array.pushMany(
244
- toLogSelections(
245
- ~addresses=Some(addresses),
246
- topicSelections->LogSelection.materializeTopicSelections(~addresses),
247
- ),
248
- )
249
- ->ignore
250
- | None => ()
251
- }
252
-
253
- // Wildcard-by-address filters fold the address into the topics,
254
- // so they still match any address.
255
- switch wildcardByContract->Utils.Dict.dangerouslyGetNonOption(contractName) {
256
- | Some(topicSelections) =>
257
- logSelections
258
- ->Array.pushMany(
259
- toLogSelections(
260
- ~addresses=None,
261
- topicSelections->LogSelection.materializeTopicSelections(~addresses),
262
- ),
263
- )
264
- ->ignore
265
- | None => ()
266
- }
267
- }
268
- })
269
- logSelections
270
- }
271
- }
272
-
273
- {
274
- getLogSelectionsOrThrow: getLogSelectionsOrThrow,
275
- }
276
- }
277
-
278
- let memoGetSelectionConfig = () => Utils.WeakMap.memoize(getSelectionConfig)
279
-
280
160
  // Type-erase a schema for storage in the field registry
281
161
  external toFieldSchema: S.t<'a> => S.t<JSON.t> = "%identity"
282
162
 
@@ -735,8 +615,8 @@ type options = {
735
615
  syncConfig: Config.sourceSync,
736
616
  url: string,
737
617
  chain: ChainMap.Chain.t,
738
- eventRouter: EventRouter.t<Internal.evmOnEventRegistration>,
739
- allEventParams: array<HyperSyncClient.Decoder.eventParamsInput>,
618
+ // The chain's registrations, indexed by their sequential `index`.
619
+ onEventRegistrations: array<Internal.evmOnEventRegistration>,
740
620
  lowercaseAddresses: bool,
741
621
  ws?: string,
742
622
  headers?: dict<string>,
@@ -748,8 +628,7 @@ let make = (
748
628
  syncConfig,
749
629
  url,
750
630
  chain,
751
- eventRouter,
752
- allEventParams,
631
+ onEventRegistrations,
753
632
  lowercaseAddresses,
754
633
  ?ws,
755
634
  ?headers,
@@ -765,12 +644,12 @@ let make = (
765
644
  }
766
645
  let name = `RPC (${urlHost})`
767
646
 
768
- let getSelectionConfig = memoGetSelectionConfig()
769
-
770
647
  let client = Rpc.makeClient(url, ~headers?)
771
648
  let rpcClient = EvmRpcClient.make(
772
649
  ~url,
773
- ~allEventParams,
650
+ ~eventRegistrations=HyperSyncClient.Registration.fromOnEventRegistrations(
651
+ onEventRegistrations,
652
+ ),
774
653
  ~checksumAddresses=!lowercaseAddresses,
775
654
  ~syncConfig,
776
655
  ~headers?,
@@ -921,7 +800,7 @@ let make = (
921
800
  ~fromBlock,
922
801
  ~toBlock,
923
802
  ~addressesByContractName,
924
- ~contractNameByAddress,
803
+ ~contractNameByAddress as _,
925
804
  ~knownHeight,
926
805
  ~partitionId,
927
806
  ~selection: FetchState.selection,
@@ -944,26 +823,22 @@ let make = (
944
823
  ->Promise.thenResolve(json => Some(parseBlockInfo(json)))
945
824
  : Promise.resolve(None)
946
825
 
947
- let {getLogSelectionsOrThrow} = getSelectionConfig(selection)
948
- let logSelections = getLogSelectionsOrThrow(~addressesByContractName)
826
+ if selection.onEventRegistrations->Utils.Array.isEmpty {
827
+ throw(
828
+ Source.GetItemsError(
829
+ UnsupportedSelection({
830
+ message: "Invalid events configuration for the partition. Nothing to fetch. Please, report to the Envio team.",
831
+ }),
832
+ ),
833
+ )
834
+ }
949
835
 
950
836
  let {items, toBlock: queriedToBlock, requestStats} = try await rpcClient.getNextPage({
951
837
  fromBlock,
952
838
  toBlockCeiling: toBlock,
953
- logSelections: logSelections->Array.map(({
954
- addresses,
955
- topicQuery,
956
- }): EvmRpcClient.logSelectionInput => {
957
- ?addresses,
958
- topics: topicQuery->Array.map(filter =>
959
- switch filter {
960
- | Rpc.GetLogs.Null => Nullable.null
961
- | Single(topic) => Nullable.make([topic])
962
- | Multiple(topics) => Nullable.make(topics)
963
- }
964
- ),
965
- }),
966
839
  partitionId,
840
+ registrationIndexes: selection.onEventRegistrations->Array.map(reg => reg.index),
841
+ addressesByContractName,
967
842
  }) catch {
968
843
  | exn =>
969
844
  switch exn->parseGetNextPageRetryError {
@@ -997,32 +872,15 @@ let make = (
997
872
  ->Promise.thenResolve(parseBlockInfo)
998
873
 
999
874
  let parsedQueueItems = await items
1000
- ->Array.filterMap(({log, params: maybeDecodedEvent}: EvmRpcClient.rpcEventItem) => {
1001
- let topic0 = log.topics[0]->Option.getOr("0x0")
1002
- let routedAddress = if lowercaseAddresses {
1003
- log.address->Address.Evm.fromAddressLowercaseOrThrow
1004
- } else {
1005
- log.address->Address.Evm.fromAddressOrThrow
1006
- }
1007
-
1008
- switch eventRouter->EventRouter.get(
1009
- ~tag=EventRouter.getEvmEventId(~sighash=topic0, ~topicCount=log.topics->Array.length),
1010
- ~contractNameByAddress,
1011
- ~contractAddress=routedAddress,
1012
- ) {
1013
- | None => None
1014
- | Some(onEventRegistration) =>
1015
- let eventConfig =
1016
- onEventRegistration.eventConfig->(
1017
- Utils.magic: Internal.eventConfig => Internal.evmEventConfig
1018
- )
1019
- switch maybeDecodedEvent
1020
- ->Nullable.toOption
1021
- ->Option.flatMap(Dict.get(_, eventConfig.contractName)) {
1022
- | Some(decoded) =>
1023
- Some(
1024
- (
1025
- async () => {
875
+ ->Array.map(({log, onEventRegistrationIndex, params: decoded}: EvmRpcClient.rpcEventItem) => {
876
+ // `log.address` comes back already normalized to the client's casing.
877
+ let onEventRegistration = onEventRegistrations->Array.getUnsafe(onEventRegistrationIndex)
878
+ let eventConfig =
879
+ onEventRegistration.eventConfig->(
880
+ Utils.magic: Internal.eventConfig => Internal.evmEventConfig
881
+ )
882
+ (
883
+ async () => {
1026
884
  let (block, transaction) = try await Promise.all2((
1027
885
  log->getEventBlockOrThrow(~selectedBlockFields=eventConfig.selectedBlockFields),
1028
886
  log->getEventTransactionOrThrow(
@@ -1074,16 +932,12 @@ let make = (
1074
932
  params: decoded,
1075
933
  block,
1076
934
  transaction,
1077
- srcAddress: routedAddress,
935
+ srcAddress: log.address,
1078
936
  logIndex: log.logIndex,
1079
937
  }->Evm.fromPayload,
1080
938
  })
1081
- }
1082
- )(),
1083
- )
1084
- | None => None
1085
939
  }
1086
- }
940
+ )()
1087
941
  })
1088
942
  ->Promise.all
1089
943
 
@@ -9,17 +9,16 @@ import * as Address from "../Address.res.mjs";
9
9
  import * as Logging from "../Logging.res.mjs";
10
10
  import * as Internal from "../Internal.res.mjs";
11
11
  import * as LazyLoader from "../LazyLoader.res.mjs";
12
- import * as EventRouter from "./EventRouter.res.mjs";
13
12
  import * as Performance from "../bindings/Performance.res.mjs";
14
13
  import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
15
14
  import * as EvmRpcClient from "./EvmRpcClient.res.mjs";
16
- import * as LogSelection from "../LogSelection.res.mjs";
17
15
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
18
16
  import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
19
17
  import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
20
18
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
21
19
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
22
20
  import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
21
+ import * as HyperSyncClient from "./HyperSyncClient.res.mjs";
23
22
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
24
23
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
25
24
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
@@ -193,69 +192,6 @@ function parseGetNextPageRetryError(exn) {
193
192
  ]);
194
193
  }
195
194
 
196
- function getSelectionConfig(selection) {
197
- let evmOnEventRegistrations = selection.onEventRegistrations;
198
- if (Utils.$$Array.isEmpty(evmOnEventRegistrations)) {
199
- throw {
200
- RE_EXN_ID: Source.GetItemsError,
201
- _1: {
202
- TAG: "UnsupportedSelection",
203
- message: "Invalid events configuration for the partition. Nothing to fetch. Please, report to the Envio team."
204
- },
205
- Error: new Error()
206
- };
207
- }
208
- let noAddressTopicSelections = [];
209
- let byContract = {};
210
- let wildcardByContract = {};
211
- let contractNames = new Set();
212
- evmOnEventRegistrations.forEach(reg => {
213
- let contractName = reg.eventConfig.contractName;
214
- let resolvedWhere = reg.resolvedWhere;
215
- if (reg.dependsOnAddresses) {
216
- contractNames.add(contractName);
217
- return Utils.Dict.pushMany(reg.isWildcard ? wildcardByContract : byContract, contractName, resolvedWhere.topicSelections);
218
- } else {
219
- noAddressTopicSelections.push(...LogSelection.materializeTopicSelections(resolvedWhere.topicSelections, []));
220
- return;
221
- }
222
- });
223
- let toLogSelections = (addresses, topicSelections) => LogSelection.compressTopicSelections(topicSelections).map(topicSelection => ({
224
- addresses: addresses,
225
- topicQuery: Rpc.GetLogs.mapTopicQuery(topicSelection)
226
- }));
227
- let noAddressLogSelections = toLogSelections(undefined, noAddressTopicSelections);
228
- let getLogSelectionsOrThrow = contractNames.size === 0 ? param => noAddressLogSelections : addressesByContractName => {
229
- let logSelections = noAddressLogSelections.slice();
230
- contractNames.forEach(contractName => {
231
- let addresses = addressesByContractName[contractName];
232
- if (addresses === undefined) {
233
- return;
234
- }
235
- if (addresses.length === 0) {
236
- return;
237
- }
238
- let topicSelections = byContract[contractName];
239
- if (topicSelections !== undefined) {
240
- logSelections.push(...toLogSelections(addresses, LogSelection.materializeTopicSelections(topicSelections, addresses)));
241
- }
242
- let topicSelections$1 = wildcardByContract[contractName];
243
- if (topicSelections$1 !== undefined) {
244
- logSelections.push(...toLogSelections(undefined, LogSelection.materializeTopicSelections(topicSelections$1, addresses)));
245
- return;
246
- }
247
- });
248
- return logSelections;
249
- };
250
- return {
251
- getLogSelectionsOrThrow: getLogSelectionsOrThrow
252
- };
253
- }
254
-
255
- function memoGetSelectionConfig() {
256
- return Utils.$$WeakMap.memoize(getSelectionConfig);
257
- }
258
-
259
195
  let lowercaseAddressSchema = S$RescriptSchema.transform(S$RescriptSchema.string, param => ({
260
196
  p: str => str.toLowerCase()
261
197
  }));
@@ -790,16 +726,15 @@ function makeThrowingGetEventTransaction(getTransactionJson, getReceiptJson, low
790
726
  function make(param) {
791
727
  let headers = param.headers;
792
728
  let lowercaseAddresses = param.lowercaseAddresses;
793
- let eventRouter = param.eventRouter;
729
+ let onEventRegistrations = param.onEventRegistrations;
794
730
  let chain = param.chain;
795
731
  let url = param.url;
796
732
  let syncConfig = param.syncConfig;
797
733
  let host = Utils.Url.getHostFromUrl(url);
798
734
  let urlHost = host !== undefined ? host : Stdlib_JsError.throwWithMessage(`The RPC url for chain ` + chain.toString() + ` is in incorrect format. The RPC url needs to start with either http:// or https://`);
799
735
  let name = `RPC (` + urlHost + `)`;
800
- let getSelectionConfig$1 = Utils.$$WeakMap.memoize(getSelectionConfig);
801
736
  let client = Rpc.makeClient(url, headers);
802
- let rpcClient = EvmRpcClient.make(url, !lowercaseAddresses, syncConfig, undefined, headers, param.allEventParams);
737
+ let rpcClient = EvmRpcClient.make(url, !lowercaseAddresses, syncConfig, undefined, headers, HyperSyncClient.Registration.fromOnEventRegistrations(onEventRegistrations));
803
738
  let pendingRequestStats = [];
804
739
  let recordRequest = (method, seconds) => {
805
740
  pendingRequestStats.push({
@@ -885,43 +820,41 @@ function make(param) {
885
820
  Error: new Error()
886
821
  };
887
822
  }, lowercaseAddresses);
888
- let getItemsOrThrow = async (fromBlock, toBlock, addressesByContractName, contractNameByAddress, knownHeight, partitionId, selection, param, retry, param$1) => {
823
+ let getItemsOrThrow = async (fromBlock, toBlock, addressesByContractName, param, knownHeight, partitionId, selection, param$1, retry, param$2) => {
889
824
  let startFetchingBatchTimeRef = Performance.now();
890
825
  let toBlock$1 = toBlock !== undefined ? Primitive_int.min(toBlock, knownHeight) : knownHeight;
891
826
  let firstBlockParentPromise = fromBlock > 0 ? LazyLoader.get(blockLoader.contents, fromBlock - 1 | 0).then(json => parseBlockInfo(json)) : Promise.resolve(undefined);
892
- let match = getSelectionConfig$1(selection);
893
- let logSelections = match.getLogSelectionsOrThrow(addressesByContractName);
894
- let match$1;
827
+ if (Utils.$$Array.isEmpty(selection.onEventRegistrations)) {
828
+ throw {
829
+ RE_EXN_ID: Source.GetItemsError,
830
+ _1: {
831
+ TAG: "UnsupportedSelection",
832
+ message: "Invalid events configuration for the partition. Nothing to fetch. Please, report to the Envio team."
833
+ },
834
+ Error: new Error()
835
+ };
836
+ }
837
+ let match;
895
838
  try {
896
- match$1 = await rpcClient.getNextPage({
839
+ match = await rpcClient.getNextPage({
897
840
  fromBlock: fromBlock,
898
841
  toBlockCeiling: toBlock$1,
899
- logSelections: logSelections.map(param => ({
900
- addresses: param.addresses,
901
- topics: param.topicQuery.map(filter => {
902
- if (filter === null) {
903
- return null;
904
- } else if (typeof filter === "string") {
905
- return [filter];
906
- } else {
907
- return filter;
908
- }
909
- })
910
- })),
911
- partitionId: partitionId
842
+ partitionId: partitionId,
843
+ registrationIndexes: selection.onEventRegistrations.map(reg => reg.index),
844
+ addressesByContractName: addressesByContractName
912
845
  });
913
846
  } catch (raw_exn) {
914
847
  let exn = Primitive_exceptions.internalToException(raw_exn);
915
- let match$2 = parseGetNextPageRetryError(exn);
916
- if (match$2 !== undefined) {
917
- match$2[2].forEach(stat => recordRequest(stat.method, stat.seconds));
848
+ let match$1 = parseGetNextPageRetryError(exn);
849
+ if (match$1 !== undefined) {
850
+ match$1[2].forEach(stat => recordRequest(stat.method, stat.seconds));
918
851
  throw {
919
852
  RE_EXN_ID: Source.GetItemsError,
920
853
  _1: {
921
854
  TAG: "FailedGettingItems",
922
855
  exn: exn,
923
- attemptedToBlock: match$2[0],
924
- retry: match$2[1]
856
+ attemptedToBlock: match$1[0],
857
+ retry: match$1[1]
925
858
  },
926
859
  Error: new Error()
927
860
  };
@@ -941,23 +874,14 @@ function make(param) {
941
874
  Error: new Error()
942
875
  };
943
876
  }
944
- let items = match$1.items;
945
- match$1.requestStats.forEach(stat => recordRequest(stat.method, stat.seconds));
946
- let latestFetchedBlockInfo = await LazyLoader.get(blockLoader.contents, match$1.toBlock).then(parseBlockInfo);
947
- let parsedQueueItems = await Promise.all(Stdlib_Array.filterMap(items, param => {
877
+ let items = match.items;
878
+ match.requestStats.forEach(stat => recordRequest(stat.method, stat.seconds));
879
+ let latestFetchedBlockInfo = await LazyLoader.get(blockLoader.contents, match.toBlock).then(parseBlockInfo);
880
+ let parsedQueueItems = await Promise.all(items.map(param => {
881
+ let decoded = param.params;
948
882
  let log = param.log;
949
- let topic0 = Stdlib_Option.getOr(log.topics[0], "0x0");
950
- let routedAddress = lowercaseAddresses ? Address.Evm.fromAddressLowercaseOrThrow(log.address) : Address.Evm.fromAddressOrThrow(log.address);
951
- let onEventRegistration = EventRouter.get(eventRouter, EventRouter.getEvmEventId(topic0, log.topics.length), routedAddress, contractNameByAddress);
952
- if (onEventRegistration === undefined) {
953
- return;
954
- }
883
+ let onEventRegistration = onEventRegistrations[param.onEventRegistrationIndex];
955
884
  let eventConfig = onEventRegistration.eventConfig;
956
- let decoded = Stdlib_Option.flatMap(Primitive_option.fromNullable(param.params), __x => __x[eventConfig.contractName]);
957
- if (decoded === undefined) {
958
- return;
959
- }
960
- let decoded$1 = Primitive_option.valFromOption(decoded);
961
885
  return (async () => {
962
886
  let match;
963
887
  try {
@@ -1007,9 +931,9 @@ function make(param) {
1007
931
  payload: {
1008
932
  contractName: eventConfig.contractName,
1009
933
  eventName: eventConfig.name,
1010
- params: decoded$1,
934
+ params: decoded,
1011
935
  chainId: chain,
1012
- srcAddress: routedAddress,
936
+ srcAddress: log.address,
1013
937
  logIndex: log.logIndex,
1014
938
  transaction: Primitive_option.some(match[1]),
1015
939
  block: Primitive_option.some(block)
@@ -1123,8 +1047,6 @@ export {
1123
1047
  getKnownRawBlockWithBackoff,
1124
1048
  getErrorMessage,
1125
1049
  parseGetNextPageRetryError,
1126
- getSelectionConfig,
1127
- memoGetSelectionConfig,
1128
1050
  lowercaseAddressSchema,
1129
1051
  checksumAddressSchema,
1130
1052
  makeBlockFieldRegistry,
@@ -1,8 +1,5 @@
1
1
  let make = (~items: array<Internal.item>, ~endBlock: int, ~chain: ChainMap.Chain.t): Source.t => {
2
- // getItemsOrThrow might be called multiple times with different partition ids.
3
- // Return all items on the first call and empty on subsequent calls to prevent
4
- // duplicate event processing.
5
- let delivered = ref(false)
2
+ let reportedHeight = max(endBlock, 1)
6
3
 
7
4
  {
8
5
  name: "SimulateSource",
@@ -16,38 +13,59 @@ let make = (~items: array<Internal.item>, ~endBlock: int, ~chain: ChainMap.Chain
16
13
  },
17
14
  getHeightOrThrow: () => {
18
15
  // Report at least height 1 so the engine doesn't treat 0 as "no blocks available"
19
- Promise.resolve({Source.height: max(endBlock, 1), requestStats: []})
16
+ Promise.resolve({Source.height: reportedHeight, requestStats: []})
20
17
  },
21
18
  getItemsOrThrow: (
22
- ~fromBlock as _,
23
- ~toBlock as _,
19
+ ~fromBlock,
20
+ ~toBlock,
24
21
  ~addressesByContractName as _,
25
- ~contractNameByAddress as _,
22
+ ~contractNameByAddress,
26
23
  ~knownHeight as _,
27
24
  ~partitionId as _,
28
- ~selection as _,
25
+ ~selection: FetchState.selection,
29
26
  ~itemsTarget as _,
30
27
  ~retry as _,
31
28
  ~logger as _,
32
29
  ) => {
33
- // Return all items on the first call, empty on subsequent.
34
- let result = if delivered.contents {
35
- []
36
- } else {
37
- delivered := true
38
- items
30
+ // Mirror a real backend: return only the items this query would match —
31
+ // in the block range, part of the selection, and (for non-wildcard events)
32
+ // emitted by an address the partition is querying. Wildcard events are
33
+ // over-fetched regardless of srcAddress, leaving the client-side address
34
+ // filter to gate them exactly as it does for a HyperSync response. Overlapping
35
+ // queries may return the same item more than once; the buffer dedups it.
36
+ let toBlockQueried = switch toBlock {
37
+ | Some(toBlock) => toBlock
38
+ | None => reportedHeight
39
39
  }
40
+ let selectionEventIds = Utils.Set.make()
41
+ selection.onEventRegistrations->Array.forEach(reg =>
42
+ selectionEventIds->Utils.Set.add(reg.eventConfig.id)->ignore
43
+ )
44
+
45
+ let parsedQueueItems = items->Array.filter(item => {
46
+ let eventItem = item->Internal.castUnsafeEventItem
47
+ let {blockNumber, onEventRegistration} = eventItem
48
+ if blockNumber < fromBlock || blockNumber > toBlockQueried {
49
+ false
50
+ } else if !(selectionEventIds->Utils.Set.has(onEventRegistration.eventConfig.id)) {
51
+ false
52
+ } else if onEventRegistration.isWildcard {
53
+ true
54
+ } else {
55
+ let sa = eventItem.payload->Internal.getPayloadSrcAddress->Address.toString
56
+ contractNameByAddress->Utils.Dict.dangerouslyGetNonOption(sa)->Option.isSome
57
+ }
58
+ })
40
59
 
41
- let reportedHeight = max(endBlock, 1)
42
60
  Promise.resolve({
43
61
  Source.knownHeight: reportedHeight,
44
62
  blockHashes: [],
45
- parsedQueueItems: result,
63
+ parsedQueueItems,
46
64
  // Simulate keeps the transaction and block inline on the payload; no store pages.
47
65
  transactionStore: None,
48
66
  blockStore: None,
49
- fromBlockQueried: 0,
50
- latestFetchedBlockNumber: reportedHeight,
67
+ fromBlockQueried: fromBlock,
68
+ latestFetchedBlockNumber: toBlockQueried,
51
69
  latestFetchedBlockTimestamp: 0,
52
70
  stats: {
53
71
  totalTimeElapsed: 0.,
@@ -1,11 +1,10 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
4
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
5
 
5
6
  function make(items, endBlock, chain) {
6
- let delivered = {
7
- contents: false
8
- };
7
+ let reportedHeight = Primitive_int.max(endBlock, 1);
9
8
  return {
10
9
  name: "SimulateSource",
11
10
  sourceFor: "Sync",
@@ -20,20 +19,38 @@ function make(items, endBlock, chain) {
20
19
  requestStats: []
21
20
  }),
22
21
  getHeightOrThrow: () => Promise.resolve({
23
- height: Primitive_int.max(endBlock, 1),
22
+ height: reportedHeight,
24
23
  requestStats: []
25
24
  }),
26
- getItemsOrThrow: (param, param$1, param$2, param$3, param$4, param$5, param$6, param$7, param$8, param$9) => {
27
- let result = delivered.contents ? [] : (delivered.contents = true, items);
28
- let reportedHeight = Primitive_int.max(endBlock, 1);
25
+ getItemsOrThrow: (fromBlock, toBlock, param, contractNameByAddress, param$1, param$2, selection, param$3, param$4, param$5) => {
26
+ let toBlockQueried = toBlock !== undefined ? toBlock : reportedHeight;
27
+ let selectionEventIds = new Set();
28
+ selection.onEventRegistrations.forEach(reg => {
29
+ selectionEventIds.add(reg.eventConfig.id);
30
+ });
31
+ let parsedQueueItems = items.filter(item => {
32
+ let blockNumber = item.blockNumber;
33
+ if (blockNumber < fromBlock || blockNumber > toBlockQueried) {
34
+ return false;
35
+ }
36
+ let onEventRegistration = item.onEventRegistration;
37
+ if (!selectionEventIds.has(onEventRegistration.eventConfig.id)) {
38
+ return false;
39
+ }
40
+ if (onEventRegistration.isWildcard) {
41
+ return true;
42
+ }
43
+ let sa = item.payload.srcAddress;
44
+ return Stdlib_Option.isSome(contractNameByAddress[sa]);
45
+ });
29
46
  return Promise.resolve({
30
47
  knownHeight: reportedHeight,
31
48
  blockHashes: [],
32
- parsedQueueItems: result,
49
+ parsedQueueItems: parsedQueueItems,
33
50
  transactionStore: undefined,
34
51
  blockStore: undefined,
35
- fromBlockQueried: 0,
36
- latestFetchedBlockNumber: reportedHeight,
52
+ fromBlockQueried: fromBlock,
53
+ latestFetchedBlockNumber: toBlockQueried,
37
54
  latestFetchedBlockTimestamp: 0,
38
55
  stats: {
39
56
  "total time elapsed (s)": 0
@@ -285,8 +285,8 @@ let make = (
285
285
  ): t => {
286
286
  let name = "SvmHyperSync"
287
287
 
288
- // Definitions drive query/decode building; the registrations drive routing
289
- // (they carry `isWildcard` and become each decoded item's `onEventRegistration`).
288
+ // Definitions drive query/decode building; registrations drive routing and
289
+ // are attached directly to decoded runtime items.
290
290
  let eventConfigs =
291
291
  onEventRegistrations->Array.map(reg =>
292
292
  reg.eventConfig->(Utils.magic: Internal.eventConfig => Internal.svmInstructionEventConfig)
@@ -488,6 +488,16 @@ let make = (
488
488
 
489
489
  switch maybeConfig {
490
490
  | None => ()
491
+ // Exclude instructions from failed transactions. HyperSync has no
492
+ // server-side predicate to filter instructions by parent-transaction
493
+ // success (`InstructionSelection` only exposes `is_inner`, and
494
+ // instruction/transaction selections union at block level rather than
495
+ // joining), so we filter client-side on the `isCommitted` flag HyperSync
496
+ // already delivers on every instruction row (a required column, zero extra
497
+ // bandwidth). When HyperSync adds a server-side `is_committed` predicate the
498
+ // query can push this down and the client-side check becomes a redundant
499
+ // safety net.
500
+ | Some(_) if !instr.isCommitted => ()
491
501
  | Some(onEventRegistration) =>
492
502
  let eventConfig =
493
503
  onEventRegistration.eventConfig->(
@@ -521,7 +531,7 @@ let make = (
521
531
  parsedQueueItems
522
532
  ->Array.push(
523
533
  Internal.Event({
524
- onEventRegistration,
534
+ onEventRegistration: (onEventRegistration :> Internal.onEventRegistration),
525
535
  chain,
526
536
  blockNumber: instr.slot,
527
537
  logIndex: synthLogIndex(instr),