envio 3.7.0 → 3.8.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.
Files changed (43) hide show
  1. package/index.d.ts +331 -142
  2. package/package.json +6 -6
  3. package/src/ChainState.res +9 -1
  4. package/src/ChainState.res.mjs +6 -1
  5. package/src/Config.res +19 -10
  6. package/src/Config.res.mjs +17 -7
  7. package/src/Envio.res +54 -76
  8. package/src/EventConfigBuilder.res +159 -30
  9. package/src/EventConfigBuilder.res.mjs +120 -24
  10. package/src/HandlerRegister.res +20 -11
  11. package/src/HandlerRegister.res.mjs +22 -5
  12. package/src/Hasura.res +30 -22
  13. package/src/Hasura.res.mjs +11 -5
  14. package/src/InMemoryStore.res +1 -6
  15. package/src/InMemoryStore.res.mjs +1 -3
  16. package/src/Internal.res +33 -13
  17. package/src/Internal.res.mjs +12 -16
  18. package/src/Main.res +2 -5
  19. package/src/MemoryStorage.res +40 -8
  20. package/src/MemoryStorage.res.mjs +38 -16
  21. package/src/Persistence.res +4 -2
  22. package/src/PgStorage.res +6 -1
  23. package/src/PgStorage.res.mjs +1 -1
  24. package/src/SimulateItems.res +261 -9
  25. package/src/SimulateItems.res.mjs +218 -47
  26. package/src/TestIndexer.res +26 -10
  27. package/src/TestIndexer.res.mjs +34 -12
  28. package/src/bindings/ClickHouse.res +33 -6
  29. package/src/bindings/ClickHouse.res.mjs +25 -4
  30. package/src/db/InternalTable.res +11 -0
  31. package/src/db/InternalTable.res.mjs +7 -0
  32. package/src/sources/EvmHyperSyncSource.res +3 -2
  33. package/src/sources/SimulateSource.res +8 -4
  34. package/src/sources/SimulateSource.res.mjs +7 -3
  35. package/src/sources/Svm.res +34 -7
  36. package/src/sources/Svm.res.mjs +32 -4
  37. package/src/sources/SvmHyperSyncClient.res +10 -12
  38. package/src/sources/SvmHyperSyncClient.res.mjs +3 -2
  39. package/src/sources/SvmHyperSyncSource.res +91 -34
  40. package/src/sources/SvmHyperSyncSource.res.mjs +77 -37
  41. package/src/sources/TransactionStore.res +38 -0
  42. package/src/sources/TransactionStore.res.mjs +5 -0
  43. package/svm.schema.json +27 -78
@@ -17,6 +17,7 @@ module Chains = {
17
17
  type field = [
18
18
  | progressFields
19
19
  | #id
20
+ | #ecosystem
20
21
  | #start_block
21
22
  | #end_block
22
23
  | #max_reorg_depth
@@ -29,6 +30,7 @@ module Chains = {
29
30
 
30
31
  let fields: array<field> = [
31
32
  #id,
33
+ #ecosystem,
32
34
  #start_block,
33
35
  #end_block,
34
36
  #max_reorg_depth,
@@ -57,6 +59,7 @@ module Chains = {
57
59
 
58
60
  type t = {
59
61
  @as("id") id: ChainId.t,
62
+ @as("ecosystem") ecosystem: string,
60
63
  @as("start_block") startBlock: int,
61
64
  @as("end_block") endBlock: Null.t<int>,
62
65
  @as("max_reorg_depth") maxReorgDepth: int,
@@ -70,6 +73,10 @@ module Chains = {
70
73
  "envio_chains",
71
74
  ~fields=[
72
75
  mkField((#id: field :> string), ChainId, ~fieldSchema=ChainId.schema, ~isPrimaryKey),
76
+ // Which ecosystem the chain belongs to (evm / fuel / svm). Chain ids are
77
+ // only unique within an ecosystem (HOS-1880: Svm ids are Envio-assigned),
78
+ // so consumers should treat (ecosystem, id) as the chain's identity.
79
+ mkField((#ecosystem: field :> string), String, ~fieldSchema=S.string),
73
80
  // Values populated from config
74
81
  mkField((#start_block: field :> string), Int32, ~fieldSchema=S.int),
75
82
  mkField((#end_block: field :> string), Int32, ~fieldSchema=S.null(S.int), ~isNullable),
@@ -104,6 +111,7 @@ module Chains = {
104
111
  let initialFromConfig = (chainConfig: Config.chain) => {
105
112
  {
106
113
  id: chainConfig.id,
114
+ ecosystem: (chainConfig.ecosystem: Ecosystem.name :> string),
107
115
  startBlock: chainConfig.startBlock,
108
116
  endBlock: chainConfig.endBlock->Null.fromOption,
109
117
  maxReorgDepth: chainConfig.maxReorgDepth,
@@ -131,6 +139,7 @@ module Chains = {
131
139
  let value = initialValues->(Utils.magic: t => dict<unknown>)->Dict.get((field :> string))
132
140
  switch typeof(value) {
133
141
  | #object => "NULL"
142
+ | #string => `'${value->(Utils.magic: option<unknown> => string)}'`
134
143
  | #number => value->(Utils.magic: option<unknown> => int)->Int.toString
135
144
  | #bigint => value->(Utils.magic: option<unknown> => bigint)->BigInt.toString
136
145
  | #boolean => value->(Utils.magic: option<unknown> => bool) ? "true" : "false"
@@ -648,6 +657,7 @@ module Views = {
648
657
  `CREATE VIEW "${pgSchema}"."${metaViewName}" AS
649
658
  SELECT
650
659
  "${(#id: Chains.field :> string)}" AS "chainId",
660
+ "${(#ecosystem: Chains.field :> string)}" AS "ecosystem",
651
661
  "${(#start_block: Chains.field :> string)}" AS "startBlock",
652
662
  "${(#end_block: Chains.field :> string)}" AS "endBlock",
653
663
  "${(#progress_block: Chains.field :> string)}" AS "progressBlock",
@@ -666,6 +676,7 @@ ORDER BY "${(#id: Chains.field :> string)}";`
666
676
  SELECT
667
677
  "${(#source_block: Chains.field :> string)}" AS "block_height",
668
678
  "${(#id: Chains.field :> string)}" AS "chain_id",
679
+ "${(#ecosystem: Chains.field :> string)}" AS "ecosystem",
669
680
  "${(#end_block: Chains.field :> string)}" AS "end_block",
670
681
  "${(#first_event_block: Chains.field :> string)}" AS "first_event_block_number",
671
682
  "${(#_is_hyper_sync: Chains.field :> string)}" AS "is_hyper_sync",
@@ -13,6 +13,7 @@ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_excep
13
13
 
14
14
  let fields = [
15
15
  "id",
16
+ "ecosystem",
16
17
  "start_block",
17
18
  "end_block",
18
19
  "max_reorg_depth",
@@ -27,6 +28,7 @@ let fields = [
27
28
 
28
29
  let table = Table.mkTable("envio_chains", undefined, [
29
30
  Table.mkField("id", "ChainId", ChainId.schema, undefined, undefined, undefined, true, undefined, undefined, undefined, undefined, undefined, undefined),
31
+ Table.mkField("ecosystem", "String", S$RescriptSchema.string, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined),
30
32
  Table.mkField("start_block", "Int32", S$RescriptSchema.int, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined),
31
33
  Table.mkField("end_block", "Int32", S$RescriptSchema.$$null(S$RescriptSchema.int), undefined, undefined, true, undefined, undefined, undefined, undefined, undefined, undefined, undefined),
32
34
  Table.mkField("max_reorg_depth", "Int32", S$RescriptSchema.int, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined),
@@ -42,6 +44,7 @@ let table = Table.mkTable("envio_chains", undefined, [
42
44
  function initialFromConfig(chainConfig) {
43
45
  return {
44
46
  id: chainConfig.id,
47
+ ecosystem: chainConfig.ecosystem,
45
48
  start_block: chainConfig.startBlock,
46
49
  end_block: Stdlib_Null.fromOption(chainConfig.endBlock),
47
50
  max_reorg_depth: chainConfig.maxReorgDepth,
@@ -73,6 +76,8 @@ function makeInitialValuesQuery(pgSchema, chainConfigs) {
73
76
  } else {
74
77
  return "false";
75
78
  }
79
+ } else if (match === "string") {
80
+ return `'` + value + `'`;
76
81
  } else if (match === "object") {
77
82
  return "NULL";
78
83
  } else {
@@ -440,6 +445,7 @@ function makeMetaViewQuery(pgSchema) {
440
445
  return `CREATE VIEW "` + pgSchema + `"."` + metaViewName + `" AS
441
446
  SELECT
442
447
  "` + "id" + `" AS "chainId",
448
+ "` + "ecosystem" + `" AS "ecosystem",
443
449
  "` + "start_block" + `" AS "startBlock",
444
450
  "` + "end_block" + `" AS "endBlock",
445
451
  "` + "progress_block" + `" AS "progressBlock",
@@ -458,6 +464,7 @@ function makeChainMetadataViewQuery(pgSchema) {
458
464
  SELECT
459
465
  "` + "source_block" + `" AS "block_height",
460
466
  "` + "id" + `" AS "chain_id",
467
+ "` + "ecosystem" + `" AS "ecosystem",
461
468
  "` + "end_block" + `" AS "end_block",
462
469
  "` + "first_event_block" + `" AS "first_event_block_number",
463
470
  "` + "_is_hyper_sync" + `" AS "is_hyper_sync",
@@ -1,7 +1,8 @@
1
1
  open Source
2
2
 
3
- // Surfaced by HyperSyncClient.getHeight (Rust) when HyperSync rejects the API
4
- // token. The corrupted-token test feeds the real server error through this
3
+ // Surfaced by the HyperSync client (Rust) when HyperSync rejects the API
4
+ // token. The corrupted-token test feeds the real server error (from the query
5
+ // endpoint; the edge no longer 401s malformed tokens on /height) through this
5
6
  // check so it can't silently drift away from what getHeightOrThrow guards on.
6
7
  let isUnauthorizedError = (message: string) => message->String.includes("401 Unauthorized")
7
8
 
@@ -4,6 +4,8 @@ let make = (
4
4
  ~chainId: ChainId.t,
5
5
  ~addressStore: AddressStore.t,
6
6
  ~ecosystem: Ecosystem.name=Evm,
7
+ ~transactionStore: option<TransactionStore.t>=None,
8
+ ~blockStore: option<BlockStore.t>=None,
7
9
  ): Source.t => {
8
10
  let reportedHeight = max(endBlock, 1)
9
11
 
@@ -106,10 +108,12 @@ let make = (
106
108
  Promise.resolve({
107
109
  Source.knownHeight: reportedHeight,
108
110
  parsedQueueItems,
109
- // Simulate keeps the transaction and block inline on the payload; no
110
- // transaction page and an empty block page (nothing to reorg-check).
111
- transactionStore: None,
112
- blockStore: BlockStore.fromJs([], ~ecosystem, ~shouldChecksum=false),
111
+ // EVM/Fuel keep transaction and block inline. SVM simulate returns a
112
+ // store page so materialize + activity attach run the same path as HyperSync.
113
+ transactionStore,
114
+ blockStore: blockStore->Option.getOr(
115
+ BlockStore.fromJs([], ~ecosystem, ~shouldChecksum=false),
116
+ ),
113
117
  fromBlockQueried: fromBlock,
114
118
  latestFetchedBlockNumber: toBlockQueried,
115
119
  stats: {
@@ -2,9 +2,13 @@
2
2
 
3
3
  import * as BlockStore from "./BlockStore.res.mjs";
4
4
  import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
5
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
5
7
 
6
- function make(items, endBlock, chainId, addressStore, ecosystemOpt) {
8
+ function make(items, endBlock, chainId, addressStore, ecosystemOpt, transactionStoreOpt, blockStoreOpt) {
7
9
  let ecosystem = ecosystemOpt !== undefined ? ecosystemOpt : "evm";
10
+ let transactionStore = transactionStoreOpt !== undefined ? Primitive_option.valFromOption(transactionStoreOpt) : undefined;
11
+ let blockStore = blockStoreOpt !== undefined ? Primitive_option.valFromOption(blockStoreOpt) : undefined;
8
12
  let reportedHeight = Primitive_int.max(endBlock, 1);
9
13
  return {
10
14
  name: "SimulateSource",
@@ -75,8 +79,8 @@ function make(items, endBlock, chainId, addressStore, ecosystemOpt) {
75
79
  return Promise.resolve({
76
80
  knownHeight: reportedHeight,
77
81
  parsedQueueItems: parsedQueueItems,
78
- transactionStore: undefined,
79
- blockStore: BlockStore.fromJs([], ecosystem, false),
82
+ transactionStore: transactionStore,
83
+ blockStore: Stdlib_Option.getOr(blockStore, BlockStore.fromJs([], ecosystem, false)),
80
84
  fromBlockQueried: fromBlock,
81
85
  latestFetchedBlockNumber: toBlockQueried,
82
86
  stats: {
@@ -14,13 +14,38 @@ let eventTransactionFieldMask = TransactionStore.makeMaskFn(transactionFields)
14
14
  // Rust store (`SvmBlockField`) — keep this order in sync.
15
15
  let blockFields = ["slot", "time", "hash", "height", "parentSlot", "parentHash"]
16
16
 
17
- // `slot`/`time`/`hash` are always included; every other block field is opt-in
18
- // via `field_selection.block_fields`. All are materialised from the store.
17
+ // `slot` is always included (the item's key); every other block field is
18
+ // opt-in via handler `fields.block`. All are materialised from the store.
19
19
  //
20
20
  // One instruction's selected block fields → store selection bitmask. Computed per
21
21
  // event at config build and cached on the event config.
22
22
  let eventBlockFieldMask = BlockStore.makeMaskFn(blockFields)
23
23
 
24
+ let attachAccountActivities = (payload: Internal.eventPayload, tx: Internal.eventTransaction) => {
25
+ let instruction = payload->(Utils.magic: Internal.eventPayload => dict<unknown>)
26
+ let transaction = tx->(Utils.magic: Internal.eventTransaction => dict<unknown>)
27
+ switch (instruction->Dict.get("accounts"), transaction->Dict.get("accountActivities")) {
28
+ | (Some(accounts), Some(activities)) if !(accounts->Array.isArray) =>
29
+ let byAddress = Dict.make()
30
+ (activities->(Utils.magic: unknown => array<Envio.svmAccountActivity>))->Array.forEach(
31
+ activity => byAddress->Dict.set(activity.address->SvmTypes.Pubkey.toString, activity),
32
+ )
33
+ (accounts->(Utils.magic: unknown => dict<Envio.svmInstructionAccount>))
34
+ ->Dict.valuesToArray
35
+ ->Array.forEach(account =>
36
+ switch byAddress->Dict.get(account.address->SvmTypes.Pubkey.toString) {
37
+ | Some(activity) =>
38
+ (account->(Utils.magic: Envio.svmInstructionAccount => dict<unknown>))->Dict.set(
39
+ "activity",
40
+ activity->(Utils.magic: Envio.svmAccountActivity => unknown),
41
+ )
42
+ | None => ()
43
+ }
44
+ )
45
+ | _ => ()
46
+ }
47
+ }
48
+
24
49
  let make = (~logger: Pino.t): Ecosystem.t => {
25
50
  name: Svm,
26
51
  blockNumberName: "height",
@@ -36,16 +61,18 @@ let make = (~logger: Pino.t): Ecosystem.t => {
36
61
  logger,
37
62
  toEvent: eventItem => eventItem.payload->(Utils.magic: Internal.eventPayload => Internal.event),
38
63
  toEventLogger: eventItem => {
39
- let instruction =
40
- eventItem.payload->(Utils.magic: Internal.eventPayload => Envio.svmInstruction)
64
+ let eventConfig =
65
+ eventItem.onEventRegistration.eventConfig->(
66
+ Utils.magic: Internal.eventConfig => Internal.svmInstructionEventConfig
67
+ )
41
68
  Logging.createChildFrom(
42
69
  ~logger,
43
70
  ~params={
44
- "program": eventItem.onEventRegistration.eventConfig.contractName,
45
- "instruction": eventItem.onEventRegistration.eventConfig.name,
71
+ "program": eventConfig.contractName,
72
+ "instruction": eventConfig.name,
46
73
  "chainId": eventItem.chainId,
47
74
  "slot": eventItem.blockNumber,
48
- "programId": instruction.programId,
75
+ "programId": eventConfig.programId,
49
76
  },
50
77
  )
51
78
  },
@@ -9,6 +9,7 @@ import * as Internal from "../Internal.res.mjs";
9
9
  import * as BlockStore from "./BlockStore.res.mjs";
10
10
  import * as Performance from "../bindings/Performance.res.mjs";
11
11
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
12
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
12
13
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
13
14
  import * as TransactionStore from "./TransactionStore.res.mjs";
14
15
 
@@ -25,6 +26,32 @@ let blockFields = [
25
26
 
26
27
  let eventBlockFieldMask = BlockStore.makeMaskFn(blockFields);
27
28
 
29
+ function attachAccountActivities(payload, tx) {
30
+ let match = payload["accounts"];
31
+ let match$1 = tx["accountActivities"];
32
+ if (match === undefined) {
33
+ return;
34
+ }
35
+ if (match$1 === undefined) {
36
+ return;
37
+ }
38
+ let accounts = Primitive_option.valFromOption(match);
39
+ if (Array.isArray(accounts)) {
40
+ return;
41
+ }
42
+ let byAddress = {};
43
+ Primitive_option.valFromOption(match$1).forEach(activity => {
44
+ byAddress[activity.address] = activity;
45
+ });
46
+ Object.values(accounts).forEach(account => {
47
+ let activity = byAddress[account.address];
48
+ if (activity !== undefined) {
49
+ account["activity"] = activity;
50
+ return;
51
+ }
52
+ });
53
+ }
54
+
28
55
  function make(logger) {
29
56
  return {
30
57
  name: "svm",
@@ -37,13 +64,13 @@ function make(logger) {
37
64
  logger: logger,
38
65
  toEvent: eventItem => eventItem.payload,
39
66
  toEventLogger: eventItem => {
40
- let instruction = eventItem.payload;
67
+ let eventConfig = eventItem.onEventRegistration.eventConfig;
41
68
  return Logging.createChildFrom(logger, {
42
- program: eventItem.onEventRegistration.eventConfig.contractName,
43
- instruction: eventItem.onEventRegistration.eventConfig.name,
69
+ program: eventConfig.contractName,
70
+ instruction: eventConfig.name,
44
71
  chainId: eventItem.chainId,
45
72
  slot: eventItem.blockNumber,
46
- programId: instruction.programId
73
+ programId: eventConfig.programId
47
74
  });
48
75
  },
49
76
  toRawEvent: param => Stdlib_JsError.throwWithMessage("Raw events are not supported for SVM")
@@ -96,6 +123,7 @@ export {
96
123
  eventTransactionFieldMask,
97
124
  blockFields,
98
125
  eventBlockFieldMask,
126
+ attachAccountActivities,
99
127
  make,
100
128
  GetFinalizedSlot,
101
129
  makeRPCSource,
@@ -27,14 +27,15 @@ module Registration = {
27
27
  // Earliest slot this registration accepts; `None` is unrestricted.
28
28
  startBlock: option<int>,
29
29
  discriminator?: string,
30
- discriminatorByteLen: int,
31
30
  isInner?: bool,
32
- includeLogs: bool,
33
31
  // DNF: outer array is OR of AND-groups.
34
32
  accountFilters: array<array<accountFilter>>,
35
33
  // camelCase Internal.svmTransactionField / svmBlockField names.
36
34
  transactionFields: array<string>,
37
35
  blockFields: array<string>,
36
+ accountActivityFields: array<string>,
37
+ logFields: array<string>,
38
+ instructionFields: array<string>,
38
39
  // Borsh schema pieces; empty accounts + absent argsJson = no schema.
39
40
  accounts: array<string>,
40
41
  argsJson?: string,
@@ -55,9 +56,7 @@ module Registration = {
55
56
  isWildcard: reg.isWildcard,
56
57
  startBlock: reg.startBlock,
57
58
  discriminator: ?eventConfig.discriminator,
58
- discriminatorByteLen: eventConfig.discriminatorByteLen,
59
59
  isInner: ?eventConfig.isInner,
60
- includeLogs: eventConfig.includeLogs,
61
60
  accountFilters: eventConfig.accountFilters->Array.map(group =>
62
61
  group->Array.map(
63
62
  (filter): accountFilter => {
@@ -68,6 +67,9 @@ module Registration = {
68
67
  ),
69
68
  transactionFields: reg.fieldSelection.transactionFields->Utils.Set.toArray,
70
69
  blockFields: reg.fieldSelection.blockFields->Utils.Set.toArray,
70
+ accountActivityFields: reg.fieldSelection.accountActivityFields->Utils.Set.toArray,
71
+ logFields: reg.fieldSelection.logFields->Utils.Set.toArray,
72
+ instructionFields: reg.fieldSelection.instructionFields->Utils.Set.toArray,
71
73
  accounts: eventConfig.accounts,
72
74
  argsJson: ?switch eventConfig.args {
73
75
  | JSON.Null => None
@@ -205,8 +207,8 @@ module EventItems = {
205
207
  }
206
208
 
207
209
  type log = {
208
- kind: string,
209
- message: string,
210
+ kind?: string,
211
+ message?: string,
210
212
  }
211
213
 
212
214
  // One routed instruction; `block` and `transaction` are materialised from
@@ -215,17 +217,13 @@ module EventItems = {
215
217
  onEventRegistrationIndex: int,
216
218
  slot: int,
217
219
  transactionIndex: int,
218
- instructionAddress: array<int>,
220
+ path: array<int>,
219
221
  programId: string,
220
222
  accounts: array<string>,
221
223
  data: string,
222
- d1?: string,
223
- d2?: string,
224
- d4?: string,
225
- d8?: string,
226
224
  isInner: bool,
227
225
  decoded?: ResponseTypes.decodedInstruction,
228
- // Present only when the routed registration opted in via `includeLogs`.
226
+ // Present only when the routed registration selected `fields.log`.
229
227
  logs?: array<log>,
230
228
  }
231
229
 
@@ -20,15 +20,16 @@ function fromOnEventRegistrations(onEventRegistrations) {
20
20
  isWildcard: reg.isWildcard,
21
21
  startBlock: reg.startBlock,
22
22
  discriminator: eventConfig.discriminator,
23
- discriminatorByteLen: eventConfig.discriminatorByteLen,
24
23
  isInner: eventConfig.isInner,
25
- includeLogs: eventConfig.includeLogs,
26
24
  accountFilters: eventConfig.accountFilters.map(group => group.map(filter => ({
27
25
  position: filter.position,
28
26
  values: filter.values
29
27
  }))),
30
28
  transactionFields: Array.from(reg.fieldSelection.transactionFields),
31
29
  blockFields: Array.from(reg.fieldSelection.blockFields),
30
+ accountActivityFields: Array.from(reg.fieldSelection.accountActivityFields),
31
+ logFields: Array.from(reg.fieldSelection.logFields),
32
+ instructionFields: Array.from(reg.fieldSelection.instructionFields),
32
33
  accounts: eventConfig.accounts,
33
34
  argsJson: tmp,
34
35
  definedTypesJson: tmp$1
@@ -10,50 +10,105 @@ type options = {
10
10
  addressStore: AddressStore.t,
11
11
  }
12
12
 
13
- // Parse the Rust-decoded instruction (args/accounts arrive as JSON strings to
14
- // side-step napi-rs's lack of native JSON passthrough) into the public shape.
15
- let parseDecoded = (
16
- d: SvmHyperSyncClient.ResponseTypes.decodedInstruction,
17
- ): Envio.svmInstructionParams => {
18
- let args = try JSON.parseOrThrow(d.argsJson) catch {
13
+ let parseArgs = (d: SvmHyperSyncClient.ResponseTypes.decodedInstruction): JSON.t =>
14
+ try JSON.parseOrThrow(d.argsJson) catch {
19
15
  | _ => JSON.Object(Dict.make())
20
16
  }
21
- let accounts = try {
22
- JSON.parseOrThrow(d.accountsJson)->(Utils.magic: JSON.t => dict<string>)
23
- } catch {
24
- | _ => Dict.make()
17
+
18
+ let namedAccounts = (
19
+ ~idlNames: array<string>,
20
+ ~accountArguments: array<string>,
21
+ ): dict<Envio.svmInstructionAccount> => {
22
+ let out = Dict.make()
23
+ idlNames->Array.forEachWithIndex((name, i) =>
24
+ switch accountArguments->Array.get(i) {
25
+ | Some(address) =>
26
+ out->Dict.set(
27
+ name,
28
+ {
29
+ Envio.address: address->SvmTypes.Pubkey.fromStringUnsafe,
30
+ accountName: name,
31
+ instructionAccountIndex: i,
32
+ },
33
+ )
34
+ | None => ()
35
+ }
36
+ )
37
+ out
38
+ }
39
+
40
+ let selectedLog = (log: SvmHyperSyncClient.EventItems.log, ~logFields: Utils.Set.t<string>): Envio.svmLog => {
41
+ let out = Dict.make()
42
+ if logFields->Utils.Set.has("kind") {
43
+ switch log.kind {
44
+ | Some(kind) => out->Dict.set("kind", kind)
45
+ | None => ()
46
+ }
25
47
  }
26
- {
27
- name: d.name,
28
- args,
29
- accounts,
30
- extraAccounts: d.extraAccounts,
48
+ if logFields->Utils.Set.has("message") {
49
+ switch log.message {
50
+ | Some(message) => out->Dict.set("message", message)
51
+ | None => ()
52
+ }
31
53
  }
54
+ out->(Utils.magic: dict<string> => Envio.svmLog)
32
55
  }
33
56
 
34
- // `block` is omitted; it's materialised from the block store at batch prep.
57
+ let setField = (out: dict<unknown>, name: string, value: 'a) =>
58
+ out->Dict.set(name, value->(Utils.magic: 'a => unknown))
59
+
60
+ // `block` and `transaction` are omitted; they're materialised from the stores
61
+ // at batch prep. Named-account `.activity` is attached then too.
62
+ // Unselected instruction keys must be omitted, not assigned `undefined`.
35
63
  let toSvmInstruction = (
36
64
  item: SvmHyperSyncClient.EventItems.item,
37
65
  ~programName,
38
66
  ~instructionName,
67
+ ~eventConfig: Internal.svmInstructionEventConfig,
68
+ ~fieldSelection: Internal.fieldSelection,
39
69
  ): Envio.svmInstruction => {
40
- programName,
41
- instructionName,
42
- programId: item.programId->SvmTypes.Pubkey.fromStringUnsafe,
43
- data: item.data,
44
- accounts: item.accounts->SvmTypes.Pubkey.fromStringsUnsafe,
45
- instructionAddress: item.instructionAddress,
46
- isInner: item.isInner,
47
- d1: ?item.d1,
48
- d2: ?item.d2,
49
- d4: ?item.d4,
50
- d8: ?item.d8,
51
- params: ?(item.decoded->Option.map(parseDecoded)),
52
- logs: ?(
53
- item.logs->Option.map(logs =>
54
- logs->Array.map((log): Envio.svmLog => {kind: log.kind, message: log.message})
70
+ let hasSelection = name => fieldSelection.instructionFields->Utils.Set.has(name)
71
+ let discriminator = switch eventConfig.discriminator {
72
+ | Some(d) => d
73
+ | None => item.data
74
+ }
75
+ let out = Dict.make()
76
+ out->setField("programName", programName)
77
+ out->setField("instructionName", instructionName)
78
+ out->setField("discriminator", discriminator)
79
+ if hasSelection("programId") {
80
+ out->setField("programId", item.programId->SvmTypes.Pubkey.fromStringUnsafe)
81
+ }
82
+ if hasSelection("data") {
83
+ out->setField("data", item.data)
84
+ }
85
+ if hasSelection("path") {
86
+ out->setField("path", item.path)
87
+ }
88
+ if hasSelection("isInner") {
89
+ out->setField("isInner", item.isInner)
90
+ }
91
+ if hasSelection("args") {
92
+ out->setField("args", item.decoded->Option.map(parseArgs))
93
+ }
94
+ if hasSelection("accounts") {
95
+ out->setField(
96
+ "accounts",
97
+ namedAccounts(~idlNames=eventConfig.accounts, ~accountArguments=item.accounts),
55
98
  )
56
- ),
99
+ }
100
+ if hasSelection("accountArguments") {
101
+ out->setField("accountArguments", item.accounts->SvmTypes.Pubkey.fromStringsUnsafe)
102
+ }
103
+ if fieldSelection.logFields->Utils.Set.size > 0 {
104
+ out->setField(
105
+ "logs",
106
+ item.logs
107
+ ->Option.getOr([])
108
+ ->Array.map(log => selectedLog(log, ~logFields=fieldSelection.logFields)),
109
+ )
110
+ }
111
+ out->(Utils.magic: dict<unknown> => Envio.svmInstruction)
57
112
  }
58
113
 
59
114
  let make = (
@@ -144,18 +199,20 @@ let make = (
144
199
  item,
145
200
  ~programName=eventConfig.contractName,
146
201
  ~instructionName=eventConfig.name,
202
+ ~eventConfig,
203
+ ~fieldSelection=onEventRegistration.fieldSelection,
147
204
  )
148
205
  Internal.Event({
149
206
  onEventRegistration,
150
207
  chainId,
151
208
  blockNumber: item.slot,
152
- // A slot orders by `(transactionIndex, instructionAddress)` — the
209
+ // A slot orders by `(transactionIndex, path)` — the
153
210
  // transaction, then the instruction's position in its CPI tree. Both
154
211
  // ride the item so the buffer comparator can order on the pair
155
212
  // directly; no single integer can hold it (Solana allows a CPI depth
156
213
  // of 5, which needs more bits than a JS integer is exact to).
157
214
  logIndex: item.transactionIndex,
158
- orderPath: item.instructionAddress,
215
+ orderPath: item.path,
159
216
  // The parent transaction is materialised from the store at batch prep.
160
217
  transactionIndex: item.transactionIndex,
161
218
  payload: payload->(Utils.magic: Envio.svmInstruction => Internal.eventPayload),