@nadohq/indexer-client 0.1.0-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.
Files changed (121) hide show
  1. package/README.md +2 -0
  2. package/dist/IndexerBaseClient.cjs +608 -0
  3. package/dist/IndexerBaseClient.cjs.map +1 -0
  4. package/dist/IndexerBaseClient.d.cts +183 -0
  5. package/dist/IndexerBaseClient.d.ts +183 -0
  6. package/dist/IndexerBaseClient.js +605 -0
  7. package/dist/IndexerBaseClient.js.map +1 -0
  8. package/dist/IndexerClient.cjs +368 -0
  9. package/dist/IndexerClient.cjs.map +1 -0
  10. package/dist/IndexerClient.d.cts +50 -0
  11. package/dist/IndexerClient.d.ts +50 -0
  12. package/dist/IndexerClient.js +348 -0
  13. package/dist/IndexerClient.js.map +1 -0
  14. package/dist/dataMappers.cjs +324 -0
  15. package/dist/dataMappers.cjs.map +1 -0
  16. package/dist/dataMappers.d.cts +31 -0
  17. package/dist/dataMappers.d.ts +31 -0
  18. package/dist/dataMappers.js +296 -0
  19. package/dist/dataMappers.js.map +1 -0
  20. package/dist/endpoints.cjs +35 -0
  21. package/dist/endpoints.cjs.map +1 -0
  22. package/dist/endpoints.d.cts +5 -0
  23. package/dist/endpoints.d.ts +5 -0
  24. package/dist/endpoints.js +10 -0
  25. package/dist/endpoints.js.map +1 -0
  26. package/dist/index.cjs +31 -0
  27. package/dist/index.cjs.map +1 -0
  28. package/dist/index.d.cts +18 -0
  29. package/dist/index.d.ts +18 -0
  30. package/dist/index.js +6 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/types/CandlestickPeriod.cjs +42 -0
  33. package/dist/types/CandlestickPeriod.cjs.map +1 -0
  34. package/dist/types/CandlestickPeriod.d.cts +13 -0
  35. package/dist/types/CandlestickPeriod.d.ts +13 -0
  36. package/dist/types/CandlestickPeriod.js +17 -0
  37. package/dist/types/CandlestickPeriod.js.map +1 -0
  38. package/dist/types/IndexerEventType.cjs +19 -0
  39. package/dist/types/IndexerEventType.cjs.map +1 -0
  40. package/dist/types/IndexerEventType.d.cts +3 -0
  41. package/dist/types/IndexerEventType.d.ts +3 -0
  42. package/dist/types/IndexerEventType.js +1 -0
  43. package/dist/types/IndexerEventType.js.map +1 -0
  44. package/dist/types/IndexerLeaderboardType.cjs +19 -0
  45. package/dist/types/IndexerLeaderboardType.cjs.map +1 -0
  46. package/dist/types/IndexerLeaderboardType.d.cts +3 -0
  47. package/dist/types/IndexerLeaderboardType.d.ts +3 -0
  48. package/dist/types/IndexerLeaderboardType.js +1 -0
  49. package/dist/types/IndexerLeaderboardType.js.map +1 -0
  50. package/dist/types/NadoTx.cjs +19 -0
  51. package/dist/types/NadoTx.cjs.map +1 -0
  52. package/dist/types/NadoTx.d.cts +49 -0
  53. package/dist/types/NadoTx.d.ts +49 -0
  54. package/dist/types/NadoTx.js +1 -0
  55. package/dist/types/NadoTx.js.map +1 -0
  56. package/dist/types/clientTypes.cjs +19 -0
  57. package/dist/types/clientTypes.cjs.map +1 -0
  58. package/dist/types/clientTypes.d.cts +467 -0
  59. package/dist/types/clientTypes.d.ts +467 -0
  60. package/dist/types/clientTypes.js +1 -0
  61. package/dist/types/clientTypes.js.map +1 -0
  62. package/dist/types/collateralEventType.cjs +19 -0
  63. package/dist/types/collateralEventType.cjs.map +1 -0
  64. package/dist/types/collateralEventType.d.cts +3 -0
  65. package/dist/types/collateralEventType.d.ts +3 -0
  66. package/dist/types/collateralEventType.js +1 -0
  67. package/dist/types/collateralEventType.js.map +1 -0
  68. package/dist/types/index.cjs +41 -0
  69. package/dist/types/index.cjs.map +1 -0
  70. package/dist/types/index.d.cts +13 -0
  71. package/dist/types/index.d.ts +13 -0
  72. package/dist/types/index.js +11 -0
  73. package/dist/types/index.js.map +1 -0
  74. package/dist/types/paginatedEventsTypes.cjs +19 -0
  75. package/dist/types/paginatedEventsTypes.cjs.map +1 -0
  76. package/dist/types/paginatedEventsTypes.d.cts +115 -0
  77. package/dist/types/paginatedEventsTypes.d.ts +115 -0
  78. package/dist/types/paginatedEventsTypes.js +1 -0
  79. package/dist/types/paginatedEventsTypes.js.map +1 -0
  80. package/dist/types/serverModelTypes.cjs +19 -0
  81. package/dist/types/serverModelTypes.cjs.map +1 -0
  82. package/dist/types/serverModelTypes.d.cts +224 -0
  83. package/dist/types/serverModelTypes.d.ts +224 -0
  84. package/dist/types/serverModelTypes.js +1 -0
  85. package/dist/types/serverModelTypes.js.map +1 -0
  86. package/dist/types/serverTypes.cjs +19 -0
  87. package/dist/types/serverTypes.cjs.map +1 -0
  88. package/dist/types/serverTypes.d.cts +304 -0
  89. package/dist/types/serverTypes.d.ts +304 -0
  90. package/dist/types/serverTypes.js +1 -0
  91. package/dist/types/serverTypes.js.map +1 -0
  92. package/dist/utils/index.cjs +25 -0
  93. package/dist/utils/index.cjs.map +1 -0
  94. package/dist/utils/index.d.cts +12 -0
  95. package/dist/utils/index.d.ts +12 -0
  96. package/dist/utils/index.js +3 -0
  97. package/dist/utils/index.js.map +1 -0
  98. package/dist/utils/indexerBalanceValue.cjs +43 -0
  99. package/dist/utils/indexerBalanceValue.cjs.map +1 -0
  100. package/dist/utils/indexerBalanceValue.d.cts +39 -0
  101. package/dist/utils/indexerBalanceValue.d.ts +39 -0
  102. package/dist/utils/indexerBalanceValue.js +16 -0
  103. package/dist/utils/indexerBalanceValue.js.map +1 -0
  104. package/package.json +53 -0
  105. package/src/IndexerBaseClient.ts +851 -0
  106. package/src/IndexerClient.ts +468 -0
  107. package/src/dataMappers.ts +362 -0
  108. package/src/endpoints.ts +7 -0
  109. package/src/index.ts +4 -0
  110. package/src/types/CandlestickPeriod.ts +11 -0
  111. package/src/types/IndexerEventType.ts +9 -0
  112. package/src/types/IndexerLeaderboardType.ts +2 -0
  113. package/src/types/NadoTx.ts +63 -0
  114. package/src/types/clientTypes.ts +679 -0
  115. package/src/types/collateralEventType.ts +4 -0
  116. package/src/types/index.ts +9 -0
  117. package/src/types/paginatedEventsTypes.ts +194 -0
  118. package/src/types/serverModelTypes.ts +271 -0
  119. package/src/types/serverTypes.ts +427 -0
  120. package/src/utils/index.ts +1 -0
  121. package/src/utils/indexerBalanceValue.ts +46 -0
@@ -0,0 +1,468 @@
1
+ import {
2
+ ProductEngineType,
3
+ QUOTE_PRODUCT_ID,
4
+ subaccountFromHex,
5
+ VLP_PRODUCT_ID,
6
+ } from '@nadohq/contracts';
7
+ import { toBigDecimal, toIntegerString } from '@nadohq/utils';
8
+
9
+ import { IndexerBaseClient } from './IndexerBaseClient';
10
+ import {
11
+ BaseIndexerPaginatedEvent,
12
+ CollateralEventType,
13
+ GetIndexerPaginatedInterestFundingPaymentsResponse,
14
+ GetIndexerPaginatedLeaderboardParams,
15
+ GetIndexerPaginatedLeaderboardResponse,
16
+ GetIndexerPaginatedOrdersParams,
17
+ GetIndexerPaginatedOrdersResponse,
18
+ GetIndexerSubaccountCollateralEventsParams,
19
+ GetIndexerSubaccountCollateralEventsResponse,
20
+ GetIndexerSubaccountInterestFundingPaymentsParams,
21
+ GetIndexerSubaccountLiquidationEventsParams,
22
+ GetIndexerSubaccountLiquidationEventsResponse,
23
+ GetIndexerSubaccountMatchEventParams,
24
+ GetIndexerSubaccountMatchEventsResponse,
25
+ GetIndexerSubaccountSettlementEventsParams,
26
+ GetIndexerSubaccountSettlementEventsResponse,
27
+ GetIndexerSubaccountVlpEventsParams,
28
+ GetIndexerSubaccountVlpEventsResponse,
29
+ IndexerCollateralEvent,
30
+ IndexerEventPerpStateSnapshot,
31
+ IndexerEventSpotStateSnapshot,
32
+ IndexerEventWithTx,
33
+ IndexerLiquidationEvent,
34
+ IndexerSettlementEvent,
35
+ IndexerVlpEvent,
36
+ PaginatedIndexerEventsResponse,
37
+ } from './types';
38
+
39
+ /**
40
+ * Indexer client providing paginated queries for historical data from the Nado indexer service
41
+ */
42
+ export class IndexerClient extends IndexerBaseClient {
43
+ async getPaginatedSubaccountMatchEvents(
44
+ params: GetIndexerSubaccountMatchEventParams,
45
+ ): Promise<GetIndexerSubaccountMatchEventsResponse> {
46
+ const {
47
+ startCursor,
48
+ maxTimestampInclusive,
49
+ limit: requestedLimit,
50
+ subaccountName,
51
+ subaccountOwner,
52
+ isolated,
53
+ productIds,
54
+ } = params;
55
+
56
+ const limit = requestedLimit + 1;
57
+ const events = await this.getMatchEvents({
58
+ startCursor,
59
+ maxTimestampInclusive,
60
+ limit,
61
+ subaccount: { subaccountName, subaccountOwner },
62
+ productIds,
63
+ isolated,
64
+ });
65
+
66
+ return this.getPaginationEventsResponse(events, requestedLimit);
67
+ }
68
+
69
+ async getPaginatedSubaccountVlpEvents(
70
+ params: GetIndexerSubaccountVlpEventsParams,
71
+ ): Promise<GetIndexerSubaccountVlpEventsResponse> {
72
+ const {
73
+ startCursor,
74
+ maxTimestampInclusive,
75
+ limit: requestedLimit,
76
+ subaccountName,
77
+ subaccountOwner,
78
+ } = params;
79
+
80
+ // There are 2 events per mint/burn for spot - one associated with the VLP product & the other with the primary quote
81
+ const limit = requestedLimit + 1;
82
+ const baseResponse = await this.getEvents({
83
+ startCursor,
84
+ maxTimestampInclusive,
85
+ eventTypes: ['mint_vlp', 'burn_vlp'],
86
+ limit: {
87
+ type: 'txs',
88
+ value: limit,
89
+ },
90
+ subaccount: { subaccountName, subaccountOwner },
91
+ });
92
+
93
+ // Now aggregate results by the submission index, use map to maintain insertion order
94
+ const eventsBySubmissionIdx = new Map<string, IndexerVlpEvent>();
95
+
96
+ baseResponse.forEach((event) => {
97
+ const mappedEvent = (() => {
98
+ const existingEvent = eventsBySubmissionIdx.get(event.submissionIndex);
99
+ if (existingEvent) {
100
+ return existingEvent;
101
+ }
102
+
103
+ const newEvent: IndexerVlpEvent = {
104
+ vlpDelta: toBigDecimal(0),
105
+ primaryQuoteDelta: toBigDecimal(0),
106
+ timestamp: event.timestamp,
107
+ submissionIndex: event.submissionIndex,
108
+ tx: event.tx,
109
+ ...subaccountFromHex(event.subaccount),
110
+ };
111
+ eventsBySubmissionIdx.set(event.submissionIndex, newEvent);
112
+
113
+ return newEvent;
114
+ })();
115
+
116
+ const balanceDelta = event.state.postBalance.amount.minus(
117
+ event.state.preBalance.amount,
118
+ );
119
+
120
+ const productId = event.state.market.productId;
121
+ if (productId === QUOTE_PRODUCT_ID) {
122
+ mappedEvent.primaryQuoteDelta = balanceDelta;
123
+ } else if (productId === VLP_PRODUCT_ID) {
124
+ mappedEvent.vlpDelta = balanceDelta;
125
+ } else {
126
+ throw Error(`Invalid product ID for VLP event ${productId}`);
127
+ }
128
+ });
129
+
130
+ // Force cast to get rid of the `Partial`
131
+ const events = Array.from(eventsBySubmissionIdx.values());
132
+ return this.getPaginationEventsResponse(events, requestedLimit);
133
+ }
134
+
135
+ async getPaginatedSubaccountCollateralEvents(
136
+ params: GetIndexerSubaccountCollateralEventsParams,
137
+ ): Promise<GetIndexerSubaccountCollateralEventsResponse> {
138
+ const {
139
+ startCursor,
140
+ maxTimestampInclusive,
141
+ limit: requestedLimit,
142
+ subaccountName,
143
+ subaccountOwner,
144
+ eventTypes,
145
+ isolated,
146
+ } = params;
147
+
148
+ const limit = requestedLimit + 1;
149
+ const baseResponse = await this.getEvents({
150
+ startCursor,
151
+ maxTimestampInclusive,
152
+ eventTypes: eventTypes ?? [
153
+ 'deposit_collateral',
154
+ 'withdraw_collateral',
155
+ 'transfer_quote',
156
+ ],
157
+ limit: {
158
+ type: 'txs',
159
+ value: limit,
160
+ },
161
+ subaccount: { subaccountName, subaccountOwner },
162
+ isolated,
163
+ });
164
+
165
+ const events = baseResponse.map((event): IndexerCollateralEvent => {
166
+ if (event.state.type !== ProductEngineType.SPOT) {
167
+ throw Error('Incorrect event state for collateral event');
168
+ }
169
+
170
+ return {
171
+ timestamp: event.timestamp,
172
+ // This cast is safe as the query param restricts to collateral events
173
+ eventType: event.eventType as CollateralEventType,
174
+ submissionIndex: event.submissionIndex,
175
+ snapshot: event.state,
176
+ amount: event.state.postBalance.amount.minus(
177
+ event.state.preBalance.amount,
178
+ ),
179
+ newAmount: event.state.postBalance.amount,
180
+ tx: event.tx,
181
+ ...subaccountFromHex(event.subaccount),
182
+ };
183
+ });
184
+
185
+ return this.getPaginationEventsResponse(events, requestedLimit);
186
+ }
187
+
188
+ async getPaginatedSubaccountOrders(
189
+ params: GetIndexerPaginatedOrdersParams,
190
+ ): Promise<GetIndexerPaginatedOrdersResponse> {
191
+ const {
192
+ startCursor,
193
+ maxTimestampInclusive,
194
+ limit: requestedLimit,
195
+ subaccountName,
196
+ subaccountOwner,
197
+ productIds,
198
+ isolated,
199
+ } = params;
200
+
201
+ const limit = requestedLimit + 1;
202
+ const baseResponse = await this.getOrders({
203
+ startCursor,
204
+ maxTimestampInclusive,
205
+ subaccount: { subaccountName, subaccountOwner },
206
+ limit,
207
+ productIds,
208
+ isolated,
209
+ });
210
+
211
+ // Same pagination meta logic as events, but duplicate for now as this return type is slightly different
212
+ const truncatedOrders = baseResponse.slice(0, requestedLimit);
213
+ const hasMore = baseResponse.length > truncatedOrders.length;
214
+ return {
215
+ meta: {
216
+ hasMore,
217
+ nextCursor: baseResponse[truncatedOrders.length]?.submissionIndex,
218
+ },
219
+ orders: truncatedOrders,
220
+ };
221
+ }
222
+
223
+ async getPaginatedSubaccountSettlementEvents(
224
+ params: GetIndexerSubaccountSettlementEventsParams,
225
+ ): Promise<GetIndexerSubaccountSettlementEventsResponse> {
226
+ const {
227
+ startCursor,
228
+ maxTimestampInclusive,
229
+ limit: requestedLimit,
230
+ subaccountName,
231
+ subaccountOwner,
232
+ } = params;
233
+
234
+ // Each settlement has a quote & perp balance change, so 2 events per settlement tx
235
+ const limit = requestedLimit + 1;
236
+ const baseResponse = await this.getEvents({
237
+ startCursor,
238
+ maxTimestampInclusive,
239
+ eventTypes: ['settle_pnl'],
240
+ limit: {
241
+ type: 'txs',
242
+ value: limit,
243
+ },
244
+ subaccount: { subaccountName, subaccountOwner },
245
+ });
246
+
247
+ const events = baseResponse
248
+ .map((event): IndexerSettlementEvent | undefined => {
249
+ if (event.state.market.productId === QUOTE_PRODUCT_ID) {
250
+ return;
251
+ }
252
+ if (event.state.type !== ProductEngineType.PERP) {
253
+ throw Error('Incorrect event state for settlement event');
254
+ }
255
+
256
+ return {
257
+ timestamp: event.timestamp,
258
+ submissionIndex: event.submissionIndex,
259
+ snapshot: event.state,
260
+ // Spot quote delta = -vQuote delta
261
+ quoteDelta: event.state.preBalance.vQuoteBalance.minus(
262
+ event.state.postBalance.vQuoteBalance,
263
+ ),
264
+ isolated: event.isolated,
265
+ tx: event.tx,
266
+ ...subaccountFromHex(event.subaccount),
267
+ };
268
+ })
269
+ .filter((event): event is IndexerSettlementEvent => !!event);
270
+
271
+ return this.getPaginationEventsResponse(events, requestedLimit);
272
+ }
273
+
274
+ async getPaginatedSubaccountLiquidationEvents(
275
+ params: GetIndexerSubaccountLiquidationEventsParams,
276
+ ): Promise<GetIndexerSubaccountLiquidationEventsResponse> {
277
+ const {
278
+ startCursor,
279
+ maxTimestampInclusive,
280
+ limit: requestedLimit,
281
+ subaccountName,
282
+ subaccountOwner,
283
+ } = params;
284
+
285
+ // There is 1 event emitted per product, including quote
286
+ // However, if the balance change is 0, then the liquidation did not touch the product
287
+ // A tx operates on a given health group, so only a spot & its associated perp can be actually liquidated within a single tx
288
+ // with an associated quote balance change
289
+ const limit = requestedLimit + 1;
290
+ const baseResponse = await this.getEvents({
291
+ startCursor,
292
+ maxTimestampInclusive,
293
+ eventTypes: ['liquidate_subaccount'],
294
+ limit: {
295
+ type: 'txs',
296
+ value: limit,
297
+ },
298
+ subaccount: { subaccountName, subaccountOwner },
299
+ });
300
+
301
+ // Now aggregate results by the submission index, use map to maintain insertion order
302
+ const eventsBySubmissionIdx = new Map<
303
+ string,
304
+ Partial<IndexerLiquidationEvent>
305
+ >();
306
+
307
+ baseResponse.forEach((event) => {
308
+ const mappedEvent = (() => {
309
+ const existingEvent = eventsBySubmissionIdx.get(event.submissionIndex);
310
+ if (existingEvent) {
311
+ return existingEvent;
312
+ }
313
+
314
+ const newEvent: Partial<IndexerLiquidationEvent> = {
315
+ perp: undefined,
316
+ spot: undefined,
317
+ quote: undefined,
318
+ timestamp: event.timestamp,
319
+ submissionIndex: event.submissionIndex,
320
+ };
321
+
322
+ return newEvent;
323
+ })();
324
+
325
+ // The original balance is the negated delta
326
+ const balanceDelta = event.state.postBalance.amount.minus(
327
+ event.state.preBalance.amount,
328
+ );
329
+
330
+ // Event without balance change - not part of this liq
331
+ // However, we could have zero balance changes for the quote product if this was a partial liquidation
332
+ if (
333
+ balanceDelta.isZero() &&
334
+ event.state.market.productId !== QUOTE_PRODUCT_ID
335
+ ) {
336
+ return;
337
+ }
338
+
339
+ if (event.state.type === ProductEngineType.PERP) {
340
+ mappedEvent.perp = {
341
+ amountLiquidated: balanceDelta.negated(),
342
+ // This cast is safe because we're checking for event.state.type
343
+ indexerEvent:
344
+ event as IndexerEventWithTx<IndexerEventPerpStateSnapshot>,
345
+ };
346
+ } else if (event.state.market.productId === QUOTE_PRODUCT_ID) {
347
+ mappedEvent.quote = {
348
+ balanceDelta,
349
+ indexerEvent:
350
+ event as IndexerEventWithTx<IndexerEventSpotStateSnapshot>,
351
+ };
352
+ } else {
353
+ mappedEvent.spot = {
354
+ amountLiquidated: balanceDelta.negated(),
355
+ indexerEvent:
356
+ event as IndexerEventWithTx<IndexerEventSpotStateSnapshot>,
357
+ };
358
+ }
359
+
360
+ eventsBySubmissionIdx.set(event.submissionIndex, mappedEvent);
361
+ });
362
+
363
+ // Force cast to get rid of the `Partial`
364
+ const events = Array.from(
365
+ eventsBySubmissionIdx.values(),
366
+ ) as IndexerLiquidationEvent[];
367
+ return this.getPaginationEventsResponse(events, requestedLimit);
368
+ }
369
+
370
+ /**
371
+ * Get all interest funding payments for a given subaccount with the standard pagination response
372
+ * This is a simple wrapper over the underlying `getInterestFundingPayments` function. Very little
373
+ * additional processing is needed because the endpoint is well structured for pagination
374
+ *
375
+ * @param params
376
+ */
377
+ async getPaginatedSubaccountInterestFundingPayments(
378
+ params: GetIndexerSubaccountInterestFundingPaymentsParams,
379
+ ): Promise<GetIndexerPaginatedInterestFundingPaymentsResponse> {
380
+ const {
381
+ limit,
382
+ productIds,
383
+ startCursor,
384
+ maxTimestampInclusive,
385
+ subaccountName,
386
+ subaccountOwner,
387
+ } = params;
388
+ const baseResponse = await this.getInterestFundingPayments({
389
+ limit,
390
+ productIds,
391
+ startCursor,
392
+ maxTimestampInclusive,
393
+ subaccount: {
394
+ subaccountName,
395
+ subaccountOwner,
396
+ },
397
+ });
398
+
399
+ return {
400
+ ...baseResponse,
401
+ meta: {
402
+ hasMore: baseResponse.nextCursor != null,
403
+ nextCursor: baseResponse.nextCursor ?? undefined,
404
+ },
405
+ };
406
+ }
407
+
408
+ /**
409
+ * Paginated leaderboard query that paginates on rank number.
410
+ *
411
+ * @param params
412
+ */
413
+ async getPaginatedLeaderboard(
414
+ params: GetIndexerPaginatedLeaderboardParams,
415
+ ): Promise<GetIndexerPaginatedLeaderboardResponse> {
416
+ const requestedLimit = params.limit;
417
+
418
+ const baseResponse = await this.getLeaderboard({
419
+ contestId: params.contestId,
420
+ rankType: params.rankType,
421
+ // Query for 1 more result for proper pagination
422
+ limit: requestedLimit + 1,
423
+ // Start cursor is the next rank number
424
+ startCursor: params.startCursor,
425
+ });
426
+
427
+ // Next cursor is the rank number of the (requestedLimit+1)th item
428
+ const nextCursor =
429
+ params.rankType === 'pnl'
430
+ ? baseResponse.participants[requestedLimit]?.pnlRank
431
+ : baseResponse.participants[requestedLimit]?.roiRank;
432
+
433
+ return {
434
+ ...baseResponse,
435
+ // Truncate the response to the requested limit
436
+ participants: baseResponse.participants.slice(0, requestedLimit),
437
+ meta: {
438
+ hasMore: baseResponse.participants.length > requestedLimit,
439
+ nextCursor:
440
+ nextCursor !== undefined ? toIntegerString(nextCursor) : undefined,
441
+ },
442
+ };
443
+ }
444
+
445
+ /**
446
+ * A util function to generate the standard pagination response for events
447
+ * @param events
448
+ * @param requestedLimit given by consumers of the SDK
449
+ * @private
450
+ */
451
+ private getPaginationEventsResponse<T extends BaseIndexerPaginatedEvent>(
452
+ events: T[],
453
+ requestedLimit: number,
454
+ ): PaginatedIndexerEventsResponse<T> {
455
+ const truncatedEvents = events.slice(0, requestedLimit);
456
+ const hasMore = events.length > truncatedEvents.length;
457
+
458
+ return {
459
+ events: truncatedEvents,
460
+ meta: {
461
+ hasMore,
462
+ // We want the NEXT available cursor, so we use the first event after the truncation cutoff
463
+ // If len(events) === len(truncatedEvents), there are no more entries and this is undefined
464
+ nextCursor: events[truncatedEvents.length]?.submissionIndex,
465
+ },
466
+ };
467
+ }
468
+ }