A Tron node serves two interfaces side by side. Use JSON-RPC (/jsonrpc) for reads that EVM tooling already knows: logs, eth_call at the latest block, blocks and receipts. ethers.js and viem work against it for reads. Use the HTTP API (/wallet and /walletsolidity) for everything that writes or touches Tron-specific state: building and broadcasting transactions, account resources, energy, bandwidth and staking. A third interface, gRPC, serves the same wallet functions for clients that prefer protobuf.
Most Tron integrations end up using both. This guide shows what each interface supports, what it refuses with the exact error, and where the limits sit, so you pick the right one per call instead of finding out in production.
We ran the node-level checks against Tron’s public TronGrid endpoint (its nodes reported java-tron v4.8.2.3 and v4.8.2.4) on 22 and 23 October 2026. We ran the BoltRPC checks on 22 and 23 October 2026. Each result below says which one it came from.
The short answer
Use JSON-RPC when you:
- read event logs with
eth_getLogs, for example TRC-20Transferevents - call contract view functions with
eth_callat the latest block - read blocks, transactions and receipts and want your existing ethers.js, viem or indexer code to keep working
- need a request shape your EVM stack already understands
Use the HTTP API when you:
- build, sign and broadcast transactions
- read or manage account resources: bandwidth, energy, staking and delegation
- read solidified (final) state through
/walletsolidity - want base58
T...addresses in requests and responses - need the TRX fee of each transaction, which JSON-RPC receipts do not carry
There is no way to broadcast through JSON-RPC at all, so any write path needs the HTTP API (or gRPC) anyway.
What Tron’s JSON-RPC supports and refuses
The interface is JSON-RPC 2.0 over POST only. A GET on /jsonrpc returned HTTP 404 on TronGrid. Errors come back with HTTP 200 and an error object, as they do on EVM chains. On a self-hosted java-tron node the interface is disabled by default and has to be switched on in the node configuration.
What it supports, grouped by job (all measured on TronGrid):
- Chain and node info:
web3_clientVersion,net_version,eth_chainId,eth_blockNumber,eth_gasPrice,eth_syncing - Blocks:
eth_getBlockByNumber,eth_getBlockByHash,eth_getBlockReceipts - Transactions:
eth_getTransactionByHash,eth_getTransactionReceipt - State and calls:
eth_getBalance,eth_getStorageAt,eth_getCode,eth_call,eth_estimateGas - Logs and filters:
eth_getLogs,eth_newFilter,eth_newBlockFilter,eth_getFilterChanges,eth_getFilterLogs,eth_uninstallFilter - Batches: a batch of two requests returned two results. The node limit is 100 requests per batch.
- Tron extension:
buildTransaction, which builds an unsigned transaction over JSON-RPC
Two numbers behave differently from Ethereum. eth_gasPrice returns the energy price in sun (0x64, which is 100, on 22 October 2026) and eth_estimateGas returns energy, not gas: 2207 for the USDT decimals() call. eth_getBalance returns TRX in sun, where 1 TRX is 1,000,000 sun.
Methods that are refused
java-tron marks twelve methods as not supported, and they answer with error -32601. The four that EVM code calls most:
eth_sendRawTransactioneth_sendTransactioneth_signeth_getTransactionCount
The rest are eth_signTransaction, parity_nextNonce, the mining methods (eth_submitWork, eth_submitHashrate) and the old compiler methods (eth_getCompilers and the eth_compile* family).
On TronGrid the message reads the method eth_sendRawTransaction does not exist/is not available. On BoltRPC the same call returned -32601 with the method eth_sendRawTransaction is not available through this endpoint, the wording BoltRPC uses for any method outside its Tron allowlist. There is no EVM account nonce on Tron. A Tron transaction carries a reference block and an expiry time instead, which you can see as ref_block_bytes and expiration in the output of /wallet/createtransaction.
buildTransaction is the one builder JSON-RPC has: it constructs an unsigned transfer, TRC-10 transfer, contract deployment or contract call. Tron’s documentation is explicit that JSON-RPC itself has no broadcast endpoint, so you sign locally and send the result through /wallet/broadcasttransaction or /wallet/broadcasthex. We ran buildTransaction on TronGrid, not on BoltRPC.
A second group is simply absent. On TronGrid, eth_feeHistory, eth_maxPriorityFeePerGas, eth_getProof, eth_createAccessList and debug_traceTransaction all answered method not found, the same reply as a made-up method name. eth_subscribe also answers method not found over HTTP. Tron prices fees in energy and bandwidth instead. A few methods exist only to keep EVM clients happy and return constants: eth_accounts returns an empty list.
Block tags: latest only for state
The tags are narrower than on Ethereum:
eth_getBlockByNumber,eth_getBlockReceiptsandeth_getLogsacceptlatest,earliestandfinalized.safeis rejected (measured oneth_getLogs:-32602,TAG safe not supported).eth_getBalance,eth_getStorageAt,eth_getCodeandeth_callaccept onlylatest. On BoltRPC,eth_getBalancewith a block number returned-32602withQUANTITY not supported, just support TAG as latest. On TronGrid,eth_callwithfinalizedreturned-32602too.eth_newFilterrejectsfinalizedwith-32602andinvalid block range params.
So you cannot ask this interface for a balance or a storage slot at an old block. If you need history, build it from logs as you scan.
Addresses: base58, 41-hex and 0x
Tron addresses have three shapes, and the two interfaces want different ones. The USDT contract in each:
- base58:
TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t - 21-byte hex with the Tron prefix:
41a614f803b6fd780986a42c78ec9c7f77e6ded13c - 20-byte EVM hex:
0xa614f803b6fd780986a42c78ec9c7f77e6ded13c
JSON-RPC accepts hex only: the 20-byte 0x form or the 21-byte 41 form, with or without 0x. A base58 address is rejected with -32602 and exception decoding Hex string: invalid characters encountered in Hex string (TronGrid). Log filters use the 20-byte form.
The HTTP API takes hex by default and base58 when you add "visible": true to the body. Without it, a base58 address fails with INVALID hex String. Log addresses inside HTTP transaction info are 20-byte hex with no 0x and no 41.
TronWeb converts between all of them. We checked these on 22 October 2026 with TronWeb 6.5.1:
import { TronWeb } from "tronweb";
TronWeb.address.toHex("TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t");
// "41a614f803b6fd780986a42c78ec9c7f77e6ded13c"
TronWeb.address.fromHex("0xa614f803b6fd780986a42c78ec9c7f77e6ded13c");
// "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"
The deposit guide goes deeper on topics and padding: Tron USDT deposits.
Reading TRC-20 events with eth_getLogs
This is where JSON-RPC earns its place. A TRC-20 Transfer is the same event as an ERC-20 one, so the same filter works. ethers 6.17.0 and viem 2.57.10 both read logs, calls, receipts and balances over /jsonrpc, with chain ID 728126428 (0x2b6653dc, the last four bytes of the genesis block hash). A block we compared on 22 October 2026 had 29 USDT Transfer logs from eth_getLogs and the same 29 in /wallet/gettransactioninfobyblocknum.
import { ethers } from "ethers";
import { TronWeb } from "tronweb";
const URL = "https://endpoints.boltrpc.io/rpc/YOUR_API_KEY/tron/jsonrpc";
const provider = new ethers.JsonRpcProvider(URL, undefined, { staticNetwork: true });
const USDT = "0xa614f803b6fd780986a42c78ec9c7f77e6ded13c";
const TRANSFER = ethers.id("Transfer(address,address,uint256)");
const toBase58 = (topic) => TronWeb.address.fromHex("0x" + topic.slice(-40));
// provider.getBlock() throws on Tron (stateRoot comes back as "0x"), so read the raw block
const block = await provider.send("eth_getBlockByNumber", ["finalized", false]);
const head = Number(block.number);
const logs = await provider.getLogs({
address: USDT,
topics: [TRANSFER],
fromBlock: head - 2,
toBlock: head,
});
for (const l of logs.slice(0, 2)) {
console.log({
block: l.blockNumber,
from: toBase58(l.topics[1]),
to: toBase58(l.topics[2]),
usdt: Number(BigInt(l.data)) / 1e6,
});
}
We ran this code against TronGrid with only the URL swapped. It printed decoded transfers with base58 senders and recipients. With viem, define a chain object whose id is 728126428 and pass it to createPublicClient. viem returns checksummed 0x addresses, so the base58 conversion is still yours.
Two details worth knowing. provider.getBlock() in ethers 6.17.0 threw BAD_DATA on a Tron block because Tron returns stateRoot as "0x", which ethers does not accept as a hash. Calling eth_getBlockByNumber through provider.send avoids it. And Tron adds a blockTimestamp field to each log, which is handy when you index.
The limits: 5000 blocks and 10000 results
The Tron node caps eth_getLogs in two ways, and both showed up in our tests:
- Block span: on TronGrid,
toBlock - fromBlockof 5000 was accepted and 5001 was rejected with-32602andexceed max block range: 5000. On BoltRPC a span of 5001 blocks returned the same error. - Result count: on BoltRPC, a span of 5000 blocks over a busy USDT range returned
-32005andquery returned more than 10000 results. A two-block span returned logs.
USDT is busy enough that the result cap bites long before the span cap. We counted 29 USDT Transfer logs in one block, and 170 over a three-block window at the finalized head. A 1,201-block USDT window on TronGrid held 51,838 logs, about 43 per block, so 10000 results is a few hundred blocks, not 5000.
Chunk by span and halve a window when the node refuses it for size:
const MAX_SPAN = 5000;
const codeOf = (e) => e?.error?.code ?? e?.info?.error?.code;
async function getLogsRange(filter, from, to) {
const out = [];
for (let start = from; start <= to; start += MAX_SPAN + 1) {
out.push(...(await getWindow(filter, start, Math.min(start + MAX_SPAN, to))));
}
return out;
}
async function getWindow(filter, from, to) {
try {
return await provider.getLogs({ ...filter, fromBlock: from, toBlock: to });
} catch (e) {
if (codeOf(e) === -32005 && to > from) {
const mid = Math.floor((from + to) / 2);
return [...(await getWindow(filter, from, mid)), ...(await getWindow(filter, mid + 1, to))];
}
throw e;
}
}
We ran this with MAX_SPAN at 1500 over a 1,201-block USDT range on TronGrid. It needed 15 calls and returned 51,838 logs, every one unique. The windows do not overlap, but still key what you store on transaction hash plus log index. The same chunking logic, plus reorg handling and storage, is in the eth_getLogs production pipeline.
Prefer polling eth_getLogs over filters. When we tested the public endpoint, filters created with eth_newFilter were unreliable: a follow-up eth_getFilterChanges often answered filter not found.
Latest versus confirmed
latest is the newest block and can still be replaced. finalized is the latest solidified block, the same idea as /walletsolidity on the HTTP side. In four paired reads on 22 October 2026 the /walletsolidity head trailed /wallet by 17 to 19 blocks, and the JSON-RPC finalized block sat one to three blocks ahead of the /walletsolidity head, even when we read both at the same moment. If you credit money from finalized, keep a small margin behind it. The deposit guide explains the solidification rule.
Building and broadcasting: the HTTP API
Everything that changes state goes through the HTTP API. The flow has three steps:
- Ask the node for an unsigned transaction. For a TRX transfer that is
/wallet/createtransaction. Contract calls use/wallet/triggersmartcontractand deployments/wallet/deploycontract. - Sign it locally.
- Send the signed transaction to
/wallet/broadcasttransaction(JSON) or/wallet/broadcasthex(hex).
The builder is a plain POST. Amounts are in sun and visible: true makes the addresses base58:
curl -s -X POST https://endpoints.boltrpc.io/rpc/YOUR_API_KEY/tron/wallet/createtransaction \
-H 'Content-Type: application/json' \
--data '{"owner_address":"SENDER_BASE58","to_address":"RECEIVER_BASE58","amount":1000000,"visible":true}'
The reply holds raw_data, raw_data_hex and the txID. We ran this call on TronGrid on 22 October 2026 and got an unsigned TransferContract back. We did not broadcast a real transaction, so the broadcast step rests on Tron’s documentation, plus a check that the route exists. A malformed body returned an error from the node, not a 404.
You rarely write these calls by hand. Point TronWeb at the endpoint and it builds, signs and broadcasts for you:
import { TronWeb } from "tronweb";
const tronWeb = new TronWeb({
fullHost: "https://endpoints.boltrpc.io/rpc/YOUR_API_KEY/tron",
});
const block = await tronWeb.trx.getCurrentBlock();
console.log("HTTP API block:", block.block_header.raw_data.number);
Confirmed reads and receipts
Most read routes under /wallet/... have a /walletsolidity/... twin that reads only solidified state. Not all do: getaccountresource, getaccountnet, createtransaction and the broadcast routes exist only under /wallet, and TronGrid answered walletsolidity/getaccountresource with HTTP 405. For per-transaction results, /walletsolidity/gettransactioninfobyblocknum returns the transaction info for every transaction in a block: fee, receipt.result and energy fields appear when they apply, so a plain TRX transfer carries only its bandwidth usage. The JSON-RPC equivalent is eth_getBlockReceipts plus eth_getLogs. Its gasUsed is the energy used, but the TRX fee is not in it. If you need to know what a transaction cost in TRX, use the HTTP response.
Errors on the HTTP side are not JSON-RPC envelopes, and they do not share one shape. A malformed request returns HTTP 200 with {"Error":"..."}. A rejected broadcast returns a body with a code such as SIGERROR or CONTRACT_VALIDATE_ERROR and no Error key. A failed contract call puts the code inside result. An unknown route returns HTTP 405, and the rate limit returns 429. Check the status code, an Error key and the code or result fields before you treat a call as successful.
Resources and staking are HTTP or gRPC only
No JSON-RPC method returns account resources. Tron meters usage in bandwidth (the byte size of a transaction) and energy (the computation a smart contract call uses). Reading and managing both is HTTP territory:
- Read resources:
/wallet/getaccountresourcereturns bandwidth, energy and TRON Power./wallet/getaccountnetreturns bandwidth. On 22 October 2026getaccountresourceshowedfreeNetLimit: 600, the free daily bandwidth per account. - Stake and delegate: the Stake 2.0 routes
freezebalancev2,unfreezebalancev2,delegateresource,undelegateresourceandwithdrawexpireunfreeze. Unfreezing starts a 14-day waiting period. - Prices:
getenergyprices,getbandwidthpricesandgetburntrx.
eth_estimateGas over JSON-RPC does give you an energy figure for a call, which is useful for a quick check. For fee planning on real withdrawals, the energy and fee_limit detail is in the USDT deposit guide.
Using both on BoltRPC
One BoltRPC key reaches both interfaces on Tron:
- JSON-RPC:
https://endpoints.boltrpc.io/rpc/YOUR_API_KEY/tron/jsonrpc - HTTP API:
https://endpoints.boltrpc.io/rpc/YOUR_API_KEY/tron/wallet/...and.../tron/walletsolidity/...
On 22 and 23 October 2026 we checked the endpoints with a BoltRPC key. eth_blockNumber on /tron/jsonrpc returned 0x534c667, eth_chainId returned 0x2b6653dc and POST /tron/wallet/getnowblock returned HTTP 200 with a blockID and block_header. The refusals and limits described above, -32601 on eth_sendRawTransaction, -32602 past 5000 blocks, -32005 past 10000 results and latest-only eth_getBalance, are what BoltRPC’s Tron JSON-RPC returned.
The Tron node has no eth_subscribe. For new blocks BoltRPC adds a newHeads stream over WebSocket at wss://endpoints.boltrpc.io/ws/YOUR_API_KEY/tron, one message per block, about every three seconds. Native gRPC is available too and is described on the Tron network page. We did not test gRPC for this guide.
Request-unit costs differ per method and are listed on pricing. Sustained scanning also needs headroom. The keyless TronGrid endpoint answered with a 3 requests per second limit when we tested, which is why we spaced our test calls. RPC rate limits explained covers how to budget for it.
FAQ
Does Tron support eth_sendRawTransaction?
No. Tron’s JSON-RPC lists eth_sendRawTransaction, eth_sendTransaction and eth_sign as unsupported and answers -32601. On BoltRPC the reply was the method eth_sendRawTransaction is not available through this endpoint. Build the transaction, sign it locally and broadcast it with /wallet/broadcasttransaction or /wallet/broadcasthex. TronWeb does all three steps.
Can I use ethers.js with Tron?
Yes, for reads. ethers 6.17.0 and viem 2.57.10 read Tron over /jsonrpc with chain ID 728126428: logs, eth_call, receipts and balances at the latest block. Pass hex addresses only. provider.getBlock() in ethers throws on a Tron block, so use provider.send("eth_getBlockByNumber", ...). Signing and broadcasting go through TronWeb and the HTTP API.
What is Tron’s eth_getLogs block range limit?
The Tron node caps eth_getLogs at 5000 blocks per request and 10000 results. A span of 5001 blocks returned -32602 with exceed max block range: 5000, and a busy USDT range returned -32005 with query returned more than 10000 results. Split the range into windows and halve any window that returns -32005.
What chain ID does Tron use for JSON-RPC?
Tron mainnet reports 0x2b6653dc from eth_chainId, which is 728126428 in decimal. It is the last four bytes of the genesis block hash. Use 728126428 when you define the chain in viem or ethers.js.
Can I read a historical balance over Tron JSON-RPC?
No. eth_getBalance, eth_getStorageAt, eth_getCode and eth_call accept only the latest tag. A block number returns -32602 with QUANTITY not supported, just support TAG as latest. Build history from logs as you scan blocks.
Build on Tron with BoltRPC
Read with JSON-RPC, write with the HTTP API and keep both behind one key. Get the endpoints on the Tron network page, then start a free trial to try them. If you are building a payments flow, the Tron USDT deposit guide turns this into a working scanner.
