// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "../ccip/IRouterClient.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; /** * @title CCIP Relay Bridge (LINK) * @notice Accepts relayed CCIP messages carrying canonical LINK on the destination chain. * @dev Companion to CCIPRelayBridge (WETH9-only). Grant ROUTER_ROLE to the same CCIPRelayRouter. */ contract CCIPRelayBridgeLINK is AccessControl { bytes32 public constant ROUTER_ROLE = keccak256("ROUTER_ROLE"); address public immutable link; address public relayRouter; mapping(bytes32 => bool) public processedTransfers; event CrossChainTransferCompleted( bytes32 indexed messageId, uint64 indexed sourceChainSelector, address indexed recipient, uint256 amount ); constructor(address _link, address _relayRouter) { require(_link != address(0), "CCIPRelayBridgeLINK: zero LINK"); require(_relayRouter != address(0), "CCIPRelayBridgeLINK: zero router"); link = _link; relayRouter = _relayRouter; _grantRole(ROUTER_ROLE, _relayRouter); _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); } function updateRelayRouter(address newRelayRouter) external onlyRole(DEFAULT_ADMIN_ROLE) { require(newRelayRouter != address(0), "CCIPRelayBridgeLINK: zero address"); _revokeRole(ROUTER_ROLE, relayRouter); relayRouter = newRelayRouter; _grantRole(ROUTER_ROLE, newRelayRouter); } function ccipReceive(IRouterClient.Any2EVMMessage calldata message) external onlyRole(ROUTER_ROLE) { require(!processedTransfers[message.messageId], "CCIPRelayBridgeLINK: already processed"); processedTransfers[message.messageId] = true; require(message.tokenAmounts.length > 0, "CCIPRelayBridgeLINK: no tokens"); require(message.tokenAmounts[0].token == link, "CCIPRelayBridgeLINK: invalid token"); uint256 amount = message.tokenAmounts[0].amount; require(amount > 0, "CCIPRelayBridgeLINK: invalid amount"); address recipient; if (message.data.length == 64) { (recipient, ) = abi.decode(message.data, (address, uint256)); } else if (message.data.length >= 128) { (recipient, , , ) = abi.decode(message.data, (address, uint256, address, uint256)); } else { revert("CCIPRelayBridgeLINK: invalid data"); } require(recipient != address(0), "CCIPRelayBridgeLINK: zero recipient"); require(IERC20(link).transfer(recipient, amount), "CCIPRelayBridgeLINK: transfer failed"); emit CrossChainTransferCompleted(message.messageId, message.sourceChainSelector, recipient, amount); } }