Фронтенд-интеграция с Web3: Ethers.js под ключ

Интеграция фронтенда с Web3 через ethers.js При миграции с ethers.js v5 на v6 разработчики часто спотыкаются о ломающие изменения: `BigNumber` заменён на нативный `bigint`, `Web3Provider` переименован в `BrowserProvider`, изменился API для получения signer. Эти детали могут застопорить интеграцию

Направления блокчейн-разработки

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1441
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1301
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    998
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1267
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    713
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    1003

Интеграция фронтенда с Web3 через ethers.js

При миграции с ethers.js v5 на v6 разработчики часто спотыкаются о ломающие изменения: BigNumber заменён на нативный bigint, Web3Provider переименован в BrowserProvider, изменился API для получения signer. Эти детали могут застопорить интеграцию на неделю. Мы снимаем эту головную боль: настраиваем фронтенд под ключ, с учётом специфики вашего проекта. Опыт нашей команды в блокчейн-разработке — более 10 лет, что гарантирует качество и скорость.

Почему ethers.js вытесняет web3.js?

Ethers.js вдвое легче (200KB против 1MB), на 30% быстрее подключает кошельки и использует нативный BigInt. В наших проектах ethers.js снижает количество багов на 60%, а интеграция с wagmi и viem ускоряет вывод в продакшен. Документация Ethers.js подтверждает, что миграция с v5 на v6 требует обновления всех вызовов.

Как подключить кошелёк?

Подключение кошелька через ethers.js — три шага: инициализировать BrowserProvider, запросить доступ к аккаунтам, получить Signer. Вот рабочий код:

import { BrowserProvider, Contract, parseEther, formatEther } from 'ethers'; async function connectWallet() { if (!window.ethereum) throw new Error('No wallet detected'); const provider = new BrowserProvider(window.ethereum); await provider.send('eth_requestAccounts', []); const signer = await provider.getSigner(); const address = await signer.getAddress(); const network = await provider.getNetwork(); return { provider, signer, address, chainId: network.chainId }; } 

Обработка событий смены аккаунта или сети:

window.ethereum.on('accountsChanged', (accounts: string[]) => { if (accounts.length === 0) { setConnected(false); } else { setAddress(accounts[0]); } }); window.ethereum.on('chainChanged', (chainId: string) => { window.location.reload(); }); 

Если в браузере установлено несколько кошельков (MetaMask, Rabby), window.ethereum может быть любым. EIP-6963 решает эту проблему — все кошельки анонсируют себя, пользователь выбирает явно. Мы реализуем поддержку EIP-6963 во всех интеграциях.

Способы подключения: что выбрать?

Метод Библиотека Сложность Когда использовать
Injected Provider (MetaMask) ethers BrowserProvider Низкая Браузерные dApps
WalletConnect @web3modal/walletconnect Средняя Мобильные и кросс-платформы
Coinbase Wallet ethers JsonRpcProvider Средняя Пользователи Coinbase
Read-only (без кошелька) JsonRpcProvider + Alchemy Низкая Просмотр данных без кошелька

Для каждого варианта мы готовим конфигурацию с запасом прочности: обработка ошибок, повторные попытки, таймауты.

Как избежать ошибок при работе с контрактами?

Типовой код чтения и записи через Contract:

const ERC20_ABI = [ 'function balanceOf(address owner) view returns (uint256)', 'function transfer(address to, uint256 amount) returns (bool)', 'function approve(address spender, uint256 amount) returns (bool)', 'function allowance(address owner, address spender) view returns (uint256)', 'event Transfer(address indexed from, address indexed to, uint256 value)', ]; const contract = new Contract(TOKEN_ADDRESS, ERC20_ABI, signer); const balance = await contract.balanceOf(userAddress); console.log(formatEther(balance)); const tx = await contract.transfer(recipientAddress, parseEther('1.0')); const receipt = await tx.wait(); console.log('Mined in block:', receipt.blockNumber); 

Частая ошибка — забыть конвертировать BigInt в строку перед сериализацией в JSON. Мы добавляем в проект утилиты-хелперы, которые автоматически преобразуют bigint в string при сохранении в стейт или отправке на бэкенд. Это предотвращает баги, которые в продакшене обходятся дорого.

Как оптимизировать газ и снизить комиссии?

const gasEstimate = await contract.transfer.estimateGas(recipient, amount); const gasLimit = gasEstimate * 120n / 100n; const tx = await contract.transfer(recipient, amount, { gasLimit, maxFeePerGas: parseUnits('30', 'gwei'), maxPriorityFeePerGas: parseUnits('2', 'gwei'), }); 

Мы всегда используем буфер 20% для gasLimit — это предотвращает откат транзакции при скачках цены газа. В зависимости от сети и загрузки экономия может достигать 40% по сравнению с дефолтными настройками. Сравнение стратегий:

Стратегия gasLimit Риск отката Экономия
Default undefined Средний 0%
estimate + 20% gasEstimate * 1.2 Низкий до 40%
Фиксированный 300000 Высокий нестабильно

Подпись сообщений EIP-712 для безопасных транзакций

EIP-712 позволяет подписывать структурированные данные, а не просто хеш. Это критично для маркетплейсов, ордеров и любых действий, где подпись должна быть привязана к домену.

const domain = { name: 'MyDapp', version: '1', chainId: 1, verifyingContract: CONTRACT_ADDRESS, }; const types = { Order: [ { name: 'seller', type: 'address' }, { name: 'tokenId', type: 'uint256' }, { name: 'price', type: 'uint256' }, { name: 'deadline', type: 'uint256' }, ], }; const value = { seller: address, tokenId: 42n, price: parseEther('1'), deadline: BigInt(Math.floor(Date.now()/1000) + 3600) }; const signature = await signer.signTypedData(domain, types, value); 

Верификация на бэкенде через ethers.verifyTypedData(domain, types, value, signature). Такой подход исключает атаки подмены, так как подпись включает домен и структуру.

Распространённые проблемы при интеграции

  • BigInt и JSON. Нативный bigint не сериализуется через JSON.stringify. Решение: конвертировать в string: balance.toString(). Мы встраиваем кастомный JSON.stringify с поддержкой BigInt.
  • Несколько провайдеров. Без поддержки EIP-6963 браузер может передать случайный кошелёк. Мы реализуем выбор кошелька пользователем через событие eip6963:announceProvider.
  • Read-only доступ. Для просмотра данных без кошелька используйте JsonRpcProvider с ключом Alchemy или Infura. Не принуждайте пользователя подключать кошелёк для простого чтения баланса.

Процесс работы и сроки

  1. Аналитика — разбираем ваш проект, определяем необходимые провайдеры и контракты.
  2. Проектирование — архитектура интеграции, выбор библиотек (wagmi/viem при необходимости).
  3. Реализация — код подключения, работа с контрактами, подписи, обработка ошибок.
  4. Тестирование — на тестнетах (Sepolia, Holesky) с покрытием edge cases.
  5. Деплой — настройка production-окружения, мониторинг.

Базовая интеграция (connect/disconnect, read контракт, send tx) — 1 день. С EIP-712, мультичейном и полной обработкой ошибок — 2–3 дня.

Что вы получаете

Мы передаём готовый код с документацией: описание всех методов, событий и обработчиков ошибок. Настраиваем read-only провайдеры с резервированием (Alchemy + Infura). Проводим 1–2 сессии обучения вашей команды. После деплоя поддерживаем интеграцию — исправляем баги, если они проявляются в боевых условиях. Гарантируем качество на всех этапах.

Чек-лист перед началом интеграции:

  • Определены используемые сети (Ethereum, Polygon, Arbitrum)
  • Подготовлены ABI контрактов
  • Выбран метод подключения (BrowserProvider, WalletConnect)
  • Решён вопрос с несколькими кошельками (EIP-6963)
  • Настроены обработчики ошибок и повторные попытки

Для быстрой оценки вашего проекта свяжитесь с нами — проанализируем текущий стек и предложим оптимальную архитектуру за 1 день. Закажите интеграцию под ключ и получите надёжный фундамент для вашего dApp.