health_factor < 1e18. You repay part of the debt and receive collateral worth the repayment plus a bonus. Supply debt assets and a budget; the controller computes repayment, seizure, refund, and protocol fee.
Goal
Bring an account withhealth_factor < 1e18 back toward health by repaying its debt, and collect the seized collateral (repayment value plus the liquidation bonus, net of the protocol fee).
Preconditions
- The target’s
health_factor(account_id)is below1e18(can_be_liquidatedreturnstrue). Otherwise#101 HealthFactorTooHigh. liquidator != account owner. Self-liquidation is rejected with#13 AccountNotInMarket.liquidatorholds at least the budgeted amount of every asset indebt_payments.- Every collateral and debt asset on the account is priced under the strict
Liquidationoracle policy. Stale, deviating, or degraded-dual prices are not tolerated. debt_paymentslists the debt assets you are willing to repay with a maximum budget each; the engine may use less and refunds the excess.
Inputs
Funds the repayment and receives the seized collateral. Authorizes the transfers. Must not own the account.
The unhealthy account being liquidated.
Each entry is
(debt_asset, max_amount) in asset-native units: your repayment budget per debt asset. Excess above the engine’s computed repayment is refunded.Estimate first
Before sending, simulate the outcome with read-only views so you know the repayment, seizure, refund, and bonus up front:| View | Returns |
|---|---|
can_be_liquidated(account_id) | bool — health_factor < 1e18. |
health_factor(account_id) | i128 (WAD) — current health factor. |
liquidation_estimations_detailed(account_id, debt_payments) | LiquidationEstimate — seized collateral, protocol fees, refunds, max payment, and bonus rate for your budget. |
liquidation_collateral_available(account_id) | i128 (WAD) — liquidation-threshold-weighted collateral. |
Liquidation engine
There is no fixed close factor. The engine picks the repayment that restores the most health it can, at a bonus that scales with how unhealthy the account is:- Try the repayment that lifts the health factor to 1.02 at an interpolated bonus; accept it if the resulting health factor is at least
1.0. - Otherwise try target 1.01.
- Otherwise fall back to
d_max = total_collateral / (1 + base_bonus)(capped at total debt) at the base bonus.
bonus = base + (max − base)·min(2·gap, 1), where gap = (target − HF) / target. base_bonus is the value-weighted average of each collateral’s liquidation_bonus_bps; max_bonus derives from the seized asset’s liquidation threshold. The deeper underwater the account, the closer the bonus moves to max.
Seizure and fee: total seizure value is repaid_usd · (1 + bonus), split across collaterals by value share. Per collateral, base = floor(seized / (1 + bonus)), bonus_part = seized − base, and the protocol fee is liquidation_fees_bps · bonus_part. The fee comes out of the bonus only, never the liquidator’s principal. The liquidator receives seized − protocol_fee. Any budget above the computed repayment is refunded.
Partial closes: the engine expands repayment sizing to avoid leaving uneconomical residual debt when a full close is feasible.
The pool calls are: repay(action) for the debt leg, then withdraw(action, is_liquidation = true, protocol_fee) for the seize leg, which skims the protocol fee as revenue and bypasses the max-utilization check.
Worked example
An account holds 900 of USDC debt. Prices in WAD; all USD figures are WAD-scaled.- Weighted collateral =
1000 · 0.78 = 780. Health factor =780 / 900 = 0.8667e18→ below1e18, liquidatable. - The single collateral fixes
base_bonus = 750 bps = 0.075. Suppose the engine’s cascade settles on repaying $300 of USDC at the interpolated bonus, which for this gap resolves to 7.5% (the base, for a shallow shortfall). - Seizure value =
300 · (1 + 0.075) = 322.5of XLM collateral. base = 322.5 / 1.075 = 300;bonus_part = 322.5 − 300 = 22.5.- Protocol fee =
0.01 · 22.5 = 0.225(1% of the bonus). Liquidator receives322.5 − 0.225 = 322.275of XLM. - After: collateral
1000 − 322.5 = 677.5, debt900 − 300 = 600. New weighted collateral677.5 · 0.78 = 528.45; health factor528.45 / 600 = 0.8808e18.
Build the call
Authorize
liquidator.require_auth() runs inside liquidate. The transaction authorizes one token transfer per debt asset from liquidator to the pool (the repayment leg). The seizure transfer (collateral from the pool to liquidator) is pool-internal and needs no extra signature. The call is permissionless: only the liquidator’s signature is required, never the target owner’s.
Verify
On success the controller emits:UpdatePositionBatchEvent:["position", "batch_update"]; the target’s reduced debt and seized supply deltas.UpdateMarketStateBatchEvent:["market", "batch_state_update"]; refreshed indexes.CleanBadDebtEvent:["debt", "bad_debt"]; only when the liquidation triggers bad-debt socialization (see below).
health_factor(account_id) (now higher, or i128::MAX if fully closed), collateral_amount_for_token / borrow_amount_for_token for the seized and repaid assets, and your own token balances for the received collateral and any refund.
Bad debt
When an account is so far underwater thattotal_debt > total_collateral and total_collateral ≤ $5 WAD (BAD_DEBT_USD_THRESHOLD = 5·WAD), it cannot be cleared profitably. The protocol socializes the loss: it seizes all remaining supply and debt, removes the account, and reduces each affected market’s supply_index proportionally (new = index · (1 − capped_bad_debt / total_supplied_value), floored at WAD). This runs inline when a liquidation crosses the threshold, or any caller can invoke clean_bad_debt(caller, account_id) directly (#114 CannotCleanBadDebt if the account is not eligible). Either path emits CleanBadDebtEvent with account_id, total_borrow_usd_wad, and total_collateral_usd_wad.
Failure modes
| Error | Cause | Fix |
|---|---|---|
#101 HealthFactorTooHigh | Target’s health factor is at or above 1e18 | Not liquidatable; wait for it to drop or check can_be_liquidated. |
#13 AccountNotInMarket | liquidator owns the account (self-liquidation) | Use a different address; you cannot liquidate yourself. |
#205 UnsafePriceNotAllowed | An asset’s oracle price is stale or deviates too far | Retry once the feed recovers; the Liquidation policy is strict. |
#114 CannotCleanBadDebt | clean_bad_debt on an account not meeting the bad-debt rule | Only call when collateral ≤ $5 WAD and debt exceeds collateral. |
Next
Health factor
The exact definition that triggers liquidation, with a worked example.
Risk parameters
Liquidation threshold, bonus, and fee per market, with configured values.

