Build An ERC20 Token Claim App in React

Build An ERC20 Token Claim App in React

⚠️ 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 will build a web application that allows users to claim ERC20 tokens in a Next.js app, and also show all the holders of our token!

Just like our NFT Drop contract, the Token Drop contract makes the token available for users to claim.

Check out our example repository with the project built out on Github and click here to see a live example of the app.

Flow

The user flow of this app is as follows, a user enters the app, connects their wallet and enters the number of tokens they want to claim. Once the claim button is clicked, the transaction is processed and the user receives their tokens!

Setup

To build this app, we need to walk through the following steps.

  1. Deploy a Token Drop contract and define the claim phases for your NFTs using the dashboard
  2. Build the Next.js project (we will be using the starter template thirdweb has made available!)
  3. Build the front end, including a Claim button for the token
  4. Show a list of token holders in our front end

Build

First, deploy a Token Drop contract from the dashboard by clicking on + Deploy new contract.

Once deployed, set your claim conditions under the Claim Phases which outline the rules of how tokens can be claimed.

To set up a project with our Web3 SDK installed, run the following command to get the Next.js JavaScript starter template from the CLI:

npx thirdweb create --next --js

Inside the _app.js file, change the chain ID to the chain you deployed the smart contract to.

import { ChainId, ThirdwebProvider } from "@thirdweb-dev/react";

// This is the chainId your dApp will work on.
const activeChainId = ChainId.Mainnet;

function MyApp({ Component, pageProps }) {
  return (
    <ThirdwebProvider desiredChainId={activeChainId}>
      <Component {...pageProps} />
    </ThirdwebProvider>
  );
}

export default MyApp;

Connect our smart contract

Inside the index.js file import the hook to connect to our Token Drop contract and pass your contract address.

import {useTokenDrop} from "@thirdweb-dev/react";
//add your own contract address below
const tokenDropContract = useTokenDrop(<CONTRACT_ADDRESS>);

Claim component

We’re going to use the useClaimToken hook from the React SDK to allow the connected wallet to claim tokens from the drop.

Because we’re allowing the user to enter the amount, we need to make use of the react hook useState to allow our app to handle the dynamic input.

const address = useAddress();
const [amountToClaim, setAmountToClaim] = useState("");
const { mutate: claimTokens } = useClaimToken(tokenDropContract);

async function claim() {
  if (!amountToClaim || !address) {
    return;
  }

  try {
    claimTokens(
      {
        to: address,
        amount: amountToClaim,
      },
      {
        onSuccess: (data) => {
          console.log("Claimed", data);
          alert("Successfully claimed!");
        },
      },
    );
  } catch (e) {
    console.error(e);
    alert(e);
  }
}

Finally, we bind the method via a function to a button

<button onClick={claim}>

The full code looks something like this 👇

import { useAddress, useClaimToken } from "@thirdweb-dev/react";
import React, { useState } from "react";

export default function Claim({ tokenDropContract }) {
  const address = useAddress();
  const [amountToClaim, setAmountToClaim] = useState("");
  const { mutate: claimTokens } = useClaimToken(tokenDropContract);

  async function claim() {
    if (!amountToClaim || !address) {
      return;
    }

    try {
      claimTokens(
        {
          to: address,
          amount: amountToClaim,
        },
        {
          onSuccess: (data) => {
            console.log("Claimed", data);
            alert("Successfully claimed!");
          },
        },
      );
    } catch (e) {
      console.error(e);
      alert(e);
    }
  }

  return (
    <div>
      <input
        type="text"
        placeholder="Enter amount to claim"
        onChange={(e) => setAmountToClaim(e.target.value)}
      />

      <button onClick={claim}>Claim</button>
    </div>
  );
}

Token Holders

Now to render out a list of our token holders, we’ll use the getAllHolderBalances method.

It’s straightforward, build the function and render it out inside the component like this.

Let’s build out the function first:

const [holders, setHolders] = useState([]);

async function checkHolders() {
  //define the method
  const balances = await token.history.getAllHolderBalances();
  //assign the data to the variable balances
  setHolders(balances);
}

Next, we’ll trigger the function upon loading the page. We can do that with the hook useEffect

useEffect(() => {
  checkHolders();
}, []);

Now we need to render out the data inside our page. We can use a map method to render out all the holders and their balances like this.

{
  holders?.map((holder) => (
    <div key={holder.holder}>
      <p>{holder.holder}</p>
      <p>
        {holder.balance.displayValue} {holder.balance.symbol}
      </p>
    </div>
  ));
}

Here is the full page, including a loading view and sorting of the balances 👇

export default function TokenHolders() {
  const [loading, setLoading] = useState(true);
  const [holders, setHolders] = useState([]);

  async function checkHolders() {
    const sdk = new ThirdwebSDK("mumbai"); // configure this to your network

    const token = sdk.getToken("0xCFbB61aF7f8F39dc946086c378D8cd997C72e2F3");

    const balances = await token.history.getAllHolderBalances();
    setHolders(balances);
    setLoading(false);
  }
  useEffect(() => {
    checkHolders();
  }, []);

  if (loading) {
    return <div>Loading...</div>;
  }

  return (
    <>
      <div>
        {holders
          .sort(
            (a, b) =>
              parseInt(b.balance.displayValue) -
              parseInt(a.balance.displayValue),
          )
          .map((holder) => (
            <div key={holder.holder}>
              <p>{truncateAddress(holder.holder)}</p>
              <p>
                {holder.balance.displayValue} {holder.balance.symbol}
              </p>
            </div>
          ))}
      </div>
    </>
  );
}

That’s it!

That’s how you integrate the Token Drop contract with a NextJS app.

This guide is just an example of how to do it. Again we went over the core logic of the app.

Feel free to go to the repo to get a full copy and start building!