// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/utils/Pausable.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "../compliance/LegallyCompliantBase.sol"; /** * @title CompliantFiatToken * @notice Generic ISO-4217 compliant fiat/commodity token (ERC-20, DEX-ready) * @dev Use for cEURC, cGBPC, cAUDC, cJPYC, cCHFC, cCADC, cXAUC, cXAUT, etc. * Full ERC-20 for DEX liquidity pools; inherits LegallyCompliantBase. * * XAU (gold) invariant — enforced by policy and integrators, not by ERC-20 math: * When `currencyCode()` is `"XAU"` (e.g. cXAUC, cXAUT), **one full token** means * **exactly one troy ounce** of fine gold: balances and `mint`/`transfer` amounts use * `10 ** decimals()` base units per troy ounce (with 6 decimals, `1_000_000` base = 1 oz). * Fiat codes (USD, EUR, …) use one full token as one unit of that currency at `decimals`. */ contract CompliantFiatToken is ERC20, Pausable, Ownable, LegallyCompliantBase { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); uint8 private immutable _decimalsStorage; string private _currencyCode; /** * @notice Constructor * @param name_ Token name (e.g. "Euro Coin (Compliant)") * @param symbol_ Token symbol (e.g. "cEURC") * @param decimals_ Token decimals (e.g. 6) * @param currencyCode_ ISO-4217 code (e.g. "EUR") * @param initialOwner Owner address * @param admin Compliance admin (DEFAULT_ADMIN_ROLE) * @param initialSupply Initial supply to mint to deployer */ constructor( string memory name_, string memory symbol_, uint8 decimals_, string memory currencyCode_, address initialOwner, address admin, uint256 initialSupply ) ERC20(name_, symbol_) Ownable(initialOwner) LegallyCompliantBase(admin) { _decimalsStorage = decimals_; _currencyCode = currencyCode_; _grantRole(MINTER_ROLE, initialOwner); if (initialSupply > 0) { _mint(msg.sender, initialSupply); } } function decimals() public view override returns (uint8) { return _decimalsStorage; } function currencyCode() external view returns (string memory) { return _currencyCode; } /** * @notice Internal transfer with compliance tracking */ 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(); } /// @notice Mint (DBIS Rail: grant MINTER_ROLE only to DBIS_GRU_MintController; revoke from others) function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) { _mint(to, amount); } function burn(uint256 amount) public { _burn(msg.sender, amount); } }