Tron RPC: A Developer's Guide to TRC-20 Transfers, Smart Contracts, and Node Setup

By John Sullivan · September 15, 2026 · 9 min read · #tron #rpc #tron rpc #rpc endpoint #developer guide

Tron is one of the most-used blockchains in the world for stablecoin transfers. More USDT moves on Tron than on any other chain — not because it's the most technically sophisticated, but because it's cheap, fast, and supported by every exchange. If you're building payment infrastructure, a wallet, or anything that moves USDT at scale, you need a Tron RPC endpoint.

Tron's RPC interface is different from both EVM chains and Solana. It uses a mix of JSON-RPC (for the fullnode API) and REST-style HTTP endpoints (for the wallet and transaction APIs). The native account model uses base58 addresses (starting with T), not hex. And instead of gas, Tron uses a dual-resource system: bandwidth and energy.

This is the practical reference for connecting to Tron via RPC. The endpoints you'll use, how to send TRC-20 transfers, how energy and bandwidth work, and how to pick a node that handles real traffic.

The essentials

Tron mainnet has:

  • Chain ID: Not applicable (Tron doesn't use EVM chain IDs)
  • Block time: ~3 seconds
  • Fee model: Bandwidth (for transfers) + Energy (for smart contracts), not gas
  • Consensus: Delegated Proof-of-Stake (27 Super Representatives)
  • Address format: Base58, starting with T (34 characters)
  • Native token: TRX

Tron's fullnode exposes three API surfaces:

  1. gRPC (port 50051) — the native protocol, used by TronWeb and most SDKs
  2. HTTP JSON-RPC (port 8090) — the fullnode API, closest to traditional RPC
  3. HTTP REST (port 8091) — the wallet API for transaction building

Most developers interact with Tron through the HTTP JSON API on port 8090, which is what we'll focus on here.

Connecting: the setup

curl (the universal fallback)

# Get current block
curl -s -X POST https://rpc.swiftnodes.io/rpc/trx?key=YOUR_API_KEY \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'

# Get TRX balance (address in hex format)
curl -s -X POST https://rpc.swiftnodes.io/rpc/trx?key=YOUR_API_KEY \
  -H "Content-Type: application/json" \
  -d '{
    "visible": true,
    "address": "TYDzsYUEpvnYmQk4zGP9sWWcTEd2MiAtW6"
  }'

# Get account resources (bandwidth + energy)
curl -s -X POST https://rpc.swiftnodes.io/rpc/trx?key=YOUR_API_KEY \
  -H "Content-Type: application/json" \
  -d '{
    "visible": true,
    "address": "TYDzsYUEpvnYmQk4zGP9sWWcTEd2MiAtW6"
  }'

TronWeb (the standard SDK)

import TronWeb from 'tronweb';

const tronWeb = new TronWeb({
  fullHost: 'https://rpc.swiftnodes.io/rpc/trx?key=YOUR_API_KEY',
  // Or use separate headers/keys:
  // fullHost: 'https://rpc.swiftnodes.io/rpc/trx',
  // headers: { 'TRON-PRO-API-KEY': 'YOUR_API_KEY' },
});

// Get balance (in SUN — 1 TRX = 1,000,000 SUN)
const balance = await tronWeb.trx.getBalance('TYDzsYUEpvnYmQk4zGP9sWWcTEd2MiAtW6');
console.log(`Balance: ${balance / 1e6} TRX`);

// Get current block
const block = await tronWeb.trx.getCurrentBlock();
console.log(`Block number: ${block.block_header.raw_data.number}`);

// Get account resources
const resources = await tronWeb.trx.getAccountResources('TYDzsYUEpvnYmQk4zGP9sWWcTEd2MiAtW6');
console.log(`Bandwidth: ${resources.freeNetLimit - (resources.freeNetUsed || 0)} free`);
console.log(`Energy: ${(resources.EnergyLimit || 0) - (resources.EnergyUsed || 0)} available`);

tronweb for TRC-20 tokens

// USDT contract on Tron
const USDT_CONTRACT = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t';

const contract = await tronWeb.contract().at(USDT_CONTRACT);

// Get USDT balance
const balance = await contract.methods.balanceOf('TYDzsYUEpvnYmQk4zGP9sWWcTEd2MiAtW6').call();
console.log(`USDT balance: ${balance / 1e6}`); // USDT has 6 decimals on Tron

// Get token info
const name = await contract.methods.name().call();
const symbol = await contract.methods.symbol().call();
const decimals = await contract.methods.decimals().call();
console.log(`${name} (${symbol}), ${decimals} decimals`);

The methods you'll actually use

Tron's fullnode API has dozens of endpoints. Here are the ones that cover 95% of real-world usage:

Method What it does When you need it
getnowblock Get the latest block Health checks, sync monitoring
getaccount Get account info (balance, voting) Wallet UIs, balance checks
getaccountresource Get bandwidth + energy Fee estimation
getblockbynum Get a specific block Block explorers, analytics
broadcasttransaction Submit a signed transaction Sending transactions
gettransactionbyid Get transaction by ID Confirming transactions
gettransactioninfobyid Get transaction receipt/fees Fee analysis, status checks
triggerconstantcontract Read-only contract call Reading contract state
triggersmartcontract Write contract call (unsigned) Sending contract transactions
getblockbylimitnext Get a range of blocks Indexing, analytics

The two methods that cause the most trouble

triggerconstantcontract is how you read smart contract state on Tron — equivalent to eth_call on Ethereum. But unlike Ethereum, Tron requires you to specify the function selector and parameter encoding manually. There's no automatic ABI parsing:

// Read a TRC-20 balance using triggerconstantcontract
const result = await tronWeb.transactionBuilder.triggerConstantContract(
  'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', // USDT contract
  'balanceOf(address)',                       // function signature
  {},                                          // options
  '7252e73c' +                                // function selector (first 4 bytes of keccak256)
  '000000000000000000000000' +                // padding
  tronWeb.address.toHex('TYDzsYUEpvnYmQk4zGP9sWWcTEd2MiAtW6').slice(2), // address param
  'TYDzsYUEpvnYmQk4zGP9sWWcTEd2MiAtW6'       // caller address
);

// Decode the result
const balance = BigInt('0x' + result.constant_result[0]);
console.log(`USDT balance: ${Number(balance) / 1e6}`);

The function selector (7252e73c for balanceOf(address)) must be computed from the function signature. If you get this wrong, the call returns garbage or fails silently.

broadcasttransaction is how you submit signed transactions. The most common mistake is not checking whether the account has enough bandwidth or energy before broadcasting. Unlike Ethereum where you just need ETH for gas, Tron requires specific resources:

  • Bandwidth for TRX transfers and basic operations (~300 bandwidth per transfer)
  • Energy for smart contract interactions (~15,000-65,000 energy per TRC-20 transfer)

If you don't have enough resources, the transaction fails but you still pay a fee. Always check resources before broadcasting.

Bandwidth and energy

Tron's fee model is its most unique feature. Instead of a single "gas" token, Tron uses two resources:

Resource Used for How to get it
Bandwidth TRX transfers, voting, basic operations Free daily allowance (~600 per account)
Energy Smart contract calls, TRC-20 transfers Staking TRX, or burning TRX at broadcast

How much does a TRC-20 transfer cost?

A USDT transfer on Tron consumes approximately 65,000 energy. If you don't have staked energy, this is converted to a TRX burn:

  • With energy staked: 0 TRX fee
  • Without energy: 14-27 TRX ($2-4 at current prices)

This is why large Tron users stake TRX for energy — a single TRC-20 transfer without energy costs more than the transfer amount for small values.

// Check if an account has enough energy for a TRC-20 transfer
const resources = await tronWeb.trx.getAccountResources(address);
const availableEnergy = (resources.EnergyLimit || 0) - (resources.EnergyUsed || 0);
const estimatedEnergy = 65000; // approximate for TRC-20 transfer

if (availableEnergy < estimatedEnergy) {
  const deficit = estimatedEnergy - availableEnergy;
  // Energy price is ~420 SUN per energy unit (changes with network)
  const trxCost = (deficit * 420) / 1e6;
  console.log(`Need ~${trxCost.toFixed(2)} TRX for energy, or stake TRX`);
}

Staking for energy

// Stake TRX for energy (delegated to your account)
const stakeTx = await tronWeb.transactionBuilder.freezeBalanceV2(
  tronWeb.toSun(1000), // 1000 TRX
  'ENERGY',
  address
);
const signed = await tronWeb.trx.sign(stakeTx, privateKey);
await tronWeb.trx.sendRawTransaction(signed);

Staking 1,000 TRX gives you approximately 27,000 energy — enough for about 1 TRC-20 transfer per day without burning TRX. The exact amount varies with the network's energy utilization rate.

Sending TRC-20 tokens

The full flow for sending USDT on Tron:

async function sendUSDT(fromAddress, toAddress, amount, privateKey) {
  const tronWeb = new TronWeb({
    fullHost: 'https://rpc.swiftnodes.io/rpc/trx?key=YOUR_API_KEY',
    privateKey,
  });

  const USDT_CONTRACT = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t';
  const amountSun = amount * 1e6; // USDT has 6 decimals

  // 1. Check balance
  const contract = await tronWeb.contract().at(USDT_CONTRACT);
  const balance = await contract.methods.balanceOf(fromAddress).call();
  if (balance < amountSun) throw new Error('Insufficient USDT balance');

  // 2. Check resources
  const resources = await tronWeb.trx.getAccountResources(fromAddress);
  const availableEnergy = (resources.EnergyLimit || 0) - (resources.EnergyUsed || 0);

  // 3. Build the transaction
  const tx = await tronWeb.transactionBuilder.triggerSmartContract(
    USDT_CONTRACT,
    'transfer(address,uint256)',
    { feeLimit: 100_000_000 }, // 100 TRX fee limit
    [
      { type: 'address', value: toAddress },
      { type: 'uint256', value: amountSun },
    ],
    fromAddress
  );

  // 4. Sign and broadcast
  const signed = await tronWeb.trx.sign(tx.transaction, privateKey);
  const result = await tronWeb.trx.sendRawTransaction(signed);

  if (result.result) {
    console.log(`Transaction sent: ${tx.transaction.txID}`);
    console.log(`Energy cost: ${availableEnergy < 65000 ? '~14-27 TRX' : 'covered by staked energy'}`);
  } else {
    console.error('Transaction failed:', result);
  }

  return tx.transaction.txID;
}

Address formats

Tron uses base58 addresses (starting with T), but internally and in smart contracts, addresses are stored as hex (starting with 41). You'll need to convert between them:

// Base58 to hex
const hex = tronWeb.address.toHex('TYDzsYUEpvnYmQk4zGP9sWWcTEd2MiAtW6');
// -> 4138...

// Hex to base58
const base58 = tronWeb.address.fromHex('4138...');
// -> TYDzsYUEpvnYmQk4zGP9sWWcTEd2MiAtW6

// Validate an address
const isValid = tronWeb.isAddress('TYDzsYUEpvnYmQk4zGP9sWWcTEd2MiAtW6');

The hex address always starts with 41 (Tron's chain prefix). If you're reading raw contract data or event logs, you'll see hex addresses. If you're displaying to users, convert to base58.

Event listening

Tron supports event subscriptions through the event API. Unlike Ethereum's WebSocket subscriptions, Tron uses a polling-based event service:

// Get events for a contract
const events = await tronWeb.event.getEventsByContractAddress(
  'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', // USDT contract
  {
    eventName: 'Transfer',
    limit: 50,
    order_by: 'block_timestamp,desc',
  }
);

events.data.forEach(event => {
  console.log(`Transfer: ${event.result.from} -> ${event.result.to}: ${event.result.value / 1e6} USDT`);
  console.log(`  Block: ${event.block_timestamp}, TX: ${event.transaction_id}`);
});

For real-time monitoring, you'll need to poll the event API periodically. Most production setups poll every 3-5 seconds (one block time) and track the last processed block to avoid duplicates.

Rate limits and production concerns

Tron RPC providers typically limit:

  • Requests per second — usually 10-50 RPS on free tiers
  • Heavy methodsgetblockbylimitnext and event queries may have lower limits
  • Broadcast rate — transaction submission may be rate-limited separately
  • Concurrent connections — WebSocket/gRPC connection limits

The failure mode is HTTP 429 or timeout on heavy methods. The fixes:

  1. Cache account resources — bandwidth and energy don't change between blocks
  2. Batch event queries — use block ranges instead of polling per-block
  3. Implement retry with backoff — Tron's network can be bursty during high activity
  4. Use the fullnode API (port 8090) over the wallet API (port 8091) for reads
async function callWithRetry(fn, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (err) {
      if (err.response?.status === 429 && i < maxRetries - 1) {
        await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
        continue;
      }
      throw err;
    }
  }
}

Choosing a Tron RPC endpoint

The decision comes down to three things:

1. Do you need high-throughput TRC-20 transfers? If you're moving USDT at scale, you need a provider that supports triggerconstantcontract and broadcasttransaction with high rate limits. Free tiers often cap these methods.

2. Do you need event streaming? If you're monitoring USDT transfers or building an indexer, you need a provider with reliable event API access and reasonable rate limits on getEventsByContractAddress.

3. What's your transaction volume? A payment processor handling 10,000 transfers/day needs a different plan than a wallet showing balances. Calculate your expected requests: balance checks + resource checks + broadcast = ~3 requests per transfer.

Other factors — latency to Super Representatives, geographic distribution, gRPC support — matter but are secondary. An endpoint with 50ms latency that rate-limits broadcasttransaction during congestion is useless when you need to move funds.

The short version

Tron mainnet runs at ~3 second blocks, uses bandwidth + energy instead of gas, and base58 addresses starting with T. The RPC is a mix of JSON-RPC and REST endpoints. USDT transfers cost ~65,000 energy (or ~14-27 TRX if you don't have staked energy). Always check resources before broadcasting. Use triggerconstantcontract for contract reads with the correct function selector. Convert between base58 and hex addresses as needed.

For Tron RPC access with high-throughput TRC-20 support and reliable event streaming, grab a free API key and point your app at:

https://rpc.swiftnodes.io/rpc/trx?key=YOUR_API_KEY
J
John Sullivan
Infrastructure Writer, SwiftNodes

John Sullivan covers RPC infrastructure, node operations, and multi-chain development at SwiftNodes — what it actually takes to keep endpoints fast, fresh, and reliable across EVM and non-EVM networks.

Related posts

Try SwiftNodes free — multi-chain RPC across 75+ networks, flat-rate pricing, pay by card or crypto, no KYC. Get an API key in 30 seconds →