Як дізнатися баланс USDT на гаманці TON одним викликом
Як отримати баланс USDT на гаманці TON одним викликом get_jetton_balance — джетон-гаманець обчислюється он-чейн, індексер не потрібен.
Гаманець агента відкритий, get_balance повернув чесні кілька GRAM — а USDT нуль. Хоча ви точно знаєте, що стейблкоїни на цю адресу приходили. Знайомо? Гаманець виглядає порожнім, хоча на ньому лежить 500 USDT. Це не баг і не зниклі гроші — ви просто спитали не в того контракту. І саме тут падає більшість перших інтеграцій ШІ-агента з TON: агент читає не ту адресу.
Чому баланс USDT — це не баланс TON-гаманця
В Ethereum ви звикли, що токен ERC-20 «лежить на адресі»: контракт токена зберігає табличку address → balance, і щоб дізнатися баланс, ви звертаєтеся до цього єдиного контракту. У TON модель інша, і це перше джерело плутанини.
Нативна монета GRAM (колишній Toncoin; мережа, як і раніше, називається TON) лежить безпосередньо на смарт-контракті вашого гаманця — її читає get_balance одним зверненням до стану акаунта. А USDT — це джетон (jetton, стандарт TON для взаємозамінних токенів). І баланс джетона зберігається не на вашому основному гаманці, а на окремому маленькому контракті — джетон-гаманці (jetton wallet).
Аналогія: ваш основний TON-гаманець — це ви як людина. А джетон-гаманець USDT — це окремий рахунок на ваше ім’я, відкритий конкретним банком (майстер-контрактом USDT). Питати «скільки в мене доларів» у самої людини безглуздо — гроші на рахунку, а не в кишені. У кожної пари «власник + джетон» — свій джетон-гаманець: для USDT одна адреса, для NOT інша, для будь-якого джетона третя.
Добра новина: адреса цього джетон-гаманця не випадкова. Вона детерміновано виводиться з двох речей:
- адреси власника (ваш звичайний TON-гаманець,
EQ…/UQ…); - адреси майстер-контракту джетона (jetton master — для USDT це один фіксований контракт).
Погана новина: щоб чесно обчислити цю адресу, треба звернутися до майстер-контракту, викликати його get-метод, а потім прочитати стан джетон-гаманця. Вручну це кілька кроків, і саме на них спотикаються інтеграції.
get_jetton_balance: один виклик замість індексера
Зазвичай цю задачу розв’язують одним із двох способів, і обидва незручні:
- Власний індексер. Підняти ноду, проіндексувати джетон-перекази в базу, підтримувати її в актуальному стані. Дорого й ненадійно заради одного числа, а дані завжди трохи відстають від чейна.
- Публічний HTTP-API. Швидко впирається в ліміти: без ключа це близько одного запиту на секунду, а в разі перевищення ви отримуєте чесний
HTTP 429 Too Many Requests. Плюс ви залежите від чужої індексації та її глибини.
Інструмент get_jetton_balance із TONNode прибирає обидві проблеми. Ви передаєте йому:
- адресу власника — звичайний TON-гаманець користувача (
EQ…/UQ…); - ідентифікатор джетона — наприклад, USDT.
Далі сервер сам робить усю роботу он-чейн:
- викликає get-метод майстер-контракту джетона, який за адресою власника повертає адресу його джетон-гаманця (те саме детерміноване виведення);
- читає баланс із цього джетон-гаманця і повертає його вам.
Жодного стороннього індексера, жодної бази, жодної розсинхронізації — лише пряме читання стану мережі через get-методи контрактів.
TONNode — це hosted MCP-сервер для TON. MCP (Model Context Protocol) — стандарт, за яким ШІ-агенти (Claude, Cursor, ChatGPT/Codex і будь-який MCP-клієнт) викликають зовнішні інструменти. Тобто get_jetton_balance — це не рядок у вашому коді, а інструмент, який агент смикає сам, коли користувач питає про баланс. Під капотом пакет @tonnode/mcp (open source, MIT) працює за нативним ADNL-протоколом TON, без проміжних HTTP-прошарків — агент спілкується з мережею напряму, а не через черговий REST-шлюз.
Приклад: промпт агенту і що повертається
Підключення локальне, без ключа і без картки. Додайте в конфіг MCP-клієнта (Claude Desktop, Cursor, будь-який MCP-сумісний):
{
"mcpServers": {
"ton": {
"command": "npx",
"args": ["-y", "@tonnode/mcp"]
}
}
}
Після цього агенту досить звичайного промпта природною мовою:
Скільки USDT на гаманці
UQAbc…xyz? Поверни людиночитане значення.
Агент сам вибере інструмент get_jetton_balance, передасть адресу власника і джетон USDT, а сервер поверне баланс — але не у звичних «доларах», а в raw-одиницях (мінімальних неподільних одиницях джетона). Крім самого балансу, у відповіді приходить і адреса джетон-гаманця, яку сервер обчислив он-чейн, — корисно, якщо далі ви хочете відстежувати перекази саме цього контракту. Сам баланс у нашому прикладі — значення на кшталт 12500000.
Але 12500000 — це не 12.5 мільйона USDT. Тут починається друга пастка, на якій легко помилитися.
Raw-одиниці та decimals: чому в USDT 6, а не 9
Блокчейни не працюють із дробами. Усі суми зберігаються в цілочисельних raw-одиницях, а «людське» значення виходить діленням на 10^decimals, де decimals — властивість конкретного джетона. Це як копійки та гривні: на низькому рівні все в копійках, а decimals каже, де поставити кому.
Ключовий нюанс TON: у різних джетонів різна кількість decimals.
- У нативного GRAM і в більшості джетонів TON
decimals = 9. - А в USDT на TON
decimals = 6.
Тому те саме raw-значення означає геть різні суми. Перераховуємо наш приклад правильно:
raw = 12500000
decimals = 6 // саме для USDT
human = 12500000 / 10^6 = 12500000 / 1_000_000 = 12.5 USDT
12.5 USDT, а не 12.5 мільйона. Якби ви за інерцією поділили на 10^9 (як для звичайного джетона), отримали б 0.0125 — помилка в тисячу разів. Те саме число — три порядки різниці. Саме так губляться гроші в наївних інтеграціях: жорстко зашитий дільник 10^9 для всього підряд. Тому ніколи не хардкодьте decimals — беріть їх із метаданих самого джетона.
get_jetton_info: звідки взяти decimals і метадані джетона
Щоб не вгадувати, є парний інструмент — get_jetton_info. Він читає майстер-контракт джетона і віддає його метадані:
- ім’я (name);
- символ (symbol);
- decimals — те саме число, за яким рахується дільник;
- емісію (total supply).
Надійний сценарій для агента — два виклики: якщо джетон незнайомий, спершу get_jetton_info, щоб дізнатися decimals, потім get_jetton_balance, щоб узяти raw-баланс, і лише потім ділення raw / 10^decimals для відображення.
1) get_jetton_info(USDT) -> decimals = 6
2) get_jetton_balance(owner, USDT) -> raw = 12500000
3) human = 12500000 / 10^6 -> 12.5 USDT
Промпт можна сформулювати так, щоб агент сам склеїв ці кроки:
Візьми decimals для USDT через get_jetton_info, потім баланс гаманця
UQAbc…xyzчерез get_jetton_balance і переведи raw у людиночитане число.
Такий порядок обов’язковий, якщо ви працюєте з довільними джетонами, а не лише з USDT: для незнайомого токена ви заздалегідь не знаєте, 6 у нього decimals чи 9. Для USDT decimals = 6 — константа, але звичка тягнути її з get_jetton_info врятує вас на першому ж нестандартному джетоні. Той самий прийом лежить в основі детекту вхідних платежів USDT на TON — там decimals критичні, щоб не сплутати 1 USDT із мікроскопічним пилом.
parse_address: офлайн-перевірка адреси перед запитом
Ще одна часта причина «нульового» балансу — крива адреса власника. Користувачі надсилають адреси в різних форматах: EQ… (bounceable), UQ… (non-bounceable), raw (0:…). Перш ніж питати баланс, адресу корисно нормалізувати через parse_address — він офлайн (без звернення до мережі) бере адресу в будь-якому з форматів, конвертує її в усі три (EQ/UQ/raw) і каже, чи коректна вона взагалі.
Це дешево, миттєво і прибирає цілий клас помилок «баланс 0, бо адреса не та» — особливо якщо адреса прийшла з ненадійного джерела, користувацького введення або чату.
Як підключити: безкоштовно локально або hosted
Три інструменти — parse_address, get_jetton_info, get_jetton_balance — дають повну, чесну відповідь на питання «скільки USDT на гаманці» без жодного стороннього індексера.
Безкоштовно локально
Пакет @tonnode/mcp — open source (MIT), лежить на npm і GitHub. Той самий публічний конфіг із прикладу вище (npx -y @tonnode/mcp) дає повний набір інструментів читання, включно з get_jetton_balance, get_jetton_info і parse_address — окремий ключ не потрібен.
Перезапускаєте клієнт — і агент уже читає баланси джетонів через ADNL. Навіщо агенту взагалі окремий MCP-сервер, а не публічний шлюз із лімітами, розбирали в нотатці про MCP для ШІ-агентів на TON. А де закінчується безкоштовний публічний конфіг і чому — у розборі лімітів публічних лайтсерверів TON.
Hosted — коли потрібна пропускна здатність
Коли локального рейту вже мало (боти, бекенди, продакшн-навантаження), перемикаєтеся на hosted-ендпоїнт зі своїм ключем. Набір інструментів скрізь однаковий — усі 16, включно з блоком читання; тарифи відрізняються лише гарантованою пропускною здатністю:
- Hobby — безкоштовно назавжди, 60 запитів/хв;
- Pro — $29/міс, 300 запитів/хв;
- Scale — $199/міс, 1200 запитів/хв.
Hosted-підключення відрізняється лише тим, що ви звертаєтеся до спільного ендпоїнта зі своїм ключем:
{
"mcpServers": {
"ton": {
"type": "http",
"url": "https://mcp.tonnode.io/mcp",
"headers": { "Authorization": "Bearer tn_live_…" }
}
}
}
Якщо робите дашборд, бота для перевірки балансів або агента, який часто опитує адреси, — беріть ключ, щоб не впиратися в ліміти публічних API і не ловити 429 у найневдаліший момент.
Коротко
- USDT на TON — джетон, і баланс лежить на окремому джетон-гаманці, а не на основній адресі.
get_jetton_balanceсам обчислює адресу джетон-гаманця он-чейн і читає баланс — індексер і власна база не потрібні.- Баланс приходить у raw-одиницях; для відображення діліть на
10^decimals. - У USDT
decimals = 6(дільник1_000_000), у більшості джетонів —9. Не хардкодьте — беріть ізget_jetton_info. - Перевірити весь шлях можна безкоштовно:
npx -y @tonnode/mcp, публічний конфіг, повний набір читання.
Безкоштовний ключ Hobby — 60 запитів/хв, без картки — видається одразу після входу: отримати ключ. Його вистачає, щоб прогнати parse_address → get_jetton_info → get_jetton_balance на реальному гаманці й переконатися, що 12500000 raw перетворюються рівно на 12.5 USDT — а не на 12.5 мільйона і не на 0.0125.
Дайте вашому агенту доступ до TON
16 MCP-інструментів: читання, некастодіальні свопи, кросчейн і гаманці. Безкоштовний тариф — 60 зап/хв, картка не потрібна.