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 the visible flag
  • 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:

FormatExampleLengthWhen used
base58checkTJRabPrwbZy45sbavfcjinPJC18kjpRTv834 chars, starts with TUser-facing (wallets, explorers, prose)
hex415CBDD86A2FA8DC4BDDD8A8F69DBA48572EEC07FB42 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.

📘

Legacy 0xa0 prefix

The 0xa0 prefix you may see in older docs or in Constant.java is a legacy net.type = testnet config option (framework/src/main/resources/config.conf notes "'testnet' is not related to TRON network Nile, Shasta or private net"). No active TRON network uses 0xa0.

The visible flag

Most API requests and responses accept a visible field that controls which address format is used throughout the payload:

visibleAddress format in request and response
false (default)hex (41...)
truebase58check (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.

SDKbase58 → hexhex → base58
TronWebtronWeb.address.toHex(b58)tronWeb.address.fromHex(hex)
TridentKeyPair.toBase58CheckAddress(hexBytes)(reverse via Numeric.hexStringToByteArray)
gotron-sdkcommon.DecodeCheck(b58)common.EncodeCheck(hexBytes)

If you must convert manually:

  • hex → base58check: prepend the 0x41 prefix byte if missing, compute double SHA-256 of 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

Constructor endpoints return both forms of the transaction body:

  • raw_data — a structured JSON object containing the contract, TAPOS reference, expiration, and timestamp
  • raw_data_hex — the protobuf serialization of raw_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), where function_signature is the function name followed by parenthesized parameter types: transfer(address,uint256)0xa9059cbb
  • Arguments — each argument padded to 32 bytes:
    • uint256 — big-endian 32-byte integer
    • address — 20 bytes of the address (drop the 0x41 prefix), left-padded with zeros to 32 bytes
    • string, 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 rangeWhat asset_name containsExample
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

  • visible flag 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 0x41 prefix 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 returns INVALID_PARAMETER. Either flip the flag or convert the address.
  • Hashing raw_data_hex as 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_data before 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