LPsLux Proposals
LP-3670

EOA Account Code (EIP-7702)

Review

Set code for EOAs enabling smart account features without migration

Category
Core
Created
2025-01-23

LP-3670: EOA Account Code (EIP-7702)

Abstract

This LP enables EOAs (Externally Owned Accounts) to temporarily delegate to smart contract code, providing smart account features without permanent migration. Compatible with EIP-7702 from the Pectra upgrade.

Motivation

Account abstraction requires smart contract wallets, but:

  • Users have assets in EOAs
  • Migration is complex and risky
  • Gas sponsorship needs contract logic

EIP-7702 allows EOAs to act as smart accounts temporarily:

  • No migration required
  • Backwards compatible
  • Enables batching, sponsorship, permissions

Specification

New Transaction Type (0x04)

type SetCodeTransaction struct {
    ChainID     *big.Int
    Nonce       uint64
    MaxPriorityFeePerGas *big.Int
    MaxFeePerGas *big.Int
    Gas         uint64
    To          *common.Address
    Value       *big.Int
    Data        []byte
    AccessList  AccessList
    
    // NEW: Code delegation authorizations
    AuthorizationList []Authorization
}

type Authorization struct {
    ChainID   uint256
    Address   common.Address  // Contract to delegate to
    Nonce     uint64
    V, R, S   *big.Int        // Signature from EOA
}

Authorization Signature

EOAs sign authorization to delegate:

func SignAuthorization(
    chainID *big.Int,
    contractAddress common.Address,
    nonce uint64,
    privateKey *ecdsa.PrivateKey,
) (v, r, s *big.Int, err error) {
    // MAGIC prefix for domain separation
    message := []byte{0x05}
    message = append(message, rlpEncode(chainID, contractAddress, nonce)...)
    
    hash := crypto.Keccak256Hash(message)
    return sign(hash, privateKey)
}

Account State

During transaction execution:

func (evm *EVM) ProcessSetCodeTx(tx *SetCodeTransaction) error {
    for _, auth := range tx.AuthorizationList {
        // Verify signature
        signer := recoverSigner(auth)
        
        // Check nonce matches EOA's current nonce
        if evm.StateDB.GetNonce(signer) != auth.Nonce {
            continue // Skip invalid
        }
        
        // Set delegation for this transaction
        code := evm.StateDB.GetCode(auth.Address)
        evm.StateDB.SetCode(signer, code)
        
        // Increment nonce
        evm.StateDB.SetNonce(signer, auth.Nonce + 1)
    }
    
    // Execute transaction with EOA having contract code
    return evm.Call(...)
}

Delegation Persistence

Code delegation persists until:

  • EOA sends another transaction (nonce increment)
  • Explicit revocation via empty authorization
// Revoke delegation
auth := Authorization{
    ChainID:  chainID,
    Address:  common.Address{}, // Zero address = revoke
    Nonce:    currentNonce,
}

Gas Costs

OperationGas
Authorization (cold)2500
Authorization (warm)100
Per authorization in list12500

Use Cases

Batched Transactions

// Smart contract code that EOA delegates to
contract BatchExecutor {
    function execute(Call[] calldata calls) external {
        for (uint i = 0; i < calls.length; i++) {
            calls[i].target.call(calls[i].data);
        }
    }
}

Gas Sponsorship

contract SponsoredExecution {
    function executeSponsored(
        address sponsor,
        bytes calldata userOp,
        bytes calldata sponsorSig
    ) external {
        // Verify sponsor signature
        // Execute user operation
        // Sponsor pays gas
    }
}

Rationale

EIP-7702 chosen over EIP-3074:

  • More flexible (any contract code)
  • Better security (explicit authorization)
  • Simpler implementation

Backwards Compatibility

  • EOAs still function normally
  • New tx type ignored by old nodes
  • No state format changes

Security Considerations

  • Authorization replay prevented by nonce
  • Chain ID binding prevents cross-chain replay
  • Revocation available via empty authorization

References

Copyright and related rights waived via CC0.