Hand of anonymous female on touchpad of laptop while surfing internet lying on mat at home during workout

Exploring EIP-7702: Practical Applications and Hands-on Tutorial

Git Repo

viem project: https://github.com/songb2/ts-eip7702
foundry project: https://github.com/songb2/eip7702

Introduction

EIP-7702 introduces account delegation on Ethereum, allowing externally owned accounts (EOAs) to delegate certain operations to smart contracts or relayers. This feature enables gas sponsorship, meta-transactions, and more flexible account management, improving developer experience and user onboarding.

In this article, we will explore the concept of EIP-7702, its practical applications, and provide a step-by-step hands-on tutorial using Viem and a local Anvil fork.


What is EIP-7702?

Traditionally, EOAs directly execute transactions and pay gas fees. With EIP-7702:

  • EOAs can authorize a smart contract or relayer to act on their behalf.
  • The relay account can pay gas, allowing the EOA to perform actions without holding ETH.
  • Events emitted from delegated operations still appear under the original EOA, maintaining transparency.

This enables:

  • Meta-transactions (users interact with dApps without owning ETH)
  • Flexible account management in multi-sig or contract wallets
  • Gasless onboarding for new users

Practical Use Cases

  1. Gasless Transactions
    • Users can sign an authorization, and relayers execute the transaction on their behalf.
    • Ideal for onboarding users who have no ETH in their wallets.
  2. Delegated Account Management
    • EOAs can delegate certain smart contract interactions to trusted contracts.
    • Useful in DeFi, gaming, or NFT minting platforms.
  3. Meta-Transactions in dApps
    • Enable interactions without requiring users to pay gas.
    • Can be combined with token-gated or subscription-based platforms.

Hands-on Tutorial: Using EIP-7702 Locally

Step 1: Create a foundry project and start a Local Anvil Fork

anvil --mnemonic "test test test test test test test test test test test junk" --fork-url https://reth-ethereum.ithaca.xyz/rpc

This creates a local Ethereum fork of the mainnet. You can use Anvil’s accounts as EOAs and relayers.


Step 2: Deploy a Sample Delegation Contract

https://github.com/songb2/eip7702/blob/master/src/Delegation.sol

// Delegation.sol
pragma solidity ^0.8.20;
 
contract Delegation {
  event Log(string message);
 
  function initialize() external payable {
    emit Log('Hello, world!');
  }
 
  function ping() external {
    emit Log('Pong!');
  }
}

Deploy this contract on the local Anvil node.


Step 3: Create viem project and Configure Viem Clients

// config.ts
import { createPublicClient, createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
 
export const relay = privateKeyToAccount('0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80')

// local customiezed fork chain (Chain ID = 1)
const anvilFork = {
    id: 1,
    name: 'Anvil Mainnet Fork',
    network: 'anvil',
    nativeCurrency: {
      decimals: 18,
      name: 'Ether',
      symbol: 'ETH',
    },
    rpcUrls: {
      default: { http: ['http://127.0.0.1:8545'] },
    },
  }
  
export const walletClient = createWalletClient({
  account: relay,
  chain: anvilFork,
  transport: http('http://127.0.0.1:8545'),
})

// query state/event
export const publicClient = createPublicClient({
    chain: anvilFork,
    transport: http('http://127.0.0.1:8545'),
  })

Step 4: Define contract abi

// contract.ts
export const abi = [
    {
      "type": "function",
      "name": "initialize",
      "stateMutability": "payable",
      "inputs": [],
      "outputs": []
    },
    {
      "type": "function",
      "name": "ping",
      "stateMutability": "nonpayable",
      "inputs": [],
      "outputs": []
    },
    {
      "type": "event",
      "name": "Log",
      "inputs": [{ "name": "message", "type": "string", "indexed": false }],
      "anonymous": false
    }
  ]
// Deployed EIP-7702 Contract Address on Anvil Mainnet Fork
  export const contractAddress = '0xD946246695A9259F3B33a78629026F61B3Ab40aF'

Step 5: Delegate and Execute Transactions

// example.ts
import { privateKeyToAccount } from 'viem/accounts'
import { publicClient, walletClient } from './config'
import { abi, contractAddress } from './contract'
import { decodeEventLog } from 'viem'

// EOA to be delegated – private key from Anvil accounts, for example account[1] from Anvil
const eoa = privateKeyToAccount(
  '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d'
)

async function main() {
  // 1️⃣ Sign authorization (EOA → Delegation contract)
  const authorization = await walletClient.signAuthorization({
    account: eoa,
    contractAddress,
  })
  console.log('Authorization signed ✅')

  // 2️⃣ Execute initialize() on Delegation via EOA
  const initTxHash = await walletClient.writeContract({
    abi,
    address: eoa.address,          // delegatee address
    authorizationList: [authorization],
    functionName: 'initialize',
  })
  console.log('Initialize Tx hash:', initTxHash)

  // Wait for transaction receipt
  const initReceipt = await publicClient.waitForTransactionReceipt({ hash: initTxHash })
  console.log('Initialize Tx receipt:', initReceipt)

  // Fetch and decode logs
  const initLogs = await publicClient.getLogs({
    address: eoa.address,
    fromBlock: initReceipt.blockNumber,
    toBlock: initReceipt.blockNumber,
  })

  const logEventAbi = abi.find(item => item.type === 'event' && item.name === 'Log')
  if (!logEventAbi) throw new Error('Log event ABI not found')

  console.log('--- Initialize() Decoded Logs ---')
  for (const log of initLogs) {
    const decoded = decodeEventLog({
      abi: [logEventAbi],
      data: log.data,
      topics: log.topics,
    })
    console.log(decoded)  // { message: "Hello, world!" }
  }

  // 3️⃣ Call ping() — no authorization required
  const pingTxHash = await walletClient.writeContract({
    abi,
    address: eoa.address,
    functionName: 'ping',
  })
  console.log('Ping Tx hash:', pingTxHash)

  // Wait for ping transaction receipt
  const pingReceipt = await publicClient.waitForTransactionReceipt({ hash: pingTxHash })

  // Fetch and decode ping logs
  const pingLogs = await publicClient.getLogs({
    address: eoa.address,
    fromBlock: pingReceipt.blockNumber,
    toBlock: pingReceipt.blockNumber,
  })

  console.log('--- ping() Decoded Logs ---')
  for (const log of pingLogs) {
    const decoded = decodeEventLog({
      abi: [logEventAbi],
      data: log.data,
      topics: log.topics,
    })
    console.log(decoded)  // { message: "Pong!" }
  }
}

main().catch(console.error)

Conclusion

EIP-7702 opens up new possibilities for gasless transactions, delegated operations, and better user onboarding in Ethereum dApps. Using Viem and a local Anvil fork, developers can experiment and integrate delegated accounts into their applications safely and efficiently.


💡 Tip: You can combine this with relayers or meta-transaction frameworks to build fully gasless dApps or enhanced UX for new Ethereum users.

Reference: https://viem.sh/docs/eip7702

Subscribe for New Articles!

Leave a Comment

Your email address will not be published. Required fields are marked *