← All articles

Transaction Types and What They Carry

A TRON transaction is not a generic envelope. It declares exactly one type, and that type — drawn from a fixed protocol enumeration — determines everything that follows: whether the transaction creates an account, whether it runs contract code, what it costs, and which keys are allowed to authorize it. Reading a transaction’s type is the first thing that tells you what it actually did.

This article walks the type taxonomy, the one transaction that explicitly creates an account, the often-conflated distinction between “system” and “smart” contracts, and the anatomy that lets a single transaction carry several signatures at once.

The type taxonomy

Every transaction names its type through the protocol’s ContractType enumeration — the authoritative list lives in TRON’s own protocol-buffer definition, Tron.proto. There are roughly forty types, and they sort into recognizable families:

FamilyExamples (type number)
Value transferTransferContract (1), TransferAssetContract (2)
Account lifecycleAccountCreateContract (0), AccountUpdateContract (10), AccountPermissionUpdateContract (46)
Resources / stakingFreezeBalanceV2Contract (54), DelegateResourceContract (57)
Consensus / governanceVoteWitnessContract (4), WitnessCreateContract (5), ProposalCreateContract (16)
TokensAssetIssueContract (6)
Smart contractsCreateSmartContract (30), TriggerSmartContract (31)

The numbers are not cosmetic — they are the runtime identity the protocol checks. A transaction’s raw_data.contract field declares its ContractType, and “the specific content within the raw_data.contract field varies depending on the transaction type.” The type tells the node which handler runs and which fields to expect.

[!WARNING] Type numbers are easy to misremember, and the protocol enum is the only authority. AccountCreateContract is type 0, not type 2 — type 2 is TransferAssetContract. When a number matters for attribution, check it against Tron.proto rather than secondary documentation.

Explicit creation: AccountCreateContract

Most new accounts on TRON come into existence implicitly — a TRX or TRC-10 transfer to an address that does not yet exist activates it as a side effect, the mechanism covered in /learn/account-activation/. But the protocol also has a transaction type whose entire purpose is to create an account, and nothing else: AccountCreateContract.

Its message is as direct as it gets. It carries exactly three fields: the owner_address doing the creating, the account_address being brought into existence, and an account type. That is the cleanest “who created whom” record the protocol produces — an explicit, single-purpose statement of creation, distinct from a transfer that happens to activate its recipient along the way.

Both paths cost the same 1 TRX account-creation fee, and both ultimately resolve to the same question an investigator cares about: which externally-owned account stood this address up. The explicit type makes the answer unambiguous.

System contracts and smart contracts

TRON’s documentation uses the phrase “system contract” in two different senses, and conflating them is the fastest way to misread the architecture.

The architecture sense. At the protocol level, “system contract” is the umbrella name for the implementation of every native transaction type. TRON’s own documentation lists transfers, TRC-10 transfers, staking, voting — and smart-contract creation and triggering — together: “We collectively refer to the implementation of these different transaction types as system contracts.” In this sense, even CreateSmartContract and TriggerSmartContract are system contracts, because they are handlers built into the node.

The operational sense. When the question is “what did this transaction do with value,” TRON’s exchange-integration guidance splits the types differently. It separates TransferContract and TransferAssetContract — labeled System Contract Type — from CreateSmartContract and TriggerSmartContract — labeled Smart Contract Type.

The distinction that matters operationally is where the value movement is legible. For a System Contract Type, the transfer is the transaction: sender, recipient, and amount are top-level fields you read directly. For a Smart Contract Type, the transaction is a call, and the real value flow happens inside execution — in internal transfers that a top-level reading never sees. Why that flow has to be decoded rather than read is the subject of /learn/smart-contracts-and-the-tvm/.

These two senses do not contradict each other. The architecture sense describes how the node is built; the operational sense describes how a transaction should be read. Holding both at once is what keeps “system contract” from sounding self-contradictory.

The anatomy of a transaction

Underneath the type, every transaction shares one structure. A Transaction is a raw_data payload, a signature array, and a result. The raw_data carries the type-specific contract, a reference to a recent block, an expiration, a timestamp, an optional fee limit, and an optional memo.

The reference block is an anti-replay binding. The ref_block_bytes and ref_block_hash fields point at a recent block, implementing what TRON calls TAPOS: “The reference block is used in the TRON TAPOS mechanism to prevent a replay of a transaction on forks that do not include the referenced block.” A transaction is cryptographically tied to a point in chain history — it cannot be lifted onto a fork that never contained the block it references.

Expiration bounds its lifetime. A transaction carries an expiration past which it can no longer be included in a block. For transactions built through the node API, that expiration is set automatically to the latest block’s timestamp plus 60 seconds — a narrow window that keeps stale transactions from lingering in the mempool.

The signature is the proof of authenticity — it “proves that the transaction could only have come from the sender and was not sent fraudulently.” And it is where the most consequential design choice hides: the signature field is a repeated one. A single transaction can carry more than one signature.

Type meets permission

That signature array is what makes multi-signature possible, and it is where transaction type and account permissions meet. A transaction names which permission authorizes it through a Permission_id field — “the default value is 0, indicating the owner permission.” Validity is then decided by quorum: “the corresponding operation is allowed only when the sum of the weights of the participating signatures exceeds the threshold.” Several parties can co-sign one transaction, and it executes only when their accumulated weight clears the bar.

The coupling runs deeper than authorization counts. A permission’s scope is defined over transaction types: each permission carries an operations bitmask in which a bit corresponds to a ContractType ID. A key can be authorized to sign TransferContract transactions but not AccountPermissionUpdateContract ones — the type of a transaction is checked against the bit for that type in the authorizing permission. The full permission model — keys, weights, thresholds, and what rotating them signals — is the subject of /learn/account-model/; here the point is narrower: the transaction is the carrier, naming its permission and bearing the signatures that satisfy it.

One more consequence falls out of the structure. Because Bandwidth is measured by a transaction’s byte size — “the raw_data of the transaction, the transaction signature, and the transaction result” — a transaction with more signatures is physically larger and costs more Bandwidth. Multi-signature security is not free; it is paid for in bytes, every time.

Sources