# Tip Floor Stream

Stream real-time tip floor data from NextBlock using JavaScript/TypeScript.

<pre class="language-typescript"><code class="lang-typescript"><strong>// Tip floor data interface
</strong>interface TipFloorData {
  time: string;
  landed_tips_25th_percentile: number;
  landed_tips_50th_percentile: number;
  landed_tips_75th_percentile: number;
  landed_tips_95th_percentile: number;
  landed_tips_99th_percentile: number;
  ema_landed_tips_50th_percentile: number;
}

<strong>// Tip strategy management
</strong>class TipStrategy {
  conservativeTip: number = 0;     // Will be set from tip floor data
  normalTip: number = 0;          // Will be set from tip floor data
  aggressiveTip: number = 0;      // Will be set from tip floor data
  priorityTip: number = 0;        // Will be set from tip floor data
  lastUpdated?: Date;

  updateFromTipFloor(tipFloor: TipFloorData): void {
    this.conservativeTip = this.solToLamports(tipFloor.landed_tips_25th_percentile);
    this.normalTip = this.solToLamports(tipFloor.landed_tips_50th_percentile);
    this.aggressiveTip = this.solToLamports(tipFloor.landed_tips_75th_percentile);
    this.priorityTip = this.solToLamports(tipFloor.landed_tips_95th_percentile);
    this.lastUpdated = new Date();
  }

  private solToLamports(sol: number): number {
    return Math.floor(sol * 1_000_000_000);
  }

  getTipForPriority(priority: 'conservative' | 'normal' | 'aggressive' | 'priority'): number {
    switch (priority) {
      case 'conservative': return this.conservativeTip;
      case 'normal': return this.normalTip;
      case 'aggressive': return this.aggressiveTip;
      case 'priority': return this.priorityTip;
      default: return this.normalTip;
    }
  }
}

<strong>// Global tip strategy instance
</strong>const globalTipStrategy = new TipStrategy();

<strong>// Stream tip floor data
</strong>async function streamTipFloor(
  // nextblockClient: any, // Your generated gRPC client
  updateFrequency: string = '1m',
  callback?: (tipFloor: TipFloorData) => Promise&#x3C;void> | void
): Promise&#x3C;void> {
  console.log(`Starting tip floor stream with frequency: ${updateFrequency}`);

  /* Uncomment when you have the generated gRPC client
  try {
    const request = { updateFrequency };
    const stream = nextblockClient.streamTipFloor(request);

    console.log('Streaming tip floor data:');

    for await (const tipFloorResponse of stream) {
      try {
        const tipFloor: TipFloorData = {
          time: tipFloorResponse.time,
          landed_tips_25th_percentile: tipFloorResponse.landed_tips_25th_percentile,
          landed_tips_50th_percentile: tipFloorResponse.landed_tips_50th_percentile,
          landed_tips_75th_percentile: tipFloorResponse.landed_tips_75th_percentile,
          landed_tips_95th_percentile: tipFloorResponse.landed_tips_95th_percentile,
          landed_tips_99th_percentile: tipFloorResponse.landed_tips_99th_percentile,
          ema_landed_tips_50th_percentile: tipFloorResponse.ema_landed_tips_50th_percentile,
        };

        console.log('Received tip floor update:');
        console.log(`  Time: ${tipFloor.time}`);
        console.log(`  25th percentile: ${tipFloor.landed_tips_25th_percentile.toFixed(6)} SOL`);
        console.log(`  50th percentile: ${tipFloor.landed_tips_50th_percentile.toFixed(6)} SOL`);
        console.log(`  75th percentile: ${tipFloor.landed_tips_75th_percentile.toFixed(6)} SOL`);
        console.log(`  95th percentile: ${tipFloor.landed_tips_95th_percentile.toFixed(6)} SOL`);
        console.log(`  EMA 50th percentile: ${tipFloor.ema_landed_tips_50th_percentile.toFixed(6)} SOL`);
        console.log('  ---');

        // Update global tip strategy
        await processTipFloorUpdate(tipFloor);

        // Call custom callback if provided
        if (callback) {
          await callback(tipFloor);
        }

      } catch (error) {
        console.error('Error processing tip floor update:', error);
      }
    }

  } catch (error) {
    console.error('Tip floor stream error:', error);
    // Implement reconnection logic here
  }
  */

<strong>  // Mock streaming for demonstration
</strong>  console.log('Mock tip floor streaming started...');

  const mockStream = setInterval(async () => {
<strong>    // Generate mock tip floor data
</strong>    const mockTipFloor: TipFloorData = {
      time: new Date().toISOString(),
      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,
    };

    console.log('Mock tip floor update:', mockTipFloor);
    await processTipFloorUpdate(mockTipFloor);

    if (callback) {
      await callback(mockTipFloor);
    }
  }, 60000); // Update every minute

<strong>  // Return a promise that never resolves (keeps streaming)
</strong>  return new Promise(() => {
    // Keep the interval running
    process.on('SIGINT', () => {
      clearInterval(mockStream);
      console.log('Tip floor streaming stopped');
      process.exit(0);
    });
  });
}

<strong>// Process tip floor updates
</strong>async function processTipFloorUpdate(tipFloor: TipFloorData): Promise&#x3C;void> {
<strong>  // Update global strategy
</strong>  globalTipStrategy.updateFromTipFloor(tipFloor);

  console.log('Updated tip strategy:');
  console.log(`  Conservative: ${globalTipStrategy.conservativeTip} lamports`);
  console.log(`  Normal: ${globalTipStrategy.normalTip} lamports`);
  console.log(`  Aggressive: ${globalTipStrategy.aggressiveTip} lamports`);
  console.log(`  Priority: ${globalTipStrategy.priorityTip} lamports`);

<strong>  // Store historical data
</strong>  await storeTipFloorData(tipFloor);

<strong>  // Trigger any pending transactions
</strong>  await triggerPendingTransactions();
}

<strong>// Historical data management
</strong>class TipFloorHistory {
  private data: TipFloorData[] = [];
  private readonly maxSize: number;

  constructor(maxSize: number = 1000) {
    this.maxSize = maxSize;
  }

  add(tipFloor: TipFloorData): void {
    if (this.data.length >= this.maxSize) {
      this.data.shift(); // Remove oldest
    }
    this.data.push(tipFloor);
  }

  getTrend(percentile: '25th' | '50th' | '75th' | '95th' = '50th', window: number = 10): number {
    if (this.data.length &#x3C; 2) return 0;

    const recentData = this.data.slice(-window);
    if (recentData.length &#x3C; 2) return 0;

    const key = `landed_tips_${percentile}_percentile` as keyof TipFloorData;
    const startValue = recentData[0][key] as number;
    const endValue = recentData[recentData.length - 1][key] as number;

    return endValue - startValue;
  }

  getAverage(percentile: '25th' | '50th' | '75th' | '95th' = '50th', window: number = 10): number {
    if (this.data.length === 0) return 0;

    const recentData = this.data.slice(-window);
    const key = `landed_tips_${percentile}_percentile` as keyof TipFloorData;
    const values = recentData.map(d => d[key] as number);

    return values.reduce((sum, val) => sum + val, 0) / values.length;
  }

  get length(): number {
    return this.data.length;
  }
}

<strong>// Global history tracker
</strong>const tipFloorHistory = new TipFloorHistory();

<strong>// Smart tip calculation with trend analysis
</strong>async function getSmartTip(
  basePriority: 'conservative' | 'normal' | 'aggressive' | 'priority' = 'normal',
  considerTrend: boolean = true
): Promise&#x3C;number> {
  const baseTip = globalTipStrategy.getTipForPriority(basePriority);

  if (!considerTrend || tipFloorHistory.length &#x3C; 2) {
    return baseTip;
  }

<strong>  // Analyze trend
</strong>  const trend = tipFloorHistory.getTrend('50th', 5);

<strong>  // Adjust tip based on trend
</strong>  let adjustmentFactor = 1.0;
  if (trend > 0.001) {
    adjustmentFactor = 1.2;
    console.log(`Tips trending up (+${trend.toFixed(6)}), increasing tip by 20%`);
  } else if (trend &#x3C; -0.001) {
    adjustmentFactor = 0.9;
    console.log(`Tips trending down (${trend.toFixed(6)}), decreasing tip by 10%`);
  } else {
    console.log(`Tips stable (${trend.toFixed(6)}), no adjustment`);
  }

  const smartTip = Math.floor(baseTip * adjustmentFactor);
  return Math.max(smartTip, 100_000); // Minimum tip of 0.0001 SOL
}

<strong>// Store tip floor data
</strong>async function storeTipFloorData(tipFloor: TipFloorData): Promise&#x3C;void> {
<strong>  // Add to history
</strong>  tipFloorHistory.add(tipFloor);

<strong>  // Optionally save to file
</strong>  const fs = require('fs').promises;
  const filename = `tip_data_${new Date().toISOString().split('T')[0]}.jsonl`;

  try {
    await fs.appendFile(filename, JSON.stringify(tipFloor) + '\n');
  } catch (error) {
    console.error('Failed to store tip floor data:', error);
  }
}

<strong>// Trigger pending transactions
</strong>async function triggerPendingTransactions(): Promise&#x3C;void> {
  console.log('Checking for pending transactions to trigger...');
<strong>  // Implementation would check your pending transaction queue
</strong>  // and submit them with updated tip amounts
}

<strong>// Get current optimal tips
</strong>function getCurrentOptimalTips(): {
  conservative: number;
  normal: number;
  aggressive: number;
  priority: number;
} {
  return {
    conservative: globalTipStrategy.getTipForPriority('conservative'),
    normal: globalTipStrategy.getTipForPriority('normal'),
    aggressive: globalTipStrategy.getTipForPriority('aggressive'),
    priority: globalTipStrategy.getTipForPriority('priority'),
  };
}
</code></pre>

## Usage Examples

<pre class="language-typescript"><code class="lang-typescript"><strong>async function tipFloorExample() {
</strong><strong>  // Connect to NextBlock (see connection.md)
</strong>  // const config = configFromEnv();
  // const manager = new NextBlockConnectionManager(config);
  // await manager.connect();
  // const nextblockClient = manager.grpcClient;

<strong>  // Custom callback for tip floor updates
</strong>  const onTipFloorUpdate = async (tipFloor: TipFloorData) => {
    console.log(`Custom handler: Received update at ${tipFloor.time}`);

<strong>    // Example: Trigger high-priority transactions when tips are low
</strong>    if (tipFloor.landed_tips_50th_percentile &#x3C; 0.002) { // Less than 0.002 SOL
      console.log('Tips are low - good time for high-priority transactions!');
      // await submitPriorityTransactions();
    }
  };

<strong>  // Start streaming in background
</strong>  const streamPromise = streamTipFloor(
    // nextblockClient,
    '1m',
    onTipFloorUpdate
  );

<strong>  // Example usage of dynamic tips
</strong>  setTimeout(async () => {
<strong>    // Get current optimal tips
</strong>    const currentTips = getCurrentOptimalTips();
    console.log('Current optimal tips:', currentTips);

<strong>    // Get smart tip with trend analysis
</strong>    const smartTip = await getSmartTip('normal', true);
    console.log(`Smart tip: ${smartTip} lamports`);

<strong>    // Example: Use tips in transaction submission
</strong>    // await submitTransactionWithTip(smartTip);

  }, 5000); // Wait for initial data

<strong>  // Keep streaming
</strong>  try {
    await streamPromise;
  } catch (error) {
    console.error('Streaming error:', error);
  }
}

<strong>// Advanced tip management example
</strong>async function advancedTipManagement() {
<strong>  // Monitor tip trends and adjust strategy
</strong>  const monitorTrends = setInterval(async () => {
    if (tipFloorHistory.length >= 10) {
      const trend5min = tipFloorHistory.getTrend('50th', 5);
      const trend10min = tipFloorHistory.getTrend('50th', 10);
      const average = tipFloorHistory.getAverage('50th', 10);

      console.log('Tip Analysis:');
      console.log(`  5-min trend: ${trend5min.toFixed(6)} SOL`);
      console.log(`  10-min trend: ${trend10min.toFixed(6)} SOL`);
      console.log(`  10-min average: ${average.toFixed(6)} SOL`);

<strong>      // Adjust strategy based on trends
</strong>      if (trend5min > 0.002 &#x26;&#x26; trend10min > 0.001) {
        console.log('Strong upward trend detected - consider higher tips');
      } else if (trend5min &#x3C; -0.002 &#x26;&#x26; trend10min &#x3C; -0.001) {
        console.log('Strong downward trend detected - can use lower tips');
      }
    }
  }, 300000); // Check every 5 minutes

<strong>  // Clean up on exit
</strong>  process.on('SIGINT', () => {
    clearInterval(monitorTrends);
    console.log('Advanced tip management stopped');
    process.exit(0);
  });
}

<strong>// Main example runner
</strong>async function main() {
  console.log('NextBlock Tip Floor Streaming Examples');

  try {
<strong>    // Start tip floor streaming
</strong>    console.log('Starting tip floor streaming...');
    
<strong>    // Run both examples concurrently
</strong>    await Promise.all([
      tipFloorExample(),
      advancedTipManagement()
    ]);

  } catch (error) {
    console.error('Error in main:', error);
  }
}

if (require.main === module) {
  main().catch(console.error);
}

export {
  TipFloorData,
  TipStrategy,
  TipFloorHistory,
  streamTipFloor,
  getSmartTip,
  getCurrentOptimalTips,
  globalTipStrategy,
};
</code></pre>


---

# 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/javascript/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.
