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;
}
}
Submitted on: 2025-11-01 20:17:42
Comments
Log in to comment.
No comments yet.