
Eip 7702
- 7 installs
- Updated January 29, 2026
- melonask/eip-7702-skills
Helps with ai & agent building tasks.
About
eip-7702 is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- eip-7702
- AI & Agent Building
- AI-coding skill
Eip 7702 by the numbers
- 7 all-time installs (skills.sh)
- Ranked #12,520 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/melonask/eip-7702-skills --skill eip-7702Add your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 7 |
|---|---|
| Last updated | January 29, 2026 |
| Repository | melonask/eip-7702-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
EIP-7702 Implementation Guide
Overview
EIP-7702 enables Externally Owned Accounts (EOAs) to temporarily delegate their code to a smart contract during a transaction. This allows EOAs to function as smart contracts, enabling features like:
- Sponsored Transactions: A relayer pays gas for the EOA.
- Batching: Multiple operations in one atomic transaction.
- Key Rotation/Recovery: Programmable access control.
1. Smart Contract Development
To use EIP-7702, you need an implementation contract. This contract will be the code that the EOA "borrows".
Key Requirement: The contract must handle authentication (ensure the EOA signed the intent) and replay protection (nonce), as the EIP-7702 authorization only delegates code, it doesn't inherently validate the _payload_ of the function call if anyone can call it.
Reference Implementation
A robust example supporting Batching and Sponsorship is available in assets/BatchCallAndSponsor.sol.
Features:
execute(calls, signature): For sponsored transactions. Requires an inner signature from the EOA verifying the batch and nonce.execute(calls): For direct execution (whenmsg.sender == address(this)).
2. Testing with Foundry
Foundry supports EIP-7702 via the prague EVM version and specific cheatcodes.
Key Cheatcodes:
signDelegation: Creates the EIP-7702 authorization signature.attachDelegation: Attaches the authorization to the next transaction.
See Foundry Guide for detailed test patterns and configuration. See assets/test/BatchCallAndSponsor.t.sol for a complete test suite.
3. Client Interaction (Viem v2)
Viem v2 provides first-class support for EIP-7702 via signAuthorization and sendTransaction/writeContract with authorizationList.
Best Practices:
- Use
writeContractwith strongly typed ABIs (as const) for safer interactions. - Ensure correct signing of raw hashes using
signMessage({ message: { raw: ... } }). - No experimental extensions are required in modern Viem versions.
See Viem Guide for code snippets and TypeScript patterns.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
/**
* @title BatchCallAndSponsor
* @notice An educational contract that allows batch execution of calls with nonce and signature verification.
*
* When an EOA upgrades via EIP‑7702, it delegates to this implementation.
* Off‑chain, the account signs a message authorizing a batch of calls. The message is the hash of:
* keccak256(abi.encodePacked(nonce, calls))
* The signature must be generated with the EOA’s private key so that, once upgraded, the recovered signer equals the account’s own address (i.e. address(this)).
*
* This contract provides two ways to execute a batch:
* 1. With a signature: Any sponsor can submit the batch if it carries a valid signature.
* 2. Directly by the smart account: When the account itself (i.e. address(this)) calls the function, no signature is required.
*
* Replay protection is achieved by using a nonce that is included in the signed message.
*/
contract BatchCallAndSponsor {
using ECDSA for bytes32;
/// @notice A nonce used for replay protection.
uint256 public nonce;
/// @notice Represents a single call within a batch.
struct Call {
address to;
uint256 value;
bytes data;
}
/// @notice Emitted for every individual call executed.
event CallExecuted(address indexed sender, address indexed to, uint256 value, bytes data);
/// @notice Emitted when a full batch is executed.
event BatchExecuted(uint256 indexed nonce, Call[] calls);
/**
* @notice Executes a batch of calls using an off–chain signature.
* @param calls An array of Call structs containing destination, ETH value, and calldata.
* @param signature The ECDSA signature over the current nonce and the call data.
*
* The signature must be produced off–chain by signing:
* The signing key should be the account’s key (which becomes the smart account’s own identity after upgrade).
*/
function execute(Call[] calldata calls, bytes calldata signature) external payable {
// Compute the digest that the account was expected to sign.
bytes memory encodedCalls;
for (uint256 i = 0; i < calls.length; i++) {
encodedCalls = abi.encodePacked(encodedCalls, calls[i].to, calls[i].value, calls[i].data);
}
bytes32 digest = keccak256(abi.encodePacked(nonce, encodedCalls));
bytes32 ethSignedMessageHash = MessageHashUtils.toEthSignedMessageHash(digest);
// Recover the signer from the provided signature.
address recovered = ECDSA.recover(ethSignedMessageHash, signature);
require(recovered == address(this), "Invalid signature");
_executeBatch(calls);
}
/**
* @notice Executes a batch of calls directly.
* @dev This function is intended for use when the smart account itself (i.e. address(this))
* calls the contract. It checks that msg.sender is the contract itself.
* @param calls An array of Call structs containing destination, ETH value, and calldata.
*/
function execute(Call[] calldata calls) external payable {
require(msg.sender == address(this), "Invalid authority");
_executeBatch(calls);
}
/**
* @dev Internal function that handles batch execution and nonce incrementation.
* @param calls An array of Call structs.
*/
function _executeBatch(Call[] calldata calls) internal {
uint256 currentNonce = nonce;
nonce++; // Increment nonce to protect against replay attacks
for (uint256 i = 0; i < calls.length; i++) {
_executeCall(calls[i]);
}
emit BatchExecuted(currentNonce, calls);
}
/**
* @dev Internal function to execute a single call.
* @param callItem The Call struct containing destination, value, and calldata.
*/
function _executeCall(Call calldata callItem) internal {
(bool success,) = callItem.to.call{value: callItem.value}(callItem.data);
require(success, "Call reverted");
emit CallExecuted(msg.sender, callItem.to, callItem.value, callItem.data);
}
// Allow the contract to receive ETH (e.g. from DEX swaps or other transfers).
fallback() external payable {}
receive() external payable {}
}
import { createWalletClient, http, encodeFunctionData, parseEther, createPublicClient, erc20Abi, keccak256, encodePacked, toBytes, type Address } from 'viem'
import { privateKeyToAccount, generatePrivateKey } from 'viem/accounts'
import { anvil } from 'viem/chains'
/**
* EIP-7702 Integration Test: Gasless Token Transfer
*
* Requirements:
* 1. Anvil running with Prague hardfork: `anvil --hardfork prague`
* 2. Implementation & Token contracts deployed (see Deploy.s.sol)
*/
// --- CONFIGURATION ---
// Update these addresses after running your deployment script
const IMPLEMENTATION_ADDRESS = '0x5FbDB2315678afecb367f032d93F642f64180aa3' as Address
const TOKEN_ADDRESS = '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512' as Address
const BATCH_ABI = [
{
"type": "function",
"name": "execute",
"inputs": [
{
"name": "calls",
"type": "tuple[]",
"components": [
{ "name": "to", "type": "address" },
{ "name": "value", "type": "uint256" },
{ "name": "data", "type": "bytes" }
]
},
{ "name": "signature", "type": "bytes" }
],
"outputs": [],
"stateMutability": "payable"
},
{
"type": "function",
"name": "nonce",
"inputs": [],
"outputs": [{ "name": "", "type": "uint256" }],
"stateMutability": "view"
}
] as const
async function main() {
const publicClient = createPublicClient({ chain: anvil, transport: http() })
// 1. Setup Accounts
// Alice: The EOA that will be "upgraded". Starts with 0 ETH.
const alice = privateKeyToAccount(generatePrivateKey())
// Bob: The Sponsor. Has ETH to pay for gas.
// (Using default Anvil Account #0 for gas)
const bob = privateKeyToAccount('0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80')
const bobClient = createWalletClient({ account: bob, chain: anvil, transport: http() })
// Charlie: The recipient
const charlie = privateKeyToAccount(generatePrivateKey())
console.log(`Alice: ${alice.address} (Upgrading...)`)
console.log(`Bob: ${bob.address} (Sponsoring...)`)
console.log(`Charlie: ${charlie.address} (Recipient)`)
// 2. Fund Alice with Tokens (Sponsor pays for the minting)
console.log("Minting 100 tokens to Alice...")
const mintHash = await bobClient.writeContract({
address: TOKEN_ADDRESS,
abi: [{ name: 'mint', type: 'function', inputs: [{type:'address', name:'to'}, {type:'uint256', name:'amount'}], outputs: [], stateMutability: 'nonpayable' }] as const,
functionName: 'mint',
args: [alice.address, parseEther('100')]
})
await publicClient.waitForTransactionReceipt({ hash: mintHash })
// 3. Prepare Batch Call (Alice wants to transfer 50 tokens to Charlie)
const calls = [
{
to: TOKEN_ADDRESS,
value: 0n,
data: encodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
args: [charlie.address, parseEther('50')]
})
}
]
// 4. Sign Inner Intent (Contract-level authentication)
// We sign a digest of the calls to satisfy BatchCallAndSponsor's security checks.
const nonce = 0n // Fresh EOA
let encodedCalls = '0x' as `0x${string}`
for (const call of calls) {
encodedCalls = encodePacked(
['bytes', 'address', 'uint256', 'bytes'],
[encodedCalls, call.to, call.value, call.data]
)
}
const digest = keccak256(encodePacked(['uint256', 'bytes'], [nonce, encodedCalls]))
// IMPORTANT: use message.raw to sign the digest bytes directly
const innerSignature = await alice.signMessage({
message: { raw: toBytes(digest) }
})
// 5. Sign EIP-7702 Authorization (Protocol-level delegation)
const authorization = await bobClient.signAuthorization({
contractAddress: IMPLEMENTATION_ADDRESS,
account: alice
})
// 6. Execute Sponsored Transaction
// Bob calls Alice's address. The authorizationList upgrades Alice to the implementation.
console.log("Sending Sponsored EIP-7702 Transaction...")
const txHash = await bobClient.writeContract({
abi: BATCH_ABI,
address: alice.address,
functionName: 'execute',
args: [calls, innerSignature],
authorizationList: [authorization]
})
await publicClient.waitForTransactionReceipt({ hash: txHash })
console.log(`Transaction Successful: ${txHash}`)
// 7. Verification
const balance = await publicClient.readContract({
address: TOKEN_ADDRESS,
abi: erc20Abi,
functionName: 'balanceOf',
args: [charlie.address]
})
console.log(`Charlie Final Balance: ${balance}`)
if (balance === parseEther('50')) console.log("SUCCESS: Gasless transfer confirmed.")
}
main().catch(console.error)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/BatchCallAndSponsor.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MockERC20 is ERC20 {
constructor() ERC20("Mock", "MCK") {}
function mint(address to, uint256 amount) public {
_mint(to, amount);
}
}
contract BatchCallAndSponsorTest is Test {
BatchCallAndSponsor public implementation;
MockERC20 public token;
uint256 internal constant ALICE_PK =
0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d;
address payable internal constant ALICE_ADDRESS =
payable(0x70997970C51812dc3A010C7d01b50e0d17dc79C8);
uint256 internal constant BOB_PK =
0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a;
address payable internal constant BOB_ADDRESS =
payable(0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC);
function setUp() public {
implementation = new BatchCallAndSponsor();
token = new MockERC20();
// Fund Alice with tokens but NO ETH (to simulate sponsored need)
token.mint(ALICE_ADDRESS, 1000e18);
// Bob has ETH to sponsor
vm.deal(BOB_ADDRESS, 10 ether);
}
function testDirectExecution() public {
// Alice needs some ETH for gas in direct execution
vm.deal(ALICE_ADDRESS, 1 ether);
BatchCallAndSponsor.Call[]
memory calls = new BatchCallAndSponsor.Call[](1);
calls[0] = BatchCallAndSponsor.Call({
to: address(token),
value: 0,
data: abi.encodeCall(ERC20.transfer, (BOB_ADDRESS, 100e18))
});
vm.signAndAttachDelegation(address(implementation), ALICE_PK);
vm.startPrank(ALICE_ADDRESS);
BatchCallAndSponsor(ALICE_ADDRESS).execute(calls);
vm.stopPrank();
assertEq(token.balanceOf(BOB_ADDRESS), 100e18);
}
function testSponsoredExecution() public {
// Alice has 0 ETH. Bob sponsors.
BatchCallAndSponsor.Call[]
memory calls = new BatchCallAndSponsor.Call[](1);
calls[0] = BatchCallAndSponsor.Call({
to: address(token),
value: 0,
data: abi.encodeCall(ERC20.transfer, (BOB_ADDRESS, 50e18))
});
// 1. Prepare signature for the contract logic (replay protection etc)
// Alice's nonce starts at 0 for a fresh EOA delegation
uint256 nonce = 0;
bytes memory encodedCalls;
for (uint256 i = 0; i < calls.length; i++) {
encodedCalls = abi.encodePacked(
encodedCalls,
calls[i].to,
calls[i].value,
calls[i].data
);
}
bytes32 digest = keccak256(abi.encodePacked(nonce, encodedCalls));
bytes32 ethSignedMessageHash = MessageHashUtils.toEthSignedMessageHash(
digest
);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(
ALICE_PK,
ethSignedMessageHash
);
bytes memory signature = abi.encodePacked(r, s, v);
// 2. Prepare EIP-7702 Delegation
Vm.SignedDelegation memory signedDelegation = vm.signDelegation(
address(implementation),
ALICE_PK
);
// 3. Bob executes
vm.startBroadcast(BOB_PK);
vm.attachDelegation(signedDelegation);
BatchCallAndSponsor(ALICE_ADDRESS).execute(calls, signature);
vm.stopBroadcast();
assertEq(token.balanceOf(BOB_ADDRESS), 50e18);
}
}
EIP-7702 Skills
A specialized set of skills for implementing, testing, and interacting with EIP-7702 delegated accounts. Designed for AI agents to assist in blockchain development.
Installation
You can add this skill to your project using the following command:
npx skills add melonask/eip-7702-skillsFeatures
- EIP-7702 Smart Contract: A reference implementation (
BatchCallAndSponsor.sol) that enables batch calls and sponsored transactions for EOAs. - Foundry Support: Pre-configured guides and test suites (
BatchCallAndSponsor.t.sol) using thepraguehardfork and delegation cheatcodes. - Viem v2 Integration: Step-by-step guides for signing authorizations and executing EIP-7702 transactions with the latest Viem APIs.
- Sponsorship Workflow: Complete logic for gasless token transfers where a relayer pays the gas fee for an EOA.
Usage
Once installed, the skill provides expert guidance on: 1. Deploying Implementation Contracts: Best practices for EIP-7702 compatible logic. 2. Signing Intents: How to properly sign off-chain authorizations. 3. Testing: How to simulate Pectra hardfork features in local development environments.
For detailed instructions, refer to the SKILL.md within the skill directory.
Foundry EIP-7702 Guide
Configuration
Enable the prague hardfork in foundry.toml:
[profile.default]
evm_version = "prague"Cheatcodes
Foundry provides cheatcodes to simulate EIP-7702 behavior in tests.
signDelegation
Signs an authorization for an implementation contract.
Vm.SignedDelegation memory signedDelegation = vm.signDelegation(address(implementation), privateKey);attachDelegation
Attaches a signed delegation to the next transaction.
vm.attachDelegation(signedDelegation);signAndAttachDelegation
Combines signing and attaching.
vm.signAndAttachDelegation(address(implementation), privateKey);Testing Pattern
1. Sponsored Transaction Test
function testSponsoredExecution() public {
// 1. Sign Delegation (Alice authorizes Implementation)
Vm.SignedDelegation memory signedDelegation = vm.signDelegation(address(implementation), ALICE_PK);
// 2. Broadcast as Sponsor (Bob)
vm.startBroadcast(BOB_PK);
// 3. Attach Delegation
vm.attachDelegation(signedDelegation);
// 4. Call function on Alice's address (which is now delegated)
// Note: You cast Alice's address to the Implementation interface
BatchCallAndSponsor(ALICE_ADDRESS).execute(calls, signature);
vm.stopBroadcast();
}2. Direct Execution Test
function testDirectExecution() public {
// 1. Sign & Attach (Alice authorizes Implementation for her own tx)
vm.signAndAttachDelegation(address(implementation), ALICE_PK);
// 2. Prank/Broadcast as Alice
vm.startPrank(ALICE_ADDRESS);
// 3. Call function on Alice's address
BatchCallAndSponsor(ALICE_ADDRESS).execute(calls);
vm.stopPrank();
}Viem v2 EIP-7702 Guide
Overview
EIP-7702 allows EOAs to designate a Smart Contract as their "implementation". In Viem v2, this is handled via the authorizationList property in transactions.
1. Setup Client
Initialize a WalletClient with an account. No experimental extensions are needed in Viem v2.23+.
import { createWalletClient, http } from "viem";
import { sepolia } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
const relay = privateKeyToAccount("0x...");
export const walletClient = createWalletClient({
account: relay,
chain: sepolia,
transport: http(),
});2. Sign Authorization
The EOA (account) must sign an authorization to designate the contract.
import { type Address } from "viem";
const eoa = privateKeyToAccount("0x...");
const implementation = "0x..." as Address;
const authorization = await walletClient.signAuthorization({
account: eoa,
contractAddress: implementation,
});3. Signing Inner Payloads (Raw Bytes)
If your implementation contract requires an inner signature and expects an Ethereum Signed Message, use signMessage with the message.raw property.
import { toBytes, keccak256 } from "viem";
const digest = keccak256("0x1234...");
const signature = await eoa.signMessage({
message: { raw: toBytes(digest) }
});4. Execute Contract Write (Sponsored)
A sponsor can execute a transaction on behalf of the EOA by passing the authorizationList.
const hash = await walletClient.writeContract({
abi,
address: eoa.address,
authorizationList: [authorization],
functionName: "execute",
args: [calls, signature],
});Full Integration Example
For a complete, runnable example using Bun and Anvil, refer to the bundled asset: assets/test-gasless.ts
This script demonstrates:
- Deploying mock tokens.
- Alice (EOA) signing authorizations.
- Bob (Sponsor) paying for Alice's token transfer.
- Proper TypeScript typing and raw byte signing.