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"
]
}
}
}
}}
Submitted on: 2025-09-24 19:42:16
Comments
Log in to comment.
No comments yet.