Pre-liquidation vs liquidation
Verified against: F-013 (2026-11-09), F-015 (2026-11-09), F-021 (2026-11-09) · spec docs/PRD.md §4.4, §6.5 · docs/work/interface-p0.md · D-004, D-010 (2026-11-10)
Status: specification. Deployed on NEAR testnet 2026-11-09 (G2 PASS 2026-11-09T20:55Z, commit 5f27f44). P0 settles at max_discount_bps (fallback, disclosed). TEE pricing is P1.
A classic liquidation sells collateral into whatever liquidity exists at that moment. Nectar's pre-liquidation sells to a buyer who committed in advance, at a discount the buyer agreed to before the stress started.
Trigger
Each position stores its own pre_liq_ltv_bps, chosen at open within the owner's [pre_liq_min_bps, pre_liq_max_bps] (6500–8000 in config.yaml). A position is triggerable when:
debt_usd_e6 · 10_000 > V_usd_e6 · pre_liq_ltv_bps and the oracle is fresh
The keeper calls trigger_preliquidation(position_id) with a function-call key that can call only that method and relay_settlement_result (D-010, F-015). It attaches no deposit and passes nothing but the id.
Waterfall (PRD §4.4)
- Flag. The condition above holds.
- Route check. PROD compares DEX depth with the best quote. This build records the comparison and takes the Pappus route.
- Pappus. Quotes for the asset are consumed in ascending
max_discount_bps, then oldest first, at most 8.- P0: each quote fills at its own
max_discount_bps(fallback pricing, D-004). - P1: OutLayer returns
clearing_discount_bps. The contract clamps it to[0, max_discount_bps], so a faulty quoter can only cost the buyer, never the borrower.
- P0: each quote fills at its own
- Settle. The debt is repaid from the buyer's escrow and the collateral moves to the buyer:
ft_transferon NEAR, or a signed settlement message on Solana (P2). The borrower keeps the rest. The contract emitssettled_near. - Reserve fills last, at its own discount, only after every quote with capacity (v2; deployed on testnet, EVIDENCE §G2b; no settlement has reached the Reserve on testnet yet, the waterfall is tested in the sandbox). See Nectar Reserve.
Seize amount
seize_units = repay_usd_e6 · (10_000 + keeper_fee_bps) · 10^decimals / (price_usd_e6 · (10_000 − disc_bps))
keeper_fee_bps is 50 in config.yaml. Whether the P0 contract charges it is fixed by WO-2 and confirmed at G1.
Why it is safer
| DEX liquidation | Nectar pre-liquidation | |
|---|---|---|
| Price | whatever the pool gives under stress | at most max_discount_bps, agreed in advance |
| Weekend | pool may be empty | escrow is in the contract |
| Keeper power | often picks route and size | passes an id only; the contract computes everything |