// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import "../vendor/openzeppelin/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; /** * @title ChainRegistry * @notice Central registry for all supported blockchains (EVM and non-EVM) * @dev Maps chain identifiers to adapter contracts and metadata */ contract ChainRegistry is Initializable, AccessControlUpgradeable, UUPSUpgradeable { bytes32 public constant REGISTRY_ADMIN_ROLE = keccak256("REGISTRY_ADMIN_ROLE"); bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); enum ChainType { EVM, // Ethereum, Polygon, Arbitrum, etc. XRPL, // XRP Ledger XDC, // XDC Network (EVM-compatible but different address format) Stellar, // Stellar Network Algorand, // Algorand Hedera, // Hedera Hashgraph Tron, // Tron TON, // The Open Network Cosmos, // Cosmos Hub Solana, // Solana Fabric, // Hyperledger Fabric Corda, // R3 Corda Indy, // Hyperledger Indy Firefly, // Hyperledger Firefly Cacti, // Hyperledger Cacti Other // Custom/unknown } struct ChainMetadata { uint256 chainId; // For EVM chains (0 for non-EVM) string chainIdentifier; // For non-EVM (e.g., "XRPL", "Fabric-Channel1") ChainType chainType; address adapter; // Bridge adapter contract address bool isActive; uint256 minConfirmations; // Required confirmations uint256 avgBlockTime; // Average block time in seconds bool requiresOracle; // Non-EVM chains need oracle string rpcEndpoint; // RPC endpoint (encrypted or public) string explorerUrl; // Block explorer URL bytes additionalData; // Chain-specific config uint256 addedAt; uint256 lastUpdated; } // EVM chains: chainId => metadata mapping(uint256 => ChainMetadata) public evmChains; // Non-EVM chains: identifier => metadata mapping(string => ChainMetadata) public nonEvmChains; // All registered chain identifiers uint256[] public registeredEVMChainIds; string[] public registeredNonEVMIdentifiers; // Adapter validation mapping(address => bool) public isValidAdapter; mapping(address => ChainType) public adapterToChainType; event ChainRegistered( uint256 indexed chainId, string indexed chainIdentifier, ChainType chainType, address adapter ); event ChainUpdated( uint256 indexed chainId, string indexed chainIdentifier, bool isActive ); event ChainDeactivated( uint256 indexed chainId, string indexed chainIdentifier ); event AdapterUpdated( uint256 indexed chainId, string indexed chainIdentifier, address oldAdapter, address newAdapter ); /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } function initialize(address admin) external initializer { __AccessControl_init(); __UUPSUpgradeable_init(); _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(REGISTRY_ADMIN_ROLE, admin); _grantRole(UPGRADER_ROLE, admin); } function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE) {} /** * @notice Register an EVM chain */ function registerEVMChain( uint256 chainId, address adapter, string calldata explorerUrl, uint256 minConfirmations, uint256 avgBlockTime, bytes calldata additionalData ) external onlyRole(REGISTRY_ADMIN_ROLE) { require(chainId != 0, "Invalid chain ID"); require(adapter != address(0), "Zero adapter"); require(adapter.code.length > 0, "Not a contract"); ChainMetadata storage chain = evmChains[chainId]; // If new chain, add to list if (chain.addedAt == 0) { registeredEVMChainIds.push(chainId); } chain.chainId = chainId; chain.chainIdentifier = string(abi.encodePacked("EVM-", chainId)); chain.chainType = ChainType.EVM; chain.adapter = adapter; chain.isActive = true; chain.minConfirmations = minConfirmations; chain.avgBlockTime = avgBlockTime; chain.requiresOracle = false; chain.explorerUrl = explorerUrl; chain.additionalData = additionalData; chain.lastUpdated = block.timestamp; if (chain.addedAt == 0) { chain.addedAt = block.timestamp; } isValidAdapter[adapter] = true; adapterToChainType[adapter] = ChainType.EVM; emit ChainRegistered(chainId, chain.chainIdentifier, ChainType.EVM, adapter); } /** * @notice Register a non-EVM chain */ function registerNonEVMChain( string calldata chainIdentifier, ChainType chainType, address adapter, string calldata explorerUrl, uint256 minConfirmations, uint256 avgBlockTime, bool requiresOracle, bytes calldata additionalData ) external onlyRole(REGISTRY_ADMIN_ROLE) { require(bytes(chainIdentifier).length > 0, "Empty identifier"); require(adapter != address(0), "Zero adapter"); require(adapter.code.length > 0, "Not a contract"); require(chainType != ChainType.EVM, "Use registerEVMChain"); ChainMetadata storage chain = nonEvmChains[chainIdentifier]; // If new chain, add to list if (chain.addedAt == 0) { registeredNonEVMIdentifiers.push(chainIdentifier); } chain.chainId = 0; chain.chainIdentifier = chainIdentifier; chain.chainType = chainType; chain.adapter = adapter; chain.isActive = true; chain.minConfirmations = minConfirmations; chain.avgBlockTime = avgBlockTime; chain.requiresOracle = requiresOracle; chain.explorerUrl = explorerUrl; chain.additionalData = additionalData; chain.lastUpdated = block.timestamp; if (chain.addedAt == 0) { chain.addedAt = block.timestamp; } isValidAdapter[adapter] = true; adapterToChainType[adapter] = chainType; emit ChainRegistered(0, chainIdentifier, chainType, adapter); } /** * @notice Update chain adapter */ function updateAdapter( uint256 chainId, string calldata chainIdentifier, address newAdapter ) external onlyRole(REGISTRY_ADMIN_ROLE) { require(newAdapter != address(0), "Zero adapter"); require(newAdapter.code.length > 0, "Not a contract"); address oldAdapter; if (chainId != 0) { ChainMetadata storage chain = evmChains[chainId]; require(chain.addedAt != 0, "Chain not registered"); oldAdapter = chain.adapter; chain.adapter = newAdapter; chain.lastUpdated = block.timestamp; } else { ChainMetadata storage chain = nonEvmChains[chainIdentifier]; require(chain.addedAt != 0, "Chain not registered"); oldAdapter = chain.adapter; chain.adapter = newAdapter; chain.lastUpdated = block.timestamp; } isValidAdapter[oldAdapter] = false; isValidAdapter[newAdapter] = true; emit AdapterUpdated(chainId, chainIdentifier, oldAdapter, newAdapter); } /** * @notice Enable/disable chain */ function setChainActive( uint256 chainId, string calldata chainIdentifier, bool active ) external onlyRole(REGISTRY_ADMIN_ROLE) { if (chainId != 0) { ChainMetadata storage chain = evmChains[chainId]; require(chain.addedAt != 0, "Chain not registered"); chain.isActive = active; chain.lastUpdated = block.timestamp; } else { ChainMetadata storage chain = nonEvmChains[chainIdentifier]; require(chain.addedAt != 0, "Chain not registered"); chain.isActive = active; chain.lastUpdated = block.timestamp; } emit ChainUpdated(chainId, chainIdentifier, active); if (!active) { emit ChainDeactivated(chainId, chainIdentifier); } } // View functions function getEVMChain(uint256 chainId) external view returns (ChainMetadata memory) { return evmChains[chainId]; } function getNonEVMChain(string calldata chainIdentifier) external view returns (ChainMetadata memory) { return nonEvmChains[chainIdentifier]; } function getAdapter(uint256 chainId, string calldata chainIdentifier) external view returns (address) { if (chainId != 0) { return evmChains[chainId].adapter; } else { return nonEvmChains[chainIdentifier].adapter; } } function isChainActive(uint256 chainId, string calldata chainIdentifier) external view returns (bool) { if (chainId != 0) { return evmChains[chainId].isActive; } else { return nonEvmChains[chainIdentifier].isActive; } } function getAllEVMChains() external view returns (uint256[] memory, ChainMetadata[] memory) { uint256 length = registeredEVMChainIds.length; ChainMetadata[] memory chains = new ChainMetadata[](length); for (uint256 i = 0; i < length; i++) { chains[i] = evmChains[registeredEVMChainIds[i]]; } return (registeredEVMChainIds, chains); } function getAllNonEVMChains() external view returns (string[] memory, ChainMetadata[] memory) { uint256 length = registeredNonEVMIdentifiers.length; ChainMetadata[] memory chains = new ChainMetadata[](length); for (uint256 i = 0; i < length; i++) { chains[i] = nonEvmChains[registeredNonEVMIdentifiers[i]]; } return (registeredNonEVMIdentifiers, chains); } function getTotalChains() external view returns (uint256, uint256) { return (registeredEVMChainIds.length, registeredNonEVMIdentifiers.length); } }