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.
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.
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).
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!
{
"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.
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>
);
};