feat: fix borrowing power

This commit is contained in:
bartosz-lipinski 2020-12-29 21:29:06 -06:00
parent 21051f776a
commit c204ac5ad9
13 changed files with 217 additions and 85 deletions

View File

@ -329,6 +329,32 @@ em {
text-align: right; text-align: right;
} }
.small-statisitc {
.ant-statistic-title {
font-size: 12px;
}
.ant-statistic-content {
max-height: 20px;
line-height: 2px;;
}
.ant-statistic-content-value-int {
font-size: 12px;
}
.ant-statistic-content-value-decimal {
font-size: 10px;
}
}
.dashboard-amount-quote-stat {
font-size: 10px;
font-style: normal;
text-align: center;
font-weight: normal;
}
@media only screen and (max-width: 600px) { @media only screen and (max-width: 600px) {
.exchange-card { .exchange-card {
width: 360px; width: 360px;

View File

@ -1,10 +1,15 @@
import React from "react"; import React from "react";
import { LendingReserve } from "../../models/lending"; import { calculateDepositAPY, LendingReserve } from "../../models/lending";
import { Card } from "antd"; import { Card, Col, Row, Statistic } from "antd";
import { PublicKey } from "@solana/web3.js"; import { PublicKey } from "@solana/web3.js";
import "./style.less"; import "./style.less";
import { LABELS } from "../../constants"; import { GUTTER, LABELS } from "../../constants";
import { ReserveUtilizationChart } from "./../../components/ReserveUtilizationChart"; import { ReserveUtilizationChart } from "./../../components/ReserveUtilizationChart";
import { useMemo } from "react";
import { formatNumber, fromLamports, wadToLamports } from "../../utils/utils";
import { useMint } from "../../contexts/accounts";
import { useMidPriceInUSD } from "../../contexts/market";
import { TokenIcon } from "../TokenIcon";
export const ReserveStatus = (props: { export const ReserveStatus = (props: {
className?: string; className?: string;
@ -17,13 +22,70 @@ export const ReserveStatus = (props: {
alignItems: "center", alignItems: "center",
}; };
const mintAddress = props.reserve.liquidityMint?.toBase58();
const liquidityMint = useMint(mintAddress);
const { price } = useMidPriceInUSD(mintAddress);
const availableLiquidity = fromLamports(
props.reserve.availableLiquidity.toNumber(),
liquidityMint
);
const availableLiquidityInUSD = price * availableLiquidity;
const totalBorrows = useMemo(
() =>
fromLamports(
wadToLamports(props.reserve.borrowedLiquidityWad),
liquidityMint
),
[props.reserve, liquidityMint]
);
const totalBorrowsInUSD = price * totalBorrows;
const depositAPY = useMemo(() => calculateDepositAPY(props.reserve), [
props.reserve
]);
const liquidationThreshold = props.reserve.config.liquidationThreshold;
const liquidationPenalty = props.reserve.config.liquidationBonus;
const maxLTV = props.reserve.config.loanToValueRatio;
return ( return (
<Card <Card
className={props.className} className={props.className}
title={<>{LABELS.RESERVE_STATUS_TITLE}</>} title={<>
<TokenIcon style={{ marginRight: 0, marginTop: 0, position: 'absolute', left: 15 }}
mintAddress={mintAddress} size={30} />
{LABELS.RESERVE_STATUS_TITLE}</>}
bodyStyle={bodyStyle} bodyStyle={bodyStyle}
> >
<div <div className="flexColumn">
<Row gutter={GUTTER}>
<Col span={12}>
<Statistic
title="Available Liquidity"
value={availableLiquidity}
valueRender={(node) => <div>
{node}
<div className="dashboard-amount-quote-stat">${formatNumber.format(availableLiquidityInUSD)}</div>
</div>}
precision={2} />
</Col>
<Col span={12}>
<Statistic
title="Total Borrowed"
value={totalBorrows}
valueRender={(node) => <div>
{node}
<div className="dashboard-amount-quote-stat">${formatNumber.format(totalBorrowsInUSD)}</div>
</div>}
precision={2} />
</Col>
</Row>
<Row gutter={GUTTER}>
<Col
span={24}
style={{ style={{
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
@ -31,6 +93,42 @@ export const ReserveStatus = (props: {
}} }}
> >
<ReserveUtilizationChart reserve={props.reserve} /> <ReserveUtilizationChart reserve={props.reserve} />
</Col>
</Row>
<Row gutter={GUTTER}>
<Col span={6}>
<Statistic
title="Maximum LTV"
className="small-statisitc"
value={maxLTV}
precision={2}
suffix="%" />
</Col>
<Col span={6}>
<Statistic
title="Liquidation threashold"
className="small-statisitc"
value={liquidationThreshold}
precision={2}
suffix="%" />
</Col>
<Col span={6}>
<Statistic
title="Liquidation penalty"
className="small-statisitc"
value={liquidationPenalty}
precision={2}
suffix="%" />
</Col>
<Col span={6}>
<Statistic
title="APY"
className="small-statisitc"
value={depositAPY * 100}
precision={2}
suffix="%" />
</Col>
</Row>
</div> </div>
</Card> </Card>
); );

View File

@ -3,9 +3,11 @@ import { LendingReserve } from "../../models/lending";
import { fromLamports, wadToLamports } from "../../utils/utils"; import { fromLamports, wadToLamports } from "../../utils/utils";
import { useMint } from "../../contexts/accounts"; import { useMint } from "../../contexts/accounts";
import { WaterWave } from "./../WaterWave"; import { WaterWave } from "./../WaterWave";
import { Statistic } from "antd";
export const ReserveUtilizationChart = (props: { reserve: LendingReserve }) => { export const ReserveUtilizationChart = (props: { reserve: LendingReserve }) => {
const liquidityMint = useMint(props.reserve.liquidityMint); const mintAddress = props.reserve.liquidityMint?.toBase58();
const liquidityMint = useMint(mintAddress);
const availableLiquidity = fromLamports( const availableLiquidity = fromLamports(
props.reserve.availableLiquidity.toNumber(), props.reserve.availableLiquidity.toNumber(),
liquidityMint liquidityMint
@ -20,10 +22,20 @@ export const ReserveUtilizationChart = (props: { reserve: LendingReserve }) => {
[props.reserve, liquidityMint] [props.reserve, liquidityMint]
); );
const percent = (availableLiquidity * 100) / (availableLiquidity + totalBorrows);
return ( return (
<WaterWave <WaterWave
style={{ height: 300 }} style={{ height: 300 }}
percent={(availableLiquidity * 100) / (availableLiquidity + totalBorrows)} showPercent={false}
title={
<Statistic
title="Utilization"
value={percent}
suffix="%"
precision={2} />
}
percent={percent}
/> />
); );
}; };

View File

@ -7,19 +7,22 @@ import { PublicKey } from "@solana/web3.js";
export const TokenIcon = (props: { export const TokenIcon = (props: {
mintAddress?: string | PublicKey; mintAddress?: string | PublicKey;
style?: React.CSSProperties; style?: React.CSSProperties;
size?: number;
className?: string; className?: string;
}) => { }) => {
const { tokenMap } = useConnectionConfig(); const { tokenMap } = useConnectionConfig();
const icon = getTokenIcon(tokenMap, props.mintAddress); const icon = getTokenIcon(tokenMap, props.mintAddress);
const size = props.size || 20;
if (icon) { if (icon) {
return ( return (
<img <img
alt="Token icon" alt="Token icon"
className={props.className} className={props.className}
key={icon} key={icon}
width={props.style?.width || "20"} width={props.style?.width || size.toString()}
height={props.style?.height || "20"} height={props.style?.height || size.toString()}
src={icon} src={icon}
style={{ style={{
marginRight: "0.5rem", marginRight: "0.5rem",
@ -38,8 +41,8 @@ export const TokenIcon = (props: {
address={props.mintAddress} address={props.mintAddress}
style={{ style={{
marginRight: "0.5rem", marginRight: "0.5rem",
width: 20, width: size,
height: 20, height: size,
marginTop: 2, marginTop: 2,
...props.style, ...props.style,
}} }}

View File

@ -4,6 +4,7 @@ import {
useTokenName, useTokenName,
useUserBalance, useUserBalance,
useBorrowedAmount, useBorrowedAmount,
useBorrowingPower,
} from "./../../hooks"; } from "./../../hooks";
import { LendingReserve } from "../../models/lending"; import { LendingReserve } from "../../models/lending";
import { formatNumber } from "../../utils/utils"; import { formatNumber } from "../../utils/utils";
@ -29,9 +30,7 @@ export const UserLendingCard = (props: {
); );
const { borrowed: totalBorrowed, borrowedInUSD, ltv, health } = useBorrowedAmount(address); const { borrowed: totalBorrowed, borrowedInUSD, ltv, health } = useBorrowedAmount(address);
const { totalInQuote: borrowingPowerInUSD, borrowingPower } = useBorrowingPower(address);
// TODO: calculate
const available = 0; // use all available deposits and convert using market rate
return ( return (
<Card <Card
@ -82,7 +81,10 @@ export const UserLendingCard = (props: {
Available to you: Available to you:
</Text> </Text>
<div className="card-cell "> <div className="card-cell ">
{formatNumber.format(available)} {name} <div>
<div><em>{formatNumber.format(borrowingPower)}</em> {name}</div>
<div className="dashboard-amount-quote">${formatNumber.format(borrowingPowerInUSD)}</div>
</div>
</div> </div>
</div> </div>

View File

@ -4,13 +4,15 @@
display: inline-block; display: inline-block;
position: relative; position: relative;
transform-origin: left; transform-origin: left;
display: flex;
align-items: center;
justify-content: center;
.text { .text {
position: absolute; position: absolute;
left: 5px;
top: calc(50% - 15px);
text-align: center; text-align: center;
width: 100%; width: 100%;
span { .title {
color: @text-color-secondary; color: @text-color-secondary;
font-size: 14px; font-size: 14px;
line-height: 22px; line-height: 22px;

View File

@ -5,7 +5,7 @@ export const WaterWave = (props: any) => {
const node = useRef<HTMLCanvasElement>(); const node = useRef<HTMLCanvasElement>();
const root = useRef<HTMLDivElement>(); const root = useRef<HTMLDivElement>();
const [radio, setRadio] = useState(1); const [radio, setRadio] = useState(1);
const { percent, title, style, color } = props; const { percent, title, style, color, showPercent } = props;
const { height } = style; const { height } = style;
const resize = useCallback(() => { const resize = useCallback(() => {
@ -54,8 +54,8 @@ export const WaterWave = (props: any) => {
/> />
</div> </div>
<div className="text" style={{ width: height }}> <div className="text" style={{ width: height }}>
{title && <span>{title}</span>} {title}
<h4>{percent.toFixed(2)}%</h4> <h4>{showPercent && `${percent.toFixed(2)}%`}</h4>
</div> </div>
</div> </div>
); );

View File

@ -4,7 +4,7 @@ export const LABELS = {
BORROWING_POWER_VALUE: "Borrowing Power", BORROWING_POWER_VALUE: "Borrowing Power",
BORROWED_VALUE: "You borrowed", BORROWED_VALUE: "You borrowed",
GIVE_SOL: "Give me SOL", GIVE_SOL: "Give me SOL",
LIQUIDATION_INFO: "This view displays all loans that can be liquidated. A liquidation is a process where borrower collateral does not cover value of the loan. It is represented by health factor falling below 1.o. When a loan is liquidated, an liquidator can purchase collateral at a discount by repaing the portio of the loan. ", LIQUIDATION_INFO: "This view displays all loans that can be liquidated. A liquidation is a process where borrower collateral does not cover value of the loan. It is represented by health factor falling below 1.0. When a loan is liquidated, an liquidator can purchase collateral at a discount by repaing the portio of the loan. ",
FAUCET_INFO: FAUCET_INFO:
"This faucet will help you fund your accounts outside of Solana main network.", "This faucet will help you fund your accounts outside of Solana main network.",
ACCOUNT_FUNDED: "Account funded.", ACCOUNT_FUNDED: "Account funded.",

View File

@ -1 +1,5 @@
export const GUTTER = [16, { xs: 8, sm: 16, md: 16, lg: 16 }] as any; export const GUTTER = [16, { xs: 8, sm: 16, md: 16, lg: 16 }] as any;
export const SMALL_STATISTIC: React.CSSProperties = {
fontSize: 10,
};

View File

@ -88,7 +88,6 @@ export function useBorrowedAmount(address?: string | PublicKey) {
result.health = Number.isFinite(result.health) ? result.health : 0; result.health = Number.isFinite(result.health) ? result.health : 0;
} }
setBorrowedInfo(result); setBorrowedInfo(result);
})(); })();
}, [connection, userObligationsByReserve, setBorrowedInfo]); }, [connection, userObligationsByReserve, setBorrowedInfo]);

View File

@ -44,14 +44,17 @@ export const HomeView = () => {
return; return;
} }
const price = midPriceInUSD(liquidityMint?.pubkey.toBase58());
let leaf = { let leaf = {
key: item.pubkey.toBase58(), key: item.pubkey.toBase58(),
marketSize: fromLamports(marketCapLamports, liquidityMint?.info) * marketSize: fromLamports(marketCapLamports, liquidityMint?.info) *
midPriceInUSD(liquidityMint?.pubkey.toBase58()), price,
borrowed: fromLamports( borrowed: fromLamports(
wadToLamports(item.info?.borrowedLiquidityWad).toNumber(), wadToLamports(item.info?.borrowedLiquidityWad).toNumber(),
liquidityMint.info liquidityMint.info
), ) *
price,
name: getTokenName(tokenMap, item.info.liquidityMint.toBase58()) name: getTokenName(tokenMap, item.info.liquidityMint.toBase58())
} }

View File

@ -5,6 +5,8 @@ import "./style.less";
import { UserLendingCard } from "./../../components/UserLendingCard"; import { UserLendingCard } from "./../../components/UserLendingCard";
import { ReserveStatus } from "./../../components/ReserveStatus"; import { ReserveStatus } from "./../../components/ReserveStatus";
import { Col, Row } from "antd";
import { GUTTER } from "../../constants";
export const ReserveView = () => { export const ReserveView = () => {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
@ -16,19 +18,21 @@ export const ReserveView = () => {
} }
return ( return (
<div className="reserve-overview"> <div className="flexColumn">
<div className="reserve-overview-container"> <Row gutter={GUTTER}>
<Col sm={24} md={12} lg={14} xl={15} xxl={18}>
<ReserveStatus <ReserveStatus
className="reserve-overview-item reserve-overview-item-left"
reserve={reserve} reserve={reserve}
address={lendingReserve.pubkey} address={lendingReserve.pubkey} />
/> </Col>
<Col sm={24} md={12} lg={10} xl={9} xxl={6}>
<UserLendingCard <UserLendingCard
className="reserve-overview-item reserve-overview-item-right" className="user-lending-card"
reserve={reserve} reserve={reserve}
address={lendingReserve.pubkey} address={lendingReserve.pubkey}
/> />
</div> </Col>
</Row>
</div> </div>
); );
}; };

View File

@ -4,27 +4,6 @@
flex: 1; flex: 1;
} }
.reserve-overview-item { .user-lending-card {
margin: 4px; height: 100%;
}
.reserve-overview-container {
display: flex;
flex-wrap: wrap;
flex: 1;
}
.reserve-overview-item-left {
flex: 60%;
}
.reserve-overview-item-right {
flex: 30%;
}
/* Responsive layout - makes a one column layout instead of a two-column layout */
@media (max-width: 600px) {
.reserve-overview-item-right, .reserve-overview-item-left {
flex: 100%;
}
} }