How to fetch nfts of wallet address
In this blog post we will learn how to fetch nfts of a wallet address with the help of the Alchemy Sdk. If you follow me you know that I launch NW3 a boilerplate to quickly build token gated app.
I need to add a sample page that will list the nfts owned by the connected wallet. Let's how we do this.
First clone the repo and install the dependencies.
git clone https://github.com/0xtiby/nw3 && cd nw3 && yarn
First we will fetch all the nfts from an api routes, create the pages/api/nfts/[address].ts
file and add the following code.
import type { NextApiRequest, NextApiResponse } from 'next'
import { NftsService } from '@/services/nfts'
import { authOptions } from '../auth/[...nextauth]'
import { getServerSession } from 'next-auth/next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'GET') {
res.status(405).end()
return
}
const { address } = req.query
const session = await getServerSession(req, res, authOptions)
if (!session) {
res.status(401).end()
return
}
const nftsService = new NftsService()
const data = await nftsService.getList(address as string)
return res.json(data)
}
In this function first we check that the method of the request is GET
and then we get the session of the user.
If the user is not authenticated we return a 401 status code. If the user is authenticated we call the getList
function of the NftsService
class.
This function will return the list of nfts of the wallet address. I prefer to wrap all the sdk calls in a service class because it's easier to move from the sdk to another one.
Let's create the services/nfts.ts
file and add the following code.
import { Alchemy, Network } from 'alchemy-sdk'
import { config } from '@/config'
export class NftsService {
alchemy: Alchemy
constructor() {
this.alchemy = new Alchemy({
apiKey: config.ALCHEMY_API_KEY,
network: config.TOKEN_GATED_NETWORK,
maxRetries: 0,
})
}
async getList(address: string) {
return this.alchemy.nft.getNftsForOwner(address)
}
}
In this class we create an instance of the Alchemy sdk and we store it in the alchemy
property. We also create a getList
function that will return the list of nfts of the wallet address.
Now that we have the backend stuff we will need to fetch this data in the frontend. To do this we will use a hook named useNfts
that we will create in the hooks/useNfts.ts
file.
import { OwnedNftsResponse } from 'alchemy-sdk'
import { keyStore } from '@/config/keystore'
import { useQuery } from 'wagmi'
export const useNfts = (address: string | undefined) => {
const { data, isLoading, error } = useQuery<OwnedNftsResponse>(
keyStore.nfts.byAddress(address as string).queryKey,
() =>
fetch(`/api/nfts/${address}`, {
headers: { 'Content-Type': 'application/json' },
}).then((res) => {
return res.json()
}),
{ enabled: !!address }
)
return {
nfts: data?.ownedNfts,
total: data?.totalCount,
isLoading,
error,
}
}
This hook use react query to fetch the data. We use the keyStore
to create the query key. We also use the useQuery
hook to fetch the data. We pass the address
as a parameter to the hook and we use it in the route to fetch the api.
Add the queryKey in the config/keystore.ts
file.
import { createQueryKeyStore } from '@lukemorales/query-key-factory'
export const keyStore = createQueryKeyStore({
nfts: {
byAddress: (address: string) => [address],
},
})
For maintainability and to avoid issues with keys in the code I prefer to use a query key factory. It's easier to refactor keys when they are all in the same place.
Now that we have the hook we create the component that nft data, we will also use a placeholder to show a loading state.
We need to install the react-placeholder
package.
yarn add react-placeholder
Then create the following files components/Nft/index.ts
,components/Nft/NftCard.tsx
,components/Nft/Placeholder.tsx
and add the following code.
// components/Nft/NftCard.tsx
import React from 'react'
interface NftCardProps {
thumbnail: string | undefined
name: string | undefined
price: number | undefined
}
export const NftCard: React.FC<NftCardProps> = ({ thumbnail, name, price }) => (
<div className="group relative">
<div className="min-h-80 aspect-w-1 aspect-h-1 lg:aspect-none w-full overflow-hidden rounded-md bg-gray-200 group-hover:opacity-75 lg:h-80">
<img
src={thumbnail}
alt={name}
className="h-full w-full object-cover object-center lg:h-full lg:w-full"
/>
</div>
<div className="mt-4 flex justify-between">
<div>
<h3 className="text-sm text-gray-700">{name}</h3>
<p className="mt-1 text-sm text-gray-500"></p>
</div>
<p className="text-sm font-medium text-gray-900"> {`${price}`}</p>
</div>
</div>
)
// components/Nft/Placeholder.tsx
import ContentLoader from 'react-content-loader'
export const Placeholder = () => (
<ContentLoader
width={800}
height={575}
viewBox="0 0 800 575"
backgroundColor="#f3f3f3"
foregroundColor="#ecebeb"
>
<rect x="12" y="58" rx="2" ry="2" width="211" height="211" />
<rect x="240" y="57" rx="2" ry="2" width="211" height="211" />
<rect x="467" y="56" rx="2" ry="2" width="211" height="211" />
<rect x="12" y="283" rx="2" ry="2" width="211" height="211" />
<rect x="240" y="281" rx="2" ry="2" width="211" height="211" />
<rect x="468" y="279" rx="2" ry="2" width="211" height="211" />
</ContentLoader>
)
// components/Nft/index.ts
export * from './Placeholder'
export * from './NftCard'
And finally the page that will use the hook and the components.
import { NftCard, Placeholder } from '@/components/Nft'
import React, { useState } from 'react'
import { Layout } from '@/components/Layout'
import { locales } from '@/locales'
import { useNfts } from '@/hooks/useNfts'
import { useSession } from 'next-auth/react'
import { withAccountCheck } from '@/hoc/withAccountCheck'
const NftsPage: React.FC = () => {
const { data, status } = useSession()
const { total, nfts, isLoading } = useNfts(data?.user.address)
return (
<Layout title={locales.nfts}>
<div className=" rounded bg-white p-6">
<div className="flex">
<div className="ml-4">
<h2 className="mt-1 text-2xl font-bold tracking-tight text-gray-900">
{`${locales.nfts} (${total})`}
</h2>
<div className="mt-6 grid grid-cols-1 gap-y-10 gap-x-6 sm:grid-cols-2 lg:grid-cols-4 xl:gap-x-8">
{isLoading === false || status === 'unauthenticated' ? (
<Placeholder />
) : (
<>
{nfts?.map((nft) => (
<NftCard
key={`${nft.contract}-${nft.tokenId}`}
name={nft.rawMetadata?.name}
price={nft.contract.openSea?.floorPrice}
thumbnail={nft.media[0].thumbnail}
/>
))}
</>
)}
</div>
</div>
</div>
</div>
</Layout>
)
}
export default withAccountCheck(NftsPage)
AND WE ARE DONE! 🎉
The code for this tutorial is available on the NW3 repository.