PresaleEnhanced

Description:

ERC20 token contract with Mintable, Pausable, Factory capabilities. Standard implementation for fungible tokens on Ethereum.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "contracts/PresaleEnhanced.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.20;\r
\r
// Inline OpenZeppelin contracts - Essential interfaces only\r
interface IERC20 {\r
    function totalSupply() external view returns (uint256);\r
    function balanceOf(address account) external view returns (uint256);\r
    function transfer(address to, uint256 amount) external returns (bool);\r
    function allowance(address owner, address spender) external view returns (uint256);\r
    function approve(address spender, uint256 amount) external returns (bool);\r
    function transferFrom(address from, address to, uint256 amount) external returns (bool);\r
}\r
\r
interface IERC20Metadata is IERC20 {\r
    function name() external view returns (string memory);\r
    function symbol() external view returns (string memory);\r
    function decimals() external view returns (uint8);\r
}\r
\r
// Enhanced SafeERC20 functionality\r
library SafeERC20 {\r
    function safeTransfer(IERC20 token, address to, uint256 value) internal {\r
        (bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(token.transfer.selector, to, value));\r
        require(success && (data.length == 0 || abi.decode(data, (bool))), "SafeERC20: transfer failed");\r
    }\r
\r
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {\r
        (bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\r
        require(success && (data.length == 0 || abi.decode(data, (bool))), "SafeERC20: transferFrom failed");\r
    }\r
}\r
\r
// Minimal AccessControl\r
contract AccessControl {\r
    mapping(bytes32 => mapping(address => bool)) private _roles;\r
    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;\r
\r
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\r
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\r
\r
    modifier onlyRole(bytes32 role) {\r
        require(hasRole(role, msg.sender), "AccessControl: missing role");\r
        _;\r
    }\r
\r
    function hasRole(bytes32 role, address account) public view returns (bool) {\r
        return _roles[role][account];\r
    }\r
\r
    function _grantRole(bytes32 role, address account) internal {\r
        if (!hasRole(role, account)) {\r
            _roles[role][account] = true;\r
            emit RoleGranted(role, account, msg.sender);\r
        }\r
    }\r
\r
    function grantRole(bytes32 role, address account) public onlyRole(DEFAULT_ADMIN_ROLE) {\r
        _grantRole(role, account);\r
    }\r
\r
    function revokeRole(bytes32 role, address account) public onlyRole(DEFAULT_ADMIN_ROLE) {\r
        if (hasRole(role, account)) {\r
            _roles[role][account] = false;\r
            emit RoleRevoked(role, account, msg.sender);\r
        }\r
    }\r
}\r
\r
// Minimal ReentrancyGuard\r
contract ReentrancyGuard {\r
    uint256 private constant _NOT_ENTERED = 1;\r
    uint256 private constant _ENTERED = 2;\r
    uint256 private _status;\r
\r
    constructor() {\r
        _status = _NOT_ENTERED;\r
    }\r
\r
    modifier nonReentrant() {\r
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");\r
        _status = _ENTERED;\r
        _;\r
        _status = _NOT_ENTERED;\r
    }\r
}\r
\r
// Minimal Pausable\r
contract Pausable {\r
    bool private _paused;\r
    event Paused(address account);\r
    event Unpaused(address account);\r
\r
    constructor() {\r
        _paused = false;\r
    }\r
\r
    modifier whenNotPaused() {\r
        require(!_paused, "Pausable: paused");\r
        _;\r
    }\r
\r
    function paused() public view returns (bool) {\r
        return _paused;\r
    }\r
\r
    function _pause() internal whenNotPaused {\r
        _paused = true;\r
        emit Paused(msg.sender);\r
    }\r
\r
    function _unpause() internal {\r
        require(_paused, "Pausable: not paused");\r
        _paused = false;\r
        emit Unpaused(msg.sender);\r
    }\r
}\r
\r
// Inline PrecisionMath library\r
library PrecisionMath {\r
    uint256 internal constant PRECISION = 10**18;\r
\r
    function toInternal(uint256 value, uint8 decimals) internal pure returns (uint256) {\r
        if (decimals == 18) return value;\r
        if (decimals > 18) return value / (10**(decimals - 18));\r
        return value * (10**(18 - decimals));\r
    }\r
\r
    function isWithinDeviation(uint256 price1, uint256 price2, uint256 bps) internal pure returns (bool) {\r
        uint256 higher = price1 > price2 ? price1 : price2;\r
        uint256 lower = price1 > price2 ? price2 : price1;\r
        uint256 deviation = ((higher - lower) * 10000) / higher;\r
        return deviation <= bps;\r
    }\r
\r
    function mulDiv(uint256 a, uint256 b, uint256 c) internal pure returns (uint256) {\r
        require(c > 0, "Division by zero");\r
        \r
        // Handle zero cases\r
        if (a == 0 || b == 0) return 0;\r
        \r
        // Check for overflow before multiplication\r
        uint256 product = a * b;\r
        require(product / a == b, "mulDiv: multiplication overflow");\r
        \r
        return product / c;\r
    }\r
}\r
\r
// Fixed price configuration for testing\r
struct PriceData {\r
    uint256 price;\r
    uint256 timestamp;\r
    uint256 roundId;\r
    bool isValid;\r
}\r
\r
interface XenonAI {\r
    function transfer(address to, uint256 amount) external returns (bool);\r
    function balanceOf(address account) external view returns (uint256);\r
    function mint(address to, uint256 amount) external;\r
}\r
\r
/**\r
 * @title Enhanced Presale Contract - Full Featured Version\r
 * @dev Secure presale with all original features + easy verification\r
 */\r
contract PresaleEnhanced is AccessControl, ReentrancyGuard, Pausable {\r
    using SafeERC20 for IERC20;\r
    using PrecisionMath for uint256;\r
\r
    // Roles\r
    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");\r
    bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");\r
    bytes32 public constant PRICE_UPDATER_ROLE = keccak256("PRICE_UPDATER_ROLE");\r
\r
    // Core contracts\r
    XenonAI public immutable projectToken;\r
    address public treasuryWallet;\r
\r
    // Fixed ETH price for testing (can be updated by admin)\r
    uint256 public ethPrice = 4500 * 10**18; // $4500 default\r
\r
    // Payment tokens - Same structure as original\r
    struct PaymentToken {\r
        IERC20 token;\r
        uint8 decimals;\r
        bool isActive;\r
        string symbol;\r
    }\r
\r
    mapping(address => PaymentToken) public paymentTokens;\r
    address[] public paymentTokenList;\r
\r
    // Token allocation and pricing - Same as original\r
    uint256 public constant PRESALE_SUPPLY = 180_000_000 * 10**18; // 60% of 300M\r
    uint256 public tokensSold = 0;\r
    uint256 public tokenPrice = 3000000000000000; // $0.003 in 18 decimals\r
\r
    // Statistics - Same as original\r
    uint256 public totalRaised = 0;\r
    uint256 public presaleStartTime;\r
    uint256 public presaleEndTime;\r
\r
    // Dynamic limits - Same as original (CONFIGURABLE)\r
    uint256 public minPurchase = 1 * 10**18; // $1 minimum\r
    uint256 public maxPurchase = 50_000 * 10**18; // $50,000 maximum\r
\r
    // Security settings - Same as original\r
    uint256 public constant MAX_PRICE_DEVIATION_BPS = 10000; // 100% max price change\r
    uint256 public emergencyWithdrawalDelay = 3 days;\r
    uint256 public emergencyWithdrawalRequest;\r
\r
    // Batch operations - Same as original\r
    struct PurchaseData {\r
        address buyer;\r
        uint256 tokenAmount;\r
        uint256 usdAmount;\r
        address paymentToken;\r
    }\r
\r
    // Tracking\r
    mapping(address => uint256) public purchases;\r
    mapping(address => bool) public hasParticipated;\r
    uint256 public totalParticipants;\r
\r
    // Events - Same as original\r
    event TokensPurchased(address indexed buyer, uint256 tokenAmount, uint256 usdAmount, address indexed paymentToken, uint256 timestamp);\r
    event TokenPriceUpdated(uint256 newPrice, uint256 timestamp);\r
    event TreasuryUpdated(address indexed newTreasury, uint256 timestamp);\r
    event PaymentTokenAdded(address indexed token, string symbol, uint8 decimals);\r
    event PaymentTokenRemoved(address indexed token);\r
    event PaymentTokenStatusChanged(address indexed token, bool isActive);\r
    event EmergencyWithdrawalRequested(uint256 timestamp);\r
    event EmergencyWithdrawalExecuted(uint256 amount, uint256 timestamp);\r
    event PurchaseLimitsUpdated(uint256 minPurchase, uint256 maxPurchase);\r
\r
    // Custom errors - Same as original\r
    error PresaleNotActive();\r
    error InvalidAmount();\r
    error PurchaseTooSmall();\r
    error PurchaseTooLarge();\r
    error SlippageTooHigh();\r
    error PresaleSupplyExceeded();\r
    error InvalidAddress();\r
    error PriceChangeTooLarge();\r
    error NoETHToWithdraw();\r
    error ETHTransferFailed();\r
    error TokenNotSupported();\r
    error EmergencyNotRequested();\r
    error TimelockNotExpired();\r
\r
    constructor(\r
        address _projectToken,\r
        address _treasuryWallet,\r
        uint256 _presaleDuration,\r
        address[] memory _paymentTokens,\r
        string[] memory _symbols\r
    ) {\r
        if (_projectToken == address(0)) revert InvalidAddress();\r
        if (_treasuryWallet == address(0)) revert InvalidAddress();\r
        if (_presaleDuration == 0) revert InvalidAmount();\r
        if (_paymentTokens.length != _symbols.length) revert InvalidAmount();\r
\r
        projectToken = XenonAI(_projectToken);\r
        treasuryWallet = _treasuryWallet;\r
        presaleStartTime = block.timestamp;\r
        presaleEndTime = block.timestamp + _presaleDuration;\r
\r
        // Setup roles\r
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);\r
        _grantRole(ADMIN_ROLE, msg.sender);\r
        _grantRole(OPERATOR_ROLE, msg.sender);\r
        _grantRole(PRICE_UPDATER_ROLE, msg.sender);\r
\r
        // Add payment tokens\r
        for (uint256 i = 0; i < _paymentTokens.length; i++) {\r
            _addPaymentToken(_paymentTokens[i], _symbols[i]);\r
        }\r
    }\r
\r
    // Internal helper - Same as original\r
    function _addPaymentToken(address tokenAddress, string memory symbol) private {\r
        if (tokenAddress == address(0)) revert InvalidAddress();\r
\r
        uint8 decimals = IERC20Metadata(tokenAddress).decimals();\r
\r
        paymentTokens[tokenAddress] = PaymentToken({\r
            token: IERC20(tokenAddress),\r
            decimals: decimals,\r
            isActive: true,\r
            symbol: symbol\r
        });\r
\r
        paymentTokenList.push(tokenAddress);\r
        emit PaymentTokenAdded(tokenAddress, symbol, decimals);\r
    }\r
\r
    // Check if presale is active - Same as original\r
    function _isPresaleActive() private view returns (bool) {\r
        return block.timestamp >= presaleStartTime &&\r
               block.timestamp <= presaleEndTime &&\r
               tokensSold < PRESALE_SUPPLY;\r
    }\r
\r
    // Purchase with ETH - Enhanced with same logic as original\r
    function purchaseWithETH(uint256 minTokens) external payable nonReentrant whenNotPaused {\r
        if (!_isPresaleActive()) revert PresaleNotActive();\r
        if (msg.value == 0) revert InvalidAmount();\r
\r
        // Use fixed ETH price for testing\r
\r
        // Convert to internal precision\r
        uint256 ethAmount18 = PrecisionMath.toInternal(msg.value, 18);\r
        uint256 usdAmount18 = PrecisionMath.mulDiv(ethAmount18, ethPrice, PrecisionMath.PRECISION);\r
\r
        // Validate purchase limits\r
        if (usdAmount18 < minPurchase) revert PurchaseTooSmall();\r
        if (usdAmount18 > maxPurchase) revert PurchaseTooLarge();\r
\r
        // Calculate tokens\r
        uint256 tokenAmount18 = PrecisionMath.mulDiv(usdAmount18, PrecisionMath.PRECISION, tokenPrice);\r
\r
        if (tokenAmount18 < minTokens) revert SlippageTooHigh();\r
        if (tokensSold + tokenAmount18 > PRESALE_SUPPLY) revert PresaleSupplyExceeded();\r
\r
        // Update state\r
        _updatePurchaseState(msg.sender, tokenAmount18, usdAmount18);\r
\r
        // Transfer ETH to treasury\r
        (bool success, ) = payable(treasuryWallet).call{value: msg.value}("");\r
        if (!success) revert ETHTransferFailed();\r
\r
        // Mint tokens to buyer\r
        projectToken.mint(msg.sender, tokenAmount18);\r
\r
        emit TokensPurchased(msg.sender, tokenAmount18, usdAmount18, address(0), block.timestamp);\r
    }\r
\r
    // Purchase with ERC20 tokens - Enhanced with same logic as original\r
    function purchaseWithToken(address tokenAddress, uint256 amount, uint256 minTokens) external nonReentrant whenNotPaused {\r
        if (!_isPresaleActive()) revert PresaleNotActive();\r
\r
        PaymentToken storage paymentToken = paymentTokens[tokenAddress];\r
        if (!paymentToken.isActive) revert TokenNotSupported();\r
        if (amount == 0) revert InvalidAmount();\r
\r
        // Convert amount to 18 decimals for calculations\r
        uint256 amount18 = PrecisionMath.toInternal(amount, paymentToken.decimals);\r
\r
        // For stablecoins, assume 1:1 USD peg\r
        uint256 usdAmount18 = amount18;\r
\r
        // Validate purchase limits\r
        if (usdAmount18 < minPurchase) revert PurchaseTooSmall();\r
        if (usdAmount18 > maxPurchase) revert PurchaseTooLarge();\r
\r
        // Calculate tokens\r
        uint256 tokenAmount18 = PrecisionMath.mulDiv(usdAmount18, PrecisionMath.PRECISION, tokenPrice);\r
\r
        if (tokenAmount18 < minTokens) revert SlippageTooHigh();\r
        if (tokensSold + tokenAmount18 > PRESALE_SUPPLY) revert PresaleSupplyExceeded();\r
\r
        // Update state\r
        _updatePurchaseState(msg.sender, tokenAmount18, usdAmount18);\r
\r
        // Transfer payment token to treasury using SafeERC20\r
        paymentToken.token.safeTransferFrom(msg.sender, treasuryWallet, amount);\r
\r
        // Mint tokens to buyer\r
        projectToken.mint(msg.sender, tokenAmount18);\r
\r
        emit TokensPurchased(msg.sender, tokenAmount18, usdAmount18, tokenAddress, block.timestamp);\r
    }\r
\r
    // Update purchase state - Same as original\r
    function _updatePurchaseState(address buyer, uint256 tokenAmount, uint256 usdAmount) private {\r
        tokensSold += tokenAmount;\r
        totalRaised += usdAmount;\r
\r
        if (!hasParticipated[buyer]) {\r
            hasParticipated[buyer] = true;\r
            totalParticipants++;\r
        }\r
\r
        purchases[buyer] += tokenAmount;\r
    }\r
\r
    // Admin functions - Same as original\r
    function updateTokenPrice(uint256 newPrice) external onlyRole(PRICE_UPDATER_ROLE) {\r
        if (newPrice == 0) revert InvalidAmount();\r
\r
        // Prevent extreme price changes\r
        if (tokenPrice > 0) {\r
            if (!PrecisionMath.isWithinDeviation(tokenPrice, newPrice, MAX_PRICE_DEVIATION_BPS)) {\r
                revert PriceChangeTooLarge();\r
            }\r
        }\r
\r
        tokenPrice = newPrice;\r
        emit TokenPriceUpdated(newPrice, block.timestamp);\r
    }\r
\r
    function updateTreasuryWallet(address newTreasury) external onlyRole(ADMIN_ROLE) {\r
        if (newTreasury == address(0)) revert InvalidAddress();\r
        treasuryWallet = newTreasury;\r
        emit TreasuryUpdated(newTreasury, block.timestamp);\r
    }\r
\r
    function updateETHPrice(uint256 newPrice) external onlyRole(PRICE_UPDATER_ROLE) {\r
        if (newPrice == 0) revert InvalidAmount();\r
        if (newPrice < 100 * 10**18 || newPrice > 20000 * 10**18) revert InvalidAmount(); // $100 - $20,000 range\r
\r
        ethPrice = newPrice;\r
        emit TokenPriceUpdated(newPrice, block.timestamp); // Reuse existing event\r
    }\r
\r
    // Purchase limit management - ADDED BACK\r
    function updatePurchaseLimits(uint256 _minPurchase, uint256 _maxPurchase) external onlyRole(ADMIN_ROLE) {\r
        if (_minPurchase == 0 || _maxPurchase == 0) revert InvalidAmount();\r
        if (_minPurchase >= _maxPurchase) revert InvalidAmount();\r
\r
        minPurchase = _minPurchase;\r
        maxPurchase = _maxPurchase;\r
\r
        emit PurchaseLimitsUpdated(_minPurchase, _maxPurchase);\r
    }\r
\r
    // Payment token management - ADDED BACK\r
    function addPaymentToken(address tokenAddress, string memory symbol) external onlyRole(ADMIN_ROLE) {\r
        if (address(paymentTokens[tokenAddress].token) != address(0)) revert InvalidAddress();\r
        _addPaymentToken(tokenAddress, symbol);\r
    }\r
\r
    function setPaymentTokenStatus(address tokenAddress, bool isActive) external onlyRole(ADMIN_ROLE) {\r
        PaymentToken storage paymentToken = paymentTokens[tokenAddress];\r
        if (address(paymentToken.token) == address(0)) revert TokenNotSupported();\r
\r
        paymentToken.isActive = isActive;\r
        emit PaymentTokenStatusChanged(tokenAddress, isActive);\r
    }\r
\r
    function removePaymentToken(address tokenAddress) external onlyRole(ADMIN_ROLE) {\r
        PaymentToken storage paymentToken = paymentTokens[tokenAddress];\r
        if (address(paymentToken.token) == address(0)) revert TokenNotSupported();\r
\r
        // Deactivate first\r
        paymentToken.isActive = false;\r
\r
        // Remove from array - find and swap with last element\r
        for (uint256 i = 0; i < paymentTokenList.length; i++) {\r
            if (paymentTokenList[i] == tokenAddress) {\r
                paymentTokenList[i] = paymentTokenList[paymentTokenList.length - 1];\r
                paymentTokenList.pop();\r
                break;\r
            }\r
        }\r
\r
        // Clear mapping\r
        delete paymentTokens[tokenAddress];\r
\r
        emit PaymentTokenRemoved(tokenAddress);\r
    }\r
\r
    // Emergency functions - ADDED BACK\r
    function requestEmergencyWithdrawal() external onlyRole(ADMIN_ROLE) {\r
        emergencyWithdrawalRequest = block.timestamp;\r
        emit EmergencyWithdrawalRequested(block.timestamp);\r
    }\r
\r
    function executeEmergencyWithdrawal() external onlyRole(ADMIN_ROLE) {\r
        if (emergencyWithdrawalRequest == 0) revert EmergencyNotRequested();\r
        if (block.timestamp < emergencyWithdrawalRequest + emergencyWithdrawalDelay) revert TimelockNotExpired();\r
\r
        uint256 balance = address(this).balance;\r
        if (balance == 0) revert NoETHToWithdraw();\r
\r
        emergencyWithdrawalRequest = 0;\r
\r
        (bool success, ) = payable(treasuryWallet).call{value: balance}("");\r
        if (!success) revert ETHTransferFailed();\r
\r
        emit EmergencyWithdrawalExecuted(balance, block.timestamp);\r
    }\r
\r
    function pause() external onlyRole(ADMIN_ROLE) {\r
        _pause();\r
    }\r
\r
    function unpause() external onlyRole(ADMIN_ROLE) {\r
        _unpause();\r
    }\r
\r
    // View functions - Same as original\r
    function calculateTokensForUSD(uint256 usdAmount) external view returns (uint256) {\r
        uint256 usdAmount18 = PrecisionMath.toInternal(usdAmount, 18);\r
        return PrecisionMath.mulDiv(usdAmount18, PrecisionMath.PRECISION, tokenPrice);\r
    }\r
\r
    function calculateUSDForTokens(uint256 tokenAmount) external view returns (uint256) {\r
        return PrecisionMath.mulDiv(tokenAmount, tokenPrice, PrecisionMath.PRECISION);\r
    }\r
\r
    function getPresaleStats() external view returns (\r
        uint256 _tokensSold,\r
        uint256 _totalRaised,\r
        uint256 _totalParticipants,\r
        uint256 _tokenPrice,\r
        uint256 _presaleEnd,\r
        bool _isActive,\r
        uint256 _remainingTokens,\r
        uint256 _currentTime\r
    ) {\r
        return (\r
            tokensSold,\r
            totalRaised,\r
            totalParticipants,\r
            tokenPrice,\r
            presaleEndTime,\r
            _isPresaleActive(),\r
            PRESALE_SUPPLY - tokensSold,\r
            block.timestamp\r
        );\r
    }\r
\r
    function getPaymentTokens() external view returns (address[] memory) {\r
        return paymentTokenList;\r
    }\r
\r
    function getActivePaymentTokens() external view returns (address[] memory) {\r
        uint256 activeCount = 0;\r
\r
        // Count active tokens\r
        for (uint256 i = 0; i < paymentTokenList.length; i++) {\r
            if (paymentTokens[paymentTokenList[i]].isActive) {\r
                activeCount++;\r
            }\r
        }\r
\r
        // Build array of active tokens\r
        address[] memory activeTokens = new address[](activeCount);\r
        uint256 index = 0;\r
\r
        for (uint256 i = 0; i < paymentTokenList.length; i++) {\r
            if (paymentTokens[paymentTokenList[i]].isActive) {\r
                activeTokens[index] = paymentTokenList[i];\r
                index++;\r
            }\r
        }\r
\r
        return activeTokens;\r
    }\r
\r
    function isPaymentTokenActive(address token) external view returns (bool) {\r
        return paymentTokens[token].isActive;\r
    }\r
\r
    function getPurchaseLimits() external view returns (uint256 _minPurchase, uint256 _maxPurchase) {\r
        return (minPurchase, maxPurchase);\r
    }\r
\r
    // Emergency receive function\r
    receive() external payable {\r
        // Allow contract to receive ETH\r
    }\r
}\r
"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 200
    },
    "evmVersion": "paris",
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    }
  }
}}

Tags:
ERC20, Token, Mintable, Pausable, Factory|addr:0x569ca003d64863280be77a58067f9a2fe71406cf|verified:true|block:23432306|tx:0xf7e66a9c8942639a2ddb5400fef5f678bf0e883b1f230e00413de8f0943100bf|first_check:1758735735

Submitted on: 2025-09-24 19:42:16

Comments

Log in to comment.

No comments yet.