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 {}
}