EIP‑7702
Status: Live on Gnosis mainnet since 7 May 2025 (Pectra).
Transaction Type:0x04
– set‑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.