Событие Transfer ERC-20: структура, топики и как слушать
Как работает событие Transfer ERC-20: его сигнатура, хеш топика, как декодировать логи, подводные камни eth_getLogs и как получать уведомления о депозитах без запуска нод.

Событие ERC-20 Transfer — это лог, который токен-контракт генерирует каждый раз при перемещении токенов. Его сигнатура — Transfer(address indexed from, address indexed to, uint256 value), и это единственный надежный способ для внешнего приложения узнать, что платеж произошел. Чтобы его отследить, вы фильтруете логи по хешу топика события, декодируете отправителя, получателя и сумму, а затем ждете, пока блок станет достаточно безопасным для доверия. Это руководство проведет вас через каждый шаг, ловушки, которые стоят реальных денег, и когда имеет смысл прекратить самостоятельный опрос логов.
TL;DR
- Стандарт определяет
Transfer(address indexed from, address indexed to, uint256 value). Оно должно срабатывать при каждом перемещении токенов, включая переводы нулевой стоимости. - Для фильтрации логов используйте хеш keccak256 от
Transfer(address,address,uint256)как топик 0. Топики 1 и 2 — это заполненные нулями адресаfromиto; сумма находится в поле data. eth_getLogsна публичных эндпоинтах имеет лимиты и может возвращать неполные результаты для больших диапазонов блоков без ошибки. Используйте пагинацию и выделенную инфраструктуру для продакшена.- В ERC-20 нет колбэка для получателя, поэтому прослушивание вне цепочки не является опциональным, если вы зачисляете балансы пользователей.
- Если вы не хотите запускать собственные индексаторы логов, сервис вебхуков может отправлять декодированные уведомления о переводах в ваш бэкенд.
Table of Contents
- What the Transfer event actually is
- Anatomy of a Transfer log
- Reading transfers with eth_getLogs
- Confirmations, reorgs and idempotency
- Why you cannot rely on the recipient to notice
- Edge cases that break naive listeners
- Build or buy: running your own listener
- Perspective: what we see when teams build deposit detection
- Getting transfer notifications without running indexers
- A minimal handler, step by step
- FAQ
- Sources
What the Transfer event actually is
Смарт-контракт может генерировать события — это сигналы, записываемые в чек транзакции как логи. Они не хранятся в состоянии контракта, поэтому их запись и чтение дешевы, но контракт не может прочитать их позже. Их аудитория — внешний мир: кошельки, блок-эксплореры, индексаторы и ваш бэкенд. Официальное руководство Ethereum по событиям описывает их как сигналы, которые dApps и все, что подключено к JSON-RPC API, могут слушать и на них реагировать.

Для токенов спецификация ERC-20 определяет событие так:
event Transfer(address indexed _from, address indexed _to, uint256 _value)Правила, которые важны на практике:
- Он ДОЛЖЕН генерироваться при передаче токенов, и это включает передачи нулевой стоимости.
- Функция
transferдолжна его генерировать, иtransferFrom— функция, которую использует тратитель после предоставления разрешения (allowance) — тоже. - При майнинге ожидается, что он генерируется с адресом
from, установленным в нулевой адрес. Сжигание (burning) зеркально отражает это: нулевой адрес выступает получателем (многие реализации следуют этому соглашению).
Последний пункт — частый источник ошибок. Слушатель, который отслеживает только «переводы на мой адрес», будет работать нормально, но тот, который трактует каждое событие Transfer как платеж от пользователя к пользователю, неверно интерпретирует майнинг и сжигание.
Баланс токена — это то, что учитывается в цепочке, а событие — лишь отчет об этом. Если вам нужен контекст по полному интерфейсу, мы рассматриваем его в нашей статье о стандарте токена ERC-20.
Анатомия лога Transfer
Когда событие генерируется, узел сохраняет лог из трёх частей: адрес контракта-эмиттера, список топиков и данные (data blob). Индексация определяет, куда попадает каждый аргумент.
| Поле лога | Содержимое для ERC-20 Transfer |
|---|---|
address | Контракт токена, сгенерировавший событие (например, контракт стейблкоина) |
topics[0] | Хеш сигнатуры события |
topics[1] | Отправитель (from), дополненный слева до 32 байт |
topics[2] | Получатель (to), дополненный слева до 32 байт |
data | value, 32-байтовое беззнаковое целое число |
Оба адреса индексированы, поэтому их можно искать через фильтр логов. Значение не индексировано, поэтому хранится в поле data, что снижает затраты в цепочке и означает, что вы не можете фильтровать по сумме на уровне узла. Если вам нужны «переводы более 1000 токенов», вы получаете данные и фильтруете их в собственном коде.
Хеш топика 0
Для запроса через JSON-RPC вам нужен хеш keccak256 канонической строки сигнатуры Transfer(address,address,uint256). Без пробелов, без имен параметров, без ключевого слова indexed. Результат:
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efДокументация ethers.js по событиям использует этот же механизм: топик 0 — это хеш сигнатуры, а топики 1 и 2 соответствуют индексированным адресам from и to, каждый дополнен до 32 байт.
Два момента сэкономят время на отладке:
- Десятичные знаки — ваша забота.
value— это сырое целое число. У токена с 6 десятичными знаками значение2500000означает 2,5 токена. Прочитайтеdecimals()из контракта (или закэшируйте это для каждого токена) перед отображением сумм. - Один хеш, разные стандарты. ERC-721 использует то же имя события и те же типы аргументов, поэтому его топик 0 идентичен. Разница в том, что ID токена индексирован, поэтому лог перевода NFT содержит четыре топика и пустое поле data, в то время как перевод фанжибл-токена содержит три топика. Проверяйте количество топиков перед декодированием, иначе вы будете разбирать перемещения NFT как платежи токенами. Наша статья о стандарте ERC-721 рассматривает эту сторону.
Чтение переводов через eth_getLogs
Классический подход — прямой вызов JSON-RPC. Фильтр, находящий все переводы одного токена на один кошелек, выглядит так:
{ "method": "eth_getLogs", "params": [{ "fromBlock": "0x10d4f00", "toBlock": "0x10d4f64", "address": "0xTokenContractAddress", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", null, "0x000000000000000000000000YourDepositAddressWithoutThe0x" ] }]}Значение null в позиции темы означает «любое значение». Здесь мы принимаем любого отправителя и фиксируем получателя. Обратите внимание, что адрес получателя дополнен нулями до 32 байт.
Если вы предпочитаете использовать библиотеку, краткое руководство, такое как этот gist для web3.py по логам переводов ERC-20, показывает тот же вызов, обернутый в Python. Для получения обновлений в реальном времени вместо истории eth_subscribe с типом logs отправляет новые совпадения через WebSocket.
Где eth_getLogs может подвести
Это выглядит просто, и это работает в обучающем руководстве. В продакшене всё иначе. В статье SQD об ограничениях eth_getLogs отмечается, что стандартные провайдеры нод часто ограничивают количество возвращаемых логов, а запрос большого диапазона блоков может дать неполные данные без какой-либо ошибки. Их рекомендация: разбивайте запросы на страницы и используйте архивные ноды для исторических данных. В той же статье предупреждается, что зависимость от публичных эндпоинтов для интенсивного продакшн-использования ненадежна из-за ограничений блум-фильтров и пробелов в индексах, которые отличаются от ноды к ноде.
Практические правила:
- Запрашивайте небольшие временные окна блоков и двигайтесь вперед, вместо того чтобы запрашивать один огромный диапазон.
- Сравнивайте количество результатов с тем, что вы ожидаете. Молчащее усечение выглядит точно так же, как спокойный день.
- Сохраняйте номер последнего обработанного блока, чтобы при перезапуске процесс возобновлялся с места остановки, а не пропускал блоки.
- Сохраняйте перекрытие в несколько блоков на каждом проходе и дедуплицируйте по хешу транзакции и индексу лога.
Подтверждения, реорги и идемпотентность
Лог, появившийся в последнем блоке, не является финальным. Цепочка может реорганизоваться, и блок, содержавший ваш перевод, может быть заменен на такой, в котором его нет. Надежный пайплайн депозитов относится к событию как к претензии, а не как к факту, пока поверх него не накопится достаточное количество блоков.
Хорошо работает простая машина состояний:
- Обнаружен (Seen). Лог детектирован. Запишите его, покажите пользователю статус «в ожидании» и не зачисляйте средства.
- Подтвержден (Confirmed). Блок старше выбранного вами порога. Зачислите баланс один раз.
- Осиротел (Orphaned). Лог исчез после реорга. Пометьте запись как недействительную и уведомите заинтересованных сторон.
Порог — это бизнес-решение. Небольшая покупка допускает меньше подтверждений, чем крупный депозит, дающий право на вывод. Выбирайте его для каждой цепочки и каждой суммы, и зафиксируйте это решение.
Каким бы ни был ваш выбор, сделайте зачисление идемпотентным. Уникальным ключом для лога является хеш транзакции плюс индекс лога, а не только хеш транзакции, потому что одна транзакция может генерировать несколько событий Transfer (например, своп генерирует как минимум два). Если ваш обработчик получает то же событие дважды — из-за повторной попытки или второго источника — он должен ничего не делать во второй раз.
Почему нельзя полагаться на то, что получатель заметит
Переводы ERC-20 с точки зрения получателя являются «тихими». Адрес кошелька не выполняет код, а получающий контракт не уведомляется ни о чем. На странице ethereum.org про ERC-20 указывается, что токены теряются при отправке в контракты, которые не распознают входящий перевод, поскольку в стандарте нет встроенного обратного вызова, и там говорится, что по состоянию на 20 июня 2024 года ERC-20 токенов на сумму более $83 656 418 было потеряно именно так. Распространенным способом защиты является проверка require в токене, блокирующая отправку в сам контракт токена.
Два вывода для разработчиков:
- Если вы запускаете контракт, который должен принимать токены, вам нужен собственный учет. Существуют стандарты вроде хуков в стиле ERC-1363 для этого, но вы не можете предполагать, что токен отправителя их поддерживает.
- Если вы запускаете бэкенд, лог Transfer — ваш единственный триггер. Пропуск одного означает, что клиент заплатил, а вы об этом никогда не узнаете.
Существует второй пробел: событие не несет ссылки на платеж. Ничто не связывает перевод с инвойсом 4471. Большинство бизнесов решают это, выдавая каждому инвойсу или пользователю уникальный адрес для депозита. Более новый предложение, ERC-7699, расширяло бы ERC-20 ссылкой на перевод, чтобы сообщение для получателя перемещалось вместе с платежом, но это лишь предложение, а популярные токены сегодня его не реализуют.
Краевые случаи, ломающие наивные слушатели
Токены в нативном стиле. В некоторых цепочках стейблкоин встроен в протокол, а не является обычным контрактом. Документация Arc по обнаружению депозитов объясняет, что нативные перемещения USDC там генерируют стандартный лог ERC-20 Transfer от системного адреса, поэтому разработчики фильтруют по этому эмиттеру, а не по отдельному контракту токена. Урок общий: читайте документацию по депозитам каждой цепочки, прежде чем предполагать, что фильтр по адресу контракта достаточно.
Токены с комиссией при переводе и ребейзинговые токены. Сумма в событии может отличаться от того, насколько изменится баланс получателя, или балансы могут смещаться без какого-либо Transfer. Если вы принимаете неизвестные токены, сверяйтесь с balanceOf вместо того, чтобы доверять только событию.
Переводы нулевой стоимости. Стандарт требует их генерации события. Злоумышленники используют это для отравления адресов, отправляя переводы нулевой стоимости, которые помещают похожий адрес в историю жертвы. Игнорируйте логи нулевой стоимости в любом потоке депозитов.
Поддельные токены. Любой может развернуть контракт, который генерирует событие Transfer с любым именем токена. Фильтруйте по адресам контрактов, которые вы узнаете, а не только по топику 0.

Другие цепочки. Та же форма события появляется в EVM-совместимых сетях, таких как Polygon, BNB Chain (BEP-20) и Arbitrum. Токены TRC-20 в TRON следуют той же идее со своим инструментарием; см. наше сравнение TRC10 и TRC20 для того, как они отличаются.
Создавать или покупать: запуск собственного слушателя
Домашний слушатель — хороший проект и справедливый выбор, когда блокчейн-данные — ваш основной продукт. Для большинства команд, принимающих платежи, это побочный квест с длинным хвостом обслуживания. Вы владеете нодой или контрактом провайдера, курсором блока, обработкой реоргов, повторными попытками, декодингом для каждого токена и тем же стеком снова для каждой дополнительной цепочки.
Примерный способ принятия решения:
| Вопрос | Если да, склоняйтесь к разработке | Если нет, склоняйтесь к API |
|---|---|---|
| Являются ли ончейн-данные вашим продуктом? | Да | Нет |
| Нужен ли вам пользовательский анализ логов, выходящий за рамки платежей? | Да | Нет |
| Есть ли у вас инженер, который будет отвечать за доступность ноды и логику реорганизаций? | Да | Нет |
| Поддерживаете ли вы несколько цепей с первого дня? | Нет | Да |
Если вы попали в правый столбец, управляемое API, которое отправляет декодированные события, устраняет весь слой индексации.
Перспектива: что мы видим, когда команды создают обнаружение депозитов
Мы создаем унифицированное API для криптоплатежей, поэтому мы постоянно видим один и тот же паттерн. Команда начинает с одного цикла eth_getLogs для одного токена в одной цепи. Это работает. Затем они добавляют вторую цепь, второй стандарт токенов, задачу повторных попыток и патч для реорганизаций, и функция платежей незаметно превращается в инфраструктурный проект.
Наш взгляд таков: событие Transfer — это правильный примитив, но неверное место для затрат вашего инженерного времени. Вашему приложению нужно чистое утверждение: этот адрес получил эту сумму этого токена в этой транзакции. Именно поэтому мы предоставляем предварительно декодированные данные вместо необработанных топиков, а уведомления приходят через вебхуки, подписанные HMAC, чтобы ваш сервер мог проверить, что сообщение действительно пришло от нас, прежде чем зачислить средства. Мы также считаем, что вам не должно быть нужно запускать свои ноды или жонглировать цепочечными эндпоинтами, чтобы достичь этого.
Совет, который мы даем независимо от инструментов: используйте один адрес депозита на плательщика или инвойс, зачисляйте идемпотентно, ждите подтверждений, соответствующих сумме на кону, и проверяйте адрес контракта токена. Эти четыре привычки предотвращают большинство инцидентов, о которых мы слышим.
— Bitblade
Получение уведомлений о переводах без запуска индексаторов
Chaingateway предоставляет разработчикам единое REST API для кошельков, переводов токенов и декодированных данных транзакций в Ethereum, Bitcoin, Tron, Solana, Polyggon, BNB Chain и Arbitrum. Для этой темы соответствующим элементом являются Вебхуки Chaingateway: вы подписываетесь на адрес, и когда перевод токена затрагивает его, уведомление в реальном времени с уже декодированными данными достигает вашей конечной точки, поэтому вы пропускаете хеширование и дополнение топиков. Наша страница Ethereum API показывает, как отправлять токены ERC-20 через REST без web3.js, а руководство по адресам ERC-20 охватывает формат адресов и проверку балансов.
Тарифы опубликованы на странице цен: Plus — 49 € в месяц, Pro — 99 €, Premium — 249 € и Enterprise — 499 € в месяц, при годовой оплате — 490 €, 990 €, 2 490 € и 4 990 € соответственно. Доступ к Blockchain API начинается с бесплатного пробного периода.
Минимальный обработчик, шаг за шагом
Независимо от того, используете вы необработанные логи или вебхуки, серверная логика остается прежней.
- Проверьте источник. Для вебхуков проверьте HMAC-подпись в необработанном теле запроса до его парсинга. Для собственного читателя логов вы уже доверяете своему узлу.
- Проверьте токен. Сравните адрес контракта со своим списком разрешённых.
- Проверьте получателя. Найдите адрес
toв своей базе адресов депозитов. Игнорируйте всё неизвестное. - Отсеките шум. Отбрасывайте переводы нулевой стоимости, а также минты и сжигания, которые вас не интересуют.
- Устраните дубликаты. Вставьте хеш транзакции и индекс лога под уникальным ограничением. Если вставка не удалась, остановитесь.
- Конвертируйте сумму. Разделите сырое значение на 10 в степени количества десятичных знаков токена, используя целочисленные или десятичные типы, никогда не используйте числа с плавающей точкой.
- Запишите как ожидающее, затем подтверждённое. Зачисляйте средства только после прохождения вашего порога подтверждений.
- Отвечайте быстро. Подтверждайте вебхук статусом успеха немедленно, а тяжёлую работу выполняйте в очереди.
Источники
- ERC-20: Token Standard (EIP-20)
- ERC-20 Token Standard | ethereum.org
- Logging data from smart contracts with events | ethereum.org
- Events | ethers.js documentation
- eth_getLogs: Limits, Pagination, and the Logs It Leaves Out | SQD
- ERC-7699: ERC-20 Transfer Reference Extension
- Detect and process deposits | Arc Docs
- Use web3.py to get ERC-20 transfer logs for a specific account
Рекомендуем
Часто задаваемые вопросы
Это хеш keccak256 от Transfer(address,address,uint256), который равен 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. Он всегда находится в топике 0 лога. Топики 1 и 2 содержат адреса отправителя и получателя.
Индексированные аргументы становятся топиками, которые узлы могут искать, но каждый из них увеличивает стоимость лога, а стандарт оставляет индексированными только два адреса. Значение попадает в поле data, поэтому вы декодируете его сами и фильтруете суммы в собственном коде.
Стандарт требует этого, включая переводы нулевой стоимости. На практике совместимые контракты это делают, но нестандартные токены могут вести себя иначе, а некоторые цепочки обрабатывают нативные активы особым образом. Обрабатывайте событие как основной сигнал и сверяйтесь с balanceOf, когда токен вам незнаком.
Только с осторожностью. Провайдеры могут ограничивать возвращаемые логи, а большие диапазоны могут возвращать неполные данные без ошибки. Разбивайте на небольшие окна, отслеживайте последний обработанный блок и используйте выделенную инфраструктуру или сервис уведомлений для всего, что работает с реальными деньгами.
Универсального числа нет. Это зависит от цепочки, суммы и вашей толерантности к риску. Установите порог для каждой цепочки и ценового уровня, и зачисляйте средства только один раз, после его прохождения.
Готовы собрать это сами? Получите свой API-ключ — 7-дневный пробный период, без карты — или посмотрите API блокчейна для полного справочника по эндпоинтам.