From 50b31859c68dead21cce7fa15f23c96c2526c1b6 Mon Sep 17 00:00:00 2001 From: Camembear Date: Fri, 28 Aug 2026 06:02:18 -0400 Subject: [PATCH 01/20] Rewrite staking pool installation for the operator CLI. --- nodes/staking-pools/installation.mdx | 202 ++++++++++++++------------- 1 file changed, 106 insertions(+), 96 deletions(-) diff --git a/nodes/staking-pools/installation.mdx b/nodes/staking-pools/installation.mdx index 9b92337..4a11c87 100644 --- a/nodes/staking-pools/installation.mdx +++ b/nodes/staking-pools/installation.mdx @@ -1,146 +1,156 @@ --- title: "Staking Pool Installation" -description: "Install and activate a staking pool with helper scripts: register, activate, set min effective balance, and optional stake." +description: "Install and activate a staking pool on the validator host with the operator CLI." --- -This guide walks validator operators through installing and activating a staking pool using the helper scripts. +Run the operator CLI on the **validator host**. That host runs `beacond` and holds `BEACOND_HOME`. The CLI checks the deposit. It fetches proofs. It shows what each transaction will do. It then prints a `cast send` command. You copy that command to a **signing machine**. A signing machine is a laptop with a Ledger. -## What you'll use +Do not attach a Ledger to the validator host. -The helper scripts are available through GitHub: +Retail stake and unstake are not in this CLI. Use the [sample frontend](https://github.com/berachain/guides/tree/main/apps/staking-pools/frontend). + +## Get the CLI + +Clone the CLI from the [guides repository](https://github.com/berachain/guides/tree/main/apps/staking-pools/install-helpers). The [install-helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md) lists commands and environment variables. ```bash git clone https://github.com/berachain/guides/ -cd guides/apps/staking-pools -ls -F -# frontend/ install-helpers/ +cd guides/apps/staking-pools/install-helpers ``` -There are many useful scripts under `install-helpers/` which wrap multi-step operations into safe, review-first commands: +Commands: -- **register.sh**: Deploys (registers) staking pool contracts and generates the deployment transaction -- **activate.sh**: Activates a deployed pool using validator proofs from the beacon chain -- **status.sh**: Verifies deployment, registration, and activation status -- **stake.sh**: Generates a staking transaction to add BERA to your pool - - - All scripts write ready-to-run `cast` commands to files for you to review and execute. They do not - send transactions without your confirmation. By default, scripts use Ledger for signing; set - `PRIVATE_KEY` in `env.sh` if you prefer a local key. - +| Command | Action | +| --- | --- | +| `deploy` | Deploy pool contracts and the 10,000 BERA deposit | +| `activate` | Activate the pool with beacon-chain proofs | +| `status` | Read pool telemetry | +| `set-min-balance` | Optional. Set `minEffectiveBalance` (default 250,000 BERA) | ## Requirements -- beacond (validator client) running and synced with your validator keys **backed up somewhere safe** -- Foundry `cast` ([getfoundry.sh](https://getfoundry.sh/)) -- `jq`, `bc`, `curl` -- Ledger hardware wallet (default) or a private key set via `PRIVATE_KEY` -- Funds: at least 10,000 BERA for the initial deposit; additional stake as desired - -## 1. Configure environment +Install these tools on the validator host: -In `install-helpers/`: - -```bash -cp env.sh.template env.sh -# Edit env.sh and set at minimum: -# BEACOND_HOME="/path/to/your/beacond/home" -# NODE_API_ADDRESS="127.0.0.1:3500" # If not auto-detected from app.toml -# Optionally set: -# PRIVATE_KEY="0x..." # Defaults to Ledger if unset -``` - -Ensure the beacon node API is enabled in your `app.toml` (`[beacon-kit.node-api]`) or provide `NODE_API_ADDRESS` in `env.sh`. The scripts will auto-detect when possible and verify connectivity before proceeding. +- Node.js 22 or later +- Foundry `cast` ([getfoundry.sh](https://getfoundry.sh/)) +- `beacond`, synced, with validator keys backed up -## 2. Register (deploy contracts) +You also need: -Run `register.sh` with your addresses to deploy the staking pool contracts: +- At least 10,000 BERA for the initial deposit +- A signing machine with a Ledger, or a private key in `PRIVATE_KEY` on the validator host -```bash -./register.sh --sr 0xSHARES_RECIPIENT --op 0xOPERATOR -``` + + Set `BEACOND_HOME` to the beacond data directory. The CLI stops `deploy`, `activate`, and + `set-min-balance` if `BEACOND_HOME` is missing. It also stops if `beacond` cannot read the + validator keys. + -1. The chain (mainnet or Bepolia) is auto-detected from your beacond configuration. -2. The initial stake is 10,000 BERA (fixed by the consensus layer). The script writes `deployment-command.sh` with this deposit. Review it. Simulate it if you want. Then run it. This should dump a successful transaction receipt. -3. The script also predicts and shows your staking pool contract addresses ahead of time. +Do not set `CHAIN` in the environment. Foundry `cast` reads `CHAIN` as its own flag. Use `CLI_CHAIN` (`mainnet` or `bepolia`). If you omit `CLI_CHAIN`, the CLI detects the network from genesis. -## 3. Wait for validator registration +## Operator flow -After deploying the contracts, wait for your validator to be registered on the beacon chain. You can check registration status with: + + + Copy the template and set `BEACOND_HOME`: -```bash -./status.sh -``` + ```bash + cp env.sh.template env.sh + # Set BEACOND_HOME="/path/to/your/beacond/home" + # Optional: PRIVATE_KEY="0x..." (private key on this host only) + source env.sh + ``` -The validator must appear on the beacon chain before you can activate the pool. + The Node API defaults to `http://127.0.0.1:3500`. Override it with `CL_NODE_API_URL` or `NODE_API_ADDRESS` if your node uses another address. -## 4. Activate the pool + Enable the Node API in `app.toml` under `[beacon-kit.node-api]` if it is off. + -Once your validator is registered, run `activate.sh` to activate the pool: + + ```bash + node pool-cli.mjs deploy --op 0xOPERATOR --sr 0xSHARES_RECIPIENT + ``` -```bash -./activate.sh -``` + The CLI does this: -Activation is a permissionless on-chain call keyed by your validator pubkey, so `activate.sh` no longer takes `--sr` or `--op`; the pool's shares recipient and operator are already baked in from `register.sh`. The script: + 1. Detects mainnet or Bepolia from `CLI_CHAIN` or genesis. + 2. Validates the `beacond` deposit. + 3. Predicts the four pool contract addresses. + 4. Runs `cast call` for `deployStakingPoolContracts`. + 5. Shows the 10,000 BERA deposit. + 6. Prints a `cast send` command. It does not broadcast. -1. Verifies the pool contract is deployed and not already active (`isActive()`). -2. Confirms `BeaconDeposit.getOperator(pubkey)` matches the pool's SmartOperator and that the validator's withdrawal credentials point at the pool's WithdrawalVault. -3. Pins the beacon-chain head slot, fetches validator pubkey, withdrawal-credentials, and balance proofs at that single slot, and preflights `activateStakingPool()` locally so a bad proof or stale timestamp surfaces before you broadcast. -4. Writes `generated/activation-command.sh` for you to review and execute. + Copy the printed command. Run it on the signing machine. The default printed command uses `--ledger`. -Proofs include a timestamp enforced by the contract (`MAX_TIMESTAMP_AGE` = 10 minutes). The generated script also hard-expires after that window; if you hit it, re-run `activate.sh` to regenerate. + To broadcast from the validator host, set `PRIVATE_KEY` and add `--execute`. The CLI refuses `--execute` without `PRIVATE_KEY`. + -## 5. Verify installation + + ```bash + node pool-cli.mjs status + ``` -Use `status.sh` to check deployment, registration, and activation: + The validator must appear on the beacon chain before you activate the pool. + -```bash -./status.sh -``` + + ```bash + node pool-cli.mjs activate + ``` -You should see the SmartOperator, StakingPool, StakingRewardsVault, and IncentiveCollector addresses, confirmation that the beacon deposit operator matches your SmartOperator, the pool's active status, and various telemetry. + Activation is a permissionless call keyed by the validator pubkey. You do not pass `--op` or `--sr`. -## 6. Set minimum effective balance + The CLI does this: - - The `minEffectiveBalance` parameter determines when your staking pool activates its validator on - the consensus layer. If you don't set this value correctly, your pool may accumulate deposits - without ever activating. - + 1. Checks that the pool is deployed and not already active. + 2. Checks that the validator is registered on the beacon chain. + 3. Pins the beacon head slot. + 4. Fetches pubkey, withdrawal-credentials, and balance proofs at that slot. + 5. Checks that withdrawal credentials match the pool WithdrawalVault. + 6. Runs `cast call` for `activateStakingPool`. It names known reverts. + 7. Refuses to print `cast send` if the proof is older than 10 minutes. + 8. Prints a `cast send` command. -Set the value using your SmartOperator contract: + Copy the printed command to the signing machine. If the 10-minute window expires, run `activate` again to fetch new proofs. + -```bash -cast send $SMART_OPERATOR_ADDRESS \ - "setMinEffectiveBalance(uint256)" $CALCULATED_MIN_STAKE \ - --ledger # or --private-key $PRIVATE_KEY -``` + + ```bash + node pool-cli.mjs status + ``` -Replace `$SMART_OPERATOR_ADDRESS` with your SmartOperator address and `$CALCULATED_MIN_STAKE` with the calculated minimum stake amount in wei (multiply BERA amount by 10^18). + You should see SmartOperator, StakingPool, StakingRewardsVault, and IncentiveCollector addresses. You should also see operator match, active state, and pool telemetry. + -For details on why this matters, how to determine the correct value, and how the consensus layer minimum works, see the [Setting Minimum Effective Balance](/nodes/staking-pools/operators#setting-minimum-effective-balance) section in the operator guide. + + + `minEffectiveBalance` controls when the pool activates the validator on the consensus layer. + If you set this value incorrectly, the pool can take deposits and never activate. + -## 7. (Optional) Stake additional BERA + ```bash + node pool-cli.mjs set-min-balance + ``` -Add stake to your pool and send stBERA to a receiver address: + The default amount is 250,000 BERA. Pass `--amount` to use another value. Copy the printed `cast send` to the signing machine. -```bash -# With BEACOND_HOME configured (pool auto-detected): -./stake.sh --amount 100 --receiver 0xRECEIVER + See [Setting Minimum Effective Balance](/nodes/staking-pools/operators#setting-minimum-effective-balance) in the operator guide. + + -# Or specify an explicit pool address: -./stake.sh --amount 100 --receiver 0xRECEIVER --staking-pool 0xPOOL -``` +## Stake additional BERA -The script writes `stake-command.sh`. Review and execute the command to submit your stake. +The operator CLI does not stake or unstake. Use the [sample frontend](https://github.com/berachain/guides/tree/main/apps/staking-pools/frontend) for extra stake. See [Building your front-end](/nodes/staking-pools/operators#building-your-front-end). -## Troubleshooting (quick) +## Troubleshooting -- **Node API not reachable**: Enable `[beacon-kit.node-api]` and confirm the address/port; or set `NODE_API_ADDRESS` in `env.sh`. The script will examine your files and tell you how to activate the API if it isn't enabled yet. -- **Missing tools**: Install Foundry (`cast`), `jq`, `bc`, and `curl` and ensure they are on your PATH. +- **`BEACOND_HOME` is required.** Set it to the beacond data directory on this validator host. +- **`cast` rejects `bepolia`.** Unset `CHAIN`. Set `CLI_CHAIN` if you need to force the network. +- **Node API not reachable.** Enable `[beacon-kit.node-api]` or set `CL_NODE_API_URL`. +- **Missing tools.** Install Node.js 22+, Foundry `cast`, and `beacond`. Put them on `PATH`. +- **`--execute` refused.** Set `PRIVATE_KEY` on the validator host, or run the printed `cast send` on the signing machine. ## What's next -- [Contract reference](/nodes/staking-pools/contracts) explains what you've deployed -- [Delegation guide](/nodes/staking-pools/delegators) explains how to receive a Foundation delegation, which is very similar to this flow +- [Contract reference](/nodes/staking-pools/contracts) describes the contracts you deployed. +- [Delegation guide](/nodes/staking-pools/delegators) describes a Foundation delegation. That flow is similar. From f49368a47f836cfe575732b71339693bb957ad23 Mon Sep 17 00:00:00 2001 From: Camembear Date: Fri, 28 Aug 2026 11:09:38 -0400 Subject: [PATCH 02/20] Add a worked example session and full command reference for the operator CLI. Thin the per-step behavior lists in the operator flow now that the command reference covers each flag and the underlying call sequence. --- nodes/staking-pools/installation.mdx | 329 ++++++++++++++++++++++++--- 1 file changed, 298 insertions(+), 31 deletions(-) diff --git a/nodes/staking-pools/installation.mdx b/nodes/staking-pools/installation.mdx index 4a11c87..11bc755 100644 --- a/nodes/staking-pools/installation.mdx +++ b/nodes/staking-pools/installation.mdx @@ -1,14 +1,12 @@ --- title: "Staking Pool Installation" -description: "Install and activate a staking pool on the validator host with the operator CLI." +description: "Install and activate a staking pool on the validator host with the operator CLI. Includes a worked example and a full command reference." --- Run the operator CLI on the **validator host**. That host runs `beacond` and holds `BEACOND_HOME`. The CLI checks the deposit. It fetches proofs. It shows what each transaction will do. It then prints a `cast send` command. You copy that command to a **signing machine**. A signing machine is a laptop with a Ledger. Do not attach a Ledger to the validator host. -Retail stake and unstake are not in this CLI. Use the [sample frontend](https://github.com/berachain/guides/tree/main/apps/staking-pools/frontend). - ## Get the CLI Clone the CLI from the [guides repository](https://github.com/berachain/guides/tree/main/apps/staking-pools/install-helpers). The [install-helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md) lists commands and environment variables. @@ -23,8 +21,10 @@ Commands: | Command | Action | | --- | --- | | `deploy` | Deploy pool contracts and the 10,000 BERA deposit | +| `status` | Read EL operator, beacon inclusion, and pool `isActive` | | `activate` | Activate the pool with beacon-chain proofs | -| `status` | Read pool telemetry | +| `stake` | Deposit BERA and mint stBERA to `--receiver` | +| `unstake` | Request or finalize a withdrawal | | `set-min-balance` | Optional. Set `minEffectiveBalance` (default 250,000 BERA) | ## Requirements @@ -41,9 +41,9 @@ You also need: - A signing machine with a Ledger, or a private key in `PRIVATE_KEY` on the validator host - Set `BEACOND_HOME` to the beacond data directory. The CLI stops `deploy`, `activate`, and - `set-min-balance` if `BEACOND_HOME` is missing. It also stops if `beacond` cannot read the - validator keys. + Set `BEACOND_HOME` to the beacond data directory. The CLI stops `deploy`, `activate`, + `stake`, `unstake`, and `set-min-balance` if `BEACOND_HOME` is missing. It also stops if + `beacond` cannot read the validator keys. Do not set `CHAIN` in the environment. Foundry `cast` reads `CHAIN` as its own flag. Use `CLI_CHAIN` (`mainnet` or `bepolia`). If you omit `CLI_CHAIN`, the CLI detects the network from genesis. @@ -71,26 +71,25 @@ Do not set `CHAIN` in the environment. Foundry `cast` reads `CHAIN` as its own f node pool-cli.mjs deploy --op 0xOPERATOR --sr 0xSHARES_RECIPIENT ``` - The CLI does this: - - 1. Detects mainnet or Bepolia from `CLI_CHAIN` or genesis. - 2. Validates the `beacond` deposit. - 3. Predicts the four pool contract addresses. - 4. Runs `cast call` for `deployStakingPoolContracts`. - 5. Shows the 10,000 BERA deposit. - 6. Prints a `cast send` command. It does not broadcast. + `deploy` validates the `beacond` deposit, predicts the four pool contract addresses, and preflights the deploy call. It then prints a `cast send` command for the 10,000 BERA deposit. It does not broadcast. See [`deploy`](#deploy) for the full option list. Copy the printed command. Run it on the signing machine. The default printed command uses `--ledger`. To broadcast from the validator host, set `PRIVATE_KEY` and add `--execute`. The CLI refuses `--execute` without `PRIVATE_KEY`. - + ```bash node pool-cli.mjs status ``` - The validator must appear on the beacon chain before you activate the pool. + `status` reports three separate facts: the EL operator, beacon inclusion, and the pool's `isActive` flag. See [`status`](#status) for what each one means. + + `isActive=false` is the expected value after deploy. It does not mean "run activate now." + + Wait until `status` says the beacon has this validator and that the pool is ready for activation. Then go to the next step. + + Set `CL_NODE_API_URL` to a Node API that can serve validator state and proofs. @@ -100,18 +99,9 @@ Do not set `CHAIN` in the environment. Foundry `cast` reads `CHAIN` as its own f Activation is a permissionless call keyed by the validator pubkey. You do not pass `--op` or `--sr`. - The CLI does this: + `activate` fetches beacon-chain proofs a few slots behind head, checks them against the pool's WithdrawalVault, and preflights `activateStakingPool`. See [`activate`](#activate) for the full sequence. - 1. Checks that the pool is deployed and not already active. - 2. Checks that the validator is registered on the beacon chain. - 3. Pins the beacon head slot. - 4. Fetches pubkey, withdrawal-credentials, and balance proofs at that slot. - 5. Checks that withdrawal credentials match the pool WithdrawalVault. - 6. Runs `cast call` for `activateStakingPool`. It names known reverts. - 7. Refuses to print `cast send` if the proof is older than 10 minutes. - 8. Prints a `cast send` command. - - Copy the printed command to the signing machine. If the 10-minute window expires, run `activate` again to fetch new proofs. + Proofs expire 10 minutes after their timestamp. Copy the printed command to the signing machine before then. If the window expires, run `activate` again to fetch new proofs. Re-running `activate` is always safe: an already-active pool reports "Pool is already activated" and does nothing. @@ -119,7 +109,29 @@ Do not set `CHAIN` in the environment. Foundry `cast` reads `CHAIN` as its own f node pool-cli.mjs status ``` - You should see SmartOperator, StakingPool, StakingRewardsVault, and IncentiveCollector addresses. You should also see operator match, active state, and pool telemetry. + You should see SmartOperator, StakingPool, StakingRewardsVault, and IncentiveCollector addresses. You should also see operator match, `isActive=true`, and pool telemetry. + + + + ```bash + node pool-cli.mjs stake --amount 100 --receiver 0xRECEIVER + ``` + + This calls `submit(address)` and sends `--amount` BERA. `--receiver` gets the stBERA. Copy the printed `cast send` to the signing machine. + + + + Unstake is two transactions. The request mints a withdrawal NFT. After the on-chain delay, you finalize it. + + ```bash + node pool-cli.mjs unstake --amount 100 --from 0xHOLDER + node pool-cli.mjs unstake --shares 50 --from 0xHOLDER + node pool-cli.mjs unstake --finalize REQUEST_ID --from 0xHOLDER + ``` + + Pass exactly one of `--amount`, `--shares`, or `--finalize`. `--from` is the address that holds stBERA. Copy each printed `cast send` to the signing machine. + + The [sample frontend](https://github.com/berachain/guides/tree/main/apps/staking-pools/frontend) remains available. See [Building your front-end](/nodes/staking-pools/operators#building-your-front-end). @@ -138,15 +150,270 @@ Do not set `CHAIN` in the environment. Foundry `cast` reads `CHAIN` as its own f -## Stake additional BERA +## Example session + +This section shows one full run on a Bepolia validator. The addresses, chain values, and figures are real. Most command output is not shown. Only the lines that show success or failure stay in. + +### Install and configure + +```bash +git clone https://github.com/berachain/guides.git +cd guides/apps/staking-pools/install-helpers + +curl -L https://getfoundry.sh/install | bash +export PATH="$PATH:$HOME/.foundry/bin" +foundryup +``` + +`foundryup` reports the installed version: + +``` +foundryup: cast verified ✓ +foundryup: done! +``` + +Set the environment and run the test suite once to confirm the CLI works on this host: + +```bash +cp env.sh.template env.sh +export BEACOND_HOME=/path/to/your/beacond/home +export CLI_CHAIN=bepolia +export EL_RPC_URL=http://127.0.0.1:8545 + +node --test test/*.test.mjs +``` + +All tests pass. + +### Deploy + +```bash +node pool-cli.mjs deploy --op 0x196802FFa2c463CADa5eb8d8F63F265610b574e9 --sr 0x196802FFa2c463CADa5eb8d8F63F265610b574e9 +``` + +``` +[success] Deposit validation: OK +[success] Preflight OK +[info] Copy and run on your signing machine (ledger on laptop): +``` + +Copy the printed `cast send` and run it on the signing machine. Predicted addresses for this pubkey: SmartOperator `0x926EcD8E8ea200C2c66A26e031b3b112f96aD0De`, StakingPool `0x6dE5C7989cCAC160b47e7c8b015C87be4b540755`. + +### Wait for beacon inclusion + +Right after deploy, `status` reports that the beacon chain does not have the validator yet: + +``` +[info] Beacon: not in head state (queried http://127.0.0.1:3500) +[info] EL operator is registered. Beacon head state does not include this validator yet. +[info] Next: Wait until the beacon chain includes the validator. Re-run status. Do not run activate yet. +``` + +Re-run `status` until it reports: + +``` +[success] Beacon has this validator (index 36, status pending_initialized). Ready for activation. +[info] Next: Run: node pool-cli.mjs activate +``` + +### Activate + +```bash +node pool-cli.mjs activate +``` + +The execution client can lag behind the beacon chain by a block or two. If it has not yet produced the block the proof needs, `activate` reports the block as missing and does nothing else: + +``` +[error] Failed to read EL block 24527917 from http://127.0.0.1:8545: block not found +``` + +This is not a failed activation. Re-run `activate`: + +``` +[success] All proofs pinned to slot 24528070 +[success] EIP-4788 timestamp: 1787927174 +[success] Preflight OK — activateStakingPool would succeed at current head +[info] Copy and run on your signing machine (ledger on laptop): +``` + +Copy the printed command to the signing machine and run it there. + +### Check status + +```bash +node pool-cli.mjs status +``` + +``` +[info] EL operator: registered (0x926ecd8e8ea200c2c66a26e031b3b112f96ad0de) +[info] Beacon: index 36, status pending_initialized +[info] Pool isActive: true (factory activateStakingPool has been called) +[success] Staking pool is ACTIVE +``` + +### Stake more BERA + +A first attempt without `--amount` shows the CLI's input checks. They run before any call to the chain: + +```bash +node pool-cli.mjs stake --receiver 0x196802FFa2c463CADa5eb8d8F63F265610b574e9 +``` + +``` +[error] --amount must be a positive number +``` + +Add `--amount` and run again, this time with `--execute` to broadcast from the validator host: + +```bash +node pool-cli.mjs stake --receiver 0x196802FFa2c463CADa5eb8d8F63F265610b574e9 --amount 240000 --execute +``` + +``` +[success] Preflight OK — submit(0x196802ffa2c463cada5eb8d8f63f265610b574e9) value 240000ether +[info] Broadcasting submit via cast send (hot key on validator)... +[success] submit broadcast: 0xa3013dea6717d5896e4d67c1d3e0c76460f724fdbcf8e46edd0f55acc6f4a911 +``` + +### Final status + +```bash +node pool-cli.mjs status +``` + +``` +[success] Staking pool is ACTIVE + Total assets (BERA): 250000.000000000000000000 + Total supply (stBERA): 250000.000000000000000000 +``` + +10,000 BERA came from the deploy step. 240,000 BERA came from the stake step. The total matches. + +## Command reference + +Entry point: `node pool-cli.mjs [options]`. Every state-changing command dry-runs with `cast call` first, then prints a `cast send` command. It does not broadcast unless you pass `--execute`. + +### Global options + +| Option | Effect | +| --- | --- | +| `--execute` | Broadcast from the validator host. Requires `PRIVATE_KEY`. Without it, `--execute` is refused and the CLI still prints the `cast send` command. | +| `--help` | Show command help. | + +### Environment variables + +| Variable | Purpose | +| --- | --- | +| `BEACOND_HOME` | beacond data directory. Required for every command. | +| `BEACOND_BIN` | Override the beacond binary. Default: `beacond` on `PATH`. | +| `CLI_CHAIN` | `mainnet` or `bepolia`. If unset, the CLI detects the network from genesis. | +| `RPC_URL` / `EL_RPC_URL` | Override the execution RPC. Default: the public Berachain endpoint for the detected chain. | +| `CL_NODE_API_URL` / `NODE_API_ADDRESS` | Beacon Node API for validator state and proofs. Default: `http://127.0.0.1:3500`. | +| `PRIVATE_KEY` | Hot key on the validator host. Required for `--execute`. | + + + Do not set `CHAIN` in the environment. Foundry `cast` reads `CHAIN` as its own flag and rejects + values like `bepolia`. Use `CLI_CHAIN` instead. + + +### `deploy` + +```bash +node pool-cli.mjs deploy --op 0xOPERATOR --sr 0xSHARES_RECIPIENT +node pool-cli.mjs deploy --op 0xOPERATOR --sr 0xSHARES_RECIPIENT --execute +``` + +| Option | Value | Required | +| --- | --- | --- | +| `--op` | Operator address for the pool | Yes | +| `--sr` | Shares-recipient address for the pool | Yes | + +`deploy` validates the deposit with `beacond`, predicts the four pool contract addresses, and preflights `deployStakingPoolContracts`. The deposit is fixed at 10,000 BERA. + +### `activate` + +```bash +node pool-cli.mjs activate +node pool-cli.mjs activate --execute +``` + +Activation takes no options besides `--execute`. The pubkey, operator, and shares recipient are already fixed by `deploy`. + +`activate` pins a beacon-chain slot a few slots behind head, so the paired execution-layer block already exists. It fetches the pubkey, withdrawal-credentials, and balance proofs at that slot, checks them against the pool's WithdrawalVault, and preflights `activateStakingPool`. The proof expires 10 minutes after its timestamp. The CLI refuses to print `cast send` past that window. + +### `status` + +```bash +node pool-cli.mjs status +``` + +`status` takes no options. It reports three separate facts: + +1. **EL operator**: whether the beacon deposit contract's operator matches this pool. +2. **Beacon inclusion**: whether the beacon chain has this validator, and its index and status. +3. **Pool `isActive`**: whether `activateStakingPool` has been called. + +Once the pool is active, `status` also prints contract addresses, total assets and supply, buffered assets, the minimum effective balance, and WBERA and legacy BGT balances. If `PRIVATE_KEY` is set, it also prints that wallet's stBERA shares and withdrawal-NFT count. + +### `stake` + +```bash +node pool-cli.mjs stake --amount 100 --receiver 0xRECEIVER +node pool-cli.mjs stake --amount 100 --receiver 0xRECEIVER --execute +``` + +| Option | Value | Required | +| --- | --- | --- | +| `--amount` | BERA to deposit | Yes | +| `--receiver` | Address that gets the minted stBERA | Yes | +| `--from` | Address the dry-run simulates from | No. Default: `--receiver`. | +| `--staking-pool` | Pool address | No. Default: looked up from the validator pubkey. | + +`stake` calls `StakingPool.submit(address)` with `--amount` as the transaction value. + +### `unstake` + +```bash +node pool-cli.mjs unstake --amount 100 --from 0xHOLDER +node pool-cli.mjs unstake --shares 50 --from 0xHOLDER +node pool-cli.mjs unstake --finalize 42 --from 0xHOLDER +``` + +| Option | Value | Required | +| --- | --- | --- | +| `--amount` | BERA to withdraw, as assets | Exactly one of `--amount`, `--shares`, `--finalize` | +| `--shares` | stBERA to redeem, as shares | Exactly one of `--amount`, `--shares`, `--finalize` | +| `--finalize` | Request ID to finalize | Exactly one of `--amount`, `--shares`, `--finalize` | +| `--from` | stBERA holder | No. Default: derived from `PRIVATE_KEY`. `--receiver` works as an alias. | +| `--staking-pool` | Pool address | No. Default: looked up from the validator pubkey. | +| `--max-fee` | EIP-7002 fee, in BERA | No. Default: the CLI probes candidate fees up to 0.01 BERA. | + +Withdrawal is two transactions. `--amount` calls `requestWithdrawal`. `--shares` calls `requestRedeem`. Either one mints a withdrawal NFT and returns a request ID. After the on-chain delay, `--finalize` calls `finalizeWithdrawalRequest` to release the funds. The pool must already be active. + +### `set-min-balance` + +```bash +node pool-cli.mjs set-min-balance +node pool-cli.mjs set-min-balance --amount 300000 --execute +``` + +| Option | Value | Required | +| --- | --- | --- | +| `--amount` | Minimum effective balance, in BERA | No. Default: 250,000 BERA. | -The operator CLI does not stake or unstake. Use the [sample frontend](https://github.com/berachain/guides/tree/main/apps/staking-pools/frontend) for extra stake. See [Building your front-end](/nodes/staking-pools/operators#building-your-front-end). +This command is optional. See [Setting Minimum Effective Balance](/nodes/staking-pools/operators#setting-minimum-effective-balance) for why the value matters and how to calculate it. ## Troubleshooting - **`BEACOND_HOME` is required.** Set it to the beacond data directory on this validator host. - **`cast` rejects `bepolia`.** Unset `CHAIN`. Set `CLI_CHAIN` if you need to force the network. - **Node API not reachable.** Enable `[beacon-kit.node-api]` or set `CL_NODE_API_URL`. +- **Pool contracts are not deployed.** The deploy `cast send` has not landed for this pubkey. `isActive` is not available yet. Confirm the transaction hash on the explorer. +- **Beacon head state does not include this validator.** The EL deposit can be in while the beacon chain has not included the validator. Wait. Re-run `status`. Do not run `activate` yet. +- **Ready for activation.** `status` shows a beacon index and CL `status`. The pool `isActive` is still false. Run `activate`. +- **`activate` cannot read an EL block.** The execution client has not produced the block the proof needs yet. Wait a few seconds and re-run `activate`. +- **Pool is already activated.** `activate` is safe to re-run. Once active, it reports this and takes no action. - **Missing tools.** Install Node.js 22+, Foundry `cast`, and `beacond`. Put them on `PATH`. - **`--execute` refused.** Set `PRIVATE_KEY` on the validator host, or run the printed `cast send` on the signing machine. From 4386c45a2598a26d1b0313e8c85a5c7b0b3bdbe3 Mon Sep 17 00:00:00 2001 From: Camembear Date: Fri, 28 Aug 2026 11:17:46 -0400 Subject: [PATCH 03/20] Reflect the wider activate proof-pin cushion and add an --execute callout. The 5-slot cushion should not run out in normal operation, so the example session no longer walks through the block-not-found error as an expected step. --- nodes/staking-pools/installation.mdx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/nodes/staking-pools/installation.mdx b/nodes/staking-pools/installation.mdx index 11bc755..6e4cf69 100644 --- a/nodes/staking-pools/installation.mdx +++ b/nodes/staking-pools/installation.mdx @@ -222,13 +222,7 @@ Re-run `status` until it reports: node pool-cli.mjs activate ``` -The execution client can lag behind the beacon chain by a block or two. If it has not yet produced the block the proof needs, `activate` reports the block as missing and does nothing else: - -``` -[error] Failed to read EL block 24527917 from http://127.0.0.1:8545: block not found -``` - -This is not a failed activation. Re-run `activate`: +`activate` pins a slot 5 blocks behind head, so the execution-layer block the proof needs already exists: ``` [success] All proofs pinned to slot 24528070 @@ -239,6 +233,11 @@ This is not a failed activation. Re-run `activate`: Copy the printed command to the signing machine and run it there. + + On the validator host, set `PRIVATE_KEY` and append `--execute` to broadcast directly instead: + `node pool-cli.mjs activate --execute`. Every state-changing command accepts `--execute`. + + ### Check status ```bash @@ -340,7 +339,7 @@ node pool-cli.mjs activate --execute Activation takes no options besides `--execute`. The pubkey, operator, and shares recipient are already fixed by `deploy`. -`activate` pins a beacon-chain slot a few slots behind head, so the paired execution-layer block already exists. It fetches the pubkey, withdrawal-credentials, and balance proofs at that slot, checks them against the pool's WithdrawalVault, and preflights `activateStakingPool`. The proof expires 10 minutes after its timestamp. The CLI refuses to print `cast send` past that window. +`activate` pins a beacon-chain slot 5 blocks behind head, so the paired execution-layer block already exists. The consensus and execution clients stay in lockstep on Berachain, so this margin should never run out in normal operation. `activate` fetches the pubkey, withdrawal-credentials, and balance proofs at that slot, checks them against the pool's WithdrawalVault, and preflights `activateStakingPool`. The proof expires 10 minutes after its timestamp. The CLI refuses to print `cast send` past that window. ### `status` @@ -412,7 +411,7 @@ This command is optional. See [Setting Minimum Effective Balance](/nodes/staking - **Pool contracts are not deployed.** The deploy `cast send` has not landed for this pubkey. `isActive` is not available yet. Confirm the transaction hash on the explorer. - **Beacon head state does not include this validator.** The EL deposit can be in while the beacon chain has not included the validator. Wait. Re-run `status`. Do not run `activate` yet. - **Ready for activation.** `status` shows a beacon index and CL `status`. The pool `isActive` is still false. Run `activate`. -- **`activate` cannot read an EL block.** The execution client has not produced the block the proof needs yet. Wait a few seconds and re-run `activate`. +- **`activate` cannot read an EL block.** `activate` pins a slot 5 blocks behind head, so this should not happen on a healthy node. If it does, the execution client is unusually far behind. Wait and re-run `activate`. - **Pool is already activated.** `activate` is safe to re-run. Once active, it reports this and takes no action. - **Missing tools.** Install Node.js 22+, Foundry `cast`, and `beacond`. Put them on `PATH`. - **`--execute` refused.** Set `PRIVATE_KEY` on the validator host, or run the printed `cast send` on the signing machine. From 97f009f66870bc7803fb1fce666679ae9004e678 Mon Sep 17 00:00:00 2001 From: Camembear Date: Sat, 29 Aug 2026 08:46:23 -0400 Subject: [PATCH 04/20] Rewrite installation.mdx for the hands-off, two-mode installer Draft for Lead review/markup: single `install` entry point, hot-key vs cold-signing mode selection, one confirmation, concise default output with --verbose, resume-safe, set-min-balance/delegated pools out of the automatic flow. deploy/activate/set-min-balance stay available as standalone commands for advanced/manual use. --- nodes/staking-pools/installation.mdx | 434 ++++++--------------------- 1 file changed, 97 insertions(+), 337 deletions(-) diff --git a/nodes/staking-pools/installation.mdx b/nodes/staking-pools/installation.mdx index 6e4cf69..f47d08e 100644 --- a/nodes/staking-pools/installation.mdx +++ b/nodes/staking-pools/installation.mdx @@ -1,306 +1,151 @@ --- title: "Staking Pool Installation" -description: "Install and activate a staking pool on the validator host with the operator CLI. Includes a worked example and a full command reference." +description: "Install and activate a staking pool with one command. The CLI runs on the validator host, checks funding, and either finishes the job itself or hands you exactly the cast commands it can't run for you." --- -Run the operator CLI on the **validator host**. That host runs `beacond` and holds `BEACOND_HOME`. The CLI checks the deposit. It fetches proofs. It shows what each transaction will do. It then prints a `cast send` command. You copy that command to a **signing machine**. A signing machine is a laptop with a Ledger. +Run the CLI on the **validator host** — the machine running `beacond`, with `BEACOND_HOME` set. There are two ways to run it, chosen by whether you give it a private key: -Do not attach a Ledger to the validator host. +- **Hot-key mode** (`PRIVATE_KEY` set): the CLI signs and broadcasts every transaction itself. No Foundry, no `cast`, no `npm install`. You confirm the plan once; everything else is hands-off. +- **Cold-signing mode** (no `PRIVATE_KEY`): the CLI never signs anything. It reads chain state itself, tells you exactly what it wants to do, and prints a `cast send` command for each transaction that genuinely needs a new signature. You run those on a separate machine — a laptop with a Ledger, or with your own key — while the CLI keeps watching the chain and moves on as soon as each one lands. -## Get the CLI +Both modes run the same installer, watch for the same on-chain facts, and are safe to kill and re-run at any point — the CLI figures out where you left off by reading the chain, not by remembering a file. -Clone the CLI from the [guides repository](https://github.com/berachain/guides/tree/main/apps/staking-pools/install-helpers). The [install-helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md) lists commands and environment variables. +## Get the CLI ```bash git clone https://github.com/berachain/guides/ cd guides/apps/staking-pools/install-helpers ``` -Commands: +Requires Node.js 22+ and `beacond` on the validator host. Nothing else — the CLI carries its own signing and chain-reading code; it does not call out to `cast`, `forge`, or any package manager at runtime. -| Command | Action | -| --- | --- | -| `deploy` | Deploy pool contracts and the 10,000 BERA deposit | -| `status` | Read EL operator, beacon inclusion, and pool `isActive` | -| `activate` | Activate the pool with beacon-chain proofs | -| `stake` | Deposit BERA and mint stBERA to `--receiver` | -| `unstake` | Request or finalize a withdrawal | -| `set-min-balance` | Optional. Set `minEffectiveBalance` (default 250,000 BERA) | +Cold-signing mode also needs a **signing machine**: any computer with `cast` installed, holding either a Ledger or a private key of your own. That machine never needs to touch the validator host. ## Requirements -Install these tools on the validator host: - -- Node.js 22 or later -- Foundry `cast` ([getfoundry.sh](https://getfoundry.sh/)) -- `beacond`, synced, with validator keys backed up - -You also need: - -- At least 10,000 BERA for the initial deposit -- A signing machine with a Ledger, or a private key in `PRIVATE_KEY` on the validator host +- At least 10,000 BERA for the initial deposit (the fixed consensus-layer deposit amount). More if you also want to stake past that. +- `beacond`, synced, with validator keys backed up. +- Hot-key mode: `PRIVATE_KEY` on the validator host, funded with the BERA above. +- Cold-signing mode: the address that will hold the funds (so the CLI can check its balance), and a signing machine with `cast`. - Set `BEACOND_HOME` to the beacond data directory. The CLI stops `deploy`, `activate`, - `stake`, `unstake`, and `set-min-balance` if `BEACOND_HOME` is missing. It also stops if - `beacond` cannot read the validator keys. + `PRIVATE_KEY` on the validator host is a hot key. Only set it if you're comfortable with that + key funding transactions directly from this machine. Otherwise, leave it unset and sign from a + separate machine. -Do not set `CHAIN` in the environment. Foundry `cast` reads `CHAIN` as its own flag. Use `CLI_CHAIN` (`mainnet` or `bepolia`). If you omit `CLI_CHAIN`, the CLI detects the network from genesis. +## Run it -## Operator flow - - - - Copy the template and set `BEACOND_HOME`: - - ```bash - cp env.sh.template env.sh - # Set BEACOND_HOME="/path/to/your/beacond/home" - # Optional: PRIVATE_KEY="0x..." (private key on this host only) - source env.sh - ``` - - The Node API defaults to `http://127.0.0.1:3500`. Override it with `CL_NODE_API_URL` or `NODE_API_ADDRESS` if your node uses another address. +```bash +cp env.sh.template env.sh +# Set BEACOND_HOME. Optionally set PRIVATE_KEY for hot-key mode. +source env.sh - Enable the Node API in `app.toml` under `[beacon-kit.node-api]` if it is off. - +node pool-cli.mjs install +``` - - ```bash - node pool-cli.mjs deploy --op 0xOPERATOR --sr 0xSHARES_RECIPIENT - ``` +The installer asks for whatever it can't determine on its own, then confirms the whole plan before touching anything. - `deploy` validates the `beacond` deposit, predicts the four pool contract addresses, and preflights the deploy call. It then prints a `cast send` command for the 10,000 BERA deposit. It does not broadcast. See [`deploy`](#deploy) for the full option list. + + + It reads your validator pubkey from `beacond`, detects the network from genesis, and checks the funding wallet's balance. - Copy the printed command. Run it on the signing machine. The default printed command uses `--ledger`. + - Hot-key mode: the funding wallet is the address behind `PRIVATE_KEY`. + - Cold-signing mode: pass `--funding-address 0x...`, or the CLI asks for it. It also asks once, up front, whether you're signing with a Ledger (press Enter) or your own key (type `key`) — this only changes the wording of the commands it prints later; it never reads a key itself. - To broadcast from the validator host, set `PRIVATE_KEY` and add `--execute`. The CLI refuses `--execute` without `PRIVATE_KEY`. + If `--operator` / `--shares-recipient` are omitted, both default to the funding wallet's address. - - ```bash - node pool-cli.mjs status - ``` - - `status` reports three separate facts: the EL operator, beacon inclusion, and the pool's `isActive` flag. See [`status`](#status) for what each one means. - - `isActive=false` is the expected value after deploy. It does not mean "run activate now." - - Wait until `status` says the beacon has this validator and that the pool is ready for activation. Then go to the next step. - - Set `CL_NODE_API_URL` to a Node API that can serve validator state and proofs. - + + The CLI shows the whole plan and asks once: - - ```bash - node pool-cli.mjs activate + ``` + Network: bepolia + Validator pubkey: 0x93be...a1 + Predicted pool: 0x6dE5...0755 + Deposit: 10,000 BERA + Additional stake: 240,000 BERA (funding wallet holds enough) + Operator: 0x1968...9e (defaulted to funding wallet) + Shares recipient: 0x1968...9e (defaulted to funding wallet) + + Proceed? [y/N] ``` - Activation is a permissionless call keyed by the validator pubkey. You do not pass `--op` or `--sr`. - - `activate` fetches beacon-chain proofs a few slots behind head, checks them against the pool's WithdrawalVault, and preflights `activateStakingPool`. See [`activate`](#activate) for the full sequence. + If the funding wallet can't cover the full deposit, the CLI says so here and stops — it does not partially deploy. If it can cover the deposit but not the additional stake, it says so and proceeds with deposit and activation only; run `stake` yourself later once more funds arrive. - Proofs expire 10 minutes after their timestamp. Copy the printed command to the signing machine before then. If the window expires, run `activate` again to fetch new proofs. Re-running `activate` is always safe: an already-active pool reports "Pool is already activated" and does nothing. + This is the only question in hot-key mode. Everything past this point runs without asking again. - - ```bash - node pool-cli.mjs status - ``` + + Deploys the pool contracts and the 10,000 BERA deposit. - You should see SmartOperator, StakingPool, StakingRewardsVault, and IncentiveCollector addresses. You should also see operator match, `isActive=true`, and pool telemetry. + - Hot-key mode: broadcasts immediately. + - Cold-signing mode: prints a `cast send` for you to run on your signing machine, then waits and watches the chain for it to land. Nothing else happens until it does. - - ```bash - node pool-cli.mjs stake --amount 100 --receiver 0xRECEIVER - ``` - - This calls `submit(address)` and sends `--amount` BERA. `--receiver` gets the stBERA. Copy the printed `cast send` to the signing machine. + + The CLI polls the beacon chain on its own. This can take a while — there's nothing to run here, and nothing to watch unless you pass `--verbose`. If you kill the process now, re-running `node pool-cli.mjs install` picks up exactly here. - - Unstake is two transactions. The request mints a withdrawal NFT. After the on-chain delay, you finalize it. - - ```bash - node pool-cli.mjs unstake --amount 100 --from 0xHOLDER - node pool-cli.mjs unstake --shares 50 --from 0xHOLDER - node pool-cli.mjs unstake --finalize REQUEST_ID --from 0xHOLDER - ``` - - Pass exactly one of `--amount`, `--shares`, or `--finalize`. `--from` is the address that holds stBERA. Copy each printed `cast send` to the signing machine. - - The [sample frontend](https://github.com/berachain/guides/tree/main/apps/staking-pools/frontend) remains available. See [Building your front-end](/nodes/staking-pools/operators#building-your-front-end). + + Once the beacon chain includes the validator, the CLI fetches activation proofs and either broadcasts (hot-key) or prints the `cast send` and waits for it to land (cold-signing). Proofs expire 10 minutes after they're fetched; if a cold-signing operator takes longer than that to run the printed command, the CLI notices, fetches a fresh proof, and prints a new command — you don't have to ask for it. - - - `minEffectiveBalance` controls when the pool activates the validator on the consensus layer. - If you set this value incorrectly, the pool can take deposits and never activate. - - - ```bash - node pool-cli.mjs set-min-balance - ``` - - The default amount is 250,000 BERA. Pass `--amount` to use another value. Copy the printed `cast send` to the signing machine. - - See [Setting Minimum Effective Balance](/nodes/staking-pools/operators#setting-minimum-effective-balance) in the operator guide. + + If step 2's plan included additional stake, the CLI deposits it the same way (broadcast or print-and-wait) and mints stBERA to the shares recipient. If it didn't, the installer is done after activation — add stake later with `node pool-cli.mjs stake`. -## Example session - -This section shows one full run on a Bepolia validator. The addresses, chain values, and figures are real. Most command output is not shown. Only the lines that show success or failure stay in. +## Output -### Install and configure - -```bash -git clone https://github.com/berachain/guides.git -cd guides/apps/staking-pools/install-helpers - -curl -L https://getfoundry.sh/install | bash -export PATH="$PATH:$HOME/.foundry/bin" -foundryup -``` - -`foundryup` reports the installed version: +Default output is a handful of lines — one per milestone, nothing you need to act on unless it stops and asks: ``` -foundryup: cast verified ✓ -foundryup: done! +Deployed. Predicted pool: 0x6dE5...0755 +Waiting for validator registration... +Registered (index 36). +Activated. +Staked 240,000 BERA. +Done. ``` -Set the environment and run the test suite once to confirm the CLI works on this host: +Pass `--verbose` for the detail behind each line — transaction hashes, RPC calls, pinned slots, proof timestamps. Use it when something needs debugging; leave it off otherwise. -```bash -cp env.sh.template env.sh -export BEACOND_HOME=/path/to/your/beacond/home -export CLI_CHAIN=bepolia -export EL_RPC_URL=http://127.0.0.1:8545 +## Resuming -node --test test/*.test.mjs -``` +`node pool-cli.mjs install` is safe to re-run at any point. It doesn't keep a run file — every time it starts, it reads the chain and `beacond` to work out which of deploy / wait / activate / stake is next, and continues from there. Re-running after the pool is already active and staked reports done and exits; it does not repeat a step. -All tests pass. +## Other commands -### Deploy +Available on their own, independent of `install` — the same standalone commands the CLI has always had, useful for advanced or manual use, debugging, or picking up a single step without running the whole installer: -```bash -node pool-cli.mjs deploy --op 0x196802FFa2c463CADa5eb8d8F63F265610b574e9 --sr 0x196802FFa2c463CADa5eb8d8F63F265610b574e9 -``` - -``` -[success] Deposit validation: OK -[success] Preflight OK -[info] Copy and run on your signing machine (ledger on laptop): -``` - -Copy the printed `cast send` and run it on the signing machine. Predicted addresses for this pubkey: SmartOperator `0x926EcD8E8ea200C2c66A26e031b3b112f96aD0De`, StakingPool `0x6dE5C7989cCAC160b47e7c8b015C87be4b540755`. - -### Wait for beacon inclusion - -Right after deploy, `status` reports that the beacon chain does not have the validator yet: - -``` -[info] Beacon: not in head state (queried http://127.0.0.1:3500) -[info] EL operator is registered. Beacon head state does not include this validator yet. -[info] Next: Wait until the beacon chain includes the validator. Re-run status. Do not run activate yet. -``` - -Re-run `status` until it reports: - -``` -[success] Beacon has this validator (index 36, status pending_initialized). Ready for activation. -[info] Next: Run: node pool-cli.mjs activate -``` - -### Activate +| Command | Action | +| --- | --- | +| `deploy` | Just the deploy step (`install` calls this internally) | +| `activate` | Just the activate step (`install` calls this internally) | +| `set-min-balance` | Set `minEffectiveBalance` manually. Not part of `install`'s plan or confirmation — see the note below. | +| `status` | Read EL operator, beacon inclusion, and pool `isActive`, plus telemetry once active | +| `stake` | Deposit BERA and mint stBERA to `--receiver`, any time after activation | +| `unstake` | Request or finalize a withdrawal | ```bash -node pool-cli.mjs activate -``` - -`activate` pins a slot 5 blocks behind head, so the execution-layer block the proof needs already exists: - -``` -[success] All proofs pinned to slot 24528070 -[success] EIP-4788 timestamp: 1787927174 -[success] Preflight OK — activateStakingPool would succeed at current head -[info] Copy and run on your signing machine (ledger on laptop): +node pool-cli.mjs status +node pool-cli.mjs stake --amount 100 --receiver 0xRECEIVER +node pool-cli.mjs unstake --amount 100 --from 0xHOLDER +node pool-cli.mjs unstake --finalize 42 --from 0xHOLDER ``` -Copy the printed command to the signing machine and run it there. +`unstake` is always two transactions — request, then finalize after the on-chain delay — and always asks you to sign both separately (hot-key or cold-signing); there's no hands-off installer for it, since it's something a holder decides to do on their own schedule, not part of installation. - On the validator host, set `PRIVATE_KEY` and append `--execute` to broadcast directly instead: - `node pool-cli.mjs activate --execute`. Every state-changing command accepts `--execute`. + `minEffectiveBalance` (the threshold that determines when your pool activates the validator on + the consensus layer) is not part of `install`'s plan, confirmation, or automatic behavior — run + `set-min-balance` yourself when you need it. See + [Setting Minimum Effective Balance](/nodes/staking-pools/operators#setting-minimum-effective-balance) + in the operator guide for why it matters and how to calculate a value. -### Check status - -```bash -node pool-cli.mjs status -``` - -``` -[info] EL operator: registered (0x926ecd8e8ea200c2c66a26e031b3b112f96ad0de) -[info] Beacon: index 36, status pending_initialized -[info] Pool isActive: true (factory activateStakingPool has been called) -[success] Staking pool is ACTIVE -``` - -### Stake more BERA - -A first attempt without `--amount` shows the CLI's input checks. They run before any call to the chain: - -```bash -node pool-cli.mjs stake --receiver 0x196802FFa2c463CADa5eb8d8F63F265610b574e9 -``` - -``` -[error] --amount must be a positive number -``` - -Add `--amount` and run again, this time with `--execute` to broadcast from the validator host: - -```bash -node pool-cli.mjs stake --receiver 0x196802FFa2c463CADa5eb8d8F63F265610b574e9 --amount 240000 --execute -``` - -``` -[success] Preflight OK — submit(0x196802ffa2c463cada5eb8d8f63f265610b574e9) value 240000ether -[info] Broadcasting submit via cast send (hot key on validator)... -[success] submit broadcast: 0xa3013dea6717d5896e4d67c1d3e0c76460f724fdbcf8e46edd0f55acc6f4a911 -``` - -### Final status - -```bash -node pool-cli.mjs status -``` - -``` -[success] Staking pool is ACTIVE - Total assets (BERA): 250000.000000000000000000 - Total supply (stBERA): 250000.000000000000000000 -``` - -10,000 BERA came from the deploy step. 240,000 BERA came from the stake step. The total matches. - -## Command reference - -Entry point: `node pool-cli.mjs [options]`. Every state-changing command dry-runs with `cast call` first, then prints a `cast send` command. It does not broadcast unless you pass `--execute`. - -### Global options - -| Option | Effect | -| --- | --- | -| `--execute` | Broadcast from the validator host. Requires `PRIVATE_KEY`. Without it, `--execute` is refused and the CLI still prints the `cast send` command. | -| `--help` | Show command help. | - -### Environment variables +## Environment variables | Variable | Purpose | | --- | --- | @@ -309,112 +154,27 @@ Entry point: `node pool-cli.mjs [options]`. Every state-changing comma | `CLI_CHAIN` | `mainnet` or `bepolia`. If unset, the CLI detects the network from genesis. | | `RPC_URL` / `EL_RPC_URL` | Override the execution RPC. Default: the public Berachain endpoint for the detected chain. | | `CL_NODE_API_URL` / `NODE_API_ADDRESS` | Beacon Node API for validator state and proofs. Default: `http://127.0.0.1:3500`. | -| `PRIVATE_KEY` | Hot key on the validator host. Required for `--execute`. | +| `PRIVATE_KEY` | Hot key on the validator host. Set it to run in hot-key mode; leave it unset for cold-signing mode. | - Do not set `CHAIN` in the environment. Foundry `cast` reads `CHAIN` as its own flag and rejects - values like `bepolia`. Use `CLI_CHAIN` instead. + Do not set `CHAIN` in the environment. Foundry `cast` (used only on the signing machine in + cold-signing mode) reads `CHAIN` as its own flag and rejects values like `bepolia`. Use + `CLI_CHAIN` instead. -### `deploy` - -```bash -node pool-cli.mjs deploy --op 0xOPERATOR --sr 0xSHARES_RECIPIENT -node pool-cli.mjs deploy --op 0xOPERATOR --sr 0xSHARES_RECIPIENT --execute -``` - -| Option | Value | Required | -| --- | --- | --- | -| `--op` | Operator address for the pool | Yes | -| `--sr` | Shares-recipient address for the pool | Yes | - -`deploy` validates the deposit with `beacond`, predicts the four pool contract addresses, and preflights `deployStakingPoolContracts`. The deposit is fixed at 10,000 BERA. - -### `activate` - -```bash -node pool-cli.mjs activate -node pool-cli.mjs activate --execute -``` - -Activation takes no options besides `--execute`. The pubkey, operator, and shares recipient are already fixed by `deploy`. - -`activate` pins a beacon-chain slot 5 blocks behind head, so the paired execution-layer block already exists. The consensus and execution clients stay in lockstep on Berachain, so this margin should never run out in normal operation. `activate` fetches the pubkey, withdrawal-credentials, and balance proofs at that slot, checks them against the pool's WithdrawalVault, and preflights `activateStakingPool`. The proof expires 10 minutes after its timestamp. The CLI refuses to print `cast send` past that window. - -### `status` - -```bash -node pool-cli.mjs status -``` - -`status` takes no options. It reports three separate facts: - -1. **EL operator**: whether the beacon deposit contract's operator matches this pool. -2. **Beacon inclusion**: whether the beacon chain has this validator, and its index and status. -3. **Pool `isActive`**: whether `activateStakingPool` has been called. - -Once the pool is active, `status` also prints contract addresses, total assets and supply, buffered assets, the minimum effective balance, and WBERA and legacy BGT balances. If `PRIVATE_KEY` is set, it also prints that wallet's stBERA shares and withdrawal-NFT count. - -### `stake` - -```bash -node pool-cli.mjs stake --amount 100 --receiver 0xRECEIVER -node pool-cli.mjs stake --amount 100 --receiver 0xRECEIVER --execute -``` - -| Option | Value | Required | -| --- | --- | --- | -| `--amount` | BERA to deposit | Yes | -| `--receiver` | Address that gets the minted stBERA | Yes | -| `--from` | Address the dry-run simulates from | No. Default: `--receiver`. | -| `--staking-pool` | Pool address | No. Default: looked up from the validator pubkey. | - -`stake` calls `StakingPool.submit(address)` with `--amount` as the transaction value. - -### `unstake` - -```bash -node pool-cli.mjs unstake --amount 100 --from 0xHOLDER -node pool-cli.mjs unstake --shares 50 --from 0xHOLDER -node pool-cli.mjs unstake --finalize 42 --from 0xHOLDER -``` - -| Option | Value | Required | -| --- | --- | --- | -| `--amount` | BERA to withdraw, as assets | Exactly one of `--amount`, `--shares`, `--finalize` | -| `--shares` | stBERA to redeem, as shares | Exactly one of `--amount`, `--shares`, `--finalize` | -| `--finalize` | Request ID to finalize | Exactly one of `--amount`, `--shares`, `--finalize` | -| `--from` | stBERA holder | No. Default: derived from `PRIVATE_KEY`. `--receiver` works as an alias. | -| `--staking-pool` | Pool address | No. Default: looked up from the validator pubkey. | -| `--max-fee` | EIP-7002 fee, in BERA | No. Default: the CLI probes candidate fees up to 0.01 BERA. | - -Withdrawal is two transactions. `--amount` calls `requestWithdrawal`. `--shares` calls `requestRedeem`. Either one mints a withdrawal NFT and returns a request ID. After the on-chain delay, `--finalize` calls `finalizeWithdrawalRequest` to release the funds. The pool must already be active. - -### `set-min-balance` - -```bash -node pool-cli.mjs set-min-balance -node pool-cli.mjs set-min-balance --amount 300000 --execute -``` - -| Option | Value | Required | -| --- | --- | --- | -| `--amount` | Minimum effective balance, in BERA | No. Default: 250,000 BERA. | +## Delegated pools -This command is optional. See [Setting Minimum Effective Balance](/nodes/staking-pools/operators#setting-minimum-effective-balance) for why the value matters and how to calculate it. +A Foundation-delegated pool (funds delegated by someone else, installed by an operator holding `VALIDATOR_ADMIN_ROLE`) follows the same shape — deploy, wait, activate, deposit — against the `DelegationHandler` instead of your own funding wallet. See [Delegation guide](/nodes/staking-pools/delegators) for how a delegation gets set up; whether it's driven by this same `install` command or a dedicated one depends on what's shipped when you read this — check the [install-helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md) for current command names. ## Troubleshooting - **`BEACOND_HOME` is required.** Set it to the beacond data directory on this validator host. -- **`cast` rejects `bepolia`.** Unset `CHAIN`. Set `CLI_CHAIN` if you need to force the network. -- **Node API not reachable.** Enable `[beacon-kit.node-api]` or set `CL_NODE_API_URL`. -- **Pool contracts are not deployed.** The deploy `cast send` has not landed for this pubkey. `isActive` is not available yet. Confirm the transaction hash on the explorer. -- **Beacon head state does not include this validator.** The EL deposit can be in while the beacon chain has not included the validator. Wait. Re-run `status`. Do not run `activate` yet. -- **Ready for activation.** `status` shows a beacon index and CL `status`. The pool `isActive` is still false. Run `activate`. -- **`activate` cannot read an EL block.** `activate` pins a slot 5 blocks behind head, so this should not happen on a healthy node. If it does, the execution client is unusually far behind. Wait and re-run `activate`. -- **Pool is already activated.** `activate` is safe to re-run. Once active, it reports this and takes no action. -- **Missing tools.** Install Node.js 22+, Foundry `cast`, and `beacond`. Put them on `PATH`. -- **`--execute` refused.** Set `PRIVATE_KEY` on the validator host, or run the printed `cast send` on the signing machine. +- **Funding wallet can't cover the deposit.** The installer stops before deploying. Fund the wallet (hot-key mode: the `PRIVATE_KEY` address; cold-signing mode: `--funding-address`) and re-run. +- **`cast` rejects `bepolia`.** Unset `CHAIN` on the signing machine. Set `CLI_CHAIN` on the validator host if you need to force the network. +- **Node API not reachable.** Enable `[beacon-kit.node-api]` in `app.toml`, or set `CL_NODE_API_URL`. +- **A printed `cast send` expired before you ran it.** Only possible for `activate` (10-minute proof window). Re-run `node pool-cli.mjs install`; it fetches a fresh proof and prints a new command. +- **A printed `cast send` failed or was rejected on the signing machine.** Re-run `node pool-cli.mjs install`. It checks the chain, sees nothing landed, and reprints the same command (or a refreshed one, for `activate`, if the proof also expired in the meantime) rather than assuming it succeeded. +- **Missing tools.** Validator host needs Node.js 22+ and `beacond`. The signing machine (cold-signing mode only) needs Foundry `cast`. ## What's next From c6def4195cae164cbc03e908f510ae1c84bd5251 Mon Sep 17 00:00:00 2001 From: Camembear Date: Mon, 31 Aug 2026 09:29:39 -0400 Subject: [PATCH 05/20] Simplify installation.mdx: drop reference tables and troubleshooting --- nodes/staking-pools/installation.mdx | 132 ++++----------------------- 1 file changed, 20 insertions(+), 112 deletions(-) diff --git a/nodes/staking-pools/installation.mdx b/nodes/staking-pools/installation.mdx index f47d08e..1e601a0 100644 --- a/nodes/staking-pools/installation.mdx +++ b/nodes/staking-pools/installation.mdx @@ -3,12 +3,12 @@ title: "Staking Pool Installation" description: "Install and activate a staking pool with one command. The CLI runs on the validator host, checks funding, and either finishes the job itself or hands you exactly the cast commands it can't run for you." --- -Run the CLI on the **validator host** — the machine running `beacond`, with `BEACOND_HOME` set. There are two ways to run it, chosen by whether you give it a private key: +Run the CLI on the **validator host** — the machine running `beacond`, with `BEACOND_HOME` set. -- **Hot-key mode** (`PRIVATE_KEY` set): the CLI signs and broadcasts every transaction itself. No Foundry, no `cast`, no `npm install`. You confirm the plan once; everything else is hands-off. -- **Cold-signing mode** (no `PRIVATE_KEY`): the CLI never signs anything. It reads chain state itself, tells you exactly what it wants to do, and prints a `cast send` command for each transaction that genuinely needs a new signature. You run those on a separate machine — a laptop with a Ledger, or with your own key — while the CLI keeps watching the chain and moves on as soon as each one lands. +- **Hot-key mode** (`PRIVATE_KEY` set): the CLI signs and broadcasts everything itself. No Foundry, no `cast`, no `npm install`. One confirmation, then hands-off. +- **Cold-signing mode** (no `PRIVATE_KEY`): the CLI never signs anything. It prints a `cast send` for each transaction that needs a new signature, for you to run on a separate signing machine, and watches the chain for it to land. -Both modes run the same installer, watch for the same on-chain facts, and are safe to kill and re-run at any point — the CLI figures out where you left off by reading the chain, not by remembering a file. +Either way: kill it and re-run it any time. It reads the chain to know where it left off — it doesn't remember a file. ## Get the CLI @@ -17,21 +17,16 @@ git clone https://github.com/berachain/guides/ cd guides/apps/staking-pools/install-helpers ``` -Requires Node.js 22+ and `beacond` on the validator host. Nothing else — the CLI carries its own signing and chain-reading code; it does not call out to `cast`, `forge`, or any package manager at runtime. - -Cold-signing mode also needs a **signing machine**: any computer with `cast` installed, holding either a Ledger or a private key of your own. That machine never needs to touch the validator host. +Requires Node.js 22+ and `beacond` on the validator host — nothing else. Cold-signing mode also needs a signing machine with `cast` and a Ledger or your own key. ## Requirements -- At least 10,000 BERA for the initial deposit (the fixed consensus-layer deposit amount). More if you also want to stake past that. -- `beacond`, synced, with validator keys backed up. -- Hot-key mode: `PRIVATE_KEY` on the validator host, funded with the BERA above. -- Cold-signing mode: the address that will hold the funds (so the CLI can check its balance), and a signing machine with `cast`. +- At least 10,000 BERA for the deposit, more if you're staking past that. +- `beacond`, synced, validator keys backed up. - `PRIVATE_KEY` on the validator host is a hot key. Only set it if you're comfortable with that - key funding transactions directly from this machine. Otherwise, leave it unset and sign from a - separate machine. + `PRIVATE_KEY` on the validator host is a hot key. Leave it unset to sign from a separate + machine instead. ## Run it @@ -44,62 +39,27 @@ source env.sh node pool-cli.mjs install ``` -The installer asks for whatever it can't determine on its own, then confirms the whole plan before touching anything. - - - It reads your validator pubkey from `beacond`, detects the network from genesis, and checks the funding wallet's balance. - - - Hot-key mode: the funding wallet is the address behind `PRIVATE_KEY`. - - Cold-signing mode: pass `--funding-address 0x...`, or the CLI asks for it. It also asks once, up front, whether you're signing with a Ledger (press Enter) or your own key (type `key`) — this only changes the wording of the commands it prints later; it never reads a key itself. - - If `--operator` / `--shares-recipient` are omitted, both default to the funding wallet's address. - - - The CLI shows the whole plan and asks once: + The CLI reads your pubkey, network, and funding balance, then shows the plan once: ``` Network: bepolia - Validator pubkey: 0x93be...a1 - Predicted pool: 0x6dE5...0755 Deposit: 10,000 BERA Additional stake: 240,000 BERA (funding wallet holds enough) - Operator: 0x1968...9e (defaulted to funding wallet) - Shares recipient: 0x1968...9e (defaulted to funding wallet) - Proceed? [y/N] ``` - If the funding wallet can't cover the full deposit, the CLI says so here and stops — it does not partially deploy. If it can cover the deposit but not the additional stake, it says so and proceeds with deposit and activation only; run `stake` yourself later once more funds arrive. - - This is the only question in hot-key mode. Everything past this point runs without asking again. - - - - Deploys the pool contracts and the 10,000 BERA deposit. - - - Hot-key mode: broadcasts immediately. - - Cold-signing mode: prints a `cast send` for you to run on your signing machine, then waits and watches the chain for it to land. Nothing else happens until it does. - - - - The CLI polls the beacon chain on its own. This can take a while — there's nothing to run here, and nothing to watch unless you pass `--verbose`. If you kill the process now, re-running `node pool-cli.mjs install` picks up exactly here. - - - - Once the beacon chain includes the validator, the CLI fetches activation proofs and either broadcasts (hot-key) or prints the `cast send` and waits for it to land (cold-signing). Proofs expire 10 minutes after they're fetched; if a cold-signing operator takes longer than that to run the printed command, the CLI notices, fetches a fresh proof, and prints a new command — you don't have to ask for it. + Not enough for the deposit → it stops here, nothing deployed. Enough for the deposit but not the stake → it proceeds through activation and leaves staking for later. This is the only question in hot-key mode. - - If step 2's plan included additional stake, the CLI deposits it the same way (broadcast or print-and-wait) and mints stBERA to the shares recipient. If it didn't, the installer is done after activation — add stake later with `node pool-cli.mjs stake`. + + Deploys the pool, waits for beacon-chain registration, activates, and stakes if funded — broadcasting itself (hot-key) or printing each `cast send` and waiting for it to land (cold-signing). Nothing to do here except wait, or run the printed commands. ## Output -Default output is a handful of lines — one per milestone, nothing you need to act on unless it stops and asks: - ``` Deployed. Predicted pool: 0x6dE5...0755 Waiting for validator registration... @@ -109,74 +69,22 @@ Staked 240,000 BERA. Done. ``` -Pass `--verbose` for the detail behind each line — transaction hashes, RPC calls, pinned slots, proof timestamps. Use it when something needs debugging; leave it off otherwise. - -## Resuming - -`node pool-cli.mjs install` is safe to re-run at any point. It doesn't keep a run file — every time it starts, it reads the chain and `beacond` to work out which of deploy / wait / activate / stake is next, and continues from there. Re-running after the pool is already active and staked reports done and exits; it does not repeat a step. - -## Other commands +That's the whole default output. Pass `--verbose` for tx hashes and RPC detail when debugging. -Available on their own, independent of `install` — the same standalone commands the CLI has always had, useful for advanced or manual use, debugging, or picking up a single step without running the whole installer: - -| Command | Action | -| --- | --- | -| `deploy` | Just the deploy step (`install` calls this internally) | -| `activate` | Just the activate step (`install` calls this internally) | -| `set-min-balance` | Set `minEffectiveBalance` manually. Not part of `install`'s plan or confirmation — see the note below. | -| `status` | Read EL operator, beacon inclusion, and pool `isActive`, plus telemetry once active | -| `stake` | Deposit BERA and mint stBERA to `--receiver`, any time after activation | -| `unstake` | Request or finalize a withdrawal | - -```bash -node pool-cli.mjs status -node pool-cli.mjs stake --amount 100 --receiver 0xRECEIVER -node pool-cli.mjs unstake --amount 100 --from 0xHOLDER -node pool-cli.mjs unstake --finalize 42 --from 0xHOLDER -``` +## Everything else -`unstake` is always two transactions — request, then finalize after the on-chain delay — and always asks you to sign both separately (hot-key or cold-signing); there's no hands-off installer for it, since it's something a holder decides to do on their own schedule, not part of installation. +`status`, `stake`, `unstake`, `deploy`, `activate`, and `set-min-balance` remain available as standalone commands — run `node pool-cli.mjs --help` or see the [install-helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md) for the full command and environment-variable reference, plus troubleshooting. - `minEffectiveBalance` (the threshold that determines when your pool activates the validator on - the consensus layer) is not part of `install`'s plan, confirmation, or automatic behavior — run - `set-min-balance` yourself when you need it. See - [Setting Minimum Effective Balance](/nodes/staking-pools/operators#setting-minimum-effective-balance) - in the operator guide for why it matters and how to calculate a value. + `set-min-balance` is not part of `install` — run it yourself. See + [Setting Minimum Effective Balance](/nodes/staking-pools/operators#setting-minimum-effective-balance). -## Environment variables - -| Variable | Purpose | -| --- | --- | -| `BEACOND_HOME` | beacond data directory. Required for every command. | -| `BEACOND_BIN` | Override the beacond binary. Default: `beacond` on `PATH`. | -| `CLI_CHAIN` | `mainnet` or `bepolia`. If unset, the CLI detects the network from genesis. | -| `RPC_URL` / `EL_RPC_URL` | Override the execution RPC. Default: the public Berachain endpoint for the detected chain. | -| `CL_NODE_API_URL` / `NODE_API_ADDRESS` | Beacon Node API for validator state and proofs. Default: `http://127.0.0.1:3500`. | -| `PRIVATE_KEY` | Hot key on the validator host. Set it to run in hot-key mode; leave it unset for cold-signing mode. | - - - Do not set `CHAIN` in the environment. Foundry `cast` (used only on the signing machine in - cold-signing mode) reads `CHAIN` as its own flag and rejects values like `bepolia`. Use - `CLI_CHAIN` instead. - - ## Delegated pools -A Foundation-delegated pool (funds delegated by someone else, installed by an operator holding `VALIDATOR_ADMIN_ROLE`) follows the same shape — deploy, wait, activate, deposit — against the `DelegationHandler` instead of your own funding wallet. See [Delegation guide](/nodes/staking-pools/delegators) for how a delegation gets set up; whether it's driven by this same `install` command or a dedicated one depends on what's shipped when you read this — check the [install-helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md) for current command names. - -## Troubleshooting - -- **`BEACOND_HOME` is required.** Set it to the beacond data directory on this validator host. -- **Funding wallet can't cover the deposit.** The installer stops before deploying. Fund the wallet (hot-key mode: the `PRIVATE_KEY` address; cold-signing mode: `--funding-address`) and re-run. -- **`cast` rejects `bepolia`.** Unset `CHAIN` on the signing machine. Set `CLI_CHAIN` on the validator host if you need to force the network. -- **Node API not reachable.** Enable `[beacon-kit.node-api]` in `app.toml`, or set `CL_NODE_API_URL`. -- **A printed `cast send` expired before you ran it.** Only possible for `activate` (10-minute proof window). Re-run `node pool-cli.mjs install`; it fetches a fresh proof and prints a new command. -- **A printed `cast send` failed or was rejected on the signing machine.** Re-run `node pool-cli.mjs install`. It checks the chain, sees nothing landed, and reprints the same command (or a refreshed one, for `activate`, if the proof also expired in the meantime) rather than assuming it succeeded. -- **Missing tools.** Validator host needs Node.js 22+ and `beacond`. The signing machine (cold-signing mode only) needs Foundry `cast`. +A Foundation-delegated pool follows the same shape against the `DelegationHandler` instead of your own wallet. See the [Delegation guide](/nodes/staking-pools/delegators) and the README for current command names. ## What's next - [Contract reference](/nodes/staking-pools/contracts) describes the contracts you deployed. -- [Delegation guide](/nodes/staking-pools/delegators) describes a Foundation delegation. That flow is similar. +- [Delegation guide](/nodes/staking-pools/delegators) describes a Foundation delegation. From c5f53af50ab5b2dd62df86c40e403052d17426f7 Mon Sep 17 00:00:00 2001 From: Camembear Date: Mon, 31 Aug 2026 12:33:21 -0400 Subject: [PATCH 06/20] Note that cold-signing mode never asks for a signing preference installation.mdx described a signing-preference prompt (Ledger vs private key) that the CLI never actually reads a key for either way; clarify it defaults to --ledger and is a flag/env override only. --- nodes/staking-pools/installation.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodes/staking-pools/installation.mdx b/nodes/staking-pools/installation.mdx index 1e601a0..161accb 100644 --- a/nodes/staking-pools/installation.mdx +++ b/nodes/staking-pools/installation.mdx @@ -17,7 +17,7 @@ git clone https://github.com/berachain/guides/ cd guides/apps/staking-pools/install-helpers ``` -Requires Node.js 22+ and `beacond` on the validator host — nothing else. Cold-signing mode also needs a signing machine with `cast` and a Ledger or your own key. +Requires Node.js 22+ and `beacond` on the validator host — nothing else. Cold-signing mode also needs a signing machine with `cast` and a Ledger or your own key. The CLI never asks which one you'll use — printed commands default to `--ledger`; pass `--signing-preference key` if you'd rather see your own key in the printed command. ## Requirements From 66c7d3d8ad5915bd3f8e23eeeb93b53962dda14a Mon Sep 17 00:00:00 2001 From: Camembear Date: Mon, 31 Aug 2026 12:56:35 -0400 Subject: [PATCH 07/20] Document finalize-all as unstake --finalize's no-id default --finalize with no id now finalizes every ready withdrawal request in one batch transaction; --finalize still finalizes just that one. --- nodes/staking-pools/installation.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nodes/staking-pools/installation.mdx b/nodes/staking-pools/installation.mdx index 161accb..3935914 100644 --- a/nodes/staking-pools/installation.mdx +++ b/nodes/staking-pools/installation.mdx @@ -75,6 +75,8 @@ That's the whole default output. Pass `--verbose` for tx hashes and RPC detail w `status`, `stake`, `unstake`, `deploy`, `activate`, and `set-min-balance` remain available as standalone commands — run `node pool-cli.mjs --help` or see the [install-helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md) for the full command and environment-variable reference, plus troubleshooting. +`unstake --finalize --from 0xHOLDER` (no id) finalizes every withdrawal request that's past the finalization delay in one batch transaction; `unstake --finalize 42 --from 0xHOLDER` finalizes just request `42`. + `set-min-balance` is not part of `install` — run it yourself. See [Setting Minimum Effective Balance](/nodes/staking-pools/operators#setting-minimum-effective-balance). From ff7da10ec651129eb0f90fd4f611812e01b8ac19 Mon Sep 17 00:00:00 2001 From: Camembear Date: Mon, 31 Aug 2026 13:06:09 -0400 Subject: [PATCH 08/20] Document the scenario-file identity fallback for standalone commands status/activate/set-min-balance/stake/unstake now read install's scenario file so BEACOND_HOME isn't needed to run them afterward on a remote validator; deploy is the one exception and is unaffected. --- nodes/staking-pools/installation.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nodes/staking-pools/installation.mdx b/nodes/staking-pools/installation.mdx index 3935914..d76912f 100644 --- a/nodes/staking-pools/installation.mdx +++ b/nodes/staking-pools/installation.mdx @@ -75,6 +75,8 @@ That's the whole default output. Pass `--verbose` for tx hashes and RPC detail w `status`, `stake`, `unstake`, `deploy`, `activate`, and `set-min-balance` remain available as standalone commands — run `node pool-cli.mjs --help` or see the [install-helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md) for the full command and environment-variable reference, plus troubleshooting. +`status`, `activate`, `set-min-balance`, `stake`, and `unstake` read the scenario file `install` already wrote in the working directory, so you don't need `BEACOND_HOME` on a remote validator to run them afterward — `--chain`/`--pubkey` still override it when passed. `deploy` is the one exception: it needs the deposit's `credentials`/`signature`/`amount`, which the scenario file deliberately doesn't carry, so it still requires a local validator or a full `--deposit`. + `unstake --finalize --from 0xHOLDER` (no id) finalizes every withdrawal request that's past the finalization delay in one batch transaction; `unstake --finalize 42 --from 0xHOLDER` finalizes just request `42`. From 90bbaef4cf0b9193099c6641daa1c72808d61407 Mon Sep 17 00:00:00 2001 From: Camembear Date: Tue, 1 Sep 2026 12:59:29 -0400 Subject: [PATCH 09/20] Document the four-command zero-flag staking-pool operator CLI. Drop env.sh.template copy-and-edit and the deleted standalone verbs. --- nodes/staking-pools/installation.mdx | 40 ++++++++++++---------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/nodes/staking-pools/installation.mdx b/nodes/staking-pools/installation.mdx index d76912f..49ef474 100644 --- a/nodes/staking-pools/installation.mdx +++ b/nodes/staking-pools/installation.mdx @@ -1,14 +1,15 @@ --- title: "Staking Pool Installation" -description: "Install and activate a staking pool with one command. The CLI runs on the validator host, checks funding, and either finishes the job itself or hands you exactly the cast commands it can't run for you." +description: "Install and activate a staking pool with one command. The CLI asks only for facts nothing can detect, then finishes the job or prints the cast commands it cannot run." --- -Run the CLI on the **validator host** — the machine running `beacond`, with `BEACOND_HOME` set. +Run the CLI on the **validator host** for S1/S2 (`BEACOND_HOME` set), or anywhere else for S3 (`BEACOND_HOME` unset). -- **Hot-key mode** (`PRIVATE_KEY` set): the CLI signs and broadcasts everything itself. No Foundry, no `cast`, no `npm install`. One confirmation, then hands-off. -- **Cold-signing mode** (no `PRIVATE_KEY`): the CLI never signs anything. It prints a `cast send` for each transaction that needs a new signature, for you to run on a separate signing machine, and watches the chain for it to land. +- **S1 hot-key** (`PRIVATE_KEY` and `BEACOND_HOME` set): the CLI signs and broadcasts everything itself. One confirmation, then hands-off. +- **S2 cold-signing** (`BEACOND_HOME` set, no `PRIVATE_KEY`): the CLI prints a `cast send` for each transaction, for you to run on a separate signing machine, and watches the chain for it to land. +- **S3 remote** (`BEACOND_HOME` unset): the CLI does not need `beacond` on this host. Paste a deposit signature; it verifies the pasted pubkey against the resolved identity pubkey. -Either way: kill it and re-run it any time. It reads the chain to know where it left off — it doesn't remember a file. +Either way: kill it and re-run `install` any time. It reads the chain to know where it left off. There is no standalone `activate`. ## Get the CLI @@ -17,12 +18,12 @@ git clone https://github.com/berachain/guides/ cd guides/apps/staking-pools/install-helpers ``` -Requires Node.js 22+ and `beacond` on the validator host — nothing else. Cold-signing mode also needs a signing machine with `cast` and a Ledger or your own key. The CLI never asks which one you'll use — printed commands default to `--ledger`; pass `--signing-preference key` if you'd rather see your own key in the printed command. +Requires Node.js 22+. `beacond` is required on the validator host (S1/S2) only. ## Requirements - At least 10,000 BERA for the deposit, more if you're staking past that. -- `beacond`, synced, validator keys backed up. +- For S1/S2: `beacond`, synced, validator keys backed up. `PRIVATE_KEY` on the validator host is a hot key. Leave it unset to sign from a separate @@ -31,17 +32,19 @@ Requires Node.js 22+ and `beacond` on the validator host — nothing else. Cold- ## Run it +Export the facts you already have in this shell. Do not copy an `env.sh.template` — pool-cli does not read or write `env.sh`. + ```bash -cp env.sh.template env.sh -# Set BEACOND_HOME. Optionally set PRIVATE_KEY for hot-key mode. -source env.sh +export BEACOND_HOME="/path/to/beacond/home" +export CLI_CHAIN=bepolia +# export PRIVATE_KEY=0x... # S1 only node pool-cli.mjs install ``` - The CLI reads your pubkey, network, and funding balance, then shows the plan once: + The CLI reads your pubkey, network, and funding balance, then shows the plan once. The additional-stake suggestion is rounded down to the nearest 1,000 BERA. Type a different whole-BERA amount at the prompt to override it. ``` Network: bepolia @@ -50,11 +53,11 @@ node pool-cli.mjs install Proceed? [y/N] ``` - Not enough for the deposit → it stops here, nothing deployed. Enough for the deposit but not the stake → it proceeds through activation and leaves staking for later. This is the only question in hot-key mode. + Not enough for the deposit → it stops here, nothing deployed. This is the only confirmation in hot-key mode. - Deploys the pool, waits for beacon-chain registration, activates, and stakes if funded — broadcasting itself (hot-key) or printing each `cast send` and waiting for it to land (cold-signing). Nothing to do here except wait, or run the printed commands. + Deploys the pool, waits for beacon-chain registration, activates, and stakes if funded — broadcasting itself (hot-key) or printing each `cast send` and waiting for it to land (cold-signing). Re-run `install` to resume after a partial failure. @@ -73,16 +76,7 @@ That's the whole default output. Pass `--verbose` for tx hashes and RPC detail w ## Everything else -`status`, `stake`, `unstake`, `deploy`, `activate`, and `set-min-balance` remain available as standalone commands — run `node pool-cli.mjs --help` or see the [install-helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md) for the full command and environment-variable reference, plus troubleshooting. - -`status`, `activate`, `set-min-balance`, `stake`, and `unstake` read the scenario file `install` already wrote in the working directory, so you don't need `BEACOND_HOME` on a remote validator to run them afterward — `--chain`/`--pubkey` still override it when passed. `deploy` is the one exception: it needs the deposit's `credentials`/`signature`/`amount`, which the scenario file deliberately doesn't carry, so it still requires a local validator or a full `--deposit`. - -`unstake --finalize --from 0xHOLDER` (no id) finalizes every withdrawal request that's past the finalization delay in one batch transaction; `unstake --finalize 42 --from 0xHOLDER` finalizes just request `42`. - - - `set-min-balance` is not part of `install` — run it yourself. See - [Setting Minimum Effective Balance](/nodes/staking-pools/operators#setting-minimum-effective-balance). - +The CLI has exactly four commands: `install`, `status`, `stake`, and `unstake`. There are no domain-fact flags. `status` never interviews; first-ever `status` with no identity refuses and names `install`. After `install`, `stake` and `unstake` resolve the receiver/holder from the scenario file or `PRIVATE_KEY`. See the [install-helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md) for environment-variable names. ## Delegated pools From c4ae702ef84e9f0d047f80a1af91ffe145275dcb Mon Sep 17 00:00:00 2001 From: Camembear Date: Tue, 1 Sep 2026 15:51:13 -0400 Subject: [PATCH 10/20] Rewrite staking pool installation for Plan A/B/C operator UX. Replace S-labels and CLI_CHAIN with EL/CL env vars, Plan naming, generated files section, role-address prompts, and compact status expectations. --- nodes/staking-pools/installation.mdx | 148 +++++++++++++++++++++------ 1 file changed, 118 insertions(+), 30 deletions(-) diff --git a/nodes/staking-pools/installation.mdx b/nodes/staking-pools/installation.mdx index 49ef474..38170fe 100644 --- a/nodes/staking-pools/installation.mdx +++ b/nodes/staking-pools/installation.mdx @@ -1,50 +1,84 @@ --- title: "Staking Pool Installation" -description: "Install and activate a staking pool with one command. The CLI asks only for facts nothing can detect, then finishes the job or prints the cast commands it cannot run." +description: "Install and activate a staking pool with one command." --- -Run the CLI on the **validator host** for S1/S2 (`BEACOND_HOME` set), or anywhere else for S3 (`BEACOND_HOME` unset). +Berachain provides staking pool installation scripts to assist with deploying your staking pool. -- **S1 hot-key** (`PRIVATE_KEY` and `BEACOND_HOME` set): the CLI signs and broadcasts everything itself. One confirmation, then hands-off. -- **S2 cold-signing** (`BEACOND_HOME` set, no `PRIVATE_KEY`): the CLI prints a `cast send` for each transaction, for you to run on a separate signing machine, and watches the chain for it to land. -- **S3 remote** (`BEACOND_HOME` unset): the CLI does not need `beacond` on this host. Paste a deposit signature; it verifies the pasted pubkey against the resolved identity pubkey. +There are important pre-requisites before you can attempt this: +1. A fully synced Berachain node, with your validator identity (`priv_validator_key.json`) backed up. +2. A funding wallet, in the form of an EOA private key or a Ledger, with at least 10,000 BERA (plus a bit for gas). -Either way: kill it and re-run `install` any time. It reads the chain to know where it left off. There is no standalone `activate`. +The installation tools support three deployment plans: +- **Plan A: On the validator, funding wallet key provided.** This is the "press enter and wait" scenario. The installation tools will take care of everything. +- **Plan B: On the validator, funding wallet in a Ledger.** The tools will figure out transactions you need to send (with Foundry's `cast`) from your local machine the Ledger is attached to. +- **Plan C: Fully remote.** The tools will direct you to execute certain commands on the validator, then provide `cast` commands to be run locally, whether with a Ledger or the funding wallet's private key. -## Get the CLI +If your funding wallet has enough to to activate the validator on the chain (250,000 BERA) the tools will offer to complete the activation; otherwise you have to attract enough stake to pass the activation threshold and begin producing blocks. + +The CLI also provides basic helpers to stake and unstake, though these are best done with the staking pool frontend (see [After install](#after-install)). + +The tools write two files in the working directory as they run. See [Generated files](#generated-files) for what each contains and how later commands use them. + +Re-run `install` to resume after a partial failure; keep the scenario and receipts files in place. + +## Get the CLI and configure the environment ```bash git clone https://github.com/berachain/guides/ cd guides/apps/staking-pools/install-helpers ``` -Requires Node.js 22+. `beacond` is required on the validator host (S1/S2) only. +* Node.js 22+ required +* `beacond` should be on your $PATH on every machine +* `cast` should be on your $PATH where you plan to transact from +* `EL_RPC_URL` should be the URL for the execution layer. Use `https://bepolia.rpc.berachain.com` or `https://rpc.berachain.com`, depending where you're activating your pool. +* `CL_NODE_API_URL` should be a URL to a Beacon Kit Node API. Node API is disabled by default in Beacon-Kit and [must be enabled](https://docs.berachain.com/nodes/beaconkit/configuration#node-api-beacon-kit-node-api) for `install` and `status`. This *does not need to be* served by your validator. You can disable it after the pool is activated. +* `PRIVATE_KEY` should be present on the system sending casts; if absent, the tools will assume you intend to use a Ledger. -## Requirements - -- At least 10,000 BERA for the deposit, more if you're staking past that. -- For S1/S2: `beacond`, synced, validator keys backed up. - - - `PRIVATE_KEY` on the validator host is a hot key. Leave it unset to sign from a separate - machine instead. - +For **Plan A** or **Plan B**, set `BEACOND_HOME` to the Beacon-Kit data directory on your validator. For **Plan C**, leave it unset and run the CLI on a machine that can reach your EL and Node API endpoints. ## Run it -Export the facts you already have in this shell. Do not copy an `env.sh.template` — pool-cli does not read or write `env.sh`. +**Plan A** (hot key on the validator): ```bash -export BEACOND_HOME="/path/to/beacond/home" -export CLI_CHAIN=bepolia -# export PRIVATE_KEY=0x... # S1 only - -node pool-cli.mjs install +$ export BEACOND_HOME="/path/to/beacond/data" +$ export EL_RPC_URL=http://bepolia.rpc.berachain.com/ +$ export CL_NODE_API_URL=http://127.0.0.1:3500 +$ export PRIVATE_KEY=0x... +$ node pool-cli.mjs install +Network: bepolia +Validator pubkey: 0xab8c...e140 +Predicted pool: 0x91ed...5dd5 +Deposit: 10,000 BERA +Additional stake: 240,000 BERA (funding wallet holds enough) +Operator address [0x1968...74e9]: +Shares recipient [0x1968...74e9]: +Operator: 0x1968...74e9 +Shares recipient: 0x1968...74e9 + +Proceed? [y/N] y +Stake amount in whole BERA [240000]: +Stake target: 240,000 BERA. +Deployed. Predicted pool: 0x91edb... +Waiting for validator registration... +Registered (index 37). +Waiting for activation proofs... +Activated. +Staked 240,000 BERA. +Done. ``` +**Plan B** (Ledger on another machine): same exports on the validator host, but leave `PRIVATE_KEY` unset. The CLI prints each `cast send` for you to run where the Ledger is attached. + +**Plan C** (remote tool host): omit `BEACOND_HOME` and `PRIVATE_KEY`. The CLI asks for a deposit signature when it needs one; run `beacond deposit create-validator` on the validator and paste the output. + +Network (`mainnet` or `bepolia`) comes from `eth_chainId` on the EL endpoint you give in `EL_RPC_URL`. You do not set the network separately. + - The CLI reads your pubkey, network, and funding balance, then shows the plan once. The additional-stake suggestion is rounded down to the nearest 1,000 BERA. Type a different whole-BERA amount at the prompt to override it. + The CLI reads your pubkey, the chain from your EL RPC, and your funding balance. It then asks you to accept or override the operator and shares-recipient addresses (defaults match the funding wallet on Plan A, or the funding address you typed on Plan B/C). After that it shows the plan once. If the wallet can stake past the activation threshold, the additional-stake suggestion is rounded down to the nearest 1,000 BERA. Type a different whole-BERA amount at the prompt to override it. ``` Network: bepolia @@ -53,11 +87,11 @@ node pool-cli.mjs install Proceed? [y/N] ``` - Not enough for the deposit → it stops here, nothing deployed. This is the only confirmation in hot-key mode. + Not enough for the 10,000 BERA deposit → it stops here, nothing deployed. Under **Plan A**, this is the only confirmation. - Deploys the pool, waits for beacon-chain registration, activates, and stakes if funded — broadcasting itself (hot-key) or printing each `cast send` and waiting for it to land (cold-signing). Re-run `install` to resume after a partial failure. + Deploys the pool, waits for beacon-chain registration, activates, and stakes if funded. **Plan A** broadcasts itself. **Plan B** and **Plan C** print each `cast send` and wait for it to land on chain. Re-run `install` to resume after a partial failure. @@ -70,17 +104,71 @@ Registered (index 36). Activated. Staked 240,000 BERA. Done. +Receipts: ./staking-pool-receipts.jsonl +Scenario: ./staking-pool-scenario.json +``` + +That is the default output for a full run. Pass `--verbose` for transaction hashes and RPC detail when debugging. + +## Generated files + +Both files land in the directory where you run `pool-cli.mjs` (typically `install-helpers/`). Back them up with your validator keys. + +### `staking-pool-scenario.json` + +Written at the start of `install`, before any on-chain step. Later commands read it so you do not re-enter network, pubkey, or pool identity. + +| Field | Meaning | +| --- | --- | +| `network` | `mainnet` or `bepolia` | +| `locality` | `local` (validator host) or `remote` (Plan C tool host) | +| `pubkey` | Your validator pubkey | +| `operator` | EL operator address recorded for the pool | +| `sharesRecipient` | Address that receives stBERA from staking | +| `fundingAddress` | Wallet that paid the deposit and stake | + +`status`, `stake`, and `unstake` use this file. If it is missing, `status` refuses and tells you to run `install` first. + +Example: + +```json +{ + "network": "bepolia", + "locality": "local", + "pubkey": "0xab8c...e140", + "operator": "0x1968...74e9", + "sharesRecipient": "0x1968...74e9", + "fundingAddress": "0x1968...74e9" +} +``` + +### `staking-pool-receipts.jsonl` + +Append-only log of **confirmed** transactions — one JSON object per line, written only after a transaction lands on chain (or after you broadcast a printed `cast send` in Plan B/C). Nothing is logged at print time. + +Typical `action` values from a full install: + +| `action` | When | +| --- | --- | +| `deploy` | Pool contracts deployed and 10,000 BERA deposit sent | +| `activate` | Pool activated on chain | +| `stake` | Additional BERA staked into the pool | + +Each line includes a `timestamp`, transaction `hash`, `amount` (BERA where relevant), and an `addresses` object (pool, factory, operator, and similar). Example deploy line: + +```json +{"timestamp":"2026-09-01T18:59:03.186Z","action":"deploy","hash":"0x070a92b1...","addresses":{"pool":"0x91ed...5dd5","operator":"0x1968...74e9","factory":"0x24b8...1D4C"},"amount":"10000"} ``` -That's the whole default output. Pass `--verbose` for tx hashes and RPC detail when debugging. +On success, `install` prints the receipts path after `Done.` if at least one receipt was written. The file is an audit trail for your records. -## Everything else +## After install -The CLI has exactly four commands: `install`, `status`, `stake`, and `unstake`. There are no domain-fact flags. `status` never interviews; first-ever `status` with no identity refuses and names `install`. After `install`, `stake` and `unstake` resolve the receiver/holder from the scenario file or `PRIVATE_KEY`. See the [install-helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md) for environment-variable names. +The CLI has four commands: `install`, `status`, `stake`, and `unstake`. Only `--verbose` and `--help` are accepted as flags. `status` is read-only; run `install` first if there is no scenario file yet. Day-to-day staking is easier through the staking pool frontend; use `stake` and `unstake` when you need the CLI. ## Delegated pools -A Foundation-delegated pool follows the same shape against the `DelegationHandler` instead of your own wallet. See the [Delegation guide](/nodes/staking-pools/delegators) and the README for current command names. +A Foundation-delegated pool follows the same shape against the `DelegationHandler` instead of your own wallet. See the [Delegation guide](/nodes/staking-pools/delegators) and the [install-helpers README](https://github.com/berachain/guides/blob/guides/staking-pool-operator-cli/apps/staking-pools/install-helpers/README.md) for delegated command names. ## What's next From 2897852a8ac3c3001150c451a4200a0efeace8a6 Mon Sep 17 00:00:00 2001 From: Camembear Date: Tue, 1 Sep 2026 20:43:53 -0400 Subject: [PATCH 11/20] Document remote install.sh staking pool landing flow. Align installation, delegators, operators, and overview pages with the shipped installer: no feature-branch checkout, validator-side deposit validate, and separate bastion vs validator scripts. --- nodes/staking-pools/delegators.mdx | 97 +++++--------- nodes/staking-pools/installation.mdx | 185 +++++++-------------------- nodes/staking-pools/operators.mdx | 5 +- nodes/staking-pools/overview.mdx | 2 +- 4 files changed, 81 insertions(+), 208 deletions(-) diff --git a/nodes/staking-pools/delegators.mdx b/nodes/staking-pools/delegators.mdx index 13e932a..2c544d0 100644 --- a/nodes/staking-pools/delegators.mdx +++ b/nodes/staking-pools/delegators.mdx @@ -1,99 +1,68 @@ --- title: "Delegation Guide" -description: "Use Foundation-delegated capital to run a staking pool: delegated create, activate, deposit, and withdraw yield." +description: "Use Foundation-delegated capital to run a staking pool: form the handler, land the pool, and withdraw yield." --- -This guide shows validator operators how to use delegated capital to stand up and run a staking pool. It builds on the Installation guide and reuses the same helper scripts. It does not cover delegator actions; coordinate with your capital provider as needed. +This guide covers **delegated staking pools** in two phases: -See [Installation](/nodes/staking-pools/installation) first. At minimum, set up `env.sh` in `install-helpers/`. +1. **Form** — deploy and fund the `DelegationHandler`, delegate capital, grant `VALIDATOR_ADMIN_ROLE` (delegator / Foundation scripts in `install-helpers/`). +2. **Land** — operator creates the pool with delegated funds, activates, and deposits the remainder (`installer/install.sh` on a remote machine). + +It does not cover delegator principal withdrawal; coordinate with your capital provider as needed. - All helper scripts write `cast` commands to files; review and run them yourself. Transactions are - never sent automatically. Signing defaults to Ledger; set `PRIVATE_KEY` in `env.sh` if you prefer - a local key. + Delegator **forming** scripts write `cast` commands to files under `generated/` for manual review. Operator **landing** uses `install.sh`, which prints each `cast send` and optionally runs it when `PRIVATE_KEY` is set. ## Assumptions -- You completed the validator Installation steps (env configured, node synced). -- The Berachain Foundation has prepared a DelegationHandler and delegated funds for your validator pubkey. +- You have a synced validator and its pubkey. +- For landing: the Foundation (or delegator) has already **formed** the handler and delegated funds for your pubkey. -## 1. Check readiness +## 1. Form the handler (delegator) -Confirm the chain, validator pubkey, and whether a delegated pool/handler is detected: +Run from `guides/apps/staking-pools/install-helpers/` on a machine with the delegator key (or generate Safe payloads for the Foundation): ```bash -./status.sh +./delegator-delegate.sh --amount 500000 --pubkey 0x... --operator 0x... ``` -If a handler exists, the script displays delegated amounts and whether a pool already exists for your pubkey. - -## 2. Create the pool with delegated funds (first 10,000 BERA) - -If the pool is not yet created, use the delegated creation script. It consumes the first 10,000 BERA from the handler to register the validator and writes a ready-to-run command file. This step is the delegated equivalent of the self-funded flow's `register.sh` (it performs the 10,000 BERA deposit and deploys the pool contracts). - -```bash -./delegated-create-pool.sh -``` +This deploys the `DelegationHandler` (if needed), funds it, and produces `delegate()` + `grantRole()` steps. The operator must hold `VALIDATOR_ADMIN_ROLE` before landing. -This will create a script with deployment commands. Review it. Then run it to submit the transaction, then wait for confirmation. +See `delegator-delegate.sh --help` for the forming flow. -## 3. Wait for validator registration +## 2. Land the pool (operator) -After deploying the contracts, wait for your validator to be registered on the beacon chain. You can check registration status with: +From `guides/apps/staking-pools/installer/` on a remote machine (not the validator): ```bash -./status.sh +export EL_RPC_URL=https://bepolia.rpc.berachain.com +export CL_NODE_API_URL=http://127.0.0.1:3500 +./install.sh ``` -The validator must appear on the beacon chain before you can activate the pool. +When your pubkey matches a formed handler with delegated funds, `install.sh` automatically: -## 4. Activate the pool +1. Creates the pool with the first 10,000 BERA of delegated funds (`createStakingPoolWithDelegatedFunds`). +2. Waits for beacon registration. +3. Activates the pool. +4. Optionally deposits remaining delegated BERA (`depositDelegatedFunds`). -Activation mirrors the Installation flow. Once the validator is recognised as registered on the beacon chain, run `activate.sh`: +Full step-by-step: [Installation](/nodes/staking-pools/installation). -```bash -./activate.sh -``` - -`activate.sh` is a permissionless call keyed on your validator pubkey and no longer takes `--sr` or `--op`. It preflights the activation locally (pool deployed, pool not already active, operator and withdrawal-credentials match, proofs verify against a single pinned slot) and writes `generated/activation-command.sh` for you to review and execute. Proofs are contract-checked against `MAX_TIMESTAMP_AGE` = 10 minutes; if you delay broadcasting beyond that window, re-run `activate.sh` to regenerate. +## 3. Withdraw yield (operator) -## 5. Deposit remaining delegated funds - -After activation, deposit the remaining delegated funds to reach your target balance (for example, 250,000 BERA on Bepolia). The script writes a command file for you to execute. +After the pool is active, operators can withdraw earned yield (not delegated principal) with: ```bash -./delegated-deposit.sh --amount 240000 +cd guides/apps/staking-pools/install-helpers +./delegated-withdraw-yield.sh ``` -## 6. Verify status - -```bash -./status.sh -``` - -You should see contract addresses, the operator match on the beacon deposit contract, and the pool marked ACTIVE. The script also reports delegated amounts when a handler is present. - -## 7. Withdraw yield (operator) - -Operators can withdraw earned yield independently of principal. The helper script writes two commands (request, then complete after cooldown): - -```bash -./delegated-withdraw-yield.sh --pubkey 0xYOUR_VALIDATOR_PUBKEY -``` - -Follow the prompts to execute the generated request and completion scripts after the cooldown. - -Yield withdrawal redeems only the **share excess** over the delegated principal still on the handler: the `DelegationHandler` computes `lockedShares = previewWithdraw(delegatedAmountUsed)` and excludes those shares from the redeemable amount. Any growth above the locked principal — protocol-fee shares accrued to the default recipient, reward-vault yield, operator-side WBERA — is freely redeemable; the delegated principal itself is not. - -Principal withdrawals are controlled by the delegator and are out of scope here. Once you have attracted additional depositors, the foundation can redeem its stake without causing your validator to exit. - - - If you are self-funded, use `stake.sh` from the [Installation - guide](/nodes/staking-pools/installation) instead of the delegated deposit flow. - +Yield withdrawal redeems only the **share excess** over the delegated principal still on the handler. Principal withdrawals are controlled by the delegator. ## Where to next -- [Contract reference](/nodes/staking-pools/contracts) explains what you've deployed -- [Operator Guide](/nodes/staking-pools/operators) explains what a staking pool operator can do +- [Installation](/nodes/staking-pools/installation) — remote landing flow (self-funded and delegated) +- [Contract reference](/nodes/staking-pools/contracts) +- [Operator Guide](/nodes/staking-pools/operators) diff --git a/nodes/staking-pools/installation.mdx b/nodes/staking-pools/installation.mdx index 38170fe..5982d37 100644 --- a/nodes/staking-pools/installation.mdx +++ b/nodes/staking-pools/installation.mdx @@ -1,176 +1,79 @@ --- title: "Staking Pool Installation" -description: "Install and activate a staking pool with one command." +description: "Install and activate a staking pool from a remote machine with cast." --- -Berachain provides staking pool installation scripts to assist with deploying your staking pool. +Berachain provides **`install.sh`** in `guides/apps/staking-pools/installer/` to walk through staking pool deployment from a **remote machine** (your laptop or a bastion). The script does not run on the validator. -There are important pre-requisites before you can attempt this: -1. A fully synced Berachain node, with your validator identity (`priv_validator_key.json`) backed up. -2. A funding wallet, in the form of an EOA private key or a Ledger, with at least 10,000 BERA (plus a bit for gas). +## Prerequisites -The installation tools support three deployment plans: -- **Plan A: On the validator, funding wallet key provided.** This is the "press enter and wait" scenario. The installation tools will take care of everything. -- **Plan B: On the validator, funding wallet in a Ledger.** The tools will figure out transactions you need to send (with Foundry's `cast`) from your local machine the Ledger is attached to. -- **Plan C: Fully remote.** The tools will direct you to execute certain commands on the validator, then provide `cast` commands to be run locally, whether with a Ledger or the funding wallet's private key. +1. A synced Berachain validator (your `priv_validator_key.json` backed up). +2. **Self-funded:** a funding wallet with at least **10,000 BERA** for the deposit, plus gas. +3. **Delegated:** the delegator has already formed the `DelegationHandler` and delegated capital (see [Delegation guide](/nodes/staking-pools/delegators)). You need gas only for activation txs. +4. On the machine where you run the script: **bash**, [Foundry](https://book.getfoundry.sh/) (`cast`), `jq`, and `curl`. +5. Reachable **EL JSON-RPC** and **Beacon Kit Node API** endpoints (Node API must be [enabled](https://docs.berachain.com/nodes/beaconkit/configuration#node-api-beacon-kit-node-api); it does not have to run on the validator host). -If your funding wallet has enough to to activate the validator on the chain (250,000 BERA) the tools will offer to complete the activation; otherwise you have to attract enough stake to pass the activation threshold and begin producing blocks. - -The CLI also provides basic helpers to stake and unstake, though these are best done with the staking pool frontend (see [After install](#after-install)). - -The tools write two files in the working directory as they run. See [Generated files](#generated-files) for what each contains and how later commands use them. - -Re-run `install` to resume after a partial failure; keep the scenario and receipts files in place. - -## Get the CLI and configure the environment +## Get the script ```bash git clone https://github.com/berachain/guides/ -cd guides/apps/staking-pools/install-helpers +cd guides/apps/staking-pools/installer ``` -* Node.js 22+ required -* `beacond` should be on your $PATH on every machine -* `cast` should be on your $PATH where you plan to transact from -* `EL_RPC_URL` should be the URL for the execution layer. Use `https://bepolia.rpc.berachain.com` or `https://rpc.berachain.com`, depending where you're activating your pool. -* `CL_NODE_API_URL` should be a URL to a Beacon Kit Node API. Node API is disabled by default in Beacon-Kit and [must be enabled](https://docs.berachain.com/nodes/beaconkit/configuration#node-api-beacon-kit-node-api) for `install` and `status`. This *does not need to be* served by your validator. You can disable it after the pool is activated. -* `PRIVATE_KEY` should be present on the system sending casts; if absent, the tools will assume you intend to use a Ledger. - -For **Plan A** or **Plan B**, set `BEACOND_HOME` to the Beacon-Kit data directory on your validator. For **Plan C**, leave it unset and run the CLI on a machine that can reach your EL and Node API endpoints. - -## Run it - -**Plan A** (hot key on the validator): +## Configure the environment ```bash -$ export BEACOND_HOME="/path/to/beacond/data" -$ export EL_RPC_URL=http://bepolia.rpc.berachain.com/ -$ export CL_NODE_API_URL=http://127.0.0.1:3500 -$ export PRIVATE_KEY=0x... -$ node pool-cli.mjs install -Network: bepolia -Validator pubkey: 0xab8c...e140 -Predicted pool: 0x91ed...5dd5 -Deposit: 10,000 BERA -Additional stake: 240,000 BERA (funding wallet holds enough) -Operator address [0x1968...74e9]: -Shares recipient [0x1968...74e9]: -Operator: 0x1968...74e9 -Shares recipient: 0x1968...74e9 - -Proceed? [y/N] y -Stake amount in whole BERA [240000]: -Stake target: 240,000 BERA. -Deployed. Predicted pool: 0x91edb... -Waiting for validator registration... -Registered (index 37). -Waiting for activation proofs... -Activated. -Staked 240,000 BERA. -Done. +export EL_RPC_URL=https://bepolia.rpc.berachain.com +export CL_NODE_API_URL=http://127.0.0.1:3500 ``` -**Plan B** (Ledger on another machine): same exports on the validator host, but leave `PRIVATE_KEY` unset. The CLI prints each `cast send` for you to run where the Ledger is attached. +| Variable | Purpose | +| --- | --- | +| `EL_RPC_URL` | Execution-layer JSON-RPC. Network (`mainnet` / `bepolia`) is read from `eth_chainId` on this URL. | +| `CL_NODE_API_URL` | Beacon Kit Node API base URL (`http://host:port`). | +| `PRIVATE_KEY` | Optional. When set, the script can run `cast send` for you after confirmation. When unset, run each printed command on your Ledger machine and paste the transaction hash back. | -**Plan C** (remote tool host): omit `BEACOND_HOME` and `PRIVATE_KEY`. The CLI asks for a deposit signature when it needs one; run `beacond deposit create-validator` on the validator and paste the output. +## Run -Network (`mainnet` or `bepolia`) comes from `eth_chainId` on the EL endpoint you give in `EL_RPC_URL`. You do not set the network separately. +```bash +./install.sh +``` - - The CLI reads your pubkey, the chain from your EL RPC, and your funding balance. It then asks you to accept or override the operator and shares-recipient addresses (defaults match the funding wallet on Plan A, or the funding address you typed on Plan B/C). After that it shows the plan once. If the wallet can stake past the activation threshold, the additional-stake suggestion is rounded down to the nearest 1,000 BERA. Type a different whole-BERA amount at the prompt to override it. - - ``` - Network: bepolia - Deposit: 10,000 BERA - Additional stake: 240,000 BERA (funding wallet holds enough) - Proceed? [y/N] - ``` - - Not enough for the 10,000 BERA deposit → it stops here, nothing deployed. Under **Plan A**, this is the only confirmation. + + The script verifies `cast`, `jq`, and `curl`, then checks that `EL_RPC_URL` and `CL_NODE_API_URL` respond. - - Deploys the pool, waits for beacon-chain registration, activates, and stakes if funded. **Plan A** broadcasts itself. **Plan B** and **Plan C** print each `cast send` and wait for it to land on chain. Re-run `install` to resume after a partial failure. + + Enter your validator pubkey. If a `DelegationHandler` exists for that pubkey with delegated funds and no pool yet, the script enters **delegated landing** mode (skips operator/shares prompts). Otherwise it warns that you are on the **self-funded** path (10,000 BERA from your wallet) and prompts for funding wallet, operator, and shares recipient. - - -## Output - -``` -Deployed. Predicted pool: 0x6dE5...0755 -Waiting for validator registration... -Registered (index 36). -Activated. -Staked 240,000 BERA. -Done. -Receipts: ./staking-pool-receipts.jsonl -Scenario: ./staking-pool-scenario.json -``` - -That is the default output for a full run. Pass `--verbose` for transaction hashes and RPC detail when debugging. -## Generated files - -Both files land in the directory where you run `pool-cli.mjs` (typically `install-helpers/`). Back them up with your validator keys. - -### `staking-pool-scenario.json` - -Written at the start of `install`, before any on-chain step. Later commands read it so you do not re-enter network, pubkey, or pool identity. - -| Field | Meaning | -| --- | --- | -| `network` | `mainnet` or `bepolia` | -| `locality` | `local` (validator host) or `remote` (Plan C tool host) | -| `pubkey` | Your validator pubkey | -| `operator` | EL operator address recorded for the pool | -| `sharesRecipient` | Address that receives stBERA from staking | -| `fundingAddress` | Wallet that paid the deposit and stake | - -`status`, `stake`, and `unstake` use this file. If it is missing, `status` refuses and tells you to run `install` first. - -Example: - -```json -{ - "network": "bepolia", - "locality": "local", - "pubkey": "0xab8c...e140", - "operator": "0x1968...74e9", - "sharesRecipient": "0x1968...74e9", - "fundingAddress": "0x1968...74e9" -} -``` - -### `staking-pool-receipts.jsonl` - -Append-only log of **confirmed** transactions — one JSON object per line, written only after a transaction lands on chain (or after you broadcast a printed `cast send` in Plan B/C). Nothing is logged at print time. - -Typical `action` values from a full install: - -| `action` | When | -| --- | --- | -| `deploy` | Pool contracts deployed and 10,000 BERA deposit sent | -| `activate` | Pool activated on chain | -| `stake` | Additional BERA staked into the pool | + + On the validator, run `beacond deposit create-validator` (the script fills withdrawal vault, amount, and genesis root for your network). Run `beacond deposit validate` with the four output fields — it must exit 0 with no output. Paste the full `create-validator` output back. The script checks all four fields, pubkey match, amount (10,000 BERA), and runs `deposit validate` locally when `BEACOND_HOME` and `beacond` are on this host. + -Each line includes a `timestamp`, transaction `hash`, `amount` (BERA where relevant), and an `addresses` object (pool, factory, operator, and similar). Example deploy line: + + **Self-funded:** prints `deployStakingPoolContracts` on the factory (10,000 BERA). **Delegated:** prints `createStakingPoolWithDelegatedFunds` on the handler. Then waits for beacon registration, fetches activation proofs, and prints `activateStakingPool`. + -```json -{"timestamp":"2026-09-01T18:59:03.186Z","action":"deploy","hash":"0x070a92b1...","addresses":{"pool":"0x91ed...5dd5","operator":"0x1968...74e9","factory":"0x24b8...1D4C"},"amount":"10000"} -``` + + Delegated mode only: after activation, optionally deposit remaining delegated BERA via `depositDelegatedFunds` on the handler (for example 240,000 BERA to reach 250,000 BERA effective balance). + + -On success, `install` prints the receipts path after `Done.` if at least one receipt was written. The file is an audit trail for your records. +Run `./install.sh` once per pool. It does not resume or skip completed steps. -## After install +On success, the script prints the staking pool address. Confirmed transactions append to `staking-pool-receipts.jsonl` (JSON lines with `timestamp`, `action`, `hash`, and optional `amount` / `pool`). Actions include `deploy`, `delegated-create`, `activate`, and `delegated-deposit`. -The CLI has four commands: `install`, `status`, `stake`, and `unstake`. Only `--verbose` and `--help` are accepted as flags. `status` is read-only; run `install` first if there is no scenario file yet. Day-to-day staking is easier through the staking pool frontend; use `stake` and `unstake` when you need the CLI. +Post-install ops on the validator use scripts in `install-helpers/` with your own `env.sh` (see `env.sh.template`). ## Delegated pools -A Foundation-delegated pool follows the same shape against the `DelegationHandler` instead of your own wallet. See the [Delegation guide](/nodes/staking-pools/delegators) and the [install-helpers README](https://github.com/berachain/guides/blob/guides/staking-pool-operator-cli/apps/staking-pools/install-helpers/README.md) for delegated command names. +**Forming** the handler (deploy, fund, delegate, grant role) is only [`delegator-delegate.sh`](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/delegator-delegate.sh). See the [Delegation guide](/nodes/staking-pools/delegators). + +**Landing** the pool (create with delegated funds, activate, deposit remainder) uses this same `./install.sh` when a handler is detected. ## What's next -- [Contract reference](/nodes/staking-pools/contracts) describes the contracts you deployed. -- [Delegation guide](/nodes/staking-pools/delegators) describes a Foundation delegation. +- [Contract reference](/nodes/staking-pools/contracts) +- [Operator guide](/nodes/staking-pools/operators) diff --git a/nodes/staking-pools/operators.mdx b/nodes/staking-pools/operators.mdx index 0ebed65..01caa71 100644 --- a/nodes/staking-pools/operators.mdx +++ b/nodes/staking-pools/operators.mdx @@ -158,7 +158,7 @@ Your front-end should: - Support batch finalization with `finalizeWithdrawalRequests([...])`. - Show staker balance, share price, and total rewards (e.g. via `previewRedeem(shares)`). -Berachain provides a React-based example template in the [guides repository](https://github.com/berachain/guides/tree/main/apps/staking-pools/frontend). Use `generate-frontend-config.sh` from `install-helpers` to generate `config.json` from your environment. +Berachain provides a React-based example template in the [guides repository](https://github.com/berachain/guides/tree/main/apps/staking-pools/frontend). On the validator, run `generate-frontend-config.sh` from `install-helpers` (with `BEACOND_HOME` in `env.sh`) to write `config.draft.json`. ## Delegation @@ -168,4 +168,5 @@ If you have received a delegation from the Berachain Foundation, see the [Delega - [Staking Pools Overview](/nodes/staking-pools/overview) - [Smart Contract Reference](/nodes/staking-pools/contracts) -- [Install-helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md) +- [Installer README](https://github.com/berachain/guides/blob/main/apps/staking-pools/installer/README.md) — remote landing +- [Install-helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md) — post-install and delegator scripts diff --git a/nodes/staking-pools/overview.mdx b/nodes/staking-pools/overview.mdx index f8b8ede..4cf47ba 100644 --- a/nodes/staking-pools/overview.mdx +++ b/nodes/staking-pools/overview.mdx @@ -69,7 +69,7 @@ Staking pools integrate with Berachain's Proof of Liquidity system. Pool reward ## Provided tools -Berachain provides tools to help you operate your staking pool. A React-based **example frontend template** provides a starting point for building your staking interface. The template demonstrates how stakers can connect wallets, deposit BERA, view positions, and request withdrawals. See the [Building your front-end](/nodes/staking-pools/operators#building-your-front-end) section in the operator guide for requirements and template usage. Bash scripts automate deployment and management operations, generating ready-to-review `cast` commands for safe execution. An interactive Python CLI tool is available for managing SmartOperator contracts. These tools are available in the [Berachain guides repository](https://github.com/berachain/guides/tree/main/apps/staking-pools), with detailed documentation in the [install-helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md). +Berachain provides tools to help you operate your staking pool. Use [`installer/install.sh`](https://github.com/berachain/guides/tree/main/apps/staking-pools/installer) for remote pool landing (self-funded or delegated). A React-based **example frontend template** provides a starting point for building your staking interface. See the [Building your front-end](/nodes/staking-pools/operators#building-your-front-end) section in the operator guide. Validator-local scripts in [`install-helpers`](https://github.com/berachain/guides/tree/main/apps/staking-pools/install-helpers) cover post-install ops (stake, unstake, status), delegator forming, and delegated yield withdraw. An interactive Python CLI tool is available for managing SmartOperator contracts. ## Getting started From be14295dfef2b8180a4134bb272c3b5c323d9b09 Mon Sep 17 00:00:00 2001 From: Camembear Date: Tue, 1 Sep 2026 21:08:52 -0400 Subject: [PATCH 12/20] Update staking pool docs for install-helpers and env prompt defaults. Point installation and delegation guides at install-helpers, document env.sh.template, and describe optional bracket defaults for pubkey and addresses. --- nodes/staking-pools/delegators.mdx | 58 +++++++--------------------- nodes/staking-pools/installation.mdx | 39 ++++++++++++++----- nodes/staking-pools/operators.mdx | 4 +- nodes/staking-pools/overview.mdx | 2 +- 4 files changed, 48 insertions(+), 55 deletions(-) diff --git a/nodes/staking-pools/delegators.mdx b/nodes/staking-pools/delegators.mdx index 2c544d0..e09c084 100644 --- a/nodes/staking-pools/delegators.mdx +++ b/nodes/staking-pools/delegators.mdx @@ -1,39 +1,29 @@ --- title: "Delegation Guide" -description: "Use Foundation-delegated capital to run a staking pool: form the handler, land the pool, and withdraw yield." +description: "Use Foundation-delegated capital to run a staking pool: form the handler, then land the pool." --- -This guide covers **delegated staking pools** in two phases: +Delegated staking pools have two phases: -1. **Form** — deploy and fund the `DelegationHandler`, delegate capital, grant `VALIDATOR_ADMIN_ROLE` (delegator / Foundation scripts in `install-helpers/`). -2. **Land** — operator creates the pool with delegated funds, activates, and deposits the remainder (`installer/install.sh` on a remote machine). +1. **Form** — `delegator-delegate.sh` deploys and funds the `DelegationHandler`, runs `delegate()`, and grants `VALIDATOR_ADMIN_ROLE`. +2. **Land** — `install.sh` creates the pool with delegated funds, activates, and optionally deposits the remainder. -It does not cover delegator principal withdrawal; coordinate with your capital provider as needed. +## 1. Form the handler - - Delegator **forming** scripts write `cast` commands to files under `generated/` for manual review. Operator **landing** uses `install.sh`, which prints each `cast send` and optionally runs it when `PRIVATE_KEY` is set. - - -## Assumptions - -- You have a synced validator and its pubkey. -- For landing: the Foundation (or delegator) has already **formed** the handler and delegated funds for your pubkey. - -## 1. Form the handler (delegator) - -Run from `guides/apps/staking-pools/install-helpers/` on a machine with the delegator key (or generate Safe payloads for the Foundation): +From `guides/apps/staking-pools/install-helpers/` on a machine with RPC access (not the validator): ```bash -./delegator-delegate.sh --amount 500000 --pubkey 0x... --operator 0x... +export EL_RPC_URL=https://bepolia.rpc.berachain.com +./delegator-delegate.sh ``` -This deploys the `DelegationHandler` (if needed), funds it, and produces `delegate()` + `grantRole()` steps. The operator must hold `VALIDATOR_ADMIN_ROLE` before landing. +The script prompts for pubkey, delegate amount, and validator-admin address. Set `VALIDATOR_PUBKEY`, `DELEGATE_AMOUNT_BERA`, and `VALIDATOR_ADMIN` in `env.sh` to pre-fill those prompts, or pass `--pubkey`, `--amount`, and `--validator-admin` to skip them. -See `delegator-delegate.sh --help` for the forming flow. +The script prints each `cast send` (or runs it when `PRIVATE_KEY` is set). Steps 3–4 (`delegate`, `grantRole`) require `DEFAULT_ADMIN_ROLE` on the handler — often the Foundation Safe. ## 2. Land the pool (operator) -From `guides/apps/staking-pools/installer/` on a remote machine (not the validator): +From `guides/apps/staking-pools/install-helpers/` (same directory as `delegator-delegate.sh`): ```bash export EL_RPC_URL=https://bepolia.rpc.berachain.com @@ -41,28 +31,10 @@ export CL_NODE_API_URL=http://127.0.0.1:3500 ./install.sh ``` -When your pubkey matches a formed handler with delegated funds, `install.sh` automatically: - -1. Creates the pool with the first 10,000 BERA of delegated funds (`createStakingPoolWithDelegatedFunds`). -2. Waits for beacon registration. -3. Activates the pool. -4. Optionally deposits remaining delegated BERA (`depositDelegatedFunds`). - -Full step-by-step: [Installation](/nodes/staking-pools/installation). - -## 3. Withdraw yield (operator) - -After the pool is active, operators can withdraw earned yield (not delegated principal) with: - -```bash -cd guides/apps/staking-pools/install-helpers -./delegated-withdraw-yield.sh -``` - -Yield withdrawal redeems only the **share excess** over the delegated principal still on the handler. Principal withdrawals are controlled by the delegator. +When a formed handler with delegated funds exists for your pubkey, `install.sh` lands the pool automatically. See [Installation](/nodes/staking-pools/installation). -## Where to next +## What's next -- [Installation](/nodes/staking-pools/installation) — remote landing flow (self-funded and delegated) +- [Installation](/nodes/staking-pools/installation) - [Contract reference](/nodes/staking-pools/contracts) -- [Operator Guide](/nodes/staking-pools/operators) +- [Operator guide](/nodes/staking-pools/operators) diff --git a/nodes/staking-pools/installation.mdx b/nodes/staking-pools/installation.mdx index 5982d37..89bb527 100644 --- a/nodes/staking-pools/installation.mdx +++ b/nodes/staking-pools/installation.mdx @@ -3,7 +3,7 @@ title: "Staking Pool Installation" description: "Install and activate a staking pool from a remote machine with cast." --- -Berachain provides **`install.sh`** in `guides/apps/staking-pools/installer/` to walk through staking pool deployment from a **remote machine** (your laptop or a bastion). The script does not run on the validator. +Berachain provides **`install.sh`** in `guides/apps/staking-pools/install-helpers/` to walk through staking pool deployment from a **remote machine** (your laptop or a bastion). The script does not run on the validator. ## Prerequisites @@ -17,21 +17,42 @@ Berachain provides **`install.sh`** in `guides/apps/staking-pools/installer/` to ```bash git clone https://github.com/berachain/guides/ -cd guides/apps/staking-pools/installer +cd guides/apps/staking-pools/install-helpers ``` ## Configure the environment +Copy the template and edit, or export variables directly: + ```bash -export EL_RPC_URL=https://bepolia.rpc.berachain.com -export CL_NODE_API_URL=http://127.0.0.1:3500 +cp env.sh.template env.sh +# edit env.sh — EL_RPC_URL, CL_NODE_API_URL, optional PRIVATE_KEY ``` | Variable | Purpose | | --- | --- | | `EL_RPC_URL` | Execution-layer JSON-RPC. Network (`mainnet` / `bepolia`) is read from `eth_chainId` on this URL. | -| `CL_NODE_API_URL` | Beacon Kit Node API base URL (`http://host:port`). | -| `PRIVATE_KEY` | Optional. When set, the script can run `cast send` for you after confirmation. When unset, run each printed command on your Ledger machine and paste the transaction hash back. | +| `CL_NODE_API_URL` | Beacon Kit Node API base URL (`http://host:port`). `install.sh` only. | +| `PRIVATE_KEY` | Optional. When set, scripts can run `cast send` for you after confirmation. When unset, run each printed command on your Ledger machine and paste the transaction hash back. | + +Optional defaults for `install.sh` (the script still asks; press Enter to accept the bracketed value): + +| Variable | Purpose | +| --- | --- | +| `VALIDATOR_PUBKEY` | Validator pubkey | +| `FUNDING_ADDRESS` | Funding wallet for self-funded deploy (cold signing; ignored when `PRIVATE_KEY` is set) | +| `OPERATOR_ADDRESS` | Operator address on the self-funded path | +| `SHARES_RECIPIENT` | Shares recipient on the self-funded path | + +Optional defaults for `delegator-delegate.sh`: + +| Variable | Purpose | +| --- | --- | +| `VALIDATOR_PUBKEY` | Validator pubkey | +| `DELEGATE_AMOUNT_BERA` | Whole BERA to delegate | +| `VALIDATOR_ADMIN` | Operator address for `VALIDATOR_ADMIN_ROLE` | + +Both scripts source `env.sh` from this directory when present. ## Run @@ -45,11 +66,11 @@ export CL_NODE_API_URL=http://127.0.0.1:3500 - Enter your validator pubkey. If a `DelegationHandler` exists for that pubkey with delegated funds and no pool yet, the script enters **delegated landing** mode (skips operator/shares prompts). Otherwise it warns that you are on the **self-funded** path (10,000 BERA from your wallet) and prompts for funding wallet, operator, and shares recipient. + Enter your validator pubkey (or press Enter when `VALIDATOR_PUBKEY` is set in `env.sh`). If a `DelegationHandler` exists for that pubkey with delegated funds and no pool yet, the script enters **delegated landing** mode (skips operator/shares prompts). Otherwise it warns that you are on the **self-funded** path (10,000 BERA from your wallet) and prompts for funding wallet, operator, and shares recipient — each with an optional env default (`FUNDING_ADDRESS`, `OPERATOR_ADDRESS`, `SHARES_RECIPIENT`). - On the validator, run `beacond deposit create-validator` (the script fills withdrawal vault, amount, and genesis root for your network). Run `beacond deposit validate` with the four output fields — it must exit 0 with no output. Paste the full `create-validator` output back. The script checks all four fields, pubkey match, amount (10,000 BERA), and runs `deposit validate` locally when `BEACOND_HOME` and `beacond` are on this host. + On the validator, run `beacond deposit create-validator` (the script fills withdrawal vault, amount, and genesis root for your network). Run `beacond deposit validate` with the four output fields on the validator — it must exit 0 with no output. Paste the full `create-validator` output back. The script checks all four fields, pubkey match, and amount (10,000 BERA). @@ -65,7 +86,7 @@ Run `./install.sh` once per pool. It does not resume or skip completed steps. On success, the script prints the staking pool address. Confirmed transactions append to `staking-pool-receipts.jsonl` (JSON lines with `timestamp`, `action`, `hash`, and optional `amount` / `pool`). Actions include `deploy`, `delegated-create`, `activate`, and `delegated-deposit`. -Post-install ops on the validator use scripts in `install-helpers/` with your own `env.sh` (see `env.sh.template`). +Post-install staker ops: use the [example frontend](https://github.com/berachain/guides/tree/main/apps/staking-pools/frontend) or `cast` against your pool contract. ## Delegated pools diff --git a/nodes/staking-pools/operators.mdx b/nodes/staking-pools/operators.mdx index 01caa71..2390ece 100644 --- a/nodes/staking-pools/operators.mdx +++ b/nodes/staking-pools/operators.mdx @@ -158,7 +158,7 @@ Your front-end should: - Support batch finalization with `finalizeWithdrawalRequests([...])`. - Show staker balance, share price, and total rewards (e.g. via `previewRedeem(shares)`). -Berachain provides a React-based example template in the [guides repository](https://github.com/berachain/guides/tree/main/apps/staking-pools/frontend). On the validator, run `generate-frontend-config.sh` from `install-helpers` (with `BEACOND_HOME` in `env.sh`) to write `config.draft.json`. +Berachain provides a React-based example template in the [guides repository](https://github.com/berachain/guides/tree/main/apps/staking-pools/frontend). Copy `config.example.json`, set your pool address and validator pubkey (from `install.sh` output or factory `getCoreContracts`), and deploy. ## Delegation @@ -168,5 +168,5 @@ If you have received a delegation from the Berachain Foundation, see the [Delega - [Staking Pools Overview](/nodes/staking-pools/overview) - [Smart Contract Reference](/nodes/staking-pools/contracts) -- [Installer README](https://github.com/berachain/guides/blob/main/apps/staking-pools/installer/README.md) — remote landing +- [Install helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md) — `install.sh` and `delegator-delegate.sh` - [Install-helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md) — post-install and delegator scripts diff --git a/nodes/staking-pools/overview.mdx b/nodes/staking-pools/overview.mdx index 4cf47ba..f71868e 100644 --- a/nodes/staking-pools/overview.mdx +++ b/nodes/staking-pools/overview.mdx @@ -69,7 +69,7 @@ Staking pools integrate with Berachain's Proof of Liquidity system. Pool reward ## Provided tools -Berachain provides tools to help you operate your staking pool. Use [`installer/install.sh`](https://github.com/berachain/guides/tree/main/apps/staking-pools/installer) for remote pool landing (self-funded or delegated). A React-based **example frontend template** provides a starting point for building your staking interface. See the [Building your front-end](/nodes/staking-pools/operators#building-your-front-end) section in the operator guide. Validator-local scripts in [`install-helpers`](https://github.com/berachain/guides/tree/main/apps/staking-pools/install-helpers) cover post-install ops (stake, unstake, status), delegator forming, and delegated yield withdraw. An interactive Python CLI tool is available for managing SmartOperator contracts. +Berachain provides [`install.sh`](https://github.com/berachain/guides/tree/main/apps/staking-pools/install-helpers/install.sh) and [`delegator-delegate.sh`](https://github.com/berachain/guides/tree/main/apps/staking-pools/install-helpers/delegator-delegate.sh) in `install-helpers/` for remote pool landing and delegated handler forming. A React-based **example frontend template** covers post-install staker ops. See [Building your front-end](/nodes/staking-pools/operators#building-your-front-end). The Python **SmartOperator Manager** CLI helps manage operator contracts on the validator. ## Getting started From d85a770084fb0aa8c7c5ea75808897bee1881074 Mon Sep 17 00:00:00 2001 From: Camembear Date: Tue, 1 Sep 2026 22:21:25 -0400 Subject: [PATCH 13/20] Rewrite staking pool installation as a single install.sh how-to. --- nodes/staking-pools/installation.mdx | 108 ++++++++++----------------- 1 file changed, 40 insertions(+), 68 deletions(-) diff --git a/nodes/staking-pools/installation.mdx b/nodes/staking-pools/installation.mdx index 89bb527..48cce70 100644 --- a/nodes/staking-pools/installation.mdx +++ b/nodes/staking-pools/installation.mdx @@ -1,100 +1,72 @@ --- -title: "Staking Pool Installation" -description: "Install and activate a staking pool from a remote machine with cast." +title: "Install a staking pool" +description: "Deploy and activate a staking pool from a remote machine with install.sh." --- -Berachain provides **`install.sh`** in `guides/apps/staking-pools/install-helpers/` to walk through staking pool deployment from a **remote machine** (your laptop or a bastion). The script does not run on the validator. +`install.sh` deploys and activates one staking pool from `guides/apps/staking-pools/install-helpers/`. Set the environment, run the script once, and read back the pool address and a receipt. The script uses Foundation-delegated funds when a DelegationHandler already exists for your validator. Otherwise it spends **10,000 BERA** plus gas from your funding wallet. -## Prerequisites +## What you need before you run install.sh -1. A synced Berachain validator (your `priv_validator_key.json` backed up). -2. **Self-funded:** a funding wallet with at least **10,000 BERA** for the deposit, plus gas. -3. **Delegated:** the delegator has already formed the `DelegationHandler` and delegated capital (see [Delegation guide](/nodes/staking-pools/delegators)). You need gas only for activation txs. -4. On the machine where you run the script: **bash**, [Foundry](https://book.getfoundry.sh/) (`cast`), `jq`, and `curl`. -5. Reachable **EL JSON-RPC** and **Beacon Kit Node API** endpoints (Node API must be [enabled](https://docs.berachain.com/nodes/beaconkit/configuration#node-api-beacon-kit-node-api); it does not have to run on the validator host). +You need a synced validator, the helper tools, and live endpoints: -## Get the script +- A synced Berachain validator +- `bash`, [Foundry](https://book.getfoundry.sh/) (`cast`), `jq`, and `curl` +- Reachable execution-layer JSON-RPC and consensus-layer Node API endpoints. [Enable the Node API](https://docs.berachain.com/nodes/beaconkit/configuration#node-api-beacon-kit-node-api). The Node API can run on any node; it doesn't have to be on the validator. + + +You also need one funding path: + +- Self-funded: at least **10,000 BERA** plus gas in the funding wallet +- Delegated: a DelegationHandler already formed and funded, plus gas (for example, 0.1 BERA) for activation + +A DelegationHandler is the Foundation contract that holds delegated capital for your validator. The Berachain Foundation creates it when they issue a delegation. + +## Clone install-helpers + +Clone the guides repo and enter the helper directory: ```bash git clone https://github.com/berachain/guides/ cd guides/apps/staking-pools/install-helpers ``` -## Configure the environment +## Set environment variables -Copy the template and edit, or export variables directly: +Copy `env.sh.template` to `env.sh` in that directory, then edit the copy. `install.sh` sources `env.sh` when the file is present. ```bash cp env.sh.template env.sh -# edit env.sh — EL_RPC_URL, CL_NODE_API_URL, optional PRIVATE_KEY ``` -| Variable | Purpose | -| --- | --- | -| `EL_RPC_URL` | Execution-layer JSON-RPC. Network (`mainnet` / `bepolia`) is read from `eth_chainId` on this URL. | -| `CL_NODE_API_URL` | Beacon Kit Node API base URL (`http://host:port`). `install.sh` only. | -| `PRIVATE_KEY` | Optional. When set, scripts can run `cast send` for you after confirmation. When unset, run each printed command on your Ledger machine and paste the transaction hash back. | - -Optional defaults for `install.sh` (the script still asks; press Enter to accept the bracketed value): - -| Variable | Purpose | -| --- | --- | -| `VALIDATOR_PUBKEY` | Validator pubkey | -| `FUNDING_ADDRESS` | Funding wallet for self-funded deploy (cold signing; ignored when `PRIVATE_KEY` is set) | -| `OPERATOR_ADDRESS` | Operator address on the self-funded path | -| `SHARES_RECIPIENT` | Shares recipient on the self-funded path | +| Variable | Required | Purpose | +| --- | --- | --- | +| `EL_RPC_URL` | Yes | Execution-layer JSON-RPC. Network (`mainnet` or `bepolia`) is read from `eth_chainId` on this URL. | +| `CL_NODE_API_URL` | Yes | Beacon Kit Node API base URL. | +| `PRIVATE_KEY` | No | Signs `cast send` on this host when set. | +| `VALIDATOR_PUBKEY` | No | Validator pubkey default. | +| `FUNDING_ADDRESS` | No | Funding wallet on the self-funded path. Ignored when `PRIVATE_KEY` is set. | +| `OPERATOR_ADDRESS` | No | Operator on the self-funded path. | +| `SHARES_RECIPIENT` | No | Shares recipient on the self-funded path. | -Optional defaults for `delegator-delegate.sh`: +Use `./install.sh --help` for usage. -| Variable | Purpose | -| --- | --- | -| `VALIDATOR_PUBKEY` | Validator pubkey | -| `DELEGATE_AMOUNT_BERA` | Whole BERA to delegate | -| `VALIDATOR_ADMIN` | Operator address for `VALIDATOR_ADMIN_ROLE` | +## Run install.sh -Both scripts source `env.sh` from this directory when present. - -## Run +The script deploys and activates one pool and doesn't resume. Run it once per pool: ```bash ./install.sh ``` - - - The script verifies `cast`, `jq`, and `curl`, then checks that `EL_RPC_URL` and `CL_NODE_API_URL` respond. - - - - Enter your validator pubkey (or press Enter when `VALIDATOR_PUBKEY` is set in `env.sh`). If a `DelegationHandler` exists for that pubkey with delegated funds and no pool yet, the script enters **delegated landing** mode (skips operator/shares prompts). Otherwise it warns that you are on the **self-funded** path (10,000 BERA from your wallet) and prompts for funding wallet, operator, and shares recipient — each with an optional env default (`FUNDING_ADDRESS`, `OPERATOR_ADDRESS`, `SHARES_RECIPIENT`). - +If a DelegationHandler with delegated funds exists for your pubkey, the script uses the delegated path. Otherwise it uses the self-funded path. - - On the validator, run `beacond deposit create-validator` (the script fills withdrawal vault, amount, and genesis root for your network). Run `beacond deposit validate` with the four output fields on the validator — it must exit 0 with no output. Paste the full `create-validator` output back. The script checks all four fields, pubkey match, and amount (10,000 BERA). - - - - **Self-funded:** prints `deployStakingPoolContracts` on the factory (10,000 BERA). **Delegated:** prints `createStakingPoolWithDelegatedFunds` on the handler. Then waits for beacon registration, fetches activation proofs, and prints `activateStakingPool`. - - - - Delegated mode only: after activation, optionally deposit remaining delegated BERA via `depositDelegatedFunds` on the handler (for example 240,000 BERA to reach 250,000 BERA effective balance). - - - -Run `./install.sh` once per pool. It does not resume or skip completed steps. - -On success, the script prints the staking pool address. Confirmed transactions append to `staking-pool-receipts.jsonl` (JSON lines with `timestamp`, `action`, `hash`, and optional `amount` / `pool`). Actions include `deploy`, `delegated-create`, `activate`, and `delegated-deposit`. - -Post-install staker ops: use the [example frontend](https://github.com/berachain/guides/tree/main/apps/staking-pools/frontend) or `cast` against your pool contract. - -## Delegated pools - -**Forming** the handler (deploy, fund, delegate, grant role) is only [`delegator-delegate.sh`](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/delegator-delegate.sh). See the [Delegation guide](/nodes/staking-pools/delegators). - -**Landing** the pool (create with delegated funds, activate, deposit remainder) uses this same `./install.sh` when a handler is detected. +On success, the script prints the staking pool address. Confirmed transactions append to `staking-pool-receipts.jsonl`. +Call the pool with `cast` for staker operations. The [Operator guide](/nodes/staking-pools/operators) covers day-two work. The [example frontend](https://github.com/berachain/guides/tree/main/apps/staking-pools/frontend) is a sample UI for end users. ## What's next +Continue with: + - [Contract reference](/nodes/staking-pools/contracts) - [Operator guide](/nodes/staking-pools/operators) +- [Delegation guide](/nodes/staking-pools/delegators) From 8a29fbc14595f3178ffa3bd3da2d7411fb85cff9 Mon Sep 17 00:00:00 2001 From: Camembear Date: Tue, 1 Sep 2026 22:22:47 -0400 Subject: [PATCH 14/20] Apply the writing handbook to staking-pools pages and the contracts generator. --- nodes/staking-pools/contracts.mdx | 32 +++++----- nodes/staking-pools/delegators.mdx | 31 ++++----- nodes/staking-pools/installation.mdx | 20 +++--- nodes/staking-pools/operators.mdx | 69 ++++++++++---------- nodes/staking-pools/overview.mdx | 96 +++++++++++++--------------- scripts/contracts/generate-pages.mjs | 32 +++++----- 6 files changed, 135 insertions(+), 145 deletions(-) diff --git a/nodes/staking-pools/contracts.mdx b/nodes/staking-pools/contracts.mdx index cf07aa7..932b600 100644 --- a/nodes/staking-pools/contracts.mdx +++ b/nodes/staking-pools/contracts.mdx @@ -1,32 +1,30 @@ --- -title: "Staking Pool Contracts" -description: "Addresses and ABIs for StakingPoolContractsFactory, DelegationHandlerFactory, WithdrawalVault, and per-pool proxies (StakingPool, SmartOperator, etc.)." +title: "Look up staking pool contract addresses" +description: "Singleton addresses and ABIs, plus the per-pool proxies StakingPoolContractsFactory deploys." --- import StakingPoolSingletonsTable from "/snippets/contracts/generated/staking-pools-singletons-table.mdx"; -This reference provides contract addresses and links to detailed documentation for each contract in the staking pools system. For an overview of how the contracts work together, see the [Staking Pools Overview](/nodes/staking-pools/overview). +This page lists staking-pool contract addresses and ABI links. The [Staking pools overview](/nodes/staking-pools/overview) explains how the contracts fit together. -## Contract addresses +## Singleton contracts -### Singleton contracts - -Singleton contracts are deployed once and shared across all staking pools. The **StakingPoolContractsFactory** is the entry point for deploying a staking pool: you call it to create and register your pool's contracts. The **DelegationHandlerFactory** deploys per-validator delegation handler proxies when using foundation delegation. The other singletons below are shared infrastructure. +These contracts are deployed once and shared by every pool: -### Deployed contracts (per pool) +Call `StakingPoolContractsFactory` to create and register a pool. Call `DelegationHandlerFactory` to deploy a per-validator handler proxy for Foundation delegation. The other rows are shared infrastructure. -When you deploy a staking pool through the StakingPoolContractsFactory, the factory creates proxy contracts for your validator. These proxy contracts are what you and your stakers interact with directly. Each validator receives unique proxy addresses for these contracts when deploying their staking pool. +## Per-pool proxy contracts -The factory returns these proxy addresses when you call `deployStakingPoolContracts`. Store these addresses for your operations and provide the StakingPool address to your stakers for deposits. +`deployStakingPoolContracts` returns proxy addresses for your validator. You and your stakers call those proxies. Store the addresses. Give stakers the StakingPool address for deposits. -**Proxy contracts deployed with your pool:** +Proxy contracts for each pool: -- **StakingPool**: Main staking functionality and staker interactions. This is the contract address your stakers use to deposit BERA and receive stBERA shares. -- **SmartOperator**: Validator operations and Proof of Liquidity integration. -- **IncentiveCollector**: Incentive token collection and conversion. Handles the incentive auction mechanism where accumulated tokens can be claimed. -- **StakingRewardsVault**: Reward collection and automatic reinvestment. Automatically compounds rewards from the consensus layer. -- **DelegationHandler**: Delegation handling for capital providers. Only deployed if you're using delegated funds from the Berachain Foundation. +- **StakingPool**: deposits, shares, and staker interactions. Stakers deposit BERA here and receive stBERA. +- **SmartOperator**: validator operations and Proof of Liquidity integration. +- **IncentiveCollector**: incentive token collection, conversion, and the incentive auction. +- **StakingRewardsVault**: consensus-layer reward collection and compounding. +- **DelegationHandler**: Foundation-delegated capital. Deployed only on the delegated path. -For detailed technical documentation of each contract's functions and behavior, see the [Berachain guides repository](https://github.com/berachain/guides/tree/main/apps/staking-pools) and the [install-helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md). +ABI JSON for each singleton is in the table above. For install and operator flows, see [Install a staking pool](/nodes/staking-pools/installation) and the [Operator guide](/nodes/staking-pools/operators). diff --git a/nodes/staking-pools/delegators.mdx b/nodes/staking-pools/delegators.mdx index e09c084..da6088f 100644 --- a/nodes/staking-pools/delegators.mdx +++ b/nodes/staking-pools/delegators.mdx @@ -1,29 +1,28 @@ --- -title: "Delegation Guide" -description: "Use Foundation-delegated capital to run a staking pool: form the handler, then land the pool." +title: "Run a pool with Foundation-delegated capital" +description: "Form a DelegationHandler, then run install.sh so the pool activates on delegated funds." --- -Delegated staking pools have two phases: +A delegated staking pool has two phases. Form the handler first. Then run `install.sh` so the pool uses those funds. -1. **Form** — `delegator-delegate.sh` deploys and funds the `DelegationHandler`, runs `delegate()`, and grants `VALIDATOR_ADMIN_ROLE`. -2. **Land** — `install.sh` creates the pool with delegated funds, activates, and optionally deposits the remainder. +A DelegationHandler is the Foundation contract that holds delegated capital for your validator. -## 1. Form the handler +## Form the handler -From `guides/apps/staking-pools/install-helpers/` on a machine with RPC access (not the validator): +Run this from `guides/apps/staking-pools/install-helpers/` on a machine with RPC access. It does not have to be the validator host: ```bash export EL_RPC_URL=https://bepolia.rpc.berachain.com ./delegator-delegate.sh ``` -The script prompts for pubkey, delegate amount, and validator-admin address. Set `VALIDATOR_PUBKEY`, `DELEGATE_AMOUNT_BERA`, and `VALIDATOR_ADMIN` in `env.sh` to pre-fill those prompts, or pass `--pubkey`, `--amount`, and `--validator-admin` to skip them. +The script deploys and funds the DelegationHandler, runs `delegate()`, and grants `VALIDATOR_ADMIN_ROLE`. It prompts for pubkey, delegate amount, and validator-admin address. Set `VALIDATOR_PUBKEY`, `DELEGATE_AMOUNT_BERA`, and `VALIDATOR_ADMIN` in `env.sh` to pre-fill those prompts, or pass `--pubkey`, `--amount`, and `--validator-admin` to skip them. -The script prints each `cast send` (or runs it when `PRIVATE_KEY` is set). Steps 3–4 (`delegate`, `grantRole`) require `DEFAULT_ADMIN_ROLE` on the handler — often the Foundation Safe. +The script prints each `cast send` (or runs it when `PRIVATE_KEY` is set). `delegate` and `grantRole` need `DEFAULT_ADMIN_ROLE` on the handler, often the Foundation Safe. -## 2. Land the pool (operator) +## Install the pool after the handler exists -From `guides/apps/staking-pools/install-helpers/` (same directory as `delegator-delegate.sh`): +Use the same `install-helpers/` directory. `install.sh` needs the Node API as well as execution-layer RPC: ```bash export EL_RPC_URL=https://bepolia.rpc.berachain.com @@ -31,10 +30,12 @@ export CL_NODE_API_URL=http://127.0.0.1:3500 ./install.sh ``` -When a formed handler with delegated funds exists for your pubkey, `install.sh` lands the pool automatically. See [Installation](/nodes/staking-pools/installation). +If a formed handler with delegated funds exists for your pubkey, `install.sh` uses the delegated path. See [Install a staking pool](/nodes/staking-pools/installation). ## What's next -- [Installation](/nodes/staking-pools/installation) -- [Contract reference](/nodes/staking-pools/contracts) -- [Operator guide](/nodes/staking-pools/operators) +Continue with: + +- [Install a staking pool](/nodes/staking-pools/installation) +- [Look up staking pool contract addresses](/nodes/staking-pools/contracts) +- [Operate a staking pool](/nodes/staking-pools/operators) diff --git a/nodes/staking-pools/installation.mdx b/nodes/staking-pools/installation.mdx index 48cce70..5644bf0 100644 --- a/nodes/staking-pools/installation.mdx +++ b/nodes/staking-pools/installation.mdx @@ -13,7 +13,6 @@ You need a synced validator, the helper tools, and live endpoints: - `bash`, [Foundry](https://book.getfoundry.sh/) (`cast`), `jq`, and `curl` - Reachable execution-layer JSON-RPC and consensus-layer Node API endpoints. [Enable the Node API](https://docs.berachain.com/nodes/beaconkit/configuration#node-api-beacon-kit-node-api). The Node API can run on any node; it doesn't have to be on the validator. - You also need one funding path: - Self-funded: at least **10,000 BERA** plus gas in the funding wallet @@ -38,15 +37,15 @@ Copy `env.sh.template` to `env.sh` in that directory, then edit the copy. `insta cp env.sh.template env.sh ``` -| Variable | Required | Purpose | -| --- | --- | --- | -| `EL_RPC_URL` | Yes | Execution-layer JSON-RPC. Network (`mainnet` or `bepolia`) is read from `eth_chainId` on this URL. | -| `CL_NODE_API_URL` | Yes | Beacon Kit Node API base URL. | -| `PRIVATE_KEY` | No | Signs `cast send` on this host when set. | -| `VALIDATOR_PUBKEY` | No | Validator pubkey default. | -| `FUNDING_ADDRESS` | No | Funding wallet on the self-funded path. Ignored when `PRIVATE_KEY` is set. | -| `OPERATOR_ADDRESS` | No | Operator on the self-funded path. | -| `SHARES_RECIPIENT` | No | Shares recipient on the self-funded path. | +| Variable | Required | Purpose | +| ------------------ | -------- | -------------------------------------------------------------------------------------------------- | +| `EL_RPC_URL` | Yes | Execution-layer JSON-RPC. Network (`mainnet` or `bepolia`) is read from `eth_chainId` on this URL. | +| `CL_NODE_API_URL` | Yes | Beacon Kit Node API base URL. | +| `PRIVATE_KEY` | No | Signs `cast send` on this host when set. | +| `VALIDATOR_PUBKEY` | No | Validator pubkey default. | +| `FUNDING_ADDRESS` | No | Funding wallet on the self-funded path. Ignored when `PRIVATE_KEY` is set. | +| `OPERATOR_ADDRESS` | No | Operator on the self-funded path. | +| `SHARES_RECIPIENT` | No | Shares recipient on the self-funded path. | Use `./install.sh --help` for usage. @@ -63,6 +62,7 @@ If a DelegationHandler with delegated funds exists for your pubkey, the script u On success, the script prints the staking pool address. Confirmed transactions append to `staking-pool-receipts.jsonl`. Call the pool with `cast` for staker operations. The [Operator guide](/nodes/staking-pools/operators) covers day-two work. The [example frontend](https://github.com/berachain/guides/tree/main/apps/staking-pools/frontend) is a sample UI for end users. + ## What's next Continue with: diff --git a/nodes/staking-pools/operators.mdx b/nodes/staking-pools/operators.mdx index 2390ece..6ce2720 100644 --- a/nodes/staking-pools/operators.mdx +++ b/nodes/staking-pools/operators.mdx @@ -1,9 +1,9 @@ --- -title: "Staking Pools Operator Guide" -description: "Configure and operate a staking pool: roles, commission, reward allocation, min effective balance, and front-end." +title: "Operate a staking pool" +description: "Roles, commission, reward allocation, min effective balance, withdrawals, and front-end." --- -This guide helps validators set up and manage staking pools to offer liquid staking services to their communities. +Day-two work after `install.sh`: roles, commission, reward allocation, min effective balance, WBERA flows, and withdrawals. Deploy first with [Install a staking pool](/nodes/staking-pools/installation). ## Quick reference @@ -40,10 +40,10 @@ Core lifecycle and PoL reward allocation: Protocol fee accrual (same percentage applies to both tracks during migration): -| Function | Contract | Purpose | -| ------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------ | -| `accrueEarnedWBERAFees()` | SmartOperator | Accrue protocol fees on **WBERA** balance growth — primary path after PoL Next. | -| `accrueEarnedBGTFees()` | SmartOperator | Accrue protocol fees on remaining **BGT** balance — **legacy / transitional** until BGT allowances are exhausted on your pool. | +| Function | Contract | Purpose | +| ------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| `accrueEarnedWBERAFees()` | SmartOperator | Accrue protocol fees on **WBERA** balance growth: primary path after PoL Next. | +| `accrueEarnedBGTFees()` | SmartOperator | Accrue protocol fees on remaining **BGT** balance: **legacy / transitional** until BGT allowances are exhausted on your pool. | Manual lever for compounding operator WBERA into pool assets: @@ -59,7 +59,7 @@ Legacy incentive surface (kept for transition; see [Deprecated BGT entry points] | --------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `claimBoostRewards()` | SmartOperator | **Legacy:** forward **BGT-era** boost incentive claims toward **`IncentiveCollector`**. Prefer WBERA-native operator flows for ongoing activity; treat as transitional. | -## Prerequisites +## What you need before you operate a pool Before setting up a staking pool, ensure you have a fully operational Berachain validator node. You'll need at least 10,000 BERA to register the pool, though activation requires at least 250,000 BERA. See the [Validator Lifecycle](/nodes/architecture/validator-lifecycle) and [Become a Validator](/nodes/guides/become-a-validator) guides. @@ -87,22 +87,22 @@ The key consideration for staking pools is ensuring sufficient stake for activat After the May 2026 PoL upgrade, per-block validator emission is a **flat fixed rate** (no boost curve). Pools no longer differentiate on attracted BGT delegation; what you control is: -- **Validator reward allocation (cutting board)** — direct emissions to vaults you and your community care about. See [Manage Reward Allocations](/nodes/guides/manage-reward-allocations). -- **Incentives attracted to vaults you allocate to** — protocols still incentivize specific Reward Vaults; a strong allocation strategy increases incentive yield to your stakers. -- **Validator commission on incentive tokens** — capped at 20%, denominated in the WBERA-era incentive flow. See [Manage Validator Incentives Commission Rate](/nodes/guides/manage-incentives-commission). +- **Validator reward allocation (cutting board)**: direct emissions to vaults you and your community care about. See [Manage Reward Allocations](/nodes/guides/manage-reward-allocations). +- **Incentives attracted to vaults you allocate to**: protocols still incentivize specific Reward Vaults; a strong allocation strategy increases incentive yield to your stakers. +- **Validator commission on incentive tokens**: capped at 20%, denominated in the WBERA-era incentive flow. See [Manage Validator Incentives Commission Rate](/nodes/guides/manage-incentives-commission). Validator emissions accrue as \$WBERA on your `SmartOperator`. Protocol fee accrual on the WBERA track uses `accrueEarnedWBERAFees()`. The same WBERA fee update also runs automatically inside `withdrawRewards`, `pullBeraToWithdrawalVault`, and `setProtocolFeePercentage()`, so calling `accrueEarnedWBERAFees()` directly is mostly a forced-settlement / monitoring tool. `accrueEarnedBGTFees()` is a no-op once chargeable BGT is exhausted. ## Automatic WBERA flows -`SmartOperator` exposes two state-changing entry points that move WBERA out of the operator: `withdrawRewards(uint256)` and `pullBeraToWithdrawalVault(uint256)`. Both are restricted by sender — `withdrawRewards` accepts the StakingPool only; `pullBeraToWithdrawalVault` accepts the WithdrawalVault only. Operators cannot call them directly, and no operator action is required to make WBERA reach stakers. +`SmartOperator` exposes two state-changing entry points that move WBERA out of the operator: `withdrawRewards(uint256)` and `pullBeraToWithdrawalVault(uint256)`. Both are restricted by sender: `withdrawRewards` accepts the StakingPool only; `pullBeraToWithdrawalVault` accepts the WithdrawalVault only. Operators cannot call them directly, and no operator action is required to make WBERA reach stakers. -- **Compounding into pool assets** — Any user `deposit()` and any call to `StakingPool.processRewards()` invokes `_collectRewards(...)`, which calls `SmartOperator.withdrawRewards(...)` on operator WBERA first, then drains the staking rewards vault. Operator WBERA is pulled first so `_getTotalAssets()` is correct before protocol fees mint staker shares via `mintFeeShares` during WBERA fee accrual. The unwrapped BERA lands in the pool, lifts `_getTotalAssets()`, and lifts share price for every staker. -- **Deposit ordering and fee fairness** — Inside `_submit`, the pool mints the depositor's stBERA **before** calling `_collectRewards(...)`. The depositor's shares are priced against the pre-collection NAV (which already includes pending operator WBERA via `rebaseableWberaAmount()`), so they pay a fair price for the yield about to settle. The collection that follows charges WBERA-track protocol fees and mints fee shares to the default recipient against the post-deposit NAV, so the new depositor's principal is not taxed as if it were accrued yield. `processRewards()` runs the same `_collectRewards → bufferedAssets bump → _processDeposit` sequence without minting user shares. -- **Covering withdrawals from operator liquidity** — `WithdrawalVault.requestWithdrawal()` precomputes `pulledFromOperator = min(amountInWei, availableWBERABalance)` and calls `pullBeraToWithdrawalVault(...)` on the normal partial-withdrawal branch. The execution-layer withdrawal request to the consensus layer is reduced to the uncovered remainder; if the operator covers in full, no consensus-layer request is issued and the EIP-7002 fee is refunded to the requester. Short-circuit (pool not yet at active threshold) and full-exit branches do not consume operator WBERA. -- **Full exit sweep** — Triggering a full exit sweeps any idle operator WBERA into the pool before the pool forwards its native balance to the WithdrawalVault. +- **Compounding into pool assets**: Any user `deposit()` and any call to `StakingPool.processRewards()` invokes `_collectRewards(...)`, which calls `SmartOperator.withdrawRewards(...)` on operator WBERA first, then drains the staking rewards vault. Operator WBERA is pulled first so `_getTotalAssets()` is correct before protocol fees mint staker shares via `mintFeeShares` during WBERA fee accrual. The unwrapped BERA is credited to the pool, lifts `_getTotalAssets()`, and lifts share price for every staker. +- **Deposit ordering and fee fairness**: Inside `_submit`, the pool mints the depositor's stBERA **before** calling `_collectRewards(...)`. The depositor's shares are priced against the pre-collection NAV (which already includes pending operator WBERA via `rebaseableWberaAmount()`), so they pay a fair price for the yield about to settle. The collection that follows charges WBERA-track protocol fees and mints fee shares to the default recipient against the post-deposit NAV, so the new depositor's principal is not taxed as if it were accrued yield. `processRewards()` runs the same `_collectRewards → bufferedAssets bump → _processDeposit` sequence without minting user shares. +- **Covering withdrawals from operator liquidity**: `WithdrawalVault.requestWithdrawal()` precomputes `pulledFromOperator = min(amountInWei, availableWBERABalance)` and calls `pullBeraToWithdrawalVault(...)` on the normal partial-withdrawal branch. The execution-layer withdrawal request to the consensus layer is reduced to the uncovered remainder; if the operator covers in full, no consensus-layer request is issued and the EIP-7002 fee is refunded to the requester. Short-circuit (pool not yet at active threshold) and full-exit branches do not consume operator WBERA. +- **Full exit sweep**: Triggering a full exit sweeps any idle operator WBERA into the pool before the pool forwards its native balance to the WithdrawalVault. -For monitoring, watch `availableWBERABalance()`, `rebaseableWberaAmount()`, and `getEarnedWBERAFeeState()` on the SmartOperator. The only operator-side write you may want to invoke explicitly is `processRewards()` on the StakingPool (permissionless `whenNotPaused`) — it forces a compounding cycle without waiting for the next user deposit. +For monitoring, watch `availableWBERABalance()`, `rebaseableWberaAmount()`, and `getEarnedWBERAFeeState()` on the SmartOperator. The only operator-side write you may want to invoke explicitly is `processRewards()` on the StakingPool (permissionless `whenNotPaused`): it forces a compounding cycle without waiting for the next user deposit. ## Configuration @@ -116,12 +116,12 @@ Direct PoL incentives to specific applications. For instructions, see [Managing ### Setting minimum effective balance -The `minEffectiveBalance` parameter is critical for validator activation. The consensus layer enforces a base minimum of **250,000 BERA**. When the validator set is full (69 validators), the minimum required increases in increments of **10,000 BERA**. Set `minEffectiveBalance` to match the current consensus layer requirement. You can check the current lowest active stake on [Berachain Hub](https://hub.berachain.com/boost/). +The `minEffectiveBalance` parameter gates validator activation. The consensus layer enforces a base minimum of **250,000 BERA**. When the validator set is full (69 validators), the minimum required increases in increments of **10,000 BERA**. Set `minEffectiveBalance` to match the current consensus layer requirement. You can check the current lowest active stake on [Berachain Hub](https://hub.berachain.com/boost/). ## Routine operations - **Monitor pool status**: Use `isActive()`, `totalAssets()`, `bufferedAssets()`, `activeThresholdReached()` on your StakingPool and SmartOperator. For operator-side liquidity, also watch **`availableWBERABalance()`**, **`rebaseableWberaAmount()`**, and **`getEarnedWBERAFeeState()`** on SmartOperator. See [Automatic WBERA flows](#automatic-wbera-flows) for how this liquidity moves. -- **Delegation operations**: Manage delegations as needed. **PoL Next:** validator emissions accrue as **\$WBERA** on **`SmartOperator`**. Accrue protocol fees on the WBERA track with **`accrueEarnedWBERAFees()`**; use **`accrueEarnedBGTFees()`** only while your pool still has chargeable **BGT** on the operator. **`claimBoostRewards()`** is a **legacy** entry point for the **BGT** incentive-distributor surface — confirm it still matches your deployed implementation before relying on it for new workflows. +- **Delegation operations**: Manage delegations as needed. **PoL Next:** validator emissions accrue as **\$WBERA** on **`SmartOperator`**. Accrue protocol fees on the WBERA track with **`accrueEarnedWBERAFees()`**; use **`accrueEarnedBGTFees()`** only while your pool still has chargeable **BGT** on the operator. **`claimBoostRewards()`** is a **legacy** entry point for the **BGT** incentive-distributor surface: confirm it still matches your deployed implementation before relying on it for new workflows. - **Emission token context**: PoL Reward Vault emissions are distributed in **\$WBERA**. - **Protocol fee**: Set via `setProtocolFeePercentage()` (up to 20%). The fee setter charges both tracks before applying the new rate. Call `accrueEarnedWBERAFees()` to force WBERA-track settlement; call `accrueEarnedBGTFees()` if you want to settle the legacy BGT track explicitly. @@ -129,26 +129,26 @@ The `minEffectiveBalance` parameter is critical for validator activation. The co The May 2026 PoL upgrade deprecates the BGT-era surface on `SmartOperator`. The following entry points stay callable for transition (all `whenNotFullyExited` where applicable) but become inert once chargeable BGT is exhausted on your operator: -- `queueBoost()`, `activateBoost()`, `queueDropBoost(uint128)` — boost-management calls; per-block emission no longer scales with delegation, so these have no effect on yield. -- `claimBgtStakerReward()` — BGT-staker reward forwarder. -- `claimBoostRewards(IBGTIncentiveDistributor.Claim[], address[])` — BGT-era boost incentive distributor forwarder. -- `accrueEarnedBGTFees()` — fee accrual on the BGT track; no-op when chargeable BGT is zero. +- `queueBoost()`, `activateBoost()`, `queueDropBoost(uint128)`: boost-management calls; per-block emission no longer scales with delegation, so these have no effect on yield. +- `claimBgtStakerReward()`: BGT-staker reward forwarder. +- `claimBoostRewards(IBGTIncentiveDistributor.Claim[], address[])`: BGT-era boost incentive distributor forwarder. +- `accrueEarnedBGTFees()`: fee accrual on the BGT track; no-op when chargeable BGT is zero. -Berachain recommends winding down BGT positions held on your `SmartOperator` (unboost, drop-boost, redeem) using the same calls during the transition window. +Unboost, drop-boost, and redeem BGT held on your `SmartOperator` with those same calls during the transition window. ## Withdrawal system The shared **`WithdrawalVault`** is a per-chain singleton (a single proxy + implementation) that finalizes withdrawal requests for every staking pool. Three sources of liquidity can satisfy a request: -- **Short-circuit** — pool not yet at the active threshold. Liquidity comes only from the pool's on-chain **`bufferedAssets`**. BeaconKit honors EIP-7002 withdrawal requests only for **active** validators; a validator that has not activated cannot withdraw via the consensus layer, so this branch never issues a CL request. If the buffer cannot cover the request, the transaction reverts rather than falling through into post-threshold or full-exit logic. -- **Standard consensus-layer withdrawal** — execution-layer request to the validator's pubkey. -- **Operator-side WBERA cover** — at request time, the vault pulls from **`availableWBERABalance()`** on your pool's `SmartOperator`, unwraps to native BERA, and applies it to the request. Coverage may be **full** (the consensus-layer request is skipped and the EIP-7002 fee is refunded), **partial** (the remainder still exits via the consensus layer), or **none** when operator liquidity is empty. +- **Short-circuit**: pool not yet at the active threshold. Liquidity comes only from the pool's on-chain **`bufferedAssets`**. BeaconKit honors EIP-7002 withdrawal requests only for **active** validators; a validator that has not activated cannot withdraw via the consensus layer, so this branch never issues a CL request. If the buffer cannot cover the request, the transaction reverts rather than falling through into post-threshold or full-exit logic. +- **Standard consensus-layer withdrawal**: execution-layer request to the validator's pubkey. +- **Operator-side WBERA cover**: at request time, the vault pulls from **`availableWBERABalance()`** on your pool's `SmartOperator`, unwraps to native BERA, and applies it to the request. Coverage may be **full** (the consensus-layer request is skipped and the EIP-7002 fee is refunded), **partial** (the remainder still exits via the consensus layer), or **none** when operator liquidity is empty. **Post-exit pools:** Once the validator has fully exited (`isFullyExited`), withdrawal handling skips the pre-threshold default-recipient gate and the post-threshold activation cooldown. BERA is already in the `WithdrawalVault`; requests proceed without re-entering activation-era guards. **Finalization delay:** `finalizeWithdrawalRequest` / `finalizeWithdrawalRequests` enforce `block.number >= requestBlock + WITHDRAWAL_REQUEST_FINALIZATION_BLOCK_DELAY` (currently **129,600** blocks, ≈3 days at ~2s block time) for every request. The cover source affects only **where the BERA comes from**; it does not shorten the cooldown. The pull from operator WBERA happens at **request** time, so covered BERA sits in the vault until the cooldown elapses. -**Retrying a full exit (`retryFullExit`):** When a full exit is triggered, the pool marks the validator fully exited and the vault submits an EIP-7002 full-exit request (withdrawal amount **0**). If that consensus-layer request fails or is dropped, anyone can call **`retryFullExit(pubkey, maxFeeToPay)`** on the shared `WithdrawalVault` to re-submit — permissionless, sending the EIP-7002 **withdrawal fee** with the call (`maxFeeToPay` is the most you are willing to pay). The validator must already be marked fully exited (`NotFullyExited` otherwise). The call reverts with **`PendingWithdrawalInFlight`** if a partial withdrawal was requested within the last **49,153** blocks (`WITHDRAWAL_FLIGHT_BLOCK_DELAY`), giving in-flight partial exits time to settle before retrying a full exit. Success emits **`FullExitRequestRetried`**. This path is for recovering a stuck post-exit CL sweep; normal staker withdrawal requests do not use it. +**Retrying a full exit (`retryFullExit`):** When a full exit is triggered, the pool marks the validator fully exited and the vault submits an EIP-7002 full-exit request (withdrawal amount **0**). If that consensus-layer request fails or is dropped, anyone can call **`retryFullExit(pubkey, maxFeeToPay)`** on the shared `WithdrawalVault` to re-submit: permissionless, sending the EIP-7002 **withdrawal fee** with the call (`maxFeeToPay` is the most you are willing to pay). The validator must already be marked fully exited (`NotFullyExited` otherwise). The call reverts with **`PendingWithdrawalInFlight`** if a partial withdrawal was requested within the last **49,153** blocks (`WITHDRAWAL_FLIGHT_BLOCK_DELAY`), giving in-flight partial exits time to settle before retrying a full exit. Success emits **`FullExitRequestRetried`**. This path is for recovering a stuck post-exit CL sweep; normal staker withdrawal requests do not use it. ## Building your front-end @@ -162,11 +162,12 @@ Berachain provides a React-based example template in the [guides repository](htt ## Delegation -If you have received a delegation from the Berachain Foundation, see the [Delegation Guide](/nodes/staking-pools/delegators) and the [DelegationHandler](https://github.com/berachain/guides/tree/main/apps/staking-pools) contract reference. +If you received a Foundation delegation, see [Run a pool with Foundation-delegated capital](/nodes/staking-pools/delegators). -## More information +## What's next -- [Staking Pools Overview](/nodes/staking-pools/overview) -- [Smart Contract Reference](/nodes/staking-pools/contracts) -- [Install helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md) — `install.sh` and `delegator-delegate.sh` -- [Install-helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md) — post-install and delegator scripts +Continue with: + +- [What staking pools do](/nodes/staking-pools/overview) +- [Look up staking pool contract addresses](/nodes/staking-pools/contracts) +- [install-helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md) diff --git a/nodes/staking-pools/overview.mdx b/nodes/staking-pools/overview.mdx index f71868e..c6fb051 100644 --- a/nodes/staking-pools/overview.mdx +++ b/nodes/staking-pools/overview.mdx @@ -1,84 +1,76 @@ --- -title: "Staking Pools Overview" -description: "Validator-operated liquid staking: stBERA, contract architecture, security, staker experience, and PoL integration." +title: "What staking pools do" +description: "Validator-operated liquid staking: stBERA, contract layout, staker flow, and Proof of Liquidity." --- -Staking Pools enable validators to offer liquid staking services to their communities. As a validator, you can build and monetize your own community of stakers, earning commission on rewards. Your stakers deposit BERA through smart contracts and receive liquid shares (stBERA) that automatically grow in value as rewards accumulate. +A staking pool lets you offer liquid staking to your community. Stakers deposit BERA and receive stBERA shares that grow as rewards compound. You earn commission on those rewards. -## What are staking pools? +## How a pool is put together -Staking Pools are validator-operated services that allow your community members to stake BERA through smart contracts, receiving liquid shares (stBERA) that represent their stake and automatically grow in value as rewards accumulate. Stakers can stake any amount without running their own validator. +`StakingPoolContractsFactory` deploys the contracts for one validator. Each pool includes: -As a validator, staking pools provide you with a way to build and monetize your own community of stakers, earning commission on rewards. Your stakers benefit from lower barriers to entry, automatic reward reinvestment, and flexible withdrawals. +- **StakingPool**: deposits, share accounting, and the stBERA token. Stakers call this contract. +- **SmartOperator**: validator operations and Proof of Liquidity. +- **StakingRewardsVault**: consensus-layer rewards, compounded back into the pool. -## Post-BGT Deprecation +Shared across pools: -The May 2026 Proof of Liquidity upgrade changes how staking pools earn and how stakers receive rewards. The contract surface and pool operations stay broadly the same. +- **WithdrawalVault**: withdrawal requests for every pool +- **AccountingOracle**: consensus-layer balances that feed `totalAssets` -- **Reward emissions are paid in \$WBERA.** Validator emissions accrue as \$WBERA on the `SmartOperator`. Operator monitoring views and the automatic flows that compound this WBERA into pool assets and into withdrawal coverage are documented in the [Operator Guide](/nodes/staking-pools/operators#automatic-wbera-flows). -- **Per-block emission is a flat fixed rate (no boost curve).** The pre-upgrade boost curve, which made BGT delegation a yield differentiator, is gone. Per-block emission no longer scales with delegation. Pools compete on **reward allocation strategy**, **attracted incentives**, and **commission rate** rather than boost. -- **BGT is deprecated.** We recommend unboosting and burning the BGT held by your `SmartOperator`. The BGT-era entry points stay callable but become inert once chargeable BGT is exhausted; see [Deprecated BGT entry points](/nodes/staking-pools/operators#deprecated-bgt-entry-points). -- **Withdrawals can be covered from operator-side WBERA.** The shared `WithdrawalVault` may pull from `availableWBERABalance()` on your `SmartOperator`, unwrap to native BERA, and apply that toward an outstanding request — fully, partially, or not at all when liquidity is insufficient. The standard consensus-layer withdrawal path remains for any uncovered remainder. +`StakingPool` inherits stBERA share mint, burn, and asset conversion. -For broader PoL changes, see [What's New](/general/proof-of-liquidity/changelog). +## What changed after BGT deprecation -## How it works +The May 2026 Proof of Liquidity upgrade changed how pools earn. The contract surface stays largely the same. -When you create a staking pool, the system deploys several interconnected contracts. Your pool includes a StakingPool contract that manages deposits and shares, a SmartOperator that handles validator operations and Proof of Liquidity integration, and a StakingRewardsVault that collects and reinvests rewards. Shared infrastructure includes a WithdrawalVault that processes withdrawal requests for all pools and an Accounting Oracle that provides consensus layer data updates. +- **Reward emissions are paid in $WBERA.** Validator emissions accrue as $WBERA on `SmartOperator`. See [Automatic WBERA flows](/nodes/staking-pools/operators#automatic-wbera-flows). +- **Per-block emission is a flat rate.** It no longer scales with BGT delegation. Pools compete on reward allocation, attracted incentives, and commission. +- **BGT is deprecated.** Unboost and burn BGT held on your `SmartOperator`. BGT-era entry points stay callable until chargeable BGT is gone. See [Deprecated BGT entry points](/nodes/staking-pools/operators#deprecated-bgt-entry-points). +- **Withdrawals can use operator WBERA.** `WithdrawalVault` may pull `availableWBERABalance()` from your `SmartOperator`, unwrap to BERA, and apply it in full, in part, or not at all. Uncovered remainder still exits through the consensus layer. -The system automates staking, reward distribution, and withdrawal management, requiring minimal manual intervention from you while providing your stakers with a seamless staking experience. +For the rest of the upgrade, see [What's New](/general/proof-of-liquidity/changelog). -### Contract architecture +## Who can do what -The staking pools system uses a factory pattern where the **StakingPoolContractsFactory** serves as the deployment mechanism, creating and managing the complete suite of contracts needed for each validator's staking pool. +`DEFAULT_ADMIN_ROLE` governs upgrades and emergency actions. `VALIDATOR_ADMIN_ROLE` is scoped to your pool. -The core functionality revolves around the **StakingPool** contract, which handles staker deposits, share management, and the fundamental operations of your staking pool. Stakers interact with this contract to stake their BERA tokens and receive stBERA shares in return. The StakingPool contract maintains the accounting for staker positions and manages the conversion between BERA and stBERA shares. +Other roles: -Validator operations are coordinated through the **SmartOperator** contract, which manages integration with Berachain's Proof of Liquidity system. This contract handles reward allocation, commission management, and validator-specific PoL operations. +- `REWARDS_ALLOCATION_MANAGER_ROLE`: reward allocation +- `COMMISSION_MANAGER_ROLE`: commission +- `PROTOCOL_FEE_MANAGER_ROLE`: protocol fee +- `INCENTIVE_COLLECTOR_MANAGER_ROLE`: incentive collector -The **StakingPool** contract inherits from the **stBERA** base contract, which provides the core token functionality for staked BERA shares, including share minting, burning, and conversion between assets and shares. +Contracts can pause. If deposits fall below the minimum effective balance, the pool can trigger a full exit. -### Security model +## What stakers see -The staking pools system implements a security model designed to protect staker funds while maintaining operational flexibility for validators and administrators. +Stakers deposit BERA and receive stBERA immediately. Rewards compound into share price. They can withdraw without a validator minimum. -Access control is managed through a role-based system that provides granular permissions for different types of operations. The `DEFAULT_ADMIN_ROLE` provides governance control over contract upgrades and emergency actions. Validators receive the `VALIDATOR_ADMIN_ROLE`, which grants them operational control over their specific staking pool while preventing interference with other validators' operations. +Withdrawal liquidity can come from: -Specialized roles handle specific aspects of the system. The `REWARDS_ALLOCATION_MANAGER_ROLE` manages reward allocation to specific applications, while the `COMMISSION_MANAGER_ROLE` handles validator commission management. The `PROTOCOL_FEE_MANAGER_ROLE` controls protocol fee settings, and the `INCENTIVE_COLLECTOR_MANAGER_ROLE` manages incentive collector operations. +- the consensus layer +- buffered funds on the pool before activation +- WBERA on your `SmartOperator`, via `WithdrawalVault` (full, partial, or none) -Emergency controls provide additional protection. Contracts can be paused in emergency situations. The system includes automatic full exit triggers that activate if the minimum balance threshold is breached, protecting stakers from potential losses. - -## Staker experience - -When stakers interact with your staking pool, they deposit BERA and immediately receive liquid stBERA shares. Rewards automatically compound as you earn rewards, increasing the value of shares over time without manual claiming or reinvestment. Stakers can withdraw at any time. **Withdrawal liquidity** can come from the **consensus layer**, from **buffered funds** on the pool before activation, and — after PoL Next on the staking-pools stack — from **WBERA held on your pool's `SmartOperator`**, which the shared **`WithdrawalVault`** may use to help satisfy requests (including **partial** coverage, with any remainder still exiting via the consensus layer). **Finalizing** a withdrawal request remains subject to the on-chain delay from the request block (commonly on the order of **three days** / **129,600** blocks at ~2s block time); see the [Operator Guide](/nodes/staking-pools/operators#withdrawal-system) for operator responsibilities and how coverage is sourced. Stakers can stake any amount without validator minimums, with full transparency through on-chain verification. +Finalizing a request waits until `requestBlock + 129,600` blocks (about three days at ~2s). See the [Operator guide](/nodes/staking-pools/operators#withdrawal-system). - **Staking pool shares (stBERA) are not LSTs.** They cannot be registered with - `IncentivesCollector` as an `LSTStakerVault` asset and cannot be deposited into the \$sWBERA - Staking Vault. Pool-level yield reaches stBERA holders through the pool's own `SmartOperator` and - `IncentiveCollector` — a separate path from the PoL Incentive Auction yield. + stBERA is not an LST. It cannot be registered with `IncentivesCollector` as an `LSTStakerVault` + asset, and it cannot be deposited into the $sWBERA Staking Vault. Pool yield reaches holders + through this pool's `SmartOperator` and `IncentiveCollector`, not through the PoL Incentive + Auction. -## Validator operations - -You deploy a staking pool through the factory contract, which creates all necessary contracts and registers your validator with the consensus layer. You configure commission rates (up to 20% of staker rewards) and can direct Proof of Liquidity incentives to specific applications. Commission is collected automatically on staker rewards, allowing you to focus on community building rather than manual reward management. - -## Integration with Proof of Liquidity - -Staking pools integrate with Berachain's Proof of Liquidity system. Pool reward flows route validator emissions to configured vault paths, and smart contracts claim and distribute PoL incentives. You can direct rewards to specific applications or ecosystem initiatives. - -## Provided tools - -Berachain provides [`install.sh`](https://github.com/berachain/guides/tree/main/apps/staking-pools/install-helpers/install.sh) and [`delegator-delegate.sh`](https://github.com/berachain/guides/tree/main/apps/staking-pools/install-helpers/delegator-delegate.sh) in `install-helpers/` for remote pool landing and delegated handler forming. A React-based **example frontend template** covers post-install staker ops. See [Building your front-end](/nodes/staking-pools/operators#building-your-front-end). The Python **SmartOperator Manager** CLI helps manage operator contracts on the validator. - -## Getting started +## What you operate -Validators can set up staking pools using the [Installation Guide](/nodes/staking-pools/installation) and manage operations with the [Operator Guide](/nodes/staking-pools/operators). +You deploy through the factory, which registers the validator. Commission is up to 20% of staker rewards and collects automatically. You can direct Proof of Liquidity incentives to specific applications. -## Smart contract reference +## Tools -For detailed information about the smart contracts and their functions, see the [Smart Contract Reference](/nodes/staking-pools/contracts). +Use [`install.sh`](https://github.com/berachain/guides/tree/main/apps/staking-pools/install-helpers/install.sh) and [`delegator-delegate.sh`](https://github.com/berachain/guides/tree/main/apps/staking-pools/install-helpers/delegator-delegate.sh) in `install-helpers/` to install a pool and form a delegated handler. A React [example frontend](/nodes/staking-pools/operators#building-your-front-end) covers post-install staker operations. The Python SmartOperator Manager CLI manages operator contracts on the validator. -## Support and resources +Start with [Install a staking pool](/nodes/staking-pools/installation). Day-two work is in the [Operator guide](/nodes/staking-pools/operators). Addresses and ABIs are in [Look up staking pool contract addresses](/nodes/staking-pools/contracts). -If you don't have contact with Berachain Validator Relations, ask on our [Discord](https://discord.gg/berachain) **#node-support** channel. +If you don't have Validator Relations contact, ask in the [Discord](https://discord.gg/berachain) **#node-support** channel. diff --git a/scripts/contracts/generate-pages.mjs b/scripts/contracts/generate-pages.mjs index b2c3f7b..556c19c 100644 --- a/scripts/contracts/generate-pages.mjs +++ b/scripts/contracts/generate-pages.mjs @@ -394,37 +394,35 @@ The following is a list of contract addresses for interacting with Berachain BEX function renderStakingPoolsPage() { return `--- -title: "Staking Pool Contracts" -description: "Addresses and ABIs for StakingPoolContractsFactory, DelegationHandlerFactory, WithdrawalVault, and per-pool proxies (StakingPool, SmartOperator, etc.)." +title: "Look up staking pool contract addresses" +description: "Singleton addresses and ABIs, plus the per-pool proxies StakingPoolContractsFactory deploys." --- import StakingPoolSingletonsTable from "/snippets/contracts/generated/staking-pools-singletons-table.mdx"; -This reference provides contract addresses and links to detailed documentation for each contract in the staking pools system. For an overview of how the contracts work together, see the [Staking Pools Overview](/nodes/staking-pools/overview). +This page lists staking-pool contract addresses and ABI links. The [Staking pools overview](/nodes/staking-pools/overview) explains how the contracts fit together. -## Contract addresses +## Singleton contracts -### Singleton contracts - -Singleton contracts are deployed once and shared across all staking pools. The **StakingPoolContractsFactory** is the entry point for deploying a staking pool: you call it to create and register your pool's contracts. The **DelegationHandlerFactory** deploys per-validator delegation handler proxies when using foundation delegation. The other singletons below are shared infrastructure. +These contracts are deployed once and shared by every pool: -### Deployed contracts (per pool) +Call \`StakingPoolContractsFactory\` to create and register a pool. Call \`DelegationHandlerFactory\` to deploy a per-validator handler proxy for Foundation delegation. The other rows are shared infrastructure. -When you deploy a staking pool through the StakingPoolContractsFactory, the factory creates proxy contracts for your validator. These proxy contracts are what you and your stakers interact with directly. Each validator receives unique proxy addresses for these contracts when deploying their staking pool. +## Per-pool proxy contracts -The factory returns these proxy addresses when you call \`deployStakingPoolContracts\`. Store these addresses for your operations and provide the StakingPool address to your stakers for deposits. +\`deployStakingPoolContracts\` returns proxy addresses for your validator. You and your stakers call those proxies. Store the addresses. Give stakers the StakingPool address for deposits. -**Proxy contracts deployed with your pool:** +Proxy contracts for each pool: -- **StakingPool**: Main staking functionality and staker interactions. This is the contract address your stakers use to deposit BERA and receive stBERA shares. -- **SmartOperator**: Validator operations and Proof of Liquidity integration. -- **IncentiveCollector**: Incentive token collection and conversion. Handles the incentive auction mechanism where accumulated tokens can be claimed. -- **StakingRewardsVault**: Reward collection and automatic reinvestment. Automatically compounds rewards from the consensus layer. -- **DelegationHandler**: Delegation handling for capital providers. Only deployed if you're using delegated funds from the Berachain Foundation. +- **StakingPool**: deposits, shares, and staker interactions. Stakers deposit BERA here and receive stBERA. +- **SmartOperator**: validator operations and Proof of Liquidity integration. +- **IncentiveCollector**: incentive token collection, conversion, and the incentive auction. +- **StakingRewardsVault**: consensus-layer reward collection and compounding. +- **DelegationHandler**: Foundation-delegated capital. Deployed only on the delegated path. -For detailed technical documentation of each contract's functions and behavior, see the [Berachain guides repository](https://github.com/berachain/guides/tree/main/apps/staking-pools) and the [install-helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md). +ABI JSON for each singleton is in the table above. For install and operator flows, see [Install a staking pool](/nodes/staking-pools/installation) and the [Operator guide](/nodes/staking-pools/operators). `; } From 684b50b75820cddf3dccc3d02cd96d5d034c42f0 Mon Sep 17 00:00:00 2001 From: Camembear Date: Tue, 1 Sep 2026 22:39:05 -0400 Subject: [PATCH 15/20] Keep published BUSD naming when regenerating contract pages. Format only generator outputs with Prettier, and stop chaining a repo-wide make format that rewrites unrelated docs. --- data/contracts.json | 8 +- scripts/contracts/generate-pages.mjs | 114 ++++++++++-------- .../generated/core-contracts-table.mdx | 4 +- 3 files changed, 72 insertions(+), 54 deletions(-) diff --git a/data/contracts.json b/data/contracts.json index 0bb9d35..5b3dac0 100644 --- a/data/contracts.json +++ b/data/contracts.json @@ -189,16 +189,16 @@ "honey": { "name": "BUSD", "abi": { - "berachainMainnet": "https://github.com/berachain/abis/blob/main/mainnet/contracts/honey/Honey.json", - "berachainBepolia": "https://github.com/berachain/abis/blob/main/bepolia/contracts/honey/Honey.json" + "berachainMainnet": "https://github.com/berachain/abis/blob/main/mainnet/contracts/busd/BUSD.json", + "berachainBepolia": "https://github.com/berachain/abis/blob/main/bepolia/contracts/BUSD/BUSD.json" }, "address": { "berachainMainnet": "0xFCBD14DC51f0A4d49d5E53C2E0950e0bC26d0Dce", "berachainBepolia": "0xFCBD14DC51f0A4d49d5E53C2E0950e0bC26d0Dce" }, "source": { - "berachainMainnet": "https://github.com/berachain/contracts/blob/main/src/honey/Honey.sol", - "berachainBepolia": "https://github.com/berachain/contracts/blob/bepolia/src/honey/Honey.sol" + "berachainMainnet": "https://github.com/berachain/contracts/blob/main/src/busd/BUSD.sol", + "berachainBepolia": "https://github.com/berachain/contracts/blob/bepolia/src/BUSD/BUSD.sol" } }, "wberaStakerVault": { diff --git a/scripts/contracts/generate-pages.mjs b/scripts/contracts/generate-pages.mjs index 556c19c..c6baa61 100644 --- a/scripts/contracts/generate-pages.mjs +++ b/scripts/contracts/generate-pages.mjs @@ -1,25 +1,43 @@ #!/usr/bin/env node +import { execFileSync } from "node:child_process"; import fs from "node:fs"; +import { createRequire } from "node:module"; import path from "node:path"; -import { execFileSync } from "node:child_process"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +function resolvePrettierPackage() { + try { + return createRequire(import.meta.url).resolve("prettier"); + } catch { + // This repo has no package.json; prettier is on PATH for `make format`. + } + + let cli; + try { + cli = execFileSync("which", ["prettier"], { encoding: "utf8" }).trim(); + } catch { + throw new Error("prettier is not on PATH; install it the same way `make format` expects"); + } + + return createRequire(pathToFileURL(fs.realpathSync(cli))).resolve("prettier"); +} -const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), "../.."); +const prettier = (await import(pathToFileURL(resolvePrettierPackage()).href)).default; + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const contracts = JSON.parse(fs.readFileSync(path.join(repoRoot, "data/contracts.json"), "utf8")); const generatedSnippetDir = "snippets/contracts/generated"; const checkMode = process.argv.includes("--check"); -const missingValue = "Not deployed"; +const missingValue = "🤓"; let changedCount = 0; let wroteCount = 0; let unchangedCount = 0; -function write(relPath, content) { +async function write(relPath, content) { const abs = path.join(repoRoot, relPath); - const next = execFileSync("prettier", ["--stdin-filepath", relPath], { - cwd: repoRoot, - encoding: "utf8", - input: `${content.trimEnd()}\n`, - }); + const config = await prettier.resolveConfig(abs); + const next = await prettier.format(content, { ...(config ?? {}), filepath: abs }); const prev = fs.existsSync(abs) ? fs.readFileSync(abs, "utf8") : null; if (prev === next) { unchangedCount += 1; @@ -43,8 +61,7 @@ function write(relPath, content) { function berascanLink(address, network) { if (!address) return missingValue; - const host = - network === "berachainBepolia" ? "https://testnet.berascan.com" : "https://berascan.com"; + const host = network === "berachainBepolia" ? "https://testnet.berascan.com" : "https://berascan.com"; return `[\`${address}\`](${host}/address/${address})`; } @@ -72,7 +89,7 @@ function resourcesCell(item, network, abiLabel = "ABI") { const resources = [ ["abi", abiLabel], ["source", "Source"], - ["reference", "Reference"], + ["reference", "Reference"] ] .map(([key, label]) => { const href = linkFor(item, key, network); @@ -85,8 +102,7 @@ function resourcesCell(item, network, abiLabel = "ABI") { function berascanUrl(address, network) { if (!address) return ""; - const host = - network === "berachainBepolia" ? "https://testnet.berascan.com" : "https://berascan.com"; + const host = network === "berachainBepolia" ? "https://testnet.berascan.com" : "https://berascan.com"; return `${host}/address/${address}`; } @@ -96,9 +112,7 @@ function resourceLinks(item, network, abiLabel = "ABI") { address ? `[Berascan](${berascanUrl(address, network)})` : "", linkFor(item, "abi", network) ? `[${abiLabel}](${linkFor(item, "abi", network)})` : "", linkFor(item, "source", network) ? `[Source](${linkFor(item, "source", network)})` : "", - linkFor(item, "reference", network) - ? `[Reference](${linkFor(item, "reference", network)})` - : "", + linkFor(item, "reference", network) ? `[Reference](${linkFor(item, "reference", network)})` : "" ].filter(Boolean); return links.length ? links.join(" · ") : missingValue; @@ -145,7 +159,7 @@ function networkRow(item, network, options = {}) { includeResources = false, combineNameAddress = false, boldName = false, - abiLabel = "ABI", + abiLabel = "ABI" } = options; const columns = combineNameAddress ? [contractCell(item, network, boldName)] @@ -194,9 +208,9 @@ function renderGettingStartedSnippet() { renderAddressCategory("Governance", governanceItems, "berachainMainnet", linkedColumns), renderAddressCategory("Staking pools", stakingPoolItems, "berachainMainnet", { ...linkedColumns, - abiLabel: "ABI JSON", + abiLabel: "ABI JSON" }), - renderAddressCategory("Other", otherItems, "berachainMainnet", linkedColumns), + renderAddressCategory("Other", otherItems, "berachainMainnet", linkedColumns) ] .filter(Boolean) .join("\n\n"); @@ -206,18 +220,15 @@ function renderGettingStartedSnippet() { renderAddressCategory("Governance", governanceItems, "berachainBepolia", linkedColumns), renderAddressCategory("Staking pools", stakingPoolItems, "berachainBepolia", { ...linkedColumns, - abiLabel: "ABI JSON", + abiLabel: "ABI JSON" }), - renderAddressCategory("Other", otherItems, "berachainBepolia", linkedColumns), + renderAddressCategory("Other", otherItems, "berachainBepolia", linkedColumns) ] .filter(Boolean) .join("\n\n"); const nftRows = Object.values(contracts.nfts) - .map( - (item) => - `| ${item.name} | \`${item.address.ethereumMainnet}\` | \`${item.address.berachainMainnet}\` |` - ) + .map((item) => `| ${item.name} | \`${item.address.ethereumMainnet}\` | \`${item.address.berachainMainnet}\` |`) .join("\n"); const nftSection = `### NFT contracts @@ -291,7 +302,7 @@ ${parts.join("\n\n")}`; return [ renderBendNetworkSection("Mainnet contracts", "berachainMainnet"), - renderBendNetworkSection("Bepolia testnet contracts", "berachainBepolia"), + renderBendNetworkSection("Bepolia testnet contracts", "berachainBepolia") ] .filter(Boolean) .join("\n\n"); @@ -469,28 +480,35 @@ To find a market ID, open [bend.berachain.com/borrow](https://bend.berachain.com `; } -write(`${generatedSnippetDir}/core-contracts-table.mdx`, renderGettingStartedSnippet()); -write(`${generatedSnippetDir}/bex-contracts-table.mdx`, renderBexSnippet()); -write(`${generatedSnippetDir}/bend-contracts-table.mdx`, renderBendContractsSnippet()); -write(`${generatedSnippetDir}/bend-markets-table.mdx`, renderBendMarketsSnippet()); -write(`${generatedSnippetDir}/staking-pools-singletons-table.mdx`, renderStakingPoolsSnippet()); - -write("build/getting-started/deployed-contracts.mdx", renderGettingStartedPage()); -write("build/bex/deployed-contracts.mdx", renderBexDeployedContractsPage()); -write("build/bend/deployed-contracts.mdx", renderBendContractsPage()); -write("build/bend/deployed-markets.mdx", renderBendMarketsPage()); -write("nodes/staking-pools/contracts.mdx", renderStakingPoolsPage()); +async function generate() { + await write(`${generatedSnippetDir}/core-contracts-table.mdx`, renderGettingStartedSnippet()); + await write(`${generatedSnippetDir}/bex-contracts-table.mdx`, renderBexSnippet()); + await write(`${generatedSnippetDir}/bend-contracts-table.mdx`, renderBendContractsSnippet()); + await write(`${generatedSnippetDir}/bend-markets-table.mdx`, renderBendMarketsSnippet()); + await write(`${generatedSnippetDir}/staking-pools-singletons-table.mdx`, renderStakingPoolsSnippet()); + + await write("build/getting-started/deployed-contracts.mdx", renderGettingStartedPage()); + await write("build/bex/deployed-contracts.mdx", renderBexDeployedContractsPage()); + await write("build/bend/deployed-contracts.mdx", renderBendContractsPage()); + await write("build/bend/deployed-markets.mdx", renderBendMarketsPage()); + await write("nodes/staking-pools/contracts.mdx", renderStakingPoolsPage()); + + if (!checkMode) { + console.log(`contracts-generate: wrote ${wroteCount} file(s), ${unchangedCount} unchanged.`); + } -if (!checkMode) { - console.log(`contracts-generate: wrote ${wroteCount} file(s), ${unchangedCount} unchanged.`); + if (checkMode) { + if (changedCount > 0) { + console.error( + `Generated contract docs are stale (${changedCount} file(s) would change). Run node scripts/contracts/generate-pages.mjs and commit the outputs.` + ); + process.exit(1); + } + console.log(`Generated contract docs are up to date (${unchangedCount} file(s) unchanged).`); + } } -if (checkMode) { - if (changedCount > 0) { - console.error( - `Generated contract docs are stale (${changedCount} file(s) would change). Run node scripts/contracts/generate-pages.mjs and commit the outputs.` - ); - process.exit(1); - } - console.log(`Generated contract docs are up to date (${unchangedCount} file(s) unchanged).`); +const thisFile = fileURLToPath(import.meta.url); +if (process.argv[1] && path.resolve(process.argv[1]) === thisFile) { + await generate(); } diff --git a/snippets/contracts/generated/core-contracts-table.mdx b/snippets/contracts/generated/core-contracts-table.mdx index 3102b15..03b3ac7 100644 --- a/snippets/contracts/generated/core-contracts-table.mdx +++ b/snippets/contracts/generated/core-contracts-table.mdx @@ -21,7 +21,7 @@ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | BGT Token
[`0x656b95E550C07a9ffe548bd4085c72418Ceb1dba`](https://berascan.com/address/0x656b95E550C07a9ffe548bd4085c72418Ceb1dba) | [ABI](https://github.com/berachain/abis/blob/main/mainnet/contracts/pol/BGT.json) · [Source](https://github.com/berachain/contracts/blob/main/src/pol/BGT.sol) | | WBERA
[`0x6969696969696969696969696969696969696969`](https://berascan.com/address/0x6969696969696969696969696969696969696969) | [ABI](https://github.com/berachain/abis/blob/main/mainnet/contracts/WBERA.json) · [Source](https://github.com/berachain/contracts/blob/main/src/WBERA.sol) | -| BUSD
[`0xFCBD14DC51f0A4d49d5E53C2E0950e0bC26d0Dce`](https://berascan.com/address/0xFCBD14DC51f0A4d49d5E53C2E0950e0bC26d0Dce) | [ABI](https://github.com/berachain/abis/blob/main/mainnet/contracts/honey/Honey.json) · [Source](https://github.com/berachain/contracts/blob/main/src/honey/Honey.sol) | +| BUSD
[`0xFCBD14DC51f0A4d49d5E53C2E0950e0bC26d0Dce`](https://berascan.com/address/0xFCBD14DC51f0A4d49d5E53C2E0950e0bC26d0Dce) | [ABI](https://github.com/berachain/abis/blob/main/mainnet/contracts/busd/BUSD.json) · [Source](https://github.com/berachain/contracts/blob/main/src/busd/BUSD.sol) | | WBERA Staker Vault (sWBERA)
[`0x118D2cEeE9785eaf70C15Cd74CD84c9f8c3EeC9a`](https://berascan.com/address/0x118D2cEeE9785eaf70C15Cd74CD84c9f8c3EeC9a) | [ABI](https://github.com/berachain/abis/blob/main/mainnet/contracts/pol/WBERAStakerVault.json) · [Source](https://github.com/berachain/contracts/blob/main/src/pol/WBERAStakerVault.sol) | ### Governance @@ -84,7 +84,7 @@ Berachain NFT contract addresses on both Ethereum (via LayerZero adapters) and B | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | BGT Token
[`0x656b95E550C07a9ffe548bd4085c72418Ceb1dba`](https://testnet.berascan.com/address/0x656b95E550C07a9ffe548bd4085c72418Ceb1dba) | [ABI](https://github.com/berachain/abis/blob/main/bepolia/contracts/pol/BGT.json) · [Source](https://github.com/berachain/contracts/blob/bepolia/src/pol/BGT.sol) | | WBERA
[`0x6969696969696969696969696969696969696969`](https://testnet.berascan.com/address/0x6969696969696969696969696969696969696969) | [ABI](https://github.com/berachain/abis/blob/main/bepolia/contracts/WBERA.json) · [Source](https://github.com/berachain/contracts/blob/bepolia/src/WBERA.sol) | -| BUSD
[`0xFCBD14DC51f0A4d49d5E53C2E0950e0bC26d0Dce`](https://testnet.berascan.com/address/0xFCBD14DC51f0A4d49d5E53C2E0950e0bC26d0Dce) | [ABI](https://github.com/berachain/abis/blob/main/bepolia/contracts/honey/Honey.json) · [Source](https://github.com/berachain/contracts/blob/bepolia/src/honey/Honey.sol) | +| BUSD
[`0xFCBD14DC51f0A4d49d5E53C2E0950e0bC26d0Dce`](https://testnet.berascan.com/address/0xFCBD14DC51f0A4d49d5E53C2E0950e0bC26d0Dce) | [ABI](https://github.com/berachain/abis/blob/main/bepolia/contracts/BUSD/BUSD.json) · [Source](https://github.com/berachain/contracts/blob/bepolia/src/BUSD/BUSD.sol) | | WBERA Staker Vault (sWBERA)
[`0x118D2cEeE9785eaf70C15Cd74CD84c9f8c3EeC9a`](https://testnet.berascan.com/address/0x118D2cEeE9785eaf70C15Cd74CD84c9f8c3EeC9a) | [ABI](https://github.com/berachain/abis/blob/main/bepolia/contracts/pol/WBERAStakerVault.json) · [Source](https://github.com/berachain/contracts/blob/bepolia/src/pol/WBERAStakerVault.sol) | ### Governance From da6030955307311c8119d905a95fe3b7578ac858 Mon Sep 17 00:00:00 2001 From: Camembear Date: Wed, 2 Sep 2026 09:08:15 -0400 Subject: [PATCH 16/20] Fold delegators guide into installation and retire the page. Merge delegator-delegate.sh steps into installation.mdx, remove nav entry, redirect old delegators URLs to #form-the-handler, and fix cross-links. --- docs.json | 12 +- nodes/staking-pools/delegators.mdx | 41 ------ nodes/staking-pools/installation.mdx | 20 ++- nodes/staking-pools/operators.mdx | 197 +++++++++------------------ 4 files changed, 88 insertions(+), 182 deletions(-) delete mode 100644 nodes/staking-pools/delegators.mdx diff --git a/docs.json b/docs.json index 55f4e5b..c76f0e1 100644 --- a/docs.json +++ b/docs.json @@ -298,7 +298,6 @@ "pages": [ "nodes/staking-pools/installation", "nodes/staking-pools/operators", - "nodes/staking-pools/delegators", "nodes/staking-pools/contracts" ] } @@ -440,7 +439,7 @@ }, { "source": "/cn/validators/staking-pools/delegators", - "destination": "/validators/staking-pools/delegators" + "destination": "/validators/staking-pools/installation#form-the-handler" }, { "source": "/cn/validators/staking-pools/contracts", @@ -464,7 +463,7 @@ }, { "source": "/ko/validators/staking-pools/delegators", - "destination": "/validators/staking-pools/delegators" + "destination": "/validators/staking-pools/installation#form-the-handler" }, { "source": "/ko/validators/staking-pools/contracts", @@ -488,12 +487,17 @@ }, { "source": "/vi/validators/staking-pools/delegators", - "destination": "/validators/staking-pools/delegators" + "destination": "/validators/staking-pools/installation#form-the-handler" }, { "source": "/vi/validators/staking-pools/contracts", "destination": "/validators/staking-pools/contracts" }, + { + "source": "/nodes/staking-pools/delegators", + "destination": "/nodes/staking-pools/installation#form-the-handler", + "permanent": true + }, { "source": "/validators/:slug*", "destination": "/nodes/:slug*", diff --git a/nodes/staking-pools/delegators.mdx b/nodes/staking-pools/delegators.mdx deleted file mode 100644 index da6088f..0000000 --- a/nodes/staking-pools/delegators.mdx +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: "Run a pool with Foundation-delegated capital" -description: "Form a DelegationHandler, then run install.sh so the pool activates on delegated funds." ---- - -A delegated staking pool has two phases. Form the handler first. Then run `install.sh` so the pool uses those funds. - -A DelegationHandler is the Foundation contract that holds delegated capital for your validator. - -## Form the handler - -Run this from `guides/apps/staking-pools/install-helpers/` on a machine with RPC access. It does not have to be the validator host: - -```bash -export EL_RPC_URL=https://bepolia.rpc.berachain.com -./delegator-delegate.sh -``` - -The script deploys and funds the DelegationHandler, runs `delegate()`, and grants `VALIDATOR_ADMIN_ROLE`. It prompts for pubkey, delegate amount, and validator-admin address. Set `VALIDATOR_PUBKEY`, `DELEGATE_AMOUNT_BERA`, and `VALIDATOR_ADMIN` in `env.sh` to pre-fill those prompts, or pass `--pubkey`, `--amount`, and `--validator-admin` to skip them. - -The script prints each `cast send` (or runs it when `PRIVATE_KEY` is set). `delegate` and `grantRole` need `DEFAULT_ADMIN_ROLE` on the handler, often the Foundation Safe. - -## Install the pool after the handler exists - -Use the same `install-helpers/` directory. `install.sh` needs the Node API as well as execution-layer RPC: - -```bash -export EL_RPC_URL=https://bepolia.rpc.berachain.com -export CL_NODE_API_URL=http://127.0.0.1:3500 -./install.sh -``` - -If a formed handler with delegated funds exists for your pubkey, `install.sh` uses the delegated path. See [Install a staking pool](/nodes/staking-pools/installation). - -## What's next - -Continue with: - -- [Install a staking pool](/nodes/staking-pools/installation) -- [Look up staking pool contract addresses](/nodes/staking-pools/contracts) -- [Operate a staking pool](/nodes/staking-pools/operators) diff --git a/nodes/staking-pools/installation.mdx b/nodes/staking-pools/installation.mdx index 5644bf0..86184dd 100644 --- a/nodes/staking-pools/installation.mdx +++ b/nodes/staking-pools/installation.mdx @@ -1,6 +1,6 @@ --- title: "Install a staking pool" -description: "Deploy and activate a staking pool from a remote machine with install.sh." +description: "Deploy and activate a staking pool from a remote machine with install.sh, including Foundation-delegated capital." --- `install.sh` deploys and activates one staking pool from `guides/apps/staking-pools/install-helpers/`. Set the environment, run the script once, and read back the pool address and a receipt. The script uses Foundation-delegated funds when a DelegationHandler already exists for your validator. Otherwise it spends **10,000 BERA** plus gas from your funding wallet. @@ -49,11 +49,28 @@ cp env.sh.template env.sh Use `./install.sh --help` for usage. +## Form the handler + +Skip this section on the self-funded path. + +On the delegated path, form the handler before you run `install.sh`. Run this from `install-helpers/` on a machine with RPC access. It does not have to be the validator host: + +```bash +export EL_RPC_URL=https://bepolia.rpc.berachain.com +./delegator-delegate.sh +``` + +The script deploys and funds the DelegationHandler, runs `delegate()`, and grants `VALIDATOR_ADMIN_ROLE`. It prompts for pubkey, delegate amount, and validator-admin address. Set `VALIDATOR_PUBKEY`, `DELEGATE_AMOUNT_BERA`, and `VALIDATOR_ADMIN` in `env.sh` to pre-fill those prompts, or pass `--pubkey`, `--amount`, and `--validator-admin` to skip them. + +The script prints each `cast send` (or runs it when `PRIVATE_KEY` is set). `delegate` and `grantRole` need `DEFAULT_ADMIN_ROLE` on the handler, often the Foundation Safe. + ## Run install.sh The script deploys and activates one pool and doesn't resume. Run it once per pool: ```bash +export EL_RPC_URL=https://bepolia.rpc.berachain.com +export CL_NODE_API_URL=http://127.0.0.1:3500 ./install.sh ``` @@ -69,4 +86,3 @@ Continue with: - [Contract reference](/nodes/staking-pools/contracts) - [Operator guide](/nodes/staking-pools/operators) -- [Delegation guide](/nodes/staking-pools/delegators) diff --git a/nodes/staking-pools/operators.mdx b/nodes/staking-pools/operators.mdx index 6ce2720..1d37fb7 100644 --- a/nodes/staking-pools/operators.mdx +++ b/nodes/staking-pools/operators.mdx @@ -1,173 +1,100 @@ --- title: "Operate a staking pool" -description: "Roles, commission, reward allocation, min effective balance, withdrawals, and front-end." +description: "After install.sh: queue commission and allocation on SmartOperator, set min effective balance, then monitor WBERA and withdrawals." --- -Day-two work after `install.sh`: roles, commission, reward allocation, min effective balance, WBERA flows, and withdrawals. Deploy first with [Install a staking pool](/nodes/staking-pools/installation). +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. -## Quick reference +If the Foundation issued a delegation, form the handler first: [Form the handler](/nodes/staking-pools/installation#form-the-handler). -### Key parameters +## Set commission -| Parameter | Range | Purpose | -| ------------------------- | ------------------------------------------ | -------------------------------------------- | -| Validator Commission | 0-20% | Commission on incentive token distribution | -| Protocol Fee | 0-20% | Fee on eligible staking-pool reward growth | -| Minimum Effective Balance | ≥ 250,000 BERA | Activation threshold and full exit safeguard | -| Withdrawal Delay | 129,600 blocks (≈3 days at ~2s block time) | Time before withdrawals can be finalized | +`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%). -### Key roles +The queue is not live yet. After `commissionChangeDelay` blocks, anyone calls `BeraChef.activateQueuedValCommission(pubkey)`. `SmartOperator` does not wrap that activate. -| Role | Controls | Function | -| ---------------------------------- | --------------------- | -------------------------------------- | -| `VALIDATOR_ADMIN_ROLE` | All other roles | Grant/revoke operational roles | -| `REWARDS_ALLOCATION_MANAGER_ROLE` | Reward allocation | Direct PoL incentives to applications | -| `COMMISSION_MANAGER_ROLE` | Commission rate | Adjust validator commission (0-20%) | -| `PROTOCOL_FEE_MANAGER_ROLE` | Protocol fee | Adjust protocol fee percentage (0-20%) | -| `INCENTIVE_COLLECTOR_MANAGER_ROLE` | Payout amount | Adjust incentive collector payout | -| `DELEGATION_MANAGER_ROLE` | Delegation operations | Manage delegations | +## Direct reward allocation -### Essential functions +`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`. -Core lifecycle and PoL reward allocation: +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. -| Function | Contract | Purpose | -| ---------------------------- | ------------- | ---------------------------- | -| `setMinEffectiveBalance()` | SmartOperator | Set activation threshold | -| `queueValCommission()` | SmartOperator | Queue commission rate change | -| `queueRewardsAllocation()` | SmartOperator | Queue reward allocation | -| `setProtocolFeePercentage()` | SmartOperator | Set protocol fee rate | +## Set the activation threshold -Protocol fee accrual (same percentage applies to both tracks during migration): +`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. -| Function | Contract | Purpose | -| ------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------- | -| `accrueEarnedWBERAFees()` | SmartOperator | Accrue protocol fees on **WBERA** balance growth: primary path after PoL Next. | -| `accrueEarnedBGTFees()` | SmartOperator | Accrue protocol fees on remaining **BGT** balance: **legacy / transitional** until BGT allowances are exhausted on your pool. | +When `totalDeposits + bufferedAssets` first reaches that floor, `StakingPool` sets `activeThresholdReached` and records `_validatorActivationBlock`. -Manual lever for compounding operator WBERA into pool assets: +A later withdrawal whose consensus-layer portion would leave `totalDeposits` below `minEffectiveBalance()` triggers a full exit (`_triggerFullExit`). -| Function | Contract | Purpose | -| ------------------ | ----------- | ------------------------------------------------------------------------------------------------------------- | -| `processRewards()` | StakingPool | Pull buffered staking rewards and any operator-held WBERA into the pool. Permissionless; callable any caller. | +## Set the protocol fee -Operator action is not required to make WBERA reach stakers. Compounding fires automatically inside any user `deposit()` and inside `processRewards()`; covering withdrawals from operator-side WBERA fires automatically inside `WithdrawalVault.requestWithdrawal()`. See [Automatic WBERA flows](#automatic-wbera-flows) below. +`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. -Legacy incentive surface (kept for transition; see [Deprecated BGT entry points](#deprecated-bgt-entry-points)): - -| Function | Contract | Purpose | -| --------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `claimBoostRewards()` | SmartOperator | **Legacy:** forward **BGT-era** boost incentive claims toward **`IncentiveCollector`**. Prefer WBERA-native operator flows for ongoing activity; treat as transitional. | - -## What you need before you operate a pool - -Before setting up a staking pool, ensure you have a fully operational Berachain validator node. You'll need at least 10,000 BERA to register the pool, though activation requires at least 250,000 BERA. See the [Validator Lifecycle](/nodes/architecture/validator-lifecycle) and [Become a Validator](/nodes/guides/become-a-validator) guides. - - - Staking pools follow the standard Berachain validator lifecycle. After deployment, your validator - will progress through the Deposited → Eligible states, but activation to the Active state depends - on the ValidatorSetCap and your validator's priority relative to other validators. - - -## Validator lifecycle - -Your staking pool integrates with Berachain's validator lifecycle. For details on validator states (Deposited, Eligible, Active, Exited, Withdrawn) and transitions, see the [Validator Lifecycle documentation](/nodes/architecture/validator-lifecycle). - -The key consideration for staking pools is ensuring sufficient stake for activation. See [Setting Minimum Effective Balance](#setting-minimum-effective-balance) below. - -## Key terms and concepts - -- **Active Threshold**: The point at which your pool has sufficient stake (`totalDeposits >= minEffectiveBalance`) to activate the validator. When `activeThresholdReached()` returns `true`, your validator enters a cooldown period before activation. -- **Minimum Effective Balance (`minEffectiveBalance`)**: The minimum stake amount required for validator activation and a safeguard that triggers full exit if deposits fall below it. This must match or exceed the current consensus layer minimum (250,000 BERA when the set is not full; when the set is full, the minimum is 10,000 BERA more than the lowest active validator). -- **Withdrawal Delay**: 129,600 blocks (≈3 days at ~2s block time) that must pass after a withdrawal request before it can be finalized. -- **Cooldown Period**: After `activeThresholdReached()` becomes `true`, there is a cooldown period before the validator activates. - -## Yield model - -After the May 2026 PoL upgrade, per-block validator emission is a **flat fixed rate** (no boost curve). -Pools no longer differentiate on attracted BGT delegation; what you control is: - -- **Validator reward allocation (cutting board)**: direct emissions to vaults you and your community care about. See [Manage Reward Allocations](/nodes/guides/manage-reward-allocations). -- **Incentives attracted to vaults you allocate to**: protocols still incentivize specific Reward Vaults; a strong allocation strategy increases incentive yield to your stakers. -- **Validator commission on incentive tokens**: capped at 20%, denominated in the WBERA-era incentive flow. See [Manage Validator Incentives Commission Rate](/nodes/guides/manage-incentives-commission). - -Validator emissions accrue as \$WBERA on your `SmartOperator`. Protocol fee accrual on the WBERA track uses `accrueEarnedWBERAFees()`. The same WBERA fee update also runs automatically inside `withdrawRewards`, `pullBeraToWithdrawalVault`, and `setProtocolFeePercentage()`, so calling `accrueEarnedWBERAFees()` directly is mostly a forced-settlement / monitoring tool. `accrueEarnedBGTFees()` is a no-op once chargeable BGT is exhausted. +`accrueEarnedWBERAFees` and `accrueEarnedBGTFees` are public, `whenNotFullyExited`, and have no role. Accrual also runs inside `withdrawRewards`, `pullBeraToWithdrawalVault`, `notifyWithdrawalRequest`, `fullExitQueueDropBoost`, and `setProtocolFeePercentage`. ## Automatic WBERA flows -`SmartOperator` exposes two state-changing entry points that move WBERA out of the operator: `withdrawRewards(uint256)` and `pullBeraToWithdrawalVault(uint256)`. Both are restricted by sender: `withdrawRewards` accepts the StakingPool only; `pullBeraToWithdrawalVault` accepts the WithdrawalVault only. Operators cannot call them directly, and no operator action is required to make WBERA reach stakers. - -- **Compounding into pool assets**: Any user `deposit()` and any call to `StakingPool.processRewards()` invokes `_collectRewards(...)`, which calls `SmartOperator.withdrawRewards(...)` on operator WBERA first, then drains the staking rewards vault. Operator WBERA is pulled first so `_getTotalAssets()` is correct before protocol fees mint staker shares via `mintFeeShares` during WBERA fee accrual. The unwrapped BERA is credited to the pool, lifts `_getTotalAssets()`, and lifts share price for every staker. -- **Deposit ordering and fee fairness**: Inside `_submit`, the pool mints the depositor's stBERA **before** calling `_collectRewards(...)`. The depositor's shares are priced against the pre-collection NAV (which already includes pending operator WBERA via `rebaseableWberaAmount()`), so they pay a fair price for the yield about to settle. The collection that follows charges WBERA-track protocol fees and mints fee shares to the default recipient against the post-deposit NAV, so the new depositor's principal is not taxed as if it were accrued yield. `processRewards()` runs the same `_collectRewards → bufferedAssets bump → _processDeposit` sequence without minting user shares. -- **Covering withdrawals from operator liquidity**: `WithdrawalVault.requestWithdrawal()` precomputes `pulledFromOperator = min(amountInWei, availableWBERABalance)` and calls `pullBeraToWithdrawalVault(...)` on the normal partial-withdrawal branch. The execution-layer withdrawal request to the consensus layer is reduced to the uncovered remainder; if the operator covers in full, no consensus-layer request is issued and the EIP-7002 fee is refunded to the requester. Short-circuit (pool not yet at active threshold) and full-exit branches do not consume operator WBERA. -- **Full exit sweep**: Triggering a full exit sweeps any idle operator WBERA into the pool before the pool forwards its native balance to the WithdrawalVault. - -For monitoring, watch `availableWBERABalance()`, `rebaseableWberaAmount()`, and `getEarnedWBERAFeeState()` on the SmartOperator. The only operator-side write you may want to invoke explicitly is `processRewards()` on the StakingPool (permissionless `whenNotPaused`): it forces a compounding cycle without waiting for the next user deposit. +You cannot call the two functions that move WBERA off `SmartOperator`: -## Configuration +- `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. -### Commission rates +What the pool and vault do: -You can set commission rates within 0-20%. Commission applies to the distribution of incentive tokens from Proof of Liquidity rewards. For step-by-step instructions, see [Manage Validator Incentives Commission Rate](/nodes/guides/manage-incentives-commission). +- `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`. -### Reward allocations - -Direct PoL incentives to specific applications. For instructions, see [Managing Validator Reward Allocations](/nodes/guides/manage-reward-allocations). - -### Setting minimum effective balance - -The `minEffectiveBalance` parameter gates validator activation. The consensus layer enforces a base minimum of **250,000 BERA**. When the validator set is full (69 validators), the minimum required increases in increments of **10,000 BERA**. Set `minEffectiveBalance` to match the current consensus layer requirement. You can check the current lowest active stake on [Berachain Hub](https://hub.berachain.com/boost/). - -## Routine operations - -- **Monitor pool status**: Use `isActive()`, `totalAssets()`, `bufferedAssets()`, `activeThresholdReached()` on your StakingPool and SmartOperator. For operator-side liquidity, also watch **`availableWBERABalance()`**, **`rebaseableWberaAmount()`**, and **`getEarnedWBERAFeeState()`** on SmartOperator. See [Automatic WBERA flows](#automatic-wbera-flows) for how this liquidity moves. -- **Delegation operations**: Manage delegations as needed. **PoL Next:** validator emissions accrue as **\$WBERA** on **`SmartOperator`**. Accrue protocol fees on the WBERA track with **`accrueEarnedWBERAFees()`**; use **`accrueEarnedBGTFees()`** only while your pool still has chargeable **BGT** on the operator. **`claimBoostRewards()`** is a **legacy** entry point for the **BGT** incentive-distributor surface: confirm it still matches your deployed implementation before relying on it for new workflows. -- **Emission token context**: PoL Reward Vault emissions are distributed in **\$WBERA**. -- **Protocol fee**: Set via `setProtocolFeePercentage()` (up to 20%). The fee setter charges both tracks before applying the new rate. Call `accrueEarnedWBERAFees()` to force WBERA-track settlement; call `accrueEarnedBGTFees()` if you want to settle the legacy BGT track explicitly. - -## Deprecated BGT entry points - -The May 2026 PoL upgrade deprecates the BGT-era surface on `SmartOperator`. The following entry points stay callable for transition (all `whenNotFullyExited` where applicable) but become inert once chargeable BGT is exhausted on your operator: - -- `queueBoost()`, `activateBoost()`, `queueDropBoost(uint128)`: boost-management calls; per-block emission no longer scales with delegation, so these have no effect on yield. -- `claimBgtStakerReward()`: BGT-staker reward forwarder. -- `claimBoostRewards(IBGTIncentiveDistributor.Claim[], address[])`: BGT-era boost incentive distributor forwarder. -- `accrueEarnedBGTFees()`: fee accrual on the BGT track; no-op when chargeable BGT is zero. - -Unboost, drop-boost, and redeem BGT held on your `SmartOperator` with those same calls during the transition window. +Watch `StakingPool.isActive` (factory `activate` succeeded), `activeThresholdReached`, `totalDeposits`, `bufferedAssets`, and on `SmartOperator`: `availableWBERABalance()`, `rebaseableWberaAmount()`, `getEarnedWBERAFeeState()`. ## Withdrawal system -The shared **`WithdrawalVault`** is a per-chain singleton (a single proxy + implementation) that finalizes withdrawal requests for every staking pool. Three sources of liquidity can satisfy a request: +Stakers call `WithdrawalVault.requestWithdrawal` or `requestRedeem`. The vault mints a non-transferable NFT and stores `requestBlock`. -- **Short-circuit**: pool not yet at the active threshold. Liquidity comes only from the pool's on-chain **`bufferedAssets`**. BeaconKit honors EIP-7002 withdrawal requests only for **active** validators; a validator that has not activated cannot withdraw via the consensus layer, so this branch never issues a CL request. If the buffer cannot cover the request, the transaction reverts rather than falling through into post-threshold or full-exit logic. -- **Standard consensus-layer withdrawal**: execution-layer request to the validator's pubkey. -- **Operator-side WBERA cover**: at request time, the vault pulls from **`availableWBERABalance()`** on your pool's `SmartOperator`, unwraps to native BERA, and applies it to the request. Coverage may be **full** (the consensus-layer request is skipped and the EIP-7002 fee is refunded), **partial** (the remainder still exits via the consensus layer), or **none** when operator liquidity is empty. +Liquidity: -**Post-exit pools:** Once the validator has fully exited (`isFullyExited`), withdrawal handling skips the pre-threshold default-recipient gate and the post-threshold activation cooldown. BERA is already in the `WithdrawalVault`; requests proceed without re-entering activation-era guards. +- **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. -**Finalization delay:** `finalizeWithdrawalRequest` / `finalizeWithdrawalRequests` enforce `block.number >= requestBlock + WITHDRAWAL_REQUEST_FINALIZATION_BLOCK_DELAY` (currently **129,600** blocks, ≈3 days at ~2s block time) for every request. The cover source affects only **where the BERA comes from**; it does not shorten the cooldown. The pull from operator WBERA happens at **request** time, so covered BERA sits in the vault until the cooldown elapses. +`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. -**Retrying a full exit (`retryFullExit`):** When a full exit is triggered, the pool marks the validator fully exited and the vault submits an EIP-7002 full-exit request (withdrawal amount **0**). If that consensus-layer request fails or is dropped, anyone can call **`retryFullExit(pubkey, maxFeeToPay)`** on the shared `WithdrawalVault` to re-submit: permissionless, sending the EIP-7002 **withdrawal fee** with the call (`maxFeeToPay` is the most you are willing to pay). The validator must already be marked fully exited (`NotFullyExited` otherwise). The call reverts with **`PendingWithdrawalInFlight`** if a partial withdrawal was requested within the last **49,153** blocks (`WITHDRAWAL_FLIGHT_BLOCK_DELAY`), giving in-flight partial exits time to settle before retrying a full exit. Success emits **`FullExitRequestRetried`**. This path is for recovering a stuck post-exit CL sweep; normal staker withdrawal requests do not use it. +`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 -Your front-end should: - -- Display withdrawal status and when each request can be finalized (use `getWithdrawalRequest(requestId)` and `requestBlock + 129600`). -- Support batch finalization with `finalizeWithdrawalRequests([...])`. -- Show staker balance, share price, and total rewards (e.g. via `previewRedeem(shares)`). - -Berachain provides a React-based example template in the [guides repository](https://github.com/berachain/guides/tree/main/apps/staking-pools/frontend). Copy `config.example.json`, set your pool address and validator pubkey (from `install.sh` output or factory `getCoreContracts`), and deploy. +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`. -## Delegation +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`. -If you received a Foundation delegation, see [Run a pool with Foundation-delegated capital](/nodes/staking-pools/delegators). - -## What's next - -Continue with: +## Deprecated BGT entry points -- [What staking pools do](/nodes/staking-pools/overview) -- [Look up staking pool contract addresses](/nodes/staking-pools/contracts) -- [install-helpers README](https://github.com/berachain/guides/blob/main/apps/staking-pools/install-helpers/README.md) +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). From 0344415d49d1d23595ce4ac014500a439a6c6e8f Mon Sep 17 00:00:00 2001 From: Camembear Date: Wed, 2 Sep 2026 09:25:49 -0400 Subject: [PATCH 17/20] Drop handler formation from public installation docs. Delegated setup is an internal Foundation process; operators only need an existing DelegationHandler. Redirect delegators URLs to installation without the form-the-handler anchor. --- docs.json | 8 ++++---- nodes/staking-pools/installation.mdx | 21 +++------------------ nodes/staking-pools/operators.mdx | 2 -- nodes/staking-pools/overview.mdx | 2 +- 4 files changed, 8 insertions(+), 25 deletions(-) diff --git a/docs.json b/docs.json index c76f0e1..995941f 100644 --- a/docs.json +++ b/docs.json @@ -439,7 +439,7 @@ }, { "source": "/cn/validators/staking-pools/delegators", - "destination": "/validators/staking-pools/installation#form-the-handler" + "destination": "/validators/staking-pools/installation" }, { "source": "/cn/validators/staking-pools/contracts", @@ -463,7 +463,7 @@ }, { "source": "/ko/validators/staking-pools/delegators", - "destination": "/validators/staking-pools/installation#form-the-handler" + "destination": "/validators/staking-pools/installation" }, { "source": "/ko/validators/staking-pools/contracts", @@ -487,7 +487,7 @@ }, { "source": "/vi/validators/staking-pools/delegators", - "destination": "/validators/staking-pools/installation#form-the-handler" + "destination": "/validators/staking-pools/installation" }, { "source": "/vi/validators/staking-pools/contracts", @@ -495,7 +495,7 @@ }, { "source": "/nodes/staking-pools/delegators", - "destination": "/nodes/staking-pools/installation#form-the-handler", + "destination": "/nodes/staking-pools/installation", "permanent": true }, { diff --git a/nodes/staking-pools/installation.mdx b/nodes/staking-pools/installation.mdx index 86184dd..71224af 100644 --- a/nodes/staking-pools/installation.mdx +++ b/nodes/staking-pools/installation.mdx @@ -1,6 +1,6 @@ --- title: "Install a staking pool" -description: "Deploy and activate a staking pool from a remote machine with install.sh, including Foundation-delegated capital." +description: "Deploy and activate a staking pool from a remote machine with install.sh." --- `install.sh` deploys and activates one staking pool from `guides/apps/staking-pools/install-helpers/`. Set the environment, run the script once, and read back the pool address and a receipt. The script uses Foundation-delegated funds when a DelegationHandler already exists for your validator. Otherwise it spends **10,000 BERA** plus gas from your funding wallet. @@ -16,9 +16,9 @@ You need a synced validator, the helper tools, and live endpoints: You also need one funding path: - Self-funded: at least **10,000 BERA** plus gas in the funding wallet -- Delegated: a DelegationHandler already formed and funded, plus gas (for example, 0.1 BERA) for activation +- Delegated: a DelegationHandler already formed and funded for your validator, plus gas (for example, 0.1 BERA) for activation -A DelegationHandler is the Foundation contract that holds delegated capital for your validator. The Berachain Foundation creates it when they issue a delegation. +The Berachain Foundation forms the DelegationHandler when they issue a delegation. That setup is internal; operators run `install.sh` once the handler exists. ## Clone install-helpers @@ -49,21 +49,6 @@ cp env.sh.template env.sh Use `./install.sh --help` for usage. -## Form the handler - -Skip this section on the self-funded path. - -On the delegated path, form the handler before you run `install.sh`. Run this from `install-helpers/` on a machine with RPC access. It does not have to be the validator host: - -```bash -export EL_RPC_URL=https://bepolia.rpc.berachain.com -./delegator-delegate.sh -``` - -The script deploys and funds the DelegationHandler, runs `delegate()`, and grants `VALIDATOR_ADMIN_ROLE`. It prompts for pubkey, delegate amount, and validator-admin address. Set `VALIDATOR_PUBKEY`, `DELEGATE_AMOUNT_BERA`, and `VALIDATOR_ADMIN` in `env.sh` to pre-fill those prompts, or pass `--pubkey`, `--amount`, and `--validator-admin` to skip them. - -The script prints each `cast send` (or runs it when `PRIVATE_KEY` is set). `delegate` and `grantRole` need `DEFAULT_ADMIN_ROLE` on the handler, often the Foundation Safe. - ## Run install.sh The script deploys and activates one pool and doesn't resume. Run it once per pool: diff --git a/nodes/staking-pools/operators.mdx b/nodes/staking-pools/operators.mdx index 1d37fb7..91a184b 100644 --- a/nodes/staking-pools/operators.mdx +++ b/nodes/staking-pools/operators.mdx @@ -5,8 +5,6 @@ description: "After install.sh: queue commission and allocation on SmartOperator 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. -If the Foundation issued a delegation, form the handler first: [Form the handler](/nodes/staking-pools/installation#form-the-handler). - ## 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%). diff --git a/nodes/staking-pools/overview.mdx b/nodes/staking-pools/overview.mdx index c6fb051..99d314e 100644 --- a/nodes/staking-pools/overview.mdx +++ b/nodes/staking-pools/overview.mdx @@ -69,7 +69,7 @@ You deploy through the factory, which registers the validator. Commission is up ## Tools -Use [`install.sh`](https://github.com/berachain/guides/tree/main/apps/staking-pools/install-helpers/install.sh) and [`delegator-delegate.sh`](https://github.com/berachain/guides/tree/main/apps/staking-pools/install-helpers/delegator-delegate.sh) in `install-helpers/` to install a pool and form a delegated handler. A React [example frontend](/nodes/staking-pools/operators#building-your-front-end) covers post-install staker operations. The Python SmartOperator Manager CLI manages operator contracts on the validator. +Use [`install.sh`](https://github.com/berachain/guides/tree/main/apps/staking-pools/install-helpers/install.sh) in `install-helpers/` to install a pool. A React [example frontend](/nodes/staking-pools/operators#building-your-front-end) covers post-install staker operations. The Python SmartOperator Manager CLI manages operator contracts on the validator. Start with [Install a staking pool](/nodes/staking-pools/installation). Day-two work is in the [Operator guide](/nodes/staking-pools/operators). Addresses and ABIs are in [Look up staking pool contract addresses](/nodes/staking-pools/contracts). From 1a8e0b22fc1077a109d1c0047ff089d9fce08275 Mon Sep 17 00:00:00 2001 From: Camembear Date: Wed, 2 Sep 2026 11:54:19 -0400 Subject: [PATCH 18/20] Fix README link-rot by dropping unpublished STRUCTURE.md link. Mintlify link-rot fails on relative links to repo-only files that are not published pages. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a258d16..21c5724 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ We welcome contributions from the community. Whether you're fixing a typo, clari - **Build**: developer guides, integrations, and contract reference - **Nodes**: node architecture, setup, operations, staking pools, and Beacon Kit reference -Content is in MDX. `docs.json` defines navigation and branding. [STRUCTURE.md](STRUCTURE.md) defines content placement. +Content is in MDX. `docs.json` defines navigation and branding. `STRUCTURE.md` defines content placement. --- From 0c191fd4140f6b3051f24c4f75723d23a8fd0a64 Mon Sep 17 00:00:00 2001 From: Camembear Date: Wed, 2 Sep 2026 12:08:22 -0400 Subject: [PATCH 19/20] Clarify install.sh env defaults and operator flow in installation.mdx. RPC and Node API URLs have script defaults; document override via env.sh and simplify the run example. Align prose with interactive cast/deposit steps. --- nodes/staking-pools/installation.mdx | 39 ++++++++++++++-------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/nodes/staking-pools/installation.mdx b/nodes/staking-pools/installation.mdx index 71224af..a96e604 100644 --- a/nodes/staking-pools/installation.mdx +++ b/nodes/staking-pools/installation.mdx @@ -3,11 +3,11 @@ title: "Install a staking pool" description: "Deploy and activate a staking pool from a remote machine with install.sh." --- -`install.sh` deploys and activates one staking pool from `guides/apps/staking-pools/install-helpers/`. Set the environment, run the script once, and read back the pool address and a receipt. The script uses Foundation-delegated funds when a DelegationHandler already exists for your validator. Otherwise it spends **10,000 BERA** plus gas from your funding wallet. +`install.sh` deploys and activates one staking pool from `guides/apps/staking-pools/install-helpers/`. You configure the environment, run the script, and execute the commands it asks you to. The script uses Foundation-delegated funds when a DelegationHandler has been provisioned for your validator. Otherwise it spends **10,000 BERA** plus gas from your funding wallet. ## What you need before you run install.sh -You need a synced validator, the helper tools, and live endpoints: +You need a synced validator, helper tools, and live endpoints: - A synced Berachain validator - `bash`, [Foundry](https://book.getfoundry.sh/) (`cast`), `jq`, and `curl` @@ -16,7 +16,7 @@ You need a synced validator, the helper tools, and live endpoints: You also need one funding path: - Self-funded: at least **10,000 BERA** plus gas in the funding wallet -- Delegated: a DelegationHandler already formed and funded for your validator, plus gas (for example, 0.1 BERA) for activation +- Delegated: a DelegationHandler already formed and funded for your validator, plus gas for activation The Berachain Foundation forms the DelegationHandler when they issue a delegation. That setup is internal; operators run `install.sh` once the handler exists. @@ -37,36 +37,35 @@ Copy `env.sh.template` to `env.sh` in that directory, then edit the copy. `insta cp env.sh.template env.sh ``` -| Variable | Required | Purpose | -| ------------------ | -------- | -------------------------------------------------------------------------------------------------- | -| `EL_RPC_URL` | Yes | Execution-layer JSON-RPC. Network (`mainnet` or `bepolia`) is read from `eth_chainId` on this URL. | -| `CL_NODE_API_URL` | Yes | Beacon Kit Node API base URL. | -| `PRIVATE_KEY` | No | Signs `cast send` on this host when set. | -| `VALIDATOR_PUBKEY` | No | Validator pubkey default. | -| `FUNDING_ADDRESS` | No | Funding wallet on the self-funded path. Ignored when `PRIVATE_KEY` is set. | -| `OPERATOR_ADDRESS` | No | Operator on the self-funded path. | -| `SHARES_RECIPIENT` | No | Shares recipient on the self-funded path. | - -Use `./install.sh --help` for usage. +| Variable | Purpose | +| ------------------ | -------------------------------------------------------------------------------------------------- | +| `EL_RPC_URL` | Execution-layer JSON-RPC. Default: Bepolia public RPC. Network is read from `eth_chainId`. | +| `CL_NODE_API_URL` | Beacon Kit Node API base URL. Default: `http://127.0.0.1:3500`. | +| `PRIVATE_KEY` | Signs `cast send` on this host when set. | +| `VALIDATOR_PUBKEY` | Validator pubkey default. | +| `FUNDING_ADDRESS` | Funding wallet on the self-funded path. Ignored when `PRIVATE_KEY` is set. | +| `OPERATOR_ADDRESS` | Operator on the self-funded path. | +| `SHARES_RECIPIENT` | Shares recipient on the self-funded path. | ## Run install.sh -The script deploys and activates one pool and doesn't resume. Run it once per pool: +Use `./install.sh --help` for usage. ```bash -export EL_RPC_URL=https://bepolia.rpc.berachain.com -export CL_NODE_API_URL=http://127.0.0.1:3500 +cp env.sh.template env.sh # optional; defaults apply without this ./install.sh ``` -If a DelegationHandler with delegated funds exists for your pubkey, the script uses the delegated path. Otherwise it uses the self-funded path. +If a DelegationHandler with delegated funds exists for your validator's pubkey, the script uses the delegated path. Otherwise it uses the self-funded path. -On success, the script prints the staking pool address. Confirmed transactions append to `staking-pool-receipts.jsonl`. +The script will walk you through obtaining the necessary information, verifying the signatures, and activating the validator. -Call the pool with `cast` for staker operations. The [Operator guide](/nodes/staking-pools/operators) covers day-two work. The [example frontend](https://github.com/berachain/guides/tree/main/apps/staking-pools/frontend) is a sample UI for end users. +Upon completion, receipts are written to `staking-pool-receipts.jsonl`. ## What's next +The [Operator guide](/nodes/staking-pools/operators) covers day-two work. The [example frontend](https://github.com/berachain/guides/tree/main/apps/staking-pools/frontend) is a sample UI for end users. + Continue with: - [Contract reference](/nodes/staking-pools/contracts) From 28336436464eb92e9a564bb4987e5f0e4a42022f Mon Sep 17 00:00:00 2001 From: Camembear Date: Wed, 2 Sep 2026 12:11:10 -0400 Subject: [PATCH 20/20] Move contract generation fixes to a separate PR. Drop BUSD ABI path and Prettier API changes from this branch; they ship in #104. Keep only the staking-pools handbook template in generate-pages.mjs. --- data/contracts.json | 8 +- scripts/contracts/generate-pages.mjs | 114 ++++++++---------- .../generated/core-contracts-table.mdx | 4 +- 3 files changed, 54 insertions(+), 72 deletions(-) diff --git a/data/contracts.json b/data/contracts.json index 5b3dac0..0bb9d35 100644 --- a/data/contracts.json +++ b/data/contracts.json @@ -189,16 +189,16 @@ "honey": { "name": "BUSD", "abi": { - "berachainMainnet": "https://github.com/berachain/abis/blob/main/mainnet/contracts/busd/BUSD.json", - "berachainBepolia": "https://github.com/berachain/abis/blob/main/bepolia/contracts/BUSD/BUSD.json" + "berachainMainnet": "https://github.com/berachain/abis/blob/main/mainnet/contracts/honey/Honey.json", + "berachainBepolia": "https://github.com/berachain/abis/blob/main/bepolia/contracts/honey/Honey.json" }, "address": { "berachainMainnet": "0xFCBD14DC51f0A4d49d5E53C2E0950e0bC26d0Dce", "berachainBepolia": "0xFCBD14DC51f0A4d49d5E53C2E0950e0bC26d0Dce" }, "source": { - "berachainMainnet": "https://github.com/berachain/contracts/blob/main/src/busd/BUSD.sol", - "berachainBepolia": "https://github.com/berachain/contracts/blob/bepolia/src/BUSD/BUSD.sol" + "berachainMainnet": "https://github.com/berachain/contracts/blob/main/src/honey/Honey.sol", + "berachainBepolia": "https://github.com/berachain/contracts/blob/bepolia/src/honey/Honey.sol" } }, "wberaStakerVault": { diff --git a/scripts/contracts/generate-pages.mjs b/scripts/contracts/generate-pages.mjs index c6baa61..556c19c 100644 --- a/scripts/contracts/generate-pages.mjs +++ b/scripts/contracts/generate-pages.mjs @@ -1,43 +1,25 @@ #!/usr/bin/env node -import { execFileSync } from "node:child_process"; import fs from "node:fs"; -import { createRequire } from "node:module"; import path from "node:path"; -import { fileURLToPath, pathToFileURL } from "node:url"; - -function resolvePrettierPackage() { - try { - return createRequire(import.meta.url).resolve("prettier"); - } catch { - // This repo has no package.json; prettier is on PATH for `make format`. - } - - let cli; - try { - cli = execFileSync("which", ["prettier"], { encoding: "utf8" }).trim(); - } catch { - throw new Error("prettier is not on PATH; install it the same way `make format` expects"); - } - - return createRequire(pathToFileURL(fs.realpathSync(cli))).resolve("prettier"); -} - -const prettier = (await import(pathToFileURL(resolvePrettierPackage()).href)).default; +import { execFileSync } from "node:child_process"; -const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); +const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), "../.."); const contracts = JSON.parse(fs.readFileSync(path.join(repoRoot, "data/contracts.json"), "utf8")); const generatedSnippetDir = "snippets/contracts/generated"; const checkMode = process.argv.includes("--check"); -const missingValue = "🤓"; +const missingValue = "Not deployed"; let changedCount = 0; let wroteCount = 0; let unchangedCount = 0; -async function write(relPath, content) { +function write(relPath, content) { const abs = path.join(repoRoot, relPath); - const config = await prettier.resolveConfig(abs); - const next = await prettier.format(content, { ...(config ?? {}), filepath: abs }); + const next = execFileSync("prettier", ["--stdin-filepath", relPath], { + cwd: repoRoot, + encoding: "utf8", + input: `${content.trimEnd()}\n`, + }); const prev = fs.existsSync(abs) ? fs.readFileSync(abs, "utf8") : null; if (prev === next) { unchangedCount += 1; @@ -61,7 +43,8 @@ async function write(relPath, content) { function berascanLink(address, network) { if (!address) return missingValue; - const host = network === "berachainBepolia" ? "https://testnet.berascan.com" : "https://berascan.com"; + const host = + network === "berachainBepolia" ? "https://testnet.berascan.com" : "https://berascan.com"; return `[\`${address}\`](${host}/address/${address})`; } @@ -89,7 +72,7 @@ function resourcesCell(item, network, abiLabel = "ABI") { const resources = [ ["abi", abiLabel], ["source", "Source"], - ["reference", "Reference"] + ["reference", "Reference"], ] .map(([key, label]) => { const href = linkFor(item, key, network); @@ -102,7 +85,8 @@ function resourcesCell(item, network, abiLabel = "ABI") { function berascanUrl(address, network) { if (!address) return ""; - const host = network === "berachainBepolia" ? "https://testnet.berascan.com" : "https://berascan.com"; + const host = + network === "berachainBepolia" ? "https://testnet.berascan.com" : "https://berascan.com"; return `${host}/address/${address}`; } @@ -112,7 +96,9 @@ function resourceLinks(item, network, abiLabel = "ABI") { address ? `[Berascan](${berascanUrl(address, network)})` : "", linkFor(item, "abi", network) ? `[${abiLabel}](${linkFor(item, "abi", network)})` : "", linkFor(item, "source", network) ? `[Source](${linkFor(item, "source", network)})` : "", - linkFor(item, "reference", network) ? `[Reference](${linkFor(item, "reference", network)})` : "" + linkFor(item, "reference", network) + ? `[Reference](${linkFor(item, "reference", network)})` + : "", ].filter(Boolean); return links.length ? links.join(" · ") : missingValue; @@ -159,7 +145,7 @@ function networkRow(item, network, options = {}) { includeResources = false, combineNameAddress = false, boldName = false, - abiLabel = "ABI" + abiLabel = "ABI", } = options; const columns = combineNameAddress ? [contractCell(item, network, boldName)] @@ -208,9 +194,9 @@ function renderGettingStartedSnippet() { renderAddressCategory("Governance", governanceItems, "berachainMainnet", linkedColumns), renderAddressCategory("Staking pools", stakingPoolItems, "berachainMainnet", { ...linkedColumns, - abiLabel: "ABI JSON" + abiLabel: "ABI JSON", }), - renderAddressCategory("Other", otherItems, "berachainMainnet", linkedColumns) + renderAddressCategory("Other", otherItems, "berachainMainnet", linkedColumns), ] .filter(Boolean) .join("\n\n"); @@ -220,15 +206,18 @@ function renderGettingStartedSnippet() { renderAddressCategory("Governance", governanceItems, "berachainBepolia", linkedColumns), renderAddressCategory("Staking pools", stakingPoolItems, "berachainBepolia", { ...linkedColumns, - abiLabel: "ABI JSON" + abiLabel: "ABI JSON", }), - renderAddressCategory("Other", otherItems, "berachainBepolia", linkedColumns) + renderAddressCategory("Other", otherItems, "berachainBepolia", linkedColumns), ] .filter(Boolean) .join("\n\n"); const nftRows = Object.values(contracts.nfts) - .map((item) => `| ${item.name} | \`${item.address.ethereumMainnet}\` | \`${item.address.berachainMainnet}\` |`) + .map( + (item) => + `| ${item.name} | \`${item.address.ethereumMainnet}\` | \`${item.address.berachainMainnet}\` |` + ) .join("\n"); const nftSection = `### NFT contracts @@ -302,7 +291,7 @@ ${parts.join("\n\n")}`; return [ renderBendNetworkSection("Mainnet contracts", "berachainMainnet"), - renderBendNetworkSection("Bepolia testnet contracts", "berachainBepolia") + renderBendNetworkSection("Bepolia testnet contracts", "berachainBepolia"), ] .filter(Boolean) .join("\n\n"); @@ -480,35 +469,28 @@ To find a market ID, open [bend.berachain.com/borrow](https://bend.berachain.com `; } -async function generate() { - await write(`${generatedSnippetDir}/core-contracts-table.mdx`, renderGettingStartedSnippet()); - await write(`${generatedSnippetDir}/bex-contracts-table.mdx`, renderBexSnippet()); - await write(`${generatedSnippetDir}/bend-contracts-table.mdx`, renderBendContractsSnippet()); - await write(`${generatedSnippetDir}/bend-markets-table.mdx`, renderBendMarketsSnippet()); - await write(`${generatedSnippetDir}/staking-pools-singletons-table.mdx`, renderStakingPoolsSnippet()); - - await write("build/getting-started/deployed-contracts.mdx", renderGettingStartedPage()); - await write("build/bex/deployed-contracts.mdx", renderBexDeployedContractsPage()); - await write("build/bend/deployed-contracts.mdx", renderBendContractsPage()); - await write("build/bend/deployed-markets.mdx", renderBendMarketsPage()); - await write("nodes/staking-pools/contracts.mdx", renderStakingPoolsPage()); - - if (!checkMode) { - console.log(`contracts-generate: wrote ${wroteCount} file(s), ${unchangedCount} unchanged.`); - } +write(`${generatedSnippetDir}/core-contracts-table.mdx`, renderGettingStartedSnippet()); +write(`${generatedSnippetDir}/bex-contracts-table.mdx`, renderBexSnippet()); +write(`${generatedSnippetDir}/bend-contracts-table.mdx`, renderBendContractsSnippet()); +write(`${generatedSnippetDir}/bend-markets-table.mdx`, renderBendMarketsSnippet()); +write(`${generatedSnippetDir}/staking-pools-singletons-table.mdx`, renderStakingPoolsSnippet()); - if (checkMode) { - if (changedCount > 0) { - console.error( - `Generated contract docs are stale (${changedCount} file(s) would change). Run node scripts/contracts/generate-pages.mjs and commit the outputs.` - ); - process.exit(1); - } - console.log(`Generated contract docs are up to date (${unchangedCount} file(s) unchanged).`); - } +write("build/getting-started/deployed-contracts.mdx", renderGettingStartedPage()); +write("build/bex/deployed-contracts.mdx", renderBexDeployedContractsPage()); +write("build/bend/deployed-contracts.mdx", renderBendContractsPage()); +write("build/bend/deployed-markets.mdx", renderBendMarketsPage()); +write("nodes/staking-pools/contracts.mdx", renderStakingPoolsPage()); + +if (!checkMode) { + console.log(`contracts-generate: wrote ${wroteCount} file(s), ${unchangedCount} unchanged.`); } -const thisFile = fileURLToPath(import.meta.url); -if (process.argv[1] && path.resolve(process.argv[1]) === thisFile) { - await generate(); +if (checkMode) { + if (changedCount > 0) { + console.error( + `Generated contract docs are stale (${changedCount} file(s) would change). Run node scripts/contracts/generate-pages.mjs and commit the outputs.` + ); + process.exit(1); + } + console.log(`Generated contract docs are up to date (${unchangedCount} file(s) unchanged).`); } diff --git a/snippets/contracts/generated/core-contracts-table.mdx b/snippets/contracts/generated/core-contracts-table.mdx index 03b3ac7..3102b15 100644 --- a/snippets/contracts/generated/core-contracts-table.mdx +++ b/snippets/contracts/generated/core-contracts-table.mdx @@ -21,7 +21,7 @@ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | BGT Token
[`0x656b95E550C07a9ffe548bd4085c72418Ceb1dba`](https://berascan.com/address/0x656b95E550C07a9ffe548bd4085c72418Ceb1dba) | [ABI](https://github.com/berachain/abis/blob/main/mainnet/contracts/pol/BGT.json) · [Source](https://github.com/berachain/contracts/blob/main/src/pol/BGT.sol) | | WBERA
[`0x6969696969696969696969696969696969696969`](https://berascan.com/address/0x6969696969696969696969696969696969696969) | [ABI](https://github.com/berachain/abis/blob/main/mainnet/contracts/WBERA.json) · [Source](https://github.com/berachain/contracts/blob/main/src/WBERA.sol) | -| BUSD
[`0xFCBD14DC51f0A4d49d5E53C2E0950e0bC26d0Dce`](https://berascan.com/address/0xFCBD14DC51f0A4d49d5E53C2E0950e0bC26d0Dce) | [ABI](https://github.com/berachain/abis/blob/main/mainnet/contracts/busd/BUSD.json) · [Source](https://github.com/berachain/contracts/blob/main/src/busd/BUSD.sol) | +| BUSD
[`0xFCBD14DC51f0A4d49d5E53C2E0950e0bC26d0Dce`](https://berascan.com/address/0xFCBD14DC51f0A4d49d5E53C2E0950e0bC26d0Dce) | [ABI](https://github.com/berachain/abis/blob/main/mainnet/contracts/honey/Honey.json) · [Source](https://github.com/berachain/contracts/blob/main/src/honey/Honey.sol) | | WBERA Staker Vault (sWBERA)
[`0x118D2cEeE9785eaf70C15Cd74CD84c9f8c3EeC9a`](https://berascan.com/address/0x118D2cEeE9785eaf70C15Cd74CD84c9f8c3EeC9a) | [ABI](https://github.com/berachain/abis/blob/main/mainnet/contracts/pol/WBERAStakerVault.json) · [Source](https://github.com/berachain/contracts/blob/main/src/pol/WBERAStakerVault.sol) | ### Governance @@ -84,7 +84,7 @@ Berachain NFT contract addresses on both Ethereum (via LayerZero adapters) and B | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | BGT Token
[`0x656b95E550C07a9ffe548bd4085c72418Ceb1dba`](https://testnet.berascan.com/address/0x656b95E550C07a9ffe548bd4085c72418Ceb1dba) | [ABI](https://github.com/berachain/abis/blob/main/bepolia/contracts/pol/BGT.json) · [Source](https://github.com/berachain/contracts/blob/bepolia/src/pol/BGT.sol) | | WBERA
[`0x6969696969696969696969696969696969696969`](https://testnet.berascan.com/address/0x6969696969696969696969696969696969696969) | [ABI](https://github.com/berachain/abis/blob/main/bepolia/contracts/WBERA.json) · [Source](https://github.com/berachain/contracts/blob/bepolia/src/WBERA.sol) | -| BUSD
[`0xFCBD14DC51f0A4d49d5E53C2E0950e0bC26d0Dce`](https://testnet.berascan.com/address/0xFCBD14DC51f0A4d49d5E53C2E0950e0bC26d0Dce) | [ABI](https://github.com/berachain/abis/blob/main/bepolia/contracts/BUSD/BUSD.json) · [Source](https://github.com/berachain/contracts/blob/bepolia/src/BUSD/BUSD.sol) | +| BUSD
[`0xFCBD14DC51f0A4d49d5E53C2E0950e0bC26d0Dce`](https://testnet.berascan.com/address/0xFCBD14DC51f0A4d49d5E53C2E0950e0bC26d0Dce) | [ABI](https://github.com/berachain/abis/blob/main/bepolia/contracts/honey/Honey.json) · [Source](https://github.com/berachain/contracts/blob/bepolia/src/honey/Honey.sol) | | WBERA Staker Vault (sWBERA)
[`0x118D2cEeE9785eaf70C15Cd74CD84c9f8c3EeC9a`](https://testnet.berascan.com/address/0x118D2cEeE9785eaf70C15Cd74CD84c9f8c3EeC9a) | [ABI](https://github.com/berachain/abis/blob/main/bepolia/contracts/pol/WBERAStakerVault.json) · [Source](https://github.com/berachain/contracts/blob/bepolia/src/pol/WBERAStakerVault.sol) | ### Governance