Skip to main content
Liquidation repays an unhealthy account’s debt and seizes collateral in one controller call. Any caller can liquidate, including the account owner. Read Health factor before implementing a liquidator. Liquidation math defines the bonus curve, repayment sizing, and fee calculations.

Check eligibility and permissions

The account must have health factor below 1e18; use is_liquidatable(account_id) or get_health_factor(account_id) to check. Every collateral and debt asset needed for valuation must have a valid price. An unavailable, stale, or disagreeing price reverts the transaction. debt_payments gives a maximum token-native budget for each debt market. The liquidator holds and authorizes those tokens. The planner can use less. A final full-close plan pulls the entire merged offer and the pool refunds excess; other plans pull only accepted amounts. Global protocol pause permits liquidation. Collateral seizure ignores listing paused and frozen flags. It checks no_seize only on generated seizure legs, after dropping rounded-zero legs (#318 SpokeAssetSeizureHalted). A paused debt listing blocks repayment; a frozen listing permits it. Missing listings permit repayment and seizure of existing positions.

Choose a seize mode

One mode applies to all collateral, seized pro rata by USD value: Credit(0) creates an account owned by the liquidator on the target’s spoke. For a nonzero receiving id, the liquidator must be its current NFT owner or an active registered manager delegated by that owner. The receiving account must use that spoke and PositionMode::Normal. Creating a receiver supply position requires a current listing. An existing receiver position retains its stamped risk parameters. Execution also checks receiver position limits and isolation rules. Credit(account_id) cannot name the account being liquidated (#133 SelfLiquidationNotAllowed). This restriction concerns the receiving account; the owner may still liquidate its own account.

Estimate the result

Call get_liquidation_estimate(account_id, debt_payments, seize_mode) using the same budgets and mode intended for execution. The result includes gross seizure, protocol fees, unused budgets, maximum payment, and bonus rate. get_liquidation_collateral(account_id) returns liquidation-threshold-weighted collateral in USD-WAD. It is the health-factor numerator, not the maximum seizure amount. The estimate does not check receiver authorization, receiver capacity, or pool cash. Simulate the full call to check execution feasibility. Seizure and fee tuples contain token addresses without hub ids. Match them to the target’s ordered (hub_id, asset) supply keys, allowing omitted legs. A unique ordered match identifies the markets. Repeated token addresses can make the match ambiguous. Use the lowest proceeds across compatible mappings, or stop if you cannot establish a bound. An address-only lookup can select the wrong index.

Enforce a profitability bound

Estimates reflect the state at the time of the read. Another liquidation or price change can alter the executable bonus and amounts. Enforce the required net proceeds in the liquidator contract and revert if they are insufficient.
Rounding can remove a small liquidation’s profit. Transfer fees have a positive one-base-unit minimum, capped by whole units paid above repayment principal. For each Transfer leg, clamp estimated gross to the floor of the held claim before subtracting fees. For Credit, subtract fee shares first, then convert the net shares once through the supply index. Simulate the complete transaction and enforce the required net proceeds.
A full-debt ideal permits smaller offers. The final plan still depends on rounding, collateral backing, and available debt legs. Low-decimal partial seizures release repayment that rounded-away collateral no longer backs. See repayment limits.

Build and authorize the call

Use the G-address of the liquidator source-account identity in these direct signer examples. A contract liquidator must supply its own invocation authorization. Only the liquidator authorizes the call and repayment transfers to the pool. The target owner’s signature is not required. The pool authorizes Transfer payouts; Credit seizure moves supply shares without transferring collateral tokens.

Verify the liquidation

Confirm getTransaction(original_hash) returns SUCCESS on the intended network before applying events. Preserve the signed envelope and original hash before submission. Follow unknown transaction recovery if submission or polling leaves the outcome unknown. The events describe several parts of settlement: LiqSeize is gross of fees; LiqCredit is net. Their event amount values use token units with different rounding. Subtracting them cannot measure an exact Credit fee, and summing them double-counts collateral. Each child event’s scaled amount is the resulting position balance. For an identified Credit invocation, calculate each market’s exact fee in shares:
Use balances immediately before that liquidation, after prior operations in the same transaction. Apply ordered child updates keyed by account, hub, and asset. An omitted receiver leg means zero credited shares for that invocation. For example, target shares can start at 100, rise to 120 through supply, then fall to 90 through liquidation. If the receiver rises from 10 to 37, the fee is 30 - 27 = 3 shares. Transaction-start balances would give the wrong result. Read the target’s health factor, collateral, and debt balances after execution. For Credit mode, also read the receiving account’s scaled positions. Check actual token balances for repayment and Transfer payouts. A valid Credit call with no net seizure has no receiver position batch.

Clean bad debt

Any caller can invoke clean_bad_debt(caller, account_id) when debt exceeds collateral and remaining collateral is at most $5 in USD-WAD. Liquidation also performs this cleanup when it leaves an eligible residue. Cleanup removes the remaining positions and account, burns the NFT, and reduces supply indexes in markets with debt losses. Other markets’ indexes are unaffected. See bad-debt accounting. Above the $5 collateral limit, governance can call force_socialize_bad_debt(account_id) when debt exceeds collateral. Permissionless cleanup rejects ineligible accounts with #114 CannotCleanBadDebt. Both paths emit CleanBadDebtEvent with account_id, total_borrow_usd_wad, and total_collateral_usd_wad. This event contains no position deltas and follows any position batch.

Common errors

Use Events for event field definitions and Errors for the full error reference. Match an error code and emitting contract from the same raw diagnostic event. A handled nested failure can precede the final propagated error. Preserve raw diagnostics; decoded display helpers can omit metadata and success flags.