Estimate a TRON USDT transfer fee before sending

Simulate USDT execution, read resource prices and budget TRX before sending, with a dated mainnet receipt sample.

By Triport Engineering.

Network
TRON mainnet
Authentication
x-token: $TRIPORT_API_KEY
Required scope
tron:rpc
Tier / rate limit
Trial and paid plans; separate ordinary-read and heavy-read budgets, listed below

What you need

Create a key in the console, or use your existing key with the required scope. A new key can use the 7-day free trial. Triport's TRON API is read-only: you sign and broadcast elsewhere. These requests simulate execution and read receipts; they do not send USDT.

Have the real sender, intended recipient and transfer amount ready. The sender must hold sufficient USDT for a successful simulation. Use the mainnet USDT contract TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t. A result for another token, sender or recipient is not interchangeable with your payment.

The protocol explanation is in TRON energy vs bandwidth. This guide calculates an API-based budget. Resource consumption, resource availability and TRX actually burned are three different quantities.

Step 1: Read the current resource prices

curl https://triport.io/rpc/tron/wallet/getchainparameters \
  -H "x-token: $TRIPORT_API_KEY" \
  -H 'Content-Type: application/json' -d '{}'


curl https://triport.io/public/tron \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_gasPrice","params":[]}'

The recorded mainnet parameter probe on 2026-10-25, approximately 12:00 UTC returned the following. They are dated observations, not application constants. The keyless eth_gasPrice read was independently repeated at 12:31 UTC on the same date: 0x64, or 100 sun per energy. That scalar does not report bandwidth prices or available resources.

ParameterRecorded valueMeaning
getEnergyFee100 sunBurn price per uncovered energy unit
getTransactionFee1,000 sunBurn price per bandwidth unit
getFreeNetLimit600Free bandwidth allowance for an activated account
getCreateAccountFee100,000 sunAccount-creation bandwidth fallback parameter
getCreateNewAccountFeeInSystemContract1,000,000 sunNative account-creation fee parameter

Read the returned chainParameter entries by key. Do not apply the last two fees automatically to a zero-USDT recipient: account activation and writing a token balance slot are different operations. See the parameter reference and resource payment rules (checked 2026-10-25).

Step 2: Simulate the transfer

Call triggerconstantcontract with function_selector: "transfer(address,uint256)". With visible: true, owner_address and contract_address are Base58. The ABI argument is different: remove the recipient's leading 41 from its hex-41 representation, then pad the remaining address to an ABI word. Do not put Base58 text or the 41 prefix into parameter. Append the amount as an unsigned ABI integer.

The encoding rules below follow the official ABI guide (checked 2026-10-25). The amount is in USDT's smallest units; the 1_000_000 example represents one USDT with its six decimal places.

// Run with Node.js built-in fetch. Provide the real inputs in environment variables.
const key = process.env.TRIPORT_API_KEY;
const owner = process.env.OWNER_BASE58;
const recipient = process.env.RECIPIENT_HEX41;
if (!key || !owner || !/^41[0-9a-f]{40}$/i.test(recipient ?? "")) {
  throw new Error("Set key, sender Base58 and recipient hex-41 address");
}
const amount = BigInt(process.env.AMOUNT_UNITS ?? "1000000");
if (amount <= 0n || amount >= 2n ** 256n) throw new Error("Invalid amount");
const parameter = recipient.slice(2).padStart(64, "0")
  + amount.toString(16).padStart(64, "0");


async function wallet(method, body) {
  const response = await fetch(`https://triport.io/rpc/tron/wallet/${method}`, {
    method: "POST",
    headers: { "x-token": key, "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  const data = await response.json();
  if (data.Error || data.error) throw new Error(JSON.stringify(data));
  return data;
}
const simulation = await wallet("triggerconstantcontract", {
  owner_address: owner,
  contract_address: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
  function_selector: "transfer(address,uint256)", parameter, visible: true,
});
// USDT returns an ABI boolean. REVERT and false returns are not valid estimates.
if (simulation.result?.result !== true || simulation.result?.message
    || !/^[0-9a-f]{64}$/i.test(simulation.constant_result?.[0] ?? "")
    || BigInt("0x" + simulation.constant_result[0]) !== 1n
    || !Number.isSafeInteger(simulation.energy_used) || simulation.energy_used <= 0) {
  throw new Error("Simulation failed or returned false; fix inputs and retry");
}
const energy = BigInt(simulation.energy_used);
console.log({ energy: String(energy), penaltyIncluded: simulation.energy_penalty });

A successful HTTP response is not proof of successful execution. A REVERT can stop early and produce a deceptively small energy count. energy_penalty is already included in energy_used; do not add it again. The recorded 2026-10-25 simulations returned 64,285 total / 49,635 penalty for an existing USDT holder, and 130,285 total / 100,635 penalty for a zero-USDT recipient. Those are historical examples; simulate your own inputs immediately before use.

Step 3: Turn it into TRX

For caller-paid energy with no deployer contribution:

energy burn (sun) = max(0, caller energy needed - usable energy) Ă— energy price
bandwidth burn (sun) = 0 if an eligible quota covers the entire transaction;
                       otherwise full charged size Ă— bandwidth price
TRX burned = (energy burn + bandwidth burn) / 1,000,000

Do not combine insufficient staked and free bandwidth quotas. The ordinary payment rule requires one source to cover the full transaction. On the recorded 2026-10-25 prices, full energy burn is 6.4285 TRX for 64,285 energy or 13.0285 TRX for 130,285. A 345-unit byte charge adds 0.345 TRX if no bandwidth quota covers it. Signed size can differ; 345 is the dated sample's ordinary transfer example, not a constant for every transaction.

Continue the preceding script to obtain an all-burn budget. The margin below is an example application policy; it is not a protocol requirement.

const parameters = await wallet("getchainparameters", {});
const prices = new Map(parameters.chainParameter.map(({ key, value }) => [key, value]));
const price = (name) => {
  const value = prices.get(name);
  if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`Bad ${name}`);
  return BigInt(value);
};
const energySun = energy * price("getEnergyFee");
const bytes = BigInt(process.env.CHARGED_BYTES ?? "345"); // replace with signed-size estimate
if (bytes <= 0n) throw new Error("Invalid charged size");
const allBurnSun = energySun + bytes * price("getTransactionFee");
const trx = (sun) => `${sun / 1000000n}.${String(sun % 1000000n).padStart(6, "0")}`;
const feeLimitSun = (energySun * 120n + 99n) / 100n; // chosen 20% margin
console.log({ allBurnTRX: trx(allBurnSun), suggestedFeeLimitSun: String(feeLimitSun) });

fee_limit caps the caller's energy budget, including resource-covered energy; bandwidth is separate. Check the network's current maximum before signing. Available resources can lower the actual burn, while state changes can invalidate a simulation. An all-burn budget does not guarantee execution or inclusion.

What real transfers paid (sample, 2026-10-25)

The recorded probe at approximately 11:55 UTC examined block 87,418,693: 76 USDT Transfer logs, then the first 40 distinct transaction hashes in log order. Each hash was read through gettransactioninfobyid. This is the original dated sample, not a claim of an independently repeated receipt measurement.

receipt.energy_usage_totalTransactions
64,28532
130,2857
220,8961

The outlier was not a simple transfer. These are transaction-wide receipts: filtering for a token event does not isolate that token's execution cost. 38 of 40 receipts had no energy burn; 2 had energy_fee > 0. 16 of 40 burned bandwidth; the remaining 24 used resource allowances. Receipts alone do not identify whether energy came from the sender's stake, delegation or another applicable contribution. One block, not a statistic.

To reproduce, query eth_getLogs for that exact block, mainnet USDT and the Transfer(address,address,uint256) topic. Deduplicate transaction hashes before taking the first 40. Strip the hash's 0x prefix for the wallet receipt request. Record receipt.energy_usage_total, receipt.energy_fee, receipt.net_usage and receipt.net_fee; missing fee fields represent zero in a successful receipt, while an empty receipt is missing data, not zero consumption. Repeat against a fresh fixed block and retain the block hash and collection time.

What this API cannot tell you

getaccountresource and estimateenergy are not served (404). An account's getaccount object is not a substitute for remaining EnergyLimit - EnergyUsed. Obtain current allowances through your wallet or a compatible resource endpoint; without them, report the all-burn scenario rather than a guaranteed fee.

Triport does not serve broadcasttransaction or /walletsolidity, and eth_sendRawTransaction is not served. Send through your own signing and broadcast path. A receipt read here is not a solidified-confirmation service.

Rate-limit budget for this flow

Configured budgets as documented on 2026-10-25, unmeasured:

Plantron_read_rpc RPStron_read_rpc_heavy RPS
7-day trial152
Basic205
Pro10020
Business25080

Simulation and log scans use the heavy bucket; parameters and receipts use ordinary reads. Cache parameters briefly, refresh before a payout batch and invalidate when governance changes them. Pace receipt collection and respect Retry-After on 429. See TRON limits for burst capacity and request accounting. These are configured key limits, not measured throughput.