envio 2.9.0 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envio",
3
- "version": "v2.9.0",
3
+ "version": "v2.10.0",
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.9.0",
27
- "envio-linux-arm64": "v2.9.0",
28
- "envio-darwin-x64": "v2.9.0",
29
- "envio-darwin-arm64": "v2.9.0"
26
+ "envio-linux-x64": "v2.10.0",
27
+ "envio-linux-arm64": "v2.10.0",
28
+ "envio-darwin-x64": "v2.10.0",
29
+ "envio-darwin-arm64": "v2.10.0"
30
30
  },
31
31
  "dependencies": {
32
32
  "@envio-dev/hypersync-client": "0.6.2",
@@ -0,0 +1,134 @@
1
+ exception LoaderTimeout(string)
2
+
3
+ type rec asyncMap<'key, 'value> = {
4
+ // The number of loaded results to keep cached. (For block loading this should be our maximum block interval)
5
+ _cacheSize: int,
6
+ // The maximum number of results we can try to load simultaneously.
7
+ _loaderPoolSize: int,
8
+ // How long to wait before retrying failures
9
+ // TODO: Add randomized exponential back-off (outdated)
10
+ _retryDelayMillis: int,
11
+ // How long to wait before cancelling a load request
12
+ _timeoutMillis: int,
13
+ // The promises we return to callers. We satisfy them asynchronously.
14
+ externalPromises: Utils.Map.t<'key, promise<'value>>,
15
+ // The handled used to populate the external promises once we have loaded their data.
16
+ resolvers: Utils.Map.t<'key, 'value => unit>,
17
+ // The keys currently being loaded
18
+ inProgress: Utils.Set.t<'key>,
19
+ // Keys for items that we have not started loading yet.
20
+ loaderQueue: SDSL.Queue.t<'key>,
21
+ // Keys for items that have been loaded already. Used to evict the oldest keys from cache.
22
+ loadedKeys: SDSL.Queue.t<'key>,
23
+ // The function used to load the result.
24
+ loaderFn: 'key => promise<'value>,
25
+ // Callback on load error
26
+ onError: option<(asyncMap<'key, 'value>, ~exn: exn) => unit>,
27
+ }
28
+
29
+ let make = (
30
+ ~loaderFn,
31
+ ~onError=?,
32
+ ~cacheSize: int=10_000,
33
+ ~loaderPoolSize: int=10,
34
+ ~retryDelayMillis=5_000,
35
+ ~timeoutMillis=300_000,
36
+ ) => // After 5 minutes (unclear what is best to do here - crash or just keep printing the error)
37
+ {
38
+ _cacheSize: cacheSize,
39
+ _loaderPoolSize: loaderPoolSize,
40
+ _retryDelayMillis: retryDelayMillis,
41
+ _timeoutMillis: timeoutMillis,
42
+ externalPromises: Utils.Map.make(),
43
+ resolvers: Utils.Map.make(),
44
+ inProgress: Utils.Set.make(),
45
+ loaderQueue: SDSL.Queue.make(),
46
+ loadedKeys: SDSL.Queue.make(),
47
+ loaderFn,
48
+ onError,
49
+ }
50
+
51
+ let deleteKey: (dict<'value>, string) => unit = (_obj, _k) => %raw(`delete _obj[_k]`)
52
+
53
+ // If something takes longer than this to load, reject the promise and try again
54
+ let timeoutAfter = timeoutMillis =>
55
+ Utils.delay(timeoutMillis)->Promise.then(() =>
56
+ Promise.reject(
57
+ LoaderTimeout(`Query took longer than ${Belt.Int.toString(timeoutMillis / 1000)} seconds`),
58
+ )
59
+ )
60
+
61
+ let rec loadNext = async (am: asyncMap<'key, 'value>, k: 'key) => {
62
+ // Track that we are loading it now
63
+ let _ = am.inProgress->Utils.Set.add(k)
64
+
65
+ let awaitTaskPromiseAndLoadNextWithTimeout = async () => {
66
+ let val = await Promise.race([am.loaderFn(k), timeoutAfter(am._timeoutMillis)])
67
+ // Resolve the external promise
68
+ am.resolvers
69
+ ->Utils.Map.get(k)
70
+ ->Belt.Option.forEach(r => {
71
+ let _ = am.resolvers->Utils.Map.delete(k)
72
+ r(val)
73
+ })
74
+
75
+ // Track that it is no longer in progress
76
+ let _ = am.inProgress->Utils.Set.delete(k)
77
+
78
+ // Track that we've loaded this key
79
+ let loadedKeysNumber = am.loadedKeys->SDSL.Queue.push(k)
80
+
81
+ // Delete the oldest key if the cache is overly full
82
+ if loadedKeysNumber > am._cacheSize {
83
+ switch am.loadedKeys->SDSL.Queue.pop {
84
+ | None => ()
85
+ | Some(old) =>
86
+ let _ = am.externalPromises->Utils.Map.delete(old)
87
+ }
88
+ }
89
+
90
+ // Load the next one, if there is anything in the queue
91
+ switch am.loaderQueue->SDSL.Queue.pop {
92
+ | None => ()
93
+ | Some(next) => await loadNext(am, next)
94
+ }
95
+ }
96
+
97
+ await (
98
+ switch await awaitTaskPromiseAndLoadNextWithTimeout() {
99
+ | _ => Promise.resolve()
100
+ | exception err =>
101
+ switch am.onError {
102
+ | None => ()
103
+ | Some(onError) => onError(am, ~exn=err)
104
+ }
105
+ await Utils.delay(am._retryDelayMillis)
106
+ awaitTaskPromiseAndLoadNextWithTimeout()
107
+ }
108
+ )
109
+ }
110
+
111
+ let get = (am: asyncMap<'key, 'value>, k: 'key): promise<'value> => {
112
+ switch am.externalPromises->Utils.Map.get(k) {
113
+ | Some(x) => x
114
+ | None => {
115
+ // Create a promise to deliver the eventual value asynchronously
116
+ let promise = Promise.make((resolve, _) => {
117
+ // Expose the resolver externally, so that we can run it from the loader.
118
+ let _ = am.resolvers->Utils.Map.set(k, resolve)
119
+ })
120
+ // Cache the promise to de-duplicate requests
121
+ let _ = am.externalPromises->Utils.Map.set(k, promise)
122
+
123
+ // Do we have a free loader in the pool?
124
+ if am.inProgress->Utils.Set.size < am._loaderPoolSize {
125
+ loadNext(am, k)->ignore
126
+ } else {
127
+ // Queue the loader
128
+ let _ = am.loaderQueue->SDSL.Queue.push(k)
129
+ }
130
+
131
+ promise
132
+ }
133
+ }
134
+ }
@@ -0,0 +1,27 @@
1
+ let toTwosComplement = (num: bigint, ~bytesLen: int) => {
2
+ let maxValue = 1n->BigInt.Bitwise.shift_left(BigInt.fromInt(bytesLen * 8))
3
+ let mask = maxValue->BigInt.sub(1n)
4
+ num->BigInt.add(maxValue)->BigInt.Bitwise.logand(mask)
5
+ }
6
+
7
+ let fromSignedBigInt = val => {
8
+ let bytesLen = 32
9
+ let val = val >= 0n ? val : val->toTwosComplement(~bytesLen)
10
+ val->Viem.bigintToHex(~options={size: bytesLen})
11
+ }
12
+
13
+ type hex = EvmTypes.Hex.t
14
+ //bytes currently does not work with genType and we also currently generate bytes as a string type
15
+ type bytesHex = string
16
+ let keccak256 = Viem.keccak256
17
+ let bytesToHex = Viem.bytesToHex
18
+ let concat = Viem.concat
19
+ let castToHexUnsafe: 'a => hex = val => val->Utils.magic
20
+ let fromBigInt: bigint => hex = val => val->Viem.bigintToHex(~options={size: 32})
21
+ let fromDynamicString: string => hex = val => val->(Utils.magic: string => hex)->keccak256
22
+ let fromString: string => hex = val => val->Viem.stringToHex(~options={size: 32})
23
+ let fromAddress: Address.t => hex = addr => addr->(Utils.magic: Address.t => hex)->Viem.pad
24
+ let fromDynamicBytes: bytesHex => hex = bytes => bytes->(Utils.magic: bytesHex => hex)->keccak256
25
+ let fromBytes: bytesHex => hex = bytes =>
26
+ bytes->(Utils.magic: bytesHex => bytes)->Viem.bytesToHex(~options={size: 32})
27
+ let fromBool: bool => hex = b => b->Viem.boolToHex(~options={size: 32})
package/src/Utils.res CHANGED
@@ -1,5 +1,12 @@
1
1
  external magic: 'a => 'b = "%identity"
2
2
 
3
+ let delay = milliseconds =>
4
+ Js.Promise2.make((~resolve, ~reject as _) => {
5
+ let _interval = Js.Global.setTimeout(_ => {
6
+ resolve()
7
+ }, milliseconds)
8
+ })
9
+
3
10
  module Option = {
4
11
  let mapNone = (opt: option<'a>, val: 'b): option<'b> => {
5
12
  switch opt {
@@ -224,6 +231,13 @@ external queueMicrotask: (unit => unit) => unit = "queueMicrotask"
224
231
  module Schema = {
225
232
  let enum = items => S.union(items->Belt.Array.mapU(S.literal))
226
233
 
234
+ // A hot fix after we use the version where it's supported
235
+ // https://github.com/DZakh/rescript-schema/blob/v8.4.0/docs/rescript-usage.md#removetypevalidation
236
+ let removeTypeValidationInPlace = schema => {
237
+ // The variables input is guaranteed to be an object, so we reset the rescript-schema type filter here
238
+ (schema->Obj.magic)["f"] = ()
239
+ }
240
+
227
241
  let getNonOptionalFieldNames = schema => {
228
242
  let acc = []
229
243
  switch schema->S.classify {
@@ -362,3 +376,15 @@ module WeakMap = {
362
376
  @send external has: (t<'k, 'v>, 'k) => bool = "has"
363
377
  @send external set: (t<'k, 'v>, 'k, 'v) => t<'k, 'v> = "set"
364
378
  }
379
+
380
+ module Map = {
381
+ type t<'k, 'v> = Js.Map.t<'k, 'v>
382
+
383
+ @new external make: unit => t<'k, 'v> = "Map"
384
+
385
+ @send external get: (t<'k, 'v>, 'k) => option<'v> = "get"
386
+ @send external unsafeGet: (t<'k, 'v>, 'k) => 'v = "get"
387
+ @send external has: (t<'k, 'v>, 'k) => bool = "has"
388
+ @send external set: (t<'k, 'v>, 'k, 'v) => t<'k, 'v> = "set"
389
+ @send external delete: (t<'k, 'v>, 'k) => bool = "delete"
390
+ }
@@ -48,10 +48,22 @@ let schema =
48
48
  S.string
49
49
  ->S.setName("BigInt")
50
50
  ->S.transform(s => {
51
- parser: (. string) =>
51
+ parser: string =>
52
52
  switch string->fromString {
53
53
  | Some(bigInt) => bigInt
54
- | None => s.fail(. "The string is not valid BigInt")
54
+ | None => s.fail("The string is not valid BigInt")
55
55
  },
56
- serializer: (. bigint) => bigint->toString,
56
+ serializer: bigint => bigint->toString,
57
57
  })
58
+
59
+ let nativeSchema: S.t<bigint> = S.custom("BigInt", s => {
60
+ {
61
+ parser: unknown => {
62
+ if Js.typeof(unknown) !== "bigint" {
63
+ s.fail("Expected bigint")
64
+ } else {
65
+ unknown->Obj.magic
66
+ }
67
+ },
68
+ }
69
+ })
@@ -0,0 +1,14 @@
1
+ /* TypeScript file generated from Ethers.res by genType. */
2
+
3
+ /* eslint-disable */
4
+ /* tslint:disable */
5
+
6
+ const EthersJS = require('./Ethers.bs.js');
7
+
8
+ import type {t as Address_t} from '../../src/Address.gen';
9
+
10
+ export const Addresses_mockAddresses: Address_t[] = EthersJS.Addresses.mockAddresses as any;
11
+
12
+ export const Addresses_defaultAddress: Address_t = EthersJS.Addresses.defaultAddress as any;
13
+
14
+ export const Addresses: { mockAddresses: Address_t[]; defaultAddress: Address_t } = EthersJS.Addresses as any;
@@ -0,0 +1,259 @@
1
+ type abi = EvmTypes.Abi.t
2
+
3
+ let makeAbi = (abi: Js.Json.t): abi => abi->Utils.magic
4
+
5
+ @deprecated("Use Address.t instead. The type will be removed in v3")
6
+ type ethAddress = Address.t
7
+ @deprecated("Use Address.Evm.fromStringOrThrow instead. The function will be removed in v3")
8
+ let getAddressFromStringUnsafe = Address.Evm.fromStringOrThrow
9
+ @deprecated("Use Address.toString instead. The function will be removed in v3")
10
+ let ethAddressToString = Address.toString
11
+ @deprecated("Use Address.schema instead. The function will be removed in v3")
12
+ let ethAddressSchema = Address.schema
13
+
14
+ type txHash = string
15
+
16
+ module Constants = {
17
+ @module("ethers") @scope("ethers") external zeroHash: string = "ZeroHash"
18
+ @module("ethers") @scope("ethers") external zeroAddress: Address.t = "ZeroAddress"
19
+ }
20
+
21
+ module Addresses = {
22
+ @genType
23
+ let mockAddresses = [
24
+ "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
25
+ "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
26
+ "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
27
+ "0x90F79bf6EB2c4f870365E785982E1f101E93b906",
28
+ "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65",
29
+ "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc",
30
+ "0x976EA74026E726554dB657fA54763abd0C3a0aa9",
31
+ "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955",
32
+ "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f",
33
+ "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720",
34
+ "0xBcd4042DE499D14e55001CcbB24a551F3b954096",
35
+ "0x71bE63f3384f5fb98995898A86B02Fb2426c5788",
36
+ "0xFABB0ac9d68B0B445fB7357272Ff202C5651694a",
37
+ "0x1CBd3b2770909D4e10f157cABC84C7264073C9Ec",
38
+ "0xdF3e18d64BC6A983f673Ab319CCaE4f1a57C7097",
39
+ "0xcd3B766CCDd6AE721141F452C550Ca635964ce71",
40
+ "0x2546BcD3c84621e976D8185a91A922aE77ECEc30",
41
+ "0xbDA5747bFD65F08deb54cb465eB87D40e51B197E",
42
+ "0xdD2FD4581271e230360230F9337D5c0430Bf44C0",
43
+ "0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199",
44
+ ]->Belt.Array.map(getAddressFromStringUnsafe)
45
+ @genType
46
+ let defaultAddress =
47
+ mockAddresses[0]
48
+ }
49
+
50
+ module BlockTag = {
51
+ type t
52
+
53
+ type semanticTag = [#latest | #earliest | #pending]
54
+ type hexString = string
55
+ type blockNumber = int
56
+
57
+ type blockTagVariant = Latest | Earliest | Pending | HexString(string) | BlockNumber(int)
58
+
59
+ let blockTagFromSemantic = (semanticTag: semanticTag): t => semanticTag->Utils.magic
60
+ let blockTagFromBlockNumber = (blockNumber: blockNumber): t => blockNumber->Utils.magic
61
+ let blockTagFromHexString = (hexString: hexString): t => hexString->Utils.magic
62
+
63
+ let blockTagFromVariant = variant =>
64
+ switch variant {
65
+ | Latest => #latest->blockTagFromSemantic
66
+ | Earliest => #earliest->blockTagFromSemantic
67
+ | Pending => #pending->blockTagFromSemantic
68
+ | HexString(str) => str->blockTagFromHexString
69
+ | BlockNumber(num) => num->blockTagFromBlockNumber
70
+ }
71
+ }
72
+
73
+ module EventFilter = {
74
+ type topic = EvmTypes.Hex.t
75
+ type t = {
76
+ address: Address.t,
77
+ topics: array<topic>,
78
+ }
79
+ }
80
+ module Filter = {
81
+ type t
82
+
83
+ //This can be used as a filter but should not assume all filters are the same type
84
+ //address could be an array of addresses like in combined filter
85
+ type filterRecord = {
86
+ address: Address.t,
87
+ topics: array<EventFilter.topic>,
88
+ fromBlock: BlockTag.t,
89
+ toBlock: BlockTag.t,
90
+ }
91
+
92
+ let filterFromRecord = (filterRecord: filterRecord): t => filterRecord->Utils.magic
93
+ }
94
+
95
+ module CombinedFilter = {
96
+ type combinedFilterRecord = {
97
+ address: array<Address.t>,
98
+ //The second element of the tuple is the
99
+ topics: array<array<EventFilter.topic>>,
100
+ fromBlock: BlockTag.t,
101
+ toBlock: BlockTag.t,
102
+ }
103
+
104
+ let combinedFilterToFilter = (combinedFilter: combinedFilterRecord): Filter.t =>
105
+ combinedFilter->Utils.magic
106
+ }
107
+
108
+ type log = {
109
+ blockNumber: int,
110
+ blockHash: string,
111
+ removed: option<bool>,
112
+ //Note: this is the index of the log in the transaction and should be used whenever we use "logIndex"
113
+ address: Address.t,
114
+ data: string,
115
+ topics: array<EventFilter.topic>,
116
+ transactionHash: txHash,
117
+ transactionIndex: int,
118
+ //Note: this logIndex is the index of the log in the block, not the transaction
119
+ @as("index") logIndex: int,
120
+ }
121
+
122
+ type transaction
123
+
124
+ type minimumParseableLogData = {topics: array<EventFilter.topic>, data: string}
125
+
126
+ //Can safely convert from log to minimumParseableLogData since it contains
127
+ //both data points required
128
+ let logToMinimumParseableLogData: log => minimumParseableLogData = Utils.magic
129
+
130
+ type logDescription<'a> = {
131
+ args: 'a,
132
+ name: string,
133
+ signature: string,
134
+ topic: string,
135
+ }
136
+
137
+ module Network = {
138
+ type t
139
+
140
+ @module("ethers") @new
141
+ external make: (~name: string, ~chainId: int) => t = "Network"
142
+
143
+ @module("ethers") @scope("Network")
144
+ external fromChainId: (~chainId: int) => t = "from"
145
+ }
146
+
147
+ module JsonRpcProvider = {
148
+ type t
149
+
150
+ type rpcOptions = {
151
+ staticNetwork?: Network.t,
152
+ // Options for FallbackProvider
153
+ /**
154
+ * The amount of time to wait before kicking off the next provider.
155
+ *
156
+ * Any providers that have not responded can still respond and be
157
+ * counted, but this ensures new providers start.
158
+ * Default: 400ms
159
+ */
160
+ stallTimeout?: int,
161
+ /**
162
+ * The priority. Lower priority providers are dispatched first.
163
+ * Default: 1
164
+ */
165
+ priority?: int,
166
+ /**
167
+ * The amount of weight a provider is given against the quorum.
168
+ * Default: 1
169
+ */
170
+ weight?: int,
171
+ }
172
+
173
+ type fallbackProviderOptions = {
174
+ // How many providers must agree on a value before reporting
175
+ // back the response
176
+ // Note: Default the half of the providers weight, so we need to set it to accept result from the first rpc
177
+ quorum?: int,
178
+ }
179
+
180
+ @module("ethers") @scope("ethers") @new
181
+ external makeWithOptions: (~rpcUrl: string, ~network: Network.t, ~options: rpcOptions) => t =
182
+ "JsonRpcProvider"
183
+
184
+ @module("ethers") @scope("ethers") @new
185
+ external makeFallbackProvider: (
186
+ ~providers: array<t>,
187
+ ~network: Network.t,
188
+ ~options: fallbackProviderOptions,
189
+ ) => t = "FallbackProvider"
190
+
191
+ let makeStatic = (~rpcUrl: string, ~network: Network.t, ~priority=?, ~stallTimeout=?): t => {
192
+ makeWithOptions(~rpcUrl, ~network, ~options={staticNetwork: network, ?priority, ?stallTimeout})
193
+ }
194
+
195
+ let make = (~rpcUrls: array<string>, ~chainId: int, ~fallbackStallTimeout): t => {
196
+ let network = Network.fromChainId(~chainId)
197
+ switch rpcUrls {
198
+ | [rpcUrl] => makeStatic(~rpcUrl, ~network)
199
+ | rpcUrls =>
200
+ makeFallbackProvider(
201
+ ~providers=rpcUrls->Js.Array2.mapi((rpcUrl, index) =>
202
+ makeStatic(~rpcUrl, ~network, ~priority=index, ~stallTimeout=fallbackStallTimeout)
203
+ ),
204
+ ~network,
205
+ ~options={
206
+ quorum: 1,
207
+ },
208
+ )
209
+ }
210
+ }
211
+
212
+ @send
213
+ external getLogs: (t, ~filter: Filter.t) => promise<array<log>> = "getLogs"
214
+
215
+ @send
216
+ external getTransaction: (t, ~transactionHash: string) => promise<transaction> = "getTransaction"
217
+
218
+ let makeGetTransactionFields = (~getTransactionByHash) => async (log: log): promise<unknown> => {
219
+ let transaction = await getTransactionByHash(log.transactionHash)
220
+ // Mutating should be fine, since the transaction isn't used anywhere else outside the function
221
+ let fields: {..} = transaction->Obj.magic
222
+
223
+ // Make it compatible with HyperSync transaction fields
224
+ fields["transactionIndex"] = log.transactionIndex
225
+ fields["input"] = fields["data"]
226
+
227
+ fields->Obj.magic
228
+ }
229
+
230
+ type listenerEvent = [#block]
231
+ @send external onEventListener: (t, listenerEvent, int => unit) => unit = "on"
232
+
233
+ @send external offAllEventListeners: (t, listenerEvent) => unit = "off"
234
+
235
+ let onBlock = (t, callback: int => unit) => t->onEventListener(#block, callback)
236
+
237
+ let removeOnBlockEventListener = t => t->offAllEventListeners(#block)
238
+
239
+ @send
240
+ external getBlockNumber: t => promise<int> = "getBlockNumber"
241
+
242
+ type block = {
243
+ _difficulty: bigint,
244
+ difficulty: int,
245
+ extraData: Address.t,
246
+ gasLimit: bigint,
247
+ gasUsed: bigint,
248
+ hash: string,
249
+ miner: Address.t,
250
+ nonce: int,
251
+ number: int,
252
+ parentHash: Address.t,
253
+ timestamp: int,
254
+ transactions: array<Address.t>,
255
+ }
256
+
257
+ @send
258
+ external getBlock: (t, int) => promise<Js.nullable<block>> = "getBlock"
259
+ }
@@ -0,0 +1,12 @@
1
+ module Queue = {
2
+ type t<'a>
3
+
4
+ @module("js-sdsl") @new external make: unit => t<'a> = "Queue"
5
+
6
+ type containerSize = int
7
+ @send external size: t<'a> => containerSize = "size"
8
+ @send external push: (t<'a>, 'a) => containerSize = "push"
9
+ @send external pop: t<'a> => option<'a> = "pop"
10
+ //Returns the front item without popping it
11
+ @send external front: t<'a> => option<'a> = "front"
12
+ }