envio 3.3.0-alpha.1 → 3.3.0-alpha.11

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.
Files changed (143) hide show
  1. package/evm.schema.json +10 -0
  2. package/index.d.ts +45 -25
  3. package/licenses/CLA.md +35 -0
  4. package/licenses/EULA.md +67 -0
  5. package/licenses/LICENSE.md +45 -0
  6. package/licenses/README.md +35 -0
  7. package/package.json +10 -9
  8. package/src/Address.res +5 -2
  9. package/src/Address.res.mjs +3 -1
  10. package/src/Api.res +1 -1
  11. package/src/Api.res.mjs +1 -1
  12. package/src/Batch.res +0 -6
  13. package/src/Batch.res.mjs +1 -12
  14. package/src/BatchProcessing.res +7 -12
  15. package/src/BatchProcessing.res.mjs +8 -7
  16. package/src/Bin.res +3 -0
  17. package/src/Bin.res.mjs +4 -0
  18. package/src/ChainFetching.res +53 -16
  19. package/src/ChainFetching.res.mjs +37 -19
  20. package/src/ChainMetadata.res +1 -11
  21. package/src/ChainMetadata.res.mjs +1 -10
  22. package/src/ChainState.res +630 -161
  23. package/src/ChainState.res.mjs +438 -101
  24. package/src/ChainState.resi +77 -4
  25. package/src/Config.res +70 -38
  26. package/src/Config.res.mjs +52 -12
  27. package/src/ContractRegisterContext.res +2 -12
  28. package/src/ContractRegisterContext.res.mjs +3 -5
  29. package/src/Core.res +34 -3
  30. package/src/Core.res.mjs +12 -0
  31. package/src/CrossChainState.res +130 -89
  32. package/src/CrossChainState.res.mjs +66 -61
  33. package/src/CrossChainState.resi +1 -1
  34. package/src/Ecosystem.res +5 -6
  35. package/src/Ecosystem.res.mjs +3 -3
  36. package/src/Envio.res +17 -11
  37. package/src/EnvioGlobal.res +53 -0
  38. package/src/EnvioGlobal.res.mjs +31 -0
  39. package/src/EventConfigBuilder.res +191 -127
  40. package/src/EventConfigBuilder.res.mjs +116 -66
  41. package/src/EventProcessing.res +77 -28
  42. package/src/EventProcessing.res.mjs +56 -29
  43. package/src/ExitOnCaughtUp.res +10 -2
  44. package/src/ExitOnCaughtUp.res.mjs +10 -1
  45. package/src/FetchState.res +684 -415
  46. package/src/FetchState.res.mjs +505 -486
  47. package/src/HandlerLoader.res +8 -104
  48. package/src/HandlerLoader.res.mjs +2 -81
  49. package/src/HandlerRegister.res +520 -144
  50. package/src/HandlerRegister.res.mjs +286 -133
  51. package/src/HandlerRegister.resi +24 -8
  52. package/src/IndexerState.res +10 -3
  53. package/src/IndexerState.res.mjs +11 -4
  54. package/src/IndexerState.resi +2 -1
  55. package/src/IndexingAddresses.res +108 -0
  56. package/src/IndexingAddresses.res.mjs +101 -0
  57. package/src/IndexingAddresses.resi +34 -0
  58. package/src/Internal.res +166 -48
  59. package/src/Internal.res.mjs +30 -2
  60. package/src/LoadLayer.res +5 -5
  61. package/src/LoadLayer.res.mjs +6 -6
  62. package/src/LogSelection.res +92 -217
  63. package/src/LogSelection.res.mjs +95 -184
  64. package/src/Main.res +71 -236
  65. package/src/Main.res.mjs +60 -145
  66. package/src/Metrics.res +74 -0
  67. package/src/Metrics.res.mjs +75 -0
  68. package/src/PgStorage.res +4 -4
  69. package/src/PgStorage.res.mjs +5 -5
  70. package/src/Prometheus.res +10 -63
  71. package/src/Prometheus.res.mjs +124 -171
  72. package/src/PruneStaleHistory.res +2 -2
  73. package/src/PruneStaleHistory.res.mjs +3 -3
  74. package/src/RawEvent.res +7 -2
  75. package/src/RawEvent.res.mjs +4 -4
  76. package/src/Rollback.res +3 -3
  77. package/src/Rollback.res.mjs +4 -4
  78. package/src/RollbackCommit.res +4 -1
  79. package/src/RollbackCommit.res.mjs +3 -2
  80. package/src/SimulateDeadInputTracker.res +85 -0
  81. package/src/SimulateDeadInputTracker.res.mjs +73 -0
  82. package/src/SimulateDeadInputTracker.resi +12 -0
  83. package/src/SimulateItems.res +128 -28
  84. package/src/SimulateItems.res.mjs +82 -40
  85. package/src/TestIndexer.res +55 -41
  86. package/src/TestIndexer.res.mjs +39 -32
  87. package/src/TopicFilter.res +1 -25
  88. package/src/TopicFilter.res.mjs +0 -74
  89. package/src/bindings/Performance.res +7 -0
  90. package/src/bindings/Performance.res.mjs +21 -0
  91. package/src/bindings/Performance.resi +7 -0
  92. package/src/bindings/Viem.res +0 -46
  93. package/src/bindings/Viem.res.mjs +1 -43
  94. package/src/sources/BlockStore.res +46 -0
  95. package/src/sources/BlockStore.res.mjs +24 -0
  96. package/src/sources/EventRouter.res +31 -49
  97. package/src/sources/EventRouter.res.mjs +12 -25
  98. package/src/sources/Evm.res +83 -72
  99. package/src/sources/Evm.res.mjs +53 -69
  100. package/src/sources/EvmChain.res +7 -34
  101. package/src/sources/EvmChain.res.mjs +6 -27
  102. package/src/sources/EvmRpcClient.res +71 -5
  103. package/src/sources/EvmRpcClient.res.mjs +13 -4
  104. package/src/sources/FieldMask.res +39 -0
  105. package/src/sources/FieldMask.res.mjs +42 -0
  106. package/src/sources/Fuel.res +9 -6
  107. package/src/sources/Fuel.res.mjs +5 -10
  108. package/src/sources/HyperFuelSource.res +46 -50
  109. package/src/sources/HyperFuelSource.res.mjs +64 -54
  110. package/src/sources/HyperSync.res +45 -42
  111. package/src/sources/HyperSync.res.mjs +68 -60
  112. package/src/sources/HyperSync.resi +14 -4
  113. package/src/sources/HyperSyncClient.res +107 -119
  114. package/src/sources/HyperSyncClient.res.mjs +36 -31
  115. package/src/sources/HyperSyncHeightStream.res +1 -8
  116. package/src/sources/HyperSyncHeightStream.res.mjs +0 -2
  117. package/src/sources/HyperSyncSource.res +60 -254
  118. package/src/sources/HyperSyncSource.res.mjs +57 -180
  119. package/src/sources/Rpc.res +15 -79
  120. package/src/sources/Rpc.res.mjs +25 -101
  121. package/src/sources/RpcSource.res +221 -514
  122. package/src/sources/RpcSource.res.mjs +216 -411
  123. package/src/sources/RpcWebSocketHeightStream.res +0 -5
  124. package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -2
  125. package/src/sources/SimulateSource.res +44 -20
  126. package/src/sources/SimulateSource.res.mjs +41 -14
  127. package/src/sources/Source.res +34 -6
  128. package/src/sources/SourceManager.res +77 -19
  129. package/src/sources/SourceManager.res.mjs +63 -21
  130. package/src/sources/SourceManager.resi +10 -0
  131. package/src/sources/Svm.res +31 -16
  132. package/src/sources/Svm.res.mjs +36 -13
  133. package/src/sources/SvmHyperSyncClient.res +5 -32
  134. package/src/sources/SvmHyperSyncSource.res +159 -134
  135. package/src/sources/SvmHyperSyncSource.res.mjs +164 -129
  136. package/src/sources/TransactionStore.res +49 -0
  137. package/src/sources/TransactionStore.res.mjs +30 -0
  138. package/src/tui/Tui.res +13 -16
  139. package/src/tui/Tui.res.mjs +12 -14
  140. package/svm.schema.json +49 -37
  141. package/src/bindings/Hrtime.res +0 -58
  142. package/src/bindings/Hrtime.res.mjs +0 -90
  143. package/src/bindings/Hrtime.resi +0 -30
@@ -8,6 +8,8 @@ let configAddresses: Config.chain => array<Internal.indexingAddress>
8
8
  let make: (
9
9
  ~chainConfig: Config.chain,
10
10
  ~fetchState: FetchState.t,
11
+ ~onEventRegistrations: array<Internal.onEventRegistration>=?,
12
+ ~indexingAddresses: IndexingAddresses.t,
11
13
  ~sourceManager: SourceManager.t,
12
14
  ~reorgDetection: ReorgDetection.t,
13
15
  ~committedProgressBlockNumber: int,
@@ -15,13 +17,16 @@ let make: (
15
17
  ~numEventsProcessed: float=?,
16
18
  ~timestampCaughtUpToHeadOrEndblock: option<Date.t>=?,
17
19
  ~isProgressAtHead: bool=?,
20
+ ~transactionStore: TransactionStore.t=?,
21
+ ~chainDensity: option<float>=?,
22
+ ~blockStore: BlockStore.t=?,
18
23
  ~logger: Pino.t,
19
24
  ) => t
20
25
 
21
26
  let makeFromConfig: (
22
27
  Config.chain,
23
28
  ~config: Config.t,
24
- ~registrations: HandlerRegister.registrations,
29
+ ~registrationsByChainId: HandlerRegister.registrationsByChainId,
25
30
  ~knownHeight: int,
26
31
  ) => t
27
32
 
@@ -32,13 +37,12 @@ let makeFromDbState: (
32
37
  ~isInReorgThreshold: bool,
33
38
  ~isRealtime: bool,
34
39
  ~config: Config.t,
35
- ~registrations: HandlerRegister.registrations,
40
+ ~registrationsByChainId: HandlerRegister.registrationsByChainId,
36
41
  ~reducedPollingInterval: int=?,
37
42
  ) => t
38
43
 
39
44
  // Accessors.
40
45
  let logger: t => Pino.t
41
- let fetchState: t => FetchState.t
42
46
  let sourceManager: t => SourceManager.t
43
47
  let chainConfig: t => Config.chain
44
48
  let reorgDetection: t => ReorgDetection.t
@@ -50,12 +54,68 @@ let pendingBudget: t => float
50
54
  let startFetchingQueries: (t, ~queries: array<FetchState.query>) => unit
51
55
  let timestampCaughtUpToHeadOrEndblock: t => option<Date.t>
52
56
 
57
+ // Fetch-frontier reads.
58
+ let knownHeight: t => int
59
+ let contractAddresses: (t, ~contractName: string) => array<Address.t>
60
+ let bufferSize: t => int
61
+ let bufferReadyCount: t => int
62
+ let getProgressPercentage: t => float
63
+ let chainDensity: t => option<float>
64
+ let effectiveDensity: t => option<float>
65
+ let hasReadyItem: t => bool
66
+ let isReadyToEnterReorgThreshold: t => bool
67
+
68
+ // Fetch control.
69
+ let targetBlock: (t, ~chainTargetItems: float) => int
70
+ let progressAtBlock: (t, ~blockNumber: int) => float
71
+ let blockAtProgress: (t, ~progress: float) => int
72
+ let getNextQuery: (
73
+ t,
74
+ ~chainTargetItems: float,
75
+ ~chunkItemsMultiplier: float=?,
76
+ ~maxTargetBlock: int=?,
77
+ ) => FetchState.nextQuery
78
+ let dispatch: (
79
+ t,
80
+ ~executeQuery: FetchState.query => promise<unit>,
81
+ ~waitForNewBlock: (~knownHeight: int) => promise<int>,
82
+ ~onNewBlock: (~knownHeight: int) => unit,
83
+ ~action: FetchState.nextQuery,
84
+ ~stateId: int,
85
+ ) => promise<unit>
86
+
87
+ // Views.
88
+ type chainData = {
89
+ chainId: float,
90
+ poweredByHyperSync: bool,
91
+ firstEventBlockNumber: option<int>,
92
+ latestProcessedBlock: option<int>,
93
+ timestampCaughtUpToHeadOrEndblock: option<Date.t>,
94
+ numEventsProcessed: float,
95
+ latestFetchedBlockNumber: int,
96
+ @as("currentBlockHeight")
97
+ knownHeight: int,
98
+ numBatchesFetched: int,
99
+ startBlock: int,
100
+ endBlock: option<int>,
101
+ numAddresses: int,
102
+ }
103
+ let toChainData: t => chainData
104
+ let toChainMetadata: t => InternalTable.Chains.metaFields
105
+ let toChainBeforeBatch: t => Batch.chainBeforeBatch
106
+ let isReadyToEnterReorgThresholdAfterBatch: (t, ~batch: Batch.t) => bool
107
+
53
108
  // Derived (pure).
54
109
  let hasProcessedToEndblock: t => bool
55
110
  let getHighestBlockBelowThreshold: t => int
56
111
  let isActivelyIndexing: t => bool
57
112
  let isReady: t => bool
58
113
  let isFetchingAtHead: t => bool
114
+ let isAtHeadWithoutEndBlock: t => bool
115
+
116
+ // Drop over-fetched events an address-param filter rejects, before contract
117
+ // registration runs on them. Uses the chain's current indexing addresses.
118
+ let filterByClientAddress: (t, array<Internal.item>) => array<Internal.item>
59
119
 
60
120
  // State transitions. The chain state is mutated only through these.
61
121
  let handleQueryResult: (
@@ -65,7 +125,20 @@ let handleQueryResult: (
65
125
  ~newItemsWithDcs: array<Internal.item>,
66
126
  ~latestFetchedBlock: FetchState.blockNumberAndTimestamp,
67
127
  ~knownHeight: int,
128
+ ~transactionStore: option<TransactionStore.t>,
129
+ ~blockStore: option<BlockStore.t>,
68
130
  ) => unit
131
+ let materializeBatchItems: (
132
+ t,
133
+ ~items: array<Internal.item>,
134
+ ~ecosystem: Ecosystem.name,
135
+ ) => promise<unit>
136
+ let materializePageItems: (
137
+ ~items: array<Internal.item>,
138
+ ~transactionStore: option<TransactionStore.t>,
139
+ ~blockStore: option<BlockStore.t>,
140
+ ~ecosystem: Ecosystem.name,
141
+ ) => promise<unit>
69
142
  let registerReorgGuard: (
70
143
  t,
71
144
  ~blockHashes: array<ReorgDetection.blockData>,
@@ -76,7 +149,7 @@ let updateKnownHeight: (t, ~knownHeight: int) => unit
76
149
  let setEndBlockToFirstEvent: (t, ~blockNumber: int) => unit
77
150
  let enterReorgThreshold: t => unit
78
151
  let advanceAfterBatch: (t, ~batch: Batch.t, ~enteringReorgThreshold: bool) => unit
79
- let applyBatchProgress: (t, ~batch: Batch.t) => unit
152
+ let applyBatchProgress: (t, ~batch: Batch.t, ~blockTimestampName: string) => unit
80
153
  let markReady: t => unit
81
154
  let rollback: (
82
155
  t,
package/src/Config.res CHANGED
@@ -23,6 +23,7 @@ type evmRpcConfig = {
23
23
  sourceFor: Source.sourceFor,
24
24
  syncConfig: option<sourceSyncOptions>,
25
25
  ws: option<string>,
26
+ headers: option<dict<string>>,
26
27
  }
27
28
 
28
29
  type sourceConfig =
@@ -156,6 +157,7 @@ let rpcConfigSchema = S.schema(s =>
156
157
  "url": s.matches(S.string),
157
158
  "for": s.matches(rpcSourceForSchema),
158
159
  "ws": s.matches(S.option(S.string)),
160
+ "headers": s.matches(S.option(S.dict(S.string))),
159
161
  "initialBlockInterval": s.matches(S.option(S.int)),
160
162
  "backoffMultiplicative": s.matches(S.option(S.float)),
161
163
  "accelerationAdditive": s.matches(S.option(S.int)),
@@ -195,17 +197,21 @@ let svmEventDescriptorSchema = S.schema(s =>
195
197
  {
196
198
  "discriminator": s.matches(S.option(S.string)),
197
199
  "discriminatorByteLen": s.matches(S.int),
198
- "includeTransaction": s.matches(S.bool),
200
+ "transactionFields": s.matches(S.array(Internal.svmTransactionFieldSchema)),
201
+ "blockFields": s.matches(S.option(S.array(Internal.svmBlockFieldSchema))),
199
202
  "includeLogs": s.matches(S.bool),
200
- "includeTokenBalances": s.matches(S.bool),
203
+ // An array of AND-groups OR-ed together: the CLI normalizes both the flat
204
+ // and `any_of` YAML shapes to `Vec<Vec<SvmAccountFilterJson>>`.
201
205
  "accountFilters": s.matches(
202
206
  S.option(
203
207
  S.array(
204
- S.schema(s =>
205
- {
206
- "position": s.matches(S.int),
207
- "values": s.matches(S.array(S.string)),
208
- }
208
+ S.array(
209
+ S.schema(s =>
210
+ {
211
+ "position": s.matches(S.int),
212
+ "values": s.matches(S.array(S.string)),
213
+ }
214
+ ),
209
215
  ),
210
216
  ),
211
217
  ),
@@ -653,7 +659,6 @@ let fromPublic = (publicConfigJson: JSON.t) => {
653
659
  ~events: option<array<_>>,
654
660
  ~abi,
655
661
  ~chainId: int,
656
- ~startBlock: option<int>,
657
662
  ~addresses: array<string>,
658
663
  ~svmDefinedTypes: JSON.t=JSON.Null,
659
664
  ) => {
@@ -676,10 +681,6 @@ let fromPublic = (publicConfigJson: JSON.t) => {
676
681
  ~kind=fuelKind,
677
682
  ~sighash,
678
683
  ~rawAbi=abi->(Utils.magic: EvmTypes.Abi.t => JSON.t),
679
- ~isWildcard=false,
680
- ~handler=None,
681
- ~contractRegister=None,
682
- ~startBlock?,
683
684
  ) :> Internal.eventConfig)
684
685
  | None =>
685
686
  JsError.throwWithMessage(
@@ -704,9 +705,9 @@ let fromPublic = (publicConfigJson: JSON.t) => {
704
705
  "svm": option<{
705
706
  "discriminator": option<string>,
706
707
  "discriminatorByteLen": int,
707
- "includeTransaction": bool,
708
+ "transactionFields": array<Internal.svmTransactionField>,
709
+ "blockFields": option<array<Internal.svmBlockField>>,
708
710
  "includeLogs": bool,
709
- "includeTokenBalances": bool,
710
711
  "accountFilters": option<
711
712
  array<array<{"position": int, "values": array<string>}>>,
712
713
  >,
@@ -740,18 +741,14 @@ let fromPublic = (publicConfigJson: JSON.t) => {
740
741
  ~programId,
741
742
  ~discriminator=svm["discriminator"],
742
743
  ~discriminatorByteLen=svm["discriminatorByteLen"],
743
- ~includeTransaction=svm["includeTransaction"],
744
+ ~transactionFields=svm["transactionFields"],
745
+ ~blockFields=?svm["blockFields"],
744
746
  ~includeLogs=svm["includeLogs"],
745
- ~includeTokenBalances=svm["includeTokenBalances"],
746
747
  ~accountFilters,
747
748
  ~isInner=svm["isInner"],
748
- ~isWildcard=false,
749
- ~handler=None,
750
- ~contractRegister=None,
751
749
  ~accounts=svm["accounts"]->Option.getOr([]),
752
750
  ~args=svm["args"]->Option.getOr(JSON.Null),
753
751
  ~definedTypes=svmDefinedTypes,
754
- ~startBlock?,
755
752
  ) :> Internal.eventConfig)
756
753
  | _ =>
757
754
  (EventConfigBuilder.buildEvmEventConfig(
@@ -759,15 +756,8 @@ let fromPublic = (publicConfigJson: JSON.t) => {
759
756
  ~eventName,
760
757
  ~sighash,
761
758
  ~params,
762
- ~isWildcard=false,
763
- ~handler=None,
764
- ~contractRegister=None,
765
- ~eventFilters=None,
766
- ~probeChainId=chainId,
767
- ~onEventBlockFilterSchema=ecosystem.onEventBlockFilterSchema,
768
759
  ~blockFields=?eventItem["blockFields"],
769
760
  ~transactionFields=?eventItem["transactionFields"],
770
- ~startBlock?,
771
761
  ~globalBlockFieldsSet,
772
762
  ~globalTransactionFieldsSet,
773
763
  ) :> Internal.eventConfig)
@@ -819,17 +809,14 @@ let fromPublic = (publicConfigJson: JSON.t) => {
819
809
  let addresses = rawAddresses->Array.map(parseAddress)
820
810
  let startBlock = chainContract->Option.flatMap(cc => cc["startBlock"])
821
811
 
822
- // Build event configs from JSON (field selections resolved inline)
823
- // chainId is threaded in so the where-callback detection probe
824
- // exercises the callback with this chain's real id — handlers
825
- // that branch on `chain.id` are taken through the same path
826
- // they will follow at runtime.
812
+ // Build event definitions from JSON (field selections resolved
813
+ // inline). Handlers and per-chain `where` filters are layered on
814
+ // later as `onEventRegistration`s in `ChainState.makeInternal`.
827
815
  let events = buildContractEvents(
828
816
  ~contractName=capitalizedName,
829
817
  ~events=contractData["events"],
830
818
  ~abi=contractData["abi"],
831
819
  ~chainId,
832
- ~startBlock,
833
820
  ~addresses=rawAddresses,
834
821
  ~svmDefinedTypes=contractData["svmAbi"]
835
822
  ->Option.map(a => a["definedTypes"])
@@ -910,6 +897,7 @@ let fromPublic = (publicConfigJson: JSON.t) => {
910
897
  sourceFor: parseRpcSourceFor(rpcConfig["for"]),
911
898
  syncConfig,
912
899
  ws: rpcConfig["ws"],
900
+ headers: rpcConfig["headers"],
913
901
  }
914
902
  })
915
903
  EvmSourceConfig({hypersync: publicChainConfig["hypersync"], rpcs})
@@ -1018,6 +1006,50 @@ let fromPublic = (publicConfigJson: JSON.t) => {
1018
1006
  }
1019
1007
  }
1020
1008
 
1009
+ // Canonicalize a user-provided address to the configured casing so it matches
1010
+ // addresses parsed from config.yaml during routing. HyperSync/RPC data arrives
1011
+ // already canonical; only user-land input (simulate srcAddress, contractRegister
1012
+ // add) can carry arbitrary casing and needs this before comparison.
1013
+ let normalizeUserAddress = (config: t, address: Address.t): Address.t =>
1014
+ switch config.ecosystem.name {
1015
+ | Ecosystem.Evm =>
1016
+ if config.lowercaseAddresses {
1017
+ address->Address.Evm.fromAddressLowercaseOrThrow
1018
+ } else {
1019
+ address->Address.Evm.fromAddressOrThrow
1020
+ }
1021
+ // TODO: Ideally we should do the same for other ecosystems
1022
+ | Ecosystem.Fuel | Ecosystem.Svm => address
1023
+ }
1024
+
1025
+ // Relaxed counterpart to normalizeUserAddress, used only for simulate srcAddress.
1026
+ // Tests commonly use short placeholder strings like "0xfoo" as a stand-in
1027
+ // identity (e.g. for a wildcard event whose srcAddress doesn't need to resolve
1028
+ // to any real contract), so only the "0x" prefix is enforced here. Under
1029
+ // address_format: checksum, a real address is checksummed even if the input
1030
+ // casing doesn't match (getAddress doesn't require the input to already be
1031
+ // checksummed) — a placeholder that isn't valid hex falls back unchanged.
1032
+ let normalizeSimulateAddress = (config: t, address: Address.t): Address.t =>
1033
+ switch config.ecosystem.name {
1034
+ | Ecosystem.Evm =>
1035
+ let str = address->Address.toString
1036
+ if !(str->String.startsWith("0x")) {
1037
+ JsError.throwWithMessage(
1038
+ `simulate: srcAddress "${str}" is invalid. Expected a string starting with "0x".`,
1039
+ )
1040
+ }
1041
+ if config.lowercaseAddresses {
1042
+ address->Address.Evm.fromAddressLowercaseOrThrow
1043
+ } else {
1044
+ try {
1045
+ address->Address.Evm.fromAddressOrThrow
1046
+ } catch {
1047
+ | _ => address
1048
+ }
1049
+ }
1050
+ | Ecosystem.Fuel | Ecosystem.Svm => address
1051
+ }
1052
+
1021
1053
  // Look up an event config by (contract, event) name. When `chainId` is given,
1022
1054
  // returns that chain's per-chain event config (matters for where-callback
1023
1055
  // probe detection, which runs with the chain's real id). Without `chainId`,
@@ -1259,11 +1291,11 @@ let throwIfIncompatible = (
1259
1291
  }
1260
1292
  }
1261
1293
 
1262
- // The returned value is a pure function of the JSON no handler
1263
- // registrations are applied here. Post-registration configs come from
1264
- // `HandlerLoader.applyRegistrations`. That purity is what lets this
1265
- // memoize without invalidation.
1266
- let loadWithoutRegistrations = () =>
1294
+ // The returned value is a pure function of the JSON: it holds only event
1295
+ // definitions, never handler/contractRegister/where state (that's layered on
1296
+ // separately as `HandlerRegister.registrationsByChainId`). That purity is
1297
+ // what lets this memoize without invalidation.
1298
+ let load = () =>
1267
1299
  switch cached.contents {
1268
1300
  | Some(c) => c
1269
1301
  | None => {
@@ -81,6 +81,7 @@ let rpcConfigSchema = S$RescriptSchema.schema(s => ({
81
81
  url: s.m(S$RescriptSchema.string),
82
82
  for: s.m(rpcSourceForSchema),
83
83
  ws: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
84
+ headers: s.m(S$RescriptSchema.option(S$RescriptSchema.dict(S$RescriptSchema.string))),
84
85
  initialBlockInterval: s.m(S$RescriptSchema.option(S$RescriptSchema.int)),
85
86
  backoffMultiplicative: s.m(S$RescriptSchema.option(S$RescriptSchema.float)),
86
87
  accelerationAdditive: s.m(S$RescriptSchema.option(S$RescriptSchema.int)),
@@ -111,13 +112,13 @@ let publicConfigChainSchema = S$RescriptSchema.schema(s => ({
111
112
  let svmEventDescriptorSchema = S$RescriptSchema.schema(s => ({
112
113
  discriminator: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
113
114
  discriminatorByteLen: s.m(S$RescriptSchema.int),
114
- includeTransaction: s.m(S$RescriptSchema.bool),
115
+ transactionFields: s.m(S$RescriptSchema.array(Internal.svmTransactionFieldSchema)),
116
+ blockFields: s.m(S$RescriptSchema.option(S$RescriptSchema.array(Internal.svmBlockFieldSchema))),
115
117
  includeLogs: s.m(S$RescriptSchema.bool),
116
- includeTokenBalances: s.m(S$RescriptSchema.bool),
117
- accountFilters: s.m(S$RescriptSchema.option(S$RescriptSchema.array(S$RescriptSchema.schema(s => ({
118
+ accountFilters: s.m(S$RescriptSchema.option(S$RescriptSchema.array(S$RescriptSchema.array(S$RescriptSchema.schema(s => ({
118
119
  position: s.m(S$RescriptSchema.int),
119
120
  values: s.m(S$RescriptSchema.array(S$RescriptSchema.string))
120
- }))))),
121
+ })))))),
121
122
  isInner: s.m(S$RescriptSchema.option(S$RescriptSchema.bool)),
122
123
  accounts: s.m(S$RescriptSchema.option(S$RescriptSchema.array(S$RescriptSchema.string))),
123
124
  args: s.m(S$RescriptSchema.option(S$RescriptSchema.json(false)))
@@ -490,7 +491,7 @@ function fromPublic(publicConfigJson) {
490
491
  };
491
492
  });
492
493
  }
493
- let buildContractEvents = (contractName, events, abi, chainId, startBlock, addresses, svmDefinedTypesOpt) => {
494
+ let buildContractEvents = (contractName, events, abi, chainId, addresses, svmDefinedTypesOpt) => {
494
495
  let svmDefinedTypes = svmDefinedTypesOpt !== undefined ? svmDefinedTypesOpt : null;
495
496
  if (events !== undefined) {
496
497
  return events.map(eventItem => {
@@ -500,10 +501,10 @@ function fromPublic(publicConfigJson) {
500
501
  let kind = eventItem.kind;
501
502
  switch (ecosystemName) {
502
503
  case "evm" :
503
- return EventConfigBuilder.buildEvmEventConfig(contractName, eventName, sighash, params, false, undefined, undefined, undefined, chainId, ecosystem.onEventBlockFilterSchema, eventItem.blockFields, eventItem.transactionFields, startBlock, Primitive_option.some(globalBlockFieldsSet), Primitive_option.some(globalTransactionFieldsSet));
504
+ return EventConfigBuilder.buildEvmEventConfig(contractName, eventName, sighash, params, eventItem.blockFields, eventItem.transactionFields, Primitive_option.some(globalBlockFieldsSet), Primitive_option.some(globalTransactionFieldsSet));
504
505
  case "fuel" :
505
506
  if (kind !== undefined) {
506
- return EventConfigBuilder.buildFuelEventConfig(contractName, eventName, kind, sighash, abi, false, undefined, undefined, startBlock);
507
+ return EventConfigBuilder.buildFuelEventConfig(contractName, eventName, kind, sighash, abi);
507
508
  } else {
508
509
  return Stdlib_JsError.throwWithMessage(`Fuel event ` + contractName + `.` + eventName + ` is missing "kind" in internal config`);
509
510
  }
@@ -518,7 +519,7 @@ function fromPublic(publicConfigJson) {
518
519
  position: af.position,
519
520
  values: af.values
520
521
  })));
521
- return EventConfigBuilder.buildSvmInstructionEventConfig(contractName, eventName, programId, svm.discriminator, svm.discriminatorByteLen, svm.includeTransaction, svm.includeLogs, svm.includeTokenBalances, accountFilters, svm.isInner, false, undefined, undefined, Stdlib_Option.getOr(svm.accounts, []), Stdlib_Option.getOr(svm.args, null), svmDefinedTypes, startBlock);
522
+ return EventConfigBuilder.buildSvmInstructionEventConfig(contractName, eventName, programId, svm.discriminator, svm.discriminatorByteLen, svm.includeLogs, svm.transactionFields, svm.blockFields, accountFilters, svm.isInner, Stdlib_Option.getOr(svm.accounts, []), Stdlib_Option.getOr(svm.args, null), svmDefinedTypes);
522
523
  }
523
524
  });
524
525
  } else {
@@ -559,7 +560,7 @@ function fromPublic(publicConfigJson) {
559
560
  let rawAddresses = Stdlib_Option.getOr(Stdlib_Option.flatMap(chainContract, cc => cc.addresses), []);
560
561
  let addresses = rawAddresses.map(parseAddress);
561
562
  let startBlock = Stdlib_Option.flatMap(chainContract, cc => cc.startBlock);
562
- let events = buildContractEvents(capitalizedName, contractData.events, contractData.abi, chainId, startBlock, rawAddresses, Stdlib_Option.getOr(Stdlib_Option.map(contractData.svmAbi, a => a.definedTypes), null));
563
+ let events = buildContractEvents(capitalizedName, contractData.events, contractData.abi, chainId, rawAddresses, Stdlib_Option.getOr(Stdlib_Option.map(contractData.svmAbi, a => a.definedTypes), null));
563
564
  return {
564
565
  name: capitalizedName,
565
566
  abi: contractData.abi,
@@ -607,7 +608,8 @@ function fromPublic(publicConfigJson) {
607
608
  url: rpcConfig.url,
608
609
  sourceFor: parseRpcSourceFor(rpcConfig.for),
609
610
  syncConfig: syncConfig,
610
- ws: rpcConfig.ws
611
+ ws: rpcConfig.ws,
612
+ headers: rpcConfig.headers
611
613
  };
612
614
  });
613
615
  sourceConfig = {
@@ -705,6 +707,42 @@ function fromPublic(publicConfigJson) {
705
707
  };
706
708
  }
707
709
 
710
+ function normalizeUserAddress(config, address) {
711
+ let match = config.ecosystem.name;
712
+ switch (match) {
713
+ case "evm" :
714
+ if (config.lowercaseAddresses) {
715
+ return Address.Evm.fromAddressLowercaseOrThrow(address);
716
+ } else {
717
+ return Address.Evm.fromAddressOrThrow(address);
718
+ }
719
+ case "fuel" :
720
+ case "svm" :
721
+ return address;
722
+ }
723
+ }
724
+
725
+ function normalizeSimulateAddress(config, address) {
726
+ let match = config.ecosystem.name;
727
+ switch (match) {
728
+ case "evm" :
729
+ if (!address.startsWith("0x")) {
730
+ Stdlib_JsError.throwWithMessage(`simulate: srcAddress "` + address + `" is invalid. Expected a string starting with "0x".`);
731
+ }
732
+ if (config.lowercaseAddresses) {
733
+ return Address.Evm.fromAddressLowercaseOrThrow(address);
734
+ }
735
+ try {
736
+ return Address.Evm.fromAddressOrThrow(address);
737
+ } catch (exn) {
738
+ return address;
739
+ }
740
+ case "fuel" :
741
+ case "svm" :
742
+ return address;
743
+ }
744
+ }
745
+
708
746
  function getEventConfig(config, contractName, eventName, chainId) {
709
747
  let chains;
710
748
  if (chainId !== undefined) {
@@ -974,7 +1012,7 @@ function throwIfIncompatible(changedPaths, resetCommand, runCommand, hasClickhou
974
1012
  Stdlib_JsError.throwWithMessage(`The following config changes are incompatible with the existing indexer data:\n\n` + bullets + `\n\nPick one:\n 1. ` + padTo(option1, col) + `# resume indexing where it left off\n 2. ` + padTo(resetCommand, col) + `# delete all indexed data and start over` + option3);
975
1013
  }
976
1014
 
977
- function loadWithoutRegistrations() {
1015
+ function load() {
978
1016
  let c = cached.contents;
979
1017
  if (c !== undefined) {
980
1018
  return c;
@@ -1011,6 +1049,8 @@ export {
1011
1049
  publicConfigStorageSchema,
1012
1050
  publicConfigSchema,
1013
1051
  fromPublic,
1052
+ normalizeUserAddress,
1053
+ normalizeSimulateAddress,
1014
1054
  getEventConfig,
1015
1055
  shouldSaveHistory,
1016
1056
  shouldPruneHistory,
@@ -1021,7 +1061,7 @@ export {
1021
1061
  canonicalJson,
1022
1062
  diffPaths,
1023
1063
  throwIfIncompatible,
1024
- loadWithoutRegistrations,
1064
+ load,
1025
1065
  getPgUserEntities,
1026
1066
  }
1027
1067
  /* schema Not a pure module */
@@ -22,18 +22,8 @@ let makeAddFunction = (~params: contractRegisterParams, ~contractName: string):
22
22
  ~logger=Ecosystem.getItemLogger(params.item, ~ecosystem=params.config.ecosystem),
23
23
  )
24
24
  }
25
- let validatedAddress = if params.config.ecosystem.name === Evm {
26
- // The value is passed from the user-land,
27
- // so we need to validate and checksum/lowercase the address.
28
- if params.config.lowercaseAddresses {
29
- contractAddress->Address.Evm.fromAddressLowercaseOrThrow
30
- } else {
31
- contractAddress->Address.Evm.fromAddressOrThrow
32
- }
33
- } else {
34
- // TODO: Ideally we should do the same for other ecosystems
35
- contractAddress
36
- }
25
+ // The value is passed from user-land, so validate and checksum/lowercase it.
26
+ let validatedAddress = params.config->Config.normalizeUserAddress(contractAddress)
37
27
 
38
28
  params.onRegister(~item=params.item, ~contractAddress=validatedAddress, ~contractName)
39
29
  }
@@ -1,6 +1,6 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
- import * as Address from "./Address.res.mjs";
3
+ import * as Config from "./Config.res.mjs";
4
4
  import * as ChainMap from "./ChainMap.res.mjs";
5
5
  import * as Ecosystem from "./Ecosystem.res.mjs";
6
6
  import * as EntityFilter from "./db/EntityFilter.res.mjs";
@@ -12,9 +12,7 @@ function makeAddFunction(params, contractName) {
12
12
  if (params.isResolved) {
13
13
  ErrorHandling.mkLogAndRaise(Ecosystem.getItemLogger(params.item, params.config.ecosystem), undefined, new Error(`Impossible to access context.chain after the contract register is resolved. Make sure you didn't miss an await in the handler.`));
14
14
  }
15
- let validatedAddress = params.config.ecosystem.name === "evm" ? (
16
- params.config.lowercaseAddresses ? Address.Evm.fromAddressLowercaseOrThrow(contractAddress) : Address.Evm.fromAddressOrThrow(contractAddress)
17
- ) : contractAddress;
15
+ let validatedAddress = Config.normalizeUserAddress(params.config, contractAddress);
18
16
  params.onRegister(params.item, validatedAddress, contractName);
19
17
  };
20
18
  }
@@ -73,4 +71,4 @@ export {
73
71
  getContractRegisterContext,
74
72
  getContractRegisterArgs,
75
73
  }
76
- /* Address Not a pure module */
74
+ /* Config Not a pure module */
package/src/Core.res CHANGED
@@ -6,23 +6,42 @@
6
6
  // tighter `Null.t` captures the exact boundary shape.
7
7
  type evmHypersyncClientCtor
8
8
  type evmRpcClientCtor
9
- type evmDecoderCtor
10
9
  type svmHypersyncClientCtor
11
10
  type hyperfuelClientCtor
11
+ type transactionStoreCtor
12
+ type blockStoreCtor
13
+ type parseConfigYamlOptions = {
14
+ schema?: string,
15
+ env?: dict<string>,
16
+ files?: dict<string>,
17
+ isRescript?: bool,
18
+ }
12
19
 
13
20
  type addon = {
14
21
  getConfigJson: (~configPath: Null.t<string>, ~directory: Null.t<string>) => string,
22
+ encodeIndexedTopic: (~abiType: string, ~value: unknown) => EvmTypes.Hex.t,
23
+ parseConfigYaml: (string, parseConfigYamlOptions) => string,
15
24
  runCli: (~args: array<string>, ~envioPackageDir: Null.t<string>) => promise<Null.t<string>>,
16
25
  @as("EvmHypersyncClient")
17
26
  evmHypersyncClient: evmHypersyncClientCtor,
18
27
  @as("EvmRpcClient")
19
28
  evmRpcClient: evmRpcClientCtor,
20
- @as("EvmDecoder")
21
- evmDecoder: evmDecoderCtor,
22
29
  @as("SvmHypersyncClient")
23
30
  svmHypersyncClient: svmHypersyncClientCtor,
24
31
  @as("HyperfuelClient")
25
32
  hyperfuelClient: hyperfuelClientCtor,
33
+ @as("TransactionStore")
34
+ transactionStore: transactionStoreCtor,
35
+ @as("BlockStore")
36
+ blockStore: blockStoreCtor,
37
+ // Ordered transaction-field names exposed for the field-code contract test
38
+ // (the ReScript `transactionFields` arrays must match the Rust ordinals).
39
+ evmTransactionFieldNames: unit => array<string>,
40
+ svmTransactionFieldNames: unit => array<string>,
41
+ // Ordered block-field names for the same contract test (`blockFields` arrays
42
+ // must match the Rust ordinals).
43
+ evmBlockFieldNames: unit => array<string>,
44
+ svmBlockFieldNames: unit => array<string>,
26
45
  }
27
46
 
28
47
  @module("node:module") external createRequire: string => {..} = "createRequire"
@@ -186,6 +205,18 @@ let getConfigJson = (~configPath=?, ~directory=?) => {
186
205
  )
187
206
  }
188
207
 
208
+ // Pure config entry point: no cwd, config file, schema file, .env, or process env lookup.
209
+ // A supplied schema is authoritative; omitted or blank schema text means an empty schema.
210
+ let parseConfigYaml = (~schema=?, ~env=?, ~files=?, ~isRescript=false, yaml) => {
211
+ let addon = getAddon()
212
+ addon.parseConfigYaml(yaml, {
213
+ ?schema,
214
+ ?env,
215
+ ?files,
216
+ isRescript,
217
+ })
218
+ }
219
+
189
220
  let runCli = args => {
190
221
  let addon = getAddon()
191
222
  addon.runCli(~args, ~envioPackageDir=Null.make(envioPackageDir))
package/src/Core.res.mjs CHANGED
@@ -180,6 +180,17 @@ function getConfigJson(configPath, directory) {
180
180
  return addon.getConfigJson(Stdlib_Null.fromOption(configPath), Stdlib_Null.fromOption(directory));
181
181
  }
182
182
 
183
+ function parseConfigYaml(schema, env, files, isRescriptOpt, yaml) {
184
+ let isRescript = isRescriptOpt !== undefined ? isRescriptOpt : false;
185
+ let addon = getAddon();
186
+ return addon.parseConfigYaml(yaml, {
187
+ schema: schema,
188
+ env: env,
189
+ files: files,
190
+ isRescript: isRescript
191
+ });
192
+ }
193
+
183
194
  function runCli(args) {
184
195
  let addon = getAddon();
185
196
  return addon.runCli(args, envioPackageDir);
@@ -196,6 +207,7 @@ export {
196
207
  addonRef,
197
208
  getAddon,
198
209
  getConfigJson,
210
+ parseConfigYaml,
199
211
  runCli,
200
212
  }
201
213
  /* envioPackageDir Not a pure module */