Thanks to Michael and Lin for feedback
tl;dr
Credible preconf protocols require proposers to verify their commitments were honored before signing a block. A naive approach, inspecting the full block body, leaks the builder’s block, which is a non-starter. Relays could fill this gap but are not required post-ePBS. The trustless solution is for the builder to submit proofs of constraint satisfaction with their bids. The downside is proof generation adds latency to the critical path, especially if constraints evolve beyond simple transaction inclusions.
EIP-7928 BALs provide a structured alternative. The BAL is a complete record of every state write in a block, produced as a byproduct of execution and committed to in the block header. Because every BAL entry has the same structure, any preconf type could be reduced to a true/false query over the same object. A single verification language can cover any preconf type (inclusion, execution, ordering, exclusion, etc) and a single verifier function can handle all without being redeployed. New preconf types become new formulas that are verifiable with the same infrastructure. This strictly improves on the guarantees possible through Merkle proofs, while reducing latency and complexity.
Background
Preconfs allow proposers to commit, before a block is built, that certain properties will hold in their next block. A user might receive a preconf guaranteeing their transaction will be included, that a value is written to specific storage slot, or that a payment above a certain threshold lands in the block.
The commitments that a proposer makes constrains how the block must be built. Today, without constraints, the builder is free to present any block to the proposer. With inclusion preconfs for example, the builder is constrained to present blocks that include the specified transactions.
Before committing to a block, proposers need a means to verify that the block satisfied all of the constraints to ensure that their commitments will be satisfied. Note the difference here is a commitment is user-facing and a constraint is builder-facing.
The Verification Problem
The obvious approach is for the proposer to inspect the full block body directly. If the block satisfies the constraints, the proposer signs. But this is a non-starter as it would allow the proposer to steal the block without paying the builder.
Relays already address this fair exchange problem today. With pessimistic relaying, the relay fully executes the block before sharing the header with the proposer to sign, and could extend this to verify preconf constraints as a trusted service. An alternative is optimistic relaying, where the relay forwards the header without fully executing the block and instead relies on retroactive fault attribution: if a builder’s block is later found invalid or constraint-violating, they are penalized. Both modes exist in production today. Neither survives ePBS, which removes the relay from the critical path entirely.
The trustless alternative is for the builder to generate proofs of constraint satisfaction alongside their bid. The simplest form is Merkle inclusion proofs against the trie roots committed to in the block header. A builder can prove a specific transaction was included, or that a specific storage slot is a set value at the end of a block, without revealing the full block body.
One problem is latency. Proof generation happens after the block is fully built. As the number of preconf constraints grows, proof generation becomes a non-trivial step on the critical path between block construction and bid submission. Every millisecond costs valuable building time that hurts proposer revenue.
The other problem is expressibility. Merkle proofs are block-level: they can prove a transaction was included or that a state slot holds a specific value, but they cannot prove transaction-level state diffs. There is no Merkle proof that a specific transaction caused a specific state change. Verifying stateful preconfs requires either full re-execution or ZK proving, which are non-starters for privacy or latency reasons.
What Block Access Lists Provide
EIP-7928 defines a Block-Level Access List (BAL) as a complete record of all accounts and storage locations accessed during block execution. Each BAL is bound to a specific bid/block via a block_access_list_hash, and by replaying the BAL, anyone can recompute the block’s final state without needing to execute the actual transactions.
In the context of preconfs, this is useful for two main reasons:
1. The BAL is a direct byproduct of execution. There is no separate proof generation step. The builder already has it when the block is built. The BAL records every state write that occurred during the block: storage slot values, balance changes, nonce increments, and code updates, each tagged with a block_access_index that captures global write ordering. This is enough for the proposer to verify any property about state outcomes directly: what value a slot was written to, whether an account’s balance crossed a threshold, whether a nonce incremented, whether two writes happened in a specific order, or whether a contract was touched at all. No transaction inspection is required for any of these checks.
2. The BAL can be verified without leaking the block’s contents. Reconstructing a block requires the actual signed transactions and their inputs, which the BAL omits. A proposer who inspects only the BAL learns nothing that would let them reconstruct and resubmit the block without paying the builder. However, if every transaction came from the public mempool, a determined proposer could in principle reconstruct it from public information alone (but in that case, they could have built the same block themselves without the builder’s BAL at all). Blocks with private order flow could not be reconstructed.
From Transaction-Commitments to Outcome Preconfs
Current preconf approaches make promises about specific signed transactions. Outcome preconfs shift away from transactions and focus solely on promises about state.
With today’s intents, you prescribe a certain outcome, like “swap X for at least Y”, solvers compete to find custom solutions, then a smart contract ultimately verifies the intent was satisfied. Outcome preconfs apply the same model at the proposer layer. When the proposer commits to “the first write to slot S will be value V,” they are committing to a state outcome, not an execution path. The builder can satisfy this constraint through any valid sequence of transactions since the constraint says nothing about which transaction produces the write, who sent it, what gas it consumed, etc.
Put simply, the proposer expresses their block’s desired outcomes, builders compete to find any valid execution path that achieves them, and the proposer verifies the outcomes were achieved (via BAL) before committing to the block (and without leaking the block contents).
A Uniform Constraint Language
The BAL is a single structure against which any outcome preconf type can be verified. This is a significant departure from how preconfs work today, where each type is its own design problem: inclusion preconfs require one proof format, ordering constraints require another, execution preconfs require another still. Each new type demands new verification logic for proposers and new on-chain slashing infrastructure. Adding a new preconf type meant a coordinated upgrade across every layer of the stack.
Because every BAL entry shares the same four fields (address, field type, value written, and ordering index), verifying any outcome preconf reduces to the same kinds of true/false questions: does a write with these properties exist? Is this address absent? Did write A happen before write B?
First-order logic (FOL) is a formal language for making exactly these kinds of true/false statements about a fixed set of objects. It is well understood and is known to be complete over finite structures: any boolean property you can express in plain English about a fixed-schema BAL can be expressed as a FOL formula.
The practical consequence is that only one verifier is ever needed. It takes a FOL formula and the BAL as inputs and returns true or false. The verifier function is built once (in the proposer’s sidecar for the normal bidding flow, and in the slashing contract for disputes) and never needs to change. A new preconf type means writing a new FOL formula, not deploying new infrastructure.
The language has two categories of primitives. Comparisons are the leaves: field equality, inequality, and ordering checks (==, >=, <, etc.) applied to the values in matched entries. The logical connectives are the structure: “there exists an entry satisfying…”, “both conditions hold”, and “this condition does not hold.” This is the same principle as building any logic circuit from a single gate type, with just minimal primitives you can get unlimited expressiveness.
Dapps would be expected to compute formulas on behalf of their users. This involves determining which storage locations are affected by the user’s transaction and are relevant to check in the BAL. For arithmetic constraints (“balance increases by Y”, “price within 5%”, “nonce increments”) the dapp provides these hints directly to the formula, meaning the language itself does not need to support arithmetic. Concretely, if Alice’s nonce is 5 and her USDC balance is 500, and she wants to receive at least 2000 USDC, the dapp reads the pre-state, computes 6 and 2500, and constructs a formula that simply checks nonce == 6 and balance >= 2500.
Outcome Preconf Patterns and L1 UX
Each of the following patterns is a formula over the BAL. The same verifier handles all of them. Note that they are meant to be demonstrative and may not be complete. In the formulas below, (A, Nonce) and (A, Balance) refer to an account A’s nonce and ETH balance fields; (C, S) refers to a storage slot S in contract C; and V is the expected value to be present. These correspond directly to the field types in a BAL entry.
Guaranteed inclusion. Alice wants her ETH transfer to Bob included in the next block. Alice’s nonce increment confirms her transaction ran and Bob’s balance increase confirms the payment landed. The two writes must share the same block_access_index to confirm they came from the same transaction. The dapp computes absolute post-state values from the parent state root before constructing the formula.
there exists a write to (Alice, Nonce) with value == n+1 [call this e1]
AND there exists a write to (Bob, Balance) with value >= bob_pre_balance + amount AND index == e1.index
Top-of-block. The user’s transaction should be the first user transaction in the block. block_access_index is assigned per-transaction where index 0 is reserved for pre-execution system contract writes (e.g. the EIP-4788 beacon root update), and user transactions are assigned indices starting at 1. This is the strongest ordering guarantee available and should be priced accordingly.
there exists a write to (Alice, Nonce) with value == n+1 AND index == 1
AND there exists a write to (C, S) with value == V AND index == 1
First contract access. A user wants to be the first to interact with a specific contract, but doesn’t want to pay for the top-of-block. The classic example is minting an NFT: first to call the mint function, indifferent to everything else. The check scopes entirely to contract C’s entries, leaving the builder full freedom to order everything else.
there exists a write to (C, S) with value == V
AND its index is less than or equal to every other write to C
Same-slot intent settlement. A user signs an intent, “swap my X ETH for at least Y USDC”, without specifying how it is filled. The builder includes both the user’s intent transaction and the solver’s fulfillment transaction. The ordering constraint in the formula ensures the intent ran before the solver paid out. Crucially, healthy builder competition is important to improve the user’s outcome.
there exists a write to (A, Nonce) with value == n+1 [call this e1]
AND there exists a write to (A, ETH_Balance) with value <= eth_pre_balance - X AND index == e1.index
AND there exists a write to (A, USDC_Balance) with value >= Y [call this e2]
AND e1.index < e2.index
Execution outcome preconf. Historically, guaranteeing specific post-state required a ZK proof over EVM execution. With BALs, a nonce increment confirms a user’s transaction ran; the storage slot check confirms the expected state change. Both writes must share the same index to confirm they came from the same transaction. No re-execution required.
there exists a write to (A, Nonce) with value == n+1 [call this e1]
AND there exists a write to (C, S) with value == V AND index == e1.index
The above patterns compose freely. A top-of-block guarantee can be layered onto a same-slot intent settlement by adding index == 1 to the nonce check. An execution outcome can be combined with an ordering constraint. Any conjunction of the above is a valid formula and is verified with the same verifier function.
Outcome Preconfs with ePBS
Because outcome preconfs are scoped to results rather than specific transactions, satisfying them becomes a market. Multiple searchers can independently compete to fill any given preconf. The builder selects the best bundle. There’s no need for a party to see any other party’s execution strategy. The sequence below shows how this fits into the bidding flow post-ePBS.
Note, the builder’s signed constraint commitment does not play a role in the on-chain flow. Its purpose is out-of-protocol: a builder that signs constraints and then submits a BAL violating them has produced a cryptographic proof of their own fault, which an external slashing or reputation system can act on.
The constraint verification step runs in the proposer’s sidecar, an off-chain implementation of the same verifier logic deployed in the slashing contract. The two are equivalent by construction, so the proposer confirms the same check the slashing contract would later run in a dispute.
Also note, in the ePBS spec, the builder’s bid contains a block_hash commitment, not the header itself. The proposer therefore needs to establish the chain of trust from BAL → block_access_list_hash → header → block_hash → bid.
Limitations
The BAL does not prove that the underlying transactions have valid signatures. A builder could construct a BAL that satisfies all outcome preconf checks but corresponds to invalid or fabricated transactions. When the payload is revealed, it would fail validation, and the proposer misses their slot. Fortunately, the proposer is still unconditionally paid the bid amount post-ePBS.
Without an out-of-protocol slashing mechanism to disincentivize this builder behavior, outcome preconfs potentially further incentivize builders to exercise their free option if breaking preconf commitments is monetizable. Having the builder or even multiple parties commit to the constraints is a step towards such a mechanism.
BAL as Dispute Evidence
If an outcome preconf dispute reaches on-chain adjudication, each constraint is independently disputable. The disputing party supplies the violated constraint, the proposer’s signature over it, the builder’s signed commitment, the BAL, and the block header. The slashing contract authenticates the BAL against block_access_list_hash and runs the same verifier the proposer’s sidecar ran during normal verification. If it returns false, the violation is proven.
The FOL formula structure is well-suited to ZK proving. Queries over a fixed-schema BAL are far cheaper to prove than arbitrary EVM execution, so a ZK proof against the committed BAL root is a plausible future optimization.
A Path Towards Enshrining
The design described in this paper is entirely out-of-protocol. Proposer and builder commitments are enforced through economic incentives and out-of-protocol slashing. A block that violates outcome preconfs is still a valid block from the network’s perspective.
There is a plausible path toward making this in-protocol. PEPC identified the need for protocol-enforced proposer commitments but left the commitment representation as an open question, whether to use EVM execution, SNARKs, or some other structure, and noted a data availability problem: how do third parties deliver commitment proofs in a way attesters can verify? BALs potentially address both since it’s already a first-class object committed to in the block header, so no separate proof delivery mechanism is required. The FOL constraint language is minimal and well-defined, and the verifier is a single function deployable at the consensus layer. Rather than having attesters re-execute commitment code, the protocol could verify FOL formulas against the authenticated BAL directly.
For this to work in-protocol, several additional pieces would need to come together:
Binding commitment broadcast. The proposer’s constraints would need to be published before the bidding process, in a way that is immutable and attributable. Something analogous to a PTC but for constraints.
BAL and header delivered with the bid. For the proposer to verify constraints before signing, the builder must include the BAL and header alongside the bid which is currently not part of the ePBS spec.
Builder penalties for invalid BALs. If the BAL submitted with a bid does not match the block eventually revealed, or if the revealed block fails to validate, the builder should be penalized. Post-ePBS, builders are already required to post collateral so much of the heavy lifting is already done.
Summary
Relay are not required under ePBS so reintroducing them as a trusted preconf verifier is not ideal. Merkle proofs offer a trustless alternative but add latency to the critical path and cannot express stateful or exclusion constraints. BALs solve both problems: they are produced as a byproduct of block execution with no additional latency, and by shifting from transaction-level to intent-level commitments, any preconf type reduces to a formula over the same BAL structure. This expressibility does not require ZK proofs on the critical path, and adding new preconf types requires no changes to the verifier or slashing infrastructure.
