交易签名与广播——API 工作流
TRON 中所有修改链上状态的操作都遵循三步工作流:构建未签名交易、在客户端用发送方私钥签名、广播已签名交易。本页介绍交易完整生命周期,附操作示例与常见开发陷阱。
TRON 中所有改变链上状态的操作(转账、智能合约交互、质押、投票等)都遵循同一套交易流程:
- 构建(Create):生成包含业务操作和链上时序信息的未签名交易。
- 签名(Sign):在客户端用发送方私钥对交易签名。
- 广播(Broadcast):把已签名交易发送到 TRON 网络,等待超级代表(SR)打包。
查询余额、读取合约状态、获取区块信息等只读操作不改变链上状态,不需要签名和广播。下面拆解写入交易的完整工作流:
前置阅读
三步工作流
构建未签名交易有多种方式:调用节点 API、用各语言 SDK、用本地加密工具包。下面以调用节点 API 为例:
flowchart LR
%% 全局样式定义
classDef boxStyle fill:#fff,stroke:#2b7a78,stroke-width:1px,color:#333;
classDef textStyle fill:none,stroke:none,color:#666,font-size:13px;
subgraph Step1 ["1. 构建"]
direction TB
A1["通过 POST 方法<br>调用节点 API<br>获取未签名的 raw_data"]
A2["⬇ 未签名交易"]:::textStyle
A1 ~~~ A2
end
subgraph Step2 ["2. 签名"]
direction TB
B1["SHA-256(raw_data)<br>+ secp256k1 签名"]
B2["⬇ 已签名交易"]:::textStyle
B1 ~~~ B2
end
subgraph Step3 ["3. 广播"]
direction TB
C1["将已签名交易 POST 到节点<br>/wallet/broadcasttransaction"]
C2["⬇ txid"]:::textStyle
C1 ~~~ C2
end
Step1 --> Step2 --> Step3
%% 应用样式
class A1,B1,C1 boxStyle;
注意交易签名必须在客户端本地执行。私钥不应离开本地设备,TRON 节点和 TronGrid 都不需要接触私钥。
第 1 步——构建未签名交易
TRON 节点为不同交易类型提供对应的构建 Endpoint。客户端提交描述操作的 JSON 请求,节点返回完整但未签名的交易对象。
| 业务操作 | 构建 Endpoint |
|---|---|
| 转账 TRX | /wallet/createtransaction |
| 转账 TRC-10 Token | /wallet/transferasset |
| 调用智能合约(包含 TRC-20 转账) | /wallet/triggersmartcontract |
| 质押 TRX(Stake 2.0) | /wallet/freezebalancev2 |
| 为超级代表(SR)投票 | /wallet/votewitnessaccount |
| 部署智能合约 | /wallet/deploycontract |
返回的 JSON 有三个关键顶层字段:
txID:raw_data的SHA-256哈希,即交易哈希,也是密码学签名的对象。raw_data:结构化的交易体(包含交易类型、参数、TAPOS 引用块及过期时间等)。raw_data_hex:raw_data经 Protobuf 序列化后的十六进制字符串。
节点还会在交易体里自动填充两个相互独立的时序字段:
- TAPOS 引用(
ref_block_bytes和ref_block_hash):把交易锚定到近期区块,防止跨分叉重放。被引用区块如果因链重组或分叉脱离规范链,交易在打包时会因 TAPOS 校验失败被拒绝。 - 过期时间(
expiration):交易的独立截止时间戳,默认有效期约 60 秒,在构建交易时确定。如果交易由节点构建(如/wallet/createtransaction等接口),默认值取自该节点config.conf中的trx.expiration.timeInMilliseconds;如果使用 SDK(TronWeb、Trident、GoTron 等)在客户端构建,expiration由 SDK 设置,多数 SDK 支持按交易覆盖。交易若未在截止前打包进区块,会被网络判定为过期;该截止时间相对链头区块的上限约为 24 小时。
重要在构建交易后的 50 秒内完成签名并广播,否则交易很可能因超时过期被网络拒绝。
第 2 步——对交易进行签名
交易签名在客户端本地完成,通常包含三个子步骤:
- 计算
raw_data的SHA-256哈希,结果与txID相同。 - 用发送方的 Secp256k1 私钥对该哈希签名。
- 把生成的 65 字节签名追加到交易对象的
signature数组(transaction.signature[])。
主流 SDK 把这三个子步骤封装为单一方法。以 TronWeb 为例,直接调用签名方法即可完成签名:
| SDK | 签名调用方法 |
|---|---|
| TronWeb (JavaScript) | tronWeb.trx.sign(unsignedTx, privateKey) |
| Trident (Java) | KeyPair.signTransaction(unsignedTx) |
| gotron-sdk (Go) | signedTx, err := tx.Sign(privateKey) |
在无法直接用主流 SDK 的环境(如硬件安全模块 HSM、硬件钱包),可从 raw_data_hex 自行计算交易哈希,用标准 Secp256k1 密码学库签名。签名原语的协议底层细节,参见交易数据结构说明。
多重签名交易遵循相同流程,区别在于:所有共同签名者针对同一个 txID 分别签名,广播前把全部签名收集进 signature 数组。详情参见账户权限管理。
第 3 步——广播已签名交易
把已签名的交易对象 POST 到 /wallet/broadcasttransaction:
POST /wallet/broadcasttransaction
Content-Type: application/json
{
"txID": "...",
"raw_data": { ... },
"raw_data_hex": "...",
"signature": ["..."]
}广播成功后,接口会返回如下响应:
{ "result": true, "txid": "f947f1283f7b43c111eba662..." }交易进入 TRON 的交易内存池(Mempool)后,等待超级代表(SR)打包进区块。要确认交易是否执行成功并上链,轮询 /walletsolidity/gettransactioninfobyid,直到交易出现在已固化区块中。
注意广播成功不等于交易执行成功。广播成功只代表交易被节点内存池接收;SR 打包执行时仍可能失败,例如智能合约触发
revert,或带宽、能量不足导致执行中断。需要最终状态时,必须查询已固化区块中的 receipt,不要把广播接口的成功返回当作最终状态。排障路径参见广播与 RPC 错误。
第 4 步——确认交易结果
广播之后,交易还会经过接收、打包、执行和固化几个阶段。不同 API 返回的是不同阶段的数据,不应混用。
| 阶段 | 推荐接口 | 返回语义 | 下一步 |
|---|---|---|---|
| 构建未签名交易 | CreateTransaction、TriggerSmartContract 等构建接口 | 返回 raw_data、raw_data_hex 和 txID,交易尚未签名、尚未进入网络 | 本地签名 |
| 收集签名权重 | GetSignWeight | 返回当前签名权重是否达到目标 permission 的 threshold | 达到 threshold 后广播 |
| 广播已签名交易 | BroadcastTransaction、BroadcastHex | result: true 只表示广播节点接受交易并放入本地 mempool | 继续查询交易状态 |
| 查询交易本体 | GetTransactionById | 返回交易原始内容,包括 raw_data、签名和合约调用参数;不包含执行收据 | 需要执行结果时查询 receipt |
| 查询执行收据 | GetTransactionInfoById | 返回手续费、Energy 消耗、合约执行状态、Event 和内部交易等 receipt 信息 | 判断执行成功或失败 |
| 查询已固化结果 | GetTransactionInfoById (SolidityNode) | 仅返回已固化区块中的 receipt | 需要最终状态时使用 |
如果业务需要最终结果,应以 SolidityNode 的 receipt 查询结果作为确认依据。查询不到 receipt 时,不要立即判定失败;应等待交易过期窗口结束,并结合区块扫描或后续查询确认交易是否上链。
对普通 TRX / TRC-10 顶层转账,交易本体通常已经包含收款地址和金额;对 TriggerSmartContract,必须读取 receipt 才能判断合约实际执行结果。TRC-20 转账应解析 receipt 中的 Transfer(address,address,uint256) Event;合约派生的 TRX / TRC-10 转账应解析 internal_transactions[]。
实际示例:质押 TRX
下面是通过 Stake 2.0 质押 TRX 获取能量的完整交易生命周期示例:
1. 构建交易
POST /wallet/freezebalancev2
Content-Type: application/json
{
"owner_address": "41e552f6487585c2b58bc2c9bb4492bc1f17132cd0",
"frozen_balance": 1000000,
"resource": "ENERGY"
}接口响应如下:
{
"visible": false,
"txID": "f947f1283f7b43c111eba662ab5d27b31dc906b77fdb0b3e52ee626335b21108",
"raw_data": {
"contract": [{
"parameter": {
"value": {
"resource": "ENERGY",
"frozen_balance": 1000000,
"owner_address": "41e552f6487585c2b58bc2c9bb4492bc1f17132cd0"
},
"type_url": "type.googleapis.com/protocol.FreezeBalanceV2Contract"
},
"type": "FreezeBalanceV2Contract"
}],
"ref_block_bytes": "a59c",
"ref_block_hash": "aef359052c4aa176",
"expiration": 1681117668000,
"timestamp": 1681117610802
},
"raw_data_hex": "0a02a59c2208aef359052c4aa17640a0c5afd3f6305a59083612550a34..."
}2. 签名交易
在本地用客户端 SDK 对上一步返回的交易对象签名:
const signedTx = await tronWeb.trx.sign(unsignedTx, privateKey);
// 此时,signedTx 对象的 signature 数组中已填入签名数据:signature[0]3. 广播交易
把签名后的完整交易对象发送到网络:
POST /wallet/broadcasttransaction
Content-Type: application/json
{ ...signedTx }接口响应如下:
{ "result": true, "txid": "f947f1283f7b43c111eba662ab5d27b31dc906b77fdb0b3e52ee626335b21108" }这笔质押 1 TRX(等于 1,000,000 sun)的交易已提交至交易内存池。交易被超级代表打包入账并固化后,质押额度和对应的能量可通过 /wallet/getaccountresource 确认。
常见陷阱
- TAPOS 引用失效:
ref_block_bytes和ref_block_hash必须指向规范主链上的近期区块。从生成交易到打包期间若发生链重组(如微分叉)使被引用区块失效,交易打包时会因TAPOS_ERROR被拒绝。解决:构建交易时尽量引用已固化区块,而非链头区块。 - 交易超时过期:与 TAPOS 独立,交易体的
expiration字段(默认约 60 秒)限定交易打包前的生存周期。节点构建的交易默认值取自节点config.conf中的trx.expiration.timeInMilliseconds;客户端 SDK 构建的交易由 SDK 设置,多数 SDK 支持按交易覆盖。expiration相对链头区块的上限约为 24 小时。构建到广播之间延迟过长会触发过期被拒。解决:构建后立即签名并广播。 visible标志不一致:visible=true时请求参数和响应中的地址为 Base58 格式(以T开头);visible=false时为十六进制 Hex 格式(以41开头)。构建交易和本地签名两环节的visible格式不一致,会导致签名校验失败。详情参见地址与数据编码。- 对错误的哈希算法签名:TRON 签名的哈希对象是
raw_data经SHA-256的结果,不是以太坊常用的 Keccak-256,也不是对raw_data_hex字符串本身做哈希(尽管两者算出的字节序列有逻辑关联)。推荐:始终用 SDK 封装的签名 API,避免自行实现哈希与签名。 - 交易过期后复用旧
txID:交易一旦过期,该txID即失效。从头重新构建并签名一笔新交易,不要对过期的raw_data重新签名再广播。 - 把广播成功当作执行成功:接口返回
{"result": true}只表明交易通过基础校验、进入内存池,不代表已被 SR 打包进区块并执行成功。智能合约抛revert、账户资源(能量 / 带宽)不足等执行失败的情况,广播阶段仍返回成功。务必调用/walletsolidity/gettransactioninfobyid检查交易的最终收据(Receipt)和执行状态。 - 重复广播同一笔交易:因网络波动重试时,重复广播完全相同的已签名交易通常返回相同
txid。TRON 会对已入池交易自动去重,所以传输不确定时重试广播是安全的。 - 在服务端托管或使用用户私钥签名:不要在中心化服务器上代持或使用用户私钥。私钥应保留在客户端、钱包插件(如 TronLink、WalletAdapter)或硬件加密模块(HSM)中。
相关资源
- 确认语义 —— 区分广播成功、交易上链、Receipt 执行结果和已固化状态
- 地址与数据编码 —— 详细了解 Base58 与 Hex 地址转换、
visible参数的影响以及 ABI 编码规范 - 广播与 RPC 错误 —— 排查
TAPOS_ERROR、TRANSACTION_EXPIRATION_ERROR、资源不足和广播失败 - 账户管理 —— TRON 地址格式、生成算法与密钥派生机制
- 交易结构解析 —— 深入剖析 TRON 交易的原生数据结构与其完整生命周期
- 账户多重签名管理 —— 多重签名配置及多个签名收集的 API 工作流
Updated 5 days ago