Solana Substreams Type Glossary

Block

pub struct Block {
    pub previous_blockhash: String,
    pub blockhash: String,
    pub parent_slot: u64,
    pub transactions: Vec<ConfirmedTransaction>,
    pub rewards: Vec<Reward>,
    pub block_time: Option<UnixTimestamp>,
    pub block_height: Option<BlockHeight>,
    pub slot: u64,
}

Fields

previous_blockhash

  • Description: Hash of the previous block in the chain
  • Type: String
  • Example: "DvLEyV2GHk86K5GojpqnRsvhfMF5kdZPKWgkYYZ5c7W"

blockhash

  • Description: Unique hash identifier for this block
  • Type: String
  • Example: "5M7Rqy8YYkzXGkegHxJNSgHqGZxQKE6LFcYBTXRJvkVZ"

parent_slot

  • Description: Slot number of the parent block
  • Type: u64
  • Example: 150243904

transactions

  • Description: Vector of confirmed transactions included in this block
  • Type: Vec<ConfirmedTransaction>
  • Example: Contains transaction data and metadata for each transaction

rewards

  • Description: Vector of rewards distributed in this block
  • Type: Vec<Reward>
  • Example: Staking rewards, transaction fees, rent collection

block_time

  • Description: Unix timestamp when block was processed
  • Type: Option<UnixTimestamp>
  • Example: Some(UnixTimestamp { timestamp: 1682439961 })

block_height

  • Description: Height of the block in the chain
  • Type: Option<BlockHeight>
  • Example: Some(BlockHeight { block_height: 189046723 })

slot

  • Description: Slot number of this block
  • Type: u64
  • Example: 150243905

Important Notes

  • This struct is backwards compatible with solana.storage.ConfirmedBlock.ConfirmedBlock from Solana Labs
  • Block time and height are optional fields since they may not be available for historical blocks

ConfirmedTransaction

pub struct ConfirmedTransaction {
    pub transaction: Option<Transaction>,
    pub meta: Option<TransactionStatusMeta>,
}

Fields

transaction

  • Description: The actual transaction data
  • Type: Option<Transaction>
  • Example:
    transaction: Some(Transaction {
        signatures: vec![
            // Base-58 encoded signature
            vec![67, 89, 115, ...], // "5Qs8dK3yPNc..."
        ],
        message: Some(Message {
            header: Some(MessageHeader {
                num_required_signatures: 1,
                num_readonly_signed_accounts: 0,
                num_readonly_unsigned_accounts: 1
            }),
            account_keys: vec![/* account public keys */],
            recent_blockhash: vec![/* blockhash bytes */],
            instructions: vec![/* compiled instructions */],
            versioned: false,
            address_table_lookups: vec![]
        })
    })

meta

  • Description: Metadata about the transaction’s execution
  • Type: Option<TransactionStatusMeta>
  • Example:
    meta: Some(TransactionStatusMeta {
        err: None,  // None means success
        fee: 5000,  // Transaction fee in lamports
        pre_balances: vec![1000000, 2000000],
        post_balances: vec![995000, 2005000],
        inner_instructions: vec![/* CPI calls */],
        log_messages: vec![
            "Program 11111111111111 invoke [1]",
            "Program 11111111111111 success"
        ],
        pre_token_balances: vec![/* token balances before */],
        post_token_balances: vec![/* token balances after */],
        rewards: vec![]
    })

Example Use Cases

fn analyze_transaction_result(tx: &ConfirmedTransaction) -> TransactionAnalysis {
    let success = tx.meta
        .as_ref()
        .and_then(|m| m.err.as_ref())
        .is_none();
    
    let fee = tx.meta
        .as_ref()
        .map(|m| m.fee)
        .unwrap_or(0);
        
    let signature = tx.transaction
        .as_ref()
        .map(|t| t.signatures.get(0))
        .flatten()
        .cloned();
        
    TransactionAnalysis {
        succeeded: success,
        fee_paid: fee,
        primary_signature: signature
    }
}

Transaction

pub struct Transaction {
    pub signatures: Vec<Vec<u8>>,
    pub message: Option<Message>,
}

Fields

signatures

  • Description: Vector of transaction signatures (in bytes)
  • Type: Vec<Vec<u8>>
  • Use Case: Contains signatures from all required signers
  • Example:
    signatures: vec![
        // Primary signature (usually from fee payer)
        vec![67, 89, 115, ...], // Base58: "5Qs8dK3yPNc..."
        
        // Additional signer (if required)
        vec![98, 76, 45, ...],  // Base58: "2ZjTR9Wkc7..."
    ]

message

  • Description: The transaction message containing all instructions and metadata
  • Type: Option<Message>
  • Example:
    message: Some(Message {
        header: Some(MessageHeader {
            num_required_signatures: 1,
            num_readonly_signed_accounts: 0,
            num_readonly_unsigned_accounts: 1
        }),
        account_keys: vec![
            // Fee payer
            vec![1, 2, 3, ...], // "11111111111111111111",
            // Program ID
            vec![4, 5, 6, ...], // "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
        ],
        recent_blockhash: vec![/* blockhash bytes */],
        instructions: vec![
            CompiledInstruction {
                program_id_index: 1,
                accounts: vec![0, 1],
                data: vec![/* instruction data */]
            }
        ],
        versioned: false,
        address_table_lookups: vec![]
    })

Example Use Cases

fn verify_transaction(tx: &Transaction) -> TransactionVerification {
    // Check number of signatures matches required signatures
    let required_sigs = tx.message.as_ref()
        .and_then(|m| m.header.as_ref())
        .map(|h| h.num_required_signatures as usize)
        .unwrap_or(0);
        
    let has_all_signatures = tx.signatures.len() >= required_sigs;
    
    // Check if transaction is versioned
    let is_versioned = tx.message
        .as_ref()
        .map(|m| m.versioned)
        .unwrap_or(false);
        
    TransactionVerification {
        valid_signature_count: has_all_signatures,
        is_versioned,
        total_accounts: tx.message
            .as_ref()
            .map(|m| m.account_keys.len())
            .unwrap_or(0)
    }
}

Important Notes

  • Key components:
    • Signatures for transaction authorization
    • Message containing all transaction details
  • Supports both legacy and versioned transactions
  • Message is optional but required for valid transactions

Message

pub struct Message {
    pub header: Option<MessageHeader>,
    pub account_keys: Vec<Vec<u8>>,
    pub recent_blockhash: Vec<u8>,
    pub instructions: Vec<CompiledInstruction>,
    pub versioned: bool,
    pub address_table_lookups: Vec<MessageAddressTableLookup>,
}

Fields

  • Description: Contains metadata about account permissions and signatures
  • Type: Option<MessageHeader>
  • Example:
    header: Some(MessageHeader {
        num_required_signatures: 2,
        num_readonly_signed_accounts: 1,
        num_readonly_unsigned_accounts: 2
    })

account_keys

  • Description: Vector of all account public keys used in the transaction
  • Type: Vec<Vec<u8>>
  • Example:
    account_keys: vec![
        vec![1, 2, 3...], // Fee payer: "HxFH8K4...",
        vec![4, 5, 6...], // Token program: "TokenkegQ...",
        vec![7, 8, 9...], // User's token account
    ]

recent_blockhash

  • Description: Recent blockhash for transaction timing and uniqueness
  • Type: Vec<u8>
  • Example: vec![232, 17, 164...] // “DvLEyV2GHk86K5…“

instructions

  • Description: Vector of compiled instructions to be executed
  • Type: Vec<CompiledInstruction>
  • Example:
    instructions: vec![
        CompiledInstruction {
            program_id_index: 1,  // Index into account_keys
            accounts: vec![0, 2], // Indexes into account_keys
            data: vec![2, 0, 0, 0, /* instruction data */]
        }
    ]

versioned

  • Description: Flag indicating if this is a versioned transaction
  • Type: bool
  • Example: false (legacy transaction format)

address_table_lookups

  • Description: Lookup table references for address compression
  • Type: Vec<MessageAddressTableLookup>
  • Example:
    address_table_lookups: vec![
        MessageAddressTableLookup {
            account_key: vec![/* lookup table address */],
            writable_indexes: vec![0, 1],
            readonly_indexes: vec![2, 3]
        }
    ]

Example Use Cases

fn analyze_message_complexity(msg: &Message) -> MessageAnalysis {
    let account_count = msg.account_keys.len();
    let instruction_count = msg.instructions.len();
    let uses_lookup_tables = !msg.address_table_lookups.is_empty();
    
    let required_sigs = msg.header
        .as_ref()
        .map(|h| h.num_required_signatures)
        .unwrap_or(0);
        
    MessageAnalysis {
        complexity: if instruction_count > 4 { "High" } else { "Normal" },
        is_versioned: msg.versioned,
        total_accounts: account_count,
        required_signatures: required_sigs,
        uses_address_lookup: uses_lookup_tables
    }
}

Important Notes

  • Contains all information needed for transaction execution:
    • Account permissions (header)
    • Account addresses (account_keys)
    • Program instructions (instructions)
    • Transaction timing (recent_blockhash)
  • Supports address lookup tables for transaction optimization

MessageHeader

pub struct MessageHeader {
    pub num_required_signatures: u32,
    pub num_readonly_signed_accounts: u32,
    pub num_readonly_unsigned_accounts: u32,
}

Fields

num_required_signatures

  • Description: Number of signatures required for this message
  • Type: u32
  • Example: 2 (transaction requires 2 signers)

num_readonly_signed_accounts

  • Description: Number of signed accounts that are read-only
  • Type: u32
  • Example: 1 (one signer account is read-only)

num_readonly_unsigned_accounts

  • Description: Number of unsigned accounts that are read-only
  • Type: u32
  • Example: 2 (two non-signer accounts are read-only)

Example Use Cases

fn analyze_account_permissions(header: &MessageHeader) -> AccountAnalysis {
    let total_accounts = header.num_required_signatures + header.num_readonly_unsigned_accounts;
    let writable_accounts = header.num_required_signatures - header.num_readonly_signed_accounts;
    
    AccountAnalysis {
        total_accounts,
        writable_count: writable_accounts,
        readonly_count: header.num_readonly_signed_accounts + header.num_readonly_unsigned_accounts,
        requires_multiple_signers: header.num_required_signatures > 1
    }
}

Important Notes

  • Account indexes in the message are ordered by:
    1. Writable signed accounts
    2. Readonly signed accounts
    3. Writable unsigned accounts
    4. Readonly unsigned accounts
  • Common patterns:
    • Single signer: num_required_signatures = 1
    • Token transfer: One writable signer (fee payer) + one readonly program ID
    • Program invocation: Mix of writable and readonly accounts based on program needs

MessageAddressTableLookup

pub struct MessageAddressTableLookup {
    pub account_key: Vec<u8>,
    pub writable_indexes: Vec<u8>,
    pub readonly_indexes: Vec<u8>,
}

Fields

account_key

  • Description: Public key of the address lookup table account
  • Type: Vec<u8>
  • Example:
    account_key: vec![/* 32 bytes */] // Base58: "ALT1nZwfRz5BVwT1TqDwh7dzwJvxQzaG1TLPNuNc5zkK"

writable_indexes

  • Description: Indexes into the lookup table for writable accounts
  • Type: Vec<u8>
  • Example:
    writable_indexes: vec![0, 2, 5] // References accounts at indexes 0, 2, and 5 in the table

readonly_indexes

  • Description: Indexes into the lookup table for readonly accounts
  • Type: Vec<u8>
  • Example:
    readonly_indexes: vec![1, 3, 4] // References readonly accounts at indexes 1, 3, and 4

Example Use Cases

fn resolve_lookup_table_accounts(
    lookup: &MessageAddressTableLookup,
    table_data: &AddressLookupTable
) -> LookupResult {
    let writable_accounts: Vec<Pubkey> = lookup.writable_indexes
        .iter()
        .map(|&idx| table_data.addresses[idx as usize])
        .collect();
        
    let readonly_accounts: Vec<Pubkey> = lookup.readonly_indexes
        .iter()
        .map(|&idx| table_data.addresses[idx as usize])
        .collect();
        
    LookupResult {
        table_account: Pubkey::new(&lookup.account_key),
        writable_accounts,
        readonly_accounts,
        total_accounts: writable_accounts.len() + readonly_accounts.len()
    }
}

Important Notes

  • Benefits:
    • Reduces transaction size
    • Lowers transaction costs
    • Enables complex transactions with many accounts
  • Common use cases:
    • DeFi protocols with many token accounts
    • NFT marketplaces handling multiple collections
    • Gaming applications with frequent state updates
    • Any dApp requiring multiple account interactions

TransactionStatusMeta

pub struct TransactionStatusMeta {
    pub err: Option<TransactionError>,
    pub fee: u64,
    pub pre_balances: Vec<u64>,
    pub post_balances: Vec<u64>,
    pub inner_instructions: Vec<InnerInstructions>,
    pub inner_instructions_none: bool,
    pub log_messages: Vec<String>,
    pub log_messages_none: bool,
    pub pre_token_balances: Vec<TokenBalance>,
    pub post_token_balances: Vec<TokenBalance>,
    pub rewards: Vec<Reward>,
}

TransactionError

pub enum TransactionError {
    AccountInUse = 0,                    // Account is being processed in another transaction
    AccountLoadedTwice = 1,              // Account appears multiple times in a transaction
    AccountNotFound = 2,                 // Referenced account does not exist
    ProgramAccountNotFound = 3,          // Program account does not exist
    InsufficientFundsForFee = 4,        // Insufficient funds for transaction fee
    InvalidAccountForFee = 5,            // Fee payer account does not exist or is invalid
    AlreadyProcessed = 6,               // Transaction has already been processed
    BlockhashNotFound = 7,              // Blockhash referenced in transaction is too old
    InstructionError = 8,               // Error from instruction processing
    CallChainTooDeep = 9,              // Cross-program invocation chain too deep
    MissingSignatureForFee = 10,       // Transaction fee-paying account did not sign
    InvalidAccountIndex = 11,           // Referenced account index is invalid
    SignatureFailure = 12,             // Transaction signature verification failed
    InvalidProgramForExecution = 13,    // Program is not executable or invalid
    SanitizeFailure = 14,              // Transaction failed sanitization checks
    ClusterMaintenance = 15,           // Cluster is in maintenance mode
    AccountBorrowOutstanding = 16,     // Account has outstanding borrowed references
    WouldExceedMaxBlockCostLimit = 17, // Transaction would exceed block cost limit
    UnsupportedVersion = 18,           // Transaction version is unsupported
    InvalidWritableAccount = 19,       // Account marked writable but cannot be written to
    WouldExceedMaxAccountCostLimit = 20, // Transaction would exceed account cost limit
    WouldExceedAccountDataBlockLimit = 21, // Transaction would exceed account data block limit
    TooManyAccountLocks = 22,          // Transaction requires too many account locks
    AddressLookupTableNotFound = 23,   // Referenced address lookup table not found
    InvalidAddressLookupTableOwner = 24, // Invalid address lookup table owner
    InvalidAddressLookupTableData = 25,  // Invalid address lookup table data
    InvalidAddressLookupTableIndex = 26, // Invalid address lookup table index
    InvalidRentPayingAccount = 27,      // Account does not meet rent requirements
    WouldExceedMaxVoteCostLimit = 28,   // Transaction would exceed vote cost limit
    WouldExceedAccountDataTotalLimit = 29, // Transaction would exceed total account data limit
    DuplicateInstruction = 30,          // Duplicate instructions in transaction
    InsufficientFundsForRent = 31,      // Insufficient funds to pay rent
    MaxLoadedAccountsDataSizeExceeded = 32, // Would exceed max loaded accounts data size
    InvalidLoadedAccountsDataSizeLimit = 33, // Invalid loaded accounts data size limit
    
}

Fields

err

  • Description: Transaction error if execution failed
  • Type: Option<TransactionError>
  • Transaction Processing Errors:
    • AccountInUse, AccountLoadedTwice: Concurrent transaction processing issues
    • AlreadyProcessed, BlockhashNotFound: Transaction timing and validity issues
    • CallChainTooDeep: Cross-program invocation (CPI) depth limits
  • Account-Related Errors:
    • AccountNotFound, ProgramAccountNotFound: Missing account errors
    • InsufficientFundsForFee, InvalidAccountForFee: Fee-related issues
    • InvalidRentPayingAccount, InsufficientFundsForRent: Rent-related issues
  • Example: Some(TransactionError::InsufficientFunds)

fee

  • Description: Transaction fee in lamports
  • Type: u64
  • Example: 5000 (0.000005 SOL)

pre_balances

  • Description: Account balances before transaction execution
  • Type: Vec<u64>
  • Example: vec![1000000, 2000000] (balances in lamports)

post_balances

  • Description: Account balances after transaction execution
  • Type: Vec<u64>
  • Example: vec![995000, 2005000] (after fee and transfer)

inner_instructions

  • Description: CPI (Cross-Program Invocation) instructions
  • Type: Vec<InnerInstructions>
  • Example: Program calls made during transaction execution

inner_instructions_none

  • Description: Flag indicating if inner instructions were not recorded
  • Type: bool
  • Example: true (inner instructions not available)

log_messages

  • Description: Program log messages from execution
  • Type: Vec<String>
  • Example:
    vec![
        "Program 11111111111111 invoke [1]",
        "Transfer: 5000 lamports to Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr",
        "Program 11111111111111 success"
    ]

log_messages_none

  • Description: Flag indicating if log messages were not recorded
  • Type: bool
  • Example: false (log messages are available)

pre_token_balances

  • Description: SPL Token balances before execution
  • Type: Vec<TokenBalance>
  • Example: Token account balances before transfers

post_token_balances

  • Description: SPL Token balances after execution
  • Type: Vec<TokenBalance>
  • Example: Token account balances after transfers

rewards

  • Description: Rewards distributed during transaction
  • Type: Vec<Reward>
  • Example: Staking rewards, transaction fee rewards

Example Use Cases

fn analyze_transaction_impact(meta: &TransactionStatusMeta) -> TransactionAnalysis {
    let succeeded = meta.err.is_none();
    let balance_changes: Vec<i64> = meta.pre_balances.iter()
        .zip(meta.post_balances.iter())
        .map(|(pre, post)| *post as i64 - *pre as i64)
        .collect();
        
    let token_transfers = meta.pre_token_balances.iter()
        .zip(meta.post_token_balances.iter())
        .filter(|(pre, post)| pre.amount != post.amount)
        .count();
        
    TransactionAnalysis {
        success: succeeded,
        fee_paid: meta.fee,
        sol_transfers: balance_changes,
        token_transfer_count: token_transfers,
        has_cpi: !meta.inner_instructions.is_empty()
    }
}

Important flags

  • inner_instructions_none and log_messages_none indicate data availability
  • Both CPI and log collection can be disabled for performance

InnerInstructions

pub struct InnerInstructions {
    pub index: u32,
    pub instructions: Vec<InnerInstruction>,
}
 
pub struct InnerInstruction {
    pub program_id_index: u32,
    pub accounts: Vec<u8>,
    pub data: Vec<u8>,
    /// Invocation stack height of an inner instruction.
    /// Available since Solana v1.14.6
    pub stack_height: Option<u32>,
}

Fields

index

  • Description: Index of the parent instruction in the transaction that generated these inner instructions
  • Type: u32
  • Example: 0 (first instruction in transaction)

instructions

  • Description: Vector of inner instructions executed by the parent instruction
  • Type: Vec<InnerInstruction>
  • Contains:
    • program_id_index: Index into transaction’s account keys for the program that was called
    • accounts: Ordered indices into transaction’s account keys for accounts used by this instruction
    • data: Instruction data passed to the program
    • stack_height: Depth of CPI (Cross-Program Invocation) call stack

Example Use Cases

fn analyze_cpi_calls(inner_instructions: &InnerInstructions) -> CPIAnalysis {
    let total_calls = inner_instructions.instructions.len();
    let max_stack_height = inner_instructions.instructions
        .iter()
        .filter_map(|ix| ix.stack_height)
        .max()
        .unwrap_or(0);
        
    let unique_programs: HashSet<_> = inner_instructions.instructions
        .iter()
        .map(|ix| ix.program_id_index)
        .collect();
        
    CPIAnalysis {
        parent_ix_index: inner_instructions.index,
        total_cpi_calls: total_calls,
        max_call_depth: max_stack_height,
        unique_programs: unique_programs.len(),
    }
}

Important Notes

  • Common patterns:
    • Token transfers often generate CPIs to Token program
    • DeFi protocols may have deep CPI chains
    • Governance programs often use CPIs for execution

Example Program Interactions

// Example of a typical DeFi swap inner instructions pattern
let swap_instructions = InnerInstructions {
    index: 0,  // Main swap instruction
    instructions: vec![
        InnerInstruction {
            program_id_index: 5,  // Token program
            accounts: vec![0, 1, 2],  // Source, destination, authority
            data: vec![/* transfer data */],
            stack_height: Some(1)
        },
        InnerInstruction {
            program_id_index: 5,  // Token program
            accounts: vec![3, 4, 2],  // Other token accounts
            data: vec![/* transfer data */],
            stack_height: Some(1)
        }
    ]
};

InnerInstruction

pub struct InnerInstruction {
    pub program_id_index: u32,
    pub accounts: Vec<u8>,
    pub data: Vec<u8>,
    /// Invocation stack height of an inner instruction.
    /// Available since Solana v1.14.6
    pub stack_height: Option<u32>,
}

Fields

program_id_index

  • Description: Index into transaction’s account keys array pointing to the program being invoked
  • Type: u32
  • Example: 5 (points to Token Program’s pubkey in account keys)

accounts

  • Description: Ordered list of indices into transaction’s account keys for accounts used by this instruction
  • Type: Vec<u8>
  • Example: vec![3, 4, 7] (references accounts at indices 3, 4, and 7 in transaction accounts)

data

  • Description: Raw instruction data passed to the program
  • Type: Vec<u8>
  • Example:
    // Token transfer instruction data
    vec![3, /* Transfer instruction */
         0, 0, 0, 0, 0, 0, 0, 100 /* Amount: 100 */]

stack_height

  • Description: Depth of this instruction in the CPI call stack
  • Type: Option<u32>
  • Example: Some(2) (instruction is 2 levels deep in CPI chain)
  • Note: Available since Solana v1.14.6

Example Use Cases

fn decode_inner_instruction(
    ix: &InnerInstruction,
    tx_accounts: &[Pubkey]
) -> DecodedInstruction {
    let program_id = tx_accounts[ix.program_id_index as usize];
    let account_addresses: Vec<Pubkey> = ix.accounts
        .iter()
        .map(|&idx| tx_accounts[idx as usize])
        .collect();
        
    match program_id {
        spl_token::ID => decode_token_instruction(&ix.data, &account_addresses),
        system_program::ID => decode_system_instruction(&ix.data, &account_addresses),
        _ => DecodedInstruction::Unknown
    }
}

Important Notes

  • Represents a single Cross-Program Invocation (CPI)
  • Key aspects:
    • All account indices reference parent transaction’s account array
    • Stack height indicates CPI nesting level
    • Data format depends on the program being called

Common Program Interactions

// Example of a token transfer inner instruction
let token_transfer = InnerInstruction {
    program_id_index: 5,  // Token program
    accounts: vec![
        1,  // Source account
        2,  // Destination account
        3,  // Authority
    ],
    data: vec![/* Transfer instruction data */],
    stack_height: Some(1)
};
 
// Example of a system program create account instruction
let create_account = InnerInstruction {
    program_id_index: 0,  // System program
    accounts: vec![
        4,  // Funding account
        5,  // New account
    ],
    data: vec![/* CreateAccount instruction data */],
    stack_height: Some(1)
};

Limitations

  • Stack height limited to 4 levels in Solana runtime
  • Account index must be valid in parent transaction
  • Program index must reference a deployed program
  • Data size limited by transaction size limits

CompiledInstruction

pub struct CompiledInstruction {
    pub program_id_index: u32,
    pub accounts: Vec<u8>,
    pub data: Vec<u8>,
}

Fields

program_id_index

  • Description: Index into the transaction’s account keys array that points to the program to execute
  • Type: u32
  • Example: 0 (System Program) or 1 (Token Program)

accounts

  • Description: Ordered list of indices into the transaction’s account keys, specifying which accounts the instruction will operate on
  • Type: Vec<u8>
  • Example:
    vec![1, 2, 3]  // References accounts at indices 1, 2, and 3 in transaction

data

  • Description: Program-specific instruction data bytes
  • Type: Vec<u8>
  • Example:
    // Transfer instruction data for System Program
    vec![
        2, 0, 0, 0,  // Transfer instruction index
        64, 66, 15, 0, 0, 0, 0, 0  // Amount (1000000 lamports)
    ]

Example Use Cases

fn decode_instruction(
    ix: &CompiledInstruction,
    account_keys: &[Pubkey]
) -> ProgramInstruction {
    let program_id = &account_keys[ix.program_id_index as usize];
    let accounts: Vec<&Pubkey> = ix.accounts
        .iter()
        .map(|&idx| &account_keys[idx as usize])
        .collect();
        
    match *program_id {
        // System Program instructions
        system_program::ID => {
            SystemInstruction::decode(&ix.data, &accounts)
        },
        // Token Program instructions
        spl_token::ID => {
            TokenInstruction::decode(&ix.data, &accounts)
        },
        _ => ProgramInstruction::Unknown
    }
}

Important Notes

  • Key differences from InnerInstruction:
    • These are top-level transaction instructions
    • No stack height (they start the CPI chain)
    • Part of the original transaction message

Common Patterns

// Example: Token Transfer Instruction
let token_transfer = CompiledInstruction {
    program_id_index: 4,  // Token program
    accounts: vec![
        1,  // Source token account
        2,  // Destination token account
        3,  // Owner's wallet
    ],
    data: vec![
        3,  // Transfer instruction discriminator
        // ... amount data ...
    ]
};
 
// Example: Create Account Instruction
let create_account = CompiledInstruction {
    program_id_index: 0,  // System program
    accounts: vec![
        1,  // Funding account
        2,  // New account address
    ],
    data: vec![
        0,  // CreateAccount instruction discriminator
        // ... space and lamport data ...
    ]
};

TokenBalance

pub struct TokenBalance {
    pub account_index: u32,
    pub mint: String,
    pub ui_token_amount: Option<UiTokenAmount>,
    pub owner: String,
    pub program_id: String,
}
 
pub struct UiTokenAmount {
    pub ui_amount: f64,
    pub decimals: u32,
    pub amount: String,
    pub ui_amount_string: String,
}

Fields

account_index

  • Description: Index of the token account in the transaction’s account keys
  • Type: u32
  • Example: 5 (references the 6th account in transaction)

mint

  • Description: Public key of the token’s mint account as base-58 string
  • Type: String
  • Example: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" (USDC mint)

ui_token_amount

  • Description: Token amount with various numeric representations
  • Type: Option<UiTokenAmount>
  • Contains:
    • ui_amount: Human-readable decimal amount (f64)
    • decimals: Token’s decimal places
    • amount: Raw token amount as string
    • ui_amount_string: Precise decimal string representation

owner

  • Description: Public key of the token account’s owner as base-58 string
  • Type: String
  • Example: "DjPi4sP9hPwV5kJQcevwSaS7oKvM9gQhSBr5XbxmrTHr"

program_id

  • Description: Public key of the token program as base-58 string
  • Type: String
  • Example: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" (Token Program)

Example Use Cases

fn analyze_token_balance_change(
    pre: &TokenBalance,
    post: &TokenBalance
) -> TokenBalanceChange {
    let pre_amount = pre.ui_token_amount.as_ref()
        .map(|amt| amt.ui_amount)
        .unwrap_or(0.0);
    let post_amount = post.ui_token_amount.as_ref()
        .map(|amt| amt.ui_amount)
        .unwrap_or(0.0);
        
    TokenBalanceChange {
        token_mint: pre.mint.clone(),
        owner: pre.owner.clone(),
        amount_change: post_amount - pre_amount,
        decimals: pre.ui_token_amount.as_ref()
            .map(|amt| amt.decimals)
            .unwrap_or(0)
    }
}

Common Token Examples

// USDC Balance Example
let usdc_balance = TokenBalance {
    account_index: 3,
    mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(),
    ui_token_amount: Some(UiTokenAmount {
        ui_amount: 100.50,
        decimals: 6,
        amount: "100500000".to_string(),
        ui_amount_string: "100.500000".to_string()
    }),
    owner: "DjPi4sP9hPwV5kJQcevwSaS7oKvM9gQhSBr5XbxmrTHr".to_string(),
    program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_string()
};
 
// NFT Balance Example
let nft_balance = TokenBalance {
    account_index: 4,
    mint: "CQNKXw1rw2eWwi812Exk4cKUjKuomZ2156STGRyXd2Mp".to_string(),
    ui_token_amount: Some(UiTokenAmount {
        ui_amount: 1.0,
        decimals: 0,
        amount: "1".to_string(),
        ui_amount_string: "1".to_string()
    }),
    owner: "DjPi4sP9hPwV5kJQcevwSaS7oKvM9gQhSBr5XbxmrTHr".to_string(),
    program_id: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_string()
};

Important Notes

  • Used to track token account balances before and after transactions
  • Supports both fungible tokens and NFTs
  • Common scenarios:
    • Token transfers
    • Liquidity pool operations
    • NFT trades
    • Staking operations

Rewards

pub struct Rewards {
    pub rewards: Vec<Reward>,
}
 
pub struct Reward {
    pub pubkey: String,
    pub lamports: i64,
    pub post_balance: u64,
    pub reward_type: i32,
    pub commission: String,
}
 

RewardType

pub enum RewardType {
    Unspecified = 0,  // Default/unknown reward type
    Fee = 1,          // Transaction fee rewards
    Rent = 2,         // Rent collection rewards
    Staking = 3,      // Staking rewards for validators/delegators
    Voting = 4,       // Voting rewards for governance participation
}

Fields

pubkey

  • Description: Base-58 encoded public key of the reward recipient
  • Type: String
  • Example: "Vote111111111111111111111111111111111111111"

lamports

  • Description: Amount of lamports rewarded (or subtracted if negative)
  • Type: i64
  • Example: 500000 (0.0005 SOL reward)

post_balance

  • Description: Account balance after reward is applied
  • Type: u64
  • Example: 1000000000 (1 SOL total balance)

reward_type

  • Description: Type of reward (Fee, Rent, Staking, or Voting)
  • Type: i32 (enum RewardType)
  • Example: 2 (Staking reward)

commission

  • Description: Vote account commission percentage when applicable
  • Type: String
  • Example: "10" (10% commission)

Example Use Cases

fn analyze_rewards(rewards: &[Reward]) -> RewardAnalysis {
    let mut analysis = RewardAnalysis::default();
    
    for reward in rewards {
        match reward.reward_type {
            0 => { // Fee rewards
                analysis.total_fee_rewards += reward.lamports;
            },
            2 => { // Staking rewards
                analysis.total_staking_rewards += reward.lamports;
                analysis.validator_commissions.push((
                    reward.pubkey.clone(),
                    reward.commission.parse().unwrap_or(0)
                ));
            },
            _ => {
                analysis.other_rewards += reward.lamports;
            }
        }
    }
    
    analysis
}

Common Reward Scenarios

// Validator Staking Reward
let staking_reward = Reward {
    pubkey: "Va1idator1111111111111111111111111111111111".to_string(),
    lamports: 1_500_000,  // 0.0015 SOL
    post_balance: 100_000_000_000,  // 100 SOL
    reward_type: 2,  // Staking
    commission: "10".to_string()  // 10% commission
};
 
// Transaction Fee Reward
let fee_reward = Reward {
    pubkey: "Co11ector1111111111111111111111111111111111".to_string(),
    lamports: 5000,  // 0.000005 SOL
    post_balance: 50_000_000_000,  // 50 SOL
    reward_type: 0,  // Fee
    commission: "".to_string()
};

Important Notes

  • Key aspects:

    • Rewards can be negative (penalties/deductions)
    • Commission only relevant for vote accounts
    • Post balance includes the reward amount
    • Pubkey identifies recipient account
  • Common use cases:

    • Validator reward tracking
    • Staking APY calculations
    • Fee distribution analysis
    • Economic activity monitoring
  • Limitations:

    • Commission is string to handle special cases
    • Reward types are fixed by protocol
    • Rewards processed at epoch boundaries

ReturnData

pub struct ReturnData {
    pub program_id: Vec<u8>,
    pub data: Vec<u8>,
}

Fields

program_id

  • Description: Public key of the program that generated the return data
  • Type: Vec<u8>
  • Example:
    // Base58: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
    vec![/* 32 bytes of program ID */]

data

  • Description: Arbitrary data returned by the program
  • Type: Vec<u8>
  • Example:
    // Example return data from a swap operation
    vec![
        // Amount out from swap
        64, 66, 15, 0, 0, 0, 0, 0,
        // Swap success flag
        1
    ]

Example Use Cases

fn process_return_data(return_data: &ReturnData) -> ProgramReturnValue {
    let program = Pubkey::new(&return_data.program_id);
    
    match program {
        // Token Swap Program
        token_swap::ID => {
            decode_swap_return_data(&return_data.data)
        },
        // Oracle Program
        oracle::ID => {
            decode_price_feed_data(&return_data.data)
        },
        // Custom Program
        _ => ProgramReturnValue::Unknown
    }
}
 
// Example decoder for swap return data
fn decode_swap_return_data(data: &[u8]) -> SwapResult {
    SwapResult {
        amount_out: u64::from_le_bytes(data[0..8].try_into().unwrap()),
        success: data[8] == 1
    }
}

Common Return Data Patterns

// Oracle price feed return data
let oracle_return = ReturnData {
    program_id: oracle_program_id.to_bytes().to_vec(),
    data: vec![
        // Price in cents (15.50 USD)
        1550u32.to_le_bytes().to_vec(),
        // Confidence interval
        10u32.to_le_bytes().to_vec(),
        // Timestamp
        1234567890u64.to_le_bytes().to_vec()
    ].concat()
};
 
// Token swap return data
let swap_return = ReturnData {
    program_id: swap_program_id.to_bytes().to_vec(),
    data: vec![
        // Amount received from swap
        1000000u64.to_le_bytes().to_vec(),
        // Minimum amount met flag
        vec![1u8]
    ].concat()
};

Important Notes

  • Added in Solana v1.8.0

  • Key uses:

    • Program-to-program communication
    • Off-chain data retrieval
    • Transaction result verification
    • Complex computation results
  • Limitations:

    • Size limited by transaction size limits
    • Only last return data preserved
    • Must be explicitly set by program
    • Not available in older versions
  • Common applications:

    • DEX swap results
    • Oracle price feeds
    • Computation results
    • Status indicators
    • Cross-program messaging