From 30dbacbd93010380d9a395a8da936b7ff8427bec Mon Sep 17 00:00:00 2001 From: nacadmin Date: Tue, 10 Mar 2026 01:55:27 +0800 Subject: [PATCH] =?UTF-8?q?[NAC-PRESALE-V2]=20=E6=96=B0=E9=A2=84=E5=94=AE?= =?UTF-8?q?=E5=90=88=E7=BA=A6=E5=92=8C=E9=83=A8=E7=BD=B2=E6=89=8B=E5=86=8C?= =?UTF-8?q?=20-=20XICPresale.sol?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/XICPresale_v2/XICPresale.sol | 443 +++++++++++++++++++++++++ 1 file changed, 443 insertions(+) create mode 100644 contracts/XICPresale_v2/XICPresale.sol diff --git a/contracts/XICPresale_v2/XICPresale.sol b/contracts/XICPresale_v2/XICPresale.sol new file mode 100644 index 0000000..47e6b04 --- /dev/null +++ b/contracts/XICPresale_v2/XICPresale.sol @@ -0,0 +1,443 @@ +// 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"); + } + } +}