NFT Minting Web Interface: From Contract to Transaction
Note: when a user clicks the Mint button, the frontend connects with the contract via a wallet. Errors like InsufficientFunds or InvalidMerkleProof scare users, and gas wars at peak demand require fast feedback. Without proper handling, a transaction can fail, costing the user $0.5 in wasted gas. We build interfaces that are predictable: wallet connection, condition checks (whitelist, limits, sale status), transaction submission with error decoding, and a final screen with the token. Clients often face incorrect whitelist status checks or wrong gas pricing. Our solution automatically selects optimal gas price via viem and verifies all conditions before submission. Our team has multi-year experience in blockchain development — we have implemented dozens of NFT projects with smart contract integration on Ethereum and EVM-compatible chains. We deliver clean TypeScript code using the latest wagmi and viem libraries.
Smart Contract Preparation
The first step is to study the ABI. A typical contract uses the ERC-721A standard for efficient batch minting. Essential functions: mint, whitelistMint, totalSupply, maxSupply, mintPrice, maxPerWallet, saleState, and numberMinted. Example ABI with viem — read contract state via multicall to fetch all data simultaneously:
// Typical mint contract functions function mint(uint256 quantity) external payable; function whitelistMint(uint256 quantity, bytes32[] calldata proof) external payable; function totalSupply() external view returns (uint256); function maxSupply() external view returns (uint256); function mintPrice() external view returns (uint256); function maxPerWallet() external view returns (uint256); function saleState() external view returns (uint8); // 0=paused, 1=whitelist, 2=public function numberMinted(address owner) external view returns (uint256); For reading state, we use multicall — it fetches totalSupply, maxSupply, mintPrice, maxPerWallet, saleState, and numberMinted for the wallet in a single RPC request. This reduces provider load and speeds up the UI.
Why Merkle Tree Is the Standard for Whitelist?
Storing all whitelist addresses in the contract costs gas at deployment. Merkle tree solves this: only the root is stored in the contract, and the proof is generated on the frontend from the user's address. It is 10x cheaper in gas. Implementation in TypeScript with the merkletreejs library:
// lib/merkle.ts import { MerkleTree } from 'merkletreejs'; import { keccak256, encodePacked } from 'viem'; // allowlist.json — array of addresses from CMS or API import allowlist from '@/data/allowlist.json'; function hashLeaf(address: string): `0x${string}` { return keccak256(encodePacked(['address'], [address as `0x${string}`])); } const leaves = allowlist.map(hashLeaf); const tree = new MerkleTree(leaves, keccak256, { sortPairs: true }); export function getMerkleProof(address: string): `0x${string}`[] { const leaf = hashLeaf(address); return tree.getHexProof(leaf) as `0x${string}`[]; } export function isWhitelisted(address: string): boolean { const leaf = hashLeaf(address); return tree.verify(tree.getHexProof(leaf), leaf, tree.getRoot()); } | Verification method | Deployment gas (10,000 addresses) | Gas per mint | Development complexity |
|---|---|---|---|
| Address array | ~3,000,000 gas (0.1 ETH) | 30,000 gas | Low |
| Merkle Tree | ~300,000 gas (0.01 ETH) | 35,000 gas | Medium |
Deployment savings amount to 0.09 ETH — a strong argument for Merkle Tree. At an average gas of 20 Gwei, that's about $500 saved per deployment. Additionally, each whitelist check becomes cheaper, which is especially noticeable with many users.
What Are the Steps for Interface Integration?
- Analyze the contract ABI: extract all functions and events needed for minting.
- Design the React component structure: MintWidget, StatusBar, ErrorDisplay, TransactionProgress.
- Implement hooks for reading state using
useReadContractanduseReadContractsfrom wagmi. - Add transaction submission logic via
useWriteContractand track status withuseWaitForTransactionReceipt. - Integrate Merkle Tree: generate the proof on the frontend and pass it to the contract.
- Test on testnet, simulate all errors, deploy to production.
These steps cover the full development cycle. At each stage, we conduct code reviews and verify compliance with best practices.
How to Handle Minting Errors?
Minting fails for many reasons: insufficient ETH, wallet limit exceeded, sale not active, invalid proof. Errors from viem contain the ABI-decoded contract message:
import { ContractFunctionRevertedError, UserRejectedRequestError } from 'viem'; function parseMintError(error: Error): string { if (error instanceof UserRejectedRequestError) { return 'Transaction rejected in wallet'; } if (error instanceof ContractFunctionRevertedError) { const reason = error.data?.errorName ?? error.message; const messages: Record<string, string> = { 'ExceedsMaxPerWallet': 'Token limit per wallet exceeded', 'SaleNotActive': 'Sale has not started yet', 'InvalidMerkleProof': 'Your address is not whitelisted', 'InsufficientFunds': 'Insufficient ETH', 'MaxSupplyReached': 'All tokens have been minted', }; return messages[reason] ?? `Contract error: ${reason}`; } return 'Unknown error'; } Common minting errors and their resolution
- InsufficientFunds: check wallet balance and increase ETH amount.
- ExceedsMaxPerWallet: user has already minted the limit.
- InvalidMerkleProof: address not in whitelist or proof expired.
- SaleNotActive: check the sale status in the contract.
- MaxSupplyReached: all tokens are sold out.
Testing and Security
Before launch, we simulate all contract states using Hardhat fork. We test edge cases: when maxSupply is reached, when the wallet has no ETH, when the proof is invalid. We follow checks-effects-interactions principles to prevent reentrancy attacks. Additionally, we set up monitoring via Etherscan API to track successful and failed transactions. This ensures users never see an incomprehensible error, and gas is not wasted.
Work Process and What's Included
| Stage | Description | Duration |
|---|---|---|
| Contract analysis | Study ABI, check mint conditions, create specification | 1-2 days |
| Design | Component architecture, configure React/Next.js, set up wagmi | 1 day |
| Implementation | Develop MintWidget, integrate Merkle Tree, handle errors | 3-4 days |
| Testing | Test on testnet, simulate errors, coverage tests | 1-2 days |
| Deployment | Set up production environment (Vercel/Netlify), DNS, Etherscan API | 1 day |
The deliverables include full TypeScript code, integration documentation, deployment instructions, and a one-month code warranty. We also provide team training on the code and support for one week after launch.
Timeline: basic version with public mint and progress bar — 2-3 days. Full implementation with whitelist, Merkle Tree, and deployment — 4-6 days.
Order minting interface development — contact us for a project assessment. Get a free consultation and an accurate cost estimate.







