Query TRC-20 Token Balance via API on TRON — TRON Wiki

Query TRC-20 Token Balance via API on TRON

10 min read · ⌘K search

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:

Code
TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t

Decimals: 6 (1 USDT = 1_000_000 base units).

Method 1: TronWeb

Code
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

Code
GET https://api.trongrid.io/v1/accounts/{address}

Response includes trc20 array with token balances keyed by contract address.

Example parsing (structure may vary):

Code
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 address
  • contract_address: token contract

Use when TronWeb unavailable (non-JS stacks).

Decimals handling

Never assume 18 decimals. Always call decimals() or use known constants:

TokenDecimals
USDT6
Many custom tokens18

Use integer math (BigInt) for financial accuracy — avoid JavaScript float errors on large balances.

TRX balance alongside tokens

Code
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:

  1. Generate unique deposit address per user or track by amount + memo pattern
  2. Poll balanceOf after expected payment window
  3. Better: event listeners on USDT Transfer events

Caching

  • Cache balances with short TTL (10–30s) for UI
  • Invalidate on detected incoming Transfer event

Rate limits

Respect TronGrid limits — batch address queries off-peak.

Multi-token support

Abstract helper:

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

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.

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.