⚠️Blast API (blastapi.io) ends Oct 31. Migrate to Dwellir and skip Alchemy's expensive compute units.
Switch Today →
Skip to main content

TRC20 Token Transfers - Complete Developer Guide

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.

Start building with TRC20 tokens →

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 balance
  • transfer(address,uint256) - Transfer tokens
  • approve(address,uint256) - Approve spending
  • transferFrom(address,address,uint256) - Transfer on behalf
  • allowance(address,address) - Check approved amount

Implementation Examples

const TRON_API = 'https://api-tron-mainnet.n.dwellir.com/YOUR_API_KEY';

// TRC20 Token Manager
class TRC20TokenManager {
constructor(apiKey) {
this.apiKey = apiKey;
this.apiUrl = `https://api-tron-mainnet.n.dwellir.com/${apiKey}`;

// Popular TRC20 contracts
this.tokens = {
USDT: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
USDC: 'TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8',
JST: 'TCFLL5dx5ZJdKnWuesXxi1VPwjLVmWZZy9',
WTRX: 'TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR'
};
}

// Get token balance
async getBalance(tokenAddress, walletAddress) {
try {
const encodedAddress = this.encodeAddress(walletAddress);

const response = await fetch(`${this.apiUrl}/wallet/triggersmartcontract`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contract_address: tokenAddress,
function_selector: 'balanceOf(address)',
parameter: encodedAddress,
owner_address: walletAddress,
visible: true
})
});

const result = await response.json();

if (result.result?.result && result.constant_result?.[0]) {
const balanceHex = result.constant_result[0];
return BigInt('0x' + balanceHex).toString();
}

return '0';
} catch (error) {
console.error('Error getting balance:', error);
throw error;
}
}

// Get token info (decimals, symbol, name)
async getTokenInfo(tokenAddress) {
try {
const [decimals, symbol, name, totalSupply] = await Promise.all([
this.callViewFunction(tokenAddress, 'decimals()'),
this.callViewFunction(tokenAddress, 'symbol()'),
this.callViewFunction(tokenAddress, 'name()'),
this.callViewFunction(tokenAddress, 'totalSupply()')
]);

return {
decimals: parseInt(decimals || '6'),
symbol: this.parseString(symbol) || 'UNKNOWN',
name: this.parseString(name) || 'Unknown Token',
totalSupply: totalSupply || '0',
address: tokenAddress
};
} catch (error) {
console.error('Error getting token info:', error);
throw error;
}
}

// Create transfer transaction
async createTransfer(tokenAddress, fromAddress, toAddress, amount) {
try {
const encodedTo = this.encodeAddress(toAddress);
const encodedAmount = this.encodeUint256(amount);
const parameters = encodedTo + encodedAmount;

const response = await fetch(`${this.apiUrl}/wallet/triggersmartcontract`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contract_address: tokenAddress,
function_selector: 'transfer(address,uint256)',
parameter: parameters,
owner_address: fromAddress,
visible: true
})
});

const result = await response.json();

if (result.result?.result) {
return {
success: true,
transaction: result.transaction,
energyUsed: result.energy_used || 0,
txId: result.transaction?.txID
};
} else {
throw new Error(result.result?.message || 'Transfer creation failed');
}
} catch (error) {
console.error('Error creating transfer:', error);
throw error;
}
}

// Create approval transaction
async createApproval(tokenAddress, ownerAddress, spenderAddress, amount) {
try {
const encodedSpender = this.encodeAddress(spenderAddress);
const encodedAmount = this.encodeUint256(amount);
const parameters = encodedSpender + encodedAmount;

const response = await fetch(`${this.apiUrl}/wallet/triggersmartcontract`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contract_address: tokenAddress,
function_selector: 'approve(address,uint256)',
parameter: parameters,
owner_address: ownerAddress,
visible: true
})
});

const result = await response.json();

if (result.result?.result) {
return {
success: true,
transaction: result.transaction,
energyUsed: result.energy_used || 0,
txId: result.transaction?.txID
};
} else {
throw new Error(result.result?.message || 'Approval creation failed');
}
} catch (error) {
console.error('Error creating approval:', error);
throw error;
}
}

// Get allowance
async getAllowance(tokenAddress, ownerAddress, spenderAddress) {
try {
const encodedOwner = this.encodeAddress(ownerAddress);
const encodedSpender = this.encodeAddress(spenderAddress);
const parameters = encodedOwner + encodedSpender;

const response = await fetch(`${this.apiUrl}/wallet/triggersmartcontract`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contract_address: tokenAddress,
function_selector: 'allowance(address,address)',
parameter: parameters,
owner_address: ownerAddress,
visible: true
})
});

const result = await response.json();

if (result.result?.result && result.constant_result?.[0]) {
const allowanceHex = result.constant_result[0];
return BigInt('0x' + allowanceHex).toString();
}

return '0';
} catch (error) {
console.error('Error getting allowance:', error);
throw error;
}
}

// Create transferFrom transaction (spend allowance)
async createTransferFrom(tokenAddress, spenderAddress, fromAddress, toAddress, amount) {
try {
const encodedFrom = this.encodeAddress(fromAddress);
const encodedTo = this.encodeAddress(toAddress);
const encodedAmount = this.encodeUint256(amount);
const parameters = encodedFrom + encodedTo + encodedAmount;

const response = await fetch(`${this.apiUrl}/wallet/triggersmartcontract`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contract_address: tokenAddress,
function_selector: 'transferFrom(address,address,uint256)',
parameter: parameters,
owner_address: spenderAddress,
visible: true
})
});

const result = await response.json();

if (result.result?.result) {
return {
success: true,
transaction: result.transaction,
energyUsed: result.energy_used || 0,
txId: result.transaction?.txID
};
} else {
throw new Error(result.result?.message || 'TransferFrom creation failed');
}
} catch (error) {
console.error('Error creating transferFrom:', error);
throw error;
}
}

// Get formatted balance with token info
async getFormattedBalance(tokenAddress, walletAddress) {
try {
const [balance, tokenInfo] = await Promise.all([
this.getBalance(tokenAddress, walletAddress),
this.getTokenInfo(tokenAddress)
]);

const formattedBalance = parseFloat(balance) / Math.pow(10, tokenInfo.decimals);

return {
raw: balance,
formatted: formattedBalance,
decimals: tokenInfo.decimals,
symbol: tokenInfo.symbol,
name: tokenInfo.name,
displayValue: `${formattedBalance.toFixed(tokenInfo.decimals)} ${tokenInfo.symbol}`
};
} catch (error) {
console.error('Error getting formatted balance:', error);
throw error;
}
}

// Portfolio balance checker
async getPortfolioBalances(walletAddress, tokenList = null) {
const tokens = tokenList || Object.entries(this.tokens);
const balances = {};

for (const [symbol, address] of tokens) {
try {
const balanceInfo = await this.getFormattedBalance(address, walletAddress);
balances[symbol] = balanceInfo;
} catch (error) {
balances[symbol] = { error: error.message };
}
}

return balances;
}

// Utility functions
async callViewFunction(contractAddress, functionSelector, parameters = '') {
const response = await fetch(`${this.apiUrl}/wallet/triggersmartcontract`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contract_address: contractAddress,
function_selector: functionSelector,
parameter: parameters,
owner_address: contractAddress,
visible: true
})
});

const result = await response.json();

if (result.result?.result && result.constant_result?.[0]) {
return result.constant_result[0];
}

return null;
}

encodeAddress(address) {
// Convert TRON Base58 address to hex parameter
// This is a simplified version - use proper encoding in production
return address.slice(1).toLowerCase().padStart(64, '0');
}

encodeUint256(value) {
return BigInt(value).toString(16).padStart(64, '0');
}

parseString(hexResult) {
if (!hexResult) return null;
try {
// Simple string parsing - use proper ABI decoding in production
const bytes = hexResult.match(/.{2}/g) || [];
return bytes.map(byte => String.fromCharCode(parseInt(byte, 16))).join('').replace(/\0/g, '');
} catch {
return null;
}
}
}

// Complete TRC20 Transfer Example
class TRC20TransferExample {
constructor(apiKey) {
this.tokenManager = new TRC20TokenManager(apiKey);
}

async transferUSDT(fromAddress, toAddress, amount, privateKey) {
try {
const usdtAddress = this.tokenManager.tokens.USDT;

console.log('1. Checking USDT balance...');
const balanceInfo = await this.tokenManager.getFormattedBalance(usdtAddress, fromAddress);
console.log(`Balance: ${balanceInfo.displayValue}`);

if (parseFloat(balanceInfo.formatted) < amount) {
throw new Error(`Insufficient balance. Have: ${balanceInfo.formatted}, Need: ${amount}`);
}

console.log('2. Creating transfer transaction...');
const amountInDecimals = amount * Math.pow(10, balanceInfo.decimals);
const transferResult = await this.tokenManager.createTransfer(
usdtAddress,
fromAddress,
toAddress,
amountInDecimals.toString()
);

console.log(`3. Transaction created: ${transferResult.txId}`);
console.log(`Energy required: ${transferResult.energyUsed}`);

// In a real application, you would:
// 4. Sign the transaction with the private key
// 5. Broadcast the signed transaction
// 6. Monitor for confirmation

return {
success: true,
txId: transferResult.txId,
transaction: transferResult.transaction,
amount: amount,
from: fromAddress,
to: toAddress,
token: 'USDT'
};

} catch (error) {
console.error('USDT transfer failed:', error);
throw error;
}
}

async setupAllowanceAndTransfer(tokenAddress, ownerAddress, spenderAddress, amount, privateKey) {
try {
console.log('1. Checking current allowance...');
const currentAllowance = await this.tokenManager.getAllowance(
tokenAddress,
ownerAddress,
spenderAddress
);

if (BigInt(currentAllowance) < BigInt(amount)) {
console.log('2. Setting up allowance...');
const approvalResult = await this.tokenManager.createApproval(
tokenAddress,
ownerAddress,
spenderAddress,
amount
);

console.log(`Approval transaction: ${approvalResult.txId}`);
// Sign and broadcast approval transaction
}

console.log('3. Creating transferFrom transaction...');
const transferResult = await this.tokenManager.createTransferFrom(
tokenAddress,
spenderAddress,
ownerAddress,
'TDestinationAddress...',
amount
);

console.log(`Transfer transaction: ${transferResult.txId}`);

return {
success: true,
approval: approvalResult?.txId,
transfer: transferResult.txId
};

} catch (error) {
console.error('Allowance transfer failed:', error);
throw error;
}
}
}

// Usage examples
(async () => {
try {
const tokenManager = new TRC20TokenManager('YOUR_API_KEY');

// Check token balance
const usdtBalance = await tokenManager.getFormattedBalance(
tokenManager.tokens.USDT,
'TRX6Q82wMqWNbCCmJPLz9mR8AZwqvUU2pN'
);
console.log('USDT Balance:', usdtBalance.displayValue);

// Get portfolio
const portfolio = await tokenManager.getPortfolioBalances('TRX6Q82wMqWNbCCmJPLz9mR8AZwqvUU2pN');
console.log('Portfolio:', portfolio);

// Transfer example
const transferExample = new TRC20TransferExample('YOUR_API_KEY');
const transferResult = await transferExample.transferUSDT(
'TJmmqjb1DK9TTZbQXzRQ2AuA94z4gKAPFh',
'TRX6Q82wMqWNbCCmJPLz9mR8AZwqvUU2pN',
10, // 10 USDT
'private_key_here'
);
console.log('Transfer result:', transferResult);

} catch (error) {
console.error('Error:', error);
}
})();

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.00014,
description: 'Pay TRX for energy consumption'
},
{
name: 'Rent energy',
cost: energyEstimate * 0.0001,
description: 'Rent energy from third-party (30% cheaper)'
},
{
name: 'Stake TRX',
cost: energyEstimate / 4000,
description: 'Stake TRX for reusable energy (best for frequent use)'
}
];

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

  1. Validate Addresses - Always validate TRON addresses before transactions
  2. Check Token Contracts - Verify token contract addresses against known lists
  3. Amount Validation - Validate amounts and handle decimal precision correctly
  4. Resource Checks - Ensure sufficient energy/bandwidth before transactions
  5. Private Key Security - Never expose private keys in client-side code
  6. Transaction Limits - Implement reasonable limits for user protection

Common Pitfalls

  1. Decimal Precision - Always account for token decimals when calculating amounts
  2. Address Format - Use correct encoding for contract parameters
  3. Gas Estimation - Factor in energy costs for smart contract calls
  4. Approval First - Remember to approve before transferFrom operations
  5. Network Congestion - Handle transaction delays and failures gracefully

Need help? Contact our support team or check the TRON documentation.