tron-fee
v1.0.0
Published
Work out what a TRC-20 transfer costs on TRON before you sign it. Reads getEnergyFee, detects whether the recipient has ever held the token, and compares burning TRX against renting energy. Zero dependencies.
Maintainers
Readme
tron-fee
Work out what a TRC-20 transfer costs on TRON before you sign it.
TRON does not auction gas. A transfer's cost is energy needed × price per unit of energy, and both values are readable in advance — so the estimate you compute is the amount you actually pay, not a bid.
Zero dependencies. Node 18+.
npx tron-fee TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t --rent 25.77 --trx-usd 0.3274 recipient TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t
already holds the token
energy needed 64,285
getEnergyFee 100 SUN per unit
burn 6.4285 TRX ($2.10)
rent @ 25.77 SUN 1.6566 TRX ($0.54)
difference 4.7719 TRX (74.2%)The problem this solves
A USDT transfer on TRON needs one of two fixed amounts of energy, and the receiving address decides which:
| Recipient | Energy | Burned at 100 SUN | |---|---:|---:| | Already holds the token | 64,285 | 6.43 TRX | | Has never held the token | 130,285 | 13.03 TRX |
The second case costs more because the contract writes a new storage entry instead of updating an existing one.
This is the single most common cause of failed transfers on TRON. A payout batch sized for the first case dies partway through, on whichever recipient happens to be new — and the failures are scattered, not adjacent. The error surfaces as OUT_OF_ENERGY, which reads like an infrastructure fault but is a budgeting one.
tron-fee resolves the correct number with a read-only call before anything is broadcast.
Install
npm install tron-fee # library
npx tron-fee --help # CLI, no installCLI
tron-fee <recipient> estimate a USDT transfer to <recipient>
tron-fee <recipient> --from <sender> subtract the sender's available energy
tron-fee --fee print getEnergyFee (SUN per energy unit)| Option | What it does |
|---|---|
| --from <address> | account for staked or delegated energy the sender already holds |
| --contract <address> | token contract (default: USDT) |
| --rent <sun> | compare the burn price against a market rate, in SUN per unit |
| --trx-usd <price> | add USD figures at this TRX price |
| --endpoint <url> | node to query (default: https://api.trongrid.io) |
| --api-key <key> | TRON-PRO-API-KEY header, if your endpoint wants one |
| --json | machine-readable output |
Library
import { estimate, getEnergyFee, TRANSFER_ENERGY } from 'tron-fee'
const fee = await getEnergyFee() // 100 (SUN per unit of energy)
const r = await estimate({
to: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
from: 'TYourSendingAddress...', // optional: subtracts available energy
rentSunPerUnit: 25.77, // optional: adds a comparison
trxUsd: 0.3274, // optional: adds USD figures
})
r.energy // 64285 or 130285
r.shortfall // energy the sender does not already have
r.burnTrx // what burning that shortfall costs
r.savingPct // 74.2Exports
| Export | Type | Purpose |
|---|---|---|
| estimate(opts) | async | full cost breakdown for one transfer |
| getEnergyFee(opts) | async | reads the getEnergyFee chain parameter, in SUN |
| getAvailableEnergy(addr, opts) | async | { limit, used, available } for an address |
| holdsToken(addr, opts) | async | whether an address already holds the token |
| addressToHex(addr) | sync | base58 → 20-byte hex, no network call |
| energyToTrx(energy, feeSun) | sync | the multiplication, in one place |
| TRANSFER_ENERGY | const | { existing: 64285, fresh: 130285 } |
| USDT_CONTRACT, SUN_PER_TRX | const | the obvious constants |
Preflight a payout batch
The pattern this library exists for: refuse to start rather than fail at transfer 91.
import { estimate } from 'tron-fee'
const recipients = [/* … */]
let totalTrx = 0
for (const to of recipients) {
const r = await estimate({ to, from: SENDER })
totalTrx += r.burnTrx
}
if (totalTrx > await trxBalanceOf(SENDER)) {
throw new Error(`batch needs ${totalTrx.toFixed(2)} TRX, sender cannot cover it`)
}One constant call per unique recipient and one parameter read per run. Against the alternative — a half-completed payout and a ledger that needs reconciling — it is the cheapest code in the integration.
Notes that will save you time
Cache getEnergyFee. It is a governance parameter, changed by vote rather than per block. Re-reading it per transaction wastes a round trip. Do not hardcode it either: code compiled against an old value under-estimates silently after a vote moves it.
Estimate per transfer, not per batch. Energy requirements do not usefully average out. One fresh address in a hundred changes that one transfer's requirement, not the batch's mean.
Subtract available energy. An address holding staked or delegated energy burns nothing until it runs out. Omitting that branch means your estimate is wrong for exactly the wallets that are configured correctly — pass from and the library handles it.
Non-USDT contracts. The 64,285 / 130,285 figures are the standard TRC-20 transfer profile. For unusual contracts, triggerconstantcontract returns an energy_used estimate for the exact call you intend to make; that is the general solution.
Where the numbers come from
Everything is read live from the chain:
getEnergyFee—POST /wallet/getchainparameters- recipient token balance —
POST /wallet/triggerconstantcontract,balanceOf(address), constant call - sender resources —
POST /wallet/getaccountresource
The figures in this README were measured on 2026-08-08: getEnergyFee 100 SUN, TRX $0.3274, best market rate for delegated energy 25.77 SUN per unit. Chain parameters move by governance vote; market rates move during the day. Run the tool rather than trusting the numbers above.
Why renting is cheaper than burning
Burning is the network's fallback when an address needs energy and has none. It is not a fee paid to anyone — the TRX is destroyed, at a rate set by the protocol with no discount and no volume tier.
Delegation is the same resource priced by a market: someone staked TRX, produced energy they are not using, and rents the surplus. On 2026-08-08 that market cleared at roughly a quarter of the burn rate.
The gap persists because burning is never chosen. It happens silently when the alternative is absent, so no buyer is ever in a position to decline it — and a price nobody declines does not converge.
A live comparison of the current market rate against the current burn rate is at stronara.com, which is maintained by the author of this library. The library itself is provider-neutral: pass whatever rate you like to --rent, or leave it out and it will only tell you what burning costs.
Testing
npm testTests are offline by design — CI should not go red because a public node had a bad minute.
Contributing
Issues and pull requests welcome. Useful directions: TRC-721 and TRC-1155 energy profiles, a batch preflight helper, and a --watch mode for the chain parameter.
Licence
MIT © 2026
