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.