// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; interface IERC6551Registry { /** * @dev The registry MUST emit the ERC6551AccountCreated event upon successful account creation. */ event ERC6551AccountCreated( address account, address indexed implementation, bytes32 salt, uint256 chainId, address indexed tokenContract, uint256 indexed tokenId ); /** * @dev The registry MUST revert with AccountCreationFailed error if the create2 operation fails. */ error AccountCreationFailed(); /** * @dev Creates a token bound account for a non-fungible token. * * If account has already been created, returns the account address without calling create2. * * Emits ERC6551AccountCreated event. * * @return account The address of the token bound account */ function createAccount( address implementation, bytes32 salt, uint256 chainId, address tokenContract, uint256 tokenId ) external returns (address account); /** * @dev Returns the computed token bound account address for a non-fungible token. * * @return account The address of the token bound account */ function account( address implementation, bytes32 salt, uint256 chainId, address tokenContract, uint256 tokenId ) external view returns (address account); } // Interface to call the initialize method on the account contract interface IERC6551AccountInitializable { function initialize( uint256 chainId, address tokenContract, uint256 tokenId ) external; } contract ERC6551Registry is IERC6551Registry { /** * @dev Creates an ERC6551 account */ function createAccount( address implementation, bytes32 salt, uint256 chainId, address tokenContract, uint256 tokenId ) external returns (address) { // Compute the account address bytes32 combinedSalt = _getCombinedSalt( salt, chainId, tokenContract, tokenId ); address predictedAddress = _computeAccountAddress( implementation, combinedSalt ); // Check if the account already exists uint256 codeSize; assembly { codeSize := extcodesize(predictedAddress) } // If account exists, return its address if (codeSize > 0) { return predictedAddress; } // Create the ERC-1167 proxy to implementation bytes memory bytecode = _getCreationBytecode(implementation); address deployedAddress; assembly { deployedAddress := create2( 0, // No ETH sent add(bytecode, 32), // Skip the length field mload(bytecode), // Length of the bytecode combinedSalt ) } // Check if deployment was successful if (deployedAddress == address(0)) { revert AccountCreationFailed(); } // Initialize the account with NFT data IERC6551AccountInitializable(deployedAddress).initialize( chainId, tokenContract, tokenId ); // Emit account created event emit ERC6551AccountCreated( deployedAddress, implementation, salt, chainId, tokenContract, tokenId ); return deployedAddress; } /** * @dev Returns the deterministic address for a TBA */ function account( address implementation, bytes32 salt, uint256 chainId, address tokenContract, uint256 tokenId ) public view returns (address) { bytes32 combinedSalt = _getCombinedSalt( salt, chainId, tokenContract, tokenId ); return _computeAccountAddress(implementation, combinedSalt); } /** * @dev Combines all parameters into a single salt */ function _getCombinedSalt( bytes32 salt, uint256 chainId, address tokenContract, uint256 tokenId ) internal pure returns (bytes32) { return keccak256(abi.encode(salt, chainId, tokenContract, tokenId)); } /** * @dev Returns the creation bytecode for a minimal proxy */ function _getCreationBytecode( address implementation ) internal pure returns (bytes memory) { return abi.encodePacked( // ERC-1167 minimal proxy bytecode hex"3d602d80600a3d3981f3363d3d373d3d3d363d73", implementation, hex"5af43d82803e903d91602b57fd5bf3" ); } /** * @dev Computes the account address using CREATE2 */ function _computeAccountAddress( address implementation, bytes32 combinedSalt ) internal view returns (address) { bytes memory bytecode = _getCreationBytecode(implementation); bytes32 bytecodeHash = keccak256(bytecode); return address( uint160( uint256( keccak256( abi.encodePacked( bytes1(0xff), address(this), combinedSalt, bytecodeHash ) ) ) ) ); } }