离线构建交易

如何在未连接节点的机器上构建并签名 TRON 交易,然后单独广播。

冷钱包、多方签名仪式和隔离密钥托管场景中,交易通常需要在无网络连接的机器上构建并签名,再把已签名交易转移到联网机器广播。

离线构建时,没有全节点替你补齐 TAPOS、过期时间等字段;这些字段必须由客户端提前准备并写入交易体。本文说明需要手动填充的字段,以及使用 TronWeb 的离线构建模式。

📘

前置阅读


离线环境需要设置哪些字段

联网调用 TronWeb 构建方法时,全节点会自动填充部分字段。离线构建时,需要自行设置:

字段作用离线填充方式
raw_data.ref_block_bytesTAPOS:锚定近期区块高度从预先获取的参考区块中截取
raw_data.ref_block_hashTAPOS:锚定同一参考区块 ID从同一参考区块中截取
raw_data.expiration交易过期时间通常设置为 now + N,最长 24 小时
raw_data.timestamp交易构建时间设置为离线机器当前 Unix 毫秒时间戳
raw_data.fee_limit写入类智能合约交易的调用方侧 Energy 预算上限调用或部署智能合约时需要设置,单位为 sun

参考区块应位于最近 65,536 个区块窗口内。常规做法是使用最新固化区块。


双机工作流

1. 联网机器:获取参考区块

先在联网机器上通过节点 API 获取区块信息,再把结果导出给离线机器:

const block = await tronWeb.trx.getCurrentBlock();
const refBlock = {
  blockID: block.blockID,
  number: block.block_header.raw_data.number
};
// 将 refBlock 保存为文件或二维码,供离线机器读取

2. 离线机器:构建并签名

离线端使用导入的参考区块填充 TAPOS 字段,然后本地签名:

const TronWeb = require('tronweb');
const tronWeb = new TronWeb({ fullHost: '' });

const builder = tronWeb.transactionBuilder;
const unsignedTxn = await builder.sendTrx(
  "TVDGpn4h...wTppnkr",   // 接收方地址
  100,                     // 金额,单位为 sun
  "TNPeeaaF...8G1NYqeL"   // 发送方地址
);

// ref_block_bytes = 区块高度按 8 字节大端序表示后的第 6 至 8 字节(不含)
// ref_block_hash  = 32 字节区块 ID 的第 8 至 16 字节(不含)
const blockNumBE = Buffer.alloc(8);
blockNumBE.writeBigUInt64BE(BigInt(refBlock.number));
const refBlockBytes = blockNumBE.slice(6, 8).toString('hex');
const refBlockHash = Buffer.from(refBlock.blockID, 'hex').slice(8, 16).toString('hex');

unsignedTxn.raw_data.ref_block_bytes = refBlockBytes;
unsignedTxn.raw_data.ref_block_hash = refBlockHash;
unsignedTxn.raw_data.expiration = Date.now() + 60 * 1000;

// TronWeb 会基于 Protobuf 序列化后的 raw_data 重新计算 txID。
// txID = SHA-256(protobuf(raw_data)); TRON 使用 Protobuf,不使用以太坊的 RLP。
const signedTxn = await tronWeb.trx.sign(unsignedTxn, privateKey);

// 将 signedTxn 保存为文件或二维码,带回联网环境

ref_block_bytesref_block_hash 的截取规则参见交易。签名消息和 65 字节签名格式参见签名校验


硬件钱包签名流程

硬件钱包托管私钥时,常见流程分三步:

  1. 联网机器构建未签名交易,并生成待签名消息哈希 SHA-256(raw_data)
  2. 硬件设备展示交易信息,用户确认后用设备内私钥签名,输出 65 字节签名。
  3. 联网机器signature 组装回交易体,再广播交易。

这种模式让私钥始终留在硬件设备中,联网机器只负责构建、组装和广播交易。


广播已签名交易

回到联网环境后,广播已签名交易:

const result = await tronWeb.trx.sendRawTransaction(signedTxn);
console.log(result.txid);

如果广播时交易已经超过 expiration,网络会以 TAPOS_ERROREXPIRED 拒绝交易。长时间冷钱包流转时,应在广播前检查过期时间。


常见问题

  • 参考区块太旧ref_block_bytes 只有 2 字节,节点只在近期区块窗口中查找参考区块。超出窗口后,TAPOS 查找会失败并抛出 TaposException。签名前应刷新参考区块,并在交易过期前完成广播。
  • 离线时钟偏差expirationtimestamp 依赖离线机器本地时钟。若本地时间与网络时间差距过大,交易可能被判定为已过期或来自未来。
  • 网络不匹配:签名交易已经绑定到参考区块所在网络。使用主网参考区块签名的交易,不能广播到 Shasta 或 Nile。

相关资源

  • 交易 — 完整交易结构与 TAPOS 字段
  • 签名校验 — 签名校验与 65 字节上链格式
  • Trident — Java SDK 的离线交易支持
  • Wallet-cli — 支持离线签名流程的命令行工具