// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/utils/Pausable.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "../compliance/LegallyCompliantBase.sol"; /** * @title CompliantMonetaryUnitToken * @notice Generic GRU monetary-unit token for non-ISO units such as BTC. * @dev Mirrors the compliant fiat token controls while keeping monetary-unit metadata separate * from ISO-4217 and commodity classifications. */ contract CompliantMonetaryUnitToken is ERC20, Pausable, Ownable, LegallyCompliantBase { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); uint8 private immutable _decimalsStorage; string private _unitCode; constructor( string memory name_, string memory symbol_, uint8 decimals_, string memory unitCode_, address initialOwner, address admin, uint256 initialSupply ) ERC20(name_, symbol_) Ownable(initialOwner) LegallyCompliantBase(admin) { _decimalsStorage = decimals_; _unitCode = unitCode_; _grantRole(MINTER_ROLE, initialOwner); if (initialSupply > 0) { _mint(msg.sender, initialSupply); } } function decimals() public view override returns (uint8) { return _decimalsStorage; } function unitCode() external view returns (string memory) { return _unitCode; } function isMonetaryUnit() external pure returns (bool) { return true; } function _update( address from, address to, uint256 amount ) internal override whenNotPaused { super._update(from, to, amount); if (from != address(0) && to != address(0)) { bytes32 legalRefHash = _generateLegalReferenceHash( from, to, amount, abi.encodePacked(symbol(), " Transfer") ); emit ValueTransferDeclared(from, to, amount, legalRefHash); } } function pause() public onlyOwner { _pause(); } function unpause() public onlyOwner { _unpause(); } function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) { _mint(to, amount); } function burn(uint256 amount) public { _burn(msg.sender, amount); } }