События контракта: подписка и обновление UI
Как фронт узнаёт об изменениях в контракте без постоянного опроса: события (events) и логи.
Событие (event) — запись, которую контракт оставляет в логах транзакции. Фронт может подписаться на события и обновлять UI, как только что-то произошло on-chain.
Представьте чат на блокчейне. Опрашивать контракт «есть новые сообщения?» каждую секунду — расточительно. Контракт при каждом сообщении эмитит событие MessageSent, а фронт просто слушает. Это Web3-аналог WebSocket-уведомлений.
Подписка на новые события
В ethers контракт умеет слушать события через contract.on(). Нужен Provider, желательно WebSocket для realtime:
const abi = [
"event Transfer(address indexed from, address indexed to, uint256 value)",
];
const token = new Contract(addr, abi, wsProvider);
token.on("Transfer", (from, to, value, event) => {
console.log(`${from} -> ${to}: ${value.toString()}`);
// обновить UI: новый перевод
});Аргументы колбэка идут в том же порядке, что параметры события в ABI, плюс последний объект event с метаданными (номер блока, hash транзакции). Не забывайте отписываться (contract.off(...) или removeAllListeners) при размонтировании компонента, иначе утечёте подписками.
Чтение прошлых событий (логов)
События пишутся в логи блоков, поэтому можно поднять и историю — через queryFilter с диапазоном блоков:
const filter = token.filters.Transfer(null, userAddress); // все переводы НА userAddress
const past = await token.queryFilter(filter, -10000); // за последние ~10000 блоков
for (const e of past) {
console.log(e.args.from, e.args.value.toString());
}Фильтры по indexed-полям работают эффективно — нода умеет искать по ним. Поэтому в контрактах важные для поиска поля помечают indexed (видно в ABI).
Как работает под капотом
Событие — это запись в специальной области receipt'а транзакции. У неё есть «топики»: первый — хеш сигнатуры события, остальные — indexed-аргументы. Неиндексированные аргументы лежат в data-поле. Подписка через WebSocket использует eth_subscribe на логи с фильтром; нода присылает новые логи по мере появления блоков. Чтение истории — это eth_getLogs по диапазону блоков и топикам. ethers по ABI декодирует сырые топики/data обратно в удобные именованные args.
Ограничение: глубина истории
Запрос eth_getLogs на огромный диапазон блоков провайдеры ограничивают (часто несколько тысяч блоков за раз). Прочитать «все события за всё время» напрямую с ноды на больших контрактах нереально — это причина существования индексаторов вроде The Graph (отдельный урок).
Частые ошибки
- Не отписываться от событий. В React при ремоунте копятся слушатели — утечка и дубли в UI.
- Слушать события по HTTP-провайдеру. Работает через медленный polling; для realtime берите WebSocket.
- Тянуть всю историю одним queryFilter. Провайдер ограничит диапазон; листайте по блокам или используйте индексатор.
Итоги
- События — способ узнать об изменениях без опроса; подписка через
contract.on(). - История читается через
queryFilter; ищите поindexed-полям. - Глубина истории с ноды ограничена — для аналитики нужен индексатор.