envio 3.0.0-alpha.21 → 3.0.0-alpha.23

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 (220) hide show
  1. package/README.md +3 -3
  2. package/bin.mjs +2 -48
  3. package/evm.schema.json +67 -0
  4. package/fuel.schema.json +67 -0
  5. package/index.d.ts +822 -38
  6. package/index.js +5 -3
  7. package/package.json +10 -8
  8. package/rescript.json +5 -9
  9. package/src/Address.res +4 -5
  10. package/src/Address.res.mjs +9 -12
  11. package/src/Api.res +15 -0
  12. package/src/Api.res.mjs +20 -0
  13. package/src/Batch.res +32 -34
  14. package/src/Batch.res.mjs +172 -187
  15. package/src/Bin.res +89 -0
  16. package/src/Bin.res.mjs +97 -0
  17. package/src/ChainFetcher.res +33 -57
  18. package/src/ChainFetcher.res.mjs +197 -227
  19. package/src/ChainManager.res +6 -14
  20. package/src/ChainManager.res.mjs +74 -85
  21. package/src/ChainMap.res +14 -16
  22. package/src/ChainMap.res.mjs +38 -38
  23. package/src/Config.res +193 -135
  24. package/src/Config.res.mjs +566 -592
  25. package/src/Core.res +182 -0
  26. package/src/Core.res.mjs +207 -0
  27. package/src/Ecosystem.res +25 -4
  28. package/src/Ecosystem.res.mjs +12 -13
  29. package/src/Env.res +20 -13
  30. package/src/Env.res.mjs +124 -113
  31. package/src/EnvSafe.res +269 -0
  32. package/src/EnvSafe.res.mjs +296 -0
  33. package/src/EnvSafe.resi +18 -0
  34. package/src/Envio.res +37 -26
  35. package/src/Envio.res.mjs +59 -60
  36. package/src/ErrorHandling.res +2 -2
  37. package/src/ErrorHandling.res.mjs +15 -15
  38. package/src/EventConfigBuilder.res +219 -81
  39. package/src/EventConfigBuilder.res.mjs +259 -202
  40. package/src/EventProcessing.res +27 -38
  41. package/src/EventProcessing.res.mjs +165 -183
  42. package/src/EventUtils.res +11 -11
  43. package/src/EventUtils.res.mjs +21 -22
  44. package/src/EvmTypes.res +0 -1
  45. package/src/EvmTypes.res.mjs +5 -5
  46. package/src/FetchState.res +360 -256
  47. package/src/FetchState.res.mjs +958 -914
  48. package/src/GlobalState.res +365 -351
  49. package/src/GlobalState.res.mjs +958 -992
  50. package/src/GlobalStateManager.res +1 -2
  51. package/src/GlobalStateManager.res.mjs +36 -44
  52. package/src/HandlerLoader.res +107 -23
  53. package/src/HandlerLoader.res.mjs +128 -38
  54. package/src/HandlerRegister.res +127 -103
  55. package/src/HandlerRegister.res.mjs +164 -164
  56. package/src/HandlerRegister.resi +12 -4
  57. package/src/Hasura.res +35 -22
  58. package/src/Hasura.res.mjs +158 -167
  59. package/src/InMemoryStore.res +20 -27
  60. package/src/InMemoryStore.res.mjs +64 -80
  61. package/src/InMemoryTable.res +34 -39
  62. package/src/InMemoryTable.res.mjs +165 -170
  63. package/src/Internal.res +52 -33
  64. package/src/Internal.res.mjs +84 -81
  65. package/src/LazyLoader.res.mjs +55 -61
  66. package/src/LoadLayer.res +77 -78
  67. package/src/LoadLayer.res.mjs +160 -189
  68. package/src/LoadManager.res +16 -21
  69. package/src/LoadManager.res.mjs +79 -84
  70. package/src/LogSelection.res +236 -68
  71. package/src/LogSelection.res.mjs +211 -141
  72. package/src/Logging.res +13 -9
  73. package/src/Logging.res.mjs +130 -143
  74. package/src/Main.res +430 -51
  75. package/src/Main.res.mjs +530 -271
  76. package/src/Persistence.res +80 -84
  77. package/src/Persistence.res.mjs +131 -132
  78. package/src/PgStorage.res +294 -167
  79. package/src/PgStorage.res.mjs +799 -817
  80. package/src/Prometheus.res +50 -58
  81. package/src/Prometheus.res.mjs +345 -373
  82. package/src/ReorgDetection.res +22 -24
  83. package/src/ReorgDetection.res.mjs +100 -106
  84. package/src/SafeCheckpointTracking.res +7 -7
  85. package/src/SafeCheckpointTracking.res.mjs +40 -43
  86. package/src/SimulateItems.res +41 -49
  87. package/src/SimulateItems.res.mjs +257 -272
  88. package/src/Sink.res +2 -2
  89. package/src/Sink.res.mjs +22 -26
  90. package/src/TableIndices.res +1 -2
  91. package/src/TableIndices.res.mjs +42 -48
  92. package/src/TestIndexer.res +196 -189
  93. package/src/TestIndexer.res.mjs +536 -536
  94. package/src/TestIndexerProxyStorage.res +16 -16
  95. package/src/TestIndexerProxyStorage.res.mjs +99 -122
  96. package/src/TestIndexerWorker.res +4 -0
  97. package/src/TestIndexerWorker.res.mjs +7 -0
  98. package/src/Throttler.res +3 -3
  99. package/src/Throttler.res.mjs +23 -24
  100. package/src/Time.res +1 -1
  101. package/src/Time.res.mjs +18 -21
  102. package/src/TopicFilter.res +3 -3
  103. package/src/TopicFilter.res.mjs +29 -30
  104. package/src/UserContext.res +93 -54
  105. package/src/UserContext.res.mjs +197 -182
  106. package/src/Utils.res +141 -86
  107. package/src/Utils.res.mjs +334 -295
  108. package/src/bindings/BigDecimal.res +0 -2
  109. package/src/bindings/BigDecimal.res.mjs +19 -23
  110. package/src/bindings/ClickHouse.res +28 -27
  111. package/src/bindings/ClickHouse.res.mjs +243 -240
  112. package/src/bindings/DateFns.res +11 -11
  113. package/src/bindings/DateFns.res.mjs +7 -7
  114. package/src/bindings/EventSource.res.mjs +2 -2
  115. package/src/bindings/Express.res +2 -5
  116. package/src/bindings/Hrtime.res +2 -2
  117. package/src/bindings/Hrtime.res.mjs +30 -32
  118. package/src/bindings/Lodash.res.mjs +1 -1
  119. package/src/bindings/NodeJs.res +14 -9
  120. package/src/bindings/NodeJs.res.mjs +20 -20
  121. package/src/bindings/Pino.res +8 -10
  122. package/src/bindings/Pino.res.mjs +40 -43
  123. package/src/bindings/Postgres.res +7 -5
  124. package/src/bindings/Postgres.res.mjs +9 -9
  125. package/src/bindings/PromClient.res +17 -2
  126. package/src/bindings/PromClient.res.mjs +30 -7
  127. package/src/bindings/SDSL.res.mjs +2 -2
  128. package/src/bindings/Viem.res +4 -4
  129. package/src/bindings/Viem.res.mjs +20 -22
  130. package/src/bindings/Vitest.res +1 -1
  131. package/src/bindings/Vitest.res.mjs +2 -2
  132. package/src/bindings/WebSocket.res +1 -1
  133. package/src/db/EntityHistory.res +9 -3
  134. package/src/db/EntityHistory.res.mjs +84 -59
  135. package/src/db/InternalTable.res +62 -60
  136. package/src/db/InternalTable.res.mjs +271 -203
  137. package/src/db/Schema.res +1 -2
  138. package/src/db/Schema.res.mjs +28 -32
  139. package/src/db/Table.res +28 -27
  140. package/src/db/Table.res.mjs +276 -292
  141. package/src/sources/EventRouter.res +21 -16
  142. package/src/sources/EventRouter.res.mjs +55 -57
  143. package/src/sources/Evm.res +17 -1
  144. package/src/sources/Evm.res.mjs +16 -8
  145. package/src/sources/EvmChain.res +15 -17
  146. package/src/sources/EvmChain.res.mjs +40 -42
  147. package/src/sources/Fuel.res +14 -1
  148. package/src/sources/Fuel.res.mjs +16 -8
  149. package/src/sources/FuelSDK.res +1 -1
  150. package/src/sources/FuelSDK.res.mjs +6 -8
  151. package/src/sources/HyperFuel.res +8 -10
  152. package/src/sources/HyperFuel.res.mjs +113 -123
  153. package/src/sources/HyperFuelClient.res.mjs +6 -7
  154. package/src/sources/HyperFuelSource.res +19 -20
  155. package/src/sources/HyperFuelSource.res.mjs +339 -356
  156. package/src/sources/HyperSync.res +11 -13
  157. package/src/sources/HyperSync.res.mjs +206 -220
  158. package/src/sources/HyperSyncClient.res +5 -7
  159. package/src/sources/HyperSyncClient.res.mjs +70 -75
  160. package/src/sources/HyperSyncHeightStream.res +8 -9
  161. package/src/sources/HyperSyncHeightStream.res.mjs +78 -86
  162. package/src/sources/HyperSyncJsonApi.res +18 -15
  163. package/src/sources/HyperSyncJsonApi.res.mjs +201 -231
  164. package/src/sources/HyperSyncSource.res +17 -21
  165. package/src/sources/HyperSyncSource.res.mjs +268 -290
  166. package/src/sources/Rpc.res +5 -5
  167. package/src/sources/Rpc.res.mjs +168 -192
  168. package/src/sources/RpcSource.res +166 -167
  169. package/src/sources/RpcSource.res.mjs +972 -1046
  170. package/src/sources/RpcWebSocketHeightStream.res +10 -11
  171. package/src/sources/RpcWebSocketHeightStream.res.mjs +131 -145
  172. package/src/sources/SimulateSource.res +1 -1
  173. package/src/sources/SimulateSource.res.mjs +35 -38
  174. package/src/sources/Source.res +1 -1
  175. package/src/sources/Source.res.mjs +3 -3
  176. package/src/sources/SourceManager.res +39 -20
  177. package/src/sources/SourceManager.res.mjs +340 -371
  178. package/src/sources/SourceManager.resi +2 -1
  179. package/src/sources/Svm.res +12 -5
  180. package/src/sources/Svm.res.mjs +44 -41
  181. package/src/tui/Tui.res +23 -12
  182. package/src/tui/Tui.res.mjs +292 -290
  183. package/src/tui/bindings/Ink.res +2 -4
  184. package/src/tui/bindings/Ink.res.mjs +35 -41
  185. package/src/tui/components/BufferedProgressBar.res +7 -7
  186. package/src/tui/components/BufferedProgressBar.res.mjs +46 -46
  187. package/src/tui/components/CustomHooks.res +1 -2
  188. package/src/tui/components/CustomHooks.res.mjs +102 -122
  189. package/src/tui/components/Messages.res +1 -2
  190. package/src/tui/components/Messages.res.mjs +38 -42
  191. package/src/tui/components/SyncETA.res +10 -11
  192. package/src/tui/components/SyncETA.res.mjs +178 -196
  193. package/src/tui/components/TuiData.res +1 -1
  194. package/src/tui/components/TuiData.res.mjs +7 -6
  195. package/src/vendored/Rest.res +52 -66
  196. package/src/vendored/Rest.res.mjs +324 -364
  197. package/svm.schema.json +67 -0
  198. package/src/Address.gen.ts +0 -8
  199. package/src/Config.gen.ts +0 -19
  200. package/src/Envio.gen.ts +0 -55
  201. package/src/EvmTypes.gen.ts +0 -6
  202. package/src/InMemoryStore.gen.ts +0 -6
  203. package/src/Internal.gen.ts +0 -64
  204. package/src/PgStorage.gen.ts +0 -10
  205. package/src/PgStorage.res.d.mts +0 -5
  206. package/src/Types.ts +0 -56
  207. package/src/bindings/BigDecimal.gen.ts +0 -14
  208. package/src/bindings/BigDecimal.res.d.mts +0 -5
  209. package/src/bindings/BigInt.gen.ts +0 -10
  210. package/src/bindings/BigInt.res +0 -70
  211. package/src/bindings/BigInt.res.d.mts +0 -5
  212. package/src/bindings/BigInt.res.mjs +0 -154
  213. package/src/bindings/Ethers.res.d.mts +0 -5
  214. package/src/bindings/Pino.gen.ts +0 -17
  215. package/src/bindings/Postgres.gen.ts +0 -8
  216. package/src/bindings/Postgres.res.d.mts +0 -5
  217. package/src/bindings/Promise.res +0 -67
  218. package/src/bindings/Promise.res.mjs +0 -26
  219. package/src/db/InternalTable.gen.ts +0 -36
  220. package/src/sources/HyperSyncClient.gen.ts +0 -19
@@ -1,20 +1,27 @@
1
- open Belt
2
-
3
- type eventParam = {
4
- name: string,
5
- abiType: string,
6
- indexed: bool,
7
- }
8
-
9
- let eventParamSchema = S.object(s => {
1
+ // Types moved to `Internal` so `Internal.evmEventConfig` can retain
2
+ // `indexedParams: array<eventParam>` for post-registration filter rebuild.
3
+ // Re-exported here as aliases for existing call sites.
4
+ type eventParamComponent = Internal.eventParamComponent
5
+ type eventParam = Internal.eventParam
6
+
7
+ let eventParamComponentSchema = S.recursive(self =>
8
+ S.object((s): eventParamComponent => {
9
+ name: s.field("name", S.string),
10
+ abiType: s.field("abiType", S.string),
11
+ components: ?s.field("components", S.option(S.array(self))),
12
+ })
13
+ )
14
+
15
+ let eventParamSchema = S.object((s): eventParam => {
10
16
  name: s.field("name", S.string),
11
17
  abiType: s.field("abiType", S.string),
12
18
  indexed: s.fieldOr("indexed", S.bool, false),
19
+ components: ?s.field("components", S.option(S.array(eventParamComponentSchema))),
13
20
  })
14
21
 
15
22
  // Normalize a value that could be a single item or an array into an array
16
23
  let normalizeOrThrow: 'a => array<'a> = value => {
17
- if Js.Array2.isArray(value->Obj.magic) {
24
+ if Array.isArray(value->Obj.magic) {
18
25
  value->Obj.magic
19
26
  } else {
20
27
  [value]
@@ -29,22 +36,22 @@ let splitTupleComponents = (inner: string): array<string> => {
29
36
  let components = []
30
37
  let depth = ref(0)
31
38
  let start = ref(0)
32
- for i in 0 to inner->Js.String2.length - 1 {
33
- let ch = inner->Js.String2.charAt(i)
39
+ for i in 0 to inner->String.length - 1 {
40
+ let ch = inner->String.charAt(i)
34
41
  if ch == "(" {
35
42
  depth := depth.contents + 1
36
43
  } else if ch == ")" {
37
44
  depth := depth.contents - 1
38
45
  } else if ch == "," && depth.contents == 0 {
39
- components->Js.Array2.push(inner->Js.String2.slice(~from=start.contents, ~to_=i))->ignore
46
+ components->Array.push(inner->String.slice(~start=start.contents, ~end=i))->ignore
40
47
  start := i + 1
41
48
  }
42
49
  }
43
50
 
44
51
  // Last component
45
- if start.contents < inner->Js.String2.length {
52
+ if start.contents < inner->String.length {
46
53
  components
47
- ->Js.Array2.push(inner->Js.String2.sliceToEnd(~from=start.contents))
54
+ ->Array.push(inner->String.slice(~start=start.contents))
48
55
  ->ignore
49
56
  }
50
57
  components
@@ -54,53 +61,53 @@ let splitTupleComponents = (inner: string): array<string> => {
54
61
 
55
62
  let rec abiTypeToSchema = (abiType: string): S.t<unknown> => {
56
63
  // Handle array types: "type[]" or "type[N]"
57
- if abiType->Js.String2.endsWith("]") {
58
- let bracketIdx = abiType->Js.String2.lastIndexOf("[")
59
- let baseType = abiType->Js.String2.slice(~from=0, ~to_=bracketIdx)
64
+ if abiType->String.endsWith("]") {
65
+ let bracketIdx = abiType->String.lastIndexOf("[")
66
+ let baseType = abiType->String.slice(~start=0, ~end=bracketIdx)
60
67
  S.array(abiTypeToSchema(baseType))->S.toUnknown
61
- } else if abiType->Js.String2.startsWith("(") && abiType->Js.String2.endsWith(")") {
68
+ } else if abiType->String.startsWith("(") && abiType->String.endsWith(")") {
62
69
  // Tuple type: "(type1,type2,...)"
63
- let inner = abiType->Js.String2.slice(~from=1, ~to_=abiType->Js.String2.length - 1)
70
+ let inner = abiType->String.slice(~start=1, ~end=abiType->String.length - 1)
64
71
  let components = splitTupleComponents(inner)
65
- let schemas = components->Array.map(c => abiTypeToSchema(c->Js.String2.trim))
72
+ let schemas = components->Array.map(c => abiTypeToSchema(c->String.trim))
66
73
  S.tuple(s => {
67
- schemas->Array.mapWithIndex((i, schema) => s.item(i, schema))
74
+ schemas->Array.mapWithIndex((schema, i) => s.item(i, schema))
68
75
  })->S.toUnknown
69
76
  } else {
70
77
  switch abiType {
71
78
  | "address" => Address.schema->S.toUnknown
72
79
  | "bool" => S.bool->S.toUnknown
73
80
  | "string" | "bytes" => S.string->S.toUnknown
74
- | t if t->Js.String2.startsWith("uint") => BigInt.schema->S.toUnknown
75
- | t if t->Js.String2.startsWith("int") => BigInt.schema->S.toUnknown
76
- | t if t->Js.String2.startsWith("bytes") => S.string->S.toUnknown
77
- | other => Js.Exn.raiseError(`Unsupported ABI type: ${other}`)
81
+ | t if t->String.startsWith("uint") => Utils.BigInt.schema->S.toUnknown
82
+ | t if t->String.startsWith("int") => Utils.BigInt.schema->S.toUnknown
83
+ | t if t->String.startsWith("bytes") => S.string->S.toUnknown
84
+ | other => JsError.throwWithMessage(`Unsupported ABI type: ${other}`)
78
85
  }
79
86
  }
80
87
  }
81
88
 
82
89
  // ABI type → schema for simulate items (accepts native JS values, not string-encoded)
83
90
  let rec abiTypeToSimulateSchema = (abiType: string): S.t<unknown> => {
84
- if abiType->Js.String2.endsWith("]") {
85
- let bracketIdx = abiType->Js.String2.lastIndexOf("[")
86
- let baseType = abiType->Js.String2.slice(~from=0, ~to_=bracketIdx)
91
+ if abiType->String.endsWith("]") {
92
+ let bracketIdx = abiType->String.lastIndexOf("[")
93
+ let baseType = abiType->String.slice(~start=0, ~end=bracketIdx)
87
94
  S.array(abiTypeToSimulateSchema(baseType))->S.toUnknown
88
- } else if abiType->Js.String2.startsWith("(") && abiType->Js.String2.endsWith(")") {
89
- let inner = abiType->Js.String2.slice(~from=1, ~to_=abiType->Js.String2.length - 1)
95
+ } else if abiType->String.startsWith("(") && abiType->String.endsWith(")") {
96
+ let inner = abiType->String.slice(~start=1, ~end=abiType->String.length - 1)
90
97
  let components = splitTupleComponents(inner)
91
- let schemas = components->Array.map(c => abiTypeToSimulateSchema(c->Js.String2.trim))
98
+ let schemas = components->Array.map(c => abiTypeToSimulateSchema(c->String.trim))
92
99
  S.tuple(s => {
93
- schemas->Array.mapWithIndex((i, schema) => s.item(i, schema))
100
+ schemas->Array.mapWithIndex((schema, i) => s.item(i, schema))
94
101
  })->S.toUnknown
95
102
  } else {
96
103
  switch abiType {
97
104
  | "address" => S.string->S.toUnknown
98
105
  | "bool" => S.bool->S.toUnknown
99
106
  | "string" | "bytes" => S.string->S.toUnknown
100
- | t if t->Js.String2.startsWith("uint") => S.bigint->S.toUnknown
101
- | t if t->Js.String2.startsWith("int") => S.bigint->S.toUnknown
102
- | t if t->Js.String2.startsWith("bytes") => S.string->S.toUnknown
103
- | other => Js.Exn.raiseError(`Unsupported ABI type: ${other}`)
107
+ | t if t->String.startsWith("uint") => S.bigint->S.toUnknown
108
+ | t if t->String.startsWith("int") => S.bigint->S.toUnknown
109
+ | t if t->String.startsWith("bytes") => S.string->S.toUnknown
110
+ | other => JsError.throwWithMessage(`Unsupported ABI type: ${other}`)
104
111
  }
105
112
  }
106
113
  }
@@ -108,13 +115,13 @@ let rec abiTypeToSimulateSchema = (abiType: string): S.t<unknown> => {
108
115
  // ============== ABI type → default value for simulate ==============
109
116
 
110
117
  let rec abiTypeToDefaultValue = (abiType: string): unknown => {
111
- if abiType->Js.String2.endsWith("]") {
118
+ if abiType->String.endsWith("]") {
112
119
  []->(Utils.magic: array<unknown> => unknown)
113
- } else if abiType->Js.String2.startsWith("(") && abiType->Js.String2.endsWith(")") {
114
- let inner = abiType->Js.String2.slice(~from=1, ~to_=abiType->Js.String2.length - 1)
120
+ } else if abiType->String.startsWith("(") && abiType->String.endsWith(")") {
121
+ let inner = abiType->String.slice(~start=1, ~end=abiType->String.length - 1)
115
122
  let components = splitTupleComponents(inner)
116
123
  components
117
- ->Array.map(c => abiTypeToDefaultValue(c->Js.String2.trim))
124
+ ->Array.map(c => abiTypeToDefaultValue(c->String.trim))
118
125
  ->(Utils.magic: array<unknown> => unknown)
119
126
  } else {
120
127
  switch abiType {
@@ -125,14 +132,96 @@ let rec abiTypeToDefaultValue = (abiType: string): unknown => {
125
132
 
126
133
  | "bool" => false->(Utils.magic: bool => unknown)
127
134
  | "string" | "bytes" => ""->(Utils.magic: string => unknown)
128
- | t if t->Js.String2.startsWith("uint") => 0n->(Utils.magic: bigint => unknown)
129
- | t if t->Js.String2.startsWith("int") => 0n->(Utils.magic: bigint => unknown)
130
- | t if t->Js.String2.startsWith("bytes") => ""->(Utils.magic: string => unknown)
135
+ | t if t->String.startsWith("uint") => 0n->(Utils.magic: bigint => unknown)
136
+ | t if t->String.startsWith("int") => 0n->(Utils.magic: bigint => unknown)
137
+ | t if t->String.startsWith("bytes") => ""->(Utils.magic: string => unknown)
131
138
  | _ => %raw(`undefined`)->(Utils.magic: 'a => unknown)
132
139
  }
133
140
  }
134
141
  }
135
142
 
143
+ // ============== Named-tuple (struct) schema helpers ==============
144
+
145
+ // Build a simulate schema that honours component names: whenever an event param
146
+ // (or nested field) has components, we decode it as an object with named fields
147
+ // rather than a positional tuple. Walks through array wrappers so `struct[]`
148
+ // still produces `array<{...}>`.
149
+ let rec componentsToSimulateSchema = (abiType: string, components: array<eventParamComponent>): S.t<
150
+ unknown,
151
+ > => {
152
+ if abiType->String.endsWith("]") {
153
+ let bracketIdx = abiType->String.lastIndexOf("[")
154
+ let baseType = abiType->String.slice(~start=0, ~end=bracketIdx)
155
+ S.array(componentsToSimulateSchema(baseType, components))->S.toUnknown
156
+ } else {
157
+ // Must be a tuple at this level: build a record keyed by component names.
158
+ S.object(s => {
159
+ let dict = Dict.make()
160
+ components->Array.forEach(c => {
161
+ let childSchema = switch c.components {
162
+ | Some(sub) => componentsToSimulateSchema(c.abiType, sub)
163
+ | None => abiTypeToSimulateSchema(c.abiType)
164
+ }
165
+ dict->Dict.set(c.name, s.field(c.name, childSchema))
166
+ })
167
+ dict
168
+ })->S.toUnknown
169
+ }
170
+ }
171
+
172
+ // Default simulate value for a component tree — mirrors `abiTypeToDefaultValue`
173
+ // but emits objects with named fields for tuples.
174
+ let rec componentsToDefaultValue = (
175
+ abiType: string,
176
+ components: array<eventParamComponent>,
177
+ ): unknown => {
178
+ if abiType->String.endsWith("]") {
179
+ []->(Utils.magic: array<unknown> => unknown)
180
+ } else {
181
+ let dict = Dict.make()
182
+ components->Array.forEach(c => {
183
+ let v = switch c.components {
184
+ | Some(sub) => componentsToDefaultValue(c.abiType, sub)
185
+ | None => abiTypeToDefaultValue(c.abiType)
186
+ }
187
+ dict->Dict.set(c.name, v)
188
+ })
189
+ dict->(Utils.magic: dict<unknown> => unknown)
190
+ }
191
+ }
192
+
193
+ // Build a post-processor that converts the raw positional tuple values
194
+ // produced by the HyperSync decoder into objects with named fields. Walks
195
+ // through array wrappers so `struct[]` becomes `array<{...}>`. Returns `None`
196
+ // for leaf params where no remapping is needed.
197
+ let rec componentsToRemapper = (
198
+ abiType: string,
199
+ components: array<eventParamComponent>,
200
+ value: unknown,
201
+ ): unknown => {
202
+ if abiType->String.endsWith("]") {
203
+ let bracketIdx = abiType->String.lastIndexOf("[")
204
+ let baseType = abiType->String.slice(~start=0, ~end=bracketIdx)
205
+ let arr = value->(Utils.magic: unknown => array<unknown>)
206
+ arr
207
+ ->Array.map(item => componentsToRemapper(baseType, components, item))
208
+ ->(Utils.magic: array<unknown> => unknown)
209
+ } else {
210
+ // Must be a tuple at this level: build an object keyed by component names.
211
+ let arr = value->(Utils.magic: unknown => array<unknown>)
212
+ let dict = Dict.make()
213
+ components->Array.forEachWithIndex((c, i) => {
214
+ let raw = arr->Array.getUnsafe(i)
215
+ let mapped = switch c.components {
216
+ | Some(sub) => componentsToRemapper(c.abiType, sub, raw)
217
+ | None => raw
218
+ }
219
+ dict->Dict.set(c.name, mapped)
220
+ })
221
+ dict->(Utils.magic: dict<unknown> => unknown)
222
+ }
223
+ }
224
+
136
225
  // ============== Build paramsRawEventSchema ==============
137
226
 
138
227
  let buildParamsSchema = (params: array<eventParam>): S.t<Internal.eventParams> => {
@@ -142,9 +231,9 @@ let buildParamsSchema = (params: array<eventParam>): S.t<Internal.eventParams> =
142
231
  ->(Utils.magic: S.t<unit> => S.t<Internal.eventParams>)
143
232
  } else {
144
233
  S.object(s => {
145
- let dict = Js.Dict.empty()
234
+ let dict = Dict.make()
146
235
  params->Array.forEach(p => {
147
- dict->Js.Dict.set(p.name, s.field(p.name, abiTypeToSchema(p.abiType)))
236
+ dict->Dict.set(p.name, s.field(p.name, abiTypeToSchema(p.abiType)))
148
237
  })
149
238
  dict
150
239
  })->(Utils.magic: S.t<dict<unknown>> => S.t<Internal.eventParams>)
@@ -153,6 +242,8 @@ let buildParamsSchema = (params: array<eventParam>): S.t<Internal.eventParams> =
153
242
 
154
243
  // Build a lenient params schema for simulate items.
155
244
  // Uses S.schema + s.matches with S.null->S.Option.getOr to fill missing fields with defaults.
245
+ // When a param carries component metadata (Solidity struct), we accept and emit a
246
+ // record with named fields rather than a positional tuple.
156
247
  let buildSimulateParamsSchema = (params: array<eventParam>): S.t<Internal.eventParams> => {
157
248
  if params->Array.length == 0 {
158
249
  S.unknown
@@ -160,16 +251,16 @@ let buildSimulateParamsSchema = (params: array<eventParam>): S.t<Internal.eventP
160
251
  ->(Utils.magic: S.t<unit> => S.t<Internal.eventParams>)
161
252
  } else {
162
253
  S.schema(s => {
163
- let dict = Js.Dict.empty()
254
+ let dict = Dict.make()
164
255
  params->Array.forEach(p => {
165
- dict->Js.Dict.set(
166
- p.name,
167
- s.matches(
168
- S.null(abiTypeToSimulateSchema(p.abiType))->S.Option.getOr(
169
- abiTypeToDefaultValue(p.abiType),
170
- ),
171
- ),
172
- )
256
+ let (paramSchema, paramDefault) = switch p.components {
257
+ | Some(components) => (
258
+ componentsToSimulateSchema(p.abiType, components),
259
+ componentsToDefaultValue(p.abiType, components),
260
+ )
261
+ | None => (abiTypeToSimulateSchema(p.abiType), abiTypeToDefaultValue(p.abiType))
262
+ }
263
+ dict->Dict.set(p.name, s.matches(S.null(paramSchema)->S.Option.getOr(paramDefault)))
173
264
  })
174
265
  dict
175
266
  })->(Utils.magic: S.t<dict<unknown>> => S.t<Internal.eventParams>)
@@ -189,18 +280,18 @@ let buildHyperSyncDecoder = (params: array<eventParam>): (
189
280
  if params->Array.length == 0 {
190
281
  _ => ()->(Utils.magic: unit => Internal.eventParams)
191
282
  } else {
192
- let indexedParams = params->Js.Array2.filter(p => p.indexed)
193
- let bodyParams = params->Js.Array2.filter(p => !p.indexed)
283
+ let indexedParams = params->Array.filter(p => p.indexed)
284
+ let bodyParams = params->Array.filter(p => !p.indexed)
194
285
 
195
286
  let fields = []
196
- indexedParams->Array.forEachWithIndex((i, p) => {
197
- fields->Js.Array2.push(`"${p.name}": t(d.indexed[${i->Int.toString}])`)->ignore
287
+ indexedParams->Array.forEachWithIndex((p, i) => {
288
+ fields->Array.push(`"${p.name}": t(d.indexed[${i->Int.toString}])`)->ignore
198
289
  })
199
- bodyParams->Array.forEachWithIndex((i, p) => {
200
- fields->Js.Array2.push(`"${p.name}": t(d.body[${i->Int.toString}])`)->ignore
290
+ bodyParams->Array.forEachWithIndex((p, i) => {
291
+ fields->Array.push(`"${p.name}": t(d.body[${i->Int.toString}])`)->ignore
201
292
  })
202
293
  // Generate: function(t) { return function(d) { return { ... } } }
203
- let body = `return function(d) { return {${fields->Js.Array2.joinWith(", ")}} }`
294
+ let body = `return function(d) { return {${fields->Array.joinUnsafe(", ")}} }`
204
295
 
205
296
  let factory: (
206
297
  HyperSyncClient.Decoder.decodedRaw => HyperSyncClient.Decoder.decodedUnderlying
@@ -211,7 +302,33 @@ let buildHyperSyncDecoder = (params: array<eventParam>): (
211
302
  ) => HyperSyncClient.Decoder.decodedEvent => Internal.eventParams
212
303
  )
213
304
 
214
- factory(HyperSyncClient.Decoder.toUnderlying)
305
+ let baseDecode = factory(HyperSyncClient.Decoder.toUnderlying)
306
+
307
+ // For any param that has tuple component metadata, rewrite its value from a
308
+ // positional array into a named record post-decode. We pre-collect the
309
+ // params that need remapping so the hot path only walks those. Indexed
310
+ // struct/tuple params arrive as keccak256 topic hashes (single hex strings)
311
+ // rather than positional arrays, so they must be skipped here — running
312
+ // componentsToRemapper on a hash would treat the hex string as an array
313
+ // and read garbage.
314
+ let paramsToRemap = params->Array.filter(p => !p.indexed && p.components->Option.isSome)
315
+
316
+ if paramsToRemap->Array.length == 0 {
317
+ baseDecode
318
+ } else {
319
+ decoded => {
320
+ let result = baseDecode(decoded)
321
+ let dict = result->(Utils.magic: Internal.eventParams => dict<unknown>)
322
+ paramsToRemap->Array.forEach(p => {
323
+ switch (p.components, dict->Dict.get(p.name)) {
324
+ | (Some(components), Some(raw)) =>
325
+ dict->Dict.set(p.name, componentsToRemapper(p.abiType, components, raw))
326
+ | _ => ()
327
+ }
328
+ })
329
+ dict->(Utils.magic: dict<unknown> => Internal.eventParams)
330
+ }
331
+ }
215
332
  }
216
333
  }
217
334
 
@@ -219,7 +336,7 @@ let buildHyperSyncDecoder = (params: array<eventParam>): (
219
336
 
220
337
  let getTopicEncoder = (abiType: string): (unknown => EvmTypes.Hex.t) => {
221
338
  // Handle array/tuple types - these get keccak256'd
222
- if abiType->Js.String2.endsWith("]") || abiType->Js.String2.startsWith("(") {
339
+ if abiType->String.endsWith("]") || abiType->String.startsWith("(") {
223
340
  TopicFilter.castToHexUnsafe->(Utils.magic: ('a => EvmTypes.Hex.t) => unknown => EvmTypes.Hex.t)
224
341
  } else {
225
342
  switch abiType {
@@ -240,33 +357,30 @@ let getTopicEncoder = (abiType: string): (unknown => EvmTypes.Hex.t) => {
240
357
  Utils.magic: (string => EvmTypes.Hex.t) => unknown => EvmTypes.Hex.t
241
358
  )
242
359
 
243
- | t if t->Js.String2.startsWith("uint") =>
360
+ | t if t->String.startsWith("uint") =>
244
361
  TopicFilter.fromBigInt->(Utils.magic: (bigint => EvmTypes.Hex.t) => unknown => EvmTypes.Hex.t)
245
- | t if t->Js.String2.startsWith("int") =>
362
+ | t if t->String.startsWith("int") =>
246
363
  TopicFilter.fromSignedBigInt->(
247
364
  Utils.magic: (bigint => EvmTypes.Hex.t) => unknown => EvmTypes.Hex.t
248
365
  )
249
366
 
250
- | t if t->Js.String2.startsWith("bytes") =>
367
+ | t if t->String.startsWith("bytes") =>
251
368
  TopicFilter.castToHexUnsafe->(
252
369
  Utils.magic: ('a => EvmTypes.Hex.t) => unknown => EvmTypes.Hex.t
253
370
  )
254
371
 
255
- | other => Js.Exn.raiseError(`Unsupported topic filter ABI type: ${other}`)
372
+ | other => JsError.throwWithMessage(`Unsupported topic filter ABI type: ${other}`)
256
373
  }
257
374
  }
258
375
  }
259
376
 
260
377
  let buildTopicGetter = (p: eventParam) => {
261
378
  let encoder = getTopicEncoder(p.abiType)
262
- (eventFilter: Js.Dict.t<Js.Json.t>) =>
379
+ (eventFilter: dict<JSON.t>) =>
263
380
  eventFilter
264
381
  ->Utils.Dict.dangerouslyGetNonOption(p.name)
265
- ->Option.mapWithDefault([], topicFilters =>
266
- topicFilters
267
- ->(Utils.magic: Js.Json.t => unknown)
268
- ->normalizeOrThrow
269
- ->Js.Array2.map(encoder)
382
+ ->Option.mapOr([], topicFilters =>
383
+ topicFilters->(Utils.magic: JSON.t => unknown)->normalizeOrThrow->Array.map(encoder)
270
384
  )
271
385
  }
272
386
 
@@ -303,24 +417,43 @@ let buildEvmEventConfig = (
303
417
  ~isWildcard: bool,
304
418
  ~handler: option<Internal.handler>,
305
419
  ~contractRegister: option<Internal.contractRegister>,
306
- ~eventFilters: option<Js.Json.t>,
420
+ ~eventFilters: option<JSON.t>,
421
+ ~probeChainId: int,
422
+ ~onEventBlockFilterSchema: S.t<option<unknown>>,
307
423
  ~blockFields: option<array<Internal.evmBlockField>>=?,
308
424
  ~transactionFields: option<array<Internal.evmTransactionField>>=?,
425
+ ~startBlock: option<int>=?,
309
426
  ~globalBlockFieldsSet: Utils.Set.t<Internal.evmBlockField>=Utils.Set.make(),
310
427
  ~globalTransactionFieldsSet: Utils.Set.t<Internal.evmTransactionField>=Utils.Set.make(),
311
428
  ): Internal.evmEventConfig => {
312
429
  let topicCount = params->Array.reduce(1, (acc, p) => p.indexed ? acc + 1 : acc)
313
- let indexedParams = params->Js.Array2.filter(p => p.indexed)
430
+ let indexedParams = params->Array.filter(p => p.indexed)
314
431
 
315
- let {getEventFiltersOrThrow, filterByAddresses} = LogSelection.parseEventFiltersOrThrow(
432
+ let {
433
+ getEventFiltersOrThrow,
434
+ filterByAddresses,
435
+ startBlock: whereStartBlock,
436
+ } = LogSelection.parseEventFiltersOrThrow(
316
437
  ~eventFilters,
317
438
  ~sighash,
318
439
  ~params=indexedParams->Array.map(p => p.name),
440
+ ~contractName,
441
+ ~probeChainId,
442
+ ~onEventBlockFilterSchema,
319
443
  ~topic1=?indexedParams->Array.get(0)->Option.map(buildTopicGetter),
320
444
  ~topic2=?indexedParams->Array.get(1)->Option.map(buildTopicGetter),
321
445
  ~topic3=?indexedParams->Array.get(2)->Option.map(buildTopicGetter),
322
446
  )
323
447
 
448
+ // `where.block.number._gte` overrides the contract-level startBlock
449
+ // when present. The user opts into this explicitly in handler code so
450
+ // it should win over the `config.yaml` contract `start_block`; absent
451
+ // `where.block`, the caller's contract-level value passes through.
452
+ let resolvedStartBlock = switch whereStartBlock {
453
+ | Some(_) as sb => sb
454
+ | None => startBlock
455
+ }
456
+
324
457
  let (selectedBlockFields, selectedTransactionFields) = resolveFieldSelection(
325
458
  ~blockFields,
326
459
  ~transactionFields,
@@ -340,9 +473,12 @@ let buildEvmEventConfig = (
340
473
  getEventFiltersOrThrow,
341
474
  filterByAddresses,
342
475
  dependsOnAddresses: !isWildcard || filterByAddresses,
476
+ startBlock: resolvedStartBlock,
343
477
  convertHyperSyncEventArgs: buildHyperSyncDecoder(params),
344
478
  selectedBlockFields,
345
479
  selectedTransactionFields,
480
+ sighash,
481
+ indexedParams,
346
482
  }
347
483
  }
348
484
 
@@ -353,10 +489,11 @@ let buildFuelEventConfig = (
353
489
  ~eventName: string,
354
490
  ~kind: string,
355
491
  ~sighash: string,
356
- ~rawAbi: Js.Json.t,
492
+ ~rawAbi: JSON.t,
357
493
  ~isWildcard: bool,
358
494
  ~handler: option<Internal.handler>,
359
495
  ~contractRegister: option<Internal.contractRegister>,
496
+ ~startBlock: option<int>=?,
360
497
  ): Internal.fuelEventConfig => {
361
498
  let fuelKind = switch kind {
362
499
  | "logData" =>
@@ -370,7 +507,7 @@ let buildFuelEventConfig = (
370
507
  | "burn" => Burn
371
508
  | "transfer" => Transfer
372
509
  | "call" => Call
373
- | other => Js.Exn.raiseError(`Unsupported Fuel event kind: ${other}`)
510
+ | other => JsError.throwWithMessage(`Unsupported Fuel event kind: ${other}`)
374
511
  }
375
512
  let paramsSchema = switch kind {
376
513
  | "mint" | "burn" =>
@@ -386,8 +523,8 @@ let buildFuelEventConfig = (
386
523
  | "logData" =>
387
524
  S.json(~validate=false)
388
525
  ->Utils.Schema.coerceToJsonPgType
389
- ->(Utils.magic: S.t<Js.Json.t> => S.t<Internal.eventParams>)
390
- | other => Js.Exn.raiseError(`Unsupported Fuel event kind: ${other}`)
526
+ ->(Utils.magic: S.t<JSON.t> => S.t<Internal.eventParams>)
527
+ | other => JsError.throwWithMessage(`Unsupported Fuel event kind: ${other}`)
391
528
  }
392
529
  {
393
530
  id: switch kind {
@@ -403,6 +540,7 @@ let buildFuelEventConfig = (
403
540
  simulateParamsSchema: paramsSchema,
404
541
  filterByAddresses: false,
405
542
  dependsOnAddresses: !isWildcard,
543
+ startBlock,
406
544
  kind: fuelKind,
407
545
  }
408
546
  }