LiteSVM Docs
Testing Your Program

Executing Instructions

Learn how to call an instruction from a Solana program in your test

Overview

After deploying a program to LiteSVM, you'll need to execute instructions to interact with the deployed program.

LiteSVM provides a simple API for creating, sending, and simulating transactions that is fully compatible with Solana's transaction model.

Basic Transaction Flow

The typical flow for executing an instruction is:

  1. Create an Instruction - Define what program to call and with what data
  2. Build a Message - Combine one or more instructions
  3. Create a Transaction - Sign the message with required signers
  4. Send or Simulate - Execute the transaction and handle results

Creating Instructions

Basic Instruction Structure

use solana_instruction::{Instruction, AccountMeta};
use solana_pubkey::Pubkey;

let instruction = Instruction {
    program_id: Pubkey::new_unique(),  // The program to call
    accounts: vec![                     // Accounts the program needs
        AccountMeta::new(account_pubkey, false),        // Writable, not signer
        AccountMeta::new_readonly(readonly_pubkey, false), // Read-only, not signer
        AccountMeta::new(signer_pubkey, true),          // Writable, signer
    ],
    data: vec![0, 1, 2, 3],             // Instruction data (program-specific)
};

Building and Sending Transactions

Method 1: Basic Transaction

use litesvm::LiteSVM;
use solana_keypair::Keypair;
use solana_message::Message;
use solana_transaction::Transaction;
use solana_signer::Signer;

let mut svm = LiteSVM::new();
let payer = Keypair::new();

// Airdrop SOL for fees
svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();

// Create instruction
let instruction = /* your instruction */;

// Build message with payer
let message = Message::new(&[instruction], Some(&payer.pubkey()));

// Create and sign transaction
let tx = Transaction::new_signed_with_payer(
    &[instruction],                 // Your instructions
    Some(&payer.pubkey()),          // Who pays transaction fees
    &[&payer],                      // All required signers
    svm.latest_blockhash(),         // Recent blockhash
);


// Send transaction
let result = svm.send_transaction(tx);

What new_signed_with_payer does:

  • Automatically builds a Message from your instructions
  • Automatically signs the transaction with all provided keypairs
  • Returns a fully signed, ready-to-send transaction

Method 2: Custom Message Transaction

let payer = Keypair::new();

// Manually construct message
let message = Message::new(&[instruction], Some(&payer.pubkey()));

// Create transaction with signers, message, and blockhash
let tx = Transaction::new(
    &[&payer],
    message,
    svm.latest_blockhash(),
);

let result = svm.send_transaction(tx);

Use this method when you need control over the Message like:

  • Versioned transactions (v0 with lookup tables)
  • Partial/multi-sig workflows
  • Durable nonces
  • Pre-signature simulation or inspection
  • Custom fee payer logic
  • Transaction size optimization

Versioned Transactions

LiteSVM supports both legacy and versioned transactions:

use solana_transaction::versioned::VersionedTransaction;
use solana_message::VersionedMessage;

// Legacy transaction (most common)
let legacy_msg = Message::new(&[instruction], Some(&payer.pubkey()));
let versioned_tx = VersionedTransaction::try_new(
    VersionedMessage::Legacy(legacy_msg),
    &[&payer]
).unwrap();

// Send versioned transaction
let result = svm.send_transaction(versioned_tx);

Transaction Results

Successful Transaction

match svm.send_transaction(tx) {
    Ok(meta) => {
        println!("Signature: {}", meta.signature);
        println!("Compute units: {}", meta.compute_units_consumed);
        println!("Logs:");
        for log in &meta.logs {
            println!("  {}", log);
        }
    }
    Err(err) => {
        println!("Transaction failed: {:?}", err.err);
        // Logs are still available on failure
        println!("Failure logs: {:?}", err.meta.logs);
    }
}

Transaction Metadata Fields

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

Simulating Transactions

Simulate transaction lets you test transactions without changing state:

// Simulate instead of sending
match svm.simulate_transaction(tx) {
    Ok(sim_result) => {
        println!("Simulation successful!");
        println!("Logs: {:?}", sim_result.meta.logs);
        println!("Compute units: {}", sim_result.meta.compute_units_consumed);
    }
    Err(err) => {
        println!("Simulation failed: {:?}", err.err);
    }
}

Error Handling

Common Transaction Errors

use solana_transaction_error::TransactionError;
use solana_instruction::error::InstructionError;

match svm.send_transaction(tx) {
    Err(failed_tx) => {
        match failed_tx.err {
            TransactionError::InsufficientFundsForFee => {
                println!("Not enough SOL for fees");
            }
            TransactionError::InvalidProgramForExecution => {
                println!("Program doesn't exist or isn't executable");
            }
            TransactionError::InstructionError(index, err) => {
                println!("Instruction {} failed: {:?}", index, err);
                match err {
                    InstructionError::Custom(code) => {
                        println!("Custom error code: {}", code);
                    }
                    InstructionError::AccountNotFound => {
                        println!("An account doesn't exist");
                    }
                    _ => {}
                }
            }
            TransactionError::BlockhashNotFound => {
                println!("Blockhash expired or invalid");
            }
            _ => println!("Other error: {:?}", failed_tx.err),
        }
    }
    Ok(_) => {}
}

Working with Program Logs

Accessing Logs

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

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

// Pretty-printed logs (formatted)
println!("{}", result.pretty_logs());

Log Output Example

Program 11111111111111111111111111111111 invoke [1]
Program log: Processing instruction
Program 11111111111111111111111111111111 consumed 2000 compute units
Program 11111111111111111111111111111111 success

Compute Budget Configuration

Setting Global Compute Budget

use solana_compute_budget::compute_budget::ComputeBudget;

let mut svm = LiteSVM::new()
    .with_compute_budget(ComputeBudget {
        compute_unit_limit: 200_000,
        ..Default::default()
    });

Per-Transaction Compute Budget

use solana_compute_budget_interface::ComputeBudgetInstruction;

let instructions = vec![
    // Set compute budget for this transaction
    ComputeBudgetInstruction::set_compute_unit_limit(400_000),
    ComputeBudgetInstruction::set_compute_unit_price(1),

    // Your actual instruction
    your_instruction,
];

System Program Instructions

LiteSVM includes system program support:

use solana_keypair::Keypair;
use solana_signer::Signer;
use solana_system_interface::instruction as system_instruction;
use solana_transaction::Transaction;


// Transfer SOL
let transfer_ix = system_instruction::transfer(
    &alice.pubkey(),
    &bob.pubkey(),
    1_000_000_000, // 1 SOL
);

// Create new account
let create_ix = system_instruction::create_account(
    &payer_pubkey,
    &new_account_pubkey,
    lamports,
    space as u64,
    &owner_program_id,
);

Summary

Executing instructions in LiteSVM follows the standard Solana transaction model:

  1. Create Instruction objects with program ID, accounts, and data
  2. Build a Transaction with the instruction and required signers
  3. Use send_transaction() to execute or simulate_transaction() to test
  4. Handle results by checking the TransactionMetadata or error details

LiteSVM provides instant execution with detailed logs and debugging information, making it ideal for testing Solana programs efficiently.