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
@@ -19,7 +19,7 @@ export class PackedRanges {
19
19
  constructor(opts?: {
20
20
  from?: number
21
21
  to?: number
22
- ranges?: [ number, number ][]
22
+ ranges?: [number, number][]
23
23
  }) {
24
24
 
25
25
  if (opts != null) {
@@ -29,7 +29,8 @@ export class PackedRanges {
29
29
  }
30
30
  }
31
31
 
32
- total () {
32
+ // Get the outer range [from..to] total capacity (regardless of tracked ranges)
33
+ total() {
33
34
  let min = this.from;
34
35
  let max = this.to ?? this.getMax();
35
36
  if (min == null || max == null) {
@@ -37,7 +38,8 @@ export class PackedRanges {
37
38
  }
38
39
  return max - min;
39
40
  }
40
- totalAdded () {
41
+ // Get the sum of all tracked range lengths (total numbers visited/added)
42
+ totalAdded() {
41
43
  let count = 0;
42
44
  for (let i = 0; i < this.ranges.length; i++) {
43
45
  let [min, max] = this.ranges[i];
@@ -45,11 +47,32 @@ export class PackedRanges {
45
47
  }
46
48
  return count;
47
49
  }
48
- totalLeft () {
50
+
51
+ /**
52
+ * Calculates the number of elements remaining to be visited/added in the range.
53
+ * This is the difference between the total capacity and the number of elements already tracked.
54
+ */
55
+ totalLeft() {
49
56
  return this.total() - this.totalAdded();
50
57
  }
51
58
 
52
59
 
60
+ /**
61
+ * Gets the next unvisited number in the range and marks it as visited.
62
+ *
63
+ * This method finds the next number that hasn't been added to the tracked ranges yet,
64
+ * automatically adds it to the ranges, and returns it. It respects the boundaries
65
+ * defined by `from` and `to` properties.
66
+ *
67
+ * The method works by:
68
+ * - Starting from `this.from` if no ranges exist
69
+ * - Filling gaps between existing ranges
70
+ * - Extending the last range if no gaps exist
71
+ * - Merging adjacent ranges when they become contiguous
72
+ *
73
+ * @returns The next unvisited number in the range, or `null` if all numbers up to `this.to` have been visited
74
+ * @throws {Error} If the internal ranges array is in an invalid state (unsorted or overlapping)
75
+ */
53
76
  next(): number {
54
77
  let min = this.from;
55
78
  if (this.ranges.length === 0) {
@@ -88,6 +111,14 @@ export class PackedRanges {
88
111
  return r0Max;
89
112
  }
90
113
 
114
+ /**
115
+ * Gets the maximum (upper bound) value from all tracked ranges.
116
+ *
117
+ * This method returns the highest number that has been visited/added to the ranges.
118
+ * It retrieves the upper bound of the last range in the sorted ranges array.
119
+ *
120
+ * @returns The maximum value in the tracked ranges, or `null` if no ranges have been added yet
121
+ */
91
122
  getMax() {
92
123
  if (this.ranges.length === 0) {
93
124
  return null;
@@ -96,6 +127,16 @@ export class PackedRanges {
96
127
  return r[1];
97
128
  }
98
129
 
130
+ /**
131
+ * Checks if a specific number has been visited/added to the tracked ranges.
132
+ *
133
+ * This method searches through all tracked ranges to determine if the given number
134
+ * falls within any of them. It performs a linear search through the ranges array,
135
+ * checking if the number is within the bounds (inclusive) of any range.
136
+ *
137
+ * @param nr - The number to check for inclusion in the tracked ranges
138
+ * @returns `true` if the number exists within any of the tracked ranges, `false` otherwise
139
+ */
99
140
  includes(nr: number) {
100
141
  for (let i = 0; i < this.ranges.length; i++) {
101
142
  let [a, b] = this.ranges[i];
@@ -106,6 +147,21 @@ export class PackedRanges {
106
147
  return false;
107
148
  }
108
149
 
150
+ /**
151
+ * Adds a number to the tracked ranges, marking it as visited.
152
+ *
153
+ * This method intelligently adds a number to the ranges by either:
154
+ * - Extending an existing range if the number is adjacent to it
155
+ * - Creating a new range if the number is isolated
156
+ * - Doing nothing if the number is already included in an existing range
157
+ *
158
+ * The method maintains the sorted order of ranges and automatically merges
159
+ * adjacent numbers into contiguous ranges for efficient storage.
160
+ *
161
+ * @param nr - The number to add to the tracked ranges
162
+ * @returns `true` if the number was successfully added (new or extended a range),
163
+ * `false` if the number was already present in the ranges
164
+ */
109
165
  add(nr: number) {
110
166
  let modified = false;
111
167
  for (let i = 0; i < this.ranges.length; i++) {
@@ -142,7 +198,43 @@ export class PackedRanges {
142
198
  }
143
199
  return true;
144
200
  }
145
- remove (nr: number) {
201
+
202
+ /**
203
+ * Adds a complete range to the tracked ranges and compacts the result.
204
+ *
205
+ * This method adds an entire range [min, max] to the tracked ranges array,
206
+ * then automatically compacts the ranges to merge any overlapping or adjacent
207
+ * ranges. This is useful for bulk operations where you want to mark multiple
208
+ * consecutive numbers as visited at once.
209
+ *
210
+ * Unlike the `add()` method which adds individual numbers, this method adds
211
+ * a complete range tuple and ensures the internal ranges array remains optimized
212
+ * by calling `compact()` after insertion.
213
+ *
214
+ * @param range - A tuple [min, max] representing the range to add, where both bounds are inclusive
215
+ */
216
+ addRange(range: [number, number]) {
217
+ this.ranges.push(range);
218
+ this.compact();
219
+ }
220
+
221
+ /**
222
+ * Removes a specific number from the tracked ranges, marking it as unvisited.
223
+ *
224
+ * This method searches for the number within the tracked ranges and removes it by either:
225
+ * - Removing the entire range if it contains only the target number
226
+ * - Shrinking the range by adjusting its lower bound if the number is at the start
227
+ * - Shrinking the range by adjusting its upper bound if the number is at the end
228
+ * - Splitting the range into two separate ranges if the number is in the middle
229
+ *
230
+ * The method maintains the sorted order of ranges and ensures efficient storage
231
+ * by handling all edge cases appropriately.
232
+ *
233
+ * @param nr - The number to remove from the tracked ranges
234
+ * @returns `true` if the number was found and successfully removed from the ranges,
235
+ * `false` if the number was not present in any of the tracked ranges
236
+ */
237
+ remove(nr: number) {
146
238
  for (let i = 0; i < this.ranges.length; i++) {
147
239
  let [min, max] = this.ranges[i];
148
240
  if (min <= nr && nr <= max) {
@@ -160,8 +252,8 @@ export class PackedRanges {
160
252
  return true;
161
253
  }
162
254
 
163
- let arr1 = [min, nr - 1] as [ number, number];
164
- let arr2 = [nr + 1, max] as [ number, number];
255
+ let arr1 = [min, nr - 1] as [number, number];
256
+ let arr2 = [nr + 1, max] as [number, number];
165
257
  this.ranges.splice(i, 1, arr1, arr2);
166
258
  return true;
167
259
  }
@@ -169,45 +261,373 @@ export class PackedRanges {
169
261
  return false;
170
262
  }
171
263
 
264
+ /**
265
+ * Removes a range of numbers from the tracked ranges, marking them as unvisited.
266
+ *
267
+ * This method subtracts the specified range from all tracked ranges, effectively
268
+ * removing all numbers within the given range from the visited/added set. It handles
269
+ * overlapping ranges by splitting or trimming existing ranges as needed.
270
+ *
271
+ * The method maintains the sorted order of ranges and ensures efficient storage
272
+ * by delegating to the `Ranges.subtract()` utility function.
273
+ *
274
+ * @param range - A tuple [min, max] representing the range to remove, where both bounds are inclusive
275
+ * @returns The current `PackedRanges` instance for method chaining
276
+ */
277
+ subtractRange(range: [number, number]) {
278
+ this.ranges = Ranges.subtract(this.ranges, range);
279
+ return this;
280
+ }
281
+
282
+ /**
283
+ * Removes multiple ranges of numbers from the tracked ranges, marking them as unvisited.
284
+ *
285
+ * This method subtracts all specified ranges from the tracked ranges, effectively
286
+ * removing all numbers within the given ranges from the visited/added set. It handles
287
+ * overlapping ranges by splitting or trimming existing ranges as needed for each
288
+ * range in the input array.
289
+ *
290
+ * The method maintains the sorted order of ranges and ensures efficient storage
291
+ * by delegating to the `Ranges.subtractMany()` utility function.
292
+ *
293
+ * @param ranges - An array of tuples [min, max] representing the ranges to remove, where both bounds are inclusive
294
+ * @returns The current `PackedRanges` instance for method chaining
295
+ */
296
+ subtractRanges(ranges: [number, number][]) {
297
+ this.ranges = Ranges.subtractMany(this.ranges, ranges);
298
+ return this;
299
+ }
300
+
301
+ /**
302
+ * Compacts the tracked ranges by merging overlapping and adjacent ranges.
303
+ *
304
+ * This method optimizes the internal ranges array by:
305
+ * - Merging overlapping ranges into single contiguous ranges
306
+ * - Joining adjacent ranges (e.g., [1,4] and [5,6] become [1,6])
307
+ * - Sorting ranges by their starting values
308
+ * - Removing redundant range entries
309
+ *
310
+ * This operation is useful after bulk modifications to ensure the ranges
311
+ * array remains in its most efficient form for storage and querying.
312
+ */
172
313
  compact() {
173
- for (let i = 0; i < this.ranges.length; i++) {
174
- let rangeA = this.ranges[i];
175
- for (let j = i + 1; j < this.ranges.length; j++) {
176
- let rangeB = this.ranges[j];
177
- if (Ranges.overlaps(rangeA, rangeB)) {
314
+ this.ranges = Ranges.compact(this.ranges);
315
+ }
316
+
317
+ /**
318
+ * Sets the tracked ranges to a new array of ranges and compacts them.
319
+ *
320
+ * This method replaces the entire internal ranges array with the provided ranges,
321
+ * then automatically compacts them to merge any overlapping or adjacent ranges.
322
+ * This is useful for bulk initialization or restoration of ranges from a saved state.
323
+ *
324
+ * After setting the ranges, the method ensures the internal ranges array is optimized
325
+ * by calling `compact()`, which merges overlapping ranges and joins adjacent ones.
326
+ *
327
+ * @param ranges - An array of tuples [min, max] representing the ranges to set, where both bounds are inclusive
328
+ */
329
+ set(ranges: [number, number][]) {
330
+ this.ranges = ranges;
331
+ this.compact();
332
+ }
333
+
334
+ /**
335
+ * Serializes the tracked ranges to a JSON string representation.
336
+ *
337
+ * This method converts the internal ranges array into a JSON string format,
338
+ * which can be used for persistence, transmission, or storage purposes.
339
+ * The serialized string can later be parsed and used to restore the ranges
340
+ * via the constructor's `ranges` option.
341
+ *
342
+ * @returns A JSON string representation of the tracked ranges array
343
+ */
344
+ serialize() {
345
+ return JSON.stringify(this.ranges);
346
+ }
347
+
348
+ /**
349
+ * Creates a new PackedRanges instance containing only the ranges starting from a specified number.
350
+ *
351
+ * This method filters the tracked ranges to include only those that contain or come after
352
+ * the specified `from` value. If a range overlaps with the `from` value, it is trimmed
353
+ * to start at `from`. All ranges before `from` are excluded from the result.
354
+ *
355
+ * The original PackedRanges instance remains unchanged; a new instance is returned with
356
+ * the filtered ranges.
357
+ *
358
+ * @param from - The starting number from which to pick ranges (inclusive)
359
+ * @returns A new PackedRanges instance containing only the ranges from the specified number onwards
360
+ */
361
+ pickFrom(from: number): PackedRanges {
362
+ let arr = Ranges.pickFrom(this.ranges, from);
363
+ return new PackedRanges({ ranges: arr });
364
+ }
365
+
366
+ /**
367
+ * Creates a new PackedRanges instance containing only the ranges up to a specified number.
368
+ *
369
+ * This method filters the tracked ranges to include only those that contain or come before
370
+ * the specified `to` value. If a range overlaps with the `to` value, it is trimmed
371
+ * to end at `to`. All ranges after `to` are excluded from the result.
372
+ *
373
+ * The original PackedRanges instance remains unchanged; a new instance is returned with
374
+ * the filtered ranges.
375
+ *
376
+ * @param to - The ending number up to which to pick ranges (inclusive)
377
+ * @returns A new PackedRanges instance containing only the ranges up to the specified number
378
+ */
379
+ pickTo(to: number): PackedRanges {
380
+ let arr = Ranges.pickTo(this.ranges, to);
381
+ return new PackedRanges({ ranges: arr });
382
+ }
383
+
384
+
385
+ /**
386
+ * Computes the intersection of multiple range arrays, returning only the ranges that are common to all inputs.
387
+ *
388
+ * This method performs a set intersection operation across multiple arrays of ranges,
389
+ * finding only those number ranges that appear in every input array. The result is
390
+ * compacted to merge any overlapping or adjacent ranges for efficient storage.
391
+ *
392
+ * For example:
393
+ * - Input: [[[1,5], [10,15]], [[3,7], [12,20]]]
394
+ * - Output: [[3,5], [12,15]] (ranges common to both arrays)
395
+ *
396
+ * @param rangesArr - An array of range arrays, where each range array contains tuples [min, max] representing inclusive number ranges
397
+ * @returns A compacted array of ranges representing the intersection of all input range arrays, or an empty array if no common ranges exist or if the input is empty
398
+ */
399
+ static intersection(rangesArr: ([number, number][])[]): [number, number][] {
400
+ if (rangesArr.length === 0) {
401
+ return [];
402
+ }
403
+ let arr = Ranges.clone(rangesArr[0]);
404
+ for (let i = 1; i < rangesArr.length; i++) {
405
+ arr = Ranges.intersection(arr, rangesArr[i]);
406
+ }
407
+ return Ranges.compact(arr);
408
+ }
409
+
410
+ /**
411
+ * Computes the union of multiple range arrays, returning all ranges that appear in any of the inputs.
412
+ *
413
+ * This method performs a set union operation across multiple arrays of ranges,
414
+ * combining all number ranges from every input array. The result is compacted to
415
+ * merge any overlapping or adjacent ranges for efficient storage.
416
+ *
417
+ * For example:
418
+ * - Input: [[[1,5], [10,15]], [[3,7], [12,20]]]
419
+ * - Output: [[1,7], [10,20]] (all ranges merged and compacted)
420
+ *
421
+ * @param rangesArr - An array of range arrays, where each range array contains tuples [min, max] representing inclusive number ranges
422
+ * @returns A compacted array of ranges representing the union of all input range arrays, or an empty array if the input is empty
423
+ */
424
+ static union(rangesArr: ([number, number][])[]): [number, number][] {
425
+ let arr = alot(rangesArr)
426
+ .mapMany(x => x)
427
+ .toArray();
428
+ return Ranges.compact(arr);
429
+ }
430
+
431
+ /**
432
+ * Computes the difference between two range arrays, removing all ranges in B from ranges in A.
433
+ *
434
+ * This method performs a set difference operation (A − B), subtracting all number ranges
435
+ * in `rangesB` from `rangesA`. The result contains only those portions of `rangesA` that
436
+ * do not overlap with any range in `rangesB`. The result is compacted to merge any
437
+ * overlapping or adjacent ranges for efficient storage.
438
+ *
439
+ * For example:
440
+ * - Input: rangesA = [[1,10], [20,30]], rangesB = [[5,15], [25,35]]
441
+ * - Output: [[1,4], [20,24]] (parts of A not covered by B)
442
+ *
443
+ * @param rangesA - The array of ranges to subtract from, where each range is a tuple [min, max] representing inclusive number ranges
444
+ * @param rangesB - The array of ranges to subtract, where each range is a tuple [min, max] representing inclusive number ranges
445
+ * @returns A compacted array of ranges representing the difference (A − B), or an empty array if all of A is covered by B
446
+ */
447
+ static subtract(rangesA: [number, number][], rangesB: [number, number][]): [number, number][] {
448
+ let arr = Ranges.subtractMany(rangesA, rangesB);
449
+ return Ranges.compact(arr);
450
+ }
451
+
452
+
453
+ static pickFrom(ranges: [number, number][], from: number): [number, number][] {
454
+ let arr = Ranges.pickFrom(ranges, from);
455
+ return arr;
456
+ }
457
+
458
+ static pickTo(ranges: [number, number][], to: number): [number, number][] {
459
+ let arr = Ranges.pickTo(ranges, to);
460
+ return arr;
461
+ }
462
+ }
463
+
464
+ namespace Range {
465
+
466
+ // Check if two ranges overlap
467
+ export function overlaps(r1: [number, number], r2: [number, number]) {
468
+ let [x1, y1] = r1;
469
+ let [x2, y2] = r2;
470
+ let d = Math.min(y1, y2) - Math.max(x1, x2);
471
+ return d > -1;
472
+ }
473
+
474
+ // AND: A ∩ B
475
+ export function intersection(r1: [number, number], r2: [number, number]) {
476
+ let [x1, y1] = r1;
477
+ let [x2, y2] = r2;
478
+ let minY = Math.min(y1, y2);
479
+ let maxX = Math.max(x1, x2);
480
+ if (minY >= maxX) {
481
+ return [maxX, minY] as [number, number];
482
+ }
483
+ return null;
484
+ }
485
+
486
+ }
487
+
488
+
489
+
490
+ namespace Ranges {
491
+ export function clone(ranges: [number, number][]) {
492
+ let arr = [] as [number, number][];
493
+ for (let [x, y] of ranges) {
494
+ arr.push([x, y]);
495
+ }
496
+ return arr;
497
+ }
498
+
499
+ // AND: A ∩ B
500
+ export function intersection(rangesA: [number, number][], rangesB: [number, number][]) {
501
+ let arr = [] as [number, number][];
502
+ for (let i = 0; i < rangesA.length; i++) {
503
+ let rA = rangesA[i];
504
+ for (let j = 0; j < rangesB.length; j++) {
505
+ let union = Range.intersection(rA, rangesB[j]);
506
+ if (union) {
507
+ arr.push(union);
508
+ continue;
509
+ }
510
+ }
511
+ }
512
+ return arr;
513
+ }
514
+
515
+ export function compact (ranges: [number, number][]): [number, number][] {
516
+ for (let i = 0; i < ranges.length; i++) {
517
+ let rangeA = ranges[i];
518
+ for (let j = i + 1; j < ranges.length; j++) {
519
+ let rangeB = ranges[j];
520
+ if (Range.overlaps(rangeA, rangeB)) {
178
521
  let [x1, y1] = rangeA;
179
522
  let [x2, y2] = rangeB;
180
523
 
181
- this.ranges[i] = [Math.min(x1, x2), Math.max(y1, y2)];
182
- this.ranges.splice(j, 1);
524
+ ranges[i] = [Math.min(x1, x2), Math.max(y1, y2)];
525
+ ranges.splice(j, 1);
183
526
  j--;
184
527
  continue;
185
528
  }
186
529
  }
187
530
  }
188
531
 
189
- this.ranges = alot(this.ranges)
532
+ ranges = alot(ranges)
190
533
  .sortBy(([x]) => x)
191
534
  .toArray();
535
+
536
+ // Join non overlapping ranges: [1,4],[5, 6] -> [1,6]
537
+ for (let i = 0; i < ranges.length - 1; i++) {
538
+ let A = ranges[i];
539
+ let B = ranges[i + 1];
540
+ let aLast = A[A.length - 1];
541
+ let bFirst = B[0];
542
+ if (aLast + 1 === bFirst) {
543
+ let bLast = B[B.length - 1];
544
+ A[A.length - 1] = bLast;
545
+ ranges.splice(i + 1, 1);
546
+ }
547
+ }
548
+
549
+ return ranges;
192
550
  }
193
551
 
194
- set(ranges: [number, number][]) {
195
- this.ranges = ranges;
196
- this.compact();
552
+ // Difference: A \ B
553
+ export function subtract(ranges:[number, number][], range: [number, number]) {
554
+ let arr = [] as [number, number][];
555
+ let [rmFrom, rmTo] = range;
556
+ for (let i = 0; i < ranges.length; i++) {
557
+ let A = ranges[i];
558
+ if (Range.overlaps(A, range) === false) {
559
+ arr.push(A);
560
+ continue;
561
+ }
562
+ let [Amin, Amax] = ranges[i];
563
+ if (Amin < rmFrom) {
564
+ arr.push([Amin, rmFrom - 1]);
565
+ }
566
+ if (Amax > rmTo) {
567
+ arr.push([rmTo + 1, Amax]);
568
+ }
569
+ }
570
+ return arr;
197
571
  }
198
- serialize() {
199
- return JSON.stringify(this.ranges);
572
+
573
+ // Difference: A \ ∩B
574
+ export function subtractMany(ranges:[number, number][], rangeArr: [number, number][]) {
575
+ let arr = ranges;
576
+ for (let range of rangeArr) {
577
+ arr = subtract(arr, range);
578
+ }
579
+ return arr;
200
580
  }
201
- }
202
581
 
582
+ export function pickFrom(ranges: [number, number][], from: number): [number, number][] {
583
+ if (ranges == null || ranges.length === 0) {
584
+ return [];
585
+ }
586
+ let arr = [] as [number, number][];
587
+ for (let i = 0; i < ranges.length; i++) {
588
+ let A = ranges[i];
589
+ let aFirst = A[0];
590
+ let aLast = A[A.length - 1];
591
+ if (aLast < from) {
592
+ // This part is fully before from, ignore, search for the next part
593
+ continue;
594
+ }
595
+ if (from < aFirst) {
596
+ // No overlapping, pick all ranges
597
+ arr = ranges.slice(i).map(([x, y]) => [x, y]);
598
+ break;
599
+ }
600
+ let overlapped = [from, aLast] as [number, number];
601
+ arr = ranges.slice(i + 1).map(([x, y]) => [x, y]);
602
+ arr.unshift(overlapped)
603
+ break;
604
+ }
605
+ return arr
606
+ }
203
607
 
608
+ export function pickTo(ranges: [number, number][], to: number): [number, number][] {
609
+ if (ranges == null || ranges.length === 0) {
610
+ return [];
611
+ }
612
+ let arr = [] as [number, number][];
613
+ for (let i = 0; i < ranges.length; i++) {
614
+ let A = ranges[i];
615
+ let aFirst = A[0];
616
+ let aLast = A[A.length - 1];
204
617
 
205
- namespace Ranges {
206
- export function overlaps (r1: [number, number], r2: [number, number]) {
207
- let [x1, y1] = r1;
208
- let [x2, y2] = r2;
209
- let d = Math.min(y1, y2) - Math.max(x1, x2);
618
+ if (aFirst > to) {
619
+ // This part starts after 'to' => break (as sorted, next parts also start after 'to')
620
+ break;
621
+ }
210
622
 
211
- return d > -1;
623
+ if (aLast < to) {
624
+ // No overlapping, pick full range
625
+ arr.push([aFirst, aLast]);
626
+ } else {
627
+ // Overlapps
628
+ arr.push([aFirst, to]);
629
+ }
630
+ }
631
+ return arr;
212
632
  }
213
633
  }
@@ -586,6 +586,10 @@ export type TLogsRangeProgress<TLogParsed> = {
586
586
  blocksPerSecond: number
587
587
  timeLeftSeconds: number
588
588
  completed: boolean
589
+ infos: {
590
+ fetched: number
591
+ cached: number
592
+ }
589
593
  }
590
594
 
591
595
  namespace RangeWorker {
@@ -658,7 +662,8 @@ namespace RangeWorker {
658
662
  blocks: perf.blocks,
659
663
  blocksPerSecond: Number(blocksPerSecFormatted),
660
664
  timeLeftSeconds: leftSeconds,
661
- completed: cursor >= toBlock
665
+ completed: cursor >= toBlock,
666
+ infos: { fetched: perf.blocks.loaded, cached: 0 }
662
667
  });
663
668
 
664
669
  let lastNodeStats = Date.now() - nodeStats;
@@ -19,8 +19,10 @@ import { $require } from '@dequanto/utils/$require';
19
19
  import { $array } from '@dequanto/utils/$array';
20
20
  import { RpcTypes } from '@dequanto/rpc/Rpc';
21
21
  import { TEth } from '@dequanto/models/TEth';
22
- import { TRpcContractCall } from '@dequanto/rpc/RpcContract';
22
+ import { RpcContract, TRpcContractCall } from '@dequanto/rpc/RpcContract';
23
23
  import { WClient } from '@dequanto/clients/ClientPool';
24
+ import { $traces } from '@dequanto/utils/$traces';
25
+ import { $abiProvider } from '@dequanto/utils/$abiProvider';
24
26
 
25
27
 
26
28
 
@@ -35,7 +37,10 @@ export class ContractReader implements IContractReader {
35
37
  private blockNumberTask: Promise<number>;
36
38
  private options: TRpcContractCall = {};
37
39
 
38
- constructor(public client: Web3Client = di.resolve(EthWeb3Client), private ctx?: { name?: string }) {
40
+ constructor(public client: Web3Client = di.resolve(EthWeb3Client), private ctx?: {
41
+ name?: string
42
+ color?: boolean
43
+ }) {
39
44
 
40
45
  }
41
46
  forBlock (mix: number | Date | undefined) {
@@ -107,7 +112,34 @@ export class ContractReader implements IContractReader {
107
112
  return result as TResult;
108
113
  } catch (error) {
109
114
  let args = params.map((x, i) => `[${i}] ${x}`).join('\n');
110
- let err = new Error(`Read ${this.ctx?.name ?? ''} ${address}.${method}(${args}) failed with ${error.message}`);
115
+
116
+ let tracesF = '';
117
+ try {
118
+ let { client } = this;
119
+ let { tx: txData } = await RpcContract.createCalldata({
120
+ address,
121
+ abi: abiArr,
122
+ method: method,
123
+ params: params,
124
+ blockNumber: blockNumber,
125
+ from: this.options.from,
126
+ });
127
+ let traces = await client.debug.traceCall(txData);
128
+ let output = await $traces.format({
129
+ tx: txData,
130
+ traces,
131
+ color: this.ctx?.color ?? false,
132
+ shortAddress: false,
133
+ async getABI(address) {
134
+ return $abiProvider.getAbi(address, client.network);
135
+ }
136
+ });
137
+ tracesF = `\nTraces: \n` + output;
138
+ } catch (error) {
139
+ // ignore
140
+ }
141
+
142
+ let err = new Error(`Read ${this.ctx?.name ?? ''} ${address}.${method}(${args}) failed with ${error.message}${tracesF}`);
111
143
  err.stack = error.stack;
112
144
  throw err;
113
145
  }
@@ -131,7 +131,7 @@ export class ContractWriter implements IContractWriter {
131
131
  let silentTxWriter = writerConfig?.silent ?? ContractWriter.SILENT;
132
132
  if (silentTxWriter === false) {
133
133
  writer.on('log', message => {
134
- $logger.log(`TxContract ${abi.name}; ${message}`);
134
+ $logger.log(`${abi.name}; ${message}`);
135
135
  });
136
136
  }
137
137
  return writer;
@@ -419,6 +419,15 @@ export class HardhatProvider {
419
419
  }
420
420
 
421
421
  private async createTmpFile(solidityCode: string, options: Parameters<HardhatProvider['deploySol']>[1] = {}) {
422
+
423
+ // Ensure the code includes the necessary pragmas and license header.
424
+ if (solidityCode.includes('pragma solidity') === false) {
425
+ solidityCode = `pragma solidity ^0.8.0;\n${solidityCode}`;
426
+ }
427
+ if (solidityCode.includes('SPDX-License-Identifier') === false) {
428
+ solidityCode = `// SPDX-License-Identifier: MIT\n${solidityCode}`;
429
+ }
430
+
422
431
  let contractName = options.contractName;
423
432
  if (contractName == null) {
424
433
  let matches = Array.from(solidityCode.matchAll(/contract\s+(?<name>[\w]+)/g));