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])];
}
}
}