ETH Price: $2,510.45 (-19.41%)
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Token Holdings

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Claim Rewards205250332024-08-14 6:19:23172 days ago1723616363IN
Kawakami: NFT Staking Pool
0 ETH0.000276493.80239627
Transfer From201723172024-06-26 0:35:47222 days ago1719362147IN
Kawakami: NFT Staking Pool
0 ETH0.00007983.1129653
Transfer From201722352024-06-26 0:19:23222 days ago1719361163IN
Kawakami: NFT Staking Pool
0 ETH0.000086023.35578789
Transfer From201722252024-06-26 0:17:23222 days ago1719361043IN
Kawakami: NFT Staking Pool
0 ETH0.000097713.81156067
Transfer From201722092024-06-26 0:14:11222 days ago1719360851IN
Kawakami: NFT Staking Pool
0 ETH0.000104074.05953672
Set Approval For...201296892024-06-20 1:32:59228 days ago1718847179IN
Kawakami: NFT Staking Pool
0 ETH0.000116044.33582071
Set Approval For...201296892024-06-20 1:32:59228 days ago1718847179IN
Kawakami: NFT Staking Pool
0 ETH0.000116044.33582071
Set Approval For...201296892024-06-20 1:32:59228 days ago1718847179IN
Kawakami: NFT Staking Pool
0 ETH0.000116044.33582071
Claim Rewards191269052024-01-31 13:57:11368 days ago1706709431IN
Kawakami: NFT Staking Pool
0 ETH0.0034985328.21009934
Claim Rewards191268632024-01-31 13:48:47368 days ago1706708927IN
Kawakami: NFT Staking Pool
0 ETH0.0019861527.31349021
Claim Rewards190562732024-01-21 15:56:11378 days ago1705852571IN
Kawakami: NFT Staking Pool
0 ETH0.0011216315.42463608
Claim Rewards190511032024-01-20 22:37:47379 days ago1705790267IN
Kawakami: NFT Staking Pool
0 ETH0.0013259912.402123
Claim Rewards190494452024-01-20 17:03:23379 days ago1705770203IN
Kawakami: NFT Staking Pool
0 ETH0.0017670519.67399757
Claim Rewards190482692024-01-20 13:07:11379 days ago1705756031IN
Kawakami: NFT Staking Pool
0 ETH0.0010832414.89676602
Claim Rewards190467142024-01-20 7:54:59379 days ago1705737299IN
Kawakami: NFT Staking Pool
0 ETH0.00141915.79883364
Claim Rewards190327522024-01-18 9:07:11381 days ago1705568831IN
Kawakami: NFT Staking Pool
0 ETH0.0036124133.78706159
Claim Rewards190287022024-01-17 19:32:11382 days ago1705519931IN
Kawakami: NFT Staking Pool
0 ETH0.0034243147.09096214
Claim Rewards190283082024-01-17 18:12:47382 days ago1705515167IN
Kawakami: NFT Staking Pool
0 ETH0.0044002641.15585759
Claim Rewards190275122024-01-17 15:30:47382 days ago1705505447IN
Kawakami: NFT Staking Pool
0 ETH0.0044202949.2145041
Claim Rewards189936222024-01-12 21:53:11387 days ago1705096391IN
Kawakami: NFT Staking Pool
0 ETH0.0021972730.21677256
Claim Rewards184038412023-10-22 5:45:35469 days ago1697953535IN
Kawakami: NFT Staking Pool
0 ETH0.000635327.07358102
Claim Rewards183380922023-10-13 1:05:47479 days ago1697159147IN
Kawakami: NFT Staking Pool
0 ETH0.000461346.34444217
Batch Withdraw183306952023-10-12 0:14:11480 days ago1697069651IN
Kawakami: NFT Staking Pool
0 ETH0.023838095.57623692
Withdraw182330702023-09-28 8:33:23493 days ago1695890003IN
Kawakami: NFT Staking Pool
0 ETH0.000994279.85961678
Withdraw182330702023-09-28 8:33:23493 days ago1695890003IN
Kawakami: NFT Staking Pool
0 ETH0.001053529.85961678
View all transactions

Latest 1 internal transaction

Advanced mode:
Parent Transaction Hash Block
From
To
155848422022-09-21 22:52:59865 days ago1663800779  Contract Creation0 ETH
Loading...
Loading

Minimal Proxy Contract for 0x463e53cecad3ec669515a8b712672eb417be8e82

Contract Name:
NFTStaking

Compiler Version
v0.8.14+commit.80d49f37

Optimization Enabled:
No with 200 runs

Other Settings:
default evmVersion, MIT license

Contract Source Code (Solidity)

Decompile Bytecode Similar Contracts
/**
 *Submitted for verification at Etherscan.io on 2022-09-21
*/

//SPDX-License-Identifier: MIT
pragma solidity 0.8.14;

/**
 * @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for
 * deploying minimal proxy contracts, also known as "clones".
 *
 */
contract Cloneable {

    /**
        @dev Deploys and returns the address of a clone of address(this
        Created by DeFi Mark To Allow Clone Contract To Easily Create Clones Of Itself
        Without redundancy
     */
    function clone() external returns(address) {
        return _clone(address(this));
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create opcode, which should never revert.
     */
    function _clone(address implementation) internal returns (address instance) {
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
            mstore(add(ptr, 0x14), shl(0x60, implementation))
            mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
            instance := create(0, ptr, 0x37)
        }
        require(instance != address(0), "ERC1167: create failed");
    }
}

/**
 * @title Owner
 * @dev Set & change owner
 */
contract Ownable {

    address private owner;
    
    // event for EVM logging
    event OwnerSet(address indexed oldOwner, address indexed newOwner);
    
    // modifier to check if caller is owner
    modifier onlyOwner() {
        // If the first argument of 'require' evaluates to 'false', execution terminates and all
        // changes to the state and to Ether balances are reverted.
        // This used to consume all gas in old EVM versions, but not anymore.
        // It is often a good idea to use 'require' to check if functions are called correctly.
        // As a second argument, you can also provide an explanation about what went wrong.
        require(msg.sender == owner, "Caller is not owner");
        _;
    }
    
    /**
     * @dev Set contract deployer as owner
     */
    constructor() {
        owner = msg.sender; // 'msg.sender' is sender of current call, contract deployer for a constructor
        emit OwnerSet(address(0), owner);
    }

    /**
     * @dev Change owner
     * @param newOwner address of new owner
     */
    function changeOwner(address newOwner) public onlyOwner {
        emit OwnerSet(owner, newOwner);
        owner = newOwner;
    }

    /**
     * @dev Return owner address 
     * @return address of owner
     */
    function getOwner() external view returns (address) {
        return owner;
    }
}

interface IERC20 {

    function totalSupply() external view returns (uint256);
    
    function symbol() external view returns(string memory);
    
    function name() external view returns(string memory);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);
    
    /**
     * @dev Returns the number of decimal places
     */
    function decimals() external view returns (uint8);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

abstract contract ReentrancyGuard {
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;
    uint256 private _status;
    constructor () {
        _status = _NOT_ENTERED;
    }

    modifier nonReentrant() {
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
        _status = _ENTERED;
        _;
        _status = _NOT_ENTERED;
    }
}

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the caller.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool _approved) external;

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes calldata data
    ) external;
}

interface IERC721Metadata {
    function tokenURI(uint256 tokenId) external view returns (string memory);
}

contract NFTStakingData is ReentrancyGuard {

    uint256 internal constant PRECISION = 10**18;

    address public NFT;
    address public rewardToken;
    uint256 public lockTime;

    address public lockTimeSetter;

    uint256 public dividendsPerNFT;
    uint256 public totalDividends;
    uint256 public totalStaked;

    string public name;
    string public symbol;

    struct UserInfo {
        uint256[] tokenIds;
        uint256 balance;
        uint256 totalExcluded;
        uint256 totalRewardsClaimed;
    }

    struct StakedTokenId {
        uint256 index;      // index in user token id array
        uint256 timeLocked; // time the id was locked
        address owner;
    }

    mapping ( address => UserInfo ) public userInfo;
    mapping ( uint256 => StakedTokenId ) public tokenInfo;
}

contract NFTStaking is NFTStakingData, Cloneable, IERC721, IERC721Metadata {

    function __init__(
        address NFT_,
        address rewardToken_,
        uint256 lockTime_,
        string calldata name_,
        string calldata symbol_,
        address lockTimeSetter_
    ) external {
        require(
            NFT_ != address(0) &&
            NFT == address(0),
            'Invalid Init'
        );

        NFT = NFT_;
        rewardToken = rewardToken_;
        lockTime = lockTime_;
        name = name_;
        symbol = symbol_;
        lockTimeSetter = lockTimeSetter_;
    }

    function setLockTime(uint256 newLockTime) external nonReentrant {
        require(
            msg.sender == lockTimeSetter,
            'Only Setter Can Call'
        );
        require(
            newLockTime <= 10**7,
            'Lock Time Too Long'
        );
        lockTime = newLockTime;
    }

    function setLockTimeSetter(address newSetter) external nonReentrant {
        require(
            msg.sender == lockTimeSetter,
            'Only Setter Can Call'
        );
        lockTimeSetter = newSetter;
    }

    function stake(uint256 tokenId) external nonReentrant {
        _stake(tokenId);
    }

    function batchStake(uint256[] calldata tokenIds) external nonReentrant {
        _batchStake(tokenIds);
    }

    function withdraw(uint256 tokenId) external nonReentrant {
        _withdraw(tokenId);
    }

    function batchWithdraw(uint256[] calldata tokenIds) external nonReentrant {
        _batchWithdraw(tokenIds);
    }

    function claimRewards() external nonReentrant {
        _claimRewards(msg.sender);
    }

    function _stake(uint256 tokenId) internal {

        // ensure message sender is owner of nft
        require(
            isOwner(tokenId, msg.sender),
            'Sender Not NFT Owner'
        );
        require(
            tokenInfo[tokenId].owner == address(0),
            'Already Staked'
        );

        // claim rewards if applicable
        _claimRewards(msg.sender);    

        // send nft to self
        IERC721(NFT).transferFrom(msg.sender, address(this), tokenId);

        // ensure nft is now owned by `this`
        require(
            isOwner(tokenId, address(this)),
            'NFT Ownership Not Transferred'
        );

        // increment total staked and user balance
        totalStaked++;
        userInfo[msg.sender].balance++;

        // reset total rewards
        userInfo[msg.sender].totalExcluded = getCumulativeDividends(userInfo[msg.sender].balance);
        
        // set current tokenId index to length of user id array
        tokenInfo[tokenId].index = userInfo[msg.sender].tokenIds.length;
        tokenInfo[tokenId].timeLocked = block.number;
        tokenInfo[tokenId].owner = msg.sender;

        // push new token id to user id array
        userInfo[msg.sender].tokenIds.push(tokenId);

        emit Transfer(address(0), msg.sender, tokenId);
    }

    function _batchStake(uint256[] calldata tokenIds) internal {

        // claim rewards if applicable
        _claimRewards(msg.sender);   

        // length of array
        uint256 len = tokenIds.length; 

        for (uint i = 0; i < len;) {
            // ensure message sender is owner of nft
            require(
                isOwner(tokenIds[i], msg.sender),
                'Sender Not NFT Owner'
            );
            require(
                tokenInfo[tokenIds[i]].owner == address(0),
                'Already Staked'
            );

            // send nft to self
            IERC721(NFT).transferFrom(msg.sender, address(this), tokenIds[i]);

            // ensure nft is now owned by `this`
            require(
                isOwner(tokenIds[i], address(this)),
                'NFT Ownership Not Transferred'
            );

            // set current tokenId index to length of user id array
            tokenInfo[tokenIds[i]].index = userInfo[msg.sender].tokenIds.length;
            tokenInfo[tokenIds[i]].timeLocked = block.number;
            tokenInfo[tokenIds[i]].owner = msg.sender;

            // push new token id to user id array
            userInfo[msg.sender].tokenIds.push(tokenIds[i]);

            emit Transfer(address(0), msg.sender, tokenIds[i]);
            unchecked { ++i; }
        }

        // increment total staked and user balance
        totalStaked += len;
        userInfo[msg.sender].balance += len;

        // reset total rewards
        userInfo[msg.sender].totalExcluded = getCumulativeDividends(userInfo[msg.sender].balance);
    }

    function _withdraw(uint256 tokenId) internal {
        require(
            isOwner(tokenId, address(this)),
            'NFT Is Not Staked'
        );
        require(
            tokenInfo[tokenId].owner == msg.sender,
            'Only Owner Can Withdraw'
        );
        require(
            hasStakedNFT(msg.sender, tokenId),
            'User Has Not Staked tokenId'
        );
        require(
            timeUntilUnlock(tokenId) == 0,
            'Token Still Locked'
        );

        // claim pending rewards if any
        _claimRewards(msg.sender);
        
        // decrement balance
        userInfo[msg.sender].balance -= 1;
        totalStaked -= 1;

        // reset total rewards
        userInfo[msg.sender].totalExcluded = getCumulativeDividends(userInfo[msg.sender].balance);

        // remove nft from user array
        _removeNFT(msg.sender, tokenId);
        
        // send nft to caller
        IERC721(NFT).transferFrom(address(this), msg.sender, tokenId);

        emit Transfer(msg.sender, address(0), tokenId);
    }

    function _batchWithdraw(uint256[] calldata tokenIds) internal {

        // claim pending rewards if any
        _claimRewards(msg.sender);

        // length of array
        uint256 len = tokenIds.length;

        // decrement balance
        userInfo[msg.sender].balance -= len;
        totalStaked -= len;

        // reset total rewards
        userInfo[msg.sender].totalExcluded = getCumulativeDividends(userInfo[msg.sender].balance);

        for (uint i = 0; i < len;) {
            
            require(
                isOwner(tokenIds[i], address(this)),
                'NFT Is Not Staked'
            );
            require(
                hasStakedNFT(msg.sender, tokenIds[i]),
                'User Has Not Staked tokenId'
            );
            require(
                timeUntilUnlock(tokenIds[i]) == 0,
                'Token Still Locked'
            );

            // remove nft from user array
            _removeNFT(msg.sender, tokenIds[i]);

            // send nft to caller
            IERC721(NFT).transferFrom(address(this), msg.sender, tokenIds[i]);

            // emit event
            emit Transfer(msg.sender, address(0), tokenIds[i]);

            unchecked { ++i; }
        }
    }

    /**
        Claims Reward For User
     */
    function _claimRewards(address user) internal {

        // return if zero balance
        if (userInfo[user].balance == 0) {
            return;
        }

        // fetch pending rewards
        uint pending = pendingRewards(user);
        uint max = rewardBalanceOf();
        if (pending > max) {
            pending = max;
        }
        
        // reset total rewards
        userInfo[user].totalExcluded = getCumulativeDividends(userInfo[user].balance);

        // return if no rewards
        if (pending == 0) {
            return;
        }

        // incremenet total rewards claimed
        unchecked {
            userInfo[user].totalRewardsClaimed += pending;
        }

        // transfer reward to user
        require(
            IERC20(rewardToken).transfer(
                user,
                pending
            ),
            'Failure Reward Transfer'
        );
    }

    /**
        Pending Token Rewards For `account`
     */
    function pendingRewards(address account) public view returns (uint256) {
        if(userInfo[account].balance == 0){ return 0; }

        uint256 accountTotalDividends = getCumulativeDividends(userInfo[account].balance);
        uint256 accountTotalExcluded = userInfo[account].totalExcluded;

        if(accountTotalDividends <= accountTotalExcluded){ return 0; }

        return accountTotalDividends - accountTotalExcluded;
    }

    /**
        Cumulative Dividends For A Number Of Tokens
     */
    function getCumulativeDividends(uint256 share) internal view returns (uint256) {
        return (share * dividendsPerNFT) / PRECISION;
    }

    function giveRewards(uint256 amount) external {
        
        uint balBefore = rewardBalanceOf();
        IERC20(rewardToken).transferFrom(
            msg.sender,
            address(this),
            amount
        );
        uint balAfter = rewardBalanceOf();
        require(
            balAfter > balBefore,
            'Zero Rewards'
        );

        uint received = balAfter - balBefore;

        totalDividends += received;
        dividendsPerNFT += ( received * PRECISION ) / totalStaked;
    }

    function _removeNFT(address user, uint256 tokenId) internal {
        
        uint lastElement = userInfo[user].tokenIds[userInfo[user].tokenIds.length - 1];
        uint removeIndex = tokenInfo[tokenId].index;

        userInfo[user].tokenIds[removeIndex] = lastElement;
        tokenInfo[lastElement].index = removeIndex;
        userInfo[user].tokenIds.pop();

        delete tokenInfo[tokenId];
    }

    function timeUntilUnlock(uint256 tokenId) public view returns (uint256) {
        uint unlockTime = tokenInfo[tokenId].timeLocked + lockTime;
        return unlockTime <= block.number ? 0 : unlockTime - block.number;
    }

    function isOwner(uint256 tokenId, address user) public view returns (bool) {
        return IERC721(NFT).ownerOf(tokenId) == user;
    }

    function listUserStakedNFTs(address user) public view returns (uint256[] memory) {
        return userInfo[user].tokenIds;
    }

    function fetchBalancePendingAndTotalRewards(address user) public view returns (uint256, uint256, uint256) {
        return (userInfo[user].balance, pendingRewards(user), userInfo[user].totalRewardsClaimed);
    }
    
    function listUserStakedNFTsAndURIs(address user) public view returns (uint256[] memory, string[] memory) {
        
        uint len = userInfo[user].tokenIds.length;
        string[] memory uris = new string[](len);
        for (uint i = 0; i < len;) {
            uris[i] = IERC721Metadata(NFT).tokenURI(userInfo[user].tokenIds[i]);
            unchecked {
                ++i;
            }
        }
        return (userInfo[user].tokenIds, uris);
    }

    function listUserStakedNFTsURIsAndRemainingLockTimes(address user) public view returns (
        uint256[] memory, 
        string[] memory,
        uint256[] memory
    ) {
        
        uint len = userInfo[user].tokenIds.length;
        string[] memory uris = new string[](len);
        uint256[] memory remainingLocks = new uint256[](len);
        for (uint i = 0; i < len;) {
            uris[i] = IERC721Metadata(NFT).tokenURI(userInfo[user].tokenIds[i]);
            remainingLocks[i] = timeUntilUnlock(userInfo[user].tokenIds[i]);
            unchecked {
                ++i;
            }
        }
        return (userInfo[user].tokenIds, uris, remainingLocks);
    }

    function listUserTotalNFTs(address user, uint min, uint max) public view returns (uint256[] memory) {
        
        IERC721 NFT_ = IERC721(NFT);
        uint len = NFT_.balanceOf(user);

        uint256[] memory ids = new uint256[](len);
        uint count = 0;

        for (uint i = min; i < max;) {

            if (NFT_.ownerOf(i) == user) {
                ids[count] = i;
                count++;
            }
            
            unchecked {++i;}
        }
        return (ids);
    }

    function listUserTotalNFTsAndUris(address user, uint min, uint max) public view returns (uint256[] memory, string[] memory) {
        
        IERC721 NFT_ = IERC721(NFT);
        uint len = NFT_.balanceOf(user);

        uint256[] memory ids = new uint256[](len);
        string[] memory uris = new string[](len);
        uint count = 0;

        for (uint i = min; i < max;) {

            if (NFT_.ownerOf(i) == user) {
                ids[count] = i;
                uris[count] = IERC721Metadata(NFT).tokenURI(i);
                count++;
            }
            
            unchecked {++i;}
        }
        return (ids, uris);
    }

    function hasStakedNFT(address user, uint256 tokenId) public view returns (bool) {
        if (userInfo[user].tokenIds.length <= tokenInfo[tokenId].index || tokenInfo[tokenId].owner != user) {
            return false;
        }
        return userInfo[user].tokenIds[tokenInfo[tokenId].index] == tokenId;
    }

    function hasStakedNFTs(address user, uint256[] calldata tokenId) public view returns (bool[] memory) {
        uint len = tokenId.length;
        bool[] memory hasStaked = new bool[](len);
        for (uint i = 0; i < len;) {
            hasStaked[i] = userInfo[user].tokenIds[tokenInfo[tokenId[i]].index] == tokenId[i];
            unchecked {
                ++i;
            }
        }
        return hasStaked;
    }

    function rewardBalanceOf() public view returns (uint256) {
        return IERC20(rewardToken).balanceOf(address(this));
    }

    function totalSupply() public view returns (uint256) {
        return IERC721(NFT).balanceOf(address(this));
    }

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view override returns (uint256 balance) {
        return userInfo[owner].balance;
    }

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view override returns (address owner) {
        return tokenInfo[tokenId].owner;
    }

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(
        address,
        address,
        uint256
    ) external override {

    }

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address,
        address,
        uint256
    ) external override {

    }

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address, uint256) external override {
        emit Approval(address(0), address(0), 0);
        return;
    }

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 a) external view override returns (address operator) {
        return a == uint(uint160(msg.sender)) ? address(0) : msg.sender;
    }

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the caller.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address, bool) external override {
        emit Approval(address(0), address(0), 0);
        return;
    }

    function isApprovedForAll(address a, address b) external view override returns (bool) {
        return a == b && a == NFT;
    }

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(
        address,
        address,
        uint256,
        bytes calldata
    ) external override {

    }

    function tokenURI(uint256 tokenId) external view override returns (string memory) {
        return IERC721Metadata(NFT).tokenURI(tokenId);
    }

}

Contract ABI

[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"NFT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"NFT_","type":"address"},{"internalType":"address","name":"rewardToken_","type":"address"},{"internalType":"uint256","name":"lockTime_","type":"uint256"},{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"},{"internalType":"address","name":"lockTimeSetter_","type":"address"}],"name":"__init__","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"batchStake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"batchWithdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"clone","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"dividendsPerNFT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"fetchBalancePendingAndTotalRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"a","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"operator","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"giveRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"hasStakedNFT","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256[]","name":"tokenId","type":"uint256[]"}],"name":"hasStakedNFTs","outputs":[{"internalType":"bool[]","name":"","type":"bool[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"a","type":"address"},{"internalType":"address","name":"b","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"user","type":"address"}],"name":"isOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"listUserStakedNFTs","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"listUserStakedNFTsAndURIs","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"string[]","name":"","type":"string[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"listUserStakedNFTsURIsAndRemainingLockTimes","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"min","type":"uint256"},{"internalType":"uint256","name":"max","type":"uint256"}],"name":"listUserTotalNFTs","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"min","type":"uint256"},{"internalType":"uint256","name":"max","type":"uint256"}],"name":"listUserTotalNFTsAndUris","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"string[]","name":"","type":"string[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lockTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lockTimeSetter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"owner","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"pendingRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardBalanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newLockTime","type":"uint256"}],"name":"setLockTime","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newSetter","type":"address"}],"name":"setLockTimeSetter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"timeUntilUnlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"tokenInfo","outputs":[{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"uint256","name":"timeLocked","type":"uint256"},{"internalType":"address","name":"owner","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalDividends","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalStaked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"userInfo","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"totalExcluded","type":"uint256"},{"internalType":"uint256","name":"totalRewardsClaimed","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.