Розробка NFT-мінтинг сторінки
Типова ситуація: колекція з 10 000 токенів, запуск через 48 годин. Контракт готов, маркетинг зроблений. Мінтинг-сторінка написана за вечір — кнопка "Mint", MetaMask підключення, усе просто. На момент запуску — 500 людей одночасно, MetaMask не встигає, транзакції зависають, whitelist не верифікується, прогресс-бар показує 0 навіть після 200 заминченних NFT. Половина аудиторії йде. Це не проблема хайпу — це проблема frontend-архітектури під нагрузку.
Ключові компоненти мінтинг-сторінки
Підключення гаманця та управління сіттю
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 маппінг та Merkle proof.
On-chain маппінг (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 повідомлення з 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: неправильна мережа, не в whitelist, sold out, paused.
Оптимізація (1 день). RPC кешування, CDN для proof-ів, gas estimate перед транзакцією.
Кошторис по часу
Стандартна мінтинг-сторінка з whitelist і прогресс-баром — 3–5 днів. Складна з фазами мінтингу (presale, public), multiple wallet types та кастомним дизайном — до 2 тижнів.
Вартість розраховується після уточнення функціональних вимог та дизайну.







