envio 2.15.0 → 2.16.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envio",
3
- "version": "v2.15.0",
3
+ "version": "v2.16.1",
4
4
  "description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
5
5
  "bin": "./bin.js",
6
6
  "repository": {
@@ -23,10 +23,10 @@
23
23
  },
24
24
  "homepage": "https://envio.dev",
25
25
  "optionalDependencies": {
26
- "envio-linux-x64": "v2.15.0",
27
- "envio-linux-arm64": "v2.15.0",
28
- "envio-darwin-x64": "v2.15.0",
29
- "envio-darwin-arm64": "v2.15.0"
26
+ "envio-linux-x64": "v2.16.1",
27
+ "envio-linux-arm64": "v2.16.1",
28
+ "envio-darwin-x64": "v2.16.1",
29
+ "envio-darwin-arm64": "v2.16.1"
30
30
  },
31
31
  "dependencies": {
32
32
  "@envio-dev/hypersync-client": "0.6.3",
package/src/Internal.res CHANGED
@@ -64,11 +64,18 @@ type genericHandlerWithLoader<'loader, 'handler, 'eventFilters> = {
64
64
  preRegisterDynamicContracts?: bool,
65
65
  }
66
66
 
67
- type baseEventConfig = {
67
+ // This is private so it's not manually constructed internally
68
+ // The idea is that it can only be coerced from fuel/evmEventConfig
69
+ // and it can include their fields. We prevent manual creation,
70
+ // so the fields are not overwritten and we can safely cast the type back to fuel/evmEventConfig
71
+ type eventConfig = private {
68
72
  id: string,
69
73
  name: string,
70
74
  contractName: string,
71
75
  isWildcard: bool,
76
+ // Usually always false for wildcard events
77
+ // But might be true for wildcard event with dynamic event filter by addresses
78
+ dependsOnAddresses: bool,
72
79
  preRegisterDynamicContracts: bool,
73
80
  loader: option<loader>,
74
81
  handler: option<handler>,
@@ -83,7 +90,7 @@ type fuelEventKind =
83
90
  | Transfer
84
91
  | Call
85
92
  type fuelEventConfig = {
86
- ...baseEventConfig,
93
+ ...eventConfig,
87
94
  kind: fuelEventKind,
88
95
  }
89
96
  type fuelContractConfig = {
@@ -98,21 +105,26 @@ type topicSelection = {
98
105
  topic3: array<EvmTypes.Hex.t>,
99
106
  }
100
107
 
108
+ type eventFiltersArgs = {chainId: int, addresses: array<Address.t>}
109
+
110
+ type eventFilters =
111
+ Static(array<topicSelection>) | Dynamic(array<Address.t> => array<topicSelection>)
112
+
101
113
  type evmEventConfig = {
102
- ...baseEventConfig,
103
- getTopicSelectionsOrThrow: (~chain: ChainMap.Chain.t) => array<topicSelection>,
114
+ ...eventConfig,
115
+ getEventFiltersOrThrow: ChainMap.Chain.t => eventFilters,
104
116
  blockSchema: S.schema<eventBlock>,
105
117
  transactionSchema: S.schema<eventTransaction>,
106
118
  convertHyperSyncEventArgs: HyperSyncClient.Decoder.decodedEvent => eventParams,
107
119
  }
108
120
  type evmContractConfig = {
109
121
  name: string,
110
- abi: Ethers.abi,
122
+ abi: EvmTypes.Abi.t,
111
123
  events: array<evmEventConfig>,
112
124
  }
113
125
 
114
126
  type eventItem = {
115
- eventConfig: baseEventConfig,
127
+ eventConfig: eventConfig,
116
128
  timestamp: int,
117
129
  chain: ChainMap.Chain.t,
118
130
  blockNumber: int,
@@ -149,5 +161,3 @@ type entity = private {id: string}
149
161
 
150
162
  @genType.import(("./bindings/OpaqueTypes.ts", "invalid"))
151
163
  type noEventFilters
152
- type eventFilters
153
- type eventFiltersArgs = {chainId: int}
@@ -57,53 +57,123 @@ let make = (~addresses, ~topicSelections) => {
57
57
  {addresses, topicSelections}
58
58
  }
59
59
 
60
- let fromEventFiltersOrThrow = {
60
+ type parsedEventFilters = {
61
+ getEventFiltersOrThrow: ChainMap.Chain.t => Internal.eventFilters,
62
+ dependsOnAddresses: bool,
63
+ }
64
+
65
+ let parseEventFiltersOrThrow = {
61
66
  let emptyTopics = []
62
67
  let noopGetter = _ => emptyTopics
63
68
 
64
69
  (
65
- ~chain,
66
70
  ~eventFilters: option<Js.Json.t>,
67
71
  ~sighash,
72
+ ~params,
68
73
  ~topic1=noopGetter,
69
74
  ~topic2=noopGetter,
70
75
  ~topic3=noopGetter,
71
- ) => {
76
+ ): parsedEventFilters => {
77
+ let dependsOnAddresses = ref(false)
72
78
  let topic0 = [sighash->EvmTypes.Hex.fromStringUnsafe]
73
- switch eventFilters {
74
- | None => [
75
- {
76
- Internal.topic0,
77
- topic1: emptyTopics,
78
- topic2: emptyTopics,
79
- topic3: emptyTopics,
80
- },
81
- ]
82
- | Some(eventFilters) => {
83
- let eventFilters = if Js.typeof(eventFilters) === "function" {
84
- (eventFilters->(Utils.magic: Js.Json.t => Internal.eventFiltersArgs => Js.Json.t))({
85
- chainId: chain->ChainMap.Chain.toChainId,
86
- })
87
- } else {
88
- eventFilters
89
- }
79
+ let default = {
80
+ Internal.topic0,
81
+ topic1: emptyTopics,
82
+ topic2: emptyTopics,
83
+ topic3: emptyTopics,
84
+ }
90
85
 
91
- switch eventFilters {
92
- | Array([]) => [%raw(`{}`)]
93
- | Array(a) => a
94
- | _ => [eventFilters]
95
- }->Js.Array2.map(eventFilter => {
96
- switch eventFilter {
97
- | Object(eventFilter) => {
98
- Internal.topic0,
99
- topic1: topic1(eventFilter),
100
- topic2: topic2(eventFilter),
101
- topic3: topic3(eventFilter),
86
+ let parse = (eventFilters: Js.Json.t): array<Internal.topicSelection> => {
87
+ switch eventFilters {
88
+ | Array([]) => [%raw(`{}`)]
89
+ | Array(a) => a
90
+ | _ => [eventFilters]
91
+ }->Js.Array2.map(eventFilter => {
92
+ switch eventFilter {
93
+ | Object(eventFilter) => {
94
+ let filterKeys = eventFilter->Js.Dict.keys
95
+ switch filterKeys {
96
+ | [] => default
97
+ | _ => {
98
+ filterKeys->Js.Array2.forEach(key => {
99
+ if params->Js.Array2.includes(key)->not {
100
+ // In TS type validation doesn't catch this
101
+ // when we have eventFilters as a callback
102
+ Js.Exn.raiseError(
103
+ `Invalid event filters configuration. The event doesn't have an indexed parameter "${key}" and can't use it for filtering`,
104
+ )
105
+ }
106
+ })
107
+ {
108
+ Internal.topic0,
109
+ topic1: topic1(eventFilter),
110
+ topic2: topic2(eventFilter),
111
+ topic3: topic3(eventFilter),
112
+ }
113
+ }
102
114
  }
103
- | _ => Js.Exn.raiseError("Invalid event filters configuration. Expected an object")
104
115
  }
105
- })
116
+ | _ => Js.Exn.raiseError("Invalid event filters configuration. Expected an object")
117
+ }
118
+ })
119
+ }
120
+
121
+ let getEventFiltersOrThrow = switch eventFilters {
122
+ | None => {
123
+ let static: Internal.eventFilters = Static([default])
124
+ _ => static
125
+ }
126
+ | Some(eventFilters) =>
127
+ if Js.typeof(eventFilters) === "function" {
128
+ let fn = eventFilters->(Utils.magic: Js.Json.t => Internal.eventFiltersArgs => Js.Json.t)
129
+ // When user passess a function to event filters we need to
130
+ // first determine whether it uses addresses or not
131
+ // Because the fetching logic will be different for wildcard events
132
+ // 1. If wildcard event doesn't use addresses,
133
+ // it should start fetching even without static addresses in the config
134
+ // 2. If wildcard event uses addresses in event filters,
135
+ // it should first wait for dynamic contract registration
136
+ // So to deterimine which case we run the function with dummy args
137
+ // and check if it uses addresses by using the getter.
138
+ try {
139
+ let args = (
140
+ {
141
+ chainId: 0,
142
+ addresses: [],
143
+ }: Internal.eventFiltersArgs
144
+ )->Utils.Object.defineProperty(
145
+ "addresses",
146
+ {
147
+ get: () => {
148
+ dependsOnAddresses := true
149
+ []
150
+ },
151
+ },
152
+ )
153
+ let _ = fn(args)
154
+ } catch {
155
+ | _ => ()
156
+ }
157
+ if dependsOnAddresses.contents {
158
+ chain => Internal.Dynamic(
159
+ addresses => fn({chainId: chain->ChainMap.Chain.toChainId, addresses})->parse,
160
+ )
161
+ } else {
162
+ // When we don't depend on addresses, can mark the event filter
163
+ // as static and avoid recalculating on every batch
164
+ chain => Internal.Static(
165
+ fn({chainId: chain->ChainMap.Chain.toChainId, addresses: []})->parse,
166
+ )
167
+ }
168
+ } else {
169
+ let static: Internal.eventFilters = Static(eventFilters->parse)
170
+ _ => static
106
171
  }
107
172
  }
173
+
174
+ {
175
+ getEventFiltersOrThrow,
176
+ dependsOnAddresses: dependsOnAddresses.contents,
177
+ }
108
178
  }
109
179
  }
package/src/Utils.res CHANGED
@@ -7,6 +7,21 @@ let delay = milliseconds =>
7
7
  }, milliseconds)
8
8
  })
9
9
 
10
+ module Object = {
11
+ // Define a type for the property descriptor
12
+ type propertyDescriptor<'a> = {
13
+ configurable?: bool,
14
+ enumerable?: bool,
15
+ writable?: bool,
16
+ value?: 'a,
17
+ get?: unit => 'a,
18
+ set?: 'a => unit,
19
+ }
20
+
21
+ @val @scope("Object")
22
+ external defineProperty: ('obj, string, propertyDescriptor<'a>) => 'obj = "defineProperty"
23
+ }
24
+
10
25
  module Option = {
11
26
  let mapNone = (opt: option<'a>, val: 'b): option<'b> => {
12
27
  switch opt {
@@ -50,6 +65,20 @@ module Dict = {
50
65
  */
51
66
  external dangerouslyGetNonOption: (dict<'a>, string) => option<'a> = ""
52
67
 
68
+ let push = (dict, key, value) => {
69
+ switch dict->dangerouslyGetNonOption(key) {
70
+ | Some(arr) => arr->Js.Array2.push(value)->ignore
71
+ | None => dict->Js.Dict.set(key, [value])
72
+ }
73
+ }
74
+
75
+ let pushMany = (dict, key, values) => {
76
+ switch dict->dangerouslyGetNonOption(key) {
77
+ | Some(arr) => arr->Js.Array2.pushMany(values)->ignore
78
+ | None => dict->Js.Dict.set(key, values)
79
+ }
80
+ }
81
+
53
82
  let merge: (dict<'a>, dict<'a>) => dict<'a> = %raw(`(dictA, dictB) => ({...dictA, ...dictB})`)
54
83
 
55
84
  let map = (dict, fn) => {
@@ -55,7 +55,7 @@ module CombinedFilter = {
55
55
  type combinedFilterRecord = {
56
56
  address?: array<Address.t>,
57
57
  //The second element of the tuple is the
58
- topics: array<array<EvmTypes.Hex.t>>,
58
+ topics: Rpc.GetLogs.topicQuery,
59
59
  fromBlock: int,
60
60
  toBlock: int,
61
61
  }
@@ -147,17 +147,18 @@ module JsonRpcProvider = {
147
147
  @send
148
148
  external getTransaction: (t, ~transactionHash: string) => promise<transaction> = "getTransaction"
149
149
 
150
- let makeGetTransactionFields = (~getTransactionByHash) => async (log: log): promise<unknown> => {
151
- let transaction = await getTransactionByHash(log.transactionHash)
152
- // Mutating should be fine, since the transaction isn't used anywhere else outside the function
153
- let fields: {..} = transaction->Obj.magic
150
+ let makeGetTransactionFields = (~getTransactionByHash) =>
151
+ async (log: log): promise<unknown> => {
152
+ let transaction = await getTransactionByHash(log.transactionHash)
153
+ // Mutating should be fine, since the transaction isn't used anywhere else outside the function
154
+ let fields: {..} = transaction->Obj.magic
154
155
 
155
- // Make it compatible with HyperSync transaction fields
156
- fields["transactionIndex"] = log.transactionIndex
157
- fields["input"] = fields["data"]
156
+ // Make it compatible with HyperSync transaction fields
157
+ fields["transactionIndex"] = log.transactionIndex
158
+ fields["input"] = fields["data"]
158
159
 
159
- fields->Obj.magic
160
- }
160
+ fields->Obj.magic
161
+ }
161
162
 
162
163
  type block = {
163
164
  _difficulty: bigint,