For the complete documentation index, see llms.txt. This page is also available as Markdown.

Pre-Create a DV with an OVM

Assign pre-created validators to customers on demand

A customer opting into staking at an unknown time, is a key trigger for enterprise staking deployments. There are two primary ways these ad-hoc demands are programatically fulfilled, using smart contracts or using/generating the private keys.

  • Smart contracts such as an Obol Validator Manager (OVM) can re-assign their (beneficial) ownership; allowing a customer to activate a pre-created deposit for this smart contract.

  • A fresh DV cluster can be created, a DKG invite can be created for waiting DV-pods ready to partake, a charon add-validators command could be triggered to add extra keys to a running cluster, or charon deposit sign could be used to alter an unused validator's withdrawal address.

This guide will focus on the former, managing validators using Obol smart contracts, and their role based access control. This approach requires less coordination for multi-operator setups, and is simpler than creating or interacting with private key material on the fly by a remote trigger.

This guide will demonstrate the key steps in preparing a DV cluster for this type of scenario. A blank OVM will be created and assigned validator keys, along with a splitter contract for distributing rewards. Adjust the number of OVMs, splitters, and their key counts for your use case. Administratorship of the OVMs and splitters will be given to a private key that will sit in a secure back end API server, and when a customer triggers an allocation of an OVM, the API server private key will make the necessary updates to an unallocated OVM, and then revoke its control over the smart contracts, leaving them ready for the customer's deposit.

The Hoodi testnet will be used for all examples.

Diagram of the on-demand Obol Validator Manager pre-deploy workflow.

OVM roles at a glance

The OVM uses a bitwise role system for permissioned actions. The owner (set in the constructor) holds all powers and can grantRoles() to other addresses. The table is an operational summary; see Obol Validator Manager for the full conceptual reference.

Role
Hex
Authorizes

owner

Every gated method below, plus grantRoles, revokeRoles, transferOwnership, renounceOwnership, and the combined transfer(newBeneficiary, newOwner) shortcut.

WITHDRAWAL_ROLE

0x01

withdraw() — initiate partial or full validator withdrawals (EIP-7002).

CONSOLIDATION_ROLE

0x02

consolidate() — merge validators or upgrade a 0x01 validator to 0x02 via self-consolidation.

SET_BENEFICIARY_ROLE

0x04

setBeneficiary() (change principal recipient) and setAmountOfPrincipalStake() (correct principal accounting).

RECOVER_FUNDS_ROLE

0x08

recoverFunds() — sweep stuck ERC20 balances. ETH cannot be recovered through this method.

SET_REWARD_ROLE

0x10

setRewardRecipient() — change the reward destination.

DEPOSIT_ROLE

0x20

deposit() — fund a validator through the OVM so principal accounting updates.

distributeFunds(), distributeFundsPull(), and withdrawPullBalance() are permissionless. principalThreshold is set at deployment and is immutable — if a different threshold is needed, deploy a fresh OVM.

Pre-requisites

To keep the cast examples neat, we'll declare the key addresses upfront here, and refer to them as environment variables in each cast command.

Fee Splitting

A key decision when it comes to preparing a Distributed Validator is how Node Operators and Service providers can be non-custodially compensated for their services. Obol Validator Managers are built to leverage Splits.org split contracts. For this demo, split contracts will be pre-created with the unallocated OVMs, and edited for customers as they appear. You may want to consider smoothing MEV across your customers using a pair of nested splitters. This is described in more detail at the end of the guide.

Contract Deployment

A safe and convenient way to deploy an OVM contract is through the existing contract factory. A splitter contract can be deployed in a similar fashion.

After you have deployed an Obol Validator Manager contract, let's save its address and an example customer address as environment variables to make the rest of the cast demo easier.

Create the DV Cluster

At this point, you can prepare a DV cluster pointed at these OVMs and split contracts. Use the charon create cluster ... --publish command if you are controlling the validator keys centrally, or charon create dkg ... ---publish if you have a group of operators taking part in the cluster. Comma separate the --withdrawal-addresses and --fee-recipient-addresses flags with your created OVMs and Pull Splits. Once you complete the key creation, you can load these artifacts into your nodes and get the cluster online and ready for deposits. At this point the last remaining action will be with the API key, which will change the ownership of an OVM to make it ready for deposits.

Assigning the Contracts to Customers

When a capital allocator (customer) is onboarding, the pre-created contracts can be assigned to that entity. The principal beneficiary address is updated to the entity's preferred address, permissions are allocated to the customer and backend's addresses as needed, and then ownership of the OVMs are transferred or burned.

Transferring Ownership

The last step before the OVM is ready for activation is to transfer the ownership of the OVM away from the backend, to either the customer, or an extremely well secured administrative multi-sig wallet like a SAFE that can intervene to update key values in future if needed. Consider that the owner of an OVM has custodial control over it.

Handling Deposits

The capital allocator can now deposit the validators that point to this withdrawal address. The validator keys are held by the provisioned DV cluster operators and the deposit data was created during cluster creation.

This step would normally be through a wallet and web interface. This example using raw private keys is for demo purposes only.

To accurately differentiate reward from principal in an OVM, the OVM contract needs to be invoked during the deposit call. Each OVM has a deposit() function exactly matching and wrapping the official deposit smart contract, and should be used for that purpose.

If a deposit is made not through the OVM, the OVM can be updated with the setAmountOfPrincipalStake() method by the owner or an address with the SET_BENEFICIARY_ROLE.

The validator(s) will enter the activation queue and the amountOfPrincipalStake value on the contract will track how much of the balance is considered the principal (owed to the beneficiary). The EL and CL rewards from any targeting validators will be sent to the OVM contract and Pull Split.

Upgrading a 0x01 validator to 0x02 (self-consolidation)

If a validator pointing at the OVM was deposited with 0x01 withdrawal credentials, its effective-balance ceiling is 32 ETH. Calling consolidate() with the validator's pubkey as both the source and the target upgrades it to 0x02 in place, raising the effective-balance ceiling to 2048 ETH and enabling reward compounding. The validator continues attesting through the upgrade — it is not exited.

The validator must be active with a balance greater than 32 ETH for the consolidation to succeed. The call must come from the OVM owner or an address holding CONSOLIDATION_ROLE (0x02). Self-consolidation is the only path that converts an existing 0x01 validator to 0x02 — a fresh deposit cannot do it.

EIP-7251 consolidation requests carry a small fee that scales with mempool pressure. Send enough ETH as msg.value to cover the fee; any excess is refunded to the excessFeeRecipient address.

Once the beacon chain processes the request, the validator's withdrawal type flips to 0x02 and its effective-balance ceiling rises. Subsequent top-up deposits up to 2048 ETH are then retained on the validator rather than being swept as partial withdrawals.

Withdrawing Validator Balance

Compounding validators (0x02 type) can have part of their principal withdrawn from active stake, or be fully exited, via the same withdraw() call. Specifying a nonzero value for amounts will initiate a partial withdrawal, while 0 will fully exit the validator. You cannot specify an amount that will leave the validator with less than 32 ether in active stake remaining.

Reward Distribution and Splitters

When withdrawals requested eventually exit the beacon chain, they appear on the OVM contract, and should be distributed to the rewardRecipient or principalRecipient (depending on if they amount to above or below the principalThreshold of 16 eth). Calling distributeFunds() will push the Ether to the correct address. Split contracts as principal or reward addresses will also need to be distributed from for the funds to land in their ultimate recipients addresses.

Appendix: MEV Smoothing

If you setup validators where every customer gets their own fee recipient address (and underlying splitter), they will each get proposals rarely (approximately twice per year for a 32 ETH validator). Due to MEV being unequally distributed, only a small number of proposals in the year contain most of the MEV. This means that most of your users will get the median amount of Ether as MEV rather than the average, and may notice a lower APR versus setups that pool and distribute their variable rewards across their users. It may be beneficial for you to instead smooth the MEV being accrued through block proposals across all depositors in the cluster. This can be achieved through two nested split contracts as follows:

  • First create an editable PullSplit we'll refer to as the Child Split. The owner of this split should be the $BACKEND_API_ADDRESS.

  • Next create a second PullSplit we'll refer to as the Parent Split. It can be immutable if preferred. It should send the majority of its inflow to the Child Split, and some amount to a set of addresses that receive operating fees for the cluster.

  • Set the parent split as the --fee-recipient-address for all validators in the cluster. This means all proposal rewards for the cluster will go to this address.

  • When a customer makes a deposit, use the $BACKEND_API_PRIVATE_KEY to update the Child Split to proportionally reflect the eth provided by all customers to the cluster.

  • As proposals by the validators earn tips and MEV, this collects on the Split Contracts. Distributing these rewards sends the ether to the fee recipients and customers.

Last updated

Was this helpful?