Common Tron transaction errors and what they actually mean
A field guide to the error codes you see when a Tron transaction fails — OUT_OF_ENERGY, REVERT, TAPOS_ERROR, CONTRACT_VALIDATE_ERROR — what each one is telling you and how to fix it.
A Tron transaction can fail in two very different places, and the difference matters for debugging. Either the network refuses to accept it (a validation error, returned synchronously on broadcast) or it's accepted, included in a block, and then fails during execution (an execution error, recorded in the receipt). The error names are different in each case, and they fail differently in your wallet.
This is a field guide to the ones you'll actually encounter — what they're saying, why they happen, and how to fix them.
The two-stage error model
Every transaction goes through validation before it's broadcast to the network, and then through execution once it lands in a block. The same transaction can clear validation cleanly and still fail execution — that's the case where a tx exists on-chain but didn't do anything.
| Stage | What it checks | Where you see it |
|---|---|---|
| Validation | Signature, account exists, balance, resource quota, format | Synchronous error from your wallet / RPC node |
| Execution | Contract bytecode runs to completion or reverts | contractRet field in the transaction receipt |
Validation errors never leave a record on chain. Execution errors do — and they still burn the resources you consumed up to the point of failure. This is the most common confusion: a failed contract call still costs you energy.
Validation errors (rejected before broadcast)
These are returned by the node's BroadcastTransaction RPC. They mean
nothing made it into a block, and you weren't charged. The error codes come from
Tron's protobuf and you'll see them verbatim in wallet error messages.
CONTRACT_VALIDATE_ERROR
The most generic validation failure. The transaction's contract payload didn't pass pre-flight checks. The accompanying message string is the actual signal — read it. Common variants:
Validate TransferContract error, balance is not sufficient— your TRX balance is below the amount you tried to send (or below the burn fee, if you have no free bandwidth and no stake).Account does not exist— Tron requires a destination account to be activated. If the receiver address has never been touched on-chain, the first deposit must include the 1 TRX activation fee. Most wallets handle this transparently; you'll only see it if you're crafting transactions manually.Validate transferAsset error, asset balance is not sufficient— same as the TRX case but for a TRC10 token.Invalid permission_id— you're trying to use a multi-sig permission that doesn't exist or doesn't grant the contract type you're signing.
CONTRACT_EXE_ERROR
Mid-execution validation — usually means the smart-contract preconditions are wrong. For example, calling an admin-only method from a non-admin address often surfaces here rather than as a REVERT, depending on the contract. Read the message.
BANDWITH_ERROR
Yes, that's the actual spelling in Tron's protobuf — BANDWITH, missing the
D. It means you didn't have enough bandwidth (free quota + staked + burn-available) to
broadcast the transaction. Either wait for the daily 1,500-point free refill, stake a small amount for daily
bandwidth, or hold enough TRX in your account that the network can burn it for the byte
fee.
TAPOS_ERROR
TAPOS = Transaction As Proof Of Stake. Every Tron transaction references a recent
block hash to prove the signer saw recent state. The reference block is encoded in ref_block_bytes and ref_block_hash. If your transaction
references a block that's been orphaned (or one that's too old), validation fails.
In practice this means: the transaction was crafted with a stale reference and sat around too long before being broadcast. Re-sign it with a current block reference and try again. Most wallets handle this automatically by using the latest block at signing time.
TRANSACTION_EXPIRATION_ERROR
Tron transactions carry an expiration timestamp — typically 60 seconds
after signing. If you submit one past expiration, the node rejects it. This happens
when a wallet queues a signed tx for offline broadcast and the user comes back too
late, or when network congestion delays propagation past the deadline. Re-sign and
resubmit.
DUP_TRANSACTION_ERROR
The network has already seen this exact transaction. Common cause: your wallet retried because the first broadcast appeared to fail (e.g. timeout), but it actually succeeded. Before resending, search the transactions feed or your address page for the hash — the original is almost always there.
TOO_BIG_TRANSACTION_ERROR
Transactions can't exceed ~500KB. You'll only hit this if you're deploying a large contract or batching a huge number of operations into one transaction. Split into smaller transactions.
SIGERROR
Signature verification failed. Either the signature was malformed, signed with the
wrong key for the claimed sender, or the transaction hash you signed doesn't match
the bytes you submitted. With a normal wallet this is rare; if you're crafting
transactions manually, check that you're hashing the canonical serialized raw_data, not the wrapped transaction.
Execution errors (failed after inclusion)
These show up in the receipt as the contractRet field, alongside the
energy and bandwidth that were consumed. The transaction exists on chain, your
resources are spent, and the contract state changes were rolled back.
OUT_OF_ENERGY
The most common execution failure. Your transaction didn't allocate enough energy
(via stake, rent, or fee_limit burn) to complete the contract call.
Tron consumed everything you gave it and aborted the execution mid-flight.
The fix is usually one of:
- Raise
fee_limit— it caps how much TRX the network is allowed to burn for energy. If the cap is too low, the call dies early. Most wallets default to something like 100 TRX, which is plenty for transfers but tight for some DeFi operations. - Make sure you have the energy. Stake for it, rent it on an energy market, or hold enough TRX to let the protocol burn for it.
- Check the operation's actual cost against the live energy cost calculator — a USDT transfer is ~31k, a SunSwap swap is ~130k, a fresh contract deployment can be 500k+.
REVERT
The contract explicitly aborted with a REVERT opcode. This is the
contract telling you "your call didn't pass my preconditions" — not a network failure.
Tron-VM is EVM-compatible, so this is the same opcode Solidity emits for failed require(), revert(), and unmet modifier checks.
Many contracts include a reason string in the revert, which gets surfaced in the
receipt's result field. Common messages on TRC20 contracts:
ERC20: transfer amount exceeds balance— sender doesn't hold what they're trying to move.ERC20: transfer amount exceeds allowance— you're using atransferFrompath without sufficientapprove()first. Run an approve.ERC20: transfer to the zero address— recipient is0x0, almost always a frontend bug.blockedorblacklisted— sender or recipient is on the token's blocklist. USDT-TRC20 has Tether-controlled blacklisting; if either address is flagged, the contract refuses the transfer.
Energy is still consumed up to the revert point, but unused energy is refunded. Bandwidth is fully consumed.
OUT_OF_TIME
Smart contract execution has a per-block time budget — by default, a single contract
call can't run for more than ~50ms of VM time. Exceed it and the execution aborts
with OUT_OF_TIME. You'll see this on unbounded loops, recursive code, or
operations that fan out into many sub-calls.
If a contract you're calling routinely hits this, the contract has a design problem — there's no way to "raise the time limit" client-side. Either split the operation across multiple transactions or get the contract author to bound the work.
BAD_JUMP_DESTINATION
The EVM bytecode tried to jump to a non-jumpable location. In practice this only happens with corrupt bytecode, certain compiler bugs, or hand-written assembly that's wrong. If you're calling a popular contract, it's not you — something is off with the contract's state or arguments. Worth filing as a bug to the contract maintainers with the failing tx hash.
STACK_OVERFLOW / STACK_UNDERFLOW
Tron-VM (and EVM) has a stack depth limit of 1024 frames. Contracts that call each other recursively, or do excessive internal calls, can exhaust it. Common in flash-loan or proxy-heavy DeFi. The fix is on the contract side, not yours.
JVM_STACK_OVER_FLOW
Distinct from STACK_OVERFLOW — this is the Java VM that runs
the node hitting its stack limit while interpreting the smart contract. Rare. Usually
triggered by pathologically deep contract nesting. Same fix path as above.
TRANSFER_FAILED
Set when a contract called address.transfer() or address.send() as part of its execution, and that internal transfer
didn't succeed. The outer transaction reverts. Most common cause: the contract is
trying to send TRX to an address that can't accept it (e.g. a contract whose
fallback function reverts), or to an unactivated account without including the
activation fee.
ILLEGAL_OPERATION / UNKNOWN
Catch-alls for "the VM saw something it couldn't interpret." Like BAD_JUMP_DESTINATION, this is almost always a contract-side issue, not a
caller issue. Report with the tx hash.
Token-specific gotchas
Some failures don't fit cleanly into the validation/execution split because they're about how a specific contract behaves.
"I sent USDT but it failed"
Almost always one of:
- No TRX in the sender for energy. USDT-TRC20 transfers cost ~31k
energy. With no stake or rent, the protocol burns TRX to pay for it. If you're
holding USDT but no TRX, the transfer can't pay for itself and fails at validation
with
balance is not sufficient. Send some TRX to the address first. fee_limittoo low. If your wallet caps energy spending too aggressively, the tx fails withOUT_OF_ENERGY. Raise the cap.- Blacklisted address. Either side flagged by Tether. The receipt
will show
REVERTwith ablockedmessage.
"My swap on SunSwap reverted"
Usually slippage. Routers reject swaps where the output would be worse than your amountOutMin parameter — common during volatile periods. The revert
message is typically INSUFFICIENT_OUTPUT_AMOUNT or EXCESSIVE_INPUT_AMOUNT. Widen your slippage tolerance and retry. If it
keeps reverting at high slippage, the pool may be drained or paused.
"Approve transaction succeeded but transferFrom still fails"
Some legacy TRC20 contracts (including USDT for a long time) refuse to approve() from a non-zero allowance to another non-zero allowance — you
have to approve(0) first, then approve(new_value). If your transferFrom keeps
reverting with an allowance error despite a fresh approve, this is the cause. Reset
to zero, then set.
How to read a failed tx on Tron Goblin
Open the transaction in the tx feed or paste the hash directly. A failed tx will show:
- Status —
failed, with the protocol error code (e.g.OUT_OF_ENERGY) right next to it. - contractRet field — the raw VM result. Matches one of the strings from this post.
- result message (when present) — the revert reason string, if the contract emitted one. This is usually what tells you the actual problem.
- Energy used — how much energy the tx consumed before failing.
Useful for setting a better
fee_limiton the retry.
TL;DR cheatsheet
| Error | What to do |
|---|---|
OUT_OF_ENERGY | Raise fee_limit, stake, or rent energy. Check actual cost. |
REVERT | Read the message. Usually a contract precondition you didn't meet. |
OUT_OF_TIME | Contract execution too slow. Not fixable client-side. |
TAPOS_ERROR | Reference block stale. Re-sign with a current block. |
TRANSACTION_EXPIRATION_ERROR | Submitted too late after signing. Re-sign and resubmit. |
BANDWITH_ERROR | No bandwidth and no TRX to burn for it. Wait for daily refill or stake. |
CONTRACT_VALIDATE_ERROR | Read the message — usually balance, account, or permission issue. |
DUP_TRANSACTION_ERROR | Already broadcast. Search for the hash before retrying. |
SIGERROR | Signature doesn't match. Check what you're hashing. |
BAD_JUMP_DESTINATION, STACK_*, ILLEGAL_OPERATION | Contract-side bug. Report with the tx hash. |
The single most useful habit: when a tx fails, open it on chain and read the contractRet plus the result message before guessing. The
network is telling you what went wrong — almost always literally.