⚠️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

#!/bin/bash
API_KEY="YOUR_API_KEY"
BASE_URL="https://api-tron-mainnet.n.dwellir.com/$API_KEY"

# Popular TRC20 token addresses
USDT_ADDRESS="TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"
USDC_ADDRESS="TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8"
JST_ADDRESS="TCFLL5dx5ZJdKnWuesXxi1VPwjLVmWZZy9"
WTRX_ADDRESS="TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR"

# Function to encode address for contract parameters
encode_address() {
local address=$1
# Simplified encoding - use proper tools in production
echo "0000000000000000000000004142b5e01c8c59a25d78acdbec2bfc7e89e5e863"
}

# Function to encode uint256
encode_uint256() {
local value=$1
printf "%064x" "$value"
}

# Get TRC20 token balance
get_token_balance() {
local token_address=$1
local wallet_address=$2

local encoded_address=$(encode_address "$wallet_address")

curl -s -X POST "$BASE_URL/wallet/triggersmartcontract" \
-H "Content-Type: application/json" \
-d "{
\"contract_address\": \"$token_address\",
\"function_selector\": \"balanceOf(address)\",
\"parameter\": \"$encoded_address\",
\"owner_address\": \"$wallet_address\",
\"visible\": true
}" | jq -r '.constant_result[0] // "0"' | xargs printf "%d"
}

# Get token info (symbol, decimals, name)
get_token_info() {
local token_address=$1

echo "Getting token information for $token_address..."

# Get symbol
local symbol_result=$(curl -s -X POST "$BASE_URL/wallet/triggersmartcontract" \
-H "Content-Type: application/json" \
-d "{
\"contract_address\": \"$token_address\",
\"function_selector\": \"symbol()\",
\"owner_address\": \"$token_address\",
\"visible\": true
}")

# Get decimals
local decimals_result=$(curl -s -X POST "$BASE_URL/wallet/triggersmartcontract" \
-H "Content-Type: application/json" \
-d "{
\"contract_address\": \"$token_address\",
\"function_selector\": \"decimals()\",
\"owner_address\": \"$token_address\",
\"visible\": true
}")

# Get name
local name_result=$(curl -s -X POST "$BASE_URL/wallet/triggersmartcontract" \
-H "Content-Type: application/json" \
-d "{
\"contract_address\": \"$token_address\",
\"function_selector\": \"name()\",
\"owner_address\": \"$token_address\",
\"visible\": true
}")

local symbol=$(echo "$symbol_result" | jq -r '.constant_result[0] // "UNKNOWN"')
local decimals_hex=$(echo "$decimals_result" | jq -r '.constant_result[0] // "6"')
local decimals=$(printf "%d" "0x$decimals_hex" 2>/dev/null || echo "6")
local name=$(echo "$name_result" | jq -r '.constant_result[0] // "Unknown"')

echo "Symbol: $symbol"
echo "Decimals: $decimals"
echo "Name: $name"

# Export for use in other functions
export TOKEN_SYMBOL="$symbol"
export TOKEN_DECIMALS="$decimals"
export TOKEN_NAME="$name"
}

# Create TRC20 transfer transaction
create_token_transfer() {
local token_address=$1
local from_address=$2
local to_address=$3
local amount=$4 # Amount in token's smallest unit

local encoded_to=$(encode_address "$to_address")
local encoded_amount=$(encode_uint256 "$amount")
local parameters="${encoded_to}${encoded_amount}"

curl -s -X POST "$BASE_URL/wallet/triggersmartcontract" \
-H "Content-Type: application/json" \
-d "{
\"contract_address\": \"$token_address\",
\"function_selector\": \"transfer(address,uint256)\",
\"parameter\": \"$parameters\",
\"owner_address\": \"$from_address\",
\"visible\": true
}"
}

# Create TRC20 approval transaction
create_token_approval() {
local token_address=$1
local owner_address=$2
local spender_address=$3
local amount=$4

local encoded_spender=$(encode_address "$spender_address")
local encoded_amount=$(encode_uint256 "$amount")
local parameters="${encoded_spender}${encoded_amount}"

curl -s -X POST "$BASE_URL/wallet/triggersmartcontract" \
-H "Content-Type: application/json" \
-d "{
\"contract_address\": \"$token_address\",
\"function_selector\": \"approve(address,uint256)\",
\"parameter\": \"$parameters\",
\"owner_address\": \"$owner_address\",
\"visible\": true
}"
}

# Get allowance
get_allowance() {
local token_address=$1
local owner_address=$2
local spender_address=$3

local encoded_owner=$(encode_address "$owner_address")
local encoded_spender=$(encode_address "$spender_address")
local parameters="${encoded_owner}${encoded_spender}"

curl -s -X POST "$BASE_URL/wallet/triggersmartcontract" \
-H "Content-Type: application/json" \
-d "{
\"contract_address\": \"$token_address\",
\"function_selector\": \"allowance(address,address)\",
\"parameter\": \"$parameters\",
\"owner_address\": \"$owner_address\",
\"visible\": true
}" | jq -r '.constant_result[0] // "0"' | xargs printf "%d"
}

# Get formatted balance
get_formatted_balance() {
local token_address=$1
local wallet_address=$2

echo "Getting balance for $wallet_address..."

# Get token info first
get_token_info "$token_address"

# Get balance
local balance=$(get_token_balance "$token_address" "$wallet_address")

# Calculate formatted balance
local formatted_balance
if [ "$TOKEN_DECIMALS" -gt 0 ]; then
formatted_balance=$(echo "scale=6; $balance / (10^$TOKEN_DECIMALS)" | bc -l)
else
formatted_balance="$balance"
fi

echo "Raw balance: $balance"
echo "Formatted balance: $formatted_balance $TOKEN_SYMBOL"
}

# Portfolio balance checker
check_portfolio() {
local wallet_address=$1

echo "=== Portfolio Balances for $wallet_address ==="
echo ""

# USDT
echo "USDT Balance:"
get_formatted_balance "$USDT_ADDRESS" "$wallet_address"
echo ""

# USDC
echo "USDC Balance:"
get_formatted_balance "$USDC_ADDRESS" "$wallet_address"
echo ""

# JST
echo "JST Balance:"
get_formatted_balance "$JST_ADDRESS" "$wallet_address"
echo ""

# WTRX
echo "WTRX Balance:"
get_formatted_balance "$WTRX_ADDRESS" "$wallet_address"
echo ""
}

# Complete transfer workflow
transfer_usdt() {
local from_address=$1
local to_address=$2
local amount_usdt=$3

echo "=== USDT Transfer Workflow ==="
echo "From: $from_address"
echo "To: $to_address"
echo "Amount: $amount_usdt USDT"
echo ""

# 1. Get token info
echo "1. Getting USDT token info..."
get_token_info "$USDT_ADDRESS"
echo ""

# 2. Check balance
echo "2. Checking balance..."
local balance=$(get_token_balance "$USDT_ADDRESS" "$from_address")
local formatted_balance=$(echo "scale=6; $balance / (10^$TOKEN_DECIMALS)" | bc -l)
echo "Current balance: $formatted_balance USDT"

# Check if sufficient balance
if (( $(echo "$formatted_balance < $amount_usdt" | bc -l) )); then
echo "❌ Insufficient balance!"
echo "Have: $formatted_balance USDT"
echo "Need: $amount_usdt USDT"
return 1
fi

# 3. Calculate amount in smallest unit
local amount_in_decimals=$(echo "scale=0; $amount_usdt * (10^$TOKEN_DECIMALS) / 1" | bc -l)
echo "Amount in smallest unit: $amount_in_decimals"
echo ""

# 4. Create transfer transaction
echo "3. Creating transfer transaction..."
local transfer_result=$(create_token_transfer "$USDT_ADDRESS" "$from_address" "$to_address" "$amount_in_decimals")

# Check if successful
local success=$(echo "$transfer_result" | jq -r '.result.result // false')

if [ "$success" = "true" ]; then
local tx_id=$(echo "$transfer_result" | jq -r '.transaction.txID')
local energy_used=$(echo "$transfer_result" | jq -r '.energy_used // 0')

echo "✅ Transfer transaction created successfully!"
echo "Transaction ID: $tx_id"
echo "Energy required: $energy_used"
echo ""
echo "Next steps:"
echo "1. Sign the transaction with your private key"
echo "2. Broadcast the signed transaction"
echo "3. Monitor for confirmation"
else
local error_message=$(echo "$transfer_result" | jq -r '.result.message // "Unknown error"')
echo "❌ Transfer creation failed: $error_message"
return 1
fi
}

# Approval workflow
setup_approval() {
local token_address=$1
local owner_address=$2
local spender_address=$3
local amount=$4

echo "=== Token Approval Workflow ==="
echo "Token: $token_address"
echo "Owner: $owner_address"
echo "Spender: $spender_address"
echo "Amount: $amount"
echo ""

# 1. Check current allowance
echo "1. Checking current allowance..."
local current_allowance=$(get_allowance "$token_address" "$owner_address" "$spender_address")
echo "Current allowance: $current_allowance"

# 2. Create approval if needed
if [ "$current_allowance" -lt "$amount" ]; then
echo ""
echo "2. Creating approval transaction..."
local approval_result=$(create_token_approval "$token_address" "$owner_address" "$spender_address" "$amount")

local success=$(echo "$approval_result" | jq -r '.result.result // false')

if [ "$success" = "true" ]; then
local tx_id=$(echo "$approval_result" | jq -r '.transaction.txID')
echo "✅ Approval transaction created: $tx_id"
else
local error_message=$(echo "$approval_result" | jq -r '.result.message // "Unknown error"')
echo "❌ Approval creation failed: $error_message"
return 1
fi
else
echo "✅ Sufficient allowance already exists"
fi
}

# Main script usage
case "${1:-help}" in
"balance")
if [ -z "$2" ] || [ -z "$3" ]; then
echo "Usage: $0 balance TOKEN_ADDRESS WALLET_ADDRESS"
exit 1
fi
get_formatted_balance "$2" "$3"
;;
"portfolio")
if [ -z "$2" ]; then
echo "Usage: $0 portfolio WALLET_ADDRESS"
exit 1
fi
check_portfolio "$2"
;;
"transfer")
if [ -z "$4" ]; then
echo "Usage: $0 transfer FROM_ADDRESS TO_ADDRESS AMOUNT"
exit 1
fi
transfer_usdt "$2" "$3" "$4"
;;
"approve")
if [ -z "$5" ]; then
echo "Usage: $0 approve TOKEN_ADDRESS OWNER_ADDRESS SPENDER_ADDRESS AMOUNT"
exit 1
fi
setup_approval "$2" "$3" "$4" "$5"
;;
"info")
if [ -z "$2" ]; then
echo "Usage: $0 info TOKEN_ADDRESS"
exit 1
fi
get_token_info "$2"
;;
*)
echo "TRON TRC20 Token Manager"
echo ""
echo "Usage: $0 {balance|portfolio|transfer|approve|info} [args...]"
echo ""
echo "Commands:"
echo " balance TOKEN_ADDRESS WALLET_ADDRESS - Get token balance"
echo " portfolio WALLET_ADDRESS - Get portfolio balances"
echo " transfer FROM_ADDRESS TO_ADDRESS AMOUNT - Transfer USDT"
echo " approve TOKEN_ADDRESS OWNER SPENDER AMOUNT - Setup token approval"
echo " info TOKEN_ADDRESS - Get token information"
echo ""
echo "Examples:"
echo " $0 balance $USDT_ADDRESS TRX6Q82wMqWNbCCmJPLz9mR8AZwqvUU2pN"
echo " $0 portfolio TRX6Q82wMqWNbCCmJPLz9mR8AZwqvUU2pN"
echo " $0 transfer TJmmqjb1DK9TTZbQXzRQ2AuA94z4gKAPFh TRX6Q82wMqWNbCCmJPLz9mR8AZwqvUU2pN 10"
echo " $0 info $USDT_ADDRESS"
;;
esac

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.