APE Price: $1.25 (+6.01%)

Contract Diff Checker

Contract Name:
Staking

Contract Source Code:

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

interface IUniswapV3Factory {
    /// @notice Returns the pool address for a given pair of tokens and a fee, or address 0 if it does not exist
    /// @dev tokenA and tokenB may be passed in either token0/token1 or token1/token0 order
    /// @param tokenA The contract address of either token0 or token1
    /// @param tokenB The contract address of the other token
    /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
    /// @return pool The pool address
    function getPool(
        address tokenA,
        address tokenB,
        uint24 fee
    ) external view returns (address pool);
}

interface INonfungiblePositionManager {
    /// @notice Emitted when liquidity is increased for a position NFT
    /// @dev Also emitted when a token is minted
    /// @param tokenId The ID of the token for which liquidity was increased
    /// @param liquidity The amount by which liquidity for the NFT position was increased
    /// @param amount0 The amount of token0 that was paid for the increase in liquidity
    /// @param amount1 The amount of token1 that was paid for the increase in liquidity
    event IncreaseLiquidity(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
    /// @notice Emitted when liquidity is decreased for a position NFT
    /// @param tokenId The ID of the token for which liquidity was decreased
    /// @param liquidity The amount by which liquidity for the NFT position was decreased
    /// @param amount0 The amount of token0 that was accounted for the decrease in liquidity
    /// @param amount1 The amount of token1 that was accounted for the decrease in liquidity
    event DecreaseLiquidity(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
    /// @notice Emitted when tokens are collected for a position NFT
    /// @dev The amounts reported may not be exactly equivalent to the amounts transferred, due to rounding behavior
    /// @param tokenId The ID of the token for which underlying tokens were collected
    /// @param recipient The address of the account that received the collected tokens
    /// @param amount0 The amount of token0 owed to the position that was collected
    /// @param amount1 The amount of token1 owed to the position that was collected
    event Collect(uint256 indexed tokenId, address recipient, uint256 amount0, uint256 amount1);

    /// @notice Returns the position information associated with a given token ID.
    /// @dev Throws if the token ID is not valid.
    /// @param tokenId The ID of the token that represents the position
    /// @return nonce The nonce for permits
    /// @return operator The address that is approved for spending
    /// @return token0 The address of the token0 for a specific pool
    /// @return token1 The address of the token1 for a specific pool
    /// @return fee The fee associated with the pool
    /// @return tickLower The lower end of the tick range for the position
    /// @return tickUpper The higher end of the tick range for the position
    /// @return liquidity The liquidity of the position
    /// @return feeGrowthInside0LastX128 The fee growth of token0 as of the last action on the individual position
    /// @return feeGrowthInside1LastX128 The fee growth of token1 as of the last action on the individual position
    /// @return tokensOwed0 The uncollected amount of token0 owed to the position as of the last computation
    /// @return tokensOwed1 The uncollected amount of token1 owed to the position as of the last computation
    function positions(uint256 tokenId)
        external
        view
        returns (
            uint96 nonce,
            address operator,
            address token0,
            address token1,
            uint24 fee,
            int24 tickLower,
            int24 tickUpper,
            uint128 liquidity,
            uint256 feeGrowthInside0LastX128,
            uint256 feeGrowthInside1LastX128,
            uint128 tokensOwed0,
            uint128 tokensOwed1
        );

    struct MintParams {
        address token0;
        address token1;
        uint24 fee;
        int24 tickLower;
        int24 tickUpper;
        uint256 amount0Desired;
        uint256 amount1Desired;
        uint256 amount0Min;
        uint256 amount1Min;
        address recipient;
        uint256 deadline;
    }

    /// @notice Creates a new position wrapped in a NFT
    /// @dev Call this when the pool does exist and is initialized. Note that if the pool is created but not initialized
    /// a method does not exist, i.e. the pool is assumed to be initialized.
    /// @param params The params necessary to mint a position, encoded as `MintParams` in calldata
    /// @return tokenId The ID of the token that represents the minted position
    /// @return liquidity The amount of liquidity for this position
    /// @return amount0 The amount of token0
    /// @return amount1 The amount of token1
    function mint(MintParams calldata params)
        external
        payable
        returns (
            uint256 tokenId,
            uint128 liquidity,
            uint256 amount0,
            uint256 amount1
        );

    struct IncreaseLiquidityParams {
        uint256 tokenId;
        uint256 amount0Desired;
        uint256 amount1Desired;
        uint256 amount0Min;
        uint256 amount1Min;
        uint256 deadline;
    }

    /// @notice Increases the amount of liquidity in a position, with tokens paid by the `msg.sender`
    /// @param params tokenId The ID of the token for which liquidity is being increased,
    /// amount0Desired The desired amount of token0 to be spent,
    /// amount1Desired The desired amount of token1 to be spent,
    /// amount0Min The minimum amount of token0 to spend, which serves as a slippage check,
    /// amount1Min The minimum amount of token1 to spend, which serves as a slippage check,
    /// deadline The time by which the transaction must be included to effect the change
    /// @return liquidity The new liquidity amount as a result of the increase
    /// @return amount0 The amount of token0 to acheive resulting liquidity
    /// @return amount1 The amount of token1 to acheive resulting liquidity
    function increaseLiquidity(IncreaseLiquidityParams calldata params)
        external
        payable
        returns (
            uint128 liquidity,
            uint256 amount0,
            uint256 amount1
        );

    struct DecreaseLiquidityParams {
        uint256 tokenId;
        uint128 liquidity;
        uint256 amount0Min;
        uint256 amount1Min;
        uint256 deadline;
    }

    /// @notice Decreases the amount of liquidity in a position and accounts it to the position
    /// @param params tokenId The ID of the token for which liquidity is being decreased,
    /// amount The amount by which liquidity will be decreased,
    /// amount0Min The minimum amount of token0 that should be accounted for the burned liquidity,
    /// amount1Min The minimum amount of token1 that should be accounted for the burned liquidity,
    /// deadline The time by which the transaction must be included to effect the change
    /// @return amount0 The amount of token0 accounted to the position's tokens owed
    /// @return amount1 The amount of token1 accounted to the position's tokens owed
    function decreaseLiquidity(DecreaseLiquidityParams calldata params)
        external
        payable
        returns (uint256 amount0, uint256 amount1);

    struct CollectParams {
        uint256 tokenId;
        address recipient;
        uint128 amount0Max;
        uint128 amount1Max;
    }

    /// @notice Collects up to a maximum amount of fees owed to a specific position to the recipient
    /// @param params tokenId The ID of the NFT for which tokens are being collected,
    /// recipient The account that should receive the tokens,
    /// amount0Max The maximum amount of token0 to collect,
    /// amount1Max The maximum amount of token1 to collect
    /// @return amount0 The amount of fees collected in token0
    /// @return amount1 The amount of fees collected in token1
    function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1);

    /// @notice Burns a token ID, which deletes it from the NFT contract. The token must have 0 liquidity and all tokens
    /// must be collected first.
    /// @param tokenId The ID of the token that is being burned
    function burn(uint256 tokenId) external payable;

    function ownerOf(uint256 tokenId) external view returns (address);
}   

interface ISwapRouter {
    struct ExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
        uint160 sqrtPriceLimitX96;
    }

    /// factory v3
    function factory() external view returns (address); 

    /// @notice Swaps `amountIn` of one token for as much as possible of another token
    /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata
    /// @return amountOut The amount of the received token
    function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);

    struct ExactInputParams {
        bytes path;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
    }

    /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path
    /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata
    /// @return amountOut The amount of the received token
    function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);

    struct ExactOutputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 deadline;
        uint256 amountOut;
        uint256 amountInMaximum;
        uint160 sqrtPriceLimitX96;
    }

    /// @notice Swaps as little as possible of one token for `amountOut` of another token
    /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata
    /// @return amountIn The amount of the input token
    function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn);

    struct ExactOutputParams {
        bytes path;
        address recipient;
        uint256 deadline;
        uint256 amountOut;
        uint256 amountInMaximum;
    }

    /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)
    /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata
    /// @return amountIn The amount of the input token
    function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn);

    function multicall(bytes[] calldata data) external returns (bytes[] calldata results);
}

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

import "./IUniswapV3.sol";

/**
 * @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;
    }
}


/**
 * @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.
 *
 * By default, the owner account will be the one that deploys the contract. 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;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing 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 {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _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);
    }
}

library SafeMath {
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;

        return c;
    }
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }

    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }

    function percentageOf(uint a, uint b) internal pure returns (uint256) {
        require(b > 0);
        return a * b / 100;
    }

    function percentageOf10000(uint a, uint b) internal pure returns (uint256) {
        require(b > 0);
        return a * b / 10000;
    }
}

interface IToken {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);
    function name() external pure returns (string memory);
    function symbol() external pure returns (string memory);
    function decimals() external pure returns (uint);
    function mint(address to, uint256 amount) external;
    function burn(uint256 amount) external;
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

interface INft {
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
    function balanceOf(address owner) external view returns (uint256 balance);
    function ownerOf(uint256 tokenId) external view returns (address owner);
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;
    function approve(address to, uint256 tokenId) external;
    function setApprovalForAll(address operator, bool _approved) external;
    function getApproved(uint256 tokenId) external view returns (address operator);
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}


library TransferHelper {

    function safeApprove(
        address token,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('approve(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::safeApprove: approve failed'
        );
    }

    function safeTransfer(
        address token,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('transfer(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::safeTransfer: transfer failed'
        );
    }

    function safeTransferFrom(
        address token,
        address from,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::transferFrom: transferFrom failed'
        );
    }

    function safeTransferETH(address to, uint256 value) internal {
        (bool success, ) = to.call{value: value}(new bytes(0));
        require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
    }

    function deposit(address _weth, uint256 _value) internal {
        (bool success, ) = _weth.call{value: _value}(new bytes(0));
        require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
    }

    function withdraw(address _weth, uint256 _value) internal {
        (bool success, bytes memory data) = _weth.call(abi.encodeWithSelector(0x2e1a7d4d, _value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::withdraw: withdraw failed'
        );
    }

}

abstract contract BaseStake is Ownable {

    struct Pool {
        address erc721;
        uint duration;
        uint reward;
    }

    bytes32[] keys;
    mapping(bytes32 => Pool) values;
    mapping(bytes32 => uint256) indexOf;
    mapping(bytes32 => bool) inserted;
    uint private poolCounter;

    function get(bytes32 key) internal view returns (bytes32, address, uint, uint) {
        Pool memory val = values[key];
        return (key, val.erc721, val.duration, val.reward);
    }

    function getKeyAtIndex(uint256 index) internal view returns (bytes32) {
        return keys[index];
    }

    function size() internal view returns (uint256) {
        return keys.length;
    }

    function push(
        uint counter,
        address erc721,
        uint duration,
        uint reward) private {

        bytes32 key = keccak256(abi.encode(counter, erc721, duration, reward));
        Pool memory val = Pool(erc721, duration, reward);

        if (inserted[key]) {
            values[key] = val;
        } else {
            inserted[key] = true;
            values[key] = val;
            indexOf[key] = keys.length;
            keys.push(key);
        }
        
    }

    function remove(bytes32 key) private {
        if (!inserted[key]) {
            return;
        }

        delete inserted[key];
        delete values[key];

        uint256 index = indexOf[key];
        bytes32 lastKey = keys[keys.length - 1];

        indexOf[lastKey] = index;
        delete indexOf[key];

        keys[index] = lastKey;
        keys.pop();
    }


    function _addPool(address erc721, uint duration, uint reward) internal {
        push(poolCounter, erc721, duration, reward);
        poolCounter += 1;
    }

    function _removePool(bytes32 key) internal {
        remove(key);
    }

    function getPools(bytes32 key) public view virtual returns (bytes32, address, uint, uint)  {
        (bytes32 id, address nft, uint duration, uint reward) = get(key);
        return (id, nft, duration, reward);
    }
    
    function getPools() public view virtual returns (bytes32[] memory, address[] memory, uint[] memory, uint[] memory)  {
        uint poolSize = size();
        bytes32[] memory ids = new bytes32[](poolSize);
        address[] memory erc721 = new address[](poolSize);
        uint[] memory durations = new uint[](poolSize);
        uint[] memory rewards = new uint[](poolSize);

        for (uint256 i = 0; i < size(); i++) {
            bytes32 key = getKeyAtIndex(i);
            (bytes32 id, address nft, uint duration, uint reward) = get(key);
            ids[i] = id;
            erc721[i] = nft; 
            durations[i] = duration;
            rewards[i] = reward;
        }

        return (ids, erc721, durations, rewards);
    }
}



contract Staking is BaseStake {
    using SafeMath for uint;

    struct Vesting {
        bytes32 uid;
        address erc721;
        uint tokenId;
        bytes32 poolId;
        uint duration;
        uint startDate;
        uint endDate;
        uint reward;
    }
    
    event LogStake(address sender, uint createdAt, bytes32 uid, address erc721, uint tokenId, bytes32 poolId, uint duration, uint startDate, uint endDate, uint reward);
    event LogUnstake(address sender, uint createdAt, bytes32 uid, address erc721, uint tokenId, bytes32 poolId, uint duration, uint startDate, uint endDate, uint reward);

    uint constant EXTRA_PERCENT = 10;
    bool public initiated;
    bool public paused;
    address public stakingToken;
    address public stakingNft;
    uint256 private decimal;
    mapping(bytes32 => Vesting) private staker;
    uint private stakerCount;
    
    constructor() BaseStake() {
    }

    modifier whenNotPaused() {
        require(!paused, "paused");
        _;
    }

    function init(address erc20, address erc721, uint256 duration) onlyOwner external  {
        require(!initiated, "already initiated");
        stakingToken = erc20;
        stakingNft = erc721;
        decimal = 10 ** IToken(erc20).decimals();

        _addPool(stakingNft, 10 * duration, 750 * decimal);
        _addPool(stakingNft, 30 * duration, 3500 * decimal);
        _addPool(stakingNft, 90 * duration, 16500 * decimal);
        initiated = true;
    }

    function withdraw(address erc721, uint256 tokenId) external onlyOwner {
        INft(erc721).transferFrom(address(this), owner(), tokenId);
    }

    function collectFees(address positionManager, uint256 tokenId) external onlyOwner {
        INonfungiblePositionManager manager = INonfungiblePositionManager(positionManager);

        (,,address token0,address token1,,,,,,,,) = manager.positions(tokenId);

        INonfungiblePositionManager.CollectParams memory params = INonfungiblePositionManager.CollectParams({
            tokenId: tokenId,
            recipient: address(this),
            amount0Max: type(uint128).max,
            amount1Max: type(uint128).max
        });

        (uint256 amount0, uint256 amount1) = manager.collect(params);
        if (amount0 > 0) {
            IToken(token0).transfer(owner(), amount0);
        }
        if (amount1 > 0) {
            IToken(token1).transfer(owner(), amount1);
        }
    }

    function pause(bool enable) onlyOwner external  {
        paused = enable;
    }

    function addPool(address[] memory erc721, uint256[] memory duration, uint256[] memory reward) onlyOwner external  {
        for (uint256 i = 0; i < erc721.length; i++) {
            _addPool(erc721[i], duration[i], reward[i]);
        }
    }

    function removePool(bytes32[] memory ids) onlyOwner external  {
        for (uint256 i = 0; i < ids.length; i++) {
            _removePool(ids[i]);
        }
    }
    
    function stake(bytes32 poolId, address[] memory erc721, uint256[] memory tokenId, bool[] memory hasExtra) whenNotPaused external {
        require(erc721.length > 0 && erc721.length == tokenId.length && erc721.length == hasExtra.length, "input error");
        require(balance() > 0, "pool empty");
        
        (bytes32 _poolId, address _nft, uint _duration, uint _reward) = get(poolId);

        for (uint256 i = 0; i < erc721.length; i++) {
            bytes32 uid = nftHash(erc721[i], tokenId[i]);
            require(poolId == _poolId && erc721[i] == _nft, "pool not found");
            require(staker[uid].uid == bytes32(0), "already staked");
            require(INft(erc721[i]).ownerOf(tokenId[i]) == msg.sender, "not owner");

            uint reward = _reward;
            if(stakingNft == erc721[i] && hasExtra[i]) {
                uint extra = reward.percentageOf(EXTRA_PERCENT);
                reward += extra;
            }

            Vesting memory item = Vesting(uid, erc721[i], tokenId[i], poolId, _duration, block.timestamp, block.timestamp + _duration, reward);
            staker[uid] = item;
            logStake(item);
            stakerCount += 1;
        }
    }

    function claimAndStake(address[] memory erc721, uint256[] memory tokenId) whenNotPaused external {
        require(erc721.length > 0 && erc721.length == tokenId.length, "input error");
        require(balance() > 0, "pool empty");

        uint totalReward = 0;
        for (uint256 i = 0; i < erc721.length; i++) {
            require(INft(erc721[i]).ownerOf(tokenId[i]) == msg.sender, "not owner");
            bytes32 uid = nftHash(erc721[i], tokenId[i]);
            Vesting memory current = staker[uid];
            require(current.uid != bytes32(0), "not staked");
            require(block.timestamp > current.endDate, "not reached yet");
            
            (bytes32 _poolId, , ,) = get(current.poolId);
            require(current.poolId == _poolId && current.erc721 == erc721[i], "pool not found");
            logUnstake(current);
            totalReward += current.reward;
            
            Vesting memory item = Vesting(uid, erc721[i], tokenId[i], current.poolId, current.duration, block.timestamp, block.timestamp +  current.duration, current.reward);
            staker[uid] = item;
            logStake(item);
        }
        sendReward(stakingToken, msg.sender, totalReward);
    }

    function unstake(address[] memory erc721, uint256[] memory tokenId) external {
        require(erc721.length > 0 && erc721.length == tokenId.length, "input error");

        uint totalReward = 0;
        for (uint256 i = 0; i < erc721.length; i++) {
            require(INft(erc721[i]).ownerOf(tokenId[i]) == msg.sender, "not owner");
            Vesting memory current = staker[nftHash(erc721[i], tokenId[i])];
            require(current.uid != bytes32(0), "not staked");
            require(block.timestamp > current.endDate, "not reached yet");

            logUnstake(current);
            totalReward += current.reward;
            stakerCount -= 1;
            delete staker[current.uid];
        }
        sendReward(stakingToken, msg.sender, totalReward);
    }

    function balance() public view returns (uint256) {
        return IToken(stakingToken).balanceOf(address(this));
    }

    function logStake(Vesting memory data) private {
        emit LogStake(msg.sender, block.timestamp, data.uid, data.erc721, data.tokenId, data.poolId, data.duration, data.startDate, data.endDate, data.reward);
    }

    function logUnstake(Vesting memory data) private {
        emit LogUnstake(msg.sender, block.timestamp, data.uid, data.erc721, data.tokenId, data.poolId, data.duration, data.startDate, data.endDate, data.reward);
    }

    function nftHash(address nft, uint256 tokenId) private pure returns (bytes32) {
        return keccak256(abi.encode(nft, tokenId));
    }

    function sendReward(address erc20, address recipient, uint amount) private returns (uint256) {
        uint _balance = balance();
        if(_balance == 0 || amount == 0) {
            return 0;
        }
        if(_balance >= amount) {
            TransferHelper.safeTransfer(erc20, recipient, amount);
            return amount;
        }

        TransferHelper.safeTransfer(erc20, recipient, _balance);
        return _balance;
    }

    function participants() public view returns (uint256) {
        return stakerCount;
    }

    function vesting(address erc721, uint256 tokenId) public view returns (Vesting memory data) {
        return staker[nftHash(erc721, tokenId)];
    }

    function vestings(address[] calldata erc721, uint256[] calldata tokenId) public view returns (Vesting[] memory data) {
        data = new Vesting[](erc721.length);
        for (uint256 i = 0; i < erc721.length; i++) {
            data[i] = staker[nftHash(erc721[i], tokenId[i])];
        }
    }
}

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

Context size (optional):