@xyo-network/chain-sdk 5.0.0 → 5.0.2

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 (23) hide show
  1. package/dist/browser/modules/ethereum/indexing/RestStakeEventsViewer.d.ts +15 -0
  2. package/dist/browser/modules/ethereum/indexing/RestStakeEventsViewer.d.ts.map +1 -1
  3. package/dist/browser/modules/ethereum/indexing/RestStakeViewer.d.ts +60 -0
  4. package/dist/browser/modules/ethereum/indexing/RestStakeViewer.d.ts.map +1 -0
  5. package/dist/browser/modules/ethereum/indexing/index.d.ts +1 -0
  6. package/dist/browser/modules/ethereum/indexing/index.d.ts.map +1 -1
  7. package/dist/neutral/ethereum.mjs +229 -9
  8. package/dist/neutral/ethereum.mjs.map +4 -4
  9. package/dist/neutral/modules/ethereum/indexing/RestStakeEventsViewer.d.ts +15 -0
  10. package/dist/neutral/modules/ethereum/indexing/RestStakeEventsViewer.d.ts.map +1 -1
  11. package/dist/neutral/modules/ethereum/indexing/RestStakeViewer.d.ts +60 -0
  12. package/dist/neutral/modules/ethereum/indexing/RestStakeViewer.d.ts.map +1 -0
  13. package/dist/neutral/modules/ethereum/indexing/index.d.ts +1 -0
  14. package/dist/neutral/modules/ethereum/indexing/index.d.ts.map +1 -1
  15. package/dist/neutral/services.mjs +1 -1
  16. package/dist/neutral/services.mjs.map +1 -1
  17. package/dist/node/modules/ethereum/indexing/RestStakeEventsViewer.d.ts +15 -0
  18. package/dist/node/modules/ethereum/indexing/RestStakeEventsViewer.d.ts.map +1 -1
  19. package/dist/node/modules/ethereum/indexing/RestStakeViewer.d.ts +60 -0
  20. package/dist/node/modules/ethereum/indexing/RestStakeViewer.d.ts.map +1 -0
  21. package/dist/node/modules/ethereum/indexing/index.d.ts +1 -0
  22. package/dist/node/modules/ethereum/indexing/index.d.ts.map +1 -1
  23. package/package.json +12 -11
@@ -1,6 +1,11 @@
1
1
  import type { EthAddress } from '@ariestools/sdk';
2
2
  import type { StakeEvent, StakeEventFilter, StakeEventName, StakeEventsViewer } from '@xyo-network/xl1-sdk';
3
3
  import { AbstractCreatableProvider } from '@xyo-network/xl1-sdk';
4
+ import { z } from 'zod';
5
+ /** Provider-specific settings read from `providerBindings.StakeEventsViewer.options`. */
6
+ export declare const RestStakeEventsViewerOptionsZod: z.ZodObject<{
7
+ headPollIntervalMs: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
8
+ }, z.core.$strip>;
4
9
  /**
5
10
  * StakeEventsViewer backed by the finalized contract-event index rather than eth_getLogs.
6
11
  * Raw indexed logs remain contract-generic; this viewer applies the staking ABI at the edge.
@@ -17,19 +22,29 @@ export declare class RestStakeEventsViewer extends AbstractCreatableProvider imp
17
22
  private readonly completeFrames;
18
23
  private readonly contractInterface;
19
24
  private currentHead?;
25
+ private headPollIntervalMs;
26
+ private lastSuccessfulSyncAt?;
20
27
  private manifestPromise?;
28
+ private positionCountCheckpoints;
21
29
  private stakeEventCache;
30
+ private readonly stakeEventsByPosition;
22
31
  private syncPromise?;
23
32
  private tailFrame?;
24
33
  get address(): EthAddress;
25
34
  get eventIndexBaseUrl(): string;
26
35
  protected get fetchFn(): typeof globalThis.fetch;
36
+ /** Creates the provider instance after its configuration has resolved. */
37
+ createHandler(): Promise<void>;
27
38
  positionCount(range: [number, number | 'latest']): Promise<number>;
28
39
  stakeEvents<TName extends StakeEventName>(range?: [number, number | 'latest'], filter?: StakeEventFilter<TName>): Promise<StakeEvent<TName>[]>;
40
+ private assertRangeAvailable;
29
41
  private decodeIndexedStakeEvents;
30
42
  private fetchJson;
31
43
  private getManifest;
44
+ private indexStakeEvents;
32
45
  private matchesFilter;
46
+ private matchingStakeEvents;
47
+ private positionCountThrough;
33
48
  private stakeEventFromLog;
34
49
  private sync;
35
50
  private syncFromHead;
@@ -1 +1 @@
1
- {"version":3,"file":"RestStakeEventsViewer.d.ts","sourceRoot":"","sources":["../../../../../src/modules/ethereum/indexing/RestStakeEventsViewer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAKjD,OAAO,KAAK,EAEV,UAAU,EAAE,gBAAgB,EAAE,cAAc,EAAE,iBAAiB,EAChE,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,yBAAyB,EAG1B,MAAM,sBAAsB,CAAA;AAK7B;;;;;GAKG;AACH,qBACa,qBAAsB,SAAQ,yBAA0B,YAAW,iBAAiB;IAC/F,MAAM,CAAC,QAAQ,CAAC,eAAe,oBAAoB;IACnD,MAAM,CAAC,QAAQ,CAAC,cAAc,sBAA2B;IACzD,MAAM,CAAC,QAAQ,CAAC,YAAY,UAAK;IACjC,MAAM,CAAC,QAAQ,CAAC,QAAQ,wBAA6B;IACrD,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAG,MAAM,CAAS;IACzC,OAAO,sBAAuC;IAE9C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAwC;IACvE,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAoD;IACtF,OAAO,CAAC,WAAW,CAAC,CAAmB;IACvC,OAAO,CAAC,eAAe,CAAC,CAAgC;IACxD,OAAO,CAAC,eAAe,CAAmB;IAC1C,OAAO,CAAC,WAAW,CAAC,CAAe;IACnC,OAAO,CAAC,SAAS,CAAC,CAAoB;IAEtC,IAAI,OAAO,IAAI,UAAU,CAExB;IAED,IAAI,iBAAiB,IAAI,MAAM,CAO9B;IAED,SAAS,KAAK,OAAO,IAAI,OAAO,UAAU,CAAC,KAAK,CAE/C;IAEK,aAAa,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAKlE,WAAW,CAAC,KAAK,SAAS,cAAc,EAC5C,KAAK,GAAE,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAiB,EAClD,MAAM,CAAC,EAAE,gBAAgB,CAAC,KAAK,CAAC,GAC/B,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;IAK/B,OAAO,CAAC,wBAAwB;YAalB,SAAS;YAMT,WAAW;IAUzB,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,iBAAiB;YAgBX,IAAI;YAOJ,YAAY;CAmC3B"}
1
+ {"version":3,"file":"RestStakeEventsViewer.d.ts","sourceRoot":"","sources":["../../../../../src/modules/ethereum/indexing/RestStakeEventsViewer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAKjD,OAAO,KAAK,EAEV,UAAU,EAAE,gBAAgB,EAAE,cAAc,EAAE,iBAAiB,EAChE,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,yBAAyB,EAG1B,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAMvB,yFAAyF;AACzF,eAAO,MAAM,+BAA+B;;iBAAiE,CAAA;AAO7G;;;;;GAKG;AACH,qBACa,qBAAsB,SAAQ,yBAA0B,YAAW,iBAAiB;IAC/F,MAAM,CAAC,QAAQ,CAAC,eAAe,oBAAoB;IACnD,MAAM,CAAC,QAAQ,CAAC,cAAc,sBAA2B;IACzD,MAAM,CAAC,QAAQ,CAAC,YAAY,UAAK;IACjC,MAAM,CAAC,QAAQ,CAAC,QAAQ,wBAA6B;IACrD,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAG,MAAM,CAAS;IACzC,OAAO,sBAAuC;IAE9C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAwC;IACvE,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAoD;IACtF,OAAO,CAAC,WAAW,CAAC,CAAmB;IACvC,OAAO,CAAC,kBAAkB,CAAgC;IAC1D,OAAO,CAAC,oBAAoB,CAAC,CAAQ;IACrC,OAAO,CAAC,eAAe,CAAC,CAAgC;IACxD,OAAO,CAAC,wBAAwB,CAAgC;IAChE,OAAO,CAAC,eAAe,CAAmB;IAC1C,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAkC;IACxE,OAAO,CAAC,WAAW,CAAC,CAAe;IACnC,OAAO,CAAC,SAAS,CAAC,CAAoB;IAEtC,IAAI,OAAO,IAAI,UAAU,CAExB;IAED,IAAI,iBAAiB,IAAI,MAAM,CAO9B;IAED,SAAS,KAAK,OAAO,IAAI,OAAO,UAAU,CAAC,KAAK,CAE/C;IAED,0EAA0E;IAC3D,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAUvC,aAAa,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAQlE,WAAW,CAAC,KAAK,SAAS,cAAc,EAC5C,KAAK,GAAE,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAiB,EAClD,MAAM,CAAC,EAAE,gBAAgB,CAAC,KAAK,CAAC,GAC/B,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;IAM/B,OAAO,CAAC,oBAAoB;IAQ5B,OAAO,CAAC,wBAAwB;YAalB,SAAS;YAMT,WAAW;IASzB,OAAO,CAAC,gBAAgB;IAiBxB,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,oBAAoB;IAe5B,OAAO,CAAC,iBAAiB;YAgBX,IAAI;YAcJ,YAAY;CAmC3B"}
@@ -0,0 +1,60 @@
1
+ import type { Address } from '@ariestools/sdk';
2
+ import type { Position, StakeEventsViewer, StakeViewer } from '@xyo-network/xl1-sdk';
3
+ import { AbstractCreatableProvider } from '@xyo-network/xl1-sdk';
4
+ import { z } from 'zod';
5
+ /** Provider-specific settings read from `providerBindings.StakeViewer.options`. */
6
+ export declare const RestStakeViewerOptionsZod: z.ZodObject<{
7
+ refreshIntervalMs: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
8
+ }, z.core.$strip>;
9
+ /**
10
+ * StakeViewer materialized from the finalized REST event index.
11
+ *
12
+ * The indexer remains the only process that tails the EVM contract. Consumers
13
+ * rebuild their local position view from immutable indexed events after the
14
+ * configured refresh interval, so API replicas never attach EVM log listeners.
15
+ */
16
+ export declare class RestStakeViewer extends AbstractCreatableProvider implements StakeViewer {
17
+ static readonly connectionTypes: readonly ["rest"];
18
+ static readonly defaultMoniker: "StakeViewer";
19
+ static readonly dependencies: ("StakeEventsViewer" | "ChainContractViewer")[];
20
+ static readonly monikers: "StakeViewer"[];
21
+ static readonly surface: "node";
22
+ readonly moniker: "StakeViewer";
23
+ private _chainContractViewer;
24
+ private _stakeEventsViewer;
25
+ private lastSuccessfulSyncAt?;
26
+ private positionIndex;
27
+ private positionsByStaked;
28
+ private positionsByStaker;
29
+ private refreshIntervalMs;
30
+ private syncPromise?;
31
+ /** Returns the finalized stake-event source used to materialize positions. */
32
+ get stakeEvents(): StakeEventsViewer;
33
+ /** Returns active finalized stake positions. */
34
+ activeStakes(): Promise<Position[]>;
35
+ /** Creates the provider after its REST binding and dependencies resolve. */
36
+ createHandler(): Promise<void>;
37
+ /** Returns the configured chain's withdrawal delay. */
38
+ minWithdrawalBlocks(): Promise<number>;
39
+ /** Returns finalized positions that have been removed but not withdrawn. */
40
+ removedStakes(): Promise<Position[]>;
41
+ /** Returns the configured rewards contract address. */
42
+ rewardsContract(): Promise<Address>;
43
+ /** Returns a finalized position by its contract id. */
44
+ stakeById(id: number): Promise<Position>;
45
+ /** Returns a staker's finalized position by its contract slot. */
46
+ stakeByStaker(staker: Address, slot: number): Promise<Position>;
47
+ /** Returns finalized positions added for an address in the supplied EVM block range. */
48
+ stakesByStaked(staked: Address, range?: [number, number | undefined]): Promise<Position[]>;
49
+ /** Returns finalized positions added by a staker in the supplied EVM block range. */
50
+ stakesByStaker(staker: Address, range?: [number, number | undefined]): Promise<Position[]>;
51
+ /** Returns the configured staking-token address. */
52
+ stakingTokenAddress(): Promise<Address>;
53
+ /** Returns finalized positions that have completed withdrawal. */
54
+ withdrawnStakes(): Promise<Position[]>;
55
+ private applyEvent;
56
+ private positionsInRange;
57
+ private rebuildPositionIndex;
58
+ private sync;
59
+ }
60
+ //# sourceMappingURL=RestStakeViewer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RestStakeViewer.d.ts","sourceRoot":"","sources":["../../../../../src/modules/ethereum/indexing/RestStakeViewer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAE9C,OAAO,KAAK,EACW,QAAQ,EAA0B,iBAAiB,EAAE,WAAW,EACtF,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,yBAAyB,EAC1B,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAKvB,mFAAmF;AACnF,eAAO,MAAM,yBAAyB;;iBAAgE,CAAA;AAEtG;;;;;;GAMG;AACH,qBACa,eAAgB,SAAQ,yBAA0B,YAAW,WAAW;IACnF,MAAM,CAAC,QAAQ,CAAC,eAAe,oBAAoB;IACnD,MAAM,CAAC,QAAQ,CAAC,cAAc,gBAAqB;IACnD,MAAM,CAAC,QAAQ,CAAC,YAAY,kDAAyD;IACrF,MAAM,CAAC,QAAQ,CAAC,QAAQ,kBAAuB;IAC/C,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAG,MAAM,CAAS;IACzC,QAAQ,CAAC,OAAO,gBAAiC;IAEjD,OAAO,CAAC,oBAAoB,CAAsB;IAClD,OAAO,CAAC,kBAAkB,CAAoB;IAC9C,OAAO,CAAC,oBAAoB,CAAC,CAAQ;IACrC,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,iBAAiB,CAAiC;IAC1D,OAAO,CAAC,iBAAiB,CAAiC;IAC1D,OAAO,CAAC,iBAAiB,CAA8B;IACvD,OAAO,CAAC,WAAW,CAAC,CAAe;IAEnC,8EAA8E;IAC9E,IAAI,WAAW,IAAI,iBAAiB,CAEnC;IAED,gDAAgD;IAC1C,YAAY,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;IAKzC,4EAA4E;IAC7D,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAY7C,uDAAuD;IACjD,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC;IAI5C,4EAA4E;IACtE,aAAa,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;IAK1C,uDAAuD;IACjD,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC;IAIzC,uDAAuD;IACjD,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAK9C,kEAAkE;IAC5D,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAMrE,wFAAwF;IAClF,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,GAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAkB,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAKhH,qFAAqF;IAC/E,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,GAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAkB,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAKhH,oDAAoD;IAC9C,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC;IAI7C,kEAAkE;IAC5D,eAAe,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;IAK5C,OAAO,CAAC,UAAU;IAoBlB,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,oBAAoB;YAoBd,IAAI;CAcnB"}
@@ -1,2 +1,3 @@
1
1
  export * from './RestStakeEventsViewer.js';
2
+ export * from './RestStakeViewer.js';
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/modules/ethereum/indexing/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/modules/ethereum/indexing/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAA;AAC1C,cAAc,sBAAsB,CAAA"}
@@ -463,14 +463,22 @@ import {
463
463
  StakeEventsViewerMoniker
464
464
  } from "@xyo-network/xl1-sdk";
465
465
  import { Interface } from "ethers";
466
+ import { z } from "zod";
466
467
  var STAKE_EVENT_NAMES = /* @__PURE__ */ new Set(["StakeAdded", "StakeRemoved", "StakeWithdrawn"]);
468
+ var DEFAULT_HEAD_POLL_INTERVAL_MS = 5e3;
469
+ var MIN_HEAD_POLL_INTERVAL_MS = 5e3;
470
+ var RestStakeEventsViewerOptionsZod = z.object({ headPollIntervalMs: z.coerce.number().optional() });
467
471
  var RestStakeEventsViewer = class extends AbstractCreatableProvider {
468
472
  moniker = RestStakeEventsViewer.defaultMoniker;
469
473
  completeFrames = /* @__PURE__ */ new Map();
470
474
  contractInterface = new Interface(IAddressStakingEvents__factory.abi);
471
475
  currentHead;
476
+ headPollIntervalMs = DEFAULT_HEAD_POLL_INTERVAL_MS;
477
+ lastSuccessfulSyncAt;
472
478
  manifestPromise;
479
+ positionCountCheckpoints = [];
473
480
  stakeEventCache = [];
481
+ stakeEventsByPosition = /* @__PURE__ */ new Map();
474
482
  syncPromise;
475
483
  tailFrame;
476
484
  get address() {
@@ -487,13 +495,34 @@ var RestStakeEventsViewer = class extends AbstractCreatableProvider {
487
495
  get fetchFn() {
488
496
  return this.context.fetchFn ?? fetch.bind(globalThis);
489
497
  }
498
+ /** Creates the provider instance after its configuration has resolved. */
499
+ async createHandler() {
500
+ await super.createHandler();
501
+ RestStakeEventsViewer.requireBoundConnection(this.config);
502
+ const { headPollIntervalMs } = RestStakeEventsViewerOptionsZod.parse(this.config.options);
503
+ if (headPollIntervalMs !== void 0) {
504
+ assertEx5(headPollIntervalMs >= MIN_HEAD_POLL_INTERVAL_MS, () => `headPollIntervalMs must be at least ${MIN_HEAD_POLL_INTERVAL_MS}ms`);
505
+ }
506
+ this.headPollIntervalMs = headPollIntervalMs ?? DEFAULT_HEAD_POLL_INTERVAL_MS;
507
+ }
490
508
  async positionCount(range) {
491
- const events = await this.stakeEvents(range);
492
- return Math.max(-1, ...events.map((event) => event.args.id)) + 1;
509
+ await this.sync();
510
+ this.assertRangeAvailable(range);
511
+ if (range[0] === 0) return this.positionCountThrough(range[1]);
512
+ const events = this.matchingStakeEvents(range);
513
+ return events.reduce((max, event) => Math.max(max, event.args.id), -1) + 1;
493
514
  }
494
515
  async stakeEvents(range = [0, "latest"], filter) {
495
516
  await this.sync();
496
- return this.stakeEventCache.filter((event) => this.matchesFilter(event, range, filter));
517
+ this.assertRangeAvailable(range);
518
+ return this.matchingStakeEvents(range, filter);
519
+ }
520
+ assertRangeAvailable(range) {
521
+ const head = assertEx5(this.currentHead, () => "EVM event index head is unavailable");
522
+ assertEx5(
523
+ range[1] === "latest" || range[1] <= head.block,
524
+ () => `Stake-event range ends at ${range[1]}, beyond EVM event index head ${head.block}`
525
+ );
497
526
  }
498
527
  decodeIndexedStakeEvents() {
499
528
  const frames = [...this.completeFrames.entries()].sort(([a], [b]) => a - b).map(([, frame]) => frame);
@@ -516,6 +545,21 @@ var RestStakeEventsViewer = class extends AbstractCreatableProvider {
516
545
  })();
517
546
  return await this.manifestPromise;
518
547
  }
548
+ indexStakeEvents(events) {
549
+ this.stakeEventCache = events;
550
+ this.positionCountCheckpoints = [];
551
+ this.stakeEventsByPosition.clear();
552
+ let maxPositionId = -1;
553
+ for (const event of events) {
554
+ const positionEvents = this.stakeEventsByPosition.get(event.args.id) ?? [];
555
+ positionEvents.push(event);
556
+ this.stakeEventsByPosition.set(event.args.id, positionEvents);
557
+ if (event.args.id > maxPositionId) {
558
+ maxPositionId = event.args.id;
559
+ this.positionCountCheckpoints.push({ count: maxPositionId + 1, time: event.time });
560
+ }
561
+ }
562
+ }
519
563
  // eslint-disable-next-line complexity
520
564
  matchesFilter(event, range, filter) {
521
565
  if (range[1] !== "latest" && event.time > range[1]) return false;
@@ -526,6 +570,25 @@ var RestStakeEventsViewer = class extends AbstractCreatableProvider {
526
570
  if (isDefined3(filter?.args?.staker) && event.args.staker !== filter.args.staker) return false;
527
571
  return true;
528
572
  }
573
+ matchingStakeEvents(range, filter) {
574
+ const positionId = filter?.args?.id;
575
+ const candidates = isDefined3(positionId) ? this.stakeEventsByPosition.get(positionId) ?? [] : this.stakeEventCache;
576
+ return candidates.filter((event) => this.matchesFilter(event, range, filter));
577
+ }
578
+ positionCountThrough(end) {
579
+ if (end === "latest") return this.positionCountCheckpoints.at(-1)?.count ?? 0;
580
+ let low = 0;
581
+ let high = this.positionCountCheckpoints.length;
582
+ while (low < high) {
583
+ const middle = Math.floor((low + high) / 2);
584
+ if (this.positionCountCheckpoints[middle].time <= end) {
585
+ low = middle + 1;
586
+ } else {
587
+ high = middle;
588
+ }
589
+ }
590
+ return this.positionCountCheckpoints[low - 1]?.count ?? 0;
591
+ }
529
592
  stakeEventFromLog(log) {
530
593
  const parsed = this.contractInterface.parseLog({ data: log.data, topics: log.topics });
531
594
  if (parsed === null || !STAKE_EVENT_NAMES.has(parsed.name)) return;
@@ -542,7 +605,12 @@ var RestStakeEventsViewer = class extends AbstractCreatableProvider {
542
605
  };
543
606
  }
544
607
  async sync() {
545
- this.syncPromise ??= this.syncFromHead().finally(() => {
608
+ const now = Date.now();
609
+ const elapsed = this.lastSuccessfulSyncAt === void 0 ? void 0 : now - this.lastSuccessfulSyncAt;
610
+ if (this.currentHead !== void 0 && elapsed !== void 0 && elapsed >= 0 && elapsed < this.headPollIntervalMs) return;
611
+ this.syncPromise ??= this.syncFromHead().then(() => {
612
+ this.lastSuccessfulSyncAt = Date.now();
613
+ }).finally(() => {
546
614
  this.syncPromise = void 0;
547
615
  });
548
616
  await this.syncPromise;
@@ -573,7 +641,7 @@ var RestStakeEventsViewer = class extends AbstractCreatableProvider {
573
641
  assertEx5(this.tailFrame.fromBlock === tailStart, () => "EVM event index tail starts at the wrong block");
574
642
  assertEx5(this.tailFrame.toBlock === head.block, () => "EVM event index tail is stale relative to its head");
575
643
  }
576
- this.stakeEventCache = this.decodeIndexedStakeEvents();
644
+ this.indexStakeEvents(this.decodeIndexedStakeEvents());
577
645
  this.currentHead = head;
578
646
  }
579
647
  };
@@ -586,8 +654,157 @@ RestStakeEventsViewer = __decorateClass([
586
654
  creatableProvider()
587
655
  ], RestStakeEventsViewer);
588
656
 
657
+ // src/modules/ethereum/indexing/RestStakeViewer.ts
658
+ import { assertEx as assertEx6, toAddress as toAddress3 } from "@ariestools/sdk";
659
+ import {
660
+ AbstractCreatableProvider as AbstractCreatableProvider2,
661
+ ChainContractViewerMoniker,
662
+ creatableProvider as creatableProvider2,
663
+ StakeEventsViewerMoniker as StakeEventsViewerMoniker2,
664
+ StakeViewerMoniker
665
+ } from "@xyo-network/xl1-sdk";
666
+ import { z as z2 } from "zod";
667
+ var DEFAULT_REFRESH_INTERVAL_MS = 5e3;
668
+ var MIN_REFRESH_INTERVAL_MS = 5e3;
669
+ var RestStakeViewerOptionsZod = z2.object({ refreshIntervalMs: z2.coerce.number().optional() });
670
+ var RestStakeViewer = class extends AbstractCreatableProvider2 {
671
+ moniker = RestStakeViewer.defaultMoniker;
672
+ _chainContractViewer;
673
+ _stakeEventsViewer;
674
+ lastSuccessfulSyncAt;
675
+ positionIndex = /* @__PURE__ */ new Map();
676
+ positionsByStaked = /* @__PURE__ */ new Map();
677
+ positionsByStaker = /* @__PURE__ */ new Map();
678
+ refreshIntervalMs = DEFAULT_REFRESH_INTERVAL_MS;
679
+ syncPromise;
680
+ /** Returns the finalized stake-event source used to materialize positions. */
681
+ get stakeEvents() {
682
+ return this._stakeEventsViewer;
683
+ }
684
+ /** Returns active finalized stake positions. */
685
+ async activeStakes() {
686
+ await this.sync();
687
+ return this.positionIndex.values().filter((position) => position.removeBlock === 0 && position.withdrawBlock === 0).toArray();
688
+ }
689
+ /** Creates the provider after its REST binding and dependencies resolve. */
690
+ async createHandler() {
691
+ await super.createHandler();
692
+ RestStakeViewer.requireBoundConnection(this.config);
693
+ const { refreshIntervalMs } = RestStakeViewerOptionsZod.parse(this.config.options);
694
+ if (refreshIntervalMs !== void 0) {
695
+ assertEx6(refreshIntervalMs >= MIN_REFRESH_INTERVAL_MS, () => `refreshIntervalMs must be at least ${MIN_REFRESH_INTERVAL_MS}ms`);
696
+ }
697
+ this.refreshIntervalMs = refreshIntervalMs ?? DEFAULT_REFRESH_INTERVAL_MS;
698
+ this._chainContractViewer = await this.locator.getInstance(ChainContractViewerMoniker);
699
+ this._stakeEventsViewer = await this.locator.getInstance(StakeEventsViewerMoniker2);
700
+ }
701
+ /** Returns the configured chain's withdrawal delay. */
702
+ async minWithdrawalBlocks() {
703
+ return await this._chainContractViewer.minWithdrawalBlocks();
704
+ }
705
+ /** Returns finalized positions that have been removed but not withdrawn. */
706
+ async removedStakes() {
707
+ await this.sync();
708
+ return this.positionIndex.values().filter((position) => position.removeBlock !== 0 && position.withdrawBlock === 0).toArray();
709
+ }
710
+ /** Returns the configured rewards contract address. */
711
+ async rewardsContract() {
712
+ return toAddress3(await this._chainContractViewer.rewardsContract());
713
+ }
714
+ /** Returns a finalized position by its contract id. */
715
+ async stakeById(id) {
716
+ await this.sync();
717
+ return assertEx6(this.positionIndex.get(id), () => `RestStakeViewer: position ${id} not found in finalized event index`);
718
+ }
719
+ /** Returns a staker's finalized position by its contract slot. */
720
+ async stakeByStaker(staker, slot) {
721
+ await this.sync();
722
+ const position = this.positionsByStaker.get(toAddress3(staker))?.at(slot);
723
+ return assertEx6(position, () => `RestStakeViewer: staker ${staker} has no finalized position at slot ${slot}`);
724
+ }
725
+ /** Returns finalized positions added for an address in the supplied EVM block range. */
726
+ async stakesByStaked(staked, range = [0, void 0]) {
727
+ await this.sync();
728
+ return this.positionsInRange(this.positionsByStaked.get(toAddress3(staked)) ?? [], range);
729
+ }
730
+ /** Returns finalized positions added by a staker in the supplied EVM block range. */
731
+ async stakesByStaker(staker, range = [0, void 0]) {
732
+ await this.sync();
733
+ return this.positionsInRange(this.positionsByStaker.get(toAddress3(staker)) ?? [], range);
734
+ }
735
+ /** Returns the configured staking-token address. */
736
+ async stakingTokenAddress() {
737
+ return toAddress3(await this._chainContractViewer.stakingTokenAddress());
738
+ }
739
+ /** Returns finalized positions that have completed withdrawal. */
740
+ async withdrawnStakes() {
741
+ await this.sync();
742
+ return this.positionIndex.values().filter((position) => position.removeBlock !== 0 && position.withdrawBlock !== 0).toArray();
743
+ }
744
+ applyEvent(index, event) {
745
+ if (event.name === "StakeAdded") {
746
+ index.set(event.args.id, {
747
+ id: event.args.id,
748
+ staked: toAddress3(event.args.staked),
749
+ staker: toAddress3(event.args.staker),
750
+ amount: event.args.amount,
751
+ addBlock: event.time,
752
+ removeBlock: 0,
753
+ withdrawBlock: 0
754
+ });
755
+ return;
756
+ }
757
+ const position = assertEx6(index.get(event.args.id), () => `RestStakeViewer: ${event.name} precedes StakeAdded for position ${event.args.id}`);
758
+ index.set(event.args.id, {
759
+ ...position,
760
+ ...event.name === "StakeRemoved" ? { removeBlock: event.time } : { withdrawBlock: event.time }
761
+ });
762
+ }
763
+ positionsInRange(positions, range) {
764
+ const [fromBlock, configuredToBlock] = range;
765
+ const toBlock = configuredToBlock ?? Number.MAX_SAFE_INTEGER;
766
+ return positions.filter((position) => position.addBlock >= fromBlock && position.addBlock <= toBlock);
767
+ }
768
+ rebuildPositionIndex(events) {
769
+ const positionIndex = /* @__PURE__ */ new Map();
770
+ for (const event of events.toSorted((left, right) => left.time - right.time)) {
771
+ this.applyEvent(positionIndex, event);
772
+ }
773
+ const positionsByStaked = /* @__PURE__ */ new Map();
774
+ const positionsByStaker = /* @__PURE__ */ new Map();
775
+ for (const position of positionIndex.values().toArray().toSorted((left, right) => left.id - right.id)) {
776
+ const staked = toAddress3(position.staked);
777
+ const staker = toAddress3(position.staker);
778
+ positionsByStaked.set(staked, [...positionsByStaked.get(staked) ?? [], position]);
779
+ positionsByStaker.set(staker, [...positionsByStaker.get(staker) ?? [], position]);
780
+ }
781
+ this.positionIndex = positionIndex;
782
+ this.positionsByStaked = positionsByStaked;
783
+ this.positionsByStaker = positionsByStaker;
784
+ }
785
+ async sync() {
786
+ const now = Date.now();
787
+ const elapsed = this.lastSuccessfulSyncAt === void 0 ? void 0 : now - this.lastSuccessfulSyncAt;
788
+ if (elapsed !== void 0 && elapsed >= 0 && elapsed < this.refreshIntervalMs) return;
789
+ this.syncPromise ??= Promise.resolve(this.stakeEvents.stakeEvents([0, "latest"])).then((events) => this.rebuildPositionIndex(events)).then(() => {
790
+ this.lastSuccessfulSyncAt = Date.now();
791
+ }).finally(() => {
792
+ this.syncPromise = void 0;
793
+ });
794
+ await this.syncPromise;
795
+ }
796
+ };
797
+ __publicField(RestStakeViewer, "connectionTypes", ["rest"]);
798
+ __publicField(RestStakeViewer, "defaultMoniker", StakeViewerMoniker);
799
+ __publicField(RestStakeViewer, "dependencies", [ChainContractViewerMoniker, StakeEventsViewerMoniker2]);
800
+ __publicField(RestStakeViewer, "monikers", [StakeViewerMoniker]);
801
+ __publicField(RestStakeViewer, "surface", "node");
802
+ RestStakeViewer = __decorateClass([
803
+ creatableProvider2()
804
+ ], RestStakeViewer);
805
+
589
806
  // src/modules/ethereum/providers/AbstractEvmRunner.ts
590
- import { assertEx as assertEx6 } from "@ariestools/sdk";
807
+ import { assertEx as assertEx7 } from "@ariestools/sdk";
591
808
  import { AbstractEvmProvider } from "@xyo-network/xl1-sdk";
592
809
  function evmContractRunnerHandleKey(moniker) {
593
810
  return `evm-contract-runner:${moniker}`;
@@ -595,7 +812,7 @@ function evmContractRunnerHandleKey(moniker) {
595
812
  var AbstractEvmRunner = class extends AbstractEvmProvider {
596
813
  _runner;
597
814
  get runner() {
598
- return assertEx6(this._runner, () => "EVM contract runner not initialized");
815
+ return assertEx7(this._runner, () => "EVM contract runner not initialized");
599
816
  }
600
817
  /** Creates the provider instance after its dependencies and configuration have resolved. */
601
818
  async createHandler() {
@@ -611,7 +828,7 @@ var AbstractEvmRunner = class extends AbstractEvmProvider {
611
828
  import { toEthAddress as toEthAddress5 } from "@ariestools/sdk";
612
829
  import { IStakedXyoChain__factory } from "@xyo-network/typechain";
613
830
  import {
614
- creatableProvider as creatableProvider2,
831
+ creatableProvider as creatableProvider3,
615
832
  StakeRunnerMoniker
616
833
  } from "@xyo-network/xl1-sdk";
617
834
  import { getAddress } from "ethers/address";
@@ -642,13 +859,16 @@ __publicField(EvmStakeRunner, "dependencies", []);
642
859
  __publicField(EvmStakeRunner, "monikers", [StakeRunnerMoniker]);
643
860
  __publicField(EvmStakeRunner, "surface", "node");
644
861
  EvmStakeRunner = __decorateClass([
645
- creatableProvider2()
862
+ creatableProvider3()
646
863
  ], EvmStakeRunner);
647
864
  export {
648
865
  AbstractEvmRunner,
649
866
  BaseEthProvider,
650
867
  EvmStakeRunner,
651
868
  RestStakeEventsViewer,
869
+ RestStakeEventsViewerOptionsZod,
870
+ RestStakeViewer,
871
+ RestStakeViewerOptionsZod,
652
872
  blockRangeChunks,
653
873
  calculateEthBlockRate,
654
874
  calculateEthTimeRate,