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
@@ -0,0 +1,441 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Core from "../Core.res.mjs";
4
+ import * as Hrtime from "../bindings/Hrtime.res.mjs";
5
+ import * as Source from "./Source.res.mjs";
6
+ import * as Belt_Array from "@rescript/runtime/lib/es6/Belt_Array.js";
7
+ import * as Prometheus from "../Prometheus.res.mjs";
8
+ import * as EventRouter from "./EventRouter.res.mjs";
9
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
10
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
11
+ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
12
+ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
13
+ import * as HyperSyncSolanaClient from "./HyperSyncSolanaClient.res.mjs";
14
+
15
+ let EventRoutingFailed = /* @__PURE__ */Primitive_exceptions.create("HyperSyncSolanaSource.EventRoutingFailed");
16
+
17
+ function buildInstructionSelections(eventConfigs) {
18
+ return Belt_Array.keepMap(eventConfigs, cfg => {
19
+ let programIdString = cfg.programId;
20
+ if (programIdString === "") {
21
+ return;
22
+ }
23
+ let match = cfg.discriminator;
24
+ let match$1 = cfg.discriminatorByteLen;
25
+ let match$2;
26
+ if (match !== undefined) {
27
+ switch (match$1) {
28
+ case 1 :
29
+ match$2 = [
30
+ [match],
31
+ undefined,
32
+ undefined,
33
+ undefined
34
+ ];
35
+ break;
36
+ case 2 :
37
+ match$2 = [
38
+ undefined,
39
+ [match],
40
+ undefined,
41
+ undefined
42
+ ];
43
+ break;
44
+ case 4 :
45
+ match$2 = [
46
+ undefined,
47
+ undefined,
48
+ [match],
49
+ undefined
50
+ ];
51
+ break;
52
+ case 8 :
53
+ match$2 = [
54
+ undefined,
55
+ undefined,
56
+ undefined,
57
+ [match]
58
+ ];
59
+ break;
60
+ default:
61
+ match$2 = [
62
+ undefined,
63
+ undefined,
64
+ undefined,
65
+ undefined
66
+ ];
67
+ }
68
+ } else {
69
+ match$2 = [
70
+ undefined,
71
+ undefined,
72
+ undefined,
73
+ undefined
74
+ ];
75
+ }
76
+ let accountFilters = cfg.accountFilters;
77
+ let a0 = Belt_Array.get(Belt_Array.keepMap(accountFilters, f => {
78
+ if (f.position === 0) {
79
+ return f.values;
80
+ }
81
+ }), 0);
82
+ let a1 = Belt_Array.get(Belt_Array.keepMap(accountFilters, f => {
83
+ if (f.position === 1) {
84
+ return f.values;
85
+ }
86
+ }), 0);
87
+ let a2 = Belt_Array.get(Belt_Array.keepMap(accountFilters, f => {
88
+ if (f.position === 2) {
89
+ return f.values;
90
+ }
91
+ }), 0);
92
+ let a3 = Belt_Array.get(Belt_Array.keepMap(accountFilters, f => {
93
+ if (f.position === 3) {
94
+ return f.values;
95
+ }
96
+ }), 0);
97
+ let a4 = Belt_Array.get(Belt_Array.keepMap(accountFilters, f => {
98
+ if (f.position === 4) {
99
+ return f.values;
100
+ }
101
+ }), 0);
102
+ let a5 = Belt_Array.get(Belt_Array.keepMap(accountFilters, f => {
103
+ if (f.position === 5) {
104
+ return f.values;
105
+ }
106
+ }), 0);
107
+ return {
108
+ programId: [programIdString],
109
+ d1: match$2[0],
110
+ d2: match$2[1],
111
+ d4: match$2[2],
112
+ d8: match$2[3],
113
+ a0: a0,
114
+ a1: a1,
115
+ a2: a2,
116
+ a3: a3,
117
+ a4: a4,
118
+ a5: a5,
119
+ isInner: cfg.isInner,
120
+ includeTransaction: cfg.includeTransaction,
121
+ includeLogs: cfg.includeLogs
122
+ };
123
+ });
124
+ }
125
+
126
+ function synthLogIndex(instr) {
127
+ let tx = instr.transactionIndex;
128
+ let addrSum = Belt_Array.reduce(instr.instructionAddress, 0, (acc, n) => ((acc << 10) + n | 0) + 1 | 0);
129
+ return (tx << 16) + addrSum | 0;
130
+ }
131
+
132
+ function serializeInstructionAddress(addr) {
133
+ return addr.map(n => n.toString()).join(",");
134
+ }
135
+
136
+ function buildSchemaHandles(eventConfigs) {
137
+ let descriptorsByProgram = {};
138
+ Belt_Array.forEach(eventConfigs, ec => {
139
+ let programIdString = ec.programId;
140
+ if (programIdString === "") {
141
+ return;
142
+ }
143
+ let hasSchema = ec.accounts.length !== 0 || ec.args !== null;
144
+ let discriminator = Stdlib_Option.getOr(ec.discriminator, "");
145
+ if (!(hasSchema && discriminator !== "")) {
146
+ return;
147
+ }
148
+ let existing = descriptorsByProgram[programIdString];
149
+ let descriptor = existing !== undefined ? Primitive_option.valFromOption(existing) : ({
150
+ programId: programIdString,
151
+ definedTypes: ec.definedTypes,
152
+ instructions: []
153
+ });
154
+ let instruction = {
155
+ name: ec.name,
156
+ discriminator: discriminator,
157
+ accounts: ec.accounts,
158
+ args: ec.args
159
+ };
160
+ descriptorsByProgram[programIdString] = {
161
+ programId: descriptor.programId,
162
+ definedTypes: descriptor.definedTypes,
163
+ instructions: descriptor.instructions.concat([instruction])
164
+ };
165
+ });
166
+ let handles = {};
167
+ Belt_Array.forEach(Object.entries(descriptorsByProgram), param => {
168
+ let json = JSON.stringify(param[1]);
169
+ let handle = Core.getAddon().registerProgramSchema(json);
170
+ handles[param[0]] = handle;
171
+ });
172
+ return handles;
173
+ }
174
+
175
+ function decodeIfPossible(instr, schemaHandlesByProgram) {
176
+ let handle = schemaHandlesByProgram[instr.programId];
177
+ if (handle === undefined) {
178
+ return;
179
+ }
180
+ let decoded = Core.getAddon().decodeInstruction(handle, instr.data, instr.accounts);
181
+ if (decoded === null) {
182
+ return;
183
+ }
184
+ let args;
185
+ try {
186
+ args = JSON.parse(decoded.argsJson);
187
+ } catch (exn) {
188
+ args = {};
189
+ }
190
+ let accounts;
191
+ try {
192
+ accounts = JSON.parse(decoded.accountsJson);
193
+ } catch (exn$1) {
194
+ accounts = {};
195
+ }
196
+ return {
197
+ name: decoded.name,
198
+ args: args,
199
+ accounts: accounts,
200
+ extraAccounts: decoded.extraAccounts
201
+ };
202
+ }
203
+
204
+ function toSvmInstruction(instr, schemaHandlesByProgram) {
205
+ return {
206
+ programId: instr.programId,
207
+ data: instr.data,
208
+ accounts: instr.accounts,
209
+ instructionAddress: instr.instructionAddress,
210
+ isInner: instr.isInner,
211
+ d1: instr.d1,
212
+ d2: instr.d2,
213
+ d4: instr.d4,
214
+ d8: instr.d8,
215
+ decoded: decodeIfPossible(instr, schemaHandlesByProgram)
216
+ };
217
+ }
218
+
219
+ function toSvmTransaction(tx) {
220
+ return {
221
+ signatures: tx.signatures,
222
+ feePayer: Stdlib_Option.map(tx.feePayer, prim => prim),
223
+ success: tx.success,
224
+ err: tx.err,
225
+ fee: Stdlib_Option.map(tx.fee, prim => BigInt(prim)),
226
+ computeUnitsConsumed: Stdlib_Option.map(tx.computeUnitsConsumed, prim => BigInt(prim)),
227
+ accountKeys: tx.accountKeys,
228
+ recentBlockhash: tx.recentBlockhash,
229
+ version: tx.version
230
+ };
231
+ }
232
+
233
+ function probeRouter(router, programId, instr, byteLengthsDesc, contractAddress, indexingAddresses) {
234
+ let probe = dN => {
235
+ let tag = EventRouter.getSvmEventId(programId, dN);
236
+ return EventRouter.get(router, tag, contractAddress, instr.slot, indexingAddresses);
237
+ };
238
+ let result = Belt_Array.reduce(byteLengthsDesc, undefined, (acc, len) => {
239
+ if (acc !== undefined) {
240
+ return acc;
241
+ }
242
+ let candidate;
243
+ switch (len) {
244
+ case 1 :
245
+ candidate = instr.d1;
246
+ break;
247
+ case 2 :
248
+ candidate = instr.d2;
249
+ break;
250
+ case 4 :
251
+ candidate = instr.d4;
252
+ break;
253
+ case 8 :
254
+ candidate = instr.d8;
255
+ break;
256
+ default:
257
+ candidate = undefined;
258
+ }
259
+ if (candidate !== undefined) {
260
+ return probe(candidate);
261
+ }
262
+ });
263
+ if (result !== undefined) {
264
+ return result;
265
+ } else {
266
+ return probe(undefined);
267
+ }
268
+ }
269
+
270
+ function make(param) {
271
+ let eventConfigs = param.eventConfigs;
272
+ let chain = param.chain;
273
+ let name = "HyperSyncSolana";
274
+ let client = HyperSyncSolanaClient.make(param.endpointUrl, param.apiToken, param.clientTimeoutMillis, param.clientMaxRetries, undefined, undefined);
275
+ let match = EventRouter.fromSvmEventConfigsOrThrow(eventConfigs, chain);
276
+ let eventRouter = match[0];
277
+ let orderingByProgram = {};
278
+ Belt_Array.forEach(match[1], o => {
279
+ orderingByProgram[o.programId] = o.byteLengthsDesc;
280
+ });
281
+ let schemaHandlesByProgram = buildSchemaHandles(eventConfigs);
282
+ let getItemsOrThrow = async (fromBlock, toBlock, param, indexingAddresses, knownHeight, param$1, param$2, retry, logger) => {
283
+ let totalTimeRef = Hrtime.makeTimer();
284
+ let pageFetchRef = Hrtime.makeTimer();
285
+ let instructionSelections = buildInstructionSelections(eventConfigs);
286
+ let query_instructions = instructionSelections;
287
+ let query = {
288
+ fromSlot: fromBlock,
289
+ toSlot: toBlock,
290
+ instructions: query_instructions
291
+ };
292
+ Prometheus.SourceRequestCount.increment(name, chain, "getInstructions");
293
+ let resp;
294
+ try {
295
+ resp = await client.get(query);
296
+ } catch (raw_exn) {
297
+ let exn = Primitive_exceptions.internalToException(raw_exn);
298
+ throw {
299
+ RE_EXN_ID: Source.GetItemsError,
300
+ _1: {
301
+ TAG: "FailedGettingItems",
302
+ exn: exn,
303
+ attemptedToBlock: Stdlib_Option.getOr(toBlock, knownHeight),
304
+ retry: {
305
+ TAG: "WithBackoff",
306
+ message: `Unexpected issue while fetching instructions from HyperSync Solana. Attempt a retry.`,
307
+ backoffMillis: retry !== 0 ? 1000 * retry | 0 : 500
308
+ }
309
+ },
310
+ Error: new Error()
311
+ };
312
+ }
313
+ let pageFetchTime = Hrtime.toSecondsFloat(Hrtime.timeSince(pageFetchRef));
314
+ let parsingRef = Hrtime.makeTimer();
315
+ let txByKey = {};
316
+ Belt_Array.forEach(resp.data.transactions, tx => {
317
+ let key = tx.slot.toString() + ":" + tx.transactionIndex.toString();
318
+ txByKey[key] = tx;
319
+ });
320
+ let logsByKey = {};
321
+ Belt_Array.forEach(resp.data.logs, log => {
322
+ let match = log.transactionIndex;
323
+ let match$1 = log.instructionAddress;
324
+ if (match === undefined) {
325
+ return;
326
+ }
327
+ if (match$1 === undefined) {
328
+ return;
329
+ }
330
+ let key = log.slot.toString() + ":" + match.toString() + ":" + serializeInstructionAddress(match$1);
331
+ let existing = logsByKey[key];
332
+ if (existing !== undefined) {
333
+ existing.push(log);
334
+ } else {
335
+ logsByKey[key] = [log];
336
+ }
337
+ });
338
+ let parsedQueueItems = [];
339
+ Belt_Array.forEach(resp.data.instructions, instr => {
340
+ let programId = instr.programId;
341
+ let byteLengths = Stdlib_Option.getOr(orderingByProgram[instr.programId], []);
342
+ let contractAddress = instr.programId;
343
+ let maybeConfig = probeRouter(eventRouter, programId, instr, byteLengths, contractAddress, indexingAddresses);
344
+ if (maybeConfig !== undefined) {
345
+ let txKey = instr.slot.toString() + ":" + instr.transactionIndex.toString();
346
+ let maybeTx = Stdlib_Option.map(txByKey[txKey], toSvmTransaction);
347
+ let logKey = instr.slot.toString() + ":" + instr.transactionIndex.toString() + ":" + serializeInstructionAddress(instr.instructionAddress);
348
+ let maybeLogs = Stdlib_Option.map(logsByKey[logKey], logs => logs.map(log => ({
349
+ kind: Stdlib_Option.getOr(log.kind, ""),
350
+ message: Stdlib_Option.getOr(log.message, "")
351
+ })));
352
+ let payload_contractName = maybeConfig.contractName;
353
+ let payload_eventName = maybeConfig.name;
354
+ let payload_instruction = toSvmInstruction(instr, schemaHandlesByProgram);
355
+ let payload_transaction = maybeConfig.includeTransaction ? maybeTx : undefined;
356
+ let payload_logs = maybeConfig.includeLogs ? maybeLogs : undefined;
357
+ let payload_slot = instr.slot;
358
+ let payload_block = {
359
+ height: instr.slot,
360
+ time: 0,
361
+ hash: ""
362
+ };
363
+ let payload = {
364
+ contractName: payload_contractName,
365
+ eventName: payload_eventName,
366
+ instruction: payload_instruction,
367
+ transaction: payload_transaction,
368
+ logs: payload_logs,
369
+ slot: payload_slot,
370
+ blockTime: undefined,
371
+ block: payload_block
372
+ };
373
+ parsedQueueItems.push({
374
+ kind: 0,
375
+ eventConfig: maybeConfig,
376
+ timestamp: 0,
377
+ chain: chain,
378
+ blockNumber: instr.slot,
379
+ logIndex: synthLogIndex(instr),
380
+ event: payload
381
+ });
382
+ }
383
+ });
384
+ let parsingTimeElapsed = Hrtime.toSecondsFloat(Hrtime.timeSince(parsingRef));
385
+ let heighestSlot = resp.nextSlot - 1 | 0;
386
+ let reorgGuard_rangeLastBlock = {
387
+ blockHash: "",
388
+ blockNumber: heighestSlot,
389
+ blockTimestamp: 0
390
+ };
391
+ let reorgGuard = {
392
+ rangeLastBlock: reorgGuard_rangeLastBlock,
393
+ prevRangeLastBlock: undefined
394
+ };
395
+ let totalTimeElapsed = Hrtime.toSecondsFloat(Hrtime.timeSince(totalTimeRef));
396
+ return {
397
+ knownHeight: knownHeight,
398
+ reorgGuard: reorgGuard,
399
+ parsedQueueItems: parsedQueueItems,
400
+ fromBlockQueried: fromBlock,
401
+ latestFetchedBlockNumber: heighestSlot,
402
+ latestFetchedBlockTimestamp: 0,
403
+ stats: {
404
+ "total time elapsed (s)": totalTimeElapsed,
405
+ "parsing time (s)": parsingTimeElapsed,
406
+ "page fetch time (s)": pageFetchTime
407
+ }
408
+ };
409
+ };
410
+ return {
411
+ name: name,
412
+ sourceFor: "Sync",
413
+ chain: chain,
414
+ poweredByHyperSync: true,
415
+ pollingInterval: 1000,
416
+ getBlockHashes: (param, param$1) => Stdlib_JsError.throwWithMessage("HyperSyncSolanaSource does not support getBlockHashes yet (reorg detection at finalized commitment is no-op in C2)"),
417
+ getHeightOrThrow: async () => {
418
+ let timer = Hrtime.makeTimer();
419
+ let h = await client.getHeight();
420
+ let seconds = Hrtime.toSecondsFloat(Hrtime.timeSince(timer));
421
+ Prometheus.SourceRequestCount.increment(name, chain, "getHeight");
422
+ Prometheus.SourceRequestCount.addSeconds(name, chain, "getHeight", seconds);
423
+ return h;
424
+ },
425
+ getItemsOrThrow: getItemsOrThrow
426
+ };
427
+ }
428
+
429
+ export {
430
+ EventRoutingFailed,
431
+ buildInstructionSelections,
432
+ synthLogIndex,
433
+ serializeInstructionAddress,
434
+ buildSchemaHandles,
435
+ decodeIfPossible,
436
+ toSvmInstruction,
437
+ toSvmTransaction,
438
+ probeRouter,
439
+ make,
440
+ }
441
+ /* Core Not a pure module */
@@ -192,18 +192,15 @@ Learn more or get a free API token at: https://envio.dev/app/api-tokens`)
192
192
  switch hscDecoder.contents {
193
193
  | Some(decoder) => decoder
194
194
  | None =>
195
- switch HyperSyncClient.Decoder.fromSignatures(allEventSignatures) {
195
+ switch HyperSyncClient.Decoder.fromSignatures(
196
+ allEventSignatures,
197
+ ~checksumAddresses=!lowercaseAddresses,
198
+ ) {
196
199
  | exception exn =>
197
200
  exn->ErrorHandling.mkLogAndRaise(
198
201
  ~msg="Failed to instantiate a decoder from hypersync client, please double check your ABI",
199
202
  )
200
- | decoder =>
201
- if lowercaseAddresses {
202
- decoder.disableChecksummedAddresses()
203
- } else {
204
- decoder.enableChecksummedAddresses()
205
- }
206
- decoder
203
+ | decoder => decoder
207
204
  }
208
205
  }
209
206
  }
@@ -135,19 +135,12 @@ Learn more or get a free API token at: https://envio.dev/app/api-tokens`);
135
135
  if (decoder !== undefined) {
136
136
  return decoder;
137
137
  }
138
- let decoder$1;
139
138
  try {
140
- decoder$1 = HyperSyncClient.Decoder.fromSignatures(allEventSignatures);
139
+ return HyperSyncClient.Decoder.fromSignatures(allEventSignatures, !lowercaseAddresses);
141
140
  } catch (raw_exn) {
142
141
  let exn = Primitive_exceptions.internalToException(raw_exn);
143
142
  return ErrorHandling.mkLogAndRaise(undefined, "Failed to instantiate a decoder from hypersync client, please double check your ABI", exn);
144
143
  }
145
- if (lowercaseAddresses) {
146
- decoder$1.disableChecksummedAddresses();
147
- } else {
148
- decoder$1.enableChecksummedAddresses();
149
- }
150
- return decoder$1;
151
144
  };
152
145
  let UndefinedValue = /* @__PURE__ */Primitive_exceptions.create("UndefinedValue");
153
146
  let makeEventBatchQueueItem = (item, params, eventConfig) => {
@@ -1022,7 +1022,7 @@ function make(param) {
1022
1022
  if (decoder !== undefined) {
1023
1023
  return decoder;
1024
1024
  } else {
1025
- return HyperSyncClient.Decoder.fromSignatures(allEventSignatures);
1025
+ return HyperSyncClient.Decoder.fromSignatures(allEventSignatures, undefined);
1026
1026
  }
1027
1027
  };
1028
1028
  let getItemsOrThrow = async (fromBlock, toBlock, addressesByContractName, indexingAddresses, knownHeight, partitionId, selection, param, param$1) => {
@@ -39,7 +39,7 @@ module GetFinalizedSlot = {
39
39
  )
40
40
  }
41
41
 
42
- let makeRPCSource = (~chain, ~rpc: string): Source.t => {
42
+ let makeRPCSource = (~chain, ~rpc: string, ~sourceFor: Source.sourceFor=Sync): Source.t => {
43
43
  let client = Rest.client(rpc)
44
44
  let chainId = chain->ChainMap.Chain.toChainId
45
45
 
@@ -54,7 +54,7 @@ let makeRPCSource = (~chain, ~rpc: string): Source.t => {
54
54
 
55
55
  {
56
56
  name,
57
- sourceFor: Sync,
57
+ sourceFor,
58
58
  chain,
59
59
  poweredByHyperSync: false,
60
60
  pollingInterval: 10_000,
@@ -60,14 +60,15 @@ let GetFinalizedSlot = {
60
60
  route: route
61
61
  };
62
62
 
63
- function makeRPCSource(chain, rpc) {
63
+ function makeRPCSource(chain, rpc, sourceForOpt) {
64
+ let sourceFor = sourceForOpt !== undefined ? sourceForOpt : "Sync";
64
65
  let client = Rest.client(rpc, undefined);
65
66
  let host = Utils.Url.getHostFromUrl(rpc);
66
67
  let urlHost = host !== undefined ? host : Stdlib_JsError.throwWithMessage(`The RPC url for chain ` + String(chain) + ` is in incorrect format. The RPC url needs to start with either http:// or https://`);
67
68
  let name = `RPC (` + urlHost + `)`;
68
69
  return {
69
70
  name: name,
70
- sourceFor: "Sync",
71
+ sourceFor: sourceFor,
71
72
  chain: chain,
72
73
  poweredByHyperSync: false,
73
74
  pollingInterval: 10000,
package/src/tui/Tui.res CHANGED
@@ -13,6 +13,7 @@ module ChainLine = {
13
13
  ~endBlock,
14
14
  ~poweredByHyperSync,
15
15
  ~eventsProcessed,
16
+ ~blockUnit: string,
16
17
  ) => {
17
18
  let chainsWidth = Pervasives.min(stdoutColumns - 2, 60)
18
19
  let headerWidth = maxChainIdLength + 10 // 10 for additional text
@@ -27,9 +28,10 @@ module ChainLine = {
27
28
  let toBlockStr = toBlock->TuiData.formatLocaleString
28
29
  let eventsStr = eventsProcessed->TuiData.formatFloatLocaleString
29
30
 
31
+ let endLabel = blockUnit === "Slots" ? " (End Slot)" : " (End Block)"
30
32
  let blocksText =
31
- `Blocks: ${progressBlockStr} / ${toBlockStr}` ++
32
- (endBlock->Option.isSome ? " (End Block)" : "") ++ ` `
33
+ `${blockUnit}: ${progressBlockStr} / ${toBlockStr}` ++
34
+ (endBlock->Option.isSome ? endLabel : "") ++ ` `
33
35
  let eventsText = `Events: ${eventsStr}`
34
36
 
35
37
  let fitsSameLine = blocksText->String.length + eventsText->String.length <= chainsWidth
@@ -210,6 +212,10 @@ module App = {
210
212
  poweredByHyperSync: (
211
213
  cf.sourceManager->SourceManager.getActiveSource
212
214
  ).poweredByHyperSync,
215
+ blockUnit: switch state.ctx.config.ecosystem.name {
216
+ | Svm => "Slots"
217
+ | Evm | Fuel => "Blocks"
218
+ },
213
219
  }: TuiData.chain
214
220
  )
215
221
  })
@@ -249,6 +255,7 @@ module App = {
249
255
  stdoutColumns={stdoutColumns}
250
256
  poweredByHyperSync={chainData.poweredByHyperSync}
251
257
  eventsProcessed={chainData.eventsProcessed}
258
+ blockUnit={chainData.blockUnit}
252
259
  />
253
260
  })
254
261
  ->React.array}
@@ -19,6 +19,7 @@ import * as JsxRuntime from "react/jsx-runtime";
19
19
  import * as BufferedProgressBar from "./components/BufferedProgressBar.res.mjs";
20
20
 
21
21
  function Tui$ChainLine(props) {
22
+ let blockUnit = props.blockUnit;
22
23
  let poweredByHyperSync = props.poweredByHyperSync;
23
24
  let endBlock = props.endBlock;
24
25
  let startBlock = props.startBlock;
@@ -33,8 +34,9 @@ function Tui$ChainLine(props) {
33
34
  let progressBlockStr = TuiData.formatLocaleString(progressBlock);
34
35
  let toBlockStr = TuiData.formatLocaleString(toBlock);
35
36
  let eventsStr = TuiData.formatFloatLocaleString(props.eventsProcessed);
36
- let blocksText = `Blocks: ` + progressBlockStr + ` / ` + toBlockStr + (
37
- Stdlib_Option.isSome(endBlock) ? " (End Block)" : ""
37
+ let endLabel = blockUnit === "Slots" ? " (End Slot)" : " (End Block)";
38
+ let blocksText = blockUnit + `: ` + progressBlockStr + ` / ` + toBlockStr + (
39
+ Stdlib_Option.isSome(endBlock) ? endLabel : ""
38
40
  ) + ` `;
39
41
  let eventsText = `Events: ` + eventsStr;
40
42
  let fitsSameLine = (blocksText.length + eventsText.length | 0) <= chainsWidth;
@@ -242,6 +244,17 @@ function Tui$App(props) {
242
244
  })
243
245
  ) : "SearchingForEvents";
244
246
  }
247
+ let match$1 = state.ctx.config.ecosystem.name;
248
+ let tmp;
249
+ switch (match$1) {
250
+ case "evm" :
251
+ case "fuel" :
252
+ tmp = "Blocks";
253
+ break;
254
+ case "svm" :
255
+ tmp = "Slots";
256
+ break;
257
+ }
245
258
  return {
246
259
  chainId: cf.chainConfig.id.toString(),
247
260
  eventsProcessed: numEventsProcessed,
@@ -254,7 +267,8 @@ function Tui$App(props) {
254
267
  poweredByHyperSync: SourceManager.getActiveSource(cf.sourceManager).poweredByHyperSync,
255
268
  progress: progress,
256
269
  latestFetchedBlockNumber: latestFetchedBlockNumber,
257
- knownHeight: knownHeight
270
+ knownHeight: knownHeight,
271
+ blockUnit: tmp
258
272
  };
259
273
  });
260
274
  let totalEventsProcessed = Stdlib_Array.reduce(chains, 0, (acc, chain) => acc + chain.eventsProcessed);
@@ -292,7 +306,8 @@ function Tui$App(props) {
292
306
  startBlock: chainData.startBlock,
293
307
  endBlock: chainData.endBlock,
294
308
  poweredByHyperSync: chainData.poweredByHyperSync,
295
- eventsProcessed: chainData.eventsProcessed
309
+ eventsProcessed: chainData.eventsProcessed,
310
+ blockUnit: chainData.blockUnit
296
311
  }, i.toString())),
297
312
  JsxRuntime.jsx(Tui$TotalEventsProcessed, {
298
313
  totalEventsProcessed: totalEventsProcessed,
@@ -30,6 +30,9 @@ type chain = {
30
30
  progress: progress,
31
31
  latestFetchedBlockNumber: int,
32
32
  knownHeight: int,
33
+ /** Localized unit noun for this chain's progress. `"Slots"` on SVM,
34
+ `"Blocks"` everywhere else. Drives the per-chain progress label. */
35
+ blockUnit: string,
33
36
  }
34
37
 
35
38
  let minOfOption: (int, option<int>) => int = (a: int, b: option<int>) => {