はじめに
業務でDID/VCベースの基盤構築を担当する中で、「ユーザーがウォレットの知識を持たなくても、DID発行時のブロックチェーントランザクションを気にせず利用できる仕組み」を実現する必要がありました。
その解決策として、Ethereumの**ERC-4337(Account Abstraction)**を調査したところ、そのアーキテクチャが非常に美しく、かつ効率的に設計されていることに感動したため、本記事にまとめます。
ERC-4337とは?
ERC-4337は、トランザクションの発行をEOAだけでなく、スマートコントラクトに抽象化する仕組みです。
従来の課題
通常、ブロックチェーンでトランザクションを発行する際には、発行元アドレスの秘密鍵で署名を行う必要があります。
EOA(Externally Owned Account)の場合:
- MetaMaskなどで管理される秘密鍵を持つ
- その秘密鍵で署名することで、トランザクションの正当性を証明
スマートコントラクトの場合:
- 秘密鍵を持たないため、通常はトランザクションを発行できない
この仕組みには以下の問題がありました:
- ユーザーは必ずEOAアドレスを持つ必要がある
-
トランザクション発行にはガス代(ETH)が必要
- ユーザーが事前に資金を用意する必要がある
- UXの大きな障壁となる
Account Abstractionによる解決
Account Abstractionを利用することで、特定のスマートコントラクトに支払いを委譲し、EOAアドレスを意識せずにユーザーがトランザクションを発行できるようになります。
アーキテクチャ
各コンポーネントの役割
①User
従来のEOAの代わりに、Smart Contract Walletを自分のアカウントとして利用するユーザー。
②UserOperation
アプリケーションで作成されるトランザクションの内容を保持する構造体。
struct UserOperation {
address sender; // 送信者のSmart Contract Walletアドレス
uint256 nonce; // リプレイ攻撃防止用
bytes initCode; // ウォレットの初期化コード(初回のみ)
bytes callData; // 実行するトランザクションの内容
uint256 callGasLimit; // callData実行に必要なガス
uint256 verificationGasLimit; // 検証に必要なガス
uint256 preVerificationGas; // Bundlerの処理に必要なガス
uint256 maxFeePerGas; // ガス価格の上限
uint256 maxPriorityFeePerGas; // マイナーへの優先手数料
bytes paymasterAndData; // Paymasterの情報
bytes signature; // ユーザーの署名
}
③Bundler
複数のUserOperationを集約してまとめて送信するオフチェーンサービス。ガス効率の向上に寄与します。
④EntryPoint
ERC-4337の中核となるスマートコントラクト。Smart Contract WalletやPaymaster Contractの関数を呼び出します。
⑤Smart Contract Wallet
以下の検証を行うスマートコントラクト:
- トランザクションの内容が正しいか
- ビジネス要件を満たしているか
- 署名の検証
- nonceの管理
⑥Paymaster Contract
UserOperationのトランザクション発行時に発生するガス代を代わりに支払うスマートコントラクト。
処理の流れ
ステップ1: UserOperationの作成と送信
- アプリケーションがUserOperationを作成
- UserOperationをハッシュ化し、ユーザーの秘密鍵で署名
- 署名データを
signatureフィールドに格納 - BundlerへUserOperationを送信
これにより、UserOperationが正式にこのユーザーのものであり、内容に改ざんがないことが証明されます。
ステップ2: Bundlerによる集約
Bundlerが複数のUserOperationを集約し、EntryPointへ一括送信します。
ステップ3: 検証フェーズ
EntryPointからSmart Contract WalletのvalidateUserOp()が呼び出されます。
検証内容:
- 署名が正しいか?
- nonceが正しいか?
- 必要なガス代が足りているか?
これは、従来のEOAトランザクションでEthereumが行う署名・nonce検証とほぼ同等の処理を、スマートコントラクト内で実現したものです。
Paymasterの検証
paymasterAndDataにPaymasterが設定されている場合、EntryPointはPaymaster ContractのvalidatePaymasterUserOp()を呼び出し、ガス代を肩代わりできるかを検証します。
🔑重要なポイント:資金管理の仕組み
ここで重要なのは、Smart Contract Wallet自体がETH残高を直接持つわけではないという点です。
実際には、EntryPointコントラクト内部に次のような仕組みが存在します:
mapping(address => DepositInfo) public deposits;
各Smart Contract Wallet(またはPaymaster)ごとに、EntryPointにデポジットされた資金情報をこのmappingで管理しています。
これにより、EntryPointは「そのアカウントがどれだけのガス代を支払えるか」を即座に参照できます。
この設計は、スマートコントラクトが直接ETHを保持できないという制約を回避しながら、安全かつガス効率的に資金状態を管理する仕組みになっています。
個人的には、この設計は本当に天才的だと思いました。
💀 実装時の最大の落とし穴:Deposit消失問題
しかし、この美しい設計には開発時に大きな落とし穴があります。
実装中に最も辛かったのが、PaymasterやSmart Contract Walletを修正して再デプロイするたびに、EntryPointにdepositしていた資金がマルっと消えるという問題でした。
// EntryPointの資金管理
mapping(address => DepositInfo) public deposits;
// 問題:
// deposits[旧Paymasterアドレス] = 0.5 ETH ← 取り残される
// deposits[新Paymasterアドレス] = 0 ETH ← ゼロからスタート
EntryPointのdeposits mappingはアドレスベースで管理されているため、新しいアドレスでデプロイすると、旧アドレスのdepositはそのまま放置されます。
開発中のよくある悲劇:
Day 1: Sepolia 0.5 ETH deposit ✅
Day 2: バグ修正 → 再デプロイ → 0.5 ETH 消失 😭
Day 2: また faucetから 0.5 ETH 補充...
Day 3: 機能追加 → 再デプロイ → 0.5 ETH 消失 😭😭
Day 3: また faucetから 0.5 ETH 補充...
(繰り返し × N回)
スマートコントラクトを修正するたびに:
- EntryPoint
- Smart Contract Wallet
- Paymaster
これら全てのアドレスが変わるため、開発中に何度も修正を繰り返した結果、累計で相当な量のSepolia ETHをfaucetから補充する羽目になりました...
結局、完璧な解決策はなく、この苦労も含めて実装経験だと割り切りました。
ただ、この経験があったからこそ、Paymasterでユーザーのガス代を肩代わりできる仕組みの価値を心から実感することができました。開発者がこれだけ苦労するのに、エンドユーザーはガス代を意識しなくて良いというのは、本当に革新的です。
ステップ4: トランザクションの実行
最後に、Smart Contract WalletのexecuteまたはexecuteBatch関数を呼び出し、UserOperation構造体内のcallDataに格納されたトランザクションを実行します。
まとめ
ERC-4337のAccount Abstractionにより、以下が実現できます:
- ✅ ユーザーがEOAアドレスを意識する必要がなくなる
- ✅ ガス代の支払いをPaymaster Contractに委譲できる
- ✅ より柔軟な認証ロジック(マルチシグ、ソーシャルリカバリーなど)の実装が可能
- ✅ Web2のようなUXをWeb3で実現できる
調査を通じて、コードレベルで詳細に解説している日本語記事が少なかったため、自分なりにまとめてみました。
もし不足している点や誤りがあれば、コメントでご指摘いただけると幸いです。
参考文献
ありがとうございました!****