// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/AccessControl.sol"; import "../emoney/interfaces/IAccountWalletRegistry.sol"; /** * @title AccountWalletRegistryExtended * @notice Extends account-wallet registry with Smart Account (contract) support * @dev Links accountRefId to contract addresses (smart accounts); walletRefId = keccak256(abi.encodePacked(smartAccount)) */ contract AccountWalletRegistryExtended is IAccountWalletRegistry, AccessControl { bytes32 public constant ACCOUNT_MANAGER_ROLE = keccak256("ACCOUNT_MANAGER_ROLE"); address public smartAccountFactory; address public entryPoint; mapping(bytes32 => WalletLink[]) private _accountToWallets; mapping(bytes32 => bytes32[]) private _walletToAccounts; mapping(bytes32 => mapping(bytes32 => uint256)) private _walletIndex; mapping(bytes32 => mapping(bytes32 => bool)) private _walletAccountExists; mapping(bytes32 => address) private _smartAccountAddress; // walletRefId => smart account address event SmartAccountLinked(bytes32 indexed accountRefId, address indexed smartAccount, bytes32 provider); constructor(address admin, address smartAccountFactory_, address entryPoint_) { _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(ACCOUNT_MANAGER_ROLE, admin); smartAccountFactory = smartAccountFactory_; entryPoint = entryPoint_; } function setSmartAccountFactory(address factory) external onlyRole(DEFAULT_ADMIN_ROLE) { smartAccountFactory = factory; } function setEntryPoint(address entryPoint_) external onlyRole(DEFAULT_ADMIN_ROLE) { entryPoint = entryPoint_; } function linkAccountToWallet(bytes32 accountRefId, bytes32 walletRefId, bytes32 provider) external override onlyRole(ACCOUNT_MANAGER_ROLE) { require(accountRefId != bytes32(0), "zero accountRefId"); require(walletRefId != bytes32(0), "zero walletRefId"); require(provider != bytes32(0), "zero provider"); if (_walletAccountExists[walletRefId][accountRefId]) { uint256 index = _walletIndex[accountRefId][walletRefId]; require(index < _accountToWallets[accountRefId].length, "index out of bounds"); WalletLink storage link = _accountToWallets[accountRefId][index]; require(link.walletRefId == walletRefId, "link mismatch"); link.active = true; link.linkedAt = uint64(block.timestamp); } else { WalletLink memory newLink = WalletLink({ walletRefId: walletRefId, linkedAt: uint64(block.timestamp), active: true, provider: provider }); _accountToWallets[accountRefId].push(newLink); _walletIndex[accountRefId][walletRefId] = _accountToWallets[accountRefId].length - 1; _walletAccountExists[walletRefId][accountRefId] = true; _walletToAccounts[walletRefId].push(accountRefId); } emit AccountWalletLinked(accountRefId, walletRefId, provider, uint64(block.timestamp)); } function linkSmartAccount(bytes32 accountRefId, address smartAccount, bytes32 provider) external onlyRole(ACCOUNT_MANAGER_ROLE) { require(smartAccount != address(0), "AccountWalletRegistryExtended: zero smartAccount"); require(_isContract(smartAccount), "AccountWalletRegistryExtended: not a contract"); bytes32 walletRefId = keccak256(abi.encodePacked(smartAccount)); this.linkAccountToWallet(accountRefId, walletRefId, provider); _smartAccountAddress[walletRefId] = smartAccount; emit SmartAccountLinked(accountRefId, smartAccount, provider); } function unlinkAccountFromWallet(bytes32 accountRefId, bytes32 walletRefId) external override onlyRole(ACCOUNT_MANAGER_ROLE) { require(_walletAccountExists[walletRefId][accountRefId], "not linked"); uint256 index = _walletIndex[accountRefId][walletRefId]; require(index < _accountToWallets[accountRefId].length, "index out of bounds"); _accountToWallets[accountRefId][index].active = false; _walletAccountExists[walletRefId][accountRefId] = false; emit AccountWalletUnlinked(accountRefId, walletRefId); } function getWallets(bytes32 accountRefId) external view override returns (WalletLink[] memory) { return _accountToWallets[accountRefId]; } function getAccounts(bytes32 walletRefId) external view override returns (bytes32[] memory) { return _walletToAccounts[walletRefId]; } function isLinked(bytes32 accountRefId, bytes32 walletRefId) external view override returns (bool) { return _walletAccountExists[walletRefId][accountRefId]; } function isActive(bytes32 accountRefId, bytes32 walletRefId) external view override returns (bool) { if (!_walletAccountExists[walletRefId][accountRefId]) return false; uint256 index = _walletIndex[accountRefId][walletRefId]; return index < _accountToWallets[accountRefId].length && _accountToWallets[accountRefId][index].active; } function isSmartAccount(bytes32 walletRefId) public view returns (bool) { return _smartAccountAddress[walletRefId] != address(0); } function isSmartAccountAddress(address addr) public view returns (bool) { return _smartAccountAddress[keccak256(abi.encodePacked(addr))] == addr; } function _isContract(address account) internal view returns (bool) { return account.code.length > 0; } }