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

Adding Dependencies

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

  • Ethereum

  • Aptos

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

Setting Up Ethereum and Aptos Providers

  • Ethereum

  • Aptos

We create both a new React 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.

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

  • Ethereum

  • Aptos

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 in IPFS which points to that asset’s CID.

  • Ethereum

  • Aptos

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).

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 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.

  • Ethereum

  • Aptos

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.

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>
  );
};