> For the complete documentation index, see [llms.txt](https://docs.curvance.com/app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.curvance.com/app/developer-docs/quick-start-guides/liquidations.md).

# Liquidations

## Quick Start: Liquidations

### Overview

Liquidations are the mechanism that keeps Curvance solvent. When a borrower's position becomes undercollateralized, third parties (liquidators) can repay part or all of the debt in exchange for seizing the borrower's collateral at a discount. Liquidators are compensated for the service; the protocol avoids bad debt accumulation.

***

### How Liquidations Work

#### Collateral requirements

Each collateral-capable cToken is configured with a collateral ratio plus two liquidation thresholds:

* **Soft requirement:** the adjusted collateral value where a position first becomes partially liquidatable.
* **Hard requirement:** the adjusted collateral value where standard protocol-derived liquidation can close the full debt position and may realize bad debt if posted collateral is insufficient.

Read the values for a given market with `marketManager.collConfig(cToken)`, which returns `(collRatio, collReqSoft, collReqHard)`. These values are BPS-denominated. `collReqSoft` and `collReqHard` are stored as `BPS + configuredPremium`, so integrations should read the returned values rather than reconstructing them from deployment inputs.

#### Health factor (`lFactor`)

| lFactor           | Meaning          | Behaviour                                                          |
| ----------------- | ---------------- | ------------------------------------------------------------------ |
| `0`               | Healthy          | Not liquidatable                                                   |
| `1` ... `WAD - 1` | Soft liquidation | Partial liquidation allowed; size and penalty scale with `lFactor` |
| `WAD` (`1e18`)    | Hard liquidation | Full position can be closed; can realize bad debt                  |

`lFactor` is computed as `mulDivUp(debt - cSoft, WAD, cHard - cSoft)` when the debt lands between the soft and hard thresholds (`LiquidityManagerIsolated.sol:575`). It interpolates linearly from "just crossed soft" to "at hard."

#### Dynamic close factor and liquidation incentive

* **Close factor:** when no auction-specific close factor is supplied, the protocol computes `closeFactor = closeFactorBase + (closeFactorCurve * lFactor / WAD)`. `closeFactorCurve` is stored as `BPS - closeFactorBase`, so a standard hard liquidation reaches a 100% close factor.
* **Liquidation incentive:** when no auction-specific incentive is supplied, the protocol computes `liqInc = liqIncBase + (liqIncCurve * lFactor / WAD)`. `liqIncBase` is stored as `BPS + baseIncentive`, and `liqIncCurve` is the distance from the base incentive to the hard-liquidation incentive.

Read the stored liquidation parameters with `marketManager.liquidationConfig(cToken)`. They are configured per cToken inside a market through `updateTokenConfig`; auction flows can temporarily supply a bounded incentive or close factor with `setTransientLiquidationConfig`, while passing zero uses protocol-derived values.

***

### When Positions Become Liquidatable

A position crosses into `lFactor > 0` when any of these hold:

* Collateral value falls relative to debt (collateral price drop).
* Debt asset value rises relative to collateral (debt price spike).
* Interest accrues on debt, pushing the position past the soft requirement.
* Any combination of the above.

***

### Two Types of Liquidations

#### Non-Auction Liquidations

Standard liquidations are permissionless when market-wide liquidations are not paused and the account has a repayable liquidation amount. Without an active auction unlock, the MarketManager uses the protocol-derived close factor and liquidation incentive from the current `lFactor`.

#### Auction-Enabled Liquidations

Auction-enabled liquidations apply only inside an authorized auction flow. The auction setup stores an unlocked collateral cToken plus optional transient incentive and close-factor values, then unlocks the market for the transaction. The MarketManager applies `AUCTION_BUFFER` only when both the market is unlocked and the requested collateral token matches the unlocked cToken; otherwise the attempted auction liquidation reverts.\
\
`AUCTION_BUFFER` discounts `cSoft` and `cHard`, so auction calculations can cross the soft threshold earlier or produce a higher `lFactor` than the non-auction path. The buffer is 9990 for correlated markets and 9950 for uncorrelated markets. Auction-specific incentive and close-factor values are bounded by each cToken's configured min/max values; zero means the protocol derives that value from `lFactor`.

***

### Liquidator Workflow

1. **Discover liquidatable accounts** by indexing position and debt activity, including `PositionUpdated`, `Borrow`, `Repay`, `CollateralUpdated`, and `Liquidated`. Account health can also change because oracle prices move or interest accrues, so event indexing should be paired with current reads.<br>

   For screening, use `ProtocolReader.liquidationValuesOf(marketManager, account, false)` for the standard path or `true` for the auction-buffered path. It returns `(cSoft, cHard, debt, lFactor, errorCodeHit)`. Treat that as a read-side screen, then simulate the liquidation entrypoint or `canLiquidate` before submitting a transaction.
2. **Validate via `canLiquidate`.** Get exact amounts, seized shares, and any bad-debt realization before committing to a tx.
3. **Execute** `liquidate()` (max amount) or `liquidateExact()` (caller-specified per-account amounts) on the debt cToken.
4. **Receive collateral.** Seized collateral shares are transferred directly to the liquidator's address.

#### Entrypoints

```solidity
// BorrowableCToken.sol:333 - liquidate as much as possible across all accounts
function liquidate(
    address[] calldata accounts,
    address collateralToken
) external;

// BorrowableCToken.sol:306 - liquidate caller-specified amounts per account
function liquidateExact(
    uint256[] calldata debtAmounts,
    address[] calldata accounts,
    address collateralToken
) external;
```

{% hint style="warning" %}
**Critical non-obvious requirement:** `accounts` **must be sorted in strictly ascending address order with no duplicates**. `canLiquidate` reverts with `MarketManager__Unauthorized()` when `priorAccount >= cachedAccount`, so sorting and deduping must happen client-side.

Self-liquidation is also blocked. If `liquidator == accounts[i]`, `canLiquidate` reverts with `MarketManager__Unauthorized()`.
{% endhint %}

**Self-liquidation is blocked.** `liquidator == accounts[i]` reverts (`:598`). You cannot liquidate yourself.

#### Pre-liquidation check via `canLiquidate`

`canLiquidate` at `MarketManagerIsolated.sol:569` is the primary off-chain helper. It's not a `view` function (it reads mutable state during price accrual) but can be simulated via `staticCall`:

```js
const debtAmounts = Array(accounts.length).fill(0n);

const [result, adjustedDebtAmounts] =
  await marketManager.canLiquidate.staticCall(
    debtAmounts,
    liquidatorAddress,
    accounts,
    {
      collateralToken: COLLATERAL_CTOKEN,
      debtToken: DEBT_CTOKEN,
      numAccounts: BigInt(accounts.length),
      liquidateExact: false,
      liquidatedShares: 0n,
      debtRepaid: 0n,
      badDebt: 0n,
    }
  );
```

For maximum liquidation through `canLiquidate`, pass a zero-filled `debtAmounts` array with the same length as `accounts` and set `liquidateExact: false`. For exact liquidation, pass one desired debt amount per account and set `liquidateExact: true`. The helper returns a tuple, not an object with `result` and `remaining` fields.

If you want the closest end-to-end simulation of the public liquidation path, simulate `debtCToken.liquidate.staticCall(accounts, collateralToken)` or `debtCToken.liquidateExact.staticCall(debtAmounts, accounts, collateralToken)`. Those paths run the debt cToken's pre-liquidation accrual and then call `marketManager.canLiquidate`.

`result` contains `liquidatedShares[]` (collateral shares you'd receive per account), total `debtRepaid`, and any `badDebtRealized`. Use this to compute expected profit after gas.

***

### Key Considerations

* **Account discovery:** index `PositionUpdated`, `Borrow`, `CollateralUpdated`, and `Repay` events. Cross-reference with a fresh `lFactor` read via `liquidationValuesOf(account, auctionBuffer=0)` before committing to a liquidation tx.
* **Profitability:** `liquidatedShares` are cToken shares, so convert seized shares through the current exchange rate and collateral price before comparing against `debtRepaid`, swap costs, flash-loan costs, and gas. In standard protocol-derived liquidations, the incentive increases with `lFactor`, but profit is not guaranteed. For `liquidateExact`, the call reverts if the requested debt amount exceeds the computed max debt or if the resulting seized shares exceed the borrower's posted collateral.
* **Smart contracts over scripts:** a Solidity liquidator contract can atomically check profitability and revert if conditions change mid-tx. Off-chain scripts can't guarantee atomicity; a competing liquidator's tx landing first can either empty the position or change pricing state.
* F**lash loans:** a liquidator contract can use `flashLoan` on the debt `BorrowableCToken` to source the repayment asset. The callback is `onFlashLoan(assets, assetsReturned, data)`. Inside the callback, execute the liquidation, convert seized collateral as needed, and end with enough debt asset plus allowance for the cToken to pull `assetsReturned`. See Flash Loans.
* **Competition:** popular liquidation opportunities are MEV-competitive. Optimize for speed (direct node RPC, private mempools), gas efficiency, and atomic tx design.
* **Oracle gating:** liquidation pricing asks the Oracle Manager for the collateral cToken share price and the debt cToken underlying price with `BAD_SOURCE` as the error-code breakpoint. If either side reaches that breakpoint, the oracle call reverts and the liquidation does not proceed. Lower-severity `CAUTION` responses do not block this path.

***
