Real liquidation price + off-chain liquidation keeper#168
Merged
Conversation
Two halves of the liquidation pipeline the perp DEX was missing.
Liquidation price: account_summary served a hardcoded "0" for every
position's liquidation price. Wire it to the shared margin math
(perplex-core::margin, the same code the on-chain engine mirrors) so the
position read path returns a real liq price. Cross-margin approximation:
each position is priced against the whole account's collateral — exact
for the common single-position case, conservative otherwise.
Keeper: nothing automatically closed underwater positions, so a losing
trader's position could sit below maintenance margin and leave the
protocol holding bad debt. Add:
- AppState::scan_liquidatable — every open position with health < 1.0.
- AppState::liquidate_position — force-close at mark, realise PnL into
the vault (bad debt clamps collateral to 0; the on-chain InsuranceFund
absorbs it once the trade flow is on-chain), remove the position, and
record the close on the public tape + the user's fills. Returns None
when the position is healthy, so a racing keeper can't close it.
- Admin routes GET /v1/admin/liquidatable and POST /v1/admin/liquidate,
gated by the x-admin-secret header against PERPLEX_ADMIN_SECRET
(routes 401 when the secret is unset, so a misconfigured deploy can't
expose force-close).
- crates/perplex-liquidator: the keeper binary. Polls the scan endpoint
and liquidates each target on an interval. When trading moves on-chain
it swaps its two HTTP calls for a PositionRegistry read +
LiquidationEngine.liquidate(); the health math already lives in
perplex-core and is shared by both sides.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Builds out point-4 liquidations in two halves.
Liquidation price.
account_summaryserved a hardcoded"0"for every position's liquidation price. It now computes the real value via the shared margin math (perplex-core::margin, the same code the on-chain engine mirrors), so the position read path / UI gets a true liq price. Cross-margin approximation: each position is priced against the whole account's collateral — exact for the common single-position case, conservative otherwise.Keeper. Nothing automatically closed underwater positions, so a losing trader could sit below maintenance margin and leave the protocol holding bad debt. Added:
AppState::scan_liquidatable— every open position with health factor < 1.0.AppState::liquidate_position— force-close at mark, realise PnL into the vault (bad debt clamps collateral to 0; the on-chainInsuranceFundabsorbs it once trading is on-chain), remove the position, record the close on the tape + the user's fills. ReturnsNonefor a healthy position so a racing keeper can't close it.GET /v1/admin/liquidatableandPOST /v1/admin/liquidate, gated byx-admin-secretagainstPERPLEX_ADMIN_SECRET(routes 401 when the secret is unset).crates/perplex-liquidator— the keeper binary; polls the scan endpoint and liquidates each target on an interval.Test
account_summaryfills a real, sub-entry liq price for a long). fmt + clippy clean across the workspace.perplex-liquidatorbinary — it found the position, force-closed it, the position vanished from memory and Postgres, the liquidation fill was recorded, and a healthy counterparty short was left untouched.Notes
PositionRegistryread +LiquidationEngine.liquidate(); the health math is already shared viaperplex-core.InsuranceFund— that hook lands with on-chain settlement.