Skip to content

Transactions

View as Markdown

Transactions on the Thru blockchain are the core atomic unit of computation that a user can submit for execution. Each transaction represents an indivisible set of operations that either all succeed or all fail together.

A Thru transaction consists of the following components:

The transaction header contains critical metadata and parameters:

struct {
uint8_t transaction_version; // [0,1) bytes: Must be 0x01
uint8_t flags; // [1,2) bytes: Transaction flags
uint16_t readwrite_accounts_cnt; // [2,4) bytes: Number of writable accounts
uint16_t readonly_accounts_cnt; // [4,6) bytes: Number of readonly accounts
uint16_t instr_data_sz; // [6,8) bytes: Size of instruction data
uint32_t req_compute_units; // [8,12) bytes: Requested compute units
uint16_t req_state_units; // [12,14) bytes: Requested state units
uint16_t req_memory_units; // [14,16) bytes: Requested memory units
uint64_t fee; // [16,24) bytes: Transaction fee
uint64_t nonce; // [24,32) bytes: Transaction nonce
uint64_t start_slot; // [32,40) bytes: Earliest valid slot
uint32_t expiry_after; // [40,44) bytes: Slots until expiry
uint16_t chain_id; // [44,46) bytes: Chain identifier
uint16_t padding_0; // [46,48) bytes: Reserved padding
pubkey_t fee_payer_pubkey; // [48,80) bytes: Fee payer's public key
pubkey_t program_pubkey; // [80,112) bytes: Program's public key
}

At the end of and after the header, the transaction contains arrays of account addresses that will be accessed during execution. The account addresses are organized in a specific order:

  1. The fee payer account is always listed first in the transaction. This is also header field fee_payer_pubkey.
  2. The program account is always listed second in the transaction. This is header field program_pubkey.
  3. The writable accounts array follows, containing all accounts that the program may modify.
  4. The read-only accounts array comes last, containing all accounts that the program may read but not modify.

Following the account addresses is the instruction data section. This contains the raw data that will be passed to the program during execution. The size of this data is specified in the header’s instr_data_sz field.

For fee payer accounts that are being created or decompressed from a compressed state, an optional state proof may be included. This proof validates the account’s existence or non-existence in the state tree.

The transaction concludes with a 64-byte Ed25519 signature from the fee payer. This signature is placed at the very end of the transaction data, after all other components including optional state proofs. The signature covers all transaction data preceding it (the “message”), ensuring the transaction’s integrity and authenticity.

The following shows the complete binary layout of a Thru transaction:

struct thru_transaction_layout {
// Transaction Header (112 bytes)
struct {
uint8_t transaction_version; // [0,1) bytes: Must be 0x01
uint8_t flags; // [1,2) bytes: Transaction flags
uint16_t readwrite_accounts_cnt; // [2,4) bytes: Number of writable accounts
uint16_t readonly_accounts_cnt; // [4,6) bytes: Number of readonly accounts
uint16_t instr_data_sz; // [6,8) bytes: Size of instruction data
uint32_t req_compute_units; // [8,12) bytes: Requested compute units
uint16_t req_state_units; // [12,14) bytes: Requested state units
uint16_t req_memory_units; // [14,16) bytes: Requested memory units
uint64_t fee; // [16,24) bytes: Transaction fee
uint64_t nonce; // [24,32) bytes: Transaction nonce
uint64_t start_slot; // [32,40) bytes: Earliest valid slot
uint32_t expiry_after; // [40,44) bytes: Slots until expiry
uint16_t chain_id; // [44,46) bytes: Chain identifier
uint16_t padding_0; // [46,48) bytes: Reserved padding
uint8_t fee_payer_pubkey[32]; // [48,80) bytes: Fee payer's public key
uint8_t program_pubkey[32]; // [80,112) bytes: Program's public key
} header;
// Account Addresses (variable size)
uint8_t readwrite_accounts[readwrite_accounts_cnt][32]; // Writable account addresses
uint8_t readonly_accounts[readonly_accounts_cnt][32]; // Read-only account addresses
// Instruction Data (variable size)
uint8_t instruction_data[instr_data_sz]; // Program instruction data
// Optional State Proof (if TN_TXN_FLAG_HAS_FEE_PAYER_PROOF flag is set)
struct {
struct {
uint64_t type_slot; // [high 2 bits: proof type, low 62 bits: slot]
uint8_t path_bitset[32]; // Bitset indicating proof path indices
} proof_header;
// Variable proof body based on type:
// For CREATION (type=2): existing_leaf_pubkey[32] + existing_leaf_hash[32] + sibling_hashes[]
// For EXISTING (type=0): sibling_hashes[]
// For UPDATING (type=1): existing_leaf_hash[32] + sibling_hashes[]
uint8_t proof_body[]; // byte length = (proof_type + popcount(path_bitset)) * sizeof(fd_hash_t)
} fee_payer_state_proof;
// Optional Account Metadata (only for EXISTING state proof type)
struct {
uint16_t magic; // [0,2) bytes: Magic number (0xC7A3)
uint8_t version; // [2,3) bytes: Account version (0x00)
uint8_t flags; // [3,4) bytes: Account flags
uint32_t data_sz; // [4,8) bytes: Account data size
uint64_t seq; // [8,16) bytes: State counter
uint8_t owner[32]; // [16,48) bytes: Account owner pubkey
uint64_t balance; // [48,56) bytes: Account balance
uint64_t nonce; // [56,64) bytes: Account nonce
} fee_payer_account_meta;
// Transaction Signature (always at the end, 64 bytes)
uint8_t fee_payer_signature[64]; // Ed25519 signature covering all preceding data
};

The following table describes each field in the transaction structure:

FieldOffsetSizeTypeDescription
Transaction Header
transaction_version01 byteuint8Transaction format version, must be 0x01
flags11 byteuint8Transaction flags (bit 0: has fee payer proof, bits 1-7: reserved)
readwrite_accounts_cnt2-32 bytesuint16Number of accounts that can be modified by the program
readonly_accounts_cnt4-52 bytesuint16Number of accounts that can only be read by the program
instr_data_sz6-72 bytesuint16Size in bytes of the instruction data section
req_compute_units8-114 bytesuint32Maximum compute units the transaction may consume
req_state_units12-132 bytesuint16Maximum state units the transaction may consume
req_memory_units14-152 bytesuint16Maximum memory units the transaction may consume
fee16-238 bytesuint64Transaction fee in native tokens
nonce24-318 bytesuint64Transaction nonce, must match fee payer’s current nonce
start_slot32-398 bytesuint64Earliest slot when transaction becomes valid
expiry_after40-434 bytesuint32Number of slots after start_slot when transaction expires
chain_id44-452 bytesuint16Chain identifier to prevent cross-chain replay attacks
padding_046-472 bytesuint16Reserved padding, must be zero
fee_payer_pubkey48-7932 bytesEd25519 pubkeyPublic key of the account paying transaction fees
program_pubkey80-11132 bytesEd25519 pubkeyPublic key of the program to execute
Account Addresses
readwrite_accounts112+32×N bytesEd25519 pubkey[]Array of writable account addresses (sorted ascending)
readonly_accountsVariable32×M bytesEd25519 pubkey[]Array of read-only account addresses (sorted ascending)
Instruction Data
instruction_dataVariableVariableuint8[]Raw data passed to the program during execution
Optional State Proof
type_slotVariable8 bytesuint64Proof type (bits 62-63) and slot number (bits 0-61)
path_bitsetVariable32 bytesuint8[32]Bitset indicating which indices have sibling hashes
proof_bodyVariableVariableuint8[]Variable proof data based on type and path_bitset
Optional Account Metadata
magicVariable2 bytesuint16Magic number identifying account metadata (0xC7A3)
versionVariable1 byteuint8Account metadata version (0x00)
flagsVariable1 byteuint8Account flags (program, privileged, compressed, etc.)
data_szVariable4 bytesuint32Size of account data in bytes
seqVariable8 bytesuint64Account state modification counter
ownerVariable32 bytesEd25519 pubkeyPublic key of the program that owns this account
balanceVariable8 bytesuint64Account balance in native tokens
nonceVariable8 bytesuint64Account nonce for transaction ordering
Transaction Signature
fee_payer_signatureLast 64 bytes64 bytesEd25519 signatureCryptographic signature from the fee payer covering all preceding transaction data

The flags field at byte offset 1 controls optional transaction features. Currently, only one flag is defined:

FlagBit PositionValueDescription
TN_TXN_FLAG_HAS_FEE_PAYER_PROOF00x01Indicates that the transaction includes a state proof for the fee payer account. When this flag is set, a state proof must be included at the end of the transaction after the instruction data.

All other bits in the flags field must be set to 0. The transaction parser will reject any transaction with unknown flags set to ensure forward compatibility.

The Thru blockchain enforces several limits on transaction structure and size:

  • The maximum transaction size is 32KiB.
  • Each transaction can reference at most 1024 accounts.
  • All account addresses must be exactly 32 bytes in length.

A valid transaction is one that is includable in the chain. A block is itself unincludable if it contains an invalid transaction. A transaction must meet the following criteria to be considered valid.

  • The transaction size must not exceed the maximum transmission unit (MTU) of 32KiB.
  • The transaction version must be exactly 0x01.
  • All fields in the transaction header must be properly formatted and aligned.
  • The account arrays must contain the correct number of accounts as specified in the header.
  • The total number of accounts referenced must not exceed 1024.
  • The fee payer’s Ed25519 signature must be cryptographically valid.
  • The signature is located at the end of the transaction (last 64 bytes).
  • The signature must cover all transaction data preceding it (the “message”), which includes the header, account addresses, instruction data, and optional state proofs.
  • No duplicate accounts are allowed anywhere in the transaction.
  • Writable accounts must be sorted in ascending lexicographic order.
  • Read-only accounts must be sorted in ascending lexicographic order.