envio 3.4.0 → 3.5.0-alpha.1

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.
@@ -2,7 +2,12 @@ type indexingAddress = Internal.indexingContract
2
2
 
3
3
  type contractConfig = {startBlock: option<int>}
4
4
 
5
- type t = dict<indexingAddress>
5
+ // Grouped by contract name, then by address string. Contracts are few, so
6
+ // per-contract operations (address count, the client-side filter's lookup
7
+ // dict) are direct, and whole-index scans (get by address, rollback) walk the
8
+ // small contract set. Address strings are globally unique across contracts
9
+ // (conflicting registrations are rejected before they reach here).
10
+ type t = dict<dict<indexingAddress>>
6
11
 
7
12
  let deriveEffectiveStartBlock = (~registrationBlock: int, ~contractStartBlock: option<int>) => {
8
13
  Pervasives.max(Pervasives.max(registrationBlock, 0), contractStartBlock->Option.getOr(0))
@@ -59,50 +64,88 @@ let makeIndexingAddress = (
59
64
  }
60
65
  }
61
66
 
67
+ let insert = (indexingAddresses: t, indexingAddress: indexingAddress) => {
68
+ indexingAddresses
69
+ ->Utils.Dict.getOrInsertEmptyDict(indexingAddress.contractName)
70
+ ->Dict.set(indexingAddress.address->Address.toString, indexingAddress)
71
+ }
72
+
62
73
  let make = (
63
74
  ~contractConfigs: dict<contractConfig>,
64
75
  ~addresses: array<Internal.indexingAddress>,
65
76
  ): t => {
66
77
  let indexingAddresses = Dict.make()
67
78
  addresses->Array.forEach(contract => {
68
- indexingAddresses->Dict.set(
69
- contract.address->Address.toString,
70
- makeIndexingAddress(~contract, ~contractConfigs),
71
- )
79
+ indexingAddresses->insert(makeIndexingAddress(~contract, ~contractConfigs))
72
80
  })
73
81
  indexingAddresses
74
82
  }
75
83
 
76
- let get = (indexingAddresses: t, address) =>
77
- indexingAddresses->Utils.Dict.dangerouslyGetNonOption(address)
84
+ // Address strings are globally unique, so the first inner dict holding the
85
+ // address owns it. for..in returns on the first hit without allocating a values
86
+ // array per lookup (get runs once per registration).
87
+ let get: (t, string) => option<indexingAddress> = %raw(`(index, address) => {
88
+ for (var contractName in index) {
89
+ var entry = index[contractName][address];
90
+ if (entry !== undefined) {
91
+ return entry;
92
+ }
93
+ }
94
+ return undefined;
95
+ }`)
96
+
97
+ let size = (indexingAddresses: t) => {
98
+ let total = ref(0)
99
+ indexingAddresses->Utils.Dict.forEach(inner => {
100
+ total := total.contents + inner->Utils.Dict.size
101
+ })
102
+ total.contents
103
+ }
78
104
 
79
- let size = (indexingAddresses: t) => indexingAddresses->Utils.Dict.size
105
+ // Number of registered addresses for a single contract — a for..in over that
106
+ // one contract's addresses. The trigger deciding when to switch a contract to
107
+ // client-side filtering reads this.
108
+ let contractCount = (indexingAddresses: t, ~contractName) =>
109
+ switch indexingAddresses->Utils.Dict.dangerouslyGetNonOption(contractName) {
110
+ | Some(inner) => inner->Utils.Dict.size
111
+ | None => 0
112
+ }
80
113
 
81
114
  let getContractAddresses = (indexingAddresses: t, ~contractName): array<Address.t> => {
82
- let addresses = []
83
- indexingAddresses->Utils.Dict.forEach(ia => {
84
- if ia.contractName === contractName {
85
- addresses->Array.push(ia.address)
86
- }
87
- })
88
- addresses
115
+ switch indexingAddresses->Utils.Dict.dangerouslyGetNonOption(contractName) {
116
+ | Some(inner) => inner->Dict.valuesToArray->Array.map(ia => ia.address)
117
+ | None => []
118
+ }
89
119
  }
90
120
 
91
- // Underlying dict for the precompiled `clientAddressFilter` only — it does raw
92
- // `indexingAddresses[srcAddress]` access in generated JS and can't take the opaque
93
- // type. Don't reach for this elsewhere; use the domain accessors above.
94
- let rawForFilter = (indexingAddresses: t): dict<indexingAddress> => indexingAddresses
121
+ let emptyContractDict: dict<indexingAddress> = Dict.make()
122
+
123
+ // The address→entry dict for a single contract, passed to that contract's
124
+ // precompiled `clientAddressFilter` (which does raw `byAddr[srcAddress]` access
125
+ // in generated JS). Every leaf of a filter references the event's own contract
126
+ // — `chain.<Contract>.addresses` only exposes the event's contract — so one
127
+ // inner dict covers the srcAddress and param-address checks alike. Returns a
128
+ // shared empty dict when the contract has no registered addresses.
129
+ let forContract = (indexingAddresses: t, ~contractName): dict<indexingAddress> =>
130
+ switch indexingAddresses->Utils.Dict.dangerouslyGetNonOption(contractName) {
131
+ | Some(inner) => inner
132
+ | None => emptyContractDict
133
+ }
95
134
 
96
135
  let register = (indexingAddresses: t, additions: dict<indexingAddress>) => {
97
- let _ = Utils.Dict.mergeInPlace(indexingAddresses, additions)
136
+ additions->Utils.Dict.forEach(indexingAddress => {
137
+ indexingAddresses->insert(indexingAddress)
138
+ })
98
139
  }
99
140
 
100
141
  let rollbackInPlace = (indexingAddresses: t, ~targetBlockNumber: int): unit => {
101
142
  // forEachWithKey is a `for..in`, so deleting the key currently being visited is
102
143
  // safe — it doesn't affect enumeration of the remaining keys.
103
- indexingAddresses->Utils.Dict.forEachWithKey((indexingContract, address) => {
104
- if indexingContract.registrationBlock > targetBlockNumber {
105
- indexingAddresses->Utils.Dict.deleteInPlace(address)
106
- }
144
+ indexingAddresses->Utils.Dict.forEach(inner => {
145
+ inner->Utils.Dict.forEachWithKey((indexingContract, address) => {
146
+ if indexingContract.registrationBlock > targetBlockNumber {
147
+ inner->Utils.Dict.deleteInPlace(address)
148
+ }
149
+ })
107
150
  })
108
151
  }
@@ -43,47 +43,75 @@ function makeIndexingAddress(contract, contractConfigs) {
43
43
  };
44
44
  }
45
45
 
46
+ function insert(indexingAddresses, indexingAddress) {
47
+ Utils.Dict.getOrInsertEmptyDict(indexingAddresses, indexingAddress.contractName)[indexingAddress.address] = indexingAddress;
48
+ }
49
+
46
50
  function make(contractConfigs, addresses) {
47
51
  let indexingAddresses = {};
48
- addresses.forEach(contract => {
49
- indexingAddresses[contract.address] = makeIndexingAddress(contract, contractConfigs);
50
- });
52
+ addresses.forEach(contract => insert(indexingAddresses, makeIndexingAddress(contract, contractConfigs)));
51
53
  return indexingAddresses;
52
54
  }
53
55
 
54
- function get(indexingAddresses, address) {
55
- return indexingAddresses[address];
56
- }
56
+ let get = ((index, address) => {
57
+ for (var contractName in index) {
58
+ var entry = index[contractName][address];
59
+ if (entry !== undefined) {
60
+ return entry;
61
+ }
62
+ }
63
+ return undefined;
64
+ });
57
65
 
58
66
  function size(indexingAddresses) {
59
- return Utils.Dict.size(indexingAddresses);
67
+ let total = {
68
+ contents: 0
69
+ };
70
+ Utils.Dict.forEach(indexingAddresses, inner => {
71
+ total.contents = total.contents + Utils.Dict.size(inner) | 0;
72
+ });
73
+ return total.contents;
74
+ }
75
+
76
+ function contractCount(indexingAddresses, contractName) {
77
+ let inner = indexingAddresses[contractName];
78
+ if (inner !== undefined) {
79
+ return Utils.Dict.size(inner);
80
+ } else {
81
+ return 0;
82
+ }
60
83
  }
61
84
 
62
85
  function getContractAddresses(indexingAddresses, contractName) {
63
- let addresses = [];
64
- Utils.Dict.forEach(indexingAddresses, ia => {
65
- if (ia.contractName === contractName) {
66
- addresses.push(ia.address);
67
- return;
68
- }
69
- });
70
- return addresses;
86
+ let inner = indexingAddresses[contractName];
87
+ if (inner !== undefined) {
88
+ return Object.values(inner).map(ia => ia.address);
89
+ } else {
90
+ return [];
91
+ }
71
92
  }
72
93
 
73
- function rawForFilter(indexingAddresses) {
74
- return indexingAddresses;
94
+ let emptyContractDict = {};
95
+
96
+ function forContract(indexingAddresses, contractName) {
97
+ let inner = indexingAddresses[contractName];
98
+ if (inner !== undefined) {
99
+ return inner;
100
+ } else {
101
+ return emptyContractDict;
102
+ }
75
103
  }
76
104
 
77
105
  function register(indexingAddresses, additions) {
78
- Object.assign(indexingAddresses, additions);
106
+ Utils.Dict.forEach(additions, indexingAddress => insert(indexingAddresses, indexingAddress));
79
107
  }
80
108
 
81
109
  function rollbackInPlace(indexingAddresses, targetBlockNumber) {
82
- Utils.Dict.forEachWithKey(indexingAddresses, (indexingContract, address) => {
110
+ Utils.Dict.forEach(indexingAddresses, inner => Utils.Dict.forEachWithKey(inner, (indexingContract, address) => {
83
111
  if (indexingContract.registrationBlock > targetBlockNumber) {
84
- return Utils.Dict.deleteInPlace(indexingAddresses, address);
112
+ return Utils.Dict.deleteInPlace(inner, address);
85
113
  }
86
- });
114
+ }));
87
115
  }
88
116
 
89
117
  export {
@@ -93,8 +121,9 @@ export {
93
121
  make,
94
122
  get,
95
123
  size,
124
+ contractCount,
96
125
  getContractAddresses,
97
- rawForFilter,
126
+ forContract,
98
127
  register,
99
128
  rollbackInPlace,
100
129
  }
@@ -21,12 +21,17 @@ let get: (t, string) => option<indexingAddress>
21
21
 
22
22
  let size: t => int
23
23
 
24
+ // Number of registered addresses for a single contract (scans just that
25
+ // contract's addresses).
26
+ let contractCount: (t, ~contractName: string) => int
27
+
24
28
  let getContractAddresses: (t, ~contractName: string) => array<Address.t>
25
29
 
26
- // Underlying dict for the precompiled `clientAddressFilter` only, which does raw
27
- // `indexingAddresses[srcAddress]` access in generated JS. Don't use elsewhere —
28
- // prefer the domain accessors so the opaque type stays enforced.
29
- let rawForFilter: t => dict<indexingAddress>
30
+ // The address→entry dict for a single contract, passed to that contract's
31
+ // precompiled `clientAddressFilter` (raw `byAddr[srcAddress]` access in
32
+ // generated JS). Don't use elsewhere — prefer the domain accessors so the
33
+ // opaque type stays enforced.
34
+ let forContract: (t, ~contractName: string) => dict<indexingAddress>
30
35
 
31
36
  let register: (t, dict<indexingAddress>) => unit
32
37
 
@@ -199,7 +199,11 @@ let handleWriteBatch = (
199
199
  if deleted->Array.length > 0 {
200
200
  entityObj->Dict.set("deleted", deleted->(Utils.magic: array<string> => unknown))
201
201
  }
202
- change->Dict.set(entityName, entityObj->(Utils.magic: dict<unknown> => unknown))
202
+ // Match the capitalized entity accessor the generated change types expose.
203
+ change->Dict.set(
204
+ entityName->Utils.String.capitalize,
205
+ entityObj->(Utils.magic: dict<unknown> => unknown),
206
+ )
203
207
  }
204
208
  })
205
209
  | None => ()
@@ -699,7 +703,9 @@ let createTestIndexer = (): t<'processConfig> => {
699
703
  entityOpsDict
700
704
  ->Dict.toArray
701
705
  ->Array.forEach(((name, ops)) => {
702
- result->Dict.set(name, ops->(Utils.magic: entityOperations => unknown))
706
+ // Expose the capitalized accessor (indexer.Pool_snapshots) the generated
707
+ // types declare, matching the handler-context keys.
708
+ result->Dict.set(name->Utils.String.capitalize, ops->(Utils.magic: entityOperations => unknown))
703
709
  })
704
710
 
705
711
  result->Dict.set(
@@ -146,7 +146,7 @@ function handleWriteBatch(state, updatedEntities, checkpointIds, checkpointChain
146
146
  if (deleted.length !== 0) {
147
147
  entityObj$1["deleted"] = deleted;
148
148
  }
149
- change[entityName] = entityObj$1;
149
+ change[Utils.$$String.capitalize(entityName)] = entityObj$1;
150
150
  });
151
151
  }
152
152
  state.processChanges.push(change);
@@ -470,7 +470,7 @@ function createTestIndexer() {
470
470
  result["chainIds"] = chainIds;
471
471
  result["chains"] = chains;
472
472
  Object.entries(entityOpsDict).forEach(param => {
473
- result[param[0]] = param[1];
473
+ result[Utils.$$String.capitalize(param[0])] = param[1];
474
474
  });
475
475
  result["process"] = processConfig => {
476
476
  if (state.processInProgress) {
@@ -1,6 +1,5 @@
1
1
  open Source
2
2
 
3
-
4
3
  // Surfaced by HyperSyncClient.getHeight (Rust) when HyperSync rejects the API
5
4
  // token. The corrupted-token test feeds the real server error through this
6
5
  // check so it can't silently drift away from what getHeightOrThrow guards on.
@@ -46,9 +45,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
46
45
  ~url=endpointUrl,
47
46
  ~apiToken,
48
47
  ~httpReqTimeoutMillis=clientTimeoutMillis,
49
- ~eventRegistrations=HyperSyncClient.Registration.fromOnEventRegistrations(
50
- onEventRegistrations,
51
- ),
48
+ ~eventRegistrations=HyperSyncClient.Registration.fromOnEventRegistrations(onEventRegistrations),
52
49
  ~enableChecksumAddresses=!lowercaseAddresses,
53
50
  ~serializationFormat,
54
51
  ~enableQueryCaching,
@@ -110,6 +107,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
110
107
  ~maxNumLogs=itemsTarget,
111
108
  ~registrationIndexes=selection.onEventRegistrations->Array.map(reg => reg.index),
112
109
  ~addressesByContractName,
110
+ ~clientFilteredContracts=selection.clientFilteredContracts,
113
111
  ) catch {
114
112
  | HyperSync.GetLogs.Error(error) =>
115
113
  throw(
@@ -180,8 +178,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`)
180
178
  let getBlock = blockNumber => blocksByNumber->Utils.Map.unsafeGet(blockNumber)
181
179
 
182
180
  pageUnsafe.items->Array.forEach(item => {
183
- let onEventRegistration =
184
- onEventRegistrations->Array.getUnsafe(item.onEventRegistrationIndex)
181
+ let onEventRegistration = onEventRegistrations->Array.getUnsafe(item.onEventRegistrationIndex)
185
182
  parsedQueueItems
186
183
  ->Array.push(makeEventBatchQueueItem(item, ~onEventRegistration))
187
184
  ->ignore
@@ -57,7 +57,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`);
57
57
  let startFetchingBatchTimeRef = Performance.now();
58
58
  let pageUnsafe;
59
59
  try {
60
- pageUnsafe = await HyperSync.GetLogs.query(client, fromBlock, toBlock, itemsTarget, selection.onEventRegistrations.map(reg => reg.index), addressesByContractName);
60
+ pageUnsafe = await HyperSync.GetLogs.query(client, fromBlock, toBlock, itemsTarget, selection.onEventRegistrations.map(reg => reg.index), addressesByContractName, selection.clientFilteredContracts);
61
61
  } catch (raw_error) {
62
62
  let error = Primitive_exceptions.internalToException(raw_error);
63
63
  if (error.RE_EXN_ID === HyperSync.GetLogs.$$Error) {
@@ -27,6 +27,10 @@ type nextPageParams = {
27
27
  // registrations passed at construction.
28
28
  registrationIndexes: array<int>,
29
29
  addressesByContractName: dict<array<Address.t>>,
30
+ // Contract names to fetch address-free even though their registrations
31
+ // depend on addresses (client-side filtering). None/empty means
32
+ // every address-dependent contract is filtered server-side.
33
+ clientFilteredContracts: option<array<string>>,
30
34
  }
31
35
 
32
36
  type nextPageResponse = {
@@ -93,13 +93,15 @@ module GetLogs = {
93
93
  ~maxNumLogs,
94
94
  ~registrationIndexes,
95
95
  ~addressesByContractName,
96
+ ~clientFilteredContracts,
96
97
  ): logsQueryPage => {
97
98
  let query: HyperSyncClient.EventItems.query = {
98
99
  fromBlock,
99
100
  toBlock,
100
- maxNumLogs,
101
+ ?maxNumLogs,
101
102
  registrationIndexes,
102
103
  addressesByContractName,
104
+ clientFilteredContracts,
103
105
  }
104
106
 
105
107
  let (res, transactionStore, blockStore) = switch await client.getEventItems(~query) {
@@ -82,13 +82,14 @@ function extractMissingParams(exn) {
82
82
  }
83
83
  }
84
84
 
85
- async function query(client, fromBlock, toBlock, maxNumLogs, registrationIndexes, addressesByContractName) {
85
+ async function query(client, fromBlock, toBlock, maxNumLogs, registrationIndexes, addressesByContractName, clientFilteredContracts) {
86
86
  let query$1 = {
87
87
  fromBlock: fromBlock,
88
88
  toBlock: toBlock,
89
89
  maxNumLogs: maxNumLogs,
90
90
  registrationIndexes: registrationIndexes,
91
- addressesByContractName: addressesByContractName
91
+ addressesByContractName: addressesByContractName,
92
+ clientFilteredContracts: clientFilteredContracts
92
93
  };
93
94
  let match;
94
95
  try {
@@ -32,9 +32,10 @@ module GetLogs: {
32
32
  ~client: HyperSyncClient.t,
33
33
  ~fromBlock: int,
34
34
  ~toBlock: option<int>,
35
- ~maxNumLogs: int,
35
+ ~maxNumLogs: option<int>,
36
36
  ~registrationIndexes: array<int>,
37
37
  ~addressesByContractName: dict<array<Address.t>>,
38
+ ~clientFilteredContracts: option<array<string>>,
38
39
  ) => promise<logsQueryPage>
39
40
  }
40
41
 
@@ -309,9 +309,14 @@ module EventItems = {
309
309
  fromBlock: int,
310
310
  // Inclusive; None queries to the end of available data.
311
311
  toBlock: option<int>,
312
- maxNumLogs: int,
312
+ // Absent means no server-side cap on the number of logs returned.
313
+ maxNumLogs?: int,
313
314
  registrationIndexes: array<int>,
314
315
  addressesByContractName: dict<array<Address.t>>,
316
+ // Contract names to fetch address-free even though their registrations
317
+ // depend on addresses (client-side filtering). None/empty means
318
+ // every address-dependent contract is filtered server-side.
319
+ clientFilteredContracts: option<array<string>>,
315
320
  }
316
321
 
317
322
  type item = {
@@ -97,7 +97,6 @@ let getErrorMessage = (exn: exn): option<string> =>
97
97
  | _ => None
98
98
  }
99
99
 
100
-
101
100
  // `EvmRpcClient.getNextPage` throws a napi error whose message is a JSON
102
101
  // payload describing the retry decision:
103
102
  // `{"kind":"Retry","attemptedToBlock":int,"errorMessage":string|null,
@@ -647,9 +646,7 @@ let make = (
647
646
  let client = Rpc.makeClient(url, ~headers?)
648
647
  let rpcClient = EvmRpcClient.make(
649
648
  ~url,
650
- ~eventRegistrations=HyperSyncClient.Registration.fromOnEventRegistrations(
651
- onEventRegistrations,
652
- ),
649
+ ~eventRegistrations=HyperSyncClient.Registration.fromOnEventRegistrations(onEventRegistrations),
653
650
  ~checksumAddresses=!lowercaseAddresses,
654
651
  ~syncConfig,
655
652
  ~headers?,
@@ -839,6 +836,7 @@ let make = (
839
836
  partitionId,
840
837
  registrationIndexes: selection.onEventRegistrations->Array.map(reg => reg.index),
841
838
  addressesByContractName,
839
+ clientFilteredContracts: selection.clientFilteredContracts,
842
840
  }) catch {
843
841
  | exn =>
844
842
  switch exn->parseGetNextPageRetryError {
@@ -879,63 +877,64 @@ let make = (
879
877
  onEventRegistration.eventConfig->(
880
878
  Utils.magic: Internal.eventConfig => Internal.evmEventConfig
881
879
  )
880
+
882
881
  (
883
882
  async () => {
884
- let (block, transaction) = try await Promise.all2((
885
- log->getEventBlockOrThrow(~selectedBlockFields=eventConfig.selectedBlockFields),
886
- log->getEventTransactionOrThrow(
887
- ~selectedTransactionFields=eventConfig.selectedTransactionFields->(
888
- Utils.magic: Utils.Set.t<string> => Utils.Set.t<Internal.evmTransactionField>
889
- ),
890
- ),
891
- )) catch {
892
- | TransactionDataNotFound({message}) =>
893
- let backoffMillis = switch retry {
894
- | 0 => 100
895
- | _ => 500 * retry
896
- }
897
- throw(
898
- Source.GetItemsError(
899
- FailedGettingItems({
900
- exn: %raw(`null`),
901
- attemptedToBlock: toBlock,
902
- retry: WithBackoff({
903
- message: `${message}. The RPC provider might be load-balanced between nodes that drift independently slightly from the head. Indexing should continue correctly after retrying the query in ${backoffMillis->Int.toString}ms.`,
904
- backoffMillis,
905
- }),
906
- }),
907
- ),
908
- )
909
- | exn =>
910
- throw(
911
- Source.GetItemsError(
912
- FailedGettingFieldSelection({
913
- message: "Failed getting selected fields. Please double-check your RPC provider returns correct data.",
914
- exn,
915
- blockNumber: log.blockNumber,
916
- logIndex: log.logIndex,
917
- }),
918
- ),
919
- )
920
- }
921
-
922
- Internal.Event({
923
- onEventRegistration: (onEventRegistration :> Internal.onEventRegistration),
924
- blockNumber: block->getBlockNumber,
925
- chain,
883
+ let (block, transaction) = try await Promise.all2((
884
+ log->getEventBlockOrThrow(~selectedBlockFields=eventConfig.selectedBlockFields),
885
+ log->getEventTransactionOrThrow(
886
+ ~selectedTransactionFields=eventConfig.selectedTransactionFields->(
887
+ Utils.magic: Utils.Set.t<string> => Utils.Set.t<Internal.evmTransactionField>
888
+ ),
889
+ ),
890
+ )) catch {
891
+ | TransactionDataNotFound({message}) =>
892
+ let backoffMillis = switch retry {
893
+ | 0 => 100
894
+ | _ => 500 * retry
895
+ }
896
+ throw(
897
+ Source.GetItemsError(
898
+ FailedGettingItems({
899
+ exn: %raw(`null`),
900
+ attemptedToBlock: toBlock,
901
+ retry: WithBackoff({
902
+ message: `${message}. The RPC provider might be load-balanced between nodes that drift independently slightly from the head. Indexing should continue correctly after retrying the query in ${backoffMillis->Int.toString}ms.`,
903
+ backoffMillis,
904
+ }),
905
+ }),
906
+ ),
907
+ )
908
+ | exn =>
909
+ throw(
910
+ Source.GetItemsError(
911
+ FailedGettingFieldSelection({
912
+ message: "Failed getting selected fields. Please double-check your RPC provider returns correct data.",
913
+ exn,
914
+ blockNumber: log.blockNumber,
926
915
  logIndex: log.logIndex,
927
- transactionIndex: log.transactionIndex,
928
- payload: {
929
- contractName: eventConfig.contractName,
930
- eventName: eventConfig.name,
931
- chainId: chain->ChainMap.Chain.toChainId,
932
- params: decoded,
933
- block,
934
- transaction,
935
- srcAddress: log.address,
936
- logIndex: log.logIndex,
937
- }->Evm.fromPayload,
938
- })
916
+ }),
917
+ ),
918
+ )
919
+ }
920
+
921
+ Internal.Event({
922
+ onEventRegistration: (onEventRegistration :> Internal.onEventRegistration),
923
+ blockNumber: block->getBlockNumber,
924
+ chain,
925
+ logIndex: log.logIndex,
926
+ transactionIndex: log.transactionIndex,
927
+ payload: {
928
+ contractName: eventConfig.contractName,
929
+ eventName: eventConfig.name,
930
+ chainId: chain->ChainMap.Chain.toChainId,
931
+ params: decoded,
932
+ block,
933
+ transaction,
934
+ srcAddress: log.address,
935
+ logIndex: log.logIndex,
936
+ }->Evm.fromPayload,
937
+ })
939
938
  }
940
939
  )()
941
940
  })
@@ -841,7 +841,8 @@ function make(param) {
841
841
  toBlockCeiling: toBlock$1,
842
842
  partitionId: partitionId,
843
843
  registrationIndexes: selection.onEventRegistrations.map(reg => reg.index),
844
- addressesByContractName: addressesByContractName
844
+ addressesByContractName: addressesByContractName,
845
+ clientFilteredContracts: selection.clientFilteredContracts
845
846
  });
846
847
  } catch (raw_exn) {
847
848
  let exn = Primitive_exceptions.internalToException(raw_exn);
@@ -82,8 +82,10 @@ type t = {
82
82
  // source should ask its backend for, from the query's own estResponseSize.
83
83
  // A HyperSync-backed source enforces it server-side, so a wrong estimate
84
84
  // truncates the response instead of overshooting the shared buffer. Sources
85
- // without an equivalent lever (RPC, Fuel, Simulate) ignore it.
86
- ~itemsTarget: int,
85
+ // without an equivalent lever (RPC, Fuel, Simulate) ignore it. None means no
86
+ // cap: bounded chunk queries fetch their whole range even if denser than
87
+ // expected, so client-side-filtered items can't truncate the range short.
88
+ ~itemsTarget: option<int>,
87
89
  ~retry: int,
88
90
  ~logger: Pino.t,
89
91
  ) => promise<blockRangeFetchResponse>,
@@ -188,7 +188,8 @@ module EventItems = {
188
188
  fromSlot: int,
189
189
  // Inclusive; None queries to the end of available data.
190
190
  toSlot: option<int>,
191
- maxNumInstructions: int,
191
+ // Absent means no server-side cap on the number of instructions returned.
192
+ maxNumInstructions?: int,
192
193
  registrationIndexes: array<int>,
193
194
  addressesByContractName: dict<array<Address.t>>,
194
195
  }
@@ -100,7 +100,7 @@ let make = (
100
100
  let query: SvmHyperSyncClient.EventItems.query = {
101
101
  fromSlot: fromBlock,
102
102
  toSlot: toBlock,
103
- maxNumInstructions: itemsTarget,
103
+ maxNumInstructions: ?itemsTarget,
104
104
  registrationIndexes: selection.onEventRegistrations->Array.map(reg => reg.index),
105
105
  addressesByContractName,
106
106
  }