// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {AereModularAccount} from "./AereModularAccount.sol"; /// @title AereModularAccountFactory - CREATE2 factory for AereModularAccount /// @notice Deterministic: getAddress(rootOwner, salt) equals the deployed address. /// The root owner is mixed into the CREATE2 salt so nobody can front-run /// a counterfactual address with a different owner. Follows the /// AerePasskeyAccountFactoryV2 conventions (idempotent createAccount, /// immutable default EntryPoint). contract AereModularAccountFactory { bytes32 public constant ACCOUNT_INIT_CODE_HASH = keccak256(type(AereModularAccount).creationCode); /// @notice The EntryPoint every account created by this factory trusts. address public immutable defaultEntryPoint; event AccountCreated(address indexed account, address indexed rootOwner, address indexed entryPoint, uint256 salt); error ZeroEntryPoint(); error AddressMismatch(); constructor(address _defaultEntryPoint) { if (_defaultEntryPoint == address(0)) revert ZeroEntryPoint(); defaultEntryPoint = _defaultEntryPoint; } /// @notice Deterministic counterfactual address for (rootOwner, salt). function getAddress(address rootOwner, uint256 salt) public view returns (address) { bytes32 saltMix = _saltMix(rootOwner, salt); return address(uint160(uint256(keccak256(abi.encodePacked( bytes1(0xff), address(this), saltMix, ACCOUNT_INIT_CODE_HASH ))))); } /// @notice Idempotent: returns the existing account if already deployed. function createAccount(address rootOwner, uint256 salt) external returns (AereModularAccount account) { address predicted = getAddress(rootOwner, salt); if (predicted.code.length > 0) { return AereModularAccount(payable(predicted)); } account = new AereModularAccount{salt: _saltMix(rootOwner, salt)}(); account.initialize(defaultEntryPoint, rootOwner); if (address(account) != predicted) revert AddressMismatch(); emit AccountCreated(address(account), rootOwner, defaultEntryPoint, salt); } function _saltMix(address rootOwner, uint256 salt) internal pure returns (bytes32) { return keccak256(abi.encode(rootOwner, salt)); } }