// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; /** * @title TokenRegistry * @notice Registry for all tokens on ChainID 138 * @dev Provides a centralized registry for token addresses, metadata, and status */ contract TokenRegistry is AccessControl { bytes32 public constant REGISTRAR_ROLE = keccak256("REGISTRAR_ROLE"); /** * @notice Token information structure */ struct TokenInfo { address tokenAddress; string name; string symbol; uint8 decimals; bool isActive; bool isNative; address bridgeAddress; // If bridged from another chain uint256 registeredAt; uint256 lastUpdated; } mapping(address => TokenInfo) private _tokens; mapping(string => address) private _tokensBySymbol; address[] private _tokenList; event TokenRegistered( address indexed tokenAddress, string name, string symbol, uint8 decimals, uint256 timestamp ); event TokenUpdated( address indexed tokenAddress, bool isActive, uint256 timestamp ); event TokenRemoved( address indexed tokenAddress, uint256 timestamp ); /** * @notice Constructor * @param admin Address that will receive DEFAULT_ADMIN_ROLE */ constructor(address admin) { _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(REGISTRAR_ROLE, admin); } /** * @notice Register a new token * @param tokenAddress Address of the token contract * @param name Token name * @param symbol Token symbol * @param decimals Number of decimals * @param isNative Whether this is a native token (e.g., ETH) * @param bridgeAddress Bridge address if this is a bridged token (address(0) if not) * @dev Requires REGISTRAR_ROLE */ function registerToken( address tokenAddress, string calldata name, string calldata symbol, uint8 decimals, bool isNative, address bridgeAddress ) external onlyRole(REGISTRAR_ROLE) { require(tokenAddress != address(0), "TokenRegistry: zero address"); require(_tokens[tokenAddress].tokenAddress == address(0), "TokenRegistry: token already registered"); require(_tokensBySymbol[symbol] == address(0), "TokenRegistry: symbol already used"); // Verify token contract exists (if not native) if (!isNative) { require(tokenAddress.code.length > 0, "TokenRegistry: invalid token contract"); } _tokens[tokenAddress] = TokenInfo({ tokenAddress: tokenAddress, name: name, symbol: symbol, decimals: decimals, isActive: true, isNative: isNative, bridgeAddress: bridgeAddress, registeredAt: block.timestamp, lastUpdated: block.timestamp }); _tokensBySymbol[symbol] = tokenAddress; _tokenList.push(tokenAddress); emit TokenRegistered(tokenAddress, name, symbol, decimals, block.timestamp); } /** * @notice Update token status * @param tokenAddress Address of the token * @param isActive New active status * @dev Requires REGISTRAR_ROLE */ function updateTokenStatus(address tokenAddress, bool isActive) external onlyRole(REGISTRAR_ROLE) { require(_tokens[tokenAddress].tokenAddress != address(0), "TokenRegistry: token not registered"); _tokens[tokenAddress].isActive = isActive; _tokens[tokenAddress].lastUpdated = block.timestamp; emit TokenUpdated(tokenAddress, isActive, block.timestamp); } /** * @notice Remove a token from the registry * @param tokenAddress Address of the token * @dev Requires REGISTRAR_ROLE */ function removeToken(address tokenAddress) external onlyRole(REGISTRAR_ROLE) { require(_tokens[tokenAddress].tokenAddress != address(0), "TokenRegistry: token not registered"); // Remove from symbol mapping delete _tokensBySymbol[_tokens[tokenAddress].symbol]; // Remove from list (swap with last element and pop) for (uint256 i = 0; i < _tokenList.length; i++) { if (_tokenList[i] == tokenAddress) { _tokenList[i] = _tokenList[_tokenList.length - 1]; _tokenList.pop(); break; } } delete _tokens[tokenAddress]; emit TokenRemoved(tokenAddress, block.timestamp); } /** * @notice Get token information * @param tokenAddress Address of the token * @return Token information struct */ function getTokenInfo(address tokenAddress) external view returns (TokenInfo memory) { return _tokens[tokenAddress]; } /** * @notice Get token address by symbol * @param symbol Token symbol * @return Token address (address(0) if not found) */ function getTokenBySymbol(string calldata symbol) external view returns (address) { address tokenAddr = _tokensBySymbol[symbol]; return tokenAddr; } /** * @notice Check if a token is registered * @param tokenAddress Address of the token * @return True if registered, false otherwise */ function isTokenRegistered(address tokenAddress) external view returns (bool) { return _tokens[tokenAddress].tokenAddress != address(0); } /** * @notice Check if a token is active * @param tokenAddress Address of the token * @return True if active, false otherwise */ function isTokenActive(address tokenAddress) external view returns (bool) { TokenInfo memory token = _tokens[tokenAddress]; return token.tokenAddress != address(0) && token.isActive; } /** * @notice Get all registered tokens * @return Array of token addresses */ function getAllTokens() external view returns (address[] memory) { return _tokenList; } /** * @notice Get count of registered tokens * @return Number of registered tokens */ function getTokenCount() external view returns (uint256) { return _tokenList.length; } }