Frame Transactions and the Three Gates to Privacy
This post takes EIP-8141 as given rather than arguing for or against it. The goal is to show how frame transactions could improve privacy protocols, and what would need to change in the public mempool rules and inclusion list enforcement to make that actually work.
Many thanks to Carlos, Ben, Milos, and Matt for feedback!
Frame transactions (EIP-8141) promise to eliminate relayers from privacy protocols. But the rules governing public mempool admission, FOCIL enforcement, and the statelessness roadmap each draw different boundaries around which transactions they support. This post analyzes where those boundaries intersect and where they conflict, particularly for privacy-preserving transactions.
how frame transactions eliminate relayers
Privacy protocols like Tornado Cash and Railgun break the on-chain link between depositor and withdrawer using zk-SNARKs. The withdrawal proves knowledge of a valid commitment in a Merkle tree without revealing which one. The problem: the withdrawerâs address is fresh and holds no ETH for gas. Today, relayers (centralized, censorable third parties, often offline when needed) bridge this gap by sponsoring withdrawal transactions.
EIP-8141 changes the economics. A frame transactionâs VERIFY frame runs as a STATICCALL: read-only, no state mutations. If VERIFY reverts before payment is approved, the transaction is invalid at the protocol level, never enters a block, and no gas is charged to anyone. A privacy withdrawal becomes:
VERIFYframe (withtx.sender = pool, so the frame targets the poolâs own storage): reads publicInputs from theSENDERframeâs calldata, verifies the Merkle root against the poolâs stored history (SLOAD), confirms the nullifier hasnât been spent (SLOAD), and executes the Groth16 pairing check against those publicInputs. If everything passes, callsAPPROVE.SENDERframe: marks the nullifier as spent, transfers the net amount to the recipient, and credits the fee to the sponsor inside the pool.
Crucially, fees no longer need to come from an external sponsor. The withdrawal itself can pay for execution: the SENDER frame can route a portion of the withdrawn funds to cover gas, removing the need for a pre-funded sender or third-party relayer. The sponsorâs share stays in the pool as an internal credit, claimable later, so the withdrawal emits one outbound transfer instead of two.
| # | Mode | Subclass | Target | Caller | Flags | Data | Role |
|---|---|---|---|---|---|---|---|
| 0 | VERIFY |
only_verify |
pool (null â tx.sender) |
ENTRY_POINT |
APPROVE_EXECUTION |
SNARK proof | Read publicInputs (root, nullifier, recipient, amount, sponsor, fee) from Frame 2 calldata, SLOAD acceptedRoots[root] and !nullifierHashes[nullifier], verify proof, APPROVE(APPROVE_EXECUTION) |
| 1 | VERIFY |
pay |
sponsor | ENTRY_POINT |
APPROVE_PAYMENT |
(empty / sponsor policy data) | Introspect Frame 2: target = pool, selector = withdraw, encoded sponsor = self, encoded fee ⼠MIN_FEE. APPROVE(APPROVE_PAYMENT) â sponsorâs ETH debited here |
| 2 | SENDER |
user_op |
pool (null â tx.sender) |
pool (tx.sender) |
APPROVE_SCOPE_NONE |
withdraw(publicInputs) |
Mark nullifierHashes[nullifier] = true, ERC20.transfer(recipient, amount â fee), sponsorCredits[sponsor][token] += fee |
A sponsorâs risk is zero for invalid proofs (VERIFY reverts, tx dropped) and zero for replayed proofs (nullifier already marked, VERIFY reverts). No trust needed, no relayer infrastructure, and no additional censorship surface.
three gates to inclusion
A privacy-focused frame transactionâs path to censorship resistance passes through three independent gates, each with its own constraints:
gate 1: public mempool admission
EIP-8141âs mempool rules, drawing from ERC-7562 but stripping staking and reputation entirely, define what can propagate through the public P2P network:
- Validation prefix must match a recognized pattern (
self_verifyoronly_verify+pay, optionally preceded bydeploy) SLOADrestricted totx.senderstorage only- Total
VERIFYgas capped atMAX_VERIFY_GAS(100,000) - Banned opcodes:
TIMESTAMP,NUMBER,BLOCKHASH,BALANCE,SELFBALANCE,SSTORE,TLOAD,TSTORE, etc. - Canonical paymaster identified by exact runtime code hash, with timelocked withdrawals and node-side pending balance tracking
- Non-canonical paymasters limited to
MAX_PENDING_TXS_USING_NON_CANONICAL_PAYMASTER(1) pending transaction each
Anything violating these rules is rejected from the public mempool but can still reach builders through alt-mempools or private channels.
gate 2: focil enforcement
FOCIL guarantees transaction inclusion: 16 IL committee members per slot build inclusion lists from their view of pending transactions. Attesters only vote for blocks that include IL transactions (or demonstrate their invalidity at post-state).
For EOAs, FOCILâs omission check is a cheap nonce/balance lookup against post-state. For FrameTxs, the omission check requires replaying the VERIFY prefix. There is no cheap proxy because transaction validity depends on execution, not just sender state. The FOCIL-frame-txs proposal defines eligibility through five constraints:
- Validation-prefix ordering:
VERIFYframes must precedeDEFAULT/SENDERframes - Bounded per-tx
VERIFYgas:verify_gas(tx) <= MAX_VERIFY_GAS_PER_FRAMETX(100,000) - Per-IL
VERIFYbudget: cumulativeVERIFYgas across an IL capped atMAX_VERIFY_GAS_PER_INCLUSION_LIST(250,000) - Basic tx sanity:
chain_id, fees, no blobs - Bounded state access:
VERIFYmay only readtx.senderand payer account state (balance,nonce, code) plus their firstNstorage slots (N= 2-4), aligned with AA-VOPS caching. Storage reads from any other contract render the transaction ineligible.
FrameTxs failing any constraint are excused from FOCIL enforcement: their omission cannot be held against a builder.
gate 3: node validation capability
Terminology:
PS = Partial Statelessness: holding some state, not all
VOPS = Validity-Only PS: holding enough state to validate txs from EOAs
AA-VOPS = VOPS + a few storage slots per account
Post-ZKEVM, nodes wonât need to hold the full state. VOPS nodes, for example, store only ~8.4 GB (nonce, balance, codeHash per account). AA-VOPS extends this by maintaining the first N storage slots for each account in the trie (so some tx.sender and payer slots are locally available for VERIFY replay). Partially stateful (PS) nodes additionally track storage for selected contracts. The node type determines which VERIFY frames it can replay locally, and therefore which transaction classes it can admit to its mempool and source into a FOCIL-like inclusion list:
| Capability | Full node | PS node | AA-VOPS | VOPS |
|---|---|---|---|---|
EOA nonce/balance check |
Yes | Yes | Yes | Yes |
AA wallet VERIFY (tx.sender storage) |
Yes | If tracked | N-slot subset |
No |
| Canonical paymaster admission (code-hash match + balance reservation) | Yes | Yes | Yes | Yes |
| Canonical paymaster VERIFY trace replay | Yes | If tracked | No | No |
| Privacy pool storage (roots, nullifiers) | Yes | If tracked | No | No |
A node that cannot validate a transaction type cannot maintain it in its mempool and should not include it in an IL. Censorship resistance for that class degrades with the fraction of capable validators.
See Frame Transactions Through a Statelessness Lens for a detailed analysis of which node types can support which EIP-8141 mempool strategies.
why privacy transactions fail all three gates
Privacy withdrawals fail all three gates under default parameters:
gate 1 (public mempool): With tx.sender = pool, the VERIFY frameâs SLOADs on the poolâs Merkle root history and nullifierHashes mapping satisfy the tx.sender-only storage restriction, but the Groth16 pairing check exceeds the 100k MAX_VERIFY_GAS cap defined in EIP-8141âs validation trace rules. Rejected.
gate 2 (FOCIL eligibility): A Groth16 pairing check exceeds the 100k per-tx VERIFY gas budget. Even with tx.sender = pool aligning the poolâs storage with the bounded state access rule, the gas cap alone disqualifies the transaction. Excused from enforcement.
gate 3 (node capability): VOPS and AA-VOPS nodes donât hold pool contract storage. Only PS nodes tracking the pool or full nodes can validate.
| Transaction type | Public mempool | FOCIL-eligible | VOPS | AA-VOPS | PS (tracking pool) |
|---|---|---|---|---|---|
| EOA frame tx (ECDSA/P256) | Yes | Yes | Yes | Yes | Yes |
Smart wallet (tx.sender storage, <= N slots) |
Yes | Yes | No | Yes | Yes |
| Canonical paymaster sponsored | Yes | Yes | No | No | If tracked |
| Privacy withdrawal | No | No | No | No | If tracked |
Privacy transactions are thus excluded from every default path to censorship resistance. The transactions most in need of censorship resistance are the ones the current design cannot protect.
This is not inevitable. The gap stems from how FOCIL enforces inclusion of FrameTxs, and the FOCIL-frame-txs proposal offers two approaches with very different implications for what can be enforced. The choice between them determines whether privacy-related FrameTxs have a path to censorship resistance or not.
focil enforcement: two approaches
Both approaches address the same problem: how builders prove they didnât censor eligible IL FrameTxs, and how attesters verify those claims.
append-loop approach
The default approach has the builder iteratively append excluded eligible IL FrameTxs to the block until no omitted FrameTx is valid at the final post-state. The cost is O(k²) in the number of excluded FrameTxs. This quadratic builder cost forces conservative parameters: MAX_VERIFY_GAS_PER_FRAMETX at 100_000 and MAX_VERIFY_GAS_PER_INCLUSION_LIST at 250_000.
Assuming 25% adversarial IL committee (4 of 16 members, which occurs about once per month with a 1% stake share) and ~100-byte txs: each IL could fit ~81 txs within MAX_BYTES_PER_INCLUSION_LIST (8 KiB), but the per-IL VERIFY gas budget allows only 2 FrameTxs with MAX_VERIFY_GAS_PER_FRAMETX gas per IL (2 * 100k = 200k <= 250k). 4 malicious ILs yield k=8 invalid FrameTxs. The append loop runs k(k+1)/2 = 36 VERIFY replays: 3.6M gas (~6% of block). Attester work: 4 * 250k = 1M gas (~1.7%). The overhead is modest, but these parameters make FOCIL largely toothless for FrameTxs: MAX_VERIFY_GAS_PER_FRAMETX at 100k excludes Groth16 proofs, PQ signatures, and any non-trivial validation logic, while MAX_VERIFY_GAS_PER_INCLUSION_LIST at 250k caps each IL at just 2 full-gas FrameTxs. An IL with room for only 2 enforceable FrameTxs is barely enforcing account abstraction at all.
validation-index approach
The follow-up eliminates the append loop. Instead, the builder excuses each excluded tx by publishing a (tx_hash, claimed_index) pair declaring the block index at which it became invalid. Attesters reconstruct state at claimed_index using Block Access Lists (EIP-7928) and replay the VERIFY prefix there. If VERIFY succeeds, the builder lied and the block fails IL conditions. Builder cost drops from O(k²) to O(k). The trade-off is added protocol complexity: the index claims are an additional network artifact, engine_newPayload would need to accept them alongside IL transactions, and the EL would need to validate them accordingly.
With builder cost no longer the bottleneck, MAX_VERIFY_GAS_PER_INCLUSION_LIST can rise to 2^20 (~1M gas), and the per-tx cap becomes less critical since each excluded tx is validated once at its claimed_index rather than iteratively. The 2^20 per-IL would be high enough for transactions from privacy protocols to benefit from FOCILâs censorship resistance guarantees.
Same 25% adversarial scenario: each IL now budgets up to 10 FrameTxs at 100k each (10 * 100k = 1M <= 2^20). Four malicious ILs * 10 = 40 excluded FrameTxs. Builder cost is linear: 40 * 100k = 4M gas (~6.7% of block). Attester cost: 4 * 2^20 â 4.2M gas (~7%). Worst case across all 16 ILs: 16 * 2^20 = 2^24 â 16.8M gas (~28%). The ~4x increase in per-IL budget eliminates the quadratic builder blowup entirely.
node capabilities as the limiting factor
The index approach opens a separation between public mempool rules and FOCIL enforcement. Public mempool rules must be strict because transactions may need re-validation after every block, so state dependencies must be small and deterministic. FOCIL under the validation-index approach replays VERIFY exactly once at a fixed claimed_index. No ongoing maintenance cost, which is what lets it afford broader state access and higher gas budgets. The flip side: FOCIL validation is on the attester critical path (within the t=4s attestation deadline), while mempool validation runs asynchronously. Higher MAX_VERIFY_GAS_PER_INCLUSION_LIST directly eats into attester time budgets.
This means IL committee members can source transactions from alt-mempools (including privacy-focused ones) and include them in their ILs, even if the default public mempool doesnât carry them. But for this to work for privacy txs, the bounded state access rule (reads limited to tx.sender and payer accounts) needs to be relaxed. And the nodes enforcing inclusion need the state to actually validate these transactions.
AA-VOPS cannot bridge this gap. It caches N storage slots per account, which is enough for simple wallet validation (owner key, nonce) but not for privacy protocols. Privacy transaction-related VERIFY frames read pool contract storage (root ring buffers, nullifier mappings), not sender storage. No value of N fixes this: the reads target a different contract entirely. Even if AA-VOPS cached N slots for every contract, it wouldnât help: nullifier mappings are keyed by hash, so the accessed slots are unpredictable and spread across the storage trie.
a canonical privacy pool
The most practical solution is a canonical pool pattern, analogous to the canonical paymaster. A canonical contract, recognized by code hash, could serve as a shared registry for multiple privacy protocols: storing per-pool append-only Merkle roots and nullifier sets in a unified, auditable layout. Designed correctly, such a pool is safe for both the public mempool and FOCIL enforcement: VERIFY frames targeting this contract read exactly two storage slots: acceptedRoots[R] and nullifierHashes[h], both at calldata-derivable keys. The access pattern is bounded, predictable, and monotone: every slot VERIFY reads only ever transitions false â true. Instead of requiring nodes to track N separate pool contracts, a canonical registry reduces the problem to one well-known address with a known storage layout, making partial statefulness for privacy practical at minimal cost.
Monotone state is what makes the design mass-invalidation-resistant. Todayâs privacy protocols use a rolling ring buffer of recent Merkle roots, evicting the oldest entry on every deposit. Under EIP-8141, that eviction is a mass-invalidation vector: a single deposit (or attacker spam) can rotate out a root that dozens of pending sponsored withdrawals depend on, evicting them all from the mempool simultaneously. Replacing the ring buffer with an append-only acceptedRoots mapping eliminates the vector entirely. A withdrawal proof generated against any historical root remains valid forever; the nullifier set already prevents double-spends, so accepting old roots is cryptographically safe. The only state change that can invalidate a pending withdrawal is a write to its specific nullifierHashes[h] slot, which requires possession of the noteâs secret. Thatâs per-tx and individually targeted, never mass.
One implementation detail: ring buffers for Merkle roots, as commonly used in todayâs privacy protocols, must be replaced with append-only mappings to avoid mass invalidation. In terms of size, thatâs unproblematic: e.g. the most commonly used Tornado Cash contract (1-ETH pool) had 81,881 deposits. That would be roughly 10.5 MB of raw key-value data (81,881 entries each in
acceptedRootsandnullifierHashes, ~64 bytes per entry), or about 25â40 MB once stored in the state trie. A PS node tracking this pool commits to well under 50 MB of extra state, which is negligible compared to the ~8.4 GB VOPS baseline.
With 16 IL committee members per slot, FOCIL needs only 1-of-16 honest members to guarantee inclusion. But âhonestâ here also means âcapableâ: a member who wants to include a privacy tx but runs a VOPS or AA-VOPS node simply cannot validate it. The 1-of-16 honesty assumption becomes a 1-of-16 capability assumption. PS nodes solve this. They donât need full state, just the contracts they care about. A PS node that tracks one canonical privacy pool adds a few MB at most. The infrastructure cost is negligible; the question is whether enough validators choose to do it.
An alternative to canonicalizing privacy pools is to let transaction senders attach the required state (storage) along with Merkle proofs against the latest state root. This approach falls short for several reasons, most importantly because these proofs become invalid after every block and must be continuously recomputed.
proposed changes
- extend the canonical-contract exception to privacy pools. EIP-8141 already admits canonical paymasters by runtime code-hash match, exempting them from the strict public mempool rules because their state-dependency is known-safe.
- raise the per-tx
VERIFYgas cap for canonical-contract frames.MAX_VERIFY_GAS = 100_000isnât enough for Groth16 (~250k). Canonical contracts have a fixed verification path, so their worst-caseVERIFYcost is a static property of the code hash. An invalid proof burns the same gas as a valid one and gets dropped, with no amplification. Keep 100k for generic frames; allow e.g. ~400k for canonical-contract frames, leaving enough room, and cap their number in the mempool - adopt validation-index FOCIL enforcement, accepting the added protocol complexity (
(tx_hash, claimed_index)mappings) to eliminate the quadratic builder overhead that forces conservative gas budgets forFrameTx - raise
MAX_VERIFY_GAS_PER_INCLUSION_LISTto2^20(=1M), letting individual txs with higherVERIFYgas (Groth16, PQ signatures) fit within per-IL budgets - relax the bounded state access rule for canonical privacy pool contracts, allowing IL committee members to source these txs from alt-mempools and making their inclusion enforceable by attesters running PS nodes that track these contracts
Together, these give privacy FrameTxs a viable path to censorship resistance: public mempool propagation under the canonical-contract exception, inclusion by PS-capable IL members, and FOCIL enforcement via the validation-index approach, without weakening DoS resistance for non-canonical FrameTxs.
Similar arguments apply to post-quantum transactions. PQ signatures are large and expensive to verify, and 100k
VERIFYgas isnât enough to cover it. Like privacy protocols, PQ txs would need an exemption from the flat cap, either by canonicalizing the verification contracts or by adding canonical precompiles.
The current public mempool rules for FrameTxs, VOPS and AA-VOPS are too rigid for privacy protocols. Canonicalizing privacy pools changes that. Recognizing them by code hash lets the public mempool safely validate their VERIFY frames, and PS nodes tracking a small set of high-value contracts (privacy pools, canonical paymasters) can build inclusion lists for them. What it buys is censorship resistance for the transactions that need it most.
appendix
why alternative mempools fail
- fragmentation doesnât compose: Every tx needing heavier validation (privacy, PQ, etc.) might have to spawn its own alt-mempool. Node operators pick winners, peer counts supporting those thin out, and the weakest alt-mempool becomes the censorship vector for whatever depends on it. Without built-in incentives, alt-mempools only scale by asking volunteers to run them altruistically, potentially even requiring those to buy more powerful and expensive hardware.
- anonymity set collapse: The anonymity set degrades from âall Ethereum nodesâ to ânodes supporting privacy mempoolsâ, and peer connections leak at the network layer.
- trivially censorable at the network layer: Bootstrap nodes, DNS records, and peer lists for a single-purpose alt-mempool are chokepoints an ISP or nation-state can drop with one rule. The public mempool is hard to censor because itâs load-bearing for the whole network; an alt-mempool isnât. Privacy traffic ends up on infrastructure structurally easier to take offline than the relayer it replaced.
- focilâs 1-of-16 assumption breaks: Timely inclusion only holds if at least one IL committee member peers with the alt-mempool and can validate the tx. The â1-of-16 honestâ assumption becomes â1-of-16 honest, subscribed, and capableâ which is a much weaker guarantee than EIP-7805 promises. This is particularly problematic if IL builders are advertised as low-hardware nodes.
- the relayer doesnât disappear, it just moves: Alt-mempool txs still have to reach a builder. Bridging nodes that forward across the boundary are exactly the trust and censorship surface frame transactions were supposed to eliminate, one hop down the stack.
frame tx <> public mempool decision tree:
canonical privacy pool example
For an example canonical privacy protocol implementation (using pseudo code), check this.

