Skip to content

Minting Components

Complete documentation for NFT minting UI components in emprops-studio.

Overview

The minting system provides a set of React components for:

  • Native cryptocurrency minting (XTZ, ETH)
  • Fiat payment minting (USD via Wert widget)
  • Batch minting with quantity selection
  • Allowlist/freelist validation
  • Multi-chain support (Tezos, Ethereum, Base)

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                    Minting Component Tree                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                    TezosMintButton                       │    │
│  ├─────────────────────────────────────────────────────────┤    │
│  │  ┌───────────────┐  ┌───────────────┐  ┌─────────────┐ │    │
│  │  │ ConnectWallet │  │BatchMintCounter│  │ MintMessage │ │    │
│  │  └───────────────┘  └───────────────┘  └─────────────┘ │    │
│  │  ┌───────────────────────────────────────────────────┐ │    │
│  │  │              USDTezosMintButton                   │ │    │
│  │  └───────────────────────────────────────────────────┘ │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                  MintButtonEthereumV1                    │    │
│  ├─────────────────────────────────────────────────────────┤    │
│  │  ┌───────────────┐  ┌───────────────┐  ┌─────────────┐ │    │
│  │  │   WalletEth   │  │BatchMintCounter│  │ AllowlistUI │ │    │
│  │  └───────────────┘  └───────────────┘  └─────────────┘ │    │
│  │  ┌───────────────────────────────────────────────────┐ │    │
│  │  │            USDEthereumMintButton                  │ │    │
│  │  └───────────────────────────────────────────────────┘ │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

TezosMintButton

File: apps/emprops-studio/components/MintButton/TezosMintButton.tsx

The primary minting component for Tezos NFTs.

Props

typescript
interface Props {
  project: Project          // Collection data
  usdPrice?: number         // USD equivalent price
  collectionDetail?: boolean // Show in collection detail view
  className?: string        // Additional CSS classes
}

Mint State

typescript
interface MintState {
  isLoading: boolean        // Fetching contract state
  isAllowed: boolean        // User can mint
  isPremint: boolean        // In allowlist phase
  isSoldOut: boolean        // No editions remaining
  isMintInProgress: boolean // Transaction pending
  isFreelist: boolean       // In freelist phase
}

Key Features

  1. Wallet Connection: Integrates with ConnectWallet component
  2. Batch Minting: Shows BatchMintCounter when batch limit > 1
  3. Phase Validation: Checks allowlist/freelist eligibility
  4. USD Payment: Renders USDTezosMintButton for fiat option
  5. Status Messages: Shows MintMessage for restricted phases

Usage

tsx
import { TezosMintButton } from '@/components/MintButton/TezosMintButton'

function CollectionPage({ project }) {
  return (
    <TezosMintButton
      project={project}
      usdPrice={45.00}
      collectionDetail={true}
    />
  )
}

Implementation Details

typescript
// Phase checking logic
const checkMintPhase = async () => {
  const contract = await tezosToolkit.wallet.at(project.mintingContractAddress)
  const storage = await contract.storage()
  const key = await getTezCollectionKey(contract)

  if (key === "projects") {
    const projectData = await storage.projects.get(project.id)
    // Check mode: ALLOWLIST (0), FREELIST (1), ALL (2)
    // Validate user's spot in lists
  } else if (key === "collections") {
    const collectionData = await storage.collections.get(project.projectId)
    // Similar validation for V2 contracts
  }
}

// Mint execution
const handleMint = async () => {
  setMintState(prev => ({ ...prev, isMintInProgress: true }))

  const result = await mintTezosToken({
    project,
    mintQuantity,
    owner: walletState.address
  })

  // Update UI, show toast, refresh gallery
  setRecentlyMinted(true)
}

USDTezosMintButton

File: apps/emprops-studio/components/MintButton/USDTezosMintButton.tsx

Enables USD payment for Tezos NFTs via the Wert widget.

Props

typescript
type Props = {
  project: Project          // Collection data
  disabled?: boolean        // Disable button
  isLoading?: boolean       // Show loading state
  defaultAddress?: string   // Pre-fill recipient address
  usdPrice?: number         // USD price display
  collectionDetail?: boolean // Detail view mode
}

Wert Integration Flow

1. User clicks "Pay with Card"

   ├─▶ Build contract call data (mint_token entrypoint)

   ├─▶ Encode as hex input

   ├─▶ Call selfApi.getTezosSignedData({
   │     owner, price, inputData, contract, network
   │   })

   ├─▶ Mount WertWidget with signed data

   ├─▶ Listen for payment-status events

   └─▶ On success: setRecentlyMinted(true)

Implementation

typescript
const handleWertPayment = async () => {
  // Build contract call
  const inputData = buildMintTokenInput(project.id, uuid())
  const hexInput = Buffer.from(inputData).toString('hex')

  // Get signed transaction data
  const signedData = await selfApi.getTezosSignedData({
    owner: recipientAddress,
    price: String(project.price),
    inputData: hexInput,
    contractAddress: project.mintingContractAddress,
    network: process.env.NEXT_PUBLIC_DAPP_NETWORK
  })

  // Mount Wert widget
  const wertWidget = new WertWidget({
    partner_id: process.env.NEXT_PUBLIC_WERT_PARTNER_ID,
    signed_data: signedData,
    commodity: 'XTZ',
    ...options
  })

  wertWidget.mount()
}

Environment Variables

VariablePurpose
NEXT_PUBLIC_WERT_ENABLEDEnable Wert payments
NEXT_PUBLIC_WERT_DISABLED_SLUGSJSON array of disabled project slugs
NEXT_PUBLIC_WERT_SANDBOX_ENABLEDUse Wert sandbox
NEXT_PUBLIC_WERT_PARTNER_IDWert partner ID
NEXT_PUBLIC_USD_PRICE_ENABLEDShow USD prices

BatchMintCounter

File: apps/emprops-studio/components/MintButton/BatchMintCounter.tsx

Quantity selection component for batch minting.

Props

typescript
interface Props {
  value: number                    // Current quantity
  batchMintLimit: number | undefined // Maximum allowed
  onChange: (counter: number) => void // Change handler
}

Features

  • Increment/decrement buttons
  • Direct number input
  • Enforces min value of 1
  • Enforces max value of batchMintLimit
  • Real-time validation

Usage

tsx
<BatchMintCounter
  value={mintQuantity}
  batchMintLimit={10}
  onChange={setMintQuantity}
/>

UI Structure

┌─────────────────────────────────────┐
│  [-]    [  5  ]    [+]              │
│   ▲        ▲        ▲               │
│   │        │        │               │
│ Minus   Number    Plus              │
│ (min=1) Input   (max=limit)         │
└─────────────────────────────────────┘

MintButtonLabel

File: apps/emprops-studio/components/MintButton/MintButtonLabel.tsx

Dynamic label display for mint button states.

Props

typescript
interface Props {
  isWalletConnected: boolean
  isWalletLoading: boolean
  price: number
  mintState: MintState
  blockchain: BlockchainName
  balance: number | undefined
  isAllowed: boolean
}

Label States

typescript
enum MintLabel {
  MINT = "Mint",
  IS_LOADING = "Loading...",
  IN_PROGRESS = "Minting, please wait",
  IS_SOLD_OUT = "Sold Out",
  INSUFFICIENT_FUNDS = "Mint",
  IS_NOT_ALLOWED = "Pre-Sale users only",
}

State Priority

1. isLoading → "Loading..."
2. isSoldOut → "Sold Out"
3. !isAllowed → "Pre-Sale users only"
4. insufficientFunds → "Mint" (with warning)
5. isMintInProgress → "Minting, please wait"
6. default → "Mint"

MintMessage

File: apps/emprops-studio/components/MintMessage/index.tsx

Status message for restricted mint phases.

Props

typescript
interface Props {
  hasEditionsLeft: boolean  // Editions remaining in phase
  list: string              // "allowlist" or "freelist"
}

Messages

ConditionMessage
hasEditionsLeft=true"Only addresses in the {list} are able to mint."
hasEditionsLeft=false"All editions reserved for the {list} have been minted."

Ethereum Components

MintButtonEthereumV1

File: apps/emprops-studio/components/Blockchain/Ethereum/v1/MintButton.tsx

Ethereum minting with allowlist support.

typescript
interface Props {
  project: Project
  usdPrice?: number
  collectionDetail?: boolean
  className?: string
}

Key Features:

  • Merkle proof fetching for allowlist validation
  • Tracks minted tokens per user
  • Calculates allowlist vs public batch limits
  • Shows allowlist limit reached status

Mint Parameters:

typescript
{
  collectionId: Number(project.projectId),
  owner: address,
  quantity: mintQuantity,
  credentials: {
    proof: allowlistProof || [],
    allowedToMint: allowlistQuantity || 0,
  },
  value: String(project.price * mintQuantity),
}

MintButtonBaseV1

File: apps/emprops-studio/components/Blockchain/Base/v1/MintButton.tsx

Base chain minting (generic EVM).

typescript
interface Props {
  project: Project
  collectionDetail?: boolean
  className?: string
}

Hooks Used:

  • useCollectionContract() - Contract instance
  • useCollectionConfig() - Batch mint limits
  • useCollectionInfo() - Status and mode

Hook Functions

useMintTezosToken

File: apps/emprops-studio/clients/contract-client.ts

typescript
export function useMintTezosToken(project: Project) {
  const clients = useClients()

  return useSWRMutation(
    ['mint-tezos', project.id],
    async (_, { arg }: { arg: MintArgs }) => {
      const toolkit = clients.wallet.getTezosToolkit()
      const contract = await toolkit.wallet.at(project.mintingContractAddress)

      let batch = toolkit.wallet.batch()
      batch = await handleContractsMintBatchs(
        batch,
        contract,
        arg.mintQuantity,
        project,
        arg.owner
      )

      const op = await batch.send()
      await op.confirmation()
      return op
    }
  )
}

handleContractsMintBatchs

Builds batch operations based on contract version:

typescript
export async function handleContractsMintBatchs(
  batch: WalletOperationBatch,
  contract: ContractAbstraction<TaquitoWallet>,
  mintQuantity: number,
  project: Project,
  owner?: string,
): Promise<WalletOperationBatch> {
  const key = await getTezCollectionKey(contract)

  if (key === "projects") {
    // V1: Multiple individual calls
    for (let i = 0; i < mintQuantity; i++) {
      batch.withContractCall(
        contract.methodsObject.mint_token({
          p_id: project.id,
          universal_id: uuid()
        }),
        { amount: project.price, mutez: true }
      )
    }
  } else if (key === "collections") {
    // V2: Single batch call
    batch.withContractCall(
      contract.methodsObject.mint({
        quantityToMint: mintQuantity,
        collectionId: Number(project.projectId),
        owner
      }),
      { amount: project.price * mintQuantity, mutez: true }
    )
  }

  return batch
}

useMintToken (Ethereum)

File: apps/emprops-studio/hooks/contract-client.ts

typescript
export function useMintToken<T>(project: Project) {
  return useSWRMutation(
    ['mint-evm', project.id],
    async (_, { arg }: { arg: T }) => {
      const facade = new ContractFactoryFacade(project.chainId)
      const contract = facade.getCollectionsContract(project.mintingContractAddress)
      return contract.mint(arg)
    }
  )
}

State Management

recentlyMintedAtom

File: apps/emprops-studio/atoms/mint.ts

typescript
import { atom } from 'jotai'

export const recentlyMintedAtom = atom(false)

Usage:

  • Set to true after successful mint
  • Triggers gallery refresh
  • Auto-resets after 3 seconds
typescript
// In mint handler
setRecentlyMinted(true)

// In gallery component
const [recentlyMinted] = useAtom(recentlyMintedAtom)

useEffect(() => {
  if (recentlyMinted) {
    refetchGallery()
  }
}, [recentlyMinted])

Configuration

Batch Mint Configuration

bash
# Per-project batch limit overrides
NEXT_PUBLIC_TEZOS_BATCH_CONFIGURATION='{
  "project-slug-1": 5,
  "project-slug-2": 10
}'

Wert Configuration

bash
# Wert widget settings
NEXT_PUBLIC_WERT_ENABLED=true
NEXT_PUBLIC_WERT_PARTNER_ID=01H...
NEXT_PUBLIC_WERT_SANDBOX_ENABLED=false
NEXT_PUBLIC_WERT_DISABLED_SLUGS='["slug1", "slug2"]'
NEXT_PUBLIC_USD_PRICE_ENABLED=true

Error Handling

Common Scenarios

ErrorCauseUI Response
WALLET_NOT_CONNECTEDNo walletShow ConnectWallet
INSUFFICIENT_FUNDSLow balanceShow warning, allow click
NOT_ON_ALLOWLISTPhase restrictionShow MintMessage
SOLD_OUTNo editionsDisable button
TX_FAILEDTransaction errorToast error message

Transaction Feedback

typescript
const triggerPostMintToast = (
  result: WalletOperationBatch,
  project: Project
) => {
  const tokenIds = txToTokenIds(result)

  toast.success(
    <div>
      Successfully minted!
      <a href={`/token/${project.slug}/${tokenIds[0]}`}>
        View Token
      </a>
    </div>
  )
}

Released under the MIT License.