Files
Meridian/contracts/core/MeridianToken.sol
Claude AI 7f001ff5f0 Initial Meridian Protocol implementation
- Complete MeridianToken standard with compliance & reserve checks
- Multi-custodian ReserveAggregator supporting bank/crypto/fund admin
- Comprehensive Compliance engine with KYC/AML/sanctions
- Full interface definitions and deployment scripts
- Test suite for core functionality
- Ready for GBP launch with Anchorage custody integration
2026-04-16 19:42:26 +00:00

260 lines
9.6 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "../interfaces/IReserveAggregator.sol";
import "../interfaces/ICompliance.sol";
/**
* @title MeridianToken
* @dev Core token contract implementing the Meridian Protocol Standard
* @notice Regulated entities can issue tokens backed by reserves with built-in compliance
*/
contract MeridianToken is ERC20, ERC20Permit, AccessControl, Pausable, ReentrancyGuard {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
bytes32 public constant COMPLIANCE_ROLE = keccak256("COMPLIANCE_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
// Reserve and compliance contracts
IReserveAggregator public reserveAggregator;
ICompliance public compliance;
// Token metadata
string public currency; // e.g., "GBP", "USD", "EUR"
string public issuerName; // e.g., "Meridian LLC", "HSBC"
uint256 public minimumCollateralRatio; // e.g., 102% = 10200 (basis points * 100)
// Minting controls
uint256 public dailyMintLimit;
uint256 public dailyBurnLimit;
mapping(uint256 => uint256) public dailyMinted; // day => amount minted
mapping(uint256 => uint256) public dailyBurned; // day => amount burned
// Events
event Minted(address indexed to, uint256 amount, bytes32 attestationHash);
event Burned(address indexed from, uint256 amount, bytes32 attestationHash);
event ReserveAggregatorUpdated(address indexed newAggregator);
event ComplianceUpdated(address indexed newCompliance);
event DailyLimitsUpdated(uint256 newMintLimit, uint256 newBurnLimit);
event MinimumCollateralRatioUpdated(uint256 newRatio);
/**
* @dev Constructor sets up the token with initial parameters
* @param name Token name (e.g., "Meridian GBP")
* @param symbol Token symbol (e.g., "MGBP")
* @param _currency Currency denomination (e.g., "GBP")
* @param _issuerName Name of issuing entity
* @param _reserveAggregator Address of reserve aggregator contract
* @param _compliance Address of compliance contract
* @param _admin Address to receive admin role
*/
constructor(
string memory name,
string memory symbol,
string memory _currency,
string memory _issuerName,
address _reserveAggregator,
address _compliance,
address _admin
) ERC20(name, symbol) ERC20Permit(name) {
require(_reserveAggregator != address(0), "Invalid reserve aggregator");
require(_compliance != address(0), "Invalid compliance contract");
require(_admin != address(0), "Invalid admin address");
currency = _currency;
issuerName = _issuerName;
reserveAggregator = IReserveAggregator(_reserveAggregator);
compliance = ICompliance(_compliance);
minimumCollateralRatio = 10200; // 102% default
dailyMintLimit = 10000000 * 10**decimals(); // 10M default
dailyBurnLimit = 10000000 * 10**decimals(); // 10M default
_grantRole(DEFAULT_ADMIN_ROLE, _admin);
_grantRole(MINTER_ROLE, _admin);
_grantRole(BURNER_ROLE, _admin);
_grantRole(COMPLIANCE_ROLE, _admin);
_grantRole(PAUSER_ROLE, _admin);
}
/**
* @dev Mint tokens with reserve attestation and compliance checks
* @param to Address to mint tokens to
* @param amount Amount to mint
* @param attestationHash Hash of reserve attestation document
*/
function mint(
address to,
uint256 amount,
bytes32 attestationHash
) external onlyRole(MINTER_ROLE) nonReentrant whenNotPaused {
require(to != address(0), "Cannot mint to zero address");
require(amount > 0, "Amount must be positive");
// Check daily mint limit
uint256 today = block.timestamp / 86400; // days since epoch
require(dailyMinted[today] + amount <= dailyMintLimit, "Daily mint limit exceeded");
// Check compliance
require(compliance.isWhitelisted(to), "Recipient not whitelisted");
require(!compliance.isSanctioned(to), "Recipient is sanctioned");
// Check reserve collateralization after mint
uint256 newTotalSupply = totalSupply() + amount;
(uint256 reserveValue, bool isValid) = reserveAggregator.getTotalReserveValue();
require(isValid, "Reserve data is stale");
uint256 requiredReserves = (newTotalSupply * minimumCollateralRatio) / 10000;
require(reserveValue >= requiredReserves, "Insufficient reserves for mint");
// Update daily tracking
dailyMinted[today] += amount;
// Mint tokens
_mint(to, amount);
emit Minted(to, amount, attestationHash);
}
/**
* @dev Burn tokens with attestation
* @param from Address to burn tokens from
* @param amount Amount to burn
* @param attestationHash Hash of burn attestation document
*/
function burn(
address from,
uint256 amount,
bytes32 attestationHash
) external onlyRole(BURNER_ROLE) nonReentrant whenNotPaused {
require(from != address(0), "Cannot burn from zero address");
require(amount > 0, "Amount must be positive");
require(balanceOf(from) >= amount, "Insufficient balance");
// Check daily burn limit
uint256 today = block.timestamp / 86400;
require(dailyBurned[today] + amount <= dailyBurnLimit, "Daily burn limit exceeded");
// Update daily tracking
dailyBurned[today] += amount;
// Burn tokens
_burn(from, amount);
emit Burned(from, amount, attestationHash);
}
/**
* @dev Override transfer to include compliance checks
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual override {
super._beforeTokenTransfer(from, to, amount);
// Skip compliance for minting/burning (handled in mint/burn functions)
if (from == address(0) || to == address(0)) {
return;
}
// Check compliance for regular transfers
require(!paused(), "Token transfers paused");
require(compliance.isWhitelisted(to), "Recipient not whitelisted");
require(!compliance.isSanctioned(from), "Sender is sanctioned");
require(!compliance.isSanctioned(to), "Recipient is sanctioned");
// Check velocity limits
require(compliance.checkVelocityLimits(from, amount), "Velocity limit exceeded");
}
/**
* @dev Get current collateralization ratio
* @return ratio Current ratio in basis points * 100 (e.g., 10200 = 102%)
* @return isValid Whether the reserve data is current and valid
*/
function getCurrentCollateralizationRatio() external view returns (uint256 ratio, bool isValid) {
(uint256 reserveValue, bool _isValid) = reserveAggregator.getTotalReserveValue();
if (!_isValid || totalSupply() == 0) {
return (0, false);
}
ratio = (reserveValue * 10000) / totalSupply();
return (ratio, true);
}
/**
* @dev Update reserve aggregator contract
*/
function updateReserveAggregator(address newAggregator) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(newAggregator != address(0), "Invalid aggregator address");
reserveAggregator = IReserveAggregator(newAggregator);
emit ReserveAggregatorUpdated(newAggregator);
}
/**
* @dev Update compliance contract
*/
function updateCompliance(address newCompliance) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(newCompliance != address(0), "Invalid compliance address");
compliance = ICompliance(newCompliance);
emit ComplianceUpdated(newCompliance);
}
/**
* @dev Update daily mint/burn limits
*/
function updateDailyLimits(
uint256 newMintLimit,
uint256 newBurnLimit
) external onlyRole(DEFAULT_ADMIN_ROLE) {
dailyMintLimit = newMintLimit;
dailyBurnLimit = newBurnLimit;
emit DailyLimitsUpdated(newMintLimit, newBurnLimit);
}
/**
* @dev Update minimum collateral ratio
*/
function updateMinimumCollateralRatio(uint256 newRatio) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(newRatio >= 10000, "Ratio must be at least 100%");
minimumCollateralRatio = newRatio;
emit MinimumCollateralRatioUpdated(newRatio);
}
/**
* @dev Pause token operations
*/
function pause() external onlyRole(PAUSER_ROLE) {
_pause();
}
/**
* @dev Unpause token operations
*/
function unpause() external onlyRole(PAUSER_ROLE) {
_unpause();
}
/**
* @dev Get remaining mint capacity for today
*/
function getRemainingMintCapacity() external view returns (uint256) {
uint256 today = block.timestamp / 86400;
return dailyMintLimit - dailyMinted[today];
}
/**
* @dev Get remaining burn capacity for today
*/
function getRemainingBurnCapacity() external view returns (uint256) {
uint256 today = block.timestamp / 86400;
return dailyBurnLimit - dailyBurned[today];
}
}