Creating a React minting app

This example uses RainbowKit and Viem adapter. The SDK also works with Ethers v5 via the EthersV5 adapter.

The repository includes a complete example at examples/rainbowkit-mint, demonstrating how to implement minting with Manifold products including Edition Products

Overview

The edition RainbowKit example showcases how to:

  • Connect wallets with RainbowKit + wagmi

  • Mint Edition products through the Manifold Client SDK

  • Run on Next.js 14 with the App Router and TypeScript

  • Display mint progress, costs, and errors in the UI

  • Complete example at examples/rainbowkit-mint

Quick start

  1. Install workspace dependencies

    pnpm install
  2. Create environment variables

    cp .env.example \
       env.local

    Fill in:

    NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=your_walletconnect_project_id
    NEXT_PUBLIC_INSTANCE_ID=your_edition_instance_id
    NEXT_PUBLIC_RPC_URL_SEPOLIA=your_alchemy_rpc_url

    NEXT_PUBLIC_INSTANCE_ID must point to an Edition product you published in Manifold Studio. NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID is optional but required if you want to support WalletConnect wallets. NEXT_PUBLIC_RPC_URL_SEPOLIA your Alchemy Sepolia RPC URL.

  3. Launch the example

pnpm dev

Visit http://localhost:3000 and connect a wallet with RainbowKit’s ConnectButton.

Key implementation steps

Ensure you have the ConnectButton component on your page. This handles wallet connections, which are required to create an Account that the SDK uses for checks and transaction execution.

'use client';

import { ConnectButton } from '@rainbow-me/rainbowkit';

export default function Home() {
  return (
    <main>
      <div>
        <ConnectButton />
      </div>
    </main>
  );
}
  1. Implement Minting Logic in MintButton.tsx

'use client';

import { ConnectButton } from '@rainbow-me/rainbowkit';
import { MintButton } from '@/components/MintButton';

export default function Home() {
  return (
    <main>
      <h1>
        Manifold SDK + RainbowKit
      </h1>

      <div>
        <ConnectButton />
        <MintButton />
      </div>
    </main>
  );
}

Core Steps

a. Create a Manifold Client with a public provider

import { createClient, createPublicProviderWagmi } from '@manifoldxyz/client-sdk';
import { useConfig } from 'wagmi';

// Get the Wagmi config from your React context
const config = useConfig();

// Create a public provider using Wagmi config
const publicProvider = createPublicProviderWagmi({ config });

// Initialize the client
const client = createClient({ publicProvider });

b. Create an Account representing the connected user.

import { createAccountViem } from '@manifoldxyz/client-sdk';
import { useWalletClient } from 'wagmi';

// Get the wallet client from wagmi
const { data: walletClient } = useWalletClient();

// Create the account adapter
const account = createAccountViem({
  walletClient,
});

c. Fetch the product and verify its type

const product = await client.getProduct(INSTANCE_ID) as EditionProduct;

d. Check the product status to ensure it’s still active

const productStatus = await product.getStatus();
if (productStatus !== 'active') {
  throw new Error(`Product is ${productStatus}`);
}

e. Prepare the purchase by specifying the amount

const preparedPurchase = await product.preparePurchase({
  address: address,
  payload: {
    quantity: 1,
  },
});

f. Execute the purchase

const order = await product.purchase({
  account,
  preparedPurchase,
});

Key points:

  • createAccountViem wraps wagmi’s wallet client so the SDK can sign and send transactions on the user’s behalf.

  • preparePurchase performs all eligibility checks (allowlists, supply, promo codes) and returns the total cost breakdown. Supply the same account so balance checks run against the connected wallet.

  • purchase executes the transaction sequence (ERC-20 approvals, mint, etc.) and returns a Receipt with the final transaction hash and minted tokens.

Display token media and on-chain stats

You can enrich the UI with product art and live supply data directly from the SDK:

const product = await client.getProduct(instanceId);

// Off-chain media and metadata (safe to render immediately)
const { asset, title, contract } = product.data.publicData;
const imageUrl = asset.image ?? asset.imagePreview;
const animationUrl = asset.animation ?? asset.animationPreview;

// Fetch on-chain data once (cost, supply, timing)
const onchainData = await product.fetchOnchainData();
const { totalMinted, totalSupply, startDate, endDate, cost } = onchainData;

return (
  <section>
    {imageUrl && <img src={imageUrl} alt={title} />}
    {animationUrl && (
      <video src={animationUrl} autoPlay loop muted playsInline />
    )}

    <dl>
      <dt>Price</dt>
      <dd>{cost.formatted}</dd>
      <dt>Minted</dt>
      <dd>{totalMinted}</dd>
      <dt>Total supply</dt>
      <dd>{totalSupply === -1 ? 'Unlimited' : totalSupply}</dd>
      <dt>Start date</dt>
      <dd>{startDate?.toLocaleString() ?? 'TBD'}</dd>
      <dt>End date</dt>
      <dd>{endDate?.toLocaleString() ?? 'Open'}</dd>
      <dt>Contract</dt>
      <dd>{contract.address}</dd>
    </dl>
  </section>
);

Best practices:

  • Check status with getStatus before attempting a purchase to verify the product is active.

  • Handle ClientSDKError codes for common cases such as ineligibility, sold-out items, or insufficient funds.

  • Call getAllocations when you need to show remaining allowlist spots.

  • Inspect Receipt.order to display which tokens were minted after purchase.

Last updated