Transactions are the fundamental unit of state change in IOTA. They specify operations to perform, objects to modify, and are signed by the sender to authorize execution.
Transaction structure
An IOTA transaction consists of several key components:
pub struct Transaction {
// Transaction data
data : TransactionData ,
// Sender signature(s)
signatures : Vec < GenericSignature >,
}
pub struct TransactionData {
// Sender address
sender : IotaAddress ,
// Gas payment configuration
gas_data : GasData ,
// The kind of transaction
kind : TransactionKind ,
// Expiration epoch
expiration : TransactionExpiration ,
}
Gas data
Every transaction must specify gas payment:
pub struct GasData {
// Coins used to pay for gas
payment : Vec < ObjectRef >,
// Address receiving gas rebate
owner : IotaAddress ,
// Maximum gas units to spend
budget : u64 ,
// Price per gas unit
price : u64 ,
}
Gas price must be at least the reference gas price (RGP) set by validators. Setting a higher price doesn’t speed up transactions but affects validator rewards distribution.
Transaction kinds
IOTA supports several transaction types:
Programmable transaction
The most flexible and common transaction type:
pub struct ProgrammableTransaction {
// Input arguments (pure values or objects)
inputs : Vec < CallArg >,
// Commands to execute sequentially
commands : Vec < Command >,
}
Commands include:
MoveCall : Invoke a Move function
TransferObjects : Transfer objects to an address
SplitCoins : Split coins into multiple coins
MergeCoins : Merge multiple coins together
MakeMoveVec : Create a vector from objects
Publish : Publish a new Move package
Upgrade : Upgrade an existing package
System transactions
Special transactions executed by the system:
ChangeEpoch : Advance to the next epoch
ConsensusCommitPrologue : Mark the start of consensus commit
Authenticator state update : Update authenticator configuration
Randomness state update : Update randomness beacon
System transactions are generated by validators and don’t pay gas. User transactions cannot be system transactions.
Programmable transaction blocks
Programmable transactions enable chaining multiple operations atomically:
let mut builder = ProgrammableTransactionBuilder :: new ();
// Add pure inputs
let amount1 = builder . pure ( 1000 u64 ) ? ;
let amount2 = builder . pure ( 2000 u64 ) ? ;
let recipient = builder . pure ( recipient_address ) ? ;
// Add object inputs
let coin = builder . obj ( ObjectArg :: ImmOrOwnedObject ( coin_ref )) ? ;
// Split coin twice
let coin1 = builder . command ( Command :: SplitCoins ( coin , vec! [ amount1 ]));
let coin2 = builder . command ( Command :: SplitCoins ( coin , vec! [ amount2 ]));
// Transfer both new coins
builder . command ( Command :: TransferObjects ( vec! [ coin1 , coin2 ], recipient ));
let pt = builder . finish ();
Key features:
All commands execute atomically (all succeed or all fail)
Results from one command can be inputs to later commands
Reduces transaction costs compared to multiple separate transactions
Enables complex multi-step operations
Programmable transaction blocks are IOTA’s most powerful feature for building complex DeFi applications, NFT platforms, and other protocols.
Arguments and results
Commands can use different types of arguments:
pub enum Argument {
// Reference to a transaction input
Input ( u16 ),
// Result from a previous command
Result ( u16 ),
// Nested result (accessing tuple elements)
NestedResult ( u16 , u16 ),
}
Call arguments
pub enum CallArg {
// Pure serialized data (primitives, structs)
Pure ( Vec < u8 >),
// Object reference
Object ( ObjectArg ),
}
pub enum ObjectArg {
// Immutable or owned object
ImmOrOwnedObject ( ObjectRef ),
// Shared object
SharedObject {
id : ObjectID ,
initial_shared_version : SequenceNumber ,
mutable : bool ,
},
// Object being received
Receiving ( ObjectRef ),
}
Transaction lifecycle
Construction
Client constructs transaction data with commands, inputs, and gas configuration.
Signing
Sender signs the transaction with their private key. Multi-sig transactions require multiple signatures.
Submission
Transaction is submitted to a validator node (full node or validator).
Validation
Validator performs initial validation:
Signature verification
Gas budget sufficiency
Object existence and ownership
Transaction well-formedness
Sequencing
For owned objects: Transaction can execute immediately
For shared objects: Transaction must go through consensus
Execution
Validator executes the transaction:
Charges storage read costs
Executes each command in sequence
Updates object versions
Emits events
Computes gas costs
Effects
Transaction effects are produced, including:
Status (success or failure)
Modified objects
Created objects
Deleted objects
Events emitted
Gas cost summary
Certificate
Effects are signed by validators to create a certificate proving execution.
Finalization
Transaction is included in a consensus checkpoint, providing finality.
Transaction effects
After execution, IOTA produces detailed transaction effects:
pub struct TransactionEffects {
// Success or failure status
status : ExecutionStatus ,
// Epoch when executed
executed_epoch : EpochId ,
// Gas costs breakdown
gas_used : GasCostSummary ,
// Objects modified (with new versions)
modified_objects : Vec <( ObjectRef , Owner )>,
// Created objects
created_objects : Vec <( ObjectRef , Owner )>,
// Deleted objects
deleted_objects : Vec < ObjectRef >,
// Events emitted
events_digest : Option < TransactionEventsDigest >,
// Transactions this depends on
dependencies : Vec < TransactionDigest >,
}
Transaction effects are deterministic and signed by validators, providing cryptographic proof of execution results.
Execution status
Transactions can have different outcomes:
pub enum ExecutionStatus {
// Transaction succeeded
Success ,
// Transaction failed with an error
Failure {
error : ExecutionError ,
// Some commands may have succeeded before failure
command : Option < u16 >,
},
}
Common error types:
InsufficientGas : Ran out of gas budget
ObjectNotFound : Referenced object doesn’t exist
InvalidObjectVersion : Object version mismatch
MoveAbort : Move code executed an abort instruction
MovePrimitiveRuntimeError : Arithmetic overflow, division by zero, etc.
Intent signing
IOTA uses intent signing to prevent signature reuse across different contexts:
pub struct Intent {
scope : IntentScope ,
version : IntentVersion ,
app_id : AppId ,
}
pub enum IntentScope {
TransactionData ,
TransactionEffects ,
CheckpointSummary ,
PersonalMessage ,
}
When signing:
Construct intent message: intent || transaction_data
Hash the intent message
Sign the hash with private key
Intent signing ensures signatures cannot be replayed in different contexts, improving security.
Multi-signature transactions
IOTA supports multi-signature transactions where multiple parties must sign:
pub struct MultiSig {
// Individual signatures
sigs : Vec < CompressedSignature >,
// Bitmap indicating which keys signed
bitmap : u16 ,
// MultiSig public key
multisig_pk : MultiSigPublicKey ,
}
Multi-sig requirements:
Define threshold (minimum signatures needed)
Each signature is from a different public key
Bitmap indicates which keys in the multisig signed
Total weight of signatures must meet threshold
Transaction builder patterns
Simple transfer
let mut builder = ProgrammableTransactionBuilder :: new ();
let recipient = builder . pure ( recipient_address ) ? ;
builder . transfer_object ( recipient , object_ref ) ? ;
let pt = builder . finish ();
Split and transfer coins
let mut builder = ProgrammableTransactionBuilder :: new ();
let coin = builder . obj ( ObjectArg :: ImmOrOwnedObject ( coin_ref )) ? ;
let amount = builder . pure ( 1000 u64 ) ? ;
let recipient = builder . pure ( recipient_address ) ? ;
let split_coin = builder . command ( Command :: SplitCoins ( coin , vec! [ amount ]));
builder . command ( Command :: TransferObjects ( vec! [ split_coin ], recipient ));
let pt = builder . finish ();
Call Move function
let mut builder = ProgrammableTransactionBuilder :: new ();
// Add arguments
let arg1 = builder . pure ( 100 u64 ) ? ;
let arg2 = builder . obj ( ObjectArg :: SharedObject { ... }) ? ;
// Call Move function
builder . move_call (
PACKAGE_ID ,
Identifier :: new ( "module_name" ) ? ,
Identifier :: new ( "function_name" ) ? ,
vec! [], // type arguments
vec! [ arg1 , arg2 ], // arguments
) ? ;
let pt = builder . finish ();
Best practices
Optimize gas usage
// Bad: Multiple separate transactions
transaction1 : transfer ( coin1 , recipient );
transaction2 : transfer ( coin2 , recipient );
transaction3 : transfer ( coin3 , recipient );
// Good: Single programmable transaction
let mut builder = ProgrammableTransactionBuilder :: new ();
let recipient = builder . pure ( recipient_address ) ? ;
builder . command ( Command :: TransferObjects (
vec! [ coin1 , coin2 , coin3 ],
recipient
));
Handle errors gracefully
const EInsufficientBalance : u64 = 0 ;
const EInvalidAmount : u64 = 1 ;
public fun transfer_amount (account: & mut Account , amount: u64 ) {
assert! (amount > 0 , EInvalidAmount );
assert! (account.balance >= amount, EInsufficientBalance );
account.balance = account.balance - amount;
}
Set appropriate gas budget
// Estimate gas needed
let estimated_gas = estimate_transaction_gas ( & transaction ) ? ;
// Add buffer for safety
let gas_budget = estimated_gas * 12 / 10 ; // 20% buffer
Setting too low a gas budget causes transactions to fail. Set too high and you waste IOTA tokens. Always estimate and add a reasonable buffer.
Transaction digests
Transactions and effects are identified by cryptographic digests:
// Transaction digest: hash of transaction data
let tx_digest : TransactionDigest = hash ( transaction_data );
// Effects digest: hash of transaction effects
let effects_digest : TransactionEffectsDigest = hash ( transaction_effects );
// Combined execution digest
pub struct ExecutionDigests {
transaction : TransactionDigest ,
effects : TransactionEffectsDigest ,
}
Digests are used for:
Uniquely identifying transactions
Referencing dependencies
Creating merkle proofs
Indexing in storage
Gas and fees Understand gas computation and fee structure
Objects and ownership Learn about objects and ownership models
Move language Write Move functions for transaction commands
Architecture Overview of transaction execution architecture