Description:
ERC20 token contract with Pausable, Oracle capabilities. Standard implementation for fungible tokens on Ethereum.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
// Sources flattened with hardhat v2.26.3 https://hardhat.org
// SPDX-License-Identifier: MIT
// File contracts/PresaleEnhanced.sol
// Original license: SPDX_License_Identifier: MIT
pragma solidity ^0.8.30;
// Inline OpenZeppelin contracts - Essential interfaces only
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
interface IERC20Metadata is IERC20 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
}
// Enhanced SafeERC20 functionality
library SafeERC20 {
function safeTransfer(IERC20 token, address to, uint256 value) internal {
(bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(token.transfer.selector, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), "SafeERC20: transfer failed");
}
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
(bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), "SafeERC20: transferFrom failed");
}
}
// Minimal AccessControl
contract AccessControl {
mapping(bytes32 => mapping(address => bool)) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
modifier onlyRole(bytes32 role) {
require(hasRole(role, msg.sender), "AccessControl: missing role");
_;
}
function hasRole(bytes32 role, address account) public view returns (bool) {
return _roles[role][account];
}
function _grantRole(bytes32 role, address account) internal {
if (!hasRole(role, account)) {
_roles[role][account] = true;
emit RoleGranted(role, account, msg.sender);
}
}
function grantRole(bytes32 role, address account) public onlyRole(DEFAULT_ADMIN_ROLE) {
_grantRole(role, account);
}
function revokeRole(bytes32 role, address account) public onlyRole(DEFAULT_ADMIN_ROLE) {
if (hasRole(role, account)) {
_roles[role][account] = false;
emit RoleRevoked(role, account, msg.sender);
}
}
}
// Minimal ReentrancyGuard
contract ReentrancyGuard {
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
modifier nonReentrant() {
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
_status = _ENTERED;
_;
_status = _NOT_ENTERED;
}
}
// Minimal Pausable
contract Pausable {
bool private _paused;
event Paused(address account);
event Unpaused(address account);
constructor() {
_paused = false;
}
modifier whenNotPaused() {
require(!_paused, "Pausable: paused");
_;
}
function paused() public view returns (bool) {
return _paused;
}
function _pause() internal whenNotPaused {
_paused = true;
emit Paused(msg.sender);
}
function _unpause() internal {
require(_paused, "Pausable: not paused");
_paused = false;
emit Unpaused(msg.sender);
}
}
// Inline PrecisionMath library
library PrecisionMath {
uint256 internal constant PRECISION = 10**18;
function toInternal(uint256 value, uint8 decimals) internal pure returns (uint256) {
if (decimals == 18) return value;
if (decimals > 18) return value / (10**(decimals - 18));
return value * (10**(18 - decimals));
}
function isWithinDeviation(uint256 price1, uint256 price2, uint256 bps) internal pure returns (bool) {
uint256 higher = price1 > price2 ? price1 : price2;
uint256 lower = price1 > price2 ? price2 : price1;
uint256 deviation = ((higher - lower) * 10000) / higher;
return deviation <= bps;
}
function mulDiv(uint256 a, uint256 b, uint256 c) internal pure returns (uint256) {
return (a * b) / c;
}
}
// Price Oracle interfaces
struct PriceData {
uint256 price;
uint256 timestamp;
uint256 roundId;
bool isValid;
}
interface IPriceOracle {
function getReliablePrice() external view returns (uint256 price, uint256 timestamp, address source);
}
interface XenonAI {
function transfer(address to, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
}
/**
* @title Enhanced Presale Contract - Full Featured Version
* @dev Secure presale with all original features + easy verification
*/
contract PresaleEnhanced is AccessControl, ReentrancyGuard, Pausable {
using SafeERC20 for IERC20;
using PrecisionMath for uint256;
// Roles
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
bytes32 public constant PRICE_UPDATER_ROLE = keccak256("PRICE_UPDATER_ROLE");
// Core contracts
XenonAI public immutable projectToken;
IPriceOracle public immutable priceOracle;
address public treasuryWallet;
// Payment tokens - Same structure as original
struct PaymentToken {
IERC20 token;
uint8 decimals;
bool isActive;
string symbol;
}
mapping(address => PaymentToken) public paymentTokens;
address[] public paymentTokenList;
// Token allocation and pricing - Same as original
uint256 public constant PRESALE_SUPPLY = 180_000_000 * 10**18; // 60% of 300M
uint256 public tokensSold = 0;
uint256 public tokenPrice = 3000000000000000; // $0.003 in 18 decimals
// Statistics - Same as original
uint256 public totalRaised = 0;
uint256 public presaleStartTime;
uint256 public presaleEndTime;
// Dynamic limits - Same as original (CONFIGURABLE)
uint256 public minPurchase = 1 * 10**18; // $1 minimum
uint256 public maxPurchase = 50_000 * 10**18; // $50,000 maximum
// Security settings - Same as original
uint256 public constant MAX_PRICE_DEVIATION_BPS = 10000; // 100% max price change
uint256 public emergencyWithdrawalDelay = 3 days;
uint256 public emergencyWithdrawalRequest;
// Batch operations - Same as original
struct PurchaseData {
address buyer;
uint256 tokenAmount;
uint256 usdAmount;
address paymentToken;
}
// Tracking
mapping(address => uint256) public purchases;
mapping(address => bool) public hasParticipated;
uint256 public totalParticipants;
// Events - Same as original
event TokensPurchased(address indexed buyer, uint256 tokenAmount, uint256 usdAmount, address indexed paymentToken, uint256 timestamp);
event TokenPriceUpdated(uint256 newPrice, uint256 timestamp);
event TreasuryUpdated(address indexed newTreasury, uint256 timestamp);
event PaymentTokenAdded(address indexed token, string symbol, uint8 decimals);
event PaymentTokenRemoved(address indexed token);
event PaymentTokenStatusChanged(address indexed token, bool isActive);
event EmergencyWithdrawalRequested(uint256 timestamp);
event EmergencyWithdrawalExecuted(uint256 amount, uint256 timestamp);
event PurchaseLimitsUpdated(uint256 minPurchase, uint256 maxPurchase);
// Custom errors - Same as original
error PresaleNotActive();
error InvalidAmount();
error PurchaseTooSmall();
error PurchaseTooLarge();
error SlippageTooHigh();
error PresaleSupplyExceeded();
error InvalidAddress();
error PriceChangeTooLarge();
error NoETHToWithdraw();
error ETHTransferFailed();
error TokenNotSupported();
error EmergencyNotRequested();
error TimelockNotExpired();
constructor(
address _projectToken,
address _priceOracle,
address _treasuryWallet,
uint256 _presaleDuration,
address[] memory _paymentTokens,
string[] memory _symbols
) {
if (_projectToken == address(0)) revert InvalidAddress();
if (_priceOracle == address(0)) revert InvalidAddress();
if (_treasuryWallet == address(0)) revert InvalidAddress();
if (_presaleDuration == 0) revert InvalidAmount();
if (_paymentTokens.length != _symbols.length) revert InvalidAmount();
projectToken = XenonAI(_projectToken);
priceOracle = IPriceOracle(_priceOracle);
treasuryWallet = _treasuryWallet;
presaleStartTime = block.timestamp;
presaleEndTime = block.timestamp + _presaleDuration;
// Setup roles
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(ADMIN_ROLE, msg.sender);
_grantRole(OPERATOR_ROLE, msg.sender);
_grantRole(PRICE_UPDATER_ROLE, msg.sender);
// Add payment tokens
for (uint256 i = 0; i < _paymentTokens.length; i++) {
_addPaymentToken(_paymentTokens[i], _symbols[i]);
}
}
// Internal helper - Same as original
function _addPaymentToken(address tokenAddress, string memory symbol) private {
if (tokenAddress == address(0)) revert InvalidAddress();
uint8 decimals = IERC20Metadata(tokenAddress).decimals();
paymentTokens[tokenAddress] = PaymentToken({
token: IERC20(tokenAddress),
decimals: decimals,
isActive: true,
symbol: symbol
});
paymentTokenList.push(tokenAddress);
emit PaymentTokenAdded(tokenAddress, symbol, decimals);
}
// Check if presale is active - Same as original
function _isPresaleActive() private view returns (bool) {
return block.timestamp >= presaleStartTime &&
block.timestamp <= presaleEndTime &&
tokensSold < PRESALE_SUPPLY;
}
// Purchase with ETH - Enhanced with same logic as original
function purchaseWithETH(uint256 minTokens) external payable nonReentrant whenNotPaused {
if (!_isPresaleActive()) revert PresaleNotActive();
if (msg.value == 0) revert InvalidAmount();
// Get ETH price from oracle
(uint256 ethPrice, , ) = priceOracle.getReliablePrice();
// Convert to internal precision
uint256 ethAmount18 = PrecisionMath.toInternal(msg.value, 18);
uint256 usdAmount18 = PrecisionMath.mulDiv(ethAmount18, ethPrice, PrecisionMath.PRECISION);
// Validate purchase limits
if (usdAmount18 < minPurchase) revert PurchaseTooSmall();
if (usdAmount18 > maxPurchase) revert PurchaseTooLarge();
// Calculate tokens
uint256 tokenAmount18 = PrecisionMath.mulDiv(usdAmount18, PrecisionMath.PRECISION, tokenPrice);
if (tokenAmount18 < minTokens) revert SlippageTooHigh();
if (tokensSold + tokenAmount18 > PRESALE_SUPPLY) revert PresaleSupplyExceeded();
// Update state
_updatePurchaseState(msg.sender, tokenAmount18, usdAmount18);
// Transfer ETH to treasury
(bool success, ) = payable(treasuryWallet).call{value: msg.value}("");
if (!success) revert ETHTransferFailed();
// Transfer tokens to buyer
if (!projectToken.transfer(msg.sender, tokenAmount18)) revert ETHTransferFailed();
emit TokensPurchased(msg.sender, tokenAmount18, usdAmount18, address(0), block.timestamp);
}
// Purchase with ERC20 tokens - Enhanced with same logic as original
function purchaseWithToken(address tokenAddress, uint256 amount, uint256 minTokens) external nonReentrant whenNotPaused {
if (!_isPresaleActive()) revert PresaleNotActive();
PaymentToken storage paymentToken = paymentTokens[tokenAddress];
if (!paymentToken.isActive) revert TokenNotSupported();
if (amount == 0) revert InvalidAmount();
// Convert amount to 18 decimals for calculations
uint256 amount18 = PrecisionMath.toInternal(amount, paymentToken.decimals);
// For stablecoins, assume 1:1 USD peg
uint256 usdAmount18 = amount18;
// Validate purchase limits
if (usdAmount18 < minPurchase) revert PurchaseTooSmall();
if (usdAmount18 > maxPurchase) revert PurchaseTooLarge();
// Calculate tokens
uint256 tokenAmount18 = PrecisionMath.mulDiv(usdAmount18, PrecisionMath.PRECISION, tokenPrice);
if (tokenAmount18 < minTokens) revert SlippageTooHigh();
if (tokensSold + tokenAmount18 > PRESALE_SUPPLY) revert PresaleSupplyExceeded();
// Update state
_updatePurchaseState(msg.sender, tokenAmount18, usdAmount18);
// Transfer payment token to treasury using SafeERC20
paymentToken.token.safeTransferFrom(msg.sender, treasuryWallet, amount);
// Transfer tokens to buyer
if (!projectToken.transfer(msg.sender, tokenAmount18)) revert ETHTransferFailed();
emit TokensPurchased(msg.sender, tokenAmount18, usdAmount18, tokenAddress, block.timestamp);
}
// Update purchase state - Same as original
function _updatePurchaseState(address buyer, uint256 tokenAmount, uint256 usdAmount) private {
tokensSold += tokenAmount;
totalRaised += usdAmount;
if (!hasParticipated[buyer]) {
hasParticipated[buyer] = true;
totalParticipants++;
}
purchases[buyer] += tokenAmount;
}
// Admin functions - Same as original
function updateTokenPrice(uint256 newPrice) external onlyRole(PRICE_UPDATER_ROLE) {
if (newPrice == 0) revert InvalidAmount();
// Prevent extreme price changes
if (tokenPrice > 0) {
if (!PrecisionMath.isWithinDeviation(tokenPrice, newPrice, MAX_PRICE_DEVIATION_BPS)) {
revert PriceChangeTooLarge();
}
}
tokenPrice = newPrice;
emit TokenPriceUpdated(newPrice, block.timestamp);
}
function updateTreasuryWallet(address newTreasury) external onlyRole(ADMIN_ROLE) {
if (newTreasury == address(0)) revert InvalidAddress();
treasuryWallet = newTreasury;
emit TreasuryUpdated(newTreasury, block.timestamp);
}
// Purchase limit management - ADDED BACK
function updatePurchaseLimits(uint256 _minPurchase, uint256 _maxPurchase) external onlyRole(ADMIN_ROLE) {
if (_minPurchase == 0 || _maxPurchase == 0) revert InvalidAmount();
if (_minPurchase >= _maxPurchase) revert InvalidAmount();
minPurchase = _minPurchase;
maxPurchase = _maxPurchase;
emit PurchaseLimitsUpdated(_minPurchase, _maxPurchase);
}
// Payment token management - ADDED BACK
function addPaymentToken(address tokenAddress, string memory symbol) external onlyRole(ADMIN_ROLE) {
if (paymentTokens[tokenAddress].token != IERC20(address(0))) revert InvalidAddress();
_addPaymentToken(tokenAddress, symbol);
}
function setPaymentTokenStatus(address tokenAddress, bool isActive) external onlyRole(ADMIN_ROLE) {
PaymentToken storage paymentToken = paymentTokens[tokenAddress];
if (address(paymentToken.token) == address(0)) revert TokenNotSupported();
paymentToken.isActive = isActive;
emit PaymentTokenStatusChanged(tokenAddress, isActive);
}
// Emergency functions - ADDED BACK
function requestEmergencyWithdrawal() external onlyRole(ADMIN_ROLE) {
emergencyWithdrawalRequest = block.timestamp;
emit EmergencyWithdrawalRequested(block.timestamp);
}
function executeEmergencyWithdrawal() external onlyRole(ADMIN_ROLE) {
if (emergencyWithdrawalRequest == 0) revert EmergencyNotRequested();
if (block.timestamp < emergencyWithdrawalRequest + emergencyWithdrawalDelay) revert TimelockNotExpired();
uint256 balance = address(this).balance;
if (balance == 0) revert NoETHToWithdraw();
emergencyWithdrawalRequest = 0;
(bool success, ) = payable(treasuryWallet).call{value: balance}("");
if (!success) revert ETHTransferFailed();
emit EmergencyWithdrawalExecuted(balance, block.timestamp);
}
function pause() external onlyRole(ADMIN_ROLE) {
_pause();
}
function unpause() external onlyRole(ADMIN_ROLE) {
_unpause();
}
// View functions - Same as original
function calculateTokensForUSD(uint256 usdAmount) external view returns (uint256) {
uint256 usdAmount18 = PrecisionMath.toInternal(usdAmount, 18);
return PrecisionMath.mulDiv(usdAmount18, PrecisionMath.PRECISION, tokenPrice);
}
function calculateUSDForTokens(uint256 tokenAmount) external view returns (uint256) {
return PrecisionMath.mulDiv(tokenAmount, tokenPrice, PrecisionMath.PRECISION);
}
function getPresaleStats() external view returns (
uint256 _tokensSold,
uint256 _totalRaised,
uint256 _totalParticipants,
uint256 _tokenPrice,
uint256 _presaleEnd,
bool _isActive,
uint256 _remainingTokens,
uint256 _currentTime
) {
return (
tokensSold,
totalRaised,
totalParticipants,
tokenPrice,
presaleEndTime,
_isPresaleActive(),
PRESALE_SUPPLY - tokensSold,
block.timestamp
);
}
function getPaymentTokens() external view returns (address[] memory) {
return paymentTokenList;
}
function isPaymentTokenActive(address token) external view returns (bool) {
return paymentTokens[token].isActive;
}
function getPurchaseLimits() external view returns (uint256 _minPurchase, uint256 _maxPurchase) {
return (minPurchase, maxPurchase);
}
// Emergency receive function
receive() external payable {
// Allow contract to receive ETH
}
}
Submitted on: 2025-09-24 18:58:02
Comments
Log in to comment.
No comments yet.