Розробка dApp (децентралізованої програми)
dApp відрізняється від звичайного веб-додатка не тим, що «використовує блокчейн» — а тим, що критична бізнес-логіка виконується on-chain, і користувач взаємодіє з нею прямо через свій гаманець, без посередника. Це принципово інша архітектура: немає backend-сервера, який «володіє» даними користувача, немає бази даних з балансами — тільки смарт-контракти та события. Все решта — проектні рішення про те, скільки off-chain інфраструктури ви готові підтримувати.
Архітектурні рішення на старті
Ступінь децентралізації
Перше чесне питання — що повинно бути on-chain, а що ні? Кожен байт у смарт-контракті коштує газу. Зберігати все на-чейн — дорого та часто безсмисленно.
Повністю on-chain: логіка + дані в контракті. Підходить для фінансових примітивів (AMM, lending, staking). Ніякого бекенду, працює через будь-який RPC.
Hybrid: логіка on-chain, UI + indexing off-chain. 90% dApps. Контракт — джерело істини для фінансових операцій, off-chain бекенд — для швидкого пошуку, сповіщень, аналітики.
Light dApp: смарт-контракт тільки для платежів/ownership, основна функціональність — звичайний веб-сервіс. Часто правильний вибір для першої версії продукту.
Stack
Стандартний стек для 2024-2025: React 18 + TypeScript + Vite, Wagmi v2 + Viem для взаємодії з блокчейном, RainbowKit або ConnectKit для підключення гаманця, TanStack Query для кешування on-chain даних. Для SSR-вимог — Next.js, але з обережністю: server components + wagmi вимагають обережної настройки.
State management: Zustand або Jotai добре підходять для dApp (менше boilerplate, ніж Redux, хорошо сполучаються з reactive wagmi hooks). Recoil — якщо проект вже використовує його.
Підключення гаманця та аутентифікація
Підтримка Multi-wallet
Користувачі приходять з MetaMask, Coinbase Wallet, WalletConnect, Ledger, Safe. Wagmi v2 + WalletConnect v2 охоплюють 95% випадків з коробки. Кастомна інтеграція потрібна рідко — тільки для корпоративних гаманців або специфічних use cases.
const config = createConfig({
chains: [mainnet, polygon, arbitrum],
transports: {
[mainnet.id]: http(process.env.VITE_ALCHEMY_MAINNET_URL),
[polygon.id]: http(process.env.VITE_ALCHEMY_POLYGON_URL),
[arbitrum.id]: http(process.env.VITE_ALCHEMY_ARBITRUM_URL),
},
connectors: [
injected(),
coinbaseWallet({ appName: 'MyDApp' }),
walletConnect({ projectId: WC_PROJECT_ID }),
],
});
SIWE (Sign-In with Ethereum)
Для dApps з off-chain компонентом (профілі, настройки, сповіщення) потрібна аутентифікація без пароля. SIWE (EIP-4361): користувач підписує текстове повідомлення з nonce, бекенд верифікує підпис, видає JWT. Це не трансакція — підпис безплатна та миттєва.
const message = new SiweMessage({
domain: window.location.host,
address: account.address,
statement: 'Sign in with Ethereum to MyDApp.',
uri: window.location.origin,
version: '1',
chainId: chain.id,
nonce: await getNonce(), // з сервера, для replay protection
});
Читання On-chain даних
Multicall та батчинг запитів
Наївний підхід — окремий RPC-виклик на кожний balanceOf, allowance, userInfo. При 20 токенах — 20 запитів, затримка ~2 секунди. Multicall3 (задеплоєна на всіх основних мережах за адресою 0xcA11bde05977b3631167028862bE2a173976CA11) дозволяє упакувати N викликів в один:
const results = await client.multicall({
contracts: tokens.map(token => ({
address: token.address,
abi: erc20Abi,
functionName: 'balanceOf',
args: [userAddress],
})),
});
Wagmi автоматично батчить useReadContracts через Multicall3. Але важливо розуміти ліміти: дуже великі batch можуть перевищити gas limit ноди.
The Graph vs. власний indexer
Для читання історичних даних (события, трансакції, агрегати) — не можна покладатися на eth_getLogs з широким діапазоном блоків: ноди лімітують запити. Два варіанти:
The Graph: GraphQL API поверх індексованих подій. Subgraph пишеться на AssemblyScript, деплоїться в Subgraph Studio. Добре для DeFi-даних (TVL, обсяги, позиції). Затримка індексування — кілька блоків.
Alchemy/Moralis API: managed indexing без написання subgraph. Швидше в старті, дорожче в scale, менше гнучкості.
Власний indexer: PostgreSQL + TypeScript сервіс, що слухає события через WebSocket. Повний контроль, але підтримка інфраструктури. Рекомендується при специфічних вимогах до даних або високих навантаженнях.
Transaction UX
Трансакції — головний джерело friction у dApps. Користувач не повинен вгадувати, що відбувається.
Стани трансакції
Повний lifecycle: idle → signing (чекаємо підпис в гаманці) → pending (трансакція в mempool) → confirming (N з M confirmations) → success/error. Кожен стан вимагає UI-зворотного зв'язку.
const { writeContractAsync, isPending } = useWriteContract();
const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({
hash: txHash,
});
Оцінка газу та EIP-1559
Wagmi/Viem використовує EIP-1559 (maxFeePerGas + maxPriorityFeePerGas) за замовчуванням на мережах, що підтримують. Для UX: показуйте estimated fee в USD до підтвердження, використовуючи eth_estimateGas + поточний gas price + ETH/USD oracle (Chainlink або CoinGecko API).
Занадто низький gasLimit — трансакція упаде з out-of-gas. Занадто високий — користувач бачить лякаючу цифру. Додавати 20% буфер до estimated gas — стандартна практика.
Approve flow
ERC-20 вимагає approve перед transferFrom. Двохшаговий процес (approve → дія) — джерело confusion. Рішення:
- Permit (EIP-2612): одна підпис замість approve-трансакції, якщо токен підтримує
- Безлімітний approve: один раз на контракт (UX добре, security погано — не рекомендуємо)
- Точний approve: approve ровно на суму поточної операції — правильно, але дві трансакції щоразу
Multichain та мережа
Chain switching
Користувач може бути підключений до неправильного чейна. Автоматичний запит на смену:
const { switchChain } = useSwitchChain();
if (chain?.id !== targetChainId) {
await switchChain({ chainId: targetChainId });
}
Для нових мереж (не в MetaMask за замовчуванням) — wallet_addEthereumChain RPC метод для додавання.
RPC resilience
Єдиний RPC — single point of failure. Для production: кілька провайдерів з fallback (Alchemy primary, Infura secondary, public RPC tertiary). Viem підтримує fallback transport з коробки.
Безпека фронтенду
- Ніякого приватного ключа на фронтенді — очевидно, але варто написати явно
- Верифікація адрес контрактів з env-змінних, а не hardcode в коді
- Content Security Policy проти XSS — особливо критично, так як XSS у dApp може привести до крадіжки коштів через фейкові трансакції
- Перевірка chainId у кожній трансакції — захист від replay attacks на іншому чейні
- ENS resolution з перевіркою зворотного lookup (address → ENS → address збігаються)
Орієнтири по строкам
MVP dApp (один чейн, базові операції, wallet connect): 2-3 тижні. Повноцінний продукт з мультичейном, аналітикою, The Graph indexing та production-ready UX: 2-3 місяці.







