Files
smom-dbis-138/contracts/bridge/EtherlinkRelayReceiver.sol
2026-03-02 12:14:09 -08:00

64 lines
2.1 KiB
Solidity

// 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";
/**
* @title EtherlinkRelayReceiver
* @notice Relay-compatible receiver on Etherlink (chain 42793). Accepts relayMintOrUnlock from off-chain relay.
* @dev When CCIP does not support Etherlink, custom relay monitors source and calls this contract.
* Idempotency via messageId; only RELAYER_ROLE can call.
*/
contract EtherlinkRelayReceiver is AccessControl, ReentrancyGuard {
using SafeERC20 for IERC20;
bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE");
mapping(bytes32 => bool) public processed;
event RelayMintOrUnlock(
bytes32 indexed messageId,
address indexed token,
address indexed recipient,
uint256 amount
);
constructor(address admin) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(RELAYER_ROLE, admin);
}
/**
* @notice Mint or unlock tokens to recipient. Only relayer; idempotent per messageId.
* @param messageId Source chain message id (for idempotency).
* @param token Token address (address(0) for native).
* @param recipient Recipient on Etherlink.
* @param amount Amount to transfer.
*/
function relayMintOrUnlock(
bytes32 messageId,
address token,
address recipient,
uint256 amount
) external onlyRole(RELAYER_ROLE) nonReentrant {
require(!processed[messageId], "already processed");
require(recipient != address(0), "zero recipient");
require(amount > 0, "zero amount");
processed[messageId] = true;
if (token == address(0)) {
(bool sent,) = payable(recipient).call{value: amount}("");
require(sent, "transfer failed");
} else {
IERC20(token).safeTransfer(recipient, amount);
}
emit RelayMintOrUnlock(messageId, token, recipient, amount);
}
receive() external payable {}
}