FluffiPresaleWithStaking

Description:

Smart contract deployed on Ethereum.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

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 approve(address spender, uint256 amount) external returns (bool);
}

contract FluffiPresaleWithStaking {
    address public owner;
    address public constant FUND_WALLET = 0xFc3381a6AA1d134DDf22f641E97c92C400959910;
    
    IERC20 public token;
    uint256 public tokenPrice; // Initial price per token in wei
    uint256 public presaleStartTime;
    uint256 public presaleEndTime;
    uint256 public totalTokensSold;
    uint256 public totalRaised;
    uint256 public constant APY = 90; // 90% APY

    bool public presaleActive;
    bool public distributionStarted = false;

    // Referral
    mapping(address => address) public referrers;
    mapping(address => uint256) public referralRewards;
    uint256 public constant REFERRAL_PERCENT = 10; // 10% of purchased tokens goes to referrer
    
    mapping(address => uint256) public tokenBalances;
    mapping(address => uint256) public stakedBalances;
    mapping(address => uint256) public stakingStartTime;
    mapping(address => uint256) public lastRewardCalculationTime;

    event TokensPurchased(address indexed buyer, uint256 amount, uint256 cost);
    event TokensStaked(address indexed staker, uint256 amount);
    event TokensUnstaked(address indexed staker, uint256 amount);
    event RewardsClaimed(address indexed staker, uint256 amount);
    event DistributionStarted();
    event TokensDistributed(address indexed recipient, uint256 amount);
    event ReferralRewardClaimed(address indexed referrer, uint256 amount);

    modifier onlyOwner() {
        require(msg.sender == owner, "Only owner can call this function");
        _;
    }

    modifier presaleIsActive() {
        require(presaleActive, "Presale is not active");
        require(block.timestamp >= presaleStartTime && block.timestamp <= presaleEndTime, "Presale not in progress");
        _;
    }

    modifier distributionHasStarted() {
        require(distributionStarted, "Distribution has not started yet");
        _;
    }

    constructor(address _tokenAddress, uint256 _tokenPrice, uint256 _presaleDurationHours) {
        owner = msg.sender;
        token = IERC20(_tokenAddress);
        tokenPrice = _tokenPrice;
        presaleStartTime = block.timestamp;
        presaleEndTime = presaleStartTime + (_presaleDurationHours * 1 hours);
        presaleActive = true;
    }

    // --- Buy tokens with optional referral ---
    function buyTokens(address referrer) external payable presaleIsActive {
        require(msg.value > 0, "Must send some ETH");

        uint256 currentPrice = getCurrentTokenPrice();
        uint256 tokensToBuy = msg.value / currentPrice;
        require(tokensToBuy > 0, "Insufficient ETH sent");

        // Handle referral
        if (referrer != address(0) && referrer != msg.sender && referrers[msg.sender] == address(0)) {
            referrers[msg.sender] = referrer;
        }

        if (referrers[msg.sender] != address(0)) {
            uint256 referralAmount = (tokensToBuy * REFERRAL_PERCENT) / 100;
            referralRewards[referrers[msg.sender]] += referralAmount;
        }

        // Update balances
        tokenBalances[msg.sender] += tokensToBuy;
        totalTokensSold += tokensToBuy;
        totalRaised += msg.value;

        // Transfer funds to secure wallet
        payable(FUND_WALLET).transfer(msg.value);

        emit TokensPurchased(msg.sender, tokensToBuy, msg.value);
    }

    // --- Calculate current token price (5% increase every 48 hours) ---
    function getCurrentTokenPrice() public view returns (uint256) {
        if (!presaleActive) return tokenPrice;

        uint256 periods = (block.timestamp - presaleStartTime) / (48 hours);
        uint256 price = tokenPrice;
        for (uint256 i = 0; i < periods; i++) {
            price = (price * 105) / 100; // +5%
        }
        return price;
    }

    // --- Claim referral rewards ---
    function claimReferralRewards() external distributionHasStarted {
        uint256 rewards = referralRewards[msg.sender];
        require(rewards > 0, "No referral rewards");
        referralRewards[msg.sender] = 0;
        require(token.transfer(msg.sender, rewards), "Referral transfer failed");
        emit ReferralRewardClaimed(msg.sender, rewards);
    }

    // --- Distribution functions ---
    function startDistribution() external onlyOwner {
        require(!distributionStarted, "Distribution already started");
        require(!presaleActive || block.timestamp > presaleEndTime, "Presale must end first");
        distributionStarted = true;
        presaleActive = false;
        emit DistributionStarted();
    }

    function distributeTokens(address[] calldata recipients) external onlyOwner distributionHasStarted {
        for (uint256 i = 0; i < recipients.length; i++) {
            address recipient = recipients[i];
            uint256 amount = tokenBalances[recipient];
            if (amount > 0) {
                tokenBalances[recipient] = 0;
                require(token.transfer(recipient, amount), "Token transfer failed");
                emit TokensDistributed(recipient, amount);
            }
        }
    }

    // --- Staking functions ---
    function stakeTokens(uint256 amount) external distributionHasStarted {
        require(amount > 0, "Cannot stake 0 tokens");
        require(tokenBalances[msg.sender] >= amount, "Insufficient token balance");

        tokenBalances[msg.sender] -= amount;
        stakedBalances[msg.sender] += amount;

        if (stakingStartTime[msg.sender] == 0) {
            stakingStartTime[msg.sender] = block.timestamp;
        }
        lastRewardCalculationTime[msg.sender] = block.timestamp;

        emit TokensStaked(msg.sender, amount);
    }

    function unstakeTokens(uint256 amount) external distributionHasStarted {
        require(amount > 0, "Cannot unstake 0 tokens");
        require(stakedBalances[msg.sender] >= amount, "Insufficient staked balance");

        _calculateAndDistributeRewards(msg.sender);

        stakedBalances[msg.sender] -= amount;
        tokenBalances[msg.sender] += amount;

        emit TokensUnstaked(msg.sender, amount);
    }

    function claimRewards() external distributionHasStarted {
        require(stakedBalances[msg.sender] > 0, "No staked tokens");
        _calculateAndDistributeRewards(msg.sender);
    }

    function _calculateAndDistributeRewards(address staker) internal {
        uint256 stakedAmount = stakedBalances[staker];
        if (stakedAmount == 0) return;

        uint256 lastCalculation = lastRewardCalculationTime[staker] > 0 ? 
            lastRewardCalculationTime[staker] : stakingStartTime[staker];

        uint256 timePassed = block.timestamp - lastCalculation;

        uint256 rewards = (stakedAmount * APY * timePassed) / (365 days * 100);

        if (rewards > 0) {
            lastRewardCalculationTime[staker] = block.timestamp;
            require(token.transfer(staker, rewards), "Reward transfer failed");
            emit RewardsClaimed(staker, rewards);
        }
    }

    function getUserInfo(address user) external view returns (
        uint256 purchasedBalance,
        uint256 stakedBalance,
        uint256 availableRewards,
        uint256 stakingStart
    ) {
        purchasedBalance = tokenBalances[user];
        stakedBalance = stakedBalances[user];
        stakingStart = stakingStartTime[user];

        if (stakedBalance > 0) {
            uint256 lastCalculation = lastRewardCalculationTime[user] > 0 ? 
                lastRewardCalculationTime[user] : stakingStartTime[user];
            if (lastCalculation > 0) {
                uint256 timePassed = block.timestamp - lastCalculation;
                availableRewards = (stakedBalance * APY * timePassed) / (365 days * 100);
            } else {
                availableRewards = 0;
            }
        } else {
            availableRewards = 0;
        }
    }

    // --- Admin functions ---
    function emergencyStopPresale() external onlyOwner {
        presaleActive = false;
    }

    function withdrawUnsoldTokens() external onlyOwner {
        require(!presaleActive, "Presale must be stopped first");
        uint256 balance = token.balanceOf(address(this));
        require(token.transfer(owner, balance), "Token transfer failed");
    }

    function updateTokenPrice(uint256 newPrice) external onlyOwner {
        require(!presaleActive, "Cannot change price during active presale");
        tokenPrice = newPrice;
    }

    function getPresaleStatus() external view returns (
        bool isActive,
        uint256 startTime,
        uint256 endTime,
        uint256 tokensSold,
        uint256 raised
    ) {
        isActive = presaleActive && block.timestamp >= presaleStartTime && block.timestamp <= presaleEndTime;
        startTime = presaleStartTime;
        endTime = presaleEndTime;
        tokensSold = totalTokensSold;
        raised = totalRaised;
    }

    function getTimeUntilPresaleEnd() external view returns (uint256) {
        if (block.timestamp >= presaleEndTime) return 0;
        return presaleEndTime - block.timestamp;
    }
}

Tags:
addr:0x50db663330bcc1933f55a15cbc66348a4c4289a8|verified:true|block:23706496|tx:0x7842b5fa0eaea67e176a881f9c10524c4b1cf6efdde3e2c103f4b44b73931ab4|first_check:1762024661

Submitted on: 2025-11-01 20:17:42

Comments

Log in to comment.

No comments yet.