Skip to main content
Liquidation on Kimia is permissionless, any keeper can call it against any underwater account. The protocol pays the liquidator directly and sweeps the rest into the insurance fund.

Trigger condition

health=free_collateral+unrealized_pnlmaint_req\text{health} = \text{free\_collateral} + \text{unrealized\_pnl} - \text{maint\_req} An account is liquidatable whenever health0\text{health} \leq 0. Keepers compute health off-chain (using the same formulas as perps-math) and submit a liquidate tx when it flips negative.

What happens in liquidate

1

Oracle validation

Fresh Pyth Hermes update; confidence and staleness must pass.
2

Health recheck on-chain

Reverts NotLiquidatable if the account is healthy.
3

Cancel resting orders

All the victim’s orders are removed; their reserved margin is forfeited to the insurance vault, not refunded to the victim. This is part of the penalty for being liquidated with open orders.
4

Close the full position

The position is closed entirely at oracle price (no slippage). Realized PnL is computed and applied to the victim’s collateral.
5

Charge the liquidation fee

fee = closed_notional × 500 / 10_000 (5% default).
6

Split the fee

  • 50% transferred from collateral_vault to the liquidator’s token account.
  • 50% transferred to the insurance_vault.
7

Settle bad debt

If the victim’s collateral went negative (oracle moved so fast the fee couldn’t be absorbed), the insurance fund covers the shortfall. If the insurance fund is also insufficient, emit BadDebtDetected and pause the market.

Fee recipient

Liquidators receive the reward in the collateral mint (USDC), transferred to a token account they pass in as liquidator_token_account. Spec:
getLiquidateInstruction({
  liquidator: wallet,
  liquidatorTokenAccount: myUsdcAta,
  userAccount: victim,
  market,
  oracle: priceUpdate,
  collateralVault,
  insuranceVault,
  orderbook,
});

Why is the position closed fully?

V1 uses whole-position liquidation rather than partial unwinds. Rationale:
  • Simpler on-chain state, no “partially-liquidated” zombie accounts.
  • Faster convergence back to solvency.
  • Clearer keeper economics, the fee is computed on the full notional.
V2 may add partial liquidation for larger positions to reduce market impact.

Insurance fund

The insurance fund is an SPL token account owned by the market PDA. It fills from:
  • 50% of every liquidation fee
  • Forfeited margin of liquidated users’ resting orders
  • Admin-seeded funds (one-time boot)
It empties only when:
  • Bad debt drains it during a liquidation
  • V2 governance votes to redirect some funds (not implemented in V1)
If the insurance fund’s balance drops to zero and a liquidation would produce further bad debt, the market emits BadDebtDetected and pauses. Admin intervention is required to re-seed and resume.

Keeper economics

  • Reward: 2.5% of closed notional (half the liquidation fee)
  • Cost: Solana tx fee + RPC latency race
  • Risk: Getting out-raced to the fill by another keeper
For V1 devnet there’s no MEV market around Kimia liquidations yet, so any single-threaded bot will win races.

Why liquidate always reads the oracle

Mark price can be any last-trade price within ±10% of oracle. That’s fine for routine trades but too loose for liquidations, a single manipulated fill could push a healthy account into “liquidatable” territory. Liquidations are closed at oracle, bypassing mark entirely. The oracle’s confidence cap (2.5%) and staleness (60 s) are the ultimate trust bounds.