// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /* ============================================================ 牛金丹 NIUJINDAN · 打新合约 总量 4444 份:1111 免费 + 3333 付费(0.0028 BNB/份)= 9.3324 BNB 每人(每个 .ai 身份)最多 4 份,限时 72 小时 满额后分账:60% 加池(LP 打进黑洞烧掉)/ 20% 团队 / 20% 分红池 未满额:到期后按已付金额退 80% ============================================================ */ interface IBEP20 { function transfer(address, uint256) external returns (bool); function transferFrom(address, address, uint256) external returns (bool); function approve(address, uint256) external returns (bool); function balanceOf(address) external view returns (uint256); } interface IPancakeRouter { function addLiquidityETH( address token, uint256 amountTokenDesired, uint256 amountTokenMin, uint256 amountETHMin, address to, uint256 deadline ) external payable returns (uint256 amountToken, uint256 amountETH, uint256 liquidity); } interface IAIIdentity { function handleOf(address user) external view returns (string memory); } contract NiuJinDanLaunch { // ==== 参数 ==== uint256 public constant TOTAL_SUPPLY = 5000; uint256 public constant FREE_SUPPLY = 500; uint256 public constant PRICE = 0.0028 ether; uint256 public constant MAX_PER_ID = 10; uint256 public constant DURATION = 72 hours; uint256 public constant REFUND_BPS = 8000; // 未开盘退 80% // 稀有度奖励(18 位小数) uint256 private constant R1 = 50_000_000 * 1e18; uint256 private constant R2 = 90_000_000 * 1e18; uint256 private constant R3 = 120_000_000 * 1e18; uint256 private constant R4 = 150_000_000 * 1e18; // ==== 状态 ==== address public owner; IAIIdentity public aiRegistry; IBEP20 public token; IPancakeRouter public router; address public teamWallet; address public dividendPool; address public constant DEAD = 0x000000000000000000000000000000000000dEaD; uint256 public constant POOL_RESERVE = 100_000_000_000 * 1e18; // 100B 固定留给建池 uint256 public startTime; uint256 public totalMinted; uint256 public paidMinted; uint256 public totalBnbPaid; bool public poolOpened; bool public liquidityAdded; mapping(address => uint256) public mintedByAddress; mapping(address => uint256) public bnbPaid; mapping(address => bool) public refunded; event Minted(address indexed user, uint256 tokenId, uint8 rarity, uint256 reward); event PoolOpened(uint256 bnbPool, uint256 tokensPool, uint256 bnbTeam, uint256 bnbDividend); event Refunded(address indexed user, uint256 amount); modifier onlyOwner() { require(msg.sender == owner, "not owner"); _; } constructor( address _registry, address _router, address _token, address _team, address _dividend, address _owner, uint256 _startTime ) { require(_startTime > block.timestamp, "start must be future"); owner = _owner; aiRegistry = IAIIdentity(_registry); router = IPancakeRouter(_router); token = IBEP20(_token); teamWallet = _team; dividendPool = _dividend; startTime = _startTime; } /* ================= 铸造 ================= */ function mint(uint256 amount) external payable { require(block.timestamp >= startTime, "not started"); require(block.timestamp < startTime + DURATION, "ended"); require(!poolOpened, "pool opened"); require(amount > 0 && amount <= MAX_PER_ID, "bad amount"); require(mintedByAddress[msg.sender] + amount <= MAX_PER_ID, "over per id"); require(totalMinted + amount <= TOTAL_SUPPLY, "sold out"); string memory handle = aiRegistry.handleOf(msg.sender); require(bytes(handle).length > 0, "no .ai id"); // 免费额度:全站前 1111 份,且每个 .ai 钱包只能免费拿 1 份 uint256 freeLeft = FREE_SUPPLY - (totalMinted - paidMinted); bool firstFree = (mintedByAddress[msg.sender] == 0 && freeLeft > 0); uint256 cost; uint256 newPaid; for (uint256 i = 0; i < amount; i++) { if (i == 0 && firstFree) { freeLeft--; } else { cost += PRICE; newPaid++; } } require(msg.value == cost, "wrong bnb"); totalMinted += amount; paidMinted += newPaid; mintedByAddress[msg.sender] += amount; bnbPaid[msg.sender] += cost; totalBnbPaid += cost; // 开奖 + 发代币 for (uint256 i = 0; i < amount; i++) { uint256 id = totalMinted - amount + i; uint256 seed = uint256(keccak256(abi.encodePacked( blockhash(block.number - 1), msg.sender, id, totalBnbPaid ))) % 100; uint8 rarity; uint256 reward; if (seed < 60) { rarity = 1; reward = R1; } else if (seed < 80) { rarity = 2; reward = R2; } else if (seed < 95) { rarity = 3; reward = R3; } else { rarity = 4; reward = R4; } // 玩家额度用完时按剩余量发,绝不超发、绝不 revert uint256 avail = token.balanceOf(address(this)); uint256 spendable = avail > POOL_RESERVE ? avail - POOL_RESERVE : 0; if (reward > spendable) reward = spendable; require(reward > 0, "reward pool empty"); require(token.transfer(msg.sender, reward), "token out"); emit Minted(msg.sender, id, rarity, reward); } if (totalMinted == TOTAL_SUPPLY) _openPool(); } /* ================= 分账 ================= */ function _openPool() internal { poolOpened = true; uint256 bal = totalBnbPaid; uint256 bnbPool = (bal * 60) / 100; uint256 bnbTeam = (bal * 20) / 100; uint256 bnbDiv = bal - bnbPool - bnbTeam; if (bnbTeam > 0) { (bool a, ) = payable(teamWallet).call{value: bnbTeam}(""); require(a, "team fail"); } if (bnbDiv > 0) { (bool b, ) = payable(dividendPool).call{value: bnbDiv}(""); require(b, "div fail"); } uint256 tokensForPool = token.balanceOf(address(this)); _addLiquidity(bnbPool, tokensForPool); emit PoolOpened(bnbPool, tokensForPool, bnbTeam, bnbDiv); } /// 加池:只做一次;万一 Pancake 调用失败,任何人都能重试,不需要 Owner function _addLiquidity(uint256 bnbAmount, uint256 tokenAmount) internal { if (liquidityAdded || tokenAmount == 0 || bnbAmount == 0) return; token.approve(address(router), tokenAmount); // 低层调用:失败不影响开盘状态,留给 retryAddLiquidity 重试 (bool ok, ) = address(router).call{value: bnbAmount}( abi.encodeWithSignature( "addLiquidityETH(address,uint256,uint256,uint256,address,uint256)", address(token), tokenAmount, 0, 0, DEAD, block.timestamp + 600 ) ); if (!ok) return; liquidityAdded = true; } /// 公开重试:开盘后若加池没成功,任何人可以调用 function retryAddLiquidity() external { require(poolOpened, "not opened"); require(!liquidityAdded, "added"); uint256 bal = totalBnbPaid; uint256 bnbPool = (bal * 60) / 100; _addLiquidity(bnbPool, token.balanceOf(address(this))); } /// 手动触发(满额前可调用则 revert) function openPool() external onlyOwner { require(totalMinted == TOTAL_SUPPLY, "not sold out"); require(!poolOpened, "opened"); _openPool(); } /* ================= 退款(80%) ================= */ function refund() external { require(!poolOpened, "pool opened"); require(block.timestamp >= startTime + DURATION, "not ended"); require(!refunded[msg.sender], "done"); uint256 paid = bnbPaid[msg.sender]; require(paid > 0, "nothing"); refunded[msg.sender] = true; uint256 back = (paid * REFUND_BPS) / 10000; (bool ok, ) = payable(msg.sender).call{value: back}(""); require(ok, "bnb fail"); emit Refunded(msg.sender, back); } /* ================= 只读 ================= */ function freeRemaining() external view returns (uint256) { return FREE_SUPPLY - (totalMinted - paidMinted); } function endTime() external view returns (uint256) { return startTime + DURATION; } /* ================= 管理 ================= */ function setWallets(address _team, address _dividend) external onlyOwner { teamWallet = _team; dividendPool = _dividend; } function rescueToken(address to, uint256 amt) external onlyOwner { require(poolOpened || block.timestamp >= startTime + DURATION, "too early"); token.transfer(to, amt); } receive() external payable {} }