Skip to main content
Every piece of data that is stored on the Thru network lives inside an account. The purpose of the blockchain is to keep the state of all of accounts up-to-date.

What is an Account?

An account is a container for data that has:
  • An address: Also known as its public key (or pubkey for short), this is a unique 32-byte identifier for the account.
  • Metadata: Information about the account’s state and properties. Important metadata fields include the owner, which is the only account allowed to modify the account’s data, and the data size, which specifies how much storage it takes up.
  • Data: The actual bytes stored in the account, which is allowed to be up to 16 MiB.
Conceptually, you can think of the Thru network as one giant map from 32-byte addresses to sequences of bytes.

Accounts and Programs

Accounts are only useful to us if they can be read and manipulated. Programs, or smart contracts, allow accounts to be updated. The logic of how accounts should be updated is written in code. Programs are themselves accounts, and their compiled code is stored in their account data. When executing, they are allowed to read from any account, and can write to the accounts that they own. For more information, see Programs.

Addresses

Every account is identified by a 32-byte address, which is a public key. There are two ways accounts get their addresses:
  • Keypair-derived accounts: You can think of these as being accounts that can be logged into. They are generated from a cryptographic keypair, which consists of a private and public key, where anyone with the private key can prove their identity, which can be verified with the public key.
  • Program-derived addresses (PDAs): These are addresses derived from a program’s address and a seed. These accounts have no private key—only the program can sign for them.

Ownership

Every account is owned by exactly one program, which is the program that created it. The owning program has exclusive write access to the account’s data. Other programs can read the account, but only the owner can modify it. Note that for keypair-derived accounts, the owner of the account is the system program.

Data

The data for an account is just a byte array. Programs interpret these bytes however they want; there is not an enforced schema. Common patterns include:
  • Fixed-size structs: Store a C struct directly in the account
  • Variable-size data: Use a header that indicates data layout
  • Nested accounts: Store references (addresses) to other accounts
/* Example: A simple account data structure */
struct user_profile {
  tn_pubkey_t owner;           /* 32 bytes */
  ulong       balance;         /* 8 bytes */
  uchar       username[32];    /* 32 bytes */
  /* Total: 72 bytes */
};
When you create an account, you specify its size. To store the user_profile struct above, you’d create an account with at least 72 bytes. Accounts can be resized later, but this requires the owning program’s permission.

Account Lifecycle

Creation

New accounts are created by programs using state proofs. A creation proof cryptographically demonstrates that an address is available—no account currently exists at that address.
/* Creating a new account with a creation proof */
tsys_account_create( account_index, seed, proof_data, proof_size );
Once created, an account exists on the blockchain and can be read or modified by programs.

Modification

The owning program can modify an account’s data at any time. Before writing to an account, the program must mark it as writable within the current transaction:
/* Make an account writable so we can modify it */
tsys_set_account_writable( account_index );

/* Now we can write to the account */
tsys_account_resize( account_index, new_size );

Compression

To save on-chain storage, accounts can be compressed. A compressed account’s data is removed from active validator storage and stored off-chain. The account still exists—its current state is committed to the blockchain through a Merkle tree—but it cannot be accessed until it’s decompressed. Compressing and decompressing accounts requires state proofs to maintain security guarantees.

Deletion

Programs can delete accounts they own. This removes the account from the blockchain entirely and frees up its storage.
/* Delete an account */
tsys_account_delete( account_index );

Compressed vs. Uncompressed Accounts

The Thru network distinguishes between compressed and uncompressed accounts: Uncompressed accounts are active and stored in validator memory. They can be accessed and modified immediately by any transaction. Compressed accounts are archived off-chain. Their state is cryptographically committed to the blockchain via a Merkle tree, but validators don’t store the full data. To use a compressed account, you must first decompress it by providing a state proof. Compression dramatically reduces storage costs for accounts that are accessed infrequently. For example, a user might compress their account when they’re not actively using the network, then decompress it months later when they want to make a transaction. See Account Compression and State Proofs for technical details on how compression works.

Accounts in Transactions

When you submit a transaction, you specify which accounts it will access. Each account is passed to the program by index:
/* Instruction data includes the account index */
struct instruction_args {
  ushort account_index;    /* Which account to operate on */
  /* ... other instruction data ... */
};
Programs access accounts through these indices. The runtime ensures that programs can only write to accounts they’re permitted to modify.

Working with Accounts in Programs

Here’s a simple example of creating and using an account in a program:
#include <thru-sdk/tn_sdk.h>
#include <thru-sdk/tn_sdk_syscall.h>

/* Account data structure */
struct counter_account {
  ulong count;
};

TSDK_ENTRYPOINT_FN void
start( uchar const * instruction_data, ulong instruction_data_sz ) {
  /* Parse instruction data (account index and creation proof) */
  ushort account_index = /* extracted from instruction_data */;
  uchar seed[TN_SEED_SIZE] = /* extracted from instruction_data */;
  uchar const * proof_data = /* extracted from instruction_data */;
  ulong proof_size = /* calculated from proof header */;

  /* Create the account */
  tsys_account_create( account_index, seed, proof_data, proof_size );

  /* Make it writable and set size */
  tsys_set_account_writable( account_index );
  tsys_account_resize( account_index, sizeof(struct counter_account) );

  /* Initialize the data */
  struct counter_account * account = tsdk_get_account_data( account_index );
  account->count = 0;

  tsdk_return( TSDK_SUCCESS );
}
Later transactions can read and modify the account’s count field, implementing a simple counter. For a more complete example, see Building a C Program.

Key Takeaways

  • Accounts are containers for data identified by 32-byte addresses
  • Programs own accounts and modify them
  • Account data is unstructured — programs define their own data layouts
  • Accounts can be compressed to reduce storage costs when not actively used
  • Transactions specify which accounts they access and programs use indices to reference them
Understanding these fundamentals will help you design efficient programs and manage state effectively on the Thru network.

Next Steps