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/Core.res
CHANGED
|
@@ -4,9 +4,41 @@
|
|
|
4
4
|
|
|
5
5
|
// NAPI encodes Rust `Option<T>` as `null | T` (never `undefined`), so the
|
|
6
6
|
// tighter `Null.t` captures the exact boundary shape.
|
|
7
|
+
//
|
|
8
|
+
// Opaque carriers for the NAPI class constructors. Static factories
|
|
9
|
+
// (`newWithAgent`, `fromConfig`, `fromSignatures`) hang off these via `@send`
|
|
10
|
+
// in `HyperSyncClient.res` / `HyperSyncSolanaClient.res`.
|
|
11
|
+
type hypersyncClientCtor
|
|
12
|
+
type hypersyncSolanaClientCtor
|
|
13
|
+
type decoderCtor
|
|
14
|
+
|
|
15
|
+
/// JS shape of one decoded instruction. Mirrors `DecodedInstructionJson` in
|
|
16
|
+
/// `packages/cli/src/hypersync_source_svm/decoder.rs`. The `argsJson` /
|
|
17
|
+
/// `accountsJson` fields are stringified to side-step napi-rs's lack of
|
|
18
|
+
/// native `serde_json::Value` passthrough; callers `JSON.parse` once.
|
|
19
|
+
type svmDecodedInstruction = {
|
|
20
|
+
name: string,
|
|
21
|
+
argsJson: string,
|
|
22
|
+
accountsJson: string,
|
|
23
|
+
extraAccounts: array<string>,
|
|
24
|
+
}
|
|
25
|
+
|
|
7
26
|
type addon = {
|
|
8
27
|
getConfigJson: (~configPath: Null.t<string>, ~directory: Null.t<string>) => string,
|
|
9
28
|
runCli: (~args: array<string>, ~envioPackageDir: Null.t<string>) => promise<Null.t<string>>,
|
|
29
|
+
@as("HypersyncClient")
|
|
30
|
+
hypersyncClient: hypersyncClientCtor,
|
|
31
|
+
@as("HypersyncSolanaClient")
|
|
32
|
+
hypersyncSolanaClient: hypersyncSolanaClientCtor,
|
|
33
|
+
@as("Decoder")
|
|
34
|
+
decoder: decoderCtor,
|
|
35
|
+
setLogLevel: string => unit,
|
|
36
|
+
registerProgramSchema: (~descriptorJson: string) => int,
|
|
37
|
+
decodeInstruction: (
|
|
38
|
+
~schemaHandle: int,
|
|
39
|
+
~dataHex: string,
|
|
40
|
+
~accounts: array<string>,
|
|
41
|
+
) => Null.t<svmDecodedInstruction>,
|
|
10
42
|
}
|
|
11
43
|
|
|
12
44
|
@module("node:module") external createRequire: string => {..} = "createRequire"
|
package/src/Env.res.mjs
CHANGED
|
@@ -7,7 +7,6 @@ import * as Belt_Option from "@rescript/runtime/lib/es6/Belt_Option.js";
|
|
|
7
7
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
8
8
|
import * as HyperSyncClient from "./sources/HyperSyncClient.res.mjs";
|
|
9
9
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
10
|
-
import * as HypersyncClient from "@envio-dev/hypersync-client";
|
|
11
10
|
|
|
12
11
|
import 'dotenv/config'
|
|
13
12
|
;
|
|
@@ -62,7 +61,7 @@ let hypersyncClientEnableQueryCaching = EnvSafe.get(envSafe, "ENVIO_HYPERSYNC_CL
|
|
|
62
61
|
|
|
63
62
|
let hypersyncLogLevel = EnvSafe.get(envSafe, "ENVIO_HYPERSYNC_LOG_LEVEL", HyperSyncClient.logLevelSchema, undefined, "info", undefined, undefined);
|
|
64
63
|
|
|
65
|
-
|
|
64
|
+
HyperSyncClient.setLogLevel(hypersyncLogLevel);
|
|
66
65
|
|
|
67
66
|
let logStrategy = EnvSafe.get(envSafe, "LOG_STRATEGY", S$RescriptSchema.$$enum([
|
|
68
67
|
"ecs-file",
|
package/src/Envio.res
CHANGED
|
@@ -20,6 +20,100 @@ type svmOnSlotArgs<'context> = {
|
|
|
20
20
|
context: 'context,
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/** Borsh-decoded instruction view. Present whenever a `ProgramSchema` was
|
|
24
|
+
attached to the program (bundled schema, Anchor IDL, or hand-written YAML
|
|
25
|
+
`accounts`/`args`). Absent (`None`) when no schema applied or the
|
|
26
|
+
discriminator didn't match any registered instruction. */
|
|
27
|
+
type svmDecodedInstruction = {
|
|
28
|
+
/** Schema-declared instruction name (matches the codegen module suffix). */
|
|
29
|
+
name: string,
|
|
30
|
+
/** Borsh-decoded args. `JSON.Object({})` for no-arg instructions
|
|
31
|
+
(e.g. `VerifyCollection`). POC types this as raw `JSON.t`; cast at the
|
|
32
|
+
handler with `(json :> MyArgsType)` until typed codegen lands. */
|
|
33
|
+
args: JSON.t,
|
|
34
|
+
/** Named accounts in schema order. Keys are exactly the schema-declared
|
|
35
|
+
names; values are base58 pubkey strings. */
|
|
36
|
+
accounts: dict<string>,
|
|
37
|
+
/** Accounts beyond the schema's named list (Anchor `remaining_accounts`,
|
|
38
|
+
IDL drift). `[]` when counts match. */
|
|
39
|
+
extraAccounts: array<string>,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type svmInstruction = {
|
|
43
|
+
programId: SvmTypes.Pubkey.t,
|
|
44
|
+
/** Raw instruction bytes as `0x`-prefixed hex. */
|
|
45
|
+
data: string,
|
|
46
|
+
accounts: array<SvmTypes.Pubkey.t>,
|
|
47
|
+
/** Path through the call tree: `[outerIndex]` for top-level instructions,
|
|
48
|
+
appended child indices for inner CPI calls. */
|
|
49
|
+
instructionAddress: array<int>,
|
|
50
|
+
isInner: bool,
|
|
51
|
+
/** Discriminator prefixes pre-extracted by HyperSync. Each is `Some` only
|
|
52
|
+
when the underlying instruction is at least that long. */
|
|
53
|
+
d1?: string,
|
|
54
|
+
d2?: string,
|
|
55
|
+
d4?: string,
|
|
56
|
+
d8?: string,
|
|
57
|
+
/** Borsh-decoded view. See [[svmDecodedInstruction]]. */
|
|
58
|
+
decoded?: svmDecodedInstruction,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type svmTransaction = {
|
|
62
|
+
signatures: array<string>,
|
|
63
|
+
feePayer?: SvmTypes.Pubkey.t,
|
|
64
|
+
success?: bool,
|
|
65
|
+
err?: string,
|
|
66
|
+
fee?: bigint,
|
|
67
|
+
computeUnitsConsumed?: bigint,
|
|
68
|
+
accountKeys: array<SvmTypes.Pubkey.t>,
|
|
69
|
+
recentBlockhash?: string,
|
|
70
|
+
version?: string,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type svmLog = {
|
|
74
|
+
kind: string,
|
|
75
|
+
message: string,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Inner block record on `svmInstructionEvent`. Field names follow EVM/Fuel
|
|
79
|
+
(`height`, `time`, `hash`) so the shared `Ecosystem.t` getters in
|
|
80
|
+
`Svm.res` work uniformly across ecosystems — `height` carries the slot. */
|
|
81
|
+
type svmInstructionEventBlock = {
|
|
82
|
+
/** Slot number. Named `height` so the shared ecosystem getter reads it. */
|
|
83
|
+
height: int,
|
|
84
|
+
/** Unix block time (seconds). `0` when HyperSync didn't return a block
|
|
85
|
+
for this instruction's slot. */
|
|
86
|
+
time: int,
|
|
87
|
+
/** Block hash. Currently always empty — populated by the future
|
|
88
|
+
reorg-guard `queryBlockHash(slot)` route. */
|
|
89
|
+
hash: string,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The per-instruction payload handlers receive on `.event`. Mirrors the
|
|
93
|
+
EVM `type event` shape inside generated per-event modules. */
|
|
94
|
+
type svmInstructionEvent = {
|
|
95
|
+
contractName: string,
|
|
96
|
+
eventName: string,
|
|
97
|
+
instruction: svmInstruction,
|
|
98
|
+
/** Parent transaction. `None` when the per-instruction
|
|
99
|
+
`include_transaction` flag is `false`. */
|
|
100
|
+
transaction: option<svmTransaction>,
|
|
101
|
+
/** Program log entries scoped to this instruction. `None` when the
|
|
102
|
+
per-instruction `include_logs` flag is `false`. */
|
|
103
|
+
logs: option<array<svmLog>>,
|
|
104
|
+
/** Convenience alias for `block.height`. */
|
|
105
|
+
slot: int,
|
|
106
|
+
/** Convenience alias for `block.time`. */
|
|
107
|
+
blockTime: option<int>,
|
|
108
|
+
block: svmInstructionEventBlock,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Arguments passed to handlers registered via `indexer.onInstruction`. */
|
|
112
|
+
type svmOnInstructionArgs<'context> = {
|
|
113
|
+
event: svmInstructionEvent,
|
|
114
|
+
context: 'context,
|
|
115
|
+
}
|
|
116
|
+
|
|
23
117
|
// Internal-only type for the `indexer.onBlock` (and SVM `onSlot`) `where`
|
|
24
118
|
// callback argument. The canonical TypeScript shape lives in
|
|
25
119
|
// `packages/envio/index.d.ts`; the ReScript declaration here is free to
|
|
@@ -484,6 +484,56 @@ let buildEvmEventConfig = (
|
|
|
484
484
|
|
|
485
485
|
// ============== Build Fuel event config ==============
|
|
486
486
|
|
|
487
|
+
let buildSvmInstructionEventConfig = (
|
|
488
|
+
~contractName: string,
|
|
489
|
+
~instructionName: string,
|
|
490
|
+
~programId: SvmTypes.Pubkey.t,
|
|
491
|
+
~discriminator: option<string>,
|
|
492
|
+
~discriminatorByteLen: int,
|
|
493
|
+
~includeTransaction: bool,
|
|
494
|
+
~includeLogs: bool,
|
|
495
|
+
~accountFilters: array<Internal.svmAccountFilter>,
|
|
496
|
+
~isInner: option<bool>,
|
|
497
|
+
~isWildcard: bool,
|
|
498
|
+
~handler: option<Internal.handler>,
|
|
499
|
+
~contractRegister: option<Internal.contractRegister>,
|
|
500
|
+
~accounts: array<string>=[],
|
|
501
|
+
~args: JSON.t=JSON.Null,
|
|
502
|
+
~definedTypes: JSON.t=JSON.Null,
|
|
503
|
+
~startBlock: option<int>=?,
|
|
504
|
+
): Internal.svmInstructionEventConfig => {
|
|
505
|
+
let paramsSchema =
|
|
506
|
+
S.json(~validate=false)
|
|
507
|
+
->Utils.Schema.coerceToJsonPgType
|
|
508
|
+
->(Utils.magic: S.t<JSON.t> => S.t<Internal.eventParams>)
|
|
509
|
+
{
|
|
510
|
+
id: switch discriminator {
|
|
511
|
+
| Some(d) => d
|
|
512
|
+
| None => "none"
|
|
513
|
+
},
|
|
514
|
+
name: instructionName,
|
|
515
|
+
contractName,
|
|
516
|
+
isWildcard,
|
|
517
|
+
handler,
|
|
518
|
+
contractRegister,
|
|
519
|
+
paramsRawEventSchema: paramsSchema,
|
|
520
|
+
simulateParamsSchema: paramsSchema,
|
|
521
|
+
filterByAddresses: false,
|
|
522
|
+
dependsOnAddresses: !isWildcard,
|
|
523
|
+
startBlock,
|
|
524
|
+
programId,
|
|
525
|
+
discriminator,
|
|
526
|
+
discriminatorByteLen,
|
|
527
|
+
includeTransaction,
|
|
528
|
+
includeLogs,
|
|
529
|
+
accountFilters,
|
|
530
|
+
isInner,
|
|
531
|
+
accounts,
|
|
532
|
+
args,
|
|
533
|
+
definedTypes,
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
487
537
|
let buildFuelEventConfig = (
|
|
488
538
|
~contractName: string,
|
|
489
539
|
~eventName: string,
|
|
@@ -350,6 +350,36 @@ function buildEvmEventConfig(contractName, eventName, sighash, params, isWildcar
|
|
|
350
350
|
};
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
+
function buildSvmInstructionEventConfig(contractName, instructionName, programId, discriminator, discriminatorByteLen, includeTransaction, includeLogs, accountFilters, isInner, isWildcard, handler, contractRegister, accountsOpt, argsOpt, definedTypesOpt, startBlock) {
|
|
354
|
+
let accounts = accountsOpt !== undefined ? accountsOpt : [];
|
|
355
|
+
let args = argsOpt !== undefined ? argsOpt : null;
|
|
356
|
+
let definedTypes = definedTypesOpt !== undefined ? definedTypesOpt : null;
|
|
357
|
+
let paramsSchema = Utils.Schema.coerceToJsonPgType(S$RescriptSchema.json(false));
|
|
358
|
+
return {
|
|
359
|
+
id: discriminator !== undefined ? discriminator : "none",
|
|
360
|
+
name: instructionName,
|
|
361
|
+
contractName: contractName,
|
|
362
|
+
isWildcard: isWildcard,
|
|
363
|
+
filterByAddresses: false,
|
|
364
|
+
dependsOnAddresses: !isWildcard,
|
|
365
|
+
handler: handler,
|
|
366
|
+
contractRegister: contractRegister,
|
|
367
|
+
paramsRawEventSchema: paramsSchema,
|
|
368
|
+
simulateParamsSchema: paramsSchema,
|
|
369
|
+
startBlock: startBlock,
|
|
370
|
+
programId: programId,
|
|
371
|
+
discriminator: discriminator,
|
|
372
|
+
discriminatorByteLen: discriminatorByteLen,
|
|
373
|
+
includeTransaction: includeTransaction,
|
|
374
|
+
includeLogs: includeLogs,
|
|
375
|
+
accountFilters: accountFilters,
|
|
376
|
+
isInner: isInner,
|
|
377
|
+
accounts: accounts,
|
|
378
|
+
args: args,
|
|
379
|
+
definedTypes: definedTypes
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
353
383
|
function buildFuelEventConfig(contractName, eventName, kind, sighash, rawAbi, isWildcard, handler, contractRegister, startBlock) {
|
|
354
384
|
let fuelKind;
|
|
355
385
|
switch (kind) {
|
|
@@ -428,6 +458,7 @@ export {
|
|
|
428
458
|
alwaysIncludedBlockFields,
|
|
429
459
|
resolveFieldSelection,
|
|
430
460
|
buildEvmEventConfig,
|
|
461
|
+
buildSvmInstructionEventConfig,
|
|
431
462
|
buildFuelEventConfig,
|
|
432
463
|
}
|
|
433
464
|
/* eventParamComponentSchema Not a pure module */
|
package/src/HandlerLoader.res
CHANGED
|
@@ -142,7 +142,18 @@ let applyRegistrations = (~config: Config.t): Config.t => {
|
|
|
142
142
|
dependsOnAddresses: Internal.dependsOnAddresses(~isWildcard, ~filterByAddresses),
|
|
143
143
|
} :> Internal.eventConfig)
|
|
144
144
|
| Svm =>
|
|
145
|
-
|
|
145
|
+
let svmEv =
|
|
146
|
+
ev->(Utils.magic: Internal.eventConfig => Internal.svmInstructionEventConfig)
|
|
147
|
+
({
|
|
148
|
+
...svmEv,
|
|
149
|
+
isWildcard,
|
|
150
|
+
handler,
|
|
151
|
+
contractRegister,
|
|
152
|
+
dependsOnAddresses: Internal.dependsOnAddresses(
|
|
153
|
+
~isWildcard,
|
|
154
|
+
~filterByAddresses=false,
|
|
155
|
+
),
|
|
156
|
+
} :> Internal.eventConfig)
|
|
146
157
|
}
|
|
147
158
|
},
|
|
148
159
|
)
|
|
@@ -109,7 +109,12 @@ function applyRegistrations(config) {
|
|
|
109
109
|
kind: ev.kind
|
|
110
110
|
};
|
|
111
111
|
case "svm" :
|
|
112
|
-
|
|
112
|
+
let newrecord = {...ev};
|
|
113
|
+
newrecord.contractRegister = contractRegister;
|
|
114
|
+
newrecord.handler = handler;
|
|
115
|
+
newrecord.dependsOnAddresses = Internal.dependsOnAddresses(isWildcard, false);
|
|
116
|
+
newrecord.isWildcard = isWildcard;
|
|
117
|
+
return newrecord;
|
|
113
118
|
}
|
|
114
119
|
});
|
|
115
120
|
return {
|
package/src/Internal.res
CHANGED
|
@@ -422,6 +422,44 @@ type evmContractConfig = {
|
|
|
422
422
|
events: array<evmEventConfig>,
|
|
423
423
|
}
|
|
424
424
|
|
|
425
|
+
type svmAccountFilter = {
|
|
426
|
+
position: int,
|
|
427
|
+
values: array<SvmTypes.Pubkey.t>,
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
type svmInstructionEventConfig = {
|
|
431
|
+
...eventConfig,
|
|
432
|
+
/** Base58 Solana program id this instruction belongs to. */
|
|
433
|
+
programId: SvmTypes.Pubkey.t,
|
|
434
|
+
/** Hex-encoded discriminator. `None` matches every instruction in the program. */
|
|
435
|
+
discriminator: option<string>,
|
|
436
|
+
/** Length of the discriminator in bytes (0 / 1 / 2 / 4 / 8). Drives the
|
|
437
|
+
`dN` selector at query time and the dispatch-key precomputation in the
|
|
438
|
+
router. */
|
|
439
|
+
discriminatorByteLen: int,
|
|
440
|
+
includeTransaction: bool,
|
|
441
|
+
includeLogs: bool,
|
|
442
|
+
accountFilters: array<svmAccountFilter>,
|
|
443
|
+
/** `None` matches both outer and inner (CPI-invoked) instructions. */
|
|
444
|
+
isInner: option<bool>,
|
|
445
|
+
/** Positional account names from the Borsh schema, in declared order.
|
|
446
|
+
`[]` means no schema is attached for this instruction. */
|
|
447
|
+
accounts: array<string>,
|
|
448
|
+
/** Borsh args layout as `Vec<ArgDef>` JSON (see `human_config::svm::ArgDef`
|
|
449
|
+
on the Rust side). `JSON.Null` means no schema is attached. */
|
|
450
|
+
args: JSON.t,
|
|
451
|
+
/** Program-level nominal-type registry (`BTreeMap<String, ArgType>` JSON).
|
|
452
|
+
Duplicated on every event of the same program — the runtime dedups by
|
|
453
|
+
`programId` when registering. `JSON.Null` when empty. */
|
|
454
|
+
definedTypes: JSON.t,
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
type svmProgramConfig = {
|
|
458
|
+
name: string,
|
|
459
|
+
programId: SvmTypes.Pubkey.t,
|
|
460
|
+
instructions: array<svmInstructionEventConfig>,
|
|
461
|
+
}
|
|
462
|
+
|
|
425
463
|
type indexingAddress = {
|
|
426
464
|
address: Address.t,
|
|
427
465
|
contractName: string,
|
package/src/Main.res
CHANGED
|
@@ -298,6 +298,57 @@ let getGlobalIndexer = (): 'indexer => {
|
|
|
298
298
|
)
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
+
// SVM identity: `{program, instruction}` from TS or
|
|
302
|
+
// `{instruction: GADT{contract, _0}}` from ReScript. Same two-format dance
|
|
303
|
+
// as the EVM `parseIdentityConfig`, but reading the SVM-native field names.
|
|
304
|
+
let parseSvmIdentityConfig = (identityConfig: 'a) => {
|
|
305
|
+
let raw =
|
|
306
|
+
identityConfig->(
|
|
307
|
+
Utils.magic: 'a => {
|
|
308
|
+
"program": unknown,
|
|
309
|
+
"instruction": unknown,
|
|
310
|
+
"where": option<JSON.t>,
|
|
311
|
+
}
|
|
312
|
+
)
|
|
313
|
+
let (programName, instructionName) = if typeof(raw["program"]) === #string {
|
|
314
|
+
(
|
|
315
|
+
raw["program"]->(Utils.magic: unknown => string),
|
|
316
|
+
raw["instruction"]->(Utils.magic: unknown => string),
|
|
317
|
+
)
|
|
318
|
+
} else {
|
|
319
|
+
let inst =
|
|
320
|
+
raw["instruction"]->(Utils.magic: unknown => {"contract": string, "_0": string})
|
|
321
|
+
(inst["contract"], inst["_0"])
|
|
322
|
+
}
|
|
323
|
+
let where = raw["where"]
|
|
324
|
+
let eventOptions: option<Internal.eventOptions<_>> = switch where {
|
|
325
|
+
| None => None
|
|
326
|
+
| Some(_) =>
|
|
327
|
+
Some({
|
|
328
|
+
where: ?(where->(Utils.magic: option<JSON.t> => option<_>)),
|
|
329
|
+
})
|
|
330
|
+
}
|
|
331
|
+
(programName, instructionName, eventOptions)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// onInstruction: delegates to HandlerRegister.setHandler. The SVM analog of
|
|
335
|
+
// onEvent; the registration store keys on `(contractName, eventName)` which
|
|
336
|
+
// for SVM is `(programName, instructionName)`.
|
|
337
|
+
let onInstructionFn = (identityConfig: 'a, handler: 'b) => {
|
|
338
|
+
HandlerRegister.throwIfFinishedRegistration(~methodName="onInstruction")
|
|
339
|
+
let (programName, instructionName, eventOptions) = parseSvmIdentityConfig(identityConfig)
|
|
340
|
+
HandlerRegister.setHandler(
|
|
341
|
+
~contractName=programName,
|
|
342
|
+
~eventName=instructionName,
|
|
343
|
+
handler->(
|
|
344
|
+
Utils.magic: 'b => Internal.genericHandler<
|
|
345
|
+
Internal.genericHandlerArgs<Internal.event, Internal.handlerContext>,
|
|
346
|
+
>
|
|
347
|
+
),
|
|
348
|
+
~eventOptions,
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
|
|
301
352
|
// contractRegister: delegates to HandlerRegister.setContractRegister
|
|
302
353
|
let contractRegisterFn = (identityConfig: 'a, handler: 'b) => {
|
|
303
354
|
HandlerRegister.throwIfFinishedRegistration(~methodName="contractRegister")
|
|
@@ -456,7 +507,7 @@ let getGlobalIndexer = (): 'indexer => {
|
|
|
456
507
|
"contractRegister",
|
|
457
508
|
"onBlock",
|
|
458
509
|
]
|
|
459
|
-
| Svm => ["name", "description", "chainIds", "chains", "onSlot"]
|
|
510
|
+
| Svm => ["name", "description", "chainIds", "chains", "onInstruction", "onSlot"]
|
|
460
511
|
}
|
|
461
512
|
keysMemo := Some(keys)
|
|
462
513
|
keys
|
|
@@ -477,6 +528,7 @@ let getGlobalIndexer = (): 'indexer => {
|
|
|
477
528
|
chains->(Utils.magic: {..} => unknown)
|
|
478
529
|
}
|
|
479
530
|
| "onEvent" => onEventFn->Utils.magic
|
|
531
|
+
| "onInstruction" => onInstructionFn->Utils.magic
|
|
480
532
|
| "contractRegister" => contractRegisterFn->Utils.magic
|
|
481
533
|
| "onBlock" | "onSlot" => onBlockFn->Utils.magic
|
|
482
534
|
| _ =>
|
|
@@ -622,7 +674,6 @@ let migrate = async (~reset) => {
|
|
|
622
674
|
~chainConfigs=config.chainMap->ChainMap.values,
|
|
623
675
|
~envioInfo=getEnvioInfo(),
|
|
624
676
|
~resetCommand="envio local db-migrate setup",
|
|
625
|
-
~runCommand=None,
|
|
626
677
|
)
|
|
627
678
|
await persistence.storage.close()
|
|
628
679
|
}
|
|
@@ -669,7 +720,6 @@ let start = async (
|
|
|
669
720
|
~chainConfigs=configWithoutRegistrations.chainMap->ChainMap.values,
|
|
670
721
|
~envioInfo=getEnvioInfo(),
|
|
671
722
|
~resetCommand=isDevelopmentMode ? "envio dev -r" : "envio start -r",
|
|
672
|
-
~runCommand=Some(isDevelopmentMode ? "envio dev" : "envio start"),
|
|
673
723
|
)
|
|
674
724
|
|
|
675
725
|
// `Config.loadWithoutRegistrations` never sees registration state; handler,
|
package/src/Main.res.mjs
CHANGED
|
@@ -249,6 +249,35 @@ function getGlobalIndexer() {
|
|
|
249
249
|
let match = parseIdentityConfig(identityConfig);
|
|
250
250
|
HandlerRegister.setHandler(match[0], match[1], handler, match[2], undefined);
|
|
251
251
|
};
|
|
252
|
+
let parseSvmIdentityConfig = identityConfig => {
|
|
253
|
+
let match;
|
|
254
|
+
if (typeof identityConfig.program === "string") {
|
|
255
|
+
match = [
|
|
256
|
+
identityConfig.program,
|
|
257
|
+
identityConfig.instruction
|
|
258
|
+
];
|
|
259
|
+
} else {
|
|
260
|
+
let inst = identityConfig.instruction;
|
|
261
|
+
match = [
|
|
262
|
+
inst.contract,
|
|
263
|
+
inst._0
|
|
264
|
+
];
|
|
265
|
+
}
|
|
266
|
+
let where = identityConfig.where;
|
|
267
|
+
let eventOptions = where !== undefined ? ({
|
|
268
|
+
where: where
|
|
269
|
+
}) : undefined;
|
|
270
|
+
return [
|
|
271
|
+
match[0],
|
|
272
|
+
match[1],
|
|
273
|
+
eventOptions
|
|
274
|
+
];
|
|
275
|
+
};
|
|
276
|
+
let onInstructionFn = (identityConfig, handler) => {
|
|
277
|
+
HandlerRegister.throwIfFinishedRegistration("onInstruction");
|
|
278
|
+
let match = parseSvmIdentityConfig(identityConfig);
|
|
279
|
+
HandlerRegister.setHandler(match[0], match[1], handler, match[2], undefined);
|
|
280
|
+
};
|
|
252
281
|
let contractRegisterFn = (identityConfig, handler) => {
|
|
253
282
|
HandlerRegister.throwIfFinishedRegistration("contractRegister");
|
|
254
283
|
let match = parseIdentityConfig(identityConfig);
|
|
@@ -340,6 +369,7 @@ function getGlobalIndexer() {
|
|
|
340
369
|
"description",
|
|
341
370
|
"chainIds",
|
|
342
371
|
"chains",
|
|
372
|
+
"onInstruction",
|
|
343
373
|
"onSlot"
|
|
344
374
|
];
|
|
345
375
|
break;
|
|
@@ -372,6 +402,8 @@ function getGlobalIndexer() {
|
|
|
372
402
|
return Config.loadWithoutRegistrations().name;
|
|
373
403
|
case "onEvent" :
|
|
374
404
|
return onEventFn;
|
|
405
|
+
case "onInstruction" :
|
|
406
|
+
return onInstructionFn;
|
|
375
407
|
case "onBlock" :
|
|
376
408
|
case "onSlot" :
|
|
377
409
|
return onBlockFn;
|
|
@@ -472,7 +504,7 @@ function getEnvioInfo() {
|
|
|
472
504
|
async function migrate(reset) {
|
|
473
505
|
let config = Config.loadWithoutRegistrations();
|
|
474
506
|
let persistence = PgStorage.makePersistenceFromConfig(config, undefined);
|
|
475
|
-
await Persistence.init(persistence, ChainMap.values(config.chainMap), Config.stripSensitiveData(Config.getPublicConfigJson()), "envio local db-migrate setup",
|
|
507
|
+
await Persistence.init(persistence, ChainMap.values(config.chainMap), Config.stripSensitiveData(Config.getPublicConfigJson()), "envio local db-migrate setup", reset);
|
|
476
508
|
return await persistence.storage.close();
|
|
477
509
|
}
|
|
478
510
|
|
|
@@ -497,7 +529,7 @@ async function start(persistence, resetOpt, isTestOpt, exitAfterFirstEventBlockO
|
|
|
497
529
|
let isDevelopmentMode = !isTest && configWithoutRegistrations.isDev;
|
|
498
530
|
let persistence$1 = persistence !== undefined ? persistence : PgStorage.makePersistenceFromConfig(configWithoutRegistrations, undefined);
|
|
499
531
|
globalPersistenceRef.contents = persistence$1;
|
|
500
|
-
await Persistence.init(persistence$1, ChainMap.values(configWithoutRegistrations.chainMap), Config.stripSensitiveData(Config.getPublicConfigJson()), isDevelopmentMode ? "envio dev -r" : "envio start -r",
|
|
532
|
+
await Persistence.init(persistence$1, ChainMap.values(configWithoutRegistrations.chainMap), Config.stripSensitiveData(Config.getPublicConfigJson()), isDevelopmentMode ? "envio dev -r" : "envio start -r", reset);
|
|
501
533
|
let match = await HandlerLoader.registerAllHandlers(configWithoutRegistrations);
|
|
502
534
|
let registrations = match[1];
|
|
503
535
|
let config = match[0];
|
package/src/Persistence.res
CHANGED
|
@@ -168,7 +168,7 @@ let make = (
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
let init = {
|
|
171
|
-
async (persistence, ~chainConfigs, ~envioInfo, ~resetCommand, ~
|
|
171
|
+
async (persistence, ~chainConfigs, ~envioInfo, ~resetCommand, ~reset=false) => {
|
|
172
172
|
try {
|
|
173
173
|
let shouldRun = switch persistence.storageStatus {
|
|
174
174
|
| Unknown => true
|
|
@@ -212,22 +212,7 @@ let init = {
|
|
|
212
212
|
| None => ["envio info is missing — storage initialized by an older envio"]
|
|
213
213
|
| Some(stored) => Config.diffPaths(~stored, ~current=envioInfo)
|
|
214
214
|
}
|
|
215
|
-
|
|
216
|
-
// public config (see Rust `StorageConfig`), so probe for
|
|
217
|
-
// `Boolean(true)`, not an object.
|
|
218
|
-
let hasClickhouse = switch envioInfo {
|
|
219
|
-
| Object(d) =>
|
|
220
|
-
switch d->Dict.get("storage") {
|
|
221
|
-
| Some(Object(s)) =>
|
|
222
|
-
switch s->Dict.get("clickhouse") {
|
|
223
|
-
| Some(Boolean(true)) => true
|
|
224
|
-
| _ => false
|
|
225
|
-
}
|
|
226
|
-
| _ => false
|
|
227
|
-
}
|
|
228
|
-
| _ => false
|
|
229
|
-
}
|
|
230
|
-
Config.throwIfIncompatible(changedPaths, ~resetCommand, ~runCommand, ~hasClickhouse)
|
|
215
|
+
Config.throwIfIncompatible(changedPaths, ~resetCommand)
|
|
231
216
|
persistence.storageStatus = Ready(initialState)
|
|
232
217
|
let progress = Dict.make()
|
|
233
218
|
initialState.chains->Array.forEach(c => {
|
package/src/Persistence.res.mjs
CHANGED
|
@@ -27,7 +27,7 @@ function make(userEntities, allEnums, storage) {
|
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
async function init(persistence, chainConfigs, envioInfo, resetCommand,
|
|
30
|
+
async function init(persistence, chainConfigs, envioInfo, resetCommand, resetOpt) {
|
|
31
31
|
let reset = resetOpt !== undefined ? resetOpt : false;
|
|
32
32
|
try {
|
|
33
33
|
let promise = persistence.storageStatus;
|
|
@@ -70,19 +70,7 @@ async function init(persistence, chainConfigs, envioInfo, resetCommand, runComma
|
|
|
70
70
|
let initialState$1 = await persistence.storage.resumeInitialState();
|
|
71
71
|
let stored = initialState$1.envioInfo;
|
|
72
72
|
let changedPaths = stored !== undefined ? Config.diffPaths(stored, envioInfo) : ["envio info is missing — storage initialized by an older envio"];
|
|
73
|
-
|
|
74
|
-
if (typeof envioInfo === "object" && envioInfo !== null && !Array.isArray(envioInfo)) {
|
|
75
|
-
let match$1 = envioInfo["storage"];
|
|
76
|
-
if (typeof match$1 === "object" && match$1 !== null && !Array.isArray(match$1)) {
|
|
77
|
-
let match$2 = match$1["clickhouse"];
|
|
78
|
-
hasClickhouse = match$2 === true;
|
|
79
|
-
} else {
|
|
80
|
-
hasClickhouse = false;
|
|
81
|
-
}
|
|
82
|
-
} else {
|
|
83
|
-
hasClickhouse = false;
|
|
84
|
-
}
|
|
85
|
-
Config.throwIfIncompatible(changedPaths, resetCommand, runCommand, hasClickhouse);
|
|
73
|
+
Config.throwIfIncompatible(changedPaths, resetCommand);
|
|
86
74
|
persistence.storageStatus = {
|
|
87
75
|
TAG: "Ready",
|
|
88
76
|
_0: initialState$1
|
package/src/SimulateItems.res
CHANGED
|
@@ -320,21 +320,34 @@ let patchConfig = (~config: Config.t, ~processConfig: JSON.t): Config.t => {
|
|
|
320
320
|
let chainIdStr = chain->ChainMap.Chain.toChainId->Int.toString
|
|
321
321
|
switch chainsDict->Dict.get(chainIdStr) {
|
|
322
322
|
| Some(processChainJson) =>
|
|
323
|
-
let
|
|
324
|
-
|
|
323
|
+
let raw = processChainJson->(Utils.magic: JSON.t => {..})
|
|
324
|
+
let simulateRaw: option<array<JSON.t>> = raw["simulate"]->Nullable.toOption
|
|
325
325
|
switch simulateRaw {
|
|
326
326
|
| Some(simulateItems) =>
|
|
327
327
|
let items = parse(~simulateItems, ~config, ~chainConfig)
|
|
328
|
-
|
|
329
|
-
let
|
|
330
|
-
(processChainJson->(Utils.magic: JSON.t => {..}))["startBlock"]->(
|
|
331
|
-
Utils.magic: 'a => int
|
|
332
|
-
)
|
|
333
|
-
let endBlock: int =
|
|
334
|
-
(processChainJson->(Utils.magic: JSON.t => {..}))["endBlock"]->(Utils.magic: 'a => int)
|
|
328
|
+
let startBlock: int = raw["startBlock"]->(Utils.magic: 'a => int)
|
|
329
|
+
let endBlock: int = raw["endBlock"]->(Utils.magic: 'a => int)
|
|
335
330
|
let source = SimulateSource.make(~items, ~endBlock, ~chain)
|
|
336
331
|
{...chainConfig, startBlock, endBlock, sourceConfig: Config.CustomSources([source])}
|
|
337
|
-
| None =>
|
|
332
|
+
| None =>
|
|
333
|
+
// No simulate items: still honor `startBlock` / `endBlock` overrides
|
|
334
|
+
// so non-EVM/Fuel ecosystems (notably SVM `indexer.onInstruction`,
|
|
335
|
+
// which doesn't support simulate items) can bound the run window
|
|
336
|
+
// via `testIndexer.process({chains: { id: {startBlock, endBlock} }})`.
|
|
337
|
+
let startBlockOpt: option<int> =
|
|
338
|
+
raw["startBlock"]
|
|
339
|
+
->(Utils.magic: 'a => Nullable.t<int>)
|
|
340
|
+
->Nullable.toOption
|
|
341
|
+
let endBlockOpt: option<int> =
|
|
342
|
+
raw["endBlock"]->(Utils.magic: 'a => Nullable.t<int>)->Nullable.toOption
|
|
343
|
+
let withStart = switch startBlockOpt {
|
|
344
|
+
| Some(sb) => {...chainConfig, startBlock: sb}
|
|
345
|
+
| None => chainConfig
|
|
346
|
+
}
|
|
347
|
+
switch endBlockOpt {
|
|
348
|
+
| Some(eb) => {...withStart, endBlock: eb}
|
|
349
|
+
| None => withStart
|
|
350
|
+
}
|
|
338
351
|
}
|
|
339
352
|
| None => chainConfig
|
|
340
353
|
}
|
|
@@ -266,20 +266,35 @@ function patchConfig(config, processConfig) {
|
|
|
266
266
|
}
|
|
267
267
|
let simulateRaw = processChainJson.simulate;
|
|
268
268
|
if (simulateRaw == null) {
|
|
269
|
-
|
|
269
|
+
let startBlockOpt = processChainJson.startBlock;
|
|
270
|
+
let endBlockOpt = processChainJson.endBlock;
|
|
271
|
+
let withStart;
|
|
272
|
+
if (startBlockOpt == null) {
|
|
273
|
+
withStart = chainConfig;
|
|
274
|
+
} else {
|
|
275
|
+
let newrecord = {...chainConfig};
|
|
276
|
+
newrecord.startBlock = startBlockOpt;
|
|
277
|
+
withStart = newrecord;
|
|
278
|
+
}
|
|
279
|
+
if (endBlockOpt == null) {
|
|
280
|
+
return withStart;
|
|
281
|
+
}
|
|
282
|
+
let newrecord$1 = {...withStart};
|
|
283
|
+
newrecord$1.endBlock = endBlockOpt;
|
|
284
|
+
return newrecord$1;
|
|
270
285
|
}
|
|
271
286
|
let items = parse(simulateRaw, config, chainConfig);
|
|
272
287
|
let startBlock = processChainJson.startBlock;
|
|
273
288
|
let endBlock = processChainJson.endBlock;
|
|
274
289
|
let source = SimulateSource.make(items, endBlock, chain);
|
|
275
|
-
let newrecord = {...chainConfig};
|
|
276
|
-
newrecord.sourceConfig = {
|
|
290
|
+
let newrecord$2 = {...chainConfig};
|
|
291
|
+
newrecord$2.sourceConfig = {
|
|
277
292
|
TAG: "CustomSources",
|
|
278
293
|
_0: [source]
|
|
279
294
|
};
|
|
280
|
-
newrecord.endBlock = endBlock;
|
|
281
|
-
newrecord.startBlock = startBlock;
|
|
282
|
-
return newrecord;
|
|
295
|
+
newrecord$2.endBlock = endBlock;
|
|
296
|
+
newrecord$2.startBlock = startBlock;
|
|
297
|
+
return newrecord$2;
|
|
283
298
|
});
|
|
284
299
|
let newrecord = {...config};
|
|
285
300
|
newrecord.chainMap = newChainMap;
|
package/src/SvmTypes.res
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module Pubkey = {
|
|
2
|
+
type t
|
|
3
|
+
let schema =
|
|
4
|
+
S.string->S.setName("SVM.Pubkey")->(Utils.magic: S.t<string> => S.t<t>)
|
|
5
|
+
external fromStringUnsafe: string => t = "%identity"
|
|
6
|
+
external fromStringsUnsafe: array<string> => array<t> = "%identity"
|
|
7
|
+
external toString: t => string = "%identity"
|
|
8
|
+
external toStrings: array<t> => array<string> = "%identity"
|
|
9
|
+
}
|