Using RainbowKit and wagmi to check that a wallet owns a specific NFT

When we announced our JellyBots NFT collection, we explained that owning JellyBots will give their holders lifetime access to all the products we create at 0x3 Studio. On our side, that means that we have to implement a web3 login (with MetaMask or any other alternative) on all the products we create and then have some logic to check that the connected wallet owns a particular NFT (in this case, a JellyBot).
The login part was already covered in the article where we described how to create a simple mint website for an NFT collection and it was made very easy thanks to the fantastic RainbowKit library. In this article, we'll focus on the other part: find out if we are the proud owner of a JellyBot.
There are actually multiple ways to do this. We could simply use this endpoint from the OpenSea API (documented here) and check that the assets
array in the response is not empty. I'm personally not a big fan of this solution because it makes us too dependent on OpenSea. If they decide to change their API, revoke our API key, or simply if they are down (which happens quite often), we are screwed. This is a very centralized solution, which is everything web3 doesn't want to be.
A much better solution would be to directly query the JellyBots smart contract. As it's a standard ERC721 smart contract (like most NFT smart contracts these days), we can query the balanceOf
function. The way it works is very simple, we just pass a wallet address and it will return a number that corresponds to the number of JellyBots that this wallet owns. But first, we need to know what's the JellyBots smart contract address. There are probably other methods, but to figure that out, I usually go to the OpenSea asset page and check the "Details" section.

There we can see the contract address and clicking on it will bring us to the contract page on Etherscan. I can then copy the contract address to the clipboard (0x8EFC99918af856699b53DB659533396822f52941
for JellyBots) but also directly interact with the contract. If I call balanceOf
with my wallet address, I can see that I own 1 JellyBot. It works!

Now that we've proven that using the balanceOf
function of the contract is the way to go, let's try to do it with code. For that, we'll use two wagmi hooks: useAccount
and useContractRead
. useAccount
is helpful to know which wallet address is currently connected. We'll then pass this address to a <JellyBotsChecker />
component that will call balanceOf
and display a message telling us if we own at least one JellyBot or not.
Here's what the code looks like:
import { ConnectButton } from "@rainbow-me/rainbowkit";
import type { NextPage } from "next";
import Head from "next/head";
import { useAccount } from "wagmi";
import JellyBotsChecker from "../components/JellyBotsChecker";
import styles from "../styles/Home.module.css";
const Home: NextPage = () => {
const { address } = useAccount();
return (
<div className={styles.container}>
<Head>
<title>RainbowKit App</title>
<meta
name="description"
content="Generated by @rainbow-me/create-rainbowkit"
/>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<ConnectButton showBalance={false} chainStatus="none" />
{address && <JellyBotsChecker address={address} />}
</main>
</div>
);
};
export default Home;
We get address
from the useAccount
hook and then only display the <JellyBotsChecker />
component if address
is defined (= if we are connected).
Now let's have a look at the code for our <JellyBotsChecker />
component...
import { useEffect, useState } from "react";
import { useContractRead, erc721ABI } from "wagmi";
const JellyBotsChecker = ({ address }: { address: string }) => {
const { data } = useContractRead({
addressOrName: "0x8EFC99918af856699b53DB659533396822f52941",
contractInterface: erc721ABI,
functionName: "balanceOf",
args: address,
});
const [hasJellyBot, setHasJellyBot] = useState(false);
useEffect(() => {
if (data) {
setHasJellyBot(data.toNumber() > 0);
}
}, [data]);
return (
<div>
{hasJellyBot ? (
<p>You own a JellyBot! Well done!</p>
) : (
<p>You don't own a JellyBot yet :(</p>
)}
</div>
);
};
export default JellyBotsChecker;
This component's job is just to make a query to the balanceOf
method of the contract and if we have one JellyBot or more, it will display a congratulations message, otherwise, it will show something a bit sadder.
To do that, we need to use the useContractRead
hook from wagmi. It takes a few parameters, including the contract address we want to query (addressOrName
), the function name (functionName
which is balanceOf
in this case) and the arguments (args
which is only the wallet address in this case). It also takes an extra contractInterface
parameter which is the contract ABI in JSON format. It's required because we need to understand how the contract is structured before being able to query it. In this case, as we're querying a contract that follows a widespread standard (ERC721), wagmi provides it to us out of the box (it also provides the ABI for ERC20, which is the standard for fungible tokens).
We then do some operations with useEffect
and useState
in React in order to have a state variable hasJellyBot
which is true or false depending on the balance of the connected wallet. Note that we have to convert data
with toNumber()
to get a number because data
's type is BigNumber
.
And here's the result:

It's nice but how about we make the code we just wrote a React hook, so we can reuse it on other projects? Let's change our JellyBotsChecker
component to use a useNFTChecker
hook that we'll write right after:
import useNFTChecker from "../hooks/useNFTChecker";
const JellyBotsChecker = ({ address }: { address: string }) => {
const { hasNFT: hasJellyBot } = useNFTChecker({
contractAddress: "0x8EFC99918af856699b53DB659533396822f52941",
walletAddress: address,
});
return (
<div>
{hasJellyBot ? (
<p>You own a JellyBot! Well done!</p>
) : (
<p>You don't own a JellyBot yet :(</p>
)}
</div>
);
};
export default JellyBotsChecker;
And here's the source code for our useNFTChecker
hook:
import { useEffect, useState } from "react";
import { useContractRead, erc721ABI } from "wagmi";
const useNFTChecker = ({
contractAddress,
walletAddress,
}: {
contractAddress: string;
walletAddress: string;
}) => {
const { data, error } = useContractRead({
addressOrName: contractAddress,
contractInterface: erc721ABI,
functionName: "balanceOf",
args: walletAddress,
});
const [hasNFT, setHasNFT] = useState(false);
useEffect(() => {
if (data) {
setHasNFT(data.toNumber() > 0);
}
}, [data]);
return { hasNFT, error };
};
export default useNFTChecker;
We decoupled the JellyBots logic from the NFT ownership checking logic. It's much cleaner and now you can reuse the hook wherever you need to use it.
That's it for today! I hope you enjoyed this tutorial but more importantly that you learned a few things along the way.
You can find the code for this small experiment on the GitHub of 0x3 Studio.