Account Model
Accounts are the fundamental units of state in the Thru network. They serve as containers for data, code, and value, enabling programs to store persistent information and users to hold tokens. All network state is organized around accounts, from user wallets and program code to application data and system configuration.
Thru implements an account model that manages state, ownership, and data for all entities on the network. Each account is uniquely identified by a 32-byte public key and contains metadata and arbitrary data that programs can read and modify. See Account Addresses for more information about how account addresses are determined.
Account Structure
Section titled “Account Structure”Each account consists of two main components: runtime metadata that defines the account’s properties and permissions, followed by variable-length data that can be up to 16MB in size. The metadata contains essential information like ownership, balance, and behavioral flags, while the data section stores arbitrary information that programs can read and modify.
Account Metadata
Section titled “Account Metadata”On chain, the runtime stores a 64-byte internal metadata header. The public C SDK exposes a packed tsdk_account_meta_t view without the runtime-only magic field. This page uses the SDK-facing names unless otherwise noted.
version · uint8 · required
Account format version. Currently supports version 1 (TN_ACCOUNT_V1 = 0x01).
flags · uint8 · required
Bitfield controlling account behavior and permissions.
Account Flag Values
TSDK_ACCOUNT_FLAG_PROGRAM · 0x01
Marks the account as an executable program. Program accounts contain bytecode that can be executed by the Thru VM.
TSDK_ACCOUNT_FLAG_PRIVILEGED · 0x02
Indicates the account has special system privileges. Reserved for system contracts and core infrastructure.
TSDK_ACCOUNT_FLAG_UNCOMPRESSABLE · 0x04
Prevents the account from being compressed in state trees. Used for frequently accessed accounts.
TSDK_ACCOUNT_FLAG_EPHEMERAL · 0x08
Marks the account as temporary. Ephemeral accounts can be garbage collected when deleted.
TSDK_ACCOUNT_FLAG_DELETED · 0x10
Marks a deleted account record, not a compressed account. With TSDK_ACCOUNT_FLAG_NEW or TSDK_ACCOUNT_FLAG_EPHEMERAL, it identifies an inactive corpse retained until block seal for possible in-block revival. Plain DELETED records without either flag are legacy tombstones; current permanent deletion does not create them.
TSDK_ACCOUNT_FLAG_NEW · 0x20
Set at creation and cleared only by decompression, including the runtime’s fee-payer decompression path. Block finalization does not clear this flag. A created account remains NEW until it has been compressed and decompressed, regardless of its age. Non-NEW accounts include accounts that have been decompressed and genesis accounts provisioned without NEW.
TSDK_ACCOUNT_FLAG_COMPRESSED · 0x40
Indicates the account is stored in compressed state trees rather than active state.
TSDK_ACCOUNT_FLAG_RECEIVE_NATIVE · 0x80
Allows an account that is not owned by the EOA program to receive positive native transfers. Accounts start with this flag clear; only the owning program can set or clear it with account_set_flags. EOA-owned accounts receive native balance without it. The flag permits receipt only and grants no authority to spend the balance.
data_sz · uint32 · required
Size of the account’s data in bytes. Maximum value is 16MB (TN_ACCOUNT_DATA_SZ_MAX = 16,777,216).
seq · uint64 · required
Account sequence number that is incremented each time the account is modified during transaction execution. The sequence is updated at the end of the transaction for accounts that were modified.
owner · tn_pubkey_t · required
32-byte public key of the program that owns this account. Only the owner program can modify account data.
balance · uint64 · required
Account balance in the network’s native token. Used for transaction fees and transfers.
nonce · uint64 · required
Transaction nonce for replay protection. Must match the transaction nonce exactly for fee payer accounts. See Transaction Execution for more details.
Account Data
Section titled “Account Data”Following the 64-byte metadata header, accounts can store up to 16MB of arbitrary data that programs can read and modify through direct memory access in the VM.
struct __attribute__(( packed )) tsdk_account_meta { uchar version; /* Account format version */ uchar flags; /* Behavior flags bitfield */ uint data_sz; /* Data size in bytes */ ulong seq; /* Account sequence number */ tn_pubkey_t owner; /* Owner program public key */ ulong balance; /* Balance in native tokens */ ulong nonce; /* Transaction nonce */};Ownership Model
Section titled “Ownership Model”Program Ownership
Section titled “Program Ownership”Every account is owned by exactly one program, identified by the owner field
in the account metadata. Ownership is permanent and cannot be transferred.
Only the owner program can:
- Modify account data during transaction execution
- Change account metadata (except balance and nonce)
- Call
tsys_account_delete()to truncate a permanent account or delete a NEW or EPHEMERAL account
In almost all cases, the owner of an account is the program that created it. The exception is for accounts owned by the externally owned account (EOA) program, which may be created by any program.
Externally Owned Accounts
Section titled “Externally Owned Accounts”Externally owned accounts (EOAs), also known as user accounts, are owned by the externally owned account program,
which is located at the address 0 (all zeros). They can be accessed by using the private key
corresponding to their address. EOAs are created using tsys_account_create_eoa()
with signature verification to prove ownership of the private key.
Account Types
Section titled “Account Types”Thru supports two fundamental account types that determine their lifecycle and storage characteristics:
Purpose: Long-lived accounts for persistent network state. Permanent does not mean immune to deletion: a created account remains in the NEW/corpse class until it has been compressed and decompressed.
Characteristics:
- Default account type (no special flags required)
- Require state proofs for creation to prevent conflicts
- Remain live with empty data after deletion only when NEW is clear; creation sets NEW, and block finalization does not clear it
- Can be compressed to optimize storage
- Support all account flags and operations
Lifecycle:
- Created with
tsys_account_create()using state proofs, with NEW set - Stay in the NEW/corpse class until compressed and decompressed; deletion even in a much later block produces a corpse that is removed at block seal unless revived
- Decompression clears NEW. Deletion of a non-NEW permanent account then truncates its data without setting DELETED; the account remains live and active
Common Use:
- User wallets and token accounts
- Program accounts containing executable code
- Application data and state storage
- System contracts and infrastructure
Purpose: Temporary accounts designed for short-term operations and automatic cleanup.
Characteristics:
- Marked with
TSDK_ACCOUNT_FLAG_EPHEMERAL - No state proof required for creation
- Cannot hold funds
- Compressing an ephemeral account deletes it, and anyone can compress it
Lifecycle:
- Created with
tsys_account_create_ephemeral() - Deletion leaves an inactive corpse that can be revived in the same block; any remaining corpse is removed at block seal
Common Use:
- Storing data temporarily to be used in a later transaction
Account Purposes
Section titled “Account Purposes”While there are only two fundamental types, accounts serve various purposes based on their flags and ownership:
User Accounts (EOAs)
Purpose: Store user funds and serve as transaction fee payers.
Also known as: Externally Owned Accounts (EOAs)
Characteristics:
- Owned by the externally owned account program at address
0(all zeros) - Created with
tsys_account_create_eoa()using signature verification - Controlled by the holder of the private key corresponding to the account’s public key address
- Typically have no custom data (data_sz = 0)
- Used for balance transfers and fee payments
Program Accounts
Purpose: Store executable bytecode for smart contracts.
- Marked with
TSDK_ACCOUNT_FLAG_PROGRAM - Contain compiled program code in account data
- Upgradable by the configured program authority unless paused or finalized by policy
Data Accounts
Purpose: Store arbitrary application state and user data.
- Owned by specific programs that can modify them
- Most flexible storage option up to 16MB
- Used for application state, user profiles, game data
System Accounts
Purpose: Core network infrastructure and privileged operations.
- Marked with
TSDK_ACCOUNT_FLAG_PRIVILEGED - Special permissions for network-level operations
- Protected from normal program access
Account Limitations
Section titled “Account Limitations”Size Constraints
Section titled “Size Constraints”Maximum Data Size · 16MB
Each account can store at most 16,777,216 bytes of data (TN_ACCOUNT_DATA_SZ_MAX).
Access Permissions
Section titled “Access Permissions”Read Access
Any program can read account metadata and data from accounts included in the transaction.
Write Access
Only the owner program can modify account data. System operations can modify balance and nonce fields.
Creation Rights
New accounts can only be created by their designated owner program during transaction execution.
Account Lifecycle
Section titled “Account Lifecycle”Creation
Section titled “Creation”Programs create accounts using the system call interface:
-
Create Account
Use
tsys_account_create()ortsys_account_create_ephemeral()to create a new account:// Create a new account with state proofulong result = tsys_account_create(account_idx, seed, proof, proof_sz);// Create ephemeral account (no state proof required)ulong result = tsys_account_create_ephemeral(account_idx, seed); -
Set Initial Size
If the account needs data storage, resize it using
tsys_account_resize():ulong result = tsys_account_resize(account_idx, new_size);
Active State
Section titled “Active State”Accounts that exist in the current state can be:
- Read by any program in transactions that include them
- Modified by their owner program through direct memory access
- Transferred funds using
tsys_account_transfer() - Resized using
tsys_account_resize() - Used as data storage for applications
Compression
Section titled “Compression”Programs can compress accounts to optimize state storage using the compression syscalls:
// Compress an account with state proofulong result = tsys_account_compress(account_idx, proof, proof_sz);
// Decompress an account with metadata, data, and proofulong result = tsys_account_decompress(account_idx, meta, data, proof, proof_sz);Ephemeral Account Compression
For ephemeral accounts, compression behaves differently:
- Calling
tsys_account_compress()on an ephemeral account marks it DELETED and truncates its data instead of compressing it - Relaxed permissions: Any program can compress (delete) an ephemeral account if it’s writable in the transaction
- No ownership check: Unlike
tsys_account_delete(), compression doesn’t require the current program to own the account - No state proof is required for this deletion path; the inactive corpse remains until block seal unless revived in the same block
Deletion
Section titled “Deletion”Programs request deletion using tsys_account_delete():
/* Ordinary program-owned account; the EOA program requires a signature. */ulong result = tsys_account_delete( account_idx, NULL );The account must exist, have zero balance and nonce, and satisfy the syscall’s ownership and write-access requirements. The result depends on its flags:
| Account class | Result of deletion | Record lifecycle |
|---|---|---|
| Non-NEW, non-EPHEMERAL: includes decompressed accounts and genesis accounts provisioned without NEW | Truncates data to zero and clears RECEIVE_NATIVE without setting DELETED | Remains live, active, and persisted |
| NEW or EPHEMERAL: includes created accounts until they have been compressed and decompressed, regardless of age | Sets DELETED, truncates data to zero, and clears RECEIVE_NATIVE | Becomes an inactive, non-existing corpse retained until block seal for possible in-block revival |
A truncated permanent account still exists. Creation at the same address and decompression return TN_VM_ERR_SYSCALL_ACCOUNT_ALREADY_EXISTS (ALREADY_EXISTS), subject to their other preconditions. Deletion does not restore an old compressed preimage or make its proof usable again. The account can be resized or compressed subject to the usual permissions and compression rules. An EOA-owned account can still receive funds; any other account must be opted in to native receipt again first.
A deleted-class corpse retains metadata, not its old data. The matching creation syscall at the same address can revive it within the block under its normal checks, preserving sequence continuity: tsys_account_create() or tsys_account_create_eoa() for a non-ephemeral NEW|DELETED corpse, and tsys_account_create_ephemeral() for an EPHEMERAL|DELETED corpse. Ephemeral accounts use a different address derivation. Decompression cannot revive a corpse and returns TN_VM_ERR_SYSCALL_ACCOUNT_ALREADY_EXISTS. Corpses that remain deleted are physically removed at block seal, not after each transaction.
Compressed accounts are different: they are inactive and non-existing, and their live records are removed when saved. Decompression requires the account preimage and a valid state proof, and remains subject to compression cooldown and same-transaction restrictions.
Plain DELETED tombstones with neither NEW nor EPHEMERAL are a legacy shape, not the result of current permanent-delete calls. They remain persisted and active for state accounting, but do not exist for most account operations and are not corpse-sweep targets. The matching tsys_account_create() or tsys_account_create_eoa() call at the same address revives one in place without a creation proof, subject to the syscall’s other checks. Decompression rejects it with TN_VM_ERR_SYSCALL_ACCOUNT_ALREADY_EXISTS because it checks whether the record is active.
Account Operations
Section titled “Account Operations”Balance Transfers
Section titled “Balance Transfers”Programs can transfer funds between accounts using the transfer syscall:
// Transfer amount from one account to anotherulong result = tsys_account_transfer(from_account_idx, to_account_idx, amount);Flag Management
Section titled “Flag Management”Programs can modify account flags using the set flags syscall:
// Set account flags (owner program only)ulong result = tsys_account_set_flags(account_idx, flags);Making Accounts Writable
Section titled “Making Accounts Writable”Before modifying account data, programs must mark accounts as writable:
// Mark account as writable for current transactionulong result = tsys_set_account_data_writable(account_idx);