Skip to main content

StakingVault

Isolated staking position with 0x02 withdrawal credentials. Holds validator funds and supports minting stETH through VaultHub while preserving non-custodial ownership. Individual vaults are deployed as beacon proxies by VaultFactory.

What is StakingVault?​

A StakingVault is the core stVault primitive:

  • holds ETH and validator balances tied to its withdrawal credentials
  • exposes owner and node operator controls
  • supports PDG-driven and direct deposits
  • integrates with VaultHub for minting, burn, and health constraints

Vaults are deployed via VaultFactory as beacon proxies. The deployed contract above is the implementation used by the beacon.

How it works​

  • The vault owner controls funding, withdrawals, exit requests, and administrative functions.
  • The node operator can force-eject validators via EIP-7002 (does not require owner cooperation).
  • The depositor role performs beacon chain deposits and staging operations.
  • When connected to VaultHub, deposits and withdrawals are gated by collateral rules.

The vault is a PinnedBeaconProxy instance, meaning it can be ossified (pinned) to prevent future upgrades once disconnected from VaultHub.

Constants​

ConstantValueDescription
_VERSION1Contract version on implementation
WC_0X02_PREFIX0x02 << 248Withdrawal credentials type prefix
PUBLIC_KEY_LENGTH48Length of validator public key in bytes

Immutable variables​

VariableDescription
DEPOSIT_CONTRACTAddress of the BeaconChainDepositContract

Storage​

struct Storage {
address nodeOperator; // Node operator address (slot 1)
address depositor; // Depositor address (slot 2)
bool beaconChainDepositsPaused; // Whether deposits are paused (slot 2)
uint256 stagedBalance; // ETH staged for activation (slot 3)
}

Staged balance​

Staged balance is ETH reserved for validator activations. This mechanism supports the Predeposit Guarantee (PDG) flow:

  1. Staging: When preparing validators via PDG, the depositor calls stage() to reserve 31 ETH per validator (the remaining 1 ETH comes from the predeposit).
  2. Staged funds are locked: Staged ETH cannot be withdrawn via withdraw() - only availableBalance() (unstaged funds) can be withdrawn.
  3. Activation: When a validator is activated via depositFromStaged(), the staged ETH is consumed for the beacon chain deposit.
  4. Unstaging: If a predeposit fails or is cancelled, unstage() releases the ETH back to available balance.

The invariant is: every 1 ETH predeposit in PDG must be paired with 31 ETH staged in the vault for the full 32 ETH validator activation.

Total vault balance = availableBalance() + stagedBalance()
availableBalance() = address(this).balance - stagedBalance

Structs​

Deposit​

Validator deposit data for beacon chain deposits:

struct Deposit {
bytes pubkey; // Validator public key (48 bytes)
bytes signature; // BLS signature (96 bytes)
uint256 amount; // Deposit amount in wei
bytes32 depositDataRoot; // Deposit data root for verification
}

View methods​

getInitializedVersion()​

function getInitializedVersion() external view returns (uint64)

Returns the highest version that has been initialized.

version()​

function version() external pure returns (uint64)

Returns contract version constant (1).

owner()​

function owner() public view returns (address)

Returns vault owner.

pendingOwner()​

function pendingOwner() public view returns (address)

Returns pending owner for 2-step ownership transfer.

nodeOperator()​

function nodeOperator() public view returns (address)

Returns node operator address.

depositor()​

function depositor() public view returns (address)

Returns depositor address.

withdrawalCredentials()​

function withdrawalCredentials() public view returns (bytes32)

Returns vault withdrawal credentials (0x02 type). Computed as 0x02 << 248 | address(this).

calculateValidatorWithdrawalFee(uint256 _numberOfKeys)​

function calculateValidatorWithdrawalFee(uint256 _numberOfKeys) external view returns (uint256)

Returns fee for validator withdrawals via EIP-7002. Note: fee may change block to block.

availableBalance()​

function availableBalance() public view returns (uint256)

Returns ETH currently available for withdrawal (total balance minus staged balance).

stagedBalance()​

function stagedBalance() external view returns (uint256)

Returns ETH staged for validator activation.

beaconChainDepositsPaused()​

function beaconChainDepositsPaused() external view returns (bool)

Returns whether beacon chain deposits are paused.

Methods​

initialize(address _owner, address _nodeOperator, address _depositor)​

function initialize(address _owner, address _nodeOperator, address _depositor) external initializer

Initializes the vault with owner, node operator, and depositor roles.

fund()​

function fund() external payable onlyOwner

Adds ETH to the vault. Reverts if msg.value is zero.

withdraw(address _recipient, uint256 _ether)​

function withdraw(address _recipient, uint256 _ether) external onlyOwner

Withdraws ETH to a recipient. Only withdraws from availableBalance() (staged funds are protected).

pauseBeaconChainDeposits()​

function pauseBeaconChainDeposits() external onlyOwner

Pauses beacon chain deposits. Reverts if already paused.

resumeBeaconChainDeposits()​

function resumeBeaconChainDeposits() external onlyOwner

Resumes beacon chain deposits. Reverts if already resumed.

depositToBeaconChain(Deposit _deposit)​

function depositToBeaconChain(Deposit calldata _deposit) external onlyDepositor whenDepositsNotPaused

Deposits validator data directly to beacon chain from available balance.

stage(uint256 _ether)​

function stage(uint256 _ether) external onlyDepositor whenDepositsNotPaused

Stages ETH for validator activation. Moves funds from available to staged balance.

unstage(uint256 _ether)​

function unstage(uint256 _ether) public onlyDepositor

Unstages ETH to make it withdrawable again. Not affected by deposit pause.

depositFromStaged(Deposit _deposit, uint256 _additionalAmount)​

function depositFromStaged(Deposit calldata _deposit, uint256 _additionalAmount) external onlyDepositor

Deposits using staged balance plus optional top-up from available balance.

Note: If _additionalAmount is zero, this operation is not affected by deposit pause - only the staged portion is used.

requestValidatorExit(bytes _pubkeys)​

function requestValidatorExit(bytes calldata _pubkeys) external onlyOwner

Requests validator exits by emitting events. Does not directly trigger exits - node operators must monitor for ValidatorExitRequested events.

triggerValidatorWithdrawals(...)​

function triggerValidatorWithdrawals(
bytes calldata _pubkeys,
uint64[] calldata _amountsInGwei,
address _excessRefundRecipient
) external payable onlyOwner

Triggers validator withdrawals via EIP-7002. Requires msg.value to cover fees.

  • If _amountsInGwei is empty, triggers full withdrawals
  • Otherwise triggers partial withdrawals with specified amounts
  • Excess fee is refunded to _excessRefundRecipient

ejectValidators(bytes _pubkeys, address _refundRecipient)​

function ejectValidators(bytes calldata _pubkeys, address _refundRecipient) external payable

Only callable by node operator (not owner). Triggers full validator exits via EIP-7002 without requiring owner cooperation. This allows node operators to force-exit validators if needed.

  • If _refundRecipient is zero, excess fee refunds go to msg.sender
  • Always triggers full withdrawals

acceptOwnership()​

function acceptOwnership() public

Accepts a pending ownership transfer. Only callable by pending owner.

transferOwnership(address _newOwner)​

function transferOwnership(address _newOwner) public onlyOwner

Initiates 2-step ownership transfer.

renounceOwnership()​

function renounceOwnership() public view onlyOwner

Blocked by design - always reverts with RenouncementNotAllowed().

setDepositor(address _depositor)​

function setDepositor(address _depositor) external onlyOwner

Updates depositor address. Reverts if same as current depositor.

ossify()​

function ossify() external onlyOwner

Pins the implementation for this vault (prevents future upgrades). Warning: This operation is irreversible. Vault cannot be connected to VaultHub after ossification.

collectERC20(address _token, address _recipient, uint256 _amount)​

function collectERC20(address _token, address _recipient, uint256 _amount) external onlyOwner

Recovers ERC-20 tokens sent to the vault.

Note: Does not support ETH recovery (reverts with EthCollectionNotAllowed if EIP-7528 ETH address is passed). Use withdraw() for ETH.

Receiving ETH​

The contract has a receive() function allowing direct ETH transfers, but the preferred method is fund() which emits proper events.