--- eip: 8043 title: Atomic ERC-7579 Module Install and Execute description: Install and use ERC-7579 modules within a single user operation author: Ernesto Garcia (@ernestognw), Taek Lee (@leekt) discussions-to: https://ethereum-magicians.org/t/erc-8043-enable-mode-for-module-installation-in-erc-7579-smart-accounts/25901 status: Draft type: Standards Track category: ERC created: 2025-10-02 requires: 712, 1271, 4337, 7579 --- ## Abstract This ERC defines a mechanism for [ERC-7579] compliant smart accounts to install and immediately use modules within a single user operation by reusing the `initCode` field when bundled in an [ERC-4337] Entrypoint v9, which silently ignores `initCode` when the sender account is already deployed, making the `initCode` field available as a signaling and data channel for existing accounts. This document specifies a magic value in the `initCode` field and an [EIP-712] encoding format to trigger _init mode_: the installation of one or more modules during the validation phase of a user operation. [ERC-7579]: ./eip-7579.md [EIP-712]: ./eip-712.md [ERC-1271]: ./eip-1271.md [ERC-4337]: ./eip-4337.md ## Motivation Current [ERC-7579] implementations require separate transactions for module installation and usage, which can hinder user experience. Use cases such as session key and spending limit modules would benefit from the ability to install and use modules in a single operation. This spec allows applications to opt in to install and use multiple modules under a single user operation, enabling greater flexibility—particularly for scenarios where multiple modules need to work together atomically. This document addresses the following issues: - **User Experience**: Reduces transaction count from N+1 to 1 for installing N modules and using them - **Consistency across implementations**: Provides a standard pattern that works across different [ERC-7579] account implementations - **Security**: Module installation parameters are committed to by the `userOpHash` (which covers `initCode`) and authorized through [EIP-712] typed signatures, ensuring both tamper-resistance and human-readable signing UX Real-world usage in production systems has validated the need and feasibility of this pattern, particularly for session key management and dynamic module installation. ## Specification The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. ### Definitions - **Init Mode**: The logic branch triggered during the validation phase allowing to install and use one or more [ERC-7579] modules within a single user operation - **Init Mode Magic Value**: The 4-byte magic value at the start of `initCode` that signals init mode - **Installation Signature**: An [EIP-712] signature by the account authorizing module installations - **User Operation Signature**: The signature in the `signature` field for validating the user operation itself ### Init Mode Magic Value Smart accounts implementing this standard MUST detect init mode by checking whether the first 4 bytes of `userOp.initCode` equal the magic value: ```solidity bytes4 constant INIT_MODE_MAGIC_VALUE = 0x80438043; if (bytes4(userOp.initCode[0:4]) == INIT_MODE_MAGIC_VALUE) { // Enter init mode } ``` ### `initCode` Encoding Format When init mode is detected, the remaining bytes of `userOp.initCode` after the magic value (i.e., `userOp.initCode[4:]`) MUST be ABI-decoded as: ```solidity abi.decode( userOp.initCode[4:], (uint256[], address[], bytes[], bytes) ) // (moduleTypeIds, modules, initDatas, installationSignature) ``` Where: - `moduleTypeIds`: Array of [ERC-7579] module type identifiers - `modules`: Array of module addresses to install - `initDatas`: Array of initialization data to pass to each module's `onInstall` function - `installationSignature`: [EIP-712] signature by the account owner authorizing the module installations ### EIP-712 Message Structure The `installationSignature` MUST be an [EIP-712] signature over the following typed data, using the account's own [EIP-712] domain: ```solidity bytes32 constant INIT_MODULES_TYPEHASH = keccak256( "InitModules(uint256[] moduleTypeIds,address[] modules,bytes[] initDatas,uint256 nonce)" ); ``` The struct hash is computed as: ```solidity bytes32 structHash = keccak256(abi.encode( INIT_MODULES_TYPEHASH, keccak256(abi.encodePacked(moduleTypeIds)), keccak256(abi.encodePacked(modules)), keccak256(abi.encode(initDatas)), // bytes[] requires element-by-element hashing nonce )); ``` Where `nonce` is `userOp.nonce`. The final digest passed to signature verification SHOULD be `_hashTypedDataV4(structHash)`, i.e., the [EIP-712] encoding with domain separator. ### Validation Flow Smart accounts MUST implement init mode on top of [ERC-4337]'s `validateUserOp` function. Implementations MUST: 1. Detect init mode by checking `bytes4(userOp.initCode[0:4]) == INIT_MODE_MAGIC_VALUE` 2. Decode `moduleTypeIds`, `modules`, `initDatas`, and `installationSignature` from `userOp.initCode[4:]` 3. Compute the [EIP-712] struct hash over `moduleTypeIds`, `modules`, `initDatas`, and `userOp.nonce`, then validate `installationSignature` using `isValidSignature(_hashTypedDataV4(structHash), installationSignature)` 4. Install each module using the standard [ERC-7579] `installModule` flow 5. Validate the user operation using `super._validateUserOp` 6. Return the validation result Implementations MUST return `SIG_VALIDATION_FAILED` (`1`) if decoding fails, the installation signature is invalid, any module installation fails, or user operation validation fails. ## Rationale ### Use of `initCode` as the Signaling and Data Channel [ERC-4337] EntryPoint v0.9 introduced the ability to include a non-empty `initCode` in a user operation for an already-deployed account, with the EntryPoint silently ignoring the factory call in that case. This makes `initCode` a natural fit for init mode: - The `initCode` field is included in the `userOpHash` computation per [ERC-4337], so module installation parameters are automatically committed to by the user operation signature - The nonce field is left entirely available for standard 2D nonce use The 4-byte magic value `0x8043` was chosen because a well-formed init mode `initCode` is always far longer than 20 bytes: the minimum ABI-encoded payload for `(uint256[], address[], bytes[], bytes)` is 256 bytes (4 _ 32-byte head offsets plus 4 _ 32-byte empty-length words), making a 4-byte prefix sufficient to unambiguously identify init mode without reserving the full 20-byte factory address slot. The value embeds the ERC number for self-documentation and avoids conflicts with Ethereum precompiles and real factory address prefixes. ### EIP-712 for Installation Authorization [EIP-712] typed data is used for `installationSignature` to provide human-readable signing requests in wallets and hardware signers. A signer presented with an `InitModules` type can see precisely which modules at which addresses are being authorized for installation, with what initialization data, at which nonce—giving the signed data unambiguous semantic meaning. This separates the owner's installation authorization from the user operation's execution authorization: the owner signs only the module installation intent, not the full user operation (gas parameters, calldata, etc.), which may be controlled by a different key such as a session key being granted in the same operation. Replay protection for `installationSignature` is provided by `userOp.nonce` included in the struct: once the user operation is executed and its nonce consumed, the same `installationSignature` cannot be replayed. Chain ID and account address binding are provided by the [EIP-712] domain separator. ### Compatibility with ERC-7579 This standard extends [ERC-7579] without breaking existing functionality: - Normal user operations continue to work unchanged - Standard module installation flows remain available - All [ERC-7579] security guarantees are preserved ## Backwards Compatibility This standard requires [ERC-4337] EntryPoint **v0.9 or later** (deployed at `0x433709009B8330FDa32311DF1C2AFA402eD8D009`). On earlier EntryPoint versions, a user operation with non-empty `initCode` for an already-deployed account is rejected by bundlers before reaching the account. Accounts not implementing this standard are unaffected: the EntryPoint does not call the factory when the account already exists, so the `initCode` contents are simply ignored by the runtime. Note that the v0.9 EntryPoint emits an `IgnoredInitCode` event whenever `initCode` is silently skipped for an existing account. Tools and indexers that use this event to detect orphaned factory calls will also observe it for every init mode user operation. Implementations SHOULD document this behavior in off-chain tooling. ## Reference Implementation ```solidity // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.27; import {AccountERC7579} from "@openzeppelin/contracts/account/extensions/draft-AccountERC7579.sol"; import {PackedUserOperation} from "@openzeppelin/contracts/interfaces/draft-IERC4337.sol"; import {ERC4337Utils} from "@openzeppelin/contracts/account/utils/draft-ERC4337Utils.sol"; import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; abstract contract InitModeAccount is AccountERC7579 { bytes4 private constant INIT_MODE_MAGIC_VALUE = 0x80438043; bytes32 private constant INIT_MODULES_TYPEHASH = keccak256("InitModules(uint256[] moduleTypeIds,address[] modules,bytes[] initDatas,uint256 nonce)"); function _validateUserOp( PackedUserOperation calldata userOp, bytes32 userOpHash ) internal virtual override returns (uint256) { if (bytes4(userOp.initCode[0:4]) == INIT_MODE_MAGIC_VALUE) { return _validateInitMode(userOp, userOpHash); } return super._validateUserOp(userOp, userOpHash); } function _validateInitMode( PackedUserOperation calldata userOp, bytes32 userOpHash ) internal returns (uint256) { ( bool success, uint256[] memory moduleTypeIds, address[] memory modules, bytes[] memory initDatas, bytes memory installationSignature ) = _tryDecodeInitModeData(userOp.initCode[4:]); if (!success) return ERC4337Utils.SIG_VALIDATION_FAILED; if (moduleTypeIds.length != modules.length || modules.length != initDatas.length) { return ERC4337Utils.SIG_VALIDATION_FAILED; } bytes32 structHash = _initModulesStructHash(moduleTypeIds, modules, initDatas, userOp.nonce); if ( isValidSignature(_hashTypedDataV4(structHash), installationSignature) != IERC1271.isValidSignature.selector ) { return ERC4337Utils.SIG_VALIDATION_FAILED; } for (uint256 i = 0; i < modules.length; i++) { _installModule(moduleTypeIds[i], modules[i], initDatas[i]); } return super._validateUserOp(userOp, userOpHash); } function _initModulesStructHash( uint256[] memory moduleTypeIds, address[] memory modules, bytes[] memory initDatas, uint256 nonce ) internal pure returns (bytes32) { bytes32[] memory initDataHashes = new bytes32[](initDatas.length); for (uint256 i = 0; i < initDatas.length; i++) { initDataHashes[i] = keccak256(initDatas[i]); } return keccak256(abi.encode( INIT_MODULES_TYPEHASH, keccak256(abi.encodePacked(moduleTypeIds)), keccak256(abi.encodePacked(modules)), keccak256(abi.encodePacked(initDataHashes)), nonce )); } function _tryDecodeInitModeData( bytes calldata data ) internal view returns ( bool success, uint256[] memory moduleTypeIds, address[] memory modules, bytes[] memory initDatas, bytes memory installationSignature ) { try this._decodeInitModeData(data) returns ( uint256[] memory _moduleTypeIds, address[] memory _modules, bytes[] memory _initDatas, bytes memory _installationSignature ) { return (true, _moduleTypeIds, _modules, _initDatas, _installationSignature); } catch { return (false, new uint256[](0), new address[](0), new bytes[](0), ""); } } function _decodeInitModeData( bytes calldata data ) external pure returns ( uint256[] memory moduleTypeIds, address[] memory modules, bytes[] memory initDatas, bytes memory installationSignature ) { return abi.decode(data, (uint256[], address[], bytes[], bytes)); } } ``` ## Security Considerations - **Authorization**: Module installations are authorized by the account owner's [EIP-712] `installationSignature`, providing human-readable wallet display of what is being authorized independently of whoever signs the user operation. - **Replay Protection**: The [EIP-712] `InitModules` struct includes `userOp.nonce`, ensuring the `installationSignature` cannot be reused once the nonce is consumed. The [EIP-712] domain separator additionally binds the signature to the specific account address and chain ID. - **Installation Ordering**: Modules should be installed only after `installationSignature` is successfully verified. This prevents unauthorized module installation in the event that signature verification fails. - **Module Validation**: Standard [ERC-7579] module validation applies (type checking, duplicate prevention, etc.). - **Array Length Validation**: Implementations should ensure that `moduleTypeIds`, `modules`, and `initDatas` arrays have the same length to prevent mismatched installations. - **EntryPoint Version**: This ERC requires the v0.9 EntryPoint. Accounts shouldn't assume init mode is available without confirming the EntryPoint version supports non-empty `initCode` for already-deployed accounts. - **`IgnoredInitCode` Event**: The v0.9 EntryPoint emits `IgnoredInitCode` for every init mode user operation. Security monitoring tools should not treat this event alone as an anomaly. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).