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:
| Urgency | Target | Use Case |
|---|---|---|
| Fastest | Next block | Time-critical payments |
| Half-hour | ~3 blocks | Normal payments |
| Hour | ~6 blocks | Non-urgent transfers |
| Economy | 24+ blocks | Batch 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.