Skip to content
Welcome to the new, unified Livepeer documentation! 👋
Guides
Mint a Video NFT

Mint a Video NFT

Livepeer is blockchain agnostic. The example below shows how to mint a video NFT using either wagmi (opens in a new tab) for EVM-compatible blockchains or aptos (opens in a new tab) for the Aptos blockchain. However, you can use Livepeer to mint NFTs with any blockchain by following the chain-specific NFT standards.

⚠️

This is an extension of the Create Asset example. Please be sure to go through that example before trying this one - you will need an asset ID from that example in this demo.

Adding Dependencies

We first add the required dependencies using npm (or your preferred package manager).

npm i wagmi ethers

We also use RainbowKit (opens in a new tab) to show the connect wallet button, but this can be replaced by any wallet connection provider (e.g. Family's ConnectKit (opens in a new tab)). You can install this with:

npm i @rainbow-me/rainbowkit

Setting Up Providers

We create both a new livepeer.js client (using a CORS-protected API key) and a wagmi client which is configured to interact with our demo NFT contract on the Polygon Mumbai (opens in a new tab) chain. This could be replaced with any EIP-721 (opens in a new tab) or EIP-1155 (opens in a new tab) contract on an EVM-compatible chain.

App.tsx
import {
  LivepeerConfig,
  createReactClient,
  studioProvider,
} from '@livepeer/react';
import { WagmiConfig, chain, createClient } from 'wagmi';
 
const wagmiClient = ...; // set up the wagmi client with RainbowKit or ConnectKit
 
const livepeerClient = createReactClient({
  provider: studioProvider({
    apiKey: process.env.NEXT_PUBLIC_STUDIO_API_KEY,
  }),
});
 
function App() {
  return (
    <WagmiConfig client={wagmiClient}>
      <RainbowKitProvider {...}>
        <LivepeerConfig client={livepeerClient}>
          <CreateAndViewAsset />
        </LivepeerConfig>
      </RainbowKitProvider>
    </WagmiConfig>
  );
}

Add Connect Wallet Button

Now that our providers are set up, we add a connect button which "logs in" a user using their wallet. We use RainbowKit for the wallet connection flow. It integrates easily with wagmi hooks, as well as WalletConnect and Metamask to support a number of popular wallets.

WagmiNft.tsx
import { ConnectButton } from '@rainbow-me/rainbowkit';
import { useAccount } from 'wagmi';
 
export const WagmiNft = () => {
  const { address } = useAccount();
 
  return (
    <div>
      <ConnectButton />
    </div>
  );
};

Upload Asset to IPFS

We then add a feature to let a user upload the Asset to IPFS. Under the hood, the livepeer provider will upload the Asset file to IPFS, then generate ERC-721 compatible metadata (opens in a new tab) in IPFS which points to that Asset's CID.

💡

In this example, the Asset ID is hardcoded in the component for simplicity, but could be dynamic (see the WagmiNft component (opens in a new tab) used for this page, which uses the query string to get the Asset ID).

WagmiNft.tsx
import { useAsset, useUpdateAsset } from '@livepeer/react';
import { ConnectButton } from '@rainbow-me/rainbowkit';
import { useMemo } from 'react';
import { useAccount } from 'wagmi';
 
const assetId = '64d3ddee-c44b-4c9c-8739-c3c530d6dfea';
 
export const WagmiNft = () => {
  const { address } = useAccount();
  const { mutate: updateAsset, status: updateStatus } = useUpdateAsset({
    assetId,
    storage: {
      ipfs: true,
      // metadata overrides can be added here
      // see the source code behind this example
    },
  });
 
  return (
    <div>
      <ConnectButton />
      {address && assetId && (
        <>
          <p>{assetId}</p>
          <button
            onClick={() => {
              updateAsset?.();
            }}
          >
            Upload to IPFS
          </button>
        </>
      )}
    </div>
  );
};

Here is an example (opens in a new tab) of the ERC-721 compatible metadata which will be created in IPFS. The metadata can also be customized to override any of these default fields!

Example IPFS JSON
{
  "name": "Spinning Earth",
  "description": "The Earth is spinning in this amazing video, and the camera is still.",
  "animation_url": "ipfs://bafybeiar26nqkdtiyrzbaxwcdm7zkr2o36xljqskdvg6z6ugwlmpkdhamy/?loop=1&v=efea4eqe0ottx346",
  "external_url": "https://lvpr.tv/?muted=0&v=efea4eqe0ottx346",
  "image": "ipfs://bafkreidmlgpjoxgvefhid2xjyqjnpmjjmq47yyrcm6ifvoovclty7sm4wm",
  "properties": {
    "com.livepeer.playbackId": "efea4eqe0ottx346",
    "video": "ipfs://bafybeiew466bk3caift2gsnzeb23qmzmpqnim32utahanj5f5ks2ycvk7y"
  }
}

Mint a Video NFT

We can now use the NFT metadata CID to mint a video NFT! After the transaction is successful, we show a link to a blockchain explorer so the user can see the blockchain confirmation.

In this example, we rely on usePrepareContractWrite (opens in a new tab) to write to our demo Polygon Mumbai NFT contract (opens in a new tab). This could be replaced by ethers or another library, but wagmi hooks make it easy to read/write with React.

WagmiNft.tsx
import { useAsset, useUpdateAsset } from '@livepeer/react';
import { ConnectButton } from '@rainbow-me/rainbowkit';
import { useRouter } from 'next/router';
 
import { useMemo } from 'react';
import { useAccount, useContractWrite, usePrepareContractWrite } from 'wagmi';
 
// The demo NFT contract ABI (exported as `const`)
// See: https://wagmi.sh/docs/typescript
import { videoNftAbi } from './videoNftAbi';
 
export const WagmiNft = () => {
  const { address } = useAccount();
  const router = useRouter();
 
  const assetId = useMemo(
    () => (router?.query?.id ? String(router?.query?.id) : undefined),
    [router?.query],
  );
 
  const { data: asset } = useAsset({
    assetId,
    enabled: assetId?.length === 36,
    refetchInterval: (asset) =>
      asset?.storage?.status?.phase !== 'ready' ? 5000 : false,
  });
  const { mutate: updateAsset } = useUpdateAsset(
    asset
      ? {
          assetId: asset.id,
          storage: {
            ipfs: true,
            metadata: {
              name,
              description,
            },
          },
        }
      : null,
  );
 
  const { config } = usePrepareContractWrite({
    // The demo NFT contract address on Polygon Mumbai
    address: '0xA4E1d8FE768d471B048F9d73ff90ED8fcCC03643',
    abi: videoNftAbi,
    // Function on the contract
    functionName: 'mint',
    // Arguments for the mint function
    args:
      address && asset?.storage?.ipfs?.nftMetadata?.url
        ? [address, asset?.storage?.ipfs?.nftMetadata?.url]
        : undefined,
    enabled: Boolean(address && asset?.storage?.ipfs?.nftMetadata?.url),
  });
 
  const {
    data: contractWriteData,
    isSuccess,
    write,
    error: contractWriteError,
  } = useContractWrite(config);
 
  return (
    <div>
      <ConnectButton />
      {address && assetId && (
        <>
          <p>{assetId}</p>
            {asset?.status?.phase === 'ready' &&
            asset?.storage?.status?.phase !== 'ready' ? (
              <button
                onClick={() => {
                  updateAsset?.();
                }}
              >
                Upload to IPFS
              </button>
            ) : contractWriteData?.hash && isSuccess ? (
              <a
                target="_blank"
                href={`https://mumbai.polygonscan.com/tx/${contractWriteData.hash}`}
              >
                <button>View Mint Transaction</button>
              </a>
            ) : contractWriteError ? (
              <p>{contractWriteError.message}</p>
            ) : asset?.storage?.status?.phase === 'ready' && write ? (
              <button
                onClick={() => {
                  write();
                }}
              >
                Mint NFT
              </button>
            ) : (
              <></>
            )}
        </>
      )}
    </div>
  );
};