Use this file to discover all available pages before exploring further.
This guide walks through applying EIP-7702 to an EOA and using the delegation for three patterns: batch payments, spending limits, and session keys. The EOA keeps its address and private key throughout.
Concept: For what EIP-7702 enables on Stable and the security considerations, see EIP-7702.
EIP-7702 introduces a new transaction type (0x04) that carries an authorizationList. Each authorization designates a smart contract whose code the EOA will execute for that transaction. The flow is:
Choose or deploy a delegate contract: a standard Solidity contract implementing the logic you want EOAs to use. You can use a deployed contract or deploy your own. Use an audited contract whenever possible.
Sign an authorization: the EOA owner signs a message authorizing the delegate contract.
Submit an EIP-7702 transaction: the transaction includes the authorization, and the EOA runs the delegate’s code during execution.
The steps below walk through this flow using Multicall3 as the delegate contract. Multicall3 is a widely deployed utility contract that aggregates multiple calls into a single transaction. By designating Multicall3 as the EIP-7702 delegate, an EOA can batch arbitrary contract interactions (token transfers, approvals, contract reads, or any combination) into one atomic transaction. Batch payments are one example: instead of sending ten separate transactions for a payroll run, the EOA executes them all at once.
Multicall3 is deployed at 0xcA11bde05977b3631167028862bE2a173976CA11 on Stable. Since it’s already deployed and widely used, you don’t need to deploy your own delegate. Signing an EIP-7702 authorization grants the delegate full execution authority over your EOA.
// Multicall3 interface (relevant functions only)interface IMulticall3 { struct Call3 { address target; bool allowFailure; bytes callData; } struct Result { bool success; bytes returnData; } /// @notice Aggregate calls, allowing each to succeed or fail independently function aggregate3(Call3[] calldata calls) external payable returns (Result[] memory returnData);}
Combine the authorization with a call to Multicall3.aggregate3. This example batches three USDT0 transfers in a single transaction.
import { ethers } from "ethers";import { wallet, USDT0_ADDRESS } from "./config";import { signAuthorization } from "./signAuthorization";const usdt0Interface = new ethers.Interface([ "function transfer(address to, uint256 amount)",]);const batchInterface = new ethers.Interface([ "function aggregate3((address target, bool allowFailure, bytes callData)[] calls) returns ((bool success, bytes returnData)[])",]);async function main() { const recipients = [ { to: "0xAlice...", amount: ethers.parseUnits("100", 6) }, { to: "0xBob...", amount: ethers.parseUnits("200", 6) }, { to: "0xCarol...", amount: ethers.parseUnits("150", 6) }, ]; const batchData = batchInterface.encodeFunctionData("aggregate3", [ recipients.map(({ to, amount }) => ({ target: USDT0_ADDRESS, allowFailure: false, callData: usdt0Interface.encodeFunctionData("transfer", [to, amount]), })), ]); const signedAuth = await signAuthorization(); const tx = await wallet.sendTransaction({ type: 4, // EIP-7702 transaction type to: wallet.address, // call is directed at the EOA itself data: batchData, // aggregate3 call to execute authorizationList: [signedAuth], maxPriorityFeePerGas: 0n, }); const receipt = await tx.wait(1); console.log("Batch transactions executed in tx:", receipt.hash);}
Batch transactions executed in tx: 0x...
The EOA executes all three calls in a single atomic transaction via Multicall3.aggregate3. The delegation persists until explicitly changed or cleared. While this example shows batch payments, the same pattern works for any combination of contract calls.
Session keys allow a dApp to execute transactions on behalf of an EOA within scoped permissions: a time window and an allowed set of target contracts. This is useful for dApps where frequent on-chain interactions would otherwise require repeated wallet approvals.
// SPDX-License-Identifier: MITpragma solidity ^0.8.24;/// @title SessionKeyExecutor/// @notice Delegate contract that grants scoped, time-limited access to a session keycontract SessionKeyExecutor { struct Session { address key; uint256 validUntil; uint256 spendingLimit; uint256 spent; } mapping(address => Session) public sessions; mapping(address => mapping(address => bool)) public allowedTargets; /// @notice Register a session key with scoped permissions function startSession( address key, uint256 validUntil, uint256 spendingLimit, address[] calldata targets ) external { sessions[msg.sender] = Session({ key: key, validUntil: validUntil, spendingLimit: spendingLimit, spent: 0 }); for (uint256 i = 0; i < targets.length; i++) { allowedTargets[msg.sender][targets[i]] = true; } } /// @notice Execute a call using the session key function executeAsSessionKey( address owner, address target, uint256 value, bytes calldata data ) external { Session storage session = sessions[owner]; require(msg.sender == session.key, "not session key"); require(block.timestamp <= session.validUntil, "session expired"); require(allowedTargets[owner][target], "target not allowed"); uint256 beforeBalance = owner.balance; (bool success,) = target.call{value: value}(data); require(success, "call failed"); session.spent += owner.balance - beforeBalance; require(session.spent <= session.spendingLimit, "budget exceeded"); } /// @notice Revoke the active session function revokeSession() external { delete sessions[msg.sender]; }}