Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit befb9b9

Browse files
feat: add LTV, and health factor calculations
1 parent 60f8739 commit befb9b9

File tree

11 files changed

+156
-63
lines changed

11 files changed

+156
-63
lines changed

src/App.less

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ body {
130130
height: 27px;
131131
}
132132

133+
em {
134+
font-weight: bold;
135+
font-style: normal;
136+
text-decoration: none;
137+
}
138+
133139
.telegram:hover {
134140
color: #2789de !important;
135141
}
@@ -247,7 +253,6 @@ body {
247253
margin: 0px;
248254
min-width: 0px;
249255
font-size: 14px;
250-
font-weight: 500;
251256
}
252257

253258
.left {
@@ -301,6 +306,7 @@ body {
301306
.dashboard-amount-quote {
302307
font-size: 10px;
303308
font-style: normal;
309+
text-align: right;
304310
}
305311

306312
@media only screen and (max-width: 600px) {

src/components/BarChartStatistic/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ export const BarChartStatistic = <T, >(props: {
2323
<Statistic
2424
title={props.title}
2525
valueRender={() =>
26-
<div style={{ width: '100%', height: 40, display: 'flex', backgroundColor: 'lightgrey',
26+
<div style={{ width: '100%', height: 37, display: 'flex', backgroundColor: 'lightgrey',
2727
fontSize: 12,
28-
lineHeight: '40px', }}>
28+
lineHeight: '37px', }}>
2929
{props.items.map((item, i) =>
3030
<div key={props.name(item)}
3131
title={props.name(item)}

src/components/UserLendingCard/index.tsx

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,15 @@ export const UserLendingCard = (props: {
2323

2424
const name = useTokenName(reserve?.liquidityMint);
2525

26-
const { balance: tokenBalance } = useUserBalance(props.reserve.liquidityMint);
27-
const { balance: collateralBalance } = useUserCollateralBalance(
26+
const { balance: tokenBalance, balanceInUSD: tokenBalanceInUSD } = useUserBalance(props.reserve.liquidityMint);
27+
const { balance: collateralBalance, balanceInUSD: collateralBalanceInUSD } = useUserCollateralBalance(
2828
props.reserve
2929
);
3030

31-
const { borrowed: totalBorrowed } = useBorrowedAmount(address);
31+
const { borrowed: totalBorrowed, borrowedInUSD, ltv, health } = useBorrowedAmount(address);
3232

3333
// TODO: calculate
34-
const healthFactor = "--";
35-
const ltv = 0;
36-
const available = 0;
34+
const available = 0; // use all available deposits and convert using market rate
3735

3836
return (
3937
<Card
@@ -58,15 +56,18 @@ export const UserLendingCard = (props: {
5856
Borrowed
5957
</Text>
6058
<div className="card-cell ">
61-
{formatNumber.format(totalBorrowed)} {name}
59+
<div>
60+
<div><em>{formatNumber.format(totalBorrowed)}</em> {name}</div>
61+
<div className="dashboard-amount-quote">${formatNumber.format(borrowedInUSD)}</div>
62+
</div>
6263
</div>
6364
</div>
6465

6566
<div className="card-row">
6667
<Text type="secondary" className="card-cell ">
6768
Health factor:
6869
</Text>
69-
<div className="card-cell ">{healthFactor}</div>
70+
<div className="card-cell ">{health.toFixed(2)}</div>
7071
</div>
7172

7273
<div className="card-row">
@@ -92,7 +93,10 @@ export const UserLendingCard = (props: {
9293
Wallet balance:
9394
</Text>
9495
<div className="card-cell ">
95-
{formatNumber.format(tokenBalance)} {name}
96+
<div>
97+
<div><em>{formatNumber.format(tokenBalance)}</em> {name}</div>
98+
<div className="dashboard-amount-quote">${formatNumber.format(tokenBalanceInUSD)}</div>
99+
</div>
96100
</div>
97101
</div>
98102

@@ -101,7 +105,10 @@ export const UserLendingCard = (props: {
101105
You already deposited:
102106
</Text>
103107
<div className="card-cell ">
104-
{formatNumber.format(collateralBalance)} {name}
108+
<div>
109+
<div><em>{formatNumber.format(collateralBalance)}</em> {name}</div>
110+
<div className="dashboard-amount-quote">${formatNumber.format(collateralBalanceInUSD)}</div>
111+
</div>
105112
</div>
106113
</div>
107114

src/hooks/useBorrowedAmount.ts

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,24 @@ import { useLendingReserve } from "./useLendingReserves";
1616
export function useBorrowedAmount(address?: string | PublicKey) {
1717
const connection = useConnection();
1818
const { userObligationsByReserve } = useUserObligationByReserve(address);
19-
const [borrowedLamports, setBorrowedLamports] = useState(0);
19+
const [borrowedInfo, setBorrowedInfo] = useState({
20+
borrowedLamports: 0,
21+
borrowedInUSD: 0,
22+
colateralInUSD: 0,
23+
ltv: 0,
24+
health: 0,
25+
});
2026
const reserve = useLendingReserve(address);
2127
const liquidityMint = useMint(reserve?.info.liquidityMint);
2228

2329
useEffect(() => {
24-
setBorrowedLamports(0);
30+
setBorrowedInfo({
31+
borrowedLamports: 0,
32+
borrowedInUSD: 0,
33+
colateralInUSD: 0,
34+
ltv: 0,
35+
health: 0,
36+
});
2537

2638
(async () => {
2739
// precache obligation mints
@@ -38,29 +50,50 @@ export function useBorrowedAmount(address?: string | PublicKey) {
3850
cache.add(new PublicKey(address), item, MintParser);
3951
});
4052

41-
setBorrowedLamports(
42-
userObligationsByReserve.reduce((result, item) => {
43-
const borrowed = wadToLamports(
44-
item.obligation.info.borrowAmountWad
45-
).toNumber();
53+
const result = {
54+
borrowedLamports: 0,
55+
borrowedInUSD: 0,
56+
colateralInUSD: 0,
57+
ltv: 0,
58+
health: 0,
59+
};
4660

47-
const owned = item.userAccounts.reduce(
48-
(amount, acc) => (amount += acc.info.amount.toNumber()),
49-
0
50-
);
51-
const obligationMint = cache.get(
52-
item.obligation.info.tokenMint
53-
) as ParsedAccount<MintInfo>;
61+
let liquidationThreshold = 0;
5462

55-
result += (borrowed * owned) / obligationMint?.info.supply.toNumber();
56-
return result;
57-
}, 0)
58-
);
63+
userObligationsByReserve.forEach((item) => {
64+
const borrowed = wadToLamports(
65+
item.obligation.info.borrowAmountWad
66+
).toNumber();
67+
68+
const owned = item.userAccounts.reduce(
69+
(amount, acc) => (amount += acc.info.amount.toNumber()),
70+
0
71+
);
72+
const obligationMint = cache.get(
73+
item.obligation.info.tokenMint
74+
) as ParsedAccount<MintInfo>;
75+
76+
result.borrowedLamports += borrowed * (owned / obligationMint?.info.supply.toNumber());
77+
result.borrowedInUSD += item.obligation.info.borrowedInQuote;
78+
result.colateralInUSD += item.obligation.info.collateralInQuote;
79+
liquidationThreshold = item.obligation.info.liquidationThreshold;
80+
}, 0);
81+
82+
if (userObligationsByReserve.length === 1) {
83+
result.ltv = userObligationsByReserve[0].obligation.info.ltv;
84+
result.health = userObligationsByReserve[0].obligation.info.health;
85+
} else {
86+
result.ltv = 100 * result.borrowedInUSD / result.colateralInUSD;
87+
result.health = result.colateralInUSD * liquidationThreshold / 100 / result.borrowedInUSD;
88+
}
89+
90+
91+
setBorrowedInfo(result);
5992
})();
60-
}, [connection, userObligationsByReserve]);
93+
}, [connection, userObligationsByReserve, setBorrowedInfo]);
6194

6295
return {
63-
borrowed: fromLamports(borrowedLamports, liquidityMint),
64-
borrowedLamports,
96+
borrowed: fromLamports(borrowedInfo.borrowedLamports, liquidityMint),
97+
...borrowedInfo,
6598
};
6699
}

src/hooks/useCollateralBalance.ts

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { PublicKey } from "@solana/web3.js";
2+
import { useEffect, useMemo, useState } from "react";
23
import { useMint } from "../contexts/accounts";
4+
import { useMarkets } from "../contexts/market";
35
import { LendingReserve, reserveMarketCap } from "../models/lending";
46
import { fromLamports } from "../utils/utils";
57
import { useUserBalance } from "./useUserBalance";
@@ -9,17 +11,43 @@ export function useUserCollateralBalance(
911
account?: PublicKey
1012
) {
1113
const mint = useMint(reserve?.collateralMint);
12-
const { balanceLamports, accounts } = useUserBalance(
14+
const { balanceLamports: userBalance, accounts } = useUserBalance(
1315
reserve?.collateralMint,
1416
account
1517
);
1618

17-
const collateralBalance = reserve &&
18-
calculateCollateralBalance(reserve, balanceLamports);
19+
const [balanceInUSD, setBalanceInUSD] = useState(0);
20+
const { marketEmitter, midPriceInUSD } = useMarkets();
21+
22+
const balanceLamports = useMemo(() => reserve &&
23+
calculateCollateralBalance(reserve, userBalance),
24+
[userBalance, reserve]);
25+
26+
const balance = useMemo(() => fromLamports(balanceLamports, mint),
27+
[balanceLamports, mint]);
28+
29+
useEffect(() => {
30+
const updateBalance = () => {
31+
setBalanceInUSD(balance * midPriceInUSD(reserve?.liquidityMint?.toBase58() || ''));
32+
}
33+
34+
const dispose = marketEmitter.onMarket((args) => {
35+
if(args.ids.has(reserve?.dexMarket.toBase58() || '')) {
36+
updateBalance();
37+
}
38+
});
39+
40+
updateBalance();
41+
42+
return () => {
43+
dispose();
44+
};
45+
}, [balance, midPriceInUSD, marketEmitter, mint, setBalanceInUSD, reserve]);
1946

2047
return {
21-
balance: fromLamports(collateralBalance, mint),
22-
balanceLamports: collateralBalance,
48+
balance,
49+
balanceLamports,
50+
balanceInUSD,
2351
mint: reserve?.collateralMint,
2452
accounts,
2553
};

src/hooks/useEnrichedLendingObligations.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ interface EnrichedLendingObligationInfo extends LendingObligation {
1818
health: number;
1919
borrowedInQuote: number;
2020
collateralInQuote: number;
21+
liquidationThreshold: number;
2122
name: string;
2223
}
2324

@@ -116,6 +117,7 @@ export function useEnrichedLendingObligations() {
116117
health,
117118
borrowedInQuote,
118119
collateralInQuote,
120+
liquidationThreshold: item.reserve.info.config.liquidationThreshold,
119121
name: getTokenName(tokenMap, reserve.liquidityMint)
120122
},
121123
} as EnrichedLendingObligation;

src/hooks/useUserBalance.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import { PublicKey } from "@solana/web3.js";
2-
import { useMemo } from "react";
2+
import { useEffect, useMemo, useState } from "react";
33
import { useMint } from "../contexts/accounts";
4+
import { useMarkets } from "../contexts/market";
45
import { fromLamports } from "../utils/utils";
56
import { useUserAccounts } from "./useUserAccounts";
67

78
export function useUserBalance(mint?: PublicKey, account?: PublicKey) {
89
const { userAccounts } = useUserAccounts();
10+
const [balanceInUSD, setBalanceInUSD] = useState(0);
11+
const { marketEmitter, midPriceInUSD } = useMarkets();
12+
913
const mintInfo = useMint(mint);
1014
const accounts = useMemo(() => {
1115
return userAccounts
@@ -24,9 +28,28 @@ export function useUserBalance(mint?: PublicKey, account?: PublicKey) {
2428
);
2529
}, [accounts]);
2630

31+
const balance = useMemo(() => fromLamports(balanceLamports, mintInfo), [mintInfo, balanceLamports]);
32+
33+
useEffect(() => {
34+
const updateBalance = () => {
35+
setBalanceInUSD(balance * midPriceInUSD(mint?.toBase58() || ''));
36+
}
37+
38+
const dispose = marketEmitter.onMarket((args) => {
39+
updateBalance();
40+
});
41+
42+
updateBalance();
43+
44+
return () => {
45+
dispose();
46+
};
47+
}, [balance, midPriceInUSD, marketEmitter, mint, setBalanceInUSD]);
48+
2749
return {
28-
balance: fromLamports(balanceLamports, mintInfo),
50+
balance,
2951
balanceLamports,
52+
balanceInUSD,
3053
accounts,
3154
};
3255
}

src/hooks/useUserDeposits.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export interface UserDeposit {
2222
reserve: ParsedAccount<LendingReserve>;
2323
}
2424

25-
export function useUserDeposits() {
25+
export function useUserDeposits(reserveAddress?: string) {
2626
const { userAccounts } = useUserAccounts();
2727
const { reserveAccounts } = useLendingReserves();
2828
const [userDeposits, setUserDeposits] = useState<UserDeposit[]>([]);
@@ -31,26 +31,29 @@ export function useUserDeposits() {
3131

3232
const reservesByCollateralMint = useMemo(() => {
3333
return reserveAccounts.reduce((result, item) => {
34-
result.set(item.info.collateralMint.toBase58(), item);
34+
if(!reserveAddress || item.pubkey.toBase58() === reserveAddress) {
35+
result.set(item.info.collateralMint.toBase58(), item);
36+
}
37+
3538
return result;
3639
}, new Map<string, ParsedAccount<LendingReserve>>());
37-
}, [reserveAccounts]);
40+
}, [reserveAccounts, reserveAddress]);
3841

3942
useEffect(() => {
4043
const activeMarkets = new Set(reserveAccounts.map(r => r.info.dexMarket.toBase58()));
4144

4245
const userDepositsFactory = () => {
4346
return userAccounts
44-
.filter((acc) => reservesByCollateralMint.has(acc.info.mint.toBase58()))
47+
.filter((acc) => reservesByCollateralMint.has(acc?.info.mint.toBase58()))
4548
.map((item) => {
4649
const reserve = reservesByCollateralMint.get(
47-
item.info.mint.toBase58()
50+
item?.info.mint.toBase58()
4851
) as ParsedAccount<LendingReserve>;
4952

5053
let collateralMint = cache.get(reserve.info.collateralMint) as ParsedAccount<MintInfo>;
5154

52-
const amountLamports = calculateCollateralBalance(reserve.info, item.info.amount.toNumber());
53-
const amount = fromLamports(amountLamports, collateralMint.info);
55+
const amountLamports = calculateCollateralBalance(reserve.info, item?.info.amount.toNumber());
56+
const amount = fromLamports(amountLamports, collateralMint?.info);
5457
const price = midPriceInUSD(reserve.info.liquidityMint.toBase58());
5558
const amountInQuote = price * amount;
5659

src/hooks/useUserObligationByReserve.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ export function useUserObligationByReserve(
1919
: collateralReserve?.toBase58();
2020
return userObligations.filter(
2121
(item) =>
22-
item.obligation.info.borrowReserve.toBase58() === borrowId &&
23-
item.obligation.info.collateralReserve.toBase58() === collateralId
22+
borrowId && collateralId ?
23+
item.obligation.info.borrowReserve.toBase58() === borrowId &&
24+
item.obligation.info.collateralReserve.toBase58() === collateralId :
25+
(borrowId && item.obligation.info.borrowReserve.toBase58() === borrowId) ||
26+
(collateralId && item.obligation.info.collateralReserve.toBase58() === collateralId)
2427
);
2528
}, [borrowReserve, collateralReserve, userObligations]);
2629

src/views/dashboard/style.less

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,6 @@
2121
flex: 200px
2222
}
2323

24-
em {
25-
font-weight: bold;
26-
font-style: normal;
27-
text-decoration: none;
28-
}
29-
3024
border-bottom: 1px solid #eee;
3125
}
3226

0 commit comments

Comments
 (0)