In this guide we create:
- An ERC-721NFT Drop Smart Contractwithcoined lazilyNFTs users can emboss
- Your ownERC-20Token smart contract
- OnNFT Staking Smart Contractwhere users can wager their NFTs and earn tokens for doing so!
In the end we will have a full stack web application where users can see what NFTs they own, put them on the smart contract and claim their rewards!
Let us do this!
what we build
You can check out a demo of the finished product below:
nft-staking-contract.thirdweb-example.com
Source code
The full source code can be accessed here:
Let's begin!
Our goal with the NFT Drop is to upload all of our NFT metadata and then allow users to come to our site and mint a random NFT from our collection.
We can do this withThirdweb's pre-built NFT drop contract!
Thirdweb's drop contracts lazily create your NFTs and make them available for your users to claim.
That's exactly what we need! Alright, let's go ahead and create our NFT Drop!
Creating the NFT drop contract
Go toThirdweb-Dashboardand connect your wallet.
Then clickProvide new contract.
What we want is theNFT-Dropcontract, let's clickDeploy nowon this one!
You can configure theName,Symbol,description,Bild, androyaltyInformation in settings before deploying your NFT Drop.
I call my NFT Dropcolored shapes
and stick with the default values ββfor the rest of the fields, but feel free to go wild and configure this to your liking!
Once satisfied, let's deploy this NFT drop on theMumbai (MATIC) Testnetz.
This will prompt you to accept a transaction in MetaMask (or any wallet you've connected to) and deploy your smart contract on the Mumbai Test network!
You may find that the MetaMask transaction requests usDeploy Proxy
. What on earth does that mean? Let's briefly explore what happens when we deploy a pre-built contract to Thirdweb.
Ready-made contracts and provision of a proxy
Thirdweb v2 introducedProxy Contracts. See the documentation at for a full breakdownHow Thirdweb pre-packaged contracts work.
In short, most of the smart contract logic was built and deployed by Thirdweb; They are providing one right nowproxy contract, which stores specific informationYOURsmart contract. B. Name, Description, Icon, Owner and Royalty Configuration.
Since all NFT contracts hosted on Thirdweb have the same logic for things like minting and burning NFTs, we can justdelegatethese function calls to the Thirdweb Base Contracts; where the "real" logic lives.
That way it's round10 times cheaperto deploy our smart contracts as we deploy a lot less code and let the Thirdweb base contracts do all the logic for us.
Here's a simple diagram to explain how this works:
Okay, now that we know what we're providing, let'sTo confirmthis transaction and proceed!
Define entitlement phases
claim phasesare conditions we can configure that define when and how users can claim NFTs from our collection.
A popular claim phase pattern is to have a claim phase where allowlisted wallets can claim and then another phase where they can claimanywallet can claim.
Let's click in the Thirdweb dashboardSet entitlement phaseand configure a simple claim stage where anyone can mint/claim our NFTs.
Click onadd stage, and configure it to your liking!
I'll change thoseHow many NFTs can be claimed per transaction?
to be1
, and accept the default values ββfor the other fields.
If you are satisfied with your application phase(s), clickUpdate claim stages
Lazy Minting NFTs
Great, now we've set up a claim phase, users can start claiming NFTs from our collection! So let's create these NFTs now.
ThirdwebsFallencontractsRotten Mintyour NFTs. That means the user whoExpectationsthe NFT is the one that actually shapes the NFT they claim.
If you add NFTs to your Drop contract, they will not be minted at this time. You prepare everything for your users to mint the NFT(s). This means that the user claiming the NFT is the one who mints it and it gets minted in their wallet. By default, the user is the one who pays the gas fees.
So now all we have to do is upload the metadata for our NFTs, such as: B. the images, names and descriptions of each NFT.
How to batch upload NFT metadata
If you don't already have any NFTs that you want to upload, you can use Thirdweb's sample files to create a dummy NFT collection. You can access both from the following links:
Upload your metadata file to the drag-and-drop area along with your images, and voila!
Your NFTs are ready for you to upload:
Make sure you're happy with how it looks, then scroll down and clickNext!
You then have the option to add oneDelayed Reveal, that's up to you! For the purpose of this guide, I will choosePoint to mint.
Then I strikeUpload 30 NFTs!
We're again prompted to approve a transaction, this time the name is a little more obvious - we areRotten embossingthe NFTs we just uploaded so I'm clickingTo confirm.
Once the transaction is confirmed we can see all of our NFTs that we just uploadedoverviewNFT Drop tab.
Brilliant! Now our users are ready to mint the NFTs from our collection.
Before we set that up, let's create our ownERC-20-Tokenalso.
Creating our token is even easier than creating our NFT drop, we follow the same path of clickingProvide new contractandDeploy now, except this time we use aSignContract instead of an NFT drop.
I configure my token with these settings and clickDeploy now, re-deploying on theMumbai (MATIC)test network.
Once we've created our token, let's mint the initial endowment so we can later reward users for staking their NFTs. We can easily do that on the dashboard by clickingmintKnopf.
For the sake of simplicity, I first create 100 of these tokens per clickcoin tokens, and approve themint tooTransaction in MetaMask.
Big! Now we have 100 tokens! In the next step we will be able to transfer these to our staking smart contract so that they can be sent as rewards through the contract.
Now let's create our staking smart contract!
The purpose of our staking contract is to store users' NFTs from our NFT drop contract and reward them with tokens from our token contract.
The longer the user's NFT stake, the more tokens they will be rewarded.
To be fair where it's due, most of this code for this staking contract comes from this repo:
But we will go through step by step how to build and understand the features of this contract, and addProvide third webto the contract so that we:
- Configure and implement it from the Thirdweb dashboard
- Generate TypeScript functions to interact with our contract
- Use the Thirdweb React and TypeScript SDKs to easily consume the contract and blockchain.
Build the project
For this guide I useNext.js&Typescriptto set up the project and we create the smart contract directly in the project for the sake of simplicity.
Feel free to use the tools you are familiar with or follow the steps in this guide to end up with the same result!
Initialize the Next.JS project with Thirdweb
To create a new project using Next.JS, TypeScript and Thirdweb, we can use Thirdwebscreate-thirdweb-dapp
CLI-Tool:
npx create-thirdweb-dapp --next --ts
Change to the newly cloned repo directory:
CD.\nft-staking-app\
Now we have a simple example of Next.JS + Thirdweb + Typescript setup where we can use Thirdweb's helpful hooks to connect, disconnect and display our MetaMask wallet on the homepage.
Writing the staking contract
We're going to use some of the [OpenZeppelin Contracts] within our contract, so we need to install those@openzeppelin/contracts
Package in our project:
npm install @openzeppelin/contracts
First, let's create a new file in the root of our project calledStakingContract.sol
, which is a Solidity file.
If you're new to Solidity, I'd recommend installing an extension that offers syntax highlighting, as it's called heresolidityto make it much easier to read.
Let's define our license, imports and contracts in this file:
// SPDX license identifier: MITPragma solidity^0.8.4;import "@openzeppelin/contracts/token/ERC20/IERC20.sol";import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";import "@openzeppelin/contracts/token/ERC721/IERC721.sol";import "@openzeppelin/contracts/security/ReentrancyGuard.sol";contract ERC721Staking is ReentrancyGuard {}
Now let's go through how thatstaking contractis structured and write it down step by step!
First, let's create some variables in which we will store the contract address of theNFT collection
andSign
that we created earlier:
// We do this so we can use the safeTransfer functionuse SafeERC20 Pro IERC20;// Interfaces for ERC20 and ERC721IERC20Publicity immutableReward token; IERC721Publicity immutablenftCollection;
We can use aStructure
to store information about a staked token:
Structure StakedToken{addressCalls;uint256tokenId; }
Now let's do oneStructure
calledCalls
, which presents the information we need about a wallet putting its NFTs on our contract:
// Information about the deployment Structure Calls{// Amount of tokens staked by the staker uint256AmountStaked;// Gestackte Token-IDsStakedToken[] StakedTokens;// Lastly, the bonuses were calculated for this user uint256timeOfLastUpdate;// Calculated but unclaimed rewards for the user. The rewards are // will be calculated every time the user writes to the smart contract uint256unclaimed rewards; }
Next we configure how many tokens we want to reward perHourthe NFT was staked:
// Rewards per hour per deposited token in Wei. uint256 Privaterewards per hour= 100000;
Somemapping
s store knowledge about:
- Wallet-Adresse an
Calls
(so we know which wallet is which staker) - Token ID to wallet address (so we know which NFT is being staked by which wallet)
// Mapping the user address to the staker info mapping(address =>Calls)PublicityStriking;// Mapping the token ID to the staker. Made for the SC to remember // Who to send the ERC721 token back to. mapping(uint256 => address)PublicitystakerAddress;
Theconstructor
, which allows us to set the value of the NFT capture contract address and the token contract address when we deploy the contract (we will do this on the Thirdweb dashboard);
// Constructor function to set the reward token and NFT collection addresses constructor(IERC721 _nftCollection, IERC20 _rewardsToken){ nftCollection=_nftCollection; RewardToken=_rewardsToken; }
Now for the features!
First up is a feature that allows the usermission
ihre NFT.
This function must also store the knowledge of which wallet address has which token IDs in the walletmapping
s we created:
// If the address has already staked ERC721 token/s, calculate the rewards. // Increase the staked amount and map msg.sender to the stake's token id // Token to send back later on payout. Finally, give timeOfLastUpdate the // value from now. function mission(uint256_tokenId)extern not reentrant {// If the wallet has tokens staked, calculate the rewards before adding the new token if(Striker[news.Sender].AmountStaked> 0) {uint256Reward=calculaterewards(news.Sender); strikernews.Sender].unclaimedRewards+=Reward; }// Wallet must own the token you are trying to stake require(nftCollection.ownerOf(_tokenId)== news.Sender,"You don't own this token!");// Transfer the token from the wallet to the smart contractnftCollection.transferFrom(news.Sender,address(this), _tokenId);// Reset stakedTokenStakedTokenMemoryStakeToken=StakedToken(news.Sender, _tokenId);// Add the token to the stakedTokens arraystrikernews.Sender].stakedTokens.to press(stakedToken);// Increase the bet amount for this walletstrikernews.Sender].AmountStaked++;// Update the mapping of the TokenId to the staker's addressstakerAddress[_tokenId]= news.Sender;// Update timeOfLastUpdate for the stakerstrikernews.Sender].timeOfLastUpdate= Block.time stamp; }
A feature that allows the userwithdraw
the NFTs they staked:
// Check if the user has wagered ERC721 tokens and if he tried to withdraw, // Calculate the rewards and store them in the unclaimedRewards // Decrement the user's amount and return him the ERC721 token function withdraw(uint256_tokenId)extern not reentrant {// Make sure the user has wagered at least one token before withdrawing require(Striker[news.Sender].AmountStaked> 0,"You have not placed any tokens");// Wallet must hold the token they are trying to withdraw require(StrakerAddress[_tokenId]== news.Sender,"You don't own this token!");// Update the rewards for this user as the number of rewards decreases with fewer tokens. uint256Reward=calculaterewards(news.Sender); strikernews.Sender].unclaimedRewards+=Reward;// Find the index of this token id in the stakedTokens array uint256Index= 0;Pro(uint256I= 0; I<strikernews.Sender].stakedTokens.Long; I++) {if(Striker[news.Sender].stakedTokens[i].tokenId==_tokenId&&strikernews.Sender].stakedTokens[i].stakedTokens!= address(0) ) { Index=I;interruption; } }// Set this token's .staker file to address 0 to mark it as unstakedstrikernews.Sender].stakedTokens[index].stakes= address(0);// Decrease the bet amount for this walletstrikernews.Sender].AmountStaked--;// Update the mapping of the tokenId to the be address (0) to indicate that the token is unstakedstakerAddress[_tokenId]= address(0);// Transfer the token back to the withdrawernftCollection.transferFrom(address(this),news.Sender, _tokenId);// Update timeOfLastUpdate for the off hookstrikernews.Sender].timeOfLastUpdate= Block.time stamp; }
Finally we have a function for the useraccept reward
so they can be rewarded with the tokens we created for staking their NFT:
// Calculate rewards for the msg.sender, check if there are any rewards // claim, set unclaimedRewards to 0 and transfer the ERC20 reward token // for the user. function accept reward()extern {uint256Reward=calculaterewards(news.Sender)+strikernews.Sender].unclaimedRewards;require(Reward> 0,"You have no rewards to claim"); strikernews.Sender].timeOfLastUpdate= Block.time stamp; strikernews.Sender].unclaimedRewards= 0; RewardsToken.safeTransfer(news.Sender, Reward); }
These are the core pieces of our contract, but you may have noticed that there is a feature we call namedCalculate bonuses
.
This function calculates the time that has elapsed since the contract last checked thisCalls
's calculated rewards.
This feature isintern, which means it's not open to the public, only other functions within that smart contract.
// Calculate rewards for param _staker by calculating elapsed time // since last update in hours and multiply by ERC721 tokens staked // und RewardsPerHour. function Calculate bonuses(address_Calls)intern outlook returns(uint256_Reward){return((( ((Block.time stamp -Strikers[_staker].timeOfLastUpdate)*Player[_Player].AmountStaked) )*rewards per hour)/ 3600); }
Amazing work! That's the whole logic of our contract, now we just have to add somethingoutlook
s so that we can retrieve this information.
We'll add a view to read the token IDs that a user currently has:
function getStakedTokens(address_User)Publicity outlook returns(StakedToken[]Memory){// Check if we know this user if(stakers[_user].amountStaked> 0) {// Return all tokens in the stakedToken array for this user that are not -1StakedToken[]Memory_stakedTokens= NeuStakedToken[](stakers[_user].amountStaked);uint256_Index= 0;Pro(uint256j= 0; j<stakers[_user].stakedTokens.Long; j++) {if(stakers[_user].stakedTokens[j].staker!=(address(0))) { _stakedTokens[_index]=stakers[_user].stakedTokens[j]; _Index++; } }return_stakedTokens; }// Otherwise return an empty array anders{return NeuStakedToken[](0); } }
Another to read the available rewards a user has:
function available awards(address_Calls)Publicity outlook returns(uint256){uint256Reward=computeRewards(_staker)+Stakers[_staker].unclaimedRewards;returnReward; }
That's it! Now we can upload our contract to the Thirdweb dashboard using Thirdweb Deploy.
Upload the contract with Thirdweb deploy
Now we can upload our contract to Thirdweb.
Let's run the following from the command line:
npx Thirdweb deployment
The result you see should look like this:
π Thirdweb-Cli v0.4.53πWARNING Unable to recognize projectTyp, Fallback to Solc CompilationINFO Recognized Thirdweb Contracts:"ERC721Staking"INFO Project compiled successfullyINFO Uploading contract data...INFO Uploaded successfullyINFO Open this link to deploy your contracts: https://thirdweb.com/contracts/deploy?ipfs=<your-ipfs-url-here>
If everything was successful, you can open this URL in your browser, which will take you to a page where you can provide your contract. No private key is required!
Let's clickDeploy nowand put our staking contract on theMumbai (MATIC)test network.
Here we have to fill in the required fields that we have defined in ourconstructor
.
When you return to the dashboard, you can access the contract addresses of your NFT collection and your token contracts and insert them into the contract parameters here:
You've just deployed your own custom smart contract in a few clicks!
Let's see how we can integrate it into our application now!
Our generated SDK
If you look at themCodeNow click on the Thirdweb dashboard and see a fully generated list of features that we can interact with just by using the Thirdweb SDK!
You can see all the functions we wrote howmission
,withdraw
, andaccept reward
.
The best part is that we don't need to use any other tools to interact with our smart contract, we can just use TypeScript! The Thirdweb SDKs do the rest.
Now let's build the front-end of this application for our users to claim and stake their NFTs and start earning rewards!
Creating the frontend application
In this guide, I use CSS modules to style the app. If you want to use the same styles as me, feel free to create a filestyles/Home.module.css
andstyles/globals.css
and use the files provided in itsample repository.
Remember to import these files if needed.
Let's go first_app.tsx
and configure theactiveChainId
to be setChainId.Mumbai;
, so:
artactiveChainId = ChainId.Mumbai;
Then visit our website atindex.tsx
, and create a simple page that allows users to navigate to two different pages:
- That
/Mint
Route where they can use our NFT collection contract to mint one of our lazy minted NFTs from the collection. - That
/Mission
Route where they can see which NFTs they own from this collection, put their NFT on the contract and claim their rewards.
Here's the code for it, with the styling removed for simplicity:
import{useRouter}von "next/router";arthome =() =>{artrouter = useRouter();return(<div> <h1>Thirdweb Deploy - Custom staking contract</h1> <div onClick={()=>router.push(`/mint`)}><h2>Mint a new NFT</h2> <p>Use the NFT Drop contract to claim an NFT from the collection.</p> </div> <div onClick={()=>router.push(`/stake`)}><Bild Those={`/Symbol/token.webp`}alt="fallen"/> <h2>Deploy your NFTs</h2> <p>Use the custom staking contract provided via<b>Thirdweb Deployment</b>{" "} to stake your NFTs and earn tokens<b>Sign</b>Contract.</p> </div> </div>);};Export OriginallyAt home;
With some additional styles and images it looks like this:
Now we need to create these pages. In yourpages
folder, create two new files namedmint.tsx
andstake.tsx
, these will be separate routes where we can handle the logic for embossing and staking.
mint side
The Mint page shows the user a button to claim the next available NFT in our NFT drop. Since we lazily coined these NFTs, the user will be the one doing itmint
s the NFT and pay the gas fee for coining.
Now let's go through the parts of this claims page!
First, let's create a functional component and import the necessary parts of the SDK to make this work:
import{ useAddress, useMetamask, useNFTDrop }von "@thirdweb-dev/react";import{useRouter}von "next/router";artmint =() =>{artrouter = useRouter();return(<div> </div>);};Export OriginallyMint;
Let's then import the helpful hooks from the@thirdweb-dev/react
Package:
// Get the address of the currently connected wallet artAdresse = useAddress();// Function to connect to the user's metamask wallet artconnectWithMetamask = useMetamask();// Get the NFT capture contract artnftDropContract = useNFTDrop("<your-NFT-drop-contract-address-here");
Now we have our NFT drop contract saved in oursnftDropContract
variable, just by using a line of code! How cool is that!
We need a function that allows users to claim an NFT from our collection. This function displaysAlarm
if the claim is successful or if something goes wrongcatch
Block. Once the user closes the successful notification, we navigate them to/Mission
Route so they can use their NFT if they want!
asynchronous function ClaimNft(){To attempt{arttx =expectnftDropContract?.claim(1);console.log(tx); Alarm("NFT Claimed!"); router.push(`/insert`); }catch(Error) {console.error(error); warning (error); } }
Now for the UI! We need to make sure the user is connected to the website with their wallet before minting; Otherwise the function will not work as there is no connected wallet.
For this we use aternaries Operatora showconnect wallet
Button if there is no connected wallet (withaddress
) or show aRequest an NFT
button if availableisa connected wallet:
<> {!Address? (<Knopf onClick={connectWithMetamask}>connect wallet</Knopf>) : (<Knopf onClick={()=>ClaimNft()}> Claim an NFT</Knopf>)} </div>
Big! Now with some extra styling, we can both seeconnect wallet
Outlook:
And theRequest an NFT
Outlook:
when we clickconnect wallet, we are prompted to connect our MetaMask wallet to this website, can then claim an NFT and are redirected to/Mission
Route after approval ofclaim
Transaction.
Now we're on the/Mission
Route, let's code this part!
stake page
The Stakes page will have three sections:
- Information about the
Sign
- Gestakte NFTs
- Unstaked NFTs
First, let's import all the necessary functions for this page:
import{ ThirdwebNftMedia, useAddress, useMetamask, useNFTDrop, useToken, useTokenBalance, useOwnedNFTs, useContract,}von "@thirdweb-dev/react";import{ BigNumber, Ether }von "Ether";importType { next page }von "next";import{ useEffect, useState }von "react";importStilevon "../styles/Home.module.css";
And we create some variables for our contract addresses so that we can easily access them in this file:
artnftDropContractAddress ="<your-nft-drop-contract-address>";arttokenContractAddress ="<your-token-contract-address>";artStakingContractAddress ="<your staking contract address>";
Now let's create the same structure withuse address
andUse metamask
to ensure the user has their wallet connected before we attempt to retrieve information:
artInsert: NextPage =() =>{// Wallet connection hooks artAdresse = useAddress();artconnectWithMetamask = useMetamask();
Connect to all our contracts:
// contract hook artnftDropContract = useNFTDrop(nftDropContractAddress);arttokenContract = useToken(tokenContractAddress);art{ contract, loading} = useContract(stakingContractAddress);
Use some helpful hooks to load:
- Balance this user has from this token
- The NFTs they have from this NFT collection:
// Load the token's balance art{Data: tokenBalance } = useTokenBalance(tokenContract, address);// Load unstaked NFTs art{Data: ownNfts } = useOwnedNFTs (nftDropContract, Adresse);
You'll find that we use them.Phone call
Function to call one of the functions we wrote for the custom contract.
Load this user's staked NFTs if there is a connected wallet:
art[stakedNfts, setStakedNfts] = useState<any[]>([]);useEffect(() =>{if(!Contract)return;asynchronous function loadStakedNfts(){artstakedTokens =expectcontract?.call("getStakedTokens", address);// Get metadata for the NFT for each staked token artstakedNfts =expect Promise.all( stakedTokens?.map(asynchronous(stakedToken: {Calls: string; tokenId: BigNumber }) => {artnf =expectnftDropContract?.get(stakedToken.tokenId);returnnft; } ) ); setStakedNfts(stakedNfts);console.Protocol("setStakedNfts", stakedNfts); }if(address) { loadStakedNfts(); } }, [address, contract, nftDropContract]);
Reload the available rewards for this user.Phone call
:
art[claimableRewards, setClaimableRewards] = useState<BigNumber>(); useEffect(() =>{if(!contract || !address)return;asynchronous function loadClaimableRewards(){artcr =expectcontract?.call("Available Awards", address); setClaimableRewards(cr); } LoadClaimableRewards(); }, [address, contract]);
stake function call:
So that the contract comes aboutTransfer
Our NFTs must have themapproval
to.
Inside themission
Function we check if the smart contract is approved and callsetApprovalForAll
if it doesn't already have permission to transfer NFTs from that wallet for that collection.
asynchronous function PfahlNft(ID: BigNumber){if(!address)return;artis approved =expectnftDropContract?.isApproved( Adresse, StakingContractAddress );// If not approved, request approval if(!is approved) {expectnftDropContract?.setApprovalForAll(stakingContractAddress,true); }artuse =expectcontract?.call("Mission", I would); }
revocation function with.Phone call
:
asynchronous function withdraw(ID: BigNumber){artwithdraw =expectcontract?.call("withdraw", I would); }
Claim Rewards feature with.Phone call
:
asynchronous function withdraw(ID: BigNumber){artwithdraw =expectcontract?.call("withdraw", I would); }
Now we can display all the information we've retrieved on the UI!
State of charge:
if(charged) {return <div>Loading</div>; }
Show a button to connect the wallet if there isn't oneaddress
:
return(<div class name={styles.container}> <h1 class name={style.h1}>Deploy your NFTs</h1> <Std class name={`${styles.dividers} ${styles.spacerOben}`} />{!address ? (<Knopf class name={styles.mainButton} onClick={connectWithMetamask}>connect wallet</Knopf>) : (<>// ... the next blocks of code go here</>)} </div> );
View the connected wallet's claimable rewards and token balance
<h2>Your tokens</h2><div class name={styles.tokenGrid}> <div class name={styles.tokenItem}> <h3 class name={styles.tokenLabel}>Claimable Rewards</h3> <p class name={styles.tokenValue}> <b>{!claimableRewards ? "Laden..." : ethers.utils.formatUnits(claimableRewards, 18)}</b>{" "} {tokenBalance?.symbol}</p> </div> <div class name={styles.tokenItem}> <h3 class name={styles.tokenLabel}>current balances</h3> <p class name={styles.tokenValue}> <b>{tokenBalance?.displayValue}</b>{tokenBalance?.symbol}</p> </div> </div>
Button for users to claim their available rewards:
<button class name={`${styles.mainButton} ${styles.spacerTop}`} onClick={() =>ClaimRewards()} > Claim Rewards </button>
Check out all of their staked NFTs:
Each mapped div contains onewithdraw
button to call thewithdraw
function and pass in the token ID of that element.
<h2>Your deployed NFTs</h2><div class name={styles.nftBoxGrid}>{stakedNfts?.map((nft) => (<div class name={styles.nftBox} key={nft.metadata.id.toString()}> <ThirdwebNftMedia metadata={nft.metadata} class name={styles.nftMedia}/> <h3>{nft.metadata.name}</h3> <Knopf class name={`${styles.mainButton} ${styles.spacerBottom}`}onClick={()=>retire(nft.metadata.id)} > Retire</Knopf> </div>))}</div>
Check out all of theirsobsessedNFTs (NFTs from this collection that are not stacked in the contract):
Each mapped div contains a button that the user can invokePfahlNft
Function that in turn passes the token ID as a parameter.
<h2>Your unused NFTs</h2><div class name={styles.nftBoxGrid}>{ownNfts?.map((nft) => (<div class name={styles.nftBox} key={nft.metadata.id.toString()}> <ThirdwebNftMedia metadata={nft.metadata} class name={styles.nftMedia}/> <h3>{nft.metadata.name}</h3> <Knopf class name={`${styles.mainButton} ${styles.spacerBottom}`}onClick={()=>stakeNft(nft.metadata.id)} > stake</Knopf> </div>))}</div>
You will also find that we use theThirdwebNftMedia
Component that renders the media object of the NFT differently depending on what kind of file type the NFT resolves.
That's it! Now users can see all the NFTs they have claimed from the/Mint
route, click themission
button and start earning rewards!
Give some money to the staking contract
One last thing, we need to give the NFT staking contract some of our tokens so they can distribute them as rewards.
We can do this via the Thirdweb CLI by going to our token contract and clicking theTransferKnopf:
Then paste the address of your staking contract and send it some tokens!
Now it's able to transfer tokens when someone tries to call youaccept reward
Function!
π₯³π
In this guide, we've created three smart contracts and combined them together to create a project that rewards users for holding the NFTs.
We have created all this:
- ERC-721 NFT Drop Contract
- ERC-20 token contract
- An NFT staking contract that rewards players with ERC-20 tokens
Disclaimer:This staking contract has not been audited and should not be used for production environments! Please do your own research and use it at your own risk.