feat: select liquidable loans
This commit is contained in:
parent
ccc78fc6ae
commit
db67a387d6
|
@ -27,11 +27,14 @@ export const LABELS = {
|
||||||
BORROW_QUESTION: "How much would you like to borrow?",
|
BORROW_QUESTION: "How much would you like to borrow?",
|
||||||
BORROW_ACTION: "Borrow",
|
BORROW_ACTION: "Borrow",
|
||||||
LIQUIDATE_ACTION: "Liquidate",
|
LIQUIDATE_ACTION: "Liquidate",
|
||||||
|
LIQUIDATE_INFO: "Connect to a wallet to view liquidable loans.",
|
||||||
|
LIQUIDATE_NO_LOANS: "There are no loans to liquidate with your wallet.",
|
||||||
TABLE_TITLE_ASSET: "Asset",
|
TABLE_TITLE_ASSET: "Asset",
|
||||||
TABLE_TITLE_YOUR_LOAN_BALANCE: "Your loan balance",
|
TABLE_TITLE_YOUR_LOAN_BALANCE: "Your loan balance",
|
||||||
TABLE_TITLE_LOAN_BALANCE: "Loan balance",
|
TABLE_TITLE_LOAN_BALANCE: "Loan balance",
|
||||||
TABLE_TITLE_DEPOSIT_BALANCE: "Your deposit balance",
|
TABLE_TITLE_DEPOSIT_BALANCE: "Your deposit balance",
|
||||||
TABLE_TITLE_APY: "APY",
|
TABLE_TITLE_APY: "APY",
|
||||||
|
TABLE_TITLE_LTV: "LTV",
|
||||||
TABLE_TITLE_BORROW_APY: "Borrow APY",
|
TABLE_TITLE_BORROW_APY: "Borrow APY",
|
||||||
TABLE_TITLE_DEPOSIT_APY: "Deposit APY",
|
TABLE_TITLE_DEPOSIT_APY: "Deposit APY",
|
||||||
TABLE_TITLE_TOTAL_BORROWED: "Total Borrowed",
|
TABLE_TITLE_TOTAL_BORROWED: "Total Borrowed",
|
||||||
|
|
|
@ -10,3 +10,4 @@ export * from "./useUserObligationByReserve";
|
||||||
export * from "./useBorrowedAmount";
|
export * from "./useBorrowedAmount";
|
||||||
export * from "./useUserDeposits";
|
export * from "./useUserDeposits";
|
||||||
export * from "./useSliderInput";
|
export * from "./useSliderInput";
|
||||||
|
export * from "./useLiquidableObligations";
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import { useUserAccounts } from "./useUserAccounts";
|
||||||
|
import { useLendingObligations } from "./useLendingObligations";
|
||||||
|
import { LendingReserve } from "../models/lending";
|
||||||
|
import { useLendingReserves } from "./useLendingReserves";
|
||||||
|
import { ParsedAccount } from "../contexts/accounts";
|
||||||
|
|
||||||
|
export const useLiquidableObligations = () => {
|
||||||
|
const { userAccounts } = useUserAccounts();
|
||||||
|
const { obligations } = useLendingObligations();
|
||||||
|
const { reserveAccounts } = useLendingReserves();
|
||||||
|
|
||||||
|
const availableReserves = useMemo(() => {
|
||||||
|
return reserveAccounts.reduce((map, reserve) => {
|
||||||
|
if (userAccounts.some(acc => acc.info.mint.toBase58() === reserve.info.liquidityMint.toBase58())) {
|
||||||
|
map.set(reserve.pubkey.toBase58(), reserve);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}, new Map<string, ParsedAccount<LendingReserve>>())
|
||||||
|
}, [reserveAccounts, userAccounts])
|
||||||
|
|
||||||
|
const liquidableObligations = useMemo(() => {
|
||||||
|
if (availableReserves.size === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return obligations
|
||||||
|
.map(obligation => (
|
||||||
|
{
|
||||||
|
obligation,
|
||||||
|
reserve: availableReserves.get(obligation.info.borrowReserve.toBase58()) as ParsedAccount<LendingReserve>
|
||||||
|
}
|
||||||
|
))
|
||||||
|
.filter(item => item.reserve)
|
||||||
|
.map(item => {
|
||||||
|
// TODO: calculate LTV
|
||||||
|
const ltv = 81;
|
||||||
|
const liquidationThreshold = item.reserve.info.config.liquidationThreshold;
|
||||||
|
const health = (ltv - liquidationThreshold) / liquidationThreshold
|
||||||
|
return {
|
||||||
|
obligation: item.obligation,
|
||||||
|
ltv,
|
||||||
|
liquidationThreshold,
|
||||||
|
health
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(item => item.ltv > item.liquidationThreshold)
|
||||||
|
.sort((a, b) => b.health - a.health);
|
||||||
|
}, [obligations, availableReserves]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
liquidableObligations
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,44 +1,36 @@
|
||||||
import { PublicKey } from "@solana/web3.js";
|
|
||||||
import BN from "bn.js";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { LABELS, ZERO } from "../../constants";
|
import { LABELS } from "../../constants";
|
||||||
import { LiquidateItem } from "./item";
|
import { LiquidateItem } from "./item";
|
||||||
import "./itemStyle.less";
|
import { useLiquidableObligations } from "./../../hooks";
|
||||||
|
import { useWallet } from "../../contexts/wallet";
|
||||||
|
import "./style.less";
|
||||||
|
|
||||||
export const LiquidateView = () => {
|
export const LiquidateView = () => {
|
||||||
|
const { liquidableObligations } = useLiquidableObligations();
|
||||||
|
const { connected } = useWallet();
|
||||||
|
|
||||||
// ParsedAccount<LendingObligation>
|
|
||||||
const obligations = [
|
|
||||||
{
|
|
||||||
pubkey: new PublicKey("2KfJP7pZ6QSpXa26RmsN6kKVQteDEdQmizLSvuyryeiW"),
|
|
||||||
account: {
|
|
||||||
executable: false,
|
|
||||||
owner: new PublicKey("2KfJP7pZ6QSpXa26RmsN6kKVQteDEdQmizLSvuyryeiW"),
|
|
||||||
lamports: 0,
|
|
||||||
data: new Buffer("x"),
|
|
||||||
},
|
|
||||||
info: {
|
|
||||||
lastUpdateSlot: ZERO,
|
|
||||||
collateralAmount: ZERO,
|
|
||||||
collateralSupply: new PublicKey("2KfJP7pZ6QSpXa26RmsN6kKVQteDEdQmizLSvuyryeiW"),
|
|
||||||
cumulativeBorrowRateWad: ZERO,
|
|
||||||
borrowAmountWad: new BN(0),
|
|
||||||
borrowReserve: new PublicKey("EwhnKnkwcAeVxHDbR5wMpjwipHFuafxTUhQaaagjUxQG"),
|
|
||||||
tokenMint: new PublicKey("2KfJP7pZ6QSpXa26RmsN6kKVQteDEdQmizLSvuyryeiW"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
return (
|
return (
|
||||||
<div className="flexColumn">
|
<div className="liquidate-container">
|
||||||
<div className="liquidate-item liquidate-header">
|
{!connected && (
|
||||||
<div>{LABELS.TABLE_TITLE_ASSET}</div>
|
<div className="liquidate-info">{LABELS.LIQUIDATE_INFO}</div>
|
||||||
<div>{LABELS.TABLE_TITLE_LOAN_BALANCE}</div>
|
)}
|
||||||
<div>{LABELS.TABLE_TITLE_APY}</div>
|
{connected && liquidableObligations.length === 0 && (
|
||||||
<div>{LABELS.TABLE_TITLE_ACTION}</div>
|
<div className="liquidate-info">{LABELS.LIQUIDATE_NO_LOANS}</div>
|
||||||
</div>
|
)}
|
||||||
{obligations.map((obligation) => (
|
{connected && liquidableObligations.length > 0 && (
|
||||||
<LiquidateItem key={obligation.pubkey.toBase58()} obligation={obligation}></LiquidateItem>
|
<div className="flexColumn">
|
||||||
))}
|
<div className="liquidate-item liquidate-header">
|
||||||
|
<div>{LABELS.TABLE_TITLE_ASSET}</div>
|
||||||
|
<div>{LABELS.TABLE_TITLE_LOAN_BALANCE}</div>
|
||||||
|
<div>{LABELS.TABLE_TITLE_APY}</div>
|
||||||
|
<div>{LABELS.TABLE_TITLE_LTV}</div>
|
||||||
|
<div>{LABELS.TABLE_TITLE_ACTION}</div>
|
||||||
|
</div>
|
||||||
|
{liquidableObligations.map((item) => (
|
||||||
|
<LiquidateItem key={item.obligation.pubkey.toBase58()} obligation={item.obligation} ltv={item.ltv}></LiquidateItem>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import { cache, ParsedAccount, useMint } from "../../contexts/accounts";
|
import { cache, ParsedAccount, useMint } from "../../contexts/accounts";
|
||||||
import { LendingObligation, LendingReserve, calculateBorrowAPY } from "../../models/lending";
|
import { LendingObligation, LendingReserve, calculateBorrowAPY } from "../../models/lending";
|
||||||
import { useTokenName } from "../../hooks";
|
import { useTokenName } from "../../hooks";
|
||||||
|
@ -15,16 +15,15 @@ import { LABELS } from "../../constants";
|
||||||
|
|
||||||
export const LiquidateItem = (props: {
|
export const LiquidateItem = (props: {
|
||||||
obligation: ParsedAccount<LendingObligation>;
|
obligation: ParsedAccount<LendingObligation>;
|
||||||
|
ltv: number
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
const { obligation } = props;
|
const { obligation, ltv } = props;
|
||||||
|
|
||||||
const borrowReserve = cache.get(obligation.info.borrowReserve) as ParsedAccount<LendingReserve>;
|
const borrowReserve = cache.get(obligation.info.borrowReserve) as ParsedAccount<LendingReserve>;
|
||||||
const tokenName = useTokenName(borrowReserve?.info.liquidityMint);
|
const tokenName = useTokenName(borrowReserve?.info.liquidityMint);
|
||||||
const liquidityMint = useMint(borrowReserve.info.liquidityMint);
|
const liquidityMint = useMint(borrowReserve.info.liquidityMint);
|
||||||
|
|
||||||
console.log("wad",obligation.info.borrowAmountWad)
|
|
||||||
|
|
||||||
const borrowAmount = fromLamports(
|
const borrowAmount = fromLamports(
|
||||||
wadToLamports(obligation.info.borrowAmountWad),
|
wadToLamports(obligation.info.borrowAmountWad),
|
||||||
liquidityMint
|
liquidityMint
|
||||||
|
@ -35,7 +34,7 @@ export const LiquidateItem = (props: {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={`/liquidate/${obligation.pubkey.toBase58()}`}>
|
<Link to={`/liquidate/${borrowReserve.pubkey.toBase58()}`}>
|
||||||
<Card>
|
<Card>
|
||||||
<div className="liquidate-item">
|
<div className="liquidate-item">
|
||||||
<span style={{ display: "flex" }}>
|
<span style={{ display: "flex" }}>
|
||||||
|
@ -48,6 +47,9 @@ export const LiquidateItem = (props: {
|
||||||
<div>
|
<div>
|
||||||
{formatPct.format(borrowAPY)}
|
{formatPct.format(borrowAPY)}
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
{formatPct.format(ltv / 100)}
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Button>
|
<Button>
|
||||||
<span>{LABELS.LIQUIDATE_ACTION}</span>
|
<span>{LABELS.LIQUIDATE_ACTION}</span>
|
||||||
|
|
|
@ -26,4 +26,18 @@
|
||||||
text-align: left;
|
text-align: left;
|
||||||
flex: 80px
|
flex: 80px
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.liquidate-info {
|
||||||
|
display: flex;
|
||||||
|
align-self: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.liquidate-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
Loading…
Reference in New Issue