envio 3.0.2-svm-alpha.0 → 3.0.2-svm-alpha.1

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/index.d.ts CHANGED
@@ -1000,6 +1000,15 @@ export type SvmInstruction = {
1000
1000
  readonly decoded?: SvmDecodedInstruction;
1001
1001
  };
1002
1002
 
1003
+ export type SvmTokenBalance = {
1004
+ readonly account?: string;
1005
+ readonly mint?: string;
1006
+ readonly owner?: string;
1007
+ /** u64 decimal string. Cast with BigInt(...) for arithmetic. */
1008
+ readonly preAmount?: string;
1009
+ readonly postAmount?: string;
1010
+ };
1011
+
1003
1012
  /** Parent transaction surfaced when an instruction's
1004
1013
  * `include_transaction` flag is `true`. */
1005
1014
  export type SvmTransaction = {
@@ -1013,6 +1022,9 @@ export type SvmTransaction = {
1013
1022
  readonly accountKeys: readonly string[];
1014
1023
  readonly recentBlockhash?: string;
1015
1024
  readonly version?: string;
1025
+ /** SPL Token / Token-2022 balance snapshots for this transaction.
1026
+ * Present when `include_token_balances` is `true`. */
1027
+ readonly tokenBalances?: readonly SvmTokenBalance[];
1016
1028
  };
1017
1029
 
1018
1030
  export type SvmLog = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envio",
3
- "version": "3.0.2-svm-alpha.0",
3
+ "version": "3.0.2-svm-alpha.1",
4
4
  "type": "module",
5
5
  "description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
6
6
  "bin": "./bin.mjs",
@@ -70,10 +70,10 @@
70
70
  "tsx": "4.21.0"
71
71
  },
72
72
  "optionalDependencies": {
73
- "envio-linux-x64": "3.0.2-svm-alpha.0",
74
- "envio-linux-x64-musl": "3.0.2-svm-alpha.0",
75
- "envio-linux-arm64": "3.0.2-svm-alpha.0",
76
- "envio-darwin-x64": "3.0.2-svm-alpha.0",
77
- "envio-darwin-arm64": "3.0.2-svm-alpha.0"
73
+ "envio-linux-x64": "3.0.2-svm-alpha.1",
74
+ "envio-linux-x64-musl": "3.0.2-svm-alpha.1",
75
+ "envio-linux-arm64": "3.0.2-svm-alpha.1",
76
+ "envio-darwin-x64": "3.0.2-svm-alpha.1",
77
+ "envio-darwin-arm64": "3.0.2-svm-alpha.1"
78
78
  }
79
79
  }
package/src/Config.res CHANGED
@@ -694,6 +694,7 @@ let fromPublic = (publicConfigJson: JSON.t) => {
694
694
  "discriminatorByteLen": int,
695
695
  "includeTransaction": bool,
696
696
  "includeLogs": bool,
697
+ "includeTokenBalances": bool,
697
698
  "accountFilters": option<
698
699
  array<{"position": int, "values": array<string>}>,
699
700
  >,
@@ -723,6 +724,7 @@ let fromPublic = (publicConfigJson: JSON.t) => {
723
724
  ~discriminatorByteLen=svm["discriminatorByteLen"],
724
725
  ~includeTransaction=svm["includeTransaction"],
725
726
  ~includeLogs=svm["includeLogs"],
727
+ ~includeTokenBalances=svm["includeTokenBalances"],
726
728
  ~accountFilters,
727
729
  ~isInner=svm["isInner"],
728
730
  ~isWildcard=false,
@@ -515,7 +515,7 @@ function fromPublic(publicConfigJson) {
515
515
  position: af.position,
516
516
  values: af.values
517
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);
518
+ return EventConfigBuilder.buildSvmInstructionEventConfig(contractName, eventName, programId, svm.discriminator, svm.discriminatorByteLen, svm.includeTransaction, svm.includeLogs, svm.includeTokenBalances, accountFilters, svm.isInner, false, undefined, undefined, Stdlib_Option.getOr(svm.accounts, []), Stdlib_Option.getOr(svm.args, null), svmDefinedTypes, startBlock);
519
519
  }
520
520
  });
521
521
  } else {
package/src/Envio.res CHANGED
@@ -58,6 +58,14 @@ type svmInstruction = {
58
58
  decoded?: svmDecodedInstruction,
59
59
  }
60
60
 
61
+ type svmTokenBalance = {
62
+ account?: SvmTypes.Pubkey.t,
63
+ mint?: SvmTypes.Pubkey.t,
64
+ owner?: SvmTypes.Pubkey.t,
65
+ preAmount?: string,
66
+ postAmount?: string,
67
+ }
68
+
61
69
  type svmTransaction = {
62
70
  signatures: array<string>,
63
71
  feePayer?: SvmTypes.Pubkey.t,
@@ -68,6 +76,7 @@ type svmTransaction = {
68
76
  accountKeys: array<SvmTypes.Pubkey.t>,
69
77
  recentBlockhash?: string,
70
78
  version?: string,
79
+ tokenBalances?: array<svmTokenBalance>,
71
80
  }
72
81
 
73
82
  type svmLog = {
@@ -492,6 +492,7 @@ let buildSvmInstructionEventConfig = (
492
492
  ~discriminatorByteLen: int,
493
493
  ~includeTransaction: bool,
494
494
  ~includeLogs: bool,
495
+ ~includeTokenBalances: bool,
495
496
  ~accountFilters: array<Internal.svmAccountFilter>,
496
497
  ~isInner: option<bool>,
497
498
  ~isWildcard: bool,
@@ -526,6 +527,7 @@ let buildSvmInstructionEventConfig = (
526
527
  discriminatorByteLen,
527
528
  includeTransaction,
528
529
  includeLogs,
530
+ includeTokenBalances,
529
531
  accountFilters,
530
532
  isInner,
531
533
  accounts,
@@ -350,7 +350,7 @@ 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) {
353
+ function buildSvmInstructionEventConfig(contractName, instructionName, programId, discriminator, discriminatorByteLen, includeTransaction, includeLogs, includeTokenBalances, accountFilters, isInner, isWildcard, handler, contractRegister, accountsOpt, argsOpt, definedTypesOpt, startBlock) {
354
354
  let accounts = accountsOpt !== undefined ? accountsOpt : [];
355
355
  let args = argsOpt !== undefined ? argsOpt : null;
356
356
  let definedTypes = definedTypesOpt !== undefined ? definedTypesOpt : null;
@@ -372,6 +372,7 @@ function buildSvmInstructionEventConfig(contractName, instructionName, programId
372
372
  discriminatorByteLen: discriminatorByteLen,
373
373
  includeTransaction: includeTransaction,
374
374
  includeLogs: includeLogs,
375
+ includeTokenBalances: includeTokenBalances,
375
376
  accountFilters: accountFilters,
376
377
  isInner: isInner,
377
378
  accounts: accounts,
package/src/Hasura.res CHANGED
@@ -40,6 +40,19 @@ let clearMetadataRoute = Rest.route(() => {
40
40
  responses,
41
41
  })
42
42
 
43
+ let reloadMetadataRoute = Rest.route(() => {
44
+ method: Post,
45
+ path: "",
46
+ input: s => {
47
+ let _ = s.field("type", S.literal("reload_metadata"))
48
+ {
49
+ "args": s.field("args", S.json(~validate=false)),
50
+ "auth": s->auth,
51
+ }
52
+ },
53
+ responses,
54
+ })
55
+
43
56
  let trackTablesRoute = Rest.route(() => {
44
57
  method: Post,
45
58
  path: "",
@@ -110,6 +123,31 @@ let clearHasuraMetadata = async (~endpoint, ~auth) => {
110
123
  }
111
124
  }
112
125
 
126
+ let reloadHasuraMetadata = async (~endpoint, ~auth) => {
127
+ try {
128
+ let result = await reloadMetadataRoute->Rest.fetch(
129
+ {
130
+ "auth": auth,
131
+ "args": {
132
+ "reload_sources": ["default"],
133
+ }->(Utils.magic: 'a => JSON.t),
134
+ },
135
+ ~client=Rest.client(endpoint),
136
+ )
137
+ let msg = switch result {
138
+ | QuerySucceeded => "Hasura metadata reloaded"
139
+ | AlreadyDone => "Hasura metadata reload acknowledged"
140
+ }
141
+ Logging.trace(msg)
142
+ } catch {
143
+ | exn =>
144
+ Logging.error({
145
+ "msg": `There was an issue reloading hasura metadata - table tracking may race with schema creation.`,
146
+ "err": exn->Utils.prettifyExn,
147
+ })
148
+ }
149
+ }
150
+
113
151
  let trackTables = async (~endpoint, ~auth, ~pgSchema, ~tableNames: array<string>) => {
114
152
  try {
115
153
  let result = await trackTablesRoute->Rest.fetch(
@@ -242,6 +280,11 @@ let trackDatabase = async (
242
280
 
243
281
  let _ = await clearHasuraMetadata(~endpoint, ~auth)
244
282
 
283
+ // Force Hasura to re-introspect the source schema before tracking, otherwise
284
+ // freshly-created user tables may be invisible to pg_track_tables and the call
285
+ // returns `metadata-warnings` (HTTP 400), leaving tracking permanently broken.
286
+ await reloadHasuraMetadata(~endpoint, ~auth)
287
+
245
288
  await trackTables(~endpoint, ~auth, ~pgSchema, ~tableNames)
246
289
 
247
290
  for i in 0 to tableNames->Array.length - 1 {
@@ -46,6 +46,21 @@ function clearMetadataRoute() {
46
46
  };
47
47
  }
48
48
 
49
+ function reloadMetadataRoute() {
50
+ return {
51
+ method: "POST",
52
+ path: "",
53
+ input: s => {
54
+ s.field("type", S$RescriptSchema.literal("reload_metadata"));
55
+ return {
56
+ args: s.field("args", S$RescriptSchema.json(false)),
57
+ auth: auth(s)
58
+ };
59
+ },
60
+ responses: responses
61
+ };
62
+ }
63
+
49
64
  function trackTablesRoute() {
50
65
  return {
51
66
  method: "POST",
@@ -112,6 +127,26 @@ async function clearHasuraMetadata(endpoint, auth) {
112
127
  }
113
128
  }
114
129
 
130
+ async function reloadHasuraMetadata(endpoint, auth) {
131
+ try {
132
+ let result = await Rest.fetch(reloadMetadataRoute, {
133
+ auth: auth,
134
+ args: {
135
+ reload_sources: ["default"]
136
+ }
137
+ }, Rest.client(endpoint, undefined));
138
+ let tmp;
139
+ tmp = result === "QuerySucceeded" ? "Hasura metadata reloaded" : "Hasura metadata reload acknowledged";
140
+ return Logging.trace(tmp);
141
+ } catch (raw_exn) {
142
+ let exn = Primitive_exceptions.internalToException(raw_exn);
143
+ return Logging.error({
144
+ msg: `There was an issue reloading hasura metadata - table tracking may race with schema creation.`,
145
+ err: Utils.prettifyExn(exn)
146
+ });
147
+ }
148
+ }
149
+
115
150
  async function trackTables(endpoint, auth, pgSchema, tableNames) {
116
151
  try {
117
152
  let result = await Rest.fetch(trackTablesRoute, {
@@ -202,6 +237,7 @@ async function trackDatabase(endpoint, auth, pgSchema, userEntities, aggregateEn
202
237
  ]);
203
238
  Logging.info("Tracking tables in Hasura");
204
239
  await clearHasuraMetadata(endpoint, auth);
240
+ await reloadHasuraMetadata(endpoint, auth);
205
241
  await trackTables(endpoint, auth, pgSchema, tableNames);
206
242
  for (let i = 0, i_finish = tableNames.length; i < i_finish; ++i) {
207
243
  let tableName = tableNames[i];
@@ -231,10 +267,12 @@ export {
231
267
  auth,
232
268
  responses,
233
269
  clearMetadataRoute,
270
+ reloadMetadataRoute,
234
271
  trackTablesRoute,
235
272
  rawBodyRoute,
236
273
  sendOperation,
237
274
  clearHasuraMetadata,
275
+ reloadHasuraMetadata,
238
276
  trackTables,
239
277
  createSelectPermission,
240
278
  createEntityRelationship,
package/src/Internal.res CHANGED
@@ -439,6 +439,7 @@ type svmInstructionEventConfig = {
439
439
  discriminatorByteLen: int,
440
440
  includeTransaction: bool,
441
441
  includeLogs: bool,
442
+ includeTokenBalances: bool,
442
443
  accountFilters: array<svmAccountFilter>,
443
444
  /** `None` matches both outer and inner (CPI-invoked) instructions. */
444
445
  isInner: option<bool>,
@@ -65,11 +65,21 @@ module QueryTypes = {
65
65
  | @as("kind") Kind
66
66
  | @as("message") Message
67
67
 
68
+ type tokenBalanceField =
69
+ | @as("slot") Slot
70
+ | @as("transaction_index") TransactionIndex
71
+ | @as("account") Account
72
+ | @as("mint") Mint
73
+ | @as("owner") Owner
74
+ | @as("pre_amount") PreAmount
75
+ | @as("post_amount") PostAmount
76
+
68
77
  type fieldSelection = {
69
78
  block?: array<blockField>,
70
79
  transaction?: array<transactionField>,
71
80
  instruction?: array<instructionField>,
72
81
  log?: array<logField>,
82
+ tokenBalance?: array<tokenBalanceField>,
73
83
  }
74
84
 
75
85
  /** Filter for selecting instructions. All non-empty fields are AND-ed: an
@@ -96,6 +106,7 @@ module QueryTypes = {
96
106
  isInner?: bool,
97
107
  includeTransaction?: bool,
98
108
  includeLogs?: bool,
109
+ includeTokenBalances?: bool,
99
110
  }
100
111
 
101
112
  type transactionSelection = {
@@ -183,11 +194,22 @@ module ResponseTypes = {
183
194
  message?: string,
184
195
  }
185
196
 
197
+ type tokenBalance = {
198
+ slot: int,
199
+ transactionIndex?: int,
200
+ account?: string,
201
+ mint?: string,
202
+ owner?: string,
203
+ preAmount?: string,
204
+ postAmount?: string,
205
+ }
206
+
186
207
  type queryResponseData = {
187
208
  blocks: array<block>,
188
209
  transactions: array<transaction>,
189
210
  instructions: array<instruction>,
190
211
  logs: array<log>,
212
+ tokenBalances: array<tokenBalance>,
191
213
  }
192
214
 
193
215
  type queryResponse = {
@@ -68,6 +68,7 @@ let buildInstructionSelections = (
68
68
  isInner: ?cfg.isInner,
69
69
  includeTransaction: cfg.includeTransaction,
70
70
  includeLogs: cfg.includeLogs,
71
+ includeTokenBalances: cfg.includeTokenBalances,
71
72
  }: HyperSyncSolanaClient.QueryTypes.instructionSelection
72
73
  ),
73
74
  )
@@ -224,6 +225,16 @@ let toSvmTransaction = (
224
225
  version: ?tx.version,
225
226
  }
226
227
 
228
+ let toSvmTokenBalance = (
229
+ tb: HyperSyncSolanaClient.ResponseTypes.tokenBalance,
230
+ ): Envio.svmTokenBalance => {
231
+ account: ?tb.account->Option.map(SvmTypes.Pubkey.fromStringUnsafe),
232
+ mint: ?tb.mint->Option.map(SvmTypes.Pubkey.fromStringUnsafe),
233
+ owner: ?tb.owner->Option.map(SvmTypes.Pubkey.fromStringUnsafe),
234
+ preAmount: ?tb.preAmount,
235
+ postAmount: ?tb.postAmount,
236
+ }
237
+
227
238
  // Probe the discriminator byte-length ordering longest-first. Stops at the
228
239
  // first router hit. Falls back to the `_none` key (program-wide handler) when
229
240
  // no discriminator-keyed handler matches.
@@ -296,6 +307,8 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientMaxRetries, clien
296
307
  // startup; reused on every decoded instruction.
297
308
  let schemaHandlesByProgram = buildSchemaHandles(eventConfigs)
298
309
 
310
+ let needsTokenBalances = eventConfigs->Belt.Array.some(cfg => cfg.includeTokenBalances)
311
+
299
312
  let getItemsOrThrow = async (
300
313
  ~fromBlock,
301
314
  ~toBlock,
@@ -311,10 +324,22 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientMaxRetries, clien
311
324
  let pageFetchRef = Hrtime.makeTimer()
312
325
 
313
326
  let instructionSelections = buildInstructionSelections(eventConfigs)
327
+ let fields = if needsTokenBalances {
328
+ Some(
329
+ (
330
+ {
331
+ tokenBalance: [Slot, TransactionIndex, Account, Mint, Owner, PreAmount, PostAmount],
332
+ }: HyperSyncSolanaClient.QueryTypes.fieldSelection
333
+ ),
334
+ )
335
+ } else {
336
+ None
337
+ }
314
338
  let query: HyperSyncSolanaClient.query = {
315
339
  fromSlot: fromBlock,
316
340
  toSlot: ?toBlock,
317
341
  instructions: instructionSelections,
342
+ fields: ?fields,
318
343
  }
319
344
 
320
345
  Prometheus.SourceRequestCount.increment(
@@ -374,6 +399,21 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientMaxRetries, clien
374
399
  }
375
400
  })
376
401
 
402
+ let tokenBalancesByTx = Dict.make()
403
+ if needsTokenBalances {
404
+ resp.data.tokenBalances->Belt.Array.forEach(tb => {
405
+ switch tb.transactionIndex {
406
+ | Some(txIdx) =>
407
+ let key = tb.slot->Int.toString ++ ":" ++ txIdx->Int.toString
408
+ switch tokenBalancesByTx->Dict.get(key) {
409
+ | Some(existing) => existing->Array.push(tb)
410
+ | None => tokenBalancesByTx->Dict.set(key, [tb])
411
+ }
412
+ | None => ()
413
+ }
414
+ })
415
+ }
416
+
377
417
  let parsedQueueItems = []
378
418
  resp.data.instructions->Belt.Array.forEach(instr => {
379
419
  let programId = instr.programId->SvmTypes.Pubkey.fromStringUnsafe
@@ -399,6 +439,17 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientMaxRetries, clien
399
439
  instr.slot->Int.toString ++ ":" ++ instr.transactionIndex->Int.toString
400
440
  let maybeTx =
401
441
  txByKey->Utils.Dict.dangerouslyGetNonOption(txKey)->Option.map(toSvmTransaction)
442
+ let maybeTx = if eventConfig.includeTokenBalances {
443
+ maybeTx->Option.map(tx => {
444
+ let maybeBalances =
445
+ tokenBalancesByTx
446
+ ->Utils.Dict.dangerouslyGetNonOption(txKey)
447
+ ->Option.map(bals => bals->Array.map(toSvmTokenBalance))
448
+ {...tx, tokenBalances: ?maybeBalances}
449
+ })
450
+ } else {
451
+ maybeTx
452
+ }
402
453
  let logKey =
403
454
  instr.slot->Int.toString ++
404
455
  ":" ++
@@ -118,7 +118,8 @@ function buildInstructionSelections(eventConfigs) {
118
118
  a5: a5,
119
119
  isInner: cfg.isInner,
120
120
  includeTransaction: cfg.includeTransaction,
121
- includeLogs: cfg.includeLogs
121
+ includeLogs: cfg.includeLogs,
122
+ includeTokenBalances: cfg.includeTokenBalances
122
123
  };
123
124
  });
124
125
  }
@@ -230,6 +231,16 @@ function toSvmTransaction(tx) {
230
231
  };
231
232
  }
232
233
 
234
+ function toSvmTokenBalance(tb) {
235
+ return {
236
+ account: Stdlib_Option.map(tb.account, prim => prim),
237
+ mint: Stdlib_Option.map(tb.mint, prim => prim),
238
+ owner: Stdlib_Option.map(tb.owner, prim => prim),
239
+ preAmount: tb.preAmount,
240
+ postAmount: tb.postAmount
241
+ };
242
+ }
243
+
233
244
  function probeRouter(router, programId, instr, byteLengthsDesc, contractAddress, indexingAddresses) {
234
245
  let probe = dN => {
235
246
  let tag = EventRouter.getSvmEventId(programId, dN);
@@ -279,15 +290,28 @@ function make(param) {
279
290
  orderingByProgram[o.programId] = o.byteLengthsDesc;
280
291
  });
281
292
  let schemaHandlesByProgram = buildSchemaHandles(eventConfigs);
293
+ let needsTokenBalances = Belt_Array.some(eventConfigs, cfg => cfg.includeTokenBalances);
282
294
  let getItemsOrThrow = async (fromBlock, toBlock, param, indexingAddresses, knownHeight, param$1, param$2, retry, logger) => {
283
295
  let totalTimeRef = Hrtime.makeTimer();
284
296
  let pageFetchRef = Hrtime.makeTimer();
285
297
  let instructionSelections = buildInstructionSelections(eventConfigs);
298
+ let fields = needsTokenBalances ? ({
299
+ tokenBalance: [
300
+ "slot",
301
+ "transaction_index",
302
+ "account",
303
+ "mint",
304
+ "owner",
305
+ "pre_amount",
306
+ "post_amount"
307
+ ]
308
+ }) : undefined;
286
309
  let query_instructions = instructionSelections;
287
310
  let query = {
288
311
  fromSlot: fromBlock,
289
312
  toSlot: toBlock,
290
- instructions: query_instructions
313
+ instructions: query_instructions,
314
+ fields: fields
291
315
  };
292
316
  Prometheus.SourceRequestCount.increment(name, chain, "getInstructions");
293
317
  let resp;
@@ -335,6 +359,22 @@ function make(param) {
335
359
  logsByKey[key] = [log];
336
360
  }
337
361
  });
362
+ let tokenBalancesByTx = {};
363
+ if (needsTokenBalances) {
364
+ Belt_Array.forEach(resp.data.tokenBalances, tb => {
365
+ let txIdx = tb.transactionIndex;
366
+ if (txIdx === undefined) {
367
+ return;
368
+ }
369
+ let key = tb.slot.toString() + ":" + txIdx.toString();
370
+ let existing = tokenBalancesByTx[key];
371
+ if (existing !== undefined) {
372
+ existing.push(tb);
373
+ } else {
374
+ tokenBalancesByTx[key] = [tb];
375
+ }
376
+ });
377
+ }
338
378
  let parsedQueueItems = [];
339
379
  Belt_Array.forEach(resp.data.instructions, instr => {
340
380
  let programId = instr.programId;
@@ -344,6 +384,12 @@ function make(param) {
344
384
  if (maybeConfig !== undefined) {
345
385
  let txKey = instr.slot.toString() + ":" + instr.transactionIndex.toString();
346
386
  let maybeTx = Stdlib_Option.map(txByKey[txKey], toSvmTransaction);
387
+ let maybeTx$1 = maybeConfig.includeTokenBalances ? Stdlib_Option.map(maybeTx, tx => {
388
+ let maybeBalances = Stdlib_Option.map(tokenBalancesByTx[txKey], bals => bals.map(toSvmTokenBalance));
389
+ let newrecord = {...tx};
390
+ newrecord.tokenBalances = maybeBalances;
391
+ return newrecord;
392
+ }) : maybeTx;
347
393
  let logKey = instr.slot.toString() + ":" + instr.transactionIndex.toString() + ":" + serializeInstructionAddress(instr.instructionAddress);
348
394
  let maybeLogs = Stdlib_Option.map(logsByKey[logKey], logs => logs.map(log => ({
349
395
  kind: Stdlib_Option.getOr(log.kind, ""),
@@ -352,7 +398,7 @@ function make(param) {
352
398
  let payload_contractName = maybeConfig.contractName;
353
399
  let payload_eventName = maybeConfig.name;
354
400
  let payload_instruction = toSvmInstruction(instr, schemaHandlesByProgram);
355
- let payload_transaction = maybeConfig.includeTransaction ? maybeTx : undefined;
401
+ let payload_transaction = maybeConfig.includeTransaction ? maybeTx$1 : undefined;
356
402
  let payload_logs = maybeConfig.includeLogs ? maybeLogs : undefined;
357
403
  let payload_slot = instr.slot;
358
404
  let payload_block = {
@@ -435,6 +481,7 @@ export {
435
481
  decodeIfPossible,
436
482
  toSvmInstruction,
437
483
  toSvmTransaction,
484
+ toSvmTokenBalance,
438
485
  probeRouter,
439
486
  make,
440
487
  }
package/svm.schema.json CHANGED
@@ -287,6 +287,13 @@
287
287
  "null"
288
288
  ]
289
289
  },
290
+ "include_token_balances": {
291
+ "description": "When true, also fetch SPL Token / Token-2022 balance snapshots for the parent transaction. Implies include_transaction: true.",
292
+ "type": [
293
+ "boolean",
294
+ "null"
295
+ ]
296
+ },
290
297
  "accounts": {
291
298
  "description": "Optional positional account names. The Nth entry names account slot N on the dispatched instruction; surfaces as `event.instruction.decoded.accounts.<name>`. Accounts beyond the named list become `extra_accounts`.",
292
299
  "type": [