RillCoin
RillCoin DocsTestnet Live
Protocol

Decay Mechanics

Complete technical reference for concentration decay — the sigmoid function, fixed-point arithmetic, cluster tracking, lineage decay, and the decay pool.

Design Principle

Concentration decay is RillCoin's core economic primitive. When any cluster of UTXOs exceeds a threshold concentration relative to the circulating supply, those holdings decay at a rate proportional to their concentration. Decayed tokens flow to the mining reward pool, redistributing wealth to active network participants. All arithmetic is integer-only using checked u64 with no floating point.

Concentration Metric

Concentration is measured in parts-per-billion (PPB). A PPB value of 1,000,000 (one million) represents a concentration of 0.1% of the circulating supply — this is the decay threshold.

Concentration formula (rill-decay/src/lib.rs)
// concentration_ppb = cluster_balance × 1_000_000_000 / circulating_supply
//
// cluster_balance:    sum of all UTXOs with this cluster_id (in rills)
// circulating_supply: total unspent supply (in rills)
// Result:             parts per billion (u64)

pub fn concentration_ppb(cluster_balance: u64, circulating_supply: u64) -> u64 {
    cluster_balance
        .checked_mul(CONCENTRATION_PRECISION)  // × 1_000_000_000
        .unwrap_or(u64::MAX)
        .checked_div(circulating_supply)
        .unwrap_or(0)
}

pub const CONCENTRATION_PRECISION: u64 = 1_000_000_000;
pub const DECAY_C_THRESHOLD_PPB:   u64 = 1_000_000;    // 0.1% of supply

A cluster must exceed DECAY_C_THRESHOLD_PPB (1,000,000 PPB = 0.1% of supply) before any decay is applied. Below this threshold, the decay rate is exactly zero.

Cluster Index

Every UTXO is tagged with a cluster_id — a 32-byte BLAKE3 hash stored in the TxOutput.cluster_id field. All UTXOs sharing a cluster_id are aggregated into a single cluster balance for decay calculation.

The cluster index is maintained in RocksDB as a mapping:

text
cluster_id  →  aggregate_balance (u64 rills)

When a UTXO is spent, its value is subtracted from the cluster balance. When a UTXO is created, its value is added. The cluster balance is always the sum of all unspent UTXOs with that cluster_id.

The default cluster_id for wallet-generated outputs is:

formula
default_cluster_id = BLAKE3(ed25519_root_public_key_bytes)

Sigmoid Decay Function

The decay rate uses a fixed-point sigmoid lookup table. The sigmoid function maps concentration (normalized by CONCENTRATION_PRECISION) to a rate value. Linear interpolation is used between table entries.

Constants:

  • SIGMOID_PRECISION = 1,000,000,000 — Fixed-point scale for sigmoid output
  • TABLE_STEP = 500,000,000 — Step size between entries (0.5 in float terms)
x (concentration)sigmoid(x)Fixed-point (× 10⁹)
0.00.5000000500,000,000
0.50.6224593622,459,300
1.00.7310586731,058,600
1.50.8175744817,574,400
2.00.8807970880,797,000
2.50.9241418924,141,800
3.00.9525741952,574,100
3.50.9706878970,687,800
4.00.9820137982,013,700
4.50.9890130989,013,000
5.00.9933071993,307,100
5.50.9959298995,929,800
6.00.9975274997,527,400
6.50.9984965998,496,500
7.00.9990889999,088,900
7.50.9994472999,447,200
8.00.9996646999,664,600

Linear Interpolation

For a concentration value x between table entries, the sigmoid output is linearly interpolated:

Sigmoid interpolation
pub fn sigmoid_fixed(x: u64) -> u64 {
    // x is in fixed-point with CONCENTRATION_PRECISION = 1_000_000_000
    // TABLE_STEP = 500_000_000 (represents 0.5 in float)

    let idx = (x / TABLE_STEP) as usize;

    if idx >= SIGMOID_TABLE.len() - 1 {
        return SIGMOID_TABLE[SIGMOID_TABLE.len() - 1];
    }

    let y0 = SIGMOID_TABLE[idx];
    let y1 = SIGMOID_TABLE[idx + 1];
    let frac = x % TABLE_STEP;  // fractional part within step

    // Linear interpolation: y0 + (y1 - y0) * frac / TABLE_STEP
    y0 + (y1.saturating_sub(y0))
            .checked_mul(frac)
            .unwrap_or(0)
            / TABLE_STEP
}

Decay Rate Calculation

The effective decay rate in PPB per block is derived from the sigmoid output:

Decay rate formula
// decay_rate = (sigmoid(concentration_x) - 0.5) × DECAY_R_MAX_PPB × 2
//
// Constants:
//   DECAY_R_MAX_PPB = 1_500_000_000  (150% per year at max, in PPB units)
//   SIGMOID_HALF    = 500_000_000    (0.5 in fixed-point)

pub fn decay_rate_ppb(concentration_ppb: u64) -> u64 {
    if concentration_ppb < DECAY_C_THRESHOLD_PPB {
        return 0;
    }

    // Normalize concentration for sigmoid input
    let x = concentration_ppb
        .checked_mul(CONCENTRATION_PRECISION)
        .unwrap_or(u64::MAX)
        / DECAY_C_THRESHOLD_PPB;

    let sig = sigmoid_fixed(x);

    // Shift by 0.5 and scale to max rate
    let shifted = sig.saturating_sub(SIGMOID_HALF);

    shifted
        .checked_mul(DECAY_R_MAX_PPB)
        .unwrap_or(u64::MAX)
        .checked_mul(2)
        .unwrap_or(u64::MAX)
        / SIGMOID_PRECISION
}

pub const DECAY_R_MAX_PPB: u64 = 1_500_000_000; // 150% per year at max concentration
pub const SIGMOID_HALF:    u64 =   500_000_000; // 0.5 in fixed-point
pub const SIGMOID_PRECISION: u64 = 1_000_000_000;

Decay Rate Interpretation

Concentration% of SupplyApprox. Annual Decay
Below threshold< 0.1%0% — no decay
At threshold (1,000,000 PPB)0.1%~0% — minimal
1% of supply (10,000,000 PPB)1%~15% / year
5% of supply (50,000,000 PPB)5%~120% / year
10%+ of supply10%+~150% / year (max)

Effective Value

The effective value of a UTXO is its nominal value reduced by accrued decay. Decay accrues linearly with the number of blocks the UTXO has been held.

Effective value calculation
// effective_value = nominal × (1 - decay_rate × blocks_held / DECAY_PRECISION)
//
// DECAY_PRECISION = 10_000_000_000 (10^10)
// All arithmetic uses checked u64 — no floating point.

pub fn effective_value(
    nominal: u64,
    decay_rate_ppb: u64,
    blocks_held: u64,
) -> u64 {
    let decay_factor = decay_rate_ppb
        .checked_mul(blocks_held)
        .unwrap_or(u64::MAX);

    let decay_amount = nominal
        .checked_mul(decay_factor)
        .unwrap_or(u64::MAX)
        .checked_div(DECAY_PRECISION)
        .unwrap_or(nominal);

    nominal.saturating_sub(decay_amount)
}

pub const DECAY_PRECISION: u64 = 10_000_000_000; // 10^10

When a UTXO is spent, the difference between nominal and effective value flows to the decay pool. Coin selection in the wallet prefers high-decay UTXOs to minimize ongoing decay losses.

Lineage Decay

In addition to concentration-based decay, RillCoin tracks lineage — how long value has been concentrated in the same cluster lineage. Lineage decay adds a secondary pressure that grows with holding duration, independent of concentration.

ConstantValueDescription
LINEAGE_HALF_LIFE52,596 blocks~100 days — lineage weight halves
LINEAGE_FULL_RESET525,960 blocks~1 year — lineage resets to zero

Lineage is tracked per cluster. When a UTXO is spent and a new output is created in the same cluster, the lineage counter increments. Lineage resets to zero after LINEAGE_FULL_RESET blocks without activity, encouraging periodic economic participation.

Decay Pool

All decayed amounts flow into a global decay pool. The decay pool is a persistent balance maintained in RocksDB, credited atomically when UTXOs are spent.

Each block, a fraction of the decay pool is released to the block miner:

formula
decay_pool_release = decay_pool_balance × DECAY_POOL_RELEASE_BPS / 10_000

where DECAY_POOL_RELEASE_BPS = 100  (1% per block)

This 1% per block release creates a smooth, continuous flow of redistributed wealth to miners rather than a sudden dump. A pool of 100,000 RILL would release 1,000 RILL to the next block's miner.

The decay pool release is included in the coinbase transaction output value and subject to the same COINBASE_MATURITY = 100 block maturity requirement.

Example: Full Decay Calculation

Suppose a cluster holds 1,000,000 RILL (100,000,000,000,000 rills) and the circulating supply is 10,000,000 RILL (1,000,000,000,000,000 rills). The cluster holds 10% of supply.

Step-by-step example
1. Concentration PPB:
   cluster_balance = 100_000_000_000_000 rills  (1,000,000 RILL)
   supply          = 1_000_000_000_000_000 rills (10,000,000 RILL)
   concentration   = 100_000_000_000_000 × 1_000_000_000 / 1_000_000_000_000_000
                   = 100_000_000 PPB  (10% of supply)

2. Decay threshold check:
   100_000_000 PPB > 1_000_000 PPB (threshold) → proceed

3. Sigmoid lookup:
   x = 100_000_000 × 1_000_000_000 / 1_000_000 = 100_000_000_000
   (well above table maximum → clamp to SIGMOID_TABLE.last())
   sigmoid(x) ≈ 999_664_600 (in fixed-point × 10^9)

4. Decay rate:
   shifted = 999_664_600 - 500_000_000 = 499_664_600
   rate    = 499_664_600 × 1_500_000_000 × 2 / 1_000_000_000
           ≈ 1_498_993_800 PPB/year

5. Effective value after 1 block (525,960 blocks/year):
   decay_rate_per_block = 1_498_993_800 / 525_960 ≈ 2_849 PPB
   decay_amount = nominal × 2_849 / 10_000_000_000
   For a 1,000,000 RILL UTXO held 1 block:
   decay = 100_000_000_000_000 × 2_849 / 10_000_000_000 ≈ 28_490 rills
         = 0.00028490 RILL per block