# Tip Floor Stream

Stream real-time tip floor data from NextBlock to optimize your transaction tips dynamically.

## Streaming Tip Floor Data

<pre class="language-rust"><code class="lang-rust"><strong>use tokio_stream::StreamExt;
</strong>use std::time::Duration;
use serde::{Deserialize, Serialize};

<strong>// Tip floor response structure
</strong>#[derive(Debug, Deserialize, Serialize)]
pub struct TipFloorData {
    pub time: String,
    pub landed_tips_25th_percentile: f64,
    pub landed_tips_50th_percentile: f64,
    pub landed_tips_75th_percentile: f64,
    pub landed_tips_95th_percentile: f64,
    pub landed_tips_99th_percentile: f64,
    pub ema_landed_tips_50th_percentile: f64,
}

<strong>// Stream tip floor updates
</strong>async fn stream_tip_floor(
    // nextblock_client: &#x26;mut YourGeneratedApiClient,
    update_frequency: String,
) -> Result&#x3C;(), Box&#x3C;dyn std::error::Error>> {
    println!("Starting tip floor stream with frequency: {}", update_frequency);
    
<strong>    // Create streaming request
</strong>    /* Uncomment when you have the generated API client
    let request = tonic::Request::new(TipFloorStreamRequest {
        update_frequency,
    });
    
    let mut stream = nextblock_client
        .stream_tip_floor(request)
        .await?
        .into_inner();
    
    println!("Streaming tip floor data:");
    
    while let Some(tip_floor_response) = stream.next().await {
        match tip_floor_response {
            Ok(tip_floor) => {
                println!("Received tip floor update:");
                println!("  Time: {}", tip_floor.time);
                println!("  25th percentile: {:.6} SOL", tip_floor.landed_tips_25th_percentile);
                println!("  50th percentile: {:.6} SOL", tip_floor.landed_tips_50th_percentile);
                println!("  75th percentile: {:.6} SOL", tip_floor.landed_tips_75th_percentile);
                println!("  95th percentile: {:.6} SOL", tip_floor.landed_tips_95th_percentile);
                println!("  99th percentile: {:.6} SOL", tip_floor.landed_tips_99th_percentile);
                println!("  EMA 50th percentile: {:.6} SOL", tip_floor.ema_landed_tips_50th_percentile);
                println!("  ---");
                
                // Process the tip floor data
                process_tip_floor_update(&#x26;tip_floor).await?;
            }
            Err(e) => {
                eprintln!("Stream error: {}", e);
                // Implement reconnection logic here
                break;
            }
        }
    }
    */
    
<strong>    // Mock streaming for demonstration
</strong>    println!("Mock tip floor streaming started...");
    let mut interval = tokio::time::interval(Duration::from_secs(60));
    
    loop {
        interval.tick().await;
        let mock_tip_floor = TipFloorData {
            time: chrono::Utc::now().to_rfc3339(),
            landed_tips_25th_percentile: 0.0011,
            landed_tips_50th_percentile: 0.005000001,
            landed_tips_75th_percentile: 0.01555,
            landed_tips_95th_percentile: 0.09339195639999975,
            landed_tips_99th_percentile: 0.4846427910400001,
            ema_landed_tips_50th_percentile: 0.005989477267191758,
        };
        
        println!("Mock tip floor update: {:#?}", mock_tip_floor);
        process_tip_floor_update(&#x26;mock_tip_floor).await?;
    }
}

<strong>// Process tip floor updates
</strong>async fn process_tip_floor_update(
    tip_floor: &#x26;TipFloorData,
) -> Result&#x3C;(), Box&#x3C;dyn std::error::Error>> {
<strong>    // Update global tip strategy
</strong>    update_tip_strategy(tip_floor).await?;
    
<strong>    // Optionally store historical data
</strong>    store_tip_floor_data(tip_floor).await?;
    
<strong>    // Trigger any pending transactions with updated tips
</strong>    trigger_pending_transactions().await?;
    
    Ok(())
}
</code></pre>

## Dynamic Tip Calculation

<pre class="language-rust"><code class="lang-rust"><strong>use std::sync::Arc;
</strong>use tokio::sync::RwLock;

<strong>// Global tip strategy state
</strong>#[derive(Debug, Clone)]
pub struct TipStrategy {
    pub conservative_tip: u64,  // 25th percentile
    pub normal_tip: u64,        // 50th percentile  
    pub aggressive_tip: u64,    // 75th percentile
    pub priority_tip: u64,      // 95th percentile
    pub last_updated: chrono::DateTime&#x3C;chrono::Utc>,
}

impl Default for TipStrategy {
    fn default() -> Self {
        Self {
            conservative_tip: 500_000,   // 0.0005 SOL
            normal_tip: 1_000_000,      // 0.001 SOL
            aggressive_tip: 2_000_000,  // 0.002 SOL
            priority_tip: 5_000_000,    // 0.005 SOL
            last_updated: chrono::Utc::now(),
        }
    }
}

<strong>// Global tip strategy instance
</strong>static TIP_STRATEGY: once_cell::sync::Lazy&#x3C;Arc&#x3C;RwLock&#x3C;TipStrategy>>> = 
    once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(TipStrategy::default())));

<strong>// Convert SOL to lamports
</strong>fn sol_to_lamports(sol: f64) -> u64 {
    (sol * 1_000_000_000.0) as u64
}

<strong>// Update tip strategy based on tip floor data
</strong>async fn update_tip_strategy(
    tip_floor: &#x26;TipFloorData,
) -> Result&#x3C;(), Box&#x3C;dyn std::error::Error>> {
    let mut strategy = TIP_STRATEGY.write().await;
    
    strategy.conservative_tip = sol_to_lamports(tip_floor.landed_tips_25th_percentile);
    strategy.normal_tip = sol_to_lamports(tip_floor.landed_tips_50th_percentile);
    strategy.aggressive_tip = sol_to_lamports(tip_floor.landed_tips_75th_percentile);
    strategy.priority_tip = sol_to_lamports(tip_floor.landed_tips_95th_percentile);
    strategy.last_updated = chrono::Utc::now();
    
    println!("Updated tip strategy:");
    println!("  Conservative: {} lamports ({:.6} SOL)", 
             strategy.conservative_tip, tip_floor.landed_tips_25th_percentile);
    println!("  Normal: {} lamports ({:.6} SOL)", 
             strategy.normal_tip, tip_floor.landed_tips_50th_percentile);
    println!("  Aggressive: {} lamports ({:.6} SOL)", 
             strategy.aggressive_tip, tip_floor.landed_tips_75th_percentile);
    println!("  Priority: {} lamports ({:.6} SOL)", 
             strategy.priority_tip, tip_floor.landed_tips_95th_percentile);
    
    Ok(())
}

<strong>// Get optimal tip for transaction priority
</strong>pub async fn get_optimal_tip(priority: TipPriority) -> u64 {
    let strategy = TIP_STRATEGY.read().await;
    
    match priority {
        TipPriority::Conservative => strategy.conservative_tip,
        TipPriority::Normal => strategy.normal_tip,
        TipPriority::Aggressive => strategy.aggressive_tip,
        TipPriority::Priority => strategy.priority_tip,
    }
}

<strong>// Tip priority levels
</strong>#[derive(Debug, Clone, Copy)]
pub enum TipPriority {
    Conservative, // 25th percentile - cheapest option
    Normal,       // 50th percentile - balanced option
    Aggressive,   // 75th percentile - faster execution
    Priority,     // 95th percentile - highest priority
}
</code></pre>

## Advanced Tip Management

<pre class="language-rust"><code class="lang-rust"><strong>use std::collections::VecDeque;
</strong>
<strong>// Historical tip data for trend analysis
</strong>#[derive(Debug)]
pub struct TipHistory {
    data: VecDeque&#x3C;TipFloorData>,
    max_size: usize,
}

impl TipHistory {
    pub fn new(max_size: usize) -> Self {
        Self {
            data: VecDeque::with_capacity(max_size),
            max_size,
        }
    }
    
    pub fn add(&#x26;mut self, tip_floor: TipFloorData) {
        if self.data.len() >= self.max_size {
            self.data.pop_front();
        }
        self.data.push_back(tip_floor);
    }
    
<strong>    // Calculate trend (increasing/decreasing tips)
</strong>    pub fn calculate_trend(&#x26;self) -> f64 {
        if self.data.len() &#x3C; 2 {
            return 0.0;
        }
        
        let recent = &#x26;self.data[self.data.len() - 1];
        let older = &#x26;self.data[self.data.len() - 2];
        
        recent.landed_tips_50th_percentile - older.landed_tips_50th_percentile
    }
    
<strong>    // Get average tip over time window
</strong>    pub fn get_average_tip(&#x26;self, percentile: &#x26;str) -> f64 {
        if self.data.is_empty() {
            return 0.0;
        }
        
        let sum: f64 = self.data.iter().map(|d| {
            match percentile {
                "25th" => d.landed_tips_25th_percentile,
                "50th" => d.landed_tips_50th_percentile,
                "75th" => d.landed_tips_75th_percentile,
                "95th" => d.landed_tips_95th_percentile,
                _ => d.landed_tips_50th_percentile,
            }
        }).sum();
        
        sum / self.data.len() as f64
    }
}

<strong>// Smart tip calculation with trend analysis
</strong>pub async fn get_smart_tip(
    base_priority: TipPriority,
    tip_history: &#x26;TipHistory,
) -> u64 {
    let base_tip = get_optimal_tip(base_priority).await;
    let trend = tip_history.calculate_trend();
    
<strong>    // Adjust tip based on trend
</strong>    let adjustment_factor = if trend > 0.001 {
        1.2 // Tips are increasing, be more aggressive
    } else if trend &#x3C; -0.001 {
        0.9 // Tips are decreasing, can be more conservative
    } else {
        1.0 // No significant trend
    };
    
    ((base_tip as f64) * adjustment_factor) as u64
}
</code></pre>

## Store Historical Data

<pre class="language-rust"><code class="lang-rust"><strong>// Store tip floor data for analysis
</strong>async fn store_tip_floor_data(
    tip_floor: &#x26;TipFloorData,
) -> Result&#x3C;(), Box&#x3C;dyn std::error::Error>> {
<strong>    // Example: Store to file (in production, use a database)
</strong>    let data_dir = std::path::Path::new("./tip_data");
    if !data_dir.exists() {
        tokio::fs::create_dir_all(data_dir).await?;
    }
    
    let filename = format!("tip_floor_{}.json", 
                          chrono::Utc::now().format("%Y%m%d"));
    let filepath = data_dir.join(filename);
    
<strong>    // Append to daily file
</strong>    let json_line = format!("{}\n", serde_json::to_string(tip_floor)?);
    tokio::fs::write(&#x26;filepath, json_line).await?;
    
    println!("Stored tip floor data to {:?}", filepath);
    Ok(())
}

<strong>// Trigger pending transactions with updated tips
</strong>async fn trigger_pending_transactions() -> Result&#x3C;(), Box&#x3C;dyn std::error::Error>> {
<strong>    // This would check for any pending transactions that are waiting
</strong>    // for better tip conditions and submit them with updated tips
    println!("Checking for pending transactions to trigger...");
    
<strong>    // Example: Check if any transactions are waiting for lower tips
</strong>    // and submit them now if conditions are favorable
    
    Ok(())
}
</code></pre>

## Usage Example

<pre class="language-rust"><code class="lang-rust"><strong>#[tokio::main]
</strong>async fn main() -> Result&#x3C;(), Box&#x3C;dyn std::error::Error>> {
<strong>    // Connect to NextBlock (see connection.md)
</strong>    // let config = NextBlockConfig::default();
    // let mut nextblock_client = create_nextblock_client(config).await?;
    
<strong>    // Start tip floor streaming in background
</strong>    let stream_handle = tokio::spawn(async move {
        if let Err(e) = stream_tip_floor(
            // &#x26;mut nextblock_client,
            "1m".to_string(), // Update every minute
        ).await {
            eprintln!("Tip floor streaming error: {}", e);
        }
    });
    
<strong>    // Example: Use dynamic tips in transaction submission
</strong>    tokio::time::sleep(Duration::from_secs(5)).await; // Wait for initial data
    
    let conservative_tip = get_optimal_tip(TipPriority::Conservative).await;
    let normal_tip = get_optimal_tip(TipPriority::Normal).await;
    let aggressive_tip = get_optimal_tip(TipPriority::Aggressive).await;
    
    println!("Current optimal tips:");
    println!("  Conservative: {} lamports", conservative_tip);
    println!("  Normal: {} lamports", normal_tip);
    println!("  Aggressive: {} lamports", aggressive_tip);
    
<strong>    // Use these tips in your transaction submissions
</strong>    // submit_transaction_with_tip(normal_tip).await?;
    
<strong>    // Keep the stream running
</strong>    stream_handle.await??;
    
    Ok(())
}
</code></pre>

## Best Practices

1. **Update frequency**: Use 1-5 minute intervals for most applications
2. **Trend analysis**: Consider tip trends when calculating optimal amounts
3. **Fallback values**: Always have default tip values in case streaming fails
4. **Historical data**: Store tip floor data for analysis and optimization
5. **Priority levels**: Use different tip strategies for different transaction types
6. **Error handling**: Implement reconnection logic for stream interruptions
7. **Resource management**: Limit historical data storage to prevent memory issues


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.nextblock.io/api/examples/rust/tip-floor-stream.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
