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 | Numeric.toHexString(Base58Check.base58ToBytes(b58)) | Base58Check.bytesToBase58(Numeric.hexStringToByteArray(hex)) |
| 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
FullNode HTTP APIs normally use the 21-byte hex form with the 41 network prefix when visible is false or omitted. JSON-RPC eth_* methods instead accept 20-byte Ethereum-style addresses and add the 41 prefix internally. Follow the parameter format documented for the endpoint rather than passing one form unchanged between the two API families.
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.
Do not change field values in raw_data or reorder elements in its arrays between constructing and signing a transaction. Those changes alter the protobuf serialization, producing a different txID and invalidating the original signature. Reordering JSON object keys alone does not change the parsed protobuf message, but reserializing the object can introduce other differences. Computing the signing hash from the bytes decoded from the node's raw_data_hex is the safest approach.
Smart-contract parameter ABI encoding
When calling a contract through /wallet/triggersmartcontract or /wallet/triggerconstantcontract, provide the call data in one of these two forms:
- Supply
function_selectorandparameter. Setfunction_selectorto the function signature, such astransfer(address,uint256), and put only the ABI-encoded arguments inparameter. The node derives and prepends the function selector. - Supply
datawith the complete ABI calldata: the 4-byte function selector followed by the encoded arguments. When using this form, omitfunction_selectorandparameter.
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. In a raw HTTP request, thefunction_selectorfield takes the function signature, not these 4 bytes. - 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)
Use the standard 20-byte address for ABI encoding. Remove the
0x41network prefix and left-pad the remaining 20 bytes to a 32-byte ABI word. ABI decoders generated by TRON Solidity retain compatibility with a historical 21-byte form, but this is TRON-specific behavior and is not supported by every standard ABI tool.
Example: TRC-20 transfer
Call transfer(address,uint256) on a TRC-20 contract, sending 100 tokens to TXT7aiRQCLiMgsMZgFceLYbQu3MynssYUK:
The function signature is transfer(address,uint256), and its selector is a9059cbb. The encoded arguments are:
to_address (32B): 000000000000000000000000eba1c3a4a5e7a26acab1e10cd6e6c6e2db6ee9c2
amount (32B): 0000000000000000000000000000000000000000000000000000000005f5e100For a raw HTTP request, use either of the following forms. These fragments show only the ABI-related fields; the complete request must also include required fields such as owner_address and contract_address.
Let the node derive and prepend the function selector:
{
"function_selector": "transfer(address,uint256)",
"parameter": "000000000000000000000000eba1c3a4a5e7a26acab1e10cd6e6c6e2db6ee9c20000000000000000000000000000000000000000000000000000000005f5e100"
}Alternatively, concatenate the selector and encoded arguments yourself and pass the complete calldata in data:
{
"data": "a9059cbb000000000000000000000000eba1c3a4a5e7a26acab1e10cd6e6c6e2db6ee9c20000000000000000000000000000000000000000000000000000000005f5e100"
}Do not include a9059cbb in parameter when using the first form. The node would prepend another selector derived from function_selector.
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— the node interprets the input as hexadecimal and rejects the request because the address format does not match. Either setvisible: trueor 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. - Changing field values or array-element order in
raw_databefore signing — these changes alter the protobuf bytes and invalidate the original signature. JSON object key order itself does not affect the parsed protobuf message; to avoid reserialization differences, hash the bytes decoded fromraw_data_hex. - 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 6 days ago