> 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
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.ferra.ag/integration/damm/smart-contract/fee-module.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
