List Wallet's NFTs with Web3-React & Alchemy SDK

November 1, 2022

4 min read

List Wallet's NFTs with Web3-React & Alchemy SDK
Watch on YouTube

Let's make a React component where you can connect a wallet and see its NFTs. We'll manage the wallet with web3-react and query the owner's NFTs with Alchemy SDK.

import {
  initializeConnector,
  Web3ReactHooks,
  Web3ReactProvider,
} from "@web3-react/core"
import { MetaMask } from "@web3-react/metamask"
import { ComponentWithChildrenProps } from "lib/shared/props"
import { EagerlyConnectWallet } from "./EagerlyConnectWallet"

export const [metaMask, metaMaskHooks] = initializeConnector<MetaMask>(
  (actions) => new MetaMask({ actions })
)

const connectors: [MetaMask, Web3ReactHooks][] = [[metaMask, metaMaskHooks]]

export const WalletProvider = ({ children }: ComponentWithChildrenProps) => {
  return (
    <Web3ReactProvider connectors={connectors}>
      <EagerlyConnectWallet />
      {children}
    </Web3ReactProvider>
  )
}

We start with WalletProvider. Here we initialize connectors and pass them to Web3ReactProvider. For this example, we will add only MetaMask support, but there are plenty of other wallet connectors. By default, the next time we open the app, web3-react won't connect to the previously used wallet, but we can change that using the connector's method, and that's what we do in the EagerlyConnectWallet component.

import { useWeb3React } from "@web3-react/core"
import { useEffect } from "react"

export const EagerlyConnectWallet = () => {
  const { connector } = useWeb3React()

  useEffect(() => {
    connector.connectEagerly?.()
  }, [connector])

  return null
}

Usually we won't handle every EVM chain, and if the user has an unsupported chain, we'll show a warning using the NetworkGuard component. Here we take chainId from the useWeb3React hook and check if it is in the supported chains list. Then, we have AlchemyProvider that takes the key from env variables, chain id from the useWeb3React hook, and creates an instance of alchemy SDK. Alchemy supports a few EVM chains, but not all of them.

import { useWeb3React } from "@web3-react/core"
import { ReactNode } from "react"
import { parseNumericEnum } from "lib/shared/utils/parseNumericEnum"
import { Modal } from "lib/ui/Modal"
import { Text } from "lib/ui/Text"

interface Props {
  children: ReactNode
}

export enum SupportedChain {
  Mainnet = 1,
  Goerli = 420,
}

const { keys: supportedChainNames, values: supportedChainIds } =
  parseNumericEnum(SupportedChain)

export const NetworkGuard = ({ children }: Props) => {
  const { chainId } = useWeb3React()

  if (chainId === undefined) {
    return <>{children}</>
  }

  if (!supportedChainIds.includes(chainId)) {
    return (
      <Modal
        title="Unsupported network"
        renderContent={() => (
          <Text size={18} color="supporting">
            👉 Please change your network to{" "}
            <Text as="span" color="regular">
              {supportedChainNames.join(" or ")}
            </Text>{" "}
          </Text>
        )}
      />
    )
  }

  return <>{children}</>
}

To connect and disconnect a wallet, we have the ManageWallet component. It shows a spinner while the user waits for activation. When the wallet is active, it displays the address and a button to disconnect. Otherwise, there is a button to connect a wallet.

import { useWeb3React } from "@web3-react/core"
import { GhostButton } from "lib/ui/buttons/rect/GhostButton"
import { PrimaryButton } from "lib/ui/buttons/rect/PrimaryButton"
import { Spinner } from "lib/ui/Spinner"
import { HStack } from "lib/ui/Stack"
import { Text } from "lib/ui/Text"

export const ManageWallet = () => {
  const { connector, isActivating, account } = useWeb3React()

  if (isActivating) {
    return <Spinner />
  }

  if (account) {
    return (
      <HStack gap={8} alignItems="center">
        <Text color="supporting3" weight="bold">
          {account}
        </Text>
        <GhostButton
          onClick={() => {
            if (connector?.deactivate) {
              connector.deactivate()
            } else {
              connector.resetState()
            }
          }}
        >
          Disconnect
        </GhostButton>
      </HStack>
    )
  }

  return (
    <PrimaryButton onClick={() => connector.activate()}>
      Connect MetaMask
    </PrimaryButton>
  )
}

We don't want to display NFTs when there is no account, so we wrap it with ConnectedWalletOnly. Then we can use the userAssertAccount hook to receive the wallet's address without checks for a wallet. To receive a list of NFTs, we have the useMyNftsQuery. It leverages the react-hook library and fetches the account's NFTs through Alchemy's SDK. Then we iterate over the list and render the NftItem component inside the grid container. We could have trouble with some images, so we wrap them with SafeImage to not display anything on failing to load.