Skip to content

feat(tron): add tx signing, fee estimation, and withdrawals#2714

Open
shamardy wants to merge 9 commits intotron-tokens-activationfrom
tron-fees-and-withdrawals
Open

feat(tron): add tx signing, fee estimation, and withdrawals#2714
shamardy wants to merge 9 commits intotron-tokens-activationfrom
tron-fees-and-withdrawals

Conversation

@shamardy
Copy link
Collaborator

Summary

Add transaction signing, fee estimation, and the full withdraw pipeline for TRX native and TRC20 token transfers. This is the fourth PR in the TRON integration series:

  1. feat(tron): initial groundwork for full TRON integration #2425 — Initial groundwork (ChainSpec, TronAddress)
  2. feat(tron): add HD wallet activation via EthCoin pipeline #2467 — HD wallet activation via EthCoin pipeline
  3. feat(tron): add TRC20 token activation and balance queries #2712 — TRC20 token activation and balance queries
  4. This PR — Transaction building, signing, fee estimation, and withdrawals

Key Features

  • Local protobuf transaction building using prost::Message derive macros — no dependency on node-side transaction creation
  • SHA256 + secp256k1 signing — same curve as EVM but with SHA256 digest (not Keccak256), TAPOS anti-replay (not nonces), and 65-byte r||s||v signatures with v ∈ {0,1}
  • Bandwidth + energy fee estimation — bandwidth charged per byte of serialized tx, energy charged per unit of smart contract execution (TRC20); both priced in SUN and converted to TRX at chain rate
  • New TxFeeDetails::Tron variant — reports bandwidth_used, energy_used, bandwidth_fee, energy_fee, total_fee in withdraw responses
  • Chain-dispatched broadcast via send_raw_tx — TRON protobuf hex routes to /wallet/broadcasthex, EVM RLP routes to eth_sendRawTransaction
  • Full withdraw support for both TRX and TRC20 in iguana and HD wallet modes, with max-send fee deduction for TRX and cross-asset fee verification for TRC20

Architecture

New modules under mm2src/coins/eth/tron/:

Module Purpose
proto.rs Minimal TRON protobuf types (Transaction, TransactionRaw, TransferContract, TriggerSmartContract) with correct non-sequential field tags
tx_builder.rs Unsigned transaction builders for TRX transfers and TRC20 transfer(address,uint256) calls, with TAPOS reference block computation
sign.rs sign_tron_transaction() — SHA256 digest → secp256k1 sign → 65-byte signature with recovery id normalization
fee.rs Bandwidth estimation from serialized tx size, energy estimation via triggerconstantcontract, fee calculation with resource deficit logic
withdraw.rs build_tron_trx_withdraw() with iterative max-send convergence, build_tron_trc20_withdraw() with cross-asset TRX balance verification

Modified files:

File Changes
eth/tron/api.rs get_account_resource(), broadcast_hex(), extended getnowblock for TAPOS block data
eth/eth_withdraw.rs TRON signing path replaces stub, TRON-specific fee estimation and memo rejection
eth.rs send_raw_tx chain dispatch (TRON → broadcast_hex, EVM → eth_sendRawTransaction)
lp_coins.rs / tx_fee_details.rs TxFeeDetails::Tron variant

Test Coverage

7 withdraw integration tests (cargo test --test mm2_tests_main --features tron-network-tests -- tron):

Test Coverage
test_trx_withdraw_and_send TRX withdraw + broadcast on Nile, exact spent_by_me/my_balance_change assertions
test_trc20_withdraw_and_send TRC20 USDT withdraw + broadcast, verifies fees paid in TRX (not token)
test_trx_withdraw_max Max TRX withdraw — fee deduction, total_amount + fee ≈ balance
test_trx_withdraw_hd HD wallet TRX withdraw from derivation index 1
test_trc20_withdraw_hd HD wallet TRC20 withdraw with energy fee verification
test_trx_withdraw_insufficient_balance Unfunded HD index 2 rejects with proper error
test_trx_fee_details_structure All TxFeeDetails::Tron fields present with correct types and values

Coin Configuration (unchanged from #2712)

{
  "coin": "TRX",
  "name": "tron",
  "fname": "TRON",
  "mm2": 1,
  "wallet_only": true,
  "chain_id": 728126428,
  "protocol": { "type": "TRON" }
}

TRON Roadmap (Next Steps)

This PR completes wallet-only TRON support. The following items remain for full TRON parity with EVM chains:

  • Atomic swap support — implement HTLC swap operations (SwapOps, MakerCoinSwapOpsV2, TakerCoinSwapOpsV2), order matching integration, and trade fee methods
  • Etomic swap contract for TVM — adapt EtomicSwapMakerV2/TakerV2 Solidity contracts for TVM deployment (TVM is EVM-compatible but uses different address format and energy model); deploy to Nile testnet → test → deploy to mainnet
  • Release full TRON integration — once swaps and contracts are complete, merge the full stack into dev and cut a release with TRX/TRC20 swap support
  • TRX staking (Stake V2) — allow users to stake TRX to acquire bandwidth and energy resources via TRON's Stake 2.0 mechanism (TIP-467), reducing transaction fees; includes staking, unstaking (14-day lock), and resource delegation
  • Dockerized TRON node for CI — run a local TRON node in Docker for deterministic integration tests (currently using public Nile testnet nodes which can be flaky)
  • Chain abstraction refactor — unify EVM and TRON under common RPC, transaction pipeline, broadcast session, and receipt finality abstractions; eliminate duplicated dispatch logic and move toward a typed chain model where invalid chain×asset combinations are unrepresentable
  • Remaining feature parity — WalletConnect signing support (TRON uses ETH protocol for WC, separate coin config), Trezor hardware wallet signing, MetaMask integration, TRON message signing/verification (different format from EVM), NFT support (TRC-721/TRC-1155), transaction history sync

Partially addresses #1542 and #1698.

🤖 Generated with Claude Code

shamardy and others added 9 commits February 12, 2026 22:53
Hand-written prost::Message structs for the minimal TRON transaction
protobuf types needed for TRX transfers and TRC20 interactions:
TransferContract, TriggerSmartContract, TransactionContract,
TransactionRaw (non-sequential tags), and Transaction.

Includes golden vector tests using real raw_data_hex from TRON
developer docs to validate wire-format compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Parse `blockID` (hex → [u8; 32]) and `timestamp` from the
`/wallet/getnowblock` response. Add `TaposBlockData` struct and
`get_block_for_tapos()` on both `TronHttpClient` and `TronApiClient`
(with node rotation) to provide validated inputs for TRON transaction
building.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…fers

Build TransactionRaw protobuf messages for native TRX transfers and
TRC20 token transfers. Reuses the shared ERC20_CONTRACT ABI for
transfer(address,uint256) encoding, matching the EVM code path.

Golden vector tests verify byte-exact output against real Nile testnet
transactions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add TRON fee estimation module with bandwidth/energy cost calculations
and chain prices API. Extract TxFeeDetails from lp_coins.rs into its
own module for better organization.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add two TRON API primitives needed for fee estimation and transaction
broadcasting:

- get_account_resource(address): fetches bandwidth/energy quotas from
  /wallet/getaccountresource, mapping TRON's mixed-case proto3 JSON
  fields into the existing TronAccountResources domain type via an
  intermediate serde struct with exact #[serde(rename)] per field.

- broadcast_hex(tx_hex): submits signed protobuf bytes via
  /wallet/broadcasthex, returning BroadcastHexResponse { txid }.
  Error responses (result: false) handled by existing
  tron_error_from_value() in post().

Both methods available on TronHttpClient and TronApiClient (with
try_clients node rotation).

Also standardizes all request structs to visible: true (Base58
addresses) for consistency, and removes a duplicated
TriggerConstantContractRequest from integration tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dispatch EthCoin::send_raw_tx() and send_raw_tx_bytes() by ChainFamily:
- EVM: keeps existing eth_sendRawTransaction path
- TRON: validates input then routes to TronApiClient::broadcast_hex()

Input validation for TRON: strip 0x prefix, hex-only chars, even length,
256 KiB size cap. Empty payloads rejected on both string and bytes paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement the full TRON withdrawal flow integrated into EthWithdraw:
- TronWithdrawContext groups shared parameters for both transfer types
- build_tron_trx_withdraw: fee estimation with convergence loop for
  max-withdraw (handles varint-encoded amount affecting tx size/fee)
- build_tron_trc20_withdraw: energy estimation, fee_limit, and TRX
  balance check for fee coverage
- validate_tron_fee_policy: rejects EVM gas options for TRON
- build_tron_withdraw in EthWithdraw: orchestrates TRON-specific RPC
  calls, signing (protobuf), and TransactionDetails assembly
- Refactor build_transaction_details into shared method used by both
  EVM and TRON paths, eliminating duplicated spent/received logic

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…and doc fixes

- Add 7 withdraw integration tests against Nile testnet (TRX/TRC20,
  iguana/HD, max, insufficient balance, fee structure validation)
- Add pre-withdraw balance checks via my_balance RPC
- Use exact vec equality for from/to address assertions (matching
  ETH/Tendermint patterns)
- Assert spent_by_me, received_by_me, my_balance_change exactly
  (TRX: amount+fee, TRC20: amount only since fee is in TRX)
- Add TronApiClient-based on-chain verification helper with node failover
- Convert 24 deterministic unit tests to cross_test! for WASM compat
- Add module-level and struct docs for fee.rs and tx_fee_details.rs
- Add second Nile testnet node (api.nileex.io) for failover
- Feature-gate TRON_API_TIMEOUT: 10s production, 60s tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant