Create TRC20 Token Transactions on TRON
Introduction
Section titled “Introduction”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.
What the API does and what it does not do
Section titled “What the API does and what it does not do”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 do | Endpoint |
|---|---|
| Create a TRON address | POST /v2/tron/addresses |
| Import an existing private key | POST /v2/tron/addresses/import |
| Read name, symbol, decimals and supply | GET /v2/tron/trc20/{contract_address} |
| Read a token balance | GET /v2/tron/balances/{address}/trc20/{contract_address} |
| Send a TRC20 transfer | POST /v2/tron/transactions/trc20 |
| Build a transfer without broadcasting | POST /v2/tron/transactions/trc20/build |
| Broadcast a transaction you signed | POST /v2/tron/transactions/broadcast |
| Get notified about incoming tokens | POST /v2/tron/webhooks |
Step 1: Create a TRON address
Section titled “Step 1: Create a TRON address”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.
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"}'import requests
url = "https://api.chaingateway.io/v2/tron/addresses"payload = {"password": "test123"}headers = { 'Accept': 'application/json', 'content-type': 'application/json', 'Authorization': 'YOUR_API_TOKEN'}
response = requests.post(url, json=payload, headers=headers)print(response.json())const axios = require('axios');
const url = 'https://api.chaingateway.io/v2/tron/addresses';const payload = { password: 'test123' };const headers = { 'Accept': 'application/json', 'content-type': 'application/json', 'Authorization': 'YOUR_API_TOKEN'};
axios.post(url, payload, { headers }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); });<?phprequire 'vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client();$response = $client->post('https://api.chaingateway.io/v2/tron/addresses', [ 'headers' => [ 'Accept' => 'application/json', 'content-type' => 'application/json', 'Authorization' => 'YOUR_API_TOKEN', ], 'json' => [ 'password' => 'test123' ]]);
echo $response->getBody();?>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.
Step 2: Read the token contract
Section titled “Step 2: Read the token contract”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.
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.
Step 3: Check the token balance
Section titled “Step 3: Check the token balance”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.
Step 4: Send the TRC20 transfer
Section titled “Step 4: Send the TRC20 transfer”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.
Parameters
Section titled “Parameters”- 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
privatekeyis not present. - privatekey: The private key of the sender, for addresses that were created or imported without a password.
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"}'import requests
url = "https://api.chaingateway.io/v2/tron/transactions/trc20"payload = { "from": "TLkkCeNdJKPNUwucdro84WjswkzM62LCTH", "to": "TUwmZghA7u2GxGxKaxM1mkCmsTF4wHs4vp", "contractaddress": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", "amount": 1, "password": "test123"}headers = { 'Accept': 'application/json', 'content-type': 'application/json', 'Authorization': 'YOUR_API_TOKEN'}
response = requests.post(url, json=payload, headers=headers)print(response.json())const axios = require('axios');
const url = 'https://api.chaingateway.io/v2/tron/transactions/trc20';const payload = { from: 'TLkkCeNdJKPNUwucdro84WjswkzM62LCTH', to: 'TUwmZghA7u2GxGxKaxM1mkCmsTF4wHs4vp', contractaddress: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', amount: 1, password: 'test123'};const headers = { 'Accept': 'application/json', 'content-type': 'application/json', 'Authorization': 'YOUR_API_TOKEN'};
axios.post(url, payload, { headers }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); });<?phprequire 'vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client();$response = $client->post('https://api.chaingateway.io/v2/tron/transactions/trc20', [ 'headers' => [ 'Accept' => 'application/json', 'content-type' => 'application/json', 'Authorization' => 'YOUR_API_TOKEN', ], 'json' => [ 'from' => 'TLkkCeNdJKPNUwucdro84WjswkzM62LCTH', 'to' => 'TUwmZghA7u2GxGxKaxM1mkCmsTF4wHs4vp', 'contractaddress' => 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', 'amount' => 1, 'password' => 'test123' ]]);
echo $response->getBody();?>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.
The sending wallet needs TRX
Section titled “The sending wallet needs TRX”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.
Signing the transaction yourself
Section titled “Signing the transaction yourself”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.
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.
Getting notified about incoming tokens
Section titled “Getting notified about incoming tokens”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.
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.
Testing on Nile
Section titled “Testing on Nile”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.