Liquidation Auctions
Liquidations in RevvFi are handled by RevvFiLiquidator, running a declining-price auction that bidders can still compete on — a hybrid of a Dutch auction (falling reference price) and an English auction (competing bids), rather than a pure first-bidder-wins model.
Auction Trigger
Any user can trigger liquidation once a position’s collateral ratio falls below the market’s liquidationThreshold, which creates an auction via RevvFiLiquidator.createAuction().
Auction Lifecycle
1. Initialization
When createAuction() is called:
- Collateral is transferred into the liquidator contract for the auction.
- Starting price is set to 100% of the outstanding debt.
- Reserve price is fixed at 80% of the outstanding debt — the floor the current price will never drop below.
- The auction runs for
auctionDuration(3 days by default).
2. Price Decay
The current price decays in discrete steps rather than continuously:
steps = (block.timestamp - startTime) / dutchAuctionStepDuration; // default: 1 hour
decrement = debtAmount * dutchAuctionPriceDecrementBps * steps / 10000; // default: 5% of debt per step
currentPrice = max(debtAmount - decrement, reservePrice);So by default the price steps down 5% of the original debt every hour until it reaches the 80% reserve floor (roughly 4 hours in), then holds there for the remainder of the 3-day window.
3. Bidding
Anyone can call placeBid(auctionId, bidAmount):
- The first bid on an auction must meet or exceed the current (declining) price.
- Every subsequent bid must exceed the previous highest bid by at least
minBidIncrementBps(1% by default) — it does not need to chase the declining reference price once a bid exists. - Each new highest bid immediately refunds the previous bidder their tokens.
- A bid placed within
auctionExtensionWindow(15 minutes by default) of the currentendTimepushesendTimeforward by that same window, preventing last-second sniping.
4. Settlement
Anyone can call settleAuction(auctionId) once block.timestamp > endTime:
- If there’s a highest bidder: their bid is transferred to the market (for lender distribution), and the collateral is transferred to them.
- If there were no bids at all: the liquidator automatically retries with a fresh auction rather than leaving collateral stuck.
- Any gap between the debt owed and the winning bid is recorded as bad debt via
Market.settleLiquidation(), which also feeds into the borrower’s reputation score.
Loss Distribution by Seniority
When a liquidation doesn’t fully cover the debt, losses are absorbed junior positions first:
- Junior positions (
seniority == 1) take losses before senior positions, in proportion to their share of the shortfall. - Senior positions (
seniority == 0) are only affected once junior positions have absorbed everything they can.
This is the seniority tier’s actual function in the protocol — it governs loss order during a shortfall, not the fill order during a normal borrow().
Worked Example
Position: 10,000 USDC debt, undercollateralized
- Auction created: starting price 10,000 USDC, reserve price 8,000 USDC, 3-day window.
- After 2 hours (2 steps @ 5%/step = 10% decayed): current price = 9,000 USDC.
- Bidder A bids 9,000 USDC — meets the current price, becomes highest bidder.
- 10 minutes before
endTime, Bidder B bids 9,200 USDC (> 9,000 × 1.01) — exceeds A’s bid by more than the minimum increment, becomes new highest bidder, A is refunded, and the auction’sendTimeis extended by 15 minutes. - No further bids arrive.
settleAuction()is called after the (extended)endTime: the market receives 9,200 USDC (recording an 800 USDC shortfall as bad debt), and Bidder B receives the collateral.