Skip to main content

Overview

Commitment levels in Solana represent different stages of transaction and block confirmation. Yellowstone gRPC allows you to specify which commitment level you want to receive updates for, letting you choose between speed and certainty.

Commitment Levels

Yellowstone gRPC supports three commitment levels defined in the protocol:
enum CommitmentLevel {
  PROCESSED = 0;
  CONFIRMED = 1;
  FINALIZED = 2;
}

Processed

Fastest, Least CertainThe transaction has been processed by a validator and added to a block, but the block may not be confirmed or finalized. This is the fastest way to see updates but carries the highest risk of reorgs.Use when: You need real-time updates and can handle potential rollbacks

Confirmed

Balanced Speed & SafetyThe block containing the transaction has been confirmed by the cluster (66%+ stake agreement). This is the recommended level for most applications.Use when: You need reliable updates without waiting for full finality

Finalized

Slowest, Most CertainThe block has been finalized by the cluster and is extremely unlikely to be rolled back. This provides the highest level of certainty.Use when: You need absolute certainty and can tolerate delays

Setting Commitment Level

use yellowstone_grpc_proto::prelude::{
    SubscribeRequest,
    SubscribeRequestFilterAccounts,
    CommitmentLevel,
};
use std::collections::HashMap;

let mut accounts = HashMap::new();
accounts.insert(
    "client".to_owned(),
    SubscribeRequestFilterAccounts {
        account: vec!["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".to_string()],
        owner: vec![],
        filters: vec![],
        nonempty_txn_signature: None,
    },
);

// Use confirmed commitment level
let request = SubscribeRequest {
    accounts,
    commitment: Some(CommitmentLevel::Confirmed as i32),
    ..Default::default()
};

Commitment Level Behavior

The commitment level affects all subscription types in your request:
  • Accounts - Updates are sent when the account is modified at the specified commitment level
  • Transactions - Transactions are sent when they reach the specified commitment level
  • Blocks - Blocks are sent when they reach the specified commitment level
  • Slots - Slots can be filtered by commitment level using filter_by_commitment

Slot Commitment Filtering

Slots are special because they progress through multiple commitment levels. You can control whether you want all slot updates or only updates at your specified commitment level:
use yellowstone_grpc_proto::prelude::{
    SubscribeRequest,
    SubscribeRequestFilterSlots,
    CommitmentLevel,
};
use std::collections::HashMap;

let mut slots = HashMap::new();
slots.insert(
    "client".to_owned(),
    SubscribeRequestFilterSlots {
        // Only receive slots at the confirmed level
        filter_by_commitment: Some(true),
        interslot_updates: Some(false),
    },
);

let request = SubscribeRequest {
    slots,
    commitment: Some(CommitmentLevel::Confirmed as i32),
    ..Default::default()
};
With filter_by_commitment: true:
  • You only receive slot updates when they reach your specified commitment level
  • Example: With confirmed commitment, you only get slots when they’re confirmed
With filter_by_commitment: false (or None):
  • You receive slot updates at all commitment levels
  • You’ll see slots progress from processed → confirmed → finalized

Slot Status vs Commitment Level

Slot updates include a status field that indicates the slot’s current state:
enum SlotStatus {
  SLOT_PROCESSED = 0;
  SLOT_CONFIRMED = 1;
  SLOT_FINALIZED = 2;
  SLOT_FIRST_SHRED_RECEIVED = 3;
  SLOT_COMPLETED = 4;
  SLOT_CREATED_BANK = 5;
  SLOT_DEAD = 6;
}
Example of processing slot updates:
use yellowstone_grpc_proto::geyser::SlotStatus;
use yellowstone_grpc_proto::prelude::{
    subscribe_update::UpdateOneof,
};

while let Some(message) = stream.next().await {
    match message?.update_oneof {
        Some(UpdateOneof::Slot(slot_update)) => {
            let status = SlotStatus::try_from(slot_update.status)
                .unwrap_or(SlotStatus::SlotProcessed);
            
            match status {
                SlotStatus::SlotProcessed => {
                    println!("Slot {} processed", slot_update.slot);
                }
                SlotStatus::SlotConfirmed => {
                    println!("Slot {} confirmed", slot_update.slot);
                }
                SlotStatus::SlotFinalized => {
                    println!("Slot {} finalized", slot_update.slot);
                }
                SlotStatus::SlotDead => {
                    println!("Slot {} died: {:?}", slot_update.slot, slot_update.dead_error);
                }
                _ => {
                    println!("Slot {} - status: {:?}", slot_update.slot, status);
                }
            }
        }
        _ => {}
    }
}

Timing Expectations

Typical timing for commitment levels on Solana:
Commitment LevelApproximate TimeReorg Risk
Processed~400-600msHigh - blocks may be orphaned
Confirmed~1-2 secondsLow - requires 66%+ stake to reorg
Finalized~12-32 secondsVery Low - requires 66%+ stake over 31+ slots
These times are approximate and depend on network conditions. Mainnet-beta typically has faster confirmation times than testnet.

Use Cases by Commitment Level

Best for:
  • Real-time price feeds
  • UI updates that can be corrected
  • Activity monitoring dashboards
  • Early detection of events
Example: Real-time DEX price monitoring
let request = SubscribeRequest {
    accounts,
    commitment: Some(CommitmentLevel::Processed as i32),
    ..Default::default()
};
// Process updates immediately but be prepared for rollbacks
Not recommended for:
  • Financial transactions
  • Permanent state changes
  • Critical business logic

Monitoring All Commitment Levels

You can create multiple subscriptions with different commitment levels if you need to track progression:
use futures::stream::StreamExt;

// Open the stream
let (mut subscribe_tx, mut stream) = client.subscribe().await?;

// Send requests for different commitment levels
subscribe_tx.send(SubscribeRequest {
    accounts: accounts.clone(),
    commitment: Some(CommitmentLevel::Processed as i32),
    ..Default::default()
}).await?;

subscribe_tx.send(SubscribeRequest {
    accounts: accounts.clone(),
    commitment: Some(CommitmentLevel::Confirmed as i32),
    ..Default::default()
}).await?;

// Process updates from all commitment levels
while let Some(message) = stream.next().await {
    // Handle updates...
}

Commitment Level with Other Features

Commitment + Data Slicing

let request = SubscribeRequest {
    accounts,
    accounts_data_slice: vec![
        SubscribeRequestAccountsDataSlice {
            offset: 0,
            length: 32,
        },
    ],
    commitment: Some(CommitmentLevel::Confirmed as i32),
    ..Default::default()
};

Commitment + Filters

let request = SubscribeRequest {
    transactions,
    commitment: Some(CommitmentLevel::Confirmed as i32),
    ..Default::default()
};
// Transaction filters are applied at the confirmed level

Best Practices

Use Confirmed by Default

Unless you have specific requirements, use confirmed commitment for the best balance of speed and safety.

Match Your Use Case

Choose processed for real-time UIs, confirmed for most apps, finalized for critical operations.

Handle Reorgs

If using processed commitment, implement logic to handle potential rollbacks.

Consider Latency

Higher commitment levels = more certainty but also more latency. Plan accordingly.

Troubleshooting

  • Check that your commitment level matches the activity you’re monitoring
  • Verify that the accounts/transactions you’re filtering for are active at that commitment level
  • Try lowering to processed to see if updates appear faster
  • This is expected with higher commitment levels
  • finalized can take 12-32 seconds
  • Consider using confirmed if the delay is too long
  • This can happen if you’re not using filter_by_commitment with slots
  • Or if you’ve subscribed multiple times with different filters
  • Check the filters field in updates to identify the source
  • This is normal - processed blocks can be orphaned
  • Use confirmed or finalized if you need stable data
  • Implement rollback logic if you must use processed

Next Steps

Build docs developers (and LLMs) love