EIP-7702 실전 가이드: Pectra 이후 스마트 계정 플로우 구축
Engineering Team
EIP-7702가 가능하게 하는 것
EIP-7702는 2025년 3월 Ethereum의 Pectra 업그레이드와 함께 활성화되어, 모든 외부 소유 계정(EOA)이 위임 지정자를 설정할 수 있는 새로운 트랜잭션 타입을 도입했습니다. 이것은 스마트 컨트랙트를 가리키는 포인터로, EOA가 트랜잭션 기간 동안 임시로 해당 코드를 채택합니다. 즉, 기존 MetaMask 지갑 주소가 새 컨트랙트 지갑을 배포하거나 주소를 변경하지 않고도 임의의 스마트 컨트랙트 로직을 실행할 수 있습니다.
이것은 이더리움 역사상 가장 중요한 UX 개선입니다.
EIP-7702 vs ERC-4337: 사용 시기
| 특성 | EIP-7702 | ERC-4337 |
|---|---|---|
| 레벨 | 프로토콜(새 tx 타입) | 애플리케이션(스마트 컨트랙트) |
| 계정 타입 | 기존 EOA 업그레이드 | 새 스마트 컨트랙트 지갑 필요 |
| 주소 | 기존 EOA 주소 유지 | 새 주소(반사실적) |
| 지속성 | 트랜잭션별 위임 | 영구적 스마트 컨트랙트 |
| 번들러 필요 | 아니오(표준 tx 플로우) | 예(별도 멤풀) |
| 가스 오버헤드 | 위임에 ~20,000 가스 | UserOp 검증에 ~42,000 가스 |
| 지원 지갑 | MetaMask, Coinbase Wallet, Rainbow | 전용(Safe, ZeroDev, Biconomy) |
| 최적 용도 | 기존 사용자 업그레이드 | 신규 사용자, 복잡한 계정 로직 |
결정 프레임워크
EIP-7702를 사용할 때:
- 사용자가 이미 EOA 지갑을 가지고 있을 때
- 배치 트랜잭션이나 가스 후원이 필요할 때
- 통합 복잡성을 최소화하고 싶을 때
ERC-4337을 사용할 때:
- 새 지갑을 구축하거나 신규 사용자를 온보딩할 때
- 영구적인 계정 로직이 필요할 때
- 모듈, 플러그인, 가디언 등 고급 기능이 필요할 때
코드 예제
배치 트랜잭션
가장 일반적인 EIP-7702 사용 사례: 단일 트랜잭션에서 여러 작업 실행.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
/// @title BatchExecutor — EIP-7702 배치 콜 위임 대상
contract BatchExecutor {
struct Call {
address target;
uint256 value;
bytes data;
}
function executeBatch(Call[] calldata calls) external payable {
for (uint256 i = 0; i < calls.length; i++) {
(bool success, bytes memory result) = calls[i].target.call{
value: calls[i].value
}(calls[i].data);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
}
}
}
ethers.js v6 클라이언트 사이드 사용법:
import { ethers } from "ethers";
const batchTx = {
type: 4,
to: userAddress,
authorizationList: [{
chainId: 1,
address: BATCH_EXECUTOR_ADDRESS,
nonce: await provider.getTransactionCount(userAddress),
yParity: 0, r: "0x...", s: "0x..."
}],
data: batchExecutorInterface.encodeFunctionData("executeBatch", [[
{
target: USDC_ADDRESS,
value: 0,
data: usdcInterface.encodeFunctionData("approve", [
UNISWAP_ROUTER, ethers.parseUnits("1000", 6)
])
},
{
target: UNISWAP_ROUTER,
value: 0,
data: routerInterface.encodeFunctionData("exactInputSingle", [{
tokenIn: USDC_ADDRESS,
tokenOut: WETH_ADDRESS,
fee: 3000,
recipient: userAddress,
amountIn: ethers.parseUnits("1000", 6),
amountOutMinimum: 0,
sqrtPriceLimitX96: 0
}])
}
]])
};
const tx = await signer.sendTransaction(batchTx);
가스 후원
/// @title SponsoredExecutor — EIP-7702를 통한 가스 후원
contract SponsoredExecutor {
mapping(address => uint256) public nonces;
function sponsoredExecute(
address user,
address target,
bytes calldata data,
uint256 userNonce,
uint256 deadline,
bytes calldata signature
) external {
require(block.timestamp <= deadline, "Expired");
require(nonces[user] == userNonce, "Invalid nonce");
nonces[user]++;
bytes32 digest = _hashTypedData(user, target, data, userNonce, deadline);
address signer = ECDSA.recover(digest, signature);
require(signer == user, "Invalid signature");
(bool success, bytes memory result) = target.call(data);
require(success, "Call failed");
}
}
소셜 리커버리
/// @title RecoveryModule — EIP-7702를 통한 소셜 리커버리
contract RecoveryModule {
struct RecoveryConfig {
address[] guardians;
uint256 threshold;
uint256 delay;
}
mapping(address => RecoveryConfig) public configs;
mapping(bytes32 => uint256) public recoveryTimestamps;
mapping(bytes32 => uint256) public approvalCounts;
mapping(bytes32 => mapping(address => bool)) public hasApproved;
function setupRecovery(
address[] calldata guardians,
uint256 threshold,
uint256 delay
) external {
require(guardians.length >= threshold, "Invalid threshold");
require(threshold >= 2, "Min 2 guardians");
configs[msg.sender] = RecoveryConfig(guardians, threshold, delay);
}
function approveRecovery(
address account,
address newOwner
) external {
RecoveryConfig memory config = configs[account];
require(_isGuardian(config, msg.sender), "Not a guardian");
bytes32 recoveryId = keccak256(abi.encode(account, newOwner));
require(!hasApproved[recoveryId][msg.sender], "Already approved");
hasApproved[recoveryId][msg.sender] = true;
approvalCounts[recoveryId]++;
if (approvalCounts[recoveryId] >= config.threshold) {
recoveryTimestamps[recoveryId] = block.timestamp + config.delay;
}
}
function executeRecovery(
address account,
address newOwner
) external {
bytes32 recoveryId = keccak256(abi.encode(account, newOwner));
uint256 timestamp = recoveryTimestamps[recoveryId];
require(timestamp > 0 && block.timestamp >= timestamp, "Not ready");
}
}
지갑 통합 현황
MetaMask
MetaMask는 버전 12.4(2025년 9월)에서 EIP-7702 지원을 추가했습니다.
Coinbase Wallet
Coinbase Wallet은 가장 적극적인 채택자로, “Smart Wallet” 기능(2025년 11월 출시)에 EIP-7702를 통합:
- 자동 배치 — approve+swap 시퀀스를 자동으로 배치
- 가스 후원 — $1 이하 수수료 트랜잭션을 Coinbase가 후원
- 원클릭 DeFi — 일반적인 DeFi 작업용 사전 구축 위임 대상
Rainbow, Rabby 등
Rainbow는 2026년 1월에 지원을 추가했습니다. Rabby는 type-4 트랜잭션을 지원하지만 배치 시각화 UI는 없습니다.
마이그레이션 가이드
1단계: 위임 대상 배포
2단계: 프론트엔드 업데이트
async function supportsEIP7702(provider: ethers.Provider): Promise<boolean> {
try {
const capabilities = await provider.send(
"wallet_getCapabilities", []
);
return capabilities?.atomicBatch?.supported === true;
} catch {
return false;
}
}
3단계: 그레이스풀 디그레이데이션 구현
async function executeSwap(tokenIn, tokenOut, amount) {
if (await supportsEIP7702(provider)) {
return executeBatchSwap(tokenIn, tokenOut, amount);
} else {
await approve(tokenIn, ROUTER, amount);
return swap(tokenIn, tokenOut, amount);
}
}
보안 고려사항
위임 대상 검증
위임 대상 컨트랙트는 트랜잭션 중 EOA 자산에 대한 전체 제어권을 가집니다.
완화: 감사 및 검증된 컨트랙트에만 위임하세요.
리플레이 보호
EIP-7702 인증 목록에는 리플레이 공격을 방지하는 nonce가 포함됩니다.
위임을 통한 피싱
완화: 지갑은 EOA가 위임하는 코드를 명확히 표시해야 합니다.
배치 실행의 재진입성
bool private locked;
modifier noReentrant() {
require(!locked, "Reentrant");
locked = true;
_;
locked = false;
}
스토리지 충돌
EIP-7201(네임스페이스 스토리지) 사용:
bytes32 constant STORAGE_SLOT = keccak256(
abi.encode(uint256(keccak256("batch.executor.storage")) - 1)
) & ~bytes32(uint256(0xff));
자주 묻는 질문
EIP-7702가 지갑 주소를 변경하나요?
아니요. EOA 주소는 그대로 유지됩니다. EIP-7702는 단일 트랜잭션 동안만 스마트 컨트랙트 기능을 부여합니다.
L2에서 EIP-7702를 사용할 수 있나요?
예. 모든 주요 L2(Arbitrum, Base, Optimism, zkSync)가 EIP-7702를 채택했습니다.
위임 대상에 버그가 있으면 어떻게 되나요?
피해는 단일 트랜잭션에 한정됩니다. 하지만 해당 트랜잭션 내에서 위임 대상은 EOA 자산에 전체 접근 권한을 가집니다.
하드웨어 지갑과 호환되나요?
예. Ledger는 펌웨어 2.3.0(2025년 12월)에서 지원을 추가했습니다.
EIP-7702로 얼마나 가스를 절약하나요?
일반적인 approve+swap 플로우에서 약 40-50% 가스 절약. 복잡한 멀티스텝 DeFi 작업에서는 60-70%에 달할 수 있습니다.