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
header
- 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:
- Writable signed accounts
- Readonly signed accounts
- Writable unsigned accounts
- 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
- Single signer:
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 issuesAlreadyProcessed
,BlockhashNotFound
: Transaction timing and validity issuesCallChainTooDeep
: Cross-program invocation (CPI) depth limits
- Account-Related Errors:
AccountNotFound
,ProgramAccountNotFound
: Missing account errorsInsufficientFundsForFee
,InvalidAccountForFee
: Fee-related issuesInvalidRentPayingAccount
,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
andlog_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) or1
(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