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
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
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
- Wallet Connection: Integrates with
ConnectWalletcomponent - Batch Minting: Shows
BatchMintCounterwhen batch limit > 1 - Phase Validation: Checks allowlist/freelist eligibility
- USD Payment: Renders
USDTezosMintButtonfor fiat option - Status Messages: Shows
MintMessagefor restricted phases
Usage
import { TezosMintButton } from '@/components/MintButton/TezosMintButton'
function CollectionPage({ project }) {
return (
<TezosMintButton
project={project}
usdPrice={45.00}
collectionDetail={true}
/>
)
}Implementation Details
// 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
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
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
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_WERT_ENABLED | Enable Wert payments |
NEXT_PUBLIC_WERT_DISABLED_SLUGS | JSON array of disabled project slugs |
NEXT_PUBLIC_WERT_SANDBOX_ENABLED | Use Wert sandbox |
NEXT_PUBLIC_WERT_PARTNER_ID | Wert partner ID |
NEXT_PUBLIC_USD_PRICE_ENABLED | Show USD prices |
BatchMintCounter
File: apps/emprops-studio/components/MintButton/BatchMintCounter.tsx
Quantity selection component for batch minting.
Props
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
<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
interface Props {
isWalletConnected: boolean
isWalletLoading: boolean
price: number
mintState: MintState
blockchain: BlockchainName
balance: number | undefined
isAllowed: boolean
}Label States
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
interface Props {
hasEditionsLeft: boolean // Editions remaining in phase
list: string // "allowlist" or "freelist"
}Messages
| Condition | Message |
|---|---|
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.
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:
{
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).
interface Props {
project: Project
collectionDetail?: boolean
className?: string
}Hooks Used:
useCollectionContract()- Contract instanceuseCollectionConfig()- Batch mint limitsuseCollectionInfo()- Status and mode
Hook Functions
useMintTezosToken
File: apps/emprops-studio/clients/contract-client.ts
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:
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
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
import { atom } from 'jotai'
export const recentlyMintedAtom = atom(false)Usage:
- Set to
trueafter successful mint - Triggers gallery refresh
- Auto-resets after 3 seconds
// In mint handler
setRecentlyMinted(true)
// In gallery component
const [recentlyMinted] = useAtom(recentlyMintedAtom)
useEffect(() => {
if (recentlyMinted) {
refetchGallery()
}
}, [recentlyMinted])Configuration
Batch Mint Configuration
# Per-project batch limit overrides
NEXT_PUBLIC_TEZOS_BATCH_CONFIGURATION='{
"project-slug-1": 5,
"project-slug-2": 10
}'Wert Configuration
# 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=trueError Handling
Common Scenarios
| Error | Cause | UI Response |
|---|---|---|
WALLET_NOT_CONNECTED | No wallet | Show ConnectWallet |
INSUFFICIENT_FUNDS | Low balance | Show warning, allow click |
NOT_ON_ALLOWLIST | Phase restriction | Show MintMessage |
SOLD_OUT | No editions | Disable button |
TX_FAILED | Transaction error | Toast error message |
Transaction Feedback
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>
)
}Related Documentation
- Contract Client - Full client reference
- Wallet Integration - Wallet setup
- Tezos Implementation - System overview
