Skip to main content
After Install a staking pool you have a pool address. Day-two writes go through that pool’s SmartOperator. BeraChef treats SmartOperator as the validator operator (beaconDepositContract.getOperator), so you do not call BeraChef with an EOA the way Manage Incentives Commission describes for a normal validator.

Set commission

COMMISSION_MANAGER_ROLE calls SmartOperator.queueValCommission(uint96 commission). That forwards to BeraChef.queueValCommission(pubkey, commission). BeraChef rejects anything above MAX_COMMISSION_RATE (2000 bps, 20%). The queue is not live yet. After commissionChangeDelay blocks, anyone calls BeraChef.activateQueuedValCommission(pubkey). SmartOperator does not wrap that activate.

Direct reward allocation

REWARDS_ALLOCATION_MANAGER_ROLE calls SmartOperator.queueRewardsAllocation(startBlock, weights), which forwards to BeraChef.queueNewRewardAllocation. startBlock must be at least block.number + rewardAllocationBlockDelay. BeraChef only accepts the call from the pubkey’s reward allocator, or from the operator if none is set. VALIDATOR_ADMIN_ROLE can set the allocator with SmartOperator.setRewardAllocator. The Distributor, not you, calls BeraChef.activateReadyQueuedRewardAllocation. Inactivity and fallback rules live on BeraChef; see Manage Reward Allocations for the current delay and inactivity span.

Set the activation threshold

VALIDATOR_ADMIN_ROLE calls SmartOperator.setMinEffectiveBalance, which calls StakingPool.setMinEffectiveBalance. The pool reverts InvalidMinEffectiveBalance unless the value is at least MIN_EFFECTIVE_BALANCE (250,000 BERA) and strictly below MAX_EFFECTIVE_BALANCE (10,000,000 BERA). If you never set it, minEffectiveBalance() returns 250,000 BERA. When totalDeposits + bufferedAssets first reaches that floor, StakingPool sets activeThresholdReached and records _validatorActivationBlock. A later withdrawal whose consensus-layer portion would leave totalDeposits below minEffectiveBalance() triggers a full exit (_triggerFullExit).

Set the protocol fee

PROTOCOL_FEE_MANAGER_ROLE calls SmartOperator.setProtocolFeePercentage. The cap is MAX_PROTOCOL_FEE (2000 bps, 20%). The setter runs BGT and WBERA fee accrual, then stores the new rate. accrueEarnedWBERAFees and accrueEarnedBGTFees are public, whenNotFullyExited, and have no role. Accrual also runs inside withdrawRewards, pullBeraToWithdrawalVault, notifyWithdrawalRequest, fullExitQueueDropBoost, and setProtocolFeePercentage.

Automatic WBERA flows

You cannot call the two functions that move WBERA off SmartOperator:
  • withdrawRewards(amount) reverts unless msg.sender is StakingPool. It unwraps WBERA and calls StakingPool.receiveRewards.
  • pullBeraToWithdrawalVault(amount) reverts unless msg.sender is WithdrawalVault. It unwraps WBERA and sends BERA to the vault.
What the pool and vault do:
  • StakingPool.submit (and receive()) and processRewards call _collectRewards, which pulls WBERA via withdrawRewards when the deposit math includes wberaToCollect, then pulls BERA from StakingRewardsVault.
  • processRewards is whenNotPaused and has no role. It compounds without a user deposit.
  • WithdrawalVault._withdraw reads availableWBERABalance(), floors that cover to 1 gwei, and passes it into notifyWithdrawalRequest. The pool applies that cover only on the normal post-threshold partial path. Short-circuit and full-exit zero the cover and do not call pullBeraToWithdrawalVault. Full cover (remaining CL amount 0) refunds msg.value. Partial cover requests only the remainder from the consensus layer.
  • _triggerFullExit calls fullExitQueueDropBoost, then _collectRewards for the whole rewards-vault balance and availableWBERABalance(), then sends the pool’s native balance to WithdrawalVault.
Watch StakingPool.isActive (factory activate succeeded), activeThresholdReached, totalDeposits, bufferedAssets, and on SmartOperator: availableWBERABalance(), rebaseableWberaAmount(), getEarnedWBERAFeeState().

Withdrawal system

Stakers call WithdrawalVault.requestWithdrawal or requestRedeem. The vault mints a non-transferable NFT and stores requestBlock. Liquidity:
  • Short-circuit (!activeThresholdReached): BERA comes only from bufferedAssets. If the buffer is short, notifyWithdrawalRequest reverts WithdrawalNotAllowed. No EIP-7002 request; the vault refunds the fee.
  • Post-threshold: notifyWithdrawalRequest reverts WithdrawalNotAllowed until block.number >= _validatorActivationBlock + ENABLE_WITHDRAWAL_COOLDOWN_BLOCKS (129,600). After that, uncovered amount goes to the consensus layer; operator WBERA may cover as above.
  • Fully exited: isFullyExited on the pool, or _isFullyExited[pubkey] on the vault. BERA is already in the vault. No new CL request; the fee is refunded.
finalizeWithdrawalRequest / finalizeWithdrawalRequests revert InvalidSender unless msg.sender is request.user, and RequestNotReady until requestBlock + WITHDRAWAL_REQUEST_FINALIZATION_BLOCK_DELAY (129,600). Cover does not shorten that delay. retryFullExit(pubkey, maxFeeToPay) is permissionless and payable. It reverts NotFullyExited unless the vault has marked the pubkey fully exited, and PendingWithdrawalInFlight if a partial withdrawal was requested in the last WITHDRAWAL_FLIGHT_BLOCK_DELAY (49,153) blocks. It re-submits an EIP-7002 full exit (amount 0). Staker requests do not use this path.

Building your front-end

Show finalize time from getWithdrawalRequest(requestId) (requestBlock + 129600). Only request.user can finalize. Use previewRedeem(shares) on the pool for share price. Batch with finalizeWithdrawalRequests. Copy the React example in the guides repository. Fill config.example.json with the pool address and validator pubkey from install.sh or factory getCoreContracts.

Deprecated BGT entry points

These stay on SmartOperator with @deprecated in the interface. They do not wrap a staking-pool-specific yield path:

Roles

DEFAULT_ADMIN_ROLE is governance on initialize. VALIDATOR_ADMIN_ROLE is the role-admin for the rest: Addresses and ABIs: Look up staking pool contract addresses.