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.
Files changed (41) hide show
  1. package/evm.schema.json +8 -8
  2. package/fuel.schema.json +12 -12
  3. package/index.d.ts +155 -1
  4. package/package.json +6 -7
  5. package/src/ChainFetcher.res +25 -1
  6. package/src/ChainFetcher.res.mjs +19 -1
  7. package/src/Config.res +145 -10
  8. package/src/Config.res.mjs +56 -16
  9. package/src/Core.res +32 -0
  10. package/src/Env.res.mjs +1 -2
  11. package/src/Envio.res +94 -0
  12. package/src/EventConfigBuilder.res +50 -0
  13. package/src/EventConfigBuilder.res.mjs +31 -0
  14. package/src/HandlerLoader.res +12 -1
  15. package/src/HandlerLoader.res.mjs +6 -1
  16. package/src/InMemoryTable.res +20 -24
  17. package/src/InMemoryTable.res.mjs +3 -19
  18. package/src/Internal.res +38 -0
  19. package/src/Main.res +53 -1
  20. package/src/Main.res.mjs +32 -0
  21. package/src/SimulateItems.res +23 -10
  22. package/src/SimulateItems.res.mjs +21 -6
  23. package/src/SvmTypes.res +9 -0
  24. package/src/SvmTypes.res.mjs +14 -0
  25. package/src/sources/EventRouter.res +65 -0
  26. package/src/sources/EventRouter.res.mjs +43 -0
  27. package/src/sources/HyperSyncClient.res +30 -157
  28. package/src/sources/HyperSyncClient.res.mjs +20 -6
  29. package/src/sources/HyperSyncSolanaClient.res +227 -0
  30. package/src/sources/HyperSyncSolanaClient.res.mjs +25 -0
  31. package/src/sources/HyperSyncSolanaSource.res +515 -0
  32. package/src/sources/HyperSyncSolanaSource.res.mjs +441 -0
  33. package/src/sources/HyperSyncSource.res +5 -8
  34. package/src/sources/HyperSyncSource.res.mjs +1 -8
  35. package/src/sources/RpcSource.res.mjs +1 -1
  36. package/src/sources/Svm.res +2 -2
  37. package/src/sources/Svm.res.mjs +3 -2
  38. package/src/tui/Tui.res +9 -2
  39. package/src/tui/Tui.res.mjs +19 -4
  40. package/src/tui/components/TuiData.res +3 -0
  41. package/svm.schema.json +345 -4
@@ -113,6 +113,26 @@ let publicConfigChainSchema = S$RescriptSchema.schema(s => ({
113
113
  contracts: s.m(S$RescriptSchema.option(S$RescriptSchema.dict(chainContractSchema)))
114
114
  }));
115
115
 
116
+ let svmEventDescriptorSchema = S$RescriptSchema.schema(s => ({
117
+ discriminator: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
118
+ discriminatorByteLen: s.m(S$RescriptSchema.int),
119
+ includeTransaction: s.m(S$RescriptSchema.bool),
120
+ includeLogs: s.m(S$RescriptSchema.bool),
121
+ accountFilters: s.m(S$RescriptSchema.option(S$RescriptSchema.array(S$RescriptSchema.schema(s => ({
122
+ position: s.m(S$RescriptSchema.int),
123
+ values: s.m(S$RescriptSchema.array(S$RescriptSchema.string))
124
+ }))))),
125
+ isInner: s.m(S$RescriptSchema.option(S$RescriptSchema.bool)),
126
+ accounts: s.m(S$RescriptSchema.option(S$RescriptSchema.array(S$RescriptSchema.string))),
127
+ args: s.m(S$RescriptSchema.option(S$RescriptSchema.json(false)))
128
+ }));
129
+
130
+ let svmAbiSchema = S$RescriptSchema.schema(s => ({
131
+ programId: s.m(S$RescriptSchema.string),
132
+ definedTypes: s.m(S$RescriptSchema.json(false)),
133
+ source: s.m(S$RescriptSchema.string)
134
+ }));
135
+
116
136
  let contractEventItemSchema = S$RescriptSchema.schema(s => ({
117
137
  event: s.m(S$RescriptSchema.string),
118
138
  name: s.m(S$RescriptSchema.string),
@@ -120,18 +140,21 @@ let contractEventItemSchema = S$RescriptSchema.schema(s => ({
120
140
  params: s.m(S$RescriptSchema.option(S$RescriptSchema.array(EventConfigBuilder.eventParamSchema))),
121
141
  kind: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
122
142
  blockFields: s.m(S$RescriptSchema.option(S$RescriptSchema.array(Internal.evmBlockFieldSchema))),
123
- transactionFields: s.m(S$RescriptSchema.option(S$RescriptSchema.array(Internal.evmTransactionFieldSchema)))
143
+ transactionFields: s.m(S$RescriptSchema.option(S$RescriptSchema.array(Internal.evmTransactionFieldSchema))),
144
+ svm: s.m(S$RescriptSchema.option(svmEventDescriptorSchema))
124
145
  }));
125
146
 
126
147
  let contractConfigSchema = S$RescriptSchema.schema(s => ({
127
148
  abi: s.m(S$RescriptSchema.json(false)),
128
149
  handler: s.m(S$RescriptSchema.option(S$RescriptSchema.string)),
129
- events: s.m(S$RescriptSchema.option(S$RescriptSchema.array(contractEventItemSchema)))
150
+ events: s.m(S$RescriptSchema.option(S$RescriptSchema.array(contractEventItemSchema))),
151
+ svmAbi: s.m(S$RescriptSchema.option(svmAbiSchema))
130
152
  }));
131
153
 
132
154
  let publicConfigEcosystemSchema = S$RescriptSchema.schema(s => ({
133
155
  chains: s.m(S$RescriptSchema.dict(publicConfigChainSchema)),
134
- contracts: s.m(S$RescriptSchema.option(S$RescriptSchema.dict(contractConfigSchema)))
156
+ contracts: s.m(S$RescriptSchema.option(S$RescriptSchema.dict(contractConfigSchema))),
157
+ programs: s.m(S$RescriptSchema.option(S$RescriptSchema.dict(contractConfigSchema)))
135
158
  }));
136
159
 
137
160
  let publicConfigEvmSchema = S$RescriptSchema.schema(s => ({
@@ -419,6 +442,7 @@ function fromPublic(publicConfigJson) {
419
442
  let lowercaseAddresses = evm !== undefined ? Stdlib_Option.getOr(Primitive_option.valFromOption(evm).addressFormat, "checksum") === "lowercase" : false;
420
443
  let match$4 = publicConfig.evm;
421
444
  let match$5 = publicConfig.fuel;
445
+ let match$6 = publicConfig.svm;
422
446
  let publicContractsConfig;
423
447
  switch (ecosystemName) {
424
448
  case "evm" :
@@ -428,25 +452,25 @@ function fromPublic(publicConfigJson) {
428
452
  publicContractsConfig = match$5 !== undefined ? Primitive_option.valFromOption(match$5).contracts : undefined;
429
453
  break;
430
454
  case "svm" :
431
- publicContractsConfig = undefined;
455
+ publicContractsConfig = match$6 !== undefined ? Primitive_option.valFromOption(match$6).programs : undefined;
432
456
  break;
433
457
  }
434
458
  let evm$1 = publicConfig.evm;
435
- let match$6;
459
+ let match$7;
436
460
  if (evm$1 !== undefined) {
437
461
  let evm$2 = Primitive_option.valFromOption(evm$1);
438
- match$6 = [
462
+ match$7 = [
439
463
  new Set(EventConfigBuilder.alwaysIncludedBlockFields.concat(Stdlib_Option.getOr(evm$2.globalBlockFields, []))),
440
464
  new Set(Stdlib_Option.getOr(evm$2.globalTransactionFields, []))
441
465
  ];
442
466
  } else {
443
- match$6 = [
467
+ match$7 = [
444
468
  new Set(EventConfigBuilder.alwaysIncludedBlockFields),
445
469
  new Set()
446
470
  ];
447
471
  }
448
- let globalTransactionFieldsSet = match$6[1];
449
- let globalBlockFieldsSet = match$6[0];
472
+ let globalTransactionFieldsSet = match$7[1];
473
+ let globalBlockFieldsSet = match$7[0];
450
474
  let contractDataByName = {};
451
475
  if (publicContractsConfig !== undefined) {
452
476
  Object.entries(publicContractsConfig).forEach(param => {
@@ -458,11 +482,13 @@ function fromPublic(publicConfigJson) {
458
482
  contractDataByName[capitalizedName] = {
459
483
  abi: abi,
460
484
  eventSignatures: eventSignatures,
461
- events: contractConfig.events
485
+ events: contractConfig.events,
486
+ svmAbi: contractConfig.svmAbi
462
487
  };
463
488
  });
464
489
  }
465
- let buildContractEvents = (contractName, events, abi, chainId, startBlock) => {
490
+ let buildContractEvents = (contractName, events, abi, chainId, startBlock, addresses, svmDefinedTypesOpt) => {
491
+ let svmDefinedTypes = svmDefinedTypesOpt !== undefined ? svmDefinedTypesOpt : null;
466
492
  if (events !== undefined) {
467
493
  return events.map(eventItem => {
468
494
  let eventName = eventItem.name;
@@ -470,17 +496,27 @@ function fromPublic(publicConfigJson) {
470
496
  let params = Stdlib_Option.getOr(eventItem.params, []);
471
497
  let kind = eventItem.kind;
472
498
  switch (ecosystemName) {
499
+ case "evm" :
500
+ return EventConfigBuilder.buildEvmEventConfig(contractName, eventName, sighash, params, false, undefined, undefined, undefined, chainId, ecosystem.onEventBlockFilterSchema, eventItem.blockFields, eventItem.transactionFields, startBlock, Primitive_option.some(globalBlockFieldsSet), Primitive_option.some(globalTransactionFieldsSet));
473
501
  case "fuel" :
474
502
  if (kind !== undefined) {
475
503
  return EventConfigBuilder.buildFuelEventConfig(contractName, eventName, kind, sighash, abi, false, undefined, undefined, startBlock);
476
504
  } else {
477
505
  return Stdlib_JsError.throwWithMessage(`Fuel event ` + contractName + `.` + eventName + ` is missing "kind" in internal config`);
478
506
  }
479
- case "evm" :
480
507
  case "svm" :
481
- break;
508
+ let len = addresses.length;
509
+ let programId = len !== 1 ? (
510
+ len !== 0 ? Stdlib_JsError.throwWithMessage(`SVM program ` + contractName + ` on chain ` + chainId.toString() + ` has multiple addresses; a program is uniquely identified by a single program_id`) : Stdlib_JsError.throwWithMessage(`SVM program ` + contractName + ` on chain ` + chainId.toString() + ` is missing a program_id`)
511
+ ) : addresses[0];
512
+ let s = eventItem.svm;
513
+ let svm = s !== undefined ? Primitive_option.valFromOption(s) : Stdlib_JsError.throwWithMessage(`SVM instruction ` + contractName + `.` + eventName + ` is missing the "svm" descriptor in internal config`);
514
+ let accountFilters = Stdlib_Option.getOr(svm.accountFilters, []).map(af => ({
515
+ position: af.position,
516
+ values: af.values
517
+ }));
518
+ return EventConfigBuilder.buildSvmInstructionEventConfig(contractName, eventName, programId, svm.discriminator, svm.discriminatorByteLen, svm.includeTransaction, svm.includeLogs, accountFilters, svm.isInner, false, undefined, undefined, Stdlib_Option.getOr(svm.accounts, []), Stdlib_Option.getOr(svm.args, null), svmDefinedTypes, startBlock);
482
519
  }
483
- return EventConfigBuilder.buildEvmEventConfig(contractName, eventName, sighash, params, false, undefined, undefined, undefined, chainId, ecosystem.onEventBlockFilterSchema, eventItem.blockFields, eventItem.transactionFields, startBlock, Primitive_option.some(globalBlockFieldsSet), Primitive_option.some(globalTransactionFieldsSet));
484
520
  });
485
521
  } else {
486
522
  return [];
@@ -517,9 +553,10 @@ function fromPublic(publicConfigJson) {
517
553
  let contractData = param[1];
518
554
  let capitalizedName = param[0];
519
555
  let chainContract = chainContracts[capitalizedName];
520
- let addresses = Stdlib_Option.getOr(Stdlib_Option.flatMap(chainContract, cc => cc.addresses), []).map(parseAddress);
556
+ let rawAddresses = Stdlib_Option.getOr(Stdlib_Option.flatMap(chainContract, cc => cc.addresses), []);
557
+ let addresses = rawAddresses.map(parseAddress);
521
558
  let startBlock = Stdlib_Option.flatMap(chainContract, cc => cc.startBlock);
522
- let events = buildContractEvents(capitalizedName, contractData.events, contractData.abi, chainId, startBlock);
559
+ let events = buildContractEvents(capitalizedName, contractData.events, contractData.abi, chainId, startBlock, rawAddresses, Stdlib_Option.getOr(Stdlib_Option.map(contractData.svmAbi, a => a.definedTypes), null));
523
560
  return {
524
561
  name: capitalizedName,
525
562
  abi: contractData.abi,
@@ -576,6 +613,7 @@ function fromPublic(publicConfigJson) {
576
613
  let rpc = publicChainConfig.rpc;
577
614
  sourceConfig = rpc !== undefined ? ({
578
615
  TAG: "SvmSourceConfig",
616
+ hypersync: publicChainConfig.hypersync,
579
617
  rpc: rpc
580
618
  }) : Stdlib_JsError.throwWithMessage(`Chain ` + chainName + ` is missing rpc endpoint in config`);
581
619
  break;
@@ -862,6 +900,8 @@ export {
862
900
  rpcConfigSchema,
863
901
  chainContractSchema,
864
902
  publicConfigChainSchema,
903
+ svmEventDescriptorSchema,
904
+ svmAbiSchema,
865
905
  contractEventItemSchema,
866
906
  contractConfigSchema,
867
907
  publicConfigEcosystemSchema,
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
- HypersyncClient.setLogLevel(hypersyncLogLevel);
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 */
@@ -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
- JsError.throwWithMessage(`SVM does not support indexer.onEvent or indexer.contractRegister. Use indexer.onSlot for per-slot handlers.`)
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
- return Stdlib_JsError.throwWithMessage(`SVM does not support indexer.onEvent or indexer.contractRegister. Use indexer.onSlot for per-slot handlers.`);
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 {
@@ -71,7 +71,6 @@ module Entity = {
71
71
  fieldNameIndices: make(~hash=TableIndices.Index.getFieldName),
72
72
  }
73
73
 
74
- exception UndefinedKey(string)
75
74
  let updateIndices = (
76
75
  self: t<'entity>,
77
76
  ~entity: 'entity,
@@ -83,8 +82,7 @@ module Entity = {
83
82
  let fieldValue =
84
83
  entity
85
84
  ->(Utils.magic: 'entity => dict<TableIndices.FieldValue.t>)
86
- ->Dict.get(fieldName)
87
- ->Option.getUnsafe
85
+ ->Dict.getUnsafe(fieldName)
88
86
  if !(index->TableIndices.Index.evaluate(~fieldName, ~fieldValue)) {
89
87
  entityIndices->Utils.Set.delete(index)->ignore
90
88
  }
@@ -93,27 +91,25 @@ module Entity = {
93
91
  self.fieldNameIndices.dict
94
92
  ->Dict.keysToArray
95
93
  ->Array.forEach(fieldName => {
96
- switch (
97
- entity->(Utils.magic: 'entity => dict<TableIndices.FieldValue.t>)->Dict.get(fieldName),
98
- self.fieldNameIndices.dict->Dict.get(fieldName),
99
- ) {
100
- | (Some(fieldValue), Some(indices)) =>
101
- indices
102
- ->values
103
- ->Array.forEach(((index, relatedEntityIds)) => {
104
- if index->TableIndices.Index.evaluate(~fieldName, ~fieldValue) {
105
- //Add entity id to indices and add index to entity indicies
106
- relatedEntityIds->Utils.Set.add(getEntityIdUnsafe(entity))->ignore
107
- entityIndices->Utils.Set.add(index)->ignore
108
- } else {
109
- relatedEntityIds->Utils.Set.delete(getEntityIdUnsafe(entity))->ignore
110
- }
111
- })
112
- | _ =>
113
- UndefinedKey(fieldName)->ErrorHandling.mkLogAndRaise(
114
- ~msg="Expected field name to exist on the referenced index and the provided entity",
115
- )
116
- }
94
+ let indices = self.fieldNameIndices.dict->Dict.getUnsafe(fieldName)
95
+ // A missing key reads as `undefined`, which matches the `None` arm of
96
+ // `FieldValue.t` (`option<...>`). Mirror `addEmptyIndex` so nullable
97
+ // FK columns that were omitted on the set entity don't crash.
98
+ let fieldValue =
99
+ entity
100
+ ->(Utils.magic: 'entity => dict<TableIndices.FieldValue.t>)
101
+ ->Dict.getUnsafe(fieldName)
102
+ indices
103
+ ->values
104
+ ->Array.forEach(((index, relatedEntityIds)) => {
105
+ if index->TableIndices.Index.evaluate(~fieldName, ~fieldValue) {
106
+ //Add entity id to indices and add index to entity indicies
107
+ relatedEntityIds->Utils.Set.add(getEntityIdUnsafe(entity))->ignore
108
+ entityIndices->Utils.Set.add(index)->ignore
109
+ } else {
110
+ relatedEntityIds->Utils.Set.delete(getEntityIdUnsafe(entity))->ignore
111
+ }
112
+ })
117
113
  })
118
114
  }
119
115
 
@@ -90,8 +90,6 @@ function make$1() {
90
90
  };
91
91
  }
92
92
 
93
- let UndefinedKey = /* @__PURE__ */Primitive_exceptions.create("InMemoryTable.Entity.UndefinedKey");
94
-
95
93
  function updateIndices(self, entity, entityIndices) {
96
94
  entityIndices.forEach(index => {
97
95
  let fieldName = TableIndices.Index.getFieldName(index);
@@ -102,22 +100,9 @@ function updateIndices(self, entity, entityIndices) {
102
100
  }
103
101
  });
104
102
  Object.keys(self.fieldNameIndices.dict).forEach(fieldName => {
105
- let match = entity[fieldName];
106
- let match$1 = self.fieldNameIndices.dict[fieldName];
107
- if (match === undefined) {
108
- return ErrorHandling.mkLogAndRaise(undefined, "Expected field name to exist on the referenced index and the provided entity", {
109
- RE_EXN_ID: UndefinedKey,
110
- _1: fieldName
111
- });
112
- }
113
- if (match$1 === undefined) {
114
- return ErrorHandling.mkLogAndRaise(undefined, "Expected field name to exist on the referenced index and the provided entity", {
115
- RE_EXN_ID: UndefinedKey,
116
- _1: fieldName
117
- });
118
- }
119
- let fieldValue = Primitive_option.valFromOption(match);
120
- Object.values(match$1.dict).forEach(param => {
103
+ let indices = self.fieldNameIndices.dict[fieldName];
104
+ let fieldValue = entity[fieldName];
105
+ Object.values(indices.dict).forEach(param => {
121
106
  let relatedEntityIds = param[1];
122
107
  let index = param[0];
123
108
  if (TableIndices.Index.evaluate(index, fieldName, fieldValue)) {
@@ -333,7 +318,6 @@ let Entity = {
333
318
  getEntityIdUnsafe: getEntityIdUnsafe,
334
319
  makeIndicesSerializedToValue: makeIndicesSerializedToValue,
335
320
  make: make$1,
336
- UndefinedKey: UndefinedKey,
337
321
  updateIndices: updateIndices,
338
322
  deleteEntityFromIndices: deleteEntityFromIndices,
339
323
  initValue: initValue,
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,