dequanto 0.2.27 → 0.2.30

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 (67) hide show
  1. package/lib/cjs/class/PackedRanges.js +422 -20
  2. package/lib/cjs/class/PackedRanges.js.map +1 -1
  3. package/lib/cjs/clients/Web3Client.js +2 -1
  4. package/lib/cjs/clients/Web3Client.js.map +1 -1
  5. package/lib/cjs/contracts/ContractReader.js +30 -1
  6. package/lib/cjs/contracts/ContractReader.js.map +1 -1
  7. package/lib/cjs/contracts/ContractWriter.js +1 -1
  8. package/lib/cjs/contracts/ContractWriter.js.map +1 -1
  9. package/lib/cjs/hardhat/HardhatProvider.js +7 -0
  10. package/lib/cjs/hardhat/HardhatProvider.js.map +1 -1
  11. package/lib/cjs/indexer/EventsIndexer.js +143 -98
  12. package/lib/cjs/indexer/EventsIndexer.js.map +1 -1
  13. package/lib/cjs/rpc/RpcContract.js +21 -13
  14. package/lib/cjs/rpc/RpcContract.js.map +1 -1
  15. package/lib/cjs/safe/GnosisSafeHandler.js +1 -1
  16. package/lib/cjs/safe/GnosisSafeHandler.js.map +1 -1
  17. package/lib/cjs/safe/transport/SafeServiceTransport.js +13 -3
  18. package/lib/cjs/safe/transport/SafeServiceTransport.js.map +1 -1
  19. package/lib/cjs/txs/TxWriter.js +5 -2
  20. package/lib/cjs/txs/TxWriter.js.map +1 -1
  21. package/lib/cjs/utils/$contract.js +7 -2
  22. package/lib/cjs/utils/$contract.js.map +1 -1
  23. package/lib/esm/class/PackedRanges.js.map +1 -1
  24. package/lib/esm/class/PackedRanges.mjs +422 -20
  25. package/lib/esm/clients/Web3Client.js.map +1 -1
  26. package/lib/esm/clients/Web3Client.mjs +2 -1
  27. package/lib/esm/contracts/ContractReader.js.map +1 -1
  28. package/lib/esm/contracts/ContractReader.mjs +30 -1
  29. package/lib/esm/contracts/ContractWriter.js.map +1 -1
  30. package/lib/esm/contracts/ContractWriter.mjs +1 -1
  31. package/lib/esm/hardhat/HardhatProvider.js.map +1 -1
  32. package/lib/esm/hardhat/HardhatProvider.mjs +7 -0
  33. package/lib/esm/indexer/EventsIndexer.js.map +1 -1
  34. package/lib/esm/indexer/EventsIndexer.mjs +143 -98
  35. package/lib/esm/rpc/RpcContract.js.map +1 -1
  36. package/lib/esm/rpc/RpcContract.mjs +21 -13
  37. package/lib/esm/safe/GnosisSafeHandler.js.map +1 -1
  38. package/lib/esm/safe/GnosisSafeHandler.mjs +1 -1
  39. package/lib/esm/safe/transport/SafeServiceTransport.js.map +1 -1
  40. package/lib/esm/safe/transport/SafeServiceTransport.mjs +13 -3
  41. package/lib/esm/txs/TxWriter.js.map +1 -1
  42. package/lib/esm/txs/TxWriter.mjs +5 -2
  43. package/lib/esm/utils/$contract.js.map +1 -1
  44. package/lib/esm/utils/$contract.mjs +7 -2
  45. package/lib/types/class/PackedRanges.d.ts +224 -0
  46. package/lib/types/clients/Web3Client.d.ts +4 -0
  47. package/lib/types/contracts/ContractReader.d.ts +1 -0
  48. package/lib/types/indexer/EventsIndexer.d.ts +5 -1
  49. package/lib/types/indexer/storage/interfaces.d.ts +1 -0
  50. package/lib/types/rpc/RpcContract.d.ts +16 -0
  51. package/lib/types/safe/GnosisSafeHandler.d.ts +1 -0
  52. package/lib/types/safe/transport/ISafeServiceTransport.d.ts +1 -0
  53. package/lib/types/utils/$contract.d.ts +1 -0
  54. package/package.json +1 -1
  55. package/src/class/PackedRanges.ts +447 -27
  56. package/src/clients/Web3Client.ts +6 -1
  57. package/src/contracts/ContractReader.ts +35 -3
  58. package/src/contracts/ContractWriter.ts +1 -1
  59. package/src/hardhat/HardhatProvider.ts +9 -0
  60. package/src/indexer/EventsIndexer.ts +173 -105
  61. package/src/indexer/storage/interfaces.ts +1 -0
  62. package/src/rpc/RpcContract.ts +34 -15
  63. package/src/safe/GnosisSafeHandler.ts +1 -1
  64. package/src/safe/transport/ISafeServiceTransport.ts +5 -1
  65. package/src/safe/transport/SafeServiceTransport.ts +15 -4
  66. package/src/txs/TxWriter.ts +6 -2
  67. package/src/utils/$contract.ts +7 -2
@@ -7,7 +7,7 @@ import { ITxLogItem } from '@dequanto/txs/receipt/ITxLogItem';
7
7
  import { $date } from '@dequanto/utils/$date';
8
8
  import { $require } from '@dequanto/utils/$require';
9
9
  import { TAddress } from '@dequanto/models/TAddress';
10
- import { IEventsIndexerMetaStore, IEventsIndexerStore, TEventsIndexerItem } from './storage/interfaces';
10
+ import { IEventsIndexerMetaStore, IEventsIndexerStore, TEventsIndexerItem, TEventsIndexerMeta } from './storage/interfaces';
11
11
  import { FsEventsIndexerStore } from './storage/FsEventsIndexerStore';
12
12
  import { FsEventsMetaStore } from './storage/FsEventsMetaStore';
13
13
  import { class_Dfr } from 'atma-utils';
@@ -15,6 +15,7 @@ import { TLogsRangeProgress } from '@dequanto/clients/Web3Client';
15
15
  import { WClient } from '@dequanto/clients/ClientPool';
16
16
  import { TEth } from '@dequanto/models/TEth';
17
17
  import { l } from '@dequanto/utils/$logger';
18
+ import { PackedRanges } from '@dequanto/class/PackedRanges';
18
19
 
19
20
 
20
21
  export class EventsIndexer <TContract extends ContractBase> {
@@ -86,18 +87,27 @@ export class EventsIndexer <TContract extends ContractBase> {
86
87
  }
87
88
  ): Promise<{
88
89
  logs: ITxLogItem<GetTypes<TContract>['Events'][TLogName]['outputParams']>[],
90
+ infos: {
91
+ fetched: number,
92
+ cached: number,
93
+ }
89
94
  }> {
90
95
  let contract = this.contract;
91
96
  let client = contract.client;
97
+ let fromBlock = filter?.fromBlock;
92
98
  let toBlock = filter?.toBlock ?? await client.getBlockNumber();
93
99
  let events = Array.isArray(event) ? event as string[] : [ event as string ];
94
100
  let filterKey = this.getFilterKey(filter?.params);
95
- let ranges = await this.getRanges(events, this.options.initialBlockNumber, toBlock, { filterKey });
96
- let logs = await this.getPastLogsRanges(ranges, events, toBlock, filter?.fromBlock, {
101
+ let ranges = await this.getRanges(events, this.options.initialBlockNumber, toBlock, {
102
+ fromBlock,
103
+ filterKey
104
+ });
105
+ let { logs, infos } = await this.getPastLogsRanges(ranges, events, toBlock, fromBlock, {
97
106
  params: filter?.params as any
98
107
  });
99
108
  return {
100
- logs: logs as any
109
+ logs: logs as any,
110
+ infos,
101
111
  };
102
112
  }
103
113
 
@@ -122,17 +132,19 @@ export class EventsIndexer <TContract extends ContractBase> {
122
132
  > {
123
133
  let contract = this.contract;
124
134
  let client = contract.client;
135
+ let fromBlock = options?.fromBlock;
125
136
  let toBlock = options?.toBlock ?? await client.getBlockNumber();
126
137
  let events = Array.isArray(event) ? event as string[] : [ event as string ];
127
138
  let filterKey = this.getFilterKey(options?.params);
128
- let ranges = await this.getRanges(events, options?.fromBlock ?? this.options.initialBlockNumber, toBlock, {
139
+ let ranges = await this.getRanges(events, this.options.initialBlockNumber, toBlock, {
140
+ fromBlock,
129
141
  filterKey
130
142
  });
131
143
 
132
144
  let dfrInner = new class_Dfr<any>();
133
145
  let dfrOuter = new class_Dfr<any>();
134
146
 
135
- this.getPastLogsRanges(ranges, events, toBlock, options?.fromBlock, {
147
+ this.getPastLogsRanges(ranges, events, toBlock, fromBlock, {
136
148
  params: options?.params,
137
149
  blockRangeLimits: options?.blockRangeLimits,
138
150
  streamed: true,
@@ -162,7 +174,7 @@ export class EventsIndexer <TContract extends ContractBase> {
162
174
  }
163
175
  }
164
176
 
165
- async removeCached (params: {
177
+ async removeCached (params?: {
166
178
  fromBlock: number
167
179
  }) {
168
180
  let events = await this.store.fetch({ fromBlock: params.fromBlock });
@@ -170,12 +182,15 @@ export class EventsIndexer <TContract extends ContractBase> {
170
182
  await this.store.removeMany(events);
171
183
 
172
184
  let metas = await this.storeMeta.fetch();
173
- let lastBlock = params.fromBlock - 1;
185
+ let lastBlock = (params?.fromBlock ?? 0) - 1;
174
186
  if (lastBlock < 0) {
187
+ // remove all metas
175
188
  await this.storeMeta.removeMany(metas)
176
189
  } else {
190
+ // adjust the lastBlock and the ranges
177
191
  metas.forEach(x => {
178
192
  x.lastBlock = Math.min(x.lastBlock, lastBlock);
193
+ x.ranges = PackedRanges.pickTo(x.ranges, x.lastBlock);
179
194
  });
180
195
  await this.storeMeta.upsertMany(metas);
181
196
  }
@@ -184,107 +199,88 @@ export class EventsIndexer <TContract extends ContractBase> {
184
199
  private async getRanges (events: string[], initialBlockNumber: number, toBlock: number, opts: {
185
200
  fromBlock?: number
186
201
  filterKey?: string
187
- }): Promise<TRange[]> {
188
- let fromBlock = opts?.fromBlock ?? initialBlockNumber;
202
+ }): Promise<TRangeLoader> {
203
+ let fromBlock = opts?.fromBlock ?? initialBlockNumber ?? await this.getInitialBlockNumber();
189
204
  if (fromBlock != null) {
190
205
  $require.lte(fromBlock, toBlock, `Invalid block range order`);
191
206
  }
192
207
  let logsMetaArr = await this.storeMeta.fetch();
193
208
  let eventsBlock = alot(logsMetaArr)
194
209
  .filter(x => x.filterKey == opts?.filterKey)
195
- .toDictionary(x => x.event, x => x.lastBlock);
196
- let logsMeta = events.map(event => {
197
- let blockNr = eventsBlock[event] ?? initialBlockNumber;
210
+ .toDictionary(x => x.event, x => x);
211
+
212
+ let logsMeta = await alot(events).mapAsync(async event => {
213
+ let lastBlock = eventsBlock[event]?.lastBlock ?? initialBlockNumber ?? await this.getInitialBlockNumber();
214
+ let ranges = eventsBlock[event]?.ranges ?? [];
198
215
  return {
199
216
  event: event,
200
- lastBlock: blockNr
201
- }
202
- });
203
- let hasInitialBlock = logsMeta.every(x => x.lastBlock != null);
204
- if (hasInitialBlock === false) {
205
- let blockNr = opts?.fromBlock ?? await this.getInitialBlockNumber();
206
- logsMeta.filter(x => x.lastBlock == null).forEach(x => x.lastBlock = blockNr);
207
- }
217
+ lastBlock: lastBlock,
218
+ ranges: ranges,
219
+ filterKey: opts?.filterKey
220
+ };
221
+ }).toArrayAsync();
208
222
 
209
- let ranges = [] as TRange[];
210
- let blockNumbers = [ ...logsMeta.map(x => x.lastBlock), toBlock ].filter(x => x <= toBlock);
223
+ let intersactionRange = PackedRanges.intersection(logsMeta.map(x => x.ranges));
224
+ let rangesToLoad = PackedRanges.subtract([[fromBlock, toBlock]], intersactionRange);
211
225
 
212
- let blockNumberSteps = alot(blockNumbers).distinct().sortBy(x => x).toArray();
213
- if (blockNumberSteps.length === 1) {
214
- let [ block ] = blockNumberSteps;
215
- ranges.push({
216
- fromBlock: block,
217
- toBlock: block,
218
- events: events
219
- });
220
- }
221
- for (let i = 0; i < blockNumberSteps.length - 1; i++) {
222
- let from = blockNumberSteps[i];
223
- if (i > 0) {
224
- // The range includes both blocks: [x, y], but we want (x, y], so we make [x + 1, y]
225
- from += 1;
226
- }
227
- let to = blockNumberSteps[i + 1];
228
- let events = logsMeta.filter(x => x.lastBlock < to).map(x =>x.event);
229
- ranges.push({
226
+ let ranges = rangesToLoad.map(([from, to]) => {
227
+ return {
230
228
  fromBlock: from,
231
229
  toBlock: to,
232
- events: events
233
- });
234
- }
235
- return ranges;
230
+ events: events,
231
+ filterKey: opts?.filterKey
232
+ } as TRange
233
+ });
234
+
235
+ return {
236
+ fromBlock,
237
+ load: ranges,
238
+ current: logsMeta,
239
+ };
236
240
  }
237
- private async getPastLogsRanges (ranges: TRange[], events: string[], toBlock: number, fromBlock?: number, options?: TEventLogOptions<any>) {
238
- // Save indexed logs every 2 minutes
241
+ // Save indexed logs every 2 minutes
242
+ private async getPastLogsRanges (ranges: TRangeLoader, events: string[], toBlock: number, fromBlock?: number, options?: TEventLogOptions<any>) {
239
243
  const PERSIST_INTERVAL = $date.parseTimespan('2min');
240
244
  // Save indexed logs every 10k logs
241
245
  const PERSIST_COUNT = 10_000;
242
246
 
247
+ fromBlock ??= ranges.fromBlock ?? 0;
248
+
243
249
  let time = Date.now();
244
250
  let contract = this.contract;
245
251
  let buffer = [] as ITxLogItem<any>[];
246
- let isStreamed = options?.streamed ?? false
252
+ let isStreamed = options?.streamed ?? false;
253
+ let infos = {
254
+ cached: 0,
255
+ fetched: 0,
256
+ };
247
257
 
258
+ let cachedStreamedBuffer = [];
248
259
  if (isStreamed && typeof options?.onProgress === 'function') {
249
- let arr = await this.getItemsFromStore({
260
+ cachedStreamedBuffer = await this.getItemsFromStore({
250
261
  fromBlock: fromBlock,
251
- toBlock: toBlock,
262
+ toBlock: toBlock + 1,
252
263
  events,
253
264
  params: options?.params,
254
265
  });
255
- if (arr?.length > 0) {
256
- let lastBlock = arr[arr.length - 1].blockNumber;
257
- await options.onProgress({
258
- logs: arr,
259
- paged: arr,
260
- completed: false,
261
- blocksPerSecond: 0,
262
- blocks: { total: 0, loaded: 0 },
263
- timeLeftSeconds: 0,
264
- latestBlock: lastBlock
265
- });
266
- // if (fromBlock != null) {
267
- // let latestFromStorage = alot(arr).max(x => x.blockNumber);
268
- // fromBlock = latestFromStorage + 1;
269
- // if (toBlock != null && fromBlock >= toBlock) {
270
- // // we got all from storage
271
- // return;
272
- // }
273
- // }
274
-
275
- // Adjust ranges to skip blocks already in storage
276
- for (let i = 0; i < ranges.length; i++) {
277
- let range = ranges[i];
278
- if (range.toBlock <= lastBlock) {
279
- ranges.splice(i, 1);
280
- i--;
281
- continue;
282
- }
283
- if (range.fromBlock < lastBlock) {
284
- range.fromBlock = lastBlock + 1;
285
- }
286
- }
287
- }
266
+ infos.cached = cachedStreamedBuffer.length;
267
+ // Note: we don't emit progress right away to keep the order in progress callback
268
+ // Consider: we have blocks [100-150] in cache
269
+ // Requesting range: [50-200]
270
+ // Later we fetch [50-100) and (150-200]
271
+ // The cached response will be inserted after block.100 to keep consistent order
272
+ // if (arr?.length > 0) {
273
+ // let lastBlock = arr[arr.length - 1].blockNumber;
274
+ // await options.onProgress({
275
+ // logs: arr,
276
+ // paged: arr,
277
+ // completed: false,
278
+ // blocksPerSecond: 0,
279
+ // blocks: { total: 0, loaded: 0 },
280
+ // timeLeftSeconds: 0,
281
+ // latestBlock: lastBlock
282
+ // });
283
+ // }
288
284
  }
289
285
 
290
286
  let bufferCount = 0;
@@ -294,8 +290,8 @@ export class EventsIndexer <TContract extends ContractBase> {
294
290
  let uniqueCount = 0;
295
291
  let unique = {};
296
292
 
297
- for (let i = 0; i < ranges.length; i++) {
298
- let range = ranges[i];
293
+ for (let i = 0; i < ranges.load.length; i++) {
294
+ let range = ranges.load[i];
299
295
  let { fromBlock, toBlock, events: rangeEvents } = range;
300
296
 
301
297
  let fetched = await contract.getPastLogs(rangeEvents?.length > 0 ? rangeEvents : events, {
@@ -305,13 +301,14 @@ export class EventsIndexer <TContract extends ContractBase> {
305
301
  toBlock: toBlock,
306
302
  blockRangeLimits: options?.blockRangeLimits,
307
303
  params: options?.params,
308
- onProgress: async info => {
304
+ onProgress: async chunk => {
309
305
  onProgressCount++;
310
306
 
311
- buffer.push(...info.logs);
312
- bufferCount += info.logs.length;
307
+ buffer.push(...chunk.logs);
308
+ bufferCount += chunk.logs.length;
309
+ infos.fetched += chunk.logs.length;
313
310
 
314
- for (let log of info.logs) {
311
+ for (let log of chunk.logs) {
315
312
  let key = log.id + '';
316
313
  if (key in unique) {
317
314
  uniqueCount += 1;
@@ -326,9 +323,9 @@ export class EventsIndexer <TContract extends ContractBase> {
326
323
  l`OnProgressCalled cyan<${ onProgressCount}> BufferCount cyan<${bufferCount}> SavedCount cyan<${savedCount}> Unique `
327
324
  }
328
325
 
329
- let isCompleted = i < ranges.length - 1
326
+ let isCompleted = i < ranges.load.length - 1
330
327
  ? false
331
- : info.completed;
328
+ : chunk.completed;
332
329
 
333
330
  let now = Date.now();
334
331
 
@@ -341,24 +338,64 @@ export class EventsIndexer <TContract extends ContractBase> {
341
338
  buffer = [];
342
339
  time = now;
343
340
  savedCount += arr.length;
344
- await this.upsert(arr, events, info.latestBlock, options);
341
+ await this.upsert(arr, events, ranges, fromBlock, chunk.latestBlock, options);
345
342
  }
346
343
 
347
344
  if (options?.onProgress) {
348
345
  // completed must be set to true only when the last Range completes
349
- info.completed = isCompleted;
350
- await options.onProgress(info);
346
+ chunk.completed = isCompleted;
347
+ if (cachedStreamedBuffer.length > 0) {
348
+ if (chunk.logs.length > 0) {
349
+ // Preserve the order of logs in progress callback:
350
+ // insert cached.blockNumber < fetched.blockNumber first
351
+ let [ first ] = chunk.logs;
352
+ let i = 0;
353
+ for (; i < cachedStreamedBuffer.length; i++) {
354
+ let x = cachedStreamedBuffer[i];
355
+ if (x.blockNumber > first.blockNumber) {
356
+ break;
357
+ }
358
+ }
359
+ if (i > 0) {
360
+ let before = cachedStreamedBuffer.splice(0, i);
361
+ chunk.logs = before.concat(chunk.logs);
362
+ }
363
+ }
364
+ if (isCompleted) {
365
+ // insert everything left from cache
366
+ chunk.logs = chunk.logs.concat(cachedStreamedBuffer);
367
+ cachedStreamedBuffer = [];
368
+ }
369
+ }
370
+ await options.onProgress({
371
+ ...chunk,
372
+ infos,
373
+ });
351
374
  }
352
375
  }
353
376
  });
354
377
 
355
378
  if (buffer.length > 0) {
356
- await this.upsert(buffer, events, toBlock, options);
379
+ await this.upsert(buffer, events, ranges, fromBlock, toBlock, options);
357
380
  }
358
381
  }
359
382
 
360
- // Upsert final, if buffer is empty, we still persist the "toBlock" value
361
- await this.upsert(buffer, events, toBlock, options);
383
+ // Upsert final, if buffer is empty, we still persist the "toBlock" value and the ranges
384
+ await this.upsert(buffer, events, ranges, fromBlock, toBlock, options);
385
+
386
+ if (isStreamed && typeof options?.onProgress === 'function' && cachedStreamedBuffer.length > 0) {
387
+ let lastBlock = cachedStreamedBuffer[cachedStreamedBuffer.length - 1].blockNumber;
388
+ await options.onProgress({
389
+ logs: cachedStreamedBuffer,
390
+ paged: cachedStreamedBuffer,
391
+ completed: true,
392
+ blocksPerSecond: 0,
393
+ blocks: { total: 0, loaded: 0 },
394
+ timeLeftSeconds: 0,
395
+ latestBlock: lastBlock,
396
+ infos,
397
+ });
398
+ }
362
399
 
363
400
  if (isStreamed === true) {
364
401
  return;
@@ -371,7 +408,13 @@ export class EventsIndexer <TContract extends ContractBase> {
371
408
  params: options?.params
372
409
  });
373
410
 
374
- return logs;
411
+ infos.fetched = bufferCount;
412
+ infos.cached = logs.length - infos.fetched;
413
+
414
+ return {
415
+ logs,
416
+ infos,
417
+ };
375
418
  }
376
419
 
377
420
 
@@ -389,7 +432,7 @@ export class EventsIndexer <TContract extends ContractBase> {
389
432
  }
390
433
  let filterKey = this.getFilterKey(filter.params);
391
434
  arr = arr.filter(x => x.filterKey == filterKey);
392
- return arr;
435
+ return alot(arr).sortBy(x => x.id).toArray();
393
436
  }
394
437
  private getFilterKey (params) {
395
438
  if (params == null) {
@@ -413,7 +456,14 @@ export class EventsIndexer <TContract extends ContractBase> {
413
456
  }
414
457
 
415
458
  @memd.deco.queued()
416
- private async upsert (logs: TEventsIndexerItem[], events: string[], latestBlock: number, options: { params? }) {
459
+ private async upsert (
460
+ logs: TEventsIndexerItem[],
461
+ events: string[],
462
+ ranges: TRangeLoader,
463
+ fromBlock: number,
464
+ latestBlock: number,
465
+ options: { params? }
466
+ ) {
417
467
  let filterKey = this.getFilterKey(options?.params);
418
468
  if (logs?.length > 0) {
419
469
  for (let log of logs) {
@@ -421,11 +471,22 @@ export class EventsIndexer <TContract extends ContractBase> {
421
471
  }
422
472
  await this.store.upsertMany(logs);
423
473
  }
424
- const logsMeta = events.map(event => ({
425
- event,
426
- lastBlock: latestBlock,
427
- filterKey: filterKey,
428
- }));
474
+ const logsMeta = events.map(event => {
475
+ let currentMeta = ranges
476
+ .current
477
+ .find(x => x.event === event);
478
+
479
+ let arr = PackedRanges.union([
480
+ currentMeta?.ranges ?? [],
481
+ [[fromBlock, latestBlock]]
482
+ ]);
483
+ return {
484
+ event,
485
+ lastBlock: latestBlock,
486
+ filterKey: filterKey,
487
+ ranges: arr,
488
+ };
489
+ });
429
490
  await this.storeMeta.upsertMany(logsMeta);
430
491
  }
431
492
  }
@@ -434,8 +495,15 @@ type TRange = {
434
495
  events: string[]
435
496
  fromBlock: number
436
497
  toBlock: number
498
+ filterKey?: string
437
499
  }
438
500
 
439
501
 
502
+ type TRangeLoader = {
503
+ fromBlock: number
504
+ current: TEventsIndexerMeta[]
505
+ load: TRange[]
506
+ }
507
+
440
508
  type GetEventLogNames<T extends ContractBase> = keyof T['Types']['Events'];
441
509
  type GetTypes<T extends ContractBase> = T['Types'];
@@ -8,6 +8,7 @@ export type TEventsIndexerMeta = {
8
8
  event: string
9
9
  lastBlock: number
10
10
  filterKey?: string
11
+ ranges?: [number, number][]
11
12
  }
12
13
 
13
14
  export interface IEventsIndexerStore {
@@ -92,40 +92,59 @@ export class RpcContract {
92
92
  return alot(requests).mapAsync(req => this.getCallRequestRaw(req)).toArrayAsync();
93
93
  }
94
94
  private async getCallRequestRaw (request: TRpcContractCall) {
95
- let address = request.address ?? this.defaults?.address;
95
+ let {
96
+ tx,
97
+ abiItem,
98
+ blockNumber
99
+ } = await RpcContract.createCalldata(request, this.defaults);
100
+
101
+ let rpc = new Rpc();
102
+ return {
103
+ methodRequest: rpc.req.eth_call(tx, blockNumber ?? 'latest'),
104
+ methodAbi: abiItem
105
+ };
106
+ }
107
+
108
+
109
+ static async createCalldata (request: TRpcContractCall, options?: {
110
+ client?: Web3Client
111
+ address?: TEth.Address
112
+ abi?: TAbiItem[]
113
+ }) {
114
+ let address = request.address ?? options?.address;
96
115
  $require.Address(address);
97
116
 
98
117
  let data = request.data;
99
118
  let { abi, method, params, blockNumber } = request;
100
- let abiArr = $web3Abi.ensureAbis(abi ?? this.defaults?.abi);
119
+ let abiArr = $web3Abi.ensureAbis(abi ?? options?.abi);
101
120
  let abiItem = abiArr?.find(x => x.name === method);
102
121
  if (abiItem == null) {
103
- abiItem = this.defaults?.abi?.find(x => x.name === method);
122
+ abiItem = options?.abi?.find(x => x.name === method);
104
123
  }
105
124
 
106
125
  if (data == null) {
107
126
  $require.notNull(abiItem, `Method ${method} not found. Available methods: ${abiArr?.map(x => x.name) }`);
108
127
  data = $abiUtils.serializeMethodCallData(abiItem, params ?? []);
109
128
  }
129
+
110
130
  if (blockNumber instanceof Date) {
111
- let resolver = di.resolve(BlockDateResolver, this.client);
131
+ let client = $require.notNull(options?.client, `Blocknumber is Date: ${blockNumber}, Client is required.`);
132
+ let resolver = di.resolve(BlockDateResolver, client);
112
133
  blockNumber = await resolver.getBlockNumberFor(blockNumber);
113
134
  }
114
- let tx = {
115
- from: request.from ?? void 0,
116
- to: address,
117
- value: $hex.ensure(request.value ?? 0n),
118
- data: data,
119
- };
120
- let rpc = new Rpc();
135
+
121
136
  return {
122
- methodRequest: rpc.req.eth_call(tx, blockNumber ?? 'latest'),
123
- methodAbi: abiItem
137
+ tx: {
138
+ from: request.from ?? void 0,
139
+ to: address,
140
+ value: $hex.ensure(request.value ?? 0n),
141
+ data: data,
142
+ },
143
+ abiItem,
144
+ blockNumber
124
145
  };
125
146
  }
126
147
 
127
-
128
-
129
148
  // async submit (contract: TRpcContract, req: TRpcContractCallReq) {
130
149
  // let abis = contract.abi;
131
150
  // let abi = abis.find(x => x.name === req.method);
@@ -175,7 +175,7 @@ export class GnosisSafeHandler {
175
175
  baseGas: 0,
176
176
  gasToken: $address.ZERO,
177
177
  refundReceiver: $address.ZERO,
178
- nonce: safeInfo.nonce,
178
+ nonce: $require.Number(Number(safeInfo.nonce) + Number(safeInfo.pending ?? 0)),
179
179
  gasPrice: 0,
180
180
  };
181
181
 
@@ -12,7 +12,11 @@ export interface ISafeServiceTransport {
12
12
 
13
13
  confirmTx(safeTxHash: string, sig: { signature: string, owner: TAddress }): Promise<SafeServiceTypes.SignatureResponse>
14
14
 
15
- getSafeInfo(safeAddress: TAddress): Promise<{ nonce: number, threshold: number }>
15
+ getSafeInfo(safeAddress: TAddress): Promise<{
16
+ pending?: number
17
+ nonce: number
18
+ threshold: number
19
+ }>
16
20
 
17
21
  estimateSafeTransaction(safeAddress: TAddress, safeTxEstimation): Promise<SafeServiceTypes.SafeMultisigTransactionEstimateResponse>
18
22
 
@@ -101,10 +101,21 @@ class SafeServiceClientInner {
101
101
  return data;
102
102
  }
103
103
 
104
- async getSafeInfo(safeAddress: string): Promise<SafeServiceTypes.SafeInfoResponse> {
105
- let url = `${this.params.txServiceUrl}/api/v1/safes/${safeAddress}`;
106
- let { data } = await $http.get<SafeServiceTypes.SafeInfoResponse>(url);
107
- return data;
104
+ async getSafeInfo(safeAddress: string): Promise<SafeServiceTypes.SafeInfoResponse & { pending?: number}> {
105
+ let safeUrl = `${this.params.txServiceUrl}/api/v1/safes/${safeAddress}`;
106
+ let { data: safeInfo } = await $http.get<SafeServiceTypes.SafeInfoResponse>(safeUrl);
107
+
108
+ let pendingUrl = `${this.params.txServiceUrl}/api/v1/safes/${safeAddress}/multisig-transactions/?executed=false&ordering=-nonce&limit=1`
109
+ let { data: pendingTxs } = await $http.get<SafeServiceTypes.SafeMultisigTransactionListResponse>(pendingUrl);
110
+ let nonce = Number(safeInfo.nonce);
111
+ let pending = 0;
112
+ if (pendingTxs.results?.length > 0) {
113
+ pending = $require.Number(Number(pendingTxs.results[0].nonce) + 1 - nonce);
114
+ }
115
+ return {
116
+ ...safeInfo,
117
+ pending
118
+ };
108
119
  }
109
120
 
110
121
  async estimateSafeTransaction(safeAddress: string, safeTransaction: SafeServiceTypes.SafeMultisigTransactionEstimate): Promise<SafeServiceTypes.SafeMultisigTransactionEstimateResponse> {
@@ -296,7 +296,9 @@ export class TxWriter extends class_EventEmitter<ITxWriterEvents> implements ITx
296
296
  tx.hash = hash;
297
297
  this.onSent.resolve(hash);
298
298
  this.emit('transactionHash', hash);
299
- this.emit('log', `Tx hash: ${hash}`);
299
+ if (this.client.platform !== 'hardhat') {
300
+ this.emit('log', `Tx hash: ${hash}`);
301
+ }
300
302
  })
301
303
  // .on('confirmation', (confNumber, receipt) => {
302
304
  // tx.hash = receipt.transactionHash ?? tx.hash;
@@ -344,7 +346,9 @@ export class TxWriter extends class_EventEmitter<ITxWriterEvents> implements ITx
344
346
  let hash = tx.hash;
345
347
  let status = receipt.status;
346
348
  let gasFormatted = $gas.formatUsed(this.builder.data, <any>receipt);
347
- this.emit('log', `Tx receipt: ${hash}.\n\tStatus: ${status}.\n\tGas used: ${gasFormatted}`);
349
+
350
+ let message = `${status ? '✓' : '✕'} ${gasFormatted} ${hash}`;
351
+ this.emit('log', message);
348
352
 
349
353
  this.onCompleted.resolve(receipt);
350
354
  } catch (error) {
@@ -299,8 +299,8 @@ export namespace $contract {
299
299
  address?: TEth.Address
300
300
  abi: TAbiItem[]
301
301
  }
302
- const knownContracts = [] as IContractMeta[];
303
- const knownContractsAddresses = {} as Record<string, IContractMeta>
302
+ let knownContracts = [] as IContractMeta[];
303
+ let knownContractsAddresses = {} as Record<string, IContractMeta>
304
304
 
305
305
  export function getFlattened() {
306
306
  return alot(knownContracts).mapMany(x => x.abi).toArray();
@@ -332,6 +332,11 @@ export namespace $contract {
332
332
  return $abiUtils.getMethodSignature(item) === selector;
333
333
  });
334
334
  }
335
+
336
+ export function clear () {
337
+ knownContracts = [];
338
+ knownContractsAddresses = {};
339
+ }
335
340
  }
336
341
 
337
342
  function formatJson(json) {