Разработка NFT-минтинг страницы
Типичная ситуация: коллекция из 10 000 токенов, запуск через 48 часов. Контракт готов, маркетинг сделан. Минтинг-страница написана за вечер — кнопка «Mint», MetaMask подключение, всё просто. В момент запуска — 500 человек одновременно, MetaMask не успевает, транзакции зависают, whitelist не верифицируется, прогресс-бар показывает 0 даже после 200 заминченных NFT. Половина аудитории уходит. Это не проблема хайпа — это проблема frontend-архитектуры под нагрузку.
Ключевые компоненты минтинг-страницы
Подключение кошелька и chain management
wagmi v2 + viem — текущий стандарт для Web3 React приложений. Подключение через WalletConnect v2 (поддерживает 300+ кошельков), MetaMask, Coinbase Wallet. useConnect, useAccount, useNetwork хуки покрывают базовые сценарии.
Критический момент: проверка и переключение сети. Пользователь подключён к Ethereum, а минтинг на Base. Нужен useSwitchChain с автоматическим запросом переключения. Если пользователь отказал — показываем блокирующее предупреждение, не даём пытаться минтить.
const { switchChain } = useSwitchChain()
const { chain } = useAccount()
if (chain?.id !== TARGET_CHAIN_ID) {
return <SwitchNetworkPrompt onSwitch={() => switchChain({ chainId: TARGET_CHAIN_ID })} />
}
Whitelist верификация
Два подхода: on-chain mapping и Merkle proof.
On-chain mapping (mapping(address => bool) public whitelist) — дорого. Добавить 5000 адресов в whitelist = 5000 транзакций или один batch через multicall. Gas на mainnet может составить >1 ETH только на setup.
Merkle proof — стандарт для крупных whitelist. Root хранится on-chain (один bytes32), доказательство для каждого адреса — off-chain, передаётся при минтинге. Frontend получает proof через API или хранит его в публичном JSON.
Верификация на frontend до отправки транзакции:
import { MerkleTree } from 'merkletreejs'
import { keccak256 } from 'viem'
const proof = merkleTree.getHexProof(keccak256(address))
const isValid = merkleTree.verify(proof, keccak256(address), merkleRoot)
Важно: верификация на frontend — только для UX (не показывать кнопку если адрес не в whitelist). Финальная проверка — в смарт-контракте. Никогда не доверяй frontend-верификации.
Прогресс-бар и realtime данные
Проблема stale data. totalSupply() меняется с каждым заминченным NFT. Если читать через useReadContract с дефолтным polling — данные устаревшие на 1-3 блока. На горячем дропе это значит прогресс-бар врёт.
Решение: WebSocket подписка на Transfer события контракта. При каждом Transfer(address(0), to, tokenId) (минт) — инкрементим счётчик локально. useWatchContractEvent из wagmi:
useWatchContractEvent({
address: CONTRACT_ADDRESS,
abi: NFT_ABI,
eventName: 'Transfer',
onLogs: (logs) => {
const mints = logs.filter(log => log.args.from === zeroAddress)
setMintedCount(prev => prev + mints.length)
}
})
Это даёт realtime обновление без polling.
Состояния транзакции
Пользователь нажал «Mint» — что дальше? Минимум 4 состояния, которые нужно показать явно:
- Awaiting signature — MetaMask открылся, ждём подтверждения
- Transaction pending — транзакция в mempool, показываем hash с ссылкой на Etherscan
- Confirmed — транзакция включена в блок, показываем NFT или ссылку на OpenSea
- Failed — revert с понятным сообщением об ошибке
useWriteContract + useWaitForTransactionReceipt из wagmi покрывают все состояния через isPending, isLoading, isSuccess, isError.
Типичная ошибка: показывать spinner без hash-а. Пользователь не знает, прошла ли транзакция, закрывает вкладку, думает что ничего не произошло — и минтит снова. Всегда показывай transaction hash как только он есть.
Обработка ошибок из контракта
Revert messages из Solidity custom errors нужно декодировать. viem делает это автоматически если в ABI есть error definitions. Но пользователю нельзя показывать техническое сообщение вроде ERC721: transfer to non ERC721Receiver implementer.
Маппинг ошибок контракта в user-friendly текст — отдельная задача. Типичные случаи:
-
MaxSupplyReached→ «Все NFT уже заминчены» -
NotWhitelisted→ «Ваш адрес не в whitelist» -
MintingPaused→ «Минтинг временно приостановлен» -
InsufficientFunds→ «Недостаточно средств для оплаты минтинга»
Производительность под нагрузку
В момент дропа сотни пользователей одновременно обращаются к RPC-провайдеру. Публичные RPC (Infura free tier) имеют rate limits, которые легко превысить. Решение: Alchemy или QuickNode с платным планом + кешироание статических данных (totalSupply, mintPrice, whitelistRoot) в собственном backend с TTL 2-5 секунд.
Merkle proof-ы для whitelist — отдаём через CDN (Cloudflare), не через бэкенд. Это снимает нагрузку и даёт sub-50ms ответ.
Процесс разработки
Разработка (3-4 дня). Next.js + wagmi + viem. Компоненты: wallet connector, mint button со всеми состояниями, progress bar с websocket обновлением, whitelist checker.
Интеграция с контрактом (1 день). ABI подключение, тестирование на testnet (Sepolia), проверка всех edge cases: wrong network, not whitelisted, sold out, paused.
Оптимизация (1 день). RPC кеширование, CDN для proof-ов, gas estimate перед транзакцией.
Ориентиры по срокам
Стандартная минтинг-страница с whitelist и прогресс-баром — 3-5 дней. Сложная с фазами минтинга (presale, public), multiple wallet types и custom дизайном — до 2 недель.
Стоимость рассчитывается после уточнения функциональных требований и дизайна.







