Create an ERC721 NFT Staking Smart Contract + Web App

Create an ERC721 NFT Staking Smart Contract + Web App - thirdweb Guides

⚠️ Warning: This guide currently uses v4 of the Connect SDK. For v5 (latest) code snippets, please check out our documentation while this guide is being updated. ⚠️

In this guide, we'll show you how to create and deploy an NFT staking smart contract, where people can stake their NFTs and earn rewards.

We'll walk through:

  1. Creating the ERC721 NFT smart contract
  2. Creating the ERC20 token smart contract
  3. Creating the staking smart contract
  4. Building a frontend web application to interact with the contract

Let's get started!

Deploy an ERC721 NFT Drop Smart Contract

If you don't already have an NFT collection smart contract, you can use the guide below to get yourself started with a gas-optimized ERC721A NFT Collection:

How To Create An NFT Collection (Drop) On Optimism
Learn How To Deploy A Collection Of ERC721 NFTs On the Optimism Network And Create A Web3 Application For Users To Mint NFTs Using thirdweb.
💡
The above guide uses Optimism, but you can use any of our supported networks.

The tokens (NFTs) in this smart contract are what the users will stake to earn ERC20 token rewards.

Deploy an ERC20 Token Smart Contract

Next, let's deploy an ERC20 token smart contract, which represents the token users will be rewarded with for staking their NFTs! To do this, head to the Contracts page in your thirdweb Dashboard and hit "Deploy new contract":

deploy new contract
deploy new contract

You will be taken to our Explore page — where you can browse smart contracts built by the top protocols in web3 and deploy them in just a few clicks!

Note: You can also use the thirdweb CLI to set up a smart contract environment by running the below command from your terminal:

npx thirdweb create contract

This will take you through an easy-to-follow flow of steps for you to create your contract. Learn more about this in our CLI guide.

Otherwise, let's get back to Explore:

thirdweb explore page
thirdweb explore page

In here, select your smart contract of choice. For this guide, we're going to use the Token contract to create our NFT collection:

deploy thirdweb's Token Contract
deploy thirdweb's Token Contract

Set up your smart contract with an image, name, description, etc., and configure which wallet address will receive the funds from primary and secondary sales.

Ensure you deploy this to the same network as your NFT collection.

Minting ERC20 Token Supply

The way the NFTStake.sol smart contract works is by transferring tokens to the user when they claim their rewards. For this reason, we need to provide the staking smart contract with a supply of tokens for it to transfer.

So let's mint some tokens in our ERC20 smart contract and transfer them to the staking contract now. To do this, go to the Tokens tab and click on Mint.

Click "Mint" to create an additional supply of your token.
Click "Mint" to create an additional supply of your token.

Enter an amount and click on Mint tokens:

Mint an additional supply of tokens
Mint an additional supply of tokens

Deploy an NFT Staking Smart Contract

Now, we will move on to the exciting part and deploy the staking contract itself!

Head to the NFTStake smart contract page, and click Deploy Now:

Deploy the ERC721 Staking Contract
Deploy the ERC721 Staking Contract

You will now need to provide some configuration for your staking contract. You can use the information below each field to help you understand what values you need to fill them out with.

Once you're ready, click Deploy Now!

Deposit ERC20 Token

Head back to your ERC20 smart contract and approve tokens for your newly deployed staking smart contract address to spend.

Head over to the explore tab on your Token contract, and click on the approvefunction. We're going to allow the staking contract to spend (amount) of tokens so that it can pay rewards to the stakers.

Enter your staking contract address and an amount that you think is suitable, and click on Execute and approve the transaction.

Approve the staking Contract to spend tokens
Approve the staking Contract to spend tokens

Finally, go to the explore tab of the Staking contract.

Here, you will see a depositRewardTokens function. This is the function we're going to use to supply our staking contract with funds for it to distribute as rewards.

Add the amount of tokens you want to deposit into the staking contract address.

Deposit Reward Tokens into the contract
Deposit Reward Tokens into the contract

Once done click on execute and you are good to go!

Creating a Staking Web Application

Now let's create an NFT staking web app where users can connect their wallets, stake/withdraw NFTs, and claim rewards.

Before we begin, you can access the full source code for this template on GitHub.

Using the thirdweb CLI, create a new Next.js & TypeScript project with the React SDK preconfigured for you, using the following command:

npx thirdweb create app --next --ts

By default the network is Mainnet, you'll need to change it to the network you deployed your smart contracts to inside the _app.tsx file.

// This is the chainId your dApp will work on.
const activeChainId = ChainId.Goerli;
💡
An API key is required to use thirdweb's infrastructure services, such as storage, RPCs, and Smart Wallet infrastructure from within the SDK. If you haven't created a key yet, you can do so for free from the thirdweb dashboard. To use an API key with the React SDK, you will need to add a clientId environment variable which is passed to the ThirdwebProvider.

Showing Owned NFTs of Connected Wallet

We will first show the NFTs that the person holds and allow them to stake these with a button. Let's edit the index.tsx page to add this logic.

Getting the tokens:

const address = useAddress();
const { contract: nftDropContract } = useContract(
  nftDropContractAddress,
  "nft-drop"
);

const { data: ownedNfts } = useOwnedNFTs(nftDropContract, address);

Rendering it:

<div>
  {ownedNfts?.map((nft) => (
    <div key={nft.metadata.id.toString()}>
      <ThirdwebNftMedia metadata={nft.metadata} />
      <h3>{nft.metadata.name}</h3>
      <Web3Button
        contractAddress={stakingContractAddress}
        action={() => stakeNft(nft.metadata.id)}
      >
        Stake
      </Web3Button>
    </div>
  ))}
</div>

We are using the ThirdwebNftMedia component from thirdweb to display the NFT, and we also need to create an stakeNft function that will prompt the user to stake the NFT:

const { contract, isLoading } = useContract(stakingContractAddress);

async function stakeNft(id: string) {
  if (!address) return;

  const isApproved = await nftDropContract?.isApproved(
    address,
    stakingContractAddress
  );
  if (!isApproved) {
    await nftDropContract?.setApprovalForAll(stakingContractAddress, true);
  }
  await contract?.call("stake", [id]);
}

Showing the staked NFTs

Now that we have staked our NFT, we also need to display the staked NFTs, so for that, we will create a new component. So, create a new file components/NFTCard.tsx and add the following:

import {
  ThirdwebNftMedia,
  useContract,
  useNFT,
  Web3Button,
} from "@thirdweb-dev/react";
import type { FC } from "react";
import {
  nftDropContractAddress,
  stakingContractAddress,
} from "../consts/contractAddresses";
import styles from "../styles/Home.module.css";

interface NFTCardProps {
  tokenId: number;
}

const NFTCard: FC<NFTCardProps> = ({ tokenId }) => {
  const { contract } = useContract(nftDropContractAddress, "nft-drop");
  const { data: nft } = useNFT(contract, tokenId);

  return (
    <>
      {nft && (
        <div className={styles.nftBox}>
          {nft.metadata && (
            <ThirdwebNftMedia
              metadata={nft.metadata}
              className={styles.nftMedia}
            />
          )}
          <h3>{nft.metadata.name}</h3>
          <Web3Button
            action={(contract) => contract?.call("withdraw", [nft.metadata.id])}
            contractAddress={stakingContractAddress}
          >
            Withdraw
          </Web3Button>
        </div>
      )}
    </>
  );
};
export default NFTCard;

This gives us a nice card to show our staked NFTs. Now, in pages/stake.tsx we will get the stakedTokenIds and display this:

const { data: stakedTokens } = useContractRead(
  contract,
  "getStakeInfo",
  address
);

<div>
  {stakedTokens &&
    stakedTokens[0]?.map((stakedToken: BigNumber) => (
      <NFTCard tokenId={stakedToken.toNumber()} key={stakedToken.toString()} />
    ))}
</div>

Show claimable rewards

const [claimableRewards, setClaimableRewards] = useState<BigNumber>();

useEffect(() => {
  if (!contract || !address) return;

  async function loadClaimableRewards() {
    const stakeInfo = await contract?.call("getStakeInfo", [address]);
    setClaimableRewards(stakeInfo[1]);
  }

  loadClaimableRewards();
}, [address, contract]);

This will get the stakeInfo from the contract and then set the rewards.

To render it, we need to format it like this:

<p>
  {!claimableRewards
    ? "Loading..."
    : ethers.utils.formatUnits(claimableRewards, 18)}
</p>

Claiming rewards

To allow the users to claim the rewards, we will create a button and attach a function to it.

<Web3Button
  action={(contract) => contract.call("claimRewards")}
  contractAddress={stakingContractAddress}
>
  Claim Rewards
</Web3Button>

And get the function from the useContractWrite hook:

const { mutateAsync: claimRewards } = useContractWrite(
  contract,
  "claimRewards"
);

Conclusion

This guide taught us how to allow your users to stake the NFTs they hold and earn rewards for staking them!

If you did as well, pat yourself on the back and share it with us on the thirdweb Discord! If you want to take a look at the code, check out the GitHub Repository.