Jettons 是 The Open Network(TON)上的同质化代币标准,由 TEP-74 定义。与以太坊上的 ERC-20 不同,jetton 余额并不存储在一个中心化合约中:主合约跟踪元数据和总供应量,而每个持有者都有一个独立的 jetton 钱包合约,用于存储其余额并执行转账。本文解释了这一架构,展示了如何读取 jetton 元数据和余额,通过可运行代码演示了转账流程,并描述了如何验证结果和诊断常见故障。它将 TEP-74 的文档化行为与操作建议以及你应针对自己的 RPC 端点运行的测量区分开来。
范围与 jettons 的两种含义
jettons 一词有两种互不相关的历史。在钱币学中,jetons(也拼作 jettons)是大约从 13 世纪到 18 世纪在欧洲生产的、类似硬币的计数代币,用于计数板。在区块链工程中,jettons 是 The Open Network(TON)的同质化代币标准,规范为 TEP-74。本文仅涵盖 TON 的含义;历史含义是有用的背景,但不是你需要实现的内容。
关于 TON 上什么是 jetton 的直接回答:它是一种同质化代币,其主合约持有元数据和总供应量,而每个持有者的余额都存在于该持有者拥有的独立 jetton 钱包合约中。转账是这些合约之间的消息,而不是单一账本中的一次余额更新。这一区别几乎决定了与 ERC-20 风格代币的所有操作差异。
- TEP-74 定义了 jetton 主合约、jetton 钱包和转账消息布局。
- 每个持有者针对每种 jetton 都有自己的 jetton 钱包合约。
- 转账是异步内部消息,因此确认不像单合约余额更新那样即时。
前提条件与假设
你需要一个支持你计划调用的方法的 TON RPC 端点、一个为任何尝试的转账提供资金的已充值钱包,以及对 TON 地址(工作链、可弹回与不可弹回形式)的基本熟悉。下面的示例使用 RPC_URL、JETTON_MASTER_ADDRESS 和 PRIVATE_KEY 等占位符;请替换为你自己的值,切勿提交真实密钥。
代码示例使用针对 TON 端点的标准 HTTP JSON-RPC 风格调用。确切的方法名称和响应结构可能因提供商而异,因此请将这些代码片段视为需要调整的过程,而非固定的 API 契约。关于端点选择,RPC Assistant 对 TON RPC 端点的比较是选择具体提供商的正确场所;本文专注于 jetton 机制和验证方法。
TEP-74 jetton 模型的结构
jetton 主合约是注册表。它存储代币的元数据(名称、符号、小数位),跟踪总供应量,并知道如何为给定所有者推导 jetton 钱包地址。它不会在一个地方存储每个持有者的余额。
jetton 钱包合约按(jetton,所有者)对部署。它持有该所有者的余额,并实现转账、销毁和销毁通知行为。当你发送 jettons 时,你的钱包合约向目标钱包合约发送内部消息,目标钱包合约记入余额,并且如果指定了目标所有者的合约,可能会向其发送通知。
这种分布式设计是需要保持的主要心智模型:要查找某人的 jetton 余额,你必须先从主合约解析其 jetton 钱包地址,然后查询该钱包。仅查询主合约只能获得供应量和元数据,而非个人余额。
- 主合约:元数据、总供应量、钱包地址推导。
- Jetton 钱包:每个所有者的余额和转账逻辑。
- 转账消息:金额、目标、响应地址、可选转发负载和转发金额。
读取 jetton 元数据和总供应量
任何 jetton 集成的第一个诊断步骤是确认你正在与正确的主合约交互并读取其元数据。针对 TON JSON-RPC 端点的可运行 curl 调用通常会在主合约上运行 get-method。确切的方法名称取决于你的提供商;下面的模式展示了 get-method 调用的形式。
针对你自己的 RPC_URL 运行此命令,并将返回的符号和小数位与区块浏览器对同一主合约地址显示的内容进行比较。不匹配通常意味着你查询了错误的合约或重写响应的代理。
curl -sS -X POST "$RPC_URL" \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "runGetMethod",
"params": {
"address": "JETTON_MASTER_ADDRESS",
"method": "get_jetton_data",
"stack": []
}
}'解析持有者的 jetton 钱包地址
由于余额存在于每个所有者的钱包合约中,你无法直接从主合约读取余额。你必须推导或查找所有者的 jetton 钱包地址。许多提供商为此提供了辅助方法;如果你的提供商没有,你可以调用主合约的钱包地址 get-method,并将所有者地址作为参数。
下面的 Python 代码片段展示了该过程:调用主合约以获取所有者的钱包地址,然后调用该钱包以读取其余额。它使用通用 JSON-RPC 客户端和占位符值,因此请根据提供商文档化的接口调整方法名称。
import json, os, urllib.request
RPC_URL = os.environ["RPC_URL"]
MASTER = os.environ["JETTON_MASTER_ADDRESS"]
OWNER = os.environ["OWNER_ADDRESS"]
def rpc(method, params):
body = json.dumps({"jsonrpc": "2.0", "id": 1, "method": method, "params": params}).encode()
req = urllib.request.Request(RPC_URL, data=body, headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=30) as r:
return json.load(r)
# Step 1: resolve the jetton wallet for this owner from the master
wallet = rpc("runGetMethod", {
"address": MASTER,
"method": "get_wallet_address",
"stack": [["slice", OWNER]],
})
print("wallet lookup:", json.dumps(wallet, indent=2))
# Step 2: read the balance from the resolved wallet contract
# (substitute the wallet address returned above)
balance = rpc("runGetMethod", {
"address": "RESOLVED_JETTON_WALLET_ADDRESS",
"method": "get_wallet_data",
"stack": [],
})
print("wallet data:", json.dumps(balance, indent=2))执行 jetton 转账及预期结果
jetton 转账是从你的 jetton 钱包到目标 jetton 钱包的内部消息。在实践中,你向自己的 jetton 钱包合约发送消息,指示其将指定金额转账给目标所有者,并附带响应地址以便你收到确认。由于 TON 消息是异步的,正确的验证模式是:提交、捕获消息哈希或交易引用,然后轮询直到目标钱包的余额反映该转账。
不要将成功发送视为结算证明。如果目标钱包未部署且消息可弹回,转账可能会弹回,或者可能在钱包合约层面失败。始终在交易确认后通过读取目标 jetton 钱包余额进行验证。
- 发送到你的 jetton 钱包,而不是直接发送到接收者的原始地址。
- 包含响应地址,以便你可以观察确认消息。
- 仅当你有意接受资金丢失到未部署钱包的风险时,才使用不可弹回消息。
- 通过重新读取目标钱包余额来验证结算,而不是信任发送响应。
带结果表的验证流程
使用以下流程端到端确认 jetton 转账。将每次观察记录在表中,以便比较运行结果并发现回归。下表是模板;请填写你自己的测量值,而不是假设任何数字。
| 步骤 | 你调用的内容 | 你记录的内容 | 通过条件 |
|---|---|---|---|
| 1 | 主合约 get_jetton_data | 符号、小数位、总供应量 | 与同一主合约的浏览器显示一致 |
| 2 | 主合约 get_wallet_address(owner) | 解析的钱包地址 | 与浏览器中该所有者的 jetton 钱包一致 |
| 3 | 钱包 get_wallet_data(发送者) | 发送前余额 | 余额等于预期的转账前金额 |
| 4 | 发送转账消息 | 消息哈希 / 交易引用 | 端点接受消息 |
| 5 | 轮询目标钱包 get_wallet_data | 确认后余额 | 增加额等于转账金额减去任何费用 |
| 6 | 重新读取发送者钱包 | 确认后余额 | 减少额等于转账金额加上费用 |
通过条件是验证逻辑;测量值由你捕获。如果步骤 5 从未显示增加,转账可能已弹回或目标钱包未部署。如果步骤 6 显示没有减少,消息可能未到达你的钱包合约。
排查常见的 jetton 故障
大多数 jetton 集成问题分为几类:错误的主合约地址、错误的钱包推导、异步时序和弹回处理。按顺序处理它们,而不是猜测。
如果元数据看起来错误,你可能查询了错误的主合约或缓存响应。如果你认为持有 jettons 的地址余额读数为零,请确认你从正确的主合约和所有者解析了 jetton 钱包,并且所有者地址格式(可弹回与不可弹回)与钱包创建方式匹配。如果转账似乎已发送但目标从未记入,请检查目标钱包是否已部署以及消息是否可弹回。
- 错误的主合约地址:元数据和供应量将与浏览器不匹配。
- 错误的所有者格式:钱包推导返回与预期不同的合约。
- 异步结算:过早轮询显示过时余额。
- 向未部署钱包发送可弹回消息:弹回后资金返回发送者。
- TON 不足以支付 gas:转账消息永远不会执行。
分布式模型的局限性与权衡
每个所有者的钱包设计通过避免每个转账都必须触及的单一热点合约来提高可扩展性,但它将复杂性转移给了客户端。你必须解析钱包地址、处理异步确认并推理部署状态。索引器和浏览器隐藏了部分内容,但任何严肃的集成都应自行实现解析和验证步骤。
还有一个测量注意事项:延迟和成功率在很大程度上取决于你使用的 RPC 端点,而不仅仅是协议。不要基于一个提供商就假设转账是快还是慢。针对你选择的端点运行上述验证流程并记录你自己的时间。关于基础设施选择,请参阅 OnFinality 专用节点选项和 RPC 定价页面,在确定生产路径之前比较承诺级别。
生产集成的后续步骤
一旦你能读取元数据、解析钱包并验证转账,下一步就是加固:在 get-method 调用周围添加带退避的重试,持久化消息引用以便在重启后对账,并监控目标余额而不是信任发送响应。如果你大规模运营,考虑使用专用节点,这样你的 jetton 查询就不会与共享流量竞争。
具体关于端点选择,请使用 RPC Assistant 关于 TON RPC 端点的指南,而不是在此硬编码提供商。将其与 OnFinality 网络目录结合,以确认哪些网络和服务适合你的架构,如果你需要托管访问而非自托管基础设施,请查看 API 服务概述。
- 持久化消息哈希并异步对账。
- 在转账提交周围添加幂等键。
- 同时监控发送者和目标 jetton 钱包。
- 在任何端点变更后重新运行验证表。