envio 3.0.0 → 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 +145 -10
- package/src/Config.res.mjs +56 -16
- 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/InMemoryTable.res +20 -24
- package/src/InMemoryTable.res.mjs +3 -19
- package/src/Internal.res +38 -0
- package/src/Main.res +53 -1
- package/src/Main.res.mjs +32 -0
- 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/evm.schema.json
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
"description": "Schema for a YAML config for an envio indexer",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"properties": {
|
|
7
|
+
"name": {
|
|
8
|
+
"description": "Name of the project",
|
|
9
|
+
"type": "string"
|
|
10
|
+
},
|
|
7
11
|
"description": {
|
|
8
12
|
"description": "Description of the project",
|
|
9
13
|
"type": [
|
|
@@ -11,10 +15,6 @@
|
|
|
11
15
|
"null"
|
|
12
16
|
]
|
|
13
17
|
},
|
|
14
|
-
"name": {
|
|
15
|
-
"description": "Name of the project",
|
|
16
|
-
"type": "string"
|
|
17
|
-
},
|
|
18
18
|
"schema": {
|
|
19
19
|
"description": "Custom path to schema.graphql file",
|
|
20
20
|
"type": [
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"null"
|
|
68
68
|
],
|
|
69
69
|
"items": {
|
|
70
|
-
"$ref": "#/$defs/
|
|
70
|
+
"$ref": "#/$defs/GlobalContract"
|
|
71
71
|
}
|
|
72
72
|
},
|
|
73
73
|
"chains": {
|
|
@@ -189,7 +189,7 @@
|
|
|
189
189
|
"evm"
|
|
190
190
|
]
|
|
191
191
|
},
|
|
192
|
-
"
|
|
192
|
+
"GlobalContract": {
|
|
193
193
|
"type": "object",
|
|
194
194
|
"properties": {
|
|
195
195
|
"name": {
|
|
@@ -418,7 +418,7 @@
|
|
|
418
418
|
"null"
|
|
419
419
|
],
|
|
420
420
|
"items": {
|
|
421
|
-
"$ref": "#/$defs/
|
|
421
|
+
"$ref": "#/$defs/ChainContract"
|
|
422
422
|
}
|
|
423
423
|
}
|
|
424
424
|
},
|
|
@@ -578,7 +578,7 @@
|
|
|
578
578
|
"url"
|
|
579
579
|
]
|
|
580
580
|
},
|
|
581
|
-
"
|
|
581
|
+
"ChainContract": {
|
|
582
582
|
"type": "object",
|
|
583
583
|
"properties": {
|
|
584
584
|
"name": {
|
package/fuel.schema.json
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
"description": "Schema for a YAML config for an envio indexer",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"properties": {
|
|
7
|
+
"name": {
|
|
8
|
+
"description": "Name of the project",
|
|
9
|
+
"type": "string"
|
|
10
|
+
},
|
|
7
11
|
"description": {
|
|
8
12
|
"description": "Description of the project",
|
|
9
13
|
"type": [
|
|
@@ -11,10 +15,6 @@
|
|
|
11
15
|
"null"
|
|
12
16
|
]
|
|
13
17
|
},
|
|
14
|
-
"name": {
|
|
15
|
-
"description": "Name of the project",
|
|
16
|
-
"type": "string"
|
|
17
|
-
},
|
|
18
18
|
"schema": {
|
|
19
19
|
"description": "Custom path to schema.graphql file",
|
|
20
20
|
"type": [
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"null"
|
|
61
61
|
],
|
|
62
62
|
"items": {
|
|
63
|
-
"$ref": "#/$defs/
|
|
63
|
+
"$ref": "#/$defs/GlobalContract"
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
66
|
"chains": {
|
|
@@ -147,7 +147,7 @@
|
|
|
147
147
|
"fuel"
|
|
148
148
|
]
|
|
149
149
|
},
|
|
150
|
-
"
|
|
150
|
+
"GlobalContract": {
|
|
151
151
|
"type": "object",
|
|
152
152
|
"properties": {
|
|
153
153
|
"name": {
|
|
@@ -183,6 +183,10 @@
|
|
|
183
183
|
"EventConfig": {
|
|
184
184
|
"type": "object",
|
|
185
185
|
"properties": {
|
|
186
|
+
"name": {
|
|
187
|
+
"description": "Name of the event in the HyperIndex generated code",
|
|
188
|
+
"type": "string"
|
|
189
|
+
},
|
|
186
190
|
"type": {
|
|
187
191
|
"description": "Explicitly set the event type you want to index. It's derived from the event name and fallbacks to LogData.",
|
|
188
192
|
"anyOf": [
|
|
@@ -194,10 +198,6 @@
|
|
|
194
198
|
}
|
|
195
199
|
]
|
|
196
200
|
},
|
|
197
|
-
"name": {
|
|
198
|
-
"description": "Name of the event in the HyperIndex generated code",
|
|
199
|
-
"type": "string"
|
|
200
|
-
},
|
|
201
201
|
"logId": {
|
|
202
202
|
"description": "An identifier of a logged type from ABI. Used for indexing LogData receipts. The option can be omitted when the event name matches the logged struct/enum name.",
|
|
203
203
|
"type": [
|
|
@@ -281,7 +281,7 @@
|
|
|
281
281
|
"null"
|
|
282
282
|
],
|
|
283
283
|
"items": {
|
|
284
|
-
"$ref": "#/$defs/
|
|
284
|
+
"$ref": "#/$defs/ChainContract"
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
287
|
},
|
|
@@ -304,7 +304,7 @@
|
|
|
304
304
|
"url"
|
|
305
305
|
]
|
|
306
306
|
},
|
|
307
|
-
"
|
|
307
|
+
"ChainContract": {
|
|
308
308
|
"type": "object",
|
|
309
309
|
"properties": {
|
|
310
310
|
"name": {
|
package/index.d.ts
CHANGED
|
@@ -961,6 +961,125 @@ export type SvmOnSlotOptions<Config extends IndexerConfigTypes = GlobalConfig> =
|
|
|
961
961
|
readonly where?: (args: SvmOnSlotWhereArgs<Config>) => SvmOnSlotWhereResult;
|
|
962
962
|
};
|
|
963
963
|
|
|
964
|
+
// ============== SVM onInstruction types ==============
|
|
965
|
+
|
|
966
|
+
/** Borsh-decoded view of an instruction. Present whenever a `ProgramSchema`
|
|
967
|
+
* was attached to the program (bundled, Anchor IDL, or hand-written
|
|
968
|
+
* `accounts`/`args` in YAML). Absent when no schema applies or the
|
|
969
|
+
* discriminator didn't match any registered instruction. */
|
|
970
|
+
export type SvmDecodedInstruction = {
|
|
971
|
+
/** Schema-declared instruction name. */
|
|
972
|
+
readonly name: string;
|
|
973
|
+
/** Borsh-decoded args object. POC types this as `unknown`; narrow with a
|
|
974
|
+
* locally-declared type until the typed-args codegen lands. */
|
|
975
|
+
readonly args: unknown;
|
|
976
|
+
/** Named accounts in schema order. Keys are exactly the schema-declared
|
|
977
|
+
* names; values are base58 pubkeys. */
|
|
978
|
+
readonly accounts: Readonly<Record<string, string>>;
|
|
979
|
+
/** Accounts beyond the schema's named list (Anchor `remaining_accounts`,
|
|
980
|
+
* IDL drift). Empty when counts match the schema. */
|
|
981
|
+
readonly extraAccounts: readonly string[];
|
|
982
|
+
};
|
|
983
|
+
|
|
984
|
+
/** A single Solana instruction matched by the indexer.
|
|
985
|
+
*
|
|
986
|
+
* `data` and discriminator prefixes are `0x`-prefixed hex strings; accounts
|
|
987
|
+
* are base58 strings. When a Borsh schema is configured (bundled, Anchor
|
|
988
|
+
* IDL, or hand-written YAML), `decoded` carries the named-accounts +
|
|
989
|
+
* decoded-args view. */
|
|
990
|
+
export type SvmInstruction = {
|
|
991
|
+
readonly programId: string;
|
|
992
|
+
readonly data: string;
|
|
993
|
+
readonly accounts: readonly string[];
|
|
994
|
+
readonly instructionAddress: readonly number[];
|
|
995
|
+
readonly isInner: boolean;
|
|
996
|
+
readonly d1?: string;
|
|
997
|
+
readonly d2?: string;
|
|
998
|
+
readonly d4?: string;
|
|
999
|
+
readonly d8?: string;
|
|
1000
|
+
readonly decoded?: SvmDecodedInstruction;
|
|
1001
|
+
};
|
|
1002
|
+
|
|
1003
|
+
/** Parent transaction surfaced when an instruction's
|
|
1004
|
+
* `include_transaction` flag is `true`. */
|
|
1005
|
+
export type SvmTransaction = {
|
|
1006
|
+
readonly signatures: readonly string[];
|
|
1007
|
+
readonly feePayer?: string;
|
|
1008
|
+
readonly success?: boolean;
|
|
1009
|
+
readonly err?: string;
|
|
1010
|
+
/** Lamports. */
|
|
1011
|
+
readonly fee?: bigint;
|
|
1012
|
+
readonly computeUnitsConsumed?: bigint;
|
|
1013
|
+
readonly accountKeys: readonly string[];
|
|
1014
|
+
readonly recentBlockhash?: string;
|
|
1015
|
+
readonly version?: string;
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
export type SvmLog = {
|
|
1019
|
+
readonly kind: string;
|
|
1020
|
+
readonly message: string;
|
|
1021
|
+
};
|
|
1022
|
+
|
|
1023
|
+
/** A single Solana instruction event delivered to a handler. Parameterised
|
|
1024
|
+
* over `Decoded` so the per-(program, instruction) overload of
|
|
1025
|
+
* `onInstruction` can narrow `event.instruction.decoded` to the
|
|
1026
|
+
* codegen-generated `{ args, accounts }` shape. */
|
|
1027
|
+
export type SvmInstructionEvent<
|
|
1028
|
+
Decoded extends SvmDecodedInstruction = SvmDecodedInstruction,
|
|
1029
|
+
> = {
|
|
1030
|
+
readonly contractName: string;
|
|
1031
|
+
readonly eventName: string;
|
|
1032
|
+
readonly instruction: Omit<SvmInstruction, "decoded"> & {
|
|
1033
|
+
readonly decoded?: Decoded;
|
|
1034
|
+
};
|
|
1035
|
+
/** Present when the instruction's `include_transaction` is `true`. */
|
|
1036
|
+
readonly transaction?: SvmTransaction;
|
|
1037
|
+
/** Present when the instruction's `include_logs` is `true`; only logs
|
|
1038
|
+
* scoped to this exact instruction (matching `instruction_address`). */
|
|
1039
|
+
readonly logs?: readonly SvmLog[];
|
|
1040
|
+
readonly slot: number;
|
|
1041
|
+
readonly blockTime?: number;
|
|
1042
|
+
};
|
|
1043
|
+
|
|
1044
|
+
/** Arguments passed to handlers registered via `indexer.onInstruction`. */
|
|
1045
|
+
export type SvmOnInstructionHandlerArgs<
|
|
1046
|
+
Config extends IndexerConfigTypes = GlobalConfig,
|
|
1047
|
+
Event extends SvmInstructionEvent = SvmInstructionEvent,
|
|
1048
|
+
> = {
|
|
1049
|
+
readonly event: Event;
|
|
1050
|
+
readonly context: SvmOnSlotContext<Config>;
|
|
1051
|
+
};
|
|
1052
|
+
|
|
1053
|
+
/** Shape extracted from `Global.config.svm.programs[P][I]`. The codegen
|
|
1054
|
+
* emits `{ args: ...; accounts: ... }` per (program, instruction); this
|
|
1055
|
+
* helper turns that into a `SvmDecodedInstruction`-compatible record. */
|
|
1056
|
+
type SvmDecodedFromProgramTable<TInstr> = TInstr extends {
|
|
1057
|
+
args: infer A;
|
|
1058
|
+
accounts: infer Acc extends Readonly<Record<string, string>>;
|
|
1059
|
+
}
|
|
1060
|
+
? {
|
|
1061
|
+
readonly name: string;
|
|
1062
|
+
readonly args: A;
|
|
1063
|
+
readonly accounts: Acc;
|
|
1064
|
+
readonly extraAccounts: readonly string[];
|
|
1065
|
+
}
|
|
1066
|
+
: SvmDecodedInstruction;
|
|
1067
|
+
|
|
1068
|
+
/** Options for an SVM `indexer.onInstruction` registration. */
|
|
1069
|
+
export type SvmOnInstructionOptions<P extends string = string, I extends string = string> = {
|
|
1070
|
+
/** Program name as declared under `chains[].programs[].name` in
|
|
1071
|
+
* `config.yaml`. */
|
|
1072
|
+
readonly program: P;
|
|
1073
|
+
/** Instruction name as declared under
|
|
1074
|
+
* `chains[].programs[].instructions[].name` in `config.yaml`. */
|
|
1075
|
+
readonly instruction: I;
|
|
1076
|
+
};
|
|
1077
|
+
|
|
1078
|
+
/** Handler function for an SVM `indexer.onInstruction` registration. */
|
|
1079
|
+
export type SvmOnInstructionHandler<
|
|
1080
|
+
Config extends IndexerConfigTypes = GlobalConfig,
|
|
1081
|
+
> = (args: SvmOnInstructionHandlerArgs<Config>) => Promise<void>;
|
|
1082
|
+
|
|
964
1083
|
// ============== Indexer Types ==============
|
|
965
1084
|
|
|
966
1085
|
// Helper: Check if an ecosystem is configured. Single-ecosystem indexers only
|
|
@@ -1138,7 +1257,7 @@ type FuelEcosystem<Config extends IndexerConfigTypes = GlobalConfig> =
|
|
|
1138
1257
|
: never
|
|
1139
1258
|
: never;
|
|
1140
1259
|
|
|
1141
|
-
// SVM ecosystem type — chains plus
|
|
1260
|
+
// SVM ecosystem type — chains plus instruction + slot handler methods.
|
|
1142
1261
|
type SvmEcosystem<Config extends IndexerConfigTypes = GlobalConfig> =
|
|
1143
1262
|
"svm" extends keyof Config
|
|
1144
1263
|
? Config["svm"] extends { chains: infer Chains }
|
|
@@ -1162,7 +1281,41 @@ type SvmEcosystem<Config extends IndexerConfigTypes = GlobalConfig> =
|
|
|
1162
1281
|
options: SvmOnSlotOptions<Config>,
|
|
1163
1282
|
handler: SvmOnSlotHandler<Config>,
|
|
1164
1283
|
) => void;
|
|
1284
|
+
} & (Config["svm"] extends {
|
|
1285
|
+
programs: infer Programs extends Record<string, Record<string, any>>;
|
|
1165
1286
|
}
|
|
1287
|
+
? {
|
|
1288
|
+
/**
|
|
1289
|
+
* Register an instruction handler. Dispatch matches on
|
|
1290
|
+
* `(programId, discriminator)` from the YAML config.
|
|
1291
|
+
* `event.instruction.decoded.args` and
|
|
1292
|
+
* `event.instruction.decoded.accounts` are typed from the
|
|
1293
|
+
* program's Borsh schema (Anchor IDL, bundled, or
|
|
1294
|
+
* hand-written `accounts`/`args` in YAML). `decoded` stays
|
|
1295
|
+
* optional at runtime because schema-matching can fail on
|
|
1296
|
+
* IDL drift or unknown discriminators.
|
|
1297
|
+
*/
|
|
1298
|
+
readonly onInstruction: <
|
|
1299
|
+
P extends keyof Programs & string,
|
|
1300
|
+
I extends keyof Programs[P] & string,
|
|
1301
|
+
>(
|
|
1302
|
+
options: SvmOnInstructionOptions<P, I>,
|
|
1303
|
+
handler: (
|
|
1304
|
+
args: SvmOnInstructionHandlerArgs<
|
|
1305
|
+
Config,
|
|
1306
|
+
SvmInstructionEvent<SvmDecodedFromProgramTable<Programs[P][I]>>
|
|
1307
|
+
>,
|
|
1308
|
+
) => Promise<void>,
|
|
1309
|
+
) => void;
|
|
1310
|
+
}
|
|
1311
|
+
: {
|
|
1312
|
+
/** Untyped fallback for indexers with no `programs` in
|
|
1313
|
+
* config. `decoded` stays the generic shape. */
|
|
1314
|
+
readonly onInstruction: (
|
|
1315
|
+
options: SvmOnInstructionOptions,
|
|
1316
|
+
handler: SvmOnInstructionHandler<Config>,
|
|
1317
|
+
) => void;
|
|
1318
|
+
})
|
|
1166
1319
|
: never
|
|
1167
1320
|
: never
|
|
1168
1321
|
: never;
|
|
@@ -1178,6 +1331,7 @@ type CodegenRequiredHint =
|
|
|
1178
1331
|
"Run 'envio codegen' to generate handler types from config.yaml. Without codegen, the indexer has no contracts, chains, or events to register handlers for.";
|
|
1179
1332
|
type CodegenRequiredFallback = {
|
|
1180
1333
|
readonly onEvent: (...hint: CodegenRequiredHint[]) => void;
|
|
1334
|
+
readonly onInstruction: (...hint: CodegenRequiredHint[]) => void;
|
|
1181
1335
|
readonly onBlock: (...hint: CodegenRequiredHint[]) => void;
|
|
1182
1336
|
readonly onSlot: (...hint: CodegenRequiredHint[]) => void;
|
|
1183
1337
|
readonly contractRegister: (...hint: CodegenRequiredHint[]) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "envio",
|
|
3
|
-
"version": "3.0.0",
|
|
3
|
+
"version": "3.0.2-svm-alpha.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
|
|
6
6
|
"bin": "./bin.mjs",
|
|
@@ -43,7 +43,6 @@
|
|
|
43
43
|
"@clickhouse/client": "1.17.0",
|
|
44
44
|
"@elastic/ecs-pino-format": "1.4.0",
|
|
45
45
|
"@envio-dev/hyperfuel-client": "1.2.2",
|
|
46
|
-
"@envio-dev/hypersync-client": "1.3.0",
|
|
47
46
|
"@fuel-ts/crypto": "0.96.1",
|
|
48
47
|
"@fuel-ts/errors": "0.96.1",
|
|
49
48
|
"@fuel-ts/hasher": "0.96.1",
|
|
@@ -71,10 +70,10 @@
|
|
|
71
70
|
"tsx": "4.21.0"
|
|
72
71
|
},
|
|
73
72
|
"optionalDependencies": {
|
|
74
|
-
"envio-linux-x64": "3.0.0",
|
|
75
|
-
"envio-linux-x64-musl": "3.0.0",
|
|
76
|
-
"envio-linux-arm64": "3.0.0",
|
|
77
|
-
"envio-darwin-x64": "3.0.0",
|
|
78
|
-
"envio-darwin-arm64": "3.0.0"
|
|
73
|
+
"envio-linux-x64": "3.0.2-svm-alpha.0",
|
|
74
|
+
"envio-linux-x64-musl": "3.0.2-svm-alpha.0",
|
|
75
|
+
"envio-linux-arm64": "3.0.2-svm-alpha.0",
|
|
76
|
+
"envio-darwin-x64": "3.0.2-svm-alpha.0",
|
|
77
|
+
"envio-darwin-arm64": "3.0.2-svm-alpha.0"
|
|
79
78
|
}
|
|
80
79
|
}
|
package/src/ChainFetcher.res
CHANGED
|
@@ -233,7 +233,31 @@ let make = (
|
|
|
233
233
|
~lowercaseAddresses,
|
|
234
234
|
)
|
|
235
235
|
| Config.FuelSourceConfig({hypersync}) => [HyperFuelSource.make({chain, endpointUrl: hypersync})]
|
|
236
|
-
| Config.SvmSourceConfig({rpc}) =>
|
|
236
|
+
| Config.SvmSourceConfig({hypersync, rpc}) =>
|
|
237
|
+
switch hypersync {
|
|
238
|
+
| None => [Svm.makeRPCSource(~chain, ~rpc)]
|
|
239
|
+
| Some(hypersyncUrl) =>
|
|
240
|
+
// HyperSync drives instruction sync; RPC remains the height oracle
|
|
241
|
+
// (Svm.makeRPCSource's `getFinalizedSlot` route) and the fallback.
|
|
242
|
+
let svmEventConfigs =
|
|
243
|
+
chainConfig.contracts
|
|
244
|
+
->Array.flatMap(contract => contract.events)
|
|
245
|
+
->(
|
|
246
|
+
Utils.magic: array<Internal.eventConfig> => array<Internal.svmInstructionEventConfig>
|
|
247
|
+
)
|
|
248
|
+
let apiToken = Env.envioApiToken
|
|
249
|
+
[
|
|
250
|
+
HyperSyncSolanaSource.make({
|
|
251
|
+
chain,
|
|
252
|
+
endpointUrl: hypersyncUrl,
|
|
253
|
+
apiToken,
|
|
254
|
+
eventConfigs: svmEventConfigs,
|
|
255
|
+
clientMaxRetries: Env.hyperSyncClientMaxRetries,
|
|
256
|
+
clientTimeoutMillis: Env.hyperSyncClientTimeoutMillis,
|
|
257
|
+
}),
|
|
258
|
+
Svm.makeRPCSource(~chain, ~rpc, ~sourceFor=Fallback),
|
|
259
|
+
]
|
|
260
|
+
}
|
|
237
261
|
// For tests: use ready-to-use sources directly
|
|
238
262
|
| Config.CustomSources(sources) => sources
|
|
239
263
|
}
|
package/src/ChainFetcher.res.mjs
CHANGED
|
@@ -21,6 +21,7 @@ import * as Stdlib_Promise from "@rescript/runtime/lib/es6/Stdlib_Promise.js";
|
|
|
21
21
|
import * as HyperFuelSource from "./sources/HyperFuelSource.res.mjs";
|
|
22
22
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
23
23
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
24
|
+
import * as HyperSyncSolanaSource from "./sources/HyperSyncSolanaSource.res.mjs";
|
|
24
25
|
import * as SafeCheckpointTracking from "./SafeCheckpointTracking.res.mjs";
|
|
25
26
|
|
|
26
27
|
function configAddresses(chainConfig) {
|
|
@@ -133,7 +134,24 @@ function make(chainConfig, indexingAddresses, startBlock, endBlock, firstEventBl
|
|
|
133
134
|
})];
|
|
134
135
|
break;
|
|
135
136
|
case "SvmSourceConfig" :
|
|
136
|
-
|
|
137
|
+
let rpc = sources.rpc;
|
|
138
|
+
let hypersync = sources.hypersync;
|
|
139
|
+
if (hypersync !== undefined) {
|
|
140
|
+
let svmEventConfigs = chainConfig.contracts.flatMap(contract => contract.events);
|
|
141
|
+
sources$1 = [
|
|
142
|
+
HyperSyncSolanaSource.make({
|
|
143
|
+
chain: chain,
|
|
144
|
+
endpointUrl: hypersync,
|
|
145
|
+
apiToken: Env.envioApiToken,
|
|
146
|
+
eventConfigs: svmEventConfigs,
|
|
147
|
+
clientMaxRetries: Env.hyperSyncClientMaxRetries,
|
|
148
|
+
clientTimeoutMillis: Env.hyperSyncClientTimeoutMillis
|
|
149
|
+
}),
|
|
150
|
+
Svm.makeRPCSource(chain, rpc, "Fallback")
|
|
151
|
+
];
|
|
152
|
+
} else {
|
|
153
|
+
sources$1 = [Svm.makeRPCSource(chain, rpc, undefined)];
|
|
154
|
+
}
|
|
137
155
|
break;
|
|
138
156
|
case "CustomSources" :
|
|
139
157
|
sources$1 = sources._0;
|
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
|
}
|