This guide details the method used in the Withdrawals API service to estimate the time from when a withdrawal request is placed in the Lido protocol until it is finalized and becomes claimable.
By demystifying this process, the document is aimed to provide both users and developers with a clear understanding of the steps involved.
The estimation of the withdrawal waiting time begins by identifying the applicable base case, based on the following conditions:
The algorithm estimates withdrawal waiting times by evaluating the current state and projecting future events.
- Check if the
BUNKER
mode is active. - Determine if the
buffer
orvaultsBalance
holds enough ether to cover withdrawal request (totalBuffer
is enough). - Choose the soonest possible case for covering the withdrawal request, which could be:
rewardsOnly
: incorporating expected rewards from consensus layer (CL) and execution layer (EL).validatorBalances
: leveraging balances from validators scheduled for withdrawalexitValidators
: considering additional validator exits necessary to accumulate sufficient funds.
Regardless of the base case detected, every possible withdrawal request finalization still aligns with the AccountingOracle report, occurring daily (with only rare exceptions), gathered for the exact reference slot (e.g., 12:00:04 UTC every day for Mainnet) happening with a submission delay about ~30 minutes (see the example tx for AccountingOracle.submitReportData(...)
).
Therefore, the withdrawal time estimation is affected by the quantization of the time scale based on the projected oracle report timestamps.
The section provides an overview of the AccountingOracle
key timings and margins.
Since the withdrawal finalization happens together with the AccountingOracle report, the first step needed is to calculate the nearest (next) report reference epoch.
- calculation of epoch of the next frame of the
AccountingOracle
report:epochOfNextReport = initialEpoch + nextFrame * epochsPerFrame
initialEpoch
andepochsPerFrame
are taken from theHashConsensus
contract instance deployed forAccountingOracle
via the getFrameConfig method, the frame length is 1 day on Mainnet.
- Withdrawal requests which were placed too close to the upcoming report will be postponed further to the closest suitable report following the upcoming one
- The margin (i.e., finalization safe border) is defined inside the
OracleReportSanityChecker
contract and can be retrieved aslimits.requestTimestampMargin
from getOracleReportLimits, the default value on Mainnet is 2 hours if the protocol is in theTURBO
(i.e., notBUNKER
) mode, see the following thread.
- It usually takes 20-30 minutes to prepare the oracle report (mostly to wait when the reference epoch is finalized).
- However, in rare cases it's possible that the report won’t happen in the current reporting frame, though it's unlikely and happened only a few times since the moment of the Lido protocol launch.
The BUNKER
mode is checked via the WithdrawalQueueERC721
contract using the utility view method here.
In the
BUNKER
mode, a fixed finalization time of 14 frames is assumed unless affected by slashing events, reflecting a more conservative approach to claimable ether accessibility.
More about the BUNKER
mode is here.
If there is enough ether from the following sources to finalize all unfinalized requests placed before and included the provided one:
- buffer ether balance retrieved from the Lido.getBufferedEther() method
- ether balance of WithdrawalVault
- ether balance of ExecutionLayerRewardsVault
i.e, totalBuffer = Lido.getBufferedEther() + balanceOf(WithdrawalVault) + balanceOf(ELRewardsVault)
Then the finalization is possible relying on the already existing totalBuffer
ether amount in the nearest oracle report (including the safe board and submission delay into consideration).
If there is not enough ether to fulfill the withdrawal request (unfinalized > totalBuffer
), the next case is to consider projected rewards for the future report. In this case, the projected rewards amount can be approximately derived from the previous report.
The following formula is utilized to get epoch
projectedRewardsPotentialEpoch = (unfinalized - totalBuffer) / rewardsPerEpoch
where unfinalized
is the amount of the withdrawal request considered summed with all of the unfinalized requests placed before.
--- we can consider this number as approximate future rewards
If there is not enough ether to fulfill the withdrawal request (unfinalized > totalBuffer
), the previous case might be appended with the known validators are to be withdrawn (when the withdrawable_epoch
is assigned).
It's needed to select the Lido-participating validators which are already in process of withdrawal and group them by calculated frame of expected withdrawal to frameBalances
, allowing to find the oracle report frame containing enough funds from:
- buffer (
totalBuffer
) - projectedRewards (
rewardsPerEpoch * epochsTillTheFrame
) - frameBalances (
object { [frame]: [sum of balances of validators with calculated withdrawal frame] }
)
- Withdrawals sweep cursor goes from 0 to the last validator index in infinite loop.
- When the cursor reaches a withdrawable validator, it withdraws ETH from that validator.
- The cursor can withdraw from a maximum of 16 validators per slot.
- We assume that all validators in network have to something to withdraw (partially or fully)
percentOfActiveValidators
is used to exclude inactive validators from the queue, ensuring more accurate calculations.- Formula to get number of slots to wait is
(number of validators to withdraw before cursor get index of validator) / 16
- By knowing number slots we can calculate frame of withdrawal
- The idea is to find the nearest epoch pretending that all needed validators were exited
- Find epoch by this formula:
unfinalizedStETH / (32ETH * churnLimit + rewardsPerEpoch)
rewardsPerEpoch
is calculated as described in the provided prediction model- Worth noting that the exited validators become withdrawable only after the withdrawal [sweep](#sweeping mean) approached them.
For simplicity, it's suggested to use the average time of the withdrawal sweep (sweepingMean
) as a constant timeframe extension when estimating the withdrawal waiting time.
More information can be found here in Full Withdrawal Process chapter*.*
it is takes 256 epoch (27.3 hours) at minimum on Mainnet, and the current avg value is 567 epochs (the values depend on the total validators number).
This guide aims to offer a comprehensive understanding of the factors influencing the estimation of withdrawal request finalization times within the Lido protocol. For further details and updates, refer to the links provided in the References section.