// SPDX-License-Identifier: MIT pragma solidity ^0.8.36; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {ERC20Pausable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; /// @title PremiumToken /// @notice ERC-20 token with modular launch controls, transfer limits, and tax distribution. /// @dev Tax and burn transfers call the parent implementation directly to avoid recursive taxation. contract PremiumToken is ERC20, ERC20Pausable, Ownable { using SafeERC20 for IERC20; error InvalidOwner(); error InvalidSupply(); error InvalidDecimals(); error InvalidTax(); error InvalidLimit(); error InvalidAddress(); error FeatureDisabled(); error CannotBlacklistOwner(); error BlacklistedAccount(); error AddressNotWhitelisted(); error TradingDisabled(); error MaxTxExceeded(); error MaxWalletExceeded(); error NothingToWithdraw(); error FeeTransferFailed(); error InvalidCap(); error CapExceeded(); error UnsafeRenounce(); error InvalidLaunchConfiguration(); error LaunchAlreadyStarted(); error LaunchCannotBeRelocked(); uint256 public constant MAX_TAX = 2_500; uint256 private constant BPS = 10_000; uint256 private constant MIN_LIMIT_BPS = 10; uint256 public immutable protectionBlocks; uint256 public immutable launchMaxTxAmount; uint256 public immutable launchMaxWalletAmount; uint256 public launchBlock; uint256 public protectionEndBlock; bool public launchStarted; uint16 private constant FEATURE_LAUNCH_PROTECTION = 1 << 11; event LaunchStarted(uint256 startBlock, uint256 endBlock); uint256 public immutable maximumSupply; uint8 private immutable _customDecimals; uint16 private immutable _features; uint256 public buyTax; uint256 public sellTax; uint256 public transferTax; uint256 public burnTax; address public taxReceiver; uint256 public maxWalletAmount; uint256 public maxTxAmount; bool public maxWalletActive; bool public maxTxActive; bool public whitelistActive; bool public tradingEnabled; mapping(address account => bool) private _blacklisted; mapping(address account => bool) private _whitelisted; mapping(address account => bool) public isTaxExempt; mapping(address account => bool) public isLimitExempt; mapping(address account => bool) public isTradingExempt; mapping(address account => bool) public automatedMarketMakers; uint16 private constant FEATURE_BURN = 1 << 0; uint16 private constant FEATURE_MINT = 1 << 1; uint16 private constant FEATURE_PAUSE = 1 << 2; uint16 private constant FEATURE_BLACKLIST = 1 << 3; uint16 private constant FEATURE_WHITELIST = 1 << 4; uint16 private constant FEATURE_MAX_WALLET = 1 << 5; uint16 private constant FEATURE_MAX_TX = 1 << 6; uint16 private constant FEATURE_TRADING_CONTROL = 1 << 7; uint16 private constant FEATURE_CAP = 1 << 8; uint16 private constant FEATURE_TRANSFER_OWNERSHIP = 1 << 9; uint16 private constant FEATURE_RENOUNCE = 1 << 10; event BlacklistUpdated(address indexed account, bool blocked); event WhitelistUpdated(address indexed account, bool allowed); event WhitelistStatusUpdated(bool enabled); event TradingStatusUpdated(bool enabled); event TaxUpdated(uint256 buyTax, uint256 sellTax, uint256 transferTax, uint256 burnTax); event TaxReceiverUpdated(address indexed receiver); event TaxExemptUpdated(address indexed account, bool exempt); event LimitExemptUpdated(address indexed account, bool exempt); event TradingExemptUpdated(address indexed account, bool exempt); event MaxWalletUpdated(uint256 amount, bool enabled); event MaxTransactionUpdated(uint256 amount, bool enabled); event AMMUpdated(address indexed account, bool enabled); event TaxCollected(address indexed from, address indexed to, address indexed receiver, uint256 receiverAmount, uint256 burnedAmount); event TaxWithdrawn(address indexed receiver, uint256 amount); constructor( string memory name_, string memory symbol_, uint8 decimals_, uint256 totalSupply_, address owner_, address feeReceiver_, bool[12] memory features_, uint256[6] memory params_, address taxReceiver_, bool tradingInitiallyEnabled_, address[] memory initialWhitelist_, address[] memory initialTaxExempt_, uint256 maximumSupply_, uint256[3] memory launchParams_ ) payable ERC20(name_, symbol_) Ownable(owner_) { if (owner_ == address(0)) revert InvalidOwner(); if (totalSupply_ == 0) revert InvalidSupply(); if (decimals_ > 18) revert InvalidDecimals(); _validateTaxConfiguration(params_[0], params_[1], params_[2], params_[3]); _validateLimit(features_[5], params_[4]); _validateLimit(features_[6], params_[5]); bool hasTax = params_[0] != 0 || params_[1] != 0 || params_[2] != 0; if (hasTax && taxReceiver_ == address(0)) revert InvalidAddress(); if (features_[8] && (!features_[1] || maximumSupply_ < totalSupply_)) revert InvalidCap(); maximumSupply = features_[8] ? maximumSupply_ * 10 ** decimals_ : 0; _customDecimals = decimals_; _features = _packFeatures(features_); buyTax = params_[0]; sellTax = params_[1]; transferTax = params_[2]; burnTax = params_[3]; taxReceiver = taxReceiver_ == address(0) ? owner_ : taxReceiver_; uint256 supply = totalSupply_ * 10 ** decimals_; if (features_[11]) { if (!features_[7] || tradingInitiallyEnabled_ || launchParams_[0] == 0 || launchParams_[0] > 100) revert InvalidLaunchConfiguration(); _validateLimit(true, launchParams_[1]); _validateLimit(true, launchParams_[2]); if ((features_[6] && launchParams_[1] > params_[5]) || (features_[5] && launchParams_[2] > params_[4])) revert InvalidLaunchConfiguration(); if ((supply * launchParams_[1]) / BPS == 0 || (supply * launchParams_[2]) / BPS == 0) revert InvalidLaunchConfiguration(); } protectionBlocks = features_[11] ? launchParams_[0] : 0; launchMaxTxAmount = features_[11] ? (supply * launchParams_[1]) / BPS : 0; launchMaxWalletAmount = features_[11] ? (supply * launchParams_[2]) / BPS : 0; maxWalletAmount = features_[5] ? (supply * params_[4]) / BPS : supply; maxTxAmount = features_[6] ? (supply * params_[5]) / BPS : supply; if ((features_[5] && maxWalletAmount == 0) || (features_[6] && maxTxAmount == 0)) revert InvalidLimit(); maxWalletActive = features_[5]; maxTxActive = features_[6]; whitelistActive = features_[4]; tradingEnabled = features_[7] ? tradingInitiallyEnabled_ : true; _grantSystemAccess(owner_); _grantSystemAccess(address(this)); _grantSystemAccess(taxReceiver); for (uint256 i; i < initialWhitelist_.length; ++i) { _setWhitelist(initialWhitelist_[i], true); } for (uint256 i; i < initialTaxExempt_.length; ++i) { _setTaxExempt(initialTaxExempt_[i], true); } _mint(owner_, supply); _forwardFee(feeReceiver_); } receive() external payable {} function decimals() public view override returns (uint8) { return _customDecimals; } function generator() external pure returns (string memory) { return "https://tokengeneratorapp.com"; } function cappedSupplyEnabled() public view returns (bool) { return _hasFeature(FEATURE_CAP); } function ownershipTransferEnabled() public view returns (bool) { return _hasFeature(FEATURE_TRANSFER_OWNERSHIP); } function renounceOwnershipEnabled() public view returns (bool) { return _hasFeature(FEATURE_RENOUNCE); } function burnEnabled() public view returns (bool) { return _hasFeature(FEATURE_BURN); } function mintEnabled() public view returns (bool) { return _hasFeature(FEATURE_MINT); } function pauseEnabled() public view returns (bool) { return _hasFeature(FEATURE_PAUSE); } function blacklistEnabled() public view returns (bool) { return _hasFeature(FEATURE_BLACKLIST); } function whitelistEnabled() public view returns (bool) { return _hasFeature(FEATURE_WHITELIST); } function maxWalletEnabled() public view returns (bool) { return _hasFeature(FEATURE_MAX_WALLET); } function maxTransactionEnabled() public view returns (bool) { return _hasFeature(FEATURE_MAX_TX); } function tradingControlEnabled() public view returns (bool) { return _hasFeature(FEATURE_TRADING_CONTROL); } function burn(uint256 amount) external { if (!burnEnabled()) revert FeatureDisabled(); _burn(msg.sender, amount); } function burnFrom(address account, uint256 amount) external { if (!burnEnabled()) revert FeatureDisabled(); _spendAllowance(account, msg.sender, amount); _burn(account, amount); } function mint(address to, uint256 amount) external onlyOwner { if (!mintEnabled()) revert FeatureDisabled(); _mint(to, amount); } function pause() external onlyOwner { if (!pauseEnabled()) revert FeatureDisabled(); _pause(); } function unpause() external onlyOwner { if (!pauseEnabled()) revert FeatureDisabled(); _unpause(); } function blacklist(address account) external onlyOwner { if (!blacklistEnabled()) revert FeatureDisabled(); if (account == owner()) revert CannotBlacklistOwner(); _blacklisted[account] = true; emit BlacklistUpdated(account, true); } function unblacklist(address account) external onlyOwner { if (!blacklistEnabled()) revert FeatureDisabled(); _blacklisted[account] = false; emit BlacklistUpdated(account, false); } function isBlacklisted(address account) external view returns (bool) { return _blacklisted[account]; } function setWhitelist(address account, bool allowed) external onlyOwner { if (!whitelistEnabled()) revert FeatureDisabled(); _setWhitelist(account, allowed); } function setWhitelistEnabled(bool enabled) external onlyOwner { if (!whitelistEnabled()) revert FeatureDisabled(); whitelistActive = enabled; emit WhitelistStatusUpdated(enabled); } function isWhitelisted(address account) external view returns (bool) { return _whitelisted[account]; } function setTradingEnabled(bool enabled) external onlyOwner { if (!tradingControlEnabled()) revert FeatureDisabled(); if (launchProtectionEnabled()) { if (enabled && !launchStarted) { _startLaunch(); return; } if (!enabled && launchStarted) revert LaunchCannotBeRelocked(); } tradingEnabled = enabled; emit TradingStatusUpdated(enabled); } function launchProtectionEnabled() public view returns (bool) { return _hasFeature(FEATURE_LAUNCH_PROTECTION); } /// @notice The start block is included; the end block is excluded. This window cannot be reset or extended. function launchProtectionActive() public view returns (bool) { return launchStarted && block.number < protectionEndBlock; } function startLaunch() external onlyOwner { if (!launchProtectionEnabled()) revert FeatureDisabled(); _startLaunch(); } function _startLaunch() private { if (launchStarted) revert LaunchAlreadyStarted(); launchStarted = true; launchBlock = block.number; protectionEndBlock = block.number + protectionBlocks; tradingEnabled = true; emit LaunchStarted(launchBlock, protectionEndBlock); emit TradingStatusUpdated(true); } function setTax(uint256 buyTax_, uint256 sellTax_, uint256 transferTax_, uint256 burnTax_) external onlyOwner { _validateTaxConfiguration(buyTax_, sellTax_, transferTax_, burnTax_); buyTax = buyTax_; sellTax = sellTax_; transferTax = transferTax_; burnTax = burnTax_; emit TaxUpdated(buyTax_, sellTax_, transferTax_, burnTax_); } function setTaxReceiver(address receiver) external onlyOwner { if (receiver == address(0)) revert InvalidAddress(); taxReceiver = receiver; _grantSystemAccess(receiver); emit TaxReceiverUpdated(receiver); } function setExemptFromTax(address account, bool exempt) external onlyOwner { _setTaxExempt(account, exempt); } function addTaxExempt(address account) external onlyOwner { _setTaxExempt(account, true); } function removeTaxExempt(address account) external onlyOwner { _setTaxExempt(account, false); } function isExemptFromTax(address account) external view returns (bool) { return isTaxExempt[account]; } function setExemptFromLimits(address account, bool exempt) external onlyOwner { if (account == address(0)) revert InvalidAddress(); isLimitExempt[account] = exempt; emit LimitExemptUpdated(account, exempt); } function setExemptFromTrading(address account, bool exempt) external onlyOwner { if (!tradingControlEnabled()) revert FeatureDisabled(); if (account == address(0)) revert InvalidAddress(); isTradingExempt[account] = exempt; emit TradingExemptUpdated(account, exempt); } function setAutomatedMarketMaker(address account, bool enabled) external onlyOwner { if (account == address(0)) revert InvalidAddress(); automatedMarketMakers[account] = enabled; isLimitExempt[account] = enabled; if (whitelistEnabled()) _setWhitelist(account, enabled); emit AMMUpdated(account, enabled); } function setMaxWallet(uint256 amount, bool enabled) external onlyOwner { if (!maxWalletEnabled()) revert FeatureDisabled(); if (enabled && (amount == 0 || amount < totalSupply() / 1_000)) revert InvalidLimit(); maxWalletAmount = amount; maxWalletActive = enabled; emit MaxWalletUpdated(amount, enabled); } function setMaxTransaction(uint256 amount, bool enabled) external onlyOwner { if (!maxTransactionEnabled()) revert FeatureDisabled(); if (enabled && (amount == 0 || amount < totalSupply() / 1_000)) revert InvalidLimit(); maxTxAmount = amount; maxTxActive = enabled; emit MaxTransactionUpdated(amount, enabled); } function transferOwnership(address newOwner) public override onlyOwner { if (!ownershipTransferEnabled()) revert FeatureDisabled(); address oldOwner = owner(); super.transferOwnership(newOwner); _migrateOwnerAccess(oldOwner, newOwner); } function renounceOwnership() public override onlyOwner { if (!renounceOwnershipEnabled()) revert FeatureDisabled(); if (paused() || !tradingEnabled) revert UnsafeRenounce(); address oldOwner = owner(); super.renounceOwnership(); _migrateOwnerAccess(oldOwner, address(0)); } /// @notice Recovers this token if tokens were transferred to the contract address directly. function manualSwap() external onlyOwner { uint256 amount = balanceOf(address(this)); if (amount == 0) revert NothingToWithdraw(); super._update(address(this), taxReceiver, amount); emit TaxWithdrawn(taxReceiver, amount); } function withdrawStuckETH() external onlyOwner { uint256 amount = address(this).balance; if (amount == 0) revert NothingToWithdraw(); (bool ok,) = payable(owner()).call{value: amount}(""); if (!ok) revert FeeTransferFailed(); } function withdrawStuckTokens(address token) external onlyOwner { if (token == address(this) || token == address(0)) revert InvalidAddress(); uint256 amount = IERC20(token).balanceOf(address(this)); if (amount == 0) revert NothingToWithdraw(); IERC20(token).safeTransfer(owner(), amount); } function _update(address from, address to, uint256 value) internal override(ERC20, ERC20Pausable) { if (from == address(0) && cappedSupplyEnabled() && totalSupply() + value > maximumSupply) revert CapExceeded(); bool regularTransfer = from != address(0) && to != address(0) && from != address(this); if (!regularTransfer) { super._update(from, to, value); return; } if (blacklistEnabled() && (_blacklisted[from] || _blacklisted[to])) revert BlacklistedAccount(); if (whitelistActive && (!_whitelisted[from] || !_whitelisted[to])) revert AddressNotWhitelisted(); if (tradingControlEnabled() && !tradingEnabled && !isTradingExempt[from] && !isTradingExempt[to]) { revert TradingDisabled(); } // An AMM is exempt from wallet limits, never from transaction caps on ordinary traders. bool txExempt = (isLimitExempt[from] && !automatedMarketMakers[from]) || (isLimitExempt[to] && !automatedMarketMakers[to]); bool protectedLaunch = launchProtectionActive(); if (!txExempt && ((maxTxActive && value > maxTxAmount) || (protectedLaunch && value > launchMaxTxAmount))) revert MaxTxExceeded(); uint256 rate = _taxRate(from, to); uint256 taxAmount; uint256 burnAmount; uint256 receiverAmount; if (rate != 0 && !isTaxExempt[from] && !isTaxExempt[to]) { taxAmount = (value * rate) / BPS; burnAmount = (value * burnTax) / BPS; if (burnAmount > taxAmount) burnAmount = taxAmount; receiverAmount = taxAmount - burnAmount; } uint256 receivedAmount = value - taxAmount; if (!isLimitExempt[to]) { uint256 nextBalance = balanceOf(to) + receivedAmount; if ((maxWalletActive && nextBalance > maxWalletAmount) || (protectedLaunch && nextBalance > launchMaxWalletAmount)) revert MaxWalletExceeded(); } if (receiverAmount != 0) super._update(from, taxReceiver, receiverAmount); if (burnAmount != 0) super._update(from, address(0), burnAmount); if (taxAmount != 0) emit TaxCollected(from, to, taxReceiver, receiverAmount, burnAmount); super._update(from, to, receivedAmount); } function _taxRate(address from, address to) private view returns (uint256) { if (automatedMarketMakers[from]) return buyTax; if (automatedMarketMakers[to]) return sellTax; return transferTax; } function _setWhitelist(address account, bool allowed) private { if (account == address(0)) revert InvalidAddress(); _whitelisted[account] = allowed; emit WhitelistUpdated(account, allowed); } function _setTaxExempt(address account, bool exempt) private { if (account == address(0)) revert InvalidAddress(); isTaxExempt[account] = exempt; emit TaxExemptUpdated(account, exempt); } function _grantSystemAccess(address account) private { if (account == address(0)) return; _whitelisted[account] = true; isTaxExempt[account] = true; isLimitExempt[account] = true; isTradingExempt[account] = true; } function _migrateOwnerAccess(address oldOwner, address newOwner) private { if (oldOwner != address(0)) { _whitelisted[oldOwner] = false; isTaxExempt[oldOwner] = false; isLimitExempt[oldOwner] = false; isTradingExempt[oldOwner] = false; } if (newOwner != address(0)) { _blacklisted[newOwner] = false; _grantSystemAccess(newOwner); } } function _hasFeature(uint16 feature) private view returns (bool) { return _features & feature != 0; } function _packFeatures(bool[12] memory enabled) private pure returns (uint16 features) { if (enabled[0]) features |= FEATURE_BURN; if (enabled[1]) features |= FEATURE_MINT; if (enabled[2]) features |= FEATURE_PAUSE; if (enabled[3]) features |= FEATURE_BLACKLIST; if (enabled[4]) features |= FEATURE_WHITELIST; if (enabled[5]) features |= FEATURE_MAX_WALLET; if (enabled[6]) features |= FEATURE_MAX_TX; if (enabled[8]) features |= FEATURE_CAP; if (enabled[9]) features |= FEATURE_TRANSFER_OWNERSHIP; if (enabled[10]) features |= FEATURE_RENOUNCE; if (enabled[7]) features |= FEATURE_TRADING_CONTROL; if (enabled[11]) features |= FEATURE_LAUNCH_PROTECTION; } function _validateLimit(bool enabled, uint256 limitBps) private pure { if (enabled && (limitBps < MIN_LIMIT_BPS || limitBps > BPS)) revert InvalidLimit(); } function _validateTaxConfiguration( uint256 buyTax_, uint256 sellTax_, uint256 transferTax_, uint256 burnTax_ ) private pure { if (buyTax_ > MAX_TAX || sellTax_ > MAX_TAX || transferTax_ > MAX_TAX || burnTax_ > MAX_TAX) { revert InvalidTax(); } if (burnTax_ != 0) { if (buyTax_ != 0 && burnTax_ > buyTax_) revert InvalidTax(); if (sellTax_ != 0 && burnTax_ > sellTax_) revert InvalidTax(); if (transferTax_ != 0 && burnTax_ > transferTax_) revert InvalidTax(); if (buyTax_ == 0 && sellTax_ == 0 && transferTax_ == 0) revert InvalidTax(); } } function _forwardFee(address feeReceiver_) private { if (msg.value == 0 || feeReceiver_ == address(0)) return; (bool ok,) = payable(feeReceiver_).call{value: msg.value}(""); if (!ok) revert FeeTransferFailed(); } }