Skip to content
We've just launched - check out our docs! 📗
  • Examples
  • React
  • Mint a Video NFT from an Asset

Mint a Video NFT

The example below shows how to mint a video NFT using either wagmi for EVM-compatible blockchains or aptos 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 to show the connect wallet button, but this can be replaced by any wallet connection provider (e.g. Family's ConnectKit). 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 chain. This could be replaced with any EIP-721 or EIP-1155 contract on an EVM-compatible chain.

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

💡

This example assumes you have chosen a component library with typical components like Button, Box, Flex, etc.

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.

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

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 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 used for this page, which uses the query string to get the Asset ID).

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 (
    <Box>
      <ConnectButton />
      {address && assetId && (
        <>
          <Text>{assetId}</Text>
          <Button
            onClick={() => {
              updateAsset?.();
            }}
          >
            Upload to IPFS
          </Button>
        </>
      )}
    </Box>
  );
};

Here is an example 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!

{
  "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 to write to our demo Polygon Mumbai NFT contract. This could be replaced by ethers or another library, but wagmi hooks make it easy to read/write with React.

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 (
    <Box>
      <ConnectButton />
      {address && assetId && (
        <>
          <Text>{assetId}</Text>
          <Flex>
            {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 ? (
              <Text>{contractWriteError.message}</Text>
            ) : asset?.storage?.status?.phase === 'ready' && write ? (
              <Button
                onClick={() => {
                  write();
                }}
              >
                Mint NFT
              </Button>
            ) : (
              <></>
            )}
          </Flex>
        </>
      )}
    </Box>
  );
};