From befb9b9851ab3da926500436d2648998a42fc3fd Mon Sep 17 00:00:00 2001 From: bartosz-lipinski <264380+bartosz-lipinski@users.noreply.github.com> Date: Tue, 22 Dec 2020 00:00:00 -0600 Subject: [PATCH] feat: add LTV, and health factor calculations --- src/App.less | 8 ++- src/components/BarChartStatistic/index.tsx | 4 +- src/components/UserLendingCard/index.tsx | 27 +++++--- src/hooks/useBorrowedAmount.ts | 75 ++++++++++++++++------ src/hooks/useCollateralBalance.ts | 38 +++++++++-- src/hooks/useEnrichedLendingObligations.ts | 2 + src/hooks/useUserBalance.ts | 27 +++++++- src/hooks/useUserDeposits.ts | 17 +++-- src/hooks/useUserObligationByReserve.ts | 7 +- src/views/dashboard/style.less | 6 -- src/views/liquidate/style.less | 8 +-- 11 files changed, 156 insertions(+), 63 deletions(-) diff --git a/src/App.less b/src/App.less index da8bb1e..8aab355 100644 --- a/src/App.less +++ b/src/App.less @@ -130,6 +130,12 @@ body { height: 27px; } +em { + font-weight: bold; + font-style: normal; + text-decoration: none; +} + .telegram:hover { color: #2789de !important; } @@ -247,7 +253,6 @@ body { margin: 0px; min-width: 0px; font-size: 14px; - font-weight: 500; } .left { @@ -301,6 +306,7 @@ body { .dashboard-amount-quote { font-size: 10px; font-style: normal; + text-align: right; } @media only screen and (max-width: 600px) { diff --git a/src/components/BarChartStatistic/index.tsx b/src/components/BarChartStatistic/index.tsx index b0b683b..3b3e336 100644 --- a/src/components/BarChartStatistic/index.tsx +++ b/src/components/BarChartStatistic/index.tsx @@ -23,9 +23,9 @@ export const BarChartStatistic = (props: { -
+ lineHeight: '37px', }}> {props.items.map((item, i) =>
- {formatNumber.format(totalBorrowed)} {name} +
+
{formatNumber.format(totalBorrowed)} {name}
+
${formatNumber.format(borrowedInUSD)}
+
@@ -66,7 +67,7 @@ export const UserLendingCard = (props: { Health factor: -
{healthFactor}
+
{health.toFixed(2)}
@@ -92,7 +93,10 @@ export const UserLendingCard = (props: { Wallet balance:
- {formatNumber.format(tokenBalance)} {name} +
+
{formatNumber.format(tokenBalance)} {name}
+
${formatNumber.format(tokenBalanceInUSD)}
+
@@ -101,7 +105,10 @@ export const UserLendingCard = (props: { You already deposited:
- {formatNumber.format(collateralBalance)} {name} +
+
{formatNumber.format(collateralBalance)} {name}
+
${formatNumber.format(collateralBalanceInUSD)}
+
diff --git a/src/hooks/useBorrowedAmount.ts b/src/hooks/useBorrowedAmount.ts index 9a1a11c..fc1c6a0 100644 --- a/src/hooks/useBorrowedAmount.ts +++ b/src/hooks/useBorrowedAmount.ts @@ -16,12 +16,24 @@ import { useLendingReserve } from "./useLendingReserves"; export function useBorrowedAmount(address?: string | PublicKey) { const connection = useConnection(); const { userObligationsByReserve } = useUserObligationByReserve(address); - const [borrowedLamports, setBorrowedLamports] = useState(0); + const [borrowedInfo, setBorrowedInfo] = useState({ + borrowedLamports: 0, + borrowedInUSD: 0, + colateralInUSD: 0, + ltv: 0, + health: 0, + }); const reserve = useLendingReserve(address); const liquidityMint = useMint(reserve?.info.liquidityMint); useEffect(() => { - setBorrowedLamports(0); + setBorrowedInfo({ + borrowedLamports: 0, + borrowedInUSD: 0, + colateralInUSD: 0, + ltv: 0, + health: 0, + }); (async () => { // precache obligation mints @@ -38,29 +50,50 @@ export function useBorrowedAmount(address?: string | PublicKey) { cache.add(new PublicKey(address), item, MintParser); }); - setBorrowedLamports( - userObligationsByReserve.reduce((result, item) => { - const borrowed = wadToLamports( - item.obligation.info.borrowAmountWad - ).toNumber(); + const result = { + borrowedLamports: 0, + borrowedInUSD: 0, + colateralInUSD: 0, + ltv: 0, + health: 0, + }; - const owned = item.userAccounts.reduce( - (amount, acc) => (amount += acc.info.amount.toNumber()), - 0 - ); - const obligationMint = cache.get( - item.obligation.info.tokenMint - ) as ParsedAccount; + let liquidationThreshold = 0; - result += (borrowed * owned) / obligationMint?.info.supply.toNumber(); - return result; - }, 0) - ); + userObligationsByReserve.forEach((item) => { + const borrowed = wadToLamports( + item.obligation.info.borrowAmountWad + ).toNumber(); + + const owned = item.userAccounts.reduce( + (amount, acc) => (amount += acc.info.amount.toNumber()), + 0 + ); + const obligationMint = cache.get( + item.obligation.info.tokenMint + ) as ParsedAccount; + + result.borrowedLamports += borrowed * (owned / obligationMint?.info.supply.toNumber()); + result.borrowedInUSD += item.obligation.info.borrowedInQuote; + result.colateralInUSD += item.obligation.info.collateralInQuote; + liquidationThreshold = item.obligation.info.liquidationThreshold; + }, 0); + + if (userObligationsByReserve.length === 1) { + result.ltv = userObligationsByReserve[0].obligation.info.ltv; + result.health = userObligationsByReserve[0].obligation.info.health; + } else { + result.ltv = 100 * result.borrowedInUSD / result.colateralInUSD; + result.health = result.colateralInUSD * liquidationThreshold / 100 / result.borrowedInUSD; + } + + + setBorrowedInfo(result); })(); - }, [connection, userObligationsByReserve]); + }, [connection, userObligationsByReserve, setBorrowedInfo]); return { - borrowed: fromLamports(borrowedLamports, liquidityMint), - borrowedLamports, + borrowed: fromLamports(borrowedInfo.borrowedLamports, liquidityMint), + ...borrowedInfo, }; } diff --git a/src/hooks/useCollateralBalance.ts b/src/hooks/useCollateralBalance.ts index 44ae9da..74c4e9f 100644 --- a/src/hooks/useCollateralBalance.ts +++ b/src/hooks/useCollateralBalance.ts @@ -1,5 +1,7 @@ import { PublicKey } from "@solana/web3.js"; +import { useEffect, useMemo, useState } from "react"; import { useMint } from "../contexts/accounts"; +import { useMarkets } from "../contexts/market"; import { LendingReserve, reserveMarketCap } from "../models/lending"; import { fromLamports } from "../utils/utils"; import { useUserBalance } from "./useUserBalance"; @@ -9,17 +11,43 @@ export function useUserCollateralBalance( account?: PublicKey ) { const mint = useMint(reserve?.collateralMint); - const { balanceLamports, accounts } = useUserBalance( + const { balanceLamports: userBalance, accounts } = useUserBalance( reserve?.collateralMint, account ); - const collateralBalance = reserve && - calculateCollateralBalance(reserve, balanceLamports); + const [balanceInUSD, setBalanceInUSD] = useState(0); + const { marketEmitter, midPriceInUSD } = useMarkets(); + + const balanceLamports = useMemo(() => reserve && + calculateCollateralBalance(reserve, userBalance), + [userBalance, reserve]); + + const balance = useMemo(() => fromLamports(balanceLamports, mint), + [balanceLamports, mint]); + + useEffect(() => { + const updateBalance = () => { + setBalanceInUSD(balance * midPriceInUSD(reserve?.liquidityMint?.toBase58() || '')); + } + + const dispose = marketEmitter.onMarket((args) => { + if(args.ids.has(reserve?.dexMarket.toBase58() || '')) { + updateBalance(); + } + }); + + updateBalance(); + + return () => { + dispose(); + }; + }, [balance, midPriceInUSD, marketEmitter, mint, setBalanceInUSD, reserve]); return { - balance: fromLamports(collateralBalance, mint), - balanceLamports: collateralBalance, + balance, + balanceLamports, + balanceInUSD, mint: reserve?.collateralMint, accounts, }; diff --git a/src/hooks/useEnrichedLendingObligations.ts b/src/hooks/useEnrichedLendingObligations.ts index cddf53f..a5f61d6 100644 --- a/src/hooks/useEnrichedLendingObligations.ts +++ b/src/hooks/useEnrichedLendingObligations.ts @@ -18,6 +18,7 @@ interface EnrichedLendingObligationInfo extends LendingObligation { health: number; borrowedInQuote: number; collateralInQuote: number; + liquidationThreshold: number; name: string; } @@ -116,6 +117,7 @@ export function useEnrichedLendingObligations() { health, borrowedInQuote, collateralInQuote, + liquidationThreshold: item.reserve.info.config.liquidationThreshold, name: getTokenName(tokenMap, reserve.liquidityMint) }, } as EnrichedLendingObligation; diff --git a/src/hooks/useUserBalance.ts b/src/hooks/useUserBalance.ts index 1871ad3..3a349b8 100644 --- a/src/hooks/useUserBalance.ts +++ b/src/hooks/useUserBalance.ts @@ -1,11 +1,15 @@ import { PublicKey } from "@solana/web3.js"; -import { useMemo } from "react"; +import { useEffect, useMemo, useState } from "react"; import { useMint } from "../contexts/accounts"; +import { useMarkets } from "../contexts/market"; import { fromLamports } from "../utils/utils"; import { useUserAccounts } from "./useUserAccounts"; export function useUserBalance(mint?: PublicKey, account?: PublicKey) { const { userAccounts } = useUserAccounts(); + const [balanceInUSD, setBalanceInUSD] = useState(0); + const { marketEmitter, midPriceInUSD } = useMarkets(); + const mintInfo = useMint(mint); const accounts = useMemo(() => { return userAccounts @@ -24,9 +28,28 @@ export function useUserBalance(mint?: PublicKey, account?: PublicKey) { ); }, [accounts]); + const balance = useMemo(() => fromLamports(balanceLamports, mintInfo), [mintInfo, balanceLamports]); + + useEffect(() => { + const updateBalance = () => { + setBalanceInUSD(balance * midPriceInUSD(mint?.toBase58() || '')); + } + + const dispose = marketEmitter.onMarket((args) => { + updateBalance(); + }); + + updateBalance(); + + return () => { + dispose(); + }; + }, [balance, midPriceInUSD, marketEmitter, mint, setBalanceInUSD]); + return { - balance: fromLamports(balanceLamports, mintInfo), + balance, balanceLamports, + balanceInUSD, accounts, }; } diff --git a/src/hooks/useUserDeposits.ts b/src/hooks/useUserDeposits.ts index 7154d79..f3a86f3 100644 --- a/src/hooks/useUserDeposits.ts +++ b/src/hooks/useUserDeposits.ts @@ -22,7 +22,7 @@ export interface UserDeposit { reserve: ParsedAccount; } -export function useUserDeposits() { +export function useUserDeposits(reserveAddress?: string) { const { userAccounts } = useUserAccounts(); const { reserveAccounts } = useLendingReserves(); const [userDeposits, setUserDeposits] = useState([]); @@ -31,26 +31,29 @@ export function useUserDeposits() { const reservesByCollateralMint = useMemo(() => { return reserveAccounts.reduce((result, item) => { - result.set(item.info.collateralMint.toBase58(), item); + if(!reserveAddress || item.pubkey.toBase58() === reserveAddress) { + result.set(item.info.collateralMint.toBase58(), item); + } + return result; }, new Map>()); - }, [reserveAccounts]); + }, [reserveAccounts, reserveAddress]); useEffect(() => { const activeMarkets = new Set(reserveAccounts.map(r => r.info.dexMarket.toBase58())); const userDepositsFactory = () => { return userAccounts - .filter((acc) => reservesByCollateralMint.has(acc.info.mint.toBase58())) + .filter((acc) => reservesByCollateralMint.has(acc?.info.mint.toBase58())) .map((item) => { const reserve = reservesByCollateralMint.get( - item.info.mint.toBase58() + item?.info.mint.toBase58() ) as ParsedAccount; let collateralMint = cache.get(reserve.info.collateralMint) as ParsedAccount; - const amountLamports = calculateCollateralBalance(reserve.info, item.info.amount.toNumber()); - const amount = fromLamports(amountLamports, collateralMint.info); + const amountLamports = calculateCollateralBalance(reserve.info, item?.info.amount.toNumber()); + const amount = fromLamports(amountLamports, collateralMint?.info); const price = midPriceInUSD(reserve.info.liquidityMint.toBase58()); const amountInQuote = price * amount; diff --git a/src/hooks/useUserObligationByReserve.ts b/src/hooks/useUserObligationByReserve.ts index 33003ff..c0b5a6b 100644 --- a/src/hooks/useUserObligationByReserve.ts +++ b/src/hooks/useUserObligationByReserve.ts @@ -19,8 +19,11 @@ export function useUserObligationByReserve( : collateralReserve?.toBase58(); return userObligations.filter( (item) => - item.obligation.info.borrowReserve.toBase58() === borrowId && - item.obligation.info.collateralReserve.toBase58() === collateralId + borrowId && collateralId ? + item.obligation.info.borrowReserve.toBase58() === borrowId && + item.obligation.info.collateralReserve.toBase58() === collateralId : + (borrowId && item.obligation.info.borrowReserve.toBase58() === borrowId) || + (collateralId && item.obligation.info.collateralReserve.toBase58() === collateralId) ); }, [borrowReserve, collateralReserve, userObligations]); diff --git a/src/views/dashboard/style.less b/src/views/dashboard/style.less index 4a5f686..ee18d70 100644 --- a/src/views/dashboard/style.less +++ b/src/views/dashboard/style.less @@ -21,12 +21,6 @@ flex: 200px } - em { - font-weight: bold; - font-style: normal; - text-decoration: none; - } - border-bottom: 1px solid #eee; } diff --git a/src/views/liquidate/style.less b/src/views/liquidate/style.less index c994f19..420b61b 100644 --- a/src/views/liquidate/style.less +++ b/src/views/liquidate/style.less @@ -15,13 +15,7 @@ & > :first-child { flex: 80px } - - em { - font-weight: bold; - font-style: normal; - text-decoration: none; - } - + border-bottom: 1px solid #eee; }