In-Depth Discussion on EIP-7702 and Best Practices
Author: Kong
Editor: Sherry
Preface
Ethereum is about to undergo the Pectra upgrade, which is undoubtedly a significant update. Numerous important Ethereum Improvement Proposals (EIPs) will be introduced through this opportunity. Among them, EIP-7702 brings a transformative change to Ethereum’s Externally Owned Accounts (EOAs). This proposal blurs the boundary between EOAs and Contract Accounts (CAs) and serves as a crucial step toward native account abstraction following EIP-4337. It introduces a new interaction model to the Ethereum ecosystem.
Currently, Pectra has been deployed on the test network and is expected to go live on the mainnet soon. This article will provide an in-depth analysis of the implementation mechanism of EIP-7702, explore the opportunities and challenges it may bring, and offer practical operational guidelines for different participants.
Protocol Analysis
Overview
EIP-7702 introduces a new transaction type that allows an EOA to specify a smart contract address and set code for it. This enables EOAs to execute code like smart contracts while still retaining the ability to initiate transactions. This feature grants EOAs programmability and composability, allowing users to implement functionalities such as social recovery, permission control, multi-signature management, zero-knowledge verification, subscription-based payments, transaction sponsorship, and batch processing within EOAs.
Notably, EIP-7702 is fully compatible with the smart contract wallets implemented by EIP-4337. The seamless integration of these two proposals greatly simplifies the development and application of new functionalities.
The specific implementation of EIP-7702 involves introducing a new transaction type, SET_CODE_TX_TYPE (0x04), with the following data structure:
rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, value, data, access_list, authorization_list, signature_y_parity, signature_r, signature_s])
The authorization_list field is defined as:
authorization_list = [[chain_id, address, nonce, y_parity, r, s], ...]
In the new transaction structure, except for the authorization_list field, all other fields follow the same semantics as EIP-4844. The authorization_list field is of list type and can contain multiple authorization entries. Each authorization entry includes:
- chain_id: Specifies the chain where the authorization is valid.
- address: Specifies the target address of the delegation.
- nonce: Must match the current nonce of the authorized account.
- y_parity, r, s: The signature data of the authorized account signing the authorization.
The authorization_list field in a transaction can contain authorization entries signed by multiple different EOAs, meaning the transaction initiator can be different from the authorizer. This allows gas sponsorship for authorization operations.
Implementation
When an authorizer signs the authorization data, they must first RLP encode the chain_id, address, nonce fields. The encoded data is then hashed using keccak256 along with a MAGIC number, generating the data to be signed[1]. Finally, the authorizer’s private key signs the hashed data, producing the y_parity, r, s values.
// Go-ethereum/core/types/tx_setcode.go#L109-L113
func (a *SetCodeAuthorization) sigHash() common.Hash {
return prefixedRlpHash(0x05, []any{
a.ChainID,
a.Address,
a.Nonce,
})
}
The MAGIC (0x05) number serves as a domain separator to ensure that different types of signatures do not conflict.
If the chain_id of an authorization entry is 0, it means the authorizer allows[2] the authorization to be replayed on all EVM-compatible chains supporting EIP-7702, provided the nonce also matches.
// Go-ethereum/core/state_transition.go#L562
if !auth.ChainID.IsZero() && auth.ChainID.CmpBig(st.evm.ChainConfig().ChainID) != 0 {
return authority, ErrAuthorizationWrongChainID
}
Once the authorization data is signed, the transaction initiator aggregates it into the authorization_list field, signs the transaction, and broadcasts it via RPC. Before a transaction is included in a block, the Proposer performs a pre-check[3], ensuring that the to address is valid. This check prevents contract creation transactions, meaning that in EIP-7702 transactions, the to address must not be empty[4].
// Go-ethereum/core/state_transition.go#L388-L390
if msg.To == nil {
return fmt.Errorf("%w (sender %v)", ErrSetCodeTxCreate, msg.From)
}
Subsequently, during transaction execution, the node first increments the transaction initiator’s nonce value and then applies the applyAuthorization
operation to each authorization entry in the authorization_list
. In the applyAuthorization
operation, the node first checks the authorization's nonce and then increments the authorization's nonce. This means that if the transaction initiator and the authorizer are the same user (EOA), the nonce value should be incremented by 1 when signing the authorization transaction.
// Go-ethereum/core/state_transition.go#L489-L497
func (st *stateTransition) execute() (*ExecutionResult, error) {
...
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)
// Apply EIP-7702 authorizations.
if msg.SetCodeAuthorizations != nil {
for _, auth := range msg.SetCodeAuthorizations {
// Note errors are ignored, we simply skip invalid authorizations here.
st.applyAuthorization(&auth)
}
}
...
}
// Go-ethereum/core/state_transition.go#L604
func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization) error {
authority, err := st.validateAuthorization(auth)
...
st.state.SetNonce(authority, auth.Nonce+1, tracing.NonceChangeAuthorization)
...
}
// Go-ethereum/core/state_transition.go#L566
func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorization) (authority common.Address, err error) {
...
if auth.Nonce+1 < auth.Nonce {
return authority, ErrAuthorizationNonceOverflow
}
...
}
When a node applies an authorization entry, if any errors are encountered, that entry will be skipped, and the transaction will not fail. Other authorization entries will continue to be applied, ensuring that in a bulk authorization scenario, no DoS risks arise.
// Go-ethereum/core/state_transition.go#L494
for _, auth := range msg.SetCodeAuthorizations {
// Note errors are ignored, we simply skip invalid authorizations here.
st.applyAuthorization(&auth)
}
Once the authorizations are applied, the code field of the authorizer’s address will be set to 0xef0100 || address
, where 0xef0100
is a fixed identifier, and address
is the delegated target address. Due to the restriction of EIP-3541, users are unable to deploy contracts starting with 0xef
via regular means, ensuring that such identifiers can only be deployed through transactions of the SET_CODE_TX_TYPE (0x04)
type.
// Go-ethereum/core/state_transition.go#L612
st.state.SetCode(authority, types.AddressToDelegation(auth.Address))
// Go-ethereum/core/types/tx_setcode.go#L45
var DelegationPrefix = []byte{0xef, 0x01, 0x00}
func AddressToDelegation(addr common.Address) []byte {
return append(DelegationPrefix, addr.Bytes()...)
}
After authorization, if the authorizer wants to revoke the authorization, they simply need to set the delegated target address to the 0
address.
Through the new transaction type introduced by EIP-7702, the authorizer (EOA) can execute code like a smart contract while retaining the ability to initiate transactions. Compared to EIP-4337, this offers users an experience closer to native account abstraction (Native AA), greatly lowering the entry barrier for users.
Best Practices
Although EIP-7702 brings new vitality to the Ethereum ecosystem, the introduction of new application scenarios also brings new risks. Below are some aspects that ecosystem participants should be cautious about during practice:
Private Key Storage
Even though EOA (Externally Owned Accounts) can leverage built-in social recovery mechanisms in smart contracts to address the issue of asset loss caused by private key loss, it still cannot avoid the risk of private key leakage. It’s important to note that after delegating, the EOA private key still has the highest control over the account; holding the private key gives the user the ability to freely dispose of the assets within the account. Users or wallet service providers, after completing the delegation for an EOA, cannot entirely eliminate the risk of private key leakage, especially in cases where there may be supply chain attacks.
For users, private key protection should always be a priority. It is critical to remember: Not your keys, not your coins.
Multi-Chain Replay
When users sign a delegation authorization, they can choose the chain ID on which the delegation will be effective. Users can also choose to use a chain ID of 0, which allows the delegation to be replayed across multiple chains. However, the same contract address on different chains may have different implementations.
Wallet service providers should check whether the chain of the delegation matches the current network and warn users about the risks of delegations signed with a chain ID of 0 that could be replayed across different chains.
Users should also be aware that the same contract address on different chains may not always have the same contract code. It’s important to understand the details of the delegated target before proceeding.
Initialization Issue
Most mainstream smart contract wallets use a proxy model, where the wallet proxy calls an initialization function via DELEGATECALL
to achieve atomic initialization and deployment of the wallet. However, with EIP-7702, the address's code
field will only be updated, and the initialization function cannot be invoked through the delegated address. This limits EIP-7702 from offering the same wallet initialization capabilities as the ERC-1967 proxy contract, which can call an initialization function during deployment.
Developers should ensure permission checks are performed during wallet initialization (e.g., via ecrecover
to verify the signing address) to avoid initialization vulnerabilities.
Storage Management
When using EIP-7702 delegation, users may need to re-delegate to different contract addresses due to changes in functionality or wallet upgrades. Different contracts may have different storage structures (e.g., slot0
may represent different types of data in different contracts), and re-delegating could cause old contract data to be reused, resulting in account locking, asset loss, and other issues.
Users should carefully handle re-delegation situations.
Developers should follow the Namespace Formula proposed in ERC-7201 to allocate variables to specified independent storage locations to mitigate storage conflicts. Additionally, ERC-7779 (draft) provides a standard process for re-delegation specific to EIP-7702, including preventing storage conflicts and validating compatibility before re-delegating.
False Recharge
After delegation, EOAs can also act as smart contracts, which may lead to centralized exchanges (CEX) facing widespread smart contract recharge risks.
CEX should use trace checks to monitor each deposit transaction to mitigate the risk of fake deposits from smart contracts.
Account Conversion
With EIP-7702 delegation, a user’s account can freely convert between an EOA and a smart contract, enabling the account to initiate transactions and also be called. This means that when the account calls itself or makes an external call, its msg.sender
will also be tx.origin
, which breaks the security assumption that only EOAs participate.
Contract developers should not assume that tx.origin
will always be an EOA. Similarly, using msg.sender == tx.origin
as a defense against reentrancy attacks will no longer be effective.
Developers should assume that future participants may all be smart contracts during the development process.
Contract Compatibility
Existing ERC-721 and ERC-777 tokens have hook functions when transferring tokens, meaning the recipient must implement the corresponding callback function to successfully receive tokens.
Developers should ensure that the target contract for the user’s delegation implements the necessary callback functions to ensure compatibility with mainstream tokens.
Phishing Checks
After implementing EIP-7702 delegation, the assets in a user’s account may be controlled by smart contracts. If the user delegates their account to a malicious contract, attackers can easily steal assets.
Wallet service providers should quickly support EIP-7702 transactions and, when users sign delegations, should prominently display the target contract to reduce the risk of phishing attacks.
Additionally, conducting in-depth automated analysis (open-source checks, permission checks, etc.) of delegated target contracts can help users avoid such risks.
Conclusion
This article explores EIP-7702 in the upcoming Ethereum Pectra upgrade. EIP-7702 introduces new transaction types that give EOAs programmability and composability, blurring the lines between EOAs and contract accounts. However, as there is currently no widely tested standard compatible with EIP-7702 contracts, various ecosystem participants, such as users, wallet providers, developers, and CEXs, face many challenges and opportunities. The best practices discussed here cannot cover all potential risks but are still valuable for practical application.
Examples
[Set EOA Account Code]
[Unset EOA Account Code]
Related Links
References
[EIP-7702] https://eips.ethereum.org/EIPS/eip-7702
[EIP-4844] https://eips.ethereum.org/EIPS/eip-4844
[Go-ethereum] https://github.com/ethereum/go-ethereum/tree/7fed9584b5426be5db6d7b0198acdec6515d9c81
[EIP-3541] https://eips.ethereum.org/EIPS/eip-3541#backwards-compatibility
[Cobo: EIP-7702实用指南] https://mp.weixin.qq.com/s/ojh9uLw-sJNArQe-U73lHQ
[Viem] https://viem.sh/experimental/eip7702/signAuthorization
[ERC-7210] https://eips.ethereum.org/EIPS/eip-7201
[ERC-7779] https://eips.ethereum.org/EIPS/eip-7779