文章

Ton 转账原理拆解

背景

  • TON 是消息驱动的账户抽象链:所有操作都是智能合约之间通过异步消息(外部消息、内部消息)互动完成。

  • 钱包是合约:用户持有的钱包其实是一个部署在链上的合约(如 v3、v4、v5 钱包),由私钥签名外部消息驱动执行转账等操作。

  • Toncoin 与 Jetton 的差异

    • Toncoin 是链的原生代币,余额挂在账户状态上,转账通过向目标地址发送“内部消息”(带 Toncoin value)完成。

    • Jetton 是代币标准(类似 ERC-20),每个代币有一个 Jetton 根合约与每个账户的 Jetton 钱包合约;转账不是直接“携带代币”,而是通过调用 Jetton 钱包合约的 transfer 方法来减少/增加余额。

  • Gas/费用模型

    • Toncoin 转账:发送消息需要内含足够 Toncoin 支付消息费和目标合约执行费。

    • Jetton 转账:需要支付两类费用:执行 transfer 的消息费与随消息附带的 Toncoin,用于目标 Jetton 钱包合约执行和可能的回执(notification)链路。

  • 工作链/分片:TON 有多链分片架构,消息投递是异步且可能跨分片,最终性靠区块确认(通常秒级)。


原生 token与其他 token 的关系

总览关系

  • Toncoin 转账:你的钱包合约发送一个“内部消息”到目标地址,消息里携带 value(Toncoin 数量),链直接结算余额。

  • 无 Toncoin 合约:Toncoin 是原生资产,没有“代币合约”。

Jetton(代币标准)结构

以某个代币(如 USDT-jetton)为例,一个 Root 对应全网一类代币,每个账户有该代币下的专属 Jetton Wallet。

  • 每个代币一个 Root 合约:负责代币元数据、规则、以及计算每个“拥有者地址”的专属 Jetton Wallet 地址。

  • 每个“账户×代币”一个 Jetton Wallet 合约:存储该账户对该代币的余额。你和对方各自拥有该代币的子钱包。

  • 转账链路

    1. 你的钱包合约向“你的 Jetton Wallet”发送内部消息,调用 transfer

    2. 你的 Jetton Wallet 扣减余额,并向“对方 Jetton Wallet”发送内部消息,附带少量 Toncoin 作为执行费。

    3. 对方 Jetton Wallet 增加余额,并可向你回执(可选)。

同一账户持有多种 Jetton 的情形

  • 同一个“你的钱包地址”,针对不同代币(A/B/C),各自拥有一个对应的 Jetton Wallet。

  • 这些 Jetton Wallet 地址可由各自的 Root 确定性推导,未使用时可能尚未部署,但地址可预先计算。

Toncoin 与 Jetton 的对比一览


Toncoin(原生):
- 无代币合约
- 余额直接在账户状态上
- 转账 = 钱包发内部消息携带 value 到目标地址 Jetton(标准代币):
- 1 个 Jetton Root(每种代币)
- 多个 Jetton Wallet(每个账户各 1 个)
- 转账 = 钱包 -> 自己的 Jetton Wallet.transfer -> 对方 Jetton Wallet
- 需要附带少量 Toncoin 用作执行/转发费用

 

单次转账:Toncoin(原生)原理

关键对象与消息

  • 钱包合约接收外部消息(由用户本地签名)。

  • 钱包合约根据指令构造内部消息(internal message)并发送至目标地址。

  • 内部消息包含:

    • value: 发送的 Toncoin 数量(nanoTON)

    • bounce: 是否允许弹回(默认对合约通常设为 true

    • stateInit: 可选,若目标地址是未部署合约,可以携带其初始代码与数据以冷启动部署

    • body: 可选应用层负载(如评论、op code 等)

典型流程

  1. 用户构造外部消息:包含到期时间、seqno、转账参数,使用私钥签名。

  2. 钱包合约校验签名与 seqno,防重放。

  3. 钱包合约发送一个内部消息到收款地址,内含 value 与可选 body

  4. 目标地址接收:

    • 若是普通钱包地址:余额直接增加;如果 bounce=true 且地址不存在,则可能回弹。

    • 若是合约地址:进入合约 recv_internal,执行逻辑,消耗 gas,可能触发子消息或回执。

  5. 手续费扣除:

    • 发送方支付网络与执行费用(包含消息大小和执行成本),从发送方余额中扣除。

    • 如消息执行触发回执,回执的费用也需要在消息中预留。

伪代码(Toncoin 单次转账)

def send_toncoin(wallet, to_addr, amount, comment=None, bounce=True):
    # 1. 构造内部消息体
    body = None
    if comment:
        # 常见做法是在 body 里放 UTF-8 文本或专用 opcode + cell
        body = build_comment_body(comment)
    
    internal_msg = {
        "to": to_addr,
        "value": amount,            # nanoTON
        "bounce": bounce,
        "stateInit": None,          # 如需首次部署对端合约,可填
        "body": body
    }

    # 2. 构造外部消息让钱包合约执行
    ext_msg = wallet.build_external_message(
        ops=[internal_msg],         # v4/v5 可一次性传多个内部消息(批量)
        timeout=now()+60,
        seqno=wallet.get_seqno()
    )

    # 3. 签名并发送
    signed = sign(ext_msg, wallet.private_key)
    send_external_message(signed)

    # 要点:需要保证钱包余额足以覆盖 amount + 所有消息/执行费用

要点:

  • 钱包合约版本支持一次发送多条内部消息,这为“批量转账”提供基础。

  • bounce 设为 false 可避免给不可反弹的地址造成失败;但一般对合约地址建议 true以免误转。


单次转账:Jetton(标准代币)原理

Jetton 标准核心合约角色:

  • Jetton Root:代币元数据与铸造权限,负责派生每个账户的 Jetton Wallet 地址。

  • Jetton Wallet(每个账户一个):实际存储该账户的代币余额,处理 transfer/accept/通知等。

转账不是“搬运代币”,而是:

  • 发起者调用“自己名下的 Jetton Wallet 合约”的 transfer,该合约减少发起者余额,同时通过消息驱动“接收方的 Jetton Wallet 合约”增加余额。

关键消息与字段(概念化)

  • op_transfer: Jetton 转账操作码

  • amount: 代币数量(通常是自然数,精度通过 decimals 解释)

  • destination: 接收者地址(用户钱包地址或直接 Jetton Wallet 地址)

  • response_destination: 回执接收地址(可为发起方钱包)

  • forward_ton_amount: 附加转发所需 Toncoin,用于对端执行与可能的通知

  • forward_payload: 附带给接收方的钱包合约的业务负载(可空)

  • custom_payload: 可选自定义负载(路由/权限等)

典型流程

  1. 查出发起者的 Jetton Wallet 地址(由 Root 派生,或本地缓存/链上查询)。

  2. 钱包合约向“发起者 Jetton Wallet”发送一条内部消息,调用其 transfer

  3. 发起者 Jetton Wallet 合约:

    • 校验调用者与签名(通过钱包合约发来的内部消息可视为授权)。

    • 扣减余额,构造并发送一条内部消息给“接收方 Jetton Wallet”。

    • 附带必要的 Toncoin(forward_ton_amount)让对端能执行成功并可能向 response_destination 回执。

  4. 接收方 Jetton Wallet 合约:

    • 若不存在,可由 stateInit 携带初始化(有些实现自动创建)。

    • 增加余额,按标准发送接收通知或回执。

  5. 费用:

    • 发起方需准备足够 Toncoin,用于多跳消息的 gas 与执行费,常见做法通过参数控制 forward_ton_amount

伪代码(Jetton 单次转账)

def send_jetton(wallet, jetton_root, to_owner_addr, jetton_amount, note=None):
    # 1. 解析/计算本人的 Jetton Wallet 地址
    my_jetton_wallet = derive_jetton_wallet(jetton_root, wallet.owner_addr)

    # 2. 准备 transfer 调用参数
    op_transfer = 0x0f8a7ea5  # 示例 opcode,实际看标准
    forward_ton_amount = to_nano(0.05)  # 预估:够对端执行与回执(视实现调整)

    forward_payload = build_comment_body(note) if note else None

    transfer_body = build_cell(
        op=op_transfer,
        query_id=random_u64(),
        amount=jetton_amount,
        destination=to_owner_addr,              # 对方“拥有者地址”,钱包合约可据此派生其 Jetton Wallet
        response_destination=wallet.owner_addr, # 回执地址
        custom_payload=None,
        forward_ton_amount=forward_ton_amount,
        forward_payload=forward_payload
    )

    # 3. 钱包合约发内部消息到 my_jetton_wallet,携带足够 Toncoin 覆盖执行与转发
    internal_msg = {
        "to": my_jetton_wallet,
        "value": to_nano(0.2),    # 需要覆盖本次 transfer 与对子钱包转发、回执的费用
        "bounce": True,
        "body": transfer_body
    }

    ext_msg = wallet.build_external_message(
        ops=[internal_msg],
        timeout=now()+60,
        seqno=wallet.get_seqno()
    )

    signed = sign(ext_msg, wallet.private_key)
    send_external_message(signed)

要点:

  • 必须为 Jetton 转账附带足够 Toncoin 作为执行费与转发费(尤其跨多跳)。

  • 部分实现支持“直接向对方 Jetton Wallet 地址”转账,但标准推荐通过“对方拥有者地址”,由合约自行解析派生。


批量转账:Toncoin 原理

批量转账的本质是:钱包合约一次接收外部消息后,内部构造并发送多条内部消息(N 笔 Toncoin 转账),以实现单交易多转发。

流程与考虑

  1. 用户外部消息中携带多个“转账指令”列表。

  2. 钱包合约迭代构造 N 条内部消息并发送。

  3. 费用评估:

    • 外部消息大小随 N 增加,费用线性增长。

    • 每条内部消息的基费 + 目标执行费 + 潜在回执费。

  4. 失败处理:

    • 某一条转账 bounce 并不自动回滚其他转账(异步、无原子性)。

    • 可通过合理 bounce 与余额检查、分批发送降低风险。

伪代码(Toncoin 批量)

def batch_send_toncoin(wallet, transfers):
    # transfers: list of {to, amount, comment?, bounce?}
    ops = []
    for t in transfers:
        body = build_comment_body(t.get("comment")) if t.get("comment") else None
        ops.append({
            "to": t["to"],
            "value": t["amount"],
            "bounce": t.get("bounce", True),
            "stateInit": None,
            "body": body
        })

    ext_msg = wallet.build_external_message(
        ops=ops,
        timeout=now()+120,
        seqno=wallet.get_seqno()
    )

    ensure_enough_balance(wallet, sum(t["amount"] for t in transfers) + estimate_fees(ops))

    signed = sign(ext_msg, wallet.private_key)
    send_external_message(signed)

要点:

  • 批量并非原子事务;每条内部消息独立投递、独立结算。

  • 钱包合约版本需支持多操作列表(如 v4/v5)。


批量转账:Jetton 原理

批量 Jetton 转账常见有两种策略:

  1. 钱包合约一次外部消息,发起多次对“自己 Jetton Wallet”的 transfer 调用(N 次)

  • 优点:保持标准、每次语义清晰。

  • 成本:链上执行与消息数量线性增长。

  1. 使用定制批量转账扩展(如多转账入口、批量打包 body)

  • 优点:压缩外部消息与签名成本。

  • 风险:需目标 Jetton 钱包/中间合约支持自定义批量接口;不是所有 Jetton 标准钱包都支持。

典型(标准做法)仍是方式 1:循环调用 transfer

伪代码(Jetton 批量,标准循环)

def batch_send_jetton(wallet, jetton_root, items):
    # items: list of {to_owner_addr, amount, note?}
    my_jetton_wallet = derive_jetton_wallet(jetton_root, wallet.owner_addr)

    ops = []
    for it in items:
        body = build_jetton_transfer_body(
            amount=it["amount"],
            destination=it["to_owner_addr"],
            response_destination=wallet.owner_addr,
            forward_ton_amount=to_nano(0.05),
            forward_payload=build_comment_body(it["note"]) if it.get("note") else None
        )
        ops.append({
            "to": my_jetton_wallet,
            "value": to_nano(0.2),  # 为本次 transfer + 转发/回执预留
            "bounce": True,
            "body": body
        })

    ext_msg = wallet.build_external_message(
        ops=ops,
        timeout=now()+180,
        seqno=wallet.get_seqno()
    )

    ensure_enough_balance(wallet, estimate_total_ton_for_fees(ops))

    signed = sign(ext_msg, wallet.private_key)
    send_external_message(signed)

要点:

  • 批量的 Toncoin成本全部由发起者承担,包含每次 transfer 的执行与对端回执费用。

  • 大批量时要注意外部消息大小限制与区块 gas 限额;可分批发送。


Bounce、StateInit 与地址存在性

  • bounce=true:当目标地址为合约且处理失败,消息可回弹,Toncoin 返还(扣除部分费);对普通未部署地址,bounce 可能导致回弹或丢失取决于目标存在性与规则。

  • stateInit:可以为“目标 Jetton 钱包”或其他合约的首次部署携带初始化数据,从而在首次转账时创建目标合约。这在 Jetton 转账中尤为常见(自动创建对方 Jetton 钱包)。

  • 地址类型:标准地址、原始地址(workchain + hash),对分片透明;合约是否已部署影响 bounce 与 gas 消耗。


费用与安全边界

  • 费用估计要覆盖:

    • 外部消息大小费

    • 每条内部消息大小费

    • 发送方与接收方执行费

    • Jetton 转账中的 forward ton 与回执链路费

  • 安全建议:

    • 对未知合约地址,建议 bounce=true,并先小额试转。

    • 批量时优先分批发送,避免单条失败导致队列拥塞或余额不足引发连锁失败。

    • 记录 query_id 以便在回执中对账与重试。

    • 注意钱包 seqno 单调递增、防重放;外部消息设置 timeout


全流程贯穿(从构思到落地)

以下以“一个统一的批量转账入口,既可发 Toncoin 又可发 Jetton”为例,贯穿整个执行链路:

  1. 准备阶段(离线)

    • 解析配置:待转账清单 items,包含类型(ton 或 jetton)、目标地址、数量、备注。

    • 对 Jetton 项:

      • 解析对应 Jetton Root,预先派生本方 Jetton Wallet 地址。

      • 为每个接收方预估是否需要 stateInit 创建其 Jetton Wallet(可选,很多实现由对端自动创建)。

    • 费用估算:按 Toncoin/Jetton 两类分别计算,确保钱包 Toncoin 余额足以覆盖所有费用与转发。

  2. 构造外部消息

    • 遍历 items:

      • 若为 Toncoin:构造一条内部消息 -> 收款地址,附加评论或业务 body。

      • 若为 Jetton:构造一条内部消息 -> 本方 Jetton Wallet,body 为 transfer 调用,设置 forward_ton_amount 与 response_destination

    • 将所有内部消息集合成一个外部消息(钱包支持多操作时)。

    • 设置 seqnotimeout,签名。

  3. 上链与投递

    • 外部消息送达钱包合约。

    • 钱包合约校验签名、递增 seqno,按序发送内部消息队列。

    • 各内部消息被目标分片路由、执行:

      • Toncoin:目标地址余额更新或合约执行。

      • Jetton:我的 Jetton Wallet 执行 transfer,路由到对方 Jetton Wallet,完成余额更新,回执可选返回。

  4. 回执与对账

    • 监听事件或解析交易:

      • Toncoin:交易成功状态、目标账户余额变化确认。

      • Jetton:根据 query_id/response_destination 收到回执消息;或直接从目标 Jetton Wallet 查询余额变更。

    • 对失败(超时、bounce、余额不足)项进行重试或人工审核。

  5. 终态

    • 所有成功项有链上交易哈希可追溯。

    • 批量与单次仅在“内部消息条目数”上不同,协议原理一致。

许可协议:  CC BY 4.0