Menu
Bitcoin Python Executable Jan 31, 2026

Estimate Bitcoin Transaction Fees

Get current fee estimates from mempool.space for optimal transaction timing

#fees #mempool #estimation #sat/vB

Overview

Query current Bitcoin network fee estimates to determine the optimal fee rate for transactions. This helps agents decide when to transact and how much to pay.

The Code

"""
Bitcoin Fee Estimator
Query mempool.space for current fee recommendations

Returns fee rates in sat/vB (satoshis per virtual byte)
"""

import requests
from typing import Optional
from dataclasses import dataclass

MEMPOOL_API = "https://mempool.space/api"


@dataclass
class FeeEstimate:
    """Fee estimate with target confirmation blocks."""
    fastest: int      # Next block
    half_hour: int    # ~3 blocks
    hour: int         # ~6 blocks
    economy: int      # ~24+ blocks
    minimum: int      # Minimum relay fee


def get_fee_estimates(testnet: bool = False) -> FeeEstimate:
    """
    Get current recommended fee rates.

    Args:
        testnet: Use testnet API if True

    Returns:
        FeeEstimate with rates in sat/vB
    """
    base_url = "https://mempool.space/testnet/api" if testnet else MEMPOOL_API

    response = requests.get(
        f"{base_url}/v1/fees/recommended",
        timeout=10
    )
    response.raise_for_status()

    data = response.json()

    return FeeEstimate(
        fastest=data["fastestFee"],
        half_hour=data["halfHourFee"],
        hour=data["hourFee"],
        economy=data["economyFee"],
        minimum=data["minimumFee"]
    )


def get_mempool_stats(testnet: bool = False) -> dict:
    """
    Get current mempool statistics.

    Returns:
        dict with mempool size, tx count, fee histogram
    """
    base_url = "https://mempool.space/testnet/api" if testnet else MEMPOOL_API

    response = requests.get(
        f"{base_url}/mempool",
        timeout=10
    )
    response.raise_for_status()

    data = response.json()

    return {
        "tx_count": data["count"],
        "total_vsize_mb": data["vsize"] / 1_000_000,
        "total_fee_btc": data["total_fee"] / 100_000_000
    }


def estimate_tx_fee(
    input_count: int,
    output_count: int,
    fee_rate: int,
    is_segwit: bool = True
) -> dict:
    """
    Estimate transaction fee for a given structure.

    Args:
        input_count: Number of inputs
        output_count: Number of outputs
        fee_rate: Fee rate in sat/vB
        is_segwit: Whether using SegWit (default True)

    Returns:
        dict with vsize and fee estimates
    """
    if is_segwit:
        # P2WPKH: ~68 vbytes per input, ~31 vbytes per output, ~10.5 overhead
        vsize = (input_count * 68) + (output_count * 31) + 10.5
    else:
        # P2PKH: ~148 bytes per input, ~34 bytes per output, ~10 overhead
        vsize = (input_count * 148) + (output_count * 34) + 10

    vsize = int(vsize)
    fee_sats = vsize * fee_rate

    return {
        "vsize": vsize,
        "fee_sats": fee_sats,
        "fee_btc": fee_sats / 100_000_000,
        "fee_rate": fee_rate
    }


def should_transact_now(
    amount_sats: int,
    urgency: str = "normal",
    max_fee_percent: float = 1.0
) -> dict:
    """
    Determine if now is a good time to transact.

    Args:
        amount_sats: Transaction amount in satoshis
        urgency: "urgent", "normal", or "low"
        max_fee_percent: Maximum acceptable fee as % of amount

    Returns:
        dict with recommendation
    """
    fees = get_fee_estimates()
    mempool = get_mempool_stats()

    # Select fee based on urgency
    urgency_map = {
        "urgent": fees.fastest,
        "normal": fees.hour,
        "low": fees.economy
    }
    fee_rate = urgency_map.get(urgency, fees.hour)

    # Estimate fee for typical 1-in-2-out transaction
    tx_estimate = estimate_tx_fee(1, 2, fee_rate)
    fee_percent = (tx_estimate["fee_sats"] / amount_sats) * 100

    # Decision logic
    is_congested = mempool["total_vsize_mb"] > 50  # >50 MB is congested
    fee_acceptable = fee_percent <= max_fee_percent

    if urgency == "urgent":
        recommendation = "send" if fee_acceptable else "wait"
    elif is_congested and not urgency == "low":
        recommendation = "wait" if not fee_acceptable else "send_with_caution"
    else:
        recommendation = "send" if fee_acceptable else "wait"

    return {
        "recommendation": recommendation,
        "fee_rate_sat_vb": fee_rate,
        "estimated_fee_sats": tx_estimate["fee_sats"],
        "fee_percent": round(fee_percent, 2),
        "mempool_size_mb": round(mempool["total_vsize_mb"], 1),
        "mempool_congested": is_congested
    }


# Example usage
if __name__ == "__main__":
    print("=== Current Fee Estimates ===")
    fees = get_fee_estimates()
    print(f"Next block:  {fees.fastest} sat/vB")
    print(f"30 minutes:  {fees.half_hour} sat/vB")
    print(f"1 hour:      {fees.hour} sat/vB")
    print(f"Economy:     {fees.economy} sat/vB")
    print(f"Minimum:     {fees.minimum} sat/vB")

    print("\n=== Mempool Status ===")
    mempool = get_mempool_stats()
    print(f"Pending TXs: {mempool['tx_count']:,}")
    print(f"Size:        {mempool['total_vsize_mb']:.1f} MB")

    print("\n=== Transaction Decision ===")
    # Should I send 100,000 sats now?
    decision = should_transact_now(
        amount_sats=100_000,
        urgency="normal",
        max_fee_percent=1.0
    )
    print(f"Amount:        100,000 sats")
    print(f"Recommendation: {decision['recommendation'].upper()}")
    print(f"Estimated fee: {decision['estimated_fee_sats']} sats ({decision['fee_percent']}%)")
    print(f"Mempool:       {'Congested' if decision['mempool_congested'] else 'Normal'}")

Usage

pip install requests
python fee_estimation.py

Example Output

=== Current Fee Estimates ===
Next block:  15 sat/vB
30 minutes:  12 sat/vB
1 hour:      10 sat/vB
Economy:     5 sat/vB
Minimum:     1 sat/vB

=== Mempool Status ===
Pending TXs: 45,231
Size:        28.4 MB

=== Transaction Decision ===
Amount:        100,000 sats
Recommendation: SEND
Estimated fee: 1090 sats (1.09%)
Mempool:       Normal

Agent Notes

Fee strategies for agents:

UrgencyTargetUse Case
FastestNext blockTime-critical payments
Half-hour~3 blocksNormal payments
Hour~6 blocksNon-urgent transfers
Economy24+ blocksBatch consolidation

When to wait: If mempool > 100 MB or fee > 50 sat/vB, consider waiting for lower fees (usually overnight UTC or weekends).

RBF consideration: If using Replace-By-Fee, start with economy rate and bump if needed.