@@ -18,6 +18,8 @@ contract StakingLens {
1818 uint256 public constant MONAD_BLOCK_REWARD = 25 ether ;
1919 uint256 public constant MONAD_BLOCKS_PER_YEAR = 78_840_000 ;
2020 uint64 public constant APY_BPS_PRECISION = 10_000 ;
21+ uint64 public constant MONAD_BOUNDARY_BLOCK_PERIOD = 50_000 ;
22+ uint64 public constant MONAD_EPOCH_SECONDS = MONAD_BOUNDARY_BLOCK_PERIOD * 2 / 5 ; // 0.4s block time
2123
2224 enum DelegationState {
2325 Active,
@@ -33,6 +35,7 @@ contract StakingLens {
3335 uint256 amount;
3436 uint256 rewards; // unclaimed rewards only for Active entries
3537 uint64 withdrawEpoch; // populated for withdrawals
38+ uint64 completionTimestamp;
3639 }
3740
3841 struct DelegatorSnapshot {
@@ -152,7 +155,8 @@ contract StakingLens {
152155 state: DelegationState.Active,
153156 amount: snap.stake,
154157 rewards: snap.rewards,
155- withdrawEpoch: 0
158+ withdrawEpoch: 0 ,
159+ completionTimestamp: 0
156160 });
157161 ++ positionCount;
158162 }
@@ -164,7 +168,8 @@ contract StakingLens {
164168 state: DelegationState.Activating,
165169 amount: snap.pendingStake,
166170 rewards: 0 ,
167- withdrawEpoch: 0
171+ withdrawEpoch: 0 ,
172+ completionTimestamp: 0
168173 });
169174 ++ positionCount;
170175 }
@@ -200,7 +205,10 @@ contract StakingLens {
200205 state: withdrawEpoch < currentEpoch ? DelegationState.AwaitingWithdrawal : DelegationState.Deactivating,
201206 amount: amount,
202207 rewards: 0 ,
203- withdrawEpoch: withdrawEpoch
208+ withdrawEpoch: withdrawEpoch,
209+ completionTimestamp: withdrawEpoch < currentEpoch
210+ ? 0
211+ : _withdrawCompletionTimestamp (withdrawEpoch, currentEpoch)
204212 });
205213
206214 ++ count;
@@ -211,6 +219,18 @@ contract StakingLens {
211219 return (count, lastWithdrawId, hasWithdrawals);
212220 }
213221
222+ function _withdrawCompletionTimestamp (uint64 withdrawEpoch , uint64 currentEpoch ) internal view returns (uint64 ) {
223+ if (withdrawEpoch < currentEpoch) {
224+ return 0 ;
225+ }
226+
227+ uint64 remainingEpochs = withdrawEpoch - currentEpoch + 1 ;
228+ uint256 completion = block .timestamp + uint256 (remainingEpochs) * uint256 (MONAD_EPOCH_SECONDS);
229+ // casting to uint64 is safe because completion timestamps are bounded by the type max guard above
230+ // forge-lint: disable-next-line(unsafe-typecast)
231+ return completion > type (uint64 ).max ? type (uint64 ).max : uint64 (completion);
232+ }
233+
214234 /**
215235 * @notice Return validator stats plus APY for a set of validator ids.
216236 * @param validatorIds If empty, uses the curated Monad validator list.
0 commit comments