如何以编程方式创建 TON 钱包(v4、v5、highload)
用 generate_wallet 一次调用以编程方式创建 TON 钱包:v3r2、v4、v5r1、highload 版本怎么选,助记词、密钥、地址,以及如何安全完成首笔入金。
假设你在发 500 个促销码,每一笔发放都需要一个独立的 TON 钱包。或者你在写一个 AI 智能体,它要在运行时创建“热”钱包、接收入金、再做后续处理。上网一搜,找到的全是 SDK 示例:引入 @ton/ton,搞清楚 WalletContractV4 和 WalletContractV5R1 的区别,手动调用 mnemonicNew(),导出密钥,计算地址,还别忘了 highload 的 subwallet_id。半天时间全砸在和业务毫无关系的基础设施代码上。而如果钱包是由 AI 智能体创建的,它必须自己会干这件事——不需要你把密码学细节硬编码进去。
下面讲讲如何用一次 generate_wallet 工具调用以编程方式创建 TON 钱包(generate wallet / TON 钱包创建),理清 v3r2、v4、v5r1 和 highload_v3 各版本的定位,并避开 bounceable 地址这个最容易吞掉首笔入金的经典坑。
为什么要以编程方式创建 TON 钱包(以及为什么不用 SDK)
用 SDK 手动创建,在第一次上生产环境之前都没什么问题。规模一上来,麻烦就浮出水面:
- 版本。 TON 里不止一种“钱包”,而是好几个合约:v3r2、v4、v5r1、highload。每个版本逻辑不同,同一把密钥在不同版本下地址也不同。版本选错,拿到的就是另一个地址。
- 地址格式。 同一个钱包既有 EQ 形式也有 UQ 形式。发放入金地址时搞混,钱会被“弹”回去(下文详述)。
- AI 智能体。 你没法给智能体(Claude、Cursor、ChatGPT/Codex)“塞一个库”——它需要的是能按描述调用的工具。通过 MCP(Model Context Protocol),智能体像调用普通函数一样调用
generate_wallet,拿到现成的结构化响应。版本、密钥和格式的逻辑全都放在 MCP 服务端。 - 多语言技术栈。 不想同时维护 Go、Python、Node 三套 TON SDK——MCP 给所有语言提供同一个接口。
generate_wallet 是 TON 的 hosted MCP 服务器 TONNode 的 16 个工具之一。它只做一件事:创建新钱包,并把你拥有它所需的一切都交给你。如果你刚接触这套思路,建议先读 TON 的 MCP 指南。
一次调用 generate_wallet:到底返回什么
generate_wallet 创建一个新 TON 钱包,返回拥有和恢复它所需的完整信息:
- 助记词(seed 短语)——人类可读的单词序列,其余一切都由它确定性派生;
- 私钥——用于签名交易;
- 公钥;
- 钱包地址——以 TON 标准格式给出。
给智能体的提示词就是这么直白:
用 generate_wallet 创建一个 v5r1 版本的新 TON 钱包,
并显示地址和助记词。
智能体会带上版本参数调用工具,返回大致如下的结构(数值为示意;TON 中的 Ed25519 密钥就是普通 hex,没有 Ethereum 的 0x 前缀):
{
"version": "v5r1",
"mnemonic": ["word1", "word2", "...", "word24"],
"public_key": "e3f1a2…",
"private_key": "9b7c4d…",
"address": {
"bounceable": "EQ…",
"non_bounceable": "UQ…",
"raw": "0:abcd…"
}
}
不需要 SDK,也不需要手动派生。接下来存到哪里,由你自己决定。关键点在于:服务器不会把这份助记词和密钥留在自己那边——详见下文非托管一节。
钱包版本 v3r2、v4、v5r1 和 highload_v3:怎么选
generate_wallet 支持四个钱包合约版本。这不是“越新越好”:每个版本都有自己的定位。
v5r1(W5)——新项目的默认选择
最新版本。支持扩展(extensions)和 gasless 场景——即手续费可以由第三方 relayer 替用户支付。注意这是 v5 合约本身的特性,不是 TONNode 提供的服务:服务器只负责创建这种钱包,并不提供任何 gasless relayer。如果你要从零构建一个现代智能体,而且并非必须用 highload,那就选 v5r1。
v4——带插件的版本
上一代主流版本。支持插件:可以给钱包挂载扩展合约(比如订阅和定期扣款)。它长期以来都是事实标准,各类钱包和服务对它的支持都很广泛。如果你依赖 v4 的插件模型,就选它。
v3r2——极简基础钱包
最小化合约,没有扩展也没有插件。行为可预期,gas 便宜。适合逻辑只有“收和发”的功能性地址。
highload_v3——批量出账专用
单独一类。为高吞吐设计的钱包:一条外部消息可以携带大量转账。批量出账、空投、奖励发放、支付网关、交易所提现用的就是它。高效率的代价是一套特殊的消息记账模型(query_id / expiration),需要小心对待——见下文参数保存一节。
| 版本 | 适用场景 |
|---|---|
v5r1 |
新项目、扩展、gasless |
v4 |
需要插件(订阅等)、最大兼容性 |
v3r2 |
简单的功能性钱包,无需花哨特性 |
highload_v3 |
批量/打包出账、高吞吐 |
出账场景的提示词示例:
生成一个用于批量出账的 highload_v3 钱包,
返回助记词、密钥和地址。
首笔入金:为什么要打到 non-bounceable 的 UQ 地址
创建钱包之后最常见、也最让人郁闷的错误,就是把首笔入金弄丢。机制是这样的:刚生成的钱包还没有部署到链上——在第一笔转账把合约代码展开之前,该地址上根本不存在合约。
TON 的地址带有一个 bounceable 标志:
- EQ(bounceable)——如果地址上没有存活的合约,网络会把转账弹(bounce)回发送方。对刚创建的钱包来说,这正是你的处境。
- UQ(non-bounceable)——即使合约尚未部署,转账也会“留在”地址上。这正是首笔入金需要的。
规则很简单:给新钱包的第一笔充值,发到 UQ 地址。发到 EQ 会被退回来,你还得纳闷余额为什么是零。等钱包发出第一笔外发交易、完成部署之后,就可以放心使用 bounceable 形式了。
怎么可靠地拿到 UQ 形式?用离线工具 parse_address——它在不访问网络的情况下转换并校验 EQ/UQ/raw 格式:
用 parse_address 把这个地址转换成 non-bounceable
的 UQ 形式,用于首笔入金
充值之后,用 get_account_state 检查状态——它会显示账户状态(uninitialized / active)、标志位和最近一笔交易:
用 get_account_state 检查 UQ… ——钱包部署了没有,
余额是多少
在合约激活之前,状态会告诉你入金还没有“唤起”钱包。格式的详细拆解见EQ/UQ/raw 地址格式一文。
Highload 钱包:subwallet_id 和 timeout 要自己记下来
这里要单独提醒选了 highload_v3 的人。在这里,只靠助记词是不够的——既无法正确重建可用的钱包,也无法复现它的行为。highload 合约除了密钥之外还有两个参数,它们在创建时设定,并写入合约的 initial data(state init):
subwallet_id——子钱包标识(让同一条 seed 短语可以派生出多个不同地址);timeout——外部消息的存活窗口,query_id与过期逻辑都建立在它之上。
重要:generate_wallet 返回的是助记词、密钥和地址——而 subwallet_id 和 timeout 属于 highload 钱包的构造参数,由你自己选定并固定下来。把钱包创建时实际使用的值记录下来,是你的责任。
highload 钱包依据 timeout 追踪哪些消息仍然“存活”。如果你只用 seed 短语恢复钱包,丢了 subwallet_id 和 timeout,那么:
- 你会得到另一个地址——
subwallet_id和timeout都属于 state init,改动其中任何一个都会改变合约的 initial data,进而改变它的地址; - 你无法复现正确的过期与消息去重逻辑——出账记账因此有被打乱的风险。
实践规则:对于 highload_v3,把 subwallet_id 和 timeout 存到助记词旁边,作为同一条钱包记录的一部分。它们不是可有可无的元数据——它们是钱包身份的一部分。
# 密钥记录示意
mnemonic: "word1 word2 … word24"
version: "highload_v3"
subwallet_id: <创建时由你设定的值>
timeout: <创建时由你设定的值>
v3r2/v4/v5r1 没有这个要求——只要有助记词、知道是哪个版本就够了。
非托管:服务器不保存密钥,也不签名
钱包是“在某台服务器上”创建的,这时最核心的问题是:密钥最终归谁?这里的答案很明确。
generate_wallet 是严格非托管的:
- 服务器从不保存私钥、助记词和资金;
- 服务器从不替你签名交易;
- 生成的钱包——助记词、密钥、地址——全部在响应中交给你,服务端不留存任何内容。
没有存放别人 seed 短语的数据库,也没有替你签名的 operator 密钥。本质上 generate_wallet 就是在确定性生成之上包了一层便捷封装:密码学计算就地完成,结果发给你,服务器手里什么都不剩。这与托管型智能体钱包有本质区别——那类服务持有密钥(或密钥的一部分)并自己签名交易。两者的差异我们单独写过一篇:托管与非托管 MCP 对比。
实践结论:立刻把 generate_wallet 的响应存进你自己的安全存储。同一个钱包,服务器不会“再给你一次”——它根本不记得,“找客服恢复”也无从谈起。这是特性,不是缺陷。
如何接入并创建第一个钱包
接入只是一分钟的事。在本地通过 npx 免费拉起 @tonnode/mcp 包;完整的读取工具集无需密钥即可使用。
MCP 客户端配置(Claude Desktop、Cursor 或任何 MCP 客户端):
{
"mcpServers": {
"ton": {
"command": "npx",
"args": ["-y", "@tonnode/mcp"]
}
}
}
@tonnode/mcp 包是开源的(MIT),托管在 npm 和 GitHub(tonnode/mcp),并通过 TON 原生 ADNL 协议工作,你的智能体和链之间没有任何 HTTP 中间层。重启客户端后,全部 16 个工具——包括 generate_wallet、parse_address 和 get_account_state——都会对智能体可用。具体客户端的接入步骤见把 Claude 和 Cursor 接入 TON。
generate_wallet 属于全部 16 个工具之列,所有套餐都能用,包括免费的 Hobby——每分钟 60 次请求,无需绑卡。登录后立刻发放免费密钥。你付钱买的是吞吐量,而不是工具的使用权。
接下来这三个工具就能覆盖“创建并安全入金”的完整流程:
generate_wallet——创建指定版本的钱包,拿走助记词、密钥、地址。parse_address——获取首笔入金用的 UQ 地址(离线)。get_account_state——确认入金已经把合约部署起来。
完整的智能体提示词示例:
用 generate_wallet 创建一个 v5r1 钱包。然后用
parse_address 给我它的 UQ 地址用于首次充值。
等我把币打进去之后,用 get_account_state 确认
钱包已经部署。
什么时候需要 hosted 密钥
本地 npx 运行使用的是公共配置,非常适合开发阶段。如果需要在高负载下有保障的吞吐量(比如用 highload 做批量出账),那就接入 hosted 端点,换成自己的密钥:
{
"mcpServers": {
"ton": {
"type": "http",
"url": "https://mcp.tonnode.io/mcp",
"headers": { "Authorization": "Bearer tn_live_…" }
}
}
}
小结
generate_wallet一次调用创建 TON 钱包,返回助记词、公私钥和地址——选好版本调一次就行,不用手工拼 SDK。- 新项目默认选 v5r1;批量出账选 highload_v3;要插件选 v4;要极简钱包选 v3r2。
- 首笔入金发到 non-bounceable 的 UQ 地址,否则会被弹回;UQ 形式用
parse_address拿,状态用get_account_state查。 - 用 highload 时自己固定
subwallet_id和timeout,和助记词存在一起——两者都属于 state init,并影响地址。 - 服务器是非托管的:不保存密钥和资金,也不签名——密钥归你。
领一个免费的 Hobby 密钥,几分钟内用 generate_wallet 创建你的第一个钱包——tonnode.io/dashboard?plan=hobby。