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;
}
.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) {
.exchange-card {
width: 360px;

View File

@ -1,10 +1,15 @@
import React from "react";
import { LendingReserve } from "../../models/lending";
import { Card } from "antd";
import { calculateDepositAPY, LendingReserve } from "../../models/lending";
import { Card, Col, Row, Statistic } from "antd";
import { PublicKey } from "@solana/web3.js";
import "./style.less";
import { LABELS } from "../../constants";
import { GUTTER, LABELS } from "../../constants";
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: {
className?: string;
@ -17,20 +22,113 @@ export const ReserveStatus = (props: {
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 (
<Card
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}
>
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "space-around",
}}
>
<ReserveUtilizationChart reserve={props.reserve} />
<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={{
display: "flex",
flexDirection: "column",
justifyContent: "space-around",
}}
>
<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>
</Card>
);

View File

@ -3,9 +3,11 @@ import { LendingReserve } from "../../models/lending";
import { fromLamports, wadToLamports } from "../../utils/utils";
import { useMint } from "../../contexts/accounts";
import { WaterWave } from "./../WaterWave";
import { Statistic } from "antd";
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(
props.reserve.availableLiquidity.toNumber(),
liquidityMint
@ -20,10 +22,20 @@ export const ReserveUtilizationChart = (props: { reserve: LendingReserve }) => {
[props.reserve, liquidityMint]
);
const percent = (availableLiquidity * 100) / (availableLiquidity + totalBorrows);
return (
<WaterWave
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: {
mintAddress?: string | PublicKey;
style?: React.CSSProperties;
size?: number;
className?: string;
}) => {
const { tokenMap } = useConnectionConfig();
const icon = getTokenIcon(tokenMap, props.mintAddress);
const size = props.size || 20;
if (icon) {
return (
<img
alt="Token icon"
className={props.className}
key={icon}
width={props.style?.width || "20"}
height={props.style?.height || "20"}
width={props.style?.width || size.toString()}
height={props.style?.height || size.toString()}
src={icon}
style={{
marginRight: "0.5rem",
@ -38,8 +41,8 @@ export const TokenIcon = (props: {
address={props.mintAddress}
style={{
marginRight: "0.5rem",
width: 20,
height: 20,
width: size,
height: size,
marginTop: 2,
...props.style,
}}

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ export const LABELS = {
BORROWING_POWER_VALUE: "Borrowing Power",
BORROWED_VALUE: "You borrowed",
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:
"This faucet will help you fund your accounts outside of Solana main network.",
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

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

View File

@ -19,7 +19,7 @@ export const HomeView = () => {
const [totals, setTotals] = useState<Totals>({
marketSize: 0,
borrowed: 0,
lentOutPct: 0,
lentOutPct: 0,
items: [],
})
@ -28,7 +28,7 @@ export const HomeView = () => {
let newTotals: Totals = {
marketSize: 0,
borrowed: 0,
lentOutPct: 0,
lentOutPct: 0,
items: [],
};
@ -44,14 +44,17 @@ export const HomeView = () => {
return;
}
const price = midPriceInUSD(liquidityMint?.pubkey.toBase58());
let leaf = {
key: item.pubkey.toBase58(),
marketSize: fromLamports(marketCapLamports, liquidityMint?.info) *
midPriceInUSD(liquidityMint?.pubkey.toBase58()),
price,
borrowed: fromLamports(
wadToLamports(item.info?.borrowedLiquidityWad).toNumber(),
liquidityMint.info
),
) *
price,
name: getTokenName(tokenMap, item.info.liquidityMint.toBase58())
}
@ -59,7 +62,7 @@ export const HomeView = () => {
newTotals.marketSize = newTotals.marketSize + leaf.marketSize;
newTotals.borrowed = newTotals.borrowed + leaf.borrowed;
});
newTotals.lentOutPct = newTotals.borrowed / newTotals.marketSize;
@ -82,8 +85,8 @@ export const HomeView = () => {
return (
<div className="flexColumn">
<Row
gutter={GUTTER}
<Row
gutter={GUTTER}
className="home-info-row" >
<Col xs={24} xl={5}>
<Card>
@ -128,16 +131,16 @@ export const HomeView = () => {
</Row>
<Card>
<div className="home-item home-header">
<div>{LABELS.TABLE_TITLE_ASSET}</div>
<div>{LABELS.TABLE_TITLE_MARKET_SIZE}</div>
<div>{LABELS.TABLE_TITLE_TOTAL_BORROWED}</div>
<div>{LABELS.TABLE_TITLE_DEPOSIT_APY}</div>
<div>{LABELS.TABLE_TITLE_BORROW_APY}</div>
</div>
{reserveAccounts.map((account) => (
<LendingReserveItem reserve={account.info} address={account.pubkey} item={totals.items.find(item => item.key === account.pubkey.toBase58())} />
))}
<div className="home-item home-header">
<div>{LABELS.TABLE_TITLE_ASSET}</div>
<div>{LABELS.TABLE_TITLE_MARKET_SIZE}</div>
<div>{LABELS.TABLE_TITLE_TOTAL_BORROWED}</div>
<div>{LABELS.TABLE_TITLE_DEPOSIT_APY}</div>
<div>{LABELS.TABLE_TITLE_BORROW_APY}</div>
</div>
{reserveAccounts.map((account) => (
<LendingReserveItem reserve={account.info} address={account.pubkey} item={totals.items.find(item => item.key === account.pubkey.toBase58())} />
))}
</Card>
</div>
);

View File

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

View File

@ -4,27 +4,6 @@
flex: 1;
}
.reserve-overview-item {
margin: 4px;
}
.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%;
}
.user-lending-card {
height: 100%;
}