> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nilemarkets.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Liquidation

> Position is liquidatable when equity (lockedMargin + unrealizedPnL) falls below maintenance margin (notional × mmBps / 10000) — permissionless trigger, PnL capped at locked margin, 0.3% penalty

Liquidation is the protocol's mechanism for closing positions that have lost too much value relative to their locked margin. When a position's equity drops below its maintenance margin threshold, anyone can trigger liquidation. This protects the liquidity pool from accumulating excessive bad debt by closing distressed positions before losses grow further.

## Liquidation Condition

A position becomes liquidatable when its equity falls below the maintenance margin (MM) threshold:

```
positionEquity < mmThreshold
```

Where:

* `positionEquity = imLocked + unrealizedPnl`
* `mmThreshold = notional * snapshotMmBps / 10000`

<Warning>
  The comparison is **strict less-than** (`<`). A position whose equity is exactly equal to its MM threshold is NOT liquidatable. This matters at the boundary -- a position at equity = 10 USDC with mmThreshold = 10 USDC remains safe.
</Warning>

## Liquidation Flow

<Steps>
  <Step title="Trigger Liquidation">
    Anyone calls `SettlementEngine.liquidatePosition(positionId)` or `SettlementEngine.batchLiquidatePositions(pairId, maxCount)`. There is no role restriction -- liquidation is fully permissionless.
  </Step>

  <Step title="Verify Preconditions">
    The contract checks that the position is `OPEN` and that it is currently liquidatable (`positionEquity < mmThreshold`). If either check fails, the transaction reverts.
  </Step>

  <Step title="Get Forward Price">
    The current forward price for the position's fixing timestamp is fetched from the OracleModule. An oracle fee is collected from the trader's collateral for this price read.
  </Step>

  <Step title="Calculate PnL">
    PnL is computed using the forward price:

    * **LONG**: `pnl = notional * (forwardPrice - entryStrike) / PRICE_PRECISION`
    * **SHORT**: `pnl = notional * (entryStrike - forwardPrice) / PRICE_PRECISION`

    Both `realizedPnl` (capped) and `marketPnl` (uncapped) are recorded.
  </Step>

  <Step title="Calculate Fees">
    Liquidation incurs both the trading fee and the liquidation penalty:

    ```
    tradingFee = notional * snapshotTradingFeeBps / 10000
    liquidationPenalty = notional * snapshotLiquidationPenaltyBps / 10000
    totalFee = tradingFee + liquidationPenalty
    ```

    Both rates use the snapshotted values from position open time. The settlement waterfall is fee-first: `actualFee = min(totalFee, marginAtRisk)`, and the remaining loss is then capped at `marginAtRisk - actualFee`. Any uncollectable loss becomes bad debt absorbed by LPs (see [Fees](/protocol/fees) for the full waterfall description).
  </Step>

  <Step title="Settle and Close">
    The position is settled with `CloseReason.LIQUIDATED`. USDC flows are distributed: PnL between trader and pool, fees to fee destinations (30% treasury, 70% pool). The `PositionLiquidated` event is emitted.
  </Step>
</Steps>

## Liquidation Guard

The protocol enforces a critical guard: **liquidatable positions cannot be closed through early termination**. When a position owner calls `closePosition()`, the contract checks `!isLiquidatable()` and reverts with `EarlyTerminationNotAllowed()` if the position is underwater.

This guard exists to ensure that the liquidation penalty is always collected on distressed positions. Without it, a trader could close their liquidatable position via early termination and avoid the penalty fee.

<Note>
  The liquidation guard is one-directional. It prevents early termination of liquidatable positions, but it does not prevent adding margin. A trader can always call `addPositionMargin` to add margin to a liquidatable position, potentially rescuing it from liquidation by raising equity above the MM threshold.
</Note>

## Account-Level Guard

The protocol also enforces an account-level liquidation guard:

**`AccountHasLiquidatablePosition`** -- A trader cannot open new positions while any of their existing positions is liquidatable. This prevents a scenario where a trader ignores a liquidatable position and continues taking on new risk. The trader must either add margin to rescue the position or wait for it to be liquidated before opening new positions.

## Permissionless Liquidation

Liquidation is fully permissionless. Any Ethereum address can call the liquidation functions:

| Function                                                    | Description                                              |
| ----------------------------------------------------------- | -------------------------------------------------------- |
| `liquidatePosition(uint256 positionId)`                     | Liquidate a single position                              |
| `batchLiquidatePositions(bytes32 pairId, uint256 maxCount)` | Liquidate up to `maxCount` eligible positions for a pair |

There is no special liquidator role, no allowlist, and no priority ordering. The first transaction to execute successfully liquidates the position.

## No Liquidator Reward

<Info>
  Unlike many DeFi protocols, the Open Nile Protocol does **not** reward the liquidator. The liquidation penalty goes entirely to fee destinations (30% treasury, 70% pool), not to the address that triggered the liquidation. The liquidator pays gas costs with no direct economic incentive.

  In M2 (testnet), the keeper service performs liquidations as a protocol service. M3 may introduce liquidator incentives for external participants.
</Info>

## Bad Debt

When a position's loss exceeds its locked margin, the excess becomes bad debt:

```
if |marketPnl| > imLocked:
    realizedPnl = -imLocked              // trader loses all locked margin
    badDebt = |marketPnl| - imLocked     // excess absorbed by pool
```

Bad debt reduces pool equity and LP share price. A `BadDebt` event is emitted with the account, position ID, and bad debt amount for off-chain monitoring.

The pool's equity is clamped at zero -- it cannot go negative even under extreme bad debt accumulation. This preserves the ERC-4626 vault accounting invariant.

## Worked Example

<Accordion title="Liquidation walkthrough with numbers">
  **Setup:**

  * Notional: 1,000 USDC
  * Side: LONG
  * Entry strike: 1.0800
  * IM locked: 20 USDC (2% of notional)
  * MM threshold: 10 USDC (1% of notional, from snapshotMmBps = 100)
  * Snapshotted trading fee: 5 bps (0.05%)
  * Snapshotted liquidation penalty: 30 bps (0.3%)

  **Price moves against the trader:**

  * Current forward price: 1.0689
  * PnL = 1,000 \* (1.0689 - 1.0800) = -11 USDC

  **Liquidation check:**

  * Position equity = 20 (IM) + (-11) (PnL) = 9 USDC
  * MM threshold = 10 USDC
  * 9 \< 10 --> **position is liquidatable**

  **Fee calculation:**

  * Trading fee = 1,000 \* 5 / 10,000 = 0.50 USDC
  * Liquidation penalty = 1,000 \* 30 / 10,000 = 3.00 USDC
  * Total fee = 3.50 USDC

  **Settlement:**

  * Margin at risk: 20 USDC
  * Loss: 11 USDC (within margin, no bad debt)
  * Margin after loss: 20 - 11 = 9 USDC
  * Fee deducted: 3.50 USDC (capped at available 9 USDC, so full amount collected)
  * **Returned to trader: 9 - 3.50 = 5.50 USDC**
  * Pool receives: 11 USDC (from trader's loss)
  * Fee destinations receive: 3.50 USDC (1.05 treasury, 2.45 pool)
</Accordion>

## Liquidation Timing

The protocol does not enforce a grace period or delay before liquidation. The moment a position's equity drops below the MM threshold, it is immediately eligible for liquidation. In practice, the keeper service checks for liquidatable positions on every cycle (aligned with block time), so there is a small operational delay between a position becoming liquidatable and actual liquidation execution.

<Columns cols={2}>
  <Card title="Before Maturity" icon="clock">
    Positions can be liquidated at any time before maturity if the forward price moves sufficiently against the trader. The forward price is the reference for the liquidation check.
  </Card>

  <Card title="At Maturity" icon="calendar-check">
    Matured positions that are also liquidatable must go through liquidation, not maturity settlement. The `settlePosition` function checks `!isLiquidatable()` and reverts for underwater positions, ensuring the liquidation penalty is collected.
  </Card>
</Columns>
