TRC20 Token Transfers - Complete Guide
Complete guide to TRC20 token transfers on TRON - from USDT to custom tokens. Learn balance checking, approvals, transfers, and error handling with Dwellir RPC.
TRON RPC endpoint: endpoint URLs, plans and pricing.
Master TRC20 Token Operations
Dwellir's TRON endpoints provide comprehensive TRC20 token support with optimized smart contract interactions. Build powerful token applications with our complete TRC20 integration guide.
Complete guide to implementing TRC20 token transfers on the TRON network. Learn how to check balances, approve spending, transfer tokens, and handle all TRC20 operations.
Overview of TRC20 Standard
TRC20 is TRON's fungible token standard, similar to Ethereum's ERC20. Popular tokens include:
- USDT (Tether) -
TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t - USDC (USD Coin) -
TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8 - JST (JUST) -
TCFLL5dx5ZJdKnWuesXxi1VPwjLVmWZZy9 - WTRX (Wrapped TRX) -
TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR
Key TRC20 Functions
balanceOf(address)- Check token balancetransfer(address,uint256)- Transfer tokensapprove(address,uint256)- Approve spendingtransferFrom(address,address,uint256)- Transfer on behalfallowance(address,address)- Check approved amount
Implementation Examples
Energy and fee_limit for USDT transfers
On TRON mainnet on 3 November 2026, a USDT transfer used 64,285 energy when the recipient already held USDT and 130,285 when it held none. At a getEnergyFee of 100 sun, a sender without staked or delegated energy burns 6.4285 TRX or 13.0285 TRX. TRON energy for USDT transfers shows the measured receipts.
fee_limitcaps all the energy a call may use, including staked and delegated energy. Size it for the 130,285 case: 13.0285 TRX plus headroom. A lower value fails transfers to new recipients withOUT_OF_ENERGY, and the network still charges the energy used.wallet/triggersmartcontractbuilds the transaction without executing it. Its response has noenergy_used, so theenergy_used || 0reads in the examples above report 0. It also returnsresult.result: truefor a transfer that will revert.- Simulate with
wallet/triggerconstantcontractbefore signing. A reverted simulation also returnsresult.result: true, so checkresult.messageandtransaction.retas well:
async function simulateTransfer(apiUrl, tokenAddress, fromAddress, parameter) {
const response = await fetch(`${apiUrl}/wallet/triggerconstantcontract`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
owner_address: fromAddress,
contract_address: tokenAddress,
function_selector: 'transfer(address,uint256)',
parameter,
visible: true
})
});
const result = await response.json();
// visible: true returns message as text; visible: false returns it as hex.
const raw = result.result?.message ?? '';
const message = /^([0-9a-f]{2})+$/i.test(raw) ? Buffer.from(raw, 'hex').toString() : raw;
const reverted = result.transaction?.ret?.[0]?.ret === 'FAILED';
if (result.result?.result !== true || message || reverted) {
throw new Error(`Transfer would fail: ${message || 'unknown reason'}`);
}
return result.energy_used;
}Best Practices
1. Error Handling
async function safeTokenTransfer(tokenAddress, fromAddress, toAddress, amount) {
try {
// 1. Validate inputs
if (!isValidTronAddress(fromAddress) || !isValidTronAddress(toAddress)) {
throw new Error('Invalid address format');
}
if (amount <= 0) {
throw new Error('Amount must be positive');
}
// 2. Check token exists
const tokenInfo = await getTokenInfo(tokenAddress);
if (!tokenInfo) {
throw new Error('Token contract not found');
}
// 3. Check balance
const balance = await getTokenBalance(tokenAddress, fromAddress);
const amountInDecimals = amount * Math.pow(10, tokenInfo.decimals);
if (parseFloat(balance) < amountInDecimals) {
throw new Error('Insufficient token balance');
}
// 4. Check energy/bandwidth
const resourceCheck = await checkResourcesForTransfer(fromAddress);
if (!resourceCheck.canExecute) {
throw new Error(`Insufficient resources: ${resourceCheck.reason}`);
}
// 5. Create transaction
const transferResult = await createTokenTransfer(
tokenAddress,
fromAddress,
toAddress,
amountInDecimals.toString()
);
return {
success: true,
transaction: transferResult,
estimatedCost: resourceCheck.estimatedCost
};
} catch (error) {
return {
success: false,
error: error.message,
code: error.code || 'UNKNOWN_ERROR'
};
}
}2. Gas Optimization
class TRC20GasOptimizer {
async optimizeTransfer(tokenAddress, fromAddress, toAddress, amount) {
// 1. Estimate energy cost
const energyEstimate = await estimateEnergyForTransfer(tokenAddress, fromAddress, amount);
// 2. Check if user has enough staked energy
const resources = await getAccountResources(fromAddress);
const availableEnergy = resources.EnergyLimit - resources.EnergyUsed;
if (availableEnergy >= energyEstimate) {
return {
strategy: 'use_staked_energy',
cost: 0,
recommendation: 'Transaction will execute for free'
};
}
// 3. Calculate different cost strategies
const strategies = [
{
name: 'Pay with TRX',
cost: energyEstimate * 0.0001, // getEnergyFee: 100 sun per energy on 3 Nov 2026
description: 'Pay TRX for energy consumption'
},
{
name: 'Stake TRX',
cost: energyEstimate / 10, // TRX locked, not spent: 9.999 energy per staked TRX per day on 3 Nov 2026
description: 'Stake TRX for energy that refills every 24 hours'
}
];
return {
energyRequired: energyEstimate,
strategies: strategies.sort((a, b) => a.cost - b.cost),
recommendation: strategies[0]
};
}
}3. Transaction Monitoring
class TRC20TransactionMonitor {
async monitorTransfer(txId, expectedAmount, expectedRecipient, tokenAddress) {
const timeout = 60000; // 1 minute
const checkInterval = 3000; // 3 seconds
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
try {
const txDetails = await getTransactionById(txId);
if (txDetails.blockNumber) {
// Transaction confirmed, verify details
const verification = await this.verifyTransfer(
txDetails,
expectedAmount,
expectedRecipient,
tokenAddress
);
return {
confirmed: true,
verified: verification.success,
details: txDetails,
verification: verification
};
}
console.log('Waiting for confirmation...');
await new Promise(resolve => setTimeout(resolve, checkInterval));
} catch (error) {
console.error('Monitoring error:', error);
}
}
return {
confirmed: false,
timeout: true,
message: 'Transaction confirmation timeout'
};
}
async verifyTransfer(txDetails, expectedAmount, expectedRecipient, tokenAddress) {
// Verify transaction called the correct contract
const contract = txDetails.raw_data?.contract?.[0];
if (contract?.parameter?.value?.contract_address !== tokenAddress) {
return { success: false, reason: 'Wrong contract called' };
}
// Decode transfer parameters (simplified)
// In production, use proper ABI decoding
const data = contract.parameter.value.data;
if (!data.startsWith('a9059cbb')) { // transfer function selector
return { success: false, reason: 'Not a transfer call' };
}
return {
success: true,
verified: true,
message: 'Transfer verified successfully'
};
}
}Security Considerations
- Validate Addresses - Always validate TRON addresses before transactions
- Check Token Contracts - Verify token contract addresses against known lists
- Amount Validation - Validate amounts and handle decimal precision correctly
- Resource Checks - Ensure sufficient energy/bandwidth before transactions
- Private Key Security - Never expose private keys in client-side code
- Transaction Limits - Implement reasonable limits for user protection
Common Pitfalls
- Decimal Precision - Always account for token decimals when calculating amounts
- Address Format - Use correct encoding for contract parameters
- Energy Estimation - Simulate with
wallet/triggerconstantcontract, checkresult.messageandtransaction.ret, and setfee_limitfor a recipient with no tokens - Approval First - Remember to approve before transferFrom operations
- Network Congestion - Handle transaction delays and failures gracefully
Need help? Contact our support team or check the TRON documentation.
wallet/votewitnessaccount
Vote for TRON Super Representatives using frozen TRX voting power via Dwellir's high-performance RPC endpoint.
wallet/getnodeinfo
Get comprehensive TRON node information including version, block height, configuration, and peer connections via Dwellir's enterprise RPC endpoint.