Query TRC-20 Token Balance via API on TRON
Reading TRC-20 balances is the foundation for payment dashboards, exchange integrations, and wallet apps on TRON. Unlike TRX (native), token balances live inside each token's smart contract — you query balanceOf(address) on the contract.
This guide shows TronWeb, TronGrid REST, and production considerations for USDT and arbitrary TRC-20 tokens.
USDT contract reference
Mainnet official USDT:
TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t
Decimals: 6 (1 USDT = 1_000_000 base units).
Method 1: TronWeb
const TronWeb = require('tronweb');
const tronWeb = new TronWeb({
fullHost: 'https://api.trongrid.io',
headers: { 'TRON-PRO-API-KEY': process.env.TRONGRID_KEY },
});
const USDT = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t';
async function getTrc20Balance(holderAddress, contractAddress = USDT) {
if (!tronWeb.isAddress(holderAddress)) throw new Error('Invalid address');
const contract = await tronWeb.contract().at(contractAddress);
const raw = await contract.balanceOf(holderAddress).call();
const decimals = await contract.decimals().call();
const divisor = Math.pow(10, Number(decimals));
return Number(raw) / divisor;
}
Note: .call() behavior may return BigNumber-like objects — normalize carefully in production.
Method 2: TronGrid account endpoint
GET https://api.trongrid.io/v1/accounts/{address}
Response includes trc20 array with token balances keyed by contract address.
Example parsing (structure may vary):
async function getUsdtFromAccountApi(address) {
const res = await fetch(
`https://api.trongrid.io/v1/accounts/${address}`,
{ headers: { 'TRON-PRO-API-KEY': process.env.TRONGRID_KEY } }
);
const json = await res.json();
const trc20 = json.data?.[0]?.trc20 || [];
for (const entry of trc20) {
const [[contract, balance]] = Object.entries(entry);
if (contract === USDT) return Number(balance) / 1e6;
}
return 0;
}
Good for listing all tokens at once; less precise for real-time payment edge cases.
Method 3: triggerconstantcontract
Low-level HTTP per trigger smart contract:
function_selector:balanceOf(address)parameter: ABI-encoded holder addresscontract_address: token contract
Use when TronWeb unavailable (non-JS stacks).
Decimals handling
Never assume 18 decimals. Always call decimals() or use known constants:
| Token | Decimals |
|---|---|
| USDT | 6 |
| Many custom tokens | 18 |
Use integer math (BigInt) for financial accuracy — avoid JavaScript float errors on large balances.
TRX balance alongside tokens
const balanceSun = await tronWeb.trx.getBalance(address); const balanceTrx = tronWeb.fromSun(balanceSun);
Users need TRX for fees even when holding only USDT.
Production patterns
Payment polling
For accept USDT payments:
- Generate unique deposit address per user or track by amount + memo pattern
- Poll
balanceOfafter expected payment window - Better: event listeners on USDT
Transferevents
Caching
- Cache balances with short TTL (10–30s) for UI
- Invalidate on detected incoming
Transferevent
Rate limits
Respect TronGrid limits — batch address queries off-peak.
Multi-token support
Abstract helper:
async function getTokenBalance(holder, tokenContract) {
const c = await tronWeb.contract().at(tokenContract);
const [raw, decimals] = await Promise.all([
c.balanceOf(holder).call(),
c.decimals().call(),
]);
return { raw: raw.toString(), decimals: Number(decimals) };
}
Batch balance queries
For many addresses, avoid N+1 constant calls. Use TronGrid v1 account tokens endpoint or parallelize with rate-limit awareness. Index deposit addresses in your database and poll only active invoices to reduce API load.
Decimal handling pitfalls
USDT uses 6 decimals; many TRC-20 tokens use 18. Never display raw balanceOf without dividing by 10**decimals. Store amounts as strings or BigInt in JavaScript to avoid floating-point errors. Off-by-one-decimal bugs have caused payment systems to credit 1e12 times intended amount in production incidents on other chains — same risk applies on TRON.
Related guides
FAQ
What is the USDT contract address on TRON mainnet?
TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t — always verify on TronScan before production use.
Does balanceOf require a private key?
No. balanceOf is a read-only contract call that any full node or TronGrid can execute.
Why does API balance differ from TronLink?
Timing, unconfirmed txs, or wrong contract address. Confirm latest block and contract.
Can I query historical balance?
Requires archival node / indexer — not standard TronGrid account endpoint. Use TronScan API or index Transfer events yourself.
How fast is balance updated after transfer?
After transaction confirms (~3s). Poll getTransaction until SUCCESS then read balance.
Thanks — your feedback helps us improve the docs.