История транзакций TON: lt, хэши и архивная глубина
Как устроена история транзакций TON: логическое время (lt), хэши и указатель последней транзакции. Чтение истории агентом через MCP и почему глубина требуе
Ты работаешь ночью, и в 3:47 прилетает сообщение: пользователь оплатил заказ в TON, а бот его не засчитал. Открываешь эксплорер — деньги на месте, входящий перевод виден. Значит, проблема не в блокчейне, а в том, как твой сервис читает историю транзакций. И тут выясняется, что в TON эта история устроена совсем не так, как ты привык по Ethereum.
Мониторинг платежей на TON выглядит обманчиво просто: опрашивай баланс и реагируй на изменение. Но на практике всё разваливается. Два платежа на одинаковую сумму в одну минуту — сколько их было, один или два? Пользователь заплатил USDT, а баланс GRAM не шелохнулся. Нужна выписка за полгода, а лайтсервер отдаёт только последние транзакции и молчит про остальное.
Если ты подключаешь ИИ-агента к TON или пишешь бэкенд, который сверяет платежи, разберёмся по делу: что такое история транзакций TON, зачем нужна пара lt + hash, почему глубина упирается в архивную ноду и как всё это читать агентом через MCP.
Что такое история транзакций в TON и зачем она агенту
В TON у каждого аккаунта — кошелька, контракта, джеттон-кошелька — есть собственная цепочка транзакций. Каждая транзакция — это результат обработки входящего сообщения: приём GRAM, перевод жетона, вызов метода контракта. Для агента (Claude, Cursor, любого MCP-клиента), который принимает платежи или сверяет расчёты, история — единственный достоверный источник: баланс говорит «сколько сейчас», а история — «что именно и когда произошло, от кого и на какую сумму».
Приземлённые задачи, ради которых агенту нужна история:
- подтвердить, что платёж дошёл («проверь, приходил ли перевод на этот адрес за последний час»);
- собрать выписку по кошельку;
- отследить конкретную транзакцию по её идентификатору;
- сверить входящие джеттоны (например, USDT) с ожидаемой суммой.
Проблема в том, что «дай мне транзакции №100–120» в TON не работает. Здесь нет глобальной сплошной нумерации блоков, по которой можно было бы «дать транзакции номер N». Порядок задаётся иначе — через логическое время.
Логическое время (lt): почему в TON нет обычной нумерации блоков
В Ethereum всё просто: есть блок № 19 000 000, в нём транзакции по порядку. Один глобальный счётчик, монотонно растёт, все ориентируются на один и тот же номер.
TON — многопоточная система: мастерчейн, воркчейны, шарды, которые делятся и сливаются под нагрузкой. Единого «номера блока», по которому можно линейно упорядочить все события сети, тут просто нет. Вместо него TON использует логическое время (lt, logical time) — монотонно растущий счётчик, которым сеть упорядочивает события, сообщения и транзакции. Он гарантирует: если событие A повлияло на событие B, то lt(A) < lt(B).
Отсюда практическое следствие: позиция транзакции задаётся не номером блока, а парой (lt, hash). lt отвечает за порядок (кто раньше), hash — за однозначную идентификацию конкретной транзакции. По отдельности они для точечного запроса бесполезны: у разных объектов могут быть близкие lt, а один только hash не говорит liteserver, откуда начинать чтение.
Хэш транзакции и пара lt + hash: указатель последней транзакции аккаунта
Хэш транзакции — это её криптографический отпечаток; на TON он приходит в base64 или hex. lt говорит «когда по порядку», hash — «какая именно», потому что на одном lt-срезе теоретически возможна неоднозначность, и без хэша liteserver транзакцию не отдаст. Запомни жёстко: пара (lt, hash) обязательна вместе. Только lt или только hash — недостаточно.
Где взять стартовую точку? Состояние аккаунта хранит указатель на самую последнюю транзакцию — last_transaction_id, это та самая пара lt + hash (поля last_trans_lt / last_trans_hash). Это «голова» списка, с которой начинается обход истории назад.
В MCP-инструментарии TONNode это делает get_account_state — он возвращает статус аккаунта, флаги и указатель на последнюю транзакцию.
Как get_transactions листает историю: lt + hash и цепочка prev_trans
Теперь ключевой момент, из-за которого история вообще листается. Каждая транзакция, кроме собственных lt и hash, содержит два поля: prev_trans_lt и prev_trans_hash — ссылку на предыдущую транзакцию этого же аккаунта. То есть транзакции образуют односвязный список, идущий назад во времени; голова — last_transaction_id из состояния.
get_transactions принимает:
account— адрес аккаунта;- стартовые
ltиhash— точку, от которой читать; count— размер пачки.
Он возвращает пачку транзакций, идя назад от указанной точки. Логика постраничного обхода:
- Взять
last_trans_lt/last_trans_hashизget_account_state— это голова. - Вызвать
get_transactions(account, lt, hash, count)— получить пачку. - У последней транзакции в пачке взять
prev_trans_ltиprev_trans_hash. - Снова вызвать
get_transactionsуже с этой парой — это следующая страница. - Повторять, пока
prev_trans_ltне станет 0 (начало жизни аккаунта) или пока не дойдёшь до нужной глубины.
Так работает пагинация в TON: не «страница 2», а «продолжи от этой пары (lt, hash)».
Свежие блоки против глубокой истории: зачем нужна архивная нода
Вот здесь большинство и спотыкается. Обычный liteserver отдаёт свежие блоки и недавнюю историю аккаунта: он хранит ограниченное окно состояния — сколько-то последних блоков, — и по мере роста сети старые данные с него вымываются. Пройди цепочку prev_trans достаточно глубоко — и в какой-то момент liteserver просто перестанет отдавать транзакции: их физически уже нет в его окне. Свежий платёж ты увидишь, а транзакцию полугодовой давности — нет.
Это не баг, а дизайн: держать полный путь каждого аккаунта с генезиса дорого. Глубокую историю хранит архивная нода — узел, который не сбрасывает старые блоки, а держит полное состояние сети за всю историю.
Практический вывод:
- детект платежа, свежая выписка, «пришло ли за последний час» — хватает обычного liteserver;
- полный аудит кошелька с самого первого дня — нужна архивная нода.
Отдельная боль — публичные лайтсерверы из глобального конфига TON. Они общие и лимитированные: под нагрузкой часто отвечают not ready или уходят в ADNL-таймаут, и глубокой истории на них тоже нет. Если ты словил именно not ready, это симптом перегруженного общего лайтсервера — разбор в заметке почему liteserver отвечает not ready и как это чинить.
Честно про TONNode: архивная нода в роадмапе и сейчас синхронизируется — архивную глубину как готовую функцию я тебе не обещаю. На сегодня это чтение свежей и недавней истории через управляемый эндпоинт, без лотереи с публичными лайтсерверами. Для полного архива с самого генезиса жди отдельного анонса.
Как читать историю TON ИИ-агентом через MCP
TONNode — это hosted MCP-сервер для TON, ровно 16 инструментов, и get_transactions входит в блок чтения. MCP (Model Context Protocol) — стандарт, по которому агент вызывает инструменты сам, без того чтобы ты руками собирал ADNL-запросы. Не нужно ставить SDK, поднимать лайтсервер и разбирать TL-B — агент дёргает инструмент напрямую.
Бесплатное локальное подключение
Полный набор чтения, публичный конфиг, пакет @tonnode/mcp — open source, MIT:
{
"mcpServers": {
"ton": {
"command": "npx",
"args": ["-y", "@tonnode/mcp"]
}
}
}
Пакет работает по нативному ADNL-протоколу TON, без HTTP-прослоек.
Hosted-эндпоинт со своим ключом
Для гарантированной пропускной способности берётся hosted-эндпоинт со своим ключом:
{
"mcpServers": {
"ton": {
"type": "http",
"url": "https://mcp.tonnode.io/mcp",
"headers": { "Authorization": "Bearer tn_live_…" }
}
}
}
Для чтения истории пригодятся:
get_account_state— стартовая точка обхода (last_trans_lt/last_trans_hash), статус и флаги аккаунта;get_transactions— сами транзакции постранично, по паре(lt, hash);parse_address— офлайн-конвертация адресов междуEQ/UQ/raw;get_jetton_infoиget_jetton_balance— для истории по жетонам.
Практика: детект платежа и постраничный обход истории
Соберём типовой сценарий — «пришёл ли платёж и на какую сумму».
Промпт агенту
Через инструмент
tonвозьмиget_account_stateдляEQC…myshop, потомget_transactionsот егоlast_trans_lt/last_trans_hash, count 20. Найди входящий перевод на 5 GRAM за последние 10 минут. Адреса отправителей приводи черезparse_addressк EQ перед сравнением. Если нужна история глубже — бериprev_trans_lt/prev_trans_hashпоследней транзакции и повторяйget_transactions.
Агент сам сходит за головой цепочки, пролистнёт пачку и сверит суммы.
Псевдокод постраничного обхода
Тот же обход в псевдокоде (вызовы — это MCP-инструменты, которые дёргает агент):
state = get_account_state(account)
lt = state.last_trans_lt
hash = state.last_trans_hash
while lt != 0:
batch = get_transactions(account, lt, hash, count=20)
for tx in batch:
if matches_expected_payment(tx): # входящее сообщение с нужной суммой/комментом
return tx
last = batch[-1]
lt = last.prev_trans_lt
hash = last.prev_trans_hash # без hash следующая страница не запросится
Детали, экономящие часы отладки
- Адреса в полях транзакций приходят в raw-форме (
0:abcd…). Прежде чем сравнивать отправителя или получателя с привычнымEQ…-адресом, приведи оба к одному формату черезparse_address— иначе сравнение строк не сойдётся, хотя адрес один и тот же. Подробнее про форматы — EQ, UQ и raw в адресах TON. - Платежи в USDT не видны на цепочке кошелька GRAM. Жетоны живут на отдельном джеттон-кошельке, чей адрес вычисляется он-чейн (
get_jetton_balanceделает это за тебя и даёт текущий баланс). Для истории по жетонам обходи транзакции джеттон-кошелька, а не основного. - Пересчитывай raw-суммы через decimals. Суммы жетонов приходят в минимальных единицах: чтобы превратить
5000000в человеческие 5 USDT, возьмиdecimalsизget_jetton_info— у USDT это 6, у большинства джеттонов 9. Перепутаешь — ошибёшься в 1000 раз. Полный разбор — как ловить входящий платёж USDT на TON. - Дедуп по
(lt, hash), а не по сумме. Два одинаковых платежа различаются только парой lt+hash — это и есть ключ идемпотентности.
Если помимо истории агенту нужно дёргать текущее состояние контракта, это делает run_get_method — как звать get-методы без SDK, показано в чтении TON без SDK через get-метод.
Коротко
- В TON нет номеров блоков как в Ethereum — позиция транзакции задаётся парой
(lt, hash): lt — порядок, hash — идентификация, и они обязательны вместе. - Транзакции аккаунта — связный список через
prev_trans_lt/prev_trans_hash; листаешь назад, начиная сlast_transaction_idизget_account_state. - Обычный liteserver отдаёт свежую и недавнюю историю; полная глубина — задача архивной ноды.
- Для джеттонов не забудь про decimals из
get_jetton_info(USDT = 6) и raw-адреса, которые надо прогнать черезparse_address.
Не хочешь возиться с публичными лайтсерверами, которые отвечают not ready? get_transactions доступен из коробки на бесплатном тарифе Hobby — 60 запросов/мин, навсегда, без карты, ключ выдаётся сразу после входа.
Подключи чтение истории TON за минуту → tonnode.io/dashboard?plan=hobby
А что ещё умеет агент на TON помимо чтения истории — все 16 инструментов на странице инструментов TONNode.
Дайте вашему агенту доступ к TON
16 MCP-инструментов: чтение, некастодиальные свапы, кроссчейн и кошельки. Бесплатный тариф — 60 запр/мин, карта не нужна.