A top-level transaction shows a wallet calling a contract. It rarely shows where the money went. When a contract executes, it can move TRX and tokens of its own accord — paying out a swap, releasing collateral, forwarding a deposit — and those movements are not separate transactions on the chain. They are internal transactions: effects of the parent call, recorded inside it, and invisible to anyone reading only the top-level transfer list.
For tracing fund flows through DeFi, this is the difference between seeing that an interaction happened and seeing what it did. This article covers what an internal transaction is, why it behaves nothing like a signed transaction, how to find them, and why the real value movement of a contract call lives here.
What an internal transaction is
TRON’s documentation defines them precisely: “during the execution of smart contract transactions, the contract may trigger other contract method invocation, or transfer TRX/TRC10 tokens to external accounts, or it may also perform operations such as staking, voting, resource delegating. Such transactions that occur during a smart contract execution are called internal transactions.” They are, in the protocol’s words, “transactions triggered in the TVM by contract accounts.”
The key phrase is “triggered … by contract accounts.” A normal transaction is initiated by a person’s wallet. An internal transaction is initiated by a contract, mid-execution, as a consequence of the code it is running. The official reference puts it plainly: internal transactions “represent all transactions happened in a smart contract call.”
Structurally, an internal transaction is not a standalone object. It is a record — caller_address, transferTo_address, the value moved, and a type note of call, create, or suicide — carried inside the parent transaction’s information, in a repeated internal_transactions field. It has no independent existence on the chain. It exists only as something the parent TriggerSmartContract did while it ran.
Why it is not a transaction in the usual sense
Because an internal transaction is an effect of execution rather than a thing a person submitted, it lacks the two features that define a top-level transaction.
It has no signature. A normal transaction carries the sender’s signature, which “proves that the transaction could only have come from the sender and was not sent fraudulently.” An internal transaction has nothing of the kind. It was not signed and broadcast — it was emitted by contract code during a transaction that was signed. There is no separate authorization because there is no separate sender: the contract acted because it was told to by the call already signed at the top level.
It carries no fee of its own. All the Energy for a contract execution is metered on the parent transaction and billed to the account that called it — the mechanics of which belong to /learn/smart-contracts-and-the-tvm/. The internal-transaction record holds the value moved and the parties, but no resource accounting; the cost lives entirely on the parent.
| Top-level transaction | Internal transaction | |
|---|---|---|
| Initiated by | An externally-owned account (a wallet) | A contract, during execution |
| Signature | Yes — proves the sender | None |
| Fee / resource cost | Pays its own Bandwidth and Energy | None of its own; carried by the parent |
| Exists on-chain as | A standalone transaction | A record inside the parent’s transaction info |
Finding them
This is where internal transactions become an investigative problem rather than a curiosity: they do not appear where you would look for them.
An internal transaction is addressable only through its parent. Calling gettransactioninfobyid with the outer transaction’s hash returns the list of internal transactions in the internal_transactions field — but, as the documentation warns, “viewing internal transactions directly through the hash of internal transactions is not supported.” You can find them only if you already know the outer transaction.
Worse, a node may not have them at all: “the TRON node does not save internal transaction information by default, and needs to be manually enabled through the node configuration file” — the saveInternalTx setting. A node that ran with it off has no record of any internal transaction that passed through it. Whether a contract’s fund flows are even visible depends on whether the node you are querying chose to index them.
In practice, the route that sidesteps the outer-hash requirement is a gateway that maintains its own index. TronGrid exposes an address-indexed endpoint — /v1/accounts/{address}/internal-transactions — that lists the internal transactions touching a given address, with the usual direction and time filters. Because TronGrid is a free public gateway, this is the practical way to ask “what flowed into and out of this wallet through contracts” without first knowing every parent transaction. The two paths are complementary: the node RPC resolves a known parent into its internal effects; the gateway endpoint indexes by address. Reading a TRON address well, the subject of /learn/reading-tron-address/, means using both.
Why the real flow lives here
A top-level TriggerSmartContract tells you a wallet called a contract. It does not tell you that the contract then sent TRX to five external accounts — that fan-out is internal. For any interaction where a contract custodies and redistributes value — a DEX swap (/learn/dex-and-amm/), a bridge deposit (/learn/bridges/), a lending withdrawal — the value movement that matters is internal, and a top-level reading sees none of it.
This also bends attribution. When a contract moves value to an address, the party recorded as the sender is the contract — the caller_address of the internal transaction — not the externally-owned account that triggered the outer call. A wallet whose inflow arrived this way was funded by a contract on the record, even though a person set the whole thing in motion one layer up. The top-level owner_address and the entity that actually sent the value are not the same.
A blind spot, closed deliberately
TRONORIGIN treats internal transactions as a first-class funding signal precisely because top-level-only reading is a blind spot — funds sent via a contract’s internal calls may not appear in the standard transaction history. A wallet whose only inflow came through a contract would look unfunded to a naive reading, and its origin would be missed.
But the signal is weighted carefully. Internal-transaction funding earns credit weaker than a direct transfer, because the human originator is one layer removed: a contract mediated the value, so the person behind it is less certain than when one wallet pays another directly. It is corroborating evidence, not a confession. That caution is the right posture for the whole category — internal transactions reveal where value truly moved, while reminding you that a contract standing between two parties is exactly the place where intent gets harder to read.
Sources
- TRON Developer Hub — Transaction — the definition of internal transactions as transfers triggered in the TVM by contract accounts during execution, the “view only via the outer transaction” addressing constraint, and that nodes do not save them by default.
- tronprotocol/documentation — InternalTransaction — internal transactions as “all transactions happened in a smart contract call,” the
caller_address/transferTo_address/callValueInfofields, thecall/create/suicidetypes, retrieval viagettransactioninfobyid, and thesaveInternalTxrequirement. - tronprotocol/protocol —
core/Tron.proto— theInternalTransactionmessage and its presence as a repeated field insideTransactionInfo, the structural proof that an internal transaction has no standalone existence. - TronGrid — Get internal transactions by address — the address-indexed endpoint for listing the internal transactions touching an account.