// 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 * @notice Bridge that accepts messages from a relay router * @dev Modified version of CCIPWETH9Bridge that accepts relay router */ contract CCIPRelayBridge is AccessControl { bytes32 public constant ROUTER_ROLE = keccak256("ROUTER_ROLE"); address public immutable weth9; address public relayRouter; // Track cross-chain transfers for replay protection mapping(bytes32 => bool) public processedTransfers; mapping(address => uint256) public nonces; event CrossChainTransferCompleted( bytes32 indexed messageId, uint64 indexed sourceChainSelector, address indexed recipient, uint256 amount ); constructor(address _weth9, address _relayRouter) { require(_weth9 != address(0), "CCIPRelayBridge: zero WETH9"); require(_relayRouter != address(0), "CCIPRelayBridge: zero router"); weth9 = _weth9; relayRouter = _relayRouter; _grantRole(ROUTER_ROLE, _relayRouter); _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); } /** * @notice Update relay router address */ function updateRelayRouter(address newRelayRouter) external onlyRole(DEFAULT_ADMIN_ROLE) { require(newRelayRouter != address(0), "CCIPRelayBridge: zero address"); _revokeRole(ROUTER_ROLE, relayRouter); relayRouter = newRelayRouter; _grantRole(ROUTER_ROLE, newRelayRouter); } /** * @notice Receive WETH9 tokens from another chain via relay * @param message The CCIP message */ function ccipReceive( IRouterClient.Any2EVMMessage calldata message ) external onlyRole(ROUTER_ROLE) { // Replay protection require(!processedTransfers[message.messageId], "CCIPRelayBridge: transfer already processed"); processedTransfers[message.messageId] = true; // Validate token amounts require(message.tokenAmounts.length > 0, "CCIPRelayBridge: no tokens"); require(message.tokenAmounts[0].token == weth9, "CCIPRelayBridge: invalid token"); uint256 amount = message.tokenAmounts[0].amount; require(amount > 0, "CCIPRelayBridge: invalid amount"); // Decode transfer data (recipient, amount, sender, nonce) (address recipient, , , ) = abi.decode( message.data, (address, uint256, address, uint256) ); require(recipient != address(0), "CCIPRelayBridge: zero recipient"); // Transfer WETH9 to recipient require(IERC20(weth9).transfer(recipient, amount), "CCIPRelayBridge: transfer failed"); emit CrossChainTransferCompleted( message.messageId, message.sourceChainSelector, recipient, amount ); } /** * @notice Get user nonce */ function getUserNonce(address user) external view returns (uint256) { return nonces[user]; } }