TON Jetton 的 decimals 是什么(USDT 是 6,不是 9)
解析 TON 上 Jetton 的 decimals:为什么 USDT 是 6 而多数 Jetton 是 9,get_jetton_info 如何返回 decimals,以及为什么它决定余额换算的成败
你在写一个显示 USDT 余额的智能体。拉取 Jetton 钱包的余额,拿到 5000000,除以 10^9——屏幕上出现的是 0.005 USDT,而不是实打实的 5 USDT。交易正常上链,RPC 有响应,get 方法返回了数字——可金额还是整整偏了 1000 倍。罪魁祸首是一个几乎所有人都习惯性硬编码的数字:decimals。
如果你在把 AI 智能体接入 TON,或者手动计算 Jetton 金额,那么 decimals 是做任何余额和兑换之前必须先搞懂的第一件事。下面我们来拆解:为什么 USDT 的 decimals = 6 而不是 TON 上惯见的 9,这个数字究竟从哪来,以及如何一次调用拿到它,而不是靠猜。
TON 上 Jetton 的 decimals:raw 单位与人类可读数字
区块链存不了小数。完全不行。在 TON 上——和几乎所有链一样——任何金额在合约里都是以整数形式存放的,也就是所谓的 raw 单位(最小不可分割单位)。合约的字典里没有也不可能有 5.5,只有类似 5500000 这样的整数。
要把这个整数变成人类看到的金额,需要一个换算比例。这个比例就是 decimals——链上存储的整数相对人类可读金额偏移的十的幂次:
human = raw / 10^decimals
raw = human * 10^decimals
打个简单的比方。你钱包里的钱以元计价,但银行的账本全部用分记账,而且全是整数:100 分 = 1 元,也就是 decimals = 2。想给人看多少元,就把分除以 10^2 = 100。Jetton 完全一样,只是十的幂次不同。
关键在于:区块链本身并不知道数字的“小数点”在哪。它只处理整数 raw 单位。展示给用户时小数点该放哪,由客户端读取 Jetton 元数据里的 decimals 来决定。decimals 搞错了——小数点跑偏,金额瞬间面目全非。
TON 上 USDT 的 decimals = 6,而多数 Jetton 是 9
两个必须记住的数字:
- TON 上的 USDT(Tether)→
decimals = 6。 也就是说1 USDT = 1 000 000 raw 单位。 - TON 上绝大多数其他 Jetton →
decimals = 9。 这同时也是默认值:如果元数据里根本没写decimals字段,按标准,客户端必须将其视为9。
先说这个 9 的来历。GRAM 本身(原 Toncoin,2026 年 6 月更名——网络仍然叫 TON)就是 9 位精度:1 GRAM = 10^9 nanogram,这里的“nano”正是 raw 单位。TON 的 Jetton 标准把这个值继承为默认值,链上绝大多数代币也确实都是 decimals = 9。于是开发者习惯了闭着眼写 / 1e9。
而 USDT 是来自 Ethereum 世界的“客人”:Tether 在那边历来就是 6 位精度。发行方在 TON 上也保留了同样的精度。所以 USDT 成了那个让几乎每个“直接硬编码 9”的人都栽跟头的例外——而它偏偏是支付场景里最常用的 Jetton。
算一算这个错误的代价。假设 Jetton 钱包里躺着 5 000 000 个 USDT raw 单位:
- 正确(
decimals = 6):5 000 000 / 10^6 = 5 USDT; - 错误(
decimals = 9):5 000 000 / 10^9 = 0.005。
整整偏了 10^(9−6) = 1000 倍。反过来也一样:用户输入“发送 5 USDT”,你却乘以 10^9,就会试图转走 1000 倍的金额。对支付机器人来说,这就是“支付成功”和“被拒绝”的差别。
decimals 从哪来:Jetton 元数据与 TEP-64 标准
必须理解一点:decimals 既不是钱包代码里的字段,也不是协议常量。 它是具体某个 Jetton 的元数据的一部分,由 TEP-64(Token Data Standard)标准定义。每个 Jetton 自行声明自己的 decimals、名称、符号和图标。
TEP-64 允许以三种格式存储元数据:
- on-chain——所有字段直接放在 Jetton 合约内的字典(dictionary)里,什么都不用下载;
- off-chain——合约里只有一个链接(
uri),包含各字段(name、symbol、decimals、image)的完整 JSON 存放在该 URI 指向的 Web 服务器或 IPFS 上; - semi-chain(混合)——一部分字段在链上字典里,另一部分通过
uri获取;客户端下载 off-chain 内容并将其与字典里的值合并。
TEP-64 的合并规则是:只要字典里存在 uri 键,客户端就必须下载链接指向的 off-chain 内容,并将其与链上字典的值合并。不同格式的读取成本也不同:on-chain 一次 get 方法就能读完,off-chain 还得多发一次 HTTP 请求。手动处理这三种情况是件“乐趣十足”的事——正因如此,让一个工具替你把这些全包了才格外省心。
USDT 的元数据:get_jetton_info 如何返回 decimals = 6
TON 上的 USDT 采用 off-chain 格式存储元数据:Jetton 主合约里放的是指向 off-chain JSON 的链接,name、symbol 以及最关键的 decimals = 6 都写在那个 JSON 里。想老老实实拼出这个 Jetton 的完整信息卡,客户端需要读合约、取出链接、下载 JSON、再解析字段。
不过你不必把 TEP-64 格式背在脑子里,也不必手动解析字典里的 cell:这正是一个工具能替你包揽的活。get_jetton_info 会自己访问合约、解析元数据,直接返回现成的 decimals = 6——后面所有的算术都建立在它之上。
一次调用拿到 decimals:get_jetton_info
与其手动读字典、识别格式(on/off/semi-chain)、顺着 URI 跑一趟再合并 JSON——不如直接用 get_jetton_info。这是 TONNode MCP 服务器的一个工具:传入 Jetton 主合约地址,返回已经拼装好的元数据——名称、符号、decimals 和总发行量。
TONNode 是面向 TON 的 hosted MCP 服务器。MCP(Model Context Protocol)是 AI 智能体(Claude、Cursor、ChatGPT/Codex 以及任何 MCP 客户端)调用工具所遵循的标准。本地接入免费,读取类工具全套可用:
{
"mcpServers": {
"ton": { "command": "npx", "args": ["-y", "@tonnode/mcp"] }
}
}
@tonnode/mcp 包是开源的(MIT),走 TON 原生的 ADNL 协议,没有 HTTP 中间层。之后只要用平常的自然语言向智能体提要求即可:
用
get_jetton_info工具查一下 TON 上 USDT Jetton 的decimals(主合约地址是某某),再算一下余额 5 000 000 raw 换算成人类可读的 USDT 是多少。
get_jetton_info 会返回 decimals = 6,之后所有算术都建立在这个数字上,而不是建立在假设上。对任意 Jetton 发起同样的调用会返回它自己的值(多数是 9,但每次都必须查)。核心规则:
不要硬编码
9。每个 Jetton 都要通过get_jetton_info读取decimals。
写死 9 在大多数代币上都能蒙对,然后在 USDT 上悄无声息地崩掉——而 USDT 恰恰是真金白银流转最多的 Jetton。至于 AI 智能体为什么需要专门的 MCP 通道接入 TON,而不是用带限流的公共 RPC,见写给 TON 上智能体的 MCP 服务器指南。
为什么错误的 decimals 会毁掉余额和兑换
decimals 不是展示层的小装饰。凡是金额跨越“raw ⇄ 人类可读”边界的地方都有它,一处出错,整条链路跟着错。
余额
get_jetton_balance 返回的 Jetton 余额是 raw 单位(对应的 Jetton 钱包在链上自动推算,你不必自己去找)。脱离 decimals,这个整数本身毫无意义——在除以 10^decimals 之前,你没法把它正确地展示给用户:
raw = 5 000 000
decimals = 6 → 5 USDT ✅
decimals = 9 → 0.005 ❌ (小 1000 倍)
智能体里的正确顺序:先 get_jetton_info → 拿到 decimals,再 get_jetton_balance → 用 raw 除以 10^decimals。至于公共 liteserver 在这类读取请求上如何撞上限流,见TON 公共 liteserver 限流实测。
兑换(Swap)
get_swap_quote 返回 GRAM⇄Jetton 的 DEX 硬报价(走 Omniston 协议,聚合 STON.fi + DeDust 的流动性)——输入金额和预计到手金额同样都是 Jetton 的 raw 单位。在这里,错误的 decimals 会打击你两次。假设用户想兑换 10 USDT:按 decimals = 6,应该向报价传 10 000 000 raw。错用了 9——你请求的就是 10 000 000 000 raw,也就是一笔 10 000 USDT 的兑换,而钱包里根本没有这么多。反方向的错误则会在解析响应时把预计到手金额低估 1000 倍,用户会以为这汇率是明抢。
同样的原则适用于跨链报价和一切交易:链上一切都是整数 raw 单位,通往人类可读金额的唯一桥梁就是正确的 decimals。
实战验证:get_jetton_balance 与 get_swap_quote
我们来搭一个智能体能自己跑完的小场景,全程没有一个硬编码数字。
分步场景
1. 查 decimals。 get_jetton_info(USDT master) → decimals = 6。
2. 读余额。 get_jetton_balance(持有者, USDT master) → 5 000 000(raw)。计算:5 000 000 / 10^6 = 5 USDT。要是用了 9,得到的会是 0.005。这是你的报警信号:看到 USDT 出现小得离谱的数字,第一反应就该检查是不是把 6 错用成了 9。
3. 请求报价。 想把 5 USDT 换成 GRAM。换成 raw 就是 5 × 10^6 = 5 000 000——把这个数原样传给 get_swap_quote。返回结果同样是 raw,而 GRAM 是 9 位精度,所以展示给用户前要把结果除以 10^9。
4. 不信任元数据? 可以直接在链上复核。run_get_method 能调用合约的任意只读 get 方法——用同样的方式对主合约调用 get_jetton_data,确认数字对得上。这条路更灵活,但要求你了解 TEP-64 格式并手动解析字典;当速度和可靠性优先时,get_jetton_info 一个响应就把问题解决了。TON 各家 RPC 服务商在 get 方法和归档数据访问上的差异,见TON RPC 服务商的诚实对比。
给智能体的提示词
整个场景的提示词平平无奇:
取 TON 上的 USDT。用
get_jetton_info读出decimals。用get_jetton_balance取我的 raw 余额并换算成人类可读的 USDT。然后用get_swap_quote计算把 5 USDT 兑换成 GRAM 的报价——别忘了按读到的decimals把 5 USDT 换算成 raw。
针对智能体场景再补一个细节:TONNode 的兑换工具是严格非托管的。服务器从不签名、从不保管密钥——build_swap_tx 返回的是未签名的 TonConnect 消息,由用户自己的钱包签名。就算 decimals 全部正确,服务器也拿不到对资金的任何控制权:它只负责计算和构造交易。至于 TON 上的兑换在哪些环节流失毫秒与延迟,见TON 高速交易智能体实战。
要点速览
decimals是链上 raw 单位与人类可读金额之间的十的幂次:human = raw / 10^decimals。- TON 的默认精度是 9 位;USDT 是 6 位(
1 USDT = 1 000 000raw)。搞混 = 偏差 1000 倍。 decimals存在于 Jetton 的元数据(TEP-64)里,而不是代码里。USDT 的元数据放在 off-chain,但get_jetton_info依然一个响应就给出decimals = 6。- 不要硬编码
9。通过get_jetton_info读取decimals,并让所有余额(get_jetton_balance)和报价(get_swap_quote)都建立在它之上。
领一个免费的 Hobby 密钥(60 请求/分钟,无需绑卡),现在就用 get_jetton_info 读取任意 Jetton 的 decimals:https://tonnode.io/dashboard?plan=hobby