envio 3.0.1 → 3.0.2-svm-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/evm.schema.json +8 -8
- package/fuel.schema.json +12 -12
- package/index.d.ts +155 -1
- package/package.json +6 -7
- package/src/ChainFetcher.res +25 -1
- package/src/ChainFetcher.res.mjs +19 -1
- package/src/Config.res +156 -94
- package/src/Config.res.mjs +60 -97
- package/src/Core.res +32 -0
- package/src/Env.res.mjs +1 -2
- package/src/Envio.res +94 -0
- package/src/EventConfigBuilder.res +50 -0
- package/src/EventConfigBuilder.res.mjs +31 -0
- package/src/HandlerLoader.res +12 -1
- package/src/HandlerLoader.res.mjs +6 -1
- package/src/Internal.res +38 -0
- package/src/Main.res +53 -3
- package/src/Main.res.mjs +34 -2
- package/src/Persistence.res +2 -17
- package/src/Persistence.res.mjs +2 -14
- package/src/SimulateItems.res +23 -10
- package/src/SimulateItems.res.mjs +21 -6
- package/src/SvmTypes.res +9 -0
- package/src/SvmTypes.res.mjs +14 -0
- package/src/sources/EventRouter.res +65 -0
- package/src/sources/EventRouter.res.mjs +43 -0
- package/src/sources/HyperSyncClient.res +30 -157
- package/src/sources/HyperSyncClient.res.mjs +20 -6
- package/src/sources/HyperSyncSolanaClient.res +227 -0
- package/src/sources/HyperSyncSolanaClient.res.mjs +25 -0
- package/src/sources/HyperSyncSolanaSource.res +515 -0
- package/src/sources/HyperSyncSolanaSource.res.mjs +441 -0
- package/src/sources/HyperSyncSource.res +5 -8
- package/src/sources/HyperSyncSource.res.mjs +1 -8
- package/src/sources/RpcSource.res.mjs +1 -1
- package/src/sources/Svm.res +2 -2
- package/src/sources/Svm.res.mjs +3 -2
- package/src/tui/Tui.res +9 -2
- package/src/tui/Tui.res.mjs +19 -4
- package/src/tui/components/TuiData.res +3 -0
- package/svm.schema.json +345 -4
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({rpc: string})
|
|
33
|
+
| SvmSourceConfig({hypersync: option<string>, rpc: string})
|
|
34
34
|
// For tests: pass custom sources directly
|
|
35
35
|
| CustomSources(array<Source.t>)
|
|
36
36
|
|
|
@@ -201,6 +201,38 @@ 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
|
+
|
|
204
236
|
let contractEventItemSchema = S.schema(s =>
|
|
205
237
|
{
|
|
206
238
|
"event": s.matches(S.string),
|
|
@@ -210,6 +242,7 @@ let contractEventItemSchema = S.schema(s =>
|
|
|
210
242
|
"kind": s.matches(S.option(S.string)),
|
|
211
243
|
"blockFields": s.matches(S.option(S.array(Internal.evmBlockFieldSchema))),
|
|
212
244
|
"transactionFields": s.matches(S.option(S.array(Internal.evmTransactionFieldSchema))),
|
|
245
|
+
"svm": s.matches(S.option(svmEventDescriptorSchema)),
|
|
213
246
|
}
|
|
214
247
|
)
|
|
215
248
|
|
|
@@ -219,6 +252,8 @@ let contractConfigSchema = S.schema(s =>
|
|
|
219
252
|
"handler": s.matches(S.option(S.string)),
|
|
220
253
|
// EVM-specific: event signatures for HyperSync queries
|
|
221
254
|
"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)),
|
|
222
257
|
}
|
|
223
258
|
)
|
|
224
259
|
|
|
@@ -226,6 +261,10 @@ let publicConfigEcosystemSchema = S.schema(s =>
|
|
|
226
261
|
{
|
|
227
262
|
"chains": s.matches(S.dict(publicConfigChainSchema)),
|
|
228
263
|
"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))),
|
|
229
268
|
}
|
|
230
269
|
)
|
|
231
270
|
|
|
@@ -523,10 +562,19 @@ let fromPublic = (publicConfigJson: JSON.t) => {
|
|
|
523
562
|
| None => false
|
|
524
563
|
}
|
|
525
564
|
|
|
526
|
-
// Parse contract configs (ABIs, events, handlers)
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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"]
|
|
530
578
|
| _ => None
|
|
531
579
|
}
|
|
532
580
|
|
|
@@ -549,6 +597,7 @@ let fromPublic = (publicConfigJson: JSON.t) => {
|
|
|
549
597
|
"abi": EvmTypes.Abi.t,
|
|
550
598
|
"eventSignatures": array<string>,
|
|
551
599
|
"events": option<array<_>>,
|
|
600
|
+
"svmAbi": option<{"programId": string, "definedTypes": JSON.t, "source": string}>,
|
|
552
601
|
}> = Dict.make()
|
|
553
602
|
switch publicContractsConfig {
|
|
554
603
|
| Some(contractsDict) =>
|
|
@@ -561,21 +610,40 @@ let fromPublic = (publicConfigJson: JSON.t) => {
|
|
|
561
610
|
| Some(events) => events->Array.map(eventItem => eventItem["event"])
|
|
562
611
|
| None => []
|
|
563
612
|
}
|
|
613
|
+
let widened =
|
|
614
|
+
contractConfig->(
|
|
615
|
+
Utils.magic: _ => {
|
|
616
|
+
"svmAbi": option<{"programId": string, "definedTypes": JSON.t, "source": string}>,
|
|
617
|
+
}
|
|
618
|
+
)
|
|
564
619
|
contractDataByName->Dict.set(
|
|
565
620
|
capitalizedName,
|
|
566
|
-
{
|
|
621
|
+
{
|
|
622
|
+
"abi": abi,
|
|
623
|
+
"eventSignatures": eventSignatures,
|
|
624
|
+
"events": contractConfig["events"],
|
|
625
|
+
"svmAbi": widened["svmAbi"],
|
|
626
|
+
},
|
|
567
627
|
)
|
|
568
628
|
})
|
|
569
629
|
| None => ()
|
|
570
630
|
}
|
|
571
631
|
|
|
572
|
-
// Build event configs for a contract from JSON event items
|
|
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).
|
|
573
639
|
let buildContractEvents = (
|
|
574
640
|
~contractName,
|
|
575
641
|
~events: option<array<_>>,
|
|
576
642
|
~abi,
|
|
577
643
|
~chainId: int,
|
|
578
644
|
~startBlock: option<int>,
|
|
645
|
+
~addresses: array<string>,
|
|
646
|
+
~svmDefinedTypes: JSON.t=JSON.Null,
|
|
579
647
|
) => {
|
|
580
648
|
switch events {
|
|
581
649
|
| None => []
|
|
@@ -606,6 +674,65 @@ let fromPublic = (publicConfigJson: JSON.t) => {
|
|
|
606
674
|
`Fuel event ${contractName}.${eventName} is missing "kind" in internal config`,
|
|
607
675
|
)
|
|
608
676
|
}
|
|
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)
|
|
609
736
|
| _ =>
|
|
610
737
|
(EventConfigBuilder.buildEvmEventConfig(
|
|
611
738
|
~contractName,
|
|
@@ -665,11 +792,11 @@ let fromPublic = (publicConfigJson: JSON.t) => {
|
|
|
665
792
|
->Dict.toArray
|
|
666
793
|
->Array.map(((capitalizedName, contractData)) => {
|
|
667
794
|
let chainContract = chainContracts->Dict.get(capitalizedName)
|
|
668
|
-
let
|
|
795
|
+
let rawAddresses =
|
|
669
796
|
chainContract
|
|
670
797
|
->Option.flatMap(cc => cc["addresses"])
|
|
671
798
|
->Option.getOr([])
|
|
672
|
-
|
|
799
|
+
let addresses = rawAddresses->Array.map(parseAddress)
|
|
673
800
|
let startBlock = chainContract->Option.flatMap(cc => cc["startBlock"])
|
|
674
801
|
|
|
675
802
|
// Build event configs from JSON (field selections resolved inline)
|
|
@@ -683,6 +810,10 @@ let fromPublic = (publicConfigJson: JSON.t) => {
|
|
|
683
810
|
~abi=contractData["abi"],
|
|
684
811
|
~chainId,
|
|
685
812
|
~startBlock,
|
|
813
|
+
~addresses=rawAddresses,
|
|
814
|
+
~svmDefinedTypes=contractData["svmAbi"]
|
|
815
|
+
->Option.map(a => a["definedTypes"])
|
|
816
|
+
->Option.getOr(JSON.Null),
|
|
686
817
|
)
|
|
687
818
|
|
|
688
819
|
{
|
|
@@ -748,7 +879,11 @@ let fromPublic = (publicConfigJson: JSON.t) => {
|
|
|
748
879
|
}
|
|
749
880
|
| Ecosystem.Svm =>
|
|
750
881
|
switch publicChainConfig["rpc"] {
|
|
751
|
-
| Some(rpc) =>
|
|
882
|
+
| Some(rpc) =>
|
|
883
|
+
SvmSourceConfig({
|
|
884
|
+
hypersync: publicChainConfig["hypersync"],
|
|
885
|
+
rpc,
|
|
886
|
+
})
|
|
752
887
|
| None => JsError.throwWithMessage(`Chain ${chainName} is missing rpc endpoint in config`)
|
|
753
888
|
}
|
|
754
889
|
}
|
|
@@ -957,18 +1092,11 @@ let rec canonicalJson = (json: JSON.t): JSON.t =>
|
|
|
957
1092
|
}
|
|
958
1093
|
|
|
959
1094
|
// Returns dotted leaf paths (`a.b[i].c`) where `stored` differs from
|
|
960
|
-
// `current
|
|
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.
|
|
1095
|
+
// `current`. Skips subtrees whose canonical JSON matches.
|
|
965
1096
|
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
|
-
|
|
969
1097
|
let acc = []
|
|
970
1098
|
let rec go = (s: JSON.t, c: JSON.t, prefix: string) => {
|
|
971
|
-
if
|
|
1099
|
+
if JSON.stringify(canonicalJson(s)) === JSON.stringify(canonicalJson(c)) {
|
|
972
1100
|
()
|
|
973
1101
|
} else {
|
|
974
1102
|
switch (s, c) {
|
|
@@ -994,91 +1122,25 @@ let diffPaths = (~stored: JSON.t, ~current: JSON.t): array<string> => {
|
|
|
994
1122
|
| (Some(sv), Some(cv)) => go(sv, cv, p)
|
|
995
1123
|
}
|
|
996
1124
|
}
|
|
997
|
-
| _ =>
|
|
1125
|
+
| _ =>
|
|
1126
|
+
// Type mismatch or scalar diff
|
|
1127
|
+
acc->Array.push(prefix === "" ? "<root>" : prefix)->ignore
|
|
998
1128
|
}
|
|
999
1129
|
}
|
|
1000
1130
|
}
|
|
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
|
-
}
|
|
1131
|
+
go(stored, current, "")
|
|
1049
1132
|
acc
|
|
1050
1133
|
}
|
|
1051
1134
|
|
|
1052
1135
|
// Throws an `incompatible config` error listing each path in `changedPaths`,
|
|
1053
|
-
// plus the remediation
|
|
1054
|
-
//
|
|
1055
|
-
//
|
|
1056
|
-
|
|
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
|
-
) => {
|
|
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) => {
|
|
1065
1140
|
if changedPaths->Array.length > 0 {
|
|
1066
1141
|
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
|
-
}
|
|
1076
1142
|
JsError.throwWithMessage(
|
|
1077
|
-
`The following config changes are incompatible with the existing indexer data:\n\n${bullets}\n\nPick one:\n 1. ${
|
|
1078
|
-
col,
|
|
1079
|
-
)}# resume indexing where it left off\n 2. ${resetCommand->padTo(
|
|
1080
|
-
col,
|
|
1081
|
-
)}# delete all indexed data and start over${option3}`,
|
|
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`,
|
|
1082
1144
|
)
|
|
1083
1145
|
}
|
|
1084
1146
|
}
|
package/src/Config.res.mjs
CHANGED
|
@@ -113,6 +113,26 @@ 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
|
+
|
|
116
136
|
let contractEventItemSchema = S$RescriptSchema.schema(s => ({
|
|
117
137
|
event: s.m(S$RescriptSchema.string),
|
|
118
138
|
name: s.m(S$RescriptSchema.string),
|
|
@@ -120,18 +140,21 @@ let contractEventItemSchema = S$RescriptSchema.schema(s => ({
|
|
|
120
140
|
params: s.m(S$RescriptSchema.option(S$RescriptSchema.array(EventConfigBuilder.eventParamSchema))),
|
|
121
141
|
kind: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
|
|
122
142
|
blockFields: s.m(S$RescriptSchema.option(S$RescriptSchema.array(Internal.evmBlockFieldSchema))),
|
|
123
|
-
transactionFields: s.m(S$RescriptSchema.option(S$RescriptSchema.array(Internal.evmTransactionFieldSchema)))
|
|
143
|
+
transactionFields: s.m(S$RescriptSchema.option(S$RescriptSchema.array(Internal.evmTransactionFieldSchema))),
|
|
144
|
+
svm: s.m(S$RescriptSchema.option(svmEventDescriptorSchema))
|
|
124
145
|
}));
|
|
125
146
|
|
|
126
147
|
let contractConfigSchema = S$RescriptSchema.schema(s => ({
|
|
127
148
|
abi: s.m(S$RescriptSchema.json(false)),
|
|
128
149
|
handler: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
|
|
129
|
-
events: s.m(S$RescriptSchema.option(S$RescriptSchema.array(contractEventItemSchema)))
|
|
150
|
+
events: s.m(S$RescriptSchema.option(S$RescriptSchema.array(contractEventItemSchema))),
|
|
151
|
+
svmAbi: s.m(S$RescriptSchema.option(svmAbiSchema))
|
|
130
152
|
}));
|
|
131
153
|
|
|
132
154
|
let publicConfigEcosystemSchema = S$RescriptSchema.schema(s => ({
|
|
133
155
|
chains: s.m(S$RescriptSchema.dict(publicConfigChainSchema)),
|
|
134
|
-
contracts: s.m(S$RescriptSchema.option(S$RescriptSchema.dict(contractConfigSchema)))
|
|
156
|
+
contracts: s.m(S$RescriptSchema.option(S$RescriptSchema.dict(contractConfigSchema))),
|
|
157
|
+
programs: s.m(S$RescriptSchema.option(S$RescriptSchema.dict(contractConfigSchema)))
|
|
135
158
|
}));
|
|
136
159
|
|
|
137
160
|
let publicConfigEvmSchema = S$RescriptSchema.schema(s => ({
|
|
@@ -419,6 +442,7 @@ function fromPublic(publicConfigJson) {
|
|
|
419
442
|
let lowercaseAddresses = evm !== undefined ? Stdlib_Option.getOr(Primitive_option.valFromOption(evm).addressFormat, "checksum") === "lowercase" : false;
|
|
420
443
|
let match$4 = publicConfig.evm;
|
|
421
444
|
let match$5 = publicConfig.fuel;
|
|
445
|
+
let match$6 = publicConfig.svm;
|
|
422
446
|
let publicContractsConfig;
|
|
423
447
|
switch (ecosystemName) {
|
|
424
448
|
case "evm" :
|
|
@@ -428,25 +452,25 @@ function fromPublic(publicConfigJson) {
|
|
|
428
452
|
publicContractsConfig = match$5 !== undefined ? Primitive_option.valFromOption(match$5).contracts : undefined;
|
|
429
453
|
break;
|
|
430
454
|
case "svm" :
|
|
431
|
-
publicContractsConfig = undefined;
|
|
455
|
+
publicContractsConfig = match$6 !== undefined ? Primitive_option.valFromOption(match$6).programs : undefined;
|
|
432
456
|
break;
|
|
433
457
|
}
|
|
434
458
|
let evm$1 = publicConfig.evm;
|
|
435
|
-
let match$
|
|
459
|
+
let match$7;
|
|
436
460
|
if (evm$1 !== undefined) {
|
|
437
461
|
let evm$2 = Primitive_option.valFromOption(evm$1);
|
|
438
|
-
match$
|
|
462
|
+
match$7 = [
|
|
439
463
|
new Set(EventConfigBuilder.alwaysIncludedBlockFields.concat(Stdlib_Option.getOr(evm$2.globalBlockFields, []))),
|
|
440
464
|
new Set(Stdlib_Option.getOr(evm$2.globalTransactionFields, []))
|
|
441
465
|
];
|
|
442
466
|
} else {
|
|
443
|
-
match$
|
|
467
|
+
match$7 = [
|
|
444
468
|
new Set(EventConfigBuilder.alwaysIncludedBlockFields),
|
|
445
469
|
new Set()
|
|
446
470
|
];
|
|
447
471
|
}
|
|
448
|
-
let globalTransactionFieldsSet = match$
|
|
449
|
-
let globalBlockFieldsSet = match$
|
|
472
|
+
let globalTransactionFieldsSet = match$7[1];
|
|
473
|
+
let globalBlockFieldsSet = match$7[0];
|
|
450
474
|
let contractDataByName = {};
|
|
451
475
|
if (publicContractsConfig !== undefined) {
|
|
452
476
|
Object.entries(publicContractsConfig).forEach(param => {
|
|
@@ -458,11 +482,13 @@ function fromPublic(publicConfigJson) {
|
|
|
458
482
|
contractDataByName[capitalizedName] = {
|
|
459
483
|
abi: abi,
|
|
460
484
|
eventSignatures: eventSignatures,
|
|
461
|
-
events: contractConfig.events
|
|
485
|
+
events: contractConfig.events,
|
|
486
|
+
svmAbi: contractConfig.svmAbi
|
|
462
487
|
};
|
|
463
488
|
});
|
|
464
489
|
}
|
|
465
|
-
let buildContractEvents = (contractName, events, abi, chainId, startBlock) => {
|
|
490
|
+
let buildContractEvents = (contractName, events, abi, chainId, startBlock, addresses, svmDefinedTypesOpt) => {
|
|
491
|
+
let svmDefinedTypes = svmDefinedTypesOpt !== undefined ? svmDefinedTypesOpt : null;
|
|
466
492
|
if (events !== undefined) {
|
|
467
493
|
return events.map(eventItem => {
|
|
468
494
|
let eventName = eventItem.name;
|
|
@@ -470,17 +496,27 @@ function fromPublic(publicConfigJson) {
|
|
|
470
496
|
let params = Stdlib_Option.getOr(eventItem.params, []);
|
|
471
497
|
let kind = eventItem.kind;
|
|
472
498
|
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));
|
|
473
501
|
case "fuel" :
|
|
474
502
|
if (kind !== undefined) {
|
|
475
503
|
return EventConfigBuilder.buildFuelEventConfig(contractName, eventName, kind, sighash, abi, false, undefined, undefined, startBlock);
|
|
476
504
|
} else {
|
|
477
505
|
return Stdlib_JsError.throwWithMessage(`Fuel event ` + contractName + `.` + eventName + ` is missing "kind" in internal config`);
|
|
478
506
|
}
|
|
479
|
-
case "evm" :
|
|
480
507
|
case "svm" :
|
|
481
|
-
|
|
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);
|
|
482
519
|
}
|
|
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));
|
|
484
520
|
});
|
|
485
521
|
} else {
|
|
486
522
|
return [];
|
|
@@ -517,9 +553,10 @@ function fromPublic(publicConfigJson) {
|
|
|
517
553
|
let contractData = param[1];
|
|
518
554
|
let capitalizedName = param[0];
|
|
519
555
|
let chainContract = chainContracts[capitalizedName];
|
|
520
|
-
let
|
|
556
|
+
let rawAddresses = Stdlib_Option.getOr(Stdlib_Option.flatMap(chainContract, cc => cc.addresses), []);
|
|
557
|
+
let addresses = rawAddresses.map(parseAddress);
|
|
521
558
|
let startBlock = Stdlib_Option.flatMap(chainContract, cc => cc.startBlock);
|
|
522
|
-
let events = buildContractEvents(capitalizedName, contractData.events, contractData.abi, chainId, 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));
|
|
523
560
|
return {
|
|
524
561
|
name: capitalizedName,
|
|
525
562
|
abi: contractData.abi,
|
|
@@ -576,6 +613,7 @@ function fromPublic(publicConfigJson) {
|
|
|
576
613
|
let rpc = publicChainConfig.rpc;
|
|
577
614
|
sourceConfig = rpc !== undefined ? ({
|
|
578
615
|
TAG: "SvmSourceConfig",
|
|
616
|
+
hypersync: publicChainConfig.hypersync,
|
|
579
617
|
rpc: rpc
|
|
580
618
|
}) : Stdlib_JsError.throwWithMessage(`Chain ` + chainName + ` is missing rpc endpoint in config`);
|
|
581
619
|
break;
|
|
@@ -780,10 +818,9 @@ function canonicalJson(json) {
|
|
|
780
818
|
}
|
|
781
819
|
|
|
782
820
|
function diffPaths(stored, current) {
|
|
783
|
-
let canonEq = (a, b) => JSON.stringify(canonicalJson(a)) === JSON.stringify(canonicalJson(b));
|
|
784
821
|
let acc = [];
|
|
785
822
|
let go = (s, c, prefix) => {
|
|
786
|
-
if (
|
|
823
|
+
if (JSON.stringify(canonicalJson(s)) === JSON.stringify(canonicalJson(c))) {
|
|
787
824
|
return;
|
|
788
825
|
}
|
|
789
826
|
if (Array.isArray(s)) {
|
|
@@ -831,92 +868,16 @@ function diffPaths(stored, current) {
|
|
|
831
868
|
}
|
|
832
869
|
acc.push(prefix === "" ? "<root>" : prefix);
|
|
833
870
|
};
|
|
834
|
-
|
|
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
|
-
}
|
|
871
|
+
go(stored, current, "");
|
|
901
872
|
return acc;
|
|
902
873
|
}
|
|
903
874
|
|
|
904
|
-
function throwIfIncompatible(changedPaths, resetCommand
|
|
875
|
+
function throwIfIncompatible(changedPaths, resetCommand) {
|
|
905
876
|
if (changedPaths.length === 0) {
|
|
906
877
|
return;
|
|
907
878
|
}
|
|
908
879
|
let bullets = changedPaths.map(p => ` - ` + p).join("\n");
|
|
909
|
-
|
|
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);
|
|
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`);
|
|
920
881
|
}
|
|
921
882
|
|
|
922
883
|
function loadWithoutRegistrations() {
|
|
@@ -939,6 +900,8 @@ export {
|
|
|
939
900
|
rpcConfigSchema,
|
|
940
901
|
chainContractSchema,
|
|
941
902
|
publicConfigChainSchema,
|
|
903
|
+
svmEventDescriptorSchema,
|
|
904
|
+
svmAbiSchema,
|
|
942
905
|
contractEventItemSchema,
|
|
943
906
|
contractConfigSchema,
|
|
944
907
|
publicConfigEcosystemSchema,
|