envio 3.7.0 → 3.9.0-resolvers.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/README.md +2 -2
- package/evm.schema.json +7 -0
- package/fuel.schema.json +7 -0
- package/index.d.ts +674 -161
- package/index.js +10 -0
- package/package.json +6 -7
- package/src/AddressRows.res +121 -0
- package/src/AddressRows.res.mjs +143 -0
- package/src/Batch.res +11 -0
- package/src/Batch.res.mjs +2 -1
- package/src/Bin.res +47 -0
- package/src/Bin.res.mjs +34 -1
- package/src/ChainState.res +108 -98
- package/src/ChainState.res.mjs +86 -67
- package/src/ChainState.resi +28 -15
- package/src/Config.res +61 -131
- package/src/Config.res.mjs +47 -79
- package/src/ContractMapping.res +66 -0
- package/src/ContractMapping.res.mjs +73 -0
- package/src/Core.res +24 -0
- package/src/Core.res.mjs +4 -0
- package/src/Ecosystem.res +7 -2
- package/src/Env.res +128 -1
- package/src/Env.res.mjs +123 -1
- package/src/Envio.res +54 -76
- package/src/EventConfigBuilder.res +316 -46
- package/src/EventConfigBuilder.res.mjs +231 -28
- package/src/FetchState.res +59 -67
- package/src/FetchState.res.mjs +34 -44
- package/src/HandlerRegister.res +26 -15
- package/src/HandlerRegister.res.mjs +27 -7
- package/src/Hasura.res +30 -22
- package/src/Hasura.res.mjs +11 -5
- package/src/InMemoryStore.res +23 -38
- package/src/InMemoryStore.res.mjs +13 -27
- package/src/IndexerState.res +40 -3
- package/src/IndexerState.res.mjs +30 -4
- package/src/IndexerState.resi +4 -1
- package/src/Internal.res +49 -30
- package/src/Internal.res.mjs +12 -16
- package/src/Main.res +27 -20
- package/src/Main.res.mjs +19 -14
- package/src/Metrics.res +15 -1
- package/src/Metrics.res.mjs +5 -1
- package/src/Persistence.res +46 -21
- package/src/Persistence.res.mjs +8 -5
- package/src/PgStorage.res +238 -79
- package/src/PgStorage.res.mjs +115 -73
- package/src/PruneStaleHistory.res +1 -1
- package/src/PruneStaleHistory.res.mjs +1 -1
- package/src/Rollback.res +62 -41
- package/src/Rollback.res.mjs +61 -19
- package/src/RollbackCommit.res +7 -9
- package/src/RollbackCommit.res.mjs +5 -6
- package/src/SimulateItems.res +254 -9
- package/src/SimulateItems.res.mjs +214 -47
- package/src/Sink.res +35 -26
- package/src/Sink.res.mjs +20 -16
- package/src/TestIndexer.res +121 -123
- package/src/TestIndexer.res.mjs +97 -102
- package/src/Utils.res +38 -0
- package/src/Utils.res.mjs +39 -2
- package/src/Writing.res +4 -2
- package/src/Writing.res.mjs +3 -2
- package/src/bindings/ClickHouse.res +311 -656
- package/src/bindings/ClickHouse.res.mjs +294 -395
- package/src/bindings/ClickHouseSink.res +303 -0
- package/src/bindings/ClickHouseSink.res.mjs +216 -0
- package/src/bindings/NodeJs.res +9 -0
- package/src/bindings/NodeJs.res.mjs +8 -1
- package/src/bindings/Postgres.res +9 -0
- package/src/bindings/Postgres.res.mjs +3 -0
- package/src/db/EntityHistory.res +29 -24
- package/src/db/EntityHistory.res.mjs +11 -19
- package/src/db/InternalTable.res +258 -83
- package/src/db/InternalTable.res.mjs +223 -85
- package/src/db/RollbackScope.res +29 -0
- package/src/db/RollbackScope.res.mjs +31 -0
- package/src/db/Table.res +28 -1
- package/src/db/Table.res.mjs +26 -2
- package/src/resolvers/ResolverProcess.res +809 -0
- package/src/resolvers/ResolverProcess.res.mjs +667 -0
- package/src/resolvers/ResolverQuery.res +317 -0
- package/src/resolvers/ResolverQuery.res.mjs +220 -0
- package/src/resolvers/collisions.js +108 -0
- package/src/resolvers/db.js +363 -0
- package/src/resolvers/dispatch.js +165 -0
- package/src/resolvers/errors.js +30 -0
- package/src/resolvers/graphqlSelection.js +292 -0
- package/src/resolvers/hasuraAction.js +84 -0
- package/src/resolvers/hasuraApply.js +323 -0
- package/src/resolvers/hasuraMetadata.js +119 -0
- package/src/resolvers/index.js +95 -0
- package/src/resolvers/manifest.js +370 -0
- package/src/resolvers/server.js +270 -0
- package/src/sources/AddressSet.res +0 -22
- package/src/sources/AddressStore.res +48 -88
- package/src/sources/AddressStore.res.mjs +11 -22
- package/src/sources/Evm.res +2 -0
- package/src/sources/Evm.res.mjs +2 -0
- package/src/sources/EvmHyperSyncSource.res +3 -2
- package/src/sources/Fuel.res +2 -0
- package/src/sources/Fuel.res.mjs +2 -0
- package/src/sources/SimulateSource.res +8 -4
- package/src/sources/SimulateSource.res.mjs +7 -3
- package/src/sources/Svm.res +36 -7
- package/src/sources/Svm.res.mjs +34 -4
- package/src/sources/SvmHyperSyncClient.res +20 -116
- package/src/sources/SvmHyperSyncClient.res.mjs +5 -7
- package/src/sources/SvmHyperSyncSource.res +89 -37
- package/src/sources/SvmHyperSyncSource.res.mjs +71 -39
- package/src/sources/TransactionStore.res +38 -0
- package/src/sources/TransactionStore.res.mjs +5 -0
- package/src/tui/Tui.res +1 -1
- package/src/tui/Tui.res.mjs +1 -1
- package/svm.schema.json +44 -156
- package/src/MemoryStorage.res +0 -724
- package/src/MemoryStorage.res.mjs +0 -601
package/src/Envio.res
CHANGED
|
@@ -20,58 +20,48 @@ type svmOnSlotArgs<'context> = {
|
|
|
20
20
|
context: 'context,
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
discriminator didn't match any registered instruction. */
|
|
27
|
-
type svmInstructionParams = {
|
|
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>,
|
|
23
|
+
type svmLamports = {
|
|
24
|
+
pre: bigint,
|
|
25
|
+
post: bigint,
|
|
40
26
|
}
|
|
41
27
|
|
|
42
|
-
type
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
when the account was closed during it. Pre and post owners differ only when
|
|
47
|
-
a `SetAuthority(AccountOwner)` runs mid-transaction. */
|
|
48
|
-
owner?: SvmTypes.Pubkey.t,
|
|
49
|
-
/** Mint decimals, for scaling the raw amounts below. */
|
|
50
|
-
decimals?: int,
|
|
51
|
-
/** Raw amount in base units before the transaction. Absent when the token
|
|
52
|
-
account was created during it. */
|
|
28
|
+
type svmAccountToken = {
|
|
29
|
+
mint: SvmTypes.Pubkey.t,
|
|
30
|
+
owner: SvmTypes.Pubkey.t,
|
|
31
|
+
decimals: int,
|
|
53
32
|
preAmount?: bigint,
|
|
54
|
-
/** Raw amount in base units after the transaction. Absent when the token
|
|
55
|
-
account was closed during it. */
|
|
56
33
|
postAmount?: bigint,
|
|
57
34
|
}
|
|
58
35
|
|
|
36
|
+
type svmAccountActivity = {
|
|
37
|
+
address: SvmTypes.Pubkey.t,
|
|
38
|
+
transactionAccountIndex?: int,
|
|
39
|
+
isSigner?: bool,
|
|
40
|
+
isWritable?: bool,
|
|
41
|
+
lamports?: svmLamports,
|
|
42
|
+
token?: svmAccountToken,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type svmInstructionAccount = {
|
|
46
|
+
address: SvmTypes.Pubkey.t,
|
|
47
|
+
accountName: string,
|
|
48
|
+
instructionAccountIndex: int,
|
|
49
|
+
activity?: svmAccountActivity,
|
|
50
|
+
}
|
|
51
|
+
|
|
59
52
|
type svmTransaction = {
|
|
60
53
|
transactionIndex?: int,
|
|
61
|
-
|
|
62
|
-
signature: string,
|
|
54
|
+
signature?: string,
|
|
63
55
|
feePayer?: SvmTypes.Pubkey.t,
|
|
64
56
|
success?: bool,
|
|
65
57
|
err?: string,
|
|
66
58
|
fee?: bigint,
|
|
67
59
|
computeUnitsConsumed?: bigint,
|
|
68
|
-
accountKeys
|
|
60
|
+
accountKeys?: array<SvmTypes.Pubkey.t>,
|
|
69
61
|
recentBlockhash?: string,
|
|
70
62
|
version?: string,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
allSignatures: array<string>,
|
|
74
|
-
tokenBalances?: array<svmTokenBalance>,
|
|
63
|
+
allSignatures?: array<string>,
|
|
64
|
+
accountActivities?: array<svmAccountActivity>,
|
|
75
65
|
}
|
|
76
66
|
|
|
77
67
|
type svmLog = {
|
|
@@ -79,57 +69,29 @@ type svmLog = {
|
|
|
79
69
|
message: string,
|
|
80
70
|
}
|
|
81
71
|
|
|
82
|
-
|
|
83
|
-
`time` can still be absent — HyperSync/Solana may not report a block time for
|
|
84
|
-
every slot. Every other field is opt-in via `field_selection.block_fields`,
|
|
85
|
-
materialised from the per-chain block store at batch prep. */
|
|
86
|
-
type svmInstructionBlock = {
|
|
87
|
-
/** Slot this instruction's block was matched in. */
|
|
72
|
+
type svmBlock = {
|
|
88
73
|
slot: int,
|
|
89
|
-
/** Unix block time (seconds). */
|
|
90
74
|
time?: int,
|
|
91
|
-
hash
|
|
92
|
-
/** Block height (distinct from slot). Absent when not selected via
|
|
93
|
-
`field_selection.block_fields`. */
|
|
75
|
+
hash?: string,
|
|
94
76
|
height?: int,
|
|
95
77
|
parentSlot?: int,
|
|
96
78
|
parentHash?: string,
|
|
97
79
|
}
|
|
98
80
|
|
|
99
|
-
/** The per-instruction payload handlers receive as their `instruction`
|
|
100
|
-
argument. Carries the matched instruction's own fields plus the
|
|
101
|
-
program/instruction names, parent transaction, scoped logs, and block. */
|
|
102
81
|
type svmInstruction = {
|
|
103
|
-
/** Program name as declared under `programs[].name` in `config.yaml`. */
|
|
104
82
|
programName: string,
|
|
105
|
-
/** Instruction name as declared under `instructions[].name` in
|
|
106
|
-
`config.yaml`. */
|
|
107
83
|
instructionName: string,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
data
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
/** Discriminator prefixes pre-extracted by HyperSync. Each is `Some` only
|
|
117
|
-
when the underlying instruction is at least that long. */
|
|
118
|
-
d1?: string,
|
|
119
|
-
d2?: string,
|
|
120
|
-
d4?: string,
|
|
121
|
-
d8?: string,
|
|
122
|
-
/** Borsh-decoded params view. See [[svmInstructionParams]]. */
|
|
123
|
-
params?: svmInstructionParams,
|
|
124
|
-
/** Parent transaction. Carries only the fields selected via
|
|
125
|
-
`field_selection.transaction_fields`; absent when none are selected. */
|
|
84
|
+
discriminator: string,
|
|
85
|
+
programId?: SvmTypes.Pubkey.t,
|
|
86
|
+
data?: string,
|
|
87
|
+
path?: array<int>,
|
|
88
|
+
isInner?: bool,
|
|
89
|
+
args?: JSON.t,
|
|
90
|
+
accounts?: dict<svmInstructionAccount>,
|
|
91
|
+
accountArguments?: array<SvmTypes.Pubkey.t>,
|
|
126
92
|
transaction?: svmTransaction,
|
|
127
|
-
/** Program log entries scoped to this instruction. Absent when the
|
|
128
|
-
per-instruction `include_logs` flag is `false`. */
|
|
129
93
|
logs?: array<svmLog>,
|
|
130
|
-
|
|
131
|
-
// before a handler ever reads it.
|
|
132
|
-
block?: svmInstructionBlock,
|
|
94
|
+
block?: svmBlock,
|
|
133
95
|
}
|
|
134
96
|
|
|
135
97
|
/** Arguments passed to handlers registered via `indexer.onInstruction`. */
|
|
@@ -315,6 +277,22 @@ type fuelSimulateItem = {
|
|
|
315
277
|
transaction?: fuelTransactionInput,
|
|
316
278
|
}
|
|
317
279
|
|
|
280
|
+
type svmSimulateItem = {
|
|
281
|
+
program: string,
|
|
282
|
+
instruction: string,
|
|
283
|
+
slot?: int,
|
|
284
|
+
path?: array<int>,
|
|
285
|
+
programId?: string,
|
|
286
|
+
data?: string,
|
|
287
|
+
isInner?: bool,
|
|
288
|
+
args?: JSON.t,
|
|
289
|
+
accounts?: dict<{address: string}>,
|
|
290
|
+
accountArguments?: array<string>,
|
|
291
|
+
logs?: array<{kind?: string, message?: string}>,
|
|
292
|
+
block?: svmBlock,
|
|
293
|
+
transaction?: unknown,
|
|
294
|
+
}
|
|
295
|
+
|
|
318
296
|
// Detects contexts where a full-screen TUI is counter-productive: piped/redirected
|
|
319
297
|
// stdout, CI, and coding agents. `CLAUDECODE` is set by Claude Code; `CI` is the
|
|
320
298
|
// de-facto convention across CI providers; `TERM=dumb` is set by editors/tools
|
|
@@ -310,33 +310,44 @@ let internalBlockFields = ["number"]
|
|
|
310
310
|
// selected.
|
|
311
311
|
let rawEventBlockFields = ["hash", "timestamp"]
|
|
312
312
|
|
|
313
|
-
let
|
|
313
|
+
let evmSelectionKinds = ["block", "transaction"]
|
|
314
|
+
let svmSelectionKinds = ["instruction", "transaction", "accountActivity", "block", "log"]
|
|
314
315
|
|
|
315
316
|
// A handler written in plain JS gets no type error for a `blocks`/`transactions`
|
|
316
317
|
// typo, and an unrecognised key would read as an empty selection — silently
|
|
317
318
|
// dropping every field `config.yaml` selected. Rejected here so the option is
|
|
318
319
|
// held to the same shape whether or not the project type-checks it.
|
|
319
|
-
let validateFieldsShapeOrThrow = (
|
|
320
|
-
|
|
321
|
-
|
|
320
|
+
let validateFieldsShapeOrThrow = (
|
|
321
|
+
fields: unknown,
|
|
322
|
+
~registration: string,
|
|
323
|
+
~validKeys: array<string>,
|
|
324
|
+
~shapeNoun: string,
|
|
325
|
+
) => {
|
|
326
|
+
if typeof(fields) !== #object || fields === %raw(`null`) || Array.isArray(fields) {
|
|
322
327
|
JsError.throwWithMessage(
|
|
323
|
-
`The fields option of ${registration} must be an object of
|
|
328
|
+
`The fields option of ${registration} must be an object of ${shapeNoun} field names.`,
|
|
324
329
|
)
|
|
325
330
|
}
|
|
326
|
-
|
|
331
|
+
fields
|
|
327
332
|
->(Utils.magic: unknown => dict<unknown>)
|
|
328
333
|
->Dict.keysToArray
|
|
329
334
|
->Array.forEach(key =>
|
|
330
|
-
if !(
|
|
335
|
+
if !(validKeys->Array.includes(key)) {
|
|
331
336
|
JsError.throwWithMessage(
|
|
332
|
-
`Invalid "${key}" key in the fields option of ${registration}. Valid keys: ${
|
|
333
|
-
|
|
337
|
+
`Invalid "${key}" key in the fields option of ${registration}. Valid keys: ${Utils.Array.quotedJoin(
|
|
338
|
+
validKeys,
|
|
334
339
|
)}.`,
|
|
335
340
|
)
|
|
336
341
|
}
|
|
337
342
|
)
|
|
338
343
|
}
|
|
339
344
|
|
|
345
|
+
let selectionList = (fields: unknown, key: string): option<array<string>> =>
|
|
346
|
+
switch (fields->(Utils.magic: unknown => dict<unknown>))->Dict.get(key) {
|
|
347
|
+
| None => None
|
|
348
|
+
| Some(value) => Some(value->(Utils.magic: unknown => array<string>))
|
|
349
|
+
}
|
|
350
|
+
|
|
340
351
|
let validBlockFields = Utils.Set.fromArray(Evm.blockFields)
|
|
341
352
|
let validTransactionFields = Utils.Set.fromArray(Evm.transactionFields)
|
|
342
353
|
|
|
@@ -345,6 +356,7 @@ let parseFieldsOrThrow = (
|
|
|
345
356
|
~valid: Utils.Set.t<string>,
|
|
346
357
|
~kind: string,
|
|
347
358
|
~registration: string,
|
|
359
|
+
~rejectEmpty=false,
|
|
348
360
|
) => {
|
|
349
361
|
let seen = Utils.Set.make()
|
|
350
362
|
let fields = switch fields {
|
|
@@ -358,9 +370,9 @@ let parseFieldsOrThrow = (
|
|
|
358
370
|
fields->Array.forEach(name => {
|
|
359
371
|
if !(valid->Utils.Set.has(name)) {
|
|
360
372
|
JsError.throwWithMessage(
|
|
361
|
-
`Invalid "${name}" field in the fields.${kind} option of ${registration}. Valid ${kind} fields: ${
|
|
362
|
-
->Utils.Set.toArray
|
|
363
|
-
|
|
373
|
+
`Invalid "${name}" field in the fields.${kind} option of ${registration}. Valid ${kind} fields: ${Utils.Array.quotedJoin(
|
|
374
|
+
valid->Utils.Set.toArray,
|
|
375
|
+
)}.`,
|
|
364
376
|
)
|
|
365
377
|
}
|
|
366
378
|
if seen->Utils.Set.has(name) {
|
|
@@ -370,6 +382,11 @@ let parseFieldsOrThrow = (
|
|
|
370
382
|
}
|
|
371
383
|
seen->Utils.Set.add(name)->ignore
|
|
372
384
|
})
|
|
385
|
+
if rejectEmpty && fields->Array.length === 0 {
|
|
386
|
+
JsError.throwWithMessage(
|
|
387
|
+
`The fields.${kind} option of ${registration} must list at least one field.`,
|
|
388
|
+
)
|
|
389
|
+
}
|
|
373
390
|
seen
|
|
374
391
|
}
|
|
375
392
|
|
|
@@ -377,21 +394,26 @@ let parseFieldsOrThrow = (
|
|
|
377
394
|
// option itself (the names are for error messages), never on the chain, so one
|
|
378
395
|
// `onEvent` call resolves once and shares the result across every chain.
|
|
379
396
|
let resolveInlineFieldSelection = (
|
|
380
|
-
fields:
|
|
397
|
+
fields: unknown,
|
|
381
398
|
~contractName: string,
|
|
382
399
|
~eventName: string,
|
|
383
400
|
~enableRawEvents: bool,
|
|
384
401
|
): Internal.fieldSelection => {
|
|
385
402
|
let registration = `the "${eventName}" event registration on contract "${contractName}"`
|
|
386
|
-
validateFieldsShapeOrThrow(
|
|
403
|
+
validateFieldsShapeOrThrow(
|
|
404
|
+
fields,
|
|
405
|
+
~registration,
|
|
406
|
+
~validKeys=evmSelectionKinds,
|
|
407
|
+
~shapeNoun="block and transaction",
|
|
408
|
+
)
|
|
387
409
|
let blockFields = parseFieldsOrThrow(
|
|
388
|
-
fields
|
|
410
|
+
selectionList(fields, "block"),
|
|
389
411
|
~valid=validBlockFields,
|
|
390
412
|
~kind="block",
|
|
391
413
|
~registration,
|
|
392
414
|
)
|
|
393
415
|
let transactionFields = parseFieldsOrThrow(
|
|
394
|
-
fields
|
|
416
|
+
selectionList(fields, "transaction"),
|
|
395
417
|
~valid=validTransactionFields,
|
|
396
418
|
~kind="transaction",
|
|
397
419
|
~registration,
|
|
@@ -498,21 +520,121 @@ let buildEvmOnEventRegistration = (
|
|
|
498
520
|
|
|
499
521
|
// ============== Build SVM instruction event config ==============
|
|
500
522
|
|
|
501
|
-
//
|
|
502
|
-
//
|
|
503
|
-
let alwaysIncludedSvmBlockFields
|
|
523
|
+
// `block.slot` is the item's own key, so an inline selection carries it
|
|
524
|
+
// whether or not the handler listed it.
|
|
525
|
+
let alwaysIncludedSvmBlockFields = ["slot"]
|
|
526
|
+
|
|
527
|
+
let validSvmInstructionFields = Utils.Set.fromArray([
|
|
528
|
+
"args",
|
|
529
|
+
"accounts",
|
|
530
|
+
"accountArguments",
|
|
531
|
+
"programId",
|
|
532
|
+
"data",
|
|
533
|
+
"path",
|
|
534
|
+
"isInner",
|
|
535
|
+
])
|
|
536
|
+
let validSvmTransactionFields = Utils.Set.fromArray([
|
|
537
|
+
"transactionIndex",
|
|
538
|
+
"signature",
|
|
539
|
+
"feePayer",
|
|
540
|
+
"success",
|
|
541
|
+
"err",
|
|
542
|
+
"fee",
|
|
543
|
+
"computeUnitsConsumed",
|
|
544
|
+
"accountKeys",
|
|
545
|
+
"recentBlockhash",
|
|
546
|
+
"version",
|
|
547
|
+
"allSignatures",
|
|
548
|
+
])
|
|
549
|
+
let validSvmAccountActivityFields = Utils.Set.fromArray([
|
|
550
|
+
"address",
|
|
551
|
+
"transactionAccountIndex",
|
|
552
|
+
"isSigner",
|
|
553
|
+
"isWritable",
|
|
554
|
+
"lamports.pre",
|
|
555
|
+
"lamports.post",
|
|
556
|
+
"token.mint",
|
|
557
|
+
"token.owner",
|
|
558
|
+
"token.decimals",
|
|
559
|
+
"token.preAmount",
|
|
560
|
+
"token.postAmount",
|
|
561
|
+
])
|
|
562
|
+
let validSvmBlockFields = Utils.Set.fromArray([
|
|
563
|
+
"slot",
|
|
564
|
+
"time",
|
|
565
|
+
"hash",
|
|
566
|
+
"height",
|
|
567
|
+
"parentSlot",
|
|
568
|
+
"parentHash",
|
|
569
|
+
])
|
|
570
|
+
let validSvmLogFields = Utils.Set.fromArray(["kind", "message"])
|
|
571
|
+
|
|
572
|
+
let resolveSvmInlineFieldSelection = (
|
|
573
|
+
fields: unknown,
|
|
574
|
+
~contractName: string,
|
|
575
|
+
~eventName: string,
|
|
576
|
+
): Internal.fieldSelection => {
|
|
577
|
+
let registration = `the "${eventName}" instruction on program "${contractName}"`
|
|
578
|
+
validateFieldsShapeOrThrow(
|
|
579
|
+
fields,
|
|
580
|
+
~registration,
|
|
581
|
+
~validKeys=svmSelectionKinds,
|
|
582
|
+
~shapeNoun="instruction, transaction, accountActivity, block and log",
|
|
583
|
+
)
|
|
584
|
+
let accountActivity = selectionList(fields, "accountActivity")
|
|
585
|
+
let log = selectionList(fields, "log")
|
|
586
|
+
let instructionFields = parseFieldsOrThrow(
|
|
587
|
+
selectionList(fields, "instruction"),
|
|
588
|
+
~valid=validSvmInstructionFields,
|
|
589
|
+
~kind="instruction",
|
|
590
|
+
~registration,
|
|
591
|
+
)
|
|
592
|
+
let transactionFields = parseFieldsOrThrow(
|
|
593
|
+
selectionList(fields, "transaction"),
|
|
594
|
+
~valid=validSvmTransactionFields,
|
|
595
|
+
~kind="transaction",
|
|
596
|
+
~registration,
|
|
597
|
+
)
|
|
598
|
+
let accountActivityFields = parseFieldsOrThrow(
|
|
599
|
+
accountActivity,
|
|
600
|
+
~valid=validSvmAccountActivityFields,
|
|
601
|
+
~kind="accountActivity",
|
|
602
|
+
~registration,
|
|
603
|
+
~rejectEmpty=accountActivity->Option.isSome,
|
|
604
|
+
)
|
|
605
|
+
let blockFields = parseFieldsOrThrow(
|
|
606
|
+
selectionList(fields, "block"),
|
|
607
|
+
~valid=validSvmBlockFields,
|
|
608
|
+
~kind="block",
|
|
609
|
+
~registration,
|
|
610
|
+
)
|
|
611
|
+
let logFields = parseFieldsOrThrow(
|
|
612
|
+
log,
|
|
613
|
+
~valid=validSvmLogFields,
|
|
614
|
+
~kind="log",
|
|
615
|
+
~registration,
|
|
616
|
+
~rejectEmpty=log->Option.isSome,
|
|
617
|
+
)
|
|
618
|
+
blockFields->Utils.Set.addMany(alwaysIncludedSvmBlockFields)
|
|
619
|
+
if accountActivityFields->Utils.Set.size > 0 {
|
|
620
|
+
transactionFields->Utils.Set.add("accountActivities")->ignore
|
|
621
|
+
}
|
|
622
|
+
Internal.makeFieldSelection(
|
|
623
|
+
~blockFields,
|
|
624
|
+
~transactionFields,
|
|
625
|
+
~instructionFields,
|
|
626
|
+
~accountActivityFields,
|
|
627
|
+
~logFields,
|
|
628
|
+
~blockMaskFn=Svm.eventBlockFieldMask,
|
|
629
|
+
~transactionMaskFn=Svm.eventTransactionFieldMask,
|
|
630
|
+
)
|
|
631
|
+
}
|
|
504
632
|
|
|
505
633
|
let buildSvmInstructionEventConfig = (
|
|
506
634
|
~contractName: string,
|
|
507
635
|
~instructionName: string,
|
|
508
636
|
~programId: SvmTypes.Pubkey.t,
|
|
509
637
|
~discriminator: option<string>,
|
|
510
|
-
~discriminatorByteLen: int,
|
|
511
|
-
~includeLogs: bool,
|
|
512
|
-
~transactionFields: array<Internal.svmTransactionField>=[],
|
|
513
|
-
~blockFields: array<Internal.svmBlockField>=[],
|
|
514
|
-
~accountFilters: array<Internal.svmAccountFilterGroup>,
|
|
515
|
-
~isInner: option<bool>,
|
|
516
638
|
~accounts: array<string>=[],
|
|
517
639
|
~args: JSON.t=JSON.Null,
|
|
518
640
|
~definedTypes: JSON.t=JSON.Null,
|
|
@@ -523,12 +645,8 @@ let buildSvmInstructionEventConfig = (
|
|
|
523
645
|
->(Utils.magic: S.t<JSON.t> => S.t<Internal.eventParams>)
|
|
524
646
|
|
|
525
647
|
let fieldSelection = Internal.makeFieldSelection(
|
|
526
|
-
~blockFields=Utils.Set.fromArray(
|
|
527
|
-
|
|
528
|
-
),
|
|
529
|
-
~transactionFields=Utils.Set.fromArray(transactionFields)->(
|
|
530
|
-
Utils.magic: Utils.Set.t<Internal.svmTransactionField> => Utils.Set.t<string>
|
|
531
|
-
),
|
|
648
|
+
~blockFields=Utils.Set.fromArray(alwaysIncludedSvmBlockFields),
|
|
649
|
+
~transactionFields=Utils.Set.make(),
|
|
532
650
|
~blockMaskFn=Svm.eventBlockFieldMask,
|
|
533
651
|
~transactionMaskFn=Svm.eventTransactionFieldMask,
|
|
534
652
|
)
|
|
@@ -543,35 +661,187 @@ let buildSvmInstructionEventConfig = (
|
|
|
543
661
|
simulateParamsSchema: paramsSchema,
|
|
544
662
|
programId,
|
|
545
663
|
discriminator,
|
|
546
|
-
discriminatorByteLen,
|
|
547
|
-
includeLogs,
|
|
548
664
|
fieldSelection,
|
|
549
|
-
accountFilters,
|
|
550
|
-
isInner,
|
|
551
665
|
accounts,
|
|
552
666
|
args,
|
|
553
667
|
definedTypes,
|
|
554
668
|
}
|
|
555
669
|
}
|
|
556
670
|
|
|
557
|
-
//
|
|
558
|
-
|
|
671
|
+
// ============== SVM `where` ==============
|
|
672
|
+
|
|
673
|
+
type parsedSvmWhere = {
|
|
674
|
+
// Disjunctive normal form: outer array is OR of AND-groups. Empty means the
|
|
675
|
+
// registration accepts any accounts.
|
|
676
|
+
accountFilters: array<Internal.svmAccountFilterGroup>,
|
|
677
|
+
isInner: option<bool>,
|
|
678
|
+
startBlock: option<int>,
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
let validSvmWhereKeys = ["accounts", "isInner", "block"]
|
|
682
|
+
|
|
683
|
+
// `{slot: {_gte?}}`. Both levels are strict: the inner `eventBlockRangeSchema`
|
|
684
|
+
// rejects the `_lte` / `_every` that only `onSlot` supports, the outer rejects
|
|
685
|
+
// a wrapper key other than `slot`.
|
|
686
|
+
type svmBlockFilter = {slot?: LogSelection.eventBlockRange}
|
|
687
|
+
let svmBlockFilterSchema: S.t<svmBlockFilter> = S.object(s => {
|
|
688
|
+
slot: ?s.field("slot", S.option(LogSelection.eventBlockRangeSchema)),
|
|
689
|
+
})->S.strict
|
|
690
|
+
|
|
691
|
+
// The source query narrows accounts through `a0..a9`, so only an instruction's
|
|
692
|
+
// leading account slots can be filtered.
|
|
693
|
+
let filterableAccountCount = 10
|
|
694
|
+
|
|
695
|
+
// Resolve the `where` option of an `onInstruction` registration. Account names
|
|
696
|
+
// are resolved against the instruction's declared accounts here, so the
|
|
697
|
+
// registration carries positions and nothing downstream needs the names.
|
|
698
|
+
let resolveSvmWhereOrThrow = (
|
|
699
|
+
where: JSON.t,
|
|
700
|
+
~contractName: string,
|
|
701
|
+
~eventName: string,
|
|
702
|
+
~accountNames: array<string>,
|
|
703
|
+
): parsedSvmWhere => {
|
|
704
|
+
let invalid = message =>
|
|
705
|
+
JsError.throwWithMessage(
|
|
706
|
+
`Invalid where configuration for the "${eventName}" instruction on program "${contractName}". ${message}`,
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
let obj = switch where {
|
|
710
|
+
| Object(obj) => obj
|
|
711
|
+
| _ => invalid("Expected an object.")
|
|
712
|
+
}
|
|
713
|
+
obj->Utils.Dict.forEachWithKey((_, key) =>
|
|
714
|
+
if !(validSvmWhereKeys->Array.includes(key)) {
|
|
715
|
+
invalid(`Unknown field "${key}". Valid fields: ${Utils.Array.quotedJoin(validSvmWhereKeys)}.`)
|
|
716
|
+
}
|
|
717
|
+
)
|
|
718
|
+
|
|
719
|
+
let isInner = switch obj->Dict.get("isInner") {
|
|
720
|
+
| None => None
|
|
721
|
+
| Some(Boolean(isInner)) => Some(isInner)
|
|
722
|
+
| Some(_) => invalid(`The "isInner" filter must be a boolean.`)
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
let parseGroup = (group: dict<JSON.t>): Internal.svmAccountFilterGroup =>
|
|
726
|
+
group
|
|
727
|
+
->Dict.toArray
|
|
728
|
+
->Array.map(((name, value)) => {
|
|
729
|
+
let position = switch accountNames->Array.indexOf(name) {
|
|
730
|
+
| -1 if accountNames->Utils.Array.isEmpty =>
|
|
731
|
+
invalid(
|
|
732
|
+
"The instruction has no named accounts to filter on. Add `accounts` and `args` to it in config.yaml, or attach an IDL.",
|
|
733
|
+
)
|
|
734
|
+
| -1 =>
|
|
735
|
+
invalid(
|
|
736
|
+
`The instruction has no account named "${name}" to filter on. Named accounts: ${Utils.Array.quotedJoin(
|
|
737
|
+
accountNames,
|
|
738
|
+
)}.`,
|
|
739
|
+
)
|
|
740
|
+
| position if position >= filterableAccountCount =>
|
|
741
|
+
invalid(
|
|
742
|
+
`Account "${name}" is at position ${position->Int.toString}, and only the first ${filterableAccountCount->Int.toString} accounts of an instruction can be filtered.`,
|
|
743
|
+
)
|
|
744
|
+
| position => position
|
|
745
|
+
}
|
|
746
|
+
let values = value->normalizeOrThrow
|
|
747
|
+
if values->Utils.Array.isEmpty {
|
|
748
|
+
invalid(`The "${name}" filter must list at least one pubkey.`)
|
|
749
|
+
}
|
|
750
|
+
{
|
|
751
|
+
Internal.position,
|
|
752
|
+
values: values->Array.map(value =>
|
|
753
|
+
switch value {
|
|
754
|
+
| JSON.String(pubkey) if Core.getAddon().isSvmPubkey(~value=pubkey) =>
|
|
755
|
+
pubkey->SvmTypes.Pubkey.fromStringUnsafe
|
|
756
|
+
| JSON.String(pubkey) =>
|
|
757
|
+
invalid(`The "${name}" filter value "${pubkey}" is not a base58 SVM pubkey.`)
|
|
758
|
+
| _ => invalid(`The "${name}" filter must list base58 pubkeys as strings.`)
|
|
759
|
+
}
|
|
760
|
+
),
|
|
761
|
+
}
|
|
762
|
+
})
|
|
763
|
+
|
|
764
|
+
// An empty AND-group matches every instruction, so it makes the whole OR
|
|
765
|
+
// vacuous. Normalized away here so the source builds one unfiltered
|
|
766
|
+
// selection instead of a match-all selection beside the narrower ones.
|
|
767
|
+
let groups = switch obj->Dict.get("accounts") {
|
|
768
|
+
| None => []
|
|
769
|
+
| Some(Object(group)) => [parseGroup(group)]
|
|
770
|
+
| Some(Array(entries)) =>
|
|
771
|
+
entries->Array.map(entry =>
|
|
772
|
+
switch entry {
|
|
773
|
+
| Object(group) => parseGroup(group)
|
|
774
|
+
| _ => invalid(`Each entry in "accounts" must be an object.`)
|
|
775
|
+
}
|
|
776
|
+
)
|
|
777
|
+
| Some(_) => invalid(`Expected "accounts" to be an object or an array of objects.`)
|
|
778
|
+
}
|
|
779
|
+
let accountFilters = groups->Array.some(Utils.Array.isEmpty) ? [] : groups
|
|
780
|
+
|
|
781
|
+
let startBlock = switch obj->Dict.get("block") {
|
|
782
|
+
| None => None
|
|
783
|
+
| Some(block) =>
|
|
784
|
+
let filter = try block->S.parseOrThrow(svmBlockFilterSchema) catch {
|
|
785
|
+
| S.Raised(exn) =>
|
|
786
|
+
invalid(
|
|
787
|
+
`The "block" filter is invalid: ${exn
|
|
788
|
+
->Utils.prettifyExn
|
|
789
|
+
->(
|
|
790
|
+
Utils.magic: exn => string
|
|
791
|
+
)}. Only \`_gte\` is supported on instruction filters — use \`indexer.onSlot\` for \`_lte\` or \`_every\`.`,
|
|
792
|
+
)
|
|
793
|
+
}
|
|
794
|
+
switch filter.slot {
|
|
795
|
+
| Some({_gte}) => _gte
|
|
796
|
+
| None => None
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
{accountFilters, isInner, startBlock}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Enrich an SVM definition into a registration: the handler binding plus the
|
|
804
|
+
// `where`-derived account/isInner filters and startBlock override.
|
|
559
805
|
let buildSvmOnEventRegistration = (
|
|
560
806
|
~eventConfig: Internal.svmInstructionEventConfig,
|
|
561
807
|
~isWildcard: bool,
|
|
562
808
|
~handler: option<Internal.handler>,
|
|
563
809
|
~contractRegister: option<Internal.contractRegister>,
|
|
810
|
+
~where: option<JSON.t>,
|
|
811
|
+
~fieldSelection: option<Internal.fieldSelection>=?,
|
|
564
812
|
~startBlock: option<int>=?,
|
|
565
813
|
): Internal.svmOnEventRegistration => {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
814
|
+
let resolvedWhere = switch where {
|
|
815
|
+
| None => {accountFilters: [], isInner: None, startBlock: None}
|
|
816
|
+
| Some(where) =>
|
|
817
|
+
where->resolveSvmWhereOrThrow(
|
|
818
|
+
~contractName=eventConfig.contractName,
|
|
819
|
+
~eventName=eventConfig.name,
|
|
820
|
+
~accountNames=eventConfig.accounts,
|
|
821
|
+
)
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
{
|
|
825
|
+
index: -1,
|
|
826
|
+
eventConfig: (eventConfig :> Internal.eventConfig),
|
|
827
|
+
handler,
|
|
828
|
+
contractRegister,
|
|
829
|
+
isWildcard,
|
|
830
|
+
filterByAddresses: false,
|
|
831
|
+
dependsOnAddresses: Internal.dependsOnAddresses(~isWildcard, ~filterByAddresses=false),
|
|
832
|
+
// `where.block.slot._gte` overrides the program-level startBlock when
|
|
833
|
+
// present, mirroring EVM's `where.block.number._gte`.
|
|
834
|
+
startBlock: switch resolvedWhere.startBlock {
|
|
835
|
+
| Some(_) as sb => sb
|
|
836
|
+
| None => startBlock
|
|
837
|
+
},
|
|
838
|
+
fieldSelection: switch fieldSelection {
|
|
839
|
+
| Some(fieldSelection) => fieldSelection
|
|
840
|
+
| None => eventConfig.fieldSelection
|
|
841
|
+
},
|
|
842
|
+
accountFilters: resolvedWhere.accountFilters,
|
|
843
|
+
isInner: resolvedWhere.isInner,
|
|
844
|
+
}
|
|
575
845
|
}
|
|
576
846
|
|
|
577
847
|
// ============== Build Fuel event config ==============
|