How to Create an ERC-20 Token Staking App

How to Create an ERC-20 Token Staking App - thirdweb Guide

This guide will show you how to create your ERC-20 staking web application, which rewards your users with ERC-20 tokens for staking other ERC-20 tokens!

We'll show you how to:

Let's get started!

Deploying & Configuring the Contract

We'll follow the steps below to set up our staking contract ready for users to start interacting with it:

  1. Deploy an ERC-20 token for staking tokens
  2. Deploy an ERC-20 token for reward tokens
  3. Deploy the staking contract
  4. Deposit reward tokens to the staking contract so that rewards are claimable by users

Once that is done, the staking contract is usable; anyone can stake tokens in it and claim their rewards.

Deploying Staking Token Smart Contract

If you don't already have an ERC20 token that you want users to stake, head to the Token smart contract page on the thirdweb dashboard and click on the Deploy Now button.

Enter the metadata of your smart contract, select the network to deploy to, and click on Deploy Now. This should open up your connected wallet and you will be asked to approve a transaction. After approval, the contract will be deployed and you will be redirected to the contract dashboard:

Dashboard for the ERC-20 token contract

We recommend minting a few tokens using the Mint button, as these will be staked in the contract in the next steps.

Take note of the smart contract address, (beneath the name), as we'll need this to deploy our staking contract shortly.

Deploying Reward Token Smart Contract

Create a new token contract that will act as the reward token. Follow the same steps as done for the staking token contract.

Deploying the Staking Smart Contract

Head to the TokenStake contract deployment page and click on Deploy Now. You should now be asked for details about your contract:

Deployment details for ERC-20 staking contract
  • Default Admin: the wallet that is allowed to change configuration of the contract and is the administrator of the contract.
  • Time Units for Rewards: How many seconds before rewards are given. e.g. 1 means every 1 second, a new amount of rewards are provided (you can update this value after deploying)
  • Rewards Ratio Numerator and Rewards Ratio Denominator: e.g. A ratio of 1 / 20 would mean 1 reward token is given every timeUnit for every 20 tokens staked.
  • ERC-20 Staking Token Address: Smart contract address of the ERC20 token you want users to stake to the contract.
  • ERC-20 Reward Token Address: Smart contract address of the ERC20 token you want users to be rewarded for staking.

For example, in the screenshot above, the users stake the 0x97 token; and are awarded 1/2 (half) the number of tokens they have staked of the 0x7F token every 1 second.

Finally, select the network you want to deploy to (this needs to be the same as your other smart contracts), and click Deploy Now!

Depositing Reward Tokens to Staking Contract

Before users can claim rewards, you must deposit sufficient reward tokens into the staking contract. Head to the reward token contract's dashboard, you should see a screen similar to the following:

Reward token dashboard on thirdweb

Click on Mint. You should be prompted with the number of tokens you desire to mint:

Minting additional tokens on the reward token contract

Enter the desired amount and click on Mint Rewards. You should be prompted to approve a transaction.

⚠️
Do not send reward tokens to the staking contract directly without using the depositRewardTokens function, as we will see in the next steps. Any tokens sent directly are not claimable as rewards; these tokens will be lost with no chance of recovering them!

We need to approve the staking contract to access the reward tokens in our wallet. Head to the Explorer tab and choose the approve function:

Approving staking contract as a spender of the reward tokens

In the Spender field, add the staking contract address. In the Amount, enter the number of tokens you wish to deposit with 18 zeroes appended to it (since our token contract uses 18 decimals). So if you are depositing 10,000 tokens, enter 10000000000000000000000. Click on Execute to process the transaction.

Head to the staking contract dashboard's Explore tab and choose the depositRewardTokens function:

Depositing reward tokens to the staking contract

Follow the same instructions above for entering the amount of tokens to deposit. Click on Execute to process the transaction.

Now the tokens deposited by you in the staking contract are claimable as rewards by the users of your contract.

Creating the Staking Application

You can access the full source code for the application on our GitHub.

Now let's create a web application project that allows users to connect their wallet and interact with our smart contracts. In a safe directory, run the following command in your terminal to create an app with our SDK installed and configured:

npx thirdweb@latest create app

On running the above command, you should be presented with an app creation wizard with configuration choices.

thirdweb CLI creating an app

After the app is created, make sure to head to _app.js and change the chain to the network you have your contracts deployed to.

For example, in this case, the contracts have been deployed on Polygon Mumbai so we will use Mumbai.

💡
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, pass the clientId to the ThirdwebProvider.

Connecting to the Smart Contract

Create a new folder called const and create a file named yourDetails.js within this new folder. Add the following line of code to the file:

export const stakingContractAddress = "<staking_contract_address>";

Remember to replace the value of the variable with your staking contract address.

Now, navigate to index.js file in the pages folder and let's load up the staking contract and other data using React hooks.

First, we'll use the useAddress() hook so that we can keep track of the connected wallet address at all times, and initialize a stateful variable called amountToStake that we will later map to a textbox to get input from the user.

const address = useAddress();
const [amountToStake, setAmountToStake] = useState(0);

Next, we'll initialize all our contracts using useContract:

const { contract: staking, isLoading: isStakingLoading } = useContract(
  stakingContractAddress,
  "custom"
);

const { contract: stakingToken, isLoading: isStakingTokenLoading } =
  useContract(stakingTokenAddress, "token");
  
const { contract: rewardToken, isLoading: isRewardTokenLoading } = useContract(
  rewardTokenAddress,
  "token"
);

Now we have our contracts, we can use the useTokenBalance hook to get the connected wallet's balance of both the staking token and reward token:

// Token balances
const { data: stakingTokenBalance, refetch: refetchStakingTokenBalance } =
  useTokenBalance(stakingToken, address);
const { data: rewardTokenBalance, refetch: refetchRewardTokenBalance } =
  useTokenBalance(rewardToken, address);

Finally, we can use the useContractRead hook to load the staking information for the current user, and use a setInterval inside of a useEffect block to re-fetch this data every 10 seconds.

// Get staking data
const {
  data: stakeInfo,
  refetch: refetchStakingInfo,
  isLoading: isStakeInfoLoading,
} = useContractRead(staking, "getStakeInfo", address || "0");

useEffect(() => {
  setInterval(() => {
    refetchData();
  }, 10000);
}, []);

const refetchData = () => {
  refetchRewardTokenBalance();
  refetchStakingTokenBalance();
  refetchStakingInfo();
};

We now have all the data our app with require from the contracts, now we need to display these values and create logic to stake user tokens.

Creating The UI

Now we can use the information we loaded from the hooks to render onto the UI, making use of the ConnectWallet button and Web3Button UI components.

We can add a button to allow user to stake, unstake, and claim their tokens:

<div className={styles.stakeContainer}>
  <input
    className={styles.textbox}
    type="number"
    value={amountToStake}
    onChange={(e) => setAmountToStake(e.target.value)}
  />
  <Web3Button
    className={styles.button}
    contractAddress={stakingContractAddress}
    action={async (contract) => {
      await stakingToken.setAllowance(
        stakingContractAddress,
        amountToStake
      );
      await contract.call(
        "stake",
        ethers.utils.parseEther(amountToStake)
      );
      alert("Tokens staked successfully!");
    }}
  >
    Stake!
  </Web3Button>
  
  <Web3Button
    className={styles.button}
    contractAddress={stakingContractAddress}
    action={async (contract) => {
      await contract.call(
        "withdraw",
        ethers.utils.parseEther(amountToStake)
      );
      alert("Tokens unstaked successfully!");
    }}
  >
    Unstake!
  </Web3Button>
  
  <Web3Button
    className={styles.button}
    contractAddress={stakingContractAddress}
    action={async (contract) => {
      await contract.call("claimRewards");
      alert("Rewards claimed successfully!");
    }}
  >
    Claim rewards!
  </Web3Button>
</div>

And a section to display the information we read from the hooks:

<div className={styles.grid}>
  <a className={styles.card}>
    <h2>Stake token balance</h2>
    <p>{stakingTokenBalance?.displayValue}</p>
  </a>

  <a className={styles.card}>
    <h2>Reward token balance</h2>
    <p>{rewardTokenBalance?.displayValue}</p>
  </a>

  <a className={styles.card}>
    <h2>Staked amount</h2>
    <p>
      {stakeInfo && ethers.utils.formatEther(stakeInfo[0].toString())}
    </p>
  </a>

  <a className={styles.card}>
    <h2>Current reward</h2>
    <p>
      {stakeInfo && ethers.utils.formatEther(stakeInfo[1].toString())}
    </p>
  </a>
</div>

That's it! Your staking app with your custom logic in smart contract is now ready.

Running the app and testing

Run the following command in the terminal to spin up a development server to test out your app:

yarn dev

Then you can go to localhost:3000 in your browser to view the app. If everything is done correctly, you should see a screen like this after connecting your wallet:

Staking app running on browser

Now, you can stake your previously minted staking tokens and watch your reward token grow over time. You can also withdraw your stake and claim your rewards using the buttons we just created.

Conclusion

In this guide, we created an ERC-20 staking app using the prebuilt TokenStake contract by thirdweb.

If you have any questions about this guide, or want to chat, feel free to join our Discord server and reach out to us in the support channel. If you have any feedback, please post it on our feedback board.