[NAC-PRESALE-V2] 新预售合约和部署手册 - XICPresale.sol

This commit is contained in:
nacadmin 2026-03-10 01:55:27 +08:00
parent 48e3b611c3
commit 30dbacbd93
1 changed files with 443 additions and 0 deletions

View File

@ -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 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");
}
}
}