Metadata provider

Our smart contract holds a reference to token metadata on IPFS, not the metadata itself. The role of the MetadataProvider is to fetch the content from IPFS and translate it to a usable metadata object.

Create ./components/providers/MetadataProvider.tsx and add the following import statement:

import { createContext, useContext, useCallback } from 'react'

Token metadata should follow the tzip-21 specification. Let's define a subset of the standard properties:

interface ZombieMetadata {
  name: string
  description?: string
  displayUri: string
  thumbnailUri: string
  attributes: {
    alive: boolean
  }
}

We will define a context props interface for every provider. In this case:

interface MetadataProviderContextProps {
  fetchMetadata: (tokenInfo: string) => Promise<ZombieMetadata>
  ipfsUriToGateway: (ipfsUri: string) => string
}

Now we will define a helper function that replaces the IPFS URI with a HTTPS gateway, as most browsers still don't support IPFS natively. This isn't ideal because gateways are more centralised.

const ipfsUriToGateway = (ipfsUri: string) =>
  ipfsUri.replace("ipfs://", "https://cloudflare-ipfs.com/ipfs/")

We will be using next/image components. Since Next 13, the image component allows new customisation and optimisations for a smoother user experience. However, they require us to declare external image sources. Add the following object to next.config.js

images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "cloudflare-ipfs.com",
      },
    ],
  },

As we will reuse the above function a few times, you might prefer to move it into a utilities file rather than importing it's use from this component (as we do in this tutorial)

Create an empty context:

const MetadataContext = createContext<MetadataProviderContextProps>({
  fetchMetadata: function (tokenInfo: string): Promise<ZombieMetadata> {
    throw new Error("Function not implemented.")
  },
  ipfsUriToGateway,
})
const useMetadataContext = () => useContext(MetadataContext)

And now the provider implementation, it simply uses the Fetch API to retrieve a JSON from the gateway and return a ZombieMetadata object.

It could be interesting here to save a cache of the metadata. It will be safe as well, as an IPFS URI is immutable.

const MetadataProvider = ({ children }: { children: React.ReactNode }) => {
  const fetchMetadata = useCallback(async (tokenInfo: string) => {
    if (!tokenInfo.startsWith("ipfs://")) {
      throw new Error("Invalid tokenInfo")
    }
    const res = await fetch(ipfsUriToGateway(tokenInfo))
    const metadata = await res.json()
    console.log(metadata)
    return metadata
  }, [])

  return (
    <MetadataContext.Provider value={{ fetchMetadata, ipfsUriToGateway }}>
      {children}
    </MetadataContext.Provider>
  )
}

export { MetadataProvider, useMetadataContext }
export type { ZombieMetadata }

Remember to include the Metadata provider in the app hierarchy in _app.tsx.

Last updated