TON 钱包 USDT 余额怎么查?一次 get_jetton_balance 调用搞定
在 TON 上一次调用 get_jetton_balance 就能查出钱包的 USDT 余额——jetton 钱包地址直接在链上计算,无需自建索引器。
智能体的钱包已经接好了,get_balance 老老实实返回了几个 GRAM——USDT 却是零。可你明明知道,稳定币确实转到过这个地址。是不是很熟悉?钱包看上去空空如也,实际上躺着 500 USDT。这不是 bug,钱也没有丢——你只是问错了合约。大多数 AI 智能体与 TON 的首次集成,恰恰就栽在这里:智能体读错了地址。
为什么 USDT 余额不是 TON 钱包的余额
在 Ethereum 上你已经习惯了 ERC-20 代币“躺在地址上”:代币合约维护一张 address → balance 的表,想查余额,问这一个合约就行。TON 的模型不一样,这是第一个容易混淆的地方。
原生代币 GRAM(前身是 Toncoin;网络本身仍然叫 TON)直接存在你钱包的智能合约上——get_balance 读一次账户状态就能拿到。而 USDT 是一种 jetton(jetton 是 TON 的同质化代币标准)。jetton 的余额并不存在你的主钱包上,而是存在一个独立的小合约——jetton 钱包(jetton wallet)上。
打个比方:你的主 TON 钱包是你本人,而 USDT 的 jetton 钱包,是某家具体的银行(USDT 的主合约)以你的名义开的一个独立账户。问一个人“我有多少美元”没有意义——钱在账户里,不在口袋里。每一对“持有人 + 代币”都有自己的 jetton 钱包:USDT 是一个地址,NOT 是另一个地址,换一种代币又是另一个。
好消息是:这个 jetton 钱包的地址不是随机的,它由两样东西确定性地推导出来:
- 持有人的地址(你的普通 TON 钱包,
EQ…/UQ…); - 代币主合约的地址(jetton master——对 USDT 来说是一个固定的合约)。
坏消息是:要老老实实算出这个地址,你得访问主合约、调用它的 get 方法,然后再读取 jetton 钱包的状态。手动走完这套流程要好几步,集成往往就是在这几步上摔跟头。
get_jetton_balance:一次调用,顶替整个索引器
这个问题通常有两种解法,而且都不省心:
- 自建索引器。 自己跑一个节点,把 jetton 转账索引进数据库,还要持续维护、保持同步。为了一个数字,又贵又脆弱,而且数据永远比链上慢半拍。
- 公共 HTTP API。 很快就会撞上限流:不带 key 大约每秒一个请求,一旦超限,就会实打实地吃到
HTTP 429 Too Many Requests。而且你还得依赖别人家的索引,以及它到底索引了多深。
TONNode 的 get_jetton_balance 工具把这两个问题都解决了。你只需传给它:
- 持有人地址——用户的普通 TON 钱包(
EQ…/UQ…); - 代币标识——比如 USDT。
之后服务器自己在链上完成全部工作:
- 调用代币主合约的 get 方法,根据持有人地址得到其 jetton 钱包的地址(就是前面说的那次确定性推导);
- 从这个 jetton 钱包读取余额,返回给你。
没有第三方索引器,没有数据库,也不存在数据不同步——只是通过合约的 get 方法直接读取网络状态。
TONNode 是一个面向 TON 的 hosted MCP 服务器。MCP(Model Context Protocol)是 AI 智能体(Claude、Cursor、ChatGPT/Codex 以及任何 MCP 客户端)调用外部工具的标准协议。也就是说,get_jetton_balance 不是你代码里的一行调用,而是当用户问起余额时智能体自己去调的工具。底层的 @tonnode/mcp 包(开源,MIT)走的是 TON 原生的 ADNL 协议,没有中间 HTTP 层——智能体直接与网络对话,而不是再套一层 REST 网关。
示例:给智能体的提示词,以及会返回什么
接入是本地的,不需要 key,也不需要绑卡。往 MCP 客户端(Claude Desktop、Cursor、任何 MCP 兼容客户端)的配置里加上:
{
"mcpServers": {
"ton": {
"command": "npx",
"args": ["-y", "@tonnode/mcp"]
}
}
}
之后给智能体一句普通的自然语言提示就够了:
钱包
UQAbc…xyz上有多少 USDT?返回人类可读的数值。
智能体会自己选中 get_jetton_balance 工具,传入持有人地址和 USDT 这个代币,服务器则返回余额——但不是熟悉的“美元”,而是 raw 单位(代币最小的不可分割单位)。除了余额本身,响应里还带着服务器在链上算出的那个 jetton 钱包地址——如果你接下来想专门跟踪这个合约的转账,这会很有用。我们例子里的余额,是一个形如 12500000 的值。
但 12500000 不是 1250 万 USDT。第二个坑从这里开始,而且非常容易踩。
Raw 单位与 decimals:为什么 USDT 是 6 而不是 9
区块链不跟小数打交道。所有金额都以整数 raw 单位存储,“人类可读”的值要除以 10^decimals 才能得到,而 decimals 是每种代币各自的属性。就像“分”和“元”:底层一律记“分”,decimals 告诉你小数点该打在哪儿。
TON 的关键细节在于:不同 jetton 的 decimals 不一样。
- 原生 GRAM 和 TON 上的大多数 jetton:
decimals = 9。 - 而 TON 上的 USDT:
decimals = 6。
所以同一个 raw 值可能代表天差地别的金额。把我们的例子正确换算一遍:
raw = 12500000
decimals = 6 // 正是 USDT 的值
human = 12500000 / 10^6 = 12500000 / 1_000_000 = 12.5 USDT
12.5 USDT,不是 1250 万。如果你按惯性除以 10^9(像对待普通 jetton 那样),得到的是 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
提示词可以这样写,让智能体自己把这几步串起来:
先用 get_jetton_info 拿到 USDT 的 decimals,再用 get_jetton_balance 查钱包
UQAbc…xyz的余额,并把 raw 换算成人类可读的数字。
如果你要处理的是任意代币而不只是 USDT,这个顺序就是强制的:面对陌生代币,你事先并不知道它的 decimals 是 6 还是 9。对 USDT 来说 decimals = 6 是常量,但养成从 get_jetton_info 取值的习惯,会在你碰到第一个非标准代币时救你一命。同样的思路也是在 TON 上检测 USDT 入账的基础——在那个场景里 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 包是开源的(MIT),在 npm 和 GitHub 上都能找到。上面示例里那份公开配置(npx -y @tonnode/mcp)就提供了全套读取工具,包括 get_jetton_balance、get_jetton_info 和 parse_address——不需要单独的 key。
重启客户端,智能体就已经在通过 ADNL 读取代币余额了。为什么智能体需要的是专属 MCP 服务器,而不是带限流的公共网关,我们在写给 TON 上 AI 智能体的 MCP 指南里讲过。而免费公开配置的边界在哪里、为什么会有边界,见TON 公共 liteserver 限制解析。
Hosted——当你需要吞吐量的时候
当本地速率不够用了(机器人、后端、生产负载),就切换到带自己 key 的 hosted 端点。工具集在哪儿都一样——全部 16 个,包括读取模块;各档套餐只差在有保证的吞吐量上:
- Hobby——永久免费,60 请求/分钟;
- Pro——$29/月,300 请求/分钟;
- Scale——$199/月,1200 请求/分钟。
Hosted 接入的唯一区别,是你带着自己的 key 访问共享端点:
{
"mcpServers": {
"ton": {
"type": "http",
"url": "https://mcp.tonnode.io/mcp",
"headers": { "Authorization": "Bearer tn_live_…" }
}
}
}
如果你在做仪表盘、查余额的机器人,或者会频繁轮询地址的智能体——去拿个 key,别去撞公共 API 的限流,也别在最不合时宜的时刻吃 429。
小结
- TON 上的 USDT 是 jetton,余额存在独立的 jetton 钱包上,而不是主地址上。
get_jetton_balance会自己在链上算出 jetton 钱包地址并读取余额——不需要索引器,也不需要自建数据库。- 余额以 raw 单位返回;展示时除以
10^decimals。 - USDT 的
decimals = 6(除数是1_000_000),大多数 jetton 是9。别硬编码——从get_jetton_info里取。 - 整条链路可以免费验证:
npx -y @tonnode/mcp,公开配置,完整的读取工具集。
免费的 Hobby key——60 请求/分钟,不用绑卡——登录后立刻发放:获取 key。它足够你在真实钱包上跑一遍 parse_address → get_jetton_info → get_jetton_balance,亲眼确认 12500000 raw 换算出来正好是 12.5 USDT——不是 1250 万,也不是 0.0125。