envio 3.0.0-alpha.2 → 3.0.0-alpha.21
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/README.md +164 -30
- package/bin.mjs +49 -0
- package/evm.schema.json +79 -169
- package/fuel.schema.json +50 -21
- package/index.d.ts +578 -1
- package/index.js +4 -0
- package/package.json +47 -31
- package/rescript.json +4 -1
- package/src/Batch.res +11 -8
- package/src/Batch.res.mjs +11 -9
- package/src/ChainFetcher.res +531 -0
- package/src/ChainFetcher.res.mjs +339 -0
- package/src/ChainManager.res +190 -0
- package/src/ChainManager.res.mjs +166 -0
- package/src/Change.res +3 -3
- package/src/Config.gen.ts +19 -0
- package/src/Config.res +725 -25
- package/src/Config.res.mjs +692 -26
- package/src/{Indexer.res → Ctx.res} +1 -1
- package/src/Ecosystem.res +9 -124
- package/src/Ecosystem.res.mjs +19 -160
- package/src/Env.res +33 -73
- package/src/Env.res.mjs +29 -85
- package/src/Envio.gen.ts +3 -1
- package/src/Envio.res +77 -9
- package/src/Envio.res.mjs +39 -1
- package/src/EventConfigBuilder.res +408 -0
- package/src/EventConfigBuilder.res.mjs +376 -0
- package/src/EventProcessing.res +469 -0
- package/src/EventProcessing.res.mjs +337 -0
- package/src/EvmTypes.gen.ts +6 -0
- package/src/EvmTypes.res +1 -0
- package/src/FetchState.res +1256 -639
- package/src/FetchState.res.mjs +1135 -612
- package/src/GlobalState.res +1224 -0
- package/src/GlobalState.res.mjs +1291 -0
- package/src/GlobalStateManager.res +68 -0
- package/src/GlobalStateManager.res.mjs +75 -0
- package/src/GlobalStateManager.resi +7 -0
- package/src/HandlerLoader.res +89 -0
- package/src/HandlerLoader.res.mjs +79 -0
- package/src/HandlerRegister.res +357 -0
- package/src/HandlerRegister.res.mjs +299 -0
- package/src/HandlerRegister.resi +30 -0
- package/src/Hasura.res +111 -175
- package/src/Hasura.res.mjs +88 -150
- package/src/InMemoryStore.res +1 -1
- package/src/InMemoryStore.res.mjs +3 -3
- package/src/InMemoryTable.res +1 -1
- package/src/InMemoryTable.res.mjs +1 -1
- package/src/Internal.gen.ts +6 -0
- package/src/Internal.res +265 -12
- package/src/Internal.res.mjs +115 -1
- package/src/LoadLayer.res +444 -0
- package/src/LoadLayer.res.mjs +296 -0
- package/src/LoadLayer.resi +32 -0
- package/src/LogSelection.res +33 -27
- package/src/LogSelection.res.mjs +6 -0
- package/src/Logging.res +21 -7
- package/src/Logging.res.mjs +16 -8
- package/src/Main.res +390 -0
- package/src/Main.res.mjs +341 -0
- package/src/Persistence.res +7 -21
- package/src/Persistence.res.mjs +3 -3
- package/src/PgStorage.gen.ts +10 -0
- package/src/PgStorage.res +116 -69
- package/src/PgStorage.res.d.mts +5 -0
- package/src/PgStorage.res.mjs +93 -50
- package/src/Prometheus.res +294 -224
- package/src/Prometheus.res.mjs +353 -340
- package/src/ReorgDetection.res +6 -10
- package/src/ReorgDetection.res.mjs +6 -6
- package/src/SafeCheckpointTracking.res +4 -4
- package/src/SafeCheckpointTracking.res.mjs +2 -2
- package/src/SimulateItems.res +353 -0
- package/src/SimulateItems.res.mjs +335 -0
- package/src/Sink.res +4 -2
- package/src/Sink.res.mjs +2 -1
- package/src/TableIndices.res +0 -1
- package/src/TestIndexer.res +913 -0
- package/src/TestIndexer.res.mjs +698 -0
- package/src/TestIndexerProxyStorage.res +205 -0
- package/src/TestIndexerProxyStorage.res.mjs +151 -0
- package/src/TopicFilter.res +1 -1
- package/src/Types.ts +1 -1
- package/src/UserContext.res +424 -0
- package/src/UserContext.res.mjs +279 -0
- package/src/Utils.res +97 -26
- package/src/Utils.res.mjs +91 -44
- package/src/bindings/BigInt.res +10 -0
- package/src/bindings/BigInt.res.mjs +15 -0
- package/src/bindings/ClickHouse.res +120 -23
- package/src/bindings/ClickHouse.res.mjs +118 -28
- package/src/bindings/DateFns.res +74 -0
- package/src/bindings/DateFns.res.mjs +22 -0
- package/src/bindings/EventSource.res +11 -2
- package/src/bindings/EventSource.res.mjs +8 -1
- package/src/bindings/Express.res +1 -0
- package/src/bindings/Hrtime.res +14 -1
- package/src/bindings/Hrtime.res.mjs +22 -2
- package/src/bindings/Hrtime.resi +4 -0
- package/src/bindings/Lodash.res +0 -1
- package/src/bindings/NodeJs.res +49 -3
- package/src/bindings/NodeJs.res.mjs +11 -3
- package/src/bindings/Pino.res +24 -10
- package/src/bindings/Pino.res.mjs +14 -8
- package/src/bindings/Postgres.gen.ts +8 -0
- package/src/bindings/Postgres.res +5 -1
- package/src/bindings/Postgres.res.d.mts +5 -0
- package/src/bindings/PromClient.res +0 -10
- package/src/bindings/PromClient.res.mjs +0 -3
- package/src/bindings/Vitest.res +144 -0
- package/src/bindings/Vitest.res.mjs +9 -0
- package/src/bindings/WebSocket.res +27 -0
- package/src/bindings/WebSocket.res.mjs +2 -0
- package/src/bindings/Yargs.res +8 -0
- package/src/bindings/Yargs.res.mjs +2 -0
- package/src/db/EntityHistory.res +7 -7
- package/src/db/EntityHistory.res.mjs +9 -9
- package/src/db/InternalTable.res +59 -111
- package/src/db/InternalTable.res.mjs +73 -104
- package/src/db/Table.res +27 -8
- package/src/db/Table.res.mjs +25 -14
- package/src/sources/Evm.res +84 -0
- package/src/sources/Evm.res.mjs +105 -0
- package/src/sources/EvmChain.res +94 -0
- package/src/sources/EvmChain.res.mjs +60 -0
- package/src/sources/Fuel.res +19 -34
- package/src/sources/Fuel.res.mjs +34 -16
- package/src/sources/FuelSDK.res +38 -0
- package/src/sources/FuelSDK.res.mjs +29 -0
- package/src/sources/HyperFuel.res +2 -2
- package/src/sources/HyperFuel.resi +1 -1
- package/src/sources/HyperFuelClient.res +2 -2
- package/src/sources/HyperFuelSource.res +35 -13
- package/src/sources/HyperFuelSource.res.mjs +26 -16
- package/src/sources/HyperSync.res +61 -60
- package/src/sources/HyperSync.res.mjs +53 -67
- package/src/sources/HyperSync.resi +6 -4
- package/src/sources/HyperSyncClient.res +29 -2
- package/src/sources/HyperSyncClient.res.mjs +9 -0
- package/src/sources/HyperSyncHeightStream.res +76 -118
- package/src/sources/HyperSyncHeightStream.res.mjs +68 -75
- package/src/sources/HyperSyncSource.res +122 -143
- package/src/sources/HyperSyncSource.res.mjs +106 -121
- package/src/sources/Rpc.res +86 -14
- package/src/sources/Rpc.res.mjs +101 -9
- package/src/sources/RpcSource.res +731 -364
- package/src/sources/RpcSource.res.mjs +845 -410
- package/src/sources/RpcWebSocketHeightStream.res +181 -0
- package/src/sources/RpcWebSocketHeightStream.res.mjs +196 -0
- package/src/sources/SimulateSource.res +59 -0
- package/src/sources/SimulateSource.res.mjs +50 -0
- package/src/sources/Source.res +7 -5
- package/src/sources/SourceManager.res +358 -221
- package/src/sources/SourceManager.res.mjs +346 -171
- package/src/sources/SourceManager.resi +17 -6
- package/src/sources/Svm.res +81 -0
- package/src/sources/Svm.res.mjs +90 -0
- package/src/tui/Tui.res +247 -0
- package/src/tui/Tui.res.mjs +337 -0
- package/src/tui/bindings/Ink.res +371 -0
- package/src/tui/bindings/Ink.res.mjs +72 -0
- package/src/tui/bindings/Style.res +123 -0
- package/src/tui/bindings/Style.res.mjs +2 -0
- package/src/tui/components/BufferedProgressBar.res +40 -0
- package/src/tui/components/BufferedProgressBar.res.mjs +57 -0
- package/src/tui/components/CustomHooks.res +122 -0
- package/src/tui/components/CustomHooks.res.mjs +179 -0
- package/src/tui/components/Messages.res +41 -0
- package/src/tui/components/Messages.res.mjs +75 -0
- package/src/tui/components/SyncETA.res +174 -0
- package/src/tui/components/SyncETA.res.mjs +263 -0
- package/src/tui/components/TuiData.res +47 -0
- package/src/tui/components/TuiData.res.mjs +34 -0
- package/svm.schema.json +112 -0
- package/bin.js +0 -48
- package/src/EventRegister.res +0 -241
- package/src/EventRegister.res.mjs +0 -240
- package/src/EventRegister.resi +0 -30
- package/src/bindings/Ethers.gen.ts +0 -14
- package/src/bindings/Ethers.res +0 -204
- package/src/bindings/Ethers.res.mjs +0 -130
- /package/src/{Indexer.res.mjs → Ctx.res.mjs} +0 -0
package/src/Config.res
CHANGED
|
@@ -8,6 +8,7 @@ type sourceSyncOptions = {
|
|
|
8
8
|
backoffMillis?: int,
|
|
9
9
|
queryTimeoutMillis?: int,
|
|
10
10
|
fallbackStallTimeout?: int,
|
|
11
|
+
pollingInterval?: int,
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
type contract = {
|
|
@@ -16,15 +17,34 @@ type contract = {
|
|
|
16
17
|
addresses: array<Address.t>,
|
|
17
18
|
events: array<Internal.eventConfig>,
|
|
18
19
|
startBlock: option<int>,
|
|
20
|
+
// EVM-specific: event sighashes for HyperSync queries
|
|
21
|
+
eventSignatures: array<string>,
|
|
19
22
|
}
|
|
20
23
|
|
|
24
|
+
// Source config parsed from internal.config.json - sources are created lazily in ChainFetcher
|
|
25
|
+
type evmRpcConfig = {
|
|
26
|
+
url: string,
|
|
27
|
+
sourceFor: Source.sourceFor,
|
|
28
|
+
syncConfig: option<sourceSyncOptions>,
|
|
29
|
+
ws: option<string>,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type sourceConfig =
|
|
33
|
+
| EvmSourceConfig({hypersync: option<string>, rpcs: array<evmRpcConfig>})
|
|
34
|
+
| FuelSourceConfig({hypersync: string})
|
|
35
|
+
| SvmSourceConfig({rpc: string})
|
|
36
|
+
// For tests: pass custom sources directly
|
|
37
|
+
| CustomSources(array<Source.t>)
|
|
38
|
+
|
|
21
39
|
type chain = {
|
|
40
|
+
name: string,
|
|
22
41
|
id: int,
|
|
23
42
|
startBlock: int,
|
|
24
43
|
endBlock?: int,
|
|
25
44
|
maxReorgDepth: int,
|
|
45
|
+
blockLag: int,
|
|
26
46
|
contracts: array<contract>,
|
|
27
|
-
|
|
47
|
+
sourceConfig: sourceConfig,
|
|
28
48
|
}
|
|
29
49
|
|
|
30
50
|
type sourceSync = {
|
|
@@ -35,11 +55,23 @@ type sourceSync = {
|
|
|
35
55
|
backoffMillis: int,
|
|
36
56
|
queryTimeoutMillis: int,
|
|
37
57
|
fallbackStallTimeout: int,
|
|
58
|
+
pollingInterval: int,
|
|
38
59
|
}
|
|
39
60
|
|
|
40
|
-
type multichain =
|
|
61
|
+
type multichain = Internal.multichain =
|
|
62
|
+
| @as("ordered") Ordered
|
|
63
|
+
| @as("unordered") Unordered
|
|
64
|
+
|
|
65
|
+
type contractHandler = {
|
|
66
|
+
name: string,
|
|
67
|
+
handler: option<string>,
|
|
68
|
+
}
|
|
41
69
|
|
|
42
70
|
type t = {
|
|
71
|
+
name: string,
|
|
72
|
+
description: option<string>,
|
|
73
|
+
handlers: string,
|
|
74
|
+
contractHandlers: array<contractHandler>,
|
|
43
75
|
shouldRollbackOnReorg: bool,
|
|
44
76
|
shouldSaveFullHistory: bool,
|
|
45
77
|
multichain: multichain,
|
|
@@ -51,31 +83,634 @@ type t = {
|
|
|
51
83
|
batchSize: int,
|
|
52
84
|
lowercaseAddresses: bool,
|
|
53
85
|
addContractNameToContractNameMapping: dict<string>,
|
|
86
|
+
userEntitiesByName: dict<Internal.entityConfig>,
|
|
87
|
+
userEntities: array<Internal.entityConfig>,
|
|
88
|
+
allEntities: array<Internal.entityConfig>,
|
|
89
|
+
allEnums: array<Table.enumConfig<Table.enum>>,
|
|
54
90
|
}
|
|
55
91
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
~
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
|
|
92
|
+
module DynamicContractRegistry = {
|
|
93
|
+
let name = "dynamic_contract_registry"
|
|
94
|
+
let index = -1
|
|
95
|
+
|
|
96
|
+
let makeId = (~chainId, ~contractAddress) => {
|
|
97
|
+
chainId->Belt.Int.toString ++ "-" ++ contractAddress->Address.toString
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@genType
|
|
101
|
+
type t = {
|
|
102
|
+
id: string,
|
|
103
|
+
@as("chain_id") chainId: int,
|
|
104
|
+
@as("registering_event_block_number") registeringEventBlockNumber: int,
|
|
105
|
+
@as("registering_event_log_index") registeringEventLogIndex: int,
|
|
106
|
+
@as("registering_event_block_timestamp") registeringEventBlockTimestamp: int,
|
|
107
|
+
@as("registering_event_contract_name") registeringEventContractName: string,
|
|
108
|
+
@as("registering_event_name") registeringEventName: string,
|
|
109
|
+
@as("registering_event_src_address") registeringEventSrcAddress: Address.t,
|
|
110
|
+
@as("contract_address") contractAddress: Address.t,
|
|
111
|
+
@as("contract_name") contractName: string,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let schema = S.schema(s => {
|
|
115
|
+
id: s.matches(S.string),
|
|
116
|
+
chainId: s.matches(S.int),
|
|
117
|
+
registeringEventBlockNumber: s.matches(S.int),
|
|
118
|
+
registeringEventLogIndex: s.matches(S.int),
|
|
119
|
+
registeringEventContractName: s.matches(S.string),
|
|
120
|
+
registeringEventName: s.matches(S.string),
|
|
121
|
+
registeringEventSrcAddress: s.matches(Address.schema),
|
|
122
|
+
registeringEventBlockTimestamp: s.matches(S.int),
|
|
123
|
+
contractAddress: s.matches(Address.schema),
|
|
124
|
+
contractName: s.matches(S.string),
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
let rowsSchema = S.array(schema)
|
|
128
|
+
|
|
129
|
+
let table = Table.mkTable(
|
|
130
|
+
name,
|
|
131
|
+
~fields=[
|
|
132
|
+
Table.mkField("id", String, ~isPrimaryKey=true, ~fieldSchema=S.string),
|
|
133
|
+
Table.mkField("chain_id", Int32, ~fieldSchema=S.int),
|
|
134
|
+
Table.mkField("registering_event_block_number", Int32, ~fieldSchema=S.int),
|
|
135
|
+
Table.mkField("registering_event_log_index", Int32, ~fieldSchema=S.int),
|
|
136
|
+
Table.mkField("registering_event_block_timestamp", Int32, ~fieldSchema=S.int),
|
|
137
|
+
Table.mkField("registering_event_contract_name", String, ~fieldSchema=S.string),
|
|
138
|
+
Table.mkField("registering_event_name", String, ~fieldSchema=S.string),
|
|
139
|
+
Table.mkField("registering_event_src_address", String, ~fieldSchema=Address.schema),
|
|
140
|
+
Table.mkField("contract_address", String, ~fieldSchema=Address.schema),
|
|
141
|
+
Table.mkField("contract_name", String, ~fieldSchema=S.string),
|
|
142
|
+
],
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
external castToInternal: t => Internal.entity = "%identity"
|
|
146
|
+
|
|
147
|
+
let entityConfig = {
|
|
148
|
+
name,
|
|
149
|
+
index,
|
|
150
|
+
schema,
|
|
151
|
+
rowsSchema,
|
|
152
|
+
table,
|
|
153
|
+
}->Internal.fromGenericEntityConfig
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Types for parsing source config from internal.config.json
|
|
157
|
+
type rpcSourceFor = | @as("sync") Sync | @as("fallback") Fallback | @as("live") Live
|
|
158
|
+
|
|
159
|
+
let rpcSourceForSchema = S.enum([Sync, Fallback, Live])
|
|
160
|
+
|
|
161
|
+
let rpcConfigSchema = S.schema(s =>
|
|
162
|
+
{
|
|
163
|
+
"url": s.matches(S.string),
|
|
164
|
+
"for": s.matches(rpcSourceForSchema),
|
|
165
|
+
"ws": s.matches(S.option(S.string)),
|
|
166
|
+
"initialBlockInterval": s.matches(S.option(S.int)),
|
|
167
|
+
"backoffMultiplicative": s.matches(S.option(S.float)),
|
|
168
|
+
"accelerationAdditive": s.matches(S.option(S.int)),
|
|
169
|
+
"intervalCeiling": s.matches(S.option(S.int)),
|
|
170
|
+
"backoffMillis": s.matches(S.option(S.int)),
|
|
171
|
+
"fallbackStallTimeout": s.matches(S.option(S.int)),
|
|
172
|
+
"queryTimeoutMillis": s.matches(S.option(S.int)),
|
|
173
|
+
"pollingInterval": s.matches(S.option(S.int)),
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
let chainContractSchema = S.schema(s =>
|
|
178
|
+
{
|
|
179
|
+
"addresses": s.matches(S.option(S.array(S.string))),
|
|
180
|
+
"startBlock": s.matches(S.option(S.int)),
|
|
181
|
+
}
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
let publicConfigChainSchema = S.schema(s =>
|
|
185
|
+
{
|
|
186
|
+
"id": s.matches(S.int),
|
|
187
|
+
"startBlock": s.matches(S.int),
|
|
188
|
+
"endBlock": s.matches(S.option(S.int)),
|
|
189
|
+
"maxReorgDepth": s.matches(S.option(S.int)),
|
|
190
|
+
"blockLag": s.matches(S.option(S.int)),
|
|
191
|
+
// EVM/Fuel source config (hypersync for EVM, hyperfuel for Fuel)
|
|
192
|
+
"hypersync": s.matches(S.option(S.string)),
|
|
193
|
+
"rpcs": s.matches(S.option(S.array(rpcConfigSchema))),
|
|
194
|
+
// SVM source config
|
|
195
|
+
"rpc": s.matches(S.option(S.string)),
|
|
196
|
+
// Per-chain contract data (addresses and optional start block)
|
|
197
|
+
"contracts": s.matches(S.option(S.dict(chainContractSchema))),
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
let contractEventItemSchema = S.schema(s =>
|
|
202
|
+
{
|
|
203
|
+
"event": s.matches(S.string),
|
|
204
|
+
"name": s.matches(S.string),
|
|
205
|
+
"sighash": s.matches(S.string),
|
|
206
|
+
"params": s.matches(S.option(S.array(EventConfigBuilder.eventParamSchema))),
|
|
207
|
+
"kind": s.matches(S.option(S.string)),
|
|
208
|
+
"blockFields": s.matches(S.option(S.array(Internal.evmBlockFieldSchema))),
|
|
209
|
+
"transactionFields": s.matches(S.option(S.array(Internal.evmTransactionFieldSchema))),
|
|
210
|
+
}
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
let contractConfigSchema = S.schema(s =>
|
|
214
|
+
{
|
|
215
|
+
"abi": s.matches(S.json(~validate=false)),
|
|
216
|
+
"handler": s.matches(S.option(S.string)),
|
|
217
|
+
// EVM-specific: event signatures for HyperSync queries
|
|
218
|
+
"events": s.matches(S.option(S.array(contractEventItemSchema))),
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
let publicConfigEcosystemSchema = S.schema(s =>
|
|
223
|
+
{
|
|
224
|
+
"chains": s.matches(S.dict(publicConfigChainSchema)),
|
|
225
|
+
"contracts": s.matches(S.option(S.dict(contractConfigSchema))),
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
type addressFormat = | @as("lowercase") Lowercase | @as("checksum") Checksum
|
|
230
|
+
|
|
231
|
+
let publicConfigEvmSchema = S.schema(s =>
|
|
232
|
+
{
|
|
233
|
+
"chains": s.matches(S.dict(publicConfigChainSchema)),
|
|
234
|
+
"contracts": s.matches(S.option(S.dict(contractConfigSchema))),
|
|
235
|
+
"addressFormat": s.matches(S.option(S.enum([Lowercase, Checksum]))),
|
|
236
|
+
"globalBlockFields": s.matches(S.option(S.array(Internal.evmBlockFieldSchema))),
|
|
237
|
+
"globalTransactionFields": s.matches(S.option(S.array(Internal.evmTransactionFieldSchema))),
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
let multichainSchema = S.enum([Ordered, Unordered])
|
|
242
|
+
|
|
243
|
+
let compositeIndexFieldSchema = S.schema(s =>
|
|
244
|
+
{
|
|
245
|
+
"fieldName": s.matches(S.string),
|
|
246
|
+
"direction": s.matches(S.string),
|
|
247
|
+
}
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
let derivedFieldSchema = S.schema(s =>
|
|
251
|
+
{
|
|
252
|
+
"fieldName": s.matches(S.string),
|
|
253
|
+
"derivedFromEntity": s.matches(S.string),
|
|
254
|
+
"derivedFromField": s.matches(S.string),
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
let propertySchema = S.schema(s =>
|
|
259
|
+
{
|
|
260
|
+
"name": s.matches(S.string),
|
|
261
|
+
"type": s.matches(S.string),
|
|
262
|
+
"isNullable": s.matches(S.option(S.bool)),
|
|
263
|
+
"isArray": s.matches(S.option(S.bool)),
|
|
264
|
+
"isIndex": s.matches(S.option(S.bool)),
|
|
265
|
+
"linkedEntity": s.matches(S.option(S.string)),
|
|
266
|
+
"enum": s.matches(S.option(S.string)),
|
|
267
|
+
"entity": s.matches(S.option(S.string)),
|
|
268
|
+
"precision": s.matches(S.option(S.int)),
|
|
269
|
+
"scale": s.matches(S.option(S.int)),
|
|
270
|
+
}
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
let entityJsonSchema = S.schema(s =>
|
|
274
|
+
{
|
|
275
|
+
"name": s.matches(S.string),
|
|
276
|
+
"properties": s.matches(S.array(propertySchema)),
|
|
277
|
+
"derivedFields": s.matches(S.option(S.array(derivedFieldSchema))),
|
|
278
|
+
"compositeIndices": s.matches(S.option(S.array(S.array(compositeIndexFieldSchema)))),
|
|
279
|
+
}
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
let getFieldTypeAndSchema = (prop, ~enumConfigsByName: dict<Table.enumConfig<Table.enum>>) => {
|
|
283
|
+
let typ = prop["type"]
|
|
284
|
+
let isNullable = prop["isNullable"]->Option.getWithDefault(false)
|
|
285
|
+
let isArray = prop["isArray"]->Option.getWithDefault(false)
|
|
286
|
+
let isIndex = prop["isIndex"]->Option.getWithDefault(false)
|
|
287
|
+
|
|
288
|
+
let (fieldType, baseSchema) = switch typ {
|
|
289
|
+
| "string" => (Table.String, S.string->S.toUnknown)
|
|
290
|
+
| "boolean" => (Table.Boolean, S.bool->S.toUnknown)
|
|
291
|
+
| "int" => (Table.Int32, S.int->S.toUnknown)
|
|
292
|
+
| "bigint" => (Table.BigInt({precision: ?prop["precision"]}), BigInt.schema->S.toUnknown)
|
|
293
|
+
| "bigdecimal" => (
|
|
294
|
+
Table.BigDecimal({
|
|
295
|
+
config: ?prop["precision"]->Option.map(p => (p, prop["scale"]->Option.getWithDefault(0))),
|
|
296
|
+
}),
|
|
297
|
+
BigDecimal.schema->S.toUnknown,
|
|
298
|
+
)
|
|
299
|
+
| "float" => (Table.Number, S.float->S.toUnknown)
|
|
300
|
+
| "serial" => (Table.Serial, S.int->S.toUnknown)
|
|
301
|
+
| "json" => (Table.Json, S.json(~validate=false)->S.toUnknown)
|
|
302
|
+
| "date" => (Table.Date, Utils.Schema.dbDate->S.toUnknown)
|
|
303
|
+
| "enum" => {
|
|
304
|
+
let enumName = prop["enum"]->Option.getExn
|
|
305
|
+
let enumConfig =
|
|
306
|
+
enumConfigsByName
|
|
307
|
+
->Js.Dict.get(enumName)
|
|
308
|
+
->Option.getExn
|
|
309
|
+
(Table.Enum({config: enumConfig}), enumConfig.schema->S.toUnknown)
|
|
310
|
+
}
|
|
311
|
+
| "entity" => {
|
|
312
|
+
let entityName = prop["entity"]->Option.getExn
|
|
313
|
+
(Table.Entity({name: entityName}), S.string->S.toUnknown)
|
|
314
|
+
}
|
|
315
|
+
| other => Js.Exn.raiseError("Unknown field type in entity config: " ++ other)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
let fieldSchema = if isArray {
|
|
319
|
+
S.array(baseSchema)->S.toUnknown
|
|
320
|
+
} else {
|
|
321
|
+
baseSchema
|
|
322
|
+
}
|
|
323
|
+
let fieldSchema = if isNullable {
|
|
324
|
+
S.null(fieldSchema)->S.toUnknown
|
|
325
|
+
} else {
|
|
326
|
+
fieldSchema
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
(fieldType, fieldSchema, isNullable, isArray, isIndex)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
let parseEnumsFromJson = (enumsJson: dict<array<string>>): array<Table.enumConfig<Table.enum>> => {
|
|
333
|
+
enumsJson
|
|
334
|
+
->Js.Dict.entries
|
|
335
|
+
->Array.map(((name, variants)) =>
|
|
336
|
+
Table.makeEnumConfig(~name, ~variants)->Table.fromGenericEnumConfig
|
|
337
|
+
)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
let parseEntitiesFromJson = (
|
|
341
|
+
entitiesJson: array<'entityJson>,
|
|
342
|
+
~enumConfigsByName: dict<Table.enumConfig<Table.enum>>,
|
|
343
|
+
): array<Internal.entityConfig> => {
|
|
344
|
+
entitiesJson->Array.mapWithIndex((index, entityJson) => {
|
|
345
|
+
let entityName = entityJson["name"]
|
|
346
|
+
|
|
347
|
+
let fields: array<Table.fieldOrDerived> = entityJson["properties"]->Array.map(prop => {
|
|
348
|
+
let (fieldType, fieldSchema, isNullable, isArray, isIndex) = getFieldTypeAndSchema(
|
|
349
|
+
prop,
|
|
350
|
+
~enumConfigsByName,
|
|
351
|
+
)
|
|
352
|
+
Table.mkField(
|
|
353
|
+
prop["name"],
|
|
354
|
+
fieldType,
|
|
355
|
+
~fieldSchema,
|
|
356
|
+
~isPrimaryKey=prop["name"] === "id",
|
|
357
|
+
~isNullable,
|
|
358
|
+
~isArray,
|
|
359
|
+
~isIndex,
|
|
360
|
+
~linkedEntity=?prop["linkedEntity"],
|
|
361
|
+
)
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
let derivedFields: array<Table.fieldOrDerived> =
|
|
365
|
+
entityJson["derivedFields"]
|
|
366
|
+
->Option.getWithDefault([])
|
|
367
|
+
->Array.map(df =>
|
|
368
|
+
Table.mkDerivedFromField(
|
|
369
|
+
df["fieldName"],
|
|
370
|
+
~derivedFromEntity=df["derivedFromEntity"],
|
|
371
|
+
~derivedFromField=df["derivedFromField"],
|
|
372
|
+
)
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
let compositeIndices =
|
|
376
|
+
entityJson["compositeIndices"]
|
|
377
|
+
->Option.getWithDefault([])
|
|
378
|
+
->Array.map(ci =>
|
|
379
|
+
ci->Array.map(
|
|
380
|
+
f => {
|
|
381
|
+
Table.fieldName: f["fieldName"],
|
|
382
|
+
direction: f["direction"] == "Asc" ? Table.Asc : Table.Desc,
|
|
383
|
+
},
|
|
384
|
+
)
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
let table = Table.mkTable(
|
|
388
|
+
entityName,
|
|
389
|
+
~fields=Array.concat(fields, derivedFields),
|
|
390
|
+
~compositeIndices,
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
// Build schema dynamically from properties
|
|
394
|
+
// Use db field names (with _id suffix for linked entities) as schema locations
|
|
395
|
+
// to match the database column names used in Table.toSqlParams
|
|
396
|
+
let schema = S.schema(s => {
|
|
397
|
+
let dict = Js.Dict.empty()
|
|
398
|
+
entityJson["properties"]->Array.forEach(
|
|
399
|
+
prop => {
|
|
400
|
+
let (_, fieldSchema, _, _, _) = getFieldTypeAndSchema(prop, ~enumConfigsByName)
|
|
401
|
+
let dbFieldName = switch prop["linkedEntity"] {
|
|
402
|
+
| Some(_) => prop["name"] ++ "_id"
|
|
403
|
+
| None => prop["name"]
|
|
404
|
+
}
|
|
405
|
+
dict->Js.Dict.set(dbFieldName, s.matches(fieldSchema))
|
|
406
|
+
},
|
|
407
|
+
)
|
|
408
|
+
dict
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
{
|
|
412
|
+
Internal.name: entityName,
|
|
413
|
+
index,
|
|
414
|
+
schema: schema->(Utils.magic: S.t<dict<unknown>> => S.t<Internal.entity>),
|
|
415
|
+
rowsSchema: S.array(schema)->(
|
|
416
|
+
Utils.magic: S.t<array<dict<unknown>>> => S.t<array<Internal.entity>>
|
|
417
|
+
),
|
|
418
|
+
table,
|
|
419
|
+
}->Internal.fromGenericEntityConfig
|
|
420
|
+
})
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
let publicConfigSchema = S.schema(s =>
|
|
424
|
+
{
|
|
425
|
+
"name": s.matches(S.string),
|
|
426
|
+
"description": s.matches(S.option(S.string)),
|
|
427
|
+
"handlers": s.matches(S.option(S.string)),
|
|
428
|
+
"multichain": s.matches(S.option(multichainSchema)),
|
|
429
|
+
"fullBatchSize": s.matches(S.option(S.int)),
|
|
430
|
+
"rollbackOnReorg": s.matches(S.option(S.bool)),
|
|
431
|
+
"saveFullHistory": s.matches(S.option(S.bool)),
|
|
432
|
+
"rawEvents": s.matches(S.option(S.bool)),
|
|
433
|
+
"evm": s.matches(S.option(publicConfigEvmSchema)),
|
|
434
|
+
"fuel": s.matches(S.option(publicConfigEcosystemSchema)),
|
|
435
|
+
"svm": s.matches(S.option(publicConfigEcosystemSchema)),
|
|
436
|
+
"enums": s.matches(S.option(S.dict(S.array(S.string)))),
|
|
437
|
+
"entities": s.matches(S.option(S.array(entityJsonSchema))),
|
|
438
|
+
}
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
let fromPublic = (publicConfigJson: Js.Json.t, ~maxAddrInPartition=5000) => {
|
|
442
|
+
// Parse public config
|
|
443
|
+
let publicConfig = try publicConfigJson->S.parseOrThrow(publicConfigSchema) catch {
|
|
444
|
+
| S.Raised(exn) =>
|
|
445
|
+
Js.Exn.raiseError(
|
|
446
|
+
`Invalid internal.config.ts: ${exn->Utils.prettifyExn->(Utils.magic: exn => string)}`,
|
|
447
|
+
)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Determine ecosystem from publicConfig (extract just chains for unified handling)
|
|
451
|
+
let (publicChainsConfig, ecosystemName) = switch (
|
|
452
|
+
publicConfig["evm"],
|
|
453
|
+
publicConfig["fuel"],
|
|
454
|
+
publicConfig["svm"],
|
|
455
|
+
) {
|
|
456
|
+
| (Some(ecosystemConfig), None, None) => (ecosystemConfig["chains"], Ecosystem.Evm)
|
|
457
|
+
| (None, Some(ecosystemConfig), None) => (ecosystemConfig["chains"], Ecosystem.Fuel)
|
|
458
|
+
| (None, None, Some(ecosystemConfig)) => (ecosystemConfig["chains"], Ecosystem.Svm)
|
|
459
|
+
| (None, None, None) =>
|
|
460
|
+
Js.Exn.raiseError("Invalid indexer config: No ecosystem configured (evm, fuel, or svm)")
|
|
461
|
+
| _ =>
|
|
70
462
|
Js.Exn.raiseError(
|
|
71
|
-
"
|
|
463
|
+
"Invalid indexer config: Multiple ecosystems are not supported for a single indexer",
|
|
464
|
+
)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Extract EVM-specific options with defaults
|
|
468
|
+
let lowercaseAddresses = switch publicConfig["evm"] {
|
|
469
|
+
| Some(evm) => evm["addressFormat"]->Option.getWithDefault(Checksum) == Lowercase
|
|
470
|
+
| None => false
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Parse contract configs (ABIs, events, handlers)
|
|
474
|
+
let publicContractsConfig = switch (ecosystemName, publicConfig["evm"], publicConfig["fuel"]) {
|
|
475
|
+
| (Ecosystem.Evm, Some(evm), _) => evm["contracts"]
|
|
476
|
+
| (Ecosystem.Fuel, _, Some(fuel)) => fuel["contracts"]
|
|
477
|
+
| _ => None
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Create global field selection Sets once (shared across events without per-event overrides)
|
|
481
|
+
let (globalBlockFieldsSet, globalTransactionFieldsSet) = switch publicConfig["evm"] {
|
|
482
|
+
| Some(evm) => (
|
|
483
|
+
Utils.Set.fromArray(
|
|
484
|
+
Array.concat(
|
|
485
|
+
EventConfigBuilder.alwaysIncludedBlockFields,
|
|
486
|
+
evm["globalBlockFields"]->Option.getWithDefault([]),
|
|
487
|
+
),
|
|
488
|
+
),
|
|
489
|
+
Utils.Set.fromArray(evm["globalTransactionFields"]->Option.getWithDefault([])),
|
|
72
490
|
)
|
|
491
|
+
| None => (Utils.Set.fromArray(EventConfigBuilder.alwaysIncludedBlockFields), Utils.Set.make())
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Build contract data lookup: ABI, event signatures, event configs (keyed by capitalized name)
|
|
495
|
+
let contractDataByName: Js.Dict.t<{
|
|
496
|
+
"abi": EvmTypes.Abi.t,
|
|
497
|
+
"eventSignatures": array<string>,
|
|
498
|
+
"events": option<array<_>>,
|
|
499
|
+
}> = Js.Dict.empty()
|
|
500
|
+
switch publicContractsConfig {
|
|
501
|
+
| Some(contractsDict) =>
|
|
502
|
+
contractsDict
|
|
503
|
+
->Js.Dict.entries
|
|
504
|
+
->Array.forEach(((contractName, contractConfig)) => {
|
|
505
|
+
let capitalizedName = contractName->Utils.String.capitalize
|
|
506
|
+
let abi = contractConfig["abi"]->(Utils.magic: Js.Json.t => EvmTypes.Abi.t)
|
|
507
|
+
let eventSignatures = switch contractConfig["events"] {
|
|
508
|
+
| Some(events) => events->Array.map(eventItem => eventItem["event"])
|
|
509
|
+
| None => []
|
|
510
|
+
}
|
|
511
|
+
contractDataByName->Js.Dict.set(
|
|
512
|
+
capitalizedName,
|
|
513
|
+
{"abi": abi, "eventSignatures": eventSignatures, "events": contractConfig["events"]},
|
|
514
|
+
)
|
|
515
|
+
})
|
|
516
|
+
| None => ()
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Build event configs for a contract from JSON event items
|
|
520
|
+
let buildContractEvents = (~contractName, ~events: option<array<_>>, ~abi) => {
|
|
521
|
+
switch events {
|
|
522
|
+
| None => []
|
|
523
|
+
| Some(eventItems) =>
|
|
524
|
+
eventItems->Array.map(eventItem => {
|
|
525
|
+
let eventName = eventItem["name"]
|
|
526
|
+
let sighash = eventItem["sighash"]
|
|
527
|
+
let params = eventItem["params"]->Option.getWithDefault([])
|
|
528
|
+
let kind = eventItem["kind"]
|
|
529
|
+
// Get handler registration data
|
|
530
|
+
let isWildcard = HandlerRegister.isWildcard(~contractName, ~eventName)
|
|
531
|
+
let handler = HandlerRegister.getHandler(~contractName, ~eventName)
|
|
532
|
+
let contractRegister = HandlerRegister.getContractRegister(~contractName, ~eventName)
|
|
533
|
+
|
|
534
|
+
switch ecosystemName {
|
|
535
|
+
| Ecosystem.Fuel =>
|
|
536
|
+
switch kind {
|
|
537
|
+
| Some(fuelKind) =>
|
|
538
|
+
(EventConfigBuilder.buildFuelEventConfig(
|
|
539
|
+
~contractName,
|
|
540
|
+
~eventName,
|
|
541
|
+
~kind=fuelKind,
|
|
542
|
+
~sighash,
|
|
543
|
+
~rawAbi=abi->(Utils.magic: EvmTypes.Abi.t => Js.Json.t),
|
|
544
|
+
~isWildcard,
|
|
545
|
+
~handler,
|
|
546
|
+
~contractRegister,
|
|
547
|
+
) :> Internal.eventConfig)
|
|
548
|
+
| None =>
|
|
549
|
+
Js.Exn.raiseError(
|
|
550
|
+
`Fuel event ${contractName}.${eventName} is missing "kind" in internal config`,
|
|
551
|
+
)
|
|
552
|
+
}
|
|
553
|
+
| _ =>
|
|
554
|
+
(EventConfigBuilder.buildEvmEventConfig(
|
|
555
|
+
~contractName,
|
|
556
|
+
~eventName,
|
|
557
|
+
~sighash,
|
|
558
|
+
~params,
|
|
559
|
+
~isWildcard,
|
|
560
|
+
~handler,
|
|
561
|
+
~contractRegister,
|
|
562
|
+
~eventFilters=HandlerRegister.getEventFilters(~contractName, ~eventName),
|
|
563
|
+
~blockFields=?eventItem["blockFields"],
|
|
564
|
+
~transactionFields=?eventItem["transactionFields"],
|
|
565
|
+
~globalBlockFieldsSet,
|
|
566
|
+
~globalTransactionFieldsSet,
|
|
567
|
+
) :> Internal.eventConfig)
|
|
568
|
+
}
|
|
569
|
+
})
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Parse address based on ecosystem and address format
|
|
574
|
+
let parseAddress = addressString => {
|
|
575
|
+
switch ecosystemName {
|
|
576
|
+
| Ecosystem.Evm =>
|
|
577
|
+
if lowercaseAddresses {
|
|
578
|
+
addressString->Address.Evm.fromStringLowercaseOrThrow
|
|
579
|
+
} else {
|
|
580
|
+
addressString->Address.Evm.fromStringOrThrow
|
|
581
|
+
}
|
|
582
|
+
| Ecosystem.Fuel | Ecosystem.Svm => addressString->Address.unsafeFromString
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Helper to convert parsed RPC config to evmRpcConfig
|
|
587
|
+
let parseRpcSourceFor = (sourceFor: rpcSourceFor): Source.sourceFor => {
|
|
588
|
+
switch sourceFor {
|
|
589
|
+
| Sync => Source.Sync
|
|
590
|
+
| Fallback => Source.Fallback
|
|
591
|
+
| Live => Source.Live
|
|
592
|
+
}
|
|
73
593
|
}
|
|
74
594
|
|
|
595
|
+
// Build chains from JSON config (no more codegenChains)
|
|
596
|
+
let chains =
|
|
597
|
+
publicChainsConfig
|
|
598
|
+
->Js.Dict.keys
|
|
599
|
+
->Js.Array2.map(chainName => {
|
|
600
|
+
let publicChainConfig = publicChainsConfig->Js.Dict.unsafeGet(chainName)
|
|
601
|
+
let chainId = publicChainConfig["id"]
|
|
602
|
+
|
|
603
|
+
// Build contracts for this chain from per-chain contract data + contract configs
|
|
604
|
+
let chainContracts = publicChainConfig["contracts"]->Option.getWithDefault(Js.Dict.empty())
|
|
605
|
+
let contracts =
|
|
606
|
+
contractDataByName
|
|
607
|
+
->Js.Dict.entries
|
|
608
|
+
->Array.map(((capitalizedName, contractData)) => {
|
|
609
|
+
// Get per-chain contract data (addresses, startBlock)
|
|
610
|
+
let chainContract = chainContracts->Js.Dict.get(capitalizedName)
|
|
611
|
+
let addresses =
|
|
612
|
+
chainContract
|
|
613
|
+
->Option.flatMap(cc => cc["addresses"])
|
|
614
|
+
->Option.getWithDefault([])
|
|
615
|
+
->Array.map(parseAddress)
|
|
616
|
+
let startBlock = chainContract->Option.flatMap(cc => cc["startBlock"])
|
|
617
|
+
|
|
618
|
+
// Build event configs from JSON (field selections resolved inline)
|
|
619
|
+
let events = buildContractEvents(
|
|
620
|
+
~contractName=capitalizedName,
|
|
621
|
+
~events=contractData["events"],
|
|
622
|
+
~abi=contractData["abi"],
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
{
|
|
626
|
+
name: capitalizedName,
|
|
627
|
+
abi: contractData["abi"],
|
|
628
|
+
addresses,
|
|
629
|
+
events,
|
|
630
|
+
startBlock,
|
|
631
|
+
eventSignatures: contractData["eventSignatures"],
|
|
632
|
+
}
|
|
633
|
+
})
|
|
634
|
+
|
|
635
|
+
// Build sourceConfig from the parsed chain config
|
|
636
|
+
let sourceConfig = switch ecosystemName {
|
|
637
|
+
| Ecosystem.Evm =>
|
|
638
|
+
let rpcs =
|
|
639
|
+
publicChainConfig["rpcs"]
|
|
640
|
+
->Option.getWithDefault([])
|
|
641
|
+
->Array.map((rpcConfig): evmRpcConfig => {
|
|
642
|
+
// Build syncConfig from flattened fields
|
|
643
|
+
let initialBlockInterval = rpcConfig["initialBlockInterval"]
|
|
644
|
+
let backoffMultiplicative = rpcConfig["backoffMultiplicative"]
|
|
645
|
+
let accelerationAdditive = rpcConfig["accelerationAdditive"]
|
|
646
|
+
let intervalCeiling = rpcConfig["intervalCeiling"]
|
|
647
|
+
let backoffMillis = rpcConfig["backoffMillis"]
|
|
648
|
+
let queryTimeoutMillis = rpcConfig["queryTimeoutMillis"]
|
|
649
|
+
let fallbackStallTimeout = rpcConfig["fallbackStallTimeout"]
|
|
650
|
+
let pollingInterval = rpcConfig["pollingInterval"]
|
|
651
|
+
let hasSyncConfig =
|
|
652
|
+
initialBlockInterval->Option.isSome ||
|
|
653
|
+
backoffMultiplicative->Option.isSome ||
|
|
654
|
+
accelerationAdditive->Option.isSome ||
|
|
655
|
+
intervalCeiling->Option.isSome ||
|
|
656
|
+
backoffMillis->Option.isSome ||
|
|
657
|
+
queryTimeoutMillis->Option.isSome ||
|
|
658
|
+
fallbackStallTimeout->Option.isSome ||
|
|
659
|
+
pollingInterval->Option.isSome
|
|
660
|
+
let syncConfig: option<sourceSyncOptions> = if hasSyncConfig {
|
|
661
|
+
Some({
|
|
662
|
+
?initialBlockInterval,
|
|
663
|
+
?backoffMultiplicative,
|
|
664
|
+
?accelerationAdditive,
|
|
665
|
+
?intervalCeiling,
|
|
666
|
+
?backoffMillis,
|
|
667
|
+
?queryTimeoutMillis,
|
|
668
|
+
?fallbackStallTimeout,
|
|
669
|
+
?pollingInterval,
|
|
670
|
+
})
|
|
671
|
+
} else {
|
|
672
|
+
None
|
|
673
|
+
}
|
|
674
|
+
{
|
|
675
|
+
url: rpcConfig["url"],
|
|
676
|
+
sourceFor: parseRpcSourceFor(rpcConfig["for"]),
|
|
677
|
+
syncConfig,
|
|
678
|
+
ws: rpcConfig["ws"],
|
|
679
|
+
}
|
|
680
|
+
})
|
|
681
|
+
EvmSourceConfig({hypersync: publicChainConfig["hypersync"], rpcs})
|
|
682
|
+
| Ecosystem.Fuel =>
|
|
683
|
+
switch publicChainConfig["hypersync"] {
|
|
684
|
+
| Some(hypersync) => FuelSourceConfig({hypersync: hypersync})
|
|
685
|
+
| None => Js.Exn.raiseError(`Chain ${chainName} is missing hypersync endpoint in config`)
|
|
686
|
+
}
|
|
687
|
+
| Ecosystem.Svm =>
|
|
688
|
+
switch publicChainConfig["rpc"] {
|
|
689
|
+
| Some(rpc) => SvmSourceConfig({rpc: rpc})
|
|
690
|
+
| None => Js.Exn.raiseError(`Chain ${chainName} is missing rpc endpoint in config`)
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
{
|
|
695
|
+
name: chainName,
|
|
696
|
+
id: chainId,
|
|
697
|
+
startBlock: publicChainConfig["startBlock"],
|
|
698
|
+
endBlock: ?publicChainConfig["endBlock"],
|
|
699
|
+
maxReorgDepth: switch ecosystemName {
|
|
700
|
+
| Ecosystem.Evm => publicChainConfig["maxReorgDepth"]->Option.getWithDefault(200)
|
|
701
|
+
// Fuel doesn't have reorgs, SVM reorg handling is not supported
|
|
702
|
+
| Ecosystem.Fuel | Ecosystem.Svm => 0
|
|
703
|
+
},
|
|
704
|
+
blockLag: publicChainConfig["blockLag"]->Option.getWithDefault(0),
|
|
705
|
+
contracts,
|
|
706
|
+
sourceConfig,
|
|
707
|
+
}
|
|
708
|
+
})
|
|
709
|
+
|
|
75
710
|
let chainMap =
|
|
76
711
|
chains
|
|
77
|
-
->Js.Array2.map(
|
|
78
|
-
(ChainMap.Chain.makeUnsafe(~chainId=
|
|
712
|
+
->Js.Array2.map(chain => {
|
|
713
|
+
(ChainMap.Chain.makeUnsafe(~chainId=chain.id), chain)
|
|
79
714
|
})
|
|
80
715
|
->ChainMap.fromArrayUnsafe
|
|
81
716
|
|
|
@@ -88,23 +723,88 @@ let make = (
|
|
|
88
723
|
})
|
|
89
724
|
})
|
|
90
725
|
|
|
91
|
-
let ecosystem =
|
|
726
|
+
let ecosystem = switch ecosystemName {
|
|
727
|
+
| Ecosystem.Evm => Evm.ecosystem
|
|
728
|
+
| Ecosystem.Fuel => Fuel.ecosystem
|
|
729
|
+
| Ecosystem.Svm => Svm.ecosystem
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// Parse enums and entities from JSON config
|
|
733
|
+
let allEnums =
|
|
734
|
+
publicConfig["enums"]
|
|
735
|
+
->Option.getWithDefault(Js.Dict.empty())
|
|
736
|
+
->parseEnumsFromJson
|
|
737
|
+
|
|
738
|
+
let enumConfigsByName =
|
|
739
|
+
allEnums
|
|
740
|
+
->Js.Array2.map(enumConfig => (enumConfig.name, enumConfig))
|
|
741
|
+
->Js.Dict.fromArray
|
|
742
|
+
|
|
743
|
+
let userEntities =
|
|
744
|
+
publicConfig["entities"]
|
|
745
|
+
->Option.getWithDefault([])
|
|
746
|
+
->parseEntitiesFromJson(~enumConfigsByName)
|
|
747
|
+
|
|
748
|
+
let allEntities = userEntities->Js.Array2.concat([DynamicContractRegistry.entityConfig])
|
|
749
|
+
|
|
750
|
+
let userEntitiesByName =
|
|
751
|
+
userEntities
|
|
752
|
+
->Js.Array2.map(entityConfig => {
|
|
753
|
+
(entityConfig.name, entityConfig)
|
|
754
|
+
})
|
|
755
|
+
->Js.Dict.fromArray
|
|
756
|
+
|
|
757
|
+
// Extract contract handlers from the public config
|
|
758
|
+
let contractHandlers = switch publicContractsConfig {
|
|
759
|
+
| Some(contractsDict) =>
|
|
760
|
+
contractsDict
|
|
761
|
+
->Js.Dict.entries
|
|
762
|
+
->Js.Array2.map(((contractName, contractConfig)) => {
|
|
763
|
+
{
|
|
764
|
+
name: contractName->Utils.String.capitalize,
|
|
765
|
+
handler: contractConfig["handler"],
|
|
766
|
+
}
|
|
767
|
+
})
|
|
768
|
+
| None => []
|
|
769
|
+
}
|
|
92
770
|
|
|
93
771
|
{
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
772
|
+
name: publicConfig["name"],
|
|
773
|
+
description: publicConfig["description"],
|
|
774
|
+
handlers: publicConfig["handlers"]->Option.getWithDefault("src/handlers"),
|
|
775
|
+
contractHandlers,
|
|
776
|
+
shouldRollbackOnReorg: publicConfig["rollbackOnReorg"]->Option.getWithDefault(true),
|
|
777
|
+
shouldSaveFullHistory: publicConfig["saveFullHistory"]->Option.getWithDefault(false),
|
|
778
|
+
multichain: publicConfig["multichain"]->Option.getWithDefault(Unordered),
|
|
97
779
|
chainMap,
|
|
98
780
|
defaultChain: chains->Array.get(0),
|
|
99
|
-
enableRawEvents,
|
|
781
|
+
enableRawEvents: publicConfig["rawEvents"]->Option.getWithDefault(false),
|
|
100
782
|
ecosystem,
|
|
101
783
|
maxAddrInPartition,
|
|
102
|
-
batchSize,
|
|
784
|
+
batchSize: publicConfig["fullBatchSize"]->Option.getWithDefault(5000),
|
|
103
785
|
lowercaseAddresses,
|
|
104
786
|
addContractNameToContractNameMapping,
|
|
787
|
+
userEntitiesByName,
|
|
788
|
+
userEntities,
|
|
789
|
+
allEntities,
|
|
790
|
+
allEnums,
|
|
105
791
|
}
|
|
106
792
|
}
|
|
107
793
|
|
|
794
|
+
let getEventConfig = (config: t, ~contractName, ~eventName) => {
|
|
795
|
+
config.chainMap
|
|
796
|
+
->ChainMap.values
|
|
797
|
+
->Js.Array2.reduce((acc, chain) => {
|
|
798
|
+
switch acc {
|
|
799
|
+
| Some(_) => acc
|
|
800
|
+
| None =>
|
|
801
|
+
chain.contracts
|
|
802
|
+
->Js.Array2.find(c => c.name == contractName)
|
|
803
|
+
->Belt.Option.flatMap(contract => contract.events->Js.Array2.find(e => e.name == eventName))
|
|
804
|
+
}
|
|
805
|
+
}, None)
|
|
806
|
+
}
|
|
807
|
+
|
|
108
808
|
let shouldSaveHistory = (config, ~isInReorgThreshold) =>
|
|
109
809
|
config.shouldSaveFullHistory || (config.shouldRollbackOnReorg && isInReorgThreshold)
|
|
110
810
|
|