Docs

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.

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

Bash
#!/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
JavaScript
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);
  }
})();
Python
import requests
import json
from typing import Dict, List, Optional, Tuple

class TRC20TokenManager:
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = f"https://api-tron-mainnet.n.dwellir.com/{api_key}"
        self.headers = {
            'Content-Type': 'application/json'
        }
        
        # Popular TRC20 contracts
        self.tokens = {
            'USDT': 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
            'USDC': 'TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8',
            'JST': 'TCFLL5dx5ZJdKnWuesXxi1VPwjLVmWZZy9',
            'WTRX': 'TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR'
        }
    
    def get_balance(self, token_address: str, wallet_address: str) -> str:
        """Get token balance for address"""
        try:
            encoded_address = self._encode_address(wallet_address)
            
            response = requests.post(
                f"{self.base_url}/wallet/triggersmartcontract",
                headers=self.headers,
                json={
                    "contract_address": token_address,
                    "function_selector": "balanceOf(address)",
                    "parameter": encoded_address,
                    "owner_address": wallet_address,
                    "visible": True
                }
            )
            
            result = response.json()
            
            if result.get('result', {}).get('result') and result.get('constant_result'):
                balance_hex = result['constant_result'][0]
                return str(int(balance_hex, 16))
            
            return '0'
        except Exception as e:
            print(f"Error getting balance: {e}")
            raise
    
    def get_token_info(self, token_address: str) -> Dict:
        """Get token information (decimals, symbol, name)"""
        try:
            info = {}
            
            # Get decimals
            decimals_result = self._call_view_function(token_address, 'decimals()')
            info['decimals'] = int(decimals_result, 16) if decimals_result else 6
            
            # Get symbol
            symbol_result = self._call_view_function(token_address, 'symbol()')
            info['symbol'] = self._parse_string(symbol_result) or 'UNKNOWN'
            
            # Get name
            name_result = self._call_view_function(token_address, 'name()')
            info['name'] = self._parse_string(name_result) or 'Unknown Token'
            
            # Get total supply
            supply_result = self._call_view_function(token_address, 'totalSupply()')
            info['totalSupply'] = str(int(supply_result, 16)) if supply_result else '0'
            
            info['address'] = token_address
            
            return info
        except Exception as e:
            print(f"Error getting token info: {e}")
            raise
    
    def create_transfer(self, token_address: str, from_address: str, 
                       to_address: str, amount: str) -> Dict:
        """Create transfer transaction"""
        try:
            encoded_to = self._encode_address(to_address)
            encoded_amount = self._encode_uint256(amount)
            parameters = encoded_to + encoded_amount
            
            response = requests.post(
                f"{self.base_url}/wallet/triggersmartcontract",
                headers=self.headers,
                json={
                    "contract_address": token_address,
                    "function_selector": "transfer(address,uint256)",
                    "parameter": parameters,
                    "owner_address": from_address,
                    "visible": True
                }
            )
            
            result = response.json()
            
            if result.get('result', {}).get('result'):
                return {
                    'success': True,
                    'transaction': result['transaction'],
                    'energy_used': result.get('energy_used', 0),
                    'tx_id': result['transaction']['txID']
                }
            else:
                raise Exception(result.get('result', {}).get('message', 'Transfer creation failed'))
        except Exception as e:
            print(f"Error creating transfer: {e}")
            raise
    
    def create_approval(self, token_address: str, owner_address: str,
                       spender_address: str, amount: str) -> Dict:
        """Create approval transaction"""
        try:
            encoded_spender = self._encode_address(spender_address)
            encoded_amount = self._encode_uint256(amount)
            parameters = encoded_spender + encoded_amount
            
            response = requests.post(
                f"{self.base_url}/wallet/triggersmartcontract",
                headers=self.headers,
                json={
                    "contract_address": token_address,
                    "function_selector": "approve(address,uint256)",
                    "parameter": parameters,
                    "owner_address": owner_address,
                    "visible": True
                }
            )
            
            result = response.json()
            
            if result.get('result', {}).get('result'):
                return {
                    'success': True,
                    'transaction': result['transaction'],
                    'energy_used': result.get('energy_used', 0),
                    'tx_id': result['transaction']['txID']
                }
            else:
                raise Exception(result.get('result', {}).get('message', 'Approval creation failed'))
        except Exception as e:
            print(f"Error creating approval: {e}")
            raise
    
    def get_allowance(self, token_address: str, owner_address: str, spender_address: str) -> str:
        """Get allowance amount"""
        try:
            encoded_owner = self._encode_address(owner_address)
            encoded_spender = self._encode_address(spender_address)
            parameters = encoded_owner + encoded_spender
            
            response = requests.post(
                f"{self.base_url}/wallet/triggersmartcontract",
                headers=self.headers,
                json={
                    "contract_address": token_address,
                    "function_selector": "allowance(address,address)",
                    "parameter": parameters,
                    "owner_address": owner_address,
                    "visible": True
                }
            )
            
            result = response.json()
            
            if result.get('result', {}).get('result') and result.get('constant_result'):
                allowance_hex = result['constant_result'][0]
                return str(int(allowance_hex, 16))
            
            return '0'
        except Exception as e:
            print(f"Error getting allowance: {e}")
            raise
    
    def get_formatted_balance(self, token_address: str, wallet_address: str) -> Dict:
        """Get formatted balance with token info"""
        try:
            balance = self.get_balance(token_address, wallet_address)
            token_info = self.get_token_info(token_address)
            
            formatted_balance = float(balance) / (10 ** token_info['decimals'])
            
            return {
                'raw': balance,
                'formatted': formatted_balance,
                'decimals': token_info['decimals'],
                'symbol': token_info['symbol'],
                'name': token_info['name'],
                'display_value': f"{formatted_balance:.{token_info['decimals']}f} {token_info['symbol']}"
            }
        except Exception as e:
            print(f"Error getting formatted balance: {e}")
            raise
    
    def get_portfolio_balances(self, wallet_address: str, token_list: List[Tuple[str, str]] = None) -> Dict:
        """Get balances for multiple tokens"""
        if token_list is None:
            token_list = list(self.tokens.items())
        
        balances = {}
        
        for symbol, address in token_list:
            try:
                balance_info = self.get_formatted_balance(address, wallet_address)
                balances[symbol] = balance_info
            except Exception as e:
                balances[symbol] = {'error': str(e)}
        
        return balances
    
    def _call_view_function(self, contract_address: str, function_selector: str, parameters: str = '') -> Optional[str]:
        """Call view function on contract"""
        response = requests.post(
            f"{self.base_url}/wallet/triggersmartcontract",
            headers=self.headers,
            json={
                "contract_address": contract_address,
                "function_selector": function_selector,
                "parameter": parameters,
                "owner_address": contract_address,
                "visible": True
            }
        )
        
        result = response.json()
        
        if result.get('result', {}).get('result') and result.get('constant_result'):
            return result['constant_result'][0]
        
        return None
    
    def _encode_address(self, address: str) -> str:
        """Encode TRON address for contract parameters"""
        # Simplified encoding - use proper library in production
        return address[1:].lower().zfill(64)
    
    def _encode_uint256(self, value: str) -> str:
        """Encode uint256 for contract parameters"""
        return format(int(value), '064x')
    
    def _parse_string(self, hex_result: str) -> Optional[str]:
        """Parse string from hex result"""
        if not hex_result:
            return None
        
        try:
            # Simple string parsing - use proper ABI decoding in production
            bytes_data = bytes.fromhex(hex_result)
            return bytes_data.decode('utf-8').rstrip('\x00')
        except:
            return None

class TRC20TransferExample:
    def __init__(self, api_key: str):
        self.token_manager = TRC20TokenManager(api_key)
    
    def transfer_usdt(self, from_address: str, to_address: str, amount: float, private_key: str) -> Dict:
        """Complete USDT transfer example"""
        try:
            usdt_address = self.token_manager.tokens['USDT']
            
            print('1. Checking USDT balance...')
            balance_info = self.token_manager.get_formatted_balance(usdt_address, from_address)
            print(f"Balance: {balance_info['display_value']}")
            
            if balance_info['formatted'] < amount:
                raise Exception(f"Insufficient balance. Have: {balance_info['formatted']}, Need: {amount}")
            
            print('2. Creating transfer transaction...')
            amount_in_decimals = int(amount * (10 ** balance_info['decimals']))
            transfer_result = self.token_manager.create_transfer(
                usdt_address,
                from_address,
                to_address,
                str(amount_in_decimals)
            )
            
            print(f"3. Transaction created: {transfer_result['tx_id']}")
            print(f"Energy required: {transfer_result['energy_used']}")
            
            # 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,
                'tx_id': transfer_result['tx_id'],
                'transaction': transfer_result['transaction'],
                'amount': amount,
                'from': from_address,
                'to': to_address,
                'token': 'USDT'
            }
            
        except Exception as e:
            print(f"USDT transfer failed: {e}")
            raise
    
    def setup_allowance_and_transfer(self, token_address: str, owner_address: str,
                                   spender_address: str, amount: str, private_key: str) -> Dict:
        """Setup allowance and create transferFrom transaction"""
        try:
            print('1. Checking current allowance...')
            current_allowance = self.token_manager.get_allowance(
                token_address,
                owner_address,
                spender_address
            )
            
            approval_result = None
            if int(current_allowance) < int(amount):
                print('2. Setting up allowance...')
                approval_result = self.token_manager.create_approval(
                    token_address,
                    owner_address,
                    spender_address,
                    amount
                )
                print(f"Approval transaction: {approval_result['tx_id']}")
                # Sign and broadcast approval transaction
            
            print('3. Creating transferFrom transaction...')
            # Create transferFrom transaction parameters
            encoded_from = self.token_manager._encode_address(owner_address)
            encoded_to = self.token_manager._encode_address('TDestinationAddress...')
            encoded_amount = self.token_manager._encode_uint256(amount)
            parameters = encoded_from + encoded_to + encoded_amount
            
            response = requests.post(
                f"{self.token_manager.base_url}/wallet/triggersmartcontract",
                headers=self.token_manager.headers,
                json={
                    "contract_address": token_address,
                    "function_selector": "transferFrom(address,address,uint256)",
                    "parameter": parameters,
                    "owner_address": spender_address,
                    "visible": True
                }
            )
            
            transfer_result = response.json()
            print(f"Transfer transaction: {transfer_result['transaction']['txID']}")
            
            return {
                'success': True,
                'approval': approval_result['tx_id'] if approval_result else None,
                'transfer': transfer_result['transaction']['txID']
            }
            
        except Exception as e:
            print(f"Allowance transfer failed: {e}")
            raise

# Usage examples
if __name__ == "__main__":
    token_manager = TRC20TokenManager('YOUR_API_KEY')
    
    try:
        # Check token balance
        usdt_balance = token_manager.get_formatted_balance(
            token_manager.tokens['USDT'],
            'TRX6Q82wMqWNbCCmJPLz9mR8AZwqvUU2pN'
        )
        print(f"USDT Balance: {usdt_balance['display_value']}")
        
        # Get portfolio
        portfolio = token_manager.get_portfolio_balances('TRX6Q82wMqWNbCCmJPLz9mR8AZwqvUU2pN')
        print("Portfolio:")
        for token, info in portfolio.items():
            if 'error' in info:
                print(f"  {token}: Error - {info['error']}")
            else:
                print(f"  {token}: {info['display_value']}")
        
        # Transfer example
        transfer_example = TRC20TransferExample('YOUR_API_KEY')
        transfer_result = transfer_example.transfer_usdt(
            'TJmmqjb1DK9TTZbQXzRQ2AuA94z4gKAPFh',
            'TRX6Q82wMqWNbCCmJPLz9mR8AZwqvUU2pN',
            10.0,  # 10 USDT
            'private_key_here'
        )
        print(f"Transfer result: {transfer_result}")
        
    except Exception as e:
        print(f"Error: {e}")

Best Practices

1. Error Handling

JavaScript
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

JavaScript
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

JavaScript
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.