Blockchain / Global / Address

Contract

0x3B7f587E4e18fEFced4C15e89B1B57C4530fEA84

Balance
1.2 SOMI
Transactions sent
1
Type
Contract · 10,676 bytes of code

IntraverseStakeProtocol Verified

Compiler
solc 0.8.24+commit.e11b9ed9
Language
Solidity
Optimization
Yes, 1000 runs
EVM version
paris
License
—
Verified
2026-10-12

contracts/IntraverseStakeProtocol.sol

and 8 more files

Solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract IntraverseStakeProtocol is ERC1155Holder, Ownable, ReentrancyGuard {
    // Errors
    error InvalidAddress();
    error RewardPerDayTooLow();
    error ContractNotWhitelisted();
    error NoStakedTokens();
    error NoRewardsToClaim();
    error TokenNotStaked();
    error TokenAlreadyStaked();
    error InvalidAmount();
    error LockPeriodNotOver();
    error InvalidLockPeriod();
    error InsufficientBalance();
    error WithdrawalFailed();

    // Events
    event Staked(address indexed user, address indexed contractAddress, uint256 tokenId, uint256 amount, uint256 timestamp);
    event Unstaked(address indexed user, address indexed contractAddress, uint256 tokenId, uint256 amount, uint256 timestamp);
    event UnstakedAll(address indexed user, uint256 timestamp);
    event RewardClaimed(address indexed user, uint256 amount, uint256 timestamp);
    event RewardPerDayUpdated(uint256 oldValue, uint256 newValue, uint256 timestamp);
    event ContractAddressAdded(address indexed contractAddress, uint256 timestamp);
    event ContractAddressRemoved(address indexed contractAddress, uint256 timestamp);
    event RewardedTokenIdUpdated(uint256 oldTokenId, uint256 newTokenId, uint256 timestamp);
    event DailyTokenCountUpdated(uint256 day, uint256 count);
    event LockPeriodUpdated(uint256 oldValue, uint256 newValue, uint256 timestamp);

    // Structs
    struct StakeInfo {
        address contractAddress;
        uint256 tokenId;
        uint256 amount;
        uint256 lastClaimTimestamp;
    }

    // Config settings
    uint256 public rewardPerDay;
    uint256 public deploymentTimestamp;
    uint256 public DAY_IN_SECONDS; // configurable for testing
    uint256 public constant MAX_DAYS_TO_PROCESS = 181; // Maximum days to process in one transaction
    mapping(address => bool) public whitelistedContracts;
    
    // The specific token ID that will receive rewards
    uint256 public rewardedTokenId;
    
    // Daily counts of rewardedTokenId NFTs staked
    mapping(uint256 => uint256) public dailyRewardedTokenCount;
    
    // Track the latest day for which token count was updated
    uint256 public lastTokenCountUpdateDay;
    
    // User data
    mapping(address => StakeInfo[]) public userStakes;
    mapping(address => mapping(address => mapping(uint256 => uint256))) public stakedTokenIndex; // user -> contract -> tokenId -> index+1 (0 means not staked)
    mapping(address => uint256) public totalUserReward;
    
    // Track all active stakers for reward updates
    address[] public allStakers;
    mapping(address => uint256) public stakerIndex; // Map staker to index in allStakers array (index+1, 0 means not in array)

    // Lock period settings
    uint256 public lockPeriodDays;
    mapping(address => uint256) public userLockTimestamp;

    constructor(uint256 _rewardPerDay, uint256 _rewardedTokenId, uint256 _dayInSeconds, uint256 _lockPeriodDays) Ownable(msg.sender) {
        if (_lockPeriodDays == 0) revert InvalidLockPeriod();
        rewardPerDay = _rewardPerDay;
        rewardedTokenId = _rewardedTokenId;
        deploymentTimestamp = block.timestamp;
        DAY_IN_SECONDS = _dayInSeconds > 0 ? _dayInSeconds : 86400; // Use custom value or default to 24 hours
        lockPeriodDays = _lockPeriodDays;
    }

    /**
     * @notice Get the current day count since contract deployment
     * @return The current day number (starting from 0)
     */
    function getCurrentDay() public view returns (uint256) {
        return (block.timestamp - deploymentTimestamp) / DAY_IN_SECONDS;
    }

    /**
     * @notice Get the day number for a specific timestamp
     * @param timestamp The timestamp to convert to a day number
     * @return The day number since contract deployment
     */
    function getDayForTimestamp(uint256 timestamp) public view returns (uint256) {
        return (timestamp - deploymentTimestamp) / DAY_IN_SECONDS;
    }

    /**
     * @notice Check if user's lock period is over
     * @param user The address of the user to check
     * @return Whether the lock period is over
     */
    function isLockPeriodOver(address user) public view returns (bool) {
        if (userLockTimestamp[user] == 0) return true;
        return block.timestamp >= userLockTimestamp[user] + (lockPeriodDays * DAY_IN_SECONDS);
    }

    /**
     * @notice Stake ERC1155 tokens in the contract
     * @param contractAddress The address of the ERC1155 contract
     * @param tokenId The ID of the token to stake
     * @param amount The amount of tokens to stake
     */
    function stake(address contractAddress, uint256 tokenId, uint256 amount) external nonReentrant returns (bool done) {
        // Validate parameters
        if (contractAddress == address(0)) revert InvalidAddress();
        if (!whitelistedContracts[contractAddress]) revert ContractNotWhitelisted();
        if (amount == 0) revert InvalidAmount();
        
        address sender = msg.sender;
        
        // Check if the user has approved this contract to transfer their tokens
        require(IERC1155(contractAddress).isApprovedForAll(sender, address(this)), 
                "ERC1155: caller is not owner nor approved");
        
        // Add user to stakers list if not already there
        if (stakerIndex[sender] == 0) {
            allStakers.push(sender);
            stakerIndex[sender] = allStakers.length;
        }
        
        // Check if user already has this token staked
        uint256 existingIndex = stakedTokenIndex[sender][contractAddress][tokenId];
        
        if (existingIndex > 0) {
            // Update existing stake
            uint256 index = existingIndex - 1;
            StakeInfo storage stakeInfo = userStakes[sender][index];
            
            // Update rewards before changing stake amount
            bool rewardsProcessed;
            (rewardsProcessed, ) = _processRewardsWithDailyLimit(sender, index, MAX_DAYS_TO_PROCESS);

            // Make sure all rewards are processed
            if (!rewardsProcessed) {
                // in this case, the user will have to call syncUserRewards()
                // to process the rewards or call the function again until done is true

                // note that in this case, nothing has changed in the contract except the lastClaimTimestamp
                // of user's stakes, which should have been updated anyway at some point
                // allStakers and stakerIndex are not affected (cause user is already in the array)
                return false;
            }
            
            // Update stake amount
            unchecked {
                stakeInfo.amount += amount;
            }
        } else {
            // Create new stake
            StakeInfo memory newStake = StakeInfo({
                contractAddress: contractAddress,
                tokenId: tokenId,
                amount: amount,
                lastClaimTimestamp: block.timestamp
            });
            
            userStakes[sender].push(newStake);
            stakedTokenIndex[sender][contractAddress][tokenId] = userStakes[sender].length;
        }
        
        // Transfer tokens to this contract
        // Need to first get approval from msg.sender
        IERC1155(contractAddress).safeTransferFrom(
            sender,
            address(this),
            tokenId,
            amount,
            ""
        );

        // Set or update lock period timestamp
        userLockTimestamp[sender] = block.timestamp;
        
        // Update daily token count if this is a rewarded token
        if (tokenId == rewardedTokenId) {
            uint256 nextDay;
            unchecked {
                nextDay = getCurrentDay() + 1;
            }
            _updateDailyRewardedTokenCount(nextDay, amount, true);
            emit DailyTokenCountUpdated(nextDay, dailyRewardedTokenCount[nextDay]);
        }
        
        emit Staked(sender, contractAddress, tokenId, amount, block.timestamp);
        return true;
    }

    /**
     * @notice Unstake specific ERC1155 tokens
     * @param contractAddress The address of the ERC1155 contract
     * @param tokenId The ID of the token to unstake
     * @param amount The amount of tokens to unstake
     */
    function unstake(address contractAddress, uint256 tokenId, uint256 amount) external nonReentrant returns (bool done) {
        // Validate parameters
        if (contractAddress == address(0)) revert InvalidAddress();
        if (amount == 0) revert InvalidAmount();
        if (!isLockPeriodOver(msg.sender)) revert LockPeriodNotOver();
        
        // Check if user has this token staked
        uint256 existingIndex = stakedTokenIndex[msg.sender][contractAddress][tokenId];
        if (existingIndex == 0) revert TokenNotStaked();
        
        uint256 index = existingIndex - 1;
        StakeInfo storage stakeInfo = userStakes[msg.sender][index];
        
        // Ensure user has enough tokens staked
        if (amount > stakeInfo.amount) revert InvalidAmount();
        
        // Update rewards before changing stake amount
        bool rewardsProcessed;
        (rewardsProcessed, ) = _processRewardsWithDailyLimit(msg.sender, index, MAX_DAYS_TO_PROCESS);
        if (!rewardsProcessed) {
            // in this case, the user will have to call syncUserRewards()
            // to process the rewards or call the function again until done is true

            // note that in this case, nothing has changed in the contract except the lastClaimTimestamp
            // of user's stakes, which should have been updated anyway at some point
            return false;
        }
        
        // Update daily token count if this is a rewarded token
        if (tokenId == rewardedTokenId) {
            uint256 nextDay = getCurrentDay() + 1;
            _updateDailyRewardedTokenCount(nextDay, amount, false);
            emit DailyTokenCountUpdated(nextDay, dailyRewardedTokenCount[nextDay]);
        }
        
        // Update stake amount or remove if all tokens unstaked
        if (amount == stakeInfo.amount) {
            // Remove stake by swapping with the last one and popping
            uint256 lastIndex = userStakes[msg.sender].length - 1;
            
            if (index != lastIndex) {
                // Swap with the last item
                StakeInfo memory lastStake = userStakes[msg.sender][lastIndex];
                userStakes[msg.sender][index] = lastStake;
                stakedTokenIndex[msg.sender][lastStake.contractAddress][lastStake.tokenId] = index + 1;
            }
            
            // Remove the last item
            userStakes[msg.sender].pop();
            stakedTokenIndex[msg.sender][contractAddress][tokenId] = 0;
            
            // Check if user has no more stakes
            if (userStakes[msg.sender].length == 0) {
                _removeStaker(msg.sender);
                // Clear lock timestamp when all stakes are removed
                userLockTimestamp[msg.sender] = 0;
            }
        } else {
            // Update stake amount
            stakeInfo.amount -= amount;
        }
        
        // Transfer tokens back to user
        IERC1155(contractAddress).safeTransferFrom(
            address(this),
            msg.sender,
            tokenId,
            amount,
            bytes("")
        );
        
        emit Unstaked(msg.sender, contractAddress, tokenId, amount, block.timestamp);
        return true;
    }


    /**
     * @notice Unstake all ERC1155 tokens for a user
     */
    function unstakeAll() external nonReentrant returns (bool done) {
        if (!isLockPeriodOver(msg.sender)) revert LockPeriodNotOver();
        
        StakeInfo[] storage stakes = userStakes[msg.sender];
        
        if (stakes.length == 0) revert NoStakedTokens();

        uint256 daysQuota = MAX_DAYS_TO_PROCESS;
        bool rewardsProcessed;
        
        // Update rewards for all stakes before unstaking
        for (uint256 i = 0; i < stakes.length; i++) {
            (rewardsProcessed, daysQuota) = _processRewardsWithDailyLimit(msg.sender, i, daysQuota);
            if (!rewardsProcessed) {
                // in this case, the user will have to call syncUserRewards()
                // to process the rewards or call the function again until done is true

                // note that in this case, nothing has changed in the contract except the lastClaimTimestamp
                // of user's stakes, which should have been updated anyway at some point
                return false;
            }
        }
        
        
        // Transfer all tokens back to user
        for (uint256 i = 0; i < stakes.length; i++) {
            StakeInfo memory stakeInfo = stakes[i];
            
            // Update daily token count if this is a rewarded token
            if (stakeInfo.tokenId == rewardedTokenId) {
                uint256 nextDay = getCurrentDay() + 1;
                _updateDailyRewardedTokenCount(nextDay, stakeInfo.amount, false);
                emit DailyTokenCountUpdated(nextDay, dailyRewardedTokenCount[nextDay]);
            }
            
            IERC1155(stakeInfo.contractAddress).safeTransferFrom(
                address(this),
                msg.sender,
                stakeInfo.tokenId,
                stakeInfo.amount,
                bytes("")
            );
            
            // Clear staking index mapping
            stakedTokenIndex[msg.sender][stakeInfo.contractAddress][stakeInfo.tokenId] = 0;
        }
        
        // Clear all stakes
        delete userStakes[msg.sender];
        
        // Remove from active stakers list
        _removeStaker(msg.sender);
        
        // Clear lock timestamp
        userLockTimestamp[msg.sender] = 0;
        
        emit UnstakedAll(msg.sender, block.timestamp);
        return true;
    }

    /**
     * @notice Helper function to update daily rewarded token count
     * @param day The day to update
     * @param amount The amount to add or subtract
     * @param isAddition Whether to add (true) or subtract (false) the amount
     */
    function _updateDailyRewardedTokenCount(uint256 day, uint256 amount, bool isAddition) internal {
        // Ensure token counts are current up to the requested day
        _syncTokenCountsUpToDay(day);
        
        // Update the token count for the specified day
        if (isAddition) {
            dailyRewardedTokenCount[day] += amount;
        } else {
            if (amount <= dailyRewardedTokenCount[day]) {
                dailyRewardedTokenCount[day] -= amount;
            } else {
                dailyRewardedTokenCount[day] = 0;
            }
        }
    }

    /**
     * @notice Synchronize token counts up to a specified day
     * @param targetDay The day to sync up to
     */
    function _syncTokenCountsUpToDay(uint256 targetDay) internal {
        // If we already have data for this day or later, no need to sync
        if (targetDay <= lastTokenCountUpdateDay) {
            return;
        }
        
        // If this is the first update ever, just set the last updated day
        // because there is no data to propagate 
        // (this is the first time the contract is being used for staking)
        if (lastTokenCountUpdateDay == 0) {
            lastTokenCountUpdateDay = targetDay;
            return;
        }
        
        // Start from the day after the last update
        uint256 startDay = lastTokenCountUpdateDay + 1;
        
        // Get the token count from the last updated day
        uint256 lastKnownCount = dailyRewardedTokenCount[lastTokenCountUpdateDay];
        
        // Propagate the last known count forward to all days up to the target day
        for (uint256 day = startDay; day <= targetDay; day++) {
            dailyRewardedTokenCount[day] = lastKnownCount;
        }
        
        // Update the last updated day
        lastTokenCountUpdateDay = targetDay;
    }

    /**
     * @notice Get the adjusted token count for a day, handling days with missing data
     * @param day The day to get the token count for
     * @return The token count for the day, using the previous day's count if missing
     */
    function _getAdjustedTokenCountForDay(uint256 day) internal view returns (uint256) {
        // If the requested day is after the last updated day, use the last known count
        if (day > lastTokenCountUpdateDay && lastTokenCountUpdateDay > 0) {
            return dailyRewardedTokenCount[lastTokenCountUpdateDay];
        }
        
        // Return the token count for the requested day (will be 0 if not set)
        return dailyRewardedTokenCount[day];
    }

    /**
     * @notice Claim rewards for all staked tokens
     */
    function claimRewards() external nonReentrant returns (bool done) {
        uint256 daysQuota = MAX_DAYS_TO_PROCESS;
        bool rewardsProcessed;
        StakeInfo[] storage stakes = userStakes[msg.sender];
        
        // Process rewards for any active stakes
        for (uint256 i = 0; i < stakes.length; i++) {
            (rewardsProcessed, daysQuota) = _processRewardsWithDailyLimit(msg.sender, i, daysQuota);
            if (!rewardsProcessed) {
                // in this case, the user will have to call syncUserRewards()
                // to process the rewards or call the function again until done is true

                // note that in this case, nothing has changed in the contract except the lastClaimTimestamp
                // of user's stakes, which should have been updated anyway at some point
                return false;
            }
        }
        
        uint256 rewardAmount = totalUserReward[msg.sender];
        
        // If no rewards to claim, just return success
        if (rewardAmount == 0) return true;
        
        totalUserReward[msg.sender] = 0;
        
        _transferReward(msg.sender, rewardAmount);
        
        emit RewardClaimed(msg.sender, rewardAmount, block.timestamp);
        return true;
    }


    /**
     * @notice Calculate the number of transactions required to fully sync a user's rewards
     * @param user The address of the user
     * @return The number of transactions needed to process all pending rewards
     */
    function requiredSyncTrxs(address user) external view returns (uint256) {
        StakeInfo[] storage stakes = userStakes[user];
        uint256 totalDays = 0;
        for (uint256 i = 0; i < stakes.length; i++) {
            StakeInfo storage stakeInfo = stakes[i];
            if (stakeInfo.tokenId != rewardedTokenId) {
                continue;
            }
            uint256 lastClaimDay = getDayForTimestamp(stakeInfo.lastClaimTimestamp);
            uint256 currentDay = getCurrentDay();
            if (currentDay - lastClaimDay > totalDays) {
                totalDays += currentDay - lastClaimDay;
            }
        }
        return (totalDays + MAX_DAYS_TO_PROCESS - 1) / MAX_DAYS_TO_PROCESS;
    }

    /**
     * @notice Process rewards for a user with a daily processing limit
     * @param user The address of the user
     * @return Whether the sync was fully completed
     */
    function syncUserRewards(address user) external returns (bool) {
        StakeInfo[] storage stakes = userStakes[user];
        uint256 daysQuota = MAX_DAYS_TO_PROCESS;
        bool rewardsProcessed;
        
        for (uint256 i = 0; i < stakes.length; i++) {
            (rewardsProcessed, daysQuota) = _processRewardsWithDailyLimit(user, i, daysQuota);
            if (!rewardsProcessed) {
                return false;
            }
        }
        return true;
    }

    /**
     * @notice Process rewards for a specific stake with a limit on days to process
     * @param user The address of the user
     * @param stakeIndex The index of the stake in the user's stakes array
     * @param daysQuota The maximum number of days to process in this transaction
     * @return done Whether all days were processed
     * @return remainingQuota The remaining days quota that can be used for other stakes
     */
    function _processRewardsWithDailyLimit(address user, uint256 stakeIndex, uint256 daysQuota) internal returns (bool, uint256) {
        StakeInfo storage stakeInfo = userStakes[user][stakeIndex];
        
        if (stakeInfo.tokenId != rewardedTokenId) {
            return (true, daysQuota);
        }

        if (block.timestamp <= stakeInfo.lastClaimTimestamp) {
            return (true, daysQuota);
        }

        uint256 lastClaimDay = getDayForTimestamp(stakeInfo.lastClaimTimestamp);
        uint256 currentDay = getCurrentDay();
        
        // Only rewards from the next day after last claim
        if (currentDay <= lastClaimDay) {
            return (true, daysQuota);
        }
        
        // Calculate reward for the unclaimed day
        uint256 reward = 0;
        bool done = false;
        if (currentDay - lastClaimDay <= daysQuota) {
            done = true;
        }
        
        // For each day, calculate the proportional share of the daily reward
        for (uint256 day = lastClaimDay + 1; day <= currentDay && daysQuota > 0; day++) {
            uint256 totalTokensStakedForDay = _getAdjustedTokenCountForDay(day);
            
            daysQuota --;
            
            // Update last claim timestamp
            stakeInfo.lastClaimTimestamp += DAY_IN_SECONDS;
            
            // Skip day with no staked tokens to avoid division by zero
            if (totalTokensStakedForDay == 0) {
                continue;
            }
            
            // Calculate the proportional share of the daily reward pool
            uint256 dailyReward = (rewardPerDay * stakeInfo.amount) / totalTokensStakedForDay;
            reward += dailyReward;
        }
        
        // Add to user's total reward
        totalUserReward[user] += reward;
        
        return (done, daysQuota);
    }

    /**
     * @notice Transfer reward to a user
     * @dev This is a placeholder for the actual reward transfer mechanism
     * @param user The address of the user to reward
     * @param amount The amount of reward to transfer
     */
    function _transferReward(address user, uint256 amount) internal {
        // Implement the reward transfer mechanism
        // This could be a token transfer, ETH transfer, or other mechanism
        // For now, we'll use a simple ETH transfer
        (bool success, ) = payable(user).call{value: amount}("");
        require(success, "Reward transfer failed");
    }

    /**
     * @notice Get all stakes for a user
     * @param user The address of the user
     * @return An array of StakeInfo structs
     */
    function getUserStakes(address user) external view returns (StakeInfo[] memory) {
        return userStakes[user];
    }

    /**
     * @notice Get the pending rewards for a user without claiming them
     * @param user The address of the user
     * @return The amount of pending rewards
     */
    function getPendingRewards(address user) external view returns (uint256) {
        StakeInfo[] storage stakes = userStakes[user];
        
        uint256 pendingReward = totalUserReward[user];
        uint256 currentDay = getCurrentDay();
        
        // Calculate additional pending rewards for each stake
        for (uint256 i = 0; i < stakes.length; i++) {
            StakeInfo storage stakeInfo = stakes[i];
            
            // Only calculate rewards for the specific token ID that's eligible
            if (stakeInfo.tokenId != rewardedTokenId) {
                continue;
            }
            
            uint256 lastClaimDay = getDayForTimestamp(stakeInfo.lastClaimTimestamp);
            
            // Only calculate rewards from the next day after last claim
            if (currentDay <= lastClaimDay) {
                continue;
            }
            
            // For each day, calculate the proportional share of the daily reward
            for (uint256 day = lastClaimDay + 1; day <= currentDay; day++) {
                uint256 totalTokensStakedForDay = _getAdjustedTokenCountForDay(day);
                
                // Skip day with no staked tokens to avoid division by zero
                if (totalTokensStakedForDay == 0) {
                    continue;
                }
                
                // Calculate the proportional share of the daily reward pool
                uint256 dailyReward = (rewardPerDay * stakeInfo.amount) / totalTokensStakedForDay;
                pendingReward += dailyReward;
            }
        }
        
        return pendingReward;
    }

    /**
     * @notice Update the reward per day
     * @param _rewardPerDay The new reward per day value
     */
    function setRewardPerDay(uint256 _rewardPerDay, uint256 _quota) external onlyOwner returns (bool done) {

        // Update all user stakes with the old reward rate until now
        bool updateCompleted = _updateAllStakesForAllUsers(_quota);
        if (!updateCompleted) {
            return false;
        }

        
        uint256 oldRewardPerDay = rewardPerDay;
        rewardPerDay = _rewardPerDay;
        
        emit RewardPerDayUpdated(oldRewardPerDay, _rewardPerDay, block.timestamp);
        return true;
    }

    /**
     * @notice Add a contract address to the whitelist
     * @param contractAddress The address to add
     */
    function addContractAddress(address contractAddress) external onlyOwner {
        if (contractAddress == address(0)) revert InvalidAddress();
        
        whitelistedContracts[contractAddress] = true;
        emit ContractAddressAdded(contractAddress, block.timestamp);
    }

    /**
     * @notice Remove a contract address from the whitelist
     * @param contractAddress The address to remove
     */
    function removeContractAddress(address contractAddress) external onlyOwner {
        if (contractAddress == address(0)) revert InvalidAddress();
        
        whitelistedContracts[contractAddress] = false;
        emit ContractAddressRemoved(contractAddress, block.timestamp);
    }

    /**
     * @notice Helper function to remove a staker from the allStakers array
     * @param staker The address of the staker to remove
     */
    function _removeStaker(address staker) internal {
        uint256 index = stakerIndex[staker];
        if (index == 0) return; // Not in the array
        
        unchecked {
            index = index - 1;
        }
        
        // If this is not the last element, swap with the last one
        uint256 lastIndex = allStakers.length - 1;
        if (index != lastIndex) {
            address lastStaker = allStakers[lastIndex];
            allStakers[index] = lastStaker;
            unchecked {
                stakerIndex[lastStaker] = index + 1;
            }
        }
        
        // Remove the last element
        allStakers.pop();
        stakerIndex[staker] = 0;
    }

    /**
     * @notice Update rewards for all stakes of all users
     * @dev This is called when reward rate changes to ensure fairness
     */
    function _updateAllStakesForAllUsers(uint256 _quota) internal returns (bool) {
        bool rewardsProcessed;

        // Iterate through all active stakers and update their rewards
        for (uint256 i = 0; i < allStakers.length; i++) {
            address staker = allStakers[i];
            
            // Update all stakes for this staker
            StakeInfo[] storage stakes = userStakes[staker];
            for (uint256 j = 0; j < stakes.length; j++) {
                (rewardsProcessed, _quota) = _processRewardsWithDailyLimit(staker, j, _quota);
                if (!rewardsProcessed) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * @dev Function to receive ETH when msg.data is empty
     */
    receive() external payable {}

    /**
     * @dev Fallback function
     */
    fallback() external payable {}

    /**
     * @notice Withdraw ETH from the contract (owner only)
     * @param amount The amount to withdraw
     */
    function withdraw(uint256 amount) external onlyOwner {
        if (amount > address(this).balance) revert InsufficientBalance();
        (bool success, ) = payable(owner()).call{value: amount}("");
        if (!success) revert WithdrawalFailed();
    }

    /**
     * @notice Get the total amount of rewarded token staked for a specific day
     * @param day The day number to query
     * @return The total amount of rewarded tokens staked on that day
     */
    function getDailyRewardedTokenCount(uint256 day) external view returns (uint256) {
        return _getAdjustedTokenCountForDay(day);
    }

    /**
     * @notice Update the lock period days
     * @param _lockPeriodDays The new lock period in days
     */
    function setLockPeriodDays(uint256 _lockPeriodDays) external onlyOwner {
        if (_lockPeriodDays == 0) revert InvalidLockPeriod();
        uint256 oldLockPeriodDays = lockPeriodDays;
        lockPeriodDays = _lockPeriodDays;
        emit LockPeriodUpdated(oldLockPeriodDays, _lockPeriodDays, block.timestamp);
    }

    /**
     * @notice Get the remaining lock time for a user in seconds
     * @param user The address of the user to check
     * @return Remaining lock time in seconds (0 if lock period is over)
     */
    function getRemainingLockTime(address user) external view returns (uint256) {
        if (userLockTimestamp[user] == 0) return 0;
        uint256 unlockTime = userLockTimestamp[user] + (lockPeriodDays * DAY_IN_SECONDS);
        if (block.timestamp >= unlockTime) return 0;
        return unlockTime - block.timestamp;
    }
}

Other files

  • @openzeppelin/contracts/access/Ownable.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
    
    pragma solidity ^0.8.20;
    
    import {Context} from "../utils/Context.sol";
    
    /**
     * @dev Contract module which provides a basic access control mechanism, where
     * there is an account (an owner) that can be granted exclusive access to
     * specific functions.
     *
     * The initial owner is set to the address provided by the deployer. This can
     * later be changed with {transferOwnership}.
     *
     * This module is used through inheritance. It will make available the modifier
     * `onlyOwner`, which can be applied to your functions to restrict their use to
     * the owner.
     */
    abstract contract Ownable is Context {
        address private _owner;
    
        /**
         * @dev The caller account is not authorized to perform an operation.
         */
        error OwnableUnauthorizedAccount(address account);
    
        /**
         * @dev The owner is not a valid owner account. (eg. `address(0)`)
         */
        error OwnableInvalidOwner(address owner);
    
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    
        /**
         * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
         */
        constructor(address initialOwner) {
            if (initialOwner == address(0)) {
                revert OwnableInvalidOwner(address(0));
            }
            _transferOwnership(initialOwner);
        }
    
        /**
         * @dev Throws if called by any account other than the owner.
         */
        modifier onlyOwner() {
            _checkOwner();
            _;
        }
    
        /**
         * @dev Returns the address of the current owner.
         */
        function owner() public view virtual returns (address) {
            return _owner;
        }
    
        /**
         * @dev Throws if the sender is not the owner.
         */
        function _checkOwner() internal view virtual {
            if (owner() != _msgSender()) {
                revert OwnableUnauthorizedAccount(_msgSender());
            }
        }
    
        /**
         * @dev Leaves the contract without owner. It will not be possible to call
         * `onlyOwner` functions. Can only be called by the current owner.
         *
         * NOTE: Renouncing ownership will leave the contract without an owner,
         * thereby disabling any functionality that is only available to the owner.
         */
        function renounceOwnership() public virtual onlyOwner {
            _transferOwnership(address(0));
        }
    
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`).
         * Can only be called by the current owner.
         */
        function transferOwnership(address newOwner) public virtual onlyOwner {
            if (newOwner == address(0)) {
                revert OwnableInvalidOwner(address(0));
            }
            _transferOwnership(newOwner);
        }
    
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`).
         * Internal function without access restriction.
         */
        function _transferOwnership(address newOwner) internal virtual {
            address oldOwner = _owner;
            _owner = newOwner;
            emit OwnershipTransferred(oldOwner, newOwner);
        }
    }
    
  • @openzeppelin/contracts/token/ERC1155/IERC1155.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/IERC1155.sol)
    
    pragma solidity ^0.8.20;
    
    import {IERC165} from "../../utils/introspection/IERC165.sol";
    
    /**
     * @dev Required interface of an ERC-1155 compliant contract, as defined in the
     * https://eips.ethereum.org/EIPS/eip-1155[ERC].
     */
    interface IERC1155 is IERC165 {
        /**
         * @dev Emitted when `value` amount of tokens of type `id` are transferred from `from` to `to` by `operator`.
         */
        event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
    
        /**
         * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
         * transfers.
         */
        event TransferBatch(
            address indexed operator,
            address indexed from,
            address indexed to,
            uint256[] ids,
            uint256[] values
        );
    
        /**
         * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
         * `approved`.
         */
        event ApprovalForAll(address indexed account, address indexed operator, bool approved);
    
        /**
         * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
         *
         * If an {URI} event was emitted for `id`, the standard
         * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
         * returned by {IERC1155MetadataURI-uri}.
         */
        event URI(string value, uint256 indexed id);
    
        /**
         * @dev Returns the value of tokens of token type `id` owned by `account`.
         */
        function balanceOf(address account, uint256 id) external view returns (uint256);
    
        /**
         * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
         *
         * Requirements:
         *
         * - `accounts` and `ids` must have the same length.
         */
        function balanceOfBatch(
            address[] calldata accounts,
            uint256[] calldata ids
        ) external view returns (uint256[] memory);
    
        /**
         * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
         *
         * Emits an {ApprovalForAll} event.
         *
         * Requirements:
         *
         * - `operator` cannot be the zero address.
         */
        function setApprovalForAll(address operator, bool approved) external;
    
        /**
         * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
         *
         * See {setApprovalForAll}.
         */
        function isApprovedForAll(address account, address operator) external view returns (bool);
    
        /**
         * @dev Transfers a `value` amount of tokens of type `id` from `from` to `to`.
         *
         * WARNING: This function can potentially allow a reentrancy attack when transferring tokens
         * to an untrusted contract, when invoking {onERC1155Received} on the receiver.
         * Ensure to follow the checks-effects-interactions pattern and consider employing
         * reentrancy guards when interacting with untrusted contracts.
         *
         * Emits a {TransferSingle} event.
         *
         * Requirements:
         *
         * - `to` cannot be the zero address.
         * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}.
         * - `from` must have a balance of tokens of type `id` of at least `value` amount.
         * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
         * acceptance magic value.
         */
        function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes calldata data) external;
    
        /**
         * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
         *
         * WARNING: This function can potentially allow a reentrancy attack when transferring tokens
         * to an untrusted contract, when invoking {onERC1155BatchReceived} on the receiver.
         * Ensure to follow the checks-effects-interactions pattern and consider employing
         * reentrancy guards when interacting with untrusted contracts.
         *
         * Emits either a {TransferSingle} or a {TransferBatch} event, depending on the length of the array arguments.
         *
         * Requirements:
         *
         * - `ids` and `values` must have the same length.
         * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
         * acceptance magic value.
         */
        function safeBatchTransferFrom(
            address from,
            address to,
            uint256[] calldata ids,
            uint256[] calldata values,
            bytes calldata data
        ) external;
    }
    
  • @openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/IERC1155Receiver.sol)
    
    pragma solidity ^0.8.20;
    
    import {IERC165} from "../../utils/introspection/IERC165.sol";
    
    /**
     * @dev Interface that must be implemented by smart contracts in order to receive
     * ERC-1155 token transfers.
     */
    interface IERC1155Receiver is IERC165 {
        /**
         * @dev Handles the receipt of a single ERC-1155 token type. This function is
         * called at the end of a `safeTransferFrom` after the balance has been updated.
         *
         * NOTE: To accept the transfer, this must return
         * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
         * (i.e. 0xf23a6e61, or its own function selector).
         *
         * @param operator The address which initiated the transfer (i.e. msg.sender)
         * @param from The address which previously owned the token
         * @param id The ID of the token being transferred
         * @param value The amount of tokens being transferred
         * @param data Additional data with no specified format
         * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
         */
        function onERC1155Received(
            address operator,
            address from,
            uint256 id,
            uint256 value,
            bytes calldata data
        ) external returns (bytes4);
    
        /**
         * @dev Handles the receipt of a multiple ERC-1155 token types. This function
         * is called at the end of a `safeBatchTransferFrom` after the balances have
         * been updated.
         *
         * NOTE: To accept the transfer(s), this must return
         * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
         * (i.e. 0xbc197c81, or its own function selector).
         *
         * @param operator The address which initiated the batch transfer (i.e. msg.sender)
         * @param from The address which previously owned the token
         * @param ids An array containing ids of each token being transferred (order and length must match values array)
         * @param values An array containing amounts of each token being transferred (order and length must match ids array)
         * @param data Additional data with no specified format
         * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
         */
        function onERC1155BatchReceived(
            address operator,
            address from,
            uint256[] calldata ids,
            uint256[] calldata values,
            bytes calldata data
        ) external returns (bytes4);
    }
    
  • @openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/utils/ERC1155Holder.sol)
    
    pragma solidity ^0.8.20;
    
    import {IERC165, ERC165} from "../../../utils/introspection/ERC165.sol";
    import {IERC1155Receiver} from "../IERC1155Receiver.sol";
    
    /**
     * @dev Simple implementation of `IERC1155Receiver` that will allow a contract to hold ERC-1155 tokens.
     *
     * IMPORTANT: When inheriting this contract, you must include a way to use the received tokens, otherwise they will be
     * stuck.
     */
    abstract contract ERC1155Holder is ERC165, IERC1155Receiver {
        /**
         * @dev See {IERC165-supportsInterface}.
         */
        function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
            return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId);
        }
    
        function onERC1155Received(
            address,
            address,
            uint256,
            uint256,
            bytes memory
        ) public virtual override returns (bytes4) {
            return this.onERC1155Received.selector;
        }
    
        function onERC1155BatchReceived(
            address,
            address,
            uint256[] memory,
            uint256[] memory,
            bytes memory
        ) public virtual override returns (bytes4) {
            return this.onERC1155BatchReceived.selector;
        }
    }
    
  • @openzeppelin/contracts/utils/Context.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
    
    pragma solidity ^0.8.20;
    
    /**
     * @dev Provides information about the current execution context, including the
     * sender of the transaction and its data. While these are generally available
     * via msg.sender and msg.data, they should not be accessed in such a direct
     * manner, since when dealing with meta-transactions the account sending and
     * paying for execution may not be the actual sender (as far as an application
     * is concerned).
     *
     * This contract is only required for intermediate, library-like contracts.
     */
    abstract contract Context {
        function _msgSender() internal view virtual returns (address) {
            return msg.sender;
        }
    
        function _msgData() internal view virtual returns (bytes calldata) {
            return msg.data;
        }
    
        function _contextSuffixLength() internal view virtual returns (uint256) {
            return 0;
        }
    }
    
  • @openzeppelin/contracts/utils/ReentrancyGuard.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
    
    pragma solidity ^0.8.20;
    
    /**
     * @dev Contract module that helps prevent reentrant calls to a function.
     *
     * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
     * available, which can be applied to functions to make sure there are no nested
     * (reentrant) calls to them.
     *
     * Note that because there is a single `nonReentrant` guard, functions marked as
     * `nonReentrant` may not call one another. This can be worked around by making
     * those functions `private`, and then adding `external` `nonReentrant` entry
     * points to them.
     *
     * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
     * consider using {ReentrancyGuardTransient} instead.
     *
     * TIP: If you would like to learn more about reentrancy and alternative ways
     * to protect against it, check out our blog post
     * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
     */
    abstract contract ReentrancyGuard {
        // Booleans are more expensive than uint256 or any type that takes up a full
        // word because each write operation emits an extra SLOAD to first read the
        // slot's contents, replace the bits taken up by the boolean, and then write
        // back. This is the compiler's defense against contract upgrades and
        // pointer aliasing, and it cannot be disabled.
    
        // The values being non-zero value makes deployment a bit more expensive,
        // but in exchange the refund on every call to nonReentrant will be lower in
        // amount. Since refunds are capped to a percentage of the total
        // transaction's gas, it is best to keep them low in cases like this one, to
        // increase the likelihood of the full refund coming into effect.
        uint256 private constant NOT_ENTERED = 1;
        uint256 private constant ENTERED = 2;
    
        uint256 private _status;
    
        /**
         * @dev Unauthorized reentrant call.
         */
        error ReentrancyGuardReentrantCall();
    
        constructor() {
            _status = NOT_ENTERED;
        }
    
        /**
         * @dev Prevents a contract from calling itself, directly or indirectly.
         * Calling a `nonReentrant` function from another `nonReentrant`
         * function is not supported. It is possible to prevent this from happening
         * by making the `nonReentrant` function external, and making it call a
         * `private` function that does the actual work.
         */
        modifier nonReentrant() {
            _nonReentrantBefore();
            _;
            _nonReentrantAfter();
        }
    
        function _nonReentrantBefore() private {
            // On the first call to nonReentrant, _status will be NOT_ENTERED
            if (_status == ENTERED) {
                revert ReentrancyGuardReentrantCall();
            }
    
            // Any calls to nonReentrant after this point will fail
            _status = ENTERED;
        }
    
        function _nonReentrantAfter() private {
            // By storing the original value once again, a refund is triggered (see
            // https://eips.ethereum.org/EIPS/eip-2200)
            _status = NOT_ENTERED;
        }
    
        /**
         * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
         * `nonReentrant` function in the call stack.
         */
        function _reentrancyGuardEntered() internal view returns (bool) {
            return _status == ENTERED;
        }
    }
    
  • @openzeppelin/contracts/utils/introspection/ERC165.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol)
    
    pragma solidity ^0.8.20;
    
    import {IERC165} from "./IERC165.sol";
    
    /**
     * @dev Implementation of the {IERC165} interface.
     *
     * Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check
     * for the additional interface id that will be supported. For example:
     *
     * ```solidity
     * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
     *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
     * }
     * ```
     */
    abstract contract ERC165 is IERC165 {
        /**
         * @dev See {IERC165-supportsInterface}.
         */
        function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
            return interfaceId == type(IERC165).interfaceId;
        }
    }
    
  • @openzeppelin/contracts/utils/introspection/IERC165.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)
    
    pragma solidity ^0.8.20;
    
    /**
     * @dev Interface of the ERC-165 standard, as defined in the
     * https://eips.ethereum.org/EIPS/eip-165[ERC].
     *
     * Implementers can declare support of contract interfaces, which can then be
     * queried by others ({ERC165Checker}).
     *
     * For an implementation, see {ERC165}.
     */
    interface IERC165 {
        /**
         * @dev Returns true if this contract implements the interface defined by
         * `interfaceId`. See the corresponding
         * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
         * to learn more about how these ids are created.
         *
         * This function call must use less than 30 000 gas.
         */
        function supportsInterface(bytes4 interfaceId) external view returns (bool);
    }
    
ABI · 68 entries
json
[
  {
    "type": "constructor",
    "inputs": [
      {
        "name": "_rewardPerDay",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "_rewardedTokenId",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "_dayInSeconds",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "_lockPeriodDays",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "name": "ContractNotWhitelisted",
    "type": "error",
    "inputs": []
  },
  {
    "name": "InsufficientBalance",
    "type": "error",
    "inputs": []
  },
  {
    "name": "InvalidAddress",
    "type": "error",
    "inputs": []
  },
  {
    "name": "InvalidAmount",
    "type": "error",
    "inputs": []
  },
  {
    "name": "InvalidLockPeriod",
    "type": "error",
    "inputs": []
  },
  {
    "name": "LockPeriodNotOver",
    "type": "error",
    "inputs": []
  },
  {
    "name": "NoRewardsToClaim",
    "type": "error",
    "inputs": []
  },
  {
    "name": "NoStakedTokens",
    "type": "error",
    "inputs": []
  },
  {
    "name": "OwnableInvalidOwner",
    "type": "error",
    "inputs": [
      {
        "name": "owner",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "name": "OwnableUnauthorizedAccount",
    "type": "error",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ]
  },
  {
    "name": "ReentrancyGuardReentrantCall",
    "type": "error",
    "inputs": []
  },
  {
    "name": "RewardPerDayTooLow",
    "type": "error",
    "inputs": []
  },
  {
    "name": "TokenAlreadyStaked",
    "type": "error",
    "inputs": []
  },
  {
    "name": "TokenNotStaked",
    "type": "error",
    "inputs": []
  },
  {
    "name": "WithdrawalFailed",
    "type": "error",
    "inputs": []
  },
  {
    "name": "ContractAddressAdded",
    "type": "event",
    "inputs": [
      {
        "name": "contractAddress",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "timestamp",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "ContractAddressRemoved",
    "type": "event",
    "inputs": [
      {
        "name": "contractAddress",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "timestamp",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "DailyTokenCountUpdated",
    "type": "event",
    "inputs": [
      {
        "name": "day",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "count",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "LockPeriodUpdated",
    "type": "event",
    "inputs": [
      {
        "name": "oldValue",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "newValue",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "timestamp",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "OwnershipTransferred",
    "type": "event",
    "inputs": [
      {
        "name": "previousOwner",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "newOwner",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "name": "RewardClaimed",
    "type": "event",
    "inputs": [
      {
        "name": "user",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "amount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "timestamp",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "RewardPerDayUpdated",
    "type": "event",
    "inputs": [
      {
        "name": "oldValue",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "newValue",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "timestamp",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "RewardedTokenIdUpdated",
    "type": "event",
    "inputs": [
      {
        "name": "oldTokenId",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "newTokenId",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "timestamp",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "Staked",
    "type": "event",
    "inputs": [
      {
        "name": "user",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "contractAddress",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "tokenId",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "amount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "timestamp",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "Unstaked",
    "type": "event",
    "inputs": [
      {
        "name": "user",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "contractAddress",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "tokenId",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "amount",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "timestamp",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "UnstakedAll",
    "type": "event",
    "inputs": [
      {
        "name": "user",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "timestamp",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "type": "fallback",
    "stateMutability": "payable"
  },
  {
    "name": "DAY_IN_SECONDS",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "MAX_DAYS_TO_PROCESS",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "addContractAddress",
    "type": "function",
    "inputs": [
      {
        "name": "contractAddress",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "allStakers",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "claimRewards",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "done",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "name": "dailyRewardedTokenCount",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "deploymentTimestamp",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "getCurrentDay",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "getDailyRewardedTokenCount",
    "type": "function",
    "inputs": [
      {
        "name": "day",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "getDayForTimestamp",
    "type": "function",
    "inputs": [
      {
        "name": "timestamp",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "getPendingRewards",
    "type": "function",
    "inputs": [
      {
        "name": "user",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "getRemainingLockTime",
    "type": "function",
    "inputs": [
      {
        "name": "user",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "getUserStakes",
    "type": "function",
    "inputs": [
      {
        "name": "user",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple[]",
        "components": [
          {
            "name": "contractAddress",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "tokenId",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "amount",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "lastClaimTimestamp",
            "type": "uint256",
            "internalType": "uint256"
          }
        ],
        "internalType": "struct IntraverseStakeProtocol.StakeInfo[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "isLockPeriodOver",
    "type": "function",
    "inputs": [
      {
        "name": "user",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "lastTokenCountUpdateDay",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "lockPeriodDays",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "onERC1155BatchReceived",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "uint256[]",
        "internalType": "uint256[]"
      },
      {
        "name": "",
        "type": "uint256[]",
        "internalType": "uint256[]"
      },
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes4",
        "internalType": "bytes4"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "name": "onERC1155Received",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes4",
        "internalType": "bytes4"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "name": "owner",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "removeContractAddress",
    "type": "function",
    "inputs": [
      {
        "name": "contractAddress",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "renounceOwnership",
    "type": "function",
    "inputs": [],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "requiredSyncTrxs",
    "type": "function",
    "inputs": [
      {
        "name": "user",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "rewardPerDay",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "rewardedTokenId",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "setLockPeriodDays",
    "type": "function",
    "inputs": [
      {
        "name": "_lockPeriodDays",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "setRewardPerDay",
    "type": "function",
    "inputs": [
      {
        "name": "_rewardPerDay",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "_quota",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "done",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "name": "stake",
    "type": "function",
    "inputs": [
      {
        "name": "contractAddress",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "tokenId",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "amount",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "done",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "name": "stakedTokenIndex",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "stakerIndex",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "supportsInterface",
    "type": "function",
    "inputs": [
      {
        "name": "interfaceId",
        "type": "bytes4",
        "internalType": "bytes4"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "syncUserRewards",
    "type": "function",
    "inputs": [
      {
        "name": "user",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "name": "totalUserReward",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "transferOwnership",
    "type": "function",
    "inputs": [
      {
        "name": "newOwner",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "unstake",
    "type": "function",
    "inputs": [
      {
        "name": "contractAddress",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "tokenId",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "amount",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "done",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "name": "unstakeAll",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "done",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "name": "userLockTimestamp",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "userStakes",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "contractAddress",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "tokenId",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "amount",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "lastClaimTimestamp",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "whitelistedContracts",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "withdraw",
    "type": "function",
    "inputs": [
      {
        "name": "amount",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "type": "receive",
    "stateMutability": "payable"
  }
]