-
Notifications
You must be signed in to change notification settings - Fork 3
feat: Add Days to Liquidation column to borrowers table #406
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -44,10 +44,15 @@ export function BorrowersTable({ chainId, market, minShares, oraclePrice, onOpen | |||||||||||||||||||
| const hasActiveFilter = minShares !== '0'; | ||||||||||||||||||||
| const tableKey = `borrowers-table-${currentPage}`; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Calculate LTV for each borrower | ||||||||||||||||||||
| // Calculate LTV and Days to Liquidation for each borrower | ||||||||||||||||||||
| // LTV = borrowAssets / (collateral * oraclePrice) | ||||||||||||||||||||
| const borrowersWithLTV = useMemo(() => { | ||||||||||||||||||||
| if (!oraclePrice || oraclePrice === 0n) return []; | ||||||||||||||||||||
| // Days to Liquidation = ln(lltv/ltv) / ln(1 + borrowApy) * 365 | ||||||||||||||||||||
| // (using continuous compounding: r = ln(1 + APY) to convert annual APY to continuous rate) | ||||||||||||||||||||
| const borrowersWithMetrics = useMemo(() => { | ||||||||||||||||||||
| if (!oraclePrice) return []; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const lltv = Number(market.lltv) / 1e16; // lltv in WAD format (e.g., 8e17 = 80%) | ||||||||||||||||||||
| const borrowApy = market.state.borrowApy; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| return borrowers.map((borrower) => { | ||||||||||||||||||||
| const borrowAssets = BigInt(borrower.borrowAssets); | ||||||||||||||||||||
|
|
@@ -58,18 +63,29 @@ export function BorrowersTable({ chainId, market, minShares, oraclePrice, onOpen | |||||||||||||||||||
| const collateralValueInLoan = (collateral * oraclePrice) / BigInt(10 ** 36); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Calculate LTV as a percentage | ||||||||||||||||||||
| // LTV = (borrowAssets / collateralValue) * 100 | ||||||||||||||||||||
| let ltv = 0; | ||||||||||||||||||||
| if (collateralValueInLoan > 0n) { | ||||||||||||||||||||
| ltv = Number((borrowAssets * 10000n) / collateralValueInLoan) / 100; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Calculate Days to Liquidation | ||||||||||||||||||||
| // Only calculate if borrower has position, LTV > 0, and borrow rate > 0 | ||||||||||||||||||||
| let daysToLiquidation: number | null = null; | ||||||||||||||||||||
| if (ltv > 0 && borrowApy > 0 && lltv > ltv) { | ||||||||||||||||||||
| // Use continuous compounding: LTV(t) = LTV * e^(r * t) where r = ln(1 + APY) | ||||||||||||||||||||
| // Solve for t when LTV(t) = lltv: t = ln(lltv/ltv) / r | ||||||||||||||||||||
| const continuousRate = Math.log(1 + borrowApy); | ||||||||||||||||||||
| const yearsToLiquidation = Math.log(lltv / ltv) / continuousRate; | ||||||||||||||||||||
| daysToLiquidation = Math.max(0, Math.round(yearsToLiquidation * 365)); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| return { | ||||||||||||||||||||
| ...borrower, | ||||||||||||||||||||
| ltv, | ||||||||||||||||||||
| daysToLiquidation, | ||||||||||||||||||||
| }; | ||||||||||||||||||||
| }); | ||||||||||||||||||||
| }, [borrowers, oraclePrice]); | ||||||||||||||||||||
| }, [borrowers, oraclePrice, market.lltv, market.state.borrowApy]); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| return ( | ||||||||||||||||||||
| <div> | ||||||||||||||||||||
|
|
@@ -120,27 +136,46 @@ export function BorrowersTable({ chainId, market, minShares, oraclePrice, onOpen | |||||||||||||||||||
| <TableHead className="text-right">BORROWED</TableHead> | ||||||||||||||||||||
| <TableHead className="text-right">COLLATERAL</TableHead> | ||||||||||||||||||||
| <TableHead className="text-right">LTV</TableHead> | ||||||||||||||||||||
| <TableHead className="text-right"> | ||||||||||||||||||||
| <Tooltip | ||||||||||||||||||||
| content={ | ||||||||||||||||||||
| <TooltipContent | ||||||||||||||||||||
| title="Days to Liquidation" | ||||||||||||||||||||
| detail="Estimated days until position reaches liquidation threshold, based on current LTV and borrow rate" | ||||||||||||||||||||
| /> | ||||||||||||||||||||
| } | ||||||||||||||||||||
| > | ||||||||||||||||||||
| <span className="cursor-help border-b border-dashed border-secondary/50"> | ||||||||||||||||||||
| DAYS TO LIQ. | ||||||||||||||||||||
| </span> | ||||||||||||||||||||
| </Tooltip> | ||||||||||||||||||||
| </TableHead> | ||||||||||||||||||||
| <TableHead className="text-right">% OF BORROW</TableHead> | ||||||||||||||||||||
| {showDeveloperOptions && <TableHead className="text-right">ACTIONS</TableHead>} | ||||||||||||||||||||
| </TableRow> | ||||||||||||||||||||
| </TableHeader> | ||||||||||||||||||||
| <TableBody className="table-body-compact"> | ||||||||||||||||||||
| {borrowersWithLTV.length === 0 && !isLoading ? ( | ||||||||||||||||||||
| {borrowersWithMetrics.length === 0 && !isLoading ? ( | ||||||||||||||||||||
| <TableRow> | ||||||||||||||||||||
| <TableCell | ||||||||||||||||||||
| colSpan={showDeveloperOptions ? 6 : 5} | ||||||||||||||||||||
| colSpan={showDeveloperOptions ? 7 : 6} | ||||||||||||||||||||
| className="text-center text-gray-400" | ||||||||||||||||||||
| > | ||||||||||||||||||||
| No borrowers found for this market | ||||||||||||||||||||
| </TableCell> | ||||||||||||||||||||
| </TableRow> | ||||||||||||||||||||
| ) : ( | ||||||||||||||||||||
| borrowersWithLTV.map((borrower) => { | ||||||||||||||||||||
| borrowersWithMetrics.map((borrower) => { | ||||||||||||||||||||
| const totalBorrow = BigInt(market.state.borrowAssets); | ||||||||||||||||||||
| const borrowerAssets = BigInt(borrower.borrowAssets); | ||||||||||||||||||||
| const percentOfBorrow = totalBorrow > 0n ? (Number(borrowerAssets) / Number(totalBorrow)) * 100 : 0; | ||||||||||||||||||||
| const percentDisplay = percentOfBorrow < 0.01 && percentOfBorrow > 0 ? '<0.01%' : `${percentOfBorrow.toFixed(2)}%`; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Days to liquidation display | ||||||||||||||||||||
| const daysDisplay = borrower.daysToLiquidation !== null | ||||||||||||||||||||
| ? `${borrower.daysToLiquidation}` | ||||||||||||||||||||
| : '—'; | ||||||||||||||||||||
|
Comment on lines
+175
to
+177
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing The PR spec says to display 🐛 Proposed fix- const daysDisplay = borrower.daysToLiquidation !== null
- ? `${borrower.daysToLiquidation}`
- : '—';
+ const daysDisplay =
+ borrower.daysToLiquidation === null
+ ? '—'
+ : borrower.daysToLiquidation > 365
+ ? '>365'
+ : `${borrower.daysToLiquidation}`;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||
|
|
||||||||||||||||||||
| return ( | ||||||||||||||||||||
| <TableRow key={`borrower-${borrower.userAddress}`}> | ||||||||||||||||||||
| <TableCell> | ||||||||||||||||||||
|
|
@@ -180,6 +215,7 @@ export function BorrowersTable({ chainId, market, minShares, oraclePrice, onOpen | |||||||||||||||||||
| </div> | ||||||||||||||||||||
| </TableCell> | ||||||||||||||||||||
| <TableCell className="text-right text-sm">{borrower.ltv.toFixed(2)}%</TableCell> | ||||||||||||||||||||
| <TableCell className="text-right text-sm">{daysDisplay}</TableCell> | ||||||||||||||||||||
| <TableCell className="text-right text-sm">{percentDisplay}</TableCell> | ||||||||||||||||||||
| {showDeveloperOptions && ( | ||||||||||||||||||||
| <TableCell className="text-right"> | ||||||||||||||||||||
|
|
||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Already-at/past-threshold positions show
'—'instead of0.When
lltv <= ltv, the guard on line 74 prevents calculation and leavesdaysToLiquidation = null, so the cell renders'—'— the same glyph used for "no position at all". A row showing LTV=82% alongside'—'reads as safe rather than already-liquidatable.Consider returning
0for this case, which keeps the display logic and meaning consistent:🐛 Proposed fix
let daysToLiquidation: number | null = null; if (ltv > 0 && borrowApy > 0 && lltv > ltv) { const continuousRate = Math.log(1 + borrowApy); const yearsToLiquidation = Math.log(lltv / ltv) / continuousRate; daysToLiquidation = Math.max(0, Math.round(yearsToLiquidation * 365)); + } else if (ltv > 0 && lltv <= ltv) { + daysToLiquidation = 0; }Also applies to: 162-166
🤖 Prompt for AI Agents