Історія транзакцій 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 зап/хв, картка не потрібна.