Pre-Liquidation
Pre-liquidation is the mechanism that allows anyone to liquidate a Grunt borrow position that has become unhealthy at its liquidation LTV, even though it remains healthy at Morpho Blue’s native market LLTV. This page covers the math, the function interface, and the callback pattern for flash-liquidation integrations.
Position Health
Section titled “Position Health”A position’s health is determined by comparing its maximum borrowing capacity to its outstanding debt:
collateralValue = collateral * oraclePrice / ORACLE_PRICE_SCALEmaxBorrow = collateralValue * lltvhealthy = maxBorrow >= totalBorrowedWhen isHealthy(liquidationLtv()) returns false but isHealthy(marketLltv) would still return true, the position is in the pre-liquidation zone. This is the window where the proportional path of preLiquidate applies.
preLiquidate also serves an earlier band: when the position’s LTV is between safeLtv() and liquidationLtv(), the same call consumes standing offers instead of liquidating proportionally. See Offer Pre-Liquidation. This page describes the proportional path only.
Liquidation Bonus
Section titled “Liquidation Bonus”Pre-liquidation uses proportional liquidation. When you repay a fraction of the position’s debt, you receive the same fraction of the position’s collateral. The bonus comes from the fact that the collateral is worth more than the debt you repaid.
Formula:
Liquidation Bonus = 1 - LTV (at liquidation time)Worked example:
Consider a position at 80% LTV:
- Collateral: 100 tokens worth $100
- Debt: $80
- LTV: 80%
A liquidator repays 50% of the debt ($40) and seizes 50% of the collateral (50 tokens worth $50).
| Before | After | |
|---|---|---|
| Collateral | 100 ($100) | 50 ($50) |
| Debt | $80 | $40 |
| LTV | 80% | 80% |
| Liquidator spent | — | $40 |
| Liquidator received | — | $50 |
| Liquidator profit | — | $10 (25% on capital) |
The effective bonus rate on the collateral is 1 - 0.80 = 20%. The return on capital deployed is $10 / $40 = 25%.
Key properties:
- Liquidators receive a proportional share of collateral relative to the debt they repay
- The bonus scales with how underwater the position is — a position at 90% LTV yields a 10% bonus, while one at 70% LTV yields a 30% bonus
- There is no cap on seized collateral beyond the position’s total collateral
- The position’s LTV remains unchanged after a proportional liquidation
Deeply Underwater Positions
Section titled “Deeply Underwater Positions”Pure proportionality breaks once the position’s LTV exceeds the Morpho market’s own LLTV. Removing the same fraction of debt and collateral preserves the LTV, so the post-withdrawal state would fail Morpho’s internal health check and the whole call would revert.
The position adjusts for this, in whichever direction the liquidator pinned:
| Mode | Adjustment |
|---|---|
seizedAssets |
The contract computes the minimum debt repayment that leaves the position healthy on Morpho after the withdrawal. At least proportional, potentially more |
repaidShares |
The contract computes the maximum collateral seizable while leaving the position healthy on Morpho after the withdrawal. At most proportional, potentially less |
So for a small partial fill on a deeply underwater position, more debt can be repaid than the seized collateral is worth. The priority in that state is restoring health on the underlying market, not the liquidator’s margin. Above the market LLTV, check the returned amounts rather than assuming the 1 - LTV bonus.
preLiquidate Function
Section titled “preLiquidate Function”function preLiquidate( address borrower, uint256 seizedAssets, // Collateral to seize (0 to calculate from repaidShares) uint256 repaidShares, // Debt shares to repay (0 to calculate from seizedAssets) bytes calldata data // Callback data (empty for no callback)) external returns (uint256 seizedAssets, uint256 repaidAssets);The borrower parameter is the address of the MorphoBorrowPosition contract itself (the position that holds collateral on Morpho Blue).
Input Options
Section titled “Input Options”You must specify exactly one of seizedAssets or repaidShares:
seizedAssets |
repaidShares |
Behavior |
|---|---|---|
> 0 |
0 |
Specify the collateral amount to seize. Debt repayment is calculated proportionally. |
0 |
> 0 |
Specify the debt shares to repay. Collateral seizure is calculated proportionally. |
> 0 |
> 0 |
Reverts. |
0 |
0 |
Reverts. |
Return Values
Section titled “Return Values”seizedAssets— the actual amount of collateral seizedrepaidAssets— the actual amount of debt assets repaid (not shares)
Callback Interface
Section titled “Callback Interface”If the data parameter is non-empty, the contract invokes a callback on msg.sender after transferring collateral but before pulling debt repayment:
interface IPreLiquidationCallback { function onPreLiquidate(uint256 repaidAssets, bytes calldata data) external;}Execution Order
Section titled “Execution Order”- Interest is accrued, then position health is checked — the proportional path requires the LTV to be above the liquidation LTV
- Collateral is transferred to the liquidator (
msg.sender) - Callback is invoked —
onPreLiquidate(repaidAssets, data)is called onmsg.sender - Debt repayment is pulled from the liquidator
This ordering enables flash-liquidation patterns: you receive the collateral first, use the callback to sell it for the debt asset (e.g., via a DEX swap), then the contract pulls the debt repayment from your balance. No upfront capital is required.
Example: Flash Liquidation Contract
Section titled “Example: Flash Liquidation Contract”A minimal liquidation bot contract would implement the callback like this:
contract FlashLiquidator is IPreLiquidationCallback { function liquidate( address position, uint256 seizedAssets ) external { IMorphoBorrowPosition(position).preLiquidate( position, // borrower seizedAssets, // collateral to seize 0, // calculate debt from collateral abi.encode(position) // trigger callback ); }
function onPreLiquidate( uint256 repaidAssets, bytes calldata data ) external { address position = abi.decode(data, (address));
// 1. Collateral is already in this contract // 2. Swap collateral → debt asset via DEX // 3. Approve debt asset for the calling position // ... swap and approve logic here ... }}Monitoring Positions
Section titled “Monitoring Positions”To find pre-liquidatable positions, monitor all deployed MorphoBorrowPosition contracts and check their health:
| View Function | Use |
|---|---|
totalBorrowed() |
Current debt including accrued interest |
totalCollateral() |
Current collateral in the position |
totalCollateralQuoted() |
Collateral value in debt asset terms |
isHealthy(ltv) |
Whether the position is at or below a given LTV threshold |
maxBorrow(ltv) |
Maximum borrowable at a given LTV threshold |
safeLtv() / liquidationLtv() |
The position’s two band thresholds |
A position is proportionally pre-liquidatable when isHealthy(liquidationLtv()) returns false, and is in the offer band when that returns true while isHealthy(safeLtv()) returns false. Track BorrowPositionCreated events from the MorphoBorrowPositionFactory to discover positions, then read safeLtv() and liquidationLtv() on each.