スマートアカウント
SimpleAccount は最小構成の単一 Owner ECDSA 参照実装です。独自の ERC-4337 スマートアカウントも実装できます。
SimpleAccount implementation は全アカウントプロキシが共有する実装アドレスであり、ユーザーのアカウントアドレスではありません。各インスタンスは Owner と salt で決まります。
1. SimpleAccountFactory API
interface ISimpleAccountFactory {
function accountImplementation() external view returns (address);
function getAddress(address owner, uint256 salt) external view returns (address);
function createAccount(address owner, uint256 salt) external returns (address);
}
| メソッド | 説明 |
|---|---|
accountImplementation | 共有 SimpleAccount 実装を返す |
getAddress | Owner と salt から反実仮想プロキシアドレスを計算(デプロイなし) |
createAccount | コードがなければ作成し、デプロイ済みなら既存アカウントを返す |
初回 UserOperation の Factory データは、Factory アドレスと ABI エンコードした createAccount(owner, salt) です。EntryPoint は Factory の返り値が UserOperation の sender と一致し、デプロイ後に sender にコードが存在することを要求します。
2. SimpleAccount API
interface ISimpleAccount {
function owner() external view returns (address);
function entryPoint() external view returns (address);
function getNonce() external view returns (uint256);
function initialize(address owner) external;
function execute(address dest, uint256 value, bytes calldata func) external;
function executeBatch(address[] calldata dest, uint256[] calldata value, bytes[] calldata func) external;
function getDeposit() external view returns (uint256);
function addDeposit() external payable;
function withdrawDepositTo(address payable withdrawAddress, uint256 amount) external;
}
2.1 アカウント状態
| メソッド | 説明 |
|---|---|
owner | 現在の単一 ECDSA Owner |
entryPoint | アカウントに紐付く EntryPoint |
getNonce | EntryPoint における key 0 の次の nonce |
initialize | プロキシ Owner を一度だけ初期化 |
Factory は作成時にプロキシを初期化します。未初期化プロキシを外部へ公開しないでください。
2.2 ビジネス実行
| メソッド | 呼び出し権限 | 動作 |
|---|---|---|
execute | Owner または紐付く EntryPoint | 単一の呼び出しを実行し revert データを転送 |
executeBatch | Owner または紐付く EntryPoint | 複数の呼び出しをアトミックに実行 |
executeBatch の宛先配列と calldata 配列は同じ長さが必要です。value 配列は空(全呼び出しで BOT を送らない)または宛先配列と同じ長さにします。
Owner が直接呼ぶ場合は通常のオンチェーントランザクションで Gas を支払います。EntryPoint から呼ぶ場合は UserOperation を使用し、アカウントまたは Paymaster の deposit から決済します。
2.3 EntryPoint deposit
| メソッド | 説明 |
|---|---|
getDeposit | EntryPoint 内の Gas deposit を照会 |
addDeposit | アカウントの deposit に入金 |
withdrawDepositTo | Owner が deposit を引き出す |
ネイティブ BOT 残高は送金・ビジネス呼び出しに、EntryPoint deposit は UserOperation Gas に使われ、互いに独立しています。
2.4 トークンの受け取り
SimpleAccount はネイティブ BOT、ERC-721、ERC-1155 を受け取り、対応する receiver インターフェース検出を実装します。
3. SimpleAccount の検証動作
SimpleAccount は BaseAccount.validateUserOp を継承します。
function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds) external returns (uint256 validationData);
| 手順 | 動作 |
|---|---|
| 呼び出し元 | 紐付く EntryPoint のみ許可 |
| 署名 hash | userOpHash に EIP-191 を適用 |
| 署名検証 | ECDSA recover の結果が Owner と一致 |
| 署名失敗 | SIG_VALIDATION_FAILED(1)を返す |
| nonce | EntryPoint が一意性を管理。既定 key は 0 |
| prefund | missingAccountFunds を EntryPoint へ支払う |
SimpleAccount は multisig、ソーシャルリカバリー、Session Key、Passkey、ERC-7579 モジュールを標準提供しません。必要なら独自アカウントを実装し、未公開インターフェースがあると仮定しないでください。
4. 独自スマートアカウント
IAccount を一から実装するか、BaseAccount を継承します。EntryPoint v0.7 と Bundler の検証規則への互換性が必要です。
4.1 必須インターフェース
interface IAccount {
function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds) external returns (uint256 validationData);
}
| 引数 | 説明 |
|---|---|
userOp | 検証・実行対象の PackedUserOperation |
userOpHash | EntryPoint と chainId に結び付く hash |
missingAccountFunds | deposit と必要 prefund の差額 |
下位 160 bit:0 は署名有効、1 は失敗、その他はアグリゲーターアドレス
次の 48 bit:validUntil(0 は期限なし)
上位 48 bit:validAfter
4.2 必須動作
msg.senderが信頼する EntryPoint か検証する。signature、認可ポリシー、必要な状態を検証する。- 署名不一致は
1を返し、形式・ポリシー違反は revert できる。 - アカウントの nonce ポリシーを適用し、一意性更新は EntryPoint に任せる。
- Paymaster なしで deposit が不足する場合は
missingAccountFundsを支払う。 - ERC-7562 の opcode、storage アクセス、外部呼び出し制限を守る。
- 推定時に決定論的でシミュレーション可能な検証を提供する。
BaseAccount のサブクラスは最低限 entryPoint() と _validateSignature(...) を提供します。必要に応じて _validateNonce(...) と _payPrefund(...) を上書きします。
4.3 実行インターフェース設計
単一実行、一括実行、モジュール実行、権限階層など独自の callData を定義できます。既定では EntryPoint が userOp.callData を直接アカウントへ送ります。
interface IAccountExecute {
function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external;
}
executeUserOp selector を使う場合、EntryPoint が完全な UserOperation と hash を渡します。このインターフェースは任意です。
4.4 Factory とデプロイ
- 決定論的パラメータから安定したアドレスを導出する。
- 作成メソッドが実際のデプロイ先を返す。
- 初回
initCodeの先頭 20 bytes に Factory アドレス、残りに calldata を置く。 - デプロイ結果を UserOperation の
senderと一致させる。 - デプロイ後は Factory フィールドを省略する。
Bundler は Factory とアカウントの検証をシミュレーションし、規則違反があれば mempool へ入る前に拒否します。