envio 3.0.2-svm-alpha.0 → 3.0.2

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 (43) hide show
  1. package/evm.schema.json +8 -8
  2. package/fuel.schema.json +12 -12
  3. package/index.d.ts +1 -155
  4. package/package.json +7 -6
  5. package/src/ChainFetcher.res +1 -25
  6. package/src/ChainFetcher.res.mjs +1 -19
  7. package/src/Config.res +94 -156
  8. package/src/Config.res.mjs +97 -60
  9. package/src/Core.res +0 -32
  10. package/src/Env.res.mjs +2 -1
  11. package/src/Envio.res +0 -94
  12. package/src/EventConfigBuilder.res +25 -63
  13. package/src/EventConfigBuilder.res.mjs +8 -37
  14. package/src/HandlerLoader.res +1 -12
  15. package/src/HandlerLoader.res.mjs +1 -6
  16. package/src/Internal.res +0 -38
  17. package/src/Main.res +3 -53
  18. package/src/Main.res.mjs +2 -34
  19. package/src/Persistence.res +17 -2
  20. package/src/Persistence.res.mjs +14 -2
  21. package/src/SimulateItems.res +10 -23
  22. package/src/SimulateItems.res.mjs +6 -21
  23. package/src/bindings/ClickHouse.res +6 -2
  24. package/src/bindings/ClickHouse.res.mjs +3 -2
  25. package/src/sources/EventRouter.res +0 -65
  26. package/src/sources/EventRouter.res.mjs +0 -43
  27. package/src/sources/HyperSyncClient.res +157 -30
  28. package/src/sources/HyperSyncClient.res.mjs +6 -20
  29. package/src/sources/HyperSyncSource.res +8 -5
  30. package/src/sources/HyperSyncSource.res.mjs +8 -1
  31. package/src/sources/RpcSource.res.mjs +1 -1
  32. package/src/sources/Svm.res +2 -2
  33. package/src/sources/Svm.res.mjs +2 -3
  34. package/src/tui/Tui.res +2 -9
  35. package/src/tui/Tui.res.mjs +4 -19
  36. package/src/tui/components/TuiData.res +0 -3
  37. package/svm.schema.json +4 -345
  38. package/src/SvmTypes.res +0 -9
  39. package/src/SvmTypes.res.mjs +0 -14
  40. package/src/sources/HyperSyncSolanaClient.res +0 -227
  41. package/src/sources/HyperSyncSolanaClient.res.mjs +0 -25
  42. package/src/sources/HyperSyncSolanaSource.res +0 -515
  43. package/src/sources/HyperSyncSolanaSource.res.mjs +0 -441
package/src/Config.res CHANGED
@@ -30,7 +30,7 @@ type evmRpcConfig = {
30
30
  type sourceConfig =
31
31
  | EvmSourceConfig({hypersync: option<string>, rpcs: array<evmRpcConfig>})
32
32
  | FuelSourceConfig({hypersync: string})
33
- | SvmSourceConfig({hypersync: option<string>, rpc: string})
33
+ | SvmSourceConfig({rpc: string})
34
34
  // For tests: pass custom sources directly
35
35
  | CustomSources(array<Source.t>)
36
36
 
@@ -201,38 +201,6 @@ let publicConfigChainSchema = S.schema(s =>
201
201
  }
202
202
  )
203
203
 
204
- let svmEventDescriptorSchema = S.schema(s =>
205
- {
206
- "discriminator": s.matches(S.option(S.string)),
207
- "discriminatorByteLen": s.matches(S.int),
208
- "includeTransaction": s.matches(S.bool),
209
- "includeLogs": s.matches(S.bool),
210
- "accountFilters": s.matches(
211
- S.option(
212
- S.array(
213
- S.schema(
214
- s => {
215
- "position": s.matches(S.int),
216
- "values": s.matches(S.array(S.string)),
217
- },
218
- ),
219
- ),
220
- ),
221
- ),
222
- "isInner": s.matches(S.option(S.bool)),
223
- "accounts": s.matches(S.option(S.array(S.string))),
224
- "args": s.matches(S.option(S.json(~validate=false))),
225
- }
226
- )
227
-
228
- let svmAbiSchema = S.schema(s =>
229
- {
230
- "programId": s.matches(S.string),
231
- "definedTypes": s.matches(S.json(~validate=false)),
232
- "source": s.matches(S.string),
233
- }
234
- )
235
-
236
204
  let contractEventItemSchema = S.schema(s =>
237
205
  {
238
206
  "event": s.matches(S.string),
@@ -242,7 +210,6 @@ let contractEventItemSchema = S.schema(s =>
242
210
  "kind": s.matches(S.option(S.string)),
243
211
  "blockFields": s.matches(S.option(S.array(Internal.evmBlockFieldSchema))),
244
212
  "transactionFields": s.matches(S.option(S.array(Internal.evmTransactionFieldSchema))),
245
- "svm": s.matches(S.option(svmEventDescriptorSchema)),
246
213
  }
247
214
  )
248
215
 
@@ -252,8 +219,6 @@ let contractConfigSchema = S.schema(s =>
252
219
  "handler": s.matches(S.option(S.string)),
253
220
  // EVM-specific: event signatures for HyperSync queries
254
221
  "events": s.matches(S.option(S.array(contractEventItemSchema))),
255
- // SVM-only: program-level Borsh schema (defined-types registry, source).
256
- "svmAbi": s.matches(S.option(svmAbiSchema)),
257
222
  }
258
223
  )
259
224
 
@@ -261,10 +226,6 @@ let publicConfigEcosystemSchema = S.schema(s =>
261
226
  {
262
227
  "chains": s.matches(S.dict(publicConfigChainSchema)),
263
228
  "contracts": s.matches(S.option(S.dict(contractConfigSchema))),
264
- // SVM-only alias: programs are the SVM analog of EVM/Fuel contracts.
265
- // Parsed via the same `contractConfigSchema` and read in `fromPublic`'s
266
- // `publicContractsConfig` switch.
267
- "programs": s.matches(S.option(S.dict(contractConfigSchema))),
268
229
  }
269
230
  )
270
231
 
@@ -562,19 +523,10 @@ let fromPublic = (publicConfigJson: JSON.t) => {
562
523
  | None => false
563
524
  }
564
525
 
565
- // Parse contract configs (ABIs, events, handlers).
566
- // SVM stores them under `svm.programs` in the public JSON — the per-program
567
- // events drive `indexer.onInstruction` registration the same way EVM/Fuel
568
- // contracts drive `onEvent`.
569
- let publicContractsConfig = switch (
570
- ecosystemName,
571
- publicConfig["evm"],
572
- publicConfig["fuel"],
573
- publicConfig["svm"],
574
- ) {
575
- | (Ecosystem.Evm, Some(evm), _, _) => evm["contracts"]
576
- | (Ecosystem.Fuel, _, Some(fuel), _) => fuel["contracts"]
577
- | (Ecosystem.Svm, _, _, Some(svm)) => svm["programs"]
526
+ // Parse contract configs (ABIs, events, handlers)
527
+ let publicContractsConfig = switch (ecosystemName, publicConfig["evm"], publicConfig["fuel"]) {
528
+ | (Ecosystem.Evm, Some(evm), _) => evm["contracts"]
529
+ | (Ecosystem.Fuel, _, Some(fuel)) => fuel["contracts"]
578
530
  | _ => None
579
531
  }
580
532
 
@@ -597,7 +549,6 @@ let fromPublic = (publicConfigJson: JSON.t) => {
597
549
  "abi": EvmTypes.Abi.t,
598
550
  "eventSignatures": array<string>,
599
551
  "events": option<array<_>>,
600
- "svmAbi": option<{"programId": string, "definedTypes": JSON.t, "source": string}>,
601
552
  }> = Dict.make()
602
553
  switch publicContractsConfig {
603
554
  | Some(contractsDict) =>
@@ -610,40 +561,21 @@ let fromPublic = (publicConfigJson: JSON.t) => {
610
561
  | Some(events) => events->Array.map(eventItem => eventItem["event"])
611
562
  | None => []
612
563
  }
613
- let widened =
614
- contractConfig->(
615
- Utils.magic: _ => {
616
- "svmAbi": option<{"programId": string, "definedTypes": JSON.t, "source": string}>,
617
- }
618
- )
619
564
  contractDataByName->Dict.set(
620
565
  capitalizedName,
621
- {
622
- "abi": abi,
623
- "eventSignatures": eventSignatures,
624
- "events": contractConfig["events"],
625
- "svmAbi": widened["svmAbi"],
626
- },
566
+ {"abi": abi, "eventSignatures": eventSignatures, "events": contractConfig["events"]},
627
567
  )
628
568
  })
629
569
  | None => ()
630
570
  }
631
571
 
632
- // Build event configs for a contract from JSON event items.
633
- //
634
- // `~addresses` is the chain-side address list. For SVM programs it's the
635
- // single base58 program_id — wired onto each instruction's event config so
636
- // the source can build `(programId, discriminator)`-keyed InstructionSelections.
637
- // EVM and Fuel ignore it (the address lives in `ChainContract.addresses` and
638
- // is looked up at dispatch time, not stamped on the event).
572
+ // Build event configs for a contract from JSON event items
639
573
  let buildContractEvents = (
640
574
  ~contractName,
641
575
  ~events: option<array<_>>,
642
576
  ~abi,
643
577
  ~chainId: int,
644
578
  ~startBlock: option<int>,
645
- ~addresses: array<string>,
646
- ~svmDefinedTypes: JSON.t=JSON.Null,
647
579
  ) => {
648
580
  switch events {
649
581
  | None => []
@@ -674,65 +606,6 @@ let fromPublic = (publicConfigJson: JSON.t) => {
674
606
  `Fuel event ${contractName}.${eventName} is missing "kind" in internal config`,
675
607
  )
676
608
  }
677
- | Ecosystem.Svm =>
678
- let programId = switch addresses {
679
- | [pid] => pid->SvmTypes.Pubkey.fromStringUnsafe
680
- | [] =>
681
- JsError.throwWithMessage(
682
- `SVM program ${contractName} on chain ${chainId->Int.toString} is missing a program_id`,
683
- )
684
- | _ =>
685
- JsError.throwWithMessage(
686
- `SVM program ${contractName} on chain ${chainId->Int.toString} has multiple addresses; a program is uniquely identified by a single program_id`,
687
- )
688
- }
689
- let widenedEventItem =
690
- eventItem->(
691
- Utils.magic: _ => {
692
- "svm": option<{
693
- "discriminator": option<string>,
694
- "discriminatorByteLen": int,
695
- "includeTransaction": bool,
696
- "includeLogs": bool,
697
- "accountFilters": option<
698
- array<{"position": int, "values": array<string>}>,
699
- >,
700
- "isInner": option<bool>,
701
- "accounts": option<array<string>>,
702
- "args": option<JSON.t>,
703
- }>,
704
- }
705
- )
706
- let svm = switch widenedEventItem["svm"] {
707
- | Some(s) => s
708
- | None =>
709
- JsError.throwWithMessage(
710
- `SVM instruction ${contractName}.${eventName} is missing the "svm" descriptor in internal config`,
711
- )
712
- }
713
- let accountFilters =
714
- (svm["accountFilters"]->Option.getOr([]))->Array.map(af => {
715
- Internal.position: af["position"],
716
- values: af["values"]->SvmTypes.Pubkey.fromStringsUnsafe,
717
- })
718
- (EventConfigBuilder.buildSvmInstructionEventConfig(
719
- ~contractName,
720
- ~instructionName=eventName,
721
- ~programId,
722
- ~discriminator=svm["discriminator"],
723
- ~discriminatorByteLen=svm["discriminatorByteLen"],
724
- ~includeTransaction=svm["includeTransaction"],
725
- ~includeLogs=svm["includeLogs"],
726
- ~accountFilters,
727
- ~isInner=svm["isInner"],
728
- ~isWildcard=false,
729
- ~handler=None,
730
- ~contractRegister=None,
731
- ~accounts=svm["accounts"]->Option.getOr([]),
732
- ~args=svm["args"]->Option.getOr(JSON.Null),
733
- ~definedTypes=svmDefinedTypes,
734
- ~startBlock?,
735
- ) :> Internal.eventConfig)
736
609
  | _ =>
737
610
  (EventConfigBuilder.buildEvmEventConfig(
738
611
  ~contractName,
@@ -792,11 +665,11 @@ let fromPublic = (publicConfigJson: JSON.t) => {
792
665
  ->Dict.toArray
793
666
  ->Array.map(((capitalizedName, contractData)) => {
794
667
  let chainContract = chainContracts->Dict.get(capitalizedName)
795
- let rawAddresses =
668
+ let addresses =
796
669
  chainContract
797
670
  ->Option.flatMap(cc => cc["addresses"])
798
671
  ->Option.getOr([])
799
- let addresses = rawAddresses->Array.map(parseAddress)
672
+ ->Array.map(parseAddress)
800
673
  let startBlock = chainContract->Option.flatMap(cc => cc["startBlock"])
801
674
 
802
675
  // Build event configs from JSON (field selections resolved inline)
@@ -810,10 +683,6 @@ let fromPublic = (publicConfigJson: JSON.t) => {
810
683
  ~abi=contractData["abi"],
811
684
  ~chainId,
812
685
  ~startBlock,
813
- ~addresses=rawAddresses,
814
- ~svmDefinedTypes=contractData["svmAbi"]
815
- ->Option.map(a => a["definedTypes"])
816
- ->Option.getOr(JSON.Null),
817
686
  )
818
687
 
819
688
  {
@@ -879,11 +748,7 @@ let fromPublic = (publicConfigJson: JSON.t) => {
879
748
  }
880
749
  | Ecosystem.Svm =>
881
750
  switch publicChainConfig["rpc"] {
882
- | Some(rpc) =>
883
- SvmSourceConfig({
884
- hypersync: publicChainConfig["hypersync"],
885
- rpc,
886
- })
751
+ | Some(rpc) => SvmSourceConfig({rpc: rpc})
887
752
  | None => JsError.throwWithMessage(`Chain ${chainName} is missing rpc endpoint in config`)
888
753
  }
889
754
  }
@@ -1092,11 +957,18 @@ let rec canonicalJson = (json: JSON.t): JSON.t =>
1092
957
  }
1093
958
 
1094
959
  // Returns dotted leaf paths (`a.b[i].c`) where `stored` differs from
1095
- // `current`. Skips subtrees whose canonical JSON matches.
960
+ // `current`, restricted to the highest-priority top-level tier with any
961
+ // diff. Tiers in order: version → name → storage → ecosystem
962
+ // (evm/fuel/svm) → entities → other top-level keys. The first tier
963
+ // containing a diff is the only one rendered; lower tiers are silenced
964
+ // so a single noisy section doesn't bury the actionable change.
1096
965
  let diffPaths = (~stored: JSON.t, ~current: JSON.t): array<string> => {
966
+ let canonEq = (a: JSON.t, b: JSON.t) =>
967
+ JSON.stringify(canonicalJson(a)) === JSON.stringify(canonicalJson(b))
968
+
1097
969
  let acc = []
1098
970
  let rec go = (s: JSON.t, c: JSON.t, prefix: string) => {
1099
- if JSON.stringify(canonicalJson(s)) === JSON.stringify(canonicalJson(c)) {
971
+ if canonEq(s, c) {
1100
972
  ()
1101
973
  } else {
1102
974
  switch (s, c) {
@@ -1122,25 +994,91 @@ let diffPaths = (~stored: JSON.t, ~current: JSON.t): array<string> => {
1122
994
  | (Some(sv), Some(cv)) => go(sv, cv, p)
1123
995
  }
1124
996
  }
1125
- | _ =>
1126
- // Type mismatch or scalar diff
1127
- acc->Array.push(prefix === "" ? "<root>" : prefix)->ignore
997
+ | _ => acc->Array.push(prefix === "" ? "<root>" : prefix)->ignore
1128
998
  }
1129
999
  }
1130
1000
  }
1131
- go(stored, current, "")
1001
+
1002
+ let getTopKey = (j: JSON.t, k: string) =>
1003
+ switch j {
1004
+ | Object(d) => d->Dict.get(k)
1005
+ | _ => None
1006
+ }
1007
+ let topKeyDiffers = (k: string) =>
1008
+ switch (getTopKey(stored, k), getTopKey(current, k)) {
1009
+ | (None, None) => false
1010
+ | (None, _) | (_, None) => true
1011
+ | (Some(s), Some(c)) => !canonEq(s, c)
1012
+ }
1013
+ let runTier = (keys: array<string>) =>
1014
+ keys->Array.forEach(k =>
1015
+ switch (getTopKey(stored, k), getTopKey(current, k)) {
1016
+ | (None, None) => ()
1017
+ | (None, _) | (_, None) => acc->Array.push(k)->ignore
1018
+ | (Some(s), Some(c)) => go(s, c, k)
1019
+ }
1020
+ )
1021
+
1022
+ switch (stored, current) {
1023
+ | (Object(sObj), Object(cObj)) =>
1024
+ let tiers = [["version"], ["name"], ["storage"], ["evm", "fuel", "svm"], ["entities"]]
1025
+ let firstHit = tiers->Array.reduce(None, (acc, tier) =>
1026
+ switch acc {
1027
+ | Some(_) => acc
1028
+ | None =>
1029
+ switch tier->Array.filter(topKeyDiffers) {
1030
+ | [] => None
1031
+ | hits => Some(hits)
1032
+ }
1033
+ }
1034
+ )
1035
+ switch firstHit {
1036
+ | Some(hits) => runTier(hits)
1037
+ | None =>
1038
+ let knownSet = Utils.Set.fromArray(tiers->Belt.Array.concatMany)
1039
+ let extras =
1040
+ Utils.Set.fromArray(Array.concat(sObj->Dict.keysToArray, cObj->Dict.keysToArray))
1041
+ ->Utils.Set.toArray
1042
+ ->Array.filter(k => !(knownSet->Utils.Set.has(k)))
1043
+ ->Array.toSorted(String.compare)
1044
+ ->Array.filter(topKeyDiffers)
1045
+ runTier(extras)
1046
+ }
1047
+ | _ => go(stored, current, "")
1048
+ }
1132
1049
  acc
1133
1050
  }
1134
1051
 
1135
1052
  // Throws an `incompatible config` error listing each path in `changedPaths`,
1136
- // plus the revert/reset remediation. `~resetCommand` lets each call site
1137
- // (`envio dev -r` / `envio start -r` / `envio local db-migrate setup`)
1138
- // surface the right wipe-and-restart command.
1139
- let throwIfIncompatible = (changedPaths: array<string>, ~resetCommand: string) => {
1053
+ // plus the remediation options. `~resetCommand` is rendered as-is for
1054
+ // option 2 (the wipe-and-redo). `~runCommand` controls option 3 (parallel
1055
+ // indexer recipe): when `None`, option 3 is omitted — the migrate flow
1056
+ // uses this because running a second indexer doesn't apply.
1057
+ // `~hasClickhouse` adds the extra env line so users running both
1058
+ // Postgres and Clickhouse get a complete override.
1059
+ let throwIfIncompatible = (
1060
+ changedPaths: array<string>,
1061
+ ~resetCommand: string,
1062
+ ~runCommand: option<string>,
1063
+ ~hasClickhouse: bool,
1064
+ ) => {
1140
1065
  if changedPaths->Array.length > 0 {
1141
1066
  let bullets = changedPaths->Array.map(p => ` - ${p}`)->Array.joinUnsafe("\n")
1067
+ let option1 = "Revert the changes above"
1068
+ let padTo = (s, col) => s ++ " "->String.repeat(Math.Int.max(col - String.length(s), 1))
1069
+ let col = Math.Int.max(String.length(option1), String.length(resetCommand)) + 2
1070
+ let option3 = switch runCommand {
1071
+ | None => ""
1072
+ | Some(cmd) =>
1073
+ let clickhouseLine = hasClickhouse ? " ENVIO_CLICKHOUSE_DATABASE=<new_db> \\\n" : ""
1074
+ `\n 3. Run a second indexer alongside this one — keep both datasets:\n ENVIO_PG_SCHEMA=<new_schema> \\\n${clickhouseLine} ENVIO_INDEXER_PORT=<new_port> \\\n ${cmd}`
1075
+ }
1142
1076
  JsError.throwWithMessage(
1143
- `The following config changes are incompatible with the existing indexer data:\n\n${bullets}\n\nPick one:\n\n 1. Revert the changes above # resume indexing where it left off\n 2. ${resetCommand} # wipe the database and re-index from scratch`,
1077
+ `The following config changes are incompatible with the existing indexer data:\n\n${bullets}\n\nPick one:\n 1. ${option1->padTo(
1078
+ col,
1079
+ )}# resume indexing where it left off\n 2. ${resetCommand->padTo(
1080
+ col,
1081
+ )}# delete all indexed data and start over${option3}`,
1144
1082
  )
1145
1083
  }
1146
1084
  }
@@ -113,26 +113,6 @@ let publicConfigChainSchema = S$RescriptSchema.schema(s => ({
113
113
  contracts: s.m(S$RescriptSchema.option(S$RescriptSchema.dict(chainContractSchema)))
114
114
  }));
115
115
 
116
- let svmEventDescriptorSchema = S$RescriptSchema.schema(s => ({
117
- discriminator: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
118
- discriminatorByteLen: s.m(S$RescriptSchema.int),
119
- includeTransaction: s.m(S$RescriptSchema.bool),
120
- includeLogs: s.m(S$RescriptSchema.bool),
121
- accountFilters: s.m(S$RescriptSchema.option(S$RescriptSchema.array(S$RescriptSchema.schema(s => ({
122
- position: s.m(S$RescriptSchema.int),
123
- values: s.m(S$RescriptSchema.array(S$RescriptSchema.string))
124
- }))))),
125
- isInner: s.m(S$RescriptSchema.option(S$RescriptSchema.bool)),
126
- accounts: s.m(S$RescriptSchema.option(S$RescriptSchema.array(S$RescriptSchema.string))),
127
- args: s.m(S$RescriptSchema.option(S$RescriptSchema.json(false)))
128
- }));
129
-
130
- let svmAbiSchema = S$RescriptSchema.schema(s => ({
131
- programId: s.m(S$RescriptSchema.string),
132
- definedTypes: s.m(S$RescriptSchema.json(false)),
133
- source: s.m(S$RescriptSchema.string)
134
- }));
135
-
136
116
  let contractEventItemSchema = S$RescriptSchema.schema(s => ({
137
117
  event: s.m(S$RescriptSchema.string),
138
118
  name: s.m(S$RescriptSchema.string),
@@ -140,21 +120,18 @@ let contractEventItemSchema = S$RescriptSchema.schema(s => ({
140
120
  params: s.m(S$RescriptSchema.option(S$RescriptSchema.array(EventConfigBuilder.eventParamSchema))),
141
121
  kind: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
142
122
  blockFields: s.m(S$RescriptSchema.option(S$RescriptSchema.array(Internal.evmBlockFieldSchema))),
143
- transactionFields: s.m(S$RescriptSchema.option(S$RescriptSchema.array(Internal.evmTransactionFieldSchema))),
144
- svm: s.m(S$RescriptSchema.option(svmEventDescriptorSchema))
123
+ transactionFields: s.m(S$RescriptSchema.option(S$RescriptSchema.array(Internal.evmTransactionFieldSchema)))
145
124
  }));
146
125
 
147
126
  let contractConfigSchema = S$RescriptSchema.schema(s => ({
148
127
  abi: s.m(S$RescriptSchema.json(false)),
149
128
  handler: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
150
- events: s.m(S$RescriptSchema.option(S$RescriptSchema.array(contractEventItemSchema))),
151
- svmAbi: s.m(S$RescriptSchema.option(svmAbiSchema))
129
+ events: s.m(S$RescriptSchema.option(S$RescriptSchema.array(contractEventItemSchema)))
152
130
  }));
153
131
 
154
132
  let publicConfigEcosystemSchema = S$RescriptSchema.schema(s => ({
155
133
  chains: s.m(S$RescriptSchema.dict(publicConfigChainSchema)),
156
- contracts: s.m(S$RescriptSchema.option(S$RescriptSchema.dict(contractConfigSchema))),
157
- programs: s.m(S$RescriptSchema.option(S$RescriptSchema.dict(contractConfigSchema)))
134
+ contracts: s.m(S$RescriptSchema.option(S$RescriptSchema.dict(contractConfigSchema)))
158
135
  }));
159
136
 
160
137
  let publicConfigEvmSchema = S$RescriptSchema.schema(s => ({
@@ -442,7 +419,6 @@ function fromPublic(publicConfigJson) {
442
419
  let lowercaseAddresses = evm !== undefined ? Stdlib_Option.getOr(Primitive_option.valFromOption(evm).addressFormat, "checksum") === "lowercase" : false;
443
420
  let match$4 = publicConfig.evm;
444
421
  let match$5 = publicConfig.fuel;
445
- let match$6 = publicConfig.svm;
446
422
  let publicContractsConfig;
447
423
  switch (ecosystemName) {
448
424
  case "evm" :
@@ -452,25 +428,25 @@ function fromPublic(publicConfigJson) {
452
428
  publicContractsConfig = match$5 !== undefined ? Primitive_option.valFromOption(match$5).contracts : undefined;
453
429
  break;
454
430
  case "svm" :
455
- publicContractsConfig = match$6 !== undefined ? Primitive_option.valFromOption(match$6).programs : undefined;
431
+ publicContractsConfig = undefined;
456
432
  break;
457
433
  }
458
434
  let evm$1 = publicConfig.evm;
459
- let match$7;
435
+ let match$6;
460
436
  if (evm$1 !== undefined) {
461
437
  let evm$2 = Primitive_option.valFromOption(evm$1);
462
- match$7 = [
438
+ match$6 = [
463
439
  new Set(EventConfigBuilder.alwaysIncludedBlockFields.concat(Stdlib_Option.getOr(evm$2.globalBlockFields, []))),
464
440
  new Set(Stdlib_Option.getOr(evm$2.globalTransactionFields, []))
465
441
  ];
466
442
  } else {
467
- match$7 = [
443
+ match$6 = [
468
444
  new Set(EventConfigBuilder.alwaysIncludedBlockFields),
469
445
  new Set()
470
446
  ];
471
447
  }
472
- let globalTransactionFieldsSet = match$7[1];
473
- let globalBlockFieldsSet = match$7[0];
448
+ let globalTransactionFieldsSet = match$6[1];
449
+ let globalBlockFieldsSet = match$6[0];
474
450
  let contractDataByName = {};
475
451
  if (publicContractsConfig !== undefined) {
476
452
  Object.entries(publicContractsConfig).forEach(param => {
@@ -482,13 +458,11 @@ function fromPublic(publicConfigJson) {
482
458
  contractDataByName[capitalizedName] = {
483
459
  abi: abi,
484
460
  eventSignatures: eventSignatures,
485
- events: contractConfig.events,
486
- svmAbi: contractConfig.svmAbi
461
+ events: contractConfig.events
487
462
  };
488
463
  });
489
464
  }
490
- let buildContractEvents = (contractName, events, abi, chainId, startBlock, addresses, svmDefinedTypesOpt) => {
491
- let svmDefinedTypes = svmDefinedTypesOpt !== undefined ? svmDefinedTypesOpt : null;
465
+ let buildContractEvents = (contractName, events, abi, chainId, startBlock) => {
492
466
  if (events !== undefined) {
493
467
  return events.map(eventItem => {
494
468
  let eventName = eventItem.name;
@@ -496,27 +470,17 @@ function fromPublic(publicConfigJson) {
496
470
  let params = Stdlib_Option.getOr(eventItem.params, []);
497
471
  let kind = eventItem.kind;
498
472
  switch (ecosystemName) {
499
- case "evm" :
500
- 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));
501
473
  case "fuel" :
502
474
  if (kind !== undefined) {
503
475
  return EventConfigBuilder.buildFuelEventConfig(contractName, eventName, kind, sighash, abi, false, undefined, undefined, startBlock);
504
476
  } else {
505
477
  return Stdlib_JsError.throwWithMessage(`Fuel event ` + contractName + `.` + eventName + ` is missing "kind" in internal config`);
506
478
  }
479
+ case "evm" :
507
480
  case "svm" :
508
- let len = addresses.length;
509
- let programId = len !== 1 ? (
510
- len !== 0 ? Stdlib_JsError.throwWithMessage(`SVM program ` + contractName + ` on chain ` + chainId.toString() + ` has multiple addresses; a program is uniquely identified by a single program_id`) : Stdlib_JsError.throwWithMessage(`SVM program ` + contractName + ` on chain ` + chainId.toString() + ` is missing a program_id`)
511
- ) : addresses[0];
512
- let s = eventItem.svm;
513
- let svm = s !== undefined ? Primitive_option.valFromOption(s) : Stdlib_JsError.throwWithMessage(`SVM instruction ` + contractName + `.` + eventName + ` is missing the "svm" descriptor in internal config`);
514
- let accountFilters = Stdlib_Option.getOr(svm.accountFilters, []).map(af => ({
515
- position: af.position,
516
- values: af.values
517
- }));
518
- return EventConfigBuilder.buildSvmInstructionEventConfig(contractName, eventName, programId, svm.discriminator, svm.discriminatorByteLen, svm.includeTransaction, svm.includeLogs, accountFilters, svm.isInner, false, undefined, undefined, Stdlib_Option.getOr(svm.accounts, []), Stdlib_Option.getOr(svm.args, null), svmDefinedTypes, startBlock);
481
+ break;
519
482
  }
483
+ 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));
520
484
  });
521
485
  } else {
522
486
  return [];
@@ -553,10 +517,9 @@ function fromPublic(publicConfigJson) {
553
517
  let contractData = param[1];
554
518
  let capitalizedName = param[0];
555
519
  let chainContract = chainContracts[capitalizedName];
556
- let rawAddresses = Stdlib_Option.getOr(Stdlib_Option.flatMap(chainContract, cc => cc.addresses), []);
557
- let addresses = rawAddresses.map(parseAddress);
520
+ let addresses = Stdlib_Option.getOr(Stdlib_Option.flatMap(chainContract, cc => cc.addresses), []).map(parseAddress);
558
521
  let startBlock = Stdlib_Option.flatMap(chainContract, cc => cc.startBlock);
559
- let events = buildContractEvents(capitalizedName, contractData.events, contractData.abi, chainId, startBlock, rawAddresses, Stdlib_Option.getOr(Stdlib_Option.map(contractData.svmAbi, a => a.definedTypes), null));
522
+ let events = buildContractEvents(capitalizedName, contractData.events, contractData.abi, chainId, startBlock);
560
523
  return {
561
524
  name: capitalizedName,
562
525
  abi: contractData.abi,
@@ -613,7 +576,6 @@ function fromPublic(publicConfigJson) {
613
576
  let rpc = publicChainConfig.rpc;
614
577
  sourceConfig = rpc !== undefined ? ({
615
578
  TAG: "SvmSourceConfig",
616
- hypersync: publicChainConfig.hypersync,
617
579
  rpc: rpc
618
580
  }) : Stdlib_JsError.throwWithMessage(`Chain ` + chainName + ` is missing rpc endpoint in config`);
619
581
  break;
@@ -818,9 +780,10 @@ function canonicalJson(json) {
818
780
  }
819
781
 
820
782
  function diffPaths(stored, current) {
783
+ let canonEq = (a, b) => JSON.stringify(canonicalJson(a)) === JSON.stringify(canonicalJson(b));
821
784
  let acc = [];
822
785
  let go = (s, c, prefix) => {
823
- if (JSON.stringify(canonicalJson(s)) === JSON.stringify(canonicalJson(c))) {
786
+ if (canonEq(s, c)) {
824
787
  return;
825
788
  }
826
789
  if (Array.isArray(s)) {
@@ -868,16 +831,92 @@ function diffPaths(stored, current) {
868
831
  }
869
832
  acc.push(prefix === "" ? "<root>" : prefix);
870
833
  };
871
- go(stored, current, "");
834
+ let getTopKey = (j, k) => {
835
+ if (typeof j === "object" && j !== null && !Array.isArray(j)) {
836
+ return j[k];
837
+ }
838
+ };
839
+ let topKeyDiffers = k => {
840
+ let match = getTopKey(stored, k);
841
+ let match$1 = getTopKey(current, k);
842
+ if (match !== undefined) {
843
+ if (match$1 !== undefined) {
844
+ return !canonEq(match, match$1);
845
+ } else {
846
+ return true;
847
+ }
848
+ } else {
849
+ return match$1 !== undefined;
850
+ }
851
+ };
852
+ let runTier = keys => {
853
+ keys.forEach(k => {
854
+ let match = getTopKey(stored, k);
855
+ let match$1 = getTopKey(current, k);
856
+ if (match !== undefined) {
857
+ if (match$1 !== undefined) {
858
+ return go(match, match$1, k);
859
+ } else {
860
+ acc.push(k);
861
+ return;
862
+ }
863
+ } else if (match$1 !== undefined) {
864
+ acc.push(k);
865
+ return;
866
+ } else {
867
+ return;
868
+ }
869
+ });
870
+ };
871
+ if (typeof stored === "object" && stored !== null && !Array.isArray(stored) && typeof current === "object" && current !== null && !Array.isArray(current)) {
872
+ let tiers = [
873
+ ["version"],
874
+ ["name"],
875
+ ["storage"],
876
+ [
877
+ "evm",
878
+ "fuel",
879
+ "svm"
880
+ ],
881
+ ["entities"]
882
+ ];
883
+ let firstHit = Stdlib_Array.reduce(tiers, undefined, (acc, tier) => {
884
+ if (acc !== undefined) {
885
+ return acc;
886
+ }
887
+ let hits = tier.filter(topKeyDiffers);
888
+ if (hits.length !== 0) {
889
+ return hits;
890
+ }
891
+ });
892
+ if (firstHit !== undefined) {
893
+ runTier(firstHit);
894
+ } else {
895
+ let knownSet = new Set(Belt_Array.concatMany(tiers));
896
+ runTier(Array.from(new Set(Object.keys(stored).concat(Object.keys(current)))).filter(k => !knownSet.has(k)).toSorted(Primitive_string.compare).filter(topKeyDiffers));
897
+ }
898
+ } else {
899
+ go(stored, current, "");
900
+ }
872
901
  return acc;
873
902
  }
874
903
 
875
- function throwIfIncompatible(changedPaths, resetCommand) {
904
+ function throwIfIncompatible(changedPaths, resetCommand, runCommand, hasClickhouse) {
876
905
  if (changedPaths.length === 0) {
877
906
  return;
878
907
  }
879
908
  let bullets = changedPaths.map(p => ` - ` + p).join("\n");
880
- Stdlib_JsError.throwWithMessage(`The following config changes are incompatible with the existing indexer data:\n\n` + bullets + `\n\nPick one:\n\n 1. Revert the changes above # resume indexing where it left off\n 2. ` + resetCommand + ` # wipe the database and re-index from scratch`);
909
+ let option1 = "Revert the changes above";
910
+ let padTo = (s, col) => s + " ".repeat(Math.max(col - s.length | 0, 1));
911
+ let col = Math.max(option1.length, resetCommand.length) + 2 | 0;
912
+ let option3;
913
+ if (runCommand !== undefined) {
914
+ let clickhouseLine = hasClickhouse ? " ENVIO_CLICKHOUSE_DATABASE=<new_db> \\\n" : "";
915
+ option3 = `\n 3. Run a second indexer alongside this one — keep both datasets:\n ENVIO_PG_SCHEMA=<new_schema> \\\n` + clickhouseLine + ` ENVIO_INDEXER_PORT=<new_port> \\\n ` + runCommand;
916
+ } else {
917
+ option3 = "";
918
+ }
919
+ 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);
881
920
  }
882
921
 
883
922
  function loadWithoutRegistrations() {
@@ -900,8 +939,6 @@ export {
900
939
  rpcConfigSchema,
901
940
  chainContractSchema,
902
941
  publicConfigChainSchema,
903
- svmEventDescriptorSchema,
904
- svmAbiSchema,
905
942
  contractEventItemSchema,
906
943
  contractConfigSchema,
907
944
  publicConfigEcosystemSchema,