# Tip floor stream

Stream real-time tip floor data from NextBlock to optimize your transaction tips dynamically. This example shows a complete implementation including connection setup, authentication, and tip calculation.

## Prerequisites

First, install the required dependencies and generate the gRPC client:

```bash
go mod init nextblock-tip-floor-example
go get google.golang.org/grpc
go get github.com/gagliardetto/solana-go
# Clone and generate the proto client
git clone https://github.com/nextblock-ag/nextblock-proto
# Follow the Go generation instructions in the proto repo
```

## Example

<pre class="language-go"><code class="lang-go"><strong>package main
</strong>
import (
    "context"
    "crypto/x509"
    "fmt"
    "log"
    "sync"
    "time"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/credentials/insecure"
    "google.golang.org/grpc/keepalive"
    // Import your generated proto client here
    // "path/to/your/generated/api"
)

<strong>// Available NextBlock endpoints
</strong>const (
    FrankfurtEndpoint = "frankfurt.nextblock.io:443"
    AmsterdamEndpoint = "amsterdam.nextblock.io:443"
    LondonEndpoint    = "london.nextblock.io:443"
    SingaporeEndpoint = "singapore.nextblock.io:443"
    NewYorkEndpoint   = "ny.nextblock.io:443"
    SaltLakeEndpoint  = "slc.nextblock.io:443"
    TokyoEndpoint     = "tokyo.nextblock.io:443"
)

<strong>// API key credentials for authentication
</strong>type ApiKeyCredentials struct {
    apiKey string
}

func NewApiKeyCredentials(apiKey string) *ApiKeyCredentials {
    return &#x26;ApiKeyCredentials{apiKey: apiKey}
}

func (a *ApiKeyCredentials) RequireTransportSecurity() bool {
    return true
}

func (a *ApiKeyCredentials) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
    return map[string]string{
        "authorization": a.apiKey,
    }, nil
}

<strong>// Tip floor data structure
</strong>type TipFloorData struct {
    Time                         string
    LandedTips25thPercentile     float64
    LandedTips50thPercentile     float64
    LandedTips75thPercentile     float64
    LandedTips95thPercentile     float64
    LandedTips99thPercentile     float64
    EMALandedTips50thPercentile  float64
}

<strong>// Tip strategy for dynamic tip calculation
</strong>type TipStrategy struct {
    mu             sync.RWMutex
    conservativeTip uint64    // 25th percentile in lamports
    normalTip      uint64    // 50th percentile in lamports
    aggressiveTip  uint64    // 75th percentile in lamports
    priorityTip    uint64    // 95th percentile in lamports
    lastUpdated    time.Time
}

func NewTipStrategy() *TipStrategy {
    return &#x26;TipStrategy{
        conservativeTip: 0,   // Will be set from tip floor data
        normalTip:      0,    // Will be set from tip floor data
        aggressiveTip:  0,    // Will be set from tip floor data
        priorityTip:    0,    // Will be set from tip floor data
        lastUpdated:    time.Now(),
    }
}

func (ts *TipStrategy) UpdateFromTipFloor(tipFloor *TipFloorData) {
    ts.mu.Lock()
    defer ts.mu.Unlock()
    
    ts.conservativeTip = solToLamports(tipFloor.LandedTips25thPercentile)
    ts.normalTip = solToLamports(tipFloor.LandedTips50thPercentile)
    ts.aggressiveTip = solToLamports(tipFloor.LandedTips75thPercentile)
    ts.priorityTip = solToLamports(tipFloor.LandedTips95thPercentile)
    ts.lastUpdated = time.Now()
}

func (ts *TipStrategy) GetTip(priority string) uint64 {
    ts.mu.RLock()
    defer ts.mu.RUnlock()
    
    switch priority {
    case "conservative":
        return ts.conservativeTip
    case "normal":
        return ts.normalTip
    case "aggressive":
        return ts.aggressiveTip
    case "priority":
        return ts.priorityTip
    default:
        return ts.normalTip
    }
}

func solToLamports(sol float64) uint64 {
    return uint64(sol * 1_000_000_000)
}

<strong>// Connection setup with authentication
</strong>func connectToNextblock(endpoint, apiKey string, useTLS bool) (*grpc.ClientConn, error) {
    var opts []grpc.DialOption
    
    // Configure transport credentials
    if useTLS {
        pool, err := x509.SystemCertPool()
        if err != nil {
            return nil, fmt.Errorf("failed to get system cert pool: %w", err)
        }
        creds := credentials.NewClientTLSFromCert(pool, "")
        opts = append(opts, grpc.WithTransportCredentials(creds))
    } else {
        opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
    }

    // Keep-alive parameters
    kacp := keepalive.ClientParameters{
        Time:                time.Minute,
        Timeout:             15 * time.Second,
        PermitWithoutStream: true,
    }
    opts = append(opts, grpc.WithKeepaliveParams(kacp))
    
    // Add API key authentication
    opts = append(opts, grpc.WithPerRPCCredentials(NewApiKeyCredentials(apiKey)))

    // Establish connection
    conn, err := grpc.NewClient(endpoint, opts...)
    if err != nil {
        return nil, fmt.Errorf("failed to connect: %w", err)
    }

    return conn, nil
}

<strong>// Stream tip floor data with complete error handling
</strong>func streamTipFloor(endpoint, apiKey string, tipStrategy *TipStrategy) error {
    fmt.Printf("Connecting to NextBlock at %s...\n", endpoint)
    
    // Establish connection
    conn, err := connectToNextblock(endpoint, apiKey, true)
    if err != nil {
        return fmt.Errorf("connection failed: %w", err)
    }
    defer conn.Close()

    // Create API client
    // nextblockApiClient := api.NewApiClient(conn)
    
    fmt.Println("Successfully connected! Starting tip floor stream...")

    /* Uncomment when you have the generated API client
    // Create streaming request
    sub, err := nextblockApiClient.StreamTipFloor(context.TODO(), &#x26;api.TipFloorStreamRequest{
        UpdateFrequency: "1m", // Update every minute
    })
    if err != nil {
        return fmt.Errorf("failed to start tip floor stream: %w", err)
    }

    fmt.Println("Streaming tip floor data (Ctrl+C to stop):")
    
    for {
        tipFloorResponse, err := sub.Recv()
        if err != nil {
            return fmt.Errorf("stream error: %w", err)
        }

        // Convert protobuf response to our struct
        tipFloor := &#x26;TipFloorData{
            Time:                         tipFloorResponse.Time,
            LandedTips25thPercentile:     tipFloorResponse.LandedTips25ThPercentile,
            LandedTips50thPercentile:     tipFloorResponse.LandedTips50ThPercentile,
            LandedTips75thPercentile:     tipFloorResponse.LandedTips75ThPercentile,
            LandedTips95thPercentile:     tipFloorResponse.LandedTips95ThPercentile,
            LandedTips99thPercentile:     tipFloorResponse.LandedTips99ThPercentile,
            EMALandedTips50thPercentile:  tipFloorResponse.EmaLandedTips50ThPercentile,
        }

        // Process the tip floor update
        processTipFloorUpdate(tipFloor, tipStrategy)
    }
    */
    
    // Mock streaming for demonstration
    fmt.Println("Mock tip floor streaming (Ctrl+C to stop):")
    ticker := time.NewTicker(time.Minute)
    defer ticker.Stop()
    
    for {
        select {
        case &#x3C;-ticker.C:
            // Generate mock tip floor data
            mockTipFloor := &#x26;TipFloorData{
                Time:                         time.Now().Format(time.RFC3339),
                LandedTips25thPercentile:     0.0011,
                LandedTips50thPercentile:     0.005000001,
                LandedTips75thPercentile:     0.01555,
                LandedTips95thPercentile:     0.09339195639999975,
                LandedTips99thPercentile:     0.4846427910400001,
                EMALandedTips50thPercentile:  0.005989477267191758,
            }
            
            processTipFloorUpdate(mockTipFloor, tipStrategy)
        }
    }
}

<strong>// Process tip floor updates and update strategy
</strong>func processTipFloorUpdate(tipFloor *TipFloorData, tipStrategy *TipStrategy) {
    fmt.Printf("\n=== Tip Floor Update at %s ===\n", tipFloor.Time)
    fmt.Printf("25th percentile: %.6f SOL\n", tipFloor.LandedTips25thPercentile)
    fmt.Printf("50th percentile: %.6f SOL\n", tipFloor.LandedTips50thPercentile)
    fmt.Printf("75th percentile: %.6f SOL\n", tipFloor.LandedTips75thPercentile)
    fmt.Printf("95th percentile: %.6f SOL\n", tipFloor.LandedTips95thPercentile)
    fmt.Printf("99th percentile: %.6f SOL\n", tipFloor.LandedTips99thPercentile)
    fmt.Printf("EMA 50th percentile: %.6f SOL\n", tipFloor.EMALandedTips50thPercentile)
    
    // Update tip strategy
    tipStrategy.UpdateFromTipFloor(tipFloor)
    
    // Display updated tip recommendations
    fmt.Printf("\n--- Updated Tip Recommendations ---\n")
    fmt.Printf("Conservative: %d lamports (low priority)\n", 
        tipStrategy.GetTip("conservative"))
    fmt.Printf("Normal:       %d lamports (standard priority)\n", 
        tipStrategy.GetTip("normal"))
    fmt.Printf("Aggressive:   %d lamports (high priority)\n", 
        tipStrategy.GetTip("aggressive"))
    fmt.Printf("Priority:     %d lamports (highest priority)\n", 
        tipStrategy.GetTip("priority"))
    fmt.Println("=====================================")
}

<strong>// Example of using dynamic tips in transaction submission
</strong>func exampleTransactionWithDynamicTip(tipStrategy *TipStrategy) {
    // Get current optimal tip based on desired priority level
    currentTip := tipStrategy.GetTip("normal")
    
    fmt.Printf("\nExample: Using dynamic tip based on current tip floor data\n")
    fmt.Printf("Current tip for normal priority: %d lamports\n", currentTip)
    fmt.Printf("Tip automatically adapts to network conditions\n")
    fmt.Printf("Higher tips = higher transaction priority\n")
    
    // Here you would use this tip amount in your transaction building
    // See submit-single-transactions.md for complete transaction examples
}

<strong>// Main function demonstrating complete usage
</strong>func main() {
    // Configuration
    apiKey := "&#x3C;your-api-key-here>"  // Replace with your actual API key
    endpoint := FrankfurtEndpoint              // Choose your preferred endpoint
    
    if apiKey == "&#x3C;your-api-key-here>" {
        log.Fatal("Please set your API key in the code")
    }
    
    // Initialize tip strategy
    tipStrategy := NewTipStrategy()
    
    // Start a goroutine to demonstrate using dynamic tips
    go func() {
        ticker := time.NewTicker(2 * time.Minute)
        defer ticker.Stop()
        
        for range ticker.C {
            exampleTransactionWithDynamicTip(tipStrategy)
        }
    }()
    
    // Start streaming tip floor data
    if err := streamTipFloor(endpoint, apiKey, tipStrategy); err != nil {
        log.Fatalf("Tip floor streaming failed: %v", err)
    }
}
</code></pre>

## Advanced Usage with Multiple Endpoints

<pre class="language-go"><code class="lang-go"><strong>// Connect to multiple endpoints for redundancy
</strong>func streamFromMultipleEndpoints(apiKey string, tipStrategy *TipStrategy) {
    endpoints := []string{
        FrankfurtEndpoint,
        AmsterdamEndpoint,
        NewYorkEndpoint,
    }
    
    var wg sync.WaitGroup
    
    for _, endpoint := range endpoints {
        wg.Add(1)
        go func(ep string) {
            defer wg.Done()
            
            fmt.Printf("Starting stream from %s\n", ep)
            if err := streamTipFloor(ep, apiKey, tipStrategy); err != nil {
                log.Printf("Stream from %s failed: %v", ep, err)
            }
        }(endpoint)
    }
    
    wg.Wait()
}

<strong>// Tip history tracking for trend analysis
</strong>type TipHistory struct {
    mu      sync.RWMutex
    history []TipFloorData
    maxSize int
}

func NewTipHistory(maxSize int) *TipHistory {
    return &#x26;TipHistory{
        history: make([]TipFloorData, 0, maxSize),
        maxSize: maxSize,
    }
}

func (th *TipHistory) Add(tipFloor TipFloorData) {
    th.mu.Lock()
    defer th.mu.Unlock()
    
    if len(th.history) >= th.maxSize {
        th.history = th.history[1:]
    }
    th.history = append(th.history, tipFloor)
}

func (th *TipHistory) GetTrend() float64 {
    th.mu.RLock()
    defer th.mu.RUnlock()
    
    if len(th.history) &#x3C; 2 {
        return 0.0
    }
    
    recent := th.history[len(th.history)-1]
    older := th.history[len(th.history)-2]
    
    return recent.LandedTips50thPercentile - older.LandedTips50thPercentile
}

<strong>// Smart tip calculation with trend analysis
</strong>func (ts *TipStrategy) GetSmartTip(priority string, tipHistory *TipHistory) uint64 {
    baseTip := ts.GetTip(priority)
    trend := tipHistory.GetTrend()
    
    // Adjust tip based on trend
    adjustmentFactor := 1.0
    if trend > 0.001 { // Tips increasing
        adjustmentFactor = 1.2
        fmt.Printf("Tips trending up (+%.6f), increasing tip by 20%%\n", trend)
    } else if trend &#x3C; -0.001 { // Tips decreasing
        adjustmentFactor = 0.9
        fmt.Printf("Tips trending down (%.6f), decreasing tip by 10%%\n", trend)
    }
    
    smartTip := uint64(float64(baseTip) * adjustmentFactor)
    return smartTip
}

func max(a, b uint64) uint64 {
    if a > b {
        return a
    }
    return b
}
</code></pre>

## Available Endpoints

Choose the endpoint closest to your location for optimal latency:

* **Frankfurt**: `frankfurt.nextblock.io:443` (Europe)
* **Amsterdam**: `amsterdam.nextblock.io:443` (Europe)
* **London**: `london.nextblock.io:443` (Europe)
* **Singapore**: `singapore.nextblock.io:443` (Asia)
* **Tokyo**: `tokyo.nextblock.io:443` (Asia)
* **New York**: `ny.nextblock.io:443` (US East)
* **Salt Lake City**: `slc.nextblock.io:443` (US West)

## Best Practices

1. **Choose optimal endpoint**: Use the endpoint closest to your location for best performance
2. **Use TLS by default**: Prefer secure connections unless you intentionally operate in a trusted internal environment
3. **Handle connection failures**: Implement retry logic and fallback endpoints
4. **Monitor tip trends**: Use historical data to make smarter tip decisions
5. **Update frequently**: Stream tip floor data continuously for best results
6. **Adapt tips dynamically**: Adjust tip amounts based on current network conditions
7. **Match priority to urgency**: Use appropriate tip levels based on your transaction urgency needs
8. **Implement graceful shutdown**: Handle interruption signals properly
9. **Log important events**: Track tip floor updates and connection 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/golang/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.
