# TRON USDT transfer history via JSON-RPC: logs, block windows and address formats

> How to rebuild an address's USDT-TRC20 history from raw event logs without a third-party indexer: which filter to send, why every request covers at most 100 blocks, how to convert TRON addresses into topics, and what a year of history costs in requests.

- Canonical: https://triport.io/blog/tron-usdt-transfer-history-rpc
- Published: 2026-09-22
- Updated: 2026-10-25
- Author: Triport Engineering
- Category: Tutorials
- Networks: TRON

## TL;DR

- USDT on TRON is a TRC-20 contract, and every transfer emits a Transfer event, so an address's USDT history is the set of Transfer logs from that contract with your address in the sender or recipient topic — two eth_getLogs filters per block window.
- Each eth_getLogs request on TRON covers at most 100 blocks (a wider range returns -32602). At one block every 3 seconds that is at most 288 requests per day of history, per filter.
- Addresses must be converted: base58 (T…) to hex-41 to a 20-byte value, then left-padded to 32 bytes for a topic. A wrong format gives you an empty result, not an error.

A payments team that settles in USDT on TRON eventually needs the answer to one question: which USDT transfers touched this address, and when? Explorers show it, and hosted indexers sell it, but the data itself is in every TRON full node, as event logs. This post rebuilds that history with TRON's Ethereum-compatible JSON-RPC and nothing else, and does the arithmetic on what it costs.

A Chainstack write-up on USDT-TRC20 infrastructure (April 2026, updated July 2026, read 2026-09-22) describes polling blocks by number, one after another, and filtering their internal transactions for the USDT contract; it does not cover reconstructing the historical transfers of one address, the `eth_getLogs` range limit, or TRON's address formats. Those three are the whole job.

## Where TRC-20 transfers live: the Transfer event

USDT on TRON is a TRC-20 token contract. Tether lists its TRON contract as `TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t`. The TRC-20 interface declares:

```text
event Transfer(address indexed _from, address indexed _to, uint _value);
```

and TRON's documentation says the event fires on `transfer` and `transferFrom`, and also on mint (with `_from` set to zero) and burn (with `_to` set to zero). TRON's event documentation describes events as data a contract chooses to emit, recorded in the transaction's `TransactionInfo`. Those are the records `eth_getLogs` searches.

A log carries up to four topics. For this event:

| Topic | Contents |
|---|---|
| `topics[0]` | The event signature hash: `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef` |
| `topics[1]` | `_from`, left-padded to 32 bytes |
| `topics[2]` | `_to`, left-padded to 32 bytes |
| `data` | `_value`, the amount in the token's smallest unit |

The signature hash is the one ERC-20 `Transfer` uses, because Solidity treats `uint` as an alias for `uint256`, so the canonical signature is `Transfer(address,address,uint256)` in both standards.

## Filtering by sender and recipient

To get one address's history you send two filters per block window: one with your address in the `_from` position (outgoing) and one with it in the `_to` position (incoming). Each filter pins the contract and the signature:

```json
{"jsonrpc":"2.0","id":1,"method":"eth_getLogs","params":[{
  "address":"0xa614f803b6fd780986a42c78ec9c7f77e6ded13c",
  "fromBlock":"0x51e5e1c",
  "toBlock":"0x51e5e7f",
  "topics":[
    "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
    null,
    "0x000000000000000000000000<your-address-as-20-bytes>"
  ]
}]}
```

`null` in a topic position is a wildcard. Swap the second and third positions for the outgoing filter. A topic position can also be an array of values, OR-matched, which is how you watch several of your own addresses in one request.

Two contract details from the Triport reference: `fromBlock` and `toBlock` are hex quantities, and the `address` field takes a `0x`-prefixed 20-byte value — not a TRON base58 address. Unknown properties in the filter object are rejected.

## The 100-block window and how to page it

On Triport, one `eth_getLogs` request on TRON may span **at most 100 blocks**, inclusive. Wider requests fail with `-32602` (invalid params); when we sent a 256-block range on 2026-09-22 the answer was `-32602` with the message "requested block range exceeds the measured upstream limit". An empty array, by contrast, is a successful query that matched nothing.

Paging is mechanical:

```ts
const WINDOW = 100; // inclusive range, so toBlock = fromBlock + 99
for (let from = start; from <= end; from += WINDOW) {
  const to = Math.min(from + WINDOW - 1, end);
  const incoming = await getLogs({ ...usdt, fromBlock: hex(from), toBlock: hex(to), topics: [SIG, null, me] });
  const outgoing = await getLogs({ ...usdt, fromBlock: hex(from), toBlock: hex(to), topics: [SIG, me, null] });
  await store(from, to, incoming, outgoing); // then persist `to` as your checkpoint
}
```

Windows must be adjacent and non-overlapping: `toBlock` is inclusive, so the next window starts at `to + 1`. Persist the last completed `to` after storing the results, so a restart resumes rather than rescans.

## Arithmetic: blocks per day ÷ 100 = calls

TRON produces a block every 3 seconds, according to its developer documentation. That gives an upper bound of 86,400 ÷ 3 = **28,800 blocks per day**, and at 100 blocks per request, **288 requests per day of history, per filter**. With incoming and outgoing filters, one day of one address's history is at most 576 requests.

On Triport, `eth_getLogs` on TRON is billed to the `tron_read_rpc_heavy` category, whose sustained limits are **2 / 5 / 20 / 80 RPS** on free / basic / pro / business. These are default, unmeasured values in the tier matrix. Bursts up to 2× sustained are tolerated, but a backfill is sustained load, so plan on the sustained figure.

| Tier | Sustained RPS | Short burst capacity | One day (576 requests) | One year (210,240 requests) |
|---|---:|---:|---:|---:|
| Free | 2 | 4 | 288 s | about 29 h |
| Basic | 5 | 10 | about 115 s | about 11.7 h |
| Pro | 20 | 40 | about 29 s | about 2.9 h |
| Business | 80 | 160 | about 7 s | about 44 min |

The burst buffer is included at no extra cost and refills at sustained RPS; there is no quota on the number of bursts. See [burst capacity](https://triport.io/docs/rate-limits-and-tiers#burst-capacity). The year column is 365 × 576 requests divided by the sustained rate. Two things change the answer:

- **More addresses cost less than you think.** A topic position accepts an array, so several of your addresses fit in one filter; the request count grows with block windows, not with addresses (until the response itself gets large).
- **Fewer blocks than the bound.** 28,800 is a ceiling from the 3-second interval; the count of blocks actually produced can be lower. Read the real range from `eth_blockNumber` at the start and end of your backfill rather than trusting the ceiling.

`eth_getLogs` shares the heavy category with the heavy wallet calls, so a backfill competes with your live traffic in that category. Running it below your limit keeps production reads from receiving `429`.

## Address formats: hex-41, base58 and visible

TRON has two representations of the same 21-byte address. The native hex form starts with `41` followed by 20 bytes; the base58 form starting with `T` is what users and explorers show. Triport's `wallet/getaccount` reference pairs them explicitly: hex-41 goes with `visible` omitted or `false`, base58 with `visible: true`, and a mismatched pair "can produce an empty object rather than an explicit error".

For logs you need a third form. `eth_getLogs` takes the EVM-style 20 bytes, so:

1. Decode base58check (`T…`) to 21 bytes; the first byte is `0x41`.
2. Drop the `41` prefix: the remaining 20 bytes are the EVM-style address.
3. For the contract `address` field, send them as `0x` + 40 hex characters.
4. For a topic, left-pad the same 20 bytes with zeros to 32 bytes.

Worked through for the USDT contract itself: `TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t` decodes to `41a614f803b6fd780986a42c78ec9c7f77e6ded13c`, so the filter address is `0xa614f803b6fd780986a42c78ec9c7f77e6ded13c`. Going back, a 20-byte value from `topics[1]` or `topics[2]` becomes a user-facing address by prefixing `41` and base58check-encoding it.

The failure mode is quiet. A topic built from the wrong representation matches nothing, and nothing is exactly what an address with no USDT activity also returns. Test your conversion against one transfer you already know about before trusting an empty history. The same trap on the wallet API is covered in [why TRON wallet/getaccount returns an empty object](https://triport.io/learn/tron-address-format-mismatch).

## Native TRX and internal transactions are not in these logs

A Transfer log scan sees TRC-20 movements of one contract, and nothing else:

- **Native TRX** is not a TRC-20 token and emits no `Transfer` event from the USDT contract, so TRX payments to the same address are invisible to this filter.
- **Other tokens** need their own contract address in the filter.
- **Anything a contract did without emitting an event** is not in the logs at all, because logs are only what contracts choose to emit.

If the job is "every value movement touching this address", USDT logs are one input, not the answer.

Sending a transfer is a different question from reading one. The sender pays for a USDT transfer in energy and bandwidth, or in burned TRX; [TRON energy vs bandwidth](https://triport.io/learn/tron-energy-bandwidth-fees) explains the two resources, and [estimate a TRON USDT transfer fee](https://triport.io/docs/guides/estimate-tron-usdt-transfer-fee) walks through a read-only estimate before you sign and broadcast elsewhere.

## When this is the wrong approach

- **You need the history in seconds, for many addresses, on demand.** At the rates above, a year of history for one address is hours on the lower tiers. An indexed store you maintain (or buy) is the right tool for interactive lookups; `eth_getLogs` paging is how you build or verify one.
- **You need live notifications.** Triport publishes no WebSocket log or mempool surface for TRON. For new transfers, poll the latest window on a timer.
- **You need TRX or internal-transaction history.** Logs will not give it to you, as above.
- **You need current balance, not history.** Read balance state directly; summing a reconstructed history reproduces it only if your history is complete.

## Sources

1. Triport, [`eth_getLogs` on TRON](https://triport.io/docs/tron/json-rpc/eth_getLogs): at most 100 blocks per request, inclusive; `-32602` on a wider range; hex block quantities; 20-byte `0x` addresses; `tron_read_rpc_heavy` 2 / 5 / 20 / 80 RPS (default, unmeasured); no TRON WebSocket log surface. Production check 2026-09-22 on the EU and CA origins: a 256-block request returned `-32602`, and a 100-block request filtered on the USDT contract returned logs.
2. Triport, [TRON limits](https://triport.io/docs/tron/limits): category table and the 100-block note.
3. Triport, [`wallet/getaccount` on TRON](https://triport.io/docs/tron/wallet/getaccount): hex-41 and base58 with `visible`; an empty object on a mismatch.
4. Triport, [Rate limits and tiers](https://triport.io/docs/rate-limits-and-tiers): 2× burst.
5. TRON Developer Hub, Blocks ("a new block every 3 seconds") — https://developers.tron.network/docs/block, read 2026-09-22.
6. TRON Developer Hub, TRC-20 protocol interface (`Transfer` event, mint and burn) — https://developers.tron.network/docs/trc20-protocol-interface; Events — https://developers.tron.network/docs/event; both read 2026-09-22.
7. Tether, supported protocols (TRON contract `TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t`) — https://tether.to/en/supported-protocols/, read 2026-09-22. The hex-41 form above was computed from it by base58check decoding (checksum verified).
8. Solidity documentation, types ("`uint` and `int` are aliases for `uint256` and `int256`") — https://docs.soliditylang.org/en/latest/types.html, read 2026-09-22. The `Transfer` signature hash matches the one in Triport's [Ethereum Pub/Sub reference](https://triport.io/docs/streaming/eth-ws) `logs` example.
9. Chainstack, USDT-TRC20 infrastructure post — https://chainstack.com/tron-rpc-usdt-trc20-infrastructure/, read 2026-09-22.

## Try it

- Reference: [eth_getLogs on TRON — filter shape, the 100-block window and limits](https://triport.io/docs/tron/json-rpc/eth_getLogs)
- Network: [TRON RPC — JSON-RPC, wallet API and limits](https://triport.io/tron-rpc)
