// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/utils/Pausable.sol"; /** * @title wXRP * @notice Wrapped XRP token (ERC-20) representing XRP locked on XRPL * @dev Mintable/burnable by authorized bridge controller only */ contract wXRP is ERC20, ERC20Burnable, AccessControl, Pausable { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); uint8 private constant DECIMALS = 18; event Minted(address indexed to, uint256 amount, bytes32 xrplTxHash); event Burned(address indexed from, uint256 amount, bytes32 xrplTxHash); error ZeroAmount(); error ZeroAddress(); constructor(address admin) ERC20("Wrapped XRP", "wXRP") { _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(MINTER_ROLE, admin); _grantRole(BURNER_ROLE, admin); } /** * @notice Mint wXRP tokens (bridge controller only) * @param to Recipient address * @param amount Amount to mint * @param xrplTxHash XRPL transaction hash that locked the XRP */ function mint( address to, uint256 amount, bytes32 xrplTxHash ) external onlyRole(MINTER_ROLE) whenNotPaused { if (to == address(0)) revert ZeroAddress(); if (amount == 0) revert ZeroAmount(); _mint(to, amount); emit Minted(to, amount, xrplTxHash); } /** * @notice Burn wXRP tokens to unlock XRP on XRPL (bridge controller only) * @param from Address to burn from * @param amount Amount to burn * @param xrplTxHash XRPL transaction hash for the unlock */ function burnFrom( address from, uint256 amount, bytes32 xrplTxHash ) external onlyRole(BURNER_ROLE) whenNotPaused { if (from == address(0)) revert ZeroAddress(); if (amount == 0) revert ZeroAmount(); _burn(from, amount); emit Burned(from, amount, xrplTxHash); } /** * @notice Override decimals to return 18 */ function decimals() public pure override returns (uint8) { return DECIMALS; } /** * @notice Pause token transfers */ function pause() external onlyRole(DEFAULT_ADMIN_ROLE) { _pause(); } /** * @notice Unpause token transfers */ function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) { _unpause(); } }