Skip to main content
Sui’s transaction model is unique in its support for Programmable Transaction Blocks (PTBs), which allow you to chain multiple operations together atomically. This enables complex operations to be executed efficiently in a single transaction.

Transaction Types

Sui supports several types of transactions:
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, IntoStaticStr)]
pub enum TransactionKind {
    /// A transaction that allows the interleaving of native commands and Move calls
    ProgrammableTransaction(ProgrammableTransaction),
    /// A system transaction that will update epoch information on-chain.
    ChangeEpoch(ChangeEpoch),
    Genesis(GenesisTransaction),
    ConsensusCommitPrologue(ConsensusCommitPrologue),
    AuthenticatorStateUpdate(AuthenticatorStateUpdate),
    EndOfEpochTransaction(Vec<EndOfEpochTransactionKind>),
    RandomnessStateUpdate(RandomnessStateUpdate),
    ConsensusCommitPrologueV2(ConsensusCommitPrologueV2),
    ConsensusCommitPrologueV3(ConsensusCommitPrologueV3),
    ConsensusCommitPrologueV4(ConsensusCommitPrologueV4),
    /// A system transaction that is expressed as a PTB
    ProgrammableSystemTransaction(ProgrammableTransaction),
}

Programmable Transactions

User-initiated transactions that can chain multiple commands

System Transactions

Special transactions for epoch changes and system state updates

Genesis Transaction

One-time transaction that initializes the network

Consensus Updates

Transactions that update consensus-related state

Programmable Transaction Blocks

Programmable transactions are the primary transaction type for users and applications.

Transaction Arguments

Transactions can take three types of arguments:
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
pub enum CallArg {
    // contains no structs or objects
    Pure(Vec<u8>),
    // an object
    Object(ObjectArg),
    // Reservation to withdraw balance from a funds accumulator
    FundsWithdrawal(FundsWithdrawalArg),
}
Pure values like numbers, strings, or addresses encoded as bytes:
const tx = new Transaction();
tx.moveCall({
    target: '0x2::coin::split',
    arguments: [
        tx.object(coinId),
        tx.pure.u64(1000), // Pure argument
    ],
});

Transaction Structure

Every transaction contains:
The address that signed and submitted the transaction
One or more coin objects used to pay for gas
Maximum amount of gas the transaction can consume
Price per unit of gas in MIST (1 SUI = 1B MIST)
The type and content of the transaction (programmable, system, etc.)
Optional epoch after which the transaction becomes invalid

Entry Functions

Entry functions are special Move functions that can be called directly from transactions:
/// Entry functions can accept a reference to the `TxContext`
/// (mutable or immutable) as their last parameter.
entry fun share(bar: u64, ctx: &mut TxContext) {
    transfer::share_object(Foo {
        id: object::new(ctx),
        bar,
    })
}

/// Parameters passed to entry functions called in a programmable
/// transaction block must be inputs to the transaction block,
/// and not results of previous transactions.
entry fun update(foo: &mut Foo, ctx: &TxContext) {
    foo.bar = ctx.epoch();
}

/// Entry functions can return types that have `drop`.
entry fun bar(foo: &Foo): u64 {
    foo.bar
}

Entry Function Rules

Entry functions have special restrictions:
  • Can only return types with drop ability
  • Parameters in PTBs must be transaction inputs, not results from previous commands
  • Must have entry visibility modifier

Public Functions vs Entry Functions

Can be called from other Move modules and chained in PTBs:
/// This function cannot be `entry` because it returns a value
/// that does not have `drop`.
public fun foo(ctx: &mut TxContext): Foo {
    Foo { id: object::new(ctx), bar: 0 }
}
  • Can return any type
  • Results can be passed to subsequent commands
  • More flexible for composition

Transaction Commands

Programmable transactions support various native commands:

MoveCall

Call a Move function

TransferObjects

Transfer objects to addresses

SplitCoins

Split coin into multiple coins

MergeCoins

Merge multiple coins into one

Publish

Publish a new Move package

MakeMoveVec

Create a vector of Move values

Upgrade

Upgrade an existing package

Shared Object Example

Here’s a practical example using a shared counter:
/// This example demonstrates a basic use of a shared object.
/// Rules:
/// - anyone can create and share a counter
/// - everyone can increment a counter by 1
/// - the owner of the counter can reset it to any value
module basics::counter {
    /// A shared counter.
    public struct Counter has key {
        id: UID,
        owner: address,
        value: u64,
    }

    /// Create and share a Counter object.
    public fun create(ctx: &mut TxContext) {
        transfer::share_object(Counter {
            id: object::new(ctx),
            owner: ctx.sender(),
            value: 0,
        })
    }

    /// Increment a counter by 1.
    public fun increment(counter: &mut Counter) {
        counter.value = counter.value + 1;
    }

    /// Set value (only runnable by the Counter owner)
    public fun set_value(counter: &mut Counter, value: u64, ctx: &TxContext) {
        assert!(counter.owner == ctx.sender());
        counter.value = value;
    }
}

Gas Configuration

From the source code:
pub const TEST_ONLY_GAS_UNIT_FOR_TRANSFER: u64 = 10_000;
pub const TEST_ONLY_GAS_UNIT_FOR_OBJECT_BASICS: u64 = 50_000;
pub const TEST_ONLY_GAS_UNIT_FOR_PUBLISH: u64 = 70_000;
pub const TEST_ONLY_GAS_UNIT_FOR_STAKING: u64 = 50_000;
pub const TEST_ONLY_GAS_UNIT_FOR_GENERIC: u64 = 50_000;
pub const TEST_ONLY_GAS_UNIT_FOR_SPLIT_COIN: u64 = 10_000;
pub const TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE: u64 = 5_000_000;

pub const GAS_PRICE_FOR_SYSTEM_TX: u64 = 1;
pub const DEFAULT_VALIDATOR_GAS_PRICE: u64 = 1000;

Type Argument Validation

Sui validates type arguments to prevent abuse:
fn type_input_validity_check(
    tag: &TypeInput,
    config: &ProtocolConfig,
    starting_count: &mut usize,
) -> UserInputResult<()> {
    let mut stack = vec![(tag, 1)];
    while let Some((tag, depth)) = stack.pop() {
        *starting_count += 1;
        fp_ensure!(
            *starting_count < config.max_type_arguments() as usize,
            UserInputError::SizeLimitExceeded {
                limit: "maximum type arguments in a call transaction".to_string(),
                value: config.max_type_arguments().to_string()
            }
        );
        fp_ensure!(
            depth < config.max_type_argument_depth(),
            UserInputError::SizeLimitExceeded {
                limit: "maximum type argument depth in a call transaction".to_string(),
                value: config.max_type_argument_depth().to_string()
            }
        );
        // ... validation logic
    }
    Ok(())
}

End-of-Epoch Transactions

Special transactions that run at epoch boundaries:
pub enum EndOfEpochTransactionKind {
    ChangeEpoch(ChangeEpoch),
    AuthenticatorStateCreate,
    AuthenticatorStateExpire(AuthenticatorStateExpire),
    RandomnessStateCreate,
    DenyListStateCreate,
    BridgeStateCreate(ChainIdentifier),
    BridgeCommitteeInit(SequenceNumber),
    StoreExecutionTimeObservations(StoredExecutionTimeObservations),
    AccumulatorRootCreate,
    CoinRegistryCreate,
    DisplayRegistryCreate,
    AddressAliasStateCreate,
    WriteAccumulatorStorageCost(WriteAccumulatorStorageCost),
}

Best Practices

Combine multiple operations in a single transaction for atomicity:
const tx = new Transaction();
const [coin] = tx.splitCoins(tx.gas, [1000]);
tx.moveCall({
    target: `${packageId}::my_module::do_something`,
    arguments: [tx.object(objectId), coin],
});
Set gas budgets high enough to cover execution but not wastefully high:
  • Simple transfers: ~1M gas units
  • Complex operations: ~10M+ gas units
  • Package publishing: ~100M+ gas units
Use current reference gas price or higher:
const gasPrice = await client.getReferenceGasPrice();
tx.setGasPrice(gasPrice);
Shared objects require consensus and are slower. Prefer owned objects when possible.

Transaction Lifecycle

Understand transaction execution flow

Gas Pricing

Learn about gas costs

Sponsored Transactions

Enable gasless transactions

Build docs developers (and LLMs) love