Encoding addresses and data
How addresses, transaction data, and smart-contract parameters are encoded on the TRON API surface. Covers base58 vs hex addresses, the visible flag, ABI encoding for contract calls, and the difference between raw_data and raw_data_hex.
The TRON API has two encoding choices that catch new integrators:
- Addresses can be sent in two formats — base58check (
T...) or hex (41...) — controlled by thevisibleflag - Transaction data appears both as a structured JSON object (
raw_data) and as its protobuf serialization (raw_data_hex)
Getting either wrong typically manifests as a signature-verification failure or a CONTRACT_VALIDATE_ERROR. This page covers both encodings, the conversion procedures, and the bugs they cause.
Prerequisites
Address formats
TRON addresses exist in two interchangeable forms:
| Format | Example | Length | When used |
|---|---|---|---|
| base58check | TJRabPrwbZy45sbavfcjinPJC18kjpRTv8 | 34 chars, starts with T | User-facing (wallets, explorers, prose) |
| hex | 415CBDD86A2FA8DC4BDDD8A8F69DBA48572EEC07FB | 42 chars, starts with 41 (Mainnet) | Wire-level (protobuf, default API requests) |
Both encode the same 21-byte address: a 1-byte network prefix (0x41 for all current TRON production networks — Mainnet, Shasta, and Nile) followed by 20 bytes derived from the public key. See Accounts for the derivation procedure.
Legacy0xa0prefixThe
0xa0prefix you may see in older docs or inConstant.javais a legacynet.type = testnetconfig option (framework/src/main/resources/config.confnotes "'testnet' is not related to TRON network Nile, Shasta or private net"). No active TRON network uses0xa0.
The visible flag
visible flagMost API requests and responses accept a visible field that controls which address format is used throughout the payload:
visible | Address format in request and response |
|---|---|
false (default) | hex (41...) |
true | base58check (T...) |
POST /wallet/createtransaction
Content-Type: application/json
{
"visible": true,
"owner_address": "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
"to_address": "TXT7aiRQCLiMgsMZgFceLYbQu3MynssYUK",
"amount": 1000000
}If you send a base58 address with visible: false (or omit the flag), the node returns a validation error. The reverse — hex with visible: true — is also rejected.
Some endpoints have a visible parameter in the URL query string (?visible=true) instead of in the JSON body. Check the endpoint documentation.
Converting between formats
All SDKs expose conversion helpers; you should rarely need to do this by hand.
| SDK | base58 → hex | hex → base58 |
|---|---|---|
| TronWeb | tronWeb.address.toHex(b58) | tronWeb.address.fromHex(hex) |
| Trident | KeyPair.toBase58CheckAddress(hexBytes) | (reverse via Numeric.hexStringToByteArray) |
| gotron-sdk | common.DecodeCheck(b58) | common.EncodeCheck(hexBytes) |
If you must convert manually:
- hex → base58check: prepend the
0x41prefix byte if missing, computedouble SHA-256of the 21-byte address, append the first 4 bytes of that double-hash, base58-encode the resulting 25 bytes - base58check → hex: base58-decode the 34-char string to 25 bytes, verify the trailing 4-byte checksum, take bytes 0–20 as the 21-byte address
The 0x41 prefix is mandatory in hex form on Mainnet. Dropping it produces a 20-byte string that the API will reject.
Transaction body: raw_data vs raw_data_hex
raw_data vs raw_data_hexConstructor endpoints return both forms of the transaction body:
raw_data— a structured JSON object containing the contract, TAPOS reference, expiration, and timestampraw_data_hex— the protobuf serialization ofraw_data, hex-encoded
The signing hash is SHA-256(raw_data_bytes), where raw_data_bytes is the protobuf-serialized form. Practically: SDKs serialize raw_data themselves or use raw_data_hex directly. Both produce the same bytes.
You should not modify raw_data between construct and sign. Any change — even reordering keys — will produce a different txID and the node will reject the broadcast.
Smart-contract parameter ABI encoding
A contract call (triggersmartcontract / triggerconstantcontract) takes a parameter field — the ABI-encoded arguments to the function being called. TRON uses the same ABI encoding as Ethereum, with one address-format difference.
Encoding rules
- Function selector — first 4 bytes of
Keccak-256(function_signature), wherefunction_signatureis the function name followed by parenthesized parameter types:transfer(address,uint256)→0xa9059cbb - Arguments — each argument padded to 32 bytes:
uint256— big-endian 32-byte integeraddress— 20 bytes of the address (drop the 0x41 prefix), left-padded with zeros to 32 bytesstring,bytes— length-prefixed dynamic encoding (see Solidity ABI spec)
Address ABI encoding drops the 0x41 prefix. TRON contract parameters use the 20-byte address (the same form Ethereum uses) padded to 32 bytes — not the 21-byte Mainnet form. Sending
0x41...(21 bytes padded to 32) is the most common ABI-encoding mistake.
Example: TRC-20 transfer
Call transfer(address,uint256) on a TRC-20 contract, sending 100 tokens to TXT7aiRQCLiMgsMZgFceLYbQu3MynssYUK:
function_selector: a9059cbb
to_address (32B): 000000000000000000000000eba1c3a4a5e7a26acab1e10cd6e6c6e2db6ee9c2
amount (32B): 0000000000000000000000000000000000000000000000000000000005f5e100
Concatenated: a9059cbb000000000000000000000000eba1c3a4a5e7a26acab1e10cd6e6c6e2db6ee9c20000000000000000000000000000000000000000000000000000000005f5e100
This becomes the parameter field of the triggersmartcontract request.
SDKs handle ABI encoding automatically when you pass typed arguments to high-level methods (tronWeb.transactionBuilder.triggerSmartContract(...)). You only need to encode manually when working with raw HTTP or when generating a transaction without a deployed contract ABI.
TRC-10 identification
TRC-10 tokens are identified by a numeric token ID, not a contract address. But the asset_name field in TRC-10 contracts means different things depending on when the transaction landed:
| Block range | What asset_name contains | Example |
|---|---|---|
| Block 5537806 onward (the vast majority of blocks) | The numeric token ID | "1002000" |
| Before block 5537806 (early-2019 and earlier) | The token's name string | "USDJ" |
Why the change? Early TRON required every TRC-10 token name to be unique, so the name itself worked as an identifier. A governance proposal (TRONSCAN #14) later allowed duplicate names — after that point, only the numeric ID stays unique.
For new integrations: treat asset_name as a token ID and reject pre-cutoff blocks. If you scan the full chain history (block explorers, historical indexers), branch on block number to parse correctly.
Common encoding mistakes
visibleflag mismatch across construct and sign — the construct response and the broadcast request must use the same flag value. SDKs handle this; raw HTTP integrators must propagate the flag.- Including the
0x41prefix in ABI-encoded addresses — ABI uses 20-byte addresses, not 21-byte Mainnet form. Drop the prefix when ABI-encoding. - Sending a base58 address with
visible: false— node returnsINVALID_PARAMETER. Either flip the flag or convert the address. - Hashing
raw_data_hexas a string instead of its bytes — the signing hash is over the protobuf bytes, not over the hex string's ASCII. Use SDK signing calls to avoid this. - Reordering keys in
raw_databefore signing — any modification invalidates the signature. Hash the bytes the node returned, don't re-serialize. - Using TRC-10 token name as a stable identifier — names were de-duplicated after proposal #14; production code should key on numeric ID.
Related resources
- API workflow — construct, sign, broadcast lifecycle
- Errors and debugging — error codes including encoding-related rejections
- Accounts — address format and key derivation
- TRC-20 contract interaction — TRC-20 ABI examples
- Transactions — full transaction structure
Updated 4 days ago