// SPDX-License-Identifier: MIT pragma solidity ^0.8.36; 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 StandardToken /// @notice ERC-20 token with optional owner controls, whitelist, and independent transfer limits. contract StandardToken is ERC20, ERC20Pausable, Ownable { error InvalidOwner(); error InvalidSupply(); error InvalidDecimals(); error InvalidLimit(); error InvalidAddress(); error FeatureDisabled(); error CannotBlacklistOwner(); error BlacklistedAccount(); error AddressNotWhitelisted(); error MaxTxExceeded(); error MaxWalletExceeded(); error FeeTransferFailed(); error InvalidCap(); error CapExceeded(); error UnsafeRenounce(); uint256 private constant BPS = 10_000; uint256 private constant MIN_LIMIT_BPS = 10; uint256 public immutable maximumSupply; uint8 private immutable _customDecimals; uint16 private immutable _features; uint256 public maxWalletAmount; uint256 public maxTxAmount; bool public maxWalletActive; bool public maxTxActive; bool public whitelistActive; mapping(address account => bool) private _blacklisted; mapping(address account => bool) private _whitelisted; mapping(address account => bool) public isLimitExempt; 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_CAP = 1 << 7; uint16 private constant FEATURE_TRANSFER_OWNERSHIP = 1 << 8; uint16 private constant FEATURE_RENOUNCE = 1 << 9; event BlacklistUpdated(address indexed account, bool blocked); event WhitelistUpdated(address indexed account, bool allowed); event WhitelistStatusUpdated(bool enabled); event LimitExemptUpdated(address indexed account, bool exempt); event MaxWalletUpdated(uint256 amount, bool enabled); event MaxTransactionUpdated(uint256 amount, bool enabled); constructor( string memory name_, string memory symbol_, uint8 decimals_, uint256 totalSupply_, address owner_, address feeReceiver_, bool[10] memory features_, uint256[2] memory limitBps_, address[] memory initialWhitelist_, uint256 maximumSupply_ ) payable ERC20(name_, symbol_) Ownable(owner_) { if (owner_ == address(0)) revert InvalidOwner(); if (totalSupply_ == 0) revert InvalidSupply(); if (decimals_ > 18) revert InvalidDecimals(); _validateLimit(features_[5], limitBps_[0]); _validateLimit(features_[6], limitBps_[1]); if (features_[7] && (!features_[1] || maximumSupply_ < totalSupply_)) revert InvalidCap(); maximumSupply = features_[7] ? maximumSupply_ * 10 ** decimals_ : 0; _customDecimals = decimals_; _features = _packFeatures(features_); uint256 supply = totalSupply_ * 10 ** decimals_; maxWalletAmount = features_[5] ? (supply * limitBps_[0]) / BPS : supply; maxTxAmount = features_[6] ? (supply * limitBps_[1]) / BPS : supply; if ((features_[5] && maxWalletAmount == 0) || (features_[6] && maxTxAmount == 0)) revert InvalidLimit(); maxWalletActive = features_[5]; maxTxActive = features_[6]; whitelistActive = features_[4]; _whitelisted[owner_] = true; _whitelisted[address(this)] = true; isLimitExempt[owner_] = true; isLimitExempt[address(this)] = true; for (uint256 i; i < initialWhitelist_.length; ++i) { _setWhitelist(initialWhitelist_[i], true); } _mint(owner_, supply); _forwardFee(feeReceiver_); } 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 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 setExemptFromLimits(address account, bool exempt) external onlyOwner { if (account == address(0)) revert InvalidAddress(); isLimitExempt[account] = exempt; emit LimitExemptUpdated(account, exempt); } 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()) revert UnsafeRenounce(); address oldOwner = owner(); super.renounceOwnership(); _migrateOwnerAccess(oldOwner, address(0)); } 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); if (regularTransfer && blacklistEnabled() && (_blacklisted[from] || _blacklisted[to])) { revert BlacklistedAccount(); } if (regularTransfer && whitelistActive && (!_whitelisted[from] || !_whitelisted[to])) { revert AddressNotWhitelisted(); } if (regularTransfer && maxTxActive && !isLimitExempt[from] && !isLimitExempt[to] && value > maxTxAmount) { revert MaxTxExceeded(); } if (regularTransfer && maxWalletActive && !isLimitExempt[to] && balanceOf(to) + value > maxWalletAmount) { revert MaxWalletExceeded(); } super._update(from, to, value); } function _setWhitelist(address account, bool allowed) private { if (account == address(0)) revert InvalidAddress(); _whitelisted[account] = allowed; emit WhitelistUpdated(account, allowed); } function _migrateOwnerAccess(address oldOwner, address newOwner) private { if (oldOwner != address(0)) { _whitelisted[oldOwner] = false; isLimitExempt[oldOwner] = false; } if (newOwner != address(0)) { _blacklisted[newOwner] = false; _whitelisted[newOwner] = true; isLimitExempt[newOwner] = true; } } function _hasFeature(uint16 feature) private view returns (bool) { return _features & feature != 0; } function _packFeatures(bool[10] 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[7]) features |= FEATURE_CAP; if (enabled[8]) features |= FEATURE_TRANSFER_OWNERSHIP; if (enabled[9]) features |= FEATURE_RENOUNCE; } function _validateLimit(bool enabled, uint256 limitBps) private pure { if (enabled && (limitBps < MIN_LIMIT_BPS || limitBps > BPS)) revert InvalidLimit(); } 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(); } }