> For the complete documentation index, see [llms.txt](https://docs.ferra.ag/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ferra.ag/integration/damm/smart-contract/fee-module.md).

# Fee Module

**Source**: sources/libraries/fee\_helper.move | sources/libraries/pair\_parameter\_helper.move

**Modules**: `ferra_damm::fee_helper` | `ferra_damm::pair_parameter_helper`

The Fee Module is the core fee calculation engine of the Ferra DAMM protocol. It combines two independent fee mechanisms — **Fee Scheduler** (time-based base fee) and **Dynamic Fee** (volatility-based variable fee) — to determine the total fee for each swap.

***

### Fee Architecture Overview

```
┌──────────────────────────────────────────────────────────────────┐
│                        Total Fee                                 │
│                                                                  │
│    total_fee = base_fee + variable_fee                           │
│    capped at MAX_FEE (50% = 500,000,000)                         │
│                                                                  │
│  ┌────────────────────────┐  ┌─────────────────────────────────┐ │
│  │      Base Fee           │  │        Variable Fee            │ │
│  │   (Fee Scheduler)       │  │      (Dynamic Fee)             │ │
│  │                         │  │                                │ │
│  │  If scheduler enabled:  │  │  If dynamic fee enabled:       │ │
│  │    Linear or Exponential│  │    Based on volatility         │ │
│  │    decay from cliff_fee │  │    accumulator × tick_spacing  │ │
│  │                         │  │                                │ │
│  │  If scheduler disabled: │  │  If dynamic fee disabled:      │ │
│  │    Static fee_rate      │  │    0                           │ │
│  └─────────────────────────┘  └────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
```

***

### PairParameters

The `PairParameters` struct is stored in each Pool and holds all fee state.

```move
struct PairParameters has store, copy, drop {
    // Static fee parameters
    fee_rate: u64,
    current_sqrt_price: u128,
    current_tick_index: I32,
    tick_spacing: u32,
    activation_timestamp: u64,

    // Fee scheduler (base fee)
    fee_scheduler_mode: u8,
    enabled_fee_scheduler: bool,
    cliff_fee_numerator: u64,
    number_of_period: u16,
    period_frequency: u64,
    fee_scheduler_reduction_factor: u64,

    // Dynamic fee parameters (static config)
    enabled_dynamic_fee: bool,
    filter_period: u16,
    decay_period: u16,
    reduction_factor: u16,
    variable_fee_control: u32,
    max_volatility_accumulator: u32,

    // Dynamic fee parameters (change on every swap)
    volatility_accumulator: u32,
    volatility_reference: u32,
    id_reference: I32,
    time_of_last_update: u64,
}
```

#### Static Fee Fields

| Field                  | Type   | Description                                                                         |
| ---------------------- | ------ | ----------------------------------------------------------------------------------- |
| `fee_rate`             | `u64`  | Base fee rate when scheduler is disabled. Precision: 10^9 (e.g., 2,500,000 = 0.25%) |
| `current_sqrt_price`   | `u128` | Current pool sqrt price (Q64.64 format)                                             |
| `current_tick_index`   | `I32`  | Current pool tick index                                                             |
| `tick_spacing`         | `u32`  | Pool tick spacing                                                                   |
| `activation_timestamp` | `u64`  | When pool/fee scheduler activates (ms)                                              |

#### Fee Scheduler Fields

| Field                            | Type   | Description                                                                      |
| -------------------------------- | ------ | -------------------------------------------------------------------------------- |
| `fee_scheduler_mode`             | `u8`   | `0` = Linear, `1` = Exponential                                                  |
| `enabled_fee_scheduler`          | `bool` | Whether fee scheduler is active                                                  |
| `cliff_fee_numerator`            | `u64`  | Initial maximum fee. Must be > 0, max 500,000,000 (50%)                          |
| `number_of_period`               | `u16`  | Total number of reduction periods                                                |
| `period_frequency`               | `u64`  | Duration of each period (ms)                                                     |
| `fee_scheduler_reduction_factor` | `u64`  | Linear: absolute reduction per period. Exponential: decay factor in basis points |

#### Dynamic Fee Config Fields

| Field                        | Type   | Description                                                                |
| ---------------------------- | ------ | -------------------------------------------------------------------------- |
| `enabled_dynamic_fee`        | `bool` | Whether dynamic fee is active                                              |
| `filter_period`              | `u16`  | Seconds before updating volatility reference. Must be <= decay\_period     |
| `decay_period`               | `u16`  | Seconds for full volatility reset. Max: 4095. Must be > 0                  |
| `reduction_factor`           | `u16`  | Decay rate in basis points (1-10000). Used to reduce volatility\_reference |
| `variable_fee_control`       | `u32`  | Scaling factor for variable fee. Max: 2,000,000                            |
| `max_volatility_accumulator` | `u32`  | Cap on volatility accumulator. Max: 1,048,575 (0xFFFFF)                    |

#### Dynamic Fee State Fields (Updated on Every Swap)

| Field                    | Type  | Description                                |
| ------------------------ | ----- | ------------------------------------------ |
| `volatility_accumulator` | `u32` | Current accumulated volatility level       |
| `volatility_reference`   | `u32` | Reference volatility from previous period  |
| `id_reference`           | `I32` | Reference tick index for delta calculation |
| `time_of_last_update`    | `u64` | Timestamp of last volatility update        |

***

### Fee Scheduler (Base Fee)

The Fee Scheduler provides time-based fee reduction from an initial cliff fee down to the base fee rate.

#### How It Works

1. Pool is created with `activation_timestamp` and `cliff_fee_numerator`
2. Before activation: fee = `cliff_fee_numerator` (maximum fee)
3. After activation: fee decreases over `number_of_period` periods
4. After all periods complete: fee = `fee_rate` (base fee)

#### get\_total\_fee\_rate

Returns the total fee rate combining base fee and variable fee.

```move
public fun get_total_fee_rate(
    params: &PairParameters,
    tick_spacing: u32,
    current_timestamp: u64,
): u64
```

**Returns**: Total fee rate, capped at `MAX_FEE` (500,000,000 = 50%)

***

#### get\_base\_fee

Returns the current base fee based on the fee scheduler state.

```move
public fun get_base_fee(
    params: &PairParameters,
    current_timestamp: u64,
): u64
```

**Logic**:

* If scheduler disabled or `activation_timestamp == 0`: returns static `fee_rate`
* If before activation or at max period: returns `fee_rate` or cliff fee accordingly
* Otherwise: calculates fee based on mode (linear or exponential)

***

#### Linear Mode (`fee_scheduler_mode = 0`)

Fee decreases by a fixed amount each period.

```move
public fun get_base_fee_linear(
    params: &PairParameters,
    period: u64,
): u64
```

**Formula**:

```
base_fee = cliff_fee_numerator - (fee_scheduler_reduction_factor × period)
```

**Example**:

* `cliff_fee_numerator = 100,000,000` (10%)
* `fee_scheduler_reduction_factor = 9,000,000` (0.9% per period)
* `number_of_period = 10`
* Result: Fee decreases from 10% → 9.1% → 8.2% → ... → 1% over 10 periods

**Validation**: Total reduction (`reduction_factor × number_of_period`) must not exceed `cliff_fee_numerator`

***

#### Exponential Mode (`fee_scheduler_mode = 1`)

Fee decays exponentially using a compound decay factor.

```move
public fun get_base_fee_exponential(
    params: &PairParameters,
    period: u64,
): u64
```

**Formula**:

```
base_fee = cliff_fee_numerator × (1 - reduction_factor / 10000) ^ period
```

Uses Q64.64 fixed-point arithmetic for precision.

**Example**:

* `cliff_fee_numerator = 100,000,000` (10%)
* `reduction_factor = 2000` (20% decay per period)
* `number_of_period = 10`
* Result: 10% → 8% → 6.4% → 5.12% → ... asymptotically approaching 0

**Validation**: `reduction_factor` must be > 0 and < 10000 (basis\_point\_max)

***

#### get\_current\_number\_period

Calculates which period we are currently in.

```move
public fun get_current_number_period(
    params: &PairParameters,
    current_timestamp: u64,
): u64
```

**Logic**:

* Before activation: returns `number_of_period` (maximum penalty = cliff fee)
* After activation: `period = (elapsed_time + period_frequency - 1) / period_frequency`
* Capped at `number_of_period`

***

#### Fee Scheduler Query Functions

| Function                                 | Returns | Description                     |
| ---------------------------------------- | ------- | ------------------------------- |
| `is_fee_scheduler_enabled(params)`       | `bool`  | Whether fee scheduler is active |
| `get_cliff_fee_numerator(params)`        | `u64`   | Initial cliff fee               |
| `get_scheduler_number_of_period(params)` | `u16`   | Total periods                   |
| `get_scheduler_period_frequency(params)` | `u64`   | Period duration (ms)            |
| `get_scheduler_reduction_factor(params)` | `u64`   | Reduction per period            |
| `get_activation_timestamp(params)`       | `u64`   | Activation time (ms)            |
| `get_fee_scheduler_mode(params)`         | `u8`    | `0` = Linear, `1` = Exponential |

***

### Dynamic Fee (Variable Fee)

The Dynamic Fee mechanism automatically adjusts fees based on real-time market volatility. During high volatility (large price movements), fees increase to protect LPs. During calm markets, fees decay back to zero.

#### How It Works

1. Each swap updates the volatility accumulator based on price movement
2. Variable fee is calculated from the volatility accumulator
3. After `filter_period`, the reference volatility decays by `reduction_factor`
4. After `decay_period`, volatility reference resets to zero

#### get\_variable\_fee

Calculates the current variable fee from volatility state.

```move
public fun get_variable_fee(
    params: &PairParameters,
    tick_spacing: u32,
): u64
```

**Formula**:

```
prod = volatility_accumulator × tick_spacing
variable_fee = (prod × prod × variable_fee_control + 99) / 100
```

**Example**:

* `volatility_accumulator = 100`
* `tick_spacing = 60`
* `variable_fee_control = 1000`
* `prod = 100 × 60 = 6000`
* `variable_fee = (6000 × 6000 × 1000 + 99) / 100 = 360,000,000` (36%)

***

#### Volatility Update Flow

On each swap, the following functions are called in sequence:

**update\_volatility\_parameters**

Entry point called on every swap to update volatility state.

```move
public(friend) fun update_volatility_parameters(
    params: &mut PairParameters,
    active_id: I32,
    timestamp: u64,
)
```

**Flow**:

1. `update_references(params, timestamp)` — handles time-based decay
2. `update_volatility_accumulator(params, active_id)` — adds new volatility

***

**update\_references**

Handles time-based volatility decay.

```move
public(friend) fun update_references(
    params: &mut PairParameters,
    timestamp: u64,
)
```

**Logic**:

```
dt = timestamp - time_of_last_update

if dt >= filter_period:
    id_reference = current_tick_index     // Update reference tick
    if dt < decay_period:
        volatility_reference = volatility_accumulator × reduction_factor / 10000
    else:
        volatility_reference = 0          // Full decay

time_of_last_update = timestamp
```

***

**update\_volatility\_accumulator**

Adds new volatility based on price movement since reference.

```move
public(friend) fun update_volatility_accumulator(
    params: &mut PairParameters,
    active_id: I32,
)
```

**Formula**:

```
delta_id = |active_id - id_reference| / tick_spacing
volatility_accumulator = min(
    volatility_reference + delta_id × 10,
    max_volatility_accumulator
)
```

The `× 10` factor is the volatility per bin constant.

***

**get\_delta\_id**

Calculates the absolute tick distance from the reference, normalized by tick spacing.

```move
public fun get_delta_id(
    params: &PairParameters,
    active_id: I32,
): u32
```

**Formula**: `|active_id - id_reference| / tick_spacing`

***

#### Dynamic Fee Query Functions

| Function                                         | Returns | Description                   |
| ------------------------------------------------ | ------- | ----------------------------- |
| `is_dynamic_fee_enabled(params)`                 | `bool`  | Whether dynamic fee is active |
| `get_dynamic_filter_period(params)`              | `u16`   | Filter period (seconds)       |
| `get_dynamic_decay_period(params)`               | `u16`   | Full decay period (seconds)   |
| `get_dynamic_reduction_factor(params)`           | `u16`   | Decay rate in basis points    |
| `get_dynamic_variable_fee_control(params)`       | `u32`   | Scaling factor                |
| `get_dynamic_max_volatility_accumulator(params)` | `u32`   | Max volatility cap            |
| `get_volatility_accumulator(params)`             | `u32`   | Current volatility level      |
| `get_volatility_reference(params)`               | `u32`   | Reference volatility          |
| `get_id_reference(params)`                       | `I32`   | Reference tick index          |
| `get_time_of_last_update(params)`                | `u64`   | Last update timestamp         |
| `get_active_id(params)`                          | `I32`   | Current tick index            |

***

### Fee Helper (Validation & Calculation)

The `fee_helper` module provides fee parameter validation and fee amount calculation utilities.

#### validate\_dynamic\_fee\_parameters

Validates dynamic fee configuration.

```move
public fun validate_dynamic_fee_parameters(
    filter_period: u16,
    decay_period: u16,
    reduction_factor: u16,
    variable_fee_control: u32,
    max_volatility_accumulator: u32,
)
```

**Validation Rules**:

* `filter_period <= decay_period`
* `0 < decay_period <= 4095`
* `0 < reduction_factor <= 10000`
* `variable_fee_control <= 2,000,000`
* `0 < max_volatility_accumulator <= 1,048,575`

***

#### validate\_base\_fee

Validates fee scheduler configuration to ensure the minimum fee after all periods is above the threshold.

```move
public fun validate_base_fee(
    fee_rate: u64,
    fee_scheduler_mode: u8,
    cliff_fee_numerator: u64,
    number_of_period: u16,
    period_frequency: u64,
    reduction_factor: u64,
)
```

**Validation Rules**:

* `fee_rate <= MAX_FEE_RATE` (10%)
* `cliff_fee_numerator > 0` and `<= MAX_FEE` (50%)
* `number_of_period > 0`
* `period_frequency > 0`
* **Linear mode**: `reduction_factor × number_of_period <= cliff_fee_numerator`
* **Exponential mode**: `0 < reduction_factor < 10000`
* Minimum fee after all periods must be >= `MIN_FEE_NUMERATOR` (100,000 = 0.01%)

***

#### Fee Amount Calculation Functions

**get\_fee\_amount\_inclusive**

Extracts fee from an amount that already includes fees.

```move
public fun get_fee_amount_inclusive(
    amount_with_fees: u64,
    total_fee_rate: u64,
): u64
```

**Formula**: `fee = amount_with_fees × total_fee_rate / PRECISION` (rounds up)

**Example**: 1% fee on 10,000 → fee = 100

***

**get\_fee\_amount\_exclusive**

Calculates fee to add on top of an amount.

```move
public fun get_fee_amount_exclusive(
    amount_without_fees: u64,
    total_fee_rate: u64,
): u64
```

**Formula**: `fee = amount × total_fee_rate / (PRECISION - total_fee_rate)` (rounds up)

**Example**: 1% fee on 9,900 → fee = 100 (total becomes \~10,000)

***

**get\_composition\_fee**

Calculates composition fee using a quadratic formula for balanced liquidity incentives.

```move
public fun get_composition_fee(
    amount_with_fees: u64,
    total_fee_rate: u64,
): u64
```

**Formula**: `fee = amount × total_fee_rate × (total_fee_rate + PRECISION) / PRECISION²`

***

**get\_protocol\_fee\_amount**

Calculates the protocol's share of a fee amount.

```move
public fun get_protocol_fee_amount(
    fee_amount: u64,
    protocol_share: u64,
): u64
```

**Formula**: `protocol_fee = fee_amount × protocol_share / BASIS_POINT_MAX` (rounds down)

**Example**: 25% protocol share on 100 fee → protocol\_fee = 25

***

### General Query Functions

| Function                         | Returns | Description          |
| -------------------------------- | ------- | -------------------- |
| `get_tick_spacing(params)`       | `u32`   | Pool tick spacing    |
| `get_fee_rate(params)`           | `u64`   | Static base fee rate |
| `get_current_sqrt_price(params)` | `u128`  | Current sqrt price   |
| `get_current_tick_index(params)` | `I32`   | Current tick index   |

***

### Constants Reference

| Constant                         | Value                | Description                           |
| -------------------------------- | -------------------- | ------------------------------------- |
| `PRECISION`                      | 1,000,000,000 (10^9) | Fee precision denominator             |
| `SQUARED_PRECISION`              | 10^18                | Precision squared for composition fee |
| `MAX_FEE_RATE`                   | 100,000,000 (10%)    | Maximum base fee rate                 |
| `MAX_FEE`                        | 500,000,000 (50%)    | Maximum total fee (base + dynamic)    |
| `MIN_FEE_NUMERATOR`              | 100,000 (0.01%)      | Minimum fee after scheduler           |
| `BASIS_POINT_MAX`                | 10,000               | Basis point denominator               |
| `MAX_VARIABLE_FEE_CONTROL`       | 2,000,000            | Max scaling factor                    |
| `MAX_DECAY_PERIOD`               | 4,095 (0xFFF)        | Max decay period in seconds           |
| `MAX_VOLATILITY_ACCUMULATOR`     | 1,048,575 (0xFFFFF)  | Max volatility level                  |
| `FEE_SCHEDULER_MODE_LINEAR`      | 0                    | Linear fee scheduler mode             |
| `FEE_SCHEDULER_MODE_EXPONENTIAL` | 1                    | Exponential fee scheduler mode        |
| `COLLECT_FEE_MODE_ON_BOTH`       | 0                    | Collect fee on input token            |
| `COLLECT_FEE_MODE_ON_QUOTE`      | 1                    | Collect fee on quote token            |

***

### Error Codes

#### fee\_helper

| Code | Constant                               | Description                                      |
| ---- | -------------------------------------- | ------------------------------------------------ |
| 502  | `E_FEE_TOO_HIGH`                       | Fee rate exceeds maximum                         |
| 505  | `E_INVALID_DECAY_PERIOD`               | Decay period is 0 or exceeds max                 |
| 506  | `E_INVALID_REDUCTION_FACTOR`           | Reduction factor is 0 or exceeds basis point max |
| 507  | `E_INVALID_VARIABLE_FEE_CONTROL`       | Variable fee control exceeds max                 |
| 508  | `E_INVALID_MAX_VOLATILITY_ACCUMULATOR` | Max volatility is 0 or exceeds max               |
| 509  | `E_INVALID_PARAMETER`                  | Filter period > decay period                     |
| 510  | `E_INVALID_FEE_SCHEDULER`              | Invalid scheduler config (zero values)           |
| 511  | `E_LINEAR_REDUCTION_TOO_HIGH`          | Linear total reduction exceeds cliff fee         |
| 512  | `E_MIN_FEE_TOO_LOW`                    | Minimum fee after scheduler below threshold      |

#### pair\_parameter\_helper

| Code | Constant              | Description                                    |
| ---- | --------------------- | ---------------------------------------------- |
| 900  | `E_INVALID_PARAMETER` | Invalid volatility parameter (exceeds 20 bits) |

***

### Usage Examples

#### Reading Current Fee State

```move
// Get total fee rate at current time
let total_fee = pool::get_total_fee_rate<USDC, TOKEN>(&pool, current_timestamp);

// Get pair parameters for detailed inspection
let params = pool::get_pair_parameters<USDC, TOKEN>(&pool);
let base_fee = pair_parameter_helper::get_base_fee(&params, current_timestamp);
let variable_fee = pair_parameter_helper::get_variable_fee(&params, tick_spacing);
let vol_acc = pair_parameter_helper::get_volatility_accumulator(&params);
```

#### Fee Scheduler Timeline

```
Time:    t0 (activation)    t1          t2         ...    tN (end)
         ┌─────────────────────────────────────────────────────┐
Linear:  │ cliff=10% → 9.1% → 8.2% → ... → 1% (base_fee)       │
         └─────────────────────────────────────────────────────┘
         ┌─────────────────────────────────────────────────────┐
Expon:   │ cliff=10% → 8% → 6.4% → 5.1% → ... → base_fee       │
         └─────────────────────────────────────────────────────┘
```

#### Dynamic Fee Response to Volatility

```
Price stable:    vol_acc=0    → variable_fee=0
Small move:      vol_acc=10   → variable_fee=(10×60)²×1000/100 = 3,600,000 (0.36%)
Large move:      vol_acc=100  → variable_fee=(100×60)²×1000/100 = 360,000,000 (36%)
After decay:     vol_acc→0    → variable_fee→0
```
