123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- pragma solidity ^0.8.24;
- import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
- import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
- import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
- import "hardhat/console.sol"; // For debugging purposes, can be removed in production
- contract Launchpad is ReentrancyGuard {
- address public owner;
- address public token; // sale token address
- uint256 public totalTokens; // total tokens for sale
- uint256 public tokensSold; // tokens sold so far
- uint256 public startTime; // beginning time of the sale
- uint256 public endTime; // ending time of the sale
- bytes32 public merkleRoot; // Merkle root for whitelisting
- bool public enableClaimToken; // enable claim token
- uint256 public claimStartTime; // start time for claiming tokens
- address public paymentToken; // payment tokens accepted for the sale
- uint256 public salePaymentPrice; // payment token price in terms of sale token
- mapping(address => uint256) public claimableTokensAmount; // user contributions for claiming tokens
- uint256 public accountsCount; // number of accounts that have contributed
- event SaleCreated(address token, uint256 totalTokens);
- event Contributed(address user, uint256 amount, address paymentToken);
- event TokensClaimed(address user, uint256 amount);
- event SaleCancelled(address token, uint256 refundedAmount);
- event EnableClaimToken(bool enabled, uint256 claimStartTime);
- event DisableClaimToken(bool disabled);
- constructor() {
- owner = msg.sender;
- accountsCount = 0;
- }
- // Modifiers
- modifier onlyOwner() {
- require(msg.sender == owner, "Only owner can call this function");
- _;
- }
- modifier saleActive() {
- require(merkleRoot != bytes32(0), "MerkleRoot not initialized");
- console.log("Claim start time:", startTime);
- console.log("Current time:", block.timestamp);
- console.log("Claiming tokens enabled:", endTime);
- require(block.timestamp >= startTime && block.timestamp <= endTime, "Sale is not active");
- _;
- }
- modifier validClaimTime() {
- require(enableClaimToken, "Claiming tokens is not enabled");
- require(block.timestamp >= claimStartTime, "Claiming tokens not started yet");
- _;
- }
- // Owner Functions
- function createSale(
- address _token,
- uint256 _totalTokens,
- uint256 _startTime,
- uint256 _endTime,
- address _paymentToken,
- uint256 _price
- ) public onlyOwner {
- require(_token != address(0), "Invalid token address");
- require(_totalTokens > 0, "Total tokens must be greater than 0");
- require(_startTime < _endTime, "Invalid time range");
- paymentToken = _paymentToken;
- salePaymentPrice = _price;
- token = _token;
- totalTokens = _totalTokens;
- startTime = _startTime;
- endTime = _endTime;
- tokensSold = 0;
- merkleRoot = bytes32(0); // Reset Merkle root
- IERC20(_token).transferFrom(msg.sender, address(this), _totalTokens); // Transfer tokens to the contracts
- emit SaleCreated(token, totalTokens);
- }
- function withdrawRemainingTokens() public onlyOwner {
- require(block.timestamp > endTime, "Sale is still active");
- uint256 remaining = IERC20(token).balanceOf(address(this));
- IERC20(token).transfer(owner, remaining);
- }
- function withdrawPayments() public onlyOwner {
- require(block.timestamp > endTime, "Sale is still active");
- if (paymentToken == address(0)) {
- payable(owner).transfer(address(this).balance);
- } else {
- uint256 balance = IERC20(paymentToken).balanceOf(address(this));
- IERC20(paymentToken).transfer(owner, balance);
- }
- }
- function cancelSale() public onlyOwner {
- require(block.timestamp < startTime, "Sale already started");
- require(token != address(0), "Sale not initialized");
- uint256 balance = IERC20(token).balanceOf(address(this));
- require(balance > 0, "No tokens to refund");
- IERC20(token).transfer(owner, balance); // Refund tokens to owner
- token = address(0);
- totalTokens = 0;
- tokensSold = 0;
- startTime = 0;
- endTime = 0;
- merkleRoot = bytes32(0); // Reset Merkle root
- emit SaleCancelled(token, balance);
- }
- function enableClaimTokens(uint256 _enableClaimTokenTime) public onlyOwner {
- require(_enableClaimTokenTime >= block.timestamp, "Enable claim time must be in the future");
- require(_enableClaimTokenTime >= endTime, "Claiming tokens can only be enabled after the sale ends");
- enableClaimToken = true;
- claimStartTime = _enableClaimTokenTime; // Set claim start time to now
- emit EnableClaimToken(enableClaimToken, claimStartTime);
- }
- function disableClaimTokens() public onlyOwner {
- require(enableClaimToken, "Claiming tokens is already disabled");
- enableClaimToken = false;
- emit DisableClaimToken(enableClaimToken);
- }
- // Setter
- function setOwner(address _newOwner) public onlyOwner {
- require(_newOwner != address(0), "Invalid owner address");
- owner = _newOwner;
- }
- function setSalePayment(address _paymentToken, uint256 _price) public onlyOwner {
- require(_price > 0, "Price must be greater than 0");
- paymentToken = _paymentToken;
- salePaymentPrice = _price;
- }
- function setMerkleRoot(bytes32 _merkleRoot) public onlyOwner {
- merkleRoot = _merkleRoot;
- }
- //Getter
- function getOwner() public view returns (address) {
- return owner;
- }
- function getSaleToken() public view returns (address) {
- return token;
- }
- function getTotalTokens() public view returns (uint256) {
- return totalTokens;
- }
- function getTokensSold() public view returns (uint256) {
- return tokensSold;
- }
- function getStartTime() public view returns (uint256) {
- return startTime;
- }
- function getEndTime() public view returns (uint256) {
- return endTime;
- }
- function getSaleDetails()
- public
- view
- returns (
- address token,
- uint256 totalTokens,
- uint256 tokensSold,
- uint256 startTime,
- uint256 endTime,
- bytes32 merkleRoot,
- address paymentToken,
- uint256 salePaymentPrice
- )
- {
- return (token, totalTokens, tokensSold, startTime, endTime, merkleRoot, paymentToken, salePaymentPrice);
- }
- function getClaimableTokens(address _user) public view returns (uint256) {
- return claimableTokensAmount[_user];
- }
- function getPaymentTokenPrice() public view returns (uint256) {
- return salePaymentPrice;
- }
- function getPaymentTokens() public view returns (address) {
- return paymentToken;
- }
- function getClaimStartTime() public view returns (uint256) {
- return claimStartTime;
- }
- function isClaimEnabled() public view returns (bool) {
- return enableClaimToken;
- }
- //Main Function
- //MUST TEST !
- function contributeETH(
- uint256 _buyAmount,
- uint256 _maxBuyAmount,
- bytes32[] memory _proof
- ) public payable saleActive nonReentrant {
- require(paymentToken == address(0), "Payment token must be ETH for this sale");
- require(msg.value > 0, "Must send ETH");
- require(_buyAmount > 0, "Must buy a positive amount");
- require(_buyAmount <= _maxBuyAmount, "Buy amount exceeds max buy amount");
- require(salePaymentPrice > 0, "Payment token not accepted for this sale");
- // 验证 Merkle 证明
- bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(msg.sender, _maxBuyAmount))));
- require(MerkleProof.verify(_proof, merkleRoot, leaf), "Invalid proof");
- uint256 cost = (salePaymentPrice / 10 ** 18) * _buyAmount; // Assuming salePaymentPrice is in wei
- require(msg.value >= cost, "Not enough ETH sent");
- tokensSold += _buyAmount;
- if (claimableTokensAmount[msg.sender] == 0) {
- accountsCount++; // Increment accounts count only if this is the first contribution
- }
- claimableTokensAmount[msg.sender] += _buyAmount; // update user's claimable tokens
- require(claimableTokensAmount[msg.sender] <= _maxBuyAmount, "Buy amount exceeds max buy amount");
- payable(msg.sender).transfer(msg.value - cost); //refund excess ETH
- emit Contributed(msg.sender, _buyAmount, address(0));
- }
- function contributeERC20(
- uint256 _buyAmount,
- uint256 _maxBuyAmount,
- bytes32[] memory _proof
- ) public saleActive nonReentrant {
- require(_buyAmount > 0, "Must send a positive amount");
- require(_buyAmount <= _maxBuyAmount, "Buy amount exceeds max buy amount");
- bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(msg.sender, _maxBuyAmount))));
- require(MerkleProof.verify(_proof, merkleRoot, leaf), "Invalid proof");
- uint256 price = salePaymentPrice;
- require(price > 0, "Payment token not accepted for this sale");
- uint256 cost = (price / 10 ** 18) * _buyAmount; // Assuming salePaymentPrice is in wei
- require(
- IERC20(paymentToken).allowance(msg.sender, address(this)) >= cost,
- "Not enough allowance for payment token"
- );
- tokensSold += _buyAmount;
- if (claimableTokensAmount[msg.sender] == 0) {
- accountsCount++; // Increment accounts count only if this is the first contribution
- }
- claimableTokensAmount[msg.sender] += _buyAmount; // update user's claimable tokens
- console.log("Claimable tokens amount for user:", claimableTokensAmount[msg.sender]);
- require(claimableTokensAmount[msg.sender] <= _maxBuyAmount, "Buy amount exceeds max buy amount");
- IERC20(paymentToken).transferFrom(msg.sender, address(this), cost); // 从用户账户转移支付代币
- emit Contributed(msg.sender, _buyAmount, paymentToken);
- }
- function claimTokens() public validClaimTime {
- require(block.timestamp > endTime, "Sale is still active");
- uint256 amount = claimableTokensAmount[msg.sender];
- require(amount > 0, "No tokens to claim");
- if (tokensSold <= totalTokens) {
- IERC20(token).transfer(msg.sender, amount); // 分发销售代币
- emit TokensClaimed(msg.sender, amount);
- } else {
- uint256 boughtAmountAvg = totalTokens / accountsCount; // 平均每个账户购买的代币数量
- uint256 refundTokenAmount = amount - boughtAmountAvg; // 计算退款金额
- uint256 refundAmount = (refundAmount / 10 ** 18) * salePaymentPrice;
- if (paymentToken == address(0)) {
- payable(msg.sender).transfer(refundAmount); // 退款ETH
- } else {
- IERC20(paymentToken).transfer(msg.sender, refundAmount); // 退款ERC20代币
- }
- IERC20(token).transfer(msg.sender, boughtAmountAvg); // 分发销售代币
- }
- }
- }
|