S Price: $0.717445 (+6.61%)

Contract Diff Checker

Contract Name:
SonicSuperRain

Contract Source Code:

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

// Interface definitions
interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
}

interface IUniswapRouter {
    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external;
    
    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external;
}

/**
 * @title SuperRain
 * @dev ERC20 token with reflection rewards in native currency
 */
contract SonicSuperRain {
    // Token setup
    string private _name = "SonicSuperRain";
    string private _symbol = "SSR";
    uint8 private constant _decimals = 18;
    uint256 private _totalSupply;
    
    // Reward token and router addresses
    address public REWARD_TOKEN;
    address constant private ROUTER = 0x95a7e403d7cF20F675fF9273D66e94d35ba49fA3; // ROUTER
    address public LP_PAIR;
    
    // Ownership
    address private _owner;
    
    // Balances and allowances
    mapping(address => uint256) private _balances;
    mapping(address => mapping(address => uint256)) private _allowances;
    
    // Fee configuration
    uint256 public taxPercentage = 5; // 5% default tax
    uint256 public swapThreshold;
    
    // Reward distribution
    uint256 public distributionInterval = 300; // 5 minutes (in seconds)
    uint256 public batchSize = 50;
    uint256 public lastProcessedTime;
    uint256 public currentIndex;
    
    // Holder management
    address[] private holders;
    mapping(address => uint256) public holderIndexes;
    
    struct HolderInfo {
        uint256 amount;
        uint256 pendingRewards;
        uint256 totalExcluded;
    }
    
    mapping(address => HolderInfo) public holderInfo;
    
    // Rewards accounting - using higher precision for calculations
    uint256 public totalShares;
    uint256 public totalDividends;
    uint256 public dividendsPerShare;
    uint256 private constant PRECISION_FACTOR = 1e18;
    
    // Special addresses
    mapping(address => bool) public excluded;
    mapping(address => bool) public exempt;
    
    // Control flags
    bool private inSwap;
    bool public tradingEnabled;
    
    // Events
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
    event SwapAndDistribute(uint256 tokenAmount, uint256 rewardAmount);
    event Excluded(address indexed account, bool status);
    event TaxUpdated(uint256 newTaxPercentage);
    event IntervalUpdated(uint256 newDistributionInterval);
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    event RewardsProcessed(uint256 holderCount);
    event RewardSent(address indexed holder, uint256 amount);
    event SharesUpdated(address indexed holder, uint256 amount);
    event TotalSharesUpdated(uint256 newValue);
    
    // Modifiers
    modifier lockTheSwap {
        inSwap = true;
        _;
        inSwap = false;
    }
    
    modifier onlyOwner() {
        require(msg.sender == _owner, "Caller is not the owner");
        _;
    }
    
    // Constructor
    constructor() {
        _owner = msg.sender;
        
        // Initial supply
        _totalSupply = 100000000 * 10**_decimals;
        _balances[msg.sender] = _totalSupply;
        
        // Set default parameters
        swapThreshold = _totalSupply * 5 / 10000; // 0.05%
        
        // Set initial reward token address
        REWARD_TOKEN = 0x039e2fB66102314Ce7b64Ce5Ce3E5183bc94aD38; // WRAPPED_SONIC
        
        // Add owner to holders array
        _addHolder(_owner, _balances[_owner]);
        
        // Approve router for future swaps
        _approve(address(this), ROUTER, _totalSupply);
        
        emit Transfer(address(0), msg.sender, _totalSupply);
    }
    
    // External functions
    
    function setRewardToken(address newRewardToken) external onlyOwner {
        require(newRewardToken != address(0), "Reward token cannot be zero address");
        REWARD_TOKEN = newRewardToken;
    }
    
    function setLPPair(address newPair) external onlyOwner {
        require(newPair != address(0), "LP pair cannot be zero address");
        LP_PAIR = newPair;
    }
    
    function setTaxPercentage(uint256 newTaxPercentage) external onlyOwner {
        taxPercentage = newTaxPercentage;
        emit TaxUpdated(newTaxPercentage);
    }
    
    function setDistributionInterval(uint256 newInterval) external onlyOwner {
        require(newInterval >= 60, "Interval must be at least 60 seconds");
        distributionInterval = newInterval;
        emit IntervalUpdated(newInterval);
    }
    
    function setBatchSize(uint256 newBatchSize) external onlyOwner {
        require(newBatchSize >= 5 && newBatchSize <= 100, "Batch size must be between 5 and 100");
        batchSize = newBatchSize;
    }
    
    function setSwapThreshold(uint256 newThreshold) external onlyOwner {
        require(newThreshold > 0, "Threshold must be greater than 0");
        swapThreshold = newThreshold;
    }
    
    function setExcluded(address account, bool status) external onlyOwner {
        if (excluded[account] != status) {
            excluded[account] = status;
            if (status) {
                // If excluding an address, remove it from holders
                if (holderInfo[account].amount > 0) {
                    _removeHolder(account);
                }
            } else {
                // If including an address, add it to holders
                _setShare(account, balanceOf(account));
            }
        }
        emit Excluded(account, status);
    }
    
    function setExempt(address account, bool status) external onlyOwner {
        exempt[account] = status;
    }
    
    function enableTrading() external onlyOwner {
        require(!tradingEnabled, "Trading already enabled");
        tradingEnabled = true;
    }
    
    function transferOwnership(address newOwner) external onlyOwner {
        require(newOwner != address(0), "New owner cannot be zero address");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
    
    /**
     * @dev Manually swap a specified amount of tokens for rewards
     * @param amount Amount of tokens to swap (0 for custom calculation based on contract balance)
     * @return swapped Amount of tokens that were actually swapped
     * @return received Amount of reward tokens received from the swap
     */
    function manualSwap(uint256 amount) external onlyOwner returns (uint256 swapped, uint256 received) {
        uint256 contractBalance = balanceOf(address(this));
        require(contractBalance > 0, "No tokens to swap");
        
        // Determine swap amount based on parameters and contract balance
        uint256 swapAmount;
        if (amount > 0 && amount <= contractBalance) {
            // Use specified amount
            swapAmount = amount;
        } else if (swapThreshold > 0 && contractBalance >= swapThreshold) {
            // Use threshold amount
            swapAmount = swapThreshold;
        } else {
            // Use entire balance
            swapAmount = contractBalance;
        }
        
        // Execute the swap and track results
        uint256 receivedAmount = _swapTokensForRewards(swapAmount);
        
        return (swapAmount, receivedAmount);
    }
    
    /**
     * @dev Manually distribute rewards to holders - optimized for cron job
     * @param batchSize Number of holders to process (0 for default batchSize)
     * @return processed Number of holders processed
     * @return currentIdx Current index position after processing
     */
    function processRewards(uint256 batchSize) external returns (uint256 processed, uint256 currentIdx) {
        require(msg.sender == _owner, "Only owner can process rewards");
        
        // No holders to process
        if (holders.length == 0) return (0, 0);
        
        // Use specified batch size or default
        uint256 batchToProcess = batchSize > 0 ? batchSize : this.batchSize();
        
        // Cap at the number of holders
        if (currentIndex + batchToProcess > holders.length) {
            batchToProcess = holders.length - currentIndex;
        }
        
        if (batchToProcess == 0) {
            // Reset index and update timestamp if we've processed everyone
            currentIndex = 0;
            lastProcessedTime = block.timestamp;
            return (0, 0);
        }
        
        uint256 startIndex = currentIndex;
        uint256 endIndex = currentIndex + batchToProcess;
        
        // Process this batch of holders
        for (uint256 i = startIndex; i < endIndex; i++) {
            if (i < holders.length) {
                _distributeRewards(holders[i]);
            }
        }
        
        // Update current index
        currentIndex = endIndex;
        
        // If we've processed everyone, reset index and update timestamp
        if (currentIndex >= holders.length) {
            currentIndex = 0;
            lastProcessedTime = block.timestamp;
            emit RewardsProcessed(holders.length);
        }
        
        return (endIndex - startIndex, currentIndex);
    }
    
    /**
     * @dev Process rewards for ALL holders in one transaction
     * @return processed Number of holders processed
     */
    function processAllRewards() external onlyOwner returns (uint256 processed) {
        // No holders to process
        if (holders.length == 0) return 0;
        
        // Process all holders
        for (uint256 i = 0; i < holders.length; i++) {
            _distributeRewards(holders[i]);
        }
        
        // Reset index and update timestamp
        currentIndex = 0;
        lastProcessedTime = block.timestamp;
        emit RewardsProcessed(holders.length);
        
        return holders.length;
    }
    
    /**
     * @dev Fix totalShares calculation and recalculate dividends
     */
    function recalculateShares() external onlyOwner {
        // Reset totalShares
        totalShares = 0;
        
        // Recalculate from actual holder balances
        for (uint256 i = 0; i < holders.length; i++) {
            address holder = holders[i];
            uint256 balance = _balances[holder];
            holderInfo[holder].amount = balance;
            totalShares += balance;
        }
        
        // Recalculate dividendsPerShare if we have dividends
        if (totalDividends > 0 && totalShares > 0) {
            dividendsPerShare = totalDividends * PRECISION_FACTOR / totalShares;
        }
        
        emit TotalSharesUpdated(totalShares);
    }
    
    /**
     * @dev Returns the contract's current balance of RAIN tokens available for swapping
     */
    function getSwappableBalance() external view returns (uint256) {
        return balanceOf(address(this));
    }
    
    /**
     * @dev Returns the contract's current balance of native SONIC available for distribution
     */
    function getRewardBalance() external view returns (uint256) {
        return address(this).balance;
    }
    
    /**
     * @dev Get detailed holder info for debugging
     */
    function getHolderInfo(address holder) external view returns (
        bool isHolder,
        uint256 balance,
        uint256 shares,
        uint256 pendingRewards,
        uint256 alreadyPaid
    ) {
        bool isInList = false;
        if (holderIndexes[holder] > 0 || (holders.length > 0 && holders[0] == holder)) {
            isInList = true;
        }
        
        return (
            isInList,
            _balances[holder],
            holderInfo[holder].amount,
            getUnpaidEarnings(holder),
            holderInfo[holder].totalExcluded
        );
    }
    
    /**
     * @dev Get detailed contract status for debugging
     */
    function getContractStatus() external view returns (
        uint256 totalHolders,
        uint256 tokenTotalSupply,
        uint256 contractTokenBalance,
        uint256 contractSonicBalance,
        uint256 trackedTotalShares,
        uint256 totalRewards,
        uint256 rewardPerShare
    ) {
        return (
            holders.length,
            _totalSupply,
            balanceOf(address(this)),
            address(this).balance,
            totalShares,
            totalDividends,
            dividendsPerShare
        );
    }
    
    // ERC20 Standard Functions
    
    function name() public view returns (string memory) {
        return _name;
    }
    
    function symbol() public view returns (string memory) {
        return _symbol;
    }
    
    function decimals() public pure returns (uint8) {
        return _decimals;
    }
    
    function totalSupply() public view returns (uint256) {
        return _totalSupply;
    }
    
    function balanceOf(address account) public view returns (uint256) {
        return _balances[account];
    }
    
    function transfer(address recipient, uint256 amount) public returns (bool) {
        _transfer(msg.sender, recipient, amount);
        return true;
    }
    
    function allowance(address owner_, address spender) public view returns (uint256) {
        return _allowances[owner_][spender];
    }
    
    function approve(address spender, uint256 amount) public returns (bool) {
        _approve(msg.sender, spender, amount);
        return true;
    }
    
    function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
        uint256 currentAllowance = _allowances[sender][msg.sender];
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
            _approve(sender, msg.sender, currentAllowance - amount);
        }
        
        _transfer(sender, recipient, amount);
        return true;
    }
    
    // Information functions
    
    function owner() public view returns (address) {
        return _owner;
    }
    
    function getHolderCount() public view returns (uint256) {
        return holders.length;
    }
    
    function isRewardsReady() public view returns (bool) {
        return block.timestamp >= lastProcessedTime + distributionInterval;
    }
    
    function getTimeUntilRewards() public view returns (uint256) {
        if (isRewardsReady()) return 0;
        return lastProcessedTime + distributionInterval - block.timestamp;
    }
    
    function getUnpaidEarnings(address account) public view returns (uint256) {
        if (holderInfo[account].amount == 0) return 0;
        
        uint256 earnedRewards = (dividendsPerShare * holderInfo[account].amount) / PRECISION_FACTOR;
        uint256 alreadyPaid = holderInfo[account].totalExcluded;
        
        if (earnedRewards <= alreadyPaid) return 0;
        return earnedRewards - alreadyPaid;
    }
    
    function getDistributionStatus() public view returns (uint256, uint256, uint256) {
        if (holders.length == 0) return (0, 0, 100);
        
        uint256 percentage = currentIndex * 100 / holders.length;
        return (holders.length, currentIndex, percentage);
    }
    
    // Internal functions
    
    function _transfer(address from, address to, uint256 amount) internal {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");
        require(amount > 0, "Transfer amount must be greater than zero");
        
        // Check if trading is enabled when interacting with LP
        if (!tradingEnabled && (from == LP_PAIR || to == LP_PAIR)) {
            require(from == _owner || to == _owner, "Trading not enabled yet");
        }
        
        // Auto-liquidity and swap
        bool canSwap = balanceOf(address(this)) >= swapThreshold;
        if (canSwap && !inSwap && from != LP_PAIR && from != _owner && to != _owner) {
            uint256 swapAmount = amount > swapThreshold ? swapThreshold : amount;
            _swapTokensForRewards(swapAmount);
        }
        
        // Fee system
        bool takeFee = !exempt[from] && !exempt[to];
        
        uint256 fees = 0;
        if (takeFee) {
            if (from == LP_PAIR || to == LP_PAIR) {
                fees = amount * taxPercentage / 100;
            }
            if (fees > 0) {
                _takeFee(from, address(this), fees);
                amount -= fees;
            }
        }
        
        // Standard transfer
        _balances[from] -= amount;
        _balances[to] += amount;
        emit Transfer(from, to, amount);
        
        // Update holder tracking
        if (!excluded[from] && from != LP_PAIR) {
            _setShare(from, _balances[from]);
        }
        
        if (!excluded[to] && to != LP_PAIR) {
            _setShare(to, _balances[to]);
        }
    }
    
    function _approve(address owner_, address spender, uint256 amount) internal {
        require(owner_ != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");
        
        _allowances[owner_][spender] = amount;
        emit Approval(owner_, spender, amount);
    }
    
    function _takeFee(address from, address to, uint256 amount) internal {
        _balances[to] += amount;
        emit Transfer(from, to, amount);
    }
    
    // Optimized swap function
    function _swapTokensForRewards(uint256 amount) internal lockTheSwap returns (uint256) {
        // Setup swap path
        address[] memory path = new address[](2);
        path[0] = address(this);      // SUPERRAIN token address
        path[1] = REWARD_TOKEN;       // WRAPPED_SONIC address
        
        // Approve router
        _approve(address(this), ROUTER, amount);
        
        // Get initial balance of native SONIC
        uint256 initialBalance = address(this).balance;
        
        try IUniswapRouter(ROUTER).swapExactTokensForETHSupportingFeeOnTransferTokens(
            amount,
            0,
            path,
            address(this),
            block.timestamp + 1200 // 20 minute deadline
        ) {
            // Calculate received native SONIC
            uint256 newBalance = address(this).balance;
            uint256 receivedSonic = newBalance - initialBalance;
            
            if (receivedSonic > 0 && totalShares > 0) {
                // Update dividends per share with higher precision
                totalDividends += receivedSonic;
                dividendsPerShare = totalDividends * PRECISION_FACTOR / totalShares;
                
                emit SwapAndDistribute(amount, receivedSonic);
            }
            
            return receivedSonic;
        } catch {
            // If swap fails, return 0
            return 0;
        }
    }
    
    // Optimized reward distribution function
    function _distributeRewards(address holder) internal {
        // Skip if holder has zero balance or is excluded
        if (holderInfo[holder].amount == 0 || excluded[holder]) return;
        
        uint256 amount = getUnpaidEarnings(holder);
        if (amount > 0) {
            uint256 contractBalance = address(this).balance;
            
            if (amount <= contractBalance) {
                // Update holder's accounting first to prevent reentrancy
                holderInfo[holder].totalExcluded = (dividendsPerShare * holderInfo[holder].amount) / PRECISION_FACTOR;
                holderInfo[holder].pendingRewards = 0;
                
                // Send native SONIC
                (bool success, ) = holder.call{value: amount}("");
                
                // Only emit event if transfer succeeded
                if (success) {
                    emit RewardSent(holder, amount);
                }
            }
        }
    }
    
    // Optimized share tracking function
    function _setShare(address holder, uint256 amount) internal {
        // Only consider meaningful balances
        if (amount < 1 * 10**_decimals) { // Minimum 1 token
            if (amount == 0 && holderInfo[holder].amount > 0) {
                _removeHolder(holder);
            }
            return;
        }
        
        if (holderInfo[holder].amount > 0) {
            // Existing holder - update totalShares correctly
            if (amount == 0) {
                _removeHolder(holder);
            } else {
                uint256 oldAmount = holderInfo[holder].amount;
                
                // Update totalShares safely
                totalShares = totalShares - oldAmount + amount;
                
                // Update holder info
                holderInfo[holder].amount = amount;
                
                emit SharesUpdated(holder, amount);
            }
        } else {
            // New holder
            _addHolder(holder, amount);
        }
    }
    
    function _addHolder(address holder, uint256 amount) internal {
        // Check if holder already exists (safety check)
        if (holderIndexes[holder] > 0 || (holders.length > 0 && holders[0] == holder)) {
            return;
        }
        
        // Add to holders array
        holders.push(holder);
        holderIndexes[holder] = holders.length - 1;
        
        // Set holder info
        holderInfo[holder].amount = amount;
        totalShares += amount;
        
        emit SharesUpdated(holder, amount);
    }
    
    function _removeHolder(address holder) internal {
        // Make sure holder exists
        if (holders.length == 0 || 
            (holderIndexes[holder] == 0 && holders[0] != holder) || 
            holderIndexes[holder] >= holders.length) {
            return;
        }
        
        // Get holder amount before removal
        uint256 holderAmount = holderInfo[holder].amount;
        
        // Handle array removal
        address lastHolder = holders[holders.length - 1];
        uint256 holderIndex = holderIndexes[holder];
        
        // Replace with last element
        holders[holderIndex] = lastHolder;
        holderIndexes[lastHolder] = holderIndex;
        
        // Remove last element
        holders.pop();
        delete holderIndexes[holder];
        
        // Update totalShares
        totalShares -= holderAmount;
        
        // Clear holder info
        delete holderInfo[holder];
        
        emit SharesUpdated(holder, 0);
    }
    
    // Rescue functions
    
    function rescueToken(address token, uint256 amount) external onlyOwner {
        IERC20(token).transfer(_owner, amount);
    }
    
    function withdrawETH() external onlyOwner {
        (bool success,) = _owner.call{value: address(this).balance}("");
        require(success, "ETH withdrawal failed");
    }
    
    // Receive function to accept ETH (native SONIC)
    receive() external payable {}
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):