Skip to main content

EIP‑7702

Status: Live on Gnosis mainnet since 7 May 2025 (Pectra).
Transaction Type: 0x04set‑code. EIP‑7702 lets any Externally‑Owned Account (EOA) attach byte‑code for the duration of a single transaction. In practice, this means:

  • Batch / atomic multi‑calls
  • Sponsored gas (paymasters)
  • Session‑keys & delegated rights
  • Smooth account‑abstraction UX without permanent smart‑wallet deployment

1. Transaction Anatomy

type = 0x04
to = <EOA address (self)>
data = <byte‑code OR ABI‑encoded delegatecall>
authorizationList[] = signed structs binding code + gas limits

During execution the EOA temporarily holds code, executes, then the EVM auto‑reverts to a pure EOA.

2. Local Dev Setup

# Node.js ≥ 22
npm i -g hardhat@next # Hardhat 3 Alpha
mkdir eip7702-demo && cd $_
npm init -y
npx hardhat@next --init # enable ESM, Node test runner, Viem
npm i viem dotenv @openzeppelin/contracts

.env

RPC_URL=https://rpc.gnosis.gateway.fm
PRIVATE_KEY=<EOA_PRIVATE_KEY>
RELAY_KEY=<OPTIONAL_RELAY_PRIVATE_KEY>

3. Example Contracts

3.1 Delegation.sol

// SPDX‑License‑Identifier: MIT
pragma solidity ^0.8.20;
contract Delegation {
event Log(string message);
function initialize() external payable {
emit Log("Hello, world!");
}
function ping() external pure {
emit Log("Pong!");
}
}

3.2 Optional Helpers

Add the usual Counter.sol, ERC20Token.sol, or a minimal SimpleDelegate.sol proxy if you want to experiment with batch calls. Deploy via a Hardhat script.


4. Sending EIP‑7702 Transactions

This flow designates a contract on‑the‑fly and invokes initialize() in the same tx.

4.1 config.ts

import { createWalletClient, http } from "viem";
import { gnosis } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
import { eip7702Actions } from "viem/experimental";
export const relay = privateKeyToAccount(process.env.RELAY_KEY!);
export const walletClient = createWalletClient({
account: relay, // relayer sponsors gas
chain: gnosis,
transport: http(process.env.RPC_URL!)
}).extend(eip7702Actions());

4.2 sendTx.ts

import { encodeFunctionData } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { walletClient } from "./config";
import { abi, contractAddress } from "./contract";
const eoa = privateKeyToAccount(process.env.PRIVATE_KEY!); // delegator
// 1. Sign authorization binding contract → EOA
const authorization = await walletClient.signAuthorization({
account: eoa,
contractAddress
});
// 2. Broadcast 0x04 transaction
const txHash = await walletClient.sendTransaction({
authorizationList: [authorization],
to: eoa.address, // self-call
data: encodeFunctionData({
abi,
functionName: "initialize"
})
});
console.log("tx sent:", txHash);

Self‑Executed Variant

If the same EOA pays gas, add executor: "self" when signing:

const authorization = await walletClient.signAuthorization({
account: eoa,
contractAddress,
executor: "self"
});

5. Contract Writes with EIP‑7702

writeContract() is syntactic sugar for the pattern above — perfect for batch calls.

5.1 writeContract.ts

import { walletClient } from "./config";
import { abi, contractAddress } from "./contract";
import { privateKeyToAccount } from "viem/accounts";
const eoa = privateKeyToAccount(process.env.PRIVATE_KEY!);
// Authorize once
const auth = await walletClient.signAuthorization({
account: eoa,
contractAddress
});
// Designate + call initialize()
await walletClient.writeContract({
abi,
address: eoa.address, // target is SELF
authorizationList: [auth],
functionName: "initialize"
});
// Later calls need **no** authorization
await walletClient.writeContract({
abi,
address: eoa.address,
functionName: "ping"
});

5.2 Batch Transfer Example

import { encodeFunctionData, parseEther } from "viem";
const batchData = encodeFunctionData({
abi,
functionName: "multiSend",
args: [[
{ to: "0xRecipient1", value: parseEther("0.01"), data: "0x" },
{ to: "0xRecipient2", value: parseEther("0.02"), data: "0x" }
]]
});
await walletClient.sendTransaction({
authorizationList: [auth],
to: eoa.address,
data: batchData
});

6. Best Practices

  • Replay‑safe: Ensure authorization.nonce == tx.nonce + 1 (executor: "self" handles this).
  • Code size ≤ 24576 bytes or the tx will revert.
  • Audit everything – attached code runs with EOA’s permissions.
  • Gas caps – always set sensible maxFeePerGas when using paymasters.