LiteSVM Docs

Transactions

Methods for handling transactions

send_transaction

pub fn send_transaction(&mut self, tx: impl Into<VersionedTransaction>) -> Result<TransactionMetadata, FailedTransactionMetadata>

Execute transaction and modify state.

let tx = Transaction::new_signed_with_payer(
    &[instruction],
    Some(&payer.pubkey()),
    &[&payer],
    svm.latest_blockhash(),
);

match svm.send_transaction(tx) {
    Ok(meta) => {
        println!("Success! Signature: {}", meta.signature);
        println!("Compute units: {}", meta.compute_units_consumed);
    }
    Err(e) => {
        println!("Failed: {:?}", e.err);
        println!("Logs: {:?}", e.meta.logs);
    }
}

simulate_transaction

pub fn simulate_transaction(&self, tx: impl Into<VersionedTransaction>) -> Result<SimulatedTransactionInfo, FailedTransactionMetadata>

Simulate transaction without modifying state.

Returns SimulatedTransactionInfo which contains:

  • meta: Transaction metadata (logs, compute units, signature, etc.)
  • post_accounts: Account states after simulation
// State before simulation
let balance_before = svm.get_balance(&pubkey).unwrap_or(0);

// Simulate transaction
let result = svm.simulate_transaction(tx.clone());

// State unchanged
assert_eq!(svm.get_balance(&pubkey).unwrap_or(0), balance_before);

// Check simulation result
if let Ok(sim_info) = result {
    println!("Would succeed");
    println!("Compute units: {}", sim_info.meta.compute_units_consumed);

    // Can inspect post_accounts to see what would change
    for (pubkey, account) in sim_info.post_accounts {
        println!("Account {} would have {} lamports", pubkey, account.lamports());
    }

    // Safe to send
    svm.send_transaction(tx).unwrap();
}

When to Use Simulation

Use simulate_transaction() to:

  • Test transaction validity without modifying state
  • Check compute unit consumption
  • Verify transaction logs without commitment
  • Test error handling paths
// Test if transaction would succeed
let result = svm.simulate_transaction(tx.clone());

match result {
    Ok(sim_info) => {
        println!("Would succeed");
        println!("Compute units: {}", sim_info.meta.compute_units_consumed);

        // Now execute for real
        svm.send_transaction(tx).unwrap();
    }
    Err(e) => {
        println!("Would fail: {:?}", e.err);
        // Don't send transaction
    }
}

Testing Error Cases

// Simulate expected failure
let result = svm.simulate_transaction(invalid_tx);
assert!(result.is_err());

match result.unwrap_err().err {
    TransactionError::InsufficientFundsForFee => {
        println!("Expected error occurred");
    }
    _ => panic!("Unexpected error"),
}

State Remains Unchanged

let balance_before = svm.get_balance(&pubkey).unwrap_or(0);

// Simulate multiple times
for _ in 0..10 {
    svm.simulate_transaction(tx.clone()).unwrap();
}

// Balance unchanged
assert_eq!(svm.get_balance(&pubkey).unwrap_or(0), balance_before);

Transaction Metadata

send_transaction() returns TransactionMetadata directly, while simulate_transaction() returns SimulatedTransactionInfo which contains meta: TransactionMetadata:

pub struct TransactionMetadata {
    pub signature: Signature,
    pub logs: Vec<String>,
    pub compute_units_consumed: u64,
    pub return_data: TransactionReturnData,
    pub inner_instructions: InnerInstructionsList,
}

Accessing Transaction Data

let result = svm.send_transaction(tx).unwrap();

// Signature
println!("Signature: {}", result.signature);

// Logs
for log in &result.logs {
    println!("{}", log);
}

// Compute units
println!("CU consumed: {}", result.compute_units_consumed);

// Return data
if let Some(data) = result.return_data.data {
    println!("Program returned: {:?}", data);
}

// Inner instructions (CPIs)
for inner in &result.inner_instructions {
    println!("CPI: {:?}", inner);
}

get_transaction

pub fn get_transaction(&self, signature: &Signature) -> Option<Result<TransactionMetadata, FailedTransactionMetadata>>

Query transaction from history. Requires with_transaction_history() to be set.

let mut svm = LiteSVM::default()
    .with_transaction_history(100);

let result = svm.send_transaction(tx).unwrap();
let signature = result.signature;

// Query later
if let Some(tx_result) = svm.get_transaction(&signature) {
    match tx_result {
        Ok(meta) => println!("Found: {:?}", meta),
        Err(e) => println!("Failed: {:?}", e.err),
    }
}

latest_blockhash

pub fn latest_blockhash(&self) -> Hash

Get current blockhash for transactions.

let blockhash = svm.latest_blockhash();

let tx = Transaction::new_signed_with_payer(
    &[instruction],
    Some(&payer.pubkey()),
    &[&payer],
    blockhash,
);

expire_blockhash

pub fn expire_blockhash(&mut self)

Force current blockhash to expire.

let old_hash = svm.latest_blockhash();
svm.expire_blockhash();
let new_hash = svm.latest_blockhash();

assert_ne!(old_hash, new_hash);

// Transactions with old_hash will now fail

Common Patterns

Validate Before Executing

// Simulate first to check validity
if svm.simulate_transaction(tx.clone()).is_ok() {
    // Execute if simulation passes
    svm.send_transaction(tx).unwrap();
} else {
    println!("Transaction would fail, skipping execution");
}

Track Compute Units

let result = svm.send_transaction(tx).unwrap();

// Assert within budget
assert!(
    result.compute_units_consumed < 100_000,
    "Transaction used too many compute units: {}",
    result.compute_units_consumed
);

Check Transaction Logs

let result = svm.send_transaction(tx).unwrap();

// Check for specific log messages
assert!(result.logs.iter().any(|log|
    log.contains("Transfer successful")
));