Skip to content

Create TRC20 Token Transactions on TRON

This tutorial covers the TRC20 endpoints of the Chaingateway V2 API: creating a TRON address, reading a token contract, checking a token balance and sending a TRC20 transfer. Examples are given in Shell (cURL), PHP (Guzzle), Python (Requests) and JavaScript (Axios).

For obtaining an API key and learning about authorization, please refer to the Quickstart section of the Chaingateway documentation.

The API has no endpoint that compiles or deploys a smart contract. There is no route that publishes a new TRC20 contract to the TRON network, and none of the TRON endpoints accept contract bytecode. Every TRC20 route takes a contractaddress of a token that already exists on chain, USDT and any other TRC20 token alike.

If you are looking for a way to deploy your own token contract, this API is not the tool for that step. Once the contract is deployed, the endpoints below cover the part that most integrations actually need: addresses, balances, transfers and notifications.

What you want to doEndpoint
Create a TRON addressPOST /v2/tron/addresses
Import an existing private keyPOST /v2/tron/addresses/import
Read name, symbol, decimals and supplyGET /v2/tron/trc20/{contract_address}
Read a token balanceGET /v2/tron/balances/{address}/trc20/{contract_address}
Send a TRC20 transferPOST /v2/tron/transactions/trc20
Build a transfer without broadcastingPOST /v2/tron/transactions/trc20/build
Broadcast a transaction you signedPOST /v2/tron/transactions/broadcast
Get notified about incoming tokensPOST /v2/tron/webhooks

The sending address has to be known to Chaingateway. Either create it through the API or import an existing private key through POST /v2/tron/addresses/import.

If you pass a password, the private key is stored encrypted and you sign later transactions with that password. If you leave the password out, the response contains the private key itself and you have to send it with every transaction request. The password approach is described in detail under Wallet Management.

Terminal window
curl --request POST\
--url https://api.chaingateway.io/v2/tron/addresses\
--header 'Accept: application/json'\
--header 'content-type: application/json'\
--header 'Authorization: YOUR_API_TOKEN'\
--data '{"password":"test123"}'

The response carries the new address:

{
"status": 201,
"ok": true,
"message": "Address created",
"data": [
{
"privateKey": "59f1c6200e01a2ca9471411f10198bfa63678d0e87cc2ca30f9c4a68dee78edc",
"publicKey": "040fe6e677442c36f7fdd5535ba3ff1cef0110d78363420f7e24b65298990e3467aed68b9a67e1396d84753db25b32a2fa3e1fc0a673f01638e9bcfab2d8f4ceb5",
"hexAddress": "41a56a6505ffbee78eb916dd44f4846874deddaebd",
"address": "TR3qx91smURF3R455V1ubqtoUsZbgqikfg"
}
]
}

Chaingateway does not store passwords. A lost password means a lost wallet.

Before you move a token, read its contract. The decimals value is the one you will need most often, because it tells you how the raw on-chain balance maps to the amount a user sees.

Terminal window
curl --request GET\
--url https://api.chaingateway.io/v2/tron/trc20/TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t\
--header 'Accept: application/json'\
--header 'Authorization: YOUR_API_TOKEN'
{
"status": 200,
"ok": true,
"message": "TRC20 contract fetched",
"data": {
"address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"symbol": "USDT",
"name": "TetherUSD",
"totalSupply": "61742996582033118",
"decimals": "6"
}
}

An unknown or non-TRC20 contract address answers with status 400 and the message Error fetching TRC20 contract.

Terminal window
curl --request GET\
--url https://api.chaingateway.io/v2/tron/balances/TVF2Mp9QY7FEGTnr3DBpFLobA6jguHyMvi/trc20/TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj\
--header 'Accept: application/json'\
--header 'Authorization: YOUR_API_TOKEN'
{
"status": 200,
"ok": true,
"message": "TRC20 balance fetched",
"data": {
"tronaddress": "TVF2Mp9QY7FEGTnr3DBpFLobA6jguHyMvi",
"decimals": 6,
"balance": "1000000000000000000",
"contractaddress": "TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj"
}
}

The balance field is the raw integer value. Divide it by 10 to the power of decimals to get the human readable amount.

POST /v2/tron/transactions/trc20 requires from, to, contractaddress and amount. For signing you add either password, if the address was created with a password, or privatekey for an unsecured or imported address.

  • from: The sender’s TRON address.
  • to: The recipient’s TRON address.
  • contractaddress: The contract address of the token.
  • amount: The amount to send.
  • password: The password of a password-protected Chaingateway address. Required when privatekey is not present.
  • privatekey: The private key of the sender, for addresses that were created or imported without a password.
Terminal window
curl --request POST\
--url https://api.chaingateway.io/v2/tron/transactions/trc20\
--header 'Accept: application/json'\
--header 'content-type: application/json'\
--header 'Authorization: YOUR_API_TOKEN'\
--data '{
"from": "TLkkCeNdJKPNUwucdro84WjswkzM62LCTH",
"to": "TUwmZghA7u2GxGxKaxM1mkCmsTF4wHs4vp",
"contractaddress": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"amount": 1,
"password": "test123"
}'

A successful call answers with the transaction hash:

{
"status": 201,
"ok": true,
"message": "Succesfully created transaction",
"data": {
"txid": "0x73344d178812d4919e9002c560781f288030edf72ece88823ef1c377dfb71f27"
}
}

Two errors come back as status 422 and are worth handling separately. balance is not sufficient means the sending address cannot pay for the transaction. A Validate signature error message means the key or password did not match the from address.

A TRC20 transfer is a smart contract call, and TRON charges energy and bandwidth for it. An address holding nothing but tokens cannot send them. Either fund the address with TRX, stake for resources through POST /v2/tron/freeze and POST /v2/tron/delegate, or let the fee be sponsored. Fee sponsoring is described under Sponsor TRC20 Fees.

If you would rather keep the private key out of the request, split the transfer into two calls. POST /v2/tron/transactions/trc20/build returns the raw transaction hex without broadcasting it. You sign that hex in your own code and hand the signed transaction to POST /v2/tron/transactions/broadcast.

Terminal window
curl --request POST\
--url https://api.chaingateway.io/v2/tron/transactions/trc20/build\
--header 'Accept: application/json'\
--header 'content-type: application/json'\
--header 'Authorization: YOUR_API_TOKEN'\
--data '{
"from": "TLkkCeNdJKPNUwucdro84WjswkzM62LCTH",
"to": "TUwmZghA7u2GxGxKaxM1mkCmsTF4wHs4vp",
"contractaddress": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"amount": 1
}'

The response contains raw_data and raw_data_hex. Nothing has reached the network at this point.

To learn about a TRC20 deposit without polling, subscribe a webhook with type set to TRC20 and the contractaddress of the token. The filters from and to narrow it down to a single address.

Terminal window
curl --request POST\
--url https://api.chaingateway.io/v2/tron/webhooks\
--header 'Accept: application/json'\
--header 'content-type: application/json'\
--header 'Authorization: YOUR_API_TOKEN'\
--data '{
"url": "https://example.com/webhook-receiver",
"type": "TRC20",
"contractaddress": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"to": "TUwmZghA7u2GxGxKaxM1mkCmsTF4wHs4vp"
}'

Writing the receiving end is covered in Create Webhooks, and verifying that a notification really came from Chaingateway in Securing Webhooks.

A notification that your server did not accept is not sent again on its own. Failed notifications are listed under GET /v2/tron/webhooks/notifications/failed and can be triggered again with POST /v2/tron/webhooks/notifications/{id}/retry.

Every call above works against the TRON Nile testnet. Add the header X-Network: testnet and use test tokens instead of mainnet contracts. The network list is documented under Supported Networks.