Tip Floor Stream
Stream real-time tip floor data from NextBlock using JavaScript/TypeScript.
// Tip floor data interface
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;
}
// Tip strategy management
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;
}
}
}
// Global tip strategy instance
const globalTipStrategy = new TipStrategy();
// Stream tip floor data
async function streamTipFloor(
// nextblockClient: any, // Your generated gRPC client
updateFrequency: string = '1m',
callback?: (tipFloor: TipFloorData) => Promise<void> | void
): Promise<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
}
*/
// Mock streaming for demonstration
console.log('Mock tip floor streaming started...');
const mockStream = setInterval(async () => {
// Generate mock tip floor data
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
// Return a promise that never resolves (keeps streaming)
return new Promise(() => {
// Keep the interval running
process.on('SIGINT', () => {
clearInterval(mockStream);
console.log('Tip floor streaming stopped');
process.exit(0);
});
});
}
// Process tip floor updates
async function processTipFloorUpdate(tipFloor: TipFloorData): Promise<void> {
// Update global strategy
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`);
// Store historical data
await storeTipFloorData(tipFloor);
// Trigger any pending transactions
await triggerPendingTransactions();
}
// Historical data management
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 < 2) return 0;
const recentData = this.data.slice(-window);
if (recentData.length < 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;
}
}
// Global history tracker
const tipFloorHistory = new TipFloorHistory();
// Smart tip calculation with trend analysis
async function getSmartTip(
basePriority: 'conservative' | 'normal' | 'aggressive' | 'priority' = 'normal',
considerTrend: boolean = true
): Promise<number> {
const baseTip = globalTipStrategy.getTipForPriority(basePriority);
if (!considerTrend || tipFloorHistory.length < 2) {
return baseTip;
}
// Analyze trend
const trend = tipFloorHistory.getTrend('50th', 5);
// Adjust tip based on trend
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 < -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
}
// Store tip floor data
async function storeTipFloorData(tipFloor: TipFloorData): Promise<void> {
// Add to history
tipFloorHistory.add(tipFloor);
// Optionally save to file
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);
}
}
// Trigger pending transactions
async function triggerPendingTransactions(): Promise<void> {
console.log('Checking for pending transactions to trigger...');
// Implementation would check your pending transaction queue
// and submit them with updated tip amounts
}
// Get current optimal tips
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'),
};
}
Usage Examples
async function tipFloorExample() {
// Connect to NextBlock (see connection.md)
// const config = configFromEnv();
// const manager = new NextBlockConnectionManager(config);
// await manager.connect();
// const nextblockClient = manager.grpcClient;
// Custom callback for tip floor updates
const onTipFloorUpdate = async (tipFloor: TipFloorData) => {
console.log(`Custom handler: Received update at ${tipFloor.time}`);
// Example: Trigger high-priority transactions when tips are low
if (tipFloor.landed_tips_50th_percentile < 0.002) { // Less than 0.002 SOL
console.log('Tips are low - good time for high-priority transactions!');
// await submitPriorityTransactions();
}
};
// Start streaming in background
const streamPromise = streamTipFloor(
// nextblockClient,
'1m',
onTipFloorUpdate
);
// Example usage of dynamic tips
setTimeout(async () => {
// Get current optimal tips
const currentTips = getCurrentOptimalTips();
console.log('Current optimal tips:', currentTips);
// Get smart tip with trend analysis
const smartTip = await getSmartTip('normal', true);
console.log(`Smart tip: ${smartTip} lamports`);
// Example: Use tips in transaction submission
// await submitTransactionWithTip(smartTip);
}, 5000); // Wait for initial data
// Keep streaming
try {
await streamPromise;
} catch (error) {
console.error('Streaming error:', error);
}
}
// Advanced tip management example
async function advancedTipManagement() {
// Monitor tip trends and adjust strategy
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`);
// Adjust strategy based on trends
if (trend5min > 0.002 && trend10min > 0.001) {
console.log('Strong upward trend detected - consider higher tips');
} else if (trend5min < -0.002 && trend10min < -0.001) {
console.log('Strong downward trend detected - can use lower tips');
}
}
}, 300000); // Check every 5 minutes
// Clean up on exit
process.on('SIGINT', () => {
clearInterval(monitorTrends);
console.log('Advanced tip management stopped');
process.exit(0);
});
}
// Main example runner
async function main() {
console.log('NextBlock Tip Floor Streaming Examples');
try {
// Start tip floor streaming
console.log('Starting tip floor streaming...');
// Run both examples concurrently
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,
};
Last updated