全部文章
6 分钟阅读

TON 钱包 USDT 余额怎么查?一次 get_jetton_balance 调用搞定

在 TON 上一次调用 get_jetton_balance 就能查出钱包的 USDT 余额——jetton 钱包地址直接在链上计算,无需自建索引器。

TON USDT 余额get_jetton_balanceTON jettonTON MCPTONNodejetton decimals

智能体的钱包已经接好了,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。

之后服务器自己在链上完成全部工作:

  1. 调用代币主合约的 get 方法,根据持有人地址得到其 jetton 钱包的地址(就是前面说的那次确定性推导);
  2. 从这个 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 上的大多数 jettondecimals = 9
  • TON 上的 USDTdecimals = 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_addressget_jetton_infoget_jetton_balance——就能对“这个钱包有多少 USDT”给出完整、诚实的答案,全程不碰任何第三方索引器。

本地免费

@tonnode/mcp 包是开源的(MIT),在 npm 和 GitHub 上都能找到。上面示例里那份公开配置(npx -y @tonnode/mcp)就提供了全套读取工具,包括 get_jetton_balanceget_jetton_infoparse_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。

让你的智能体接入 TON

16 个 MCP 工具:读取、非托管兑换、跨链与钱包。免费档 60 次/分钟,无需银行卡。