NFT质押
NFTStakingV3-BringYourOwnNFTCollection.sol
// SPDX-License-Identifier: MIT LICENSE /* Follow/Subscribe Youtube, Github, IM, Tiktok for more amazing content!! @Net2Dev THIS CONTRACT IS AVAILABLE FOR EDUCATIONAL PURPOSES ONLY. YOU ARE SOLELY REPONSIBLE FOR ITS USE. I AM NOT RESPONSIBLE FOR ANY OTHER USE. THIS IS TRAINING/EDUCATIONAL MATERIAL. ONLY USE IT IF YOU AGREE TO THE TERMS SPECIFIED ABOVE. */ pragma solidity 0.8.4; import "./N2DRewards.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; contract NFTStaking is Ownable, IERC721Receiver { uint256 public totalStaked; // struct to store a stake's token, owner, and earning values struct Stake { uint24 tokenId; uint48 timestamp; address owner; } event NFTStaked(address owner, uint256 tokenId, uint256 value); event NFTUnstaked(address owner, uint256 tokenId, uint256 value); event Claimed(address owner, uint256 amount); // reference to the Block NFT contract ERC721Enumerable nft; N2DRewards token; // maps tokenId to stake mapping(uint256 => Stake) public vault; constructor(ERC721Enumerable _nft, N2DRewards _token) { nft = _nft; token = _token; } function stake(uint256[] calldata tokenIds) external { uint256 tokenId; totalStaked += tokenIds.length; for (uint i = 0; i < tokenIds.length; i++) { tokenId = tokenIds[i]; require(nft.ownerOf(tokenId) == msg.sender, "not your token"); require(vault[tokenId].tokenId == 0, 'already staked'); nft.transferFrom(msg.sender, address(this), tokenId); emit NFTStaked(msg.sender, tokenId, block.timestamp); vault[tokenId] = Stake({ owner: msg.sender, tokenId: uint24(tokenId), timestamp: uint48(block.timestamp) }); } } function _unstakeMany(address account, uint256[] calldata tokenIds) internal { uint256 tokenId; totalStaked -= tokenIds.length; for (uint i = 0; i < tokenIds.length; i++) { tokenId = tokenIds[i]; Stake memory staked = vault[tokenId]; require(staked.owner == msg.sender, "not an owner"); delete vault[tokenId]; emit NFTUnstaked(account, tokenId, block.timestamp); nft.transferFrom(address(this), account, tokenId); } } function claim(uint256[] calldata tokenIds) external { _claim(msg.sender, tokenIds, false); } function claimForAddress(address account, uint256[] calldata tokenIds) external { _claim(account, tokenIds, false); } function unstake(uint256[] calldata tokenIds) external { _claim(msg.sender, tokenIds, true); } // @Net2Dev - Follow me on Youtube , Tiktok, Instagram // TOKEN REWARDS CALCULATION // MAKE SURE YOU CHANGE THE VALUE ON BOTH CLAIM AND EARNINGINFO FUNCTIONS. // Find the following line and update accordingly based on how much you want // to reward users with ERC-20 reward tokens. // I hope you get the idea based on the example. // rewardmath = 100 ether .... (This gives 1 token per day per NFT staked to the staker) // rewardmath = 200 ether .... (This gives 2 tokens per day per NFT staked to the staker) // rewardmath = 500 ether .... (This gives 5 tokens per day per NFT staked to the staker) // rewardmath = 1000 ether .... (This gives 10 tokens per day per NFT staked to the staker) function _claim(address account, uint256[] calldata tokenIds, bool _unstake) internal { uint256 tokenId; uint256 earned = 0; uint256 rewardmath = 0; for (uint i = 0; i < tokenIds.length; i++) { tokenId = tokenIds[i]; Stake memory staked = vault[tokenId]; require(staked.owner == account, "not an owner"); uint256 stakedAt = staked.timestamp; rewardmath = 100 ether * (block.timestamp - stakedAt) / 86400 ; earned = rewardmath / 100; vault[tokenId] = Stake({ owner: account, tokenId: uint24(tokenId), timestamp: uint48(block.timestamp) }); } if (earned > 0) { token.mint(account, earned); } if (_unstake) { _unstakeMany(account, tokenIds); } emit Claimed(account, earned); } function earningInfo(address account, uint256[] calldata tokenIds) external view returns (uint256[1] memory info) { uint256 tokenId; uint256 earned = 0; uint256 rewardmath = 0; for (uint i = 0; i < tokenIds.length; i++) { tokenId = tokenIds[i]; Stake memory staked = vault[tokenId]; require(staked.owner == account, "not an owner"); uint256 stakedAt = staked.timestamp; rewardmath = 100 ether * (block.timestamp - stakedAt) / 86400; earned = rewardmath / 100; } if (earned > 0) { return [earned]; } } // should never be used inside of transaction because of gas fee function balanceOf(address account) public view returns (uint256) { uint256 balance = 0; uint256 supply = nft.totalSupply(); for(uint i = 1; i <= supply; i++) { if (vault[i].owner == account) { balance += 1; } } return balance; } // should never be used inside of transaction because of gas fee function tokensOfOwner(address account) public view returns (uint256[] memory ownerTokens) { uint256 supply = nft.totalSupply(); uint256[] memory tmp = new uint256[](supply); uint256 index = 0; for(uint tokenId = 1; tokenId <= supply; tokenId++) { if (vault[tokenId].owner == account) { tmp[index] = vault[tokenId].tokenId; index +=1; } } uint256[] memory tokens = new uint256[](index); for(uint i = 0; i < index; i++) { tokens[i] = tmp[i]; } return tokens; } function onERC721Received( address, address from, uint256, bytes calldata ) external pure override returns (bytes4) { require(from == address(0x0), "Cannot send nfts to Vault directly"); return IERC721Receiver.onERC721Received.selector; } }
nftstakingV2.sol
// SPDX-License-Identifier: MIT LICENSE /* Follow/Subscribe Youtube, Github, IM, Tiktok for more amazing content!! @Net2Dev THIS CONTRACT IS AVAILABLE FOR EDUCATIONAL PURPOSES ONLY. YOU ARE SOLELY REPONSIBLE FOR ITS USE. I AM NOT RESPONSIBLE FOR ANY OTHER USE. THIS IS TRAINING/EDUCATIONAL MATERIAL. ONLY USE IT IF YOU AGREE TO THE TERMS SPECIFIED ABOVE. */ pragma solidity ^0.8.4; import "./N2DRewards.sol"; import "./Collection.sol"; contract NFTStaking is Ownable, IERC721Receiver { uint256 public totalStaked; // struct to store a stake's token, owner, and earning values struct Stake { uint24 tokenId; uint48 timestamp; address owner; } event NFTStaked(address owner, uint256 tokenId, uint256 value); event NFTUnstaked(address owner, uint256 tokenId, uint256 value); event Claimed(address owner, uint256 amount); // reference to the Block NFT contract Collection nft; N2DRewards token; // maps tokenId to stake mapping(uint256 => Stake) public vault; constructor(Collection _nft, N2DRewards _token) { nft = _nft; token = _token; } function stake(uint256[] calldata tokenIds) external { uint256 tokenId; totalStaked += tokenIds.length; for (uint i = 0; i < tokenIds.length; i++) { tokenId = tokenIds[i]; require(nft.ownerOf(tokenId) == msg.sender, "not your token"); require(vault[tokenId].tokenId == 0, 'already staked'); nft.transferFrom(msg.sender, address(this), tokenId); emit NFTStaked(msg.sender, tokenId, block.timestamp); vault[tokenId] = Stake({ owner: msg.sender, tokenId: uint24(tokenId), timestamp: uint48(block.timestamp) }); } } function _unstakeMany(address account, uint256[] calldata tokenIds) internal { uint256 tokenId; totalStaked -= tokenIds.length; for (uint i = 0; i < tokenIds.length; i++) { tokenId = tokenIds[i]; Stake memory staked = vault[tokenId]; require(staked.owner == msg.sender, "not an owner"); delete vault[tokenId]; emit NFTUnstaked(account, tokenId, block.timestamp); nft.transferFrom(address(this), account, tokenId); } } function claim(uint256[] calldata tokenIds) external { _claim(msg.sender, tokenIds, false); } function claimForAddress(address account, uint256[] calldata tokenIds) external { _claim(account, tokenIds, false); } function unstake(uint256[] calldata tokenIds) external { _claim(msg.sender, tokenIds, true); } // @Net2Dev - Follow me on Youtube , Tiktok, Instagram // TOKEN REWARDS CALCULATION // MAKE SURE YOU CHANGE THE VALUE ON BOTH CLAIM AND EARNINGINFO FUNCTIONS. // Find the following line and update accordingly based on how much you want // to reward users with ERC-20 reward tokens. // I hope you get the idea based on the example. // rewardmath = 100 ether .... (This gives 1 token per day per NFT staked to the staker) // rewardmath = 200 ether .... (This gives 2 tokens per day per NFT staked to the staker) // rewardmath = 500 ether .... (This gives 5 tokens per day per NFT staked to the staker) // rewardmath = 1000 ether .... (This gives 10 tokens per day per NFT staked to the staker) function _claim(address account, uint256[] calldata tokenIds, bool _unstake) internal { uint256 tokenId; uint256 earned = 0; uint256 rewardmath = 0; for (uint i = 0; i < tokenIds.length; i++) { tokenId = tokenIds[i]; Stake memory staked = vault[tokenId]; require(staked.owner == account, "not an owner"); uint256 stakedAt = staked.timestamp; rewardmath = 100 ether * (block.timestamp - stakedAt) / 86400 ; earned = rewardmath / 100; vault[tokenId] = Stake({ owner: account, tokenId: uint24(tokenId), timestamp: uint48(block.timestamp) }); } if (earned > 0) { token.mint(account, earned); } if (_unstake) { _unstakeMany(account, tokenIds); } emit Claimed(account, earned); } function earningInfo(address account, uint256[] calldata tokenIds) external view returns (uint256[1] memory info) { uint256 tokenId; uint256 earned = 0; uint256 rewardmath = 0; for (uint i = 0; i < tokenIds.length; i++) { tokenId = tokenIds[i]; Stake memory staked = vault[tokenId]; require(staked.owner == account, "not an owner"); uint256 stakedAt = staked.timestamp; rewardmath = 100 ether * (block.timestamp - stakedAt) / 86400; earned = rewardmath / 100; } if (earned > 0) { return [earned]; } } // should never be used inside of transaction because of gas fee function balanceOf(address account) public view returns (uint256) { uint256 balance = 0; uint256 supply = nft.totalSupply(); for(uint i = 1; i <= supply; i++) { if (vault[i].owner == account) { balance += 1; } } return balance; } // should never be used inside of transaction because of gas fee function tokensOfOwner(address account) public view returns (uint256[] memory ownerTokens) { uint256 supply = nft.totalSupply(); uint256[] memory tmp = new uint256[](supply); uint256 index = 0; for(uint tokenId = 1; tokenId <= supply; tokenId++) { if (vault[tokenId].owner == account) { tmp[index] = vault[tokenId].tokenId; index +=1; } } uint256[] memory tokens = new uint256[](index); for(uint i = 0; i < index; i++) { tokens[i] = tmp[i]; } return tokens; } function onERC721Received( address, address from, uint256, bytes calldata ) external pure override returns (bytes4) { require(from == address(0x0), "Cannot send nfts to Vault directly"); return IERC721Receiver.onERC721Received.selector; } }