How to Check a USDT Balance on a TON Wallet in One Call
Get a TON wallet's USDT balance with one get_jetton_balance call — the jetton wallet is computed on-chain, no indexer required.
Your agent's wallet is up, get_balance dutifully returns a few GRAM — and USDT shows zero. Even though you know for a fact that stablecoins have been sent to this address. Sound familiar? The wallet looks empty while 500 USDT is sitting right there. That's not a bug, and the money isn't lost — you just asked the wrong contract. This is exactly where most first-time AI-agent integrations with TON fall over: the agent reads the wrong address.
Why a USDT balance is not your TON wallet's balance
On Ethereum you're used to an ERC-20 token "living at your address": the token contract keeps an address → balance table, and to read a balance you query that one contract. TON works differently, and that's the first source of confusion.
The native coin GRAM (formerly Toncoin; the network is still called TON) sits directly on your wallet's smart contract — get_balance reads it with a single account-state lookup. USDT, however, is a jetton (TON's standard for fungible tokens). And a jetton balance is stored not on your main wallet but on a separate tiny contract — the jetton wallet.
Here's an analogy: your main TON wallet is you as a person. Your USDT jetton wallet is a bank account opened in your name by one specific bank (the USDT master contract). Asking the person "how many dollars do you have" is pointless — the money is in the account, not in their pocket. Every owner-plus-token pair gets its own jetton wallet: one address for USDT, another for NOT, a third for any other jetton.
The good news: the jetton wallet's address is not random. It's derived deterministically from two things:
- the owner's address (your regular TON wallet,
EQ…/UQ…); - the address of the jetton's master contract (the jetton master — for USDT that's one fixed contract).
The bad news: to compute that address properly, you have to call a get-method on the master contract and then read the jetton wallet's state. Done by hand, that's several steps — and those steps are exactly where integrations trip up.
get_jetton_balance: one call instead of an indexer
This problem is usually solved in one of two ways, and both are painful:
- Run your own indexer. Spin up a node, index jetton transfers into a database, keep it fresh. Expensive and fragile for the sake of a single number, and the data always lags slightly behind the chain.
- Use a public HTTP API. You hit the rate limits fast: without a key it's roughly one request per second, and once you exceed that you get an honest
HTTP 429 Too Many Requests. Plus you depend on someone else's indexing and how deep it goes.
The get_jetton_balance tool from TONNode removes both problems. You pass it:
- the owner's address — the user's regular TON wallet (
EQ…/UQ…); - the jetton identifier — USDT, for example.
The server then does all the work on-chain:
- it calls the jetton master contract's get-method, which takes the owner's address and returns the address of their jetton wallet (that deterministic derivation);
- it reads the balance from that jetton wallet and returns it to you.
No third-party indexer, no database, no sync drift — just direct reads of network state through contract get-methods.
TONNode is a hosted MCP server for TON. MCP (Model Context Protocol) is the standard that lets AI agents (Claude, Cursor, ChatGPT/Codex, and any MCP client) call external tools. In other words, get_jetton_balance isn't a line in your code — it's a tool the agent invokes on its own when a user asks about a balance. Under the hood, the @tonnode/mcp package (open source, MIT) speaks TON's native ADNL protocol, with no HTTP middlemen — the agent talks to the network directly instead of going through yet another REST gateway.
Example: the prompt and what comes back
The setup is local — no key, no card. Add this to your MCP client config (Claude Desktop, Cursor, anything MCP-compatible):
{
"mcpServers": {
"ton": {
"command": "npx",
"args": ["-y", "@tonnode/mcp"]
}
}
}
After that, a plain natural-language prompt is all the agent needs:
How much USDT is in wallet
UQAbc…xyz? Return a human-readable value.
The agent picks the get_jetton_balance tool itself, passes the owner address and the USDT jetton, and the server returns the balance — but not in the familiar "dollars". It comes back in raw units (the jetton's smallest indivisible units). Along with the balance, the response includes the jetton wallet address the server computed on-chain — handy if you later want to track transfers on that specific contract. The balance itself in our example looks like 12500000.
But 12500000 is not 12.5 million USDT. This is the second trap, and it's an easy one to fall into.
Raw units and decimals: why USDT has 6, not 9
Blockchains don't do fractions. Every amount is stored as an integer in raw units, and the "human" value comes from dividing by 10^decimals, where decimals is a property of the specific jetton. Think cents and dollars: everything at the low level is in cents, and decimals tells you where to put the decimal point.
The key TON nuance: different jettons have different decimals.
- Native GRAM and most TON jettons use
decimals = 9. - USDT on TON uses
decimals = 6.
So the same raw value can mean wildly different amounts. Let's redo our example correctly:
raw = 12500000
decimals = 6 // USDT specifically
human = 12500000 / 10^6 = 12500000 / 1_000_000 = 12.5 USDT
12.5 USDT, not 12.5 million. If you had divided by 10^9 out of habit (as you would for a typical jetton), you'd get 0.0125 — off by a factor of a thousand. Same number, three orders of magnitude apart. This is precisely how money gets lost in naive integrations: a hardcoded 10^9 divisor applied to everything. So never hardcode decimals — read them from the jetton's own metadata.
get_jetton_info: where decimals and jetton metadata come from
To take the guesswork out of it, there's a companion tool — get_jetton_info. It reads the jetton's master contract and returns its metadata:
- name;
- symbol;
- decimals — the number that determines the divisor;
- total supply.
The reliable pattern for an agent is two calls: for an unfamiliar jetton, first get_jetton_info to learn decimals, then get_jetton_balance to fetch the raw balance, and only then the raw / 10^decimals division for display.
1) get_jetton_info(USDT) -> decimals = 6
2) get_jetton_balance(owner, USDT) -> raw = 12500000
3) human = 12500000 / 10^6 -> 12.5 USDT
You can phrase the prompt so the agent chains those steps on its own:
Get the decimals for USDT via get_jetton_info, then the balance of wallet
UQAbc…xyzvia get_jetton_balance, and convert the raw value into a human-readable number.
This order is mandatory if you work with arbitrary jettons rather than just USDT: for an unknown token you can't know in advance whether it has 6 decimals or 9. For USDT, decimals = 6 is a constant, but the habit of pulling it from get_jetton_info will save you on the very first non-standard jetton you run into. The same technique underpins detecting incoming USDT payments on TON — there, decimals are critical so you don't mistake 1 USDT for microscopic dust.
parse_address: an offline address check before the query
Another common cause of a "zero" balance is a mangled owner address. Users paste addresses in all sorts of formats: EQ… (bounceable), UQ… (non-bounceable), raw (0:…). Before asking for a balance, it pays to normalize the address with parse_address — it works offline (no network calls), takes an address in any of those formats, converts it into all three (EQ/UQ/raw), and tells you whether it's valid at all.
It's cheap, instant, and eliminates an entire class of "balance is 0 because the address is wrong" errors — especially when the address came from an untrusted source, user input, or a chat message.
How to connect: free locally, or hosted
Three tools — parse_address, get_jetton_info, get_jetton_balance — give you a complete, honest answer to "how much USDT is in this wallet" without a single third-party indexer.
Free, locally
The @tonnode/mcp package is open source (MIT), published on npm and GitHub. The same public config from the example above (npx -y @tonnode/mcp) gives you the full set of read tools, including get_jetton_balance, get_jetton_info, and parse_address — no separate key needed.
Restart your client, and the agent is already reading jetton balances over ADNL. Why an agent needs a dedicated MCP server instead of a rate-limited public gateway is covered in our post on MCP for AI agents on TON. And where the free public config ends and why — in the breakdown of public TON liteserver limits.
Hosted — when you need throughput
When the local rate limit stops being enough (bots, backends, production load), you switch to the hosted endpoint with your own key. The toolset is identical everywhere — all 16 tools, the read block included; the plans differ only in guaranteed throughput:
- Hobby — free forever, 60 requests/min;
- Pro — $29/mo, 300 requests/min;
- Scale — $199/mo, 1200 requests/min.
The hosted setup differs only in that you hit a shared endpoint with your key:
{
"mcpServers": {
"ton": {
"type": "http",
"url": "https://mcp.tonnode.io/mcp",
"headers": { "Authorization": "Bearer tn_live_…" }
}
}
}
If you're building a dashboard, a balance-checking bot, or an agent that polls addresses frequently — grab a key so you don't run into public API limits and catch a 429 at the worst possible moment.
TL;DR
- USDT on TON is a jetton, and the balance lives on a separate jetton wallet, not on your main address.
get_jetton_balancecomputes the jetton wallet address on-chain and reads the balance itself — no indexer, no database of your own.- The balance comes back in raw units; divide by
10^decimalsfor display. - USDT has
decimals = 6(divisor1_000_000); most jettons have9. Don't hardcode it — pull it fromget_jetton_info. - You can test the whole path for free:
npx -y @tonnode/mcp, the public config, the full read toolset.
A free Hobby key — 60 requests/min, no card — is issued right after sign-in: get a key. It's enough to run parse_address → get_jetton_info → get_jetton_balance against a real wallet and see for yourself that 12500000 raw turns into exactly 12.5 USDT — not 12.5 million and not 0.0125.
Give your agent TON access
16 MCP tools: reads, non-custodial swaps, cross-chain and wallets. Free tier — 60 req/min, no card required.