Credit mode, or 0 in Transfer mode.
Preconditions
- The target’s
get_health_factor(account_id)is below1e18(is_liquidatablereturnstrue). Otherwise#101 HealthFactorTooHigh. liquidatorholds at least the budgeted amount of every asset indebt_payments.- Every collateral and debt asset on the account resolves a valid price. A stale, deviating, or unavailable feed reverts the whole call — the protocol will not liquidate on a price it does not trust.
debt_paymentslists the debt assets you are willing to repay with a maximum budget each; the engine may use less and simply leaves the excess with you.
Self-liquidation of an account is allowed.
liquidate is fully
permissionless, including by the account’s own owner. The only self-reference
that is rejected is SeizeMode::Credit(account_id) naming the liquidated account
itself, which would hand its collateral straight back —
#133 SelfLiquidationNotAllowed.Seize modes
OneSeizeMode governs the whole call, because seizure is pro-rata across the
account’s entire collateral set — a per-asset choice would have no meaning.
Credit(0) creates the receiving account, owned by the liquidator and bound to
the liquidated account’s spoke. A non-zero id must already exist, not be the
liquidated account, be owned by or delegated to the liquidator, sit in the
liquidated account’s spoke, and be in PositionMode::Normal.
Share credit exists for liveness: a seizure that must be paid in underlying can
fail purely because the market has no spare cash, exactly when liquidations
matter most. Moving shares needs none. Because scaled amounts are
index-independent, a share credit is also immune to index drift between planning
and application, which the transfer path is not.
Estimate first
The liquidation curve
There is no fixed close factor, and the bonus is not a constant. Both are driven by the account’s live health factor against its spoke’s configured curve (SpokeConfig):
Read the live values with
get_spoke(spoke_id).
How the bonus is picked
The bonus sits between a floor and a ceiling, and slides between them as the account gets sicker. First, one ratio matters throughout:base). Take each collateral position’s stamped
liquidation_bonus and average them, weighted by each position’s USD value.
The ceiling (max). The largest bonus that still satisfies
(1 + bonus) · proportion_seized = 1. Above that, a seizure would take more
value than the threshold allows.
The slide. The bonus ramps linearly as health factor falls:
target_hf you get exactly base. At or below hf_for_max_bonus
the scale hits 1 and you get the full ramp.
One last clamp. The result is capped at hf · BPS / proportion_seized − BPS,
which is the most the account’s current health factor can support. If that cap
lands below the account’s own base, no partial close can be priced at all —
so the engine closes the entire debt at the base bonus instead.
How much you can repay
The ideal repayment is whatever brings health factor totarget_hf at that
bonus. It is capped twice: by d_max = total_collateral / (1 + bonus), and by
total debt.
One adjustment: if repaying the ideal would leave less than $5 of debt behind,
the ideal is raised to a full close. That lets you finish the dust stub. It
does not require you to.
What you actually pay is min(offered, ideal). The protocol never pulls more
than the ideal — it trims each entry, and drops whole legs, before any transfer.
So you simply keep the excess.
Those trimmed amounts are what
get_liquidation_estimate calls refunds. The
name is misleading: no refund transfer happens. The money never leaves your
wallet in the first place.#135 FullCloseRequired means a partial repayment fell below the ideal while
that last bonus clamp was binding. Repay the full debt instead.
Seizure and fee
Total seizure value isrepaid_usd · (1 + bonus). It is split across the
account’s collaterals in proportion to their value.
The protocol fee is liquidation_fees bps of the bonus portion only. It
never touches the principal you repaid.
If the debt token delivers less than it is sent, every seizure shrinks in
proportion, floor-rounded. LiquidationEvent.repaid_usd_wad always reports the
measured repayment, never the planned one.
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). In Transfer mode the seizure payout is pool-internal and needs
no extra signature; in Credit mode no collateral tokens move at all. Only the
liquidator’s signature is required, never the target owner’s.
Liquidation is not pause-gated and is not blocked by a collateral’s paused
flag — but a collateral carrying no_seize cannot be taken
(#318 SpokeAssetSeizureHalted).
Verify
On success:LiquidationEvent—["position", "liquidation"];liquidator,account_id,repaid_usd_wad(measured),bonus_bps. It carries no seizure or fee figure.UpdatePositionBatchEvent—["position", "batch_update"];LiqRepayandLiqSeizelegs on the liquidated account. ACreditliquidation publishes two batches: the liquidated account’s first, the receiving account’s second (supply-side only, taggedLiqCredit).PoolMarketStateBatchEvent—["market", "batch_state_update"]; refreshed indexes.CleanBadDebtEvent—["debt", "bad_debt"]; only when the liquidation triggers bad-debt socialization.
LiqSeize is gross of the protocol fee; LiqCredit is net. In
share-credit mode the fee is exactly LiqSeize.amount − LiqCredit.amount.
Summing both tags as the same quantity double-counts.
Confirm with get_health_factor(account_id), get_collateral_amount /
get_borrow_amount for the seized and repaid assets, and your own token
balances.
Bad debt
Anyone can socialize an account’s bad debt when both of these hold:- Total debt exceeds total collateral, and
- the remaining collateral is at or below
BAD_DEBT_USD_THRESHOLD = 5 · WAD($5).
SUPPLY_INDEX_FLOOR_RAW = RAY / 1_000 (0.001), not
at WAD. Only suppliers of that one market absorb the loss.
This runs inline when a liquidation leaves an eligible residue, or any caller may
invoke clean_bad_debt(caller, account_id) directly
(#114 CannotCleanBadDebt if the account is not eligible).
Above the $5 threshold, socialization requires the governance-only
force_socialize_bad_debt(account_id), which drops the dust cap and needs only
that debt exceeds collateral.
Either path emits CleanBadDebtEvent with account_id,
total_borrow_usd_wad, and total_collateral_usd_wad. It records no position
deltas and is published after any position batch.
Failure modes
Next
Health factor
The exact definition that triggers liquidation, with a worked example.
Risk parameters
Liquidation threshold, bonus, fee, and the halt flags.

