// SPDX-License-Identifier: MIT pragma solidity 0.8.30; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; interface IIdentity { function ownerOf(uint256) external view returns(address); } interface IFactory { function createPair(address,address) external returns(address); } interface IPair { function mint(address) external returns(uint256); function totalSupply() external view returns(uint256); function getReserves() external view returns(uint112,uint112,uint32); } interface IWrapped {function deposit() external payable;function transfer(address,uint256) external returns(bool);function balanceOf(address) external view returns(uint256);} /// @notice Engineering draft. Not audited. No live deployment is approved by this source. contract CluckLaunchV2 is ERC20, ReentrancyGuard { uint256 public constant SUPPLY=1_000_000_000 ether; uint256 public constant PRICE=0.0033 ether; uint256 public constant POOL_TOKENS=300_000_000 ether; uint256 public constant REF_TOKENS=100_000_000 ether; address public constant DEAD=address(0xdead); IIdentity public immutable registry; IWrapped public immutable wrapped; IPair public immutable pair; address public immutable marketing; uint256 public immutable paidCap; uint256 public immutable freeCap; uint256 public immutable walletCap; uint256 public immutable tokensPerShare; uint256 public immutable rewardPerShare; uint256 public immutable start; uint256 public immutable end; uint256 public paidCount; uint256 public freeCount; uint256 public soldOutAt; bool public opened; uint256 public openedAt; uint256 public totalPaid; uint256 public marketingCredit; uint256 public rewardAllocated; uint256 public taxEpoch; uint256 public currentTax; uint256 public expiredTax; uint256 public collectedTax; uint256 public burnedTax; mapping(uint256=>uint256) public paidById; mapping(address=>uint256) public paidByWallet; mapping(uint256=>bool) public freeById; mapping(address=>bool) public freeByWallet; mapping(uint256=>address) public idWallet; mapping(address=>uint256) public walletId; mapping(address=>bool) public walletBound; mapping(uint256=>uint256) public bonusById; mapping(address=>uint256) public bonusByWallet; mapping(uint256=>uint256) public referrerId; mapping(address=>uint256) public allocatedTokens; mapping(uint256=>bool) public referralBound; mapping(uint256=>address) public referrer; mapping(address=>uint256) public paidBNB; event MarketingPaid(address indexed recipient,uint256 amount); event MarketingDeferred(uint256 amount); event BonusGranted(uint256 indexed id,address indexed wallet,uint256 amount); event Bought(address indexed wallet,uint256 indexed id,uint256 qty,address referral); event FreeClaimed(address indexed wallet,uint256 indexed id); event LaunchDeferred(bytes reason); event Launched(uint256 bnb,uint256 tokens,uint256 liquidity); event TaxBurned(uint256 amount,uint256 epoch); error Invalid(); constructor(address identity,address factory,address wbnb,address budget,uint256 paid,uint256 free,uint256 cap,uint256 begins,string memory n,string memory s) ERC20(n,s){ if(identity.code.length==0||factory.code.length==0||wbnb.code.length==0||budget==address(0)||paid==0||free==0||cap==0||cap>10||begins=end||registry.ownerOf(id)!=msg.sender)revert Invalid();} function _bind(uint256 id) private { if(idWallet[id]==address(0)){if(walletBound[msg.sender])revert Invalid();idWallet[id]=msg.sender;walletId[msg.sender]=id;walletBound[msg.sender]=true;} else if(idWallet[id]!=msg.sender||!walletBound[msg.sender]||walletId[msg.sender]!=id)revert Invalid(); } function remainingPaid(uint256 id,address account) public view returns(uint256){ if((idWallet[id]!=address(0)&&idWallet[id]!=account)||(walletBound[account]&&walletId[account]!=id))return 0; uint256 a=walletCap+bonusById[id]-paidById[id];uint256 b=walletCap+bonusByWallet[account]-paidByWallet[account];uint256 c=paidCap-paidCount; return a=freeCap||freeById[id]||freeByWallet[msg.sender])revert Invalid(); freeById[id]=true;freeByWallet[msg.sender]=true;freeCount++;_allocate(msg.sender,tokensPerShare);emit FreeClaimed(msg.sender,id); } /// @param refId Zero means no referrer, permanently fixed on the first paid purchase. function buy(uint256 id,uint256 qty,uint256 refId) external payable nonReentrant { _active(id);_bind(id);if(qty==0||qty>remainingPaid(id,msg.sender)||msg.value!=qty*PRICE)revert Invalid(); if(!referralBound[id]){ referralBound[id]=true; if(refId!=0){address r=registry.ownerOf(refId);if(refId==id||r==msg.sender||paidById[refId]==0||idWallet[refId]!=r)revert Invalid();referrer[id]=r;referrerId[id]=refId;} }else if(refId!=0)revert Invalid(); address r=referrer[id];if(r!=address(0)){uint256 reward=qty*rewardPerShare;_allocate(r,reward);rewardAllocated+=reward;uint256 rid=referrerId[id];bonusById[rid]+=qty;bonusByWallet[r]+=qty;emit BonusGranted(rid,r,qty);} paidCount+=qty;paidById[id]+=qty;paidByWallet[msg.sender]+=qty;_allocate(msg.sender,qty*tokensPerShare);paidBNB[msg.sender]+=msg.value;totalPaid+=msg.value; emit Bought(msg.sender,id,qty,r); if(paidCount==paidCap){soldOutAt=block.timestamp;if(gasleft()>1_650_000){try this.attemptLaunch{gas:1_400_000}(){}catch(bytes memory why){emit LaunchDeferred(why);}}else emit LaunchDeferred("gas budget");} } function launch() external nonReentrant { _launch(); } function attemptLaunch() external {if(msg.sender!=address(this))revert Invalid();_launch();} function _launch() private { if(opened||soldOutAt==0||block.timestamp>=soldOutAt+1 days)revert Invalid(); (uint112 a,uint112 b,)=pair.getReserves(); // Before launch no one can obtain CLUCK. A WBNB-only donation cannot mint LP. // Preserve donations exclusively as additional liquidity, never sales proceeds. // Handles both unsynced donations and donations already recorded via sync(). if(pair.totalSupply()!=0||balanceOf(address(pair))!=0||(a!=0&&b!=0))revert Invalid(); uint256 donatedWBNB=wrapped.balanceOf(address(pair)); uint256 poolBNB=totalPaid*8500/10000; opened=true;openedAt=block.timestamp;marketingCredit=totalPaid-poolBNB; uint256 unused=(freeCap-freeCount)*tokensPerShare+REF_TOKENS-rewardAllocated; if(unused!=0)super._update(address(this),address(0),unused); wrapped.deposit{value:poolBNB}(); super._update(address(this),address(pair),POOL_TOKENS); if(!wrapped.transfer(address(pair),poolBNB))revert Invalid(); uint256 lp=pair.mint(DEAD);if(lp==0)revert Invalid(); if(balanceOf(address(pair))!=POOL_TOKENS||wrapped.balanceOf(address(pair))!=poolBNB+donatedWBNB)revert Invalid(); emit Launched(poolBNB,POOL_TOKENS,lp); try this.attemptMarketing{gas:65000}(){}catch{emit MarketingDeferred(marketingCredit);} } function failed() public view returns(bool){return !opened&&(soldOutAt==0?block.timestamp>=end:block.timestamp>=soldOutAt+1 days);} function refund(address payable recipient) external nonReentrant { uint256 amount=paidBNB[msg.sender];if(!failed()||amount==0||recipient==address(0))revert Invalid(); paidBNB[msg.sender]=0;_cancelAllocation(msg.sender);(bool ok,)=recipient.call{value:amount}("");if(!ok)revert Invalid(); } function attemptMarketing() external {if(msg.sender!=address(this))revert Invalid();_payMarketing();} function collectMarketing() external nonReentrant {_payMarketing();} function _payMarketing() private { if(!opened||marketingCredit==0)revert Invalid();uint256 amount=marketingCredit;marketingCredit=0; (bool ok,)=payable(marketing).call{value:amount,gas:30000}("");if(!ok)revert Invalid();emit MarketingPaid(marketing,amount); } function cancelFailedTokens() external nonReentrant {if(!failed())revert Invalid();_cancelAllocation(msg.sender);} function _cancelAllocation(address account) private {uint256 amount=allocatedTokens[account];if(amount!=0){allocatedTokens[account]=0;super._update(account,address(0),amount);}} function _rollTax() private {uint256 e=(block.timestamp-openedAt)/1 hours;if(e>taxEpoch){expiredTax+=currentTax;currentTax=0;taxEpoch=e;}} function _burnExpired() private returns(uint256 amount){_rollTax();amount=expiredTax;if(amount!=0){expiredTax=0;burnedTax+=amount;super._update(address(this),address(0),amount);emit TaxBurned(amount,taxEpoch);}} function burnAccruedTax() external nonReentrant returns(uint256){if(!opened)revert Invalid();return _burnExpired();} function _update(address from,address to,uint256 value) internal override { if(from==address(0)){super._update(from,to,value);return;} if(!opened)revert Invalid(); if(from==address(pair)||to==address(pair)){ _burnExpired();uint256 tax=value/100;if(tax!=0){collectedTax+=tax;currentTax+=tax;super._update(from,address(this),tax);}super._update(from,to,value-tax); }else super._update(from,to,value); } }