NAC_Blockchain/contracts/XICPresale_v2/XICPresale.sol

444 lines
17 KiB
Solidity
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 XIC25亿
* - 价格:$0.02 USDT / XIC
* - 预售硬顶:$50,000,000 USDT5000万
* - 无最小/最大单笔限制
* - 预售时长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 XIC18 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"
);
// 等值 USDT6d用于统计
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 当前价格USD18 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");
}
}
}