Account abstraction at the speed of Boop
HappyChain
0x20Af
March 20th, 2025

Today, we introduce our new account abstraction standard for the EVM, called Boop.

It’s simple, it’s fast, and we see it as a superior alternative to ERC-4337 for low-fee chains like every Ethereum L2.


TL;DR

  • We're building Boop — a minimal standard and implementation for account abstraction that is low latency and simple.

  • Boop is a familiar alternative to ERC-4337.

  • ERC-4337's design & tooling lead to high-latency transactions.

  • ERC-4337 is very complex because of its design choices. This makes fixing the tooling hard as well.

  • Boop improves the experience of devs and users for most use cases today (app accounts, wallet accounts) on low-fee networks like every Ethereum L2.

  • Boop delivers a massive user and developer experience improvement for more demanding use cases, like fully-onchain games.

  • Boop will come with an account extension specification that fixes glaring flaws in ERC-7579 (extension for ERC-4337) and makes it actually possible to write extensions that work with multiple account implementations.

  • Our prototype is 3-4 times faster than common ERC-4337 solutions, with more low-hanging fruits to be harvested. HappyChain will eventually feature shreds, which will bring this to <500ms, anywhere on earth.


The goal of account abstraction (AA) is to make user accounts on the blockchain programmable and customizable. Two of the most common goals are gas sponsorship (allowing external entities to pay for the account's fees), and “session keys” or “automatic approvals” — enabling external entities to make some transactions on the account's behalf, in some specific situations.

Today, account abstraction is synonymous with ERC-4337 (”Account Abstraction Using Alt Mempool”). 4337 is a carefully designed standard that enables account abstraction without requiring change to the Ethereum protocol. However, while integrating 4337 into the Happy Wallet, we found that 4337 has significant downsides and limitations, which made it subpar for our use case (fully-onchain games). In particular, as implemented today it is very high-latency and its design is very complex.

4337's design leads to high latency between transaction initiation and transaction inclusion onchain. We currently regularly experience > 6s latency on a chain with 2s blocktime! Most of that is to blame on the tooling around 4337, but even the theoretical minimum time is higher than where we want it to be (see Appendix A for a thorough analysis, including sequence diagrams that shed some light on the situation).

4337 is also very complex, and so is the tooling around it. This makes it hard to jump in and fix issues when we encounter them (which is often). It also makes it hard to “fork” the standard into something more amenable to low latency.

The “4337 complexity” claim has been thrown out a lot on Twitter and other online forums, but it is generally unsubstantiated. Why is it complex? Is this complexity essential (does it buy us something) or accidental? What design goals or design choice create the complexity?

We have buried ourselves in the 4337 trenches, and we have answers! Keep reading to find out.

Our understanding of the design space led to the decision to create a new standard (and all the tools) around it. It's called Boop 👉🐈

Boop follows the general 4337 architecture (there is an entrypoint, accounts, paymaster, and an entity that relays transactions to the chain). However, it differs significantly in the details. We'll walk over the design in this article as well.

Table of Contents

  • ERC-4337 design trade-offs

  • Boop design trade-offs

  • How Boop works ← skip here if you just want to see what we built

ERC-4337 design trade-offs

The 4337 design goal

ERC-4337 is designed with interoperability in mind. You have roughly four players in the system: bundlers, account implementation, paymasters, and end users. The fragile equilibrium that 4337 tries to solve for is that any combination of those can interact, and that nobody gets shafted by the other parties in the process.

Additionally, 4337 maximally optimizes for gas consumption whenever possible (but note that AA transactions aka user operations or “userOps” still incur significant gas overhead). This is because its first customer is the Ethereum blockchain itself, where gas is expensive.

This is a worthy goal, and if you accept these constraints, then I think 4337 is an excellent design. But as stated earlier, this comes at the cost of high latency and significant complexity.

We believe that for most use cases today (including our gaming use case, which is of course our north star), better trade-offs are possible.

Alt mempool & bundling considered harmful

The alternative mempool is part of the title of the EIP, and drives a lot of the downsides of 4337.

Why an alt mempool?

  1. Interoperability: any userOp sent by a user with its selection of account and paymaster could be serviced by any bundler.

  2. Gas efficiency: In 4337, the relayer that disburses the transaction fees (to be refunded by the sender or by a paymaster) is called “bundler”. This is because they bundle multiple userOps together to lower the gas fee per userOp, by distributing the 21k gas EVM transaction base cost, as well as the cost to load many AA contracts (EntryPoint, paymasters, staking…) amongst all userOps.

That comes at a cost:

  1. Latency: 4337 calls for all bundlers to simulate transactions when they enter the mempool, and then to simulate the bundles before submitting. If any userOp fails, the bundler must remove it, and retry the simulation. In the worst case, a bundle may have to be simulated many times. These are not simple simulations either — they need to leverage tracing to verify complex validity properties for every userOp. But the worst part of the latency here actually comes from having to wait for other userOps to arrive before your userOp can be submitted.

  2. Complexity: A bundler is vulnerable to both offchain griefing (just like any other web service, people can spam him with busywork), and onchain griefing: a userOp could simulate just fine but then revert onchain, leading to a loss of the gas fees incurred by the bundler. 4337 (and its companion ERC-7562) try hard to protect against it with a slew of validation rules.

    We took a hard look at this — the ERC-4337 design is sound but cannot prevent all forms of griefing. However, it successfully makes what griefing can be done economically stupid.

    However, in a high-throughput/low-fee environment, most of the rules are unnecessary. Instead the bulk of the protection is carried by the “staking” mechanism where account factories and paymasters have locked some capital for a certain duration. This mechanism could live outside of the core protocol, at the discretion of the bundlers — which is our chosen approach in Boop. See Appendix B for an in-depth analysis.

Why have an alt mempool at all? Blockchains already have perfectly functional mempools. It's only required because of bundling and its gas efficiency gains. But on L2 chains, where gas is much less of a concern, you could just do away with it entirely, and relay a single userOp at a time.

“Censorship resistance” is sometimes an argument for the mempool, but it's a strawman when any honest actor can spin up a bundler and end censorship. If the tools were a little bit more mature, it would also be easy for an end-user to do this directly.

The 4337 mempool is seemingly live on some mainnets, but you'd be hard-pressed to find details on it besides some announcement posts. I do think it's fair to say that it's mostly unused today.

And that's because…

You don't need that much trustlessness

Today, users of account abstraction are mostly apps that want to provide gas sponsorship for their users, in a dedicated app account.

But the truth is that if you have the technical capability to run a paymaster service, you can also run a bundler. In practice, apps look for a one-stop-shop solution that can provide the bundler and the paymaster, and deploy smart accounts on behalf of users. This is the business model of Pimlico, Biconomy, and many many others. This means the bundler, account and paymaster can trust each other!

This is only the first stage of account abstraction. Another use case is wallets that want to relay transactions for their users. This includes HappyChain, Ambire, and Candide. These will usually impose usage of their own accounts (it's actually hard to make things work across accounts, more on this shortly). Multi-paymaster support is desirable, but it's usually easy to have a whitelisting process for those, or to use staking to minimize the downside (as ultimately 4337 does as well).

Ultimately trustlessness sits at the wrong level of the stack in 4337. It is best implemented on top of the base protocol, as a set of measures that the bundler will take to avoid griefing. These are necessarily heuristic in nature (i.e. 100% protection is impossible), and can always be downgraded to whitelisting in most cases. Doing so allows removing a lot of tedious logic from the contracts, and simplifies the implementation of simple trustful bundlers (covering today's use cases).

The only reason trustlessness is embedded so deeply in 4337 is because you need it to make the alt mempool work.

Native AA

4337 was designed as not requiring protocol changes. Now the long-term vision is to have protocol-level AA in Ethereum, as in fact some blockchains already have.

One direction being explored is to enshrine 4337 at the protocol level (RIP-7560). I have spent less time thinking about this, but at first glance it looks like a terrible idea, enshrining many 4337 concepts when they are no longer required. Bundling is perhaps the best example: if you work at the protocol level, you can eliminate the gas overhead, and thus bundling is no longer required (you still need a relayer to pay the fees prior to a refund, though this role can be easily fulfilled by the user himself or by the paymaster).

Balance fragmentation

One of the tools in the arsenal of 4337 to combat griefing is staking. Staking serves two roles:

  1. Fees refunded to bundlers are not sent directly, but instead deducted from a staking balance. Stake withdrawal is on a timelock, which prevents accounts and paymaster from withdrawing payment at the last second.

  2. Bundlers can require a minimum amount staked and a minimum staking time before trusting accounts, paymaster, and account factories. This prevents griefing from constantly redeploying these contracts to sybil attack a bundler.

We think (2) makes a lot of sense — and should most likely be implemented on top of the core protocol as a mechanism for bundlers to protect themselves, but (1) is unacceptable for accounts.

One of our motivations in the design of the Happy Wallet was to avoid the existing fragmentation where each app would get its own account, with significant difficulties in managing these and moving funds across them. Staking for payment reintroduces this, where you now need to explicitly top up a separate “gas tank” to make transactions directly.

Granted, this is not much of a concern for HappyChain where we expect sponsorship, including from HappyChain directly to cover most user actions. But as we conceive of Boop as a much more broadly applicable standard, this seems like a retrograde step in user experience.

Specification uncertainty

While we consider ERC-4337 to be well-designed, we can't say the same about ERC-7579, which standardizes 4337 account extensions. These extensions enable to change how the account validates or executes userOps, change the operation of their fallback functions, and define hooks.

We're sure the authors had the best intentions in leaving the spec open-ended, but the end result is that it is incomplete, and extensions in the wild can be incompatible.

7579 only specifies how the modules are installed, uninstalled, and detected. It does not however say anything about how the modules are used or triggered, nor the interface that the modules should adopt!

Let's consider the most relevant example: defining a validator extension that can support automatic approvals (session keys). These are scoped to very precise circumstances, and cannot be used to validate all userOps. The userOp must then indicate it wishes to use the custom validator. This is not standardized, so some implementations encode the validator address in the nonce, while some append it to the signature field.

Consider now an extension that needs access to extra data to perform its duties. 4337 provides no extension field for this purpose and the data must thus be encoded … somewhere. Usually in the signature or calldata fields. Since 7579 does not specify module interfaces, it is up to the contract to know how its extensions work, what data they expect, and how to transmit that to them.

The end result of this is that extensions end up being account-specific, conforming to the interface expected and supported by a specific account implementation.

This is in complete contradiction with the rationale for using ERC-7579 (now the dominant standard) over the superior (imho) ERC-6900 (though the previous link has some fair concerns about 6900 too):

ERC-7579 is a minimal interface that solves one problem only: modules should be compatible across smart accounts.

(We'll also point out the irony of ZeroDev pushing for ERC-7579 and compatibility, when their SDK does not actually enable using 7579 directly, but instead forces you to use their own unstandardized & cumbersome custom layer built on top of 7579. Oops.)

Boop has a much better solution here, which properly specifies the behaviour and interface of extensions while being … simple. It also makes it simple to pass data for various extensions in a non-conflicted way by having an extraData field structured like a dictionary (addressing some of the concerns about ERC-6900).

Besides extensions, 4337 also has a few specification fails that we correct. For instance, the destination of the call made by a user operation is not explicitly specified, and is traditionally encoded in the calldata field — but this is not standard and can thus not be strictly relied on.

Boop design trade-offs

I hope to have convinced you that 4337 has issues, at least for specific use cases. If you want to do DeFi on Ethereum with a smart account, it might still be a very good solution! Though in my opinion, this is not the exciting future that account abstraction unlocks.

Let's now have a look at what Boop does differently.

HappyChain is an L2 chain for fully-onchain games. Gaming transactions require extremely low latency. There are a lot of parts in making that happen, but a crucial first step in the design is making sure the transactions hit the sequencer as fast as possible. This is the main reason 4337 did not work for us.

In brief, our design goals are:

  • Low latency between transaction initiation and transaction inclusion

  • Simplicity in the design, especially keeping the core protocol minimal

  • Hackability / forkability

To achieve these, we are willing to make some concessions:

  • The core protocol will only have very minimal protections against griefing, requiring either trust or protection at a higher level (e.g. with staking).

  • Get rid of bundling, meaning a loss of gas efficiency. HappyChain will be a superscale chain, leveraging technology from RiseChain and Celestia. Since we control the nodes, we have the ability to play with gas policies, as well as the option to enshrine account abstraction at the chain level down the line.

Let's expand on some of these points.

We don't care about trustlessness so much — HappyChain will impose its own (customizable!) accounts and will run its own submitter (our relayer, like a bundler without the bundling). We have our own paymaster contract that we trust, and we will introduce a mechanism to trust third-party paymasters. The two most promising mechanisms here are staking (just like 4337) and paymasters deployed from a trusted source — we want to offer a framework to easily and automatically deploy paymasters with customizable policies, and deploy them on behalf of projects, ensuring us that the paymaster is safe by construction.

We care about simplicity — because we're writing software and have to maintain it. We want to push as much as possible outside of the core protocol. This includes griefing prevention and the ability to trust random third parties.

We also want to optimize for hacking. Because we don't do bundling, there is no need for everyone to use the same EntryPoint contract (accounts can be set up to support multiple entry points). Want to customize something? Fork the code and off to the races! So keeping the code simple and understandable is a major design goal.

Note that while trustlessness is optional (but achievable), Boop does enable compatibility to all spec-compliant implementations. This is a notable improvement in the case of account extensions.

How Boop works

Despite its simplicity, Boop affords a good deal of flexibility. Let's explore some typical flows.

Paying Submitter

Let's start with a simple example where a user wants to send a transaction, paid for by the submitter. This is the flow implemented in v1 of the Happy Wallet for transactions sponsored by HappyChain.

The user (or in reality, the wallet) will call a single method (boop_execute) on the submitter.

No other calls, not even blockchain RPC calls, are required!

The submitter then simulates the transaction to get the gas limits, and queries the chain for the proper gas price. It then submits the transaction onchain to the entry point contract.

This magic works because we allow the user to sign a boop without gas values whenever he is not the fee payer (the submitter or the paymaster contract (see later) can validate the gas values).

Gas estimation is worth a digression. Just like 4337, Boop needs more than just the gas limit of the submitter transaction. It also needs a gas limit for the call made by the account. We can either trust or monitor accounts & paymaster, so we don't require gas limits for them as 4337 does, but the called contract could be anything, and thus we need a gas limit for protection there. To estimate those two limits, the submitters make an eth_call to simulate the submit run, but specifies 0x0...0 as the sender. This turns the entrypoint, account, and paymaster into simulation mode, adopting relaxed validation rules. The call returns a struct that not only includes the estimated gas values, but also data to help debug failures, like a code indicating the cause of failure (validation? execution? payment? unexpected revert?) and the revert data if applicable.

(This compared favorably with ERC-4337 which requires a completely different code path for gas estimations.)

The boop is packed in a super simple format (basically just concatenating the fields), which leads to > 200 bytes of data savings (yes, Solidity's ABI encoding really sucks). The size of the fixed-size part of a boop's data is also around 200 bytes, which means that this makes gas token transfers 50% smaller! It is then decoded once at the start into an easy-to-manipulate struct.

The entry point logic is a straightforward affair, which decodes the boop, calls validate on the account, and if that succeeds, execute. Since the submitter is the payer, no need for refunds.

For comparison, let's see how ERC-4337 handles the same scenario:

The chart clearly illustrates Boop's advantages in simplicity and latency: one roundtrip originating from the user for Boop vs 5 for 4337, and 3 roundtrips originating from the bundler for Boop vs at least 4 for 4337.

Multiple notes to be made regarding the comparison (feel free to skip if you don't care about technicalities):

  • We didn't include the account deployment pathways, as Boop leaves account deployment outside of the core spec (if account deployment at the time of the first boop is desired, that can be achieved with a multicall, or a preceding call from the submitter).

  • In both cases (Boop & 4337), the entry point checks the payment, which is not represented in either diagram.

  • Paymaster service capabilities are standardized in ERC-7677.

  • In theory, it is possible to skip the paymaster service and rely purely on the onchain paymaster as Boop does. In practice, all service providers today route sponsorship requests through their paymaster service. 4337 service providers do not provide a combined endpoint that performs sponsorship and submission at the same time (which is what Boop's paying submitter does, and without incurring any onchain gas overhead).

  • The call to pm_getPaymasterStubData could be skipped, but only if the format of the paymasterData is known and the stub data is made such that an overestimation of the submitter fee will be provided. In practice, this is not done in any SDK or frontend library.

External Paymasters

What about third-party paymasters? Here's the new flow with the difference highlighted in blue.

The difference is trivial: after execution, the entry points calls payout on the paymaster contract with the boop (which can optionally include paymaster-specific data), and the actual amount of gas spent. The paymaster contract validates that it is willing to pay for the Boop and that the requested payment (consumed gas + submitter fee) is reasonable, then transfers the payment to the entry point. The entry point then validates that the payment has been made, then returns (or reverts if not).

Self (account) payment

What if the account wants to pay itself? It's trivial: the account just needs to implement the paymaster interface and specify itself as the paymaster!

There is one snag though: in this case, the Boop spec enforces that the user must sign over the gas values. This is because we expect account payment logic to be very simple — basically just a transfer: the entry point is trusted and so we know the boop passed validation. But if we don't sign the gas values, the submitter (which is to say, anyone) could steal funds from the account by asking a massive submitter fee, or they could be griefed by applications consuming an undue amount of gas. Paymaster will generally either target a specific use case or have bespoke accounting logic, but accounts are much more open-ended.

Boops in the wild

Besides purely submitting transactions (or boops in this case), the infrastructure and ecosystem also matter. By nature of being very lightweight, boops are easy to work with.

Here's a breakdown of the structure of a Boop (fields have been re-ordered for logical grouping):

struct Boop {
    address account;        // sender
    address dest;           // destination of target call
    uint256 value;          // gas tokens in wei to transfer to dest
    bytes callData;         // calldata for target call
    uint192 nonceTrack;     // nonce namespace (enables parallel tx tracks)
    uint64 nonceValue;      // nonce within track

    // Gas and Fees
    uint32 gasLimit;        // for submitter tx
    uint32 executeGasLimit; // for IHappyAccount.execute
    uint256 maxFeePerGas;   // wei max fee (basefee + tip)
    int256 submitterFee;    // flat wei submitter fee

    // Payment and Validation
    bytes validatorData;    // validation-specific data (e.g. signature)
    address paymaster;      // fee payer (IHappyPaymaster
                            // or 0x0 for submitter
    bytes paymasterData;    // paymaster-specific data
    bytes extraData;        // for extensions
}

The submitter allows monitoring boop status, exposing the following endpoints (in addition to the previously discussed boop_execute and boop_estimateGas):

  • boop_submit(boop) — equivalent to submitting a transaction, but not waiting for the receipt

  • boop_receipt(hash, timeout) — waits for the receipt then returns it, a timeout can be passed, which allows this function to be used for polling if set to 0

  • boop_state(hash) — returns the current state of a boop (e.g. included — along with the receipt, failed — along with the failure reason, submitted to the chain, pending processing, …).

  • boop_pending(address) — returns all pending boops (not yet included onchain) that the submitter is tracking.

  • boop_cancel(hash) — cancels the given boop if it has not yet been submitted onchain.

What about explorer support? The good news is that by default the information you get from a submitter transaction is almost as good as the information you get about an userOp on BlockScout, which is the most widely used interface for this today!The only missing piece of data is the boop sender, but it will be surfaced via the Boop event that logs the entirety of the Boop data structure. And unlike 4337, you’ll see the target of the call too!

Things could definitely improve there, but by making zero effort, we're pretty much as good as the 4337 state-of-the-art!

In general, Boops are easy to work with programmatically — you can filter transactions via the Boop event then decode the packed Boop passed to the event using our libraries.

Account Extensions

Boop's extension story is extremely simple. We’ll focus on validator extensions, but execution extensions follow the same pattern.

An account supporting extensions implements the following interface:

interface IExtendedAccount {
    enum ExtensionType {
        Validator,
        Executor
    }

    // Reverts if the extension is already present.
    function addExtension(address ext, ExtensionType type) external;

    // Reverts if the validator is not present.
    function removeExtension(address ext, ExtensionType type) external;

    function hasExtension(address ext, ExtensionType type) 
        external returns (bool);
}

And the validators must implement the following interface:

interface ICustomBoopValidator {
    // Same interface as IBoopAccount.validate
    function validate(Boop memory boop) external returns(bytes4);
}

To signal that a custom validator is used, the account must parse the extraData field. This field is structured as a packed list of (key, length, value) triplets, where both the key and the length are encoded on three bytes. We supply a library function to easily extract the value of a given key.

The key 1 is reserved for custom validator addresses, and if present, the account must delegate its validation to the custom validator.

If you want to compose validators (e.g. require multiple validators to assent, or require a single or even a quorum of validators to assent), this can be implemented as a custom composition validator.

Furthermore, accounts are allowed to inline common validators (which we'll do for session keys), as long as the spec is respected, with the exception that these validators are not required to be uninstallable.

That's pretty much the whole validator extension spec!(Okay, there are also events emitted on install/uninstall.)

What about EIP-7702?

There's a misleading narrative out there that somehow 7702 changes things with respect to account abstraction standards. It doesn't.

The only thing EIP-7702 does is it lets EOA become contracts (potentially temporarily). The main use case is to let them become smart accounts, which is great!

Both ERC-4337 and Boop are fully compatible with EIP-7702.

It's only the start

We'll release the Boop spec and open-source code soon (hopefully within two weeks). If you want an early sneak peek, my DMs are open.

The code is vastly simpler than 4337's. For instance, the entry point contract is only ~300 lines of code, all in a single easy-to-parse function, including interface documentation & the simulation code path vs > 1000 lines for the 4337 entry point, which includes neither and depends on many helpers.

Boop is only the start of the latency battle. While our initial testnet will have 2s block time, our subsequent testnet will integrate the tech from Rise Labs and enable shreds — ultra-fast (< 10ms) per-transaction confirmations. Waiting for blocks no longer required!

We'll also make an indexer available that listens to sequencer-emitted shreds. In a game context, this means every player can learn of other players’ actions right after the sequencer confirms it. If the sequencer, submitter, and indexer are colocated in the same data center, this brings to quasi-parity with web2 gaming latency!

Besides this, we also want to build better simulation capabilities. In games, the success of a transaction often depends on the preceding transactions by the same player and other players in the same game. The inability to simulate transactions in that context can cause the simulation to fail (or to report an inaccurate gas limit), which can cause “pipeline stalls” where we have to wait for the preceding transactions to be included on-chain to be able to properly simulate the next one. This is an issue if we want to enable games where a user can submit many transactions over a few seconds!

As always, our goal remains the same: make builders’ lives easier. Just submit the transaction and don't worry about it! 🤠

Subscribe to HappyChain
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.
Arweave Transaction
lwKKHA02fwkzFzo…oDmoia8Po_nxDxM
Author Address
0x20Af38e22e1722F…c96c00EECd566b2
Content Digest
x-u881uWh93iVHC…Dopvyb4NoSy-Tos
More from HappyChain
View All

Skeleton

Skeleton

Skeleton

0 Collectors