├── Collection.sol ├── N2D-NFT-ERC721-Staking-with-ERC20-TokenRewards.pdf ├── N2D-NFT-MultiVault-SmartContract-Logical-Design-Overview.pdf ├── N2DRewards-MaxSupply-ERC20-Contract.sol ├── N2DRewards.sol ├── NFTStaking.sol ├── NFTStakingV3-BringYourOwnNFTCollection.sol ├── README.md ├── nftstakingV2.sol └── nftstakingvault.sol /Collection.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT LICENSE 2 | 3 | /* 4 | N2D NFT ERC721 NFT Smart Contract. 5 | 6 | Follow/Subscribe Youtube, Github, IM, Tiktok 7 | for more amazing content!! 8 | @Net2Dev 9 | ███╗░░██╗███████╗████████╗██████╗░██████╗░███████╗██╗░░░██╗ 10 | ████╗░██║██╔════╝╚══██╔══╝╚════██╗██╔══██╗██╔════╝██║░░░██║ 11 | ██╔██╗██║█████╗░░░░░██║░░░░░███╔═╝██║░░██║█████╗░░╚██╗░██╔╝ 12 | ██║╚████║██╔══╝░░░░░██║░░░██╔══╝░░██║░░██║██╔══╝░░░╚████╔╝░ 13 | ██║░╚███║███████╗░░░██║░░░███████╗██████╔╝███████╗░░╚██╔╝░░ 14 | ╚═╝░░╚══╝╚══════╝░░░╚═╝░░░╚══════╝╚═════╝░╚══════╝░░░╚═╝░░░ 15 | THIS CONTRACT IS AVAILABLE FOR EDUCATIONAL 16 | PURPOSES ONLY. YOU ARE SOLELY REPONSIBLE 17 | FOR ITS USE. I AM NOT RESPONSIBLE FOR ANY 18 | OTHER USE. THIS IS TRAINING/EDUCATIONAL 19 | MATERIAL. ONLY USE IT IF YOU AGREE TO THE 20 | TERMS SPECIFIED ABOVE. 21 | */ 22 | 23 | 24 | import "@openzeppelin/contracts/access/Ownable.sol"; 25 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; 26 | 27 | pragma solidity ^0.8.4; 28 | 29 | contract Collection is ERC721Enumerable, Ownable { 30 | 31 | 32 | using Strings for uint256; 33 | string public baseURI; 34 | string public baseExtension = ".json"; 35 | uint256 public maxSupply = 100000; 36 | uint256 public maxMintAmount = 5; 37 | bool public paused = false; 38 | 39 | constructor() ERC721("Net2Dev NFT Collection", "N2D") {} 40 | 41 | 42 | function _baseURI() internal view virtual override returns (string memory) { 43 | return "ipfs://QmYB5uWZqfunBq7yWnamTqoXWBAHiQoirNLmuxMzDThHhi/"; 44 | 45 | } 46 | 47 | function mint(address _to, uint256 _mintAmount) public payable { 48 | uint256 supply = totalSupply(); 49 | require(!paused); 50 | require(_mintAmount > 0); 51 | require(_mintAmount <= maxMintAmount); 52 | require(supply + _mintAmount <= maxSupply); 53 | 54 | for (uint256 i = 1; i <= _mintAmount; i++) { 55 | _safeMint(_to, supply + i); 56 | } 57 | } 58 | 59 | 60 | function walletOfOwner(address _owner) 61 | public 62 | view 63 | returns (uint256[] memory) 64 | { 65 | uint256 ownerTokenCount = balanceOf(_owner); 66 | uint256[] memory tokenIds = new uint256[](ownerTokenCount); 67 | for (uint256 i; i < ownerTokenCount; i++) { 68 | tokenIds[i] = tokenOfOwnerByIndex(_owner, i); 69 | } 70 | return tokenIds; 71 | } 72 | 73 | 74 | function tokenURI(uint256 tokenId) 75 | public 76 | view 77 | virtual 78 | override 79 | returns (string memory) { 80 | require( 81 | _exists(tokenId), 82 | "ERC721Metadata: URI query for nonexistent token" 83 | ); 84 | 85 | string memory currentBaseURI = _baseURI(); 86 | return 87 | bytes(currentBaseURI).length > 0 88 | ? string(abi.encodePacked(currentBaseURI, tokenId.toString(), baseExtension)) 89 | : ""; 90 | } 91 | // only owner 92 | 93 | function setmaxMintAmount(uint256 _newmaxMintAmount) public onlyOwner() { 94 | maxMintAmount = _newmaxMintAmount; 95 | } 96 | 97 | function setBaseURI(string memory _newBaseURI) public onlyOwner() { 98 | baseURI = _newBaseURI; 99 | } 100 | 101 | function setBaseExtension(string memory _newBaseExtension) public onlyOwner() { 102 | baseExtension = _newBaseExtension; 103 | } 104 | 105 | function pause(bool _state) public onlyOwner() { 106 | paused = _state; 107 | } 108 | 109 | function withdraw() public payable onlyOwner() { 110 | require(payable(msg.sender).send(address(this).balance)); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /N2D-NFT-ERC721-Staking-with-ERC20-TokenRewards.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net2devcrypto/nftstaking/bc21f2e994f3e487ce085b4de36043bad179d96d/N2D-NFT-ERC721-Staking-with-ERC20-TokenRewards.pdf -------------------------------------------------------------------------------- /N2D-NFT-MultiVault-SmartContract-Logical-Design-Overview.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net2devcrypto/nftstaking/bc21f2e994f3e487ce085b4de36043bad179d96d/N2D-NFT-MultiVault-SmartContract-Logical-Design-Overview.pdf -------------------------------------------------------------------------------- /N2DRewards-MaxSupply-ERC20-Contract.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT LICENSE 2 | 3 | 4 | /* 5 | 6 | Follow/Subscribe Youtube, Github, IM, Tiktok 7 | for more amazing content!! 8 | @Net2Dev 9 | ███╗░░██╗███████╗████████╗██████╗░██████╗░███████╗██╗░░░██╗ 10 | ████╗░██║██╔════╝╚══██╔══╝╚════██╗██╔══██╗██╔════╝██║░░░██║ 11 | ██╔██╗██║█████╗░░░░░██║░░░░░███╔═╝██║░░██║█████╗░░╚██╗░██╔╝ 12 | ██║╚████║██╔══╝░░░░░██║░░░██╔══╝░░██║░░██║██╔══╝░░░╚████╔╝░ 13 | ██║░╚███║███████╗░░░██║░░░███████╗██████╔╝███████╗░░╚██╔╝░░ 14 | ╚═╝░░╚══╝╚══════╝░░░╚═╝░░░╚══════╝╚═════╝░╚══════╝░░░╚═╝░░░ 15 | THIS CONTRACT IS AVAILABLE FOR EDUCATIONAL 16 | PURPOSES ONLY. YOU ARE SOLELY REPONSIBLE 17 | FOR ITS USE. I AM NOT RESPONSIBLE FOR ANY 18 | OTHER USE. THIS IS TRAINING/EDUCATIONAL 19 | MATERIAL. ONLY USE IT IF YOU AGREE TO THE 20 | TERMS SPECIFIED ABOVE. 21 | */ 22 | 23 | pragma solidity 0.8.4; 24 | import "@openzeppelin/contracts/access/Ownable.sol"; 25 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 26 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 27 | import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 28 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 29 | 30 | contract N2DRewards is ERC20, ERC20Burnable, Ownable { 31 | using SafeMath for uint256; 32 | 33 | mapping(address => uint256) private _balances; 34 | mapping(address => bool) controllers; 35 | 36 | uint256 private _totalSupply; 37 | uint256 private MAXSUP; 38 | uint256 constant MAXIMUMSUPPLY=1000000*10**18; 39 | 40 | constructor() ERC20("N2DRewards", "N2DR") { 41 | _mint(msg.sender, 1000000 * 10 ** 18); 42 | 43 | } 44 | 45 | function mint(address to, uint256 amount) external { 46 | require(controllers[msg.sender], "Only controllers can mint"); 47 | require((MAXSUP+amount)<=MAXIMUMSUPPLY,"Maximum supply has been reached"); 48 | _totalSupply = _totalSupply.add(amount); 49 | MAXSUP=MAXSUP.add(amount); 50 | _balances[to] = _balances[to].add(amount); 51 | _mint(to, amount); 52 | } 53 | 54 | function burnFrom(address account, uint256 amount) public override { 55 | if (controllers[msg.sender]) { 56 | _burn(account, amount); 57 | } 58 | else { 59 | super.burnFrom(account, amount); 60 | } 61 | } 62 | 63 | function addController(address controller) external onlyOwner { 64 | controllers[controller] = true; 65 | } 66 | 67 | function removeController(address controller) external onlyOwner { 68 | controllers[controller] = false; 69 | } 70 | 71 | function totalSupply() public override view returns (uint256) { 72 | return _totalSupply; 73 | } 74 | 75 | function maxSupply() public pure returns (uint256) { 76 | return MAXIMUMSUPPLY; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /N2DRewards.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT LICENSE 2 | 3 | 4 | /* 5 | 6 | Follow/Subscribe Youtube, Github, IM, Tiktok 7 | for more amazing content!! 8 | @Net2Dev 9 | ███╗░░██╗███████╗████████╗██████╗░██████╗░███████╗██╗░░░██╗ 10 | ████╗░██║██╔════╝╚══██╔══╝╚════██╗██╔══██╗██╔════╝██║░░░██║ 11 | ██╔██╗██║█████╗░░░░░██║░░░░░███╔═╝██║░░██║█████╗░░╚██╗░██╔╝ 12 | ██║╚████║██╔══╝░░░░░██║░░░██╔══╝░░██║░░██║██╔══╝░░░╚████╔╝░ 13 | ██║░╚███║███████╗░░░██║░░░███████╗██████╔╝███████╗░░╚██╔╝░░ 14 | ╚═╝░░╚══╝╚══════╝░░░╚═╝░░░╚══════╝╚═════╝░╚══════╝░░░╚═╝░░░ 15 | THIS CONTRACT IS AVAILABLE FOR EDUCATIONAL 16 | PURPOSES ONLY. YOU ARE SOLELY REPONSIBLE 17 | FOR ITS USE. I AM NOT RESPONSIBLE FOR ANY 18 | OTHER USE. THIS IS TRAINING/EDUCATIONAL 19 | MATERIAL. ONLY USE IT IF YOU AGREE TO THE 20 | TERMS SPECIFIED ABOVE. 21 | */ 22 | 23 | pragma solidity 0.8.4; 24 | import "@openzeppelin/contracts/access/Ownable.sol"; 25 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 26 | import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 27 | 28 | 29 | contract N2DRewards is ERC20, ERC20Burnable, Ownable { 30 | 31 | mapping(address => bool) controllers; 32 | 33 | constructor() ERC20("N2DRewards", "N2DR") { } 34 | 35 | function mint(address to, uint256 amount) external { 36 | require(controllers[msg.sender], "Only controllers can mint"); 37 | _mint(to, amount); 38 | } 39 | 40 | function burnFrom(address account, uint256 amount) public override { 41 | if (controllers[msg.sender]) { 42 | _burn(account, amount); 43 | } 44 | else { 45 | super.burnFrom(account, amount); 46 | } 47 | } 48 | 49 | function addController(address controller) external onlyOwner { 50 | controllers[controller] = true; 51 | } 52 | 53 | function removeController(address controller) external onlyOwner { 54 | controllers[controller] = false; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /NFTStaking.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT LICENSE 2 | 3 | 4 | /* 5 | 6 | Follow/Subscribe Youtube, Github, IM, Tiktok 7 | for more amazing content!! 8 | @Net2Dev 9 | ███╗░░██╗███████╗████████╗██████╗░██████╗░███████╗██╗░░░██╗ 10 | ████╗░██║██╔════╝╚══██╔══╝╚════██╗██╔══██╗██╔════╝██║░░░██║ 11 | ██╔██╗██║█████╗░░░░░██║░░░░░███╔═╝██║░░██║█████╗░░╚██╗░██╔╝ 12 | ██║╚████║██╔══╝░░░░░██║░░░██╔══╝░░██║░░██║██╔══╝░░░╚████╔╝░ 13 | ██║░╚███║███████╗░░░██║░░░███████╗██████╔╝███████╗░░╚██╔╝░░ 14 | ╚═╝░░╚══╝╚══════╝░░░╚═╝░░░╚══════╝╚═════╝░╚══════╝░░░╚═╝░░░ 15 | THIS CONTRACT IS AVAILABLE FOR EDUCATIONAL 16 | PURPOSES ONLY. YOU ARE SOLELY REPONSIBLE 17 | FOR ITS USE. I AM NOT RESPONSIBLE FOR ANY 18 | OTHER USE. THIS IS TRAINING/EDUCATIONAL 19 | MATERIAL. ONLY USE IT IF YOU AGREE TO THE 20 | TERMS SPECIFIED ABOVE. 21 | */ 22 | 23 | pragma solidity 0.8.4; 24 | 25 | import "https://github.com/net2devcrypto/n2dstaking/N2DRewards.sol"; 26 | import "https://github.com/net2devcrypto/n2dstaking/Collection.sol"; 27 | 28 | contract NFTStaking is Ownable, IERC721Receiver { 29 | 30 | uint256 public totalStaked; 31 | 32 | // struct to store a stake's token, owner, and earning values 33 | struct Stake { 34 | uint24 tokenId; 35 | uint48 timestamp; 36 | address owner; 37 | } 38 | 39 | event NFTStaked(address owner, uint256 tokenId, uint256 value); 40 | event NFTUnstaked(address owner, uint256 tokenId, uint256 value); 41 | event Claimed(address owner, uint256 amount); 42 | 43 | // reference to the Block NFT contract 44 | Collection nft; 45 | N2DRewards token; 46 | 47 | // maps tokenId to stake 48 | mapping(uint256 => Stake) public vault; 49 | 50 | constructor(Collection _nft, N2DRewards _token) { 51 | nft = _nft; 52 | token = _token; 53 | } 54 | 55 | function stake(uint256[] calldata tokenIds) external { 56 | uint256 tokenId; 57 | totalStaked += tokenIds.length; 58 | for (uint i = 0; i < tokenIds.length; i++) { 59 | tokenId = tokenIds[i]; 60 | require(nft.ownerOf(tokenId) == msg.sender, "not your token"); 61 | require(vault[tokenId].tokenId == 0, 'already staked'); 62 | 63 | nft.transferFrom(msg.sender, address(this), tokenId); 64 | emit NFTStaked(msg.sender, tokenId, block.timestamp); 65 | 66 | vault[tokenId] = Stake({ 67 | owner: msg.sender, 68 | tokenId: uint24(tokenId), 69 | timestamp: uint48(block.timestamp) 70 | }); 71 | } 72 | } 73 | 74 | function _unstakeMany(address account, uint256[] calldata tokenIds) internal { 75 | uint256 tokenId; 76 | totalStaked -= tokenIds.length; 77 | for (uint i = 0; i < tokenIds.length; i++) { 78 | tokenId = tokenIds[i]; 79 | Stake memory staked = vault[tokenId]; 80 | require(staked.owner == msg.sender, "not an owner"); 81 | 82 | delete vault[tokenId]; 83 | emit NFTUnstaked(account, tokenId, block.timestamp); 84 | nft.transferFrom(address(this), account, tokenId); 85 | } 86 | } 87 | 88 | function claim(uint256[] calldata tokenIds) external { 89 | _claim(msg.sender, tokenIds, false); 90 | } 91 | 92 | function claimForAddress(address account, uint256[] calldata tokenIds) external { 93 | _claim(account, tokenIds, false); 94 | } 95 | 96 | function unstake(uint256[] calldata tokenIds) external { 97 | _claim(msg.sender, tokenIds, true); 98 | } 99 | 100 | function _claim(address account, uint256[] calldata tokenIds, bool _unstake) internal { 101 | uint256 tokenId; 102 | uint256 earned = 0; 103 | 104 | for (uint i = 0; i < tokenIds.length; i++) { 105 | tokenId = tokenIds[i]; 106 | Stake memory staked = vault[tokenId]; 107 | require(staked.owner == account, "not an owner"); 108 | uint256 stakedAt = staked.timestamp; 109 | earned += 100000 ether * (block.timestamp - stakedAt) / 1 days; 110 | vault[tokenId] = Stake({ 111 | owner: account, 112 | tokenId: uint24(tokenId), 113 | timestamp: uint48(block.timestamp) 114 | }); 115 | 116 | } 117 | if (earned > 0) { 118 | earned = earned / 10; 119 | token.mint(account, earned); 120 | } 121 | if (_unstake) { 122 | _unstakeMany(account, tokenIds); 123 | } 124 | emit Claimed(account, earned); 125 | } 126 | 127 | function earningInfo(uint256[] calldata tokenIds) external view returns (uint256[2] memory info) { 128 | uint256 tokenId; 129 | uint256 totalScore = 0; 130 | uint256 earned = 0; 131 | Stake memory staked = vault[tokenId]; 132 | uint256 stakedAt = staked.timestamp; 133 | earned += 100000 ether * (block.timestamp - stakedAt) / 1 days; 134 | uint256 earnRatePerSecond = totalScore * 1 ether / 1 days; 135 | earnRatePerSecond = earnRatePerSecond / 100000; 136 | // earned, earnRatePerSecond 137 | return [earned, earnRatePerSecond]; 138 | } 139 | 140 | // should never be used inside of transaction because of gas fee 141 | function balanceOf(address account) public view returns (uint256) { 142 | uint256 balance = 0; 143 | uint256 supply = nft.totalSupply(); 144 | for(uint i = 1; i <= supply; i++) { 145 | if (vault[i].owner == account) { 146 | balance += 1; 147 | } 148 | } 149 | return balance; 150 | } 151 | 152 | // should never be used inside of transaction because of gas fee 153 | function tokensOfOwner(address account) public view returns (uint256[] memory ownerTokens) { 154 | 155 | uint256 supply = nft.totalSupply(); 156 | uint256[] memory tmp = new uint256[](supply); 157 | 158 | uint256 index = 0; 159 | for(uint tokenId = 1; tokenId <= supply; tokenId++) { 160 | if (vault[tokenId].owner == account) { 161 | tmp[index] = vault[tokenId].tokenId; 162 | index +=1; 163 | } 164 | } 165 | 166 | uint256[] memory tokens = new uint256[](index); 167 | for(uint i = 0; i < index; i++) { 168 | tokens[i] = tmp[i]; 169 | } 170 | 171 | return tokens; 172 | } 173 | 174 | function onERC721Received( 175 | address, 176 | address from, 177 | uint256, 178 | bytes calldata 179 | ) external pure override returns (bytes4) { 180 | require(from == address(0x0), "Cannot send nfts to Vault directly"); 181 | return IERC721Receiver.onERC721Received.selector; 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /NFTStakingV3-BringYourOwnNFTCollection.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT LICENSE 2 | 3 | 4 | /* 5 | 6 | Follow/Subscribe Youtube, Github, IM, Tiktok 7 | for more amazing content!! 8 | @Net2Dev 9 | ███╗░░██╗███████╗████████╗██████╗░██████╗░███████╗██╗░░░██╗ 10 | ████╗░██║██╔════╝╚══██╔══╝╚════██╗██╔══██╗██╔════╝██║░░░██║ 11 | ██╔██╗██║█████╗░░░░░██║░░░░░███╔═╝██║░░██║█████╗░░╚██╗░██╔╝ 12 | ██║╚████║██╔══╝░░░░░██║░░░██╔══╝░░██║░░██║██╔══╝░░░╚████╔╝░ 13 | ██║░╚███║███████╗░░░██║░░░███████╗██████╔╝███████╗░░╚██╔╝░░ 14 | ╚═╝░░╚══╝╚══════╝░░░╚═╝░░░╚══════╝╚═════╝░╚══════╝░░░╚═╝░░░ 15 | THIS CONTRACT IS AVAILABLE FOR EDUCATIONAL 16 | PURPOSES ONLY. YOU ARE SOLELY REPONSIBLE 17 | FOR ITS USE. I AM NOT RESPONSIBLE FOR ANY 18 | OTHER USE. THIS IS TRAINING/EDUCATIONAL 19 | MATERIAL. ONLY USE IT IF YOU AGREE TO THE 20 | TERMS SPECIFIED ABOVE. 21 | */ 22 | 23 | pragma solidity 0.8.4; 24 | 25 | import "https://github.com/net2devcrypto/n2dstaking/N2DRewards.sol"; 26 | import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; 27 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; 28 | 29 | contract NFTStaking is Ownable, IERC721Receiver { 30 | 31 | uint256 public totalStaked; 32 | 33 | // struct to store a stake's token, owner, and earning values 34 | struct Stake { 35 | uint24 tokenId; 36 | uint48 timestamp; 37 | address owner; 38 | } 39 | 40 | event NFTStaked(address owner, uint256 tokenId, uint256 value); 41 | event NFTUnstaked(address owner, uint256 tokenId, uint256 value); 42 | event Claimed(address owner, uint256 amount); 43 | 44 | // reference to the Block NFT contract 45 | ERC721Enumerable nft; 46 | N2DRewards token; 47 | 48 | // maps tokenId to stake 49 | mapping(uint256 => Stake) public vault; 50 | 51 | constructor(ERC721Enumerable _nft, N2DRewards _token) { 52 | nft = _nft; 53 | token = _token; 54 | } 55 | 56 | function stake(uint256[] calldata tokenIds) external { 57 | uint256 tokenId; 58 | totalStaked += tokenIds.length; 59 | for (uint i = 0; i < tokenIds.length; i++) { 60 | tokenId = tokenIds[i]; 61 | require(nft.ownerOf(tokenId) == msg.sender, "not your token"); 62 | require(vault[tokenId].tokenId == 0, 'already staked'); 63 | 64 | nft.transferFrom(msg.sender, address(this), tokenId); 65 | emit NFTStaked(msg.sender, tokenId, block.timestamp); 66 | 67 | vault[tokenId] = Stake({ 68 | owner: msg.sender, 69 | tokenId: uint24(tokenId), 70 | timestamp: uint48(block.timestamp) 71 | }); 72 | } 73 | } 74 | 75 | function _unstakeMany(address account, uint256[] calldata tokenIds) internal { 76 | uint256 tokenId; 77 | totalStaked -= tokenIds.length; 78 | for (uint i = 0; i < tokenIds.length; i++) { 79 | tokenId = tokenIds[i]; 80 | Stake memory staked = vault[tokenId]; 81 | require(staked.owner == msg.sender, "not an owner"); 82 | 83 | delete vault[tokenId]; 84 | emit NFTUnstaked(account, tokenId, block.timestamp); 85 | nft.transferFrom(address(this), account, tokenId); 86 | } 87 | } 88 | 89 | function claim(uint256[] calldata tokenIds) external { 90 | _claim(msg.sender, tokenIds, false); 91 | } 92 | 93 | function claimForAddress(address account, uint256[] calldata tokenIds) external { 94 | _claim(account, tokenIds, false); 95 | } 96 | 97 | function unstake(uint256[] calldata tokenIds) external { 98 | _claim(msg.sender, tokenIds, true); 99 | } 100 | 101 | // @Net2Dev - Follow me on Youtube , Tiktok, Instagram 102 | // TOKEN REWARDS CALCULATION 103 | // MAKE SURE YOU CHANGE THE VALUE ON BOTH CLAIM AND EARNINGINFO FUNCTIONS. 104 | // Find the following line and update accordingly based on how much you want 105 | // to reward users with ERC-20 reward tokens. 106 | // I hope you get the idea based on the example. 107 | // rewardmath = 100 ether .... (This gives 1 token per day per NFT staked to the staker) 108 | // rewardmath = 200 ether .... (This gives 2 tokens per day per NFT staked to the staker) 109 | // rewardmath = 500 ether .... (This gives 5 tokens per day per NFT staked to the staker) 110 | // rewardmath = 1000 ether .... (This gives 10 tokens per day per NFT staked to the staker) 111 | 112 | function _claim(address account, uint256[] calldata tokenIds, bool _unstake) internal { 113 | uint256 tokenId; 114 | uint256 earned = 0; 115 | uint256 rewardmath = 0; 116 | 117 | for (uint i = 0; i < tokenIds.length; i++) { 118 | tokenId = tokenIds[i]; 119 | Stake memory staked = vault[tokenId]; 120 | require(staked.owner == account, "not an owner"); 121 | uint256 stakedAt = staked.timestamp; 122 | rewardmath = 100 ether * (block.timestamp - stakedAt) / 86400 ; 123 | earned = rewardmath / 100; 124 | vault[tokenId] = Stake({ 125 | owner: account, 126 | tokenId: uint24(tokenId), 127 | timestamp: uint48(block.timestamp) 128 | }); 129 | } 130 | if (earned > 0) { 131 | token.mint(account, earned); 132 | } 133 | if (_unstake) { 134 | _unstakeMany(account, tokenIds); 135 | } 136 | emit Claimed(account, earned); 137 | } 138 | 139 | function earningInfo(address account, uint256[] calldata tokenIds) external view returns (uint256[1] memory info) { 140 | uint256 tokenId; 141 | uint256 earned = 0; 142 | uint256 rewardmath = 0; 143 | 144 | for (uint i = 0; i < tokenIds.length; i++) { 145 | tokenId = tokenIds[i]; 146 | Stake memory staked = vault[tokenId]; 147 | require(staked.owner == account, "not an owner"); 148 | uint256 stakedAt = staked.timestamp; 149 | rewardmath = 100 ether * (block.timestamp - stakedAt) / 86400; 150 | earned = rewardmath / 100; 151 | 152 | } 153 | if (earned > 0) { 154 | return [earned]; 155 | } 156 | } 157 | 158 | // should never be used inside of transaction because of gas fee 159 | function balanceOf(address account) public view returns (uint256) { 160 | uint256 balance = 0; 161 | uint256 supply = nft.totalSupply(); 162 | for(uint i = 1; i <= supply; i++) { 163 | if (vault[i].owner == account) { 164 | balance += 1; 165 | } 166 | } 167 | return balance; 168 | } 169 | 170 | // should never be used inside of transaction because of gas fee 171 | function tokensOfOwner(address account) public view returns (uint256[] memory ownerTokens) { 172 | 173 | uint256 supply = nft.totalSupply(); 174 | uint256[] memory tmp = new uint256[](supply); 175 | 176 | uint256 index = 0; 177 | for(uint tokenId = 1; tokenId <= supply; tokenId++) { 178 | if (vault[tokenId].owner == account) { 179 | tmp[index] = vault[tokenId].tokenId; 180 | index +=1; 181 | } 182 | } 183 | 184 | uint256[] memory tokens = new uint256[](index); 185 | for(uint i = 0; i < index; i++) { 186 | tokens[i] = tmp[i]; 187 | } 188 | 189 | return tokens; 190 | } 191 | 192 | function onERC721Received( 193 | address, 194 | address from, 195 | uint256, 196 | bytes calldata 197 | ) external pure override returns (bytes4) { 198 | require(from == address(0x0), "Cannot send nfts to Vault directly"); 199 | return IERC721Receiver.onERC721Received.selector; 200 | } 201 | 202 | } 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nftstaking 2 | 🔥🔥Stake your ERC721 NFTs and issue ERC20 Tokens as Rewards, A Repo to support the full video tutorial series on performing NFT Staking ERC721 and earn ERC20 tokens 3 | 4 | Net2Dev NFT Staking with ERC20 Token Rewards Youtube Video Tutorial Series 5 | https://www.youtube.com/watch?v=i6pPI5phMA0&list=PLLkrq2VBYc1YAIXfxuuh1DohmPZybsELt 6 | 7 | A NFT ERC721 Staking smart contract that will issue reward tokens to all NFT holders. The files 8 | on the repo attached are used to: 9 | 10 | - Deploy the NFT Collection Smart Contract. (Collection.sol) 11 | - Deploy the ERC20 Staking Rewards Token Smart Token Contract. (N2DRewards.sol) 12 | - Deploy the NFT Staking Smart Contract. (NFTStaking.sol) 13 | - Stake and claim the ERC20 Staking Reward Tokens !! 14 | 15 | ** THE FILES ATTACHED TO THIS REPO ARE FOR EDUCATIONAL PURPOSES ONLY ** 16 | ** NOT FINANCIAL ADVISE ** 17 | ** USE IT AT YOUR OWN RISK, I'M NOT RESPONSIBLE FOR ANY USE, ISSUES ETC.. ** 18 | -------------------------------------------------------------------------------- /nftstakingV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT LICENSE 2 | 3 | 4 | /* 5 | 6 | Follow/Subscribe Youtube, Github, IM, Tiktok 7 | for more amazing content!! 8 | @Net2Dev 9 | ███╗░░██╗███████╗████████╗██████╗░██████╗░███████╗██╗░░░██╗ 10 | ████╗░██║██╔════╝╚══██╔══╝╚════██╗██╔══██╗██╔════╝██║░░░██║ 11 | ██╔██╗██║█████╗░░░░░██║░░░░░███╔═╝██║░░██║█████╗░░╚██╗░██╔╝ 12 | ██║╚████║██╔══╝░░░░░██║░░░██╔══╝░░██║░░██║██╔══╝░░░╚████╔╝░ 13 | ██║░╚███║███████╗░░░██║░░░███████╗██████╔╝███████╗░░╚██╔╝░░ 14 | ╚═╝░░╚══╝╚══════╝░░░╚═╝░░░╚══════╝╚═════╝░╚══════╝░░░╚═╝░░░ 15 | THIS CONTRACT IS AVAILABLE FOR EDUCATIONAL 16 | PURPOSES ONLY. YOU ARE SOLELY REPONSIBLE 17 | FOR ITS USE. I AM NOT RESPONSIBLE FOR ANY 18 | OTHER USE. THIS IS TRAINING/EDUCATIONAL 19 | MATERIAL. ONLY USE IT IF YOU AGREE TO THE 20 | TERMS SPECIFIED ABOVE. 21 | */ 22 | 23 | pragma solidity 0.8.4; 24 | 25 | import "https://github.com/net2devcrypto/n2dstaking/N2DRewards.sol"; 26 | import "https://github.com/net2devcrypto/n2dstaking/Collection.sol"; 27 | 28 | contract NFTStaking is Ownable, IERC721Receiver { 29 | 30 | uint256 public totalStaked; 31 | 32 | // struct to store a stake's token, owner, and earning values 33 | struct Stake { 34 | uint24 tokenId; 35 | uint48 timestamp; 36 | address owner; 37 | } 38 | 39 | event NFTStaked(address owner, uint256 tokenId, uint256 value); 40 | event NFTUnstaked(address owner, uint256 tokenId, uint256 value); 41 | event Claimed(address owner, uint256 amount); 42 | 43 | // reference to the Block NFT contract 44 | Collection nft; 45 | N2DRewards token; 46 | 47 | // maps tokenId to stake 48 | mapping(uint256 => Stake) public vault; 49 | 50 | constructor(Collection _nft, N2DRewards _token) { 51 | nft = _nft; 52 | token = _token; 53 | } 54 | 55 | function stake(uint256[] calldata tokenIds) external { 56 | uint256 tokenId; 57 | totalStaked += tokenIds.length; 58 | for (uint i = 0; i < tokenIds.length; i++) { 59 | tokenId = tokenIds[i]; 60 | require(nft.ownerOf(tokenId) == msg.sender, "not your token"); 61 | require(vault[tokenId].tokenId == 0, 'already staked'); 62 | 63 | nft.transferFrom(msg.sender, address(this), tokenId); 64 | emit NFTStaked(msg.sender, tokenId, block.timestamp); 65 | 66 | vault[tokenId] = Stake({ 67 | owner: msg.sender, 68 | tokenId: uint24(tokenId), 69 | timestamp: uint48(block.timestamp) 70 | }); 71 | } 72 | } 73 | 74 | function _unstakeMany(address account, uint256[] calldata tokenIds) internal { 75 | uint256 tokenId; 76 | totalStaked -= tokenIds.length; 77 | for (uint i = 0; i < tokenIds.length; i++) { 78 | tokenId = tokenIds[i]; 79 | Stake memory staked = vault[tokenId]; 80 | require(staked.owner == msg.sender, "not an owner"); 81 | 82 | delete vault[tokenId]; 83 | emit NFTUnstaked(account, tokenId, block.timestamp); 84 | nft.transferFrom(address(this), account, tokenId); 85 | } 86 | } 87 | 88 | function claim(uint256[] calldata tokenIds) external { 89 | _claim(msg.sender, tokenIds, false); 90 | } 91 | 92 | function claimForAddress(address account, uint256[] calldata tokenIds) external { 93 | _claim(account, tokenIds, false); 94 | } 95 | 96 | function unstake(uint256[] calldata tokenIds) external { 97 | _claim(msg.sender, tokenIds, true); 98 | } 99 | 100 | // @Net2Dev - Follow me on Youtube , Tiktok, Instagram 101 | // TOKEN REWARDS CALCULATION 102 | // MAKE SURE YOU CHANGE THE VALUE ON BOTH CLAIM AND EARNINGINFO FUNCTIONS. 103 | // Find the following line and update accordingly based on how much you want 104 | // to reward users with ERC-20 reward tokens. 105 | // I hope you get the idea based on the example. 106 | // rewardmath = 100 ether .... (This gives 1 token per day per NFT staked to the staker) 107 | // rewardmath = 200 ether .... (This gives 2 tokens per day per NFT staked to the staker) 108 | // rewardmath = 500 ether .... (This gives 5 tokens per day per NFT staked to the staker) 109 | // rewardmath = 1000 ether .... (This gives 10 tokens per day per NFT staked to the staker) 110 | 111 | function _claim(address account, uint256[] calldata tokenIds, bool _unstake) internal { 112 | uint256 tokenId; 113 | uint256 earned = 0; 114 | uint256 rewardmath = 0; 115 | 116 | for (uint i = 0; i < tokenIds.length; i++) { 117 | tokenId = tokenIds[i]; 118 | Stake memory staked = vault[tokenId]; 119 | require(staked.owner == account, "not an owner"); 120 | uint256 stakedAt = staked.timestamp; 121 | rewardmath = 100 ether * (block.timestamp - stakedAt) / 86400 ; 122 | earned = rewardmath / 100; 123 | vault[tokenId] = Stake({ 124 | owner: account, 125 | tokenId: uint24(tokenId), 126 | timestamp: uint48(block.timestamp) 127 | }); 128 | } 129 | if (earned > 0) { 130 | token.mint(account, earned); 131 | } 132 | if (_unstake) { 133 | _unstakeMany(account, tokenIds); 134 | } 135 | emit Claimed(account, earned); 136 | } 137 | 138 | function earningInfo(address account, uint256[] calldata tokenIds) external view returns (uint256[1] memory info) { 139 | uint256 tokenId; 140 | uint256 earned = 0; 141 | uint256 rewardmath = 0; 142 | 143 | for (uint i = 0; i < tokenIds.length; i++) { 144 | tokenId = tokenIds[i]; 145 | Stake memory staked = vault[tokenId]; 146 | require(staked.owner == account, "not an owner"); 147 | uint256 stakedAt = staked.timestamp; 148 | rewardmath = 100 ether * (block.timestamp - stakedAt) / 86400; 149 | earned = rewardmath / 100; 150 | 151 | } 152 | if (earned > 0) { 153 | return [earned]; 154 | } 155 | } 156 | 157 | // should never be used inside of transaction because of gas fee 158 | function balanceOf(address account) public view returns (uint256) { 159 | uint256 balance = 0; 160 | uint256 supply = nft.totalSupply(); 161 | for(uint i = 1; i <= supply; i++) { 162 | if (vault[i].owner == account) { 163 | balance += 1; 164 | } 165 | } 166 | return balance; 167 | } 168 | 169 | // should never be used inside of transaction because of gas fee 170 | function tokensOfOwner(address account) public view returns (uint256[] memory ownerTokens) { 171 | 172 | uint256 supply = nft.totalSupply(); 173 | uint256[] memory tmp = new uint256[](supply); 174 | 175 | uint256 index = 0; 176 | for(uint tokenId = 1; tokenId <= supply; tokenId++) { 177 | if (vault[tokenId].owner == account) { 178 | tmp[index] = vault[tokenId].tokenId; 179 | index +=1; 180 | } 181 | } 182 | 183 | uint256[] memory tokens = new uint256[](index); 184 | for(uint i = 0; i < index; i++) { 185 | tokens[i] = tmp[i]; 186 | } 187 | 188 | return tokens; 189 | } 190 | 191 | function onERC721Received( 192 | address, 193 | address from, 194 | uint256, 195 | bytes calldata 196 | ) external pure override returns (bytes4) { 197 | require(from == address(0x0), "Cannot send nfts to Vault directly"); 198 | return IERC721Receiver.onERC721Received.selector; 199 | } 200 | 201 | } 202 | -------------------------------------------------------------------------------- /nftstakingvault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT LICENSE 2 | 3 | 4 | /* 5 | 6 | Follow/Subscribe Youtube, Github, IM, Tiktok 7 | for more amazing content!! 8 | @Net2Dev 9 | ███╗░░██╗███████╗████████╗██████╗░██████╗░███████╗██╗░░░██╗ 10 | ████╗░██║██╔════╝╚══██╔══╝╚════██╗██╔══██╗██╔════╝██║░░░██║ 11 | ██╔██╗██║█████╗░░░░░██║░░░░░███╔═╝██║░░██║█████╗░░╚██╗░██╔╝ 12 | ██║╚████║██╔══╝░░░░░██║░░░██╔══╝░░██║░░██║██╔══╝░░░╚████╔╝░ 13 | ██║░╚███║███████╗░░░██║░░░███████╗██████╔╝███████╗░░╚██╔╝░░ 14 | ╚═╝░░╚══╝╚══════╝░░░╚═╝░░░╚══════╝╚═════╝░╚══════╝░░░╚═╝░░░ 15 | THIS CONTRACT IS AVAILABLE FOR EDUCATIONAL 16 | PURPOSES ONLY. YOU ARE SOLELY REPONSIBLE 17 | FOR ITS USE. I AM NOT RESPONSIBLE FOR ANY 18 | OTHER USE. THIS IS TRAINING/EDUCATIONAL 19 | MATERIAL. ONLY USE IT IF YOU AGREE TO THE 20 | TERMS SPECIFIED ABOVE. 21 | */ 22 | 23 | pragma solidity 0.8.4; 24 | 25 | import "https://github.com/net2devcrypto/n2dstaking/N2DRewards.sol"; 26 | import "https://github.com/net2devcrypto/n2dstaking/Collection.sol"; 27 | 28 | contract NFTStaking is Ownable, IERC721Receiver { 29 | 30 | struct vaultInfo { 31 | Collection nft; 32 | N2DRewards token; 33 | string name; 34 | } 35 | 36 | vaultInfo[] public VaultInfo; 37 | 38 | // struct to store a stake's token, owner, and earning values 39 | struct Stake { 40 | uint24 tokenId; 41 | uint48 timestamp; 42 | address owner; 43 | } 44 | 45 | uint256 public totalStaked; 46 | mapping(uint256 => Stake) public vault; 47 | event NFTStaked(address owner, uint256 tokenId, uint256 value); 48 | event NFTUnstaked(address owner, uint256 tokenId, uint256 value); 49 | event Claimed(address owner, uint256 amount); 50 | 51 | function addVault( 52 | Collection _nft, 53 | N2DRewards _token, 54 | string calldata _name 55 | ) public { 56 | VaultInfo.push( 57 | vaultInfo({ 58 | nft: _nft, 59 | token: _token, 60 | name: _name 61 | }) 62 | ); 63 | } 64 | 65 | function stake(uint256 _pid, uint256[] calldata tokenIds) external { 66 | uint256 tokenId; 67 | totalStaked += tokenIds.length; 68 | vaultInfo storage vaultid = VaultInfo[_pid]; 69 | for (uint i = 0; i < tokenIds.length; i++) { 70 | tokenId = tokenIds[i]; 71 | require(vaultid.nft.ownerOf(tokenId) == msg.sender, "not your token"); 72 | require(vault[tokenId].tokenId == 0, 'already staked'); 73 | 74 | vaultid.nft.transferFrom(msg.sender, address(this), tokenId); 75 | emit NFTStaked(msg.sender, tokenId, block.timestamp); 76 | 77 | vault[tokenId] = Stake({ 78 | owner: msg.sender, 79 | tokenId: uint24(tokenId), 80 | timestamp: uint48(block.timestamp) 81 | }); 82 | } 83 | } 84 | 85 | function _unstakeMany(address account, uint256[] calldata tokenIds, uint256 _pid) internal { 86 | uint256 tokenId; 87 | totalStaked -= tokenIds.length; 88 | vaultInfo storage vaultid = VaultInfo[_pid]; 89 | for (uint i = 0; i < tokenIds.length; i++) { 90 | tokenId = tokenIds[i]; 91 | Stake memory staked = vault[tokenId]; 92 | require(staked.owner == msg.sender, "not an owner"); 93 | 94 | delete vault[tokenId]; 95 | emit NFTUnstaked(account, tokenId, block.timestamp); 96 | vaultid.nft.transferFrom(address(this), account, tokenId); 97 | } 98 | } 99 | 100 | function claim(uint256[] calldata tokenIds, uint256 _pid) external { 101 | _claim(msg.sender, tokenIds, _pid, false); 102 | } 103 | 104 | function claimForAddress(address account, uint256[] calldata tokenIds, uint256 _pid) external { 105 | _claim(account, tokenIds, _pid, false); 106 | } 107 | 108 | function unstake(uint256[] calldata tokenIds, uint256 _pid) external { 109 | _claim(msg.sender, tokenIds, _pid, true); 110 | } 111 | 112 | function _claim(address account, uint256[] calldata tokenIds, uint256 _pid, bool _unstake) internal { 113 | uint256 tokenId; 114 | uint256 earned = 0; 115 | vaultInfo storage vaultid = VaultInfo[_pid]; 116 | for (uint i = 0; i < tokenIds.length; i++) { 117 | tokenId = tokenIds[i]; 118 | Stake memory staked = vault[tokenId]; 119 | require(staked.owner == account, "not an owner"); 120 | uint256 stakedAt = staked.timestamp; 121 | earned += 100000 ether * (block.timestamp - stakedAt) / 1 days; 122 | vault[tokenId] = Stake({ 123 | owner: account, 124 | tokenId: uint24(tokenId), 125 | timestamp: uint48(block.timestamp) 126 | }); 127 | 128 | } 129 | if (earned > 0) { 130 | earned = earned / 10; 131 | vaultid.token.mint(account, earned); 132 | } 133 | if (_unstake) { 134 | _unstakeMany(account, tokenIds, _pid); 135 | } 136 | emit Claimed(account, earned); 137 | } 138 | 139 | function earningInfo(uint256[] calldata tokenIds) external view returns (uint256[2] memory info) { 140 | uint256 tokenId; 141 | uint256 totalScore = 0; 142 | uint256 earned = 0; 143 | Stake memory staked = vault[tokenId]; 144 | uint256 stakedAt = staked.timestamp; 145 | earned += 100000 ether * (block.timestamp - stakedAt) / 1 days; 146 | uint256 earnRatePerSecond = totalScore * 1 ether / 1 days; 147 | earnRatePerSecond = earnRatePerSecond / 100000; 148 | // earned, earnRatePerSecond 149 | return [earned, earnRatePerSecond]; 150 | } 151 | 152 | // should never be used inside of transaction because of gas fee 153 | function balanceOf(address account,uint256 _pid) public view returns (uint256) { 154 | uint256 balance = 0; 155 | vaultInfo storage vaultid = VaultInfo[_pid]; 156 | uint256 supply = vaultid.nft.totalSupply(); 157 | for(uint i = 1; i <= supply; i++) { 158 | if (vault[i].owner == account) { 159 | balance += 1; 160 | } 161 | } 162 | return balance; 163 | } 164 | 165 | // should never be used inside of transaction because of gas fee 166 | function tokensOfOwner(address account, uint256 _pid) public view returns (uint256[] memory ownerTokens) { 167 | vaultInfo storage vaultid = VaultInfo[_pid]; 168 | uint256 supply = vaultid.nft.totalSupply(); 169 | uint256[] memory tmp = new uint256[](supply); 170 | 171 | uint256 index = 0; 172 | for(uint tokenId = 1; tokenId <= supply; tokenId++) { 173 | if (vault[tokenId].owner == account) { 174 | tmp[index] = vault[tokenId].tokenId; 175 | index +=1; 176 | } 177 | } 178 | 179 | uint256[] memory tokens = new uint256[](index); 180 | for(uint i = 0; i < index; i++) { 181 | tokens[i] = tmp[i]; 182 | } 183 | 184 | return tokens; 185 | } 186 | 187 | function onERC721Received( 188 | address, 189 | address from, 190 | uint256, 191 | bytes calldata 192 | ) external pure override returns (bytes4) { 193 | require(from == address(0x0), "Cannot send nfts to Vault directly"); 194 | return IERC721Receiver.onERC721Received.selector; 195 | } 196 | 197 | } 198 | --------------------------------------------------------------------------------