@xyo-network/chain-sdk 4.0.5 → 4.0.6

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 (42) hide show
  1. package/dist/browser/modules/ethereum/block/blockRangeChunks.d.ts +8 -0
  2. package/dist/browser/modules/ethereum/block/blockRangeChunks.d.ts.map +1 -0
  3. package/dist/browser/modules/ethereum/block/estimateBlockNumberAt.d.ts +17 -0
  4. package/dist/browser/modules/ethereum/block/estimateBlockNumberAt.d.ts.map +1 -0
  5. package/dist/browser/modules/ethereum/block/index.d.ts +2 -0
  6. package/dist/browser/modules/ethereum/block/index.d.ts.map +1 -1
  7. package/dist/browser/modules/ethereum/drivers/EvmStakeEventsViewer.d.ts +27 -0
  8. package/dist/browser/modules/ethereum/drivers/EvmStakeEventsViewer.d.ts.map +1 -1
  9. package/dist/browser/modules/ethereum/drivers/EvmStakeViewer.d.ts.map +1 -1
  10. package/dist/browser/modules/ethereum/rpc/isRpcRateLimitError.d.ts +6 -0
  11. package/dist/browser/modules/ethereum/rpc/isRpcRateLimitError.d.ts.map +1 -0
  12. package/dist/browser/modules/ethereum/rpc/withRpcRateLimitBackoff.d.ts +19 -0
  13. package/dist/browser/modules/ethereum/rpc/withRpcRateLimitBackoff.d.ts.map +1 -0
  14. package/dist/neutral/ethereum.mjs +249 -65
  15. package/dist/neutral/ethereum.mjs.map +4 -4
  16. package/dist/neutral/modules/ethereum/block/blockRangeChunks.d.ts +8 -0
  17. package/dist/neutral/modules/ethereum/block/blockRangeChunks.d.ts.map +1 -0
  18. package/dist/neutral/modules/ethereum/block/estimateBlockNumberAt.d.ts +17 -0
  19. package/dist/neutral/modules/ethereum/block/estimateBlockNumberAt.d.ts.map +1 -0
  20. package/dist/neutral/modules/ethereum/block/index.d.ts +2 -0
  21. package/dist/neutral/modules/ethereum/block/index.d.ts.map +1 -1
  22. package/dist/neutral/modules/ethereum/drivers/EvmStakeEventsViewer.d.ts +27 -0
  23. package/dist/neutral/modules/ethereum/drivers/EvmStakeEventsViewer.d.ts.map +1 -1
  24. package/dist/neutral/modules/ethereum/drivers/EvmStakeViewer.d.ts.map +1 -1
  25. package/dist/neutral/modules/ethereum/rpc/isRpcRateLimitError.d.ts +6 -0
  26. package/dist/neutral/modules/ethereum/rpc/isRpcRateLimitError.d.ts.map +1 -0
  27. package/dist/neutral/modules/ethereum/rpc/withRpcRateLimitBackoff.d.ts +19 -0
  28. package/dist/neutral/modules/ethereum/rpc/withRpcRateLimitBackoff.d.ts.map +1 -0
  29. package/dist/node/modules/ethereum/block/blockRangeChunks.d.ts +8 -0
  30. package/dist/node/modules/ethereum/block/blockRangeChunks.d.ts.map +1 -0
  31. package/dist/node/modules/ethereum/block/estimateBlockNumberAt.d.ts +17 -0
  32. package/dist/node/modules/ethereum/block/estimateBlockNumberAt.d.ts.map +1 -0
  33. package/dist/node/modules/ethereum/block/index.d.ts +2 -0
  34. package/dist/node/modules/ethereum/block/index.d.ts.map +1 -1
  35. package/dist/node/modules/ethereum/drivers/EvmStakeEventsViewer.d.ts +27 -0
  36. package/dist/node/modules/ethereum/drivers/EvmStakeEventsViewer.d.ts.map +1 -1
  37. package/dist/node/modules/ethereum/drivers/EvmStakeViewer.d.ts.map +1 -1
  38. package/dist/node/modules/ethereum/rpc/isRpcRateLimitError.d.ts +6 -0
  39. package/dist/node/modules/ethereum/rpc/isRpcRateLimitError.d.ts.map +1 -0
  40. package/dist/node/modules/ethereum/rpc/withRpcRateLimitBackoff.d.ts +19 -0
  41. package/dist/node/modules/ethereum/rpc/withRpcRateLimitBackoff.d.ts.map +1 -0
  42. package/package.json +16 -16
@@ -162,6 +162,42 @@ var BaseEthProvider = class {
162
162
  }
163
163
  };
164
164
 
165
+ // src/modules/ethereum/block/blockRangeChunks.ts
166
+ function blockRangeChunks(fromBlock, toBlock, chunkSize) {
167
+ if (!Number.isInteger(chunkSize) || chunkSize < 1) {
168
+ throw new TypeError(`chunkSize must be an integer >= 1, got ${chunkSize}`);
169
+ }
170
+ if (!Number.isFinite(fromBlock) || !Number.isFinite(toBlock)) {
171
+ throw new TypeError(`fromBlock and toBlock must be finite numbers, got [${fromBlock}, ${toBlock}]`);
172
+ }
173
+ if (fromBlock > toBlock) {
174
+ return [];
175
+ }
176
+ const chunks = [];
177
+ for (let start = fromBlock; start <= toBlock; start += chunkSize) {
178
+ const end = Math.min(start + chunkSize - 1, toBlock);
179
+ chunks.push([start, end]);
180
+ }
181
+ return chunks;
182
+ }
183
+
184
+ // src/modules/ethereum/block/estimateBlockNumberAt.ts
185
+ import { assertEx } from "@ariestools/sdk";
186
+ var DEFAULT_EVM_BLOCK_TIME_SEC = 12;
187
+ var DEFAULT_ESTIMATE_BLOCK_DATE = new Date(Date.UTC(2025, 5, 1));
188
+ function estimateBlockNumberFromHead(headNumber, headTimestampSec, targetDate = DEFAULT_ESTIMATE_BLOCK_DATE, avgBlockTimeSec = DEFAULT_EVM_BLOCK_TIME_SEC) {
189
+ const targetTimestampSec = Math.floor(targetDate.getTime() / 1e3);
190
+ if (targetTimestampSec >= headTimestampSec) {
191
+ return headNumber;
192
+ }
193
+ const blocksBack = Math.ceil((headTimestampSec - targetTimestampSec) / avgBlockTimeSec);
194
+ return Math.max(0, headNumber - blocksBack);
195
+ }
196
+ async function estimateBlockNumberAt(provider, targetDate = DEFAULT_ESTIMATE_BLOCK_DATE, avgBlockTimeSec = DEFAULT_EVM_BLOCK_TIME_SEC) {
197
+ const head = assertEx(await provider.getBlock("latest"), () => new Error("Failed to get latest eth block"));
198
+ return estimateBlockNumberFromHead(head.number, head.timestamp, targetDate, avgBlockTimeSec);
199
+ }
200
+
165
201
  // src/modules/ethereum/block/rate/blockRate.ts
166
202
  import { isDefined, isFalsy } from "@ariestools/sdk";
167
203
  import {
@@ -210,7 +246,7 @@ var calculateEthBlockRate = async (provider, range, timeUnit) => {
210
246
 
211
247
  // src/modules/ethereum/block/rate/timeRate.ts
212
248
  import {
213
- assertEx,
249
+ assertEx as assertEx2,
214
250
  isDefined as isDefined2,
215
251
  isDefinedNotNull
216
252
  } from "@ariestools/sdk";
@@ -222,12 +258,12 @@ import {
222
258
  var DEFAULT_TOLERANCE_MS = 3e4;
223
259
  var DEFAULT_MAX_ATTEMPTS = 20;
224
260
  var calculateEthTimeRate = async (provider, timeConfig, startBlockNumber, timeUnit, toleranceMs = DEFAULT_TOLERANCE_MS, maxAttempts = DEFAULT_MAX_ATTEMPTS) => {
225
- assertEx(Object.keys(timeConfig ?? {}).length === 1, () => "Only one time unit should be specified in timeConfig");
261
+ assertEx2(Object.keys(timeConfig ?? {}).length === 1, () => "Only one time unit should be specified in timeConfig");
226
262
  const startBlock = isDefinedNotNull(startBlockNumber) ? await provider.getBlock(BigInt(startBlockNumber)) : null;
227
- assertEx(isDefinedNotNull(startBlockNumber) && isDefinedNotNull(startBlock), () => "Start block could not be resolved");
228
- const resolvedStartBlock = isDefinedNotNull(startBlock) ? startBlock : assertEx(await provider.getBlock("latest"));
263
+ assertEx2(isDefinedNotNull(startBlockNumber) && isDefinedNotNull(startBlock), () => "Start block could not be resolved");
264
+ const resolvedStartBlock = isDefinedNotNull(startBlock) ? startBlock : assertEx2(await provider.getBlock("latest"));
229
265
  const timeInMilliseconds = getTimeConfigInMilliseconds(timeConfig);
230
- assertEx(timeInMilliseconds > 0, () => "Time duration must be greater than zero");
266
+ assertEx2(timeInMilliseconds > 0, () => "Time duration must be greater than zero");
231
267
  const blocksPerMillisecondRate = 1 / (12 * 1e3);
232
268
  const initialBlocksInDuration = Math.floor(blocksPerMillisecondRate * timeInMilliseconds);
233
269
  const endBlockNumber = await findEndBlockRecursive(
@@ -246,14 +282,14 @@ var calculateEthTimeRate = async (provider, timeConfig, startBlockNumber, timeUn
246
282
  };
247
283
  var findEndBlockRecursive = async (provider, startBlock, targetTimeMs, estimatedBlocksBack, toleranceMs, attemptsRemaining) => {
248
284
  console.log(`Attempts remaining: ${attemptsRemaining}, Estimated blocks back: ${estimatedBlocksBack}`);
249
- assertEx(attemptsRemaining > 0, () => "Maximum attempts reached while searching for end block");
285
+ assertEx2(attemptsRemaining > 0, () => "Maximum attempts reached while searching for end block");
250
286
  const startBlockEpoch = startBlock.timestamp;
251
287
  const estimatedEndBlockNumber = startBlock.number - estimatedBlocksBack;
252
288
  if (estimatedEndBlockNumber < 0) {
253
289
  throw new Error("Estimated end block number is less than zero");
254
290
  }
255
291
  const endBlock = await provider.getBlock(estimatedEndBlockNumber);
256
- const resolvedEndBlock = assertEx(
292
+ const resolvedEndBlock = assertEx2(
257
293
  isDefined2(endBlock) ? endBlock : void 0,
258
294
  () => `Could not retrieve block ${estimatedEndBlockNumber} for time rate calculation`
259
295
  );
@@ -296,7 +332,7 @@ import { createGenesisBlock } from "#protocol";
296
332
  // src/modules/ethereum/createChainContract.ts
297
333
  import {
298
334
  asAddress,
299
- assertEx as assertEx2,
335
+ assertEx as assertEx3,
300
336
  ETH_ZERO_ADDRESS,
301
337
  hexToBigInt,
302
338
  toEthAddress
@@ -324,7 +360,7 @@ var createChainContract = async (signer, stakingTokenAddress, genesisBlockHash,
324
360
  );
325
361
  await deployResponse.waitForDeployment();
326
362
  const xyoChainContractAddress = await deployResponse.getAddress();
327
- return assertEx2(asAddress(xyoChainContractAddress), () => "Invalid staking contract address");
363
+ return assertEx3(asAddress(xyoChainContractAddress), () => "Invalid staking contract address");
328
364
  } catch (ex) {
329
365
  console.log(ex);
330
366
  throw ex;
@@ -334,7 +370,7 @@ var createChainContract = async (signer, stakingTokenAddress, genesisBlockHash,
334
370
  // src/modules/ethereum/createChainRewardsContract.ts
335
371
  import {
336
372
  asAddress as asAddress2,
337
- assertEx as assertEx3,
373
+ assertEx as assertEx4,
338
374
  toFixedPoint
339
375
  } from "@ariestools/sdk";
340
376
  import { XyoChainRewards__factory as XyoChainRewardsFactory } from "@xyo-network/typechain";
@@ -361,7 +397,7 @@ var createChainRewardsContract = async (signer, genesisReward = 0n, gas = gasCon
361
397
  );
362
398
  await deployResponse.waitForDeployment();
363
399
  const xyoChainRewardsAddress = await deployResponse.getAddress();
364
- return assertEx3(asAddress2(xyoChainRewardsAddress), () => "Invalid rewards contract address");
400
+ return assertEx4(asAddress2(xyoChainRewardsAddress), () => "Invalid rewards contract address");
365
401
  } catch (ex) {
366
402
  console.log(ex);
367
403
  throw ex;
@@ -371,7 +407,7 @@ var createChainRewardsContract = async (signer, genesisReward = 0n, gas = gasCon
371
407
  // src/modules/ethereum/nextContractAddress.ts
372
408
  import {
373
409
  asAddress as asAddress3,
374
- assertEx as assertEx4,
410
+ assertEx as assertEx5,
375
411
  toEthAddress as toEthAddress2
376
412
  } from "@ariestools/sdk";
377
413
  import { getCreateAddress } from "ethers/address";
@@ -379,11 +415,11 @@ var nextContractAddressForAddress = async (address, provider) => {
379
415
  const deployerAddress = toEthAddress2(address);
380
416
  const nonce = await provider.getTransactionCount(deployerAddress);
381
417
  const contractAddress = getCreateAddress({ from: deployerAddress, nonce });
382
- return assertEx4(asAddress3(contractAddress), () => "Unable to convert contract address to address");
418
+ return assertEx5(asAddress3(contractAddress), () => "Unable to convert contract address to address");
383
419
  };
384
420
  var nextContractAddress = async (signer) => {
385
- const provider = assertEx4(signer.provider, () => "Unable to obtain provider from signer");
386
- const address = assertEx4(asAddress3(await signer.getAddress()), () => "Unable to obtain address from signer");
421
+ const provider = assertEx5(signer.provider, () => "Unable to obtain provider from signer");
422
+ const address = assertEx5(asAddress3(await signer.getAddress()), () => "Unable to obtain address from signer");
387
423
  return await nextContractAddressForAddress(address, provider);
388
424
  };
389
425
 
@@ -414,7 +450,7 @@ async function createChain(signer, stakingTokenAddress, initialBlockProducer, re
414
450
 
415
451
  // src/modules/ethereum/drivers/EvmStakeEventsViewer.ts
416
452
  import {
417
- assertEx as assertEx7,
453
+ assertEx as assertEx8,
418
454
  exists,
419
455
  isDefined as isDefined3,
420
456
  toAddress as toAddress3,
@@ -444,7 +480,7 @@ var AbstractEvmProvider = class extends AbstractCreatableProvider {
444
480
  };
445
481
 
446
482
  // src/modules/ethereum/providers/AbstractEvmRunner.ts
447
- import { assertEx as assertEx5 } from "@ariestools/sdk";
483
+ import { assertEx as assertEx6 } from "@ariestools/sdk";
448
484
 
449
485
  // src/modules/ethereum/providers/AbstractEvmViewer.ts
450
486
  var AbstractEvmViewer = class extends AbstractEvmProvider {
@@ -453,8 +489,8 @@ var AbstractEvmViewer = class extends AbstractEvmProvider {
453
489
  // src/modules/ethereum/providers/AbstractEvmRunner.ts
454
490
  var AbstractEvmRunner = class extends AbstractEvmViewer {
455
491
  static async paramsHandler(params) {
456
- const runner = assertEx5(params?.runner, () => new Error("No runner defined"));
457
- const provider = assertEx5(runner.provider, () => new Error("Runner must have a provider defined"));
492
+ const runner = assertEx6(params?.runner, () => new Error("No runner defined"));
493
+ const provider = assertEx6(runner.provider, () => new Error("Runner must have a provider defined"));
458
494
  return { ...await super.paramsHandler({ ...params, provider }), runner };
459
495
  }
460
496
  };
@@ -462,7 +498,7 @@ var AbstractEvmRunner = class extends AbstractEvmViewer {
462
498
  // src/modules/ethereum/providers/EvmChainContractViewer.ts
463
499
  import {
464
500
  asHash,
465
- assertEx as assertEx6,
501
+ assertEx as assertEx7,
466
502
  toAddress as toAddress2,
467
503
  toEthAddress as toEthAddress5,
468
504
  toHex
@@ -495,7 +531,7 @@ var EvmChainContractViewer = class extends AbstractEvmViewer {
495
531
  let contractViewer = this;
496
532
  let forkedAtBlockNumber = await contractViewer.forkedAtBlockNumber();
497
533
  while (forkedAtBlockNumber !== null && blockNumber <= forkedAtBlockNumber) {
498
- contractViewer = assertEx6(await contractViewer.forkedChainContractViewer(), () => "Forked chain contract viewer is undefined");
534
+ contractViewer = assertEx7(await contractViewer.forkedChainContractViewer(), () => "Forked chain contract viewer is undefined");
499
535
  forkedAtBlockNumber = await contractViewer.forkedAtBlockNumber();
500
536
  chainId = await contractViewer.chainId();
501
537
  }
@@ -641,6 +677,93 @@ EvmStakeTotalsViewer = __decorateClass([
641
677
  creatableProvider3()
642
678
  ], EvmStakeTotalsViewer);
643
679
 
680
+ // src/modules/ethereum/rpc/withRpcRateLimitBackoff.ts
681
+ import { delay } from "@ariestools/sdk";
682
+
683
+ // src/modules/ethereum/rpc/isRpcRateLimitError.ts
684
+ function isRpcRateLimitError(error) {
685
+ const seen = /* @__PURE__ */ new Set();
686
+ const queue = [error];
687
+ while (queue.length > 0) {
688
+ const current = queue.pop();
689
+ if (current === void 0 || current === null || seen.has(current)) {
690
+ continue;
691
+ }
692
+ seen.add(current);
693
+ if (typeof current === "string") {
694
+ if (matchesRateLimitText(current)) {
695
+ return true;
696
+ }
697
+ continue;
698
+ }
699
+ if (typeof current === "number") {
700
+ if (isRateLimitCode(current)) {
701
+ return true;
702
+ }
703
+ continue;
704
+ }
705
+ if (typeof current !== "object") {
706
+ continue;
707
+ }
708
+ if (Array.isArray(current)) {
709
+ const items = current;
710
+ queue.push(...items);
711
+ continue;
712
+ }
713
+ const record = current;
714
+ for (const key of ["code", "message", "shortMessage", "reason", "status", "statusCode"]) {
715
+ if (key in record) {
716
+ queue.push(record[key]);
717
+ }
718
+ }
719
+ for (const key of ["error", "info", "data", "value", "body", "payload"]) {
720
+ if (key in record) {
721
+ queue.push(record[key]);
722
+ }
723
+ }
724
+ }
725
+ return false;
726
+ }
727
+ function isRateLimitCode(code) {
728
+ return code === 429 || code === -32005 || code === -32016;
729
+ }
730
+ function matchesRateLimitText(text) {
731
+ const normalized = text.toLowerCase();
732
+ return normalized.includes("too many requests") || normalized.includes("rate limit") || normalized.includes("rate exceeded") || normalized.includes("call rate limit") || normalized.includes("exceeded the quota");
733
+ }
734
+
735
+ // src/modules/ethereum/rpc/withRpcRateLimitBackoff.ts
736
+ async function withRpcRateLimitBackoff(fn, options = {}) {
737
+ const {
738
+ backoff = 2,
739
+ initialDelayMs = 1e3,
740
+ maxAttempts = 8,
741
+ maxDelayMs = 3e4,
742
+ onRetry
743
+ } = options;
744
+ if (!Number.isInteger(maxAttempts) || maxAttempts < 1) {
745
+ throw new TypeError(`maxAttempts must be an integer >= 1, got ${maxAttempts}`);
746
+ }
747
+ let delayMs = initialDelayMs;
748
+ let lastError;
749
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
750
+ try {
751
+ return await fn();
752
+ } catch (error) {
753
+ lastError = error;
754
+ const isLastAttempt = attempt >= maxAttempts;
755
+ if (!isRpcRateLimitError(error) || isLastAttempt) {
756
+ throw error;
757
+ }
758
+ const waitMs = Math.min(delayMs, maxDelayMs);
759
+ onRetry?.(error, attempt, waitMs);
760
+ await delay(waitMs);
761
+ delayMs = Math.min(delayMs * backoff, maxDelayMs);
762
+ }
763
+ }
764
+ throw lastError;
765
+ }
766
+
644
767
  // src/modules/ethereum/drivers/EvmStakeEventZod.ts
645
768
  import {
646
769
  EthAddressZod,
@@ -696,11 +819,25 @@ var asEvmStakeWithdrawnEvent = zodAsFactory(EvmStakeWithdrawnEventZod, "asEvmSta
696
819
  var toEvmStakeWithdrawnEvent = zodToFactory(EvmStakeWithdrawnEventZod, "toEvmStakeWithdrawnEvent");
697
820
 
698
821
  // src/modules/ethereum/drivers/EvmStakeEventsViewer.ts
822
+ var DEFAULT_EVENT_QUERY_BLOCK_CHUNK_SIZE = 2e3;
823
+ var DEFAULT_EVENT_QUERY_MAX_ATTEMPTS = 8;
824
+ var DEFAULT_EVENT_QUERY_INITIAL_DELAY_MS = 1e3;
699
825
  var EvmStakeEventsViewer = class extends AbstractEvmViewer {
700
826
  moniker = EvmStakeEventsViewer.defaultMoniker;
827
+ /** Set at start when `params.deploymentBlock` is omitted (estimated 2025-06-01). */
828
+ defaultDeploymentBlock = 0;
701
829
  eventCache = /* @__PURE__ */ new Map();
702
830
  get deploymentBlock() {
703
- return this.params.deploymentBlock ?? 0;
831
+ return this.params.deploymentBlock ?? this.defaultDeploymentBlock;
832
+ }
833
+ get eventQueryBlockChunkSize() {
834
+ return this.params.eventQueryBlockChunkSize ?? DEFAULT_EVENT_QUERY_BLOCK_CHUNK_SIZE;
835
+ }
836
+ get eventQueryInitialDelayMs() {
837
+ return this.params.eventQueryInitialDelayMs ?? DEFAULT_EVENT_QUERY_INITIAL_DELAY_MS;
838
+ }
839
+ get eventQueryMaxAttempts() {
840
+ return this.params.eventQueryMaxAttempts ?? DEFAULT_EVENT_QUERY_MAX_ATTEMPTS;
704
841
  }
705
842
  async positionCount(range) {
706
843
  const events = await this.stakeEvents(range);
@@ -747,9 +884,7 @@ var EvmStakeEventsViewer = class extends AbstractEvmViewer {
747
884
  }
748
885
  const validatedEvent = validatedEventResult.data;
749
886
  const { eventName, blockNumber } = validatedEvent;
750
- this.logger?.log(`Processing ${eventName} event for stake ID ${id} at block ${blockNumber}`);
751
887
  const cacheKey = `${Number(id)}|${eventName}|${staker}`;
752
- const existingStakeEvent = this.eventCache.get(cacheKey);
753
888
  const stakeEvent = {
754
889
  name: eventName,
755
890
  args: {
@@ -760,62 +895,101 @@ var EvmStakeEventsViewer = class extends AbstractEvmViewer {
760
895
  },
761
896
  time: blockNumber
762
897
  };
763
- if (isDefined3(existingStakeEvent)) {
764
- this.logger?.warn(`Received ${eventName} event for stake ID ${id} that is already cached, updating cache...`);
765
- this.logger?.warn("1");
766
- this.logger?.warn("Previous event", existingStakeEvent);
767
- this.logger?.warn("2");
768
- this.logger?.warn("New event", stakeEvent);
769
- this.logger?.warn("3");
770
- }
771
898
  this.eventCache.set(cacheKey, stakeEvent);
772
899
  return stakeEvent;
773
900
  }
774
901
  async startHandler() {
775
902
  try {
776
- const currentEthBlock = assertEx7(await this.contract.runner?.provider?.getBlockNumber(), () => new Error("Failed to egt currentEthBlock"));
777
- await this.contract.on(this.contract.getEvent("StakeAdded"), (staked, staker, id, amount, event) => {
778
- this.onEventHandler(staked, staker, id, amount, event);
779
- });
780
- await this.contract.on(this.contract.getEvent("StakeRemoved"), (staked, staker, id, amount, event) => {
781
- this.onEventHandler(staked, staker, id, amount, event);
782
- });
783
- await this.contract.on(this.contract.getEvent("StakeWithdrawn"), (staked, staker, id, amount, event) => {
784
- this.onEventHandler(staked, staker, id, amount, event);
785
- });
786
- await this.loadHistoricalEvents(currentEthBlock);
903
+ const provider = assertEx8(this.contract.runner?.provider, () => new Error("Failed to get provider"));
904
+ const historyEndBlock = assertEx8(await provider.getBlockNumber(), () => new Error("Failed to get currentEthBlock"));
905
+ if (!isDefined3(this.params.deploymentBlock)) {
906
+ this.defaultDeploymentBlock = await estimateBlockNumberAt(provider);
907
+ }
908
+ await this.loadHistoricalEvents(this.deploymentBlock, historyEndBlock);
909
+ await this.attachLiveListeners();
910
+ const latestBlock = assertEx8(await provider.getBlockNumber(), () => new Error("Failed to get latestEthBlock"));
911
+ if (latestBlock > historyEndBlock) {
912
+ await this.loadHistoricalEvents(historyEndBlock + 1, latestBlock);
913
+ }
787
914
  } catch (error) {
788
915
  this.logger?.error("EthereumChainStakeEvents failed to start:", error);
789
916
  throw error;
790
917
  }
791
918
  }
792
- async loadHistoricalEvents(currentEthBlock) {
919
+ async attachLiveListeners() {
920
+ await this.contract.on(this.contract.getEvent("StakeAdded"), (staked, staker, id, amount, event) => {
921
+ this.onEventHandler(staked, staker, id, amount, event);
922
+ });
923
+ await this.contract.on(this.contract.getEvent("StakeRemoved"), (staked, staker, id, amount, event) => {
924
+ this.onEventHandler(staked, staker, id, amount, event);
925
+ });
926
+ await this.contract.on(this.contract.getEvent("StakeWithdrawn"), (staked, staker, id, amount, event) => {
927
+ this.onEventHandler(staked, staker, id, amount, event);
928
+ });
929
+ }
930
+ async loadHistoricalEvents(fromBlock, toBlock) {
931
+ if (fromBlock > toBlock) {
932
+ return;
933
+ }
793
934
  const stakeEventGroups = await Promise.all([
794
- this.stakeEventsNative([this.deploymentBlock, currentEthBlock], { name: "StakeAdded" }),
795
- this.stakeEventsNative([this.deploymentBlock, currentEthBlock], { name: "StakeRemoved" }),
796
- this.stakeEventsNative([this.deploymentBlock, currentEthBlock], { name: "StakeWithdrawn" })
935
+ this.stakeEventsNative([fromBlock, toBlock], { name: "StakeAdded" }),
936
+ this.stakeEventsNative([fromBlock, toBlock], { name: "StakeRemoved" }),
937
+ this.stakeEventsNative([fromBlock, toBlock], { name: "StakeWithdrawn" })
797
938
  ]);
798
939
  const allStakeEvents = stakeEventGroups.flat().toSorted((a, b) => a.time - b.time);
799
- console.warn(`Caching ${allStakeEvents.length} stake events from chain starting at block ${this.deploymentBlock}`);
940
+ const chunks = blockRangeChunks(fromBlock, toBlock, this.eventQueryBlockChunkSize);
941
+ this.logger?.log(
942
+ `Cached ${allStakeEvents.length} stake events from blocks ${fromBlock}-${toBlock} (${chunks.length} chunk(s) of up to ${this.eventQueryBlockChunkSize} blocks)`
943
+ );
800
944
  for (const event of allStakeEvents) {
801
945
  this.eventCache.set(`${Number(event.args.id)}|${event.name}|${event.args.staker}`, event);
802
946
  }
803
947
  }
804
948
  async stakeEventsNative(range = [0, "latest"], filter) {
805
949
  const name = filter?.name;
806
- if (isDefined3(name)) {
807
- const stakedFilter = filter?.args?.staked;
808
- const stakerFilter = filter?.args?.staker;
809
- const stakedFilterEth = isDefined3(stakedFilter) ? toEthAddress8(stakedFilter) : void 0;
810
- const stakerFilterEth = isDefined3(stakerFilter) ? toEthAddress8(stakerFilter) : void 0;
811
- const ethFilter = this.contract.filters[name](stakedFilterEth, stakerFilterEth);
812
- const ethEvents = await this.contract.queryFilter(ethFilter, range[0], range[1]);
813
- return ethEvents.map((e) => {
814
- assertEx7(e.eventName === name, () => `Event name mismatch expected '${name}' but got '${e.eventName}'`);
815
- return this.onEventHandler(e.args.staked, e.args.by, e.args.id, e.args.amount, e, true);
816
- }).filter(exists);
950
+ if (!isDefined3(name)) {
951
+ throw new Error("Only filtered stake events (by name) are supported");
817
952
  }
818
- throw new Error("Only filtered stake events (by name) are supported");
953
+ const stakedFilter = filter?.args?.staked;
954
+ const stakerFilter = filter?.args?.staker;
955
+ const stakedFilterEth = isDefined3(stakedFilter) ? toEthAddress8(stakedFilter) : void 0;
956
+ const stakerFilterEth = isDefined3(stakerFilter) ? toEthAddress8(stakerFilter) : void 0;
957
+ const ethFilter = this.contract.filters[name](stakedFilterEth, stakerFilterEth);
958
+ const fromBlock = range[0];
959
+ const toBlock = range[1] === "latest" ? assertEx8(await this.contract.runner?.provider?.getBlockNumber(), () => new Error("Failed to resolve latest block")) : range[1];
960
+ if (fromBlock > toBlock) {
961
+ return [];
962
+ }
963
+ const chunkSize = this.eventQueryBlockChunkSize;
964
+ const chunks = blockRangeChunks(fromBlock, toBlock, chunkSize);
965
+ const ethEventGroups = [];
966
+ for (const [chunkFrom, chunkTo] of chunks) {
967
+ try {
968
+ ethEventGroups.push(await withRpcRateLimitBackoff(
969
+ () => this.contract.queryFilter(ethFilter, chunkFrom, chunkTo),
970
+ {
971
+ initialDelayMs: this.eventQueryInitialDelayMs,
972
+ maxAttempts: this.eventQueryMaxAttempts,
973
+ onRetry: (_error, attempt, delayMs) => {
974
+ this.logger?.warn(
975
+ `Rate limited fetching ${name} logs for blocks ${chunkFrom}-${chunkTo} (attempt ${attempt}/${this.eventQueryMaxAttempts}); backing off ${delayMs}ms`
976
+ );
977
+ }
978
+ }
979
+ ));
980
+ } catch (error) {
981
+ this.logger?.error(
982
+ `eth_getLogs failed for ${name} blocks ${chunkFrom}-${chunkTo} after rate-limit retries (from=${fromBlock} to=${toBlock}):`,
983
+ error
984
+ );
985
+ throw error;
986
+ }
987
+ }
988
+ const ethEvents = ethEventGroups.flat();
989
+ return ethEvents.map((e) => {
990
+ assertEx8(e.eventName === name, () => `Event name mismatch expected '${name}' but got '${e.eventName}'`);
991
+ return this.onEventHandler(e.args.staked, e.args.by, e.args.id, e.args.amount, e, true);
992
+ }).filter(exists);
819
993
  }
820
994
  };
821
995
  __publicField(EvmStakeEventsViewer, "connectionTypes", ["evm-rpc"]);
@@ -829,7 +1003,8 @@ EvmStakeEventsViewer = __decorateClass([
829
1003
 
830
1004
  // src/modules/ethereum/drivers/EvmStakeViewer.ts
831
1005
  import {
832
- assertEx as assertEx8,
1006
+ assertEx as assertEx9,
1007
+ isDefined as isDefined4,
833
1008
  toAddress as toAddress4,
834
1009
  toEthAddress as toEthAddress9
835
1010
  } from "@ariestools/sdk";
@@ -883,6 +1058,7 @@ var EvmStakeViewer = class extends AbstractEvmViewer {
883
1058
  async createHandler() {
884
1059
  await super.createHandler();
885
1060
  this._stakeEventsViewer = await this.locator.getInstance(StakeEventsViewerMoniker2);
1061
+ this._deploymentBlock = isDefined4(this.params.deploymentBlock) ? this.params.deploymentBlock : await estimateBlockNumberAt(this.params.provider);
886
1062
  }
887
1063
  minWithdrawalBlocks() {
888
1064
  return 0;
@@ -983,14 +1159,14 @@ var EvmStakeViewer = class extends AbstractEvmViewer {
983
1159
  });
984
1160
  });
985
1161
  await this.contract.on(this.contract.getEvent("StakeRemoved"), (staked, staker, id, amount, event) => {
986
- const stake = assertEx8(this._stakeCache.get(Number(id)));
1162
+ const stake = assertEx9(this._stakeCache.get(Number(id)));
987
1163
  this._stakeCache.set(Number(id), {
988
1164
  ...stake,
989
1165
  removeBlock: event.blockNumber
990
1166
  });
991
1167
  });
992
1168
  await this.contract.on(this.contract.getEvent("StakeWithdrawn"), (staked, staker, id, amount, event) => {
993
- const stake = assertEx8(this._stakeCache.get(Number(id)));
1169
+ const stake = assertEx9(this._stakeCache.get(Number(id)));
994
1170
  this._stakeCache.set(Number(id), {
995
1171
  ...stake,
996
1172
  withdrawBlock: event.blockNumber
@@ -1023,7 +1199,7 @@ var EvmStakeViewer = class extends AbstractEvmViewer {
1023
1199
  return;
1024
1200
  }
1025
1201
  if (event.name === "StakeRemoved") {
1026
- const stake = assertEx8(this._stakeCache.get(event.args.id));
1202
+ const stake = assertEx9(this._stakeCache.get(event.args.id));
1027
1203
  this._stakeCache.set(event.args.id, {
1028
1204
  ...stake,
1029
1205
  removeBlock: event.time
@@ -1031,7 +1207,7 @@ var EvmStakeViewer = class extends AbstractEvmViewer {
1031
1207
  return;
1032
1208
  }
1033
1209
  if (event.name === "StakeWithdrawn") {
1034
- const stake = assertEx8(this._stakeCache.get(event.args.id));
1210
+ const stake = assertEx9(this._stakeCache.get(event.args.id));
1035
1211
  this._stakeCache.set(event.args.id, {
1036
1212
  ...stake,
1037
1213
  withdrawBlock: event.time
@@ -1062,6 +1238,11 @@ export {
1062
1238
  AbstractEvmRunner,
1063
1239
  AbstractEvmViewer,
1064
1240
  BaseEthProvider,
1241
+ DEFAULT_ESTIMATE_BLOCK_DATE,
1242
+ DEFAULT_EVENT_QUERY_BLOCK_CHUNK_SIZE,
1243
+ DEFAULT_EVENT_QUERY_INITIAL_DELAY_MS,
1244
+ DEFAULT_EVENT_QUERY_MAX_ATTEMPTS,
1245
+ DEFAULT_EVM_BLOCK_TIME_SEC,
1065
1246
  EthHashRegEx,
1066
1247
  EthHashZod,
1067
1248
  EvmChainContractViewer,
@@ -1082,11 +1263,14 @@ export {
1082
1263
  asEvmStakeEvent,
1083
1264
  asEvmStakeRemovedEvent,
1084
1265
  asEvmStakeWithdrawnEvent,
1266
+ blockRangeChunks,
1085
1267
  calculateEthBlockRate,
1086
1268
  calculateEthTimeRate,
1087
1269
  createChain,
1088
1270
  createChainContract,
1089
1271
  createChainRewardsContract,
1272
+ estimateBlockNumberAt,
1273
+ estimateBlockNumberFromHead,
1090
1274
  ethBlockRate,
1091
1275
  getDefaultGasConfig,
1092
1276
  getEthBlockRateBlocks,