Все статьи
6 мин чтения

Как узнать баланс USDT на кошельке TON одним вызовом

Как получить баланс USDT на кошельке TON одним вызовом get_jetton_balance — джеттон-кошелёк считается он-чейн, индексер не нужен.

баланс USDT TONget_jetton_balanceджеттоны TONMCP для TONTONNodedecimals жетона

Кошелёк агента открыт, 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.

Дальше сервер сам делает всю работу он-чейн:

  1. вызывает get-метод мастер-контракта жетона, который по адресу владельца возвращает адрес его джеттон-кошелька (тот самый детерминированный вывод);
  2. читает баланс с этого джеттон-кошелька и возвращает его вам.

Никакого стороннего индексера, никакой базы, никакой рассинхронизации — только прямое чтение состояния сети через 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/mcpopen 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 запр/мин, карта не нужна.