UnitFlow LogoUnitFlow Docs

Liquidations

Liquidations are the mechanism that keeps the UnitFlow Lending protocol solvent. When a borrower's health factor falls below 1.0, their position becomes undercollateralised relative to the protocol's risk parameters. Any wallet can then repay part of that debt and receive the borrower's collateral at a discount — the liquidation bonus.

Health Factor

The health factor (HF) is a single number that summarises the safety of a position:

healthFactor = (totalCollateralUSD × liquidationThreshold) / totalDebtUSD
             = (totalCollateralUSD × 0.6667) / totalDebtUSD

// Expressed as a WAD (1e18) integer on-chain
// HF = 1.5e18  →  displayed as "1.50"
// HF = MaxUint256  →  no debt, displayed as "∞"
Health FactorStatusUI Colour
≥ 2.0SafeGreen
1.5 – 2.0Moderate riskYellow
1.1 – 1.5High riskOrange
1.0 – 1.1CriticalRed (pulsing dot)
< 1.0LiquidatableRed

What Causes HF to Drop?

  • Borrow interest accruing over time (debt grows, collateral stays the same)
  • Withdrawing collateral while a borrow is active
  • Borrowing more against the same collateral
  • A fall in the oracle price of the collateral asset (relevant when non-stablecoin assets are added)

Liquidation Mechanics

Close Factor — 50%

A liquidator can repay at most 50% of a borrower's outstanding debt in a single call. This prevents a single liquidation from over-correcting the position and ensures borrowers retain some collateral after a liquidation event.

maxDebtToCover = totalDebtUSD × 0.50   // in USD
debtToCover    = min(requestedAmount, maxDebtToCover)

Collateral Seizure

The liquidator specifies which collateral asset they want to receive. The engine calculates the equivalent collateral amount at the oracle price, applies the liquidation bonus, and transfers it from the borrower's position to the liquidator:

collateralToSeize = debtToCoverUSD / collateralPriceUSD × (1 + liquidationBonus)
// liquidationBonus is set per reserve by the Configurator

Transaction Flow

// Liquidator calls:
LiquidationEngine.liquidate(
  borrower:        0xBorrowerAddress,
  collateralAsset: 0x3600...0000,   // USDC — asset to receive
  debtAsset:       0x89b5...d72a,   // EURC — asset to repay
  debtToCover:     500_000          // 0.5 EURC (6 decimals)
)

// Engine internally:
// 1. Checks HF < 1e18 (position is liquidatable)
// 2. Checks debtToCover ≤ 50% of outstanding debt
// 3. Transfers debtToCover from liquidator to pool (repays debt)
// 4. Burns borrower's dTokens proportionally
// 5. Calculates collateralToSeize with bonus
// 6. Transfers collateral from borrower's position to liquidator
// 7. Updates borrower's userCollateral mapping
// 8. Emits Liquidation event

Liquidation Dashboard

The Liquidation Dashboard at the bottom of the Lending page (visible when your wallet is connected) shows all positions with a health factor below 1.2. It is populated by:

  1. Scanning Supply events from the last 50,000 blocks to discover all unique borrower addresses.
  2. Calling LiquidationEngine.getHealthFactor() for each address in a batched multicall.
  3. Fetching full account data for addresses with HF < 1.2.

Each row shows the borrower address, their collateral amount and asset, their debt amount and asset, and their health factor badge. Positions with HF < 1.0 show a red Liquidate button. Clicking it:

  1. Approves the LiquidationEngine to spend your debt asset (if needed)
  2. Calls liquidate() with 50% of the borrower's debt as debtToCover
  3. Shows a Done link to the transaction on ArcScan on success

Running a Liquidation Bot

For automated liquidation, you can monitor the pool off-chain and call the LiquidationEngine directly. A minimal bot pattern:

import { createPublicClient, createWalletClient, http } from 'viem'
import { arcTestnet } from './chains'

const publicClient = createPublicClient({ chain: arcTestnet, transport: http() })

// 1. Discover borrowers from Supply events
const logs = await publicClient.getLogs({
  address: LENDING_POOL,
  event: supplyEventAbi,
  fromBlock: latestBlock - 50_000n,
})
const borrowers = [...new Set(logs.map(l => l.args.user))]

// 2. Check health factors in a single multicall
const hfResults = await publicClient.multicall({
  contracts: borrowers.map(b => ({
    address: LIQUIDATION_ENGINE,
    abi: liquidationEngineAbi,
    functionName: 'getHealthFactor',
    args: [b],
  })),
})

// 3. Liquidate any position with HF < 1e18
for (const [i, result] of hfResults.entries()) {
  if (result.status === 'success' && result.result < 10n ** 18n) {
    const borrower = borrowers[i]
    // fetch account data, compute debtToCover, call liquidate()
  }
}

Protecting Your Position

To avoid being liquidated:

  • Keep HF above 1.5 — this gives you a comfortable buffer against interest accrual and any oracle price movements.
  • Repay regularly — even small repayments reset the interest clock and improve your HF.
  • Supply more collateral — adding collateral directly increases your HF without requiring a repayment.
  • Monitor the Borrow APY — high utilisation means high borrow rates, which means your debt grows faster. Consider repaying during high-rate periods.