Events и логи — как хранятся данные
Как смарт-контракты записывают events в logs, где они лежат, как читать их через ethers.js и чем logs отличаются от State.
Events и логи
Event — это запись, которую смарт-контракт оставляет во время выполнения транзакции.
Такие записи называются logs. Они попадают в receipt транзакции и помогают приложениям отслеживать, что произошло: перевод токена, создание пула, mint NFT, изменение параметра.
Events не меняют State
State хранит текущее состояние:
balanceOf(IVAN) = 100 WETH
balanceOf(ANNA) = 25 WETHLogs хранят историю событий:
Transfer(from: IVAN, to: ANNA, value: 5 WETH)
Transfer(from: ANNA, to: PETR, value: 1 WETH)Контракт не может прочитать старые logs из своего кода. Logs нужны внешним приложениям: frontend, backend, индексаторам, аналитике и block explorer.
Читаем Transfer events у WETH
import { ethers } from "ethers";
const provider = new ethers.JsonRpcProvider("https://ethereum.publicnode.com");
const wethAddress = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
const wethAbi = [
"event Transfer(address indexed from, address indexed to, uint256 value)",
"function decimals() view returns (uint8)",
"function symbol() view returns (string)",
];
const weth = new ethers.Contract(wethAddress, wethAbi, provider);
const latestBlock = await provider.getBlockNumber();
const fromBlock = latestBlock - 100;
const decimals = await weth.decimals();
const symbol = await weth.symbol();
const events = await weth.queryFilter(
weth.filters.Transfer(),
fromBlock,
latestBlock,
);
for (const event of events.slice(0, 5)) {
const { from, to, value } = event.args;
console.log("Tx:", event.transactionHash);
console.log("From:", from);
console.log("To:", to);
console.log("Value:", ethers.formatUnits(value, decimals), symbol);
console.log("Block:", event.blockNumber);
console.log("---");
}Этот код читает реальные Transfer events WETH за последние 100 блоков Ethereum mainnet и выводит первые пять переводов.
Как это работает
event Transfer(address indexed from, address indexed to, uint256 value) — описание event в ABI.
indexed — помечает поля, по которым RPC может быстро фильтровать logs.
weth.filters.Transfer() — создаёт фильтр для всех Transfer events WETH.
weth.queryFilter(filter, fromBlock, latestBlock) — ищет logs в диапазоне блоков.
event.args — декодированные аргументы event. Без ABI вы бы получили только raw topics и data.
Topics и data
На низком уровне log состоит из адреса контракта, topics и data.
address: 0xC02a...56Cc2
topics:
[0] hash("Transfer(address,address,uint256)")
[1] indexed from
[2] indexed to
data:
valuetopics[0] определяет тип event. Остальные topics хранят indexed аргументы.
Неиндексированные аргументы лежат в data. В Transfer это value.
Фильтруем по адресу получателя
import { ethers } from "ethers";
const provider = new ethers.JsonRpcProvider("https://ethereum.publicnode.com");
const wethAddress = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
const vitalikAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
const abi = [
"event Transfer(address indexed from, address indexed to, uint256 value)",
];
const weth = new ethers.Contract(wethAddress, abi, provider);
const latestBlock = await provider.getBlockNumber();
const fromBlock = latestBlock - 10_000;
const filter = weth.filters.Transfer(null, vitalikAddress);
const events = await weth.queryFilter(filter, fromBlock, latestBlock);
console.log("Incoming WETH transfers:", events.length);Первый аргумент null значит: любой from. Второй аргумент фильтрует to, потому что to помечен как indexed.
Где events используются
| Задача | Как помогают logs |
|---|---|
| История переводов | читать Transfer events |
| UI после транзакции | показать результат из receipt |
| Индексатор | построить базу данных по events |
| Аналитика | считать объёмы, пользователей, действия |
Events дешевле, чем хранить те же данные в State, но они не заменяют State. Контракт не может использовать logs как источник истины для своей логики.
Частые ошибки
Считать event изменением State → event только записывает log. State меняют операции записи в storage, балансы и вызовы контрактов.
Пытаться читать logs из смарт-контракта → logs доступны через RPC и receipt, но не доступны контракту во время выполнения.
Запрашивать слишком большой диапазон блоков → публичные RPC часто ограничивают eth_getLogs. Делите историю на небольшие диапазоны.
Что дальше
- Что такое ABI — как ABI декодирует logs в понятные аргументы
- Как работает транзакция изнутри — где receipt хранит результат и logs
- Чтение данных из контракта — как читать State через
viewфункции