@derek By the way, what is the motivation for atomic batching only being defined for SENDER frames? Doesn’t it prevent DEFAULT frames from relying on transaction level post-transaction assertion modules?
Good point – I think we wanted to exclude VERIFY frames from atomic batching because that’d make validating transactions more difficult since you need to make sure VERIFY frames are not reverted by other frames, but I don’t see a reason why DEFAULT frames should be excluded too. In fact, I think it might even be OK to allow any frames (including VERIFY) to be atomically batched, and simply forbid atomic batching with VERIFY frames in the mempool rules (but not in the protocol itself). That way, if private pools want to use atomically batched VERIFY frames to enable use cases like revert protection, they are free to do so. What do you think?
Read through the thread carefully. Adding a contract-level perspective that maps onto a few of the still-open debates.
Background: we’ve been operating EVVM, a smart-contract-native virtual blockchain framework, since 2023. It runs validation, signature schemes, async nonces, batched execution, and gas sponsorship inside Core.sol. ~200 community deployments across 10+ EVM-compatible chains. Each instance can configure its own rules independently of the others on the same host.
Three observations from the production context that overlap with current discussion:
1. The “what about institutional rules” gap. Most of the thread reasons about a universal frame transaction. Some real users (banks, regulated DeFi, public-sector pilots) need per-environment policy: KYC/AML gates, allowlists, custom delegatecall guards in front of execution. EVVM instances configure those policies through per-deployment contracts. None of that is reachable at protocol level without breaking universality, and 8141 correctly does not try to. Worth being explicit in the spec or rationale that the contract layer remains the place where policy variance lives, because real adopters will land there first.
2. Async-execution compatibility as a contract-level property. @DanielVF and @chunter’s concerns about Monad / Base / Tempo are real for protocol-level validation. EVVM-style validation sidesteps that entire class because it runs inside the contract at execution time, against the post-state of prior transactions in the block. The host chain treats it as a regular contract call. Async execution and contract-level AA can coexist by design: nothing in this model depends on validation being known at mempool time. Not an argument against 8141, an architecture point that may be worth referencing for chains where 8141 cannot ship.
3. Two implementation choices in 8141 that we made differently, observable in EVVM today:
-
Per-operation success tracking in batches, vs the all-or-nothing atomic batching in @derek’s recent PR. EVVM’s
batchPayreturnsbool[] results. The trade-off between application-class needs (e.g. independent dust transfers vs approve-then-swap dependencies) is one we hit early; whether 8141’s atomic-only mode covers both classes cleanly or forces the per-op case into separate transactions is worth checking before lock-in. -
Non-authoritative reservation as the only reservation primitive (covered in the EIP-8250 thread). We deliberately did not ship authoritative locks because of DoS-by-lock-grabbing and bad-UI lockup vectors; this likely matters at protocol level too if keyed nonces ship.
References:
-
Signature constructor: https://www.evvm.dev
Best,
German Abal / @ariutokintumi
Co-Founder, Architect & Security of EVVM
Some super tiny nits as I’m reading the spec:
frame.target is None→ should this beframe.target = b""?APPROVE_PAYMENT_AND_EXECUTION→ should beAPPROVE_EXECUTION_AND_PAYMENTto match the order of evaluation.- Point 3 of the policy summary is confusing.
- “paymaster frame” isn’t really defined before it’s used.
Random other thoughts:
- This proposal is consuming five opcode slots. Is that acceptable? The alternatives aren’t particularly attractive.
- Why make
FRAMEDATACOPYrevert on out of bounds whenCALLDATACOPYdoes not? Is there a benefit that outweighs the extraFRAMEPARAM(0x04, ...)call for contracts that know exactly how much they need to copy?
Sounds good. By the way, do you think the same can be said about other things where VERIFY frames are treated differently, say, for example the SSTORE not being allowed on the protocol level vs. as a mempool rule?
Afaik, VERIFY frames are implictly batched to anything… a reverted VERIFY renders the entire TX invalid
My understanding is that the reason why VERIFY frames must not do SSTORE is because we want frame transactions to maintain the invariant that even if you remove all the VERIFY frames from a txn, the txn will still do the same thing. That way, block builders can aggregate VERIFY frames and remove them from txns, which is what allows VERIFY frames to carry large quantum signatures (since they will be removed anyways).
That’s true, but currently no other frames may revert a VERIFY frame, whereas if we allowed a VERIFY frame to be batched with other frames, it could be reverted by another frame.
@alex-forshtat-tbk atomic batching now supports any frames: Update EIP-8141: Support atomic batching with any frames by derekchiang · Pull Request #11652 · ethereum/EIPs · GitHub
Awesome, thank you! ![]()
Sounds really cool, and I would like to learn more about the details of how this aggregation can be done in practice.
To be clear, I am not pushing for non-static VERIFY frames, it intuitively makes sense that verification is a static call - I just wanted to make sure there is a clear rationale for other opcodes being limited in the mempool rules, but not SSTORE etc.
Also, if state changes make it harder to aggregate a VERIFY frame, should we think about rephrasing the behaviour of APPROVE (i.e. “Increment the sender’s nonce… and collect the transaction’s maximum cost from payer”)?
I had a question about Example 3 (ERC20 paymaster). Given the current EIP language I don’t see how it would be possible? More specifically - I’m not sure what paymaster it would use.
It can’t be using the canonical paymaster since that’s defined to have a fixed bytecode that uses ecrecover for checking authorization.
If it’s using a non-canonical paymaster, then it’s restricted to only performing storage reads on tx.sender. This means it can’t check the user has enough ERC20 tokens.
Is there some mechanism I’m missing?
I also wanted to say - a lot of the thread has been about how complexity in the verification phase is dangerous. There’s also utility in that complexity. We don’t want to scope the design so tightly that only known use cases are supported. Just like how supporting arbitrary user-verification can lead to new solutions, arbitrary paymaster-verification is also super powerful and limitations should be intentional. I’ve been working on a few alternative privacy-focused paymasters for Kohaku that would definitely benefit from more flexibility and wouldn’t be possible given the current doc.
Hi @trebor, there are a few ways to do ERC-20 sponsoring depending on who and how you want to assign griefing risk. In example 3, the sponsor would not be able to check on-chain the user’s ERC-20 balance due to the restrictions you mentioned. The sponsor would need to verify the user’s balance off-chain before deciding to approve the payment for the transaction. This is a similar risk profile of other sponsoring systems today, because there is no native AA, the sponsor has to at least front the cost of data and initial validation. It just requires the sponsor be more sophisticated about who they sponsor. If a user front runs the sponsor the sponsor can degrade or reject service for that user.
It is certainly possible to support ERC-20 payments in the public mempool and actually gate validity on it, but it would introduce a lot of complexity and require some hard governance decisions. So we are holding off on supporting it for now.
Thanks for the clarification @matt. I’ll see if I can make a PR clarifying this in the EIP, since right now example 3 is misleading. It currently states that Frame 1 “Checks that the user has enough ERC-20 tokens“, which wouldn’t be possible given it requires a state read. Frame 1 should instead be a signature check performed by the canonical paymaster.
I would like to ask again whether this EIP removes the limitations of EIP-7825?
I was looking at the spec recently and, unless I’m missing something, there is no reasonable way to do custom signature outside the default supported ones (ECDSA on the k1 and r1 curves). This means that even doing something as ubiquitous as passkeys is quite a pain:
From reading the specification, AFAICT, there is no way to introduce custom signature bytes that are simultaneously accessible by the EVM (in a smart contract) and not included in the canonical frame hash. Going back to the passkey example, naively I would have expected to use the canonical frame transaction hash as the WebAuthn challenge and then compute the WebAuthn specific hash that ultimately gets signed with ECDSA in the EVM. However, with the current spec I am required to compute some frame transaction hash in the EVM for the challenge (otherwise, the signature depends on the frame transaction hash which depends on the signature - which means you get this “self-referential signature” problem when you try to use the canonical frame transaction hash; I don’t think custom msg field in signature frames work either, you get the same circular dependency issue). IMO this is a huge loss of functionality compared to previous iterations of this EIP.
This does not only apply to passkeys, but any other custom signature schemes that are implemented on the EVM (PQ hash based signatures, ZK proof style “signatures” for private transactions, etc.).
If I’m missing something obvious, please correct me (I would suggest adding an example for this in the EIP itself). Otherwise, I would really hope that custom EVM signature schemes be considered as a first class citizen for this EIP. Passkeys alone is, IMO, a sufficiently in demand flow to warrant its consideration.
@nlordell The data for the verify frames is NOT included in frame hash. This means you can compute frame hash, sign it, then insert the signature into the verify frame.
Or am I misunderstanding your question?
This is also exactly how I remember previous iterations of the EIP. However, AFAICT, the current spec only seems to omit signature bytes from signature frames where msg = 0 and include verify frame calldata: EIP-8141: Frame Transaction
The canonical signature hash is defined such that any signature with empty
msgwill have its rawsignaturebytes elided:def compute_sig_hash(tx: FrameTx) -> Hash: for i, sig in enumerate(tx.signatures): if len(sig.msg) == 0: tx.signatures[i].signature = Bytes() return keccak(bytes([FRAME_TX_TYPE]) + rlp(tx))
Nope, sounds like you understood it
.
Good catch @nlordell, this is a mistake I made when merging the signature list PR. Originally I had a signature type for arbitrary signature bytes which would allow any crypto primitives to use the canonical hash, but later on I some how thought it wasn’t needed. I will update the PR soon to resolve this – definitely not our intention to disallow alternative signature schemes.
Strongly agree with the direction here. What I like most about the frame model is that it collapses validation and execution back into one execution environment, plain EVM, instead of the two-tier split ERC-4337 ended up with (UserOperation simulation off-chain, actual execution on-chain, bundler infra in between). Making VERIFY just a STATICCALL-restricted frame inside the same transaction, with APPROVE as the explicit hinge point, feels like the “right” primitive that account abstraction has been reaching for since ERC-4337, it’s the same idea, just moved from the application layer into the protocol, which is where a lowest-common-denominator account primitive probably belongs.
The thing I keep coming back to: rigidity in Ethereum isn’t just at the transaction-validation layer that this EIP fixes, it’s also at the state layer. A contract today has exactly one way to hold non-token, non-balance state: raw storage slots with no protocol-recognized shape or lifecycle. Every app re-invents its own conventions for anything that isn’t a balance (inboxes, sessions, subscriptions, content pointers), the same way every account used to re-invent its own auth logic before this EIP. I’ve been drafting a much smaller, narrower proposal in that direction, a generic, precompile-based typed-object primitive with an optional deferred-call mechanism, explicitly modeled on the same instinct this EIP embodies: push flexibility into the base layer once a pattern is common enough, rather than leaving every app to hand-roll it in app-specific storage. Happy to share if useful context, but mentioning it mainly because I think “native flexibility, validation-side and state-side” is a coherent direction to be pushing on together, not two unrelated asks.
Two things I’d be curious about from people closer to client implementation:
- How does paymaster balance reservation behave under real contention, if many pending frame transactions target the same canonical paymaster near its
MAX_PENDING_TXS_USING_NON_CANONICAL_PAYMASTERceiling, does eviction/replacement create fee-bump griefing opportunities that ERC-4337’s reputation system was partly designed to prevent? - Given
ORIGINnow returns the frame’s caller instead of the traditional tx origin, has there been any measurement of how muchtx.origin-based security logic in deployed contracts this could silently break, beyond the usual “it’s a discouraged pattern” caveat?
Either way, this feels like the right shape for native AA. Excited to see where CFI review lands.