TON 地址格式:EQ、UQ 与 raw 的区别,为什么首笔充值会被“弹回”
详解 TON 地址格式 EQ、UQ 与 raw 的区别,bounceable 与 non-bounceable 标志的含义,以及新钱包首笔充值被弹回发送方的原因与正确做法。
开发者生成一个全新钱包,复制地址,先转 5 个 GRAM 进去当 gas——一分钟后,币原路回到了发送方钱包。新地址的余额:零。没有报错,没有“reverted”,交易显示成功。钱就这么……弹回去了。在 TON 的群聊里这是经典桥段:“转到自己的地址上,怎么没到账”。而原因几乎总是同一个——搞混了地址格式和 bounceable 标志。
下面我们按事实逐一拆解:TON 的地址格式 EQ、UQ 和 raw 到底是什么,bounceable 和 non-bounceable 有什么区别,以及为什么给新钱包的第一笔充值会退回发送方。顺便演示一下如何在 AI 智能体里用一次工具调用就把这些全部验证清楚,既不用装 SDK,也不用跑节点。
三种 TON 地址格式:raw、EQ 与 UQ,区别在哪
TON 里的任何地址,底层其实都是同一个东西:workchain 编号加上合约 state init 的 256 位哈希。这就是 raw 格式:
0:83dfd552e63729b472fcbcc8c45ebcc6691702558b68ec7527e1ba403a0f31a8
冒号左边是 workchain(通常 0 是基础工作链,-1 是主链),右边是 hex 表示的 256 位哈希。这个格式直白、无歧义,但没有校验和:打错一个字符,得到的就是另一个看起来同样有效的地址。合约的 get 方法和底层工具通常要的正是 raw 格式,但它几乎从不直接展示给用户。
第二种表示是 user-friendly 格式——就是你在钱包和区块浏览器里看到的那 48 个 base64url 字符:
EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N (bounceable)
UQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqEBI (non-bounceable)
它里面编码的还是那对“workchain + 哈希”,外加一个标志字节和结尾的 CRC16 校验和。CRC 能挡住手误:写错的地址在发出之前就通不过校验。而 EQ 和 UQ 这两个前缀,正是从这个标志字节来的。
有两点核心认知,值得一开始就记牢:
- raw 和 user-friendly 是同一个地址的两种写法。
- EQ 和 UQ 是同一个 user-friendly 地址的两个变体,区别只有一个标志位。
也就是说,同一个钱包的 EQ… 和 UQ… 指向的是同一个 raw 哈希、同一个合约、同一份余额。它们不是两个不同的钱包,也不是两个不同的账户。区别只在于表达发送方意图的那一个比特。
Bounceable(EQ)对比 non-bounceable(UQ):这个标志到底意味着什么
user-friendly 地址里的那个标志字节决定了前缀:
| 标志 | 标签 | 前缀(mainnet) | 前缀(testnet) |
|---|---|---|---|
| bounceable | 0x11 |
EQ |
kQ |
| non-bounceable | 0x51 |
UQ |
0Q |
testnet 的标签会再加上 0x80——于是有了 kQ 和 0Q 这两个看起来很奇怪的前缀。不过我们关心的是标志的语义,不是标签的算术。
Bounceable(EQ) 的字面意思是:“如果接收方那边出了问题,把币退还给我”。这是为智能合约准备的保护机制。当你把钱发给一个本该处理它的合约,而它执行报错、还没部署或者 gas 不够时,你不希望币就这样悬在半空。bounce 机制会把币扣除手续费后退回发送方。
Non-bounceable(UQ) 则相反:“送到就放那儿,不管发生什么”。没有任何退回——币直接落在地址上。
对普通人之间的日常转账来说,这个区别几乎察觉不到——直到遇上一个特定场景。
为什么给未部署钱包的第一笔充值会“弹回”
坑就在这里。在 TON 里,钱包地址在钱包合约真正部署上链之前就已经存在。地址是由合约代码和初始数据(公钥)确定性哈希出来的,所以在生成助记词的那一刻你就能知道地址,全程离线。但在这个地址上发生第一笔交易之前,账户处于 uninit(未初始化)状态:地址存在,上面却没有合约代码。
现在看 bounceable 转账会发生什么:
- 你往这个 uninit 地址发了一笔 EQ(bounceable)格式的转账。
- 网络尝试投递这笔币并“调用”接收方合约。但没人能接收——地址上没有合约。
- bounce 被触发:既然投递失败,币就退回发送方,扣掉手续费。
结果就是开头说的那次“弹回”。交易成功了,新钱包的余额却依然是零,钱回到了它来的地方。没有人偷走任何东西,网络完全按设计运行——只是你在不该用 bounceable 的地方用了它。
那同一笔转账改用 UQ(non-bounceable)格式发呢?
- 网络尝试把币投递到 uninit 地址。
- 标志关闭了 bounce——无需退回。
- 币留在地址上,哪怕合约还没部署。
钱到账了。之后,当你从它发出第一笔出账交易时,合约代码会随之部署,账户变为 active。从这一刻起,它可以正常接收任何转账,包括 bounceable。UQ 规则只对尚未部署的钱包的第一笔充值至关重要。
好消息是:生态正在逐步替用户堵上这个坑。v5r1 钱包和不少客户端默认展示的就是 non-bounceable(UQ) 形式的地址——正是为了让新手别丢掉第一笔充值。但只要你开始以编程方式处理地址——在后端、脚本或 AI 智能体里——这个标志的责任就又回到了你身上。
第一笔转账的正确姿势:用 UQ 而不是 EQ
实用规则一行就能写完:
给新(uninit)钱包的第一笔充值——永远发到 UQ。之后怎么发都行。
对任何需要向用户打款的服务,完整的判断逻辑是:
- 收款地址处于 uninit 状态 → 发到 UQ(non-bounceable)。
- 地址是 active → 可以发到 EQ(bounceable)。
- 转给智能合约(DEX、jetton minter、escrow)→ 用 EQ,出错时钱能退回来。
问题在于,肉眼看 EQ 和 UQ 只差前缀一个字符,而 uninit/active 状态从地址本身根本看不出来。两项检查都必须靠程序完成。好在这里并不需要往项目里拖 @ton/ton、起一个 provider、手动解析 cell——这两个问题用 MCP 服务器的两个工具就能解决,AI 智能体自己会去调。
如果钱包生成和首次充值对你来说还是个新话题,可以看TON 钱包创建指南,那里有单独的讲解。
parse_address:一次调用,离线转换和校验地址
parse_address 是 TONNode(面向 TON 的云端 MCP 服务器)提供的离线工具。它解码地址、换算各种格式并校验 CRC16,完全不访问网络:既不需要节点也不需要密钥——纯粹是对字符串做数学运算,所以瞬间完成,也不消耗任何限额。
它能做的事:
- 在
EQ ⇄ UQ ⇄ raw之间任意方向转换; - 校验地址有效性(校验和是否匹配);
- 显示 bounceable 标志和 workchain 编号。
MCP(Model Context Protocol)是 AI 智能体(Claude、Cursor、ChatGPT/Codex 以及任何 MCP 客户端)调用工具所遵循的标准。在客户端配置里加一条记录就能接入 MCP 服务器。本地免费版本自带全套读取工具:
{
"mcpServers": {
"ton": {
"command": "npx",
"args": ["-y", "@tonnode/mcp"]
}
}
}
之后给智能体一句普通的提示词就够了:
拿这个地址
EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N,给我它的 non-bounceable(UQ)和 raw 格式。顺便验证一下校验和是否有效。
智能体会调用 parse_address,返回同一个钱包的 UQ 版本、raw 哈希和有效性状态。不用再手动折腾 base64url,也不用担心在 48 个字符里打错哪一个。
get_account_state:发送之前如何判断钱包是否已部署
搞定格式还不算完——还得知道地址处于什么状态:active 还是 uninit。这已经是链上问题了,由 get_account_state 来回答。这个工具返回账户状态、各项标志以及最近一笔交易的数据。
发出第一笔转账前的判断逻辑:
get_account_state返回 uninit → 地址还没部署 → 发到 UQ。- 返回 active → 钱包已部署 → 放心发到 EQ。
给智能体的提示词:
用 get_account_state 检查地址
UQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqEBI是否已部署。如果状态是 uninit,提醒我第一笔充值必须用 UQ 格式发送。
整套组合拳是这样的:generate_wallet 创建钱包(支持 v3r2/v4/v5r1/highload_v3 版本)并返回地址,而这个地址在创建那一刻必然是 uninit 的——网络还不知道它的存在。因此给它的第一笔充值必须严格发到 UQ。发送前,用 get_account_state 确认地址确实处于 uninit,用 parse_address 保证你拿到的正是 non-bounceable 表示。充值之后再调一次 get_balance,确认币落在了地址上,而不是被弹了回去。
关于生成还有一点很重要:generate_wallet 把助记词、密钥和地址返回给用户——服务器不存储它们,也绝不替你签署任何东西。这是非托管机制,并且覆盖所有 swap、跨链和钱包类工具。
一个术语小注:GRAM 是改名后的 Toncoin(2026 年 6 月更名),网络本身仍然叫 TON。
清单:如何不丢掉 TON 上的第一笔充值
每次给新钱包发第一笔转账时的简短流程:
- 生成了地址(
generate_wallet或你自己的 SDK)——默认把它当作 uninit。 - 检查状态:用
get_account_state看是uninit还是active。 - 如果是 uninit——用
parse_address把收款地址转成 UQ,第一笔充值只发给它。 - 如果是 active——可以用
EQ,对合约来说这甚至更推荐。 - 发往智能合约时永远用 bounceable(
EQ),出故障时钱才能退回。 - 充值之后用
get_balance核对——币应该落在地址上,而不是弹回去。 - 记住:
EQ和UQ是同一个钱包(同一个 raw 哈希);你选择的不是地址,而是投递失败时的行为。
两项关键检查——parse_address(离线)和 get_account_state(链上)——开箱即用,不需要自建任何基础设施。领一个免费的 Hobby 密钥(60 请求/分钟,无需绑卡),直接在 AI 智能体里调用这两个工具:https://tonnode.io/dashboard?plan=hobby。
之后按同样的套路,还能顺手解决一系列相邻问题:捕捉 TON 上的 USDT 入账、一次调用读取 USDT 余额,或者不用 SDK 调用合约的 get 方法。地址格式是其余一切的地基:把 EQ/UQ 搞明白一次,“弹回”的充值就再也不会打你个措手不及。