Dynamic Escalation of the Per-Output Miner Fee

Abstract

This BIP adds a dynamic escalation layer on top of the static per-output miner fee from its predecessor. It has a hard consensus dependency on Static Per-Output Miner Fee: the dynamic rules are inert for any block at or below the static fee BIP's activation height, regardless of this BIP's own signaling or lock-in status. Until that height, this BIP adds no fee floor.

The problem is long-run decay. A static satoshi amount fixed under today's conditions becomes trivial as Bitcoin's price and fee levels rise over decades. Without automatic adjustment, the static fee stops working as a real disincentive, and the only fix is another soft fork to raise the constant. Repeated soft forks to update a fixed number create a permanent dependency on governance processes that can be captured, delayed, or blocked.

This BIP specifies a dynamic fee component derived from the 25th percentile fee rate sampled over 288-block windows, smoothed by an exponential moving average, confirmed by multi-window persistence, and bounded by an upward-only rate-of-change cap. The active per-output fee is the maximum of the static fee and the dynamic fee. In normal and high fee conditions the dynamic fee sits above the static fee and tracks economic conditions. In prolonged low-fee regimes the static fee governs and the dynamic component contributes nothing.

This BIP is an optional long-term upgrade to the static fee. The static fee BIP works fully without it. Dedicated embedding channels are closed by Permanent Data Channel Closure, which does not depend on this BIP.


Motivation

The Known Limitation of the Static Fee

The static per-output miner fee closes both the value vector and the count vector of UTXO spam with a permanent, non-recoverable cost per output. It is the main piece of the fee layer. It has one known limit, already noted in its security considerations: a fixed satoshi amount decays in real terms as Bitcoin's price and fee levels rise.

At 20 sats per output and current BTC prices, creating 100,000 outputs costs 2 million sats. That is a material cost today. At 10x BTC price it is one-tenth the real cost. At 100x BTC price it approaches noise:

BTC price multipleCost of 100k outputs @ 20 satsReal deterrent?
1× (today)2,000,000 satsMaterial
10×2,000,000 sats (1/10 real cost)Weakening
100×2,000,000 sats (1/100 real cost)Noise
Nominal sats stay fixed; real economic cost decays as BTC appreciates. Without automatic adjustment, another soft fork is eventually required.

The static fee does not need a fix today, but without automatic adjustment it will eventually need a soft fork. That soft fork will face the same political and review surface as any other consensus change, with the extra difficulty of being an explicit fee increase rather than a new capability.

The dynamic escalation layer removes that future dependency. It is calibrated to track multi-decade fee drift, not short-term fee spikes, so it adjusts slowly and predictably rather than reacting to noise.

Why This Is a Separate BIP

The static fee is simple, small, and high-leverage. Putting the dynamic machinery in the same proposal raises implementation complexity, expands review surface, and raises the chance that neither piece activates. Splitting the proposals means the most important property, a permanent per-output cost, can land in consensus first. This BIP is the optional upgrade that keeps that cost from decaying over time.

Separation also lets the network gain operational experience with the static fee before adding the dynamic layer. If the static fee is enough in practice, the dynamic BIP may not need to activate on the same timeline. If the static fee decays faster than expected because of BTC price appreciation, the dynamic BIP has a clearer and more urgent case.

Anti-Decay Is the Only Job of the Dynamic Layer

The dynamic component has one job: keep the static fee from becoming economically trivial over decades. It is not meant to respond to short-term spam waves, react to individual fee spikes, or replace the static fee as the primary deterrent. During short-term fee anomalies the dynamic fee should stay stable. During genuine multi-year fee drift it should move up. The design choices below (slow EMA, multi-window persistence, upward-only rate cap) all follow from that single role.

Division of labor in the fee layer
Split so the permanent cost can activate first. Dynamic machinery is useful but not urgent.

Specification

Dependency

This BIP is only valid when Static Per-Output Miner Fee is active. If the static fee BIP has not activated, this BIP has no effect. Implementations must check for static fee BIP activation before applying dynamic escalation logic.

Active Fee

The active per-output fee for any given 288-block period is:

active_fee = max(static_fee, dynamic_fee)

Where static_fee is the constant from the static fee BIP and dynamic_fee is computed as specified below. The static fee BIP's validation rule is unchanged except that static_fee is replaced by active_fee:

tx_fee >= active_fee × n_outputs

Coinbase accounting is unchanged from existing consensus. The dynamic amount is paid as ordinary tx_fee, not minted.

During any period in which the dynamic fee falls below or equals the static fee, the active fee equals the static fee and validation matches the static fee BIP.

Period Alignment

A period is the half-open height interval [n × 288, (n + 1) × 288) for integer n >= 0, aligned to genesis (height // 288). The dynamic fee computed from the transactions in period n − 1 applies to non-coinbase transactions in period n.

This BIP performs no dynamic computation until the first full 288-block period that begins at or after this BIP's activation_height. Until that boundary, active_fee = static_fee. If the static fee BIP is not yet active, this BIP is inert and adds no floor.

Initial state at the first computation boundary:

Persistence requires two consecutive windows above threshold, so the first computed period cannot raise dynamic_fee. The second consecutive window can.

Dynamic Fee Computation

The dynamic fee is computed at each 288-block boundary and cached for the following period. Computation happens only at boundaries, never per block.

Figure: Five-step pipeline. Each layer exists to track multi-decade drift without reacting to short-term fee noise or cheap manipulation.

Step 1: Sample the fee rate

This step is the sole definition of fee sampling in the fee-layer BIPs. The static fee BIP needs no fee sampling; all fee rate computation starts here.

Collect the fee rate for every fee-paying transaction in the previous 288-block period. Fee rate is in milli-sat/vB as a non-negative integer:

vsize = ceil(weight / 4)          # vbytes, weight in WU
rate  = floor(tx_fee * 1000 / vsize)   # milli-sat per vB

tx_fee is sum(input_values) - sum(output_values) as in the static fee BIP. Exclude coinbase transactions. Exclude transactions with tx_fee = 0. Rate uses that transaction's own fee and weight only: no package or CPFP attribution.

Compute the 25th percentile of the resulting integer set. Sort ascending. For N samples, the 25th percentile is the element at index floor((N − 1) × 25 / 100) (0-based). No interpolation on even N. If the set has fewer than min_tx_count fee-paying transactions, do not update p25_ema_previous or dynamic_fee_previous (hold). Do not set them to zero. A thin sample must not collapse or freeze-from-zero the dynamic component. The period still advances: set active_fee_previous = active_fee_current so the grace window tracks the held fee. active_fee_current stays unchanged.

Step 2: Apply the exponential moving average

All EMA math is integer, toward zero:

ALPHA_DEN = 1000
alpha_n   = <integer, 100 means α = 0.1>
p25_ema   = (alpha_n × p25_current + (ALPHA_DEN − alpha_n) × p25_ema_previous) / ALPHA_DEN

A slow alpha_n toward 100 tracks multi-year fee drift while ignoring short-term spikes. Because the dynamic layer's only job is anti-decay over decades rather than short-term spam response, alpha_n must be tuned slow. A faster value toward 300 is appropriate only if sensitivity analysis shows a real benefit from faster response without instability.

Step 3: Apply multi-window persistence

if p25_ema_current > persistence_threshold
   and p25_ema_previous > persistence_threshold:
    candidate_dynamic = (k × p25_ema_current) / 1000
else:
    candidate_dynamic = dynamic_fee_previous

persistence_threshold is in milli-sat/vB, the same units as p25_ema. k is an integer vbyte scale that converts that EMA into a per-output satoshi fee. Truncation toward zero. k is not the serialized size of an output. It is a calibrated scale so that at a reference p25 (about 5 to 10 sat/vB) the dynamic component is the same order of magnitude as static_fee. A single k, rather than per-script-type values, matches the static fee's uniform-per-output design. A single elevated 288-block window (~2 days) does not move the dynamic fee. An attacker must sustain elevated fee conditions across at least two consecutive windows (~4 days) to produce any upward movement.

Step 4: Apply the rate-of-change cap

R_NUM = 1
R_DEN = 4                          # R = 0.25 unless calibration sets otherwise
cap_base = max(dynamic_fee_previous, static_fee)
max_increase = cap_base + (cap_base × R_NUM) / R_DEN
dynamic_fee_new = min(candidate_dynamic, max_increase)
dynamic_fee_new = max(0, dynamic_fee_new)

All integer, toward zero. cap_base is never 0 while the static fee BIP is active, so a zero dynamic_fee_previous cannot trap the cap at 0. The first upward move is bounded by static_fee × (1 + R), not by an uncapped jump to candidate_dynamic.

The dynamic fee may not increase faster than that bound per 288-block period. Decreases are uncapped: the dynamic fee falls freely back toward zero when fee conditions normalize, at which point the static fee governs.

Step 5: Apply the static fee floor

active_fee = max(static_fee, dynamic_fee_new)

Transaction Fee Floor During the Grace Window

The static fee BIP has no grace window at activation. This BIP changes active_fee every 288 blocks, so in-flight transactions need a short hold at period boundaries.

During the first G blocks of a new 288-block period, the required floor is the previous period's active_fee. After G blocks, the new period's active_fee applies. Coinbase rules are unchanged.

if block_height < period_start + G:
    required_fee = active_fee_previous_period
else:
    required_fee = active_fee_current_period

tx_fee >= required_fee × n_outputs

Consensus State

Each node must carry the following state across 288-block period boundaries, in addition to the static fee BIP's activation state:

All values are deterministic from block history and need no external input. Implementations must also retain EMA state snapshots at every period boundary for the most recent 2,016 blocks of boundaries to support reorg recovery, as specified in Security Considerations.

Parameters

All parameters are consensus parameters adjustable by a future soft fork. Initial values are provisional pending calibration.

ParameterDescriptionProvisional value
kInteger vbyte scale from p25 EMA (milli-sat/vB) to sat per output2-4
alpha_nEMA numerator over 1000 (100 = α 0.1)100-300 (tune slow)
persistence_thresholdMinimum p25 EMA (milli-sat/vB) for a window to count toward persistenceTBD via calibration
R_NUM / R_DENMaximum upward rate of change per period1/4
GGrace window in blocks at period boundaries6-12
min_tx_countMinimum fee-paying transactions for a valid sample1,000
Prefer slow alpha_n unless sensitivity tables show a real benefit from faster response without instability. k is a scale, not output vsize.

Activation

This BIP is meant to deploy via a soft fork using BIP-8 or BIP-9 style signaling, with a minimum activation window of one year and no mandatory lock-in fallback. Miners who do not signal are not penalized.

This BIP has an explicit consensus dependency: it is inert for any block whose height is less than or equal to the static fee BIP's activation_height, regardless of this BIP's own signaling or lock-in status. Until the static fee BIP is active, active_fee is not applied by this BIP (the static BIP's floor is also absent). After the static fee BIP is active and this BIP has completed its first full 288-block period, active_fee = max(static_fee, dynamic_fee) as specified. Signaling order is not enough; implementations must enforce the height dependency at the consensus layer.

BIP-8/9 without lock-in-on-timeout is miner signaling. Node-operator interest is not the threshold. User-activated soft fork deployment is a separate choice and is not specified here.


Rationale

Why 288 Blocks as the Sample Window

The dynamic fee's job is tracking multi-decade fee drift, not reacting to short-term spam waves. A 288-block (~2 day) window provides enough transactions for a statistically meaningful 25th percentile during active periods, while staying short enough to detect genuine sustained changes without unreasonable lag. Combined with the EMA, the effective response time to sustained fee changes is much longer than 288 blocks regardless of window size.

Why the 25th Percentile

The 25th percentile represents cheaper legitimate transactions and is less sensitive to outliers than the median. A small number of high-fee transactions cannot push it far upward, which keeps the dynamic fee from pricing out legitimate use during fee spikes driven by monetary activity rather than spam.

Why EMA Rather Than Hard Window Reset

A hard window reset creates cliff-edge transitions and makes the fee reactive to single-window anomalies. The EMA smooths toward prevailing conditions over many periods, which is the right behavior for a mechanism tracking multi-decade drift rather than reacting to individual events.

Why Multi-Window Persistence

A single elevated 288-block window is achievable by a moderately funded actor for whom fee spend is an acceptable operating cost. Requiring two consecutive windows doubles the sustained cost and duration of any manipulation attempt. The persistence check adds no per-block computation and is the main defense against short-term fee flooding aimed at ratcheting the dynamic fee upward.

Why an Upward-Only Rate-of-Change Cap

Even with EMA smoothing and persistence, a sustained campaign could ratchet the dynamic fee upward over many periods. The rate cap bounds how fast that can happen regardless of fee pressure: even a perfectly sustained campaign can raise the dynamic fee by only R percent per period, so rapid ratcheting is slow, expensive, and publicly visible on chain. The cap is asymmetric on purpose: the dynamic fee falls freely when pressure stops, so a manipulation attempt does not leave a permanently elevated floor once the attacker withdraws.

Why k Is a Scale, Not Output Size

The p25 EMA is in milli-sat/vB. Multiplying by an integer k and dividing by 1000 produces sats per output. Dimensionally k has units of vbytes, but it is not set to the serialized size of a P2WPKH or P2TR output (31–43 vB). Using literal output vsize would make dynamic_fee track weight fees one-for-one and would overprice Lightning and CoinJoin during congestion. Calibration sets k so that at a reference p25 of about 5 to 10 sat/vB, dynamic_fee is the same order as static_fee (provisional k in 2–4). A single k matches the static fee's uniform-per-output model: the externalized cost being priced is per UTXO slot, not per script type.

Interaction with Permanent Data Channel Closure

The Permanent Data Channel Closure pre-proposal targets dedicated high-bandwidth data channels. This BIP targets UTXO creation economics. They operate on different surfaces. Dynamic escalation does not change that split. See The Achievable Floor and Bitcoin Is Not a Hard Drive.


Backwards Compatibility

The active fee replaces the static fee in the per-transaction floor. During any period in which the dynamic fee is below the static fee, validation matches the static fee BIP. The dynamic layer is additive: it can only raise the active fee above the static fee floor, never below it.

Wallets and fee estimators that already account for the static per-output fee must also track the current period's active fee, which may differ from the static fee during elevated fee conditions. From the user's point of view there is still one fee; wallets present a single total that uses whichever of the two rates is currently required, including the grace-window hold at period boundaries.


Reference Implementation

High-level pseudocode, assuming the static fee BIP is active:

STATIC_FEE = <from static fee BIP>
ALPHA_N = 100
ALPHA_DEN = 1000
K = <2-4 pending calibration>
R_NUM, R_DEN = 1, 4
MIN_TX_COUNT = 1000
G = <6-12>
RATE_SCALE = 1000  # milli-sat/vB

def tx_vsize(tx):
    return (tx.weight + 3) // 4

def tx_fee(tx):
    return sum(inp.value for inp in tx.inputs) - sum(out.value for out in tx.outputs)

def sample_rates(period_blocks):
    rates = []
    for block in period_blocks:
        for tx in block.transactions:
            if tx.is_coinbase or tx_fee(tx) == 0:
                continue
            rates.append((tx_fee(tx) * RATE_SCALE) // tx_vsize(tx))
    rates.sort()
    return rates

def percentile_25(rates):
    n = len(rates)
    return rates[(n - 1) * 25 // 100]

def compute_period_active_fee(last_288_blocks, state):
    rates = sample_rates(last_288_blocks)
    if len(rates) < MIN_TX_COUNT:
        state.active_fee_previous = state.active_fee_current
        return state.active_fee_current  # hold ema and dynamic_fee; rotate grace window

    p25_current = percentile_25(rates)
    p25_ema = (ALPHA_N * p25_current + (ALPHA_DEN - ALPHA_N) * state.p25_ema_previous) // ALPHA_DEN

    if (p25_ema > PERSISTENCE_THRESHOLD and
            state.p25_ema_previous > PERSISTENCE_THRESHOLD):
        candidate = (K * p25_ema) // RATE_SCALE
    else:
        candidate = state.dynamic_fee_previous

    cap_base = max(state.dynamic_fee_previous, STATIC_FEE)
    max_increase = cap_base + (cap_base * R_NUM) // R_DEN
    dynamic_fee = min(candidate, max_increase)
    dynamic_fee = max(0, dynamic_fee)
    active_fee = max(STATIC_FEE, dynamic_fee)

    state.p25_ema_previous = p25_ema
    state.dynamic_fee_previous = dynamic_fee
    state.active_fee_previous = state.active_fee_current
    state.active_fee_current = active_fee
    return active_fee

def required_fee(block_height, period_start, state):
    if block_height < period_start + G:
        return state.active_fee_previous
    return state.active_fee_current

def is_valid_transaction(tx, block_height, period_start, state):
    if tx.is_coinbase:
        return True
    return tx_fee(tx) >= required_fee(block_height, period_start, state) * len(tx.outputs)

Detailed test vectors, integer arithmetic precision requirements, and edge-case handling will be provided in a future numbered BIP submission.


Calibration Checklist

This checklist is separate from and follows the static fee BIP's calibration. The static fee BIP's calibration must pass before this checklist begins.

A. Dynamic Parameter Sensitivity

B. Anti-Manipulation Stress Test

C. Collateral Damage at Dynamic Rates

D. Consensus Edge Cases

E. Exit Criteria


Security Considerations

Fee rate manipulation. Moving the 25th percentile meaningfully requires sustaining elevated transaction volume across a full 288-block window. The three-layer dynamic architecture (EMA, persistence, rate cap) makes rapid ratcheting slow, expensive, and publicly visible on chain. A state-level actor treating fee spend as an operating cost can still move the dynamic fee upward over many periods; the cap bounds how fast, and the fee falls freely once pressure is removed.

Static fee as irreducible floor. The dynamic fee can never push the active fee below the static fee. The static fee BIP's security properties are fully preserved regardless of what the dynamic layer does.

Low-activity window. If the 288-block sample contains fewer than min_tx_count fee-paying transactions, EMA and dynamic_fee are held, not zeroed. Zeroing would re-introduce a from-zero rate-cap trap. The static fee still floors active_fee.

Period boundary floor. The grace window keeps the required per-transaction floor unambiguous at every period boundary. The previous period's active_fee governs during the grace window; the new rate governs after. Coinbase value is not part of this BIP.

Rate cap from zero. cap_base = max(dynamic_fee_previous, static_fee) so a zero previous dynamic fee cannot pin max_increase at 0.

Reorg safety. On a reorg crossing a 288-block period boundary, the EMA state must revert to the correct state for the reorganized chain tip. Implementations must cache EMA state snapshots at every period boundary. The consensus rule is: retain snapshots for the most recent 2,016 blocks (one difficulty adjustment period, about two weeks) of period boundaries. That bounds the cache to at most seven snapshots at any time and covers any reorg that Bitcoin's proof-of-work economics makes plausible, without relying on heuristic depth estimates. A reorg deeper than 2,016 blocks is treated as a chain split requiring operator intervention, consistent with existing Bitcoin consensus assumptions. Implementations that do not retain the required snapshots cannot correctly validate blocks after a deep reorg and must re-sync from a known-good checkpoint.


References


This document is licensed under the BSD 2-Clause License.