Cover photo

Account Abstraction: The Story So Far

From EIP-86 to Frame Transactions

In April of 2016, issue #86 was opened in Ethereum's EIPs repository. Entitled Proposed initial abstraction changes for Metropolis, this was the first major proposal trying to solve the problem of account abstraction (AA). Ten years and more than eight thousand EIPs later, EIP-8141 (Frame Transactions) was proposed as the headliner for the Hegota upgrade. This article tells the story of how we went from EIP-86 to EIP-8141 and what it took to get there.

My goal here isn't to enumerate and briefly explain each proposal. Instead, I want to give a better sense of the problem itself: what trade-offs are involved, how the thinking evolved over time, and why we are still talking about it. While I'm not assuming any familiarity with AA, I do expect the reader to have some basic Ethereum knowledge, such as the difference between EOAs and contracts, the CREATE2 opcode, and how nonces work. There are other concepts we'll need, but we'll cover them before we start.

Subscribe

Basics

This section introduces some basic concepts that are important to understand the rest of the article. We'll also start building a mental framework that will help us think about the different account abstraction proposals we'll explore.

What's not abstract about accounts?

Let's start with a simple example to explain how Ethereum accounts work and what their limitations are. We'll come back to this example many times throughout the article to explain different AA mechanisms.

Alice wants to send 1 ETH to Bob, who has never interacted with Ethereum before. Bob wants to receive that ETH and then transfer 0.1 of it to Charlie. Assuming Bob wants to use an EOA (the default path for most new users), this is what will happen next:

  1. Bob generates an ECDSA pair and derives a public address from it. He shares the address with Alice, and she sends the ETH.

  2. Once Bob receives the ETH from Alice, he builds a transaction that looks like this: { to: charlie, value: 0.1 ETH, nonce: 0, gasLimit: 21000, gasPrice: X }. He signs that transaction with the private key from the first step, and then sends the signed transaction to some node. The transaction is propagated through the network.

  3. Nodes that see Bob's transaction perform some checks to verify that it's valid. For example, they:

    • Check that the signature is valid and use it to recover the address of the sender (Bob, in this example).

    • Check that Bob's balance is at least gasLimit * gasPrice, since that's the maximum amount of ETH he could end up spending from the transaction.

    • Check that the nonce of the transaction matches the nonce of Bob's account.

    If any of these checks fail, the transaction is dropped. Otherwise, the nodes add it to their mempool.1

  4. Eventually, a block builder creates a block that includes Bob's transaction. Once that happens, Bob and Charlie can see that the 0.1 ETH was transferred.

There are many things in this example that are "hardcoded" at the protocol level:

  • An EOA is managed by a single, unchanging ECDSA key.

  • Access is all-or-nothing: if you have the private key, you can do anything you want with the account; if you don't have it, you can do nothing at all.

  • The account sending a transaction and the account paying for its gas are always the same.

  • The block builder is always paid in ETH.

  • Replay protection is handled by a single, monotonically increasing nonce.

These aspects rule out several use cases: you can't rotate the key of an EOA, allow other people to use it within some limits, or have some social recovery mechanism in case you lose your key. Applications can't sponsor the gas cost of transactions for users. UX mechanisms like batching are not possible. The list goes on.

There's also another big problem: it's widely assumed that ECDSA will be broken by quantum computers at some point in the future. This means that all EOAs, as they exist today, will eventually be compromised. This is an existential risk that needs to be fixed sooner or later (and sooner would be nice).

What about smart wallets?

Instead of an EOA, it's always been possible to use a contract that acts as a wallet. These "smart wallets" fix every problem we mentioned above: you can have multisig schemes, use complex authorization mechanisms, rely on cryptographic primitives other than ECDSA, etc.

The problem is... who calls the smart wallet? That call, wherever it comes from, is ultimately part of a transaction, and transactions can only be initiated by EOAs.

Smart wallets are the best solution, because the most powerful way to abstract something is to let it be arbitrary code. But they aren't a complete solution as long as they need EOAs. In that sense, the problem of account abstraction can be reframed as: how do we initiate actions from smart wallets without using EOAs?

Now that we have the basics in place, let's start with our story, which takes us back to the beginning of Ethereum itself.

Part 1: Early attempts (2015–2020)

One of Ethereum's goals from the start, and arguably its entire raison d'être, is the high degree of abstraction that the platform offers.

— Vitalik Buterin, July 2015

The fact that EOAs were too rigid and quantum-unsafe was known even before Ethereum's first block was mined. At the time, though, the separation between EOAs and contracts was deemed a reasonable trade-off to ship the chain sooner. But less than a year after the launch, there was already one proposal addressing the problem.

EIP-86: Unsigned transactions

In the Basics section, we said that smart wallets fix every account abstraction issue, since they can be used as accounts and implemented in any desired way. The problem would end here if it weren't for the fact that smart wallets still need those pesky EOAs to call them.

With this in mind, the solution proposed by EIP-86 is laughably simple: just allow unsigned transactions to be mined. The EIP adds a new kind of transaction that doesn't include a signature (and therefore doesn't have a sender), and that builders can include in new blocks. To use a smart wallet, you simply call it with one of these EIP-86 transactions. The data includes everything the wallet needs to verify it's a valid call and to execute the intended action.

The main question is: why would block builders include these transactions? If they don't have a sender, then there's no one to pay for their inclusion. EIP-86's answer is that wallets themselves can pay the block builder after verifying that the transaction is valid. This is possible because the address of the block builder can be obtained through the COINBASE opcode.

The following pseudocode shows what an EIP-86 smart wallet would look like. For simplicity, the wallet in this example uses a single ECDSA key, so its functionality is not really different from that of an EOA. But since this is code, the validation could do anything else, like requiring 2 out of 3 signatures from a list of approved addresses, or allowing key rotation.

# get the destination, value, gas price, and signature from the transaction data
to = tx.data[0:32]
value = tx.data[32:64]
gasPrice = tx.data[64:96]
sig = tx.data[96:128]

# check the signature is correct
verify(tx.data[0:96], sig)

# figure out how much to pay the block builder and transfer it
gasPayment = tx.gasLimit * gasPrice
transfer(block.coinbase, gasPayment)

# do whatever you want in the rest of the execution; here
# we just make an ETH transfer to some destination
transfer(to, value)

To check if a given EIP-86 transaction is valid, a node simply executes it and checks at the end that there was a payment. If there wasn't, it drops the transaction.

Alice, Bob, and EIP-86

Let's go through our Alice and Bob example to understand how EIP-86 would work in practice. Bob starts by sharing the address of his EIP-86 wallet with Alice, who then sends it 1 ETH (she doesn't care whether Bob's address is an EOA or a contract). Once he receives the transfer, Bob builds a piece of transaction data that looks like this:

txData = [charlieAddress, 0.1 ETH, gasPrice, sign([charlieAddress, 0.1 ETH, gasPrice])]

where the signature is computed over all the other fields. He then sends this data in an EIP-86 transaction, with his own wallet as the target and some reasonable gas limit:

{
  "to": bobAddress,
  "gasLimit": 100000,
  "data": txData
}

When nodes receive Bob's transaction, they execute it to check if it's valid (that is, if it pays the coinbase address). The transaction then stays in the mempool until eventually a block builder includes it in a block.

Deployments with EIP-86

Did you notice how I cheated? When we introduced the Alice and Bob example, we explicitly said that Bob had never interacted with Ethereum before. But in the previous section I assumed that he already had a smart wallet whose address he could share with Alice. So let's fix that.

In principle we seem to have a circular problem: Bob wants to receive some ETH using an EIP-86 wallet but he can't deploy such a wallet if he doesn't have ETH. To make things harder, we are trying to accomplish this without involving an EOA; we want our AA solution to be complete. This seems unsolvable, but we'll actually see a few approaches that work.

In the case of EIP-86, one solution arises from the following fact about the EVM: when the initialization of a contract (its constructor, in Solidity terms) is executed, it can use whatever ETH is already present in the address where it will be deployed. This means that the wallet can pay the block builder for its own deployment, just as it does for normal transactions.

With that in mind, Bob can share with Alice the address where he knows his contract will eventually be deployed. Once the address has the ETH that Alice sent, Bob can use an EIP-86 transaction to deploy the wallet. Our problem reduces to how Bob knows where his smart wallet will be deployed.

Nowadays the answer is obvious: we use some CREATE2 factory to predict the deployment address. But the CREATE2 opcode wouldn't be added to Ethereum until 2019, three years after EIP-86 was proposed. Instead, the EIP itself introduces a CREATE2-like behavior for deployment transactions. This also makes sense if you remember that in normal transactions the deployment address is derived from the sender and its nonce, but in an EIP-86 transaction we don't have either of those! Deriving the address from the init code and some salt is then a natural idea.

To recap: Bob builds an EIP-86 deployment transaction for his smart wallet but doesn't propagate it yet; he only uses it to know the address where it will eventually be deployed. He shares that address with Alice and, once she sends the ETH, Bob propagates the deployment transaction, whose init code at some point pays the block builder. After all this, Bob can use a normal EIP-86 transaction to send some ETH to Charlie from the just deployed wallet, as we explained in the previous section.

EIP-86 problems

Believe it or not, EIP-86 satisfies all of our AA requirements: you can deploy and interact with smart wallets without needing an EOA, and there's no constraint on what those wallets can do. Problem solved? Well, no, because EIP-86 introduces as many issues as it solves.

A major issue with EIP-86 is that the block builder must execute the whole transaction to figure out if they are going to get paid. One could suggest running it only until a transfer to the coinbase happens, but this wouldn't work: the transaction could fail right at the end and revert its state changes, including the payment. Compare this with a normal EOA transaction, where the validation has a fixed, upfront computational cost and the block builder is paid regardless of whether the transaction reverts.

Having a fixed, reasonable validation cost is important to prevent certain Denial of Service (DoS) attacks against the network. You can try to clog nodes today by sending a large number of invalid transactions, but the fact that the validation cost is constant means that the problem is manageable. Things change if you allow invalid transactions that can force the node to do unbounded work. With EIP-86 you could send thousands of unsigned transactions that use as much gas as possible and revert right at the end. The node would get overwhelmed by having to run them all. And even if it can take measures against the person doing the DoS, the balance of power shifts in favor of the attacker.

Another issue is related to refunds. If you send a normal transaction with a gas limit of 100,000 and the execution cost is less than that, the unused gas is "returned" to you at the end. With EIP-86 that's not really possible, because the block builder has no obligation to say "hey, you didn't need to pay that much, here's your change" and forcing this at the protocol level is difficult.

A third issue has to do with the uniqueness of transaction hashes. In normal transactions, the nonce is unique for each sender, and so the combination of nonce and signature is unique for each transaction. Since a transaction hash is computed over the whole signed transaction, the hashes themselves are therefore unique. That's not the case with EIP-86 because there are no nonces.

This is, in theory, a solvable problem, because there's nothing at the protocol-level that requires hashes to be unique. In practice, though, many things would break in the upper layers. To give just one example: an API that receives a transaction hash and returns a transaction object would have to be updated to return a list instead. And since that would be a breaking change in itself, every consumer of that API would have to be updated too.

The idea of breaking hash uniqueness was already considered disruptive in 2016, when Ethereum had just launched. In 2026, the idea of breaking absolutely everything for the niche benefits of nonce abstraction is unthinkable. For this and other reasons, nonce abstraction was eventually dropped as a goal. In the rest of the article, we'll ignore anything related to this, even if in reality many subsequent proposals kept attempting to abstract nonce management.

EIP-859: PAYGAS

The two main problems of EIP-86 are that the cost of validating a transaction is not constant, and that you can't refund unused gas. EIP-859, proposed in 2018, solves both of those problems by adding a new opcode: PAYGAS. This opcode takes a gas price as its input, multiplies it by the transaction gas limit, and pays this value to the block builder from the contract's balance (halting if this balance is not enough). In a sense, this is a shortcut to what we were already doing manually when we obtained the coinbase address and made a transfer to it. But having an opcode gives us two properties we can't get without changing the EVM:

  • PAYGAS works as a checkpoint. If the transaction fails after PAYGAS was used, the state is reverted to the point when the opcode was executed. The payment to the block builder and any state changes that happened before it are kept.

  • At the end of the transaction, any unused gas is refunded to the account using the same gas price that PAYGAS had as its input.

The smart wallet we used for EIP-86 doesn't need to change that much to work with EIP-859:

# get the destination, value, and signature from the transaction data
to = tx.data[0:32]
value = tx.data[32:64]
gasPrice = tx.data[64:96]
sig = tx.data[96:128]

# check the signature is correct
verify(tx.data[0:96], sig)

# use the PAYGAS opcode to pay the block builder
paygas(gasPrice)

# do whatever you want in the rest of the execution; here
# we just make an ETH transfer to some target
transfer(to, value)

This opcode clearly solves our refunds problem. What about the validation time?

For EIP-86, we suggested that nodes could expect the payment to happen near the start of the execution, and reject transactions that don't pay "soon enough". The problem with this idea was that a transaction could indeed pay close to the beginning and revert later. If we have a checkpointing opcode like PAYGAS, though, that same idea works: the node can ignore anything that happens after it because the rules of the protocol dictate that the payment is irreversible.

In more concrete terms, nodes can agree on a gas threshold (say, 200,000 gas) within which they expect PAYGAS to happen. If the opcode is not executed within that limit, the transaction is considered invalid and dropped. Crucially, this is a rule about the mempool, not the protocol. This distinction matters in the context of AA, so let's dive deeper into it.

Mempool rules

It will soon become clear that issues arising from transaction validation and the mempool are the main source of complexity in account abstraction. To better understand why, let's see how the mempool works in practice.

If you've been in Ethereum long enough, you're likely familiar with the concept of bumping a transaction: you send something with a gas price that happens to be too low, your transaction takes too long to be included, and you send another transaction with the same fields but a higher gas price. The latter transaction "replaces" the previous one in the mempool and the new one is included in a block faster.2

Did you know, though, that some nodes will only accept the new transaction if the new gas price is at least 10% higher than the previous one? There is a reason for this3, but the important part for us is that there's nothing in the protocol rules that says things should work that way. To illustrate this idea, imagine that a node has a pending transaction T with gas price X and then it receives a newly built block with a transaction that replaces T and has gas price Y, where Y is only 1% greater than X. The node can't say "that gas bump was under 10%, rejected!" There's nothing invalid in the block.

The same distinction applies to EIP-859 and the gas threshold within which PAYGAS should be used. Each node can decide how to set that threshold, but that doesn't mean they would reject blocks that execute PAYGAS after it; this only matters when deciding which pending transactions are valid.

In summary, we have two kinds of rules: "hard" protocol rules, which all nodes must follow if they want to participate in the network, and "soft" mempool rules where each node can do what they want but where EIPs might suggest useful guidelines.4

The multi-invalidation problem

EIP-859 seems like a keeper: it adds support for account abstraction and it doesn't seem to have DoS vectors related to transaction validation. But it has a huge DoS vector related to transaction invalidation, something it took the community a while to realize.

To understand this, imagine an EIP-859 wallet with a function that sends ETH to some address only if the balance of that address is zero:

function transferIfNoBalance(address to, uint value, uint gasPrice, bytes memory sig) public {
  // ...check that signature is valid...
 
  // check that the address's balance is zero
  require(to.balance == 0);

  // pay the block builder
  paygas(gasPrice);

  // make the transfer
  (bool success, ) = to.call{value: value}("");
  require(success);
}

There's nothing inherently wrong with this functionality, and one can even imagine a scenario where this could be useful. The problem is this: what happens if you build 100 transactions that use this functionality targeting the same address? If the balance of the address is 0, then all of those transactions will be valid on their own. But the moment one of them is included in a block, the other 99 become invalid and have to be dropped. The same idea applies to 200 or 1000 transactions. This means we can spam the mempool with as many of those transactions as we want but the cost of doing so (the cost of the only transaction that will be included) is always the same.

This problem, known as "the multi-invalidation problem", is a big headache for any account abstraction proposal, as we'll see many times in the rest of the article.

EIP-2938: PAYGAS revisited

We can think of EIP-859 as essentially being EIP-86 with a new opcode and a basic mempool guideline: don't use too much gas during the validation phase. EIP-2938, the last proposal we'll see in this first part, is in turn essentially the same as EIP-859 but with more mempool guidelines.5 The reason for this is the multi-invalidation problem.

To understand why new mempool guidelines are needed, let's look at transaction invalidation from scratch. Suppose a node has a pending transaction from an EOA in its mempool, and a new block arrives. What can happen in this block that invalidates that pending transaction? There are only two possibilities: the nonce of the sender changes, or its balance drops below the upfront cost of the transaction. In both cases, the culprit has to be a transaction from the same sender.

This is a good property to have: when a transaction gets included in a block it can only invalidate up to one pending transaction in the mempool. As we saw with the multi-invalidation problem, EIP-859 doesn't have that property. And at the root of this issue is the fact that the validity of an EIP-859 transaction can depend on anything that is observable from the EVM: the balance or code of any account, storage slots, the block timestamp, etc.

EIP-2938 attempts to fix this by adding more mempool guidelines, specifying what can happen before PAYGAS is executed. Here's the relevant section of the EIP:

post image
EIP-2938 mempool guidelines

As you can see, these guidelines are more arcane than EIP-859's simple "don't use too much gas before calling PAYGAS", because they have to make sure that the validity of a transaction can only be affected by a different transaction from the same wallet. Here I have to stress that these are not protocol rules. A block can have a transaction that uses any of these opcodes before PAYGAS is executed and it will be valid. It's just that most nodes would reject that transaction in the first place.

When EIP-2938 was introduced in 2020, it was considered a solid proposal, but it didn't end up being adopted. The reason was less about technical concerns than about engineering bandwidth: core devs were completely focused on the Merge. In that context, it was hard to prioritize an EIP that added a new transaction type, a new opcode with deep changes to the EVM, and a set of complex mempool rules. And so the goal of native account abstraction was put on hold, leaving the ecosystem to explore its own solutions.

Part 2: Outside the protocol

The greatest victory is that which requires no battle.

— Sun Tzu, as quoted by Yoav Weiss, January 2019

In Part 1 we explored a few proposals aiming for the moon: to get all the benefits of account abstraction from a single EIP. But not every AA-related issue is equally important. As Ethereum gained popularity, a frequent source of friction was the need for ETH to do almost anything. Try some app? You need ETH. Received some tokens and want to swap them? You need ETH.

From the beginning, this problem was recognized and people experimented with workarounds that didn't require waiting for protocol upgrades. The experience from these efforts, combined with the shelving of native AA efforts, eventually converged into ERC-4337, a project with the ambitious goal of getting as close to account abstraction as possible without changing the protocol.

Meta-transactions

To send a transaction in Ethereum you use your private key to sign a transaction object, proving you own the account and want a specific action performed from it. When a block builder receives this object, it validates its signature and executes the corresponding action from the signer's account.

This idea—signing data to prove to someone that you want something done on your behalf—can be used at the contract level too. An ERC20 contract can have a special function that receives signed data to perform a transfer. If that function is called with the proper arguments, the contract can do something analogous to what block builders do: recover the address from the signature, verify it has enough balance, and move the specified amount of tokens to the target address. And crucially, anyone can call that function, even if they weren't the ones who signed the data.

This is in essence the idea of meta-transactions: separating the actor that signs from the one that wraps that signed data in a transaction, the so-called relayer. The question is, why would anyone act as a relayer? Whoever does it has to spend ETH to send the transaction, and therefore must have an incentive to do it. In the token example, the recipient might be relaying the meta-transaction because they benefit from the underlying action (the token transfer). An application might also decide to sponsor the first interactions from verified users, letting them use the app without having to pay gas; in this case, the incentive is user acquisition.

Meta-transactions work and have been used in production to let users interact with apps without needing ETH.6 But they have several issues:

  • Meta-transactions depend on a relayer wrapping your signed data and sending it in a transaction. But how do you find and use a relayer? Depending on the design, there is a risk of ending up with a centralized system where only a couple of relayers are realistically available.7

  • A meta-transaction has a considerable gas overhead compared with a normal transaction.

  • Meta-transactions are app-centric, meaning that every token or app needs to individually implement support for them.

The last item is the worst one, because it means this approach simply won't scale. While some projects might do it if it makes sense for their goals, it's unreasonable to expect every single application out there to have support for meta-transactions.

ERC-4337

While the attempts to have native support for account abstraction were put on hold, the dream didn't really go away. Instead, most of the energy went to try to accomplish as much as possible without needing any protocol upgrades. These efforts resulted in ERC-43378, perhaps the most well-known of all AA proposals.

ERC-4337 is quite complex, so I won't go into detail on it (check the resources at the end for a great explainer). At the same time, it's impossible to properly understand the rest of the history of AA without at least some idea of how ERC-4337 works and some of the concepts it introduced, so let's do a quick overview.

The way I like to think about ERC-4337 is the following. We saw in the previous section that meta-transactions are cool, but they need every app to implement support for them. But what if smart wallets support meta-transactions? If you can sign data to execute arbitrary actions with your wallet, and if you can find a relayer that will wrap it in a transaction for you, then you get many of the benefits of account abstraction without having to change anything in the protocol.

This is, of course, easier said than done. There are several problems raised by this approach, and most of the complexity around ERC-4337 stems from those issues. For now, let's only mention two.

The first problem is that relayers need to know that the result of relaying an action will benefit them somehow. They can do this by auditing the code of every wallet they support, but this results in an "M x N" problem: each new relayer needs to support multiple wallets, and every new wallet generates work for every existing relayer. For this and other reasons, ERC-4337 uses a well-audited singleton contract called EntryPoint that coordinates relayers with wallets. This turns things into an "M + N" problem, because each new relayer or wallet only needs to implement compatibility with that contract.

The other problem is gas overhead. So far we've talked as if each meta-transaction corresponds to a single contract action, but it's more efficient to gather multiple actions and send them all together in a single transaction. That's what relayers do in ERC-4337, and that's the reason why they are called bundlers, which is the term we'll use from now on.

Alice, Bob, and ERC-4337

What would our Alice and Bob example look like if Bob used ERC-4337?

As with the EIP-86 example, Bob doesn't have a smart wallet or any ETH to deploy one. But just like before, this is not a problem as long as he can predict the address where his wallet will be, and by this point CREATE2 was already part of the protocol. We won't dive into how deployments work in ERC-4337, but the short version is that they can happen "on the fly" and are handled by the EntryPoint contract.

post image
Bob sends 0.1 ETH to Charlie using an ERC-4337 wallet

Bob figures out where his wallet will be deployed and shares this address with Alice. Once she transfers the ETH, Bob signs a user operation: a structured piece of data defined by ERC-4337 specifying the action that the wallet should perform. In our case, this user operation will indicate that 0.1 ETH should be transferred to Charlie, plus some fields specifying that the wallet should be deployed. This user operation is received by a bundler who sends it along with other user operations to the EntryPoint contract. When Bob's user operation is executed, the wallet is deployed and called, Charlie receives his 0.1 ETH, and the wallet pays the bundler.

Paymasters

There's one final concept introduced by ERC-4337 which deserves mention. In the previous example, we said that Bob's wallet pays the bundler for including his user operation in a transaction. If things always worked that way, then this aspect wouldn't be fundamentally different from how EOAs work: the entity initiating some action (the wallet in this case) is always the one that pays for its cost. ERC-4337 uncouples these two responsibilities with the concept of paymasters. A paymaster is an optional part of a user operation, and it represents an entity that is willing to pay for it.

To understand how a paymaster could be useful, imagine that Alice sends USDC to Bob instead of ETH. The wallet can't use it to pay the bundler for sending the user operation; only ETH can be used for that. But Bob can modify his user operation to include a paymaster that accepts tokens.

ERC-4337 also defines a "paymaster post op" callback, which is executed at the end to let paymasters perform final checks or do some cleanup work. The details don't matter, but the concept will be important later when we try to generalize the whole mechanism.

ERC-7562: Standardizing mempool rules

Everything we've explained about ERC-4337 shows that you can get many of the benefits of native account abstraction without having to change the protocol. The mechanism is in fact quite similar to the EIPs we explored in the first part. In those proposals, the smart wallet executes some validation logic and then pays the block builder; in ERC-4337, the wallet validates a user operation and pays a bundler. But this is the exact same setup that led to the multi-invalidation problem. Imagine the same example we saw before but in an ERC-4337 context: someone creates hundreds of user operations that transfer ETH to some account but that are only valid if the target's balance is zero. If a bundler includes multiple of those user operations, it will only be paid for the first of them to be executed.

ERC-4337 largely follows EIP-2938's approach here: add guidelines about which opcodes can be used during validation. These guidelines evolved over time, reflecting a more detailed understanding of the multi-invalidation problem and the trade-offs between preventing it and letting wallets do as much as they want during validation. In fact, the rules became so complex that they were moved to a different spec, ERC-7562.

But there is a big difference between mempool guidelines in a native AA proposal like EIP-2938 and an out-of-protocol one like ERC-4337. A node can know in detail what's happening in the EVM during the execution of a transaction, but a bundler is just a regular user for whom the execution is by default a black box. This is not an unsolvable problem: there are alternative JSON-RPC methods that can be used to simulate a transaction and get a detailed trace of its execution, which can then be used to check that guidelines are respected. But this adds to the overall complexity of the proposal.

ERC-4337 in the wild

ERC-4337 was deployed to production in 2023, and it has been maintained and upgraded since then. To be honest, it's unclear to me how much adoption it has had. The BundleBear website has some interesting usage stats, but it's hard to evaluate them because they are in absolute terms instead of, say, showing which percentage of the overall onchain activity goes through bundlers.

Still, what matters is that ERC-4337 worked and contributed to a much deeper understanding of the account abstraction problem. And its technical achievements are impressive: smart wallets that can be used with relative gas efficiency without needing an EOA, that can be deployed on the fly, and whose operations can be funded by third parties; all without needing changes to the protocol.

But ERC-4337 still has downsides:

  • Since EOAs are still needed for bundling, it can't be a long-term solution (nor does it claim to be one).

  • While bundling helps with gas efficiency, there's some unavoidable overhead that can only be eliminated through a native AA mechanism.

  • Because user operations are not first-class citizens like transactions, they won't benefit from future protocol features. For example, inclusion lists wouldn't prevent user operations from being censored.

For all these reasons, there continued to be a need for protocol changes, resulting in a second wave of native account abstraction proposals. We'll discuss them in the fourth and final part, but first we need to rewind once more and talk about a parallel attempt at dealing with the limitations of Ethereum accounts.

Part 3: Empowering EOAs

I am once again asking for your support of EIP-3074.

— lightclient, September 2022

At the beginning of this article, we listed some pain points caused by the lack of account abstraction. And in the previous part, we saw how some of these pain points could be addressed with two out-of-protocol approaches: app-centric support for meta-transactions, or smart wallets that can be used through ERC-4337. In both cases we are avoiding a direct use of EOAs due to their limitations. But what if we tried to fix the worst of those shortcomings instead? Why not empower EOAs to make Ethereum better now, even if they are going to go away in the long term?

There have been many proposals aiming to make EOAs more powerful, but here we'll only focus on two of them: EIP-3074 and its successor, EIP-7702.

EIP-3074: The proposal

By 2020, Ethereum usage had grown significantly. New tokens and protocols were constantly emerging and the legendary DeFi summer was around the corner. This surge in interest only made it more frustrating to need ETH for everything.

At the core of this problem was the fact that one could only initiate an action from an EOA by sending a transaction from it, and this always requires ETH. The proposals we've seen so far tried to circumvent EOAs entirely and rely on smart wallets instead. But an alternative solution is to add some other mechanism to make calls from EOAs.

To understand the mechanism that EIP-3074 proposes, let's recap the relationship between transactions and calls. A transaction is made of at least one top-level call, the one that comes from the EOA sending the transaction (even a plain ETH transfer is considered a call in this context). If the recipient is a contract, then this contract in turn could make multiple calls, which in turn could execute other contracts that make more calls, and so on. But notice that there is one and only one call coming from an EOA: the one at the beginning.

The essence of EIP-3074 is in allowing a contract to "impersonate" an EOA and make a call from it. Of course, the EOA has to authorize this somehow, and the way to do it is not that different from how meta-transactions work: the user signs some structured data with their private key to let some contract make calls from their address.

To be more specific: EIP-3074 adds two new opcodes, AUTH and AUTHCALL. The first one verifies a signature over some data specific to the EIP. If that signature is correct, then for the rest of the execution the contract is allowed to use AUTHCALL, which works pretty much as a normal call but where the sender is the EOA that signed the data.

Alice, Bob, and EIP-3074

EIP-3074 is about making EOAs more powerful, not about smart wallets, so we'll use a different scenario for our Alice and Bob example. Alice sends 100 USDC to an EOA that Bob generated, and he wants to send 10 of those USDC to Charlie. Bob's EOA is fresh and doesn't have any ETH. Normally this would mean that he can't do anything with it, but with EIP-3074 he can sign an authorization for a contract compatible with EIP-3074 (called an invoker in the EIP) that can transfer the tokens on his behalf.

As with meta-transactions, though, Bob still needs to find a relayer9 that will call the invoker with the data he signed. And, as always, the relayer needs an incentive to perform this work. In this example, the invoker might be written so that it sends part of the USDC to the relayer in addition to the 10 USDC it sends to Charlie.

post image
Bob transfers USDC to Charlie using EIP-3074

EIP-3074 questions

While EIP-3074 is a conceptually simple proposal, there are details that need to be clarified that can have a substantial impact on the end result. For example:

  • Can the authorization be used multiple times? One possible answer is to forbid it at the protocol level, for example by adding a nonce mechanism to authorizations. Alternatively, re-use could be allowed for maximal flexibility, in which case it is the invoker's responsibility to implement replay protection at the application layer.

  • Can authorizations be revoked? Suppose Bob signs and propagates an authorization but then regrets it. Can he invalidate it? How?

  • How can users know what the impact of signing a certain authorization will be? This is a UX problem for every scenario where users sign something, but it's especially important in EIP-3074 because a single bad signature could be used to completely drain an account.

These and many other questions meant that EIP-3074 was proposed and delayed many times. But in 2024, the proposal was finally accepted for inclusion in the Pectra upgrade.

Pushback

Maybe you remember there was some drama around EIP-3074. And as with any controversy, the account of what happened depends on who you ask. I'll briefly explain one version of the story (perhaps the most often repeated one) which I believe is broadly correct. But keep in mind that it might be missing a good deal of nuance.

After EIP-3074 was accepted as part of the Pectra upgrade, it started to receive some serious pushback, especially from the team working on ERC-4337. As we explained in Part 2, ERC-4337 went live in 2023 and there was already some talk of making it more useful by enshrining it into the protocol (of which more in the next part). A major complaint about EIP-3074 was that it wasn't aligned with that roadmap. For example:

  • EIP-3074 needs relayers to send authorizations. The work on decentralized bundlers by the ERC-4337 team couldn't be reused for this, so a new relayer system would be needed.

  • As in any meta-transaction scheme, relayers need an incentive to do their job: the resulting on-chain action has to benefit them somehow. Here we have the same problem we've seen a couple of times: as a relayer, you can run a simulation to check that you'll be paid, but there's no guarantee that this is what will actually happen when the transaction is finally included in a block. ERC-4337 solved this with the EntryPoint contract but, again, this solution couldn't be leveraged by EIP-3074.

  • By making EOAs more powerful, the EIP was going against the long-term goal of users migrating to smart wallets.10 And by adding opcodes that explicitly relied on ECDSA, the EIP was going against the long-term goal of deprecating non-quantum-resistant cryptography.

But the UX problems EIP-3074 was trying to address were real, and waiting for full AA (migration to smart wallets included) seemed unrealistic to many. The bottom line is that there were two groups of people in the community honestly trying to make the protocol better, but with different philosophies about what the best next move was.

EIP-7702

Things seemed to be at a stalemate, with an EIP that had technically been approved for inclusion receiving pushback that couldn't be ignored. But then, right before a crucial meeting to discuss the topic, Vitalik famously drafted a new, alternative EIP in 22 minutes. This was EIP-7702, and it covered most of the use cases the EIP-3074 authors cared about while being compatible with the long-term vision of the ERC-4337 team.

To understand how EIP-7702 works, remember that EOAs can be the targets of calls from other EOAs or contracts, but the only thing these calls can do is transfer ETH. If they have some data, it is ignored.

post image
tx/call to an EOA

EIP-7702 lets EOAs optionally change that behavior by delegating to a contract the handling of incoming calls. To do that, they have to sign an authorization specifying the address to which they want to delegate. These authorizations can be enabled on-chain by including them as a field of a new type of transaction.

Alice, Bob, and EIP-7702

In the USDC version of our Alice and Bob example, Bob has received 100 USDC in his EOA but he can't do anything with them because he has no ETH. With EIP-7702 he has a way out: he can sign an authorization to delegate how the EOA handles incoming calls. The target of the delegation can be any contract, but the most logical choice is a smart wallet implementation.

post image
Bob sends USDC to Charlie using EIP-7702. Here the relayer could be the ERC-4337 entry point, if the wallet implementation is compatible with it.

Once this happens, Bob's EOA is practically indistinguishable from a smart wallet. He still needs someone to call it on his behalf, but that's exactly the kind of problem that ERC-4337 is meant to help with, and what any native AA scheme will try to solve. This is one of the main reasons why EIP-7702 is considered more aligned with the AA roadmap than EIP-3074 was.

EIP-7702 was good enough to satisfy both sides of the debate. It was included in the Pectra upgrade as a replacement for EIP-3074 and is now live on mainnet and several L2 chains.

Part 4: The dream of native account abstraction

Even enshrined in-protocol account abstraction is still a massive "de-enshrinement" compared to the status quo.

— Vitalik Buterin, September 2023

As we discussed in Part 2, ERC-4337 has limitations that can only be solved by enshrining some of its features at the protocol level. In fact, in 2023, the very same year that ERC-4337 went live, a proposal was drafted with this goal. Since then, the conversation has evolved and culminated in EIP-8141 (Frame Transactions), a proposal that attempts to take all the lessons from this story and wrap them into a single AA proposal.

Enshrining ERC-4337

Remember how ERC-4337 works: a user signs a "user operation" which is later included in a transaction by a bundler and processed by the EntryPoint. This contract handles the logic related to checking that the operation is correct, executing the call, paying the bundler, etc. What we would like is a similar mechanism that's handled directly by the protocol. That is: user operations become transactions, the work of bundlers is done by block builders, and the logic coded in the EntryPoint contract becomes part of the rules of the chain.

The first two attempts at this were RIP-756011 (proposed in 2023) and EIP-7701 (proposed a year later and, yes, the EIP immediately before EIP-7702). There are many cosmetic differences between these two proposals, but they boil down to the same idea, so I'll just focus on EIP-7701 here.

EIP-7701 proposes adding a new transaction type with these fields:

[
  chain_id,
  nonce,
  sender, sender_validation_data,
  deployer, deployer_data,
  paymaster, paymaster_data,
  sender_execution_data,
  max_priority_fee_per_gas, max_fee_per_gas,
  sender_validation_gas, paymaster_validation_gas,
  sender_execution_gas, paymaster_post_op_gas,
  access_list,
  authorization_list
]

As you can see, most of these concepts come directly from ERC-4337. The idea is that the protocol itself does the work of the EntryPoint: it calls sender (presumably a smart wallet) with sender_validation_data to check if they indeed want to make that call, it validates the paymaster if present using paymaster_data, and so on.

How does a wallet or paymaster communicate whether they accept the transaction? In ERC-4337, this is done by implementing a certain interface that is called by the EntryPoint contract, but in EIP-7701 they have to communicate with the protocol itself. For this reason, the EIP adds two new opcodes, CURRENT_ROLE and ACCEPT_ROLE, which let contracts say "yes, I want to send this call" or "no, I don't want to pay for this transaction."

To be able to decide how to respond, wallets and paymasters need visibility into the whole transaction. In ERC-4337 the whole user operation object is always passed around, but in EIP-7701 the data in each call is only what is relevant to that phase of execution. To make up for this, the proposal also adds a new group of TXPARAM* opcodes that let a contract inspect other fields of the transaction. For example, a paymaster can use these opcodes to check who the sender is and the data with which it's called.

EIP-7701 fixes the main issues with ERC-4337: it doesn't need EOAs to work, it's more gas-efficient, and it benefits from any improvements to transactions. What it doesn't do, nor does it try to, is to fix the problems inherent to account abstraction, like the multi-invalidation problem. Mempool guidelines are still needed and, while the draft doesn't really mention them (perhaps because the EIP didn't make that much progress), they are clearly necessary.

In summary, EIP-7701 takes ERC-4337 and transforms each of its parts into a native equivalent. I don't know about you, but this feels wrong to me. ERC-4337 had to deal with the constraint of not being able to modify the protocol, and this limitation certainly influenced its design. Using its ideas verbatim seems like taking the shortest path to enshrinement. A better way to approach the problem is to ask: what is the minimal set of native features that enable the capabilities we want?

EIP-8141: Frame Transactions

The core problem of account abstraction is how to allow users to interact with smart wallets without needing an EOA. What's involved in those interactions? Here are the key steps, according to what we've learned so far:

  1. A smart wallet is created on the fly if needed.

  2. A smart wallet is asked if it wants to perform a certain action.

  3. A paymaster is asked if it wants to pay for the interaction.

  4. The action from the smart wallet is performed.

  5. The paymaster is allowed to do some work at the end.

These steps can be grouped into two categories. Items 2 and 3 are about querying a contract to check if it wants to play a certain role in the interaction (making a call in the case of the wallet, paying for it in the case of the paymaster). The rest of the items are about making a call: to a factory to deploy the wallet, to a paymaster for its post-op, and of course to the wallet itself to perform the main action.

But notice that this second category (making calls) can in turn be divided: when we perform the main action, we are interested in the wallet being the sender; when we deploy the contract or execute the paymaster post-op, on the other hand, we don't care who makes the call. Putting it all together, we can define three categories of steps in an AA interaction:

  • Asking a contract if it's willing to play a certain role

  • Making a call from a specific address

  • Making a call without caring about the sender

EIP-8141 proposes a new transaction type composed of frames, each belonging to one of those three categories. The EIP calls these categories "modes" and gives them the names VERIFY, SENDER, and DEFAULT.

And that's it, that's the core idea of frame transactions. There's a fair amount of extra detail, like new opcodes similar to the ones we saw for EIP-7701, but all that complexity is just a means to an end. And, of course, our old friend, the multi-invalidation problem, is alive and well. The answer here hasn't changed much: you need mempool guidelines, and EIP-8141 simply defers to the ERC-7562 rules originally defined by ERC-4337.

Alice, Bob, and EIP-8141

Let's revisit our Alice and Bob example one final time. As always, Bob hasn't interacted with Ethereum before, so the first thing he'll do is predict where his smart wallet will be deployed. Alice transfers ETH to that address and then Bob builds an EIP-8141 transaction with three frames:

  • One DEFAULT frame to deploy the wallet.

  • One VERIFY frame asking the newly created wallet to confirm whether it wants to perform the desired action (send ETH to Charlie) and pay for the whole transaction.

  • One SENDER frame corresponding to the actual call.

The relationship between the VERIFY and SENDER frames might need some clarification. The VERIFY frame receives some data, and it can also use the new opcodes to inspect the rest of the transaction. A simple wallet that just checks a signature could get it from the VERIFY data and assert that it indeed corresponds to the SENDER data, but more complex schemes are of course possible.

When a node receives this transaction, it will check that it's valid. In the context of EIP-8141, that means executing the VERIFY frames and checking both that they succeed and that they don't use any opcodes forbidden by ERC-7562.

I'm glossing over many details here; check the EIP for the full picture. Hopefully knowing why the proposal looks the way it does will help you navigate the spec.

Issues with frame transactions

If you've read this far and I've done a good enough job, then it should be easy to sympathize with the idea that frame transactions are the "endgame" for AA, given how simple the core concepts are. And yet, EIP-8141 was rejected as the headliner for the Hegota upgrade. It now lives in a weird limbo: it's a headliner-sized EIP that is "considered for inclusion" as a non-headliner. Whatever that means.

There are many reasons for this rejection. Here are some:

  • "It adds a lot of complexity to the mempool." People against frame transactions would prefer something less abstract but more mempool-friendly, like a hardcoded set of transaction sub-types. EIP-8141 proponents favor maximum power combined with "soft" mempool guidelines that can evolve over time.

  • "There's no need for so much generalization." Opponents say that there are 80/20 solutions that cover the most important use cases related to AA. Proponents argue that the protocol shouldn't be in charge of making these decisions and should let higher-level layers figure it out.

  • "It's not really quantum safe." Opponents say that frame transactions don't really solve the quantum issue because more things are needed, like new precompiles. EIP-8141 proponents agree but argue it's a necessary first step in the right direction.

In my opinion, some of these criticisms are more valid than others. The mempool complexity is real but, as we've seen time and again in this article, this problem seems inherent to having full account abstraction. Dealing with it by using hand-picked 80/20 solutions seems short-sighted to me and completely against Ethereum's philosophy. On the other hand, the fact that this is not a complete solution to the quantum threat is absolutely true. The problem here is that the EIP-8141 authors shot themselves in the foot by originally putting the post-quantum (PQ) motivation up front in the spec without any nuance (though this has been fixed since then).

That last point illustrates something I believe is not taken into account often enough. The AA or PQ problems don't need to be solved with the stroke of a single EIP, as much as that would help with the walkaway test. But, someone asks, what if Ethereum suddenly stops having upgrades after Hegota? Well... if that happens it's because we have much bigger issues than the lack of full account abstraction or quantum resistance. In this sense, I agree with the 80/20 proponents but from a different angle: we should add enough basic, neutral functionality (plus mempool guidelines) to help with the biggest current UX issues and pave the way for PQ, but nothing much beyond that. At the time of writing, though, the EIP is only growing bigger, adding new capabilities that feel like feature creep to me.

Still, at the end of the day these are details that can be worked out. There's no target date for the Hegota upgrade, but it will probably happen in early 2027, giving us plenty of time to iron out the spec. The debate today shouldn't be about those details; the question is whether the core of EIP-8141 is the right path to AA and whether it's urgent enough to be included as soon as possible. I think the answer to both questions is yes.

Epilogue: What's next?

At the time of writing, EIP-8141 is "considered for inclusion" for Hegota, meaning that it might be part of the upgrade but it's not guaranteed. So what's in store for account abstraction? Short term, I think there are three possible scenarios:

  • Frame transactions are included in Hegota in some form.

  • Frame transactions are not included, but some alternative AA mechanism is.

  • No AA features are added to Hegota.

I'm fairly sure the second option won't happen. While EIP-8141 didn't have the consensus needed to become a headliner, it has more than enough support to block any alternative. Unless, that is, someone pulls an EIP-7702 at the last minute. But I think this is unlikely: the underlying problem here is way more complex than the one EIP-3074 was trying to solve.

This leaves us with two possibilities: frame transactions are scheduled for Hegota, or account abstraction is postponed to the next upgrade. Personally, I don't think that postponing AA six months would be catastrophic; after all, it has been postponed for ten years. The problem is less about the delay than about what it signals: if EIP-8141 can't get consensus due to core philosophical issues rather than workable details, will six months change anything? Does the problem's wickedness, combined with the different priorities and philosophies of the community, mean that the dream of native account abstraction cannot be realized? Let's hope that's not the case.

Subscribe

Resources

  1. The mempool of a node is the set of transactions that could be mined at some point in the future. While each node technically has its own mempool, people often use “the mempool” to refer to the group of pending transactions in the network irrespective of a particular node.

  2. This is almost never necessary nowadays, thanks to EIP-1559.

  3. Suppose a node accepts replacement transactions without a minimum gas price difference. An attacker can send a transaction with gas price X, then a bumped one with gas price 1.001*X, then another one with 1.002*X, and so on. This makes the node validate thousands of transactions when only one of them is finally mined. Enforcing a minimum bump of 10% means that the gas price grows exponentially, making this kind of denial of service extremely expensive. For example, if an attacker sent 100 bumped transactions, the last of those would have to use a gas price 13,780 times higher than the first one.

  4. There is even more nuance about mempool guidelines. There are some rules at the network-level (not protocol-level) that are necessary to let nodes know which transactions they can propagate without being penalized in the p2p network. But explaining that in more detail would make this article even longer than it already is, so we’ll simplify and think about mempool rules as being up to each node.

  5. There are several other differences between EIP-859 and EIP-2938, but they aren’t very relevant. For example, EIP-2938 uses a new EIP-2718 transaction type, while EIP-859 and EIP-86, as you can guess by their numbers, don’t.

  6. A good example of how meta-transactions have been used is ERC-2612, an extension to the ERC20 spec. It adds a permit function that receives some signed data and sets an allowance from the signing account to some address. This is different from my simplified example, which transferred tokens directly, but the core idea is the same.

  7. For a noble but ultimately abandoned effort to solve the problem of centralization in the context of meta-transactions, you can read about Gas Stations Network. This article has a good explanation of how it worked.

  8. EIP means Ethereum Improvement Proposal. EIPs usually involve changes to the protocol itself. ERC means Ethereum Request for Comments and it's used for standards at the application layer, like the ERC20 token standard. Technically ERCs are a subset of EIPs, but I’m not sure anyone cares.

  9. EIP-3074 uses the word “sponsor” instead of relayer. We’ll keep calling them relayers anyway, to avoid introducing even more terminology.

  10. I strongly dislike the idea that EOAs shouldn't be improved just to protect the AA roadmap. Ethereum too often falls into the trap of only thinking long-term. As laudable as that might be, sometimes you need to balance it with short/medium term improvements.

  11. RIP means Rollup Improvement Proposal. RIPs are like EIPs, but aimed at L2s instead of the main chain.