envio 3.3.0-alpha.1 → 3.3.0-alpha.2

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 (58) hide show
  1. package/package.json +6 -6
  2. package/src/Batch.res +0 -6
  3. package/src/Batch.res.mjs +0 -11
  4. package/src/BatchProcessing.res +2 -12
  5. package/src/BatchProcessing.res.mjs +2 -7
  6. package/src/ChainFetching.res +17 -6
  7. package/src/ChainFetching.res.mjs +11 -11
  8. package/src/ChainMetadata.res +1 -11
  9. package/src/ChainMetadata.res.mjs +1 -10
  10. package/src/ChainState.res +150 -1
  11. package/src/ChainState.res.mjs +138 -9
  12. package/src/ChainState.resi +51 -1
  13. package/src/Config.res +3 -6
  14. package/src/Config.res.mjs +2 -3
  15. package/src/Core.res +7 -0
  16. package/src/CrossChainState.res +9 -26
  17. package/src/CrossChainState.res.mjs +7 -16
  18. package/src/Ecosystem.res +6 -1
  19. package/src/Envio.res +3 -2
  20. package/src/EventConfigBuilder.res +19 -5
  21. package/src/EventConfigBuilder.res.mjs +6 -4
  22. package/src/EventProcessing.res +26 -0
  23. package/src/EventProcessing.res.mjs +20 -0
  24. package/src/Internal.res +43 -4
  25. package/src/Internal.res.mjs +18 -0
  26. package/src/Main.res +3 -50
  27. package/src/Main.res.mjs +2 -23
  28. package/src/Rollback.res +1 -1
  29. package/src/Rollback.res.mjs +1 -1
  30. package/src/SimulateItems.res +3 -0
  31. package/src/SimulateItems.res.mjs +2 -1
  32. package/src/sources/Evm.res +52 -38
  33. package/src/sources/Evm.res.mjs +48 -36
  34. package/src/sources/Fuel.res +3 -1
  35. package/src/sources/Fuel.res.mjs +1 -0
  36. package/src/sources/HyperFuelSource.res +5 -0
  37. package/src/sources/HyperFuelSource.res.mjs +2 -0
  38. package/src/sources/HyperSync.res +4 -1
  39. package/src/sources/HyperSync.res.mjs +5 -3
  40. package/src/sources/HyperSync.resi +1 -0
  41. package/src/sources/HyperSyncClient.res +9 -78
  42. package/src/sources/HyperSyncClient.res.mjs +1 -22
  43. package/src/sources/HyperSyncSource.res +3 -4
  44. package/src/sources/HyperSyncSource.res.mjs +2 -1
  45. package/src/sources/RpcSource.res +7 -2
  46. package/src/sources/RpcSource.res.mjs +3 -1
  47. package/src/sources/SimulateSource.res +3 -1
  48. package/src/sources/SimulateSource.res.mjs +1 -0
  49. package/src/sources/Source.res +4 -0
  50. package/src/sources/Svm.res +25 -2
  51. package/src/sources/Svm.res.mjs +27 -2
  52. package/src/sources/SvmHyperSyncClient.res +3 -29
  53. package/src/sources/SvmHyperSyncSource.res +60 -84
  54. package/src/sources/SvmHyperSyncSource.res.mjs +59 -78
  55. package/src/sources/TransactionStore.res +111 -0
  56. package/src/sources/TransactionStore.res.mjs +77 -0
  57. package/src/tui/Tui.res +13 -16
  58. package/src/tui/Tui.res.mjs +12 -14
@@ -188,7 +188,6 @@ let toSvmInstruction = (
188
188
  instr: SvmHyperSyncClient.ResponseTypes.instruction,
189
189
  ~programName,
190
190
  ~instructionName,
191
- ~transaction,
192
191
  ~logs,
193
192
  ~block,
194
193
  ): Envio.svmInstruction => {
@@ -204,35 +203,10 @@ let toSvmInstruction = (
204
203
  d4: ?instr.d4,
205
204
  d8: ?instr.d8,
206
205
  params: ?(instr.decoded->Option.map(parseDecoded)),
207
- ?transaction,
208
206
  ?logs,
209
207
  block,
210
208
  }
211
209
 
212
- let toSvmTransaction = (tx: SvmHyperSyncClient.ResponseTypes.transaction): Envio.svmTransaction => {
213
- signatures: tx.signatures,
214
- accountKeys: tx.accountKeys->SvmTypes.Pubkey.fromStringsUnsafe,
215
- feePayer: ?(tx.feePayer->Option.map(SvmTypes.Pubkey.fromStringUnsafe)),
216
- success: ?tx.success,
217
- err: ?tx.err,
218
- // u64 lamports / compute units arrive as `int` over napi. Convert to
219
- // `bigint` so the public type stays defensible even for pathological values.
220
- fee: ?(tx.fee->Option.map(BigInt.fromInt)),
221
- computeUnitsConsumed: ?(tx.computeUnitsConsumed->Option.map(BigInt.fromInt)),
222
- recentBlockhash: ?tx.recentBlockhash,
223
- version: ?tx.version,
224
- }
225
-
226
- let toSvmTokenBalance = (
227
- tb: SvmHyperSyncClient.ResponseTypes.tokenBalance,
228
- ): Envio.svmTokenBalance => {
229
- account: ?(tb.account->Option.map(SvmTypes.Pubkey.fromStringUnsafe)),
230
- mint: ?(tb.mint->Option.map(SvmTypes.Pubkey.fromStringUnsafe)),
231
- owner: ?(tb.owner->Option.map(SvmTypes.Pubkey.fromStringUnsafe)),
232
- preAmount: ?tb.preAmount,
233
- postAmount: ?tb.postAmount,
234
- }
235
-
236
210
  // Probe the discriminator byte-length ordering longest-first. Stops at the
237
211
  // first router hit. Falls back to the `_none` key (program-wide handler) when
238
212
  // no discriminator-keyed handler matches.
@@ -273,6 +247,27 @@ let probeRouter = (
273
247
  }
274
248
  }
275
249
 
250
+ // Map a selected transaction field to the extra query-side column it needs.
251
+ // `transactionIndex` is always fetched as the store key, and `tokenBalances`
252
+ // lives in a separate table (requested via `needsTokenBalances`), so neither
253
+ // adds a transaction column here.
254
+ let toQueryTxField = (field: Internal.svmTransactionField): option<
255
+ SvmHyperSyncClient.QueryTypes.transactionField,
256
+ > =>
257
+ switch field {
258
+ | TransactionIndex => None
259
+ | Signatures => Some(Signatures)
260
+ | FeePayer => Some(FeePayer)
261
+ | Success => Some(Success)
262
+ | Err => Some(Err)
263
+ | Fee => Some(Fee)
264
+ | ComputeUnitsConsumed => Some(ComputeUnitsConsumed)
265
+ | AccountKeys => Some(AccountKeys)
266
+ | RecentBlockhash => Some(RecentBlockhash)
267
+ | Version => Some(Version)
268
+ | TokenBalances => None
269
+ }
270
+
276
271
  let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: options): t => {
277
272
  let name = "SvmHyperSync"
278
273
  let chainId = chain->ChainMap.Chain.toChainId
@@ -298,9 +293,38 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
298
293
  orderingByProgram->Dict.set(o.programId->SvmTypes.Pubkey.toString, o.byteLengthsDesc)
299
294
  )
300
295
 
301
- let needsTransactions = eventConfigs->Array.some(cfg => cfg.includeTransaction)
302
296
  let needsLogs = eventConfigs->Array.some(cfg => cfg.includeLogs)
303
- let needsTokenBalances = eventConfigs->Array.some(cfg => cfg.includeTokenBalances)
297
+
298
+ // Union of selected transaction fields across the chain's events. Drives both
299
+ // the query column selection (fetch only what's used) and the materialisation
300
+ // mask. `slot`/`transactionIndex` are always fetched as the store key.
301
+ let selectedTxFields = Utils.Set.make()
302
+ eventConfigs->Array.forEach(cfg =>
303
+ cfg.selectedTransactionFields
304
+ ->(Utils.magic: Utils.Set.t<string> => Utils.Set.t<Internal.svmTransactionField>)
305
+ ->Utils.Set.forEach(field => selectedTxFields->Utils.Set.add(field)->ignore)
306
+ )
307
+ let needsTokenBalances = selectedTxFields->Utils.Set.has(Internal.TokenBalances)
308
+ let txQueryFields = {
309
+ // Slot + TransactionIndex are always fetched so the store can be keyed by
310
+ // (slot, transactionIndex).
311
+ let fields: array<SvmHyperSyncClient.QueryTypes.transactionField> = [Slot, TransactionIndex]
312
+ selectedTxFields
313
+ ->Utils.Set.toArray
314
+ ->Array.forEach(field =>
315
+ switch toQueryTxField(field) {
316
+ | Some(queryField) => fields->Array.push(queryField)
317
+ | None => ()
318
+ }
319
+ )
320
+ fields
321
+ }
322
+ // Every selected field except `tokenBalances` (its own table) is read off a
323
+ // stored transaction record, so the transaction table must be fetched to
324
+ // populate the store — even when only `transactionIndex` (a key column) is
325
+ // selected.
326
+ let txFieldCount = selectedTxFields->Utils.Set.size
327
+ let needsTransactions = txFieldCount > 1 || (txFieldCount === 1 && !needsTokenBalances)
304
328
 
305
329
  let getItemsOrThrow = async (
306
330
  ~fromBlock,
@@ -323,23 +347,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
323
347
  // opted-into table needs its columns spelled out here.
324
348
  let fields: SvmHyperSyncClient.QueryTypes.fieldSelection = {
325
349
  block: [Slot, Blockhash, BlockTime],
326
- transaction: ?(
327
- needsTransactions
328
- ? Some([
329
- Slot,
330
- TransactionIndex,
331
- Signatures,
332
- FeePayer,
333
- Success,
334
- Err,
335
- Fee,
336
- ComputeUnitsConsumed,
337
- AccountKeys,
338
- RecentBlockhash,
339
- Version,
340
- ])
341
- : None
342
- ),
350
+ transaction: ?(needsTransactions ? Some(txQueryFields) : None),
343
351
  log: ?(needsLogs ? Some([Slot, TransactionIndex, InstructionAddress, Kind, Message]) : None),
344
352
  tokenBalance: ?(
345
353
  needsTokenBalances
@@ -358,7 +366,7 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
358
366
 
359
367
  Prometheus.SourceRequestCount.increment(~sourceName=name, ~chainId, ~method="getInstructions")
360
368
 
361
- let resp = try await client.get(~query) catch {
369
+ let (resp, transactionStore) = try await client.get(~query) catch {
362
370
  | exn =>
363
371
  throw(
364
372
  Source.GetItemsError(
@@ -390,13 +398,6 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
390
398
  }
391
399
  })
392
400
 
393
- // Per (slot, transaction_index) lookup for parent transactions.
394
- let txByKey = Dict.make()
395
- resp.data.transactions->Array.forEach(tx => {
396
- let key = tx.slot->Int.toString ++ ":" ++ tx.transactionIndex->Int.toString
397
- txByKey->Dict.set(key, tx)
398
- })
399
-
400
401
  // Per (slot, transaction_index, instruction_address) lookup for logs
401
402
  // scoped to a single instruction. `instructionAddress: None` logs are
402
403
  // attached to no instruction (rare; usually only system messages).
@@ -418,21 +419,6 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
418
419
  }
419
420
  })
420
421
 
421
- let tokenBalancesByTx = Dict.make()
422
- if needsTokenBalances {
423
- resp.data.tokenBalances->Array.forEach(tb => {
424
- switch tb.transactionIndex {
425
- | Some(txIdx) =>
426
- let key = tb.slot->Int.toString ++ ":" ++ txIdx->Int.toString
427
- switch tokenBalancesByTx->Dict.get(key) {
428
- | Some(existing) => existing->Array.push(tb)
429
- | None => tokenBalancesByTx->Dict.set(key, [tb])
430
- }
431
- | None => ()
432
- }
433
- })
434
- }
435
-
436
422
  let parsedQueueItems = []
437
423
  resp.data.instructions->Array.forEach(instr => {
438
424
  let programId = instr.programId->SvmTypes.Pubkey.fromStringUnsafe
@@ -454,20 +440,6 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
454
440
  switch maybeConfig {
455
441
  | None => ()
456
442
  | Some(eventConfig) =>
457
- let txKey = instr.slot->Int.toString ++ ":" ++ instr.transactionIndex->Int.toString
458
- let maybeTx =
459
- txByKey->Utils.Dict.dangerouslyGetNonOption(txKey)->Option.map(toSvmTransaction)
460
- let maybeTx = if eventConfig.includeTokenBalances {
461
- maybeTx->Option.map(tx => {
462
- let maybeBalances =
463
- tokenBalancesByTx
464
- ->Utils.Dict.dangerouslyGetNonOption(txKey)
465
- ->Option.map(bals => bals->Array.map(toSvmTokenBalance))
466
- {...tx, tokenBalances: ?maybeBalances}
467
- })
468
- } else {
469
- maybeTx
470
- }
471
443
  let logKey =
472
444
  instr.slot->Int.toString ++
473
445
  ":" ++
@@ -492,7 +464,6 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
492
464
  instr,
493
465
  ~programName=eventConfig.contractName,
494
466
  ~instructionName=eventConfig.name,
495
- ~transaction=eventConfig.includeTransaction ? maybeTx : None,
496
467
  ~logs=eventConfig.includeLogs ? maybeLogs : None,
497
468
  ~block={
498
469
  slot: instr.slot,
@@ -510,6 +481,8 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
510
481
  blockNumber: instr.slot,
511
482
  blockHash: "",
512
483
  logIndex: synthLogIndex(instr),
484
+ // The parent transaction is materialised from the store at batch prep.
485
+ transactionIndex: instr.transactionIndex,
513
486
  payload: payload->(Utils.magic: Envio.svmInstruction => Internal.eventPayload),
514
487
  }),
515
488
  )
@@ -539,6 +512,8 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
539
512
  {
540
513
  latestFetchedBlockTimestamp: latestBlockTime,
541
514
  parsedQueueItems,
515
+ // Raw transactions kept in Rust; materialised (selected fields) at batch prep.
516
+ transactionStore: Some(transactionStore),
542
517
  latestFetchedBlockNumber: highestSlot,
543
518
  stats: {totalTimeElapsed, parsingTimeElapsed, pageFetchTime},
544
519
  knownHeight,
@@ -563,7 +538,8 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientTimeoutMillis}: o
563
538
  maxNumBlocks: 1000,
564
539
  }
565
540
  Prometheus.SourceRequestCount.increment(~sourceName=name, ~chainId, ~method="getBlockHashes")
566
- let resp = await client.get(~query)
541
+ // Block-only query; the store page is empty.
542
+ let (resp, _) = await client.get(~query)
567
543
  resp.data.blocks->Array.forEach(b =>
568
544
  blockDatas
569
545
  ->Array.push({
@@ -166,7 +166,7 @@ function parseDecoded(d) {
166
166
  };
167
167
  }
168
168
 
169
- function toSvmInstruction(instr, programName, instructionName, transaction, logs, block) {
169
+ function toSvmInstruction(instr, programName, instructionName, logs, block) {
170
170
  return {
171
171
  programName: programName,
172
172
  instructionName: instructionName,
@@ -180,36 +180,11 @@ function toSvmInstruction(instr, programName, instructionName, transaction, logs
180
180
  d4: instr.d4,
181
181
  d8: instr.d8,
182
182
  params: Stdlib_Option.map(instr.decoded, parseDecoded),
183
- transaction: transaction,
184
183
  logs: logs,
185
184
  block: block
186
185
  };
187
186
  }
188
187
 
189
- function toSvmTransaction(tx) {
190
- return {
191
- signatures: tx.signatures,
192
- feePayer: Stdlib_Option.map(tx.feePayer, prim => prim),
193
- success: tx.success,
194
- err: tx.err,
195
- fee: Stdlib_Option.map(tx.fee, prim => BigInt(prim)),
196
- computeUnitsConsumed: Stdlib_Option.map(tx.computeUnitsConsumed, prim => BigInt(prim)),
197
- accountKeys: tx.accountKeys,
198
- recentBlockhash: tx.recentBlockhash,
199
- version: tx.version
200
- };
201
- }
202
-
203
- function toSvmTokenBalance(tb) {
204
- return {
205
- account: Stdlib_Option.map(tb.account, prim => prim),
206
- mint: Stdlib_Option.map(tb.mint, prim => prim),
207
- owner: Stdlib_Option.map(tb.owner, prim => prim),
208
- preAmount: tb.preAmount,
209
- postAmount: tb.postAmount
210
- };
211
- }
212
-
213
188
  function probeRouter(router, programId, instr, byteLengthsDesc, contractAddress, indexingAddresses) {
214
189
  let probe = dN => {
215
190
  let tag = EventRouter.getSvmEventId(programId, dN);
@@ -247,6 +222,32 @@ function probeRouter(router, programId, instr, byteLengthsDesc, contractAddress,
247
222
  }
248
223
  }
249
224
 
225
+ function toQueryTxField(field) {
226
+ switch (field) {
227
+ case "signatures" :
228
+ return "signatures";
229
+ case "feePayer" :
230
+ return "fee_payer";
231
+ case "success" :
232
+ return "success";
233
+ case "err" :
234
+ return "err";
235
+ case "fee" :
236
+ return "fee";
237
+ case "computeUnitsConsumed" :
238
+ return "compute_units_consumed";
239
+ case "accountKeys" :
240
+ return "account_keys";
241
+ case "recentBlockhash" :
242
+ return "recent_blockhash";
243
+ case "version" :
244
+ return "version";
245
+ case "transactionIndex" :
246
+ case "tokenBalances" :
247
+ return;
248
+ }
249
+ }
250
+
250
251
  function make(param) {
251
252
  let eventConfigs = param.eventConfigs;
252
253
  let chain = param.chain;
@@ -259,9 +260,27 @@ function make(param) {
259
260
  match[1].forEach(o => {
260
261
  orderingByProgram[o.programId] = o.byteLengthsDesc;
261
262
  });
262
- let needsTransactions = eventConfigs.some(cfg => cfg.includeTransaction);
263
263
  let needsLogs = eventConfigs.some(cfg => cfg.includeLogs);
264
- let needsTokenBalances = eventConfigs.some(cfg => cfg.includeTokenBalances);
264
+ let selectedTxFields = new Set();
265
+ eventConfigs.forEach(cfg => {
266
+ cfg.selectedTransactionFields.forEach(field => {
267
+ selectedTxFields.add(field);
268
+ });
269
+ });
270
+ let needsTokenBalances = selectedTxFields.has("tokenBalances");
271
+ let fields = [
272
+ "slot",
273
+ "transaction_index"
274
+ ];
275
+ Array.from(selectedTxFields).forEach(field => {
276
+ let queryField = toQueryTxField(field);
277
+ if (queryField !== undefined) {
278
+ fields.push(queryField);
279
+ return;
280
+ }
281
+ });
282
+ let txFieldCount = selectedTxFields.size;
283
+ let needsTransactions = txFieldCount > 1 || txFieldCount === 1 && !needsTokenBalances;
265
284
  let getItemsOrThrow = async (fromBlock, toBlock, param, indexingAddresses, knownHeight, param$1, param$2, retry, logger) => {
266
285
  let totalTimeRef = Hrtime.makeTimer();
267
286
  let pageFetchRef = Hrtime.makeTimer();
@@ -271,19 +290,7 @@ function make(param) {
271
290
  "blockhash",
272
291
  "block_time"
273
292
  ];
274
- let fields_transaction = needsTransactions ? [
275
- "slot",
276
- "transaction_index",
277
- "signatures",
278
- "fee_payer",
279
- "success",
280
- "err",
281
- "fee",
282
- "compute_units_consumed",
283
- "account_keys",
284
- "recent_blockhash",
285
- "version"
286
- ] : undefined;
293
+ let fields_transaction = needsTransactions ? fields : undefined;
287
294
  let fields_log = needsLogs ? [
288
295
  "slot",
289
296
  "transaction_index",
@@ -300,7 +307,7 @@ function make(param) {
300
307
  "pre_amount",
301
308
  "post_amount"
302
309
  ] : undefined;
303
- let fields = {
310
+ let fields$1 = {
304
311
  block: fields_block,
305
312
  transaction: fields_transaction,
306
313
  log: fields_log,
@@ -308,7 +315,7 @@ function make(param) {
308
315
  };
309
316
  let query_toSlot = Stdlib_Option.map(toBlock, toBlock => toBlock + 1 | 0);
310
317
  let query_instructions = instructionSelections;
311
- let query_fields = fields;
318
+ let query_fields = fields$1;
312
319
  let query = {
313
320
  fromSlot: fromBlock,
314
321
  toSlot: query_toSlot,
@@ -316,9 +323,9 @@ function make(param) {
316
323
  fields: query_fields
317
324
  };
318
325
  Prometheus.SourceRequestCount.increment(name, chain, "getInstructions");
319
- let resp;
326
+ let match;
320
327
  try {
321
- resp = await client.get(query);
328
+ match = await client.get(query);
322
329
  } catch (raw_exn) {
323
330
  let exn = Primitive_exceptions.internalToException(raw_exn);
324
331
  throw {
@@ -336,6 +343,7 @@ function make(param) {
336
343
  Error: new Error()
337
344
  };
338
345
  }
346
+ let resp = match[0];
339
347
  let pageFetchTime = Hrtime.toSecondsFloat(Hrtime.timeSince(pageFetchRef));
340
348
  let parsingRef = Hrtime.makeTimer();
341
349
  let blockTimeBySlot = {};
@@ -346,11 +354,6 @@ function make(param) {
346
354
  return;
347
355
  }
348
356
  });
349
- let txByKey = {};
350
- resp.data.transactions.forEach(tx => {
351
- let key = tx.slot.toString() + ":" + tx.transactionIndex.toString();
352
- txByKey[key] = tx;
353
- });
354
357
  let logsByKey = {};
355
358
  resp.data.logs.forEach(log => {
356
359
  let match = log.transactionIndex;
@@ -369,22 +372,6 @@ function make(param) {
369
372
  logsByKey[key] = [log];
370
373
  }
371
374
  });
372
- let tokenBalancesByTx = {};
373
- if (needsTokenBalances) {
374
- resp.data.tokenBalances.forEach(tb => {
375
- let txIdx = tb.transactionIndex;
376
- if (txIdx === undefined) {
377
- return;
378
- }
379
- let key = tb.slot.toString() + ":" + txIdx.toString();
380
- let existing = tokenBalancesByTx[key];
381
- if (existing !== undefined) {
382
- existing.push(tb);
383
- } else {
384
- tokenBalancesByTx[key] = [tb];
385
- }
386
- });
387
- }
388
375
  let parsedQueueItems = [];
389
376
  resp.data.instructions.forEach(instr => {
390
377
  let programId = instr.programId;
@@ -392,14 +379,6 @@ function make(param) {
392
379
  let contractAddress = instr.programId;
393
380
  let maybeConfig = probeRouter(eventRouter, programId, instr, byteLengths, contractAddress, indexingAddresses);
394
381
  if (maybeConfig !== undefined) {
395
- let txKey = instr.slot.toString() + ":" + instr.transactionIndex.toString();
396
- let maybeTx = Stdlib_Option.map(txByKey[txKey], toSvmTransaction);
397
- let maybeTx$1 = maybeConfig.includeTokenBalances ? Stdlib_Option.map(maybeTx, tx => {
398
- let maybeBalances = Stdlib_Option.map(tokenBalancesByTx[txKey], bals => bals.map(toSvmTokenBalance));
399
- let newrecord = {...tx};
400
- newrecord.tokenBalances = maybeBalances;
401
- return newrecord;
402
- }) : maybeTx;
403
382
  let logKey = instr.slot.toString() + ":" + instr.transactionIndex.toString() + ":" + serializeInstructionAddress(instr.instructionAddress);
404
383
  let maybeLogs = Stdlib_Option.map(logsByKey[logKey], logs => logs.map(log => ({
405
384
  kind: Stdlib_Option.getOr(log.kind, ""),
@@ -407,7 +386,7 @@ function make(param) {
407
386
  })));
408
387
  let slotKey = instr.slot.toString();
409
388
  let blockTime = blockTimeBySlot[slotKey];
410
- let payload = toSvmInstruction(instr, maybeConfig.contractName, maybeConfig.name, maybeConfig.includeTransaction ? maybeTx$1 : undefined, maybeConfig.includeLogs ? maybeLogs : undefined, {
389
+ let payload = toSvmInstruction(instr, maybeConfig.contractName, maybeConfig.name, maybeConfig.includeLogs ? maybeLogs : undefined, {
411
390
  slot: instr.slot,
412
391
  time: Stdlib_Option.getOr(blockTime, 0),
413
392
  hash: ""
@@ -420,6 +399,7 @@ function make(param) {
420
399
  blockNumber: instr.slot,
421
400
  blockHash: "",
422
401
  logIndex: synthLogIndex(instr),
402
+ transactionIndex: instr.transactionIndex,
423
403
  payload: payload
424
404
  });
425
405
  }
@@ -436,6 +416,7 @@ function make(param) {
436
416
  knownHeight: knownHeight,
437
417
  blockHashes: blockHashes,
438
418
  parsedQueueItems: parsedQueueItems,
419
+ transactionStore: Primitive_option.some(match[1]),
439
420
  fromBlockQueried: fromBlock,
440
421
  latestFetchedBlockNumber: highestSlot,
441
422
  latestFetchedBlockTimestamp: latestBlockTime,
@@ -469,7 +450,8 @@ function make(param) {
469
450
  maxNumBlocks: query_maxNumBlocks
470
451
  };
471
452
  Prometheus.SourceRequestCount.increment(name, chain, "getBlockHashes");
472
- let resp = await client.get(query);
453
+ let match = await client.get(query);
454
+ let resp = match[0];
473
455
  resp.data.blocks.forEach(b => {
474
456
  blockDatas.push({
475
457
  blockHash: b.blockhash,
@@ -549,9 +531,8 @@ export {
549
531
  buildProgramSchemas,
550
532
  parseDecoded,
551
533
  toSvmInstruction,
552
- toSvmTransaction,
553
- toSvmTokenBalance,
554
534
  probeRouter,
535
+ toQueryTxField,
555
536
  make,
556
537
  }
557
538
  /* Prometheus Not a pure module */
@@ -0,0 +1,111 @@
1
+ // Binding to the Rust `TransactionStore` napi class. Transactions are kept in
2
+ // Rust as raw structs (their large fields never enter JS until read) keyed by
3
+ // (blockNumber, transactionIndex). One store lives per chain on `ChainState`;
4
+ // each fetch response contributes a page that is merged in. At batch
5
+ // preparation the selected fields are materialised in bulk, off the JS thread,
6
+ // in columnar form and zipped into plain JS objects on the main thread.
7
+ type t
8
+
9
+ @send external classNew: Core.transactionStoreCtor => t = "new"
10
+ let make = (): t => Core.getAddon().transactionStore->classNew
11
+
12
+ // Field-name → bit-index map from an ordered field-name array. The index is the
13
+ // field code shared with the Rust store (`EvmTxField`/`SvmTxField`).
14
+ let fieldCodes = (fields: array<string>): dict<int> => {
15
+ let codes = Dict.make()
16
+ fields->Array.forEachWithIndex((name, i) => codes->Dict.set(name, i))
17
+ codes
18
+ }
19
+
20
+ let pow2: int => float = %raw(`c => Math.pow(2, c)`)
21
+
22
+ // Union of an ecosystem's selected transaction fields as a bitmask float (bit
23
+ // `code` set ⇔ selected). Built arithmetically to dodge 32-bit JS bitwise ops.
24
+ let mask = (eventConfigs: array<Internal.eventConfig>, ~codes: dict<int>): float => {
25
+ let selected = Utils.Set.make()
26
+ eventConfigs->Array.forEach(eventConfig =>
27
+ eventConfig.selectedTransactionFields->Utils.Set.forEach(name =>
28
+ switch codes->Utils.Dict.dangerouslyGetNonOption(name) {
29
+ | Some(code) => selected->Utils.Set.add(code)->ignore
30
+ | None => ()
31
+ }
32
+ )
33
+ )
34
+ selected->Utils.Set.toArray->Array.reduce(0., (mask, code) => mask +. pow2(code))
35
+ }
36
+
37
+ // Drain another store (a fetch-response page) into this one.
38
+ @send external merge: (t, t) => unit = "merge"
39
+
40
+ // Bulk-materialise the fields selected by `mask` (one bit per field code) for
41
+ // the given transactions, off the JS thread. Result is aligned with the input.
42
+ @send
43
+ external materialize: (
44
+ t,
45
+ ~blockNumbers: array<int>,
46
+ ~transactionIndices: array<int>,
47
+ ~mask: float,
48
+ ) => promise<array<Internal.eventTransaction>> = "materialize"
49
+
50
+ // Drop transactions for blocks at or below the given block (already processed).
51
+ @send external prune: (t, int) => unit = "prune"
52
+
53
+ // Drop transactions for blocks above the given block (rolled back).
54
+ @send external rollback: (t, int) => unit = "rollback"
55
+
56
+ // Materialise the mask-selected fields for the store-backed items and write the
57
+ // resulting transaction onto each item's payload. Items that already carry an
58
+ // inline transaction (RPC/simulate/Fuel) are skipped. Store-backed items always
59
+ // get a transaction object — the selected fields, or `{}` when the chain
60
+ // selected none — so `event.transaction` is never `undefined` (matching the
61
+ // inline sources). Deduped per (blockNumber, transactionIndex).
62
+ let materializeItems = async (store: t, ~items: array<Internal.item>, ~mask: float) => {
63
+ // Store-backed items arrive in (block, logIndex) order, and a transaction's
64
+ // logs are contiguous within a block, so events sharing a (blockNumber,
65
+ // transactionIndex) are adjacent. Group them by extending the current run
66
+ // rather than hashing a string key per item. A key recurring non-adjacently
67
+ // just splits into two groups (one redundant decode) — never incorrect.
68
+ let blockNumbers = []
69
+ let transactionIndices = []
70
+ let payloadGroups = []
71
+
72
+ items->Array.forEach(item =>
73
+ switch item {
74
+ | Internal.Event(_) =>
75
+ let eventItem = item->Internal.castUnsafeEventItem
76
+ switch eventItem.payload->Internal.getPayloadTransaction->Nullable.toOption {
77
+ | Some(_) => () // RPC/simulate/Fuel carry the transaction inline.
78
+ | None =>
79
+ let {blockNumber, transactionIndex} = eventItem
80
+ let last = payloadGroups->Array.length - 1
81
+ if (
82
+ last >= 0 &&
83
+ blockNumbers->Array.getUnsafe(last) == blockNumber &&
84
+ transactionIndices->Array.getUnsafe(last) == transactionIndex
85
+ ) {
86
+ payloadGroups->Array.getUnsafe(last)->Array.push(eventItem.payload)
87
+ } else {
88
+ blockNumbers->Array.push(blockNumber)
89
+ transactionIndices->Array.push(transactionIndex)
90
+ payloadGroups->Array.push([eventItem.payload])
91
+ }
92
+ }
93
+ | Internal.Block(_) => ()
94
+ }
95
+ )
96
+
97
+ if payloadGroups->Utils.Array.notEmpty {
98
+ // A zero mask selects no fields, so there's nothing to decode — each
99
+ // store-backed item still gets an empty transaction object, so
100
+ // `event.transaction` is never undefined (matching the inline sources).
101
+ let txs = if mask == 0. {
102
+ payloadGroups->Array.map((_): Internal.eventTransaction => %raw(`{}`))
103
+ } else {
104
+ await store->materialize(~blockNumbers, ~transactionIndices, ~mask)
105
+ }
106
+ payloadGroups->Array.forEachWithIndex((payloads, i) => {
107
+ let tx = txs->Array.getUnsafe(i)
108
+ payloads->Array.forEach(payload => payload->Internal.setPayloadTransaction(tx))
109
+ })
110
+ }
111
+ }
@@ -0,0 +1,77 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Core from "../Core.res.mjs";
4
+ import * as Utils from "../Utils.res.mjs";
5
+ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
6
+
7
+ function make() {
8
+ return Core.getAddon().TransactionStore.new();
9
+ }
10
+
11
+ function fieldCodes(fields) {
12
+ let codes = {};
13
+ fields.forEach((name, i) => {
14
+ codes[name] = i;
15
+ });
16
+ return codes;
17
+ }
18
+
19
+ let pow2 = (c => Math.pow(2, c));
20
+
21
+ function mask(eventConfigs, codes) {
22
+ let selected = new Set();
23
+ eventConfigs.forEach(eventConfig => {
24
+ eventConfig.selectedTransactionFields.forEach(name => {
25
+ let code = codes[name];
26
+ if (code !== undefined) {
27
+ selected.add(code);
28
+ return;
29
+ }
30
+ });
31
+ });
32
+ return Stdlib_Array.reduce(Array.from(selected), 0, (mask, code) => mask + pow2(code));
33
+ }
34
+
35
+ async function materializeItems(store, items, mask) {
36
+ let blockNumbers = [];
37
+ let transactionIndices = [];
38
+ let payloadGroups = [];
39
+ items.forEach(item => {
40
+ if (item.kind !== 0) {
41
+ return;
42
+ }
43
+ let match = item.payload.transaction;
44
+ if (!(match == null)) {
45
+ return;
46
+ }
47
+ let transactionIndex = item.transactionIndex;
48
+ let blockNumber = item.blockNumber;
49
+ let last = payloadGroups.length - 1 | 0;
50
+ if (last >= 0 && blockNumbers[last] === blockNumber && transactionIndices[last] === transactionIndex) {
51
+ payloadGroups[last].push(item.payload);
52
+ } else {
53
+ blockNumbers.push(blockNumber);
54
+ transactionIndices.push(transactionIndex);
55
+ payloadGroups.push([item.payload]);
56
+ }
57
+ });
58
+ if (!Utils.$$Array.notEmpty(payloadGroups)) {
59
+ return;
60
+ }
61
+ let txs = mask === 0 ? payloadGroups.map(param => ({})) : await store.materialize(blockNumbers, transactionIndices, mask);
62
+ payloadGroups.forEach((payloads, i) => {
63
+ let tx = txs[i];
64
+ payloads.forEach(payload => {
65
+ payload.transaction = tx;
66
+ });
67
+ });
68
+ }
69
+
70
+ export {
71
+ make,
72
+ fieldCodes,
73
+ pow2,
74
+ mask,
75
+ materializeItems,
76
+ }
77
+ /* Core Not a pure module */