Key takeaways
- An internal transaction is value a contract moves during execution — a record tucked inside its parent transaction, with no signature, no fee, and no standalone existence on-chain.
- The real money in a DeFi call lives here. A top-level read tells you a wallet hit a contract; it can't tell you the contract then paid out five accounts.
- They're hard to see on purpose: nodes don't index them by default, and you can only fetch them through the parent hash — or a gateway that indexes by address.
- The recorded sender of internal value is the contract, not the person who triggered the call — which is why attribution gets slippery here.
A top-level transaction shows a wallet calling a contract. It rarely shows where the money went. When a contract runs, it can move TRX and tokens on its own — paying out a swap, releasing collateral, forwarding a deposit — and those movements never hit the chain as separate transactions. They’re internal transactions: effects of the parent call, recorded inside it, and invisible to anyone reading only the top-level transfer list. For tracing DeFi fund flows that gap is most of the problem. This article covers what these records are, why they lack the defining features of a normal transaction, and where to actually find them.
What an internal transaction is
TRON’s docs put it 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 “transactions triggered in the TVM by contract accounts.”
The operative words are “by contract accounts.” A normal transaction starts at a person’s wallet; an internal one starts at a contract, mid-execution, because the code it’s running told it to. The reference is blunt about scope: internal transactions “represent all transactions happened in a smart contract call.”
There’s no separate object to point at. An internal transaction 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 info, in a repeated internal_transactions field. It exists only as something the parent TriggerSmartContract did while it ran.
Two things about that record mislead people, and both are worth fixing before you rely on it.
note: "create" does not mean an account was created. The three values are TVM operation categories, not outcomes: create is contract deployment (a CREATE or CREATE2 opcode), and suicide is SELFDESTRUCT. An internal transfer that brings a wallet into existence is an ordinary value-bearing call and carries note: "call" like any other. Filtering on create to find account creations returns deployments and misses every activation.
An internal transfer can activate an account. A contract sending TRX or a TRC-10 token to an address that doesn’t yet exist creates it — see Account Activation for the fee mechanics. That makes transferTo_address the field that matters: match it against the address you’re investigating, and the parent transaction tells you both which contract moved the value and which wallet signed for the call.
Why it doesn’t behave like a transaction
An internal transaction is an effect of execution that no person submitted, so it lacks the two features that define a top-level one.
No signature. A normal transaction carries the sender’s signature — the cryptographic proof that it could only have come from whoever holds the private key. An internal transaction has nothing like it. The authorization lives one level up: the outer call was signed, and the contract acted because that call told it to.
No fee. All the Energy for a contract execution is metered on the parent and billed to the calling account, the mechanics of which belong to smart contracts and the TVM. The internal record holds the value moved and the parties involved, but no resource accounting. The cost sits 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; 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: they don’t show up where you’d reach for them.
An internal transaction is addressable only through its parent. Call gettransactioninfobyid with the outer hash and the internal_transactions field comes back — but, the docs warn, “viewing internal transactions directly through the hash of internal transactions is not supported.” You find them only if you already know the outer transaction.
It gets worse: a node may not hold 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 anything that passed through.
The way around the outer-hash requirement is a gateway with its own index. TronGrid exposes an address-indexed endpoint, /v1/accounts/{address}/internal-transactions, listing the internal transactions touching a given address with the usual direction and time filters. As a free public gateway, it’s the practical way to ask “what flowed in and out of this wallet through contracts” without first knowing every parent transaction. In practice you end up using both routes; the workflow around them belongs to reading a TRON address.
Why the real flow lives here
A top-level TriggerSmartContract tells you a wallet called a contract. It does not tell you the contract then sent TRX to five external accounts — that fan-out is all internal. For any interaction where a contract custodies and redistributes value — a DEX swap, a bridge deposit, a lending withdrawal — the movement that matters is internal.
It also bends attribution. When a contract moves value to an address, the party recorded as the sender is the contract — the internal transaction’s caller_address. The externally-owned account that triggered the outer call appears only on the parent. A wallet funded this way reads, on the record, as funded by a contract, even though a person set the whole thing in motion one layer up.
A blind spot any trace must close
Funds sent through a contract’s internal calls may never appear in the standard transaction history. A wallet financed that way reads as unfunded until someone decodes the internal effects of the calls it was involved in, and its origin slips past in the meantime.
There is a weight-of-evidence point here too. Internal-transaction funding is weaker evidence than a direct transfer: the human originator is one layer removed, a contract mediated the value, so the person behind it stays less certain than when one wallet pays another outright. Read it as corroborating evidence, not proof. That caution fits the whole category — internal transactions show where value actually moved, and a contract standing between two parties is where intent is hardest to read.
Sources
- Internal transactions — TRON Developer Hub — the live home of the topic: internal transactions as TRX moves triggered inside contract execution, the addressing constraint that they are reached through the outer transaction, and the node setting that must be enabled before a node records them. TRON split this page off the Transactions page and rewrote it; the wording quoted in the body belongs to the combined page, cited from the archive below.
- Transactions — TRON Developer Hub, archived 2026-04-10 — the revision carrying the wording quoted in the body: internal transactions as “transactions triggered in the TVM by contract accounts,” the constraint that “viewing internal transactions directly through the hash of internal transactions is not supported,” and the default that “The TRON node does not save internal transaction information by default, and needs to be manually enabled through the node configuration file”.
- 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.
- smart_contract.proto — tronprotocol/protocol — the protocol definition of
TriggerSmartContract(owner_address,contract_address,call_value,data,call_token_value,token_id) — the message type this chapter names. Protocol-authoritative: a contract type is either declared incore/contract/or it does not exist.