Пошаговый практический туториал по работе с Ethers.js: подключение кошелька, чтение и запись в контракт, обработка событий и стратегии работы с gas. Примерное время выполнения — 45–75 минут.
0
Статья была полезной?
Комментарии (0)
Войдите или зарегистрируйтесь, чтобы оставить комментарий
Загрузка комментариев…
Что вы изучите
Как подключить кошелёк (MetaMask) через Ethers.js 6.9.0 (релиз 2025).
Чтение состояния контракта: вызовы view и callStatic.
Отправка транзакций: составление, оценка газа и отслеживание в блокчейне.
Подписка и выгрузка событий (events) через провайдера WebSocket и queryFilter.
Практические приёмы по работе с ошибками gas и переключением сети.
Альтернативы Ethers.js и сценарии применения.
Требования
Node.js 20.x (LTS, релиз 2023), npm 10.x — рекомендуется установка на 2025–2026 год.
ethers@6.9.0 (релиз 2025), размер npm-пакета ~1.2 МБ, bundle ~250 КБ при tree-shaking.
Браузер с MetaMask 11.x или выше (2025), порт WebSocket провайдера: 443 для wss.
Минимум 2 ГБ RAM для локальной разработки, 1 CPU или 2 vCPU для CI-пайплайнов; 4 ГБ рекомендуется при запуске локального ноды.
Порты: 8545 для локальной node (Ganache/Hardhat), 443 для HTTPS/WSS к провайдерам (Alchemy/Infura).
Зачем Ethers.js?
Ethers.js — легковесная TypeScript/JavaScript-библиотека для взаимодействия с Ethereum-сетями; версия 6.x получила упрощённые интерфейсы BrowserProvider/JsonRpcProvider и оптимизацию для ESM. В 2025 году экосистема ориентируется на минимальный runtime-overhead, безопасные абстракции Signer и простоту интеграции с MetaMask и провайдерами типа Alchemy/Infura. Ethers.js выгоден для фронтенда и backend, когда нужен контролируемый размер бандла и ясная типизация контрактов.
Для подробных практик по деплою и CI см. Web3 и по инфраструктуре — DevOps.
Шаг 1: connect wallet
Команда: установка и базовая инициализация в проекте.
Пояснение: команда создаёт проект и устанавливает ethers@6.9.0 (релиз 2025). Устанавливается ~1.2 МБ с локальным кешем npm; время выполнения на SSD ~3–6 секунд в зависимости от скорости сети.
Ожидаемый вывод:
added 1 package, and audited 1 package in 3s
found 0 vulnerabilities
Типичная ошибка и фикс:
Ошибка: npm ERR! code ENOTFOUND
Причина: проблемы с сетью или DNS при установке.
Фикс: проверить соединение, выполнить "npm cache verify" и повторить установку. Если используется корпоративный прокси, установить переменные среды HTTP_PROXY/HTTPS_PROXY.
Код для подключения кошелька в браузере (MetaMask):
/* index.js — фронтенд */
import { ethers } from "ethers";
async function connectWallet() {
if (typeof window.ethereum === "undefined") {
console.error("MetaMask не найден");
return;
}
// Запрос прав у пользователя
await window.ethereum.request({ method: "eth_requestAccounts" });
// Ethers.js v6: BrowserProvider принимает window.ethereum
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const address = await signer.getAddress();
console.log("Connected address:", address);
return { provider, signer };
}
Ожидаемый консольный вывод (успех):
Connected address: 0xAbC123...45Ef
Возможная ошибка и исправление:
Ошибка: MetaMask не одобрил доступ (User rejected the request).
Фикс: попросить пользователя снова подтвердить, показать UI с описанием причины доступа. Для автоматической проверки наличия аккаунтов применить метод "eth_accounts" перед запросом прав.
Ошибка: window.ethereum undefined.
Фикс: предложить установить MetaMask или использовать WalletConnect провайдер.
Скриншот подключения MetaMask через Ethers.js
Шаг 2: чтение контракта
Команда: пример создания провайдера и чтения view-функции контракта.
Пояснение: используем JsonRpcProvider для чтения без требуемого Signer. Время выполнения запроса ~200–800 мс на публичных провайдерах, на локальной node ~10–50 мс.
Ошибка: invalid argument 0: hex string has length 0
Причина: неверный адрес токена или некорректная переменная окружения RPC_URL.
Фикс: проверить адреса в формате 0x..., проверить переменные окружения.
Ошибка: contract method not found (ABI mismatch).
Причина: ABI не содержит вызываемой функции или сигнатура отличается.
Фикс: сгенерировать корректный ABI с помощью solc/TypeChain или взять ABI из транзакции деплоя.
Шаг 3: транзакции
Команда: отправка транзакции с помощью Signer и контроль газовой политики EIP-1559.
/* send-tx.js */
import { ethers } from "ethers";
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const privateKey = process.env.PRIVATE_KEY;
const wallet = new ethers.Wallet(privateKey, provider);
const tx = {
to: "0xRecipientAddress",
value: ethers.parseEther("0.01")
};
async function send() {
// Рекомендуется получить данные о текущих ставках
const feeData = await provider.getFeeData();
console.log("feeData:", feeData);
// Подстроить tx под EIP-1559 при необходимости
if (feeData.maxFeePerGas && feeData.maxPriorityFeePerGas) {
tx.maxFeePerGas = feeData.maxFeePerGas;
tx.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas;
}
const signedTx = await wallet.sendTransaction(tx);
console.log("tx hash:", signedTx.hash);
const receipt = await signedTx.wait(1); // ждать 1 подтверждения
console.log("receipt:", receipt.transactionHash, receipt.status);
}
send();
Пояснение: отправка простой ETH-транзакции. Получение feeData занимает ~100–600 мс на публичном провайдере. Использование EIP-1559 повышает вероятность включения в блок в 2025 при динамичных сборах.
Ошибка: insufficient funds for gas * price + value
Причина: недостаточно средств на счёте для оплаты value плюс комиссия.
Фикс: пополнить кошелёк или уменьшить value/gasLimit.
Ошибка: nonce too low
Причина: локальный nonce конфликтует с on-chain nonce.
Фикс: синхронизовать nonce через provider.getTransactionCount(address), использовать wallet.sendTransaction с manual nonce.
Ошибка: gas estimation failed
Причина: метод контракта вызывает revert при текущих параметрах.
Фикс: использовать callStatic для тестового прогона или проверить входные данные транзакции.
Шаг 4: events
Команда: подписка на события контракта и получение исторических событий.
/* events.js */
import { ethers } from "ethers";
// Для подписки в реальном времени используйте WebSocket провайдер
const wsProvider = new ethers.WebSocketProvider(process.env.WSS_URL);
const contract = new ethers.Contract("0xTokenAddress", [
"event Transfer(address indexed from, address indexed to, uint256 value)"
], wsProvider);
// Подписка в реальном времени
contract.on("Transfer", (from, to, value, event) => {
console.log(`Transfer from ${from} to ${to} value ${value.toString()}`);
// event.blockNumber, event.transactionHash доступны
});
// Получение исторических событий
async function queryBackfill() {
const filter = contract.filters.Transfer(null, null);
const events = await contract.queryFilter(filter, 12000000, 12001000); // диапазон блоков
console.log(`Found ${events.length} transfers`);
}
queryBackfill();
Пояснение: WebSocketProvider даёт push-уведомления, queryFilter работает через JSON-RPC и ресурсоёмок при больших диапазонах блоков. Использование WSS_URL от Alchemy/Infura предпочтительнее для production.
Ожидаемый вывод:
Transfer from 0xFrom... to 0xTo... value 1000000000000000000
Found 3 transfers
Типичные ошибки и их решения:
Ошибка: WebSocket not open
Причина: блокировка порта или неверный WSS URL.
Фикс: проверить WSS_URL, использовать wss://eth-mainnet.alchemyapi.io/v2/KEY, убедиться что соединение не блокирует firewall.
Ошибка: queryFilter rate limit
Причина: публичный провайдер ограничивает количество запросов и диапазон блоков в одном запросе.
Фикс: делить диапазон на чанки по 1000–5000 блоков, кешировать результаты, использовать архивный узел при необходимости исторического доступа.
Скриншот логов событий Transfer из Ethers.js
Шаг 5: проверка сети и утилиты
Команда: проверки chainId, переключение сети и утилитарные методы (populateTransaction, callStatic, estimateGas).
/* utils.js */
import { ethers } from "ethers";
async function networkChecks(provider) {
const network = await provider.getNetwork();
console.log("chainId:", network.chainId, "name:", network.name);
}
async function switchToChain() {
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0x5' }] // Goerli chainId в hex
});
} catch (switchError) {
// Код 4902 — сеть не добавлена в MetaMask
console.error('Switch error', switchError);
}
}
async function prepareTx(contract, signer) {
const populated = await contract.populateTransaction.transfer("0xTo", 1000);
populated.gasLimit = await signer.estimateGas(populated);
console.log('populated tx', populated);
}
Пояснение: getNetwork даёт chainId; метод wallet_switchEthereumChain позволяет фронтенду потребовать у MetaMask переключения сети. populateTransaction позволяет собрать tx без подписи для анализа и вычисления gasLimit.
Ошибка: Method wallet_switchEthereumChain не поддерживается.
Фикс: проверить версию MetaMask; предлагать пользователю ручное переключение.
Ошибка: estimateGas throws error.
Фикс: выполнить callStatic для симуляции, проверить входные данные и параметры sender/nonce, добавить буфер к gasLimit (например +20%).
Какие альтернативы?
Web3.js — исторически первая крупная библиотека для JS, остаётся опцией для старых проектов, но имеет более громоздкий API и большие бандлы. Web3.py подходит для серверной автоматизации на Python и удобна для аналитических скриптов. Для Rust есть ethers-rs, полезен в высокопроизводительных приложениях и бэкендах, где важна скорость и безопасность типов. При выборе учитывайте: размер бандла (важен для фронтенда), поддержка TypeScript, набор утилит (providers, signers), и требования к WebSocket/HTTP соединениям.
Ошибки, связанные с газом, делятся на несколько типов: недостаток средств (insufficient funds), неверная оценка (gas estimation failed), сетевые пики (высокая базовая плата после EIP-1559) и отклонённые транзакции из-за low priority. Ethers.js предоставляет инструментальные средства: provider.getFeeData(), signer.estimateGas(), contract.populateTransaction(), callStatic(). Для 2025 года рекомендуемая стратегия:
Всегда вызывать provider.getFeeData() для получения актуальных maxFeePerGas и maxPriorityFeePerGas.
Оценивать газ через signer.estimateGas(tx) и добавлять буфер 10–30% для сложных контрактов.
Использовать replace-by-fee (RBF): отправить новую транзакцию с тем же nonce и увеличенным maxFeePerGas для ускорения.
При частых reverts применять callStatic для симуляции состояния и расчёта revert-условий.
Пример расчёта платы и применения RBF:
const feeData = await provider.getFeeData();
let maxFee = feeData.maxFeePerGas ?? ethers.parseUnits("100", "gwei");
maxFee = Math.floor(Number(maxFee) * 1.15); // +15% буфер
// отправка первой транзакции
const txResponse = await wallet.sendTransaction({ to, value, maxFeePerGas: maxFee, maxPriorityFeePerGas: feeData.maxPriorityFeePerGas });
// если транзакция висит долго — заменить
const increasedFee = Math.floor(maxFee * 1.5);
await wallet.sendTransaction({ to, value, nonce: txResponse.nonce, maxFeePerGas: increasedFee, maxPriorityFeePerGas: feeData.maxPriorityFeePerGas });
Примечание: конкретные числа (gwei) и проценты зависят от сети и текущей нагрузки. Для простого ERC20 transfer ожидаемая газовая стоимость ~65 000–85 000 gas; для сложных взаимодействий с DeFi контрактами значения могут превышать 300 000 gas. Мониторьте расходы и логируйте feeData в production для аналитики.
Частые вопросы
как подключить Ethers.js в проект на React?
Установите ethers@6.9.0 через npm, затем используйте BrowserProvider и React-хук для хранения provider/signer. В компоненте вызовите window.ethereum.request({ method: 'eth_requestAccounts' }) и создайте new ethers.BrowserProvider(window.ethereum). Полученный signer используйте для подписания транзакций. Важно: держите приватные ключи только на стороне пользователя (не сохраняйте в localStorage). Для серверных задач применяйте JsonRpcProvider с API-ключами от Alchemy или Infura.
что делать, если контракт не возвращает ожидаемое значение при вызове read?
Проверьте ABI и адрес контракта, убедитесь, что используете provider с правильной сетью (chainId). Используйте provider.getNetwork() чтобы сверить. Если метод полагается на состояние, которое меняется в результате транзакций, проверьте, не требуется ли выполнение callStatic для симуляции. Также проверьте, не вызывает ли функция revert при текущих параметрах — для этого вызовите contract.callStatic.method(...).
почему транзакция остаётся в pending долгое время?
Частые причины: слишком низкие maxFeePerGas/maxPriorityFeePerGas относительно базовой платы сети, недостаточный баланс для покрытия комиссии, или провайдер ограничил доставку транзакции (rate limiting). Стратегии: получить provider.getFeeData() и увеличить плату, использовать replace-by-fee с тем же nonce, либо отменить транзакцию путём отправки нулевой транзакции с тем же nonce и более высокой платой. Также проверьте mempool-политику узлов вашего провайдера.
чем заменить Ethers.js для серверного скрипта на Python или Rust?
Для Python заменой будет web3.py — зрелая библиотека с поддержкой большинства JSON-RPC методов и удобной сериализацией. Для Rust — ethers-rs, дающая типобезопасные контракты и высокую производительность. Выбор зависит от требуемого стека и потребностей: для быстрого скрипта подойдёт web3.py, для высоконагруженных сервисов — ethers-rs.
сколько стоит транзакция приблизительно в 2025 году?
Стоимость транзакции сильно зависит от сети и её загрузки. На Ethereum mainnet в 2025 году простая транзакция перевода ETH обычно стоит от $0.5 до $5 при нормальной нагрузке; сложные взаимодействия с крупными DeFi-протоколами могут доходить до десятков долларов. Используйте provider.getFeeData() для расчёта в реальном времени и мониторьте базовую плату (baseFeePerGas). Для снижения затрат рассматривайте Layer-2 решения или агрегаторы транзакций.
Ethers.js для взаимодействия с контрактом | KtoHto
Комментарии (0)
Войдите или зарегистрируйтесь, чтобы оставить комментарий
Загрузка комментариев…