envio 2.7.2 → 2.7.4

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.7.2",
3
+ "version": "v2.7.4",
4
4
  "description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
5
5
  "bin": "./bin.js",
6
6
  "repository": {
@@ -23,13 +23,16 @@
23
23
  },
24
24
  "homepage": "https://envio.dev",
25
25
  "optionalDependencies": {
26
- "envio-linux-x64": "v2.7.2",
27
- "envio-linux-arm64": "v2.7.2",
28
- "envio-darwin-x64": "v2.7.2",
29
- "envio-darwin-arm64": "v2.7.2"
26
+ "envio-linux-x64": "v2.7.4",
27
+ "envio-linux-arm64": "v2.7.4",
28
+ "envio-darwin-x64": "v2.7.4",
29
+ "envio-darwin-arm64": "v2.7.4"
30
30
  },
31
31
  "dependencies": {
32
- "rescript": "11.1.3"
32
+ "@envio-dev/hypersync-client": "0.6.2",
33
+ "rescript": "11.1.3",
34
+ "rescript-schema": "8.2.0",
35
+ "viem": "2.21.0"
33
36
  },
34
37
  "files": [
35
38
  "bin.js",
package/rescript.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envio",
3
- "namespace": true,
3
+ "namespace": false,
4
4
  "sources": [
5
5
  {
6
6
  "dir": "src",
@@ -11,5 +11,10 @@
11
11
  "package-specs": {
12
12
  "module": "commonjs",
13
13
  "in-source": true
14
- }
14
+ },
15
+ "gentypeconfig": {
16
+ "generatedFileExtension": ".gen.ts"
17
+ },
18
+ "bs-dependencies": ["rescript-schema"],
19
+ "bsc-flags": ["-open RescriptSchema"]
15
20
  }
@@ -0,0 +1,29 @@
1
+ @genType.import(("./bindings/OpaqueTypes.ts", "Address"))
2
+ type t
3
+
4
+ let schema = S.string->S.setName("Address")->(Utils.magic: S.t<string> => S.t<t>)
5
+
6
+ external toString: t => string = "%identity"
7
+
8
+ external unsafeFromString: string => t = "%identity"
9
+
10
+ module Evm = {
11
+ @module("viem")
12
+ external fromStringOrThrow: string => t = "getAddress"
13
+ // Reassign since the function might be used in the handler code
14
+ // and we don't want to have a "viem" import there. It's needed to keep "viem" a dependency
15
+ // of generated code instead of adding it to the indexer project dependencies.
16
+ // Also, we want a custom error message, which is searchable in our codebase.
17
+ let fromStringOrThrow = string => {
18
+ try {
19
+ fromStringOrThrow(string)
20
+ } catch {
21
+ | _ =>
22
+ Js.Exn.raiseError(
23
+ `Address "${string}" is invalid. Expected a 20-byte hex string starting with 0x.`,
24
+ )
25
+ }
26
+ }
27
+
28
+ let fromAddressOrThrow = address => address->toString->fromStringOrThrow
29
+ }
package/src/EvmTypes.res CHANGED
@@ -1,7 +1,14 @@
1
1
  module Hex = {
2
2
  type t
3
+ /**No string validation in schema*/
4
+ let schema =
5
+ S.string->S.setName("EVM.Hex")->(Utils.magic: S.t<string> => S.t<t>)
3
6
  external fromStringUnsafe: string => t = "%identity"
4
7
  external fromStringsUnsafe: array<string> => array<t> = "%identity"
5
8
  external toString: t => string = "%identity"
6
9
  external toStrings: array<t> => array<string> = "%identity"
7
10
  }
11
+
12
+ module Abi = {
13
+ type t
14
+ }
@@ -0,0 +1,55 @@
1
+ type t = {
2
+ mutable lastRunTimeMillis: float,
3
+ mutable isRunning: bool,
4
+ mutable isAwaitingInterval: bool,
5
+ mutable scheduled: option<unit => promise<unit>>,
6
+ intervalMillis: float,
7
+ logger: Pino.t,
8
+ }
9
+
10
+ let make = (~intervalMillis: int, ~logger) => {
11
+ lastRunTimeMillis: 0.,
12
+ isRunning: false,
13
+ isAwaitingInterval: false,
14
+ scheduled: None,
15
+ intervalMillis: intervalMillis->Belt.Int.toFloat,
16
+ logger,
17
+ }
18
+
19
+ let rec startInternal = async (throttler: t) => {
20
+ switch throttler {
21
+ | {scheduled: Some(fn), isRunning: false, isAwaitingInterval: false} =>
22
+ let timeSinceLastRun = Js.Date.now() -. throttler.lastRunTimeMillis
23
+
24
+ //Only execute if we are passed the interval
25
+ if timeSinceLastRun >= throttler.intervalMillis {
26
+ throttler.isRunning = true
27
+ throttler.scheduled = None
28
+ throttler.lastRunTimeMillis = Js.Date.now()
29
+
30
+ switch await fn() {
31
+ | exception exn =>
32
+ throttler.logger->Pino.errorExn(
33
+ Pino.createPinoMessageWithError("Scheduled action failed in throttler", exn),
34
+ )
35
+ | _ => ()
36
+ }
37
+ throttler.isRunning = false
38
+
39
+ await throttler->startInternal
40
+ } else {
41
+ //Store isAwaitingInterval in state so that timers don't continuously get created
42
+ throttler.isAwaitingInterval = true
43
+ let _ = Js.Global.setTimeout(() => {
44
+ throttler.isAwaitingInterval = false
45
+ throttler->startInternal->ignore
46
+ }, Belt.Int.fromFloat(throttler.intervalMillis -. timeSinceLastRun))
47
+ }
48
+ | _ => ()
49
+ }
50
+ }
51
+
52
+ let schedule = (throttler: t, fn) => {
53
+ throttler.scheduled = Some(fn)
54
+ throttler->startInternal->ignore
55
+ }
package/src/Utils.res ADDED
@@ -0,0 +1,346 @@
1
+ external magic: 'a => 'b = "%identity"
2
+
3
+ module Option = {
4
+ let mapNone = (opt: option<'a>, val: 'b): option<'b> => {
5
+ switch opt {
6
+ | None => Some(val)
7
+ | Some(_) => None
8
+ }
9
+ }
10
+
11
+ let catchToNone: (unit => 'a) => option<'a> = unsafeFunc => {
12
+ try {
13
+ unsafeFunc()->Some
14
+ } catch {
15
+ | _ => None
16
+ }
17
+ }
18
+
19
+ let flatten = opt =>
20
+ switch opt {
21
+ | None => None
22
+ | Some(opt) => opt
23
+ }
24
+
25
+ let getExn = (opt, message) => {
26
+ switch opt {
27
+ | None => Js.Exn.raiseError(message)
28
+ | Some(v) => v
29
+ }
30
+ }
31
+ }
32
+
33
+ module Tuple = {
34
+ /**Access a tuple value by its index*/
35
+ @warning("-27")
36
+ let get = (tuple: 'a, index: int): option<'b> => %raw(`tuple[index]`)
37
+ }
38
+
39
+ module Dict = {
40
+ @get_index
41
+ /**
42
+ It's the same as `Js.Dict.get` but it doesn't have runtime overhead to check if the key exists.
43
+ */
44
+ external dangerouslyGetNonOption: (dict<'a>, string) => option<'a> = ""
45
+
46
+ let merge: (dict<'a>, dict<'a>) => dict<'a> = %raw(`(dictA, dictB) => ({...dictA, ...dictB})`)
47
+
48
+ let updateImmutable: (
49
+ dict<'a>,
50
+ string,
51
+ 'a,
52
+ ) => dict<'a> = %raw(`(dict, key, value) => ({...dict, [key]: value})`)
53
+ }
54
+
55
+ module Math = {
56
+ let minOptInt = (a, b) =>
57
+ switch (a, b) {
58
+ | (Some(a), Some(b)) => Pervasives.min(a, b)->Some
59
+ | (Some(a), None) => Some(a)
60
+ | (None, Some(b)) => Some(b)
61
+ | (None, None) => None
62
+ }
63
+ }
64
+ module Array = {
65
+ @val external jsArrayCreate: int => array<'a> = "Array"
66
+
67
+ /* Given a comaprator and two sorted lists, combine them into a single sorted list */
68
+ let mergeSorted = (f: ('a, 'a) => bool, xs: array<'a>, ys: array<'a>) => {
69
+ if Array.length(xs) == 0 {
70
+ ys
71
+ } else if Array.length(ys) == 0 {
72
+ xs
73
+ } else {
74
+ let n = Array.length(xs) + Array.length(ys)
75
+ let result = jsArrayCreate(n)
76
+
77
+ let rec loop = (i, j, k) => {
78
+ if i < Array.length(xs) && j < Array.length(ys) {
79
+ if f(xs[i], ys[j]) {
80
+ result[k] = xs[i]
81
+ loop(i + 1, j, k + 1)
82
+ } else {
83
+ result[k] = ys[j]
84
+ loop(i, j + 1, k + 1)
85
+ }
86
+ } else if i < Array.length(xs) {
87
+ result[k] = xs[i]
88
+ loop(i + 1, j, k + 1)
89
+ } else if j < Array.length(ys) {
90
+ result[k] = ys[j]
91
+ loop(i, j + 1, k + 1)
92
+ }
93
+ }
94
+
95
+ loop(0, 0, 0)
96
+ result
97
+ }
98
+ }
99
+
100
+ /**
101
+ Creates a shallow copy of the array and sets the value at the given index
102
+ */
103
+ let setIndexImmutable = (arr: array<'a>, index: int, value: 'a): array<'a> => {
104
+ let shallowCopy = arr->Belt.Array.copy
105
+ shallowCopy->Js.Array2.unsafe_set(index, value)
106
+ shallowCopy
107
+ }
108
+
109
+ let transposeResults = (results: array<result<'a, 'b>>): result<array<'a>, 'b> => {
110
+ let rec loop = (index: int, output: array<'a>): result<array<'a>, 'b> => {
111
+ if index >= Array.length(results) {
112
+ Ok(output)
113
+ } else {
114
+ switch results->Js.Array2.unsafe_get(index) {
115
+ | Ok(value) => {
116
+ output[index] = value
117
+ loop(index + 1, output)
118
+ }
119
+ | Error(_) as err => err->(magic: result<'a, 'b> => result<array<'a>, 'b>)
120
+ }
121
+ }
122
+ }
123
+
124
+ loop(0, Belt.Array.makeUninitializedUnsafe(results->Js.Array2.length))
125
+ }
126
+
127
+ /**
128
+ Helper to check if a value exists in an array
129
+ */
130
+ let includes = (arr: array<'a>, val: 'a) =>
131
+ arr->Js.Array2.find(item => item == val)->Belt.Option.isSome
132
+
133
+ let isEmpty = (arr: array<_>) =>
134
+ switch arr {
135
+ | [] => true
136
+ | _ => false
137
+ }
138
+
139
+ let awaitEach = async (arr: array<'a>, fn: 'a => promise<unit>) => {
140
+ for i in 0 to arr->Array.length - 1 {
141
+ let item = arr[i]
142
+ await item->fn
143
+ }
144
+ }
145
+
146
+ /**
147
+ Creates a new array removing the item at the given index
148
+
149
+ Index > array length or < 0 results in a copy of the array
150
+ */
151
+ let removeAtIndex = (array, index) => {
152
+ if index < 0 || index >= array->Array.length {
153
+ array->Array.copy
154
+ } else {
155
+ let head = array->Js.Array2.slice(~start=0, ~end_=index)
156
+ let tail = array->Belt.Array.sliceToEnd(index + 1)
157
+ [...head, ...tail]
158
+ }
159
+ }
160
+
161
+ let last = (arr: array<'a>): option<'a> => arr->Belt.Array.get(arr->Array.length - 1)
162
+
163
+ let findReverseWithIndex = (arr: array<'a>, fn: 'a => bool): option<('a, int)> => {
164
+ let rec loop = (index: int) => {
165
+ if index < 0 {
166
+ None
167
+ } else {
168
+ let item = arr[index]
169
+ if fn(item) {
170
+ Some((item, index))
171
+ } else {
172
+ loop(index - 1)
173
+ }
174
+ }
175
+ }
176
+ loop(arr->Array.length - 1)
177
+ }
178
+
179
+ /**
180
+ Currently a bug in rescript if you ignore the return value of spliceInPlace
181
+ https://github.com/rescript-lang/rescript-compiler/issues/6991
182
+ */
183
+ @send
184
+ external spliceInPlace: (array<'a>, ~pos: int, ~remove: int) => array<'a> = "splice"
185
+ }
186
+
187
+ module String = {
188
+ let capitalize = str => {
189
+ str->Js.String2.slice(~from=0, ~to_=1)->Js.String.toUpperCase ++
190
+ str->Js.String2.sliceToEnd(~from=1)
191
+ }
192
+ }
193
+
194
+ /**
195
+ Useful when an unsafe unwrap is needed on Result type
196
+ and Error holds an exn. This is better than Result.getExn
197
+ because the excepion is not just NOT_FOUND but will rather
198
+ bet the actual underlying exn
199
+ */
200
+ let unwrapResultExn = res =>
201
+ switch res {
202
+ | Ok(v) => v
203
+ | Error(exn) => exn->raise
204
+ }
205
+
206
+ external queueMicrotask: (unit => unit) => unit = "queueMicrotask"
207
+
208
+ module Schema = {
209
+ let getNonOptionalFieldNames = schema => {
210
+ let acc = []
211
+ switch schema->S.classify {
212
+ | Object({items}) =>
213
+ items->Js.Array2.forEach(item => {
214
+ switch item.schema->S.classify {
215
+ // Check for null, since we generate S.null schema for db serializing
216
+ // In the future it should be changed to Option
217
+ | Null(_) => ()
218
+ | _ => acc->Js.Array2.push(item.location)->ignore
219
+ }
220
+ })
221
+ | _ => ()
222
+ }
223
+ acc
224
+ }
225
+
226
+ let getCapitalizedFieldNames = schema => {
227
+ switch schema->S.classify {
228
+ | Object({items}) => items->Js.Array2.map(item => item.location->String.capitalize)
229
+ | _ => []
230
+ }
231
+ }
232
+
233
+ // When trying to serialize data to Json pg type, it will fail with
234
+ // PostgresError: column "params" is of type json but expression is of type boolean
235
+ // If there's bool or null on the root level. It works fine as object field values.
236
+ let coerceToJsonPgType = schema => {
237
+ schema->S.preprocess(s => {
238
+ switch s.schema->S.classify {
239
+ | Literal(Null(_))
240
+ | // This is a workaround for Fuel Bytes type
241
+ Unknown => {serializer: _ => %raw(`"null"`)}
242
+ | Null(_)
243
+ | Bool => {
244
+ serializer: unknown => {
245
+ if unknown === %raw(`null`) {
246
+ %raw(`"null"`)
247
+ } else if unknown === %raw(`false`) {
248
+ %raw(`"false"`)
249
+ } else if unknown === %raw(`true`) {
250
+ %raw(`"true"`)
251
+ } else {
252
+ unknown
253
+ }
254
+ },
255
+ }
256
+ | _ => {}
257
+ }
258
+ })
259
+ }
260
+ }
261
+
262
+ module Set = {
263
+ type t<'value>
264
+
265
+ /*
266
+ * Constructor
267
+ */
268
+ @ocaml.doc("Creates a new `Set` object.") @new
269
+ external make: unit => t<'value> = "Set"
270
+
271
+ @ocaml.doc("Creates a new `Set` object.") @new
272
+ external fromEntries: array<'value> => t<'value> = "Set"
273
+
274
+ /*
275
+ * Instance properties
276
+ */
277
+ @ocaml.doc("Returns the number of values in the `Set` object.") @get
278
+ external size: t<'value> => int = "size"
279
+
280
+ /*
281
+ * Instance methods
282
+ */
283
+ @ocaml.doc("Appends `value` to the `Set` object. Returns the `Set` object with added value.")
284
+ @send
285
+ external add: (t<'value>, 'value) => t<'value> = "add"
286
+
287
+ @ocaml.doc("Removes all elements from the `Set` object.") @send
288
+ external clear: t<'value> => unit = "clear"
289
+
290
+ @ocaml.doc(
291
+ "Removes the element associated to the `value` and returns a boolean asserting whether an element was successfully removed or not. `Set.prototype.has(value)` will return `false` afterwards."
292
+ )
293
+ @send
294
+ external delete: (t<'value>, 'value) => bool = "delete"
295
+
296
+ @ocaml.doc(
297
+ "Returns a boolean asserting whether an element is present with the given value in the `Set` object or not."
298
+ )
299
+ @send
300
+ external has: (t<'value>, 'value) => bool = "has"
301
+
302
+ external toArray: t<'a> => array<'a> = "Array.from"
303
+
304
+ /*
305
+ * Iteration methods
306
+ */
307
+ /*
308
+ /// NOTE - if we need iteration we can add this back - currently it requires the `rescript-js-iterator` library.
309
+ @ocaml.doc(
310
+ "Returns a new iterator object that yields the **values** for each element in the `Set` object in insertion order."
311
+ )
312
+ @send
313
+ external values: t<'value> => Js_iterator.t<'value> = "values"
314
+
315
+ @ocaml.doc("An alias for `Set.prototype.values()`.") @send
316
+ external keys: t<'value> => Js_iterator.t<'value> = "values"
317
+
318
+ @ocaml.doc("Returns a new iterator object that contains **an array of [value, value]** for each element in the `Set` object, in insertion order.
319
+
320
+ This is similar to the [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) object, so that each entry's `key` is the same as its `value` for a `Set`.")
321
+ @send
322
+ external entries: t<'value> => Js_iterator.t<('value, 'value)> = "entries"
323
+ */
324
+ @ocaml.doc(
325
+ "Calls `callbackFn` once for each value present in the `Set` object, in insertion order."
326
+ )
327
+ @send
328
+ external forEach: (t<'value>, 'value => unit) => unit = "forEach"
329
+
330
+ @ocaml.doc(
331
+ "Calls `callbackFn` once for each value present in the `Set` object, in insertion order."
332
+ )
333
+ @send
334
+ external forEachWithSet: (t<'value>, ('value, 'value, t<'value>) => unit) => unit = "forEach"
335
+ }
336
+
337
+ module WeakMap = {
338
+ type t<'k, 'v> = Js.WeakMap.t<'k, 'v>
339
+
340
+ @new external make: unit => t<'k, 'v> = "WeakMap"
341
+
342
+ @send external get: (t<'k, 'v>, 'k) => option<'v> = "get"
343
+ @send external unsafeGet: (t<'k, 'v>, 'k) => 'v = "get"
344
+ @send external has: (t<'k, 'v>, 'k) => bool = "has"
345
+ @send external set: (t<'k, 'v>, 'k, 'v) => t<'k, 'v> = "set"
346
+ }
@@ -0,0 +1,481 @@
1
+ type cfg = {
2
+ url?: string,
3
+ bearerToken?: string,
4
+ httpReqTimeoutMillis?: int,
5
+ maxNumRetries?: int,
6
+ retryBackoffMs?: int,
7
+ retryBaseMs?: int,
8
+ retryCeilingMs?: int,
9
+ enableChecksumAddresses?: bool,
10
+ }
11
+
12
+ module QueryTypes = {
13
+ type blockField =
14
+ | Number
15
+ | Hash
16
+ | ParentHash
17
+ | Nonce
18
+ | Sha3Uncles
19
+ | LogsBloom
20
+ | TransactionsRoot
21
+ | StateRoot
22
+ | ReceiptsRoot
23
+ | Miner
24
+ | Difficulty
25
+ | TotalDifficulty
26
+ | ExtraData
27
+ | Size
28
+ | GasLimit
29
+ | GasUsed
30
+ | Timestamp
31
+ | Uncles
32
+ | BaseFeePerGas
33
+ | BlobGasUsed
34
+ | ExcessBlobGas
35
+ | ParentBeaconBlockRoot
36
+ | WithdrawalsRoot
37
+ | Withdrawals
38
+ | L1BlockNumber
39
+ | SendCount
40
+ | SendRoot
41
+ | MixHash
42
+
43
+ type transactionField =
44
+ | BlockHash
45
+ | BlockNumber
46
+ | From
47
+ | Gas
48
+ | GasPrice
49
+ | Hash
50
+ | Input
51
+ | Nonce
52
+ | To
53
+ | TransactionIndex
54
+ | Value
55
+ | V
56
+ | R
57
+ | S
58
+ | YParity
59
+ | MaxPriorityFeePerGas
60
+ | MaxFeePerGas
61
+ | ChainId
62
+ | AccessList
63
+ | MaxFeePerBlobGas
64
+ | BlobVersionedHashes
65
+ | CumulativeGasUsed
66
+ | EffectiveGasPrice
67
+ | GasUsed
68
+ | ContractAddress
69
+ | LogsBloom
70
+ | Kind
71
+ | Root
72
+ | Status
73
+ | L1Fee
74
+ | L1GasPrice
75
+ | L1GasUsed
76
+ | L1FeeScalar
77
+ | GasUsedForL1
78
+
79
+ type logField =
80
+ | Removed
81
+ | LogIndex
82
+ | TransactionIndex
83
+ | TransactionHash
84
+ | BlockHash
85
+ | BlockNumber
86
+ | Address
87
+ | Data
88
+ | Topic0
89
+ | Topic1
90
+ | Topic2
91
+ | Topic3
92
+
93
+ type traceField =
94
+ | From
95
+ | To
96
+ | CallType
97
+ | Gas
98
+ | Input
99
+ | Init
100
+ | Value
101
+ | Author
102
+ | RewardType
103
+ | BlockHash
104
+ | BlockNumber
105
+ | Address
106
+ | Code
107
+ | GasUsed
108
+ | Output
109
+ | Subtraces
110
+ | TraceAddress
111
+ | TransactionHash
112
+ | TransactionPosition
113
+ | Kind
114
+ | Error
115
+
116
+ type fieldSelection = {
117
+ block?: array<blockField>,
118
+ transaction?: array<transactionField>,
119
+ log?: array<logField>,
120
+ trace?: array<traceField>,
121
+ }
122
+ type topicFilter = array<EvmTypes.Hex.t>
123
+ type topic0 = topicFilter
124
+ type topic1 = topicFilter
125
+ type topic2 = topicFilter
126
+ type topic3 = topicFilter
127
+ type topicSelection = (topic0, topic1, topic2, topic3)
128
+ let makeTopicSelection = (~topic0=[], ~topic1=[], ~topic2=[], ~topic3=[]) => (
129
+ topic0,
130
+ topic1,
131
+ topic2,
132
+ topic3,
133
+ )
134
+
135
+ type logSelection = {
136
+ /**
137
+ * Address of the contract, any logs that has any of these addresses will be returned.
138
+ * Empty means match all.
139
+ */
140
+ address?: array<Address.t>,
141
+ /**
142
+ * Topics to match, each member of the top level array is another array, if the nth topic matches any
143
+ * topic specified in topics[n] the log will be returned. Empty means match all.
144
+ */
145
+ topics: topicSelection,
146
+ }
147
+
148
+ let makeLogSelection = (~address, ~topics) => {address, topics}
149
+
150
+ type transactionSelection = {
151
+ /**
152
+ * Address the transaction should originate from. If transaction.from matches any of these, the transaction
153
+ * will be returned. Keep in mind that this has an and relationship with to filter, so each transaction should
154
+ * match both of them. Empty means match all.
155
+ */
156
+ from?: array<Address.t>,
157
+ /**
158
+ * Address the transaction should go to. If transaction.to matches any of these, the transaction will
159
+ * be returned. Keep in mind that this has an and relationship with from filter, so each transaction should
160
+ * match both of them. Empty means match all.
161
+ */
162
+ @as("to")
163
+ to_?: array<Address.t>,
164
+ /** If first 4 bytes of transaction input matches any of these, transaction will be returned. Empty means match all. */
165
+ sighash?: array<string>,
166
+ /** If tx.status matches this it will be returned. */
167
+ status?: int,
168
+ /** If transaction.type matches any of these values, the transaction will be returned */
169
+ kind?: array<int>,
170
+ contractAddress?: array<Address.t>,
171
+ }
172
+
173
+ type traceSelection = {
174
+ from?: array<Address.t>,
175
+ @as("to") to_?: array<Address.t>,
176
+ address?: array<Address.t>,
177
+ callType?: array<string>,
178
+ rewardType?: array<string>,
179
+ kind?: array<string>,
180
+ sighash?: array<string>,
181
+ }
182
+
183
+ type blockSelection = {
184
+ /**
185
+ * Hash of a block, any blocks that have one of these hashes will be returned.
186
+ * Empty means match all.
187
+ */
188
+ hash?: array<string>,
189
+ /**
190
+ * Miner address of a block, any blocks that have one of these miners will be returned.
191
+ * Empty means match all.
192
+ */
193
+ miner?: array<Address.t>,
194
+ }
195
+
196
+ type joinMode = | @as(0) Default | @as(1) JoinAll | @as(2) JoinNothing
197
+
198
+ type query = {
199
+ /** The block to start the query from */
200
+ fromBlock: int,
201
+ /**
202
+ * The block to end the query at. If not specified, the query will go until the
203
+ * end of data. Exclusive, the returned range will be [from_block..to_block).
204
+ *
205
+ * The query will return before it reaches this target block if it hits the time limit
206
+ * configured on the server. The user should continue their query by putting the
207
+ * next_block field in the response into from_block field of their next query. This implements
208
+ * pagination.
209
+ */
210
+ @as("toBlock")
211
+ toBlockExclusive?: int,
212
+ /**
213
+ * List of log selections, these have an or relationship between them, so the query will return logs
214
+ * that match any of these selections.
215
+ */
216
+ logs?: array<logSelection>,
217
+ /**
218
+ * List of transaction selections, the query will return transactions that match any of these selections and
219
+ * it will return transactions that are related to the returned logs.
220
+ */
221
+ transactions?: array<transactionSelection>,
222
+ /**
223
+ * List of trace selections, the query will return traces that match any of these selections and
224
+ * it will re turn traces that are related to the returned logs.
225
+ */
226
+ traces?: array<traceSelection>,
227
+ /** List of block selections, the query will return blocks that match any of these selections */
228
+ blocks?: array<blockSelection>,
229
+ /**
230
+ * Field selection. The user can select which fields they are interested in, requesting less fields will improve
231
+ * query execution time and reduce the payload size so the user should always use a minimal number of fields.
232
+ */
233
+ fieldSelection: fieldSelection,
234
+ /**
235
+ * Maximum number of blocks that should be returned, the server might return more blocks than this number but
236
+ * it won't overshoot by too much.
237
+ */
238
+ maxNumBlocks?: int,
239
+ /**
240
+ * Maximum number of transactions that should be returned, the server might return more transactions than this number but
241
+ * it won't overshoot by too much.
242
+ */
243
+ maxNumTransactions?: int,
244
+ /**
245
+ * Maximum number of logs that should be returned, the server might return more logs than this number but
246
+ * it won't overshoot by too much.
247
+ */
248
+ maxNumLogs?: int,
249
+ /**
250
+ * Maximum number of traces that should be returned, the server might return more traces than this number but
251
+ * it won't overshoot by too much.
252
+ */
253
+ maxNumTraces?: int,
254
+ /**
255
+ * Selects join mode for the query,
256
+ * Default: join in this order logs -> transactions -> traces -> blocks
257
+ * JoinAll: join everything to everything. For example if logSelection matches log0, we get the
258
+ * associated transaction of log0 and then we get associated logs of that transaction as well. Applites similarly
259
+ * to blocks, traces.
260
+ * JoinNothing: join nothing.
261
+ */
262
+ joinMode?: joinMode,
263
+ }
264
+ }
265
+
266
+ module ResponseTypes = {
267
+ type withdrawal = {
268
+ index?: string,
269
+ validatorIndex?: string,
270
+ address?: Address.t,
271
+ amount?: string,
272
+ }
273
+
274
+ type block = {
275
+ number?: int,
276
+ hash?: string,
277
+ parentHash?: string,
278
+ nonce?: bigint,
279
+ sha3Uncles?: string,
280
+ logsBloom?: string,
281
+ transactionsRoot?: string,
282
+ stateRoot?: string,
283
+ receiptsRoot?: string,
284
+ miner?: Address.t,
285
+ difficulty?: bigint,
286
+ totalDifficulty?: bigint,
287
+ extraData?: string,
288
+ size?: bigint,
289
+ gasLimit?: bigint,
290
+ gasUsed?: bigint,
291
+ timestamp?: int,
292
+ uncles?: array<string>,
293
+ baseFeePerGas?: bigint,
294
+ blobGasUsed?: bigint,
295
+ excessBlobGas?: bigint,
296
+ parentBeaconBlockRoot?: string,
297
+ withdrawalsRoot?: string,
298
+ withdrawals?: array<withdrawal>,
299
+ l1BlockNumber?: int,
300
+ sendCount?: string,
301
+ sendRoot?: string,
302
+ mixHash?: string,
303
+ }
304
+
305
+ type accessList = {
306
+ address?: Address.t,
307
+ storageKeys?: array<string>,
308
+ }
309
+
310
+ type transaction = {
311
+ blockHash?: string,
312
+ blockNumber?: int,
313
+ from?: string,
314
+ gas?: bigint,
315
+ gasPrice?: bigint,
316
+ hash?: string,
317
+ input?: string,
318
+ nonce?: bigint,
319
+ to?: string,
320
+ transactionIndex?: int,
321
+ value?: bigint,
322
+ v?: string,
323
+ r?: string,
324
+ s?: string,
325
+ yParity?: string,
326
+ maxPriorityFeePerGas?: bigint,
327
+ maxFeePerGas?: bigint,
328
+ chainId?: int,
329
+ accessList?: array<accessList>,
330
+ maxFeePerBlobGas?: bigint,
331
+ blobVersionedHashes?: array<string>,
332
+ cumulativeGasUsed?: bigint,
333
+ effectiveGasPrice?: bigint,
334
+ gasUsed?: bigint,
335
+ contractAddress?: string,
336
+ logsBloom?: string,
337
+ kind?: int,
338
+ root?: string,
339
+ status?: int,
340
+ l1Fee?: bigint,
341
+ l1GasPrice?: bigint,
342
+ l1GasUsed?: bigint,
343
+ l1FeeScalar?: int,
344
+ gasUsedForL1?: bigint,
345
+ }
346
+
347
+ type log = {
348
+ removed?: bool,
349
+ @as("logIndex") index?: int,
350
+ transactionIndex?: int,
351
+ transactionHash?: string,
352
+ blockHash?: string,
353
+ blockNumber?: int,
354
+ address?: Address.t,
355
+ data?: string,
356
+ topics?: array<Js.Nullable.t<EvmTypes.Hex.t>>,
357
+ }
358
+
359
+ type event = {
360
+ transaction?: transaction,
361
+ block?: block,
362
+ log: log,
363
+ }
364
+
365
+ type rollbackGuard = {
366
+ /** Block number of the last scanned block */
367
+ blockNumber: int,
368
+ /** Block timestamp of the last scanned block */
369
+ timestamp: int,
370
+ /** Block hash of the last scanned block */
371
+ hash: string,
372
+ /**
373
+ * Block number of the first scanned block in memory.
374
+ *
375
+ * This might not be the first scanned block. It only includes blocks that are in memory (possible to be rolled back).
376
+ */
377
+ firstBlockNumber: int,
378
+ /**
379
+ * Parent hash of the first scanned block in memory.
380
+ *
381
+ * This might not be the first scanned block. It only includes blocks that are in memory (possible to be rolled back).
382
+ */
383
+ firstParentHash: string,
384
+ }
385
+
386
+ type eventResponse = {
387
+ /** Current height of the source hypersync instance */
388
+ archiveHeight: option<int>,
389
+ /**
390
+ * Next block to query for, the responses are paginated so,
391
+ * the caller should continue the query from this block if they
392
+ * didn't get responses up to the to_block they specified in the Query.
393
+ */
394
+ nextBlock: int,
395
+ /** Total time it took the hypersync instance to execute the query. */
396
+ totalExecutionTime: int,
397
+ /** Response data */
398
+ data: array<event>,
399
+ /** Rollback guard, supposed to be used to detect rollbacks */
400
+ rollbackGuard: option<rollbackGuard>,
401
+ }
402
+ }
403
+
404
+ type query = QueryTypes.query
405
+ type eventResponse = ResponseTypes.eventResponse
406
+
407
+ //Todo, add bindings for these types
408
+ type streamConfig
409
+ type queryResponse
410
+ type queryResponseStream
411
+ type eventStream
412
+ type t = {
413
+ getHeight: unit => promise<int>,
414
+ collect: (~query: query, ~config: streamConfig) => promise<queryResponse>,
415
+ collectEvents: (~query: query, ~config: streamConfig) => promise<eventResponse>,
416
+ collectParquet: (~path: string, ~query: query, ~config: streamConfig) => promise<unit>,
417
+ get: (~query: query) => promise<queryResponse>,
418
+ getEvents: (~query: query) => promise<eventResponse>,
419
+ stream: (~query: query, ~config: streamConfig) => promise<queryResponseStream>,
420
+ streamEvents: (~query: query, ~config: streamConfig) => promise<eventStream>,
421
+ }
422
+
423
+ @module("@envio-dev/hypersync-client") @scope("HypersyncClient") external new: cfg => t = "new"
424
+
425
+ let defaultToken = "3dc856dd-b0ea-494f-b27e-017b8b6b7e07"
426
+
427
+ let make = (~url, ~bearerToken: option<string>, ~httpReqTimeoutMillis, ~maxNumRetries) =>
428
+ new({
429
+ url,
430
+ enableChecksumAddresses: true,
431
+ bearerToken: bearerToken->Belt.Option.getWithDefault(defaultToken),
432
+ httpReqTimeoutMillis,
433
+ maxNumRetries,
434
+ })
435
+
436
+ module Decoder = {
437
+ type rec decodedSolType<'a> = {val: 'a}
438
+
439
+ @unboxed
440
+ type rec decodedRaw =
441
+ | DecodedBool(bool)
442
+ | DecodedStr(string)
443
+ | DecodedNum(bigint)
444
+ | DecodedVal(decodedSolType<decodedRaw>)
445
+ | DecodedArr(array<decodedRaw>)
446
+
447
+ @unboxed
448
+ type rec decodedUnderlying =
449
+ | Bool(bool)
450
+ | Str(string)
451
+ | Num(bigint)
452
+ | Arr(array<decodedUnderlying>)
453
+
454
+ let rec toUnderlying = (d: decodedRaw): decodedUnderlying => {
455
+ switch d {
456
+ | DecodedVal(v) => v.val->toUnderlying
457
+ | DecodedBool(v) => Bool(v)
458
+ | DecodedStr(v) => Str(v)
459
+ | DecodedNum(v) => Num(v)
460
+ | DecodedArr(v) => v->Belt.Array.map(toUnderlying)->Arr
461
+ }
462
+ }
463
+
464
+ type decodedEvent = {
465
+ indexed: array<decodedRaw>,
466
+ body: array<decodedRaw>,
467
+ }
468
+
469
+ type log
470
+ type t = {
471
+ enableChecksummedAddresses: unit => unit,
472
+ disableChecksummedAddresses: unit => unit,
473
+ decodeLogs: array<log> => promise<array<Js.Nullable.t<decodedEvent>>>,
474
+ decodeLogsSync: array<log> => array<Js.Nullable.t<decodedEvent>>,
475
+ decodeEvents: array<ResponseTypes.event> => promise<array<Js.Nullable.t<decodedEvent>>>,
476
+ decodeEventsSync: array<ResponseTypes.event> => array<Js.Nullable.t<decodedEvent>>,
477
+ }
478
+
479
+ @module("@envio-dev/hypersync-client") @scope("Decoder")
480
+ external fromSignatures: array<string> => t = "fromSignatures"
481
+ }
@@ -0,0 +1,175 @@
1
+ type logLevelBuiltin = [
2
+ | #trace
3
+ | #debug
4
+ | #info
5
+ | #warn
6
+ | #error
7
+ | #fatal
8
+ ]
9
+ @genType
10
+ type logLevelUser = [
11
+ | // NOTE: pino does better when these are all lowercase - some parts of the code lower case logs.
12
+ #udebug
13
+ | #uinfo
14
+ | #uwarn
15
+ | #uerror
16
+ ]
17
+ type logLevel = [logLevelBuiltin | logLevelUser]
18
+
19
+ type pinoMessageBlob
20
+ type pinoMessageBlobWithError
21
+ @genType
22
+ type t = {
23
+ trace: pinoMessageBlob => unit,
24
+ debug: pinoMessageBlob => unit,
25
+ info: pinoMessageBlob => unit,
26
+ warn: pinoMessageBlob => unit,
27
+ error: pinoMessageBlob => unit,
28
+ fatal: pinoMessageBlob => unit,
29
+ }
30
+ @send external errorExn: (t, pinoMessageBlobWithError) => unit = "error"
31
+
32
+ // Bind to the 'level' property getter
33
+ @get external getLevel: t => logLevel = "level"
34
+
35
+ @ocaml.doc(`Get the available logging levels`) @get
36
+ external levels: t => 'a = "levels"
37
+
38
+ // Bind to the 'level' property setter
39
+ @set external setLevel: (t, logLevel) => unit = "level"
40
+
41
+ @ocaml.doc(`Identity function to help co-erce any type to a pino log message`)
42
+ let createPinoMessage = (message): pinoMessageBlob => Utils.magic(message)
43
+ let createPinoMessageWithError = (message, err): pinoMessageBlobWithError => {
44
+ //See https://github.com/pinojs/pino-std-serializers for standard pino serializers
45
+ //for common objects. We have also defined the serializer in this format in the
46
+ // serializers type below: `type serializers = {err: Js.Json.t => Js.Json.t}`
47
+ Utils.magic({
48
+ "msg": message,
49
+ "err": err,
50
+ })
51
+ }
52
+
53
+ module Transport = {
54
+ type t
55
+ type optionsObject
56
+ let makeTransportOptions: 'a => optionsObject = Utils.magic
57
+
58
+ // NOTE: this config is pretty polymorphic - so keeping this as all optional fields.
59
+ type rec transportTarget = {
60
+ target?: string,
61
+ targets?: array<transportTarget>,
62
+ options?: optionsObject,
63
+ levels?: dict<int>,
64
+ level?: logLevel,
65
+ }
66
+ @module("pino")
67
+ external make: transportTarget => t = "transport"
68
+ }
69
+
70
+ @module external makeWithTransport: Transport.t => t = "pino"
71
+
72
+ type hooks = {logMethod: (array<string>, string, logLevel) => unit}
73
+
74
+ type formatters = {
75
+ level: (string, int) => Js.Json.t,
76
+ bindings: Js.Json.t => Js.Json.t,
77
+ log: Js.Json.t => Js.Json.t,
78
+ }
79
+
80
+ type serializers = {err: Js.Json.t => Js.Json.t}
81
+
82
+ type options = {
83
+ name?: string,
84
+ level?: logLevel,
85
+ customLevels?: dict<int>,
86
+ useOnlyCustomLevels?: bool,
87
+ depthLimit?: int,
88
+ edgeLimit?: int,
89
+ mixin?: unit => Js.Json.t,
90
+ mixinMergeStrategy?: (Js.Json.t, Js.Json.t) => Js.Json.t,
91
+ redact?: array<string>,
92
+ hooks?: hooks,
93
+ formatters?: formatters,
94
+ serializers?: serializers,
95
+ msgPrefix?: string,
96
+ base?: Js.Json.t,
97
+ enabled?: bool,
98
+ crlf?: bool,
99
+ timestamp?: bool,
100
+ messageKey?: string,
101
+ }
102
+
103
+ @module external make: options => t = "pino"
104
+ @module external makeWithOptionsAndTransport: (options, Transport.t) => t = "pino"
105
+
106
+ type childParams
107
+ let createChildParams: 'a => childParams = Utils.magic
108
+ @send external child: (t, childParams) => t = "child"
109
+
110
+ module ECS = {
111
+ @module
112
+ external make: 'a => options = "@elastic/ecs-pino-format"
113
+ }
114
+
115
+ /**
116
+ Jank solution to make logs use console log wrather than stream.write so that ink
117
+ can render the logs statically.
118
+ */
119
+ module MultiStreamLogger = {
120
+ type stream = {write: string => unit}
121
+ type multiStream = {stream: stream, level: logLevel}
122
+ type multiStreamRes
123
+ @module("pino") external multistream: array<multiStream> => multiStreamRes = "multistream"
124
+
125
+ @module external makeWithMultiStream: (options, multiStreamRes) => t = "pino"
126
+
127
+ type destinationOpts = {
128
+ dest: string, //file path
129
+ sync: bool,
130
+ mkdir: bool,
131
+ }
132
+ @module("pino") external destination: destinationOpts => stream = "destination"
133
+
134
+ type prettyFactoryOpts = {...options, customColors?: string}
135
+ @module("pino-pretty")
136
+ external prettyFactory: prettyFactoryOpts => string => string = "prettyFactory"
137
+
138
+ let makeFormatter = logLevels => {
139
+ prettyFactory({
140
+ customLevels: logLevels,
141
+ customColors: "fatal:bgRed,error:red,warn:yellow,info:green,udebug:bgBlue,uinfo:bgGreen,uwarn:bgYellow,uerror:bgRed,debug:blue,trace:gray",
142
+ })
143
+ }
144
+
145
+ let makeStreams = (~userLogLevel, ~formatter, ~logFile, ~defaultFileLogLevel) => {
146
+ let stream = {
147
+ stream: {write: v => formatter(v)->Js.log},
148
+ level: userLogLevel,
149
+ }
150
+ let maybeFileStream = logFile->Belt.Option.mapWithDefault([], dest => [
151
+ {
152
+ level: defaultFileLogLevel,
153
+ stream: destination({dest, sync: false, mkdir: true}),
154
+ },
155
+ ])
156
+ [stream]->Belt.Array.concat(maybeFileStream)
157
+ }
158
+
159
+ let make = (
160
+ ~userLogLevel: logLevel,
161
+ ~customLevels: dict<int>,
162
+ ~logFile: option<string>,
163
+ ~options: option<options>,
164
+ ~defaultFileLogLevel,
165
+ ) => {
166
+ let options = switch options {
167
+ | Some(opts) => {...opts, customLevels, level: userLogLevel}
168
+ | None => {customLevels, level: userLogLevel}
169
+ }
170
+ let formatter = makeFormatter(customLevels)
171
+ let ms = makeStreams(~userLogLevel, ~formatter, ~logFile, ~defaultFileLogLevel)->multistream
172
+
173
+ makeWithMultiStream(options, ms)
174
+ }
175
+ }
@@ -0,0 +1,28 @@
1
+ type eventLog = {
2
+ abi: EvmTypes.Abi.t,
3
+ data: string,
4
+ topics: array<EvmTypes.Hex.t>,
5
+ }
6
+
7
+ type decodedEvent<'a> = {
8
+ eventName: string,
9
+ args: 'a,
10
+ }
11
+
12
+ @module("viem") external decodeEventLogOrThrow: eventLog => decodedEvent<'a> = "decodeEventLog"
13
+
14
+ type hex = EvmTypes.Hex.t
15
+ @module("viem") external toHex: 'a => hex = "toHex"
16
+ @module("viem") external keccak256: hex => hex = "keccak256"
17
+ @module("viem") external keccak256Bytes: bytes => hex = "keccak256"
18
+ @module("viem") external pad: hex => hex = "pad"
19
+ @module("viem")
20
+ external encodePacked: (~types: array<string>, ~values: array<'a>) => hex = "encodePacked"
21
+
22
+ type sizeOptions = {size: int}
23
+ @module("viem") external intToHex: (int, ~options: sizeOptions=?) => hex = "numberToHex"
24
+ @module("viem") external bigintToHex: (bigint, ~options: sizeOptions=?) => hex = "numberToHex"
25
+ @module("viem") external stringToHex: (string, ~options: sizeOptions=?) => hex = "stringToHex"
26
+ @module("viem") external boolToHex: (bool, ~options: sizeOptions=?) => hex = "boolToHex"
27
+ @module("viem") external bytesToHex: (bytes, ~options: sizeOptions=?) => hex = "bytesToHex"
28
+ @module("viem") external concat: array<hex> => hex = "concat"