> ## Documentation Index
> Fetch the complete documentation index at: https://berachain-422fce37-docs-staking-pool-install-cli.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Operate a staking pool

> After install.sh: queue commission and allocation on SmartOperator, set min effective balance, then monitor WBERA and withdrawals.

After [Install a staking pool](/nodes/staking-pools/installation) 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](/nodes/guides/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](/nodes/guides/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](https://github.com/berachain/guides/tree/main/apps/staking-pools/frontend). 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:

| Call                      | Who                                      |
| ------------------------- | ---------------------------------------- |
| `queueBoost()`            | `BGT_MANAGER_ROLE`, `whenNotFullyExited` |
| `queueDropBoost(uint128)` | `BGT_MANAGER_ROLE`                       |
| `redeemBGT(uint256)`      | `BGT_MANAGER_ROLE`, `whenNotFullyExited` |
| `activateBoost()`         | no role                                  |
| `dropBoost()`             | no role                                  |
| `claimBgtStakerReward()`  | no role                                  |
| `claimBoostRewards(...)`  | no role                                  |
| `accrueEarnedBGTFees()`   | no role, `whenNotFullyExited`            |

## Roles

`DEFAULT_ADMIN_ROLE` is governance on initialize. `VALIDATOR_ADMIN_ROLE` is the role-admin for the rest:

| Role                               | What it can call                               |
| ---------------------------------- | ---------------------------------------------- |
| `VALIDATOR_ADMIN_ROLE`             | `setMinEffectiveBalance`, `setRewardAllocator` |
| `COMMISSION_MANAGER_ROLE`          | `queueValCommission`                           |
| `REWARDS_ALLOCATION_MANAGER_ROLE`  | `queueRewardsAllocation`                       |
| `PROTOCOL_FEE_MANAGER_ROLE`        | `setProtocolFeePercentage`                     |
| `INCENTIVE_COLLECTOR_MANAGER_ROLE` | `queueIncentiveCollectorPayoutAmountChange`    |
| `BGT_MANAGER_ROLE`                 | `queueBoost`, `queueDropBoost`, `redeemBGT`    |

Addresses and ABIs: [Look up staking pool contract addresses](/nodes/staking-pools/contracts).
