// SPDX-License-Identifier: MIT
// File: @openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165Upgradeable {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// File: @openzeppelin/contracts-upgradeable/interfaces/IERC2981Upgradeable.sol
// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC2981.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface for the NFT Royalty Standard.
*
* A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal
* support for royalty payments across all NFT marketplaces and ecosystem participants.
*
* _Available since v4.5._
*/
interface IERC2981Upgradeable is IERC165Upgradeable {
/**
* @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of
* exchange. The royalty amount is denominated and should be paid in that same unit of exchange.
*/
function royaltyInfo(
uint256 tokenId,
uint256 salePrice
) external view returns (address receiver, uint256 royaltyAmount);
}
// File: contracts/IN2M_ERCStorage.sol
pragma solidity ^0.8.20;
interface IN2M_ERCStorage {
/// @notice This event is emitted when a token is minted using an affiliate
/// @param affiliate The affiliate address
event AffiliateSell(address indexed affiliate);
/// @notice Error thrown when trying to mint a token with a given id which is already minted
error TokenAlreadyMinted();
/// @notice Error thrown when input variable differ in length
error InvalidInputSizesDontMatch();
/// @notice Error thrown when input variable differ in length
error InputSizeMismatch();
/// @notice Error thrown when trying to mint a token with a given invalid id
error InvalidTokenId();
/// @notice Error thrown when trying to redeem random tickets with no amount to redeem
error NothingToRedeem();
/// @notice Error thrown when trying to redeem random tickets too soon
error CantRevealYetWaitABitToBeAbleToRedeem();
/// @notice Error thrown when the input amount is not valid
error InvalidAmount();
/// @notice Error thrown when trying to mint a sold out collection or the amount to mint exceeds the remaining supply
error CollectionSoldOut();
/// @notice Error thrown when trying to presale/whitelist mint and the collection current phase is `closed`
error PresaleNotOpen();
/// @notice Error thrown when trying to mint and the collection current phase is not `open`
error PublicSaleNotOpen();
/// @notice Error thrown when trying to mint but the sale has already finished
error SaleFinished();
/// @notice Error thrown when trying to mint more than the allowance to mint
error NotEnoughAmountToMint();
/// @notice Error thrown when sending funds to a free minting
error InvalidMintFeeForFreeMinting();
/// @notice Error thrown when the sent amount is not valid
error InvalidMintFee();
/// @notice Royalty fee can't be higher than 10%
error RoyaltyFeeTooHigh();
/// @notice Invalid input. Total supply must be greater than zero
error TotalSupplyMustBeGreaterThanZero();
/// @notice Can't set BaseURI and Placeholder at the same time
error CantSetBaseURIAndPlaceholderAtTheSameTime();
/// @notice No BaseURI nor Placeholder set
error NoBaseURINorPlaceholderSet();
/// @notice Can't transfer a Soulbound Token (SBT)
error NonTransferrableSoulboundNFT();
/// @notice The input revenue percentages are not valid
error InvalidRevenuePercentage();
/// @notice Can't mint until specified drop date
error WaitUntilDropDate();
/// @notice Trying to use mintPresale method in a collection with a minting type that doesn't support whitelist
error PresaleInvalidMintingType();
/// @notice Metadata is already fixed. Can't change metadata once fixed
error MetadataAlreadyFixed();
/// @notice Invalid collection minting type for the current minting function
error InvalidMintingType();
/// @notice The address exceeded the max per address amount
error MaxPerAddressExceeded();
/// @notice The given signature doesn't match the input values
error SignatureMismatch();
/// @notice Reentrancy Guard protection
error ReentrancyGuard();
/// @notice New Placeholder can't be empty
error NewPlaceholderCantBeEmpty();
/// @notice New BaseURI can't be empty
error NewBaseURICantBeEmpty();
/// @notice Invalid percentage or discount values
error InvalidPercentageOrDiscountValues();
/// @notice Can't lower current percentages
error CantLowerCurrentPercentages();
/// @notice Contract MetadataURI already fixed
error ContractMetadataURIAlreadyFixed();
/// @notice Only owner of N2M can call this function
error OnlyOwnerOrN2M();
/// @notice Only the given affiliate or N2M can call this function
error OnlyAffiliateOrN2M();
/// @notice The signature has expired
error SignatureExpired();
/// @notice Invalid phase can't be set without giving a date, use the proper functions
error InvalidPhaseWithoutDate();
/// @notice Invalid drop date
error InvalidDropDate();
/// @notice Operator address is filtered
error AddressFiltered(address filtered);
struct RandomTicket {
uint256 amount;
uint256 blockNumberToReveal;
}
struct RevenueAddress {
address to;
uint16 percentage;
}
struct AffiliateInformation {
bool enabled;
uint16 affiliatePercentage;
uint16 userDiscount;
}
enum SalePhase {
CLOSED,
PRESALE,
PUBLIC,
DROP_DATE,
DROP_AND_END_DATE
}
enum MintingType {
SEQUENTIAL,
RANDOM,
SPECIFY,
CUSTOM_URI
}
enum OperatorFilterStatus {
ENABLED_NOT_INITIALIZED,
ENABLED_EXISTS,
DISABLED_NOT_INITIALIZED,
DISABLED_EXISTS
}
/// @notice Returns true if the metadata is fixed and immutable. If the metadata hasn't been fixed yet it will return false. Once fixed, it can't be changed by anyone.
function isMetadataFixed() external view returns (bool);
}
// File: contracts/IN2M_ERCBase.sol
pragma solidity ^0.8.20;
interface IN2M_ERCBase is IERC2981Upgradeable, IN2M_ERCStorage {
/// @notice To be called to create the collection. Can only be called once.
function initialize
(
string memory tokenName,
string memory tokenSymbol,
uint256 iMintPrice,
bytes32 baseURICIDHash,
bytes32 placeholderImageCIDHash,
RevenueAddress[] calldata revenueAddresses,
address iErc20PaymentAddress,
uint32 iTotalSupply,
uint16 iRoyaltyFee,
bool soulboundCollection,
MintingType iMintingType
) external payable;
/// @notice A descriptive name for a collection of NFTs in this contract
function name() external view returns (string memory);
/// @notice An abbreviated name for NFTs in this contract
/// @return the collection symbol
function symbol() external view returns (string memory);
/// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
/// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC
/// 3986. The URI may point to a JSON file that conforms to the "ERC721
/// Metadata JSON Schema".
function tokenURI(uint256 tokenId) external view returns (string memory);
/// @notice Mints one NFT to the caller (msg.sender). Requires `minting type` to be `sequential` and the `mintPrice` to be send (if `Native payment`) or approved (if `ERC-20` payment).
function mint() external payable;
/// @notice Mints `amount` NFTs to the caller (msg.sender). Requires `minting type` to be `sequential` and the `mintPrice` to be send (if `Native payment`) or approved (if `ERC-20` payment).
/// @param amount The number of NFTs to mint
function mint(uint256 amount) external payable;
/// @notice Mints `amount` NFTs to the caller (msg.sender) with a given `affiliate`. Requires `minting type` to be `sequential` and the `mintPrice` to be send (if `Native payment`) or approved (if `ERC-20` payment).
/// @param amount The number of NFTs to mint
/// @param affiliate The affiliate address
function mint(uint256 amount, address affiliate) external payable;
/// @notice Mints `amount` NFTs to `to` address. Requires `minting type` to be `sequential` and the `mintPrice` to be send (if `Native payment`) or approved (if `ERC-20` payment).
/// @param to The address of the NFTs receiver
/// @param amount The number of NFTs to mint
function mintTo(address to, uint256 amount) external payable;
/// @notice Mints `amount` NFTs to `to` address with a given `affiliate`. Requires `minting type` to be `sequential` and the `mintPrice` to be send (if `Native payment`) or approved (if `ERC-20` payment).
/// @param to The address of the NFTs receiver
/// @param amount The number of NFTs to mint
/// @param affiliate The affiliate address
function mintTo(address to, uint256 amount, address affiliate) external payable;
/// @notice Two phases on-chain random minting. Mints `amount` NFTs tickets to `to` address. Requires `minting type` to be `random` and the `mintPrice` to be send (if `Native payment`) or approved (if `ERC-20` payment). Once minted, those tickets must be redeemed for an actual token calling `redeemRandom()`.
/// @param to The address of the NFTs receiver
/// @param amount The number of NFTs to mint
function mintRandomTo(address to, uint256 amount) external payable;
/// @notice Two phases on-chain random minting. Mints `amount` NFTs tickets to `to` address with a given `affiliate`. Requires `minting type` to be `random` and the `mintPrice` to be send (if `Native payment`) or approved (if `ERC-20` payment). Once minted, those tickets must be redeemed for an actual token calling `redeemRandom()`.
/// @param to The address of the NFTs receiver
/// @param amount The number of NFTs to mint
/// @param affiliate The affiliate address
function mintRandomTo(address to, uint256 amount, address affiliate) external payable;
/// @notice Redeems remaining random tickets generated from `msg.sender` by calling `mintRandomTo` for actual NFTs.
function redeemRandom() external payable;
/// @notice Mints `amount` NFTs to `to` address. Requires `minting type` to be `specify` and the `mintPrice` to be send (if `Native payment`) or approved (if `ERC-20` payment).
/// @param to The address of the NFTs receiver
/// @param tokenIds An array of the specified tokens. They must not be minted, otherwise, it will revert.
function mintSpecifyTo(address to, uint256[] memory tokenIds) external payable;
/// @notice Mints `amount` NFTs to `to` address with a given `affiliate`. Requires `minting type` to be `specify` and the `mintPrice` to be send (if `Native payment`) or approved (if `ERC-20` payment).
/// @param to The address of the NFTs receiver
/// @param tokenIds An array of the specified tokens. They must not be minted, otherwise, it will revert.
/// @param affiliate The affiliate address
function mintSpecifyTo(address to, uint256[] memory tokenIds, address affiliate) external payable;
/// @notice Mints one NFT to `to` address. Requires `minting type` to be `customURI`.
/// @param to The address of the NFTs receiver
/// @param customURICIDHash The CID of the given token.
/// @param soulbound True if the NFT is a Soulbound Token (SBT). If set, it can't be transferred.
function mintCustomURITo(address to, bytes32 customURICIDHash, bool soulbound) external payable;
/// @notice Only owner can call this function. Free of charge. Mints sizeof(`to`) to `to` addresses. Requires `minting type` to be `sequential`.
/// @param to The addresses of the NFTs receivers
/// @param soulbound True if the NFT is a Soulbound Token (SBT). If set, it can't be transferred.
function airdropSequential(address[] memory to, bool soulbound) external payable;
/// @notice Only owner can call this function. Free of charge. Mints sizeof(`to`) to `to` addresses with random tokenIds. Requires `minting type` to be `random`.
/// @param to The addresses of the NFTs receivers
/// @param soulbound True if the NFT is a Soulbound Token (SBT). If set, it can't be transferred.
function airdropRandom(address[] memory to, bool soulbound) external payable;
/// @notice Only owner can call this function. Free of charge. Mints sizeof(`to`) to `to` addresses with specified tokenIds. Requires `minting type` to be `specify`.
/// @param to The addresses of the NFTs receivers
/// @param tokenIds An array of the specified tokens. They must not be minted, otherwise, it will revert.
/// @param soulbound True if the NFT is a Soulbound Token (SBT). If set, it can't be transferred.
function airdropSpecify(address[] memory to, uint256[] memory tokenIds, bool soulbound) external payable;
/// @notice Mints `amount` of NFTs to `to` address with optional specified tokenIds. This function must be called only if a valid `signature` is given during a whitelisting/presale.
/// @param to The addresses of the NFTs receivers
/// @param tokenIds An optional array of the specified tokens. They must not be minted, otherwise, it will revert. Only used if minting type is `specify`.
/// @param freeMinting True is minting is free
/// @param customFee Zero is fee is different from `mintingPrice`.
/// @param maxAmount Max Amount to be minted with the given `signature`.
/// @param amount Amount to mint.
/// @param soulbound True if the NFT is a Soulbound Token (SBT). If set, it can't be transferred.
/// @param signature Valid `signature` for the presale/whitelist.
function mintPresale (
address to,
uint256[] memory tokenIds,
bool freeMinting,
uint256 customFee,
uint256 maxAmount,
uint256 amount,
bool soulbound,
bytes calldata signature) payable external;
/// @notice Returns the minting price of one NFT.
/// @return Mint price for one NFT in native coin or ERC-20.
function mintPrice() external view returns (uint256);
/// @notice Returns the current total supply.
/// @return Current total supply.
function totalSupply() external view returns (uint256);
/// @notice Max amount of NFTs to be hold per address.
/// @return Max per address allowed.
function maxPerAddress() external view returns (uint16);
}
// File: contracts/IN2M_ERCLibrary.sol
pragma solidity ^0.8.20;
interface IN2M_ERCLibrary is IN2M_ERCStorage {
function setAndRevealBaseURI(bytes32 baseURICIDHash) external;
function changeMintPrice(uint256 newMintPrice) external;
function contractURI() external view returns (string memory);
function setContractURI(bytes32 newContractURIMetadataCIDHash) external;
function setAffiliatesPercentageAndDiscount(uint16 userDiscount, uint16 affiliatePercentage, address affiliateAddress) external;
function affiliateWithdraw(address affiliate) external;
function withdrawERC20(address erc20Address) external;
function withdrawERC20Pro(uint256 signatureExpireDate, uint n2mFee, address erc20Address, bytes calldata signature) external;
function withdraw() external;
function withdrawPro(uint256 signatureExpireDate, uint256 n2mFee, bytes calldata signature) external;
function setReverseENSName(address rerverseResolver, string calldata collectionENSName) external;
function initializeAndSetReverseENSName(address resolver, string calldata collectionENSName) external;
function changePlaceholderImageCID(bytes32 newPlaceholderImageCIDHash) external;
function setPhase(SalePhase newPhase) external;
function setDropDate(uint256 dropDateTimestamp) external;
function setDropAndEndDate(uint256 dropDateTimestamp, uint256 endDateTimestamp) external;
function setMaxPerAddress(uint16 newMaxPerAddress) external;
function isOperatorFilterRegistryEnabled() external view returns (bool);
function enableOperatorFilterRegistry() external;
function disableOperatorFilterRegistry() external;
function collectionSize() external view returns (uint256);
function randomTickets(address affiliate) external view returns (uint amount, uint blockNumberToReveal);
function affiliatesInfo(address affiliate) external view returns (bool enabled, uint16 affiliatePercentage, uint16 userDiscount);
function pendingAffiliateBalance(address affiliate) external view returns (uint256);
function pendingTotalAffiliatesBalance() external view returns (uint256);
function royaltyFee() external view returns (uint256);
function withdrawnAmount() external view returns (uint256);
function withdrawnERC20Amount(address erc20) external view returns (uint256);
function erc20PaymentAddress() external view returns (address);
function currentPhase() external view returns (SalePhase);
function mintingType() external view returns (MintingType);
function saleDates() external view returns (uint256 dropDateTimestamp, uint256 endDateTimestamp);
function isOpen() external view returns (bool);
function ownerMaxRevenue() external view returns (uint256);
}
// File: contracts/IN2M_ERCCommon.sol
pragma solidity ^0.8.20;
interface IN2M_ERCCommon is IN2M_ERCBase, IN2M_ERCLibrary {
/// @notice Returns the address of the current collection owner.
/// @return The address of the owner.
function owner() external view returns (address);
}
// File: @openzeppelin/contracts/governance/utils/IVotes.sol
// OpenZeppelin Contracts (last updated v5.0.0) (governance/utils/IVotes.sol)
pragma solidity ^0.8.20;
/**
* @dev Common interface for {ERC20Votes}, {ERC721Votes}, and other {Votes}-enabled contracts.
*/
interface IVotes {
/**
* @dev The signature used has expired.
*/
error VotesExpiredSignature(uint256 expiry);
/**
* @dev Emitted when an account changes their delegate.
*/
event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);
/**
* @dev Emitted when a token transfer or delegate change results in changes to a delegate's number of voting units.
*/
event DelegateVotesChanged(address indexed delegate, uint256 previousVotes, uint256 newVotes);
/**
* @dev Returns the current amount of votes that `account` has.
*/
function getVotes(address account) external view returns (uint256);
/**
* @dev Returns the amount of votes that `account` had at a specific moment in the past. If the `clock()` is
* configured to use block numbers, this will return the value at the end of the corresponding block.
*/
function getPastVotes(address account, uint256 timepoint) external view returns (uint256);
/**
* @dev Returns the total supply of votes available at a specific moment in the past. If the `clock()` is
* configured to use block numbers, this will return the value at the end of the corresponding block.
*
* NOTE: This value is the sum of all available votes, which is not necessarily the sum of all delegated votes.
* Votes that have not been delegated are still part of total supply, even though they would not participate in a
* vote.
*/
function getPastTotalSupply(uint256 timepoint) external view returns (uint256);
/**
* @dev Returns the delegate that `account` has chosen.
*/
function delegates(address account) external view returns (address);
/**
* @dev Delegates votes from the sender to `delegatee`.
*/
function delegate(address delegatee) external;
/**
* @dev Delegates votes from signer to `delegatee`.
*/
function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) external;
}
// File: @openzeppelin/contracts/utils/introspection/IERC165.sol
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// File: @openzeppelin/contracts/token/ERC721/IERC721.sol
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.0;
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @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`.
*
* 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;
/**
* @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 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: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* 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 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 the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @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);
}
// File: @openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)
pragma solidity ^0.8.0;
/**
* @title ERC-721 Non-Fungible Token Standard, optional metadata extension
* @dev See https://eips.ethereum.org/EIPS/eip-721
*/
interface IERC721Metadata is IERC721 {
/**
* @dev Returns the token collection name.
*/
function name() external view returns (string memory);
/**
* @dev Returns the token collection symbol.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(uint256 tokenId) external view returns (string memory);
}
// File: contracts/IKryptoPunks.sol
pragma solidity ^0.8.20;
interface IKryptoPunks is IERC721Metadata, IN2M_ERCCommon, IVotes {
function name() external view override(IERC721Metadata, IN2M_ERCBase) returns (string memory);
function tokenURI(uint256 tokenId) external view override(IERC721Metadata, IN2M_ERCBase) returns (string memory);
function symbol() external view override(IERC721Metadata, IN2M_ERCBase) returns (string memory);
function supportsInterface(bytes4 interfaceId) external view override(IERC165, IERC165Upgradeable) returns (bool);
}
// File: @openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20Upgradeable {
/**
* @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);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, 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 `from` to `to` 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 from, address to, uint256 amount) external returns (bool);
}
// File: @openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20MetadataUpgradeable is IERC20Upgradeable {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
// File: contracts/IWBPunkToken.sol
pragma solidity ^0.8.11;
interface IWBPunkToken is IERC20MetadataUpgradeable {
/**
* @notice The body of a request to mint tokens.
*
* @param to The receiver of the tokens to mint.
* @param primarySaleRecipient The receiver of the primary sale funds from the mint.
* @param quantity The quantity of tpkens to mint.
* @param price Price to pay for minting with the signature.
* @param currency The currency in which the price per token must be paid.
* @param validityStartTimestamp The unix timestamp after which the request is valid.
* @param validityEndTimestamp The unix timestamp after which the request expires.
* @param uid A unique identifier for the request.
*/
struct MintRequest {
address to;
address primarySaleRecipient;
uint256 quantity;
uint256 price;
address currency;
uint128 validityStartTimestamp;
uint128 validityEndTimestamp;
bytes32 uid;
}
/// @dev Emitted when an account with MINTER_ROLE mints an NFT.
event TokensMinted(address indexed mintedTo, uint256 quantityMinted);
/// @dev Emitted when tokens are minted.
event TokensMintedWithSignature(address indexed signer, address indexed mintedTo, MintRequest mintRequest);
/**
* @notice Verifies that a mint request is signed by an account holding
* MINTER_ROLE (at the time of the function call).
*
* @param req The mint request.
* @param signature The signature produced by an account signing the mint request.
*
* returns (success, signer) Result of verification and the recovered address.
*/
function verify(MintRequest calldata req, bytes calldata signature)
external
view
returns (bool success, address signer);
/**
* @dev Creates `amount` new tokens for `to`.
*
* See {ERC20-_mint}.
*
* Requirements:
*
* - the caller must have the `MINTER_ROLE`.
*/
function mintTo(address to, uint256 amount) external;
/**
* @notice Mints an NFT according to the provided mint request.
*
* @param req The mint request.
* @param signature he signature produced by an account signing the mint request.
*/
function mintWithSignature(MintRequest calldata req, bytes calldata signature) external payable;
}
// File: @openzeppelin/contracts/security/ReentrancyGuard.sol
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
}
// File: @openzeppelin/contracts/token/ERC721/IERC721Receiver.sol
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
pragma solidity ^0.8.0;
/**
* @title ERC721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC721 asset contracts.
*/
interface IERC721Receiver {
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
*
* The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
// File: contracts/NFT-Staking.sol
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes memory) {
return msg.data;
}
}
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() {
address msgSender = _msgSender();
_owner = msgSender;
emit OwnershipTransferred(address(0), 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 {
emit OwnershipTransferred(_owner, address(0));
_owner = 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");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
pragma solidity ^0.8.20;
contract NFTStakingVault is Context, IERC721Receiver, ReentrancyGuard, Ownable{
// *********** //
// VARIABLES //
// *********** //
// total count of NFT staked in this Vault by all users
uint256 public totalItemsStaked;
IKryptoPunks immutable nft;
IWBPunkToken immutable token;
struct Stake {
// packed to save gas
address owner;
uint64 stakedAt;
}
// tokenId => Stake
mapping(uint256 => Stake) vault;
//user => nft stake id -> rewards claimed
mapping(address => mapping (uint256 => uint256)) RewardsClaimed;
// ******** //
// EVENTS //
// ******** //
event ItemsStaked(uint256[] tokenId, address owner);
event ItemsUnstaked(uint256[] tokenIds, address owner);
event Claimed(address owner, uint256 reward);
// ******** //
// ERRORS //
// ******** //
error NFTStakingVault__ItemAlreadyStaked();
error NFTStakingVault__NotItemOwner();
constructor(address _nftAddress, address _tokenAddress) {
nft = IKryptoPunks(_nftAddress);
token = IWBPunkToken(_tokenAddress);
}
// *********** //
// FUNCTIONS //
// *********** //
/// @notice allow caller to stake multiple NFTs
/// @dev only NFT owner should be able to call this, should have approved ERC721 transfer
/// @param tokenIds array of token ids (uint256) to be staked
function stake(uint256[] calldata tokenIds) external nonReentrant{
uint256 stakedCount = tokenIds.length;
for (uint256 i; i < stakedCount; ) {
uint256 tokenId = tokenIds[i];
if (vault[tokenId].owner != address(0)) {
revert NFTStakingVault__ItemAlreadyStaked();
}
if (nft.ownerOf(tokenId) != _msgSender()) {
revert NFTStakingVault__NotItemOwner();
}
nft.safeTransferFrom(_msgSender(), address(this), tokenId);
require(
nft.ownerOf(tokenId) == address(this),
'NFT Ownership Not Transferred to contract'
);
vault[tokenId] = Stake(_msgSender(), uint64(block.timestamp));
unchecked {
++i;
}
}
totalItemsStaked = totalItemsStaked + stakedCount;
// emit final event after for loop to save gas
emit ItemsStaked(tokenIds, _msgSender());
}
/// @notice allow caller to unstake multiple NFTs white also claiming any accrued rewards
/// @dev only NFTs owner should be able to call this
/// @param tokenIds array of token ids (uint256) to be unstaked
function unstake(uint256[] calldata tokenIds) external nonReentrant{
_claim(_msgSender(), tokenIds, true);
}
/// @notice allow caller to claim accrued rewards on staked NFTs
/// @dev only NFT owner should be able to call this, will not unstake NFTs
/// @param tokenIds array of token ids (uint256) to claim rewards for
function claim(uint256[] calldata tokenIds) external nonReentrant{
_claim(_msgSender(), tokenIds, false);
}
/// @notice allow owner to rescue reward tokens
/// @dev only contract owner should be ablt to call this function
/// @param amount amount in wei
function rescueTokens(
uint256 amount
) external onlyOwner {
token.transfer(owner(), amount);
}
/// @notice internal function to claim user rewards accrued
/// @dev calculate rewards based on staking period of each token using '_calculateReward'
/// @param user address of user to claim for, must be owner of tokenIds
/// @param tokenIds array of token ids (uint256) to claim rewards for
/// @param unstakeAll bool true if user wants to unstake, false otherwise
function _claim(
address user,
uint256[] calldata tokenIds,
bool unstakeAll
) internal {
uint256 tokenId;
uint256 rewardEarned;
uint256 len = tokenIds.length;
for (uint256 i; i < len; ) {
tokenId = tokenIds[i];
if (vault[tokenId].owner != user) {
revert NFTStakingVault__NotItemOwner();
}
uint256 _stakedAt = uint256(vault[tokenId].stakedAt);
uint256 stakingPeriod = block.timestamp - _stakedAt;
uint256 _totalReward = _calculateReward(stakingPeriod);
_totalReward -= RewardsClaimed[user][tokenId];
if(_totalReward > 0){
rewardEarned += _totalReward;
RewardsClaimed[user][tokenId] += _totalReward;
}
unchecked {
++i;
}
}
if (rewardEarned != 0) {
token.transfer(user, rewardEarned);
emit Claimed(user, rewardEarned);
}
if (unstakeAll) {
_unstake(user, tokenIds);
}
}
/// @notice internal function to unstake user staked tokens
/// @dev will not claim rewards, should always be called after claiming
/// @param user address of user to unstake for
/// @param tokenIds array of token ids (uint256) to unstake
function _unstake(address user, uint256[] calldata tokenIds) internal {
uint256 unstakedCount = tokenIds.length;
for (uint256 i; i < unstakedCount; ) {
uint256 tokenId = tokenIds[i];
require(vault[tokenId].owner == user, "Not Owner");
delete vault[tokenId];
RewardsClaimed[user][tokenId] = 0;
nft.safeTransferFrom(address(this), user, tokenId);
require(
nft.ownerOf(tokenId) == user,
'NFT Ownership Not Transferred to user'
);
unchecked {
++i;
}
}
totalItemsStaked = totalItemsStaked - unstakedCount;
// emit final event after for loop to save gas
emit ItemsUnstaked(tokenIds, user);
}
/// @notice internal function to get Total Reward of NFT
/// @dev calculate the daily reward based on the NFT staking period using pre-defined logic
/// @param stakingPeriod time period during which the NFT was stake
function _calculateReward(uint256 stakingPeriod) internal pure returns (uint256 totalReward) {
// Initial reward of 5 tokens per day
uint256 initialReward = 5 * 1e18;
// Increment in reward per day
uint256 rewardIncrement = 5 * 1e16; // 0.05 tokens
// Maximum staking period with increasing rewards (180 days)
uint256 maxIncreasingPeriod = 180 days;
// Fixed reward after 180 days
uint256 fixedReward = 14 * 1e18;
uint256 fullDays = stakingPeriod / 1 days;
uint256 remainingDuration = stakingPeriod % 1 days;
if (stakingPeriod <= maxIncreasingPeriod) {
// Calculate total reward for the initial increasing period
if (fullDays > 0){
for (uint256 day = 1; day <= fullDays; day++) {
totalReward += initialReward + (rewardIncrement * (day - 1));
}
// Calculate proportional reward for the remaining duration within the last day
totalReward += (rewardIncrement * fullDays * remainingDuration) / 1 days;
}else {
//calculate reward for less than one day
totalReward += (initialReward * remainingDuration) / 1 days;
}
} else {
// Calculate increment rewards till 180 days
for (uint256 day = 1; day <= maxIncreasingPeriod/1 days; day++) {
totalReward += initialReward + (rewardIncrement * (day - 1));
}
// Calculate total reward as fixed each day after 180 days
totalReward += (stakingPeriod - maxIncreasingPeriod) / 1 days * fixedReward;
//Calculate for remainig duration of last day
totalReward += (fixedReward * remainingDuration) / 1 days;
}
return totalReward;
}
function _calculateRewardPerDay(uint256 stakingPeriod) internal pure returns (uint256 dailyReward) {
// Initial reward of 5 tokens per day
uint256 initialReward = 5 * 1e18;
// Increment in reward per day
uint256 rewardIncrement = 5 * 1e16; // 0.05 tokens
// Maximum staking period with increasing rewards (180 days)
uint256 maxIncreasingPeriod = 180 days;
// Fixed reward after 180 days
uint256 fixedReward = 14 * 1e18;
if (stakingPeriod <= maxIncreasingPeriod) {
uint256 fullDays = stakingPeriod / 1 days;
if(fullDays > 0){
dailyReward = initialReward + (rewardIncrement * (stakingPeriod - 1 days) ) / 1 days;
}else {
//day1
dailyReward = initialReward;
}
} else {
// Fixed reward of 14 tokens per day after 180 days
dailyReward = fixedReward;
}
}
// ********* //
// GETTERS //
// ********* //
/// @notice returns daily reward earned given token staking period
/// @dev calls _calculateRewardPerDay function
/// @param stakingPeriod time period during which the NFT was stake
/// @return dailyReward daily reward based on staking period
function getDailyReward(
uint256 stakingPeriod
) external pure returns (uint256 dailyReward) {
dailyReward = _calculateRewardPerDay(stakingPeriod);
}
/// @notice get the total rewards accrued by all tokens staked by user
/// @dev uses same reward formula implemented in "_claim"
/// @param user address of tokens owner
/// @return rewardEarned total reward accrued by user's staked token
function getTotalRewardEarned(
address user
) external view returns (uint256 rewardEarned) {
uint256[] memory tokens = tokensOfOwner(user);
uint256 len = tokens.length;
for (uint256 i; i < len; ) {
uint256 _stakedAt = uint256(vault[tokens[i]].stakedAt);
uint256 stakingPeriod = block.timestamp - _stakedAt;
uint256 _totalReward = _calculateReward(stakingPeriod);
_totalReward -= RewardsClaimed[user][tokens[i]];
if(_totalReward > 0){
rewardEarned += _totalReward;
}
unchecked {
++i;
}
}
}
/// @notice get rewards accrued by a given staked token
/// @dev uses same reward formula implemented in "_claim"
/// @param _tokenId id of the token for which reward calculation is needed
/// @return rewardEarned reward accrued by _tokenId
function getRewardEarnedPerNft(
uint256 _tokenId
) external view returns (uint256 rewardEarned) {
address user = vault[_tokenId].owner;
uint256 _stakedAt = uint256(vault[_tokenId].stakedAt);
uint256 stakingPeriod = block.timestamp - _stakedAt;
uint256 _totalReward = _calculateReward(stakingPeriod);
_totalReward -= RewardsClaimed[user][_tokenId];
if(_totalReward > 0 ){
rewardEarned = _totalReward;
}
}
/// @notice returns count of token staked by user
/// @param user address of tokens owner
/// @return nftStakedbalance number of NFTs staked by user
function balanceOf(
address user
) public view returns (uint256 nftStakedbalance) {
uint256 supply = nft.totalSupply();
unchecked {
for (uint256 i; i <= supply; ++i) {
if (vault[i].owner == user) {
nftStakedbalance += 1;
}
}
}
}
/// @notice returns all tokens staked by the user
/// @param user address of tokens owner
/// @return tokens array of token Ids (uint256) staked by the user
function tokensOfOwner(
address user
) public view returns (uint256[] memory tokens) {
uint256 balance = balanceOf(user);
if (balance == 0) return tokens;
uint256 supply = nft.totalSupply();
tokens = new uint256[](balance);
uint256 counter;
unchecked {
for (uint256 i; i <= supply; ++i) {
if (vault[i].owner == user) {
tokens[counter] = i;
counter++;
if (counter == balance) return tokens;
}
}
}
}
/// @notice allow vault contract (address(this)) to receive ERC721 tokens
function onERC721Received(
address /**operator*/,
address /**from*/,
uint256 /**amount*/,
bytes calldata //data
) external pure override returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}
}
{
"compilationTarget": {
"NFTStakingVault.sol": "NFTStakingVault"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_nftAddress","type":"address"},{"internalType":"address","name":"_tokenAddress","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"NFTStakingVault__ItemAlreadyStaked","type":"error"},{"inputs":[],"name":"NFTStakingVault__NotItemOwner","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"reward","type":"uint256"}],"name":"Claimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256[]","name":"tokenId","type":"uint256[]"},{"indexed":false,"internalType":"address","name":"owner","type":"address"}],"name":"ItemsStaked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":false,"internalType":"address","name":"owner","type":"address"}],"name":"ItemsUnstaked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"nftStakedbalance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"stakingPeriod","type":"uint256"}],"name":"getDailyReward","outputs":[{"internalType":"uint256","name":"dailyReward","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"getRewardEarnedPerNft","outputs":[{"internalType":"uint256","name":"rewardEarned","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getTotalRewardEarned","outputs":[{"internalType":"uint256","name":"rewardEarned","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"rescueTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"tokensOfOwner","outputs":[{"internalType":"uint256[]","name":"tokens","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalItemsStaked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"unstake","outputs":[],"stateMutability":"nonpayable","type":"function"}]