The problem
When a user signs a transaction today, they commit to calldata, not to outcomes. The wallet simulates and shows what it expects; nothing in the protocol binds execution to that expectation. A malicious frontend, a proxy upgrade, state drift, or a routing change between sign and inclusion can produce a different outcome, and the user has no recourse.
Existing mitigations (protocol-specific slippage params, wallet-side simulation, off-chain screening, ERC-7730 clear-signing) are all above the protocol and none generalize.
Why this can’t be solved above the protocol
I’ve been working on an intent-based state-transition framework — ERC TBD: Intent-Based State Transition — from the framework layer through to dapp contracts for months. I keep hitting the same wall:
In the EVM, there is no way to guarantee that a piece of code MUST run before the transaction ends. A RETURN anywhere in the call stack ends that frame, and there is no consensus-level hook that runs after the outermost call. Any verifier I place “at the end” can be skipped by routing the user’s call through a different path and returning before reaching it. The framework can require the verifier by convention, not by consensus.
The EL’s end-of-execution check is the only place where a piece of code is guaranteed to run regardless of how the call stack unwound. That’s the gap this idea fills.
The proposed primitive
A new transaction type carries a signed event manifest in an extended access_list: per address, a declaration of what log events the transaction must emit ([item, ...], with per-topic and per-data-word value predicates), may emit anything ([]), or must not emit (0x). The EVM checks the manifest during and at the end of execution; mismatch reverts the transaction.
Predicates admit exact / range / union / wildcard forms, so manifests can express slippage tolerances and recipient sets, not just point equality. The same primitive composes naturally with ERC-7683 (cross-chain outcome commitment), ERC-7730 (the rendered surface and the on-chain check are bit-for-bit the same artifact), ERC-4337, and EIP-7702.
As a side benefit, the global emitter whitelist is a strictly richer execution-graph hint than the legacy access list: every contract the transaction touches via a state-changing path shows up, so proposers running EIP-7928-style parallel execution get tighter conflict prediction for free.
Full draft (with grammar, matching rules, gas accounting, test cases, and rationale): EIPS/eip-draft_event_manifest.md
Questions I’d value feedback on
- Is enforcement-via-events the right shape? Events are ABI-typed and stable across implementation changes, which is why I chose them over storage post-state predicates. Are there outcomes you’d want to bind that events don’t expose?
- Greedy matching vs. bipartite. The draft uses greedy (single pass per
LOG), at the cost of requiring wallets to order “specific before broad.” Acceptable, or argues for end-of-execution maximum matching? - Dynamic-data events. Currently every data word must be declared (count derived from a simulation trace), making
bytes/stringevents fragile to state drift. Worth a tail-wildcard form now, or defer? - ERC-4337 bundler economics. For a
UserOperation’s manifest to reach the EVM, the bundler has to submit the new tx type and pay intrinsic gas for the manifest bytes. Deal-breaker or workable?
The draft is a working document, not a PR to ethereum/EIPs yet — I’d like to converge on the core primitive here before formalizing.