Skip to main content
This article describes the four possible states of an account on TON Blockchain. Understanding these states is crucial for accurately predicting transaction outcomes and ensuring the correct deployment.

What is the account status?

The account status is a formal indicator of what actions can occur with an account, what it can store, whether it contains a contract code or not. In other words, it is one of the main factors determining the behavior of a given account during a transaction. The account status at the beginning and end of a transaction is recorded in the corresponding TL-B block in the fields orig_status and end_status. Thus, it allows developers to always view the current status of an account before sending it a message and restore the history of its status changes.

Status variety

Each account exists in one of the following statuses:
  • nonexist: the default status for accounts with no transaction history or that were deleted. Contains no code, data, or balance. All 2256 accounts start in this status.
  • uninit: holds a balance and metadata, but no code and persistent data. It cannot execute logic but retains funds (and accumulate the storage fee) until the contract code is deployed.
  • active: contains code, data, and a balance. Fully deployed and operational, capable of processing messages.
  • frozen: occurs when the storage debt of an active account exceeds 0.1 TON. Only the hashes of the previous code and data cells are preserved. While frozen, the contract cannot execute. To unfreeze, send a message with the valid state_init and sufficient funds for storage fees. Recovery is complex; avoid reaching this state. A project to unfreeze accounts is available here.

Why exactly these four statuses?

Although the need for the active and nonexist statuses is obvious, the purpose of the uninit and frozen statuses is not immediately clear. nonexist vs uninit: As was mentioned above, each account starts in the nonexist status. Beside code and persistent data, in this status an account also has no metadata, so it does not accumulate the storage fee. At the same time, the uninit status of an account indicates that some actions were performed with it and, possibly, it is prepared for the deployment. So, the uninit account always has the positive balance and some addtional information for which it must pay storage fee. Sending an internal message with valid state_init and proper value to nonexist account results in its deployment and the status changes to active. The key point is that the deployment occurs during the compute phase, which requires a suitable number of nanotons to run. But often you want to be able to deploy an account through an external message, to which you can also attach state_init, but it is impossible to attach value! This is also the purpose for which uninit exists. You can initially transfer the balance to the account via an internal message, notifying the rest of the blockchain participants of your intention to deploy an account in the future. And only then, including through an external message, to deploy. frozen vs uninit: When the active account’s storage debt exceeds 0.1 TON, it becomes frozen or uninit. It is possible to restore the account from these statuses by paying off the debt and attaching state_init. If the account code and its persistent data have not changed during the lifetime of the account, then there is no problem restoring it from uninit. But what if they have changed? Since the uninit status does not allow you to store any information about account’s history, its last state will be lost forever. To prevent such situations, the frozen status exists. The main difference between the uninit and frozen statuses is that in addition to metadata, the frozen account contains hash of the last account state. Thus, it becomes possible to restore the last state of an account before it was frozen by sending a state_nint to it, whose hash matches the one recorded on the account.

Status transitions

We present here a diagram that describes all potential changes in the account status during the receipt of internal or external messages.

Diagram

In the diagram below, there are four nodes representing the four different account statuses. Each arrow and loop corresponds to a change in the account status at the end of a given transaction. The parameters in the blocks above the arrows (and loops) briefly describe what caused the transaction and also contain some fields that affect the change in the account status. alt text So, let’s look at what changes can occur to a nonexist account depending on the messages that come to it.
  • Receiving external messages: no changes.
  • Receiving internal messages:
    • With valid state_init and sufficient value: the contract is deployed on its address that becomes active before processing the message.
    • Without/with invalid state_init or with insufficient value: if the message is bounceable, then it returns to the sender, and the account status isn’t changed. Otherwise, with no value in the message, the account status isn’t changed. Finally, it becomes uninit if received a valid state_init but insufficient nanotons or if state_init is absent or invalid.
With the diagram Legend below, you can inspect all possible changes in the account status when receiving messages with different parameters.

Legend

  • type: message type.
    • any;
    • internal;
    • external;
  • bounce: bounce flag of an internal message.
    • any;
    • true;
    • false;
  • value: an amount of nanotons in a message.
    • any;
    • 0;
    • > 0.
  • StateInit: StateInit structure.
    • any;
    • none;
    • invalid: the address computed from a given state_init does not match the recipient address;
    • valid: the computed address matches a recipient address;
    • valid last state: must be state_init of the last successful transaction before the account change to frozen.
  • balance: The account balance in nanotons after Storage phase of the transaction.
    • 0;
    • > 0;
    • < 40000;
    • >= 40000;
    • (0, 40000).
  • storage_fees_due: the number of storage fees that were charged but could not be conducted. In the diagram this field indicates storage_fees_due after Storage phase.
    • 0;
    • < 0.1;
    • < 1;
    • >= 0.1;
    • >= 1.
  • send_dest_if_zero: is there any out-going message with flag 32 in Action phase?
    • any;
    • false;
    • true;
    • invalid: there was no Action phase.
  • zero_bal_after_dest_act: did the account balance become zero when sending some of messages with the flag 32? This field is meaningful only if there’s at least one such message during Action phase.
    • any;
    • false;
    • true.
  • action_phase_is_successful: was Action phase successful?
    • false;
    • true.
  • account_state_changed: has the account’s state changed during its lifetime?
    • false;
    • true.

Key points

We additionally review some important points regarding the statuses except nonexist. Sending to uninit account:
  • Messages of any type without state_init: changes to nonexist if its balance becomes zero.
  • Messages of any type with valid state_init: changes to active if the balance is at least 40000 nanotons.
Sending to frozen account:
  • Messages of any type: Changes to nonexist if its storage_fees_due exceeds 1 TON and the balance is zero.
  • Internal message with valid state_init (non-bounceable): changes to active if its storage_fees_due becomes zero.
  • Internal message with valid state_init (bounceable): changes to active with the same debt and the account balance equals message’s balance minus compute and action fees.
Sending to active account:
  • Messages of any type: changes to frozen if its storage_fees_due exceeds 0.1 TON and the account’s state has ever changed.
  • Messages of any type: changes to uninit if its storage_fees_due exceeds 0.1 TON and the account’s state has never changed.
  • Messages of any type: if in the action list there is an outgoing message with the flag 32 but Action phase was unsuccessful or the balance after this action is positive, the account status doesn’t change.
  • Messages of any type with any state_init: new state_init will be ignored and therefore doesn’t change the account status.
Deployment strategy: the standard practice for deploying a wallet is to first send a non-bounceable message with Toncoin to its address. This transitions the account to the uninit status. The wallet owner can then deploy the contract in a subsequent transaction, using the pre-funded balance. Protection against errors: standard wallets and applications manage these complexities by automatically setting the bounce flag based on the status of the destination account. Developers of custom applications must implement similar logic to prevent fund loss.

Summary

  • The account statuses (nonexist, uninit, active, frozen) defines behavior.
  • Correct handling of state_init and the bounce flag is crucial for successful deployment and avoiding unintended fund transfers.
  • There are many cases when the account status can become nonexist or frozen. Keep track of the amount of TON on the account balance!
  • Each new state_init is ignored when the account status is active.
I