This article is compiled from a presentation by Alfred from imToken Labs at ETHTaipei
Hello, I am Alfred. Today I will give a talk about the issues of 4337 on L2s.
I currently work at imToken Labs as a blockchain developer. You can find more information on my persional website. Or discuss anything with me through the twitter.
What Causes the Difference?
We often subconsciously assume that the implementation of ERC-4337 should be the same across different EVM-compatible Layer 2s. However, due to protocol differences and certain synchronization challenges, implementing 4337 on L2s is not the same as on Ethereum.
- Protocol Differences: Variations in EIPs/RIPs, hard forks, opcodes, gas structures, and fee mechanisms.
- Asynchronous Nature: you cannot simply send one transaction and expect it to affect accounts across all chains simultaneously.
Definition of a Multi-Chain Account
In the past, this distinction was rarely discussed for EOAs. Even though assets were not inherently synchronized, EOAs shared the same address and private key across all chains, meaning the authorization mechanism was identical.
However, contract/AA accounts operate as smart contract wallets. This means that across different chains, the account addresses, authorization methods, and even ownership states may differ, leading to more than just asset fragmentation.
Authorization Issues
The Challenge of Asynchronicity
Ownership Issue
Ensuring Cross-Chain Consistency
Let’s take the discussion of account further. If we change account ownership (in brief, modifying a variable) on Chain X, how do we ensure that Chain Y updates as well?
What happens if transferOwnership()
succeeds on one chain but not another? This creates a desynchronization risk in ownership states, leading to several challenges:
- Changing signature verification mechanisms (e.g., switching to Passkeys or multisig) requires updates on all chains.
- If a user's control key is lost, how can we recover their account across all chains?
These questions highlight the fundamental challenge of managing AA accounts in a multi-chain environment, requiring new solutions for synchronization, security, and key recovery.
Solutions
Is there any protocol that can automate multi-chain authorization updates or make management easier?
Multi-Chain Signing: Signing Once, Executing Across Chains
One approach is Multi-Chain Signing, where users sign a single userOp
once and broadcast it across multiple chains, eliminating the need to sign individually for each chain. There are currently two main implementation methods:
-
Coinbase’s Approach
- Directly modifies the
userOp
signature. - Requires a consistent nonce strategy (ensuring nonce increments correctly across all chains).
- Signature does not include Chain ID, allowing the same signature to work across chains.
- This method is limited to specific operations (e.g., ownership updates, contract upgrades), and all chains must succeed together—otherwise, nonce desynchronization could occur.
- Directly modifies the
-
Zerodev’s Merkle Tree Approach
- Groups different chain-specific operations into a Merkle Tree.
- Combines multiple
userOpHash
values from different chains into a single Merkle Root. - The user signs just once on the Merkle Root, allowing execution across multiple chains.
Both methods share a common trait: they bypass the Chain ID restriction during signature verification.
- Coinbase’s approach ignores Chain ID altogether.
- Zerodev’s approach includes all possible Chain IDs within the signed Merkle Root.
Wallet Provider Support
Another solution is to rely on wallet providers to manage multi-chain authorization.
-
Web-based wallets need a database to track the user's current authentication methods per chain:
- X Chain uses PassKeyX
- B Chain uses PassKeyB
- C Chain uses ECDSA
- This ensures a seamless and secure user experience—users shouldn't have to guess or check an explorer to determine which signature mechanism is in use.
Additionally, wallets should provide:
- Alerts when different chains have inconsistent authorization mechanisms.
- A one-click sync function to unify authorization methods across all chains.
By implementing these solutions, we can reduce complexity and improve the usability of multi-chain AA accounts.
Address Issues
Why It Matters
A moment of silence for Wintermute
Having the same address across chains is crucial because it can lead to significant issues:
-
User Experience Degradation
- From an EOA perspective, users expect their account address to be the same across all EVM-compatible chains.
-
Asset Transfer Risks
- A sender might unknowingly send funds to a recipient's address on a chain where they don’t have control, leading to potential loss of funds.
-
Cross-Chain Bridges Assumptions
- Some bridges assume the same address exists on both chains when transferring assets. If users aren’t careful, they may bridge assets to an address they don’t control on another chain.
-
ENS Name Risks
- Users who register their ENS name on different EVM-compatible chains using the same address might face unintended consequences.
- ENS assumes that your registered address matches your Ethereum Mainnet address, which may not always be the case on L2s.
Caused by Hardforks
Differences in contract addresses across chains are primarily due to protocol differences, including:
- Whether an L2 follows L1 hard forks in a timely manner.
- Whether an L2 supports all L1 precompiled contracts and EIPs/RIPs.
-
Unique design choices in L2 protocols, such as:
- zkSync using a different encoding method for
CREATE/CREATE2
opcodes, affecting address derivation. - Ethereum Mainnet not supporting certain L2-exclusive features, such as RIP-7212 P256 precompiled contract.
- zkSync using a different encoding method for
Why Wasn’t This an Issue Before?
Past hard forks, like The Merge (EIP-4399: DIFFICULTY
becomes PREVRANDAO
), only affected a small subset of contracts, so they were not widely discussed.
However, newer changes like PUSH0
impact almost all contracts—making address inconsistency a critical issue today.
And The rise of ERC-4337 and smart contract wallets over the past few years has amplified the importance of consistent contract addresses across chains.
Some Past and Future Hard Fork Impacts:
Shanghai (Shapella) - PUSH0
With the Shanghai upgrade introducing the new PUSH0
opcode, certain chains (such as Arbitrum and Optimism) initially did not support this opcode. Therefore, we need to explicitly specify the EVM version and Solidity compiler version to maintain control over addresses.
Our goal is to ensure that the Proxy Factory address remains unchanged. To achieve this, we use: --evm-version paris
and --use 0.8.19
. This ensures that the PUSH0
opcode is not generated during compilation.
Cancun (Dencun) - TSTORE/TLOAD
The impact of TSTORE
in Cancun may not be as significant as PUSH0
, because currently, the Solidity compiler does not allow transient storage to be used as a data storage location in high-level Solidity code.
At present, data stored in this location can only be manipulated through inline assembly using the TSTORE
and TLOAD
opcodes.
In the future, once EOF (EVM Object Format) goes live, we will likely encounter similar issues.
Solution
How can we ensure that the same contract produces identical results across different chains? The simple answer: we must fix both the EVM version and the Solidity version.
However, as more hard forks introduce new opcodes, maintaining compatibility will become increasingly complex.
For now, we can focus on PUSH0
, comparing 0.8.20 vs. 0.8.19 and Shanghai vs. Paris. But once Cancun introduces TSTORE
/TLOAD
, developers may need the ability to enable PUSH0
but not TSTORE
, or disable both. The number of options and decision-making criteria will only grow over time.
Open questions: how do we make these choices? Are there any selection methods or decision-making guidelines?
Whatever, some key takeaways should be kept in our minds:
- Do not assume that an account will have the same address across all chains. This should be a fundamental mindset shift.
- Utilizing ENS can significantly enhance the user experience by providing a more consistent and user-friendly approach to addressing.
Fee (PVG) Issues
For wallet providers, calculating pre-verification gas on L2 is a challenge because it involves both L2 and L1 data costs, making the setup of PVG difficult. Next, I will explain how to calculate PVG.
Recap the PVG
The behavior of the Bundler sending userOp to the Entry Point is a regular transaction (a.k.a bundleTransaction), and it is a superset of the Verification Phase and Execution Phase. Specifically, some gas for this action is not defined under the VerificationGasLimit
and CallGasLimit
.
In other words, PreVerificationGas
covers all the gas costs that "cannot be checked via changes in gasleft()
on-chain." These gas costs are paid by the Bundler.
In the image from Tenderly gas profile, you can see how much gas doesn't include within the
_validatePrepayment()
(Verification Phase) and_executeUserOp()
(Execution Phase), it should be covered by bundler.
PVG includes the following:
-
The cost of processing a userOp in the EntryPoint contract (bundleTransaction),
- e.g., 21,000 stipend, as well as additional fees for other operations used in
handleOps
(expenses outside the Verification Phase or Execution Phase loops). - e.g.,
calldata
costs (memory usage/copy in thehandleOps
function), for example, as the calldata to be processed increases (e.g., from 1 ERC-20 transfer call to 30 calls), Memory Expansion (used byMLOAD
/MSTORE
like(perWord + 31) / 32
) and Intrinsic Gas will also rise.
- e.g., 21,000 stipend, as well as additional fees for other operations used in
-
Additional expenses:
- A fee for prioritizing the bundling of the userOp from the mempool (subsidy).
- A fee for Bundlers to prioritize their bundleTx with the Block Builder (passed on to the userOp’s sender).
- Transaction fees on Layer 2 must account for Layer 1 security costs (this is the focus of the current discussion).
- …
The focus of this discussion is not about how much profit Bundlers aim to make. Instead, we will focus on how to reasonably calculate PVG on Layer 2.
How to get PVG on L2
TotalCostForBundleTx = L1Cost + L2Cost
The total transaction cost that the Bundler needs to pay when submitting a bundleTx on Layer 2 must include both Layer 1 and Layer 2 costs.
- L1 Cost refers to the security costs of this transaction on Layer 1, which include the Rollup Contract processing fees and calldata storage fees.
- L2 Cost refers to the processing costs of this transaction on Layer 2 (the bundleTx cost).
TotalCostForBundleTx = L1Cost + L2Cost
= GasPrice * TotalGasUsed
The total transaction cost paid on Layer 2 is expressed as GasPrice
and TotalGasUsed
. Since the transaction occurs on Layer 2, the GasPrice
refers specifically to the Gas Price on Layer 2 (e.g., Optimism).
TotalGasUsed = L2GasUsed + L1SecurityFee
= L2GasUsed + L1CalldataCost / L2GasPrice
TotalGasUsed = L2GasUsed + (L1GasPrice * L1CalldataSize) / L2GasPrice
Since we need to account for the L1 cost in the TotalGasUsed
, the amount of gas required to cover the L1 cost on L2 is defined as L1CalldataCost / L2GasPrice
, which represents the L1SecurityFee
in terms of how much gas is consumed from the L2 perspective.
Through the earlier algebraic calculations, we can derive the "maximum total gas usage" for a userOp on L2, which is: TotalGasUsed = L2GasUsed + (L1GasPrice * L1CalldataSize) / L2GasPrice
.
TotalGasUsed = (VerificationGasLimit + CallGasLimit) + (PVG)
= L2GasUsed + L1SecurityFee
= (VerificationGasLimit + CallGasLimit + PVGForL2) + (PVGForL1)
PVG = PVGForL2 + PVGForL1
= PVGForL2 + L1SecurityFee
= PVGForL2 + L1CalldataCost / L2GasPrice
= PVGForL2 + (L1CalldataPrice * L1CalldataSize) / L2GasPrice
The total PVG on L2 is composed of two parts: the "L1 security fee" and the "portion related to handling AA transactions on L2, excluding the Verification and Execution Phases".
The former has already been defined as L1SecurityFee
(or PVGForL1
), and the latter is defined as PVGForL2
.
Thus, the "complete PVG required for this userOp when sent on L2" is: PVG = PVGForL2 + L1CalldataCost / L2GasPrice.
Solution
-
Is there a better or simpler way to calculate PVG? Perhaps we can explore one long-term solution with RIP-7560's
builderFee
:- In RIP-7560, the cross-rollup native AA standard is defined, and in the fields definition of a userOp (or what we call RIP-7560 Transaction), PVG is replaced with
builderFee
, which specifically refers to the L1 data fee. -
This approach can help clarify the calculation of PVG. The intrinsic portion of PVG (21000 + every byte of data supplied) is set as the constant
AA_BASE_GAS_COST
.maxPossibleGasCost = AA_BASE_GAS_COST + validationGasLimit + paymasterValidationGasLimit + callGasLimit + paymasterPostOpGasLimit
- In RIP-7560, the cross-rollup native AA standard is defined, and in the fields definition of a userOp (or what we call RIP-7560 Transaction), PVG is replaced with
- Due to the complexity of calculating PVG, users may be charged what appear to be unreasonable PVG or gas fees. It is advisable to either set up their own Bundler or establish a business partnership with bundlers to maintain stable product operation and optimal earnings.
Contract Accessibility
Infra
will affect contract logic
- Most EVM-compatible Layer 2 solutions support all of Ethereum's precompiles, but some, like Scroll, may not fully support certain precompiles due to circuit limitations.
- P256 Verifier: Before the introduction of RIP-7212, we often relied on Daimo's P256 Verifier, which required the chain we wanted to deploy on to have this verifier. However, with RIP-7212 now in place, we need the chain to support the P256 precompiled directly.
Service
will not affect contract logic, but may affect produce stable
Many services depend on nodes, such as Bundlers, Paymasters, and Relayers. Before actually sending transactions or performing signing, they often simulate actions on the chain.
For instance, the widely used Alchemy Bundler is expected to support the P256 Precompiled only by Q2 2025. This means there is a gap of more than six months between the deployment of RIP-7212 on Layer 2 and the estimated time when support for it will be available.
🚢 The Fjord upgrade has shipped to OP Mainnet with RIP-7212 now available on the Superchain. Make sure to update your software to take full advantage of the new features: https://t.co/LM3UHnx53R https://t.co/hfdzMjJ2Au
— OP Labs (@OPLabsPBC) July 10, 2024
From a product perspective, when we reach a certain scale and aim to keep up with the latest technologies, it’s best to self-host the Bundler and Paymaster.
imo devs still feel a lot of complexity, starting from "which bundler and paymaster combination should I choose"? the dev tooling is OK could be 100x better.
— Georgios Konstantopoulos (@gakonst) February 20, 2025
Tool
will not affect contract logic, but may affect SDK or scripts logic
In addition to service support, we must also pay attention to whether development frameworks and tools support the new features (e.g., Foundry, Slither). There is always a period of transition before and after a hard fork, where ecosystem tools need time to catch up.
During this period, deploying contracts to the mainnet, launching products, or upgrading contracts can be risky and inconvenient. It’s better to wait until the tools are stable before going live.
For example, we frequently use the Foundry Create2 Factory and MultiCall3 contracts. However, if these contracts are not available on the chain, our deployment or testing scripts may become unusable.
Conclusion
Today's talk explored the complexities of ERC-4337 on Layer 2, covering issues such as account synchronization, authorization mechanisms, address consistency, and PVG calculations. Here are the key takeaways:
-
Cross-chain accounts require new synchronization and authorization mechanisms
- Multi-Chain Signing
- Wallet Provider Support
-
Address divergence is inevitable but manageable
- Don't assume the address same
- ENS is a good choice
-
PVG calculation on L2 is more complex than expected
- RIP-7560
- Set up your own bundler
The challenges of ERC-4337 on Layer 2 are not just technical—they represent an adaptation process for the entire ecosystem.
The future of Account Abstraction on L2 will depend on how infrastructure providers, wallet developers, and Rollup teams collaborate to make it a true standard.
Thank you!