// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/access/AccessControl.sol"; import "./WLPReceiptToken.sol"; import "./interfaces/IWLPProgramEvents.sol"; /** * @title PublicChainMintController * @notice Destination-chain controller: mints `wLP` once per `lockRef` (replay protection). * @dev Relayer must verify Chain 138 `LPLocked` event / attestation off-chain or via future ZK proof. */ contract PublicChainMintController is AccessControl, IWLPProgramEvents { bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE"); WLPReceiptToken public immutable wlp; /// @notice Optional Chain 138 locker address for documentation / future cross-verify hooks. address public immutable chain138Locker; mapping(bytes32 => bool) public mintedForLock; bool public mintPaused; constructor(address wlp_, address chain138Locker_, address admin) { require(wlp_ != address(0) && admin != address(0), "MintCtl: zero"); wlp = WLPReceiptToken(wlp_); chain138Locker = chain138Locker_; _grantRole(DEFAULT_ADMIN_ROLE, admin); } /** * @notice Idempotent mint keyed by `lockRef` (must match Chain 138 locker emission). */ function mintForLock(bytes32 lockRef, address recipient, uint256 amount) external onlyRole(RELAYER_ROLE) { require(!mintPaused, "MintCtl: paused"); require(lockRef != bytes32(0), "MintCtl: zero lockRef"); require(recipient != address(0) && amount > 0, "MintCtl: bad args"); require(!mintedForLock[lockRef], "MintCtl: replay"); mintedForLock[lockRef] = true; wlp.mint(recipient, amount); emit WLPMinted(lockRef, recipient, amount); } function setMintPaused(bool paused) external onlyRole(DEFAULT_ADMIN_ROLE) { mintPaused = paused; } }