Skip to main content

SDK Full Withdrawal Guide

Step 1: Begin withdrawal on the consensus layer

For a validator to exit, it is mandatory to first broadcast an exit message (referred to as a voluntary withdrawal) on the consensus layer. It takes approximately 2 epochs to finalize the broadcast message. Once done, the consensus layer initiates the exit and starts processing final rewards for the validator. Once the rewards are processed, the last withdrawal is of 32 ETH, unless the validator is leaking, in which case it will be less than 32 ETH. All the withdrawal ETH is sent to the withdrawal address associated with the validator. For all the Stakehouse validators, the account manager contract address is the withdrawal address.

const broadcastVoluntaryWithdrawalTx = await sdk.withdrawal.broadcastVoluntaryWithdrawal(BEACON_NODE, blsKeystore, password);
console.log("tx: ", broadcastVoluntaryWithdrawalTx);

Step 2: Get the finalized epoch report

Finalised epoch report is the report for a validator received from the consensus layer. This report is necessary for the execution layer (the smart contracts) to identify a BLS public key and its status.

let finalisedReport = await sdk.balanceReport.getFinalisedEpochReport(BEACON_NODE, blsPublicKey);
console.log("finalisedReport: ", finalisedReport);

Step 3: Authenticate the finalized epoch report

let authenticatedReport = await sdk.balanceReport.authenticateReport(BEACON_NODE, finalisedReport);
console.log("authenticatedReport: ", authenticatedReport);

Step 4: Report voluntary withdrawal

Report voluntary withdrawal to the Stakehouse contracts on the execution layer so the protocol knows the validator’s intent to exit.

await sdk.withdrawal.reportVoluntaryWithdrawal(
stakehouseAddress,
authenticateReportResult
)

Step 5: Get the validator index

const validatorIndex = finalisedReport.validatorIndex;
console.log("validatorIndex: ", validatorIndex);

Step 6: Get total ETH sent to BLS public key

Total ETH is the amount of ETH deposited for the BLS public key on the consensus layer.

const totalETHSentToBLSPublicKey = await sdk.balanceReport.getTotalETHSentToBlsKey(blsPublicKey);
console.log("totalETHSentToBLSPublicKey: ", totalETHSentToBLSPublicKey.toString());

Step 7: Get SLOT indexes

To report all the withdrawals to the contract, it is necessary to get the first and last SLOT index of the validator. Using this, the user can further query all the sweep reports and report them to the contracts to maximize ETH rewards.

const slotIndexes = await sdk.balanceReport.getStartAndEndSlotByValidatorIndex(
validatorIndex
);
const firstSlotIndex = slotIndexes[1];
const lastSlotIndex = slotIndexes[0];

Step 8: Get all the dETH sweeps

Now that the first and last SLOT index is known, use it to query all the dETH sweeps.

let sweeps = await sdk.balanceReport.getDETHSweeps(
validatorIndex,
firstSlotIndex,
lastSlotIndex
);
console.log("dETHSweeps: ", sweeps);

Step 9: Get the final sweep

The final sweep is the last withdrawal for the BLS public key which amounts to 32 ETH (if the validator was leaking or slashed, then the final withdrawal will be less than 32 ETH).

let finalSweep = sdk.balanceReport.getFinalSweep(
BEACON_NODE,
validatorIndex
);

Step 10: Formatting and filtering dETH sweeps

The dETH sweeps received from the previous step contains the final sweep. The deposit router and the contracts would reject these sweep reports if it contains the final sweep. Hence, it is necessary to filter it out.

const filteredInsideSweeps = sweeps.sweeps.filter(
(sweep: any) =>
sweep.withdrawal_index &&
Number(sweep.withdrawal_index) !== Number(finalSweep.sweep.index)
)

sweeps = { sweeps: filteredInsideSweeps }

The sweeps received previously may also have some sweeps which have already been reported. So, it is important to only include the unreported sweeps.

let unreportedSweeps = await sdk.withdrawal.filterUnreportedSweepReports(sweeps.sweeps)
sweeps = { sweeps: unreportedSweeps }

Step 11: Calculate sum of all the dETH sweeps

const sumOfSweeps = sdk.balanceReport.calculateSumOfSweeps(sweeps.sweeps);
console.log("sumOfSweeps: ", sumOfSweeps.toString());

Step 12: Verify and report sweeps

The contract needs the sweep reports to be verified. Hence, all the sweeps need to be sent to the deposit router for verification. The deposit router will generate a signature valid for 20 minutes. This signature will be attached along with the sweep reports. The verified report needs to be formatted and then sent to the contracts.

if (!sumOfSweeps.eq(ethers.BigNumber.from('0'))) {
const verifyAndReport = await sdk.withdrawal.verifyAndReportAllSweepsAtOnce(
stakeHouse,
totalETHSentToBLSPublicKey.toString(),
sweeps.sweeps,
finalisedReport,
true
)

listOfUnverifiedReports = verifyAndReport.listOfUnverifiedReports
}

Step 13: Calculate sum of unverified sweeps

The `verifyAndReportAllSweepsAtOnce` function returns the transaction hash of the sweeps reported to the contract as well as a list of all the sweep reports which failed to be verified so that the user can retry to verify them.

const sumOfUnVerifiedReports = sdk.balanceReport.calculateSumOfSweeps(
listOfUnverifiedReports
);

Step 14: Generate final report

The contracts require some additional validator data along with the sweeps. To get this data, `generateFinalReport` function can be used.

let finalReport = await sdk.balanceReport.generateFinalReport(
BEACON_NODE_URL,
withdrawValidatorId,
totalETHSentToBLSPublicKey,
sumOfUnVerifiedReports,
listOfUnverifiedReports,
finalSweep
)

Step 15: Formatting final report for verification

The final report needs to be verified by the deposit router similar to all sweep reports.

finalReport.blsPublicKey = sdk.utils.remove0x(finalReport.blsPublicKey)
finalReport.totalETHSentToBLSKey = totalETHSentToBLSPublicKey.toString()
finalReport.sumOfUnreportedSweeps = sumOfUnVerifiedReports.toString()

Step 16: Verify final report

The final report generated needs to be verified by the deposit router before reporting it to the contracts.

let verifyFinalReport
verifyFinalReport = await sdk.balanceReport.verifyFinalReport(
finalReport
)

Step 17: Submit final report and withdrawa

This is the last step in the withdrawal process. The final report (which is the last withdrawal) is reported to the contract and the unstaking is trigerred, both in the same transaction. This step will be different for LSD validators and solo-stakers.
a) For solo-stakers:

const reportAndUnstakeTx = await sdk.withdrawal.reportFinalSweepAndWithdraw(
finalReport.totalETHSentToBLSPublicKey,
finalReport.unreportedSweeps,
verifyFinalReport
)
console.log("reportAndUnstakeTx: ", reportAndUnstakeTx)

b) For LSD validators:

const reportAndUnstakeTx = await sdk.wizard.executeFullWithdrawalInRageQuitAssistant(
rageQuitAssistantAddress,
finalReport.totalETHSentToBLSPublicKey,
finalReport.unreportedSweeps,
verifyFinalReport
)
console.log("reportAndUnstakeTx: ", reportAndUnstakeTx)