The origin tracer is paused. The library is open and still growing.What’s paused, and when it returns

← All articles

Internal Transactions

Part ofWho Controls a TRON Wallet? A Complete Guide to TRON Address Attribution

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.

Signed TriggerSmartContract SIGNATURE · FEE · ONE PARENT HASH [ RUNS ] Contract account caller_address OF EACH CHILD internal_transactions[ ] NO SIGNATURE · NO FEE [ FUNDS ] External account A transferTo_address External account B transferTo_address Watched wallet ONLY INFLOW KEY THE CONTRACT — NOT THE SIGNER — IS THE RECORDED SENDER OF EACH
One signed call, many internal effects — and the contract is the recorded sender on each.

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 transactionInternal transaction
Initiated byAn externally-owned account (a wallet)A contract, during execution
SignatureYes — proves the senderNone
Fee / resource costPays its own Bandwidth and EnergyNone; carried by the parent
Exists on-chain asA standalone transactionA 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.

Known parent hash gettransactioninfobyid NEEDS saveInternalTx · OFF BY DEFAULT Any address /v1/accounts/{addr}/internal-transactions TRONGRID · NO PARENT HASH NEEDED KEY [ RESOLVES ] [ INDEXES ] Internal transactions RECORDS INSIDE THE PARENT TX DIRECT LOOKUP BY AN INTERNAL HASH — NOT SUPPORTED
The node RPC resolves a parent you already have; the gateway indexes by address, so you don't need it.

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 wallet whose only inflow came through a contract looks unfunded to a naive read — and its origin gets missed.

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/callValueInfo fields, the call/create/suicide types, retrieval via gettransactioninfobyid, and the saveInternalTx requirement.
  • tronprotocol/protocol — core/Tron.proto — the InternalTransaction message and its presence as a repeated field inside TransactionInfo, 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 in core/contract/ or it does not exist.