// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /** * @title XICPresale * @notice NAC XIC Token Presale Contract — 购买即时发放版本 v2 * @dev 用户支付 USDT 后,XIC 代币在同一笔交易内立即转入用户钱包 * * 核心参数: * - 预售总量:2,500,000,000 XIC(25亿) * - 价格:$0.02 USDT / XIC * - 预售硬顶:$50,000,000 USDT(5000万) * - 无最小/最大单笔限制 * - 预售时长:180天(半年),到期自动停止 * - 预售结束后未售出 XIC 可由 Owner 回收 * - 支持 USDT 和 BNB 两种支付方式 * * 合约关联: * - XIC Token: 0x59FF34dD59680a7125782b1f6df2A86ed46F5A24 * - BSC USDT: 0x55d398326f99059fF775485246999027B3197955 */ interface IERC20 { function transfer(address to, uint256 amount) external returns (bool); function transferFrom(address from, address to, uint256 amount) external returns (bool); function balanceOf(address account) external view returns (uint256); function allowance(address owner, address spender) external view returns (uint256); function decimals() external view returns (uint8); } interface IPriceOracle { function getBNBPrice() external view returns (uint256); // BNB/USD price, 18 decimals } contract XICPresale { // ─── Constants ───────────────────────────────────────────────────────────── uint256 public constant PRESALE_DURATION = 180 days; // 预售时长:半年 // ─── State Variables ─────────────────────────────────────────────────────── address public owner; address public wallet; // USDT/BNB 收款钱包地址 IERC20 public immutable xicToken; // XIC 代币合约 IERC20 public immutable usdt; // BSC USDT 合约 // 价格:$0.02 USDT per XIC(18 decimals 精度) uint256 public tokenPrice = 2e16; // 0.02 * 1e18 // 预售总量:25亿 XIC uint256 public hardCap = 2_500_000_000 * 1e18; // 预售时间 uint256 public presaleStartTime; // 预售开始时间(Unix 时间戳) uint256 public presaleEndTime; // 预售结束时间(= startTime + 180 days) bool public presaleStarted; // 是否已启动 // 预售状态(Owner 可手动暂停/恢复) bool public presalePaused = false; // 统计 uint256 public totalTokensSold; // 已售 XIC 总量(18 decimals) uint256 public totalRaised; // 已筹 USDT 总量(6 decimals) // 用户购买记录 mapping(address => uint256) public userPurchases; // 用户购买的 XIC 总量 mapping(address => uint256) public userSpent; // 用户花费的 USDT 总量 // BNB 价格预言机 address public priceOracle; // ─── Events ──────────────────────────────────────────────────────────────── event PresaleStarted(uint256 startTime, uint256 endTime); event PresalePaused(bool paused); event PresaleEnded(uint256 totalSold, uint256 totalRaised); event TokensPurchased( address indexed buyer, uint256 usdtAmount, uint256 tokenAmount, string paymentMethod ); event UnsoldTokensRecovered(uint256 amount); event WalletChanged(address newWallet); event TokenPriceChanged(uint256 newPrice); event HardCapChanged(uint256 newHardCap); event EmergencyWithdraw(address token, uint256 amount); // ─── Modifiers ───────────────────────────────────────────────────────────── modifier onlyOwner() { require(msg.sender == owner, "Presale: caller is not owner"); _; } modifier whenActive() { require(presaleStarted, "Presale: not started yet"); require(!presalePaused, "Presale: presale is paused"); require(block.timestamp <= presaleEndTime, "Presale: presale has ended"); require(totalTokensSold < hardCap, "Presale: hard cap reached"); _; } modifier afterPresale() { require( presaleStarted && (block.timestamp > presaleEndTime || totalTokensSold >= hardCap), "Presale: presale still active" ); _; } // ─── Constructor ─────────────────────────────────────────────────────────── /** * @param _xicToken XIC 代币合约地址 * @param _usdt BSC USDT 合约地址 * @param _wallet 收款钱包地址(接收 USDT 和 BNB) * @param _oracle BNB 价格预言机地址(可为 address(0) 表示不支持 BNB 购买) */ constructor( address _xicToken, address _usdt, address _wallet, address _oracle ) { require(_xicToken != address(0), "Invalid XIC token address"); require(_usdt != address(0), "Invalid USDT address"); require(_wallet != address(0), "Invalid wallet address"); owner = msg.sender; xicToken = IERC20(_xicToken); usdt = IERC20(_usdt); wallet = _wallet; priceOracle = _oracle; } // ─── Owner: Start Presale ────────────────────────────────────────────────── /** * @notice 启动预售(Owner 调用一次,之后自动计时 180 天) * @dev 调用前请确保合约已持有足够的 XIC(至少 25亿 XIC) */ function startPresale() external onlyOwner { require(!presaleStarted, "Presale: already started"); uint256 xicBalance = xicToken.balanceOf(address(this)); require(xicBalance >= hardCap, "Presale: insufficient XIC in contract"); presaleStarted = true; presaleStartTime = block.timestamp; presaleEndTime = block.timestamp + PRESALE_DURATION; emit PresaleStarted(presaleStartTime, presaleEndTime); } /** * @notice 暂停 / 恢复预售(紧急情况使用) */ function setPaused(bool _paused) external onlyOwner { presalePaused = _paused; emit PresalePaused(_paused); } // ─── Core Purchase Functions ─────────────────────────────────────────────── /** * @notice 用 USDT 购买 XIC(即时发放) * @param usdtAmount USDT 数量(6 decimals,例如 100 USDT = 100_000_000) * * 购买流程: * 1. 用户调用 USDT.approve(presaleAddress, usdtAmount) * 2. 调用此函数 * 3. 合约将 USDT 从用户转入 wallet * 4. 合约立即将 XIC 转入用户钱包 */ function buyWithUSDT(uint256 usdtAmount) external whenActive { require(usdtAmount > 0, "Presale: amount must be > 0"); // 将 USDT (6d) 转换为 18d 精度,再计算 XIC 数量 // XIC = usdtAmount * 1e12 * 1e18 / tokenPrice uint256 tokenAmount = (usdtAmount * 1e12 * 1e18) / tokenPrice; require(tokenAmount > 0, "Presale: token amount too small"); require( totalTokensSold + tokenAmount <= hardCap, "Presale: exceeds hard cap" ); // 检查合约 XIC 余额 require( xicToken.balanceOf(address(this)) >= tokenAmount, "Presale: insufficient XIC in contract" ); // 检查用户 USDT 授权和余额 require( usdt.allowance(msg.sender, address(this)) >= usdtAmount, "Presale: insufficient USDT allowance" ); require( usdt.balanceOf(msg.sender) >= usdtAmount, "Presale: insufficient USDT balance" ); // 1. 收取 USDT → wallet require( usdt.transferFrom(msg.sender, wallet, usdtAmount), "Presale: USDT transfer failed" ); // 2. 立即发放 XIC → 买家 require( xicToken.transfer(msg.sender, tokenAmount), "Presale: XIC transfer failed" ); // 更新统计 totalTokensSold += tokenAmount; totalRaised += usdtAmount; userPurchases[msg.sender] += tokenAmount; userSpent[msg.sender] += usdtAmount; emit TokensPurchased(msg.sender, usdtAmount, tokenAmount, "USDT"); } /** * @notice 用 BNB 购买 XIC(即时发放) * @dev 需要价格预言机,直接发送 BNB 到合约地址也可触发 */ function buyWithBNB() external payable whenActive { require(msg.value > 0, "Presale: BNB amount must be > 0"); require(priceOracle != address(0), "Presale: BNB purchase not supported"); uint256 bnbPriceUSD = IPriceOracle(priceOracle).getBNBPrice(); require(bnbPriceUSD > 0, "Presale: invalid BNB price"); // BNB 价值(18d)→ XIC 数量 uint256 usdValue18 = (msg.value * bnbPriceUSD) / 1e18; uint256 tokenAmount = (usdValue18 * 1e18) / tokenPrice; require(tokenAmount > 0, "Presale: token amount too small"); require( totalTokensSold + tokenAmount <= hardCap, "Presale: exceeds hard cap" ); require( xicToken.balanceOf(address(this)) >= tokenAmount, "Presale: insufficient XIC in contract" ); // 1. 转发 BNB → wallet (bool bnbOk, ) = wallet.call{value: msg.value}(""); require(bnbOk, "Presale: BNB transfer to wallet failed"); // 2. 立即发放 XIC → 买家 require( xicToken.transfer(msg.sender, tokenAmount), "Presale: XIC transfer failed" ); // 等值 USDT(6d)用于统计 uint256 usdtEquiv = usdValue18 / 1e12; totalTokensSold += tokenAmount; totalRaised += usdtEquiv; userPurchases[msg.sender] += tokenAmount; userSpent[msg.sender] += usdtEquiv; emit TokensPurchased(msg.sender, usdtEquiv, tokenAmount, "BNB"); } // ─── Post-Presale: Recover Unsold Tokens ────────────────────────────────── /** * @notice 预售结束后,Owner 回收合约内未售出的 XIC * @dev 只能在预售结束后(超时或售罄)调用 */ function recoverUnsoldTokens() external onlyOwner afterPresale { uint256 remaining = xicToken.balanceOf(address(this)); require(remaining > 0, "Presale: no unsold tokens to recover"); require( xicToken.transfer(owner, remaining), "Presale: recovery transfer failed" ); emit UnsoldTokensRecovered(remaining); emit PresaleEnded(totalTokensSold, totalRaised); } // ─── View Functions ──────────────────────────────────────────────────────── /** * @notice 计算指定 USDT 金额可购买的 XIC 数量 * @param usdtAmount USDT 金额(6 decimals) */ function calculateTokenAmount(uint256 usdtAmount) external view returns (uint256) { return (usdtAmount * 1e12 * 1e18) / tokenPrice; } /** * @notice 计算指定 BNB 金额可购买的 XIC 数量 */ function calculateTokenAmountForBNB(uint256 bnbAmount) external view returns (uint256) { if (priceOracle == address(0)) return 0; uint256 bnbPriceUSD = IPriceOracle(priceOracle).getBNBPrice(); if (bnbPriceUSD == 0) return 0; uint256 usdValue18 = (bnbAmount * bnbPriceUSD) / 1e18; return (usdValue18 * 1e18) / tokenPrice; } /** * @notice 合约当前持有的 XIC 余额(可售量) */ function availableXIC() external view returns (uint256) { return xicToken.balanceOf(address(this)); } /** * @notice 预售进度(已售 / 硬顶,basis points 0-10000) */ function presaleProgress() external view returns ( uint256 sold, uint256 cap, uint256 progressBps ) { sold = totalTokensSold; cap = hardCap; progressBps = cap > 0 ? (sold * 10000) / cap : 0; } /** * @notice 预售剩余时间(秒) */ function timeRemaining() external view returns (uint256) { if (!presaleStarted || block.timestamp >= presaleEndTime) return 0; return presaleEndTime - block.timestamp; } /** * @notice 预售是否当前可购买 */ function isPresaleActive() external view returns (bool) { return presaleStarted && !presalePaused && block.timestamp <= presaleEndTime && totalTokensSold < hardCap; } /** * @notice 获取 BNB 当前价格(USD,18 decimals) */ function getBNBPrice() external view returns (uint256) { if (priceOracle == address(0)) return 0; return IPriceOracle(priceOracle).getBNBPrice(); } // ─── Owner Admin Functions ───────────────────────────────────────────────── function setWallet(address _wallet) external onlyOwner { require(_wallet != address(0), "Invalid wallet address"); wallet = _wallet; emit WalletChanged(_wallet); } function setTokenPrice(uint256 _tokenPrice) external onlyOwner { require(_tokenPrice > 0, "Invalid token price"); tokenPrice = _tokenPrice; emit TokenPriceChanged(_tokenPrice); } function setHardCap(uint256 _hardCap) external onlyOwner { require(_hardCap >= totalTokensSold, "Hard cap below sold amount"); hardCap = _hardCap; emit HardCapChanged(_hardCap); } function setPriceOracle(address _oracle) external onlyOwner { priceOracle = _oracle; } function transferOwnership(address newOwner) external onlyOwner { require(newOwner != address(0), "Invalid owner address"); owner = newOwner; } /** * @notice 紧急提取合约内的任意代币(误转入的情况) * @dev 不可在预售进行中提取 XIC,防止影响购买 */ function emergencyWithdraw(address token, uint256 amount) external onlyOwner { // 预售进行中,禁止提取 XIC(防止合约无法发放) if ( token == address(xicToken) && presaleStarted && block.timestamp <= presaleEndTime && totalTokensSold < hardCap ) { revert("Presale: cannot withdraw XIC during active presale"); } IERC20 tokenContract = IERC20(token); require(tokenContract.balanceOf(address(this)) >= amount, "Insufficient balance"); require(tokenContract.transfer(owner, amount), "Transfer failed"); emit EmergencyWithdraw(token, amount); } /** * @notice 提取合约内误转入的 BNB */ function withdrawBNB() external onlyOwner { uint256 balance = address(this).balance; require(balance > 0, "No BNB to withdraw"); (bool ok, ) = owner.call{value: balance}(""); require(ok, "BNB withdrawal failed"); } // ─── Receive ─────────────────────────────────────────────────────────────── receive() external payable { if (presaleStarted && !presalePaused && priceOracle != address(0)) { // 直接发送 BNB 触发购买 require(msg.value > 0, "No BNB sent"); uint256 bnbPriceUSD = IPriceOracle(priceOracle).getBNBPrice(); require(bnbPriceUSD > 0, "Invalid BNB price"); uint256 usdValue18 = (msg.value * bnbPriceUSD) / 1e18; uint256 tokenAmount = (usdValue18 * 1e18) / tokenPrice; require(tokenAmount > 0, "Token amount too small"); require(totalTokensSold + tokenAmount <= hardCap, "Hard cap reached"); require(xicToken.balanceOf(address(this)) >= tokenAmount, "Insufficient XIC"); (bool bnbOk, ) = wallet.call{value: msg.value}(""); require(bnbOk, "BNB transfer failed"); require(xicToken.transfer(msg.sender, tokenAmount), "XIC transfer failed"); uint256 usdtEquiv = usdValue18 / 1e12; totalTokensSold += tokenAmount; totalRaised += usdtEquiv; userPurchases[msg.sender] += tokenAmount; userSpent[msg.sender] += usdtEquiv; emit TokensPurchased(msg.sender, usdtEquiv, tokenAmount, "BNB"); } } }