Разработка dApp (децентрализованного приложения)
dApp отличается от обычного веб-приложения не тем, что «использует блокчейн» — а тем, что критическая бизнес-логика исполняется на-чейн, и пользователь взаимодействует с ней напрямую через свой кошелёк, без посредника. Это принципиально другая архитектура: нет backend-сервера, который «владеет» данными пользователя, нет базы данных с балансами — только смарт-контракты и события. Всё остальное — проектные решения о том, сколько off-chain инфраструктуры вы готовы поддерживать.
Архитектурные решения на старте
Степень децентрализации
Первый вопрос — честный: что должно быть on-chain, а что нет? Каждый байт в смарт-контракте стоит газа. Хранить всё на-чейн — дорого и часто бессмысленно.
Полностью on-chain: логика + данные в контракте. Подходит для финансовых примитивов (AMM, lending, staking). Никакого бэкенда, работает через любой RPC.
Hybrid: логика on-chain, UI + indexing off-chain. 90% dApps. Контракт — source of truth для финансовых операций, off-chain backend — для быстрого поиска, нотификаций, аналитики.
Light dApp: смарт-контракт только для payments/ownership, основная функциональность — обычный веб-сервис. Часто правильный выбор для первой версии продукта.
Stack
Стандартный стек для 2024-2025: React 18 + TypeScript + Vite, Wagmi v2 + Viem для взаимодействия с блокчейном, RainbowKit или ConnectKit для wallet connection, TanStack Query для кэширования on-chain данных. Для SSR-требований — Next.js, но с осторожностью: server components + wagmi требуют аккуратной настройки.
State management: Zustand или Jotai хорошо подходят для dApp (меньше бойлерплейта, чем Redux, хорошо сочетаются с reactive wagmi hooks). Recoil — если проект уже использует его.
Wallet connection и authentication
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, backend верифицирует подпись, выдаёт 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 data fetching
Multicall и батчинг запросов
Naïve подход — отдельный 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,
});
Gas estimation и 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 из коробки.
Безопасность frontend
- Никакого приватного ключа на frontend — очевидно, но стоит написать явно
- Верификация адресов контрактов из env-переменных, а не hardcode в коде
- Content Security Policy против XSS — особенно критично, так как XSS в dApp может привести к кражe funds через фейковые транзакции
- Проверка chainId в каждой транзакции — защита от replay attacks на другом чейне
- ENS resolution с проверкой обратного lookup (address → ENS → address совпадает)
Ориентиры по срокам
MVP dApp (один чейн, базовые operations, wallet connect): 2-3 недели. Полноценный продукт с мультичейном, аналитикой, The Graph indexing и production-ready UX: 2-3 месяца.







