Skip to content
Welcome to the new, unified Livepeer documentation! πŸ‘‹
Guides
Mint a Video NFT

Mint a Video NFT

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.

⚠️

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.

Step 1: 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

Step 2: 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>
  );
}

Step 3: 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>
  );
};

Step 4: 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"
  }
}

Step 5: 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>
  );
};