Tip floor stream
Prerequisites
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 repoExample
package main
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"
)
// Available NextBlock endpoints
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"
)
// API key credentials for authentication
type ApiKeyCredentials struct {
apiKey string
}
func NewApiKeyCredentials(apiKey string) *ApiKeyCredentials {
return &ApiKeyCredentials{apiKey: apiKey}
}
func (a *ApiKeyCredentials) RequireTransportSecurity() bool {
return false
}
func (a *ApiKeyCredentials) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"authorization": a.apiKey,
}, nil
}
// Tip floor data structure
type TipFloorData struct {
Time string
LandedTips25thPercentile float64
LandedTips50thPercentile float64
LandedTips75thPercentile float64
LandedTips95thPercentile float64
LandedTips99thPercentile float64
EMALandedTips50thPercentile float64
}
// Tip strategy for dynamic tip calculation
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 &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)
}
// Connection setup with authentication
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
}
// Stream tip floor data with complete error handling
func streamTipFloor(endpoint, apiKey string, tipStrategy *TipStrategy) error {
fmt.Printf("Connecting to NextBlock at %s...\n", endpoint)
// Establish connection
conn, err := connectToNextblock(endpoint, apiKey, false)
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(), &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 := &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 <-ticker.C:
// Generate mock tip floor data
mockTipFloor := &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)
}
}
}
// Process tip floor updates and update strategy
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("=====================================")
}
// Example of using dynamic tips in transaction submission
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
}
// Main function demonstrating complete usage
func main() {
// Configuration
apiKey := "<your-api-key-here>" // Replace with your actual API key
endpoint := FrankfurtEndpoint // Choose your preferred endpoint
if apiKey == "<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)
}
}Advanced Usage with Multiple Endpoints
Available Endpoints
Best Practices
Last updated