In this guide, we will allow users to claim ERC20 tokens in a Next.js app and show the holders of our token!
Intro
Just like our NFT Drop contract, the Token Drop contract makes the token available for users to claim. In other words, the actual minting happens when a user claims the token. In this app a user can claim our token. Right at that time the ERC20 token is minted and sent to the wallet of that user. This app also shows all the wallets that hold this token.
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. Please note, the tokens have to be imported in the wallet to be visible inside the wallet. Once the transaction is processed, the user’s wallet is added to the list of token holders.
Setup
To build this app, we need to walk through the following steps.
- Deploy a Token Drop contract and define the claim phases for your NFTs
- Build the NextJS project (we will be using the starter template thirdweb has made available!)
- Build the front end, including a
Claim
button for the token - Build a list of token holders in our front end
Build
First, deploy a Token Drop
contract from thirdweb’s dashboard by clicking on
+ Deploy new contract
. Navigate to the Token Drop
contract by clicking on Pre-built contracts
and then Release a drop
.
Don't forget to set your claim conditions under Claim Phases
once you have deployed!
Next, go ahead and run the following command to get the Next.js JavaScript starter template.
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;
We will go through the logic but won't show every piece of code. If you want to have a look at the full piece of code, check out the repo!
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!