Skip to main content
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(1000u64)?;
let amount2 = builder.pure(2000u64)?;
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:

Input 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

1

Construction

Client constructs transaction data with commands, inputs, and gas configuration.
2

Signing

Sender signs the transaction with their private key. Multi-sig transactions require multiple signatures.
3

Submission

Transaction is submitted to a validator node (full node or validator).
4

Validation

Validator performs initial validation:
  • Signature verification
  • Gas budget sufficiency
  • Object existence and ownership
  • Transaction well-formedness
5

Sequencing

For owned objects: Transaction can execute immediately For shared objects: Transaction must go through consensus
6

Execution

Validator executes the transaction:
  • Charges storage read costs
  • Executes each command in sequence
  • Updates object versions
  • Emits events
  • Computes gas costs
7

Effects

Transaction effects are produced, including:
  • Status (success or failure)
  • Modified objects
  • Created objects
  • Deleted objects
  • Events emitted
  • Gas cost summary
8

Certificate

Effects are signed by validators to create a certificate proving execution.
9

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:
  1. Construct intent message: intent || transaction_data
  2. Hash the intent message
  3. 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(1000u64)?;
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(100u64)?;
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

Build docs developers (and LLMs) love