This EIP introduces two EIP-7685 request types and corresponding predeploy contracts for EIP-7732 builders. A builder deposit contract handles initial registration and stake top-ups. A builder exit contract lets a builder's execution_address trigger a full exit. Both contracts follow the request-bus pattern of EIP-7002 and EIP-7251. Builders no longer onboard through the validator deposit flow or exit through the voluntary-exit operation.
EIP-7732 introduces builders as a separate, staked consensus-layer actor, but reuses validator flows for their lifecycle. A builder is registered by a validator deposit whose withdrawal credential carries the 0xB0 BUILDER_WITHDRAWAL_PREFIX, and it exits through a builder branch of the voluntary-exit operation. Dedicated builder request types improve on this in several ways.
Dedicated types make the actor explicit from the request type alone. The consensus layer no longer routes deposits by inspecting credential prefixes, and the validator and builder registries are keyed independently.
A builder deposit's proof-of-possession is verified inline when the deposit is processed, unlike a validator deposit's, which is deferred to the churn-limited pending_deposits queue. Builder deposits carried on the validator deposit request inherit its high per-payload ceiling. A dedicated request type isolates the verification work and caps it at MAX_DEPOSIT_REQUESTS_PER_BLOCK per block.
Under EIP-7732, a builder can only exit with a signature from its BLS key, the same hot key that signs its bids. The exit contract instead authorizes exit by the builder's execution_address, as EIP-7002 does for validators.
Builders that must exist at the fork are unaffected. The EIP-7732 fork-transition onboarding of builder-credentialed pending deposits is retained, and only post-fork onboarding moves to the new contract.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.
| Name | Value | Comment |
|---|---|---|
BUILDER_DEPOSIT_CONTRACT_ADDRESS |
0x0000bFF46984e3725691FA540a8C7589300D8282 |
Predeploy address of the builder deposit contract |
BUILDER_EXIT_CONTRACT_ADDRESS |
0x000064D678505ad48F8cCb093BC65613800E8282 |
Predeploy address of the builder exit contract |
BUILDER_DEPOSIT_REQUEST_TYPE |
0x03 |
EIP-7685 request-type byte for builder deposits |
BUILDER_EXIT_REQUEST_TYPE |
0x04 |
EIP-7685 request-type byte for builder exits |
SYSTEM_ADDRESS |
0xfffffffffffffffffffffffffffffffffffffffe |
Address that invokes the end-of-block system call (as in EIP-7002) |
MAX_DEPOSIT_REQUESTS_PER_BLOCK |
64 |
Maximum records the builder deposit contract drains into one block |
TARGET_DEPOSIT_REQUESTS_PER_BLOCK |
8 |
Per-block request count above which the fee rises for the deposit contract |
MAX_EXIT_REQUESTS_PER_BLOCK |
16 |
Maximum records the builder exit contract drains into one block |
TARGET_EXIT_REQUESTS_PER_BLOCK |
2 |
Per-block request count above which the fee rises for the exit contract |
MIN_REQUEST_FEE |
1 |
Minimum request fee, in wei |
REQUEST_FEE_UPDATE_FRACTION |
17 |
Controls the fee's rate of change |
BUILDER_MIN_DEPOSIT |
1000000000000000000 |
Minimum credited stake for a deposit, in wei (1 ETH — the EIP-7732 builder minimum) |
BUILDER_DEPOSIT_CONTRACT_RUNTIME_CODE |
see Reference Implementation | Runtime bytecode of the builder deposit contract |
BUILDER_EXIT_CONTRACT_RUNTIME_CODE |
see Reference Implementation | Runtime bytecode of the builder exit contract |
Final request-type values MUST be unique across all active EIP-7685 request types. Allocation is coordinated in consensus-specs, where the existing types are defined (electra/beacon-chain.md).
Both contracts are deployed by a CREATE2 factory (EIP-7997). Each address is determined by the factory, a salt, and the contract's init code. The salt was mined so that the addresses above result from the current reference bytecode.
The contracts MUST be deployed before the fork that activates this EIP. If there is no code at either address once the EIP is active, every block from activation onward MUST be invalid.
Both predeploys follow the EIP-7002 and EIP-7251 contract design, with minor tweaks, and reuse their storage layout. There is no Solidity-compatible ABI. Each contract dispatches on the caller and on calldatasize alone. Calls that match none of the cases below MUST revert.
A call from any address other than SYSTEM_ADDRESS, with calldata of exactly the contract's input size, submits a request. The contract MUST validate the request and the value sent (see below), append one record to its queue, increment the per-block count, and emit the accepted record as an anonymous log.
A call from any address other than SYSTEM_ADDRESS, with empty calldata, returns the current fee without modifying state. The contract MUST revert if any value is attached.
At the end of each block, the contract is called by SYSTEM_ADDRESS. If any calldata is included, the queue is permanently disabled via an inhibitor. If no calldata is included, it MUST dequeue up to its per-block maximum of records (MAX_DEPOSIT_REQUESTS_PER_BLOCK or MAX_EXIT_REQUESTS_PER_BLOCK, oldest first), return their concatenation as its request_data, and reset the per-block count. Records beyond the cap remain queued for subsequent blocks.
The execution layer prepends the contract's request-type byte and includes request_type ++ request_data in the block requests list, committed via the requests_hash (EIP-7685). The system call follows the same rules as in EIP-7002. It runs with a dedicated gas limit of 30_000_000 that does not count against the block gas limit. If either contract's system call fails, the block MUST be invalid.
Each request carries a fee, computed as in EIP-7002:
fee = fake_exponential(MIN_REQUEST_FEE, excess, REQUEST_FEE_UPDATE_FRACTION)
where fake_exponential is the EIP-1559-style integer approximation of MIN_REQUEST_FEE * e**(excess / REQUEST_FEE_UPDATE_FRACTION). The fee rises super-linearly while blocks contain more than the target number of requests and decays back to MIN_REQUEST_FEE otherwise. It is charged on top of any staked value and is left locked in the contract.
Both contracts are modified to apply fee increases for each write path, rather than at end-of-block, as in EIP-7002.
A deposit request is submitted by calling BUILDER_DEPOSIT_CONTRACT_ADDRESS with calldata of exactly 184 bytes:
| Bytes | Field | Description |
|---|---|---|
0:48 |
pubkey |
48-byte BLS public key |
48:80 |
withdrawal_credentials |
32-byte commitment (version byte + execution_address) |
80:88 |
amount |
Big-endian uint64, in gwei |
88:184 |
signature |
96-byte BLS proof-of-possession |
A deposit request serves both a builder's first deposit and subsequent top-ups. The contract MUST reject the request unless amount * 1 gwei >= BUILDER_MIN_DEPOSIT and msg.value >= amount * 1 gwei + fee. Any value beyond amount * 1 gwei + fee is retained by the contract and not credited to the builder.
On success the contract queues the 184 input bytes. The dequeued record is the input verbatim with amount converted to little-endian, as in EIP-7002. The contract does not verify the signature. It is carried in the record and verified by the consensus layer. Submitters SHOULD verify the proof-of-possession off-chain before broadcasting a first deposit.
An exit request is submitted by calling BUILDER_EXIT_CONTRACT_ADDRESS with calldata of exactly 48 bytes, the pubkey of the builder to exit. The contract MUST require msg.value >= fee. No value is staked. On success it queues a record of source_address (20) ++ pubkey (48), where source_address is msg.sender.
Authorization is by source_address, as in EIP-7002. The contract records msg.sender and performs no further check. The consensus layer honors the request only when source_address equals the target builder's execution_address.
The consensus layer decodes each dequeued record into one of two SSZ containers, selected by request type:
class BuilderDepositRequest(Container):
pubkey: Bytes48
withdrawal_credentials: Bytes32
amount: uint64 # Gwei
signature: Bytes96
class BuilderExitRequest(Container):
source_address: Bytes20
pubkey: Bytes48
A type's request_data is the concatenation of the fixed-size SSZ serializations of its records, which is exactly the bytes the system call returns, in the same order. BuilderDepositRequest is the EIP-6110 DepositRequest without the index field.
Detailed state-transition behavior is specified in consensus-specs. In summary:
BuilderDepositRequest for a pubkey not in the builder registry registers a new builder if its signature is a valid proof-of-possession over (pubkey, withdrawal_credentials, amount) under DOMAIN_BUILDER_DEPOSIT, a builder-specific signing domain. A record with an invalid signature is ignored and its stake forfeited. Deposits are applied immediately rather than routed through the validator pending_deposits queue.BuilderDepositRequest for an already-registered pubkey is a top-up. The amount is credited and the record's withdrawal_credentials and signature are ignored, as with validator deposits.BuilderExitRequest initiates a full exit only if its pubkey is an active builder, its source_address equals that builder's execution_address, and the builder has no pending balance to withdraw. Otherwise the record is dropped rather than re-queued, and the request must be resubmitted.The builder branch of process_deposit_request is removed, so a deposit to the validator deposit contract is always an ordinary validator deposit. Builders are created and topped up only through BUILDER_DEPOSIT_REQUEST_TYPE. A post-fork deposit to the validator contract with a 0xB0 credential mints a validator that cannot withdraw its balance, so builder deposits MUST be sent to the builder deposit contract.
The one-time EIP-7732 onboarding of builder-credentialed pending deposits at the fork is retained, so builders exist from the first slot. This is the only path that onboards builders through the validator deposit contract. The 0xB0 BUILDER_WITHDRAWAL_PREFIX is deprecated afterward.
The builder branch of process_voluntary_exit is removed, making the voluntary-exit operation validator-only. Builders exit only through BUILDER_EXIT_REQUEST_TYPE.
This mirrors withdrawals (0x01) and consolidations (0x02). The execution layer needs no new read semantics, and the consensus layer routes by request type rather than by inspecting credentials.
This matches the validator deposit contract. The proof-of-possession is checked on a pubkey's first appearance, and later deposits credit stake. A top-up cannot redirect a builder's withdrawals because its withdrawal_credentials and signature are ignored.
execution_addressA builder's BLS key is hot, since it signs bids continuously, so it should not also authorize exits. Routing exit through the cold execution_address mirrors the EIP-7002 rationale for validator withdrawal credentials and gives builders a single, well-defined exit authorizer.
The same demand-responsive fee as EIP-7002 and EIP-7251 meters submission, together with the per-block caps and the per-deposit stake.
Some applications depend on builders existing from the first slot of the fork, which post-fork deposits to the new contract cannot provide. The existing EIP-7732 onboarding path is therefore retained for the initial builder set.
This EIP is additive at the execution layer. It introduces new contracts at previously empty addresses and does not modify the validator deposit contract or the validator request predeploys. At the consensus layer it modifies the EIP-7732 builder lifecycle as described in Changes to EIP-7732. Builders onboarded at the fork are unaffected.
See src/builder_deposits and src/builder_deposits.
The exit contract records msg.sender as source_address and performs no further check. The request carries no signature, so the source_address check during consensus layer processing is the only exit authorization; without it an arbitrary caller could exit any builder.
Builder deposits are signed under DOMAIN_BUILDER_DEPOSIT, distinct from the validator DOMAIN_DEPOSIT, so deposit proofs-of-possession cannot be replayed across the two classes. Fork-transition seed deposits are made through the validator deposit contract and are necessarily signed under DOMAIN_DEPOSIT.
A deposit's fields are public in calldata, so a third party can resubmit them for an already-registered builder at an amount it funds itself. Such a replay is a top-up whose credentials and signature are ignored. It credits stake and redirects nothing.
Exit requires a zero pending balance, every winning bid adds pending balance, and only the execution_address can authorize exit. Where the execution_address (capital owner) and the BLS key (bidding operator) are held by different parties, an operator that keeps winning bids can indefinitely block the owner's exit. Delegating parties should retain off-chain control over the operator's bidding.
The per-block caps bound the drain rate, not enqueue, so the in-state queues can grow across blocks. Growth is gated by value, since each deposit locks at least BUILDER_MIN_DEPOSIT plus the fee. An attacker submitting valid proofs-of-possession forfeits nothing, because the stake remains a withdrawable builder balance, so post-fork onboarding can be delayed behind a backlog of attacker deposits at the cost of locked capital. This is tolerable because the time-critical initial builder set is seeded through the fork transition rather than the steady-state contract.
The request fee, any overpayment, and the principal of a first deposit that the consensus layer rejects for an invalid proof-of-possession are permanently locked in the predeploy. The execution layer does not verify BLS signatures, so the off-chain proof-of-possession check advised in Deposit requests is a submitter's only protection against losing a first deposit's principal.
Copyright and related rights waived via CC0.