How EIP-7702 Works | Account Abstraction Implementation

Table of contents:
If you're building wallet infrastructure, relayer services, or protocol tooling on Ethereum, and you want account abstraction features without migrating users to smart contract wallets, this guide is for you.
On May 7, 2025, Ethereum activated the Pectra upgrade. One of the core changes included in that fork was EIP-7702, co-authored by Vitalik Buterin.
EIP-7702 allows an Externally Owned Account to temporarily execute smart contract logic while keeping:
- The same address
- The same balance
- The same transaction history
There is no smart wallet deployment.
There is no address migration.
There is no wrapper contract.
Instead, the protocol writes a delegation marker into the EOA’s code field and resolves execution to a delegate contract at runtime.
In this article, we will:
- Break down the EIP-7702 specification at the protocol level
- Examine how it is implemented in geth
- Compare EIP-7702 with ERC-4337 and ERC-2771
- Walk through a concrete Gas Station architecture using Type-4 transactions
Before diving into the transaction format and client implementation, we need to clarify the problem EIP-7702 is addressing.
The Core Limitation of EOAs
Traditional EOAs have structural constraints:
- One transaction per signature
- Gas must be paid in ETH
- No programmable spending limits
- No session keys
- Loss of private key means irreversible loss of funds
Account abstraction efforts aim to remove these constraints.
ERC-4337 introduced Smart Contract Wallets to address most of them. It adds validation logic, batching, sponsored gas via Paymasters, and custom signature schemes.
However, ERC-4337 requires deploying a new contract wallet. That means:
- New address
- No inherited transaction history
- No legacy integrations bound to the old EOA
For many systems, that migration cost is non-trivial.
What ERC-4337 Did — and the Trade-Off
ERC-4337 moved account abstraction into an application-layer architecture:
- Bundlers package UserOperations
- EntryPoint validates and executes
- Paymasters sponsor gas
- Smart contract wallets define custom validation logic
The advantage is flexibility.
The cost is infrastructure complexity and address migration.
If your goal is multi-user aggregation, permissionless Paymasters, or signature aggregation, ERC-4337 fits well.
If your goal is to extend an existing EOA with delegation, batching, or sponsored execution under a known operator, the migration requirement becomes friction.
What EIP-7702 Changes
EIP-7702 takes a protocol-level approach instead of an application-layer stack.
It introduces:
- A new transaction type (0x04, SetCodeTransaction)
- An authorization_list field
- A delegation marker stored directly in the EOA’s code
Execution works differently from ERC-4337:
- No EntryPoint
- No Smart Contract Wallet deployment
- No change to the user’s address
The EVM checks whether the account’s code matches a delegation prefix.
If it does, it loads bytecode from the delegate contract.
Execution runs in the EOA’s storage and balance context.
From the perspective of target contracts:
- msg.sender remains the original EOA
- No forwarder contract is involved
- No contract modification is required
That design choice is critical for compatibility with existing DeFi protocols.
EIP-7702 Technical Specification
EIP-7702 introduces a new Ethereum transaction type that lets your EOA attach smart contract logic through a signed authorization. It adds an authorization_list to the EIP-1559 transaction format, defines a raw ECDSA signing flow with a 0x05 prefix, and writes a delegation marker directly into the account’s code field.
Now let’s break this down step by step so you can see how it affects your protocol or wallet logic.
SetCodeTransaction (Transaction Type 4 / 0x04)
EIP-7702 introduces Transaction Type 4 (0x04), called SetCodeTransaction.
The proposal extends the EIP-1559 payload by adding a new field: authorization_list.
TransactionPayload = rlp([
chain_id,
nonce,
max_priority_fee_per_gas,
max_fee_per_gas,
gas_limit,
destination,
value,
data,
access_list,
authorization_list, // NEW FIELD
signature_y_parity,
signature_r,
signature_s
])
Authorization Tuple
Each entry in the authorization_list contains:
authorization_list = [
[chain_id, address, nonce, y_parity, r, s],
...
]
Here’s what each field represents:
- chain_id — network identifier (0 marks cross-chain authorization)
- address — delegate contract
- nonce — current EOA nonce to prevent replay
- y_parity, r, s — ECDSA signature components
This tuple explicitly defines which contract can execute logic on behalf of your EOA and which chain it applies to. Your client validates this tuple independently of the main transaction signature.
So, What EIP-7702 Adds to the Protocol
Only one structural difference: authorization_list.
Everything else—gas, fees, nonce rules—works the same way as EIP-1559.
Conceptually it works this way:
- The user signs a message authorizing delegation to a contract
- The relayer includes this in a Type-4 transaction
- The protocol sets special code on the EOA
- The EOA call executes delegate code in its own context
No new address. No wrapper. No forwarder.
The authorization_list contains the delegation information that allows your EOA to temporarily assign execution rights to a contract.
Signing Flow with 0x05 Prefix
EIP-7702 uses a magic prefix 0x05 to sign authorizations:
message_hash = keccak256(
0x05 || rlp([chain_id, address, nonce])
)
This creates a raw ECDSA signature instead of EIP-712 typed data. That means you don’t have to wrestle with typed data domains or complex schema rules when adding EIP-7702 transaction signing to your wallet or protocol.
So, the key points are:
- Prefix is always 0x05.
- Payload is RLP-encoded.
- Signature is standard ECDSA.
- Integration into wallets or protocols is straightforward, with fewer opportunities for mistakes.
Delegation Marker in EOA Code
When authorization is applied, the EVM writes a 23-byte marker into the EOA’s code field:
0xef0100 || delegate_address // 3 + 20 = 23 bytes
Where:
- 0xef0100 — fixed 3-byte prefix
- delegate_address — 20-byte contract address
Why 0xef?
EIP-3541 prohibits deploying contracts starting with 0xef. That guarantees this marker can never collide with normal contract bytecode.
When the EVM executes a call:
- It checks if code matches delegation prefix
- If yes, it loads code from delegate address
- Execution runs in EOA storage context
It follows protocol-level DELEGATECALL semantics. After writing the marker, the EOA keeps its address, balance, and storage, while execution runs through the delegate’s code. Basically, your account gains temporary logic without changing its identity.
Summing Up EIP-7702 Transaction Signing Changes
EIP-7702 introduces a few key updates you need to be aware of if you’re building wallets, infrastructure, or protocol tooling:
- Transaction Type 4 (0x04) – a new transaction type called SetCodeTransaction.
- authorization_list field – a dedicated place to list delegate signatures.
- Prefixed ECDSA signing (0x05) – all authorization tuples use a fixed prefix for signing.
- Delegation marker in the EOA code – a 23-byte marker signals when an EOA executes delegate code.
To make sure your code works with EIP-7702 transactions:
- Sign each authorization tuple with the 0x05 prefix.
- Validate each delegate tuple in the authorization_list.
- Process Type-4 transactions alongside standard EIP-1559 transactions.
Everything else follows Ethereum’s execution rules, so your existing logic mostly stays intact.
Now that we covered the EIP-7702 specification and transaction signing changes, let’s see how it’s implemented in Go-Ethereum (geth v1.15.x).
EIP-7702 Authorization Structure in Geth
Geth defines SetCodeTx in core/types/tx_setcode.go. It extends a standard EIP-1559 transaction with an AuthList field for authorizations:
type SetCodeTx struct {
ChainID *uint256.Int
Nonce uint64
GasTipCap *uint256.Int
GasFeeCap *uint256.Int
Gas uint64
To common.Address // must not be nil
Value *uint256.Int
Data []byte
AccessList AccessList
AuthList []SetCodeAuthorization // new field for authorization tuples
V, R, S *uint256.Int
}
What SetCodeTx Adds
The key addition is the AuthList. It stores the authorization tuples required for prefixed signing and delegation. All other fields stay consistent with EIP-1559, so Type-4 transactions integrate without extra changes.
Authorization tuple fields:
- ChainID – ensures the authorization applies to the correct network.
- Address – the delegate account linked to the EOA.
- Nonce – prevents replay of old authorizations.
- V, R, S – ECDSA signature using the 0x05 prefix.
AuthList allows Geth to validate authorizations and apply delegation markers, giving the EOA temporary logic from a delegate contract while keeping the account itself unchanged.
Authorization Tuple Structure
Each authorization in AuthList is represented as SetCodeAuthorization in Go:
type SetCodeAuthorization struct {
ChainID uint256.Int
Address common.Address
Nonce uint64
V uint8
R, S uint256.Int
}
Geth can now store and process the authorization_list directly in a transaction, validate it, and apply delegation markers, fully supporting the EIP-7702 flow at the node level.
Geth can now store and process the authorization_list directly in a transaction, validate it, and apply delegation markers, fully supporting the EIP-7702 flow at the node level.
EIP 7702 Delegation Marker in Geth
Geth uses a fixed byte prefix to mark delegated code in an EOA. This ensures the node can recognize when an account’s execution should load code from a delegate contract.
var DelegationPrefix = []byte{0xef, 0x01, 0x00}
}
Let’s look at the key functions for delegation markers creation and reading in EOAs.
- Creating a Delegation Marker
The AddressToDelegation function appends the delegate’s address to the fixed prefix, producing a 23-byte marker stored in the EOA code:
func AddressToDelegation(addr common.Address) []byte {
return append(DelegationPrefix, addr.Bytes()...)
}
The marker lets Geth distinguish delegated code from normal contracts and forward execution to the delegate contract without modifying the EOA’s storage or balance.
- Parsing a Delegation Marker
The ParseDelegation function reads an EOA’s code and checks for the delegation prefix. If found, it extracts the delegate address:
func ParseDelegation(code []byte) (common.Address, bool) {
if len(code) != 23 ||
!bytes.HasPrefix(code, DelegationPrefix) {
return common.Address{}, false
}
return common.BytesToAddress(code[3:]), true
}
By parsing the marker, Geth can determine whether an EOA is using delegated logic and route execution accordingly, while preserving the account’s original storage and balance.
Let’s walk through how go-ethereum processes this.
Before diving into low-level details, it helps to see how Geth processes EIP-7702 authorizations at a high level. Execution in state_transition.go follows three main steps:
- Increment transaction sender nonce
- Apply authorizations
- Perform EVM call
Authorization validation checks:
- Chain ID matches (or zero)
- Nonce matches current EOA nonce
- Signature recovers authority
- Account has no conflicting contract code
- Nonce overflow protection
If valid:
- Code field is replaced with delegation marker
- Nonce is incremented
Then execution proceeds.
Resolution happens in evm.go:
- If code is delegation marker
- Load code from delegate address
- Execute in original EOA context
From a protocol standpoint, this is minimal surface area.
How to Execute EIP-7702 Authorizations
Geth’s core/state_transition.go defines the process for applying EIP-7702 authorizations. Execution involves three main steps: incrementing the sender nonce, applying authorization tuples, and running the EVM call. Each step ensures proper transaction sequencing and enforces delegation markers according to the EIP-7702 Ethereum account abstraction specification.
func (st *stateTransition) execute() (*ExecutionResult, error) {
// 1. Increment the transaction sender's nonce
st.state.SetNonce(msg.From,
st.state.GetNonce(msg.From)+1,
tracing.NonceChangeEoACall)
// 2. Apply EIP-7702 authorizations
if msg.SetCodeAuthorizations != nil {
for _, auth := range msg.SetCodeAuthorizations {
// Errors are ignored — invalid authorizations are simply skipped
st.applyAuthorization(&auth)
}
}
// 3. Execute the EVM call
ret, st.gasRemaining, vmerr = st.evm.Call(
msg.From, *msg.To, msg.Data,
st.gasRemaining, value)
// Additional execution handling happens here...
}
Let’s walk through how go-ethereum processes this:
Step 1: Increment Sender Nonce
Incrementing the sender’s nonce prevents replay attacks and guarantees that transactions execute in order. Every transaction in an EIP-7702 wallet increases the nonce before any delegation logic is applied.
Step 2: Apply EIP-7702 Authorization Tuples
Geth processes each authorization tuple in the SetCodeAuthorizations list. Invalid or expired authorizations are skipped. Valid tuples attach delegation markers to the externally owned account (EOA), linking it to the delegated contract while preserving the account’s storage and balance.
Delegation markers allow the EOA to execute code from another contract while maintaining its own account context, demonstrating how EIP-7702 transaction signing changes work at the node level.
Step 3: Execute EVM Call
How to Apply EIP 7702 Authorization for EOA
The applyAuthorization function sets the execution code for an externally owned account according to EIP-7702 authorizations. It validates the authorization, assigns a delegation marker if needed, and updates the authorizer’s nonce.
func (st *stateTransition) applyAuthorization(
auth *types.SetCodeAuthorization) error {
// Validate the authorization and recover the authorizer's address
authority, err := st.validateAuthorization(auth)
if err != nil {
return err
}
// Revocation: if the address is zero, clear the EOA code
if auth.Address == (common.Address{}) {
st.state.SetCode(authority, nil,
tracing.CodeChangeAuthorization)
} else {
// Set the delegation designator pointing to the delegate contract
st.state.SetCode(authority,
types.AddressToDelegation(auth.Address),
tracing.CodeChangeAuthorization)
}
// Increment the authorizer's nonce to prevent replay
st.state.SetNonce(authority, auth.Nonce+1,
tracing.NonceChangeAuthorization)
return nil
}Step 1: Validate the Authorization
Validation recovers the authorizer address and ensures the authorization follows the rules defined in EIP-7702. Invalid authorizations cause the function to return an error without modifying the state.
Step 2: Apply or Revoke Delegation Code
Assigning a zero address clears the code, effectively revoking any previous delegation. We’re going to cover how to revoke delegation in more detail later in this article. Non-zero addresses store a delegation designator, pointing the EOA to the target delegate contract while preserving the EOA’s balance and storage.
Step 3: Increment the Authorizer Nonce
The authorizer’s nonce is incremented to prevent replay attacks and ensure proper ordering of subsequent EIP-7702 transactions.
applyAuthorization executes EIP-7702 logic for EOAs by combining validation, delegation assignment, and nonce updates, allowing Geth to enforce delegation markers securely and correctly.
Authorization Validation Step in the Geth Execution Flow
During transaction execution, Geth validates each SetCodeAuthorization before any state mutation occurs. The validateAuthorization function enforces all invariants required by EIP-7702 inside the state transition pipeline.
func (st *stateTransition) validateAuthorization(
auth *types.SetCodeAuthorization,
) (common.Address, error) {
// 1. Chain ID: must be 0 (cross-chain) or match the current network
if !auth.ChainID.IsZero() &&
auth.ChainID.CmpBig(st.evm.ChainConfig().ChainID) != 0 {
return common.Address{},
ErrAuthorizationWrongChainID
}
// 2. Nonce overflow check: ensure incrementing the nonce won't wrap around
if auth.Nonce+1 < auth.Nonce {
return common.Address{},
ErrAuthorizationNonceOverflow
}
// 3. Recover the authorizer address from the signature (ECDSA / ecrecover)
authority, err := auth.Authority()
if err != nil {
return common.Address{}, err
}
// 4. Add the recovered address to the access list (EIP-2929)
st.state.AddAddressToAccessList(authority)
// 5. Code must be empty or already a valid delegation marker
code := st.state.GetCode(authority)
if len(code) > 0 {
if _, ok := types.ParseDelegation(code); !ok {
return common.Address{},
ErrAuthorizationDestinationIsContract
}
}
// 6. Authorization nonce must exactly match the account's current nonce
if auth.Nonce != st.state.GetNonce(authority) {
return common.Address{},
ErrAuthorizationNonceMismatch
}
// Return the recovered and validated authority address
return authority, nil
}Validation happens before applyAuthorization modifies account code.
1. Chain ID Check
The authorization is accepted only if:
- ChainID == 0, or
- ChainID matches the current network’s chain ID.
Mismatch causes immediate rejection, preventing cross-chain replay.
2. Nonce Overflow Guard
Overflow detection ensures that incrementing the nonce will not wrap around. Any overflow attempt results in failure.
3. Signature Recovery (ecrecover)
Signature recovery derives the authorizer address from the signed authorization payload. Failure stops execution.
Successful recovery proves that the signer controls the account whose code may be modified.
4. Access List Update (EIP-2929)
The recovered address is added to the access list. Under EIP-2929 rules, access list updates affect gas accounting for cold versus warm accesses during execution.
5. Code Constraint: Empty or Delegated Only
Accounts are allowed to:
- Have no code, or
- Already contain a valid delegation designator.
If the account contains regular contract bytecode, validation fails. Geth does not permit overwriting deployed smart contracts using EIP-7702.
6. Nonce Equality Requirement
Authorization nonce must exactly match the account’s current nonce. Any mismatch causes rejection.
Strict equality guarantees ordering and replay protection for delegation updates.
Role of validateAuthorization in the Execution Pipeline
Within the state transition:
- Sender nonce is incremented.
- Each authorization is validated using validateAuthorization.
- Only after successful validation does applyAuthorization modify account code and nonce.
- EVM execution begins.
Validation therefore acts as a gatekeeper step in the EIP-7702 execution flow inside Geth.
How to Revoke EIP-7702 Authorization in Geth
Revocation in EIP-7702 is performed by submitting a new authorization where the Address field is set to address(0).
From the original function:
if auth.Address == (common.Address{}) {
st.state.SetCode(authority, nil,
tracing.CodeChangeAuthorization)
}
When auth.Address equals the zero address:
- SetCode(authority, nil) clears the code stored on the EOA.
- Any previously stored delegation designator is removed.
- The account returns to behaving like a normal EOA with no delegated execution logic.
No separate “revoke” opcode exists. Revocation is achieved by writing nil code through a new valid authorization.
Delegation Resolution Inside the EVM Execution Path
After validateAuthorization and applyAuthorization complete, execution reaches the EVM. If the caller is an EOA that previously stored a delegation designator under EIP-7702, the EVM resolves which bytecode should be executed.
The logic lives in core/vm/evm.go:
func (evm *EVM) resolveCode(addr common.Address) []byte {
code := evm.StateDB.GetCode(addr)
// Check for delegation designator
if target, ok := types.ParseDelegation(code); ok {
// Load code from delegate contract
return evm.StateDB.GetCode(target)
}
return code
}
Step 1: Load Account Code
The EVM retrieves the code stored at the account address.
- For a regular EOA, code is empty.
- For a smart contract, code contains deployed bytecode.
- For an EIP-7702 delegated EOA, code contains a delegation designator.
Step 2: Detect Delegation Marker
ParseDelegation checks whether the stored bytes represent a delegation designator instead of normal contract bytecode.
If parsing succeeds:
- target becomes the delegate contract address.
- Execution bytecode is resolved from that target.
Step 3: Load Delegate Contract Code
The EVM loads the bytecode from the delegate contract address.
If no delegation marker exists, execution proceeds with the originally retrieved code:
return code
Protocol-Level Delegatecall Semantics
It is important to know that code executes in the context of the EOA — using its storage and balance — while being loaded from the delegate contract. This follows DELEGATECALL semantics at the protocol level.
- EOA context preserved: Storage and balance changes affect the EOA, not the delegate.
- Delegate contract code loaded: Execution logic comes from the delegate contract’s bytecode.
This distinction ensures that delegated execution does not modify the EOA’s state outside the intended logic.
How to Revoke EIP-7702 Delegation
Delegation under EIP-7702 works by storing a delegation designator as the EOA’s account code:
st.state.SetCode(
authority,
types.AddressToDelegation(auth.Address),
tracing.CodeChangeAuthorization)
The EOA does not deploy contract bytecode in the traditional sense. Instead, it stores a special delegation marker that points to another contract’s address. During execution, the EVM resolves that marker and runs the delegate’s code while keeping the EOA’s balance and storage context.
Revocation happens by submitting a new valid SetCodeAuthorization where:
auth.Address == (common.Address{})
The corresponding branch in applyAuthorization clears the account code:
st.state.SetCode(authority, nil,
tracing.CodeChangeAuthorization)
Clearing the code removes the delegation designator entirely. After that state transition:
- No delegation marker remains on the EOA.
- The account no longer resolves execution to a delegate contract.
- The EOA behaves like a standard externally owned account again.
Nonce handling is still enforced:
st.state.SetNonce(authority, auth.Nonce+1,
tracing.NonceChangeAuthorization)
Summary
Revoke EIP-7702 authorization by submitting a new signed authorization with Address = address(0).
Revoke EIP-7702 delegation by clearing the delegation designator, which is implemented internally as SetCode(authority, nil).
No separate revocation opcode exists. State transition logic replaces the stored delegation marker with empty code.
ERC-7702 vs ERC-4337
Both ERC-7702 and ERC-4337 are designed for account abstraction, but they take fundamentally different approaches. Let’s break down the core specs and see where each one makes sense.
Key Specifications Differences
When ERC-4337 Fits
ERC-4337 makes sense in the following use cases:
- Permissionless Paymasters: You need a public gas sponsorship service, for example in a DeFi protocol, without relying on a trusted operator.
- Transaction Aggregation: You want to bundle multiple users’ operations into a single on-chain transaction, e.g., BLS signature aggregation.
When to use EIP-7702
EIP-7702 is enough when your flows are limited to:
- Batching transactions for a single user
- Session keys and delegated permissions
- Gas sponsorship from a known operator
- Spending limits and time locks
- Social recovery
Use EIP-7702 when the goal is single-user delegation, session keys, or trusted gas sponsorship — it preserves the EOA and keeps implementation lightweight. ERC-4337 fits scenarios requiring public paymasters or batching multiple users’ operations into a single transaction. Choosing between them depends on whether your flow needs minimal EOA delegation or a full smart contract wallet stack.
Comparison EIP 7702 Gas Sponsorship with ERC-2771
Before EIP-7702, gas sponsorship was implemented using ERC-2771, the standard for meta-transactions.
How ERC-2771 Works
ERC-2771 uses a Trusted Forwarder pattern:
(bool success,) = target.call(
abi.encodePacked(data, from) // 20 bytes appended
);
// Recipient contract extracts the sender
function _msgSender() internal view returns (address) {
if (isTrustedForwarder(msg.sender)) {
// Last 20 bytes of calldata = actual sender
return address(bytes20(msg.data[msg.data.length - 20:]));
}
return msg.sender;
}
Key Difference: msg.sender
The main distinction between ERC-2771 and EIP-7702 is what msg.sender resolves to in the target contract:
ERC-2771 requires the target contract to use _msgSender() instead of msg.sender. Without this, existing contracts like Uniswap or Aave cannot interact with meta-transactions.
EIP-7702 handles delegation at the protocol level: the target contract sees the real EOA as msg.sender, not an intermediate contract, which allows interaction with standard DeFi contracts without modification.
Gas Station Transaction Flow
Gas Station allows users to execute transactions without holding ETH. The user signs their intent and a relayer submits the transaction, while execution still occurs in the user’s account context.
Let’s take a look at flowchart to see how the transaction moves from the user through the relayer and executes on-chain.

How the Flow Works
- User Signs the Intent – The user’s EOA signs both a gas authorization for the Gas Station and the batch of calls they want executed.
- Relayer Submits the Transaction – The Gas Station relayer collects the signed data and submits a Type-4 EIP-7702 transaction on behalf of the user.
- EVM Validation and Preparation – On-chain, the EVM checks that the target address is valid and that the authorization list is not empty. It calculates intrinsic gas, increments the sender nonce, and applies authorization markers linking the user EOA to the delegated batch contract.
- Batch Execution – The EVM calls the user’s EOA with the encoded batch data. Delegation ensures the delegate contract executes the requested operations.
- Execution in User Context – All calls run in the user’s context. The msg.sender in the target contract reflects the Gas Station relayer, while address(this) points to the user’s EOA.
Delegate Contract for Batch Execution
The GasStationDelegate contract executes multiple calls in a single transaction. Each call specifies a target contract, an ETH value, and calldata. The contract processes these calls sequentially while preserving the user’s EOA as the context for balances, storage, and msg.sender.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract GasStationDelegate {
struct Call {
address target;
uint256 value;
bytes data;
}
function executeBatch(Call[] calldata calls) external {
for (uint i = 0; i < calls.length; i++) {
(bool success, bytes memory result) =
calls[i].target.call{value: calls[i].value}(calls[i].data);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
}
}
}
How it works
- Loops through each Call and executes it on the target contract.
- Reverts the batch if any call fails, returning the original error.
- Ensures the user’s EOA context is preserved, so msg.sender, storage, and balances behave as if the user called directly.
Gas Station Backend Flow (Go)
The backend constructs and signs a SetCode transaction that instructs the user’s EOA to execute the batch via the delegate contract:
func buildSetCodeTx(
userAuth *types.SetCodeAuthorization,
userAddress common.Address,
callData []byte,
relayerKey *ecdsa.PrivateKey,
) (*types.Transaction, error) {
tx := types.NewTx(&types.SetCodeTx{
ChainID: uint256.NewInt(1), // Mainnet
Nonce: relayerNonce,
GasTipCap: uint256.NewInt(1e9),
GasFeeCap: uint256.NewInt(50e9),
Gas: 500000,
To: userAddress, // call user EOA
Value: uint256.NewInt(0),
Data: callData, // executeBatch encoded
AuthList: []types.SetCodeAuthorization{
*userAuth,
},
})
signer := types.NewPragueSigner(big.NewInt(1)) // chainId
return types.SignTx(tx, signer, relayerKey)
}
What’s happening here
- The relayer builds a SetCode transaction for the user’s EOA.
- callData contains the batch of operations the delegate contract will run.
- The transaction is signed by the relayer but executes with the user’s EOA as context — msg.sender inside the targets resolves to the user, not the relayer.
EIP-7702 Step-by-Step Gasless Swap Example
Let’s say Alice wants to swap USDC for DAI but has no ETH to pay for gas. Using the Gas Station with EIP-7702, she can execute this swap while keeping her EOA as the context for the transaction.
// 1. User signs the authorization
auth := &types.SetCodeAuthorization{
ChainID: *uint256.NewInt(1),
Address: GAS_STATION_DELEGATE,
Nonce: userNonce,
}
auth.Sign(userPrivateKey)
// 2. Forming a batch of operations
calls := []Call{
{
Target: USDC_ADDRESS,
Value: 0,
Data: encodeApprove(DEX_ROUTER, amount),
},
{
Target: DEX_ROUTER,
Value: 0,
Data: encodeSwap(USDC, DAI, amount, minOut),
},
}
// 3. Gas Station sends a Type-4 transaction
// DEX Router sees msg.sender = userAddress ✓Step 1: Alice signs authorization
Alice signs a SetCodeAuthorization for her EOA to delegate execution to the GasStationDelegate. This proves she approved the batch operation without sending ETH herself.
Step 2: Construct the batch of operations
Alice wants to first approve the DEX router to spend her USDC, then swap USDC for DAI. These calls are packed into a batch that the delegate contract will execute sequentially.
Step 3: Gas Station relayer submits the transaction
The Gas Station picks up Alice’s signed authorization and submits a Type-4 transaction. The GasStationDelegateexecutes the batch on behalf of Alice.
- The DEX router sees msg.sender as Alice’s EOA, not the Gas Station.
- Storage and balances are updated in Alice’s context.
- Alice completes the swap without holding ETH.
Observations from the execution flow
- Batch execution allows multiple operations in one transaction.
- EIP-7702 preserves msg.sender as the user EOA, ensuring compatibility with standard DeFi contracts.
- Gas sponsorship is handled externally by the relayer, so users can interact with contracts without ETH.
EIP-7702 Security Considerations
Delegating execution through EIP-7702 introduces specific risks that need careful handling.
Risks in Delegation
- Malicious delegate contract — the delegate can fully access the user’s EOA storage and balances. Only audited delegates should be used.
- Front-running initialization — EIP-7702 does not support init code; an attacker could intercept a user’s authorization before execution.
- Storage collision — replacing a delegate may conflict with existing storage layouts (see ERC-7201).
- Cross-chain replay — authorizations with chainId = 0 are valid on all chains, enabling replay attacks.
Protections at the Contract Level
Contracts can block delegated calls with a modifier like:
modifier noEIP7702Delegation() {
bytes memory code = msg.sender.code;
if (code.length >= 3 &&
code[0] == 0xef &&
code[1] == 0x01 &&
code[2] == 0x00) {
revert("Delegation not allowed");
}
_;
}
This ensures only direct EOA calls reach the contract.
Protections at the Gas Station Level
- Delegate whitelist — only allow authorizations for vetted delegate contracts.
- Rate limiting — restrict the number of sponsored transactions per user.
- Gas price caps — prevent manipulation of transaction fees.
- Simulation — test transactions before execution to catch failures or unexpected behavior.
- Monitoring — track abnormal relayer activity to identify potential misuse.
Gas Costs of EIP-7702 Transactions
Delegated transactions under EIP-7702 introduce extra gas components compared to a standard transfer.
Gas Breakdown
A simple transfer using EIP-7702 can increase gas by roughly 50%. However, batching multiple operations in a single transaction offsets these costs, often resulting in net savings compared to executing N individual transactions.
Where EIP-7702 Fits Best in System Design
Use it when you need:
- Gas sponsorship with known operator
- Session keys
- Spending limits
- Batch execution
- Social recovery
Avoid it when:
- You need permissionless Paymasters
- You aggregate multiple users per transaction
- You require advanced signature aggregation
Architecturally:
7702 minimizes additional surface area while preserving universal dApp compatibility.
Wrapping Up EIP-7702
EIP-7702 bridges the simplicity and compatibility of EOAs with capabilities typically reserved for Smart Contract Wallets.
How it Stands Out
- Compared to ERC-4337: preserves the user’s address, maintains native msg.sender, and has lower gas overhead.
- Compared to ERC-2771: works with any existing contract without requiring modifications.
- Compared to EIP-3074: forward-compatible with the broader Account Abstraction roadmap.
Deployment Recommendations
- Accept authorizations only from audited delegate contracts.
- Use chain-specific authorizations (chainId != 0) to prevent cross-chain replay.
- Plan for nonce management when handling sponsored transactions.
- Test thoroughly on Sepolia before deploying to mainnet.
EIP-7702 opens access to Account Abstraction features for every existing Ethereum wallet while keeping interactions straightforward and compatible.
Key Takeaways
What is EIP-7702?
EIP-7702 is an Ethereum proposal that allows an Externally Owned Account (EOA) to temporarily execute smart contract code via a signed authorization, without changing its address, balance, or transaction history.
It introduces a new transaction type (Type 4 / 0x04) and an authorization_list field that enables protocol-level delegation. This design brings account abstraction capabilities directly to standard EOAs.
How Does EIP-7702 Work?
At a high level:
- The user signs an authorization using a 0x05-prefixed ECDSA signature.
- A relayer submits a SetCodeTransaction (0x04).
- The EVM writes a 23-byte delegation marker (0xef0100 || delegate_address) into the EOA’s code field.
- During execution, the EVM loads bytecode from the delegate contract.
- Execution runs in the EOA’s storage and balance context.
No new wallet address. No wrapper contract. No forwarder logic in target contracts.
This is protocol-level delegation with DELEGATECALL semantics.
What Type of Transaction Is an EIP-7702 Tx?
An EIP-7702 transaction is:
- Transaction Type 4 (0x04)
- An extension of the EIP-1559 transaction format
- Includes a new authorization_list field
- Uses raw ECDSA signing with a 0x05 prefix
Everything else — gas rules, nonce behavior, fee mechanics — follows standard Ethereum execution rules.
What Are EIP-7702 Authorizations?
An authorization tuple contains:
- chain_id
- delegate address
- nonce
- y_parity, r, s (ECDSA signature)
Each authorization explicitly states:
- Which contract can execute logic on behalf of the EOA
- On which chain
- At which nonce
The client validates these independently before applying delegation.
Is EIP-7702 Implemented?
Yes. Go-Ethereum (Geth v1.15.x) includes full support for EIP-7702:
- Native SetCodeTx structure
- Validation of AuthList
- Delegation marker handling
- Execution resolution inside evm.go
This means EIP-7702 is implemented at the client level and supported by Ethereum nodes running recent Geth versions.
How to Revoke EIP-7702 Authorization?
EIP-7702 authorization is revoked by submitting a new valid signed authorization where the Address field is set to address(0).
When processed, the client executes: SetCode(authority, nil)
This clears the EOA’s code field and removes any previously stored delegation marker. The revocation:
- Requires a valid 0x05-prefixed ECDSA signature
- Requires nonce equality
- Increments the authorizer’s nonce
- Follows the same validation path as delegation
There is no dedicated revoke opcode. Revocation is performed through a new SetCode authorization transaction (Type 4 / 0x04).
How to Revoke EIP-7702 Delegation?
EIP-7702 delegation is revoked by clearing the delegation designator stored in the EOA’s code field.
Delegation works by writing a 23-byte marker: 0xef0100 || delegate_address
To revoke delegation, a new SetCodeAuthorization must be submitted where: auth.Address == address(0)
Inside the state transition, the client executes: st.state.SetCode(authority, nil)
After this state update:
- No delegation marker remains
- The EVM no longer resolves code from a delegate contract
- The EOA behaves as a standard externally owned account
- Nonce is incremented to prevent replay
Delegation revocation is not automatic. It must be explicitly authorized and validated like any other EIP-7702 authorization.
EIP-7702 Account Abstraction: What Changes?
EIP-7702 introduces a minimal-surface account abstraction model:
- Keeps original EOA
- Preserves msg.sender
- Enables batching
- Enables session keys
- Enables gas sponsorship
- Enables spending limits and social recovery
Unlike full smart contract wallets, it does not require deploying a new account contract.
ERC-7702 vs ERC-4337
Ethereum builders often compare:
ERC-7702
- Keeps original EOA
- Native msg.sender
- Lower gas overhead (~12.5k per authorization)
- Works with any existing DeFi contract
- Requires protocol support (Type 4 tx)
ERC-4337
- Uses smart contract wallets
- Requires EntryPoint and Bundlers
- Higher validation gas (~42k+)
- Enables permissionless Paymasters
- No protocol changes
Use EIP-7702 when:
- You need single-user delegation
- You control the relayer
- You want maximum dApp compatibility
Use ERC-4337 when:
- You need public Paymasters
- You aggregate multiple users per transaction
- You require signature aggregation
EIP-7702 vs ERC-2771 (Meta-Transactions)
ERC-2771 uses a Trusted Forwarder and modifies msg.sender logic.
EIP-7702 operates at the protocol level:
- Target contracts see the real EOA
- No contract modification required
- No calldata appending tricks
This makes EIP-7702 compatible with existing DeFi protocols without rewriting contract logic.
Gas Costs of EIP-7702 Transactions
Typical additional gas components:
- ~12,500 gas per authorization
- 25,000 gas for new delegation
- Cold account access: 2,600 gas
A simple transfer may cost ~50% more.
However, batching multiple operations in one transaction often results in net gas savings compared to N separate transactions.
Where EIP-7702 Fits Best
EIP-7702 is ideal for:
- Gas sponsorship with a known operator
- Session keys
- Spending limits
- Batch execution
- Social recovery
Avoid it when you need:
- Permissionless Paymasters
- Multi-user aggregation
- Advanced signature aggregation
Architecturally, it provides a lightweight bridge between EOAs and smart contract wallets without forcing migration.
Summary
EIP-7702 brings account abstraction to EOAs without changing their identity.
It introduces:
- A new transaction type (0x04)
- An authorization_list field
- A delegation marker stored in the EOA code
- 0x05-prefixed ECDSA signing
It is implemented in Geth, compatible with existing DeFi contracts, and suitable for controlled gas sponsorship and delegation flows.
For protocols, wallet builders, and infrastructure teams, EIP-7702 represents a minimal yet powerful extension to Ethereum’s execution model — expanding what EOAs can do without replacing them.







