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:
- gRPC (port 50051) — the native protocol, used by TronWeb and most SDKs
- HTTP JSON-RPC (port 8090) — the fullnode API, closest to traditional RPC
- 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 methods —
getblockbylimitnextand 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:
- Cache account resources — bandwidth and energy don't change between blocks
- Batch event queries — use block ranges instead of polling per-block
- Implement retry with backoff — Tron's network can be bursty during high activity
- 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
Top comments (0)