Skip to main content

EIP-7702 Developer Guide

Introduction

EIP-7702, part of Ethereum's account abstraction roadmap, was introduced in the Pectra hard fork. It allows existing EOAs to adopt smart contract features by delegating control to a designated contract.

Regular EOA wallets upgrade to EIP-7702 smart accounts by authorizing a contract. The upgraded wallet has a "delegation indicator" that points to the authorized contract. When a transaction is sent to the EOA, it executes the code of the authorized contract. To downgrade back to a regular EOA, the wallet authorizes the burn address.

This upgrade allows users to:

  • Batch transactions
  • Use session keys
  • Sponsor gas
  • Pay with ERC20s
  • Enjoy chain abstraction
  • Use passkeys

All while retaining their familiar EOA address and without migrating funds.

Key Benefits

  1. Gas efficiency – No deployment required.
  2. Seamless UX – Enjoy AA features directly.
  3. Multichain support – Reuse authorizations across chains.
  4. Flexible integration – Simple to integrate for devs.
  5. Custom logic – Not limited to a fixed wallet type.

Technical Deep Dive

EIP-7702 complements ERC-4337, introducing a new transaction type 0x04 (SET_CODE_TX_TYPE) that lets EOAs delegate execution.

Delegation and Authorization

  • Delegation: EOA chooses a smart contract for execution.
  • Authorization: Signed message with chain ID, contract address, nonce, and signature.

New Transaction Type

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])

// authorization_list = [[chain_id, address, nonce, y_parity, r, s], ...]

authorization_list

  • address is the delegated implementation.
  • Signer (the EOA) is derived from signature + payload.

Type 4 Behavior

For each tuple:

  1. Validate chain_id, nonce.
  2. Derive authority = ecrecover(keccak(0x05 || rlp(...))).
  3. Add authority to accessed_addresses.
  4. Ensure code is empty or already delegated.
  5. Ensure nonce matches.
  6. Add gas refund if applicable.
  7. Set code: 0xef0100 || address (delegation designation).
  8. If address == 0x0, clear code.
  9. Bump nonce.

Delegation is permanent unless explicitly revoked.


Resources

  1. Viem Guide
  2. BatchCallAndSponsor.sol
  3. MetaMask Delegator
  4. Awesome EIP-7702
  5. EIP-7702 Portal
  6. EIP-7851
  7. Privy React Recipe
  8. ZeroDev Blog
  9. ERC-5792 Demo
  10. Stats Dashboard
  11. Fusion Module Alt
  12. Stats
  13. Adoption Tracker

For Devs: Using EIP-7702

Prerequisites

npm i @startale-scs/aa-sdk viem

Generate credentials on SCS Portal. Retrieve:

  • bundlerUrl
  • paymasterUrl
  • paymasterId
  • implementationAddress
Flow Considerations

This guide applies to embedded EOA wallet flows only. External wallets like MetaMask have restrictions:

  • External wallets (MetaMask, etc.) only allow delegation to their own smart account implementations
  • Embedded wallets (Privy, etc.) provide full control over delegation targets

For web2 user onboarding: Use this EIP-7702 flow to upgrade EOAs to Startale smart accounts

For MetaMask users: Use the regular flow without EIP-7702, making MetaMask signer the main controller of the deployed Startale smart account

Setup

import { http, createPublicClient, createWalletClient, privateKeyToAccount, generatePrivateKey, encodeFunctionData } from "viem";
import { createSCSPaymasterClient, createSmartAccountClient, toStartaleSmartAccount } from "@startale-scs/aa-sdk";
import { soneiumMinato } from "viem/chains";

const signer = privateKeyToAccount(generatePrivateKey());
const chain = soneiumMinato;

const publicClient = createPublicClient({ transport: http(), chain });
const walletClient = createWalletClient({ account: signer, chain, transport: http() });
const paymaster = createSCSPaymasterClient({ transport: http(paymasterUrl) });
const scsContext = { calculateGasLimits: true, paymasterId };
What is set code transaction?

A transaction where Authorizations are executed onchain, thereby upgrading EOAs to smart accounts. One set code txn can contain multiple Authorizations.

Manual Authorization

// put below in .env
// STARTALE_ACCOUNT_IMPLEMENTATION_ADDRESS=0x000000b8f5f723A680d3D7EE624Fe0bC84a6E05A
const implementationAddress = process.env.STARTALE_ACCOUNT_IMPLEMENTATION_ADDRESS as Address;
const authorization = await walletClient.signAuthorization({ contractAddress: implementationAddress });

const smartAccountClient = createSmartAccountClient({
account: await toStartaleSmartAccount({
signer,
chain,
transport: http(),
accountAddress: eoaAddress,
eip7702Auth: authorization,
}),
transport: http(bundlerUrl),
client: publicClient,
paymaster,
paymasterContext: scsContext,
});

Automatic Authorization

const smartAccountClient = createSmartAccountClient({
account: await toStartaleSmartAccount({
signer,
chain,
transport: http(),
accountAddress: eoaAddress,
eip7702Account: signer,
}),
transport: http(bundlerUrl),
client: publicClient,
paymaster,
paymasterContext: scsContext,
});

Sending Transactions

const callData = encodeFunctionData({ abi: CounterAbi, functionName: "count" });

const hash = await smartAccountClient.sendUserOperation({
calls: [{ to: counterContract, value: 0n, data: callData }],
});

const receipt = await smartAccountClient.waitForUserOperationReceipt({ hash });
console.log("receipt", receipt);

const isDelegated = await smartAccountClient.account.isDelegated();
console.log("isDelegated", isDelegated);

Helpers

const isDelegated = await smartAccountClient.account.isDelegated();

const tx = await smartAccountClient.account.unDelegate();
const unDelegateReceipt = await publicClient.waitForTransactionReceipt({ hash: tx });

External Wallets

Expect restrictions on which smart account implementations are supported. Likely flows:

  1. Prompt user to upgrade with pre-approved contract
  2. Limited to wallet provider’s implementation

Embedded Wallets

Dapps integrate against their own embedded wallets with full feature support. No dependency on external wallet compatibility.


Tutorial

How to upgrade an EOA to a Startale smart account and batch-mint NFTs gaslessly

// Follow steps from setup and authorization

import { http, createPublicClient, createWalletClient, privateKeyToAccount, generatePrivateKey } from "viem";
import { createSCSPaymasterClient, createSmartAccountClient, toStartaleSmartAccount } from "@startale-scs/aa-sdk";

const signer = privateKeyToAccount(generatePrivateKey());
const chain = soneiumMinato;

// Get this from SCS portal dashboard and put in env
const paymasterId = process.env.PAYMASTER_ID;

const publicClient = createPublicClient({ transport: http(), chain });
const walletClient = createWalletClient({ account: signer, chain, transport: http() });
const paymaster = createSCSPaymasterClient({ transport: http(paymasterUrl) });
const scsContext = { calculateGasLimits: true, paymasterId };

// Startale NFT contract address deployed on Soneium soneiumMinato
// NFT_CONTRACT_ADDRESS=0xa37f9d4E7E296C7eda4cB711738595B5f19AF8A7
const nftContract = process.env.NFT_CONTRACT_ADDRESS as Address;

// full NFT contract abi is here: https://pastebin.com/zscdV3cF
const NFTAbi = [
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "safeMint",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
];


// put below in .env
// STARTALE_ACCOUNT_IMPLEMENTATION_ADDRESS=0x000000b8f5f723A680d3D7EE624Fe0bC84a6E05A
const implementationAddress = process.env.STARTALE_ACCOUNT_IMPLEMENTATION_ADDRESS as Address;
const authorization = await walletClient.signAuthorization({ contractAddress: implementationAddress });

const smartAccountClient = createSmartAccountClient({
account: await toStartaleSmartAccount({
signer,
chain,
transport: http(),
accountAddress: eoaAddress,
eip7702Auth: authorization,
}),
transport: http(bundlerUrl),
client: publicClient,
paymaster,
paymasterContext: scsContext,
});

const mintNFTCallData = encodeFunctionData({
abi: NFTAbi,
functionName: "safeMint",
args: [smartAccountClient.account.address] // Receiver address is SA address itself
});

const hash = await smartAccountClient.sendUserOperation({
calls: [
{
to: nftContract as Address,
value: BigInt(0),
data: mintNFTCallData,
},
{
to: nftContract as Address,
value: BigInt(0),
data: mintNFTCallData,
},
],
// No need to pass anything else separately
});

const receipt = await smartAccountClient.waitForUserOperationReceipt({ hash });
console.log("receipt", receipt);

After above action, check your EOA address on block explorer it would show it as EOA + Smart account code and containing multiple NFTs which we batch minted without paying gas.