// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "../../interfaces/IChainAdapter.sol"; import "../../interfaces/IAlltraTransport.sol"; import "../../UniversalCCIPBridge.sol"; /** * @title AlltraAdapter * @notice Bridge adapter for ALL Mainnet (EVM-compatible) * @dev ALL Mainnet (651940) is not supported by CCIP. Use setAlltraTransport() to set * AlltraCustomBridge (or other IAlltraTransport) for 138 <-> 651940 flows. * @dev Chain ID: 651940 (0x9f2a4) - https://chainlist.org/chain/651940 */ contract AlltraAdapter is IChainAdapter, AccessControl, ReentrancyGuard { using SafeERC20 for IERC20; bytes32 public constant BRIDGE_OPERATOR_ROLE = keccak256("BRIDGE_OPERATOR_ROLE"); // ALL Mainnet Chain ID - confirmed from ChainList uint256 public constant ALLTRA_MAINNET = 651940; UniversalCCIPBridge public universalBridge; IAlltraTransport public alltraTransport; // When set, used for 651940 instead of CCIP bool public isActive; /// @notice Configurable bridge fee (default 0.001 ALL). Set via setBridgeFee() after deployment. uint256 public bridgeFee; mapping(bytes32 => BridgeRequest) public bridgeRequests; mapping(address => uint256) public nonces; event AlltraBridgeInitiated( bytes32 indexed requestId, address indexed sender, address indexed token, uint256 amount, address recipient ); event AlltraBridgeConfirmed( bytes32 indexed requestId, bytes32 indexed alltraTxHash ); constructor(address admin, address _bridge) { _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(BRIDGE_OPERATOR_ROLE, admin); universalBridge = UniversalCCIPBridge(payable(_bridge)); isActive = true; bridgeFee = 1000000000000000; // 0.001 ALL default; update via setBridgeFee() per ALL Mainnet fee structure } /** * @notice Set custom transport for 138 <-> 651940 (no CCIP). When set, bridge() uses this instead of UniversalCCIPBridge. */ function setAlltraTransport(address _transport) external onlyRole(DEFAULT_ADMIN_ROLE) { alltraTransport = IAlltraTransport(_transport); } function getChainType() external pure override returns (string memory) { return "EVM"; // Generic chain type to distinguish from ALLTRA (orchestration layer) } function getChainIdentifier() external pure override returns (uint256 chainId, string memory identifier) { return (ALLTRA_MAINNET, "ALL-Mainnet"); // Updated identifier } function validateDestination(bytes calldata destination) external pure override returns (bool) { // Standard EVM address validation if (destination.length != 20) return false; address dest = address(bytes20(destination)); return dest != address(0); } function bridge( address token, uint256 amount, bytes calldata destination, bytes calldata recipient ) external payable override nonReentrant returns (bytes32 requestId) { require(isActive, "Adapter inactive"); require(amount > 0, "Zero amount"); require(this.validateDestination(destination), "Invalid destination"); address recipientAddr = address(bytes20(destination)); // Generate request ID requestId = keccak256(abi.encodePacked( msg.sender, token, amount, recipientAddr, nonces[msg.sender]++, block.timestamp )); // Lock tokens if (token == address(0)) { require(msg.value >= amount, "Insufficient ETH"); } else { IERC20(token).safeTransferFrom(msg.sender, address(this), amount); } // Create bridge request bridgeRequests[requestId] = BridgeRequest({ sender: msg.sender, token: token, amount: amount, destinationData: destination, requestId: requestId, status: BridgeStatus.Locked, createdAt: block.timestamp, completedAt: 0 }); // ALL Mainnet (651940) is not supported by CCIP. Use custom transport when set. if (address(alltraTransport) != address(0) && alltraTransport.isConfigured()) { alltraTransport.lockAndRelay{value: msg.value}(token, amount, recipientAddr); } else { // Fallback to UniversalCCIPBridge only if destination were CCIP-supported; 651940 is not. revert("AlltraAdapter: set AlltraCustomBridge via setAlltraTransport for 651940"); } emit AlltraBridgeInitiated(requestId, msg.sender, token, amount, recipientAddr); return requestId; } function getBridgeStatus(bytes32 requestId) external view override returns (BridgeRequest memory) { return bridgeRequests[requestId]; } function cancelBridge(bytes32 requestId) external override returns (bool) { BridgeRequest storage request = bridgeRequests[requestId]; require(request.status == BridgeStatus.Pending || request.status == BridgeStatus.Locked, "Cannot cancel"); require(msg.sender == request.sender, "Not request sender"); // Refund tokens if (request.token == address(0)) { payable(request.sender).transfer(request.amount); } else { IERC20(request.token).safeTransfer(request.sender, request.amount); } request.status = BridgeStatus.Cancelled; return true; } /// @notice Returns the current bridge fee (configurable via setBridgeFee). /// @param token Unused; fee is global per adapter. /// @param amount Unused. /// @param destination Unused. /// @return fee Current bridgeFee in wei. function estimateFee( address token, uint256 amount, bytes calldata destination ) external view override returns (uint256 fee) { return bridgeFee; } /// @notice Update bridge fee. Call after deployment when ALL Mainnet fee structure is known. function setBridgeFee(uint256 _fee) external onlyRole(DEFAULT_ADMIN_ROLE) { bridgeFee = _fee; } function setIsActive(bool _isActive) external onlyRole(DEFAULT_ADMIN_ROLE) { isActive = _isActive; } function confirmBridge(bytes32 requestId, bytes32 alltraTxHash) external onlyRole(BRIDGE_OPERATOR_ROLE) { BridgeRequest storage request = bridgeRequests[requestId]; require(request.status == BridgeStatus.Locked, "Invalid status"); request.status = BridgeStatus.Confirmed; request.completedAt = block.timestamp; emit AlltraBridgeConfirmed(requestId, alltraTxHash); } }