メインコンテンツへスキップ
ブロックチェーンMar 28, 2026

EIP-7702実践ガイド:Pectra後のスマートアカウントフロー構築

OS
Open Soft Team

Engineering Team

EIP-7702が実現すること

EIP-7702は、2025年3月のEthereumのPectraアップグレードで有効化され、任意の外部所有アカウント(EOA)がデリゲーションデシグネーターを設定できる新しいトランザクションタイプを導入しました。これはスマートコントラクトへのポインターで、EOAがトランザクション中に一時的にそのコードを採用します。つまり、既存のMetaMaskウォレットアドレスが、新しいコントラクトウォレットをデプロイしたりアドレスを変更したりすることなく、任意のスマートコントラクトロジックを実行できます。

これはEthereum史上最も重要なUX改善です。

EIP-7702 vs ERC-4337:使い分け

特徴EIP-7702ERC-4337
レベルプロトコル(新txタイプ)アプリケーション(スマートコントラクト)
アカウントタイプ既存EOAをアップグレード新しいスマートコントラクトウォレットが必要
アドレス既存EOAアドレスを維持新アドレス(反事実的)
永続性トランザクションごとのデリゲーション永続的スマートコントラクト
バンドラー必要不要(標準txフロー)必要(別メンプール)
ガスオーバーヘッドデリゲーションに~20,000ガスUserOp検証に~42,000ガス
対応ウォレットMetaMask, Coinbase Wallet, Rainbow専用(Safe, ZeroDev, Biconomy)
最適な用途既存ユーザーのアップグレード新規ユーザー、複雑なアカウントロジック

判断フレームワーク

EIP-7702を使用する場合:

  • ユーザーが既にEOAウォレットを持っている
  • バッチトランザクションやガススポンサーシップが必要
  • 統合の複雑さを最小限にしたい

ERC-4337を使用する場合:

  • 新しいウォレットを構築するか新規ユーザーをオンボーディング
  • 永続的なアカウントロジックが必要
  • モジュール、プラグイン、ガーディアンなどの高度な機能が必要

コード例

バッチトランザクション

// 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%に達することもあります。