Skip to content
On this page

L4-1: Smart Wallets + Paymaster(スマートウォレット + ガス代払い)

1. 問題

従来のイーサリアムアカウント(EOA、外部所有アカウント)は単一の秘密鍵によって制御されます。このモデルには根本的な制限があります。(1) 秘密鍵の紛失 = 資金の永久喪失、回復メカニズムなし、(2) すべての操作に ETH でのガス支払いが必要で、新規ユーザーは「鶏が先か卵が先か」のジレンマに直面、(3) 日次支出制限、タイムロック、複数人による共同署名などの複雑な権限ポリシーを実装できない、(4) ユーザー体験が不格好で、すべてのトランザクションに確認と待機が必要。

ERC-4337(アカウントアブストラクション、Account Abstraction)は、コンセンサスレイヤーを修正しないソリューションを提案しています。スマートコントラクトウォレット(EOA の代わり)、Bundler(トランザクションのバンドル)、Paymaster(ガス代払い)を導入します。ユーザー操作はもはや従来のイーサリアムトランザクションではなく、「ユーザー操作」(UserOperation)です。これは操作の意図を含むデータ構造で、スマートウォレットによって署名され、Bundler によってバンドルされて提出されます。

このチャレンジでは、完全な ERC-4337 スタックのコアコンポーネントを構築します。スマートウォレットコントラクト(SmartWallet.sol)と、ERC-20 トークンでガスを支払えるようにする TokenPaymaster(TokenPaymaster.sol)です。ユーザーは ETH ではなく ERC-20 トークンでガス代を支払いながら、スマートウォレットが提供するすべての利点を享受できます。

2. 理由

ERC-4337 はすでに3000万以上のスマートアカウントに採用されており、イーサリアムアカウントアブストラクションのデファクトスタンダードです。Vitalik Buterin は、アカウントアブストラクションが主流の Web3 採用を実現するための必要な道筋であると繰り返し強調しています。それは新規ユーザーのオンボーディングにおける技術的摩擦を解消します。2026年、EntryPoint v0.7 はすべての主要 EVM チェーン上で CREATE2 によって同一アドレス(0x0000000071727De22E5E9d8BAf0edAc6f37da032)にデプロイされ、イーサリアムインフラのコアコンポーネントとなっています。

ERC-4337 を理解することは Web3 エコシステム全体にとって重要です。

  • フロントエンド開発:UserOperation を構築し、スマートウォレットで署名する方法を理解する必要がある
  • コントラクト開発validateUserOpexecute のインターフェース、および EntryPoint のライフサイクルを理解する必要がある
  • インフラ開発:Bundler と Paymaster の構築は新興のインフラ領域である
  • セキュリティエンジニアリング:スマートウォレットは EOA とは異なる攻撃面を導入し、異なるセキュリティ上の考慮が必要である

EIP-7702(EOA でコードをスマートコントラクトに委任することでアカウントアブストラクションを実現する「EOA アップグレード」アプローチ)とは異なり、ERC-4337 は完全なスマートコントラクトウォレットソリューションであり、EOA の存在に依存しません。この2つは補完的な技術パスです。EIP-7702 は既存の EOA ユーザーにアップグレードパスを提供し、ERC-4337 はネイティブなスマートウォレットの新規ユーザーを対象としています。

3. 解決策

完全な ERC-4337 アーキテクチャは4つの協調コンポーネントで構成されています。このチャレンジでは、そのうち2つのコアコントラクトを実装します。

3.1 アーキテクチャ概要

ユーザー(ETH を持っていない可能性あり)
  |
  ├─→ UserOperation に署名
  |    {sender, nonce, callData, paymasterAndData, signature, ...}
  |

Bundler(alt mempool 内の UserOperation をリッスン)
  |
  ├─→ simulateValidation() — 検証フェーズをシミュレーション
  ├─→ 複数の UserOperation をバンドルにパッケージ化
  ├─→ handleOps([userOp1, userOp2, ...]) — EntryPoint に提出
  |

EntryPoint (0x0000000071727De22E5E9d8BAf0edAc6f37da032)
  |
  ├─→ 検証フェーズ:
  │     Wallet._validateSignature(userOp, userOpHash)
  │     Paymaster._validatePaymasterUserOp(userOp, userOpHash, maxCost)
  |
  ├─→ 実行フェーズ:
  │     Wallet.execute(to, value, data) または executeBatch(...)
  |     検証フェーズが成功した場合、Bundler がガスを立て替え
  |
  └─→ 実行後フェーズ:
        Paymaster._postOp(mode, context, actualGasCost)
        ユーザーから ERC-20 トークンを引き落とし、Bundler に補償

3.2 SmartWallet(スマートウォレットコントラクト)

SmartWallet.sol は ERC-4337 互換のスマートコントラクトウォレットで、2つのコアインターフェースを実装します。

署名検証(_validateSignature):EntryPoint によって検証フェーズで呼び出されます。検証ロジック:

  1. userOp.signature(65バイト ECDSA 署名)から署名者アドレスを復元
  2. 復元されたアドレスが owner と一致するかチェック
  3. userOp.nonce がウォレットの現在の nonce と一致するかチェック(リプレイ防止)
  4. 検証成功は0、失敗は1を返す(SIG_VALIDATION_FAILED)

署名復元の実装は標準的な v 値の正規化(v < 27 の場合は27を加算)をサポートし、EIP-155 リプレイ保護の v 値エンコーディング(chainId * 2 + 35)にも互換性があります。

トランザクション実行(execute / executeBatch)

  • execute():単一の呼び出しを実行。EntryPoint のみが呼び出し可能(onlyEntryPoint 修飾子)。成功後に nonce をインクリメント。
  • executeBatch():アトミックなバッチ実行。すべての呼び出しが成功するか、すべてがロールバックされる。渡される3つの配列(to、values、datas)は長さが一致している必要がある。

セキュリティゲーティング:すべての実行関数は onlyEntryPoint 修飾子を使用し、EntryPoint のみが実行をトリガーできることを保証します。これが ERC-4337 のコアセキュリティモデルです。ユーザー操作は実行前に完全な検証パイプライン(ガス支払い、署名検証、nonce チェックを含む)を通過しなければなりません。

ETH の受け取りreceive() 関数により、ウォレットは直接 ETH 転送を受け取ることができます(他のユーザーやコントラクトからの入金など)。

3.3 TokenPaymaster(トークンガス代払いコントラクト)

TokenPaymaster.sol はユーザーが ERC-20 トークンでガスを支払うことを許可する Paymaster を実装します。コアメカニズム:

検証(_validatePaymasterUserOp):EntryPoint の検証フェーズで呼び出され、3つのパラメータを受け取ります。

  1. userOp:完全なユーザー操作。paymasterAndData フィールドにはエンコードされた maxTokenCost(ユーザーが支払いを許可する最大トークン数)が含まれる
  2. userOpHash:ユーザー操作のハッシュ(この実装では未使用だが、完全な実装では追加の署名検証に使用される)
  3. maxCost:EntryPoint によって推定されたこの操作の最大ガスコスト

検証チェック:

  • paymasterAndData から maxTokenCost を解析(バイトオフセット20から32バイトの uint256 を読み取り)
  • 固定為替レート TOKENS_PER_ETH = 2000 に基づいて tokenCost = maxCost * 2000 を計算
  • ユーザーが十分なトークン承認を提供しているか検証(token.allowance(userOp.sender, address(this))
  • Paymaster が EntryPoint に十分な ETH 預金を持っているか検証(entryPoint.balanceOf(address(this))
  • context(エンコードされた (sender, tokenCost))を返し、_postOp で使用

実行後(_postOp):ユーザー操作の実行後に EntryPoint によって呼び出されます。

  • 操作が成功した場合のみ(mode == PostOpMode.opSucceeded)引き落とし
  • 実際に消費されたガスに基づいて最終トークン手数料を計算:actualTokenCost = actualGasCost * TOKENS_PER_ETH
  • 事前承認された tokenCost 上限を超えない
  • ユーザーアカウントから Paymaster コントラクトへの transferFrom を実行

預金管理:Paymaster は Bundler のガスを支払うために EntryPoint に ETH を預ける必要があります。

  • deposit():誰でも EntryPoint に ETH を預けることができる(Paymaster の預金としてマークされる)
  • withdraw():owner のみが EntryPoint から ETH を ower アドレスに引き出せる

為替レートモデル:現在はハードコードされた為替レート TOKENS_PER_ETH = 2000 を使用しています。これは簡略化です。プロダクション用 Paymaster はリアルタイムの ETH/トークン為替レートを取得するために Chainlink や他のオラクルを統合する必要があります。

参考ソースファイル:src/level4/SmartWallet.solsrc/level4/TokenPaymaster.sol

3.4 Bundler ロジック(概念レベル)

Bundler は ERC-4337 における重要なインフラコンポーネントであり、以下を担当します。

  1. alt mempool のリッスン:UserOperation を収集(従来のトランザクションメモリプールとは異なる)
  2. シミュレーション検証EntryPoint.simulateValidation() を呼び出して各 UserOperation が成功するかチェック
  3. ガスソート:ガス価格(maxPriorityFeePerGas)で UserOperation を降順にソート(優先手数料が高いものを優先)
  4. バッチ提出EntryPoint.handleOps() を呼び出してすべての UserOperation をバッチ提出
  5. ガス補償:Bundler がすべてのガスを立て替え、Paymaster またはウォレットの EntryPoint 預金から補償を受ける

3.5 UserOperation データ構造

struct UserOperation {
    address sender;              // スマートウォレットアドレス
    uint256 nonce;               // リプレイ防止カウンター
    bytes   initCode;            // ファクトリデプロイコード(新規ウォレットの初回操作)
    bytes   callData;            // 実行する calldata
    uint256 callGasLimit;        // 実行フェーズのガス制限
    uint256 verificationGasLimit;// 検証フェーズのガス制限
    uint256 preVerificationGas;  // Bundler 補償
    uint256 maxFeePerGas;        // EIP-1559 最大手数料
    uint256 maxPriorityFeePerGas;// EIP-1559 優先手数料
    bytes   paymasterAndData;    // Paymaster アドレス(20B) + カスタムデータ
    bytes   signature;           // userOpHash に対するウォレットの署名
}

4. 遭遇した落とし穴

  • 検証と実行フェーズのガス分離:ERC-4337 は検証フェーズ(署名チェック、Paymaster 検証)と実行フェーズを分離しています。検証フェーズが過剰なガスを消費して verificationGasLimit を超えた場合、操作全体が直接失敗し、Bundler はガス補償を受けられません
  • Paymaster の ETH 預金不足_validatePaymasterUserOp で預金チェックが失敗した場合、Bundler は補償を拒否されますが、_validatePaymasterUserOp 自体が消費したガスも回収できません
  • トークン為替レートの変動TokenPaymaster.sol はハードコードされた為替レート TOKENS_PER_ETH = 2000 を使用しています。ETH/トークン価格が大幅に変動した場合、ユーザーは過剰または過少のトークンを支払う可能性があります
  • paymasterAndData のデコード_decodeMaxTokenCost() はインラインアセンブリを使用して calldata のオフセット20バイトから uint256 を読み取ります。paymasterAndData のフォーマットが正しくない場合(52バイト未満)、ゴミデータや範囲外データを読み取ります
  • nonce 同期の問題:Bundler が複数の UserOperation を提出する際、ある UserOperation の nonce が他の操作と連続していない場合、handleOps 呼び出し全体が失敗する可能性があります
  • スマートウォレットに回復メカニズムがない:現在の実装は単一の owner のみです。owner の秘密鍵が失われた場合、ウォレット内の資金は永久にロックされます。ソーシャルリカバリーやタイムロックのメカニズムがありません
  • EntryPoint アドレスのハードコード:EntryPoint がアップグレードされた場合(v0.6 から v0.7 へ)、ウォレットと Paymaster は新しい EntryPoint を指すように再デプロイする必要があります

5. 落とし穴の原因

検証フェーズのガス管理は ERC-4337 の最も特殊な課題です。従来のトランザクションでは、ガスは連続的な消費プロセスです。ERC-4337 では、検証フェーズのガスは verificationGasLimit によって独立して価格付けされます。Bundler は操作を実行する前に検証ガスを支払い、検証が失敗した場合、Bundler は補償を受けられないだけでなく、検証フェーズのガスコストも自ら負担します。これによりインセンティブの問題が生じます。Bundler は検証ロジックが複雑な(高ガスの)UserOperation を受け入れたがりません。

より具体的には、_validatePaymasterUserOp 内の token.allowance()entryPoint.balanceOf() 呼び出しはどちらも外部コントラクト呼び出しであり、単純なストレージ読み取りよりもはるかに多くのガスを消費します。Paymaster の検証ロジックが複雑すぎる場合、Bundler は潜在的な損失のためにこれらの UserOperation のパッケージ化を拒否する可能性があります。これにより、Paymaster 開発者は検証効率と機能の完全性の間でトレードオフを迫られます。

Paymaster の預金不足は連鎖的な障害です。預金が _validatePaymasterUserOp でのチェックを通過しても、実際の実行時(数秒から数分後)に他の UserOperation が預金を消費したために不足した場合、Bundler は _postOp フェーズで補償を受けられなくなります。ERC-4337 は、Paymaster が EntryPoint に十分な預金を保持することを要求することでこの問題に対処します。預金は検証フェーズで事実上「ロック」され、他の UserOperation によって消費されることはありません。

ハードコードされた為替レートの危険性はアービトラージにあります。TOKENS_PER_ETH = 2000 だが市場価格が 2500 の場合、ユーザーは市場価格を下回るトークンでガスを支払うことができ、Paymaster が損失を負います。市場価格が 1500 の場合、ユーザーはトークンを過剰に支払う必要があり、ユーザー体験が悪化する可能性があります。プロダクション用 Paymaster はリアルタイムのオラクル価格を使用しなければなりません。

6. 落とし穴の解決方法

検証ガス最適化

  • _validatePaymasterUserOp 内の外部呼び出しを最小限に抑える。entryPoint.balanceOf() の結果をキャッシュすることを検討
  • CALL の代わりに SLOAD を使用してホワイトリスト情報をチェック
  • 複雑な検証ロジックについては、一部のチェックを実行フェーズに移動(ウォレットコントラクト自身が負担)
  • Bundler の収益期待モデルを構築。シミュレーション後に補償がコストをカバーするか計算

為替レートオラクル統合(プロダクションコード例):

solidity
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

contract TokenPaymaster {
    AggregatorV3Interface public tokenPriceFeed; // Token/ETH 価格

    function getTokenCost(uint256 ethCost) public view returns (uint256) {
        (, int256 price,,,) = tokenPriceFeed.latestRoundData();
        // price は token/ETH(1e8 でスケーリング)、ethCost をカバーするのに必要なトークン数を計算
        return (ethCost * uint256(price)) / 1e8;
    }
}

nonce 管理の改善:ERC-4337 のキーベースの nonce 管理を使用(EntryPoint v0.7 は2次元 nonce (key, sequence) をサポート)。これにより複数の独立した nonce シーケンスが可能になり、並行 UserOperation をサポートします。

ウォレット回復メカニズムSmartWallet に所有者移転機能を追加(EntryPoint または owner 自身を通じて呼び出し可能):

solidity
function transferOwnership(address newOwner) external {
    require(msg.sender == owner || msg.sender == entryPoint, "Not authorized");
    owner = newOwner;
}

より高度な実装では、複数のガーディアンが投票によって owner を置き換えるソーシャルリカバリーモジュールを追加できます。

預金監視:自動預金補充スクリプトを構築。Paymaster の EntryPoint 内残高が安全閾値を下回った場合に自動的に deposit() を呼び出します。

javascript
async function monitorPaymasterDeposit(paymaster, entryPoint, minBalance) {
  const balance = await entryPoint.balanceOf(paymaster.address);
  if (balance < minBalance) {
    await paymaster.deposit({ value: topUpAmount });
  }
}

7. 技術ポイント

技術ポイント説明
ERC-4337アカウントアブストラクション標準。UserOperation が従来のトランザクションを置き換え
EntryPointグローバルシングルトンコントラクト。全EVMチェーンで同一アドレス(CREATE2)
UserOperationsender/nonce/callData/signature/paymasterAndData を含む
署名検証 (_validateSignature)EntryPoint が検証フェーズで呼び出し。0=成功、1=失敗を返す
バッチ実行 (executeBatch)アトミックバッチ呼び出し。全成功または全ロールバック
Paymasterガスを代払い。ユーザーは ERC-20 トークンで支払い可能
検証→実行→実行後3フェーズパイプライン:validate → execute → postOp
検証フェーズガスverificationGasLimit で独立価格付け。失敗時は Bundler が負担
paymasterAndData20バイト Paymaster アドレス + カスタムデータ(maxTokenCost など)
EntryPoint 預金Paymaster が Bundler 補償のために EntryPoint に ETH を預ける
PostOpModeopSucceeded/opReverted/postOpReverted。実行後の手数料引き落としを制御
ECDSA 署名復元v 値正規化(v<27 なら +27)、ecrecover プリコンパイル

Built with AiAda