Transaction
In the TON blockchain, any change to an account's state is recorded via a transaction. Unlike messages, transactions do not move, send, or receive anything. These terms are often confused, but it's important to understand that a transaction is simply a record of all changes that occurred to a specific account.
In this section, you’ll explore how a transaction is structured—how it progresses through each phase, how to retrieve transaction data using APIs, and how to determine whether an on‑chain event is successful.
Transaction structure
TL-B
Before diving into how transactions work in TON, we first need to understand their structure using TL-B(Type Language – Binary). It's worth noting that there are several types of transactions in TON; however, for this guide, we will focus solely on ordinary transaction. These are the transactions relevant for payment processing and the development of most applications built on TON.
trans_ord$0000 credit_first:Bool
storage_ph:(Maybe TrStoragePhase)
credit_ph:(Maybe TrCreditPhase)
compute_ph:TrComputePhase action:(Maybe ^TrActionPhase)
aborted:Bool bounce:(Maybe TrBouncePhase)
destroyed:Bool
= TransactionDescr;
According to the TL-B schema, a transaction consists of the following fields:
Field | Type | Description |
---|---|---|
credit_first | Bool | Indicates whether the credit phase should be executed first. This depends on whether the bounce flag is set. This will be explained in more detail in a later section. |
storage_ph | Maybe TrStoragePhase | The storage phase, responsible for handling fees related to the account's persistent storage. |
credit_ph | Maybe TrCreditPhase | The credit phase, responsible for processing the transfer of value delivered with the incoming message, if it's an internal message (types #1 or #2). |
compute_ph | TrComputePhase | The compute phase, responsible for executing the smart contract code stored in the account's code cell. |
action | Maybe ^TrActionPhase | The action phase, responsible for handling any actions generated during the compute phase. |
aborted | Bool | Indicates whether the transaction was aborted during one of the phases. If true , the transaction was not executed, and changes from the compute_ph and action phases were not applied. |
bounce | Maybe TrBouncePhase | TrBouncePhase The bounce phase, responsible for handling errors that occurred during the compute_ph or action phases. |
destroyed | Bool | Indicates whether the account was destroyed during the execution of the transaction. |
Other types of transactions—such as trans_storage, trans_tick_tock, trans_split_prepare, and others—are used for internal events that are invisible to end users. These include shard splitting and merging, tick-tock transactions, etc.
Since they are not relevant to DApp development, we will not cover them in this tutorial.
Credit phase
This phase is relatively small and straightforward. If you look at the blockchain source code, you’ll see that the main logic of this phase is to credit the contract’s balance with the remaining value from the incoming message.
credit_phase->credit = msg_balance_remaining;
if (!msg_balance_remaining.is_valid()) {
LOG(ERROR) << "cannot compute the amount to be credited in the credit phase of transaction";
return false;
}
// NB: msg_balance_remaining may be deducted from balance later during bounce phase
balance += msg_balance_remaining;
if (!balance.is_valid()) {
LOG(ERROR) << "cannot credit currency collection to account";
return false;
}
The credit phase is serialized in TL-B as follows:
tr_phase_credit$_ due_fees_collected:(Maybe Grams)
credit:CurrencyCollection = TrCreditPhase;
HThis phase consists of the following two fields:
Field | Type | Description |
---|---|---|
due_fees_collected | Maybe Grams | The amount of storage fees collected. This field is present if the account has no balance and has accumulated storage. |
credit | CurrencyCollection | The amount credited to the account as a result of receiving the incoming message. |
Storage phase
In this phase, the blockchain processes fees related to the account's persistent storage. Let's start by looking at the TL-B schema:
tr_phase_storage$_ storage_fees_collected:Grams
storage_fees_due:(Maybe Grams)
status_change:AccStatusChange
= TrStoragePhase;
This phase contains the following fields:
Field | Type | Description |
---|---|---|
storage_fees_collected | Grams | The amount of storage fees collected from the account. |
storage_fees_due | Maybe Grams | The amount of storage fees that were charged but could not be collected due to insufficient balance. This represents accumulated debt. |
status_change | AccStatusChange | The change in the account's status after the transaction is executed. |
The storage_fees_due
field is of type Maybe
because it is only present when the account has insufficient balance to cover the storage fees. When the account has enough funds, this field is omitted.
The AccStatusChange
field indicates whether the account's status changed during this phase. For example:
- If the debt exceeds 0.1 TON, the account becomes frozen.
- If the debt exceeds 1 TON, the account is deleted.
Compute phase
The compute phase is one of the most complex stages of a transaction. This is where the smart contract code, stored in the account’s state, is executed.
Unlike previous phases, the TL-B definition for the compute phase includes multiple variants.
tr_phase_compute_skipped$0 reason:ComputeSkipReason
= TrComputePhase;
tr_phase_compute_vm$1 success:Bool msg_state_used:Bool
account_activated:Bool gas_fees:Grams
^[ gas_used:(VarUInteger 7)
gas_limit:(VarUInteger 7) gas_credit:(Maybe (VarUInteger 3))
mode:int8 exit_code:int32 exit_arg:(Maybe int32)
vm_steps:uint32
vm_init_state_hash:bits256 vm_final_state_hash:bits256 ]
= TrComputePhase;
cskip_no_state$00 = ComputeSkipReason;
cskip_bad_state$01 = ComputeSkipReason;
cskip_no_gas$10 = ComputeSkipReason;
cskip_suspended$110 = ComputeSkipReason;
To start, note that the compute phase can be skipped entirely. In that case, the reason for skipping is explicitly recorded and can be one of the following:
Skip reason | Description |
---|---|
cskip_no_state | The smart contract has no state and, therefore, no code, so execution is not possible. |
cskip_bad_state | Raised in two cases: when the fixed_prefix_length field has an invalid value or when the StateInit provided in the incoming message does not match the account’s address. |
cskip_no_gas | The incoming message did not provide enough TON to cover the gas required to execute the smart contract. |
cskip_suspended | The account is frozen, so execution is disabled. This was used to freeze early miner accounts during the stabilization of TON’s tokenomics. |
The fixed_prefix_length
field can be used to specify a fixed prefix for the account address, ensuring that the account resides in a specific shard. This topic is outside the scope of this guide, but more information is available here.
Now that we've covered the reasons why the compute phase might be skipped, let's examine what happens when the smart contract code is executed. The following fields are used to describe the result:
Field | Type | Description |
---|---|---|
success | Bool | Indicates whether the compute phase was completed successfully. If false , any state changes made during this phase are discarded. |
msg_state_used , account_activated , mode , vm_init_state_hash , vm_final_state_hash | - | These fields are currently unused in the blockchain. They are always recorded as zero values. |
gas_fees | Grams | The amount of fees paid for executing the smart contract code. |
gas_used , gas_limit | VarUInteger | The actual amount of gas used and the maximum gas limit set for execution. |
gas_credit | Maybe (VarUInteger 3) | Used only in external messages. Since external messages cannot carry TON, a small gas credit is granted to allow the smart contract to start execution and decide whether it wants to continue using its balance. |
exit_code | int32 | The virtual machine exit code. A value of 0 or 1 (alternative success) indicates successful execution. Any other value means the contract code exited with an error—except in cases where the commit instruction was used. Note: for convenience, developers often refer to this as the smart contract exit code, though this isn’t technically accurate. |
exit_arg | Maybe int32 | The virtual machine threw an optional argument on failure. Useful for debugging smart contract errors. |
vm_steps | uint32 | The number of steps executed by the virtual machine during code execution. |
The commit
instruction is used to persist any changes made before it is called, even if an error occurs later in the same phase. These changes will only be rolled back if the Action phase fails.
Action phase
Once the smart contract code has finished executing, the Action phase begins. If any actions were created during the compute phase, they are processed at this stage.
There are precisely 4 types of actions in TON:
action_send_msg#0ec3c86d mode:(## 8)
out_msg:^(MessageRelaxed Any) = OutAction;
action_set_code#ad4de08e new_code:^Cell = OutAction;
action_reserve_currency#36e6b809 mode:(## 8)
currency:CurrencyCollection = OutAction;
libref_hash$0 lib_hash:bits256 = LibRef;
libref_ref$1 library:^Cell = LibRef;
action_change_library#26fa1dd4 mode:(## 7)
libref:LibRef = OutAction;
Type | Description |
---|---|
action_send_msg | Sends a message. |
action_set_code | Updates the smart contract’s code. |
action_reserve_currency | Reserves a portion of the account’s balance. This is especially useful for gas management. |
action_change_library | Changes the library used by the smart contract. |
These actions are executed in the order in which they were created during code execution. A total of up to 255 actions can be made.
Next, let’s examine the TL-B schema, which defines the structure of the action phase.
tr_phase_action$_ success:Bool valid:Bool no_funds:Bool
status_change:AccStatusChange
total_fwd_fees:(Maybe Grams) total_action_fees:(Maybe Grams)
result_code:int32 result_arg:(Maybe int32) tot_actions:uint16
spec_actions:uint16 skipped_actions:uint16 msgs_created:uint16
action_list_hash:bits256 tot_msg_size:StorageUsed
= TrActionPhase;
The action phase includes the following fields:
Field | Type | Description |
---|---|---|
success | Bool | Indicates whether the action phase was successfully completed. If this value is false , all changes made during this phase are discarded. Changes made during the compute phase are also reverted. |
valid | Bool | Indicates whether the action phase was valid. If this value is false , it means that invalid actions were created during smart contract execution. Each action type has its validity criteria. |
no_funds | Bool | Indicates whether there were sufficient funds in the account to execute the actions. If false , the action phase was interrupted due to lack of funds. |
status_change | AccStatusChange | The change in account status after the action phase. Since account deletion happens through actions (via mode 32), this field may indicate whether the account was deleted. |
total_fwd_fees | Maybe Grams | The total amount of forwarding fees paid for messages created during the action phase. |
total_action_fees | Maybe Grams | The total amount of fees paid for executing actions. |
result_code | int32 | The result code of the action execution. A value of 0 means all actions were successfully completed. |
result_arg | Maybe int32 | An error message is returned in case of an error. Useful for debugging smart contract code. |
tot_actions | uint16 | Total number of actions created during smart contract execution. |
spec_actions | uint16 | A number of special actions (all except action_send_msg ). |
skipped_actions | uint16 | Number of actions skipped during smart contract execution. Refers to message sends that failed but had the ignore_errors flag (value 2) set. |
msgs_created | uint16 | Number of messages created during action execution. |
action_list_hash | bits256 | The hash of the action list. |
tot_msg_size | StorageUsed | Total size of the messages. |
Bounce phase
If the Compute phase or Action phase ends with an error, and the incoming message has the bounce
flag set, the system triggers the Bounce phase.
For the bounce phase to trigger due to an error in the action phase, the failed action must have flag 16 set, which enables bounce on error.
tr_phase_bounce_negfunds$00 = TrBouncePhase;
tr_phase_bounce_nofunds$01 msg_size:StorageUsed
req_fwd_fees:Grams = TrBouncePhase;
tr_phase_bounce_ok$1 msg_size:StorageUsed
msg_fees:Grams fwd_fees:Grams = TrBouncePhase;
The tr_phase_bounce_negfunds
type is not used in the current version of the blockchain. The other two types function as follows:
Type | Description |
---|---|
tr_phase_bounce_nofunds | Indicates that the account does not have enough funds to process the message that should be bounced back to the sender. |
tr_phase_bounce_ok | Indicates that the system successfully processes the bounce and sends the message back to the sender. |
In this phase, msg_fees
and fwd_fees
are calculated based on the total forwarding fee fwd_fees
for the message:
- One-third of the fee goes into
msg_fees
and is charged immediately. - The remaining two-thirds go into
fwd_fees
.
Full transaction body
Now that we've reviewed the transaction header and its description, we can look at what a complete transaction in TON looks like. First, examine the TL-B schema:
transaction$0111 account_addr:bits256 lt:uint64
prev_trans_hash:bits256 prev_trans_lt:uint64 now:uint32
outmsg_cnt:uint15
orig_status:AccountStatus end_status:AccountStatus
^[ in_msg:(Maybe ^(Message Any)) out_msgs:(HashmapE 15 ^(Message Any)) ]
total_fees:CurrencyCollection state_update:^(HASH_UPDATE Account)
description:^TransactionDescr = Transaction;
This schema shows that a transaction includes the following fields:
Field | Type | Description |
---|---|---|
account_addr | bits256 | The account's address to which the transaction belongs. |
lt | uint64 | Logical time of the transaction. |
prev_trans_hash | bits256 | The hash of the previous transaction executed on this account. |
prev_trans_lt | uint64 | Logical time of the previous transaction on this account. |
now | uint32 | Time the transaction is created, in Unix timestamp format. |
outmsg_cnt | uint15 | Number of outbound messages generated during transaction execution. |
orig_status | AccountStatus | Account status before the transaction. |
end_status | AccountStatus | Account status after the transaction. |
in_msg | Maybe ^(Message Any) | Incoming message processed during the transaction. For ordinary transactions, this field is always present. |
out_msgs | HashmapE 15 ^(Message Any) | Outgoing messages generated during the transaction. |
total_fees | CurrencyCollection | Total fees paid for executing the transaction. |
state_update | ^(HASH_UPDATE Account) | Contains the hash of the previous account state and the hash of the new state. |
description | ^TransactionDescr | Transaction description containing execution phase details. We covered this earlier. |
The orig_status
and end_status
fields indicate how the account state changes as a result of the transaction. There are 4 possible statuses:
acc_state_uninit$00 = AccountStatus;
acc_state_frozen$01 = AccountStatus;
acc_state_active$10 = AccountStatus;
acc_state_nonexist$11 = AccountStatus;