Skip to content

Instantly share code, notes, and snippets.

@wolovim
Last active May 7, 2025 14:25
Show Gist options
  • Save wolovim/4c4a9bd66a60c5f98e25d652930c84c2 to your computer and use it in GitHub Desktop.
Save wolovim/4c4a9bd66a60c5f98e25d652930c84c2 to your computer and use it in GitHub Desktop.
web3.py EIP-7702 worked example
COUNTER_ADDRESS = "0x5bC6068195306fC5148f125D7dFcF525185813E6"
COUNTER_ABI = """[
{
"stateMutability": "nonpayable",
"type": "function",
"name": "set_number",
"inputs": [
{
"name": "new_number",
"type": "uint256"
}
],
"outputs": []
},
{
"stateMutability": "nonpayable",
"type": "function",
"name": "lock",
"inputs": [],
"outputs": []
},
{
"stateMutability": "nonpayable",
"type": "function",
"name": "unlock",
"inputs": [],
"outputs": []
},
{
"stateMutability": "nonpayable",
"type": "function",
"name": "get_number",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256"
}
]
},
{
"stateMutability": "view",
"type": "function",
"name": "number",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256"
}
]
},
{
"stateMutability": "view",
"type": "function",
"name": "locked",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bool"
}
]
},
{
"stateMutability": "nonpayable",
"type": "constructor",
"inputs": [
{
"name": "initial_number",
"type": "uint256"
}
],
"outputs": []
}
]"""
MULTICALL_ADDRESS = "0xca11bde05977b3631167028862be2a173976ca11"
MULTICALL_ABI = '[{"inputs":[{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct Multicall3.Call[]","name":"calls","type":"tuple[]"}],"name":"aggregate","outputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"bytes[]","name":"returnData","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bool","name":"allowFailure","type":"bool"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct Multicall3.Call3[]","name":"calls","type":"tuple[]"}],"name":"aggregate3","outputs":[{"components":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"returnData","type":"bytes"}],"internalType":"struct Multicall3.Result[]","name":"returnData","type":"tuple[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bool","name":"allowFailure","type":"bool"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct Multicall3.Call3Value[]","name":"calls","type":"tuple[]"}],"name":"aggregate3Value","outputs":[{"components":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"returnData","type":"bytes"}],"internalType":"struct Multicall3.Result[]","name":"returnData","type":"tuple[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct Multicall3.Call[]","name":"calls","type":"tuple[]"}],"name":"blockAndAggregate","outputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"bytes32","name":"blockHash","type":"bytes32"},{"components":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"returnData","type":"bytes"}],"internalType":"struct Multicall3.Result[]","name":"returnData","type":"tuple[]"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"getBasefee","outputs":[{"internalType":"uint256","name":"basefee","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"name":"getBlockHash","outputs":[{"internalType":"bytes32","name":"blockHash","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBlockNumber","outputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getChainId","outputs":[{"internalType":"uint256","name":"chainid","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockCoinbase","outputs":[{"internalType":"address","name":"coinbase","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockDifficulty","outputs":[{"internalType":"uint256","name":"difficulty","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockGasLimit","outputs":[{"internalType":"uint256","name":"gaslimit","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockTimestamp","outputs":[{"internalType":"uint256","name":"timestamp","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getEthBalance","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastBlockHash","outputs":[{"internalType":"bytes32","name":"blockHash","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"requireSuccess","type":"bool"},{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct Multicall3.Call[]","name":"calls","type":"tuple[]"}],"name":"tryAggregate","outputs":[{"components":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"returnData","type":"bytes"}],"internalType":"struct Multicall3.Result[]","name":"returnData","type":"tuple[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bool","name":"requireSuccess","type":"bool"},{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct Multicall3.Call[]","name":"calls","type":"tuple[]"}],"name":"tryBlockAndAggregate","outputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"bytes32","name":"blockHash","type":"bytes32"},{"components":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"returnData","type":"bytes"}],"internalType":"struct Multicall3.Result[]","name":"returnData","type":"tuple[]"}],"stateMutability":"payable","type":"function"}]'
# 1. import dependencies
from eth_account import Account
from dotenv import load_dotenv
import os
from web3 import Web3, HTTPProvider
from artifacts import MULTICALL_ABI, MULTICALL_ADDRESS, COUNTER_ADDRESS, COUNTER_ABI
import json
load_dotenv()
MULTICALL_ADDRESS = Web3.to_checksum_address(MULTICALL_ADDRESS)
MULTICALL_ABI = json.loads(MULTICALL_ABI)
COUNTER_ADDRESS = Web3.to_checksum_address(COUNTER_ADDRESS)
COUNTER_ABI = json.loads(COUNTER_ABI)
HOODI_CHAIN_ID = 560048
# 2. instantiate w3
w3 = Web3(HTTPProvider("https://ethereum-hoodi-rpc.publicnode.com"))
# 3. check the starting state of the contract
counter_contract = w3.eth.contract(address=COUNTER_ADDRESS, abi=COUNTER_ABI)
locked = counter_contract.functions.locked().call()
number = counter_contract.functions.number().call()
print(f"Counter state: locked={locked}, number={number}")
# 4. load EOA account (with Hoodi testnet ETH) from private key
acct = Account.from_key(os.getenv("TEST_PK"))
acct_code = w3.eth.get_code(acct.address)
nonce = w3.eth.get_transaction_count(acct.address)
print(f"Account code before: {acct_code.hex()}")
print(f"Account nonce before: {nonce}")
# 5. build an authorization utilizing the multicall contract
auth = {
"chainId": HOODI_CHAIN_ID,
"address": MULTICALL_ADDRESS,
"nonce": nonce + 1,
}
# 6. sign the auth with the EOA
signed_auth = acct.sign_authorization(auth)
# 7. build and encode the data to provide to aggregate function
def calculate_multicall_data():
multicall_contract = w3.eth.contract(address=MULTICALL_ADDRESS, abi=MULTICALL_ABI)
counter_contract = w3.eth.contract(address=COUNTER_ADDRESS, abi=COUNTER_ABI)
lock_calldata = counter_contract.encode_abi("lock")
unlock_calldata = counter_contract.encode_abi("unlock")
new_number = 6
set_number_calldata = counter_contract.encode_abi("set_number", args=[new_number])
# Structure the calls for the aggregate function
# aggregate((address target, bytes callData)[])
calls = [
(COUNTER_ADDRESS, unlock_calldata),
(COUNTER_ADDRESS, set_number_calldata),
(COUNTER_ADDRESS, lock_calldata),
]
# Encode the outer call data for the aggregate function
return multicall_contract.encode_abi("aggregate", args=[calls])
# 8. build the type 4 transaction
tx = {
"chainId": HOODI_CHAIN_ID,
"nonce": nonce,
"gas": 1_000_000,
"maxFeePerGas": 10**10,
"maxPriorityFeePerGas": 10**9,
"to": acct.address,
"authorizationList": [signed_auth],
"data": calculate_multicall_data(),
}
# 9. sign and send the transaction
signed_tx = acct.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction)
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
# 10. verify the results
counter_contract = w3.eth.contract(address=COUNTER_ADDRESS, abi=COUNTER_ABI)
current_number = counter_contract.functions.number().call()
is_locked = counter_contract.functions.locked().call()
print(f"Counter state after: locked={is_locked}, number={current_number}")
new_acct_code = w3.eth.get_code(acct.address)
new_nonce = w3.eth.get_transaction_count(acct.address)
print(f"Account code after: {new_acct_code.hex()}")
print(f"Account nonce after: {new_nonce}")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment