All posts
8 min read

Jetton Decimals on TON — Why USDT Uses 6, Not 9

Jetton decimals on TON explained: why USDT uses 6 while most jettons use 9, how get_jetton_info returns decimals, and why it matters for balances and swaps

jetton decimalsUSDT on TONjetton metadataTEP-64TON MCPraw units

You're building an agent that shows a USDT balance. You query the jetton wallet, get back 5000000, divide by 10^9 — and the screen says 0.005 USDT instead of an honest 5 USDT. Transactions go through, the RPC responds, the get-method returns a number — yet the amount is off by exactly 1000x. The culprit is a single digit that almost everyone hardcodes out of habit: decimals.

If you're wiring an AI agent into TON or doing jetton math by hand, decimals is the first thing to understand before touching any balances or swaps. Let's dig into why USDT has decimals = 6 instead of TON's usual 9, where that number even comes from, and how to fetch it with a single call instead of guessing.

Jetton decimals on TON: raw units vs the human-readable amount

A blockchain can't store fractions. At all. On TON, like almost everywhere else, every amount lives in the contract as an integer — so-called raw units (the smallest indivisible units). There is no 5.5 anywhere in a contract's dictionary and there never will be — only an integer like 5500000.

To turn that integer into an amount a human can read, you need a scale factor. That scale is decimals — the power of ten by which the stored integer is shifted relative to the human-readable amount:

human = raw / 10^decimals
raw   = human * 10^decimals

The analogy is simple. The money in your wallet is in dollars, but the bank's ledger counts everything in cents, as integers: 100 cents = 1 dollar, i.e. decimals = 2. Need to show a human dollars? Divide the cents by 10^2 = 100. Jettons work exactly the same way, just with a different power of ten.

The key point: the blockchain itself has no idea where the decimal point goes. It operates on integer raw units. Where to put the point when displaying an amount is the client's call, made by reading decimals from the jetton's metadata. Get decimals wrong — the point slides, and the amount you show is pure fiction.

USDT decimals on TON = 6, most other jettons = 9

Two numbers to memorize:

  • USDT (Tether) on TON → decimals = 6. Meaning 1 USDT = 1 000 000 raw units.
  • Most other TON jettons → decimals = 9. That's also the default: if the decimals field is missing from the metadata entirely, the standard requires clients to assume 9.

Where the nine comes from. GRAM itself (formerly Toncoin, renamed in June 2026 — the network is still called TON) has 9 decimal places: 1 GRAM = 10^9 nanograms, and that "nano" is the raw unit. TON's jetton standard inherited the value as its default, and the vast majority of tokens on the network live with decimals = 9. Developers get used to typing / 1e9 without a second thought.

USDT, though, is a guest from the Ethereum world, where Tether has historically used 6 decimals. The issuer kept that familiar precision on TON as well. So USDT is the one exception that trips up nearly everyone who "just hardcoded 9" — and it happens to be the single most-used jetton for payments.

Let's price the mistake. Say a jetton wallet holds 5 000 000 raw units of USDT:

  • correct (decimals = 6): 5 000 000 / 10^6 = 5 USDT;
  • wrong (decimals = 9): 5 000 000 / 10^9 = 0.005.

Off by a factor of exactly 10^(9−6) = 1000. And in reverse: if the user types "send 5 USDT" and you multiply by 10^9, you'll try to move 1000 times more than intended. For a payment bot, that's the difference between "paid" and "declined".

Where decimals comes from: jetton metadata and the TEP-64 standard

Here's the important part: decimals is not a field in wallet code and not a protocol constant. It's part of a specific jetton's metadata, defined by the TEP-64 standard (Token Data Standard). Every jetton declares its own decimals, name, symbol, and image.

TEP-64 allows metadata to be stored in three formats:

  • on-chain — all fields sit in a dictionary right inside the jetton contract; nothing to download;
  • off-chain — the contract holds only a link (uri), and the full JSON with the fields (name, symbol, decimals, image) lives at that URI on a web server or in IPFS;
  • semi-chain (hybrid) — some fields in the on-chain dictionary, some behind the uri; the client downloads the off-chain content and merges it with the dictionary values.

The TEP-64 merge rule goes like this: if the dictionary contains a uri key, the client must fetch the off-chain content at that link and merge it with the values from the on-chain dictionary. Different formats mean different read costs: on-chain takes a single get-method call, off-chain needs an extra HTTP request on top. Handling all three cases by hand is its own special kind of fun — and this is exactly where it's nice to have a single tool do it for you.

USDT metadata: how get_jetton_info returns decimals = 6

USDT on TON stores its metadata in the off-chain format: the jetton master contract holds a link to an off-chain JSON, and that JSON is where name, symbol and — crucially — decimals = 6 are written. To honestly assemble a jetton's profile, a client has to read the contract, extract the link, download the JSON behind it, and parse the fields.

You don't need to keep the TEP-64 format in your head or parse dictionary cells manually: that's precisely the work one tool takes off your plate. get_jetton_info reads the contract, parses the metadata, and hands back a ready-made decimals = 6 — and all the arithmetic that follows builds on that.

Getting decimals in one call: get_jetton_info

Instead of manually reading the dictionary, detecting the format (on/off/semi-chain), following URIs and merging JSON, there's get_jetton_info. It's a tool on the TONNode MCP server: it takes a jetton master contract address and returns assembled metadata — name, symbol, decimals, and total supply.

TONNode is a hosted MCP server for TON. MCP (Model Context Protocol) is the standard that AI agents (Claude, Cursor, ChatGPT/Codex, any MCP client) use to call tools. Hook it up locally for free, with the full read toolset:

{
  "mcpServers": {
    "ton": { "command": "npx", "args": ["-y", "@tonnode/mcp"] }
  }
}

The @tonnode/mcp package is open source (MIT) and talks TON's native ADNL protocol — no HTTP middlemen. From there, a plain human-language request is all your agent needs:

Using the get_jetton_info tool, look up the decimals of the USDT jetton on TON (master contract address goes here) and work out what a balance of 5,000,000 raw comes to in human-readable USDT.

get_jetton_info will return decimals = 6, and every bit of arithmetic from then on rests on that number, not on an assumption. The same call for any other jetton returns that jetton's own value (usually 9, but always check). The one rule that matters:

Don't hardcode 9. Read decimals from get_jetton_info for every jetton.

Nine will work for most tokens and silently break on USDT — which is exactly the jetton where real money moves most often. Why an AI agent needs dedicated MCP access to TON instead of a rate-limited public RPC is covered in the post on the MCP server for TON agents.

Why the wrong decimals breaks balances and swaps

decimals isn't display cosmetics. It shows up everywhere an amount crosses the "raw ⇄ human" boundary, and one mistake propagates down the entire chain.

Balances

get_jetton_balance returns the jetton balance in raw units (the correct jetton wallet is computed on-chain — you don't have to hunt for it yourself). On its own, that integer means nothing without decimals — you can't display it to the user correctly until you divide by 10^decimals:

raw = 5 000 000
decimals = 6  → 5 USDT      ✅
decimals = 9  → 0.005       ❌ (1000x too small)

The right order inside an agent: get_jetton_info first → take decimals, then get_jetton_balance → divide raw by 10^decimals. And for how public liteservers hit their limits on exactly this kind of read traffic, see the breakdown of TON public liteserver limits.

Swaps

get_swap_quote returns a firm DEX quote for GRAM⇄jetton (via the Omniston protocol, STON.fi + DeDust liquidity) — and both the input amount and the estimated output are in the jetton's raw units too. Here a wrong decimals bites twice. Say the user wants to swap 10 USDT: with decimals = 6 the quote needs 10 000 000 raw. Apply 9 by mistake and you've requested 10 000 000 000 raw — a swap for 10 000 USDT that the wallet doesn't have. Make the reverse mistake when parsing the response, and the expected output shrinks 1000-fold — leaving the user convinced the rate is highway robbery.

The same principle applies to cross-chain quotes and to any transaction: on-chain, everything is integer raw units, and the only bridge to human-readable amounts is the correct decimals.

Putting it into practice: get_jetton_balance and get_swap_quote

Let's assemble a short scenario an agent can run end to end, with not a single hardcoded digit.

The step-by-step

1. Look up decimals. get_jetton_info(USDT master) → decimals = 6.

2. Read the balance. get_jetton_balance(owner, USDT master) → 5 000 000 (raw). Compute: 5 000 000 / 10^6 = 5 USDT. Had we used 9, we'd get 0.005. That's your smoke test: if a USDT figure comes back unexpectedly tiny, check first whether 9 was applied instead of 6.

3. Request a quote. We want to swap 5 USDT into GRAM. In raw that's 5 × 10^6 = 5 000 000 — and that's the exact amount to pass into get_swap_quote. The result comes back in raw too, and GRAM is 9-decimal, so divide the response by 10^9 before showing it to the user.

4. Don't trust the metadata? You can double-check on-chain directly. run_get_method calls any read-only get-method on a contract — the same way you can hit get_jetton_data on the master contract and confirm the numbers line up. It's more flexible, but it means knowing the TEP-64 format and parsing the dictionary by hand; when speed and reliability matter, get_jetton_info settles it in one response. For how TON RPC providers differ in get-method and archive access, see the honest comparison of TON RPC providers.

The agent prompt

The prompt for the whole scenario looks downright mundane:

Take USDT on TON. Read decimals via get_jetton_info. Fetch my balance in raw via get_jetton_balance and convert it to human-readable USDT. Then use get_swap_quote to price a swap of 5 USDT into GRAM — don't forget to convert 5 USDT into raw using the decimals you read.

One more detail for agent scenarios: TONNode's swap tools are strictly non-custodial. The server never signs anything and never holds keys — build_swap_tx returns an unsigned TonConnect message that the user's wallet signs. Even a correct decimals gives the server zero control over funds: all it does is compute and assemble the transaction. For where milliseconds and latency leak in TON swaps, see the deep dive on high-speed trading on TON.

TL;DR

  • decimals is the power of ten between raw units on the blockchain and the human-readable amount: human = raw / 10^decimals.
  • The TON default is 9 decimals; USDT is 6 (1 USDT = 1 000 000 raw). Mix them up and you're off by 1000x.
  • decimals lives in the jetton's metadata (TEP-64), not in code. USDT stores its metadata off-chain, yet get_jetton_info still returns decimals = 6 in a single response.
  • Don't hardcode 9. Read decimals via get_jetton_info and build every balance (get_jetton_balance) and every quote (get_swap_quote) on top of it.

Grab a free Hobby key (60 requests/min, no card required) and read any jetton's decimals via get_jetton_info right now: https://tonnode.io/dashboard?plan=hobby

Give your agent TON access

16 MCP tools: reads, non-custodial swaps, cross-chain and wallets. Free tier — 60 req/min, no card required.