Fee Estimation

Advanced fee estimation algorithms for optimal transaction cost management.

Overview

The fee estimation system provides intelligent algorithms to determine optimal transaction fees based on network conditions, urgency requirements, and cost optimization strategies.

Fee Estimation Algorithms

Dynamic Fee Calculation

use std::collections::VecDeque;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeeEstimate {
    pub sat_per_byte: f64,
    pub total_fee: u64,
    pub confirmation_target: u32,
    pub confidence_level: f64,
}

pub struct FeeEstimator {
    mempool_analyzer: MempoolAnalyzer,
    historical_data: VecDeque<FeeRecord>,
    network_monitor: NetworkMonitor,
}

impl FeeEstimator {
    pub fn estimate_fee(&self, target_confirmations: u32, tx_size: usize) -> FeeEstimate {
        let base_fee = self.calculate_base_fee(target_confirmations);
        let network_adjustment = self.get_network_adjustment();
        let congestion_multiplier = self.get_congestion_multiplier();

        let adjusted_fee = base_fee * network_adjustment * congestion_multiplier;

        FeeEstimate {
            sat_per_byte: adjusted_fee,
            total_fee: (adjusted_fee * tx_size as f64) as u64,
            confirmation_target: target_confirmations,
            confidence_level: self.calculate_confidence(adjusted_fee),
        }
    }

    fn calculate_base_fee(&self, target_blocks: u32) -> f64 {
        // Analyze recent blocks for fee distribution
        let recent_fees = self.get_recent_confirmed_fees(target_blocks * 2);

        match target_blocks {
            1 => recent_fees.percentile(90.0),  // High priority
            3 => recent_fees.percentile(75.0),  // Medium priority
            6 => recent_fees.percentile(50.0),  // Standard priority
            _ => recent_fees.percentile(25.0),   // Low priority
        }
    }

    fn get_network_adjustment(&self) -> f64 {
        let current_difficulty = self.network_monitor.get_difficulty();
        let hash_rate = self.network_monitor.get_hash_rate();
        let block_time = self.network_monitor.get_avg_block_time();

        // Adjust based on network health
        if block_time > 600.0 {  // Slower than 10 minutes
            1.2  // Increase fee
        } else if block_time < 480.0 {  // Faster than 8 minutes
            0.9  // Decrease fee
        } else {
            1.0  // No adjustment
        }
    }
}

Machine Learning Fee Prediction

import numpy as np
from sklearn.ensemble import RandomForestRegressor
from sklearn.preprocessing import StandardScaler

class MLFeePredictor:
    def __init__(self):
        self.model = RandomForestRegressor(
            n_estimators=100,
            max_depth=10,
            random_state=42
        )
        self.scaler = StandardScaler()
        self.feature_names = [
            'mempool_size',
            'avg_block_time',
            'difficulty',
            'hash_rate',
            'tx_count_1h',
            'tx_count_24h',
            'weekend_indicator',
            'hour_of_day',
            'day_of_week'
        ]

    def extract_features(self, network_state):
        """Extract features for fee prediction"""
        features = np.array([
            network_state['mempool_size'],
            network_state['avg_block_time'],
            network_state['difficulty'],
            network_state['hash_rate'],
            network_state['tx_count_1h'],
            network_state['tx_count_24h'],
            1 if network_state['is_weekend'] else 0,
            network_state['hour'],
            network_state['day_of_week']
        ]).reshape(1, -1)

        return self.scaler.transform(features)

    def predict_fee(self, network_state, target_confirmations):
        """Predict optimal fee for target confirmation time"""
        features = self.extract_features(network_state)
        base_prediction = self.model.predict(features)[0]

        # Adjust based on target confirmations
        urgency_multiplier = {
            1: 1.5,   # Next block
            2: 1.2,   # Within 2 blocks
            3: 1.0,   # Within 3 blocks
            6: 0.8,   # Within 6 blocks
            12: 0.6   # Within 12 blocks
        }.get(target_confirmations, 0.5)

        return max(1.0, base_prediction * urgency_multiplier)

Real-time Fee Monitoring

Mempool Analysis

#[derive(Debug)]
pub struct MempoolAnalyzer {
    tx_pool: HashMap<Txid, MempoolTransaction>,
    fee_histogram: FeeHistogram,
}

impl MempoolAnalyzer {
    pub fn analyze_mempool(&mut self) -> MempoolAnalysis {
        self.update_tx_pool();

        let size_buckets = self.create_size_buckets();
        let fee_distribution = self.calculate_fee_distribution();
        let congestion_level = self.assess_congestion();

        MempoolAnalysis {
            total_transactions: self.tx_pool.len(),
            total_size: self.calculate_total_size(),
            fee_distribution,
            size_buckets,
            congestion_level,
            estimated_clear_time: self.estimate_clear_time(),
        }
    }

    fn assess_congestion(&self) -> CongestionLevel {
        let size_mb = self.calculate_total_size() as f64 / 1_000_000.0;

        match size_mb {
            s if s < 50.0 => CongestionLevel::Low,
            s if s < 150.0 => CongestionLevel::Medium,
            s if s < 300.0 => CongestionLevel::High,
            _ => CongestionLevel::Critical,
        }
    }
}

Fee Rate Tracking

interface FeeRate {
  satPerByte: number;
  timestamp: Date;
  confirmationTarget: number;
  confidence: number;
}

class FeeRateTracker {
  private rates: Map<number, FeeRate[]> = new Map();

  async updateFeeRates(): Promise<void> {
    const targets = [1, 2, 3, 6, 12, 24];

    for (const target of targets) {
      const rate = await this.fetchFeeRate(target);

      if (!this.rates.has(target)) {
        this.rates.set(target, []);
      }

      const targetRates = this.rates.get(target)!;
      targetRates.push(rate);

      // Keep only last 100 readings
      if (targetRates.length > 100) {
        targetRates.shift();
      }
    }
  }

  getFeeRecommendation(targetBlocks: number, priority: 'low' | 'medium' | 'high'): number {
    const rates = this.rates.get(targetBlocks) || [];
    if (rates.length === 0) return 1; // Fallback

    const recent = rates.slice(-10);
    const average = recent.reduce((sum, rate) => sum + rate.satPerByte, 0) / recent.length;

    const multipliers = {
      low: 0.8,
      medium: 1.0,
      high: 1.3
    };

    return Math.ceil(average * multipliers[priority]);
  }
}

Fee Optimization Strategies

Batch Transaction Optimization

pub struct TransactionBatcher {
    pending_outputs: Vec<TxOutput>,
    fee_estimator: FeeEstimator,
    min_batch_size: usize,
}

impl TransactionBatcher {
    pub fn optimize_batch(&self, outputs: &[TxOutput]) -> BatchOptimization {
        let single_tx_fees: Vec<u64> = outputs.iter()
            .map(|output| self.estimate_single_tx_fee(output))
            .collect();

        let batch_fee = self.estimate_batch_fee(outputs);
        let total_single_fees: u64 = single_tx_fees.iter().sum();

        BatchOptimization {
            batch_fee,
            individual_fees: single_tx_fees,
            savings: total_single_fees.saturating_sub(batch_fee),
            recommended: batch_fee < total_single_fees,
        }
    }

    fn estimate_batch_fee(&self, outputs: &[TxOutput]) -> u64 {
        // Calculate batch transaction size
        let input_size = 148; // Average input size
        let output_size = 34;  // Average output size
        let overhead = 10;     // Transaction overhead

        let tx_size = input_size + (outputs.len() * output_size) + overhead;
        let fee_rate = self.fee_estimator.estimate_fee(6, tx_size).sat_per_byte;

        (tx_size as f64 * fee_rate) as u64
    }
}

RBF (Replace-by-Fee) Strategy

pub struct RBFManager {
    pending_transactions: HashMap<Txid, PendingTransaction>,
    fee_estimator: FeeEstimator,
}

impl RBFManager {
    pub fn should_replace_fee(&self, txid: &Txid) -> Option<FeeReplacement> {
        let tx = self.pending_transactions.get(txid)?;
        let blocks_waiting = self.calculate_blocks_waiting(tx);

        if blocks_waiting < tx.target_confirmations {
            return None; // Still within target
        }

        let current_fee_rate = tx.fee as f64 / tx.size as f64;
        let recommended_fee_rate = self.fee_estimator
            .estimate_fee(tx.target_confirmations, tx.size)
            .sat_per_byte;

        if recommended_fee_rate > current_fee_rate * 1.1 {
            Some(FeeReplacement {
                old_fee: tx.fee,
                new_fee: (recommended_fee_rate * tx.size as f64) as u64,
                fee_rate: recommended_fee_rate,
                urgency: self.calculate_urgency(blocks_waiting, tx.target_confirmations),
            })
        } else {
            None
        }
    }
}

API Integration

REST API Endpoints

# Get fee estimates for different confirmation targets
GET /api/v1/fees/estimate?targets=1,3,6,12

Response:
{
  "estimates": {
    "1": {"sat_per_byte": 25.5, "confidence": 0.95},
    "3": {"sat_per_byte": 18.2, "confidence": 0.90},
    "6": {"sat_per_byte": 12.8, "confidence": 0.85},
    "12": {"sat_per_byte": 8.4, "confidence": 0.80}
  },
  "timestamp": "2025-06-17T10:00:00Z"
}

# Get optimal fee for specific transaction
POST /api/v1/fees/calculate
Content-Type: application/json

{
  "tx_size": 250,
  "target_confirmations": 3,
  "priority": "medium"
}

Response:
{
  "fee_estimate": {
    "sat_per_byte": 18.2,
    "total_fee": 4550,
    "confirmation_target": 3,
    "confidence_level": 0.90
  }
}

WebSocket Streaming

const ws = new WebSocket('wss://api.anya-core.org/v1/fees/stream');

ws.onmessage = (event) => {
  const feeUpdate = JSON.parse(event.data);
  console.log('Fee update:', feeUpdate);

  // Update UI with new fee recommendations
  updateFeeDisplay(feeUpdate.estimates);
};

// Subscribe to fee updates
ws.send(JSON.stringify({
  method: 'subscribe',
  params: ['fees.estimate', 'fees.mempool']
}));

Performance Metrics

Fee Accuracy Tracking

pub struct FeeAccuracyTracker {
    predictions: VecDeque<FeePrediction>,
    actual_results: HashMap<Txid, ActualResult>,
}

impl FeeAccuracyTracker {
    pub fn track_prediction(&mut self, prediction: FeePrediction) {
        self.predictions.push_back(prediction);

        // Keep only recent predictions
        while self.predictions.len() > 1000 {
            self.predictions.pop_front();
        }
    }

    pub fn calculate_accuracy(&self) -> AccuracyMetrics {
        let mut correct_predictions = 0;
        let mut total_predictions = 0;
        let mut fee_efficiency_sum = 0.0;

        for prediction in &self.predictions {
            if let Some(actual) = self.actual_results.get(&prediction.txid) {
                total_predictions += 1;

                if actual.confirmed_in_target {
                    correct_predictions += 1;
                }

                fee_efficiency_sum += actual.fee_paid as f64 / prediction.estimated_fee as f64;
            }
        }

        AccuracyMetrics {
            prediction_accuracy: correct_predictions as f64 / total_predictions as f64,
            average_fee_efficiency: fee_efficiency_sum / total_predictions as f64,
            total_predictions,
        }
    }
}

Configuration

Fee Estimation Settings

fee_estimation:
  enabled: true
  update_interval: 30  # seconds

  algorithms:
    historical_analysis:
      enabled: true
      lookback_blocks: 144  # ~24 hours
      weight_decay: 0.95

    ml_prediction:
      enabled: true
      model_update_frequency: 3600  # seconds
      feature_window: 720  # data points

    mempool_analysis:
      enabled: true
      sampling_interval: 10  # seconds

  targets:
    default: [1, 2, 3, 6, 12, 24]
    priority_mapping:
      urgent: 1
      high: 2
      medium: 6
      low: 12
      economy: 24

  rbf:
    enabled: true
    bump_threshold: 1.1  # 10% fee increase minimum
    max_replacements: 3

See Also


This documentation is part of the Anya Enterprise Analytics suite.