feat: reserve utilization

This commit is contained in:
bartosz-lipinski 2020-11-25 00:16:37 -06:00
parent f9adc64ca9
commit 49324447ba
15 changed files with 327 additions and 253 deletions

View File

@ -10,7 +10,7 @@ import {
depositInstruction,
initReserveInstruction,
LendingReserve,
} from "./../models/lending/reserve";
} from "./../models/lending";
import { AccountLayout, MintInfo, Token } from "@solana/spl-token";
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../constants/ids";
import {

View File

@ -9,7 +9,7 @@ import { notify } from "../utils/notifications";
import {
LendingReserve,
withdrawInstruction,
} from "./../models/lending/reserve";
} from "./../models/lending";
import { AccountLayout, Token } from "@solana/spl-token";
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../constants/ids";
import { findOrCreateAccountByMint } from "./account";

View File

@ -1,115 +1,10 @@
import React, { useEffect, useMemo, useRef } from "react";
import React, { } from "react";
import { LendingReserve } from "../../models/lending";
import { Card } from "antd";
import { PublicKey } from "@solana/web3.js";
import "./style.less";
import { LABELS } from "../../constants";
import echarts from "echarts";
import { formatNumber, formatUSD, fromLamports, wadToLamports } from "../../utils/utils";
import { useMint } from "../../contexts/accounts";
export const ReserveUtilizationChart = (props: {
reserve: LendingReserve;
}) => {
const chartDiv = useRef<HTMLDivElement>(null);
// dispose chart
useEffect(() => {
const div = chartDiv.current;
return () => {
let instance = div && echarts.getInstanceByDom(div);
instance && instance.dispose();
};
}, []);
const liquidityMint = useMint(props.reserve.liquidityMint);
const avilableLiquidity = fromLamports(
props.reserve.totalLiquidity.toNumber(),
liquidityMint
);
const totalBorrows = useMemo(
() =>
fromLamports(wadToLamports(props.reserve.totalBorrowsWad), liquidityMint),
[props.reserve, liquidityMint]
);
useEffect(() => {
if (!chartDiv.current) {
return;
}
let instance = echarts.getInstanceByDom(chartDiv.current);
if (!instance) {
instance = echarts.init(chartDiv.current as any);
}
const data = [
{
name: 'Available Liquidity',
value: avilableLiquidity,
tokens: avilableLiquidity,
},
{
name: 'Total Borrowed',
value: totalBorrows,
tokens: totalBorrows,
},
];
instance.setOption({
tooltip: {
trigger: "item",
formatter: function (params: any) {
var val = formatUSD.format(params.value);
var tokenAmount = formatNumber.format(params.data.tokens);
return `${params.name}: \n${val}\n(${tokenAmount})`;
},
},
series: [
{
name: "Liquidity",
type: "pie",
radius: ['50%', '70%'],
top: 0,
bottom: 0,
left: 0,
right: 0,
animation: false,
label: {
fontSize: 14,
show: true,
formatter: function (params: any) {
var val = formatUSD.format(params.value);
var tokenAmount = formatNumber.format(params.data.tokens);
return `{c|${params.name}}\n{r|${tokenAmount}}\n{r|${val}}`;
},
rich: {
c: {
color: "#999",
lineHeight: 22,
align: "center",
},
r: {
color: "#999",
align: "right",
},
},
color: "rgba(255, 255, 255, 0.5)",
},
itemStyle: {
normal: {
borderColor: "#000",
},
},
data,
},
],
});
}, [totalBorrows, avilableLiquidity]);
return <div ref={chartDiv} style={{ height: 300, width: 400 }} />
}
import { ReserveUtilizationChart } from "./../../components/ReserveUtilizationChart";
export const ReserveStatus = (props: {
className?: string;

View File

@ -0,0 +1,108 @@
import React, { useEffect, useMemo, useRef } from "react";
import { LendingReserve } from "../../models/lending";
import echarts from "echarts";
import { formatNumber, formatUSD, fromLamports, wadToLamports } from "../../utils/utils";
import { useMint } from "../../contexts/accounts";
export const ReserveUtilizationChart = (props: {
reserve: LendingReserve;
}) => {
const chartDiv = useRef<HTMLDivElement>(null);
// dispose chart
useEffect(() => {
const div = chartDiv.current;
return () => {
let instance = div && echarts.getInstanceByDom(div);
instance && instance.dispose();
};
}, []);
const liquidityMint = useMint(props.reserve.liquidityMint);
const avilableLiquidity = fromLamports(
props.reserve.totalLiquidity.toNumber(),
liquidityMint
);
const totalBorrows = useMemo(
() =>
fromLamports(wadToLamports(props.reserve.totalBorrowsWad), liquidityMint),
[props.reserve, liquidityMint]
);
useEffect(() => {
if (!chartDiv.current) {
return;
}
let instance = echarts.getInstanceByDom(chartDiv.current);
if (!instance) {
instance = echarts.init(chartDiv.current as any);
}
const data = [
{
name: 'Available Liquidity',
value: avilableLiquidity,
tokens: avilableLiquidity,
},
{
name: 'Total Borrowed',
value: totalBorrows,
tokens: totalBorrows,
},
];
instance.setOption({
tooltip: {
trigger: "item",
formatter: function (params: any) {
var val = formatUSD.format(params.value);
var tokenAmount = formatNumber.format(params.data.tokens);
return `${params.name}: \n${val}\n(${tokenAmount})`;
},
},
series: [
{
name: "Liquidity",
type: "pie",
radius: ['50%', '70%'],
top: 0,
bottom: 0,
left: 0,
right: 0,
animation: false,
label: {
fontSize: 14,
show: true,
formatter: function (params: any) {
var val = formatUSD.format(params.value);
var tokenAmount = formatNumber.format(params.data.tokens);
return `{c|${params.name}}\n{r|${tokenAmount}}\n{r|${val}}`;
},
rich: {
c: {
color: "#999",
lineHeight: 22,
align: "center",
},
r: {
color: "#999",
align: "right",
},
},
color: "rgba(255, 255, 255, 0.5)",
},
itemStyle: {
normal: {
borderColor: "#000",
},
},
data,
},
],
});
}, [totalBorrows, avilableLiquidity]);
return <div ref={chartDiv} style={{ height: 300, width: 400 }} />
}

View File

@ -6,6 +6,7 @@ import { formatNumber, formatPct, fromLamports } from "../../utils/utils";
import { Card, Typography } from "antd";
import { useMint } from "../../contexts/accounts";
import { PublicKey } from "@solana/web3.js";
import { Link } from "react-router-dom";
const { Text } = Typography;
@ -101,11 +102,13 @@ export const SideReserveOverview = (props: {
justifyContent: "center",
}}
>
<TokenIcon
mintAddress={reserve?.liquidityMint}
style={{ width: 30, height: 30 }}
/>{" "}
{name} Reserve Overview
<Link to={`/reserve/${props.address}`}>
<TokenIcon
mintAddress={reserve?.liquidityMint}
style={{ width: 30, height: 30 }}
/>{" "}
{name} Reserve Overview
</Link>
</div>
}
>

View File

@ -23,7 +23,9 @@ export const LABELS = {
TABLE_TITLE_ASSET: "Asset",
TABLE_TITLE_LOAN_BALANCE: "Your loan balan",
TABLE_TITLE_APY: "APY",
TABLE_TITLE_APR: "APR",
TABLE_TITLE_ACTION: "Action",
TABLE_TITLE_MAX_BORROW: "Available fro you",
DASHBOARD_TITLE_LOANS: "Loans",
DASHBOARD_TITLE_DEPOSITS: "Deposts",
WITHDRAW_ACTION: "Withdraw",

View File

@ -18,6 +18,7 @@ import {
} from "./accounts";
import { PublicKey } from "@solana/web3.js";
import { DexMarketParser } from "../models/dex";
import { usePrecacheMarket } from "./market";
export interface LendingContextState {}
@ -39,14 +40,19 @@ export function LendingProvider({ children = null as any }) {
export const useLending = () => {
const connection = useConnection();
const [lendingAccounts, setLendingAccounts] = useState<any[]>([]);
const precacheMarkets = usePrecacheMarket()
// TODO: query for all the dex from reserves
const processAccount = useCallback((item) => {
if (isLendingReserve(item.account)) {
return cache.add(
const reserve = cache.add(
item.pubkey.toBase58(),
item.account,
LendingReserveParser
);
return reserve;
} else if (isLendingMarket(item.account)) {
return cache.add(
item.pubkey.toBase58(),
@ -71,12 +77,14 @@ export const useLending = () => {
.map(processAccount)
.filter((item) => item !== undefined);
const lendingReserves = accounts
.filter(
(acc) => (acc?.info as LendingReserve).lendingMarket !== undefined
)
.map((acc) => acc as ParsedAccount<LendingReserve>);
const toQuery = [
...accounts
.filter(
(acc) => (acc?.info as LendingReserve).lendingMarket !== undefined
)
.map((acc) => acc as ParsedAccount<LendingReserve>)
...lendingReserves
.map((acc) => {
const result = [
cache.registerParser(

View File

@ -17,6 +17,8 @@ export interface MarketsContextState {
marketByMint: Map<string, SerumMarket>;
subscribeToMarket: (mint: string) => () => void;
precacheMarkets: (mints: string[]) => void;
}
const REFRESH_INTERVAL = 30_000;
@ -28,17 +30,15 @@ const marketEmitter = new EventEmitter();
export function MarketProvider({ children = null as any }) {
const { endpoint } = useConnectionConfig();
const accountsToObserve = useMemo(() => new Map<string, number>(), []);
const [marketMints, setMarketMints] = useState<string[]>();
const connection = useMemo(() => new Connection(endpoint, "recent"), [
endpoint,
]);
// TODO: identify which markets to query ...
const mints = useMemo(() => [] as PublicKey[], []);
const marketByMint = useMemo(() => {
return [...new Set(mints).values()].reduce((acc, key) => {
const mintAddress = key.toBase58();
return [...new Set(marketMints).values()].reduce((acc, key) => {
const mintAddress = key;
const SERUM_TOKEN = TOKEN_MINTS.find(
(a) => a.address.toBase58() === mintAddress
@ -58,7 +58,7 @@ export function MarketProvider({ children = null as any }) {
return acc;
}, new Map<string, SerumMarket>()) as Map<string, SerumMarket>;
}, [mints]);
}, [marketMints]);
useEffect(() => {
let timer = 0;
@ -185,6 +185,10 @@ export function MarketProvider({ children = null as any }) {
[marketByMint, accountsToObserve]
);
const precacheMarkets = useCallback((mints: string[]) => {
setMarketMints([...new Set([...marketMints, ...mints]).values()])
}, [setMarketMints, marketMints]);
return (
<MarketsContext.Provider
value={{
@ -193,6 +197,7 @@ export function MarketProvider({ children = null as any }) {
accountsToObserve,
marketByMint,
subscribeToMarket,
precacheMarkets,
}}
>
{children}
@ -231,6 +236,11 @@ export const useMidPriceInUSD = (mint: string) => {
return { price, isBase: price === 1.0 };
};
export const usePrecacheMarket = () => {
const context = useMarkets();
return context.precacheMarkets;
}
const getMidPrice = (marketAddress?: string, mintAddress?: string) => {
const SERUM_TOKEN = TOKEN_MINTS.find(
(a) => a.address.toBase58() === mintAddress

View File

@ -7,8 +7,10 @@ import {
import BN from "bn.js";
import * as BufferLayout from "buffer-layout";
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../../constants/ids";
import { WAD } from "../../constants/math";
import * as Layout from "./../../utils/layout";
import { LendingInstruction } from "./lending";
import { LendingReserve } from "./reserve";
/// Borrow tokens from a reserve by depositing collateral tokens. The number of borrowed tokens
/// is calculated by market price. The debt obligation is tokenized.
@ -98,3 +100,30 @@ export const borrowInstruction = (
data,
});
};
// deposit APY utilization currentUtilizationRate * borrowAPY
export const calculateBorrowAPY = (reserve: LendingReserve) => {
const totalBorrows = reserve.totalBorrowsWad.div(WAD).toNumber();
const currentUtilization =
totalBorrows / (reserve.totalLiquidity.toNumber() + totalBorrows);
const optimalUtilization = reserve.config.optimalUtilizationRate;
let borrowAPY;
if (currentUtilization < optimalUtilization) {
const normalized_factor = currentUtilization / optimalUtilization;
const optimalBorrowRate = reserve.config.optimalBorrowRate / 100;
const minBorrowRate = reserve.config.minBorrowRate / 100;
borrowAPY =
normalized_factor * (optimalBorrowRate - minBorrowRate) + minBorrowRate;
} else {
const normalized_factor =
(currentUtilization - optimalUtilization) / (100 - optimalUtilization);
const optimalBorrowRate = reserve.config.optimalBorrowRate / 100;
const maxBorrowRate = reserve.config.maxBorrowRate / 100;
borrowAPY =
normalized_factor * (maxBorrowRate - optimalBorrowRate) +
optimalBorrowRate;
}
return borrowAPY;
};

View File

@ -0,0 +1,73 @@
import {
PublicKey,
SYSVAR_CLOCK_PUBKEY,
TransactionInstruction,
} from "@solana/web3.js";
import BN from "bn.js";
import * as BufferLayout from "buffer-layout";
import { WAD } from "../../constants";
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../../constants/ids";
import * as Layout from "./../../utils/layout";
import { calculateBorrowAPY } from "./borrow";
import { LendingInstruction } from "./lending";
import { LendingReserve } from "./reserve";
/// Deposit liquidity into a reserve. The output is a collateral token representing ownership
/// of the reserve liquidity pool.
///
/// 0. `[writable]` Liquidity input SPL Token account. $authority can transfer $liquidity_amount
/// 1. `[writable]` Collateral output SPL Token account,
/// 2. `[writable]` Reserve account.
/// 3. `[writable]` Reserve liquidity supply SPL Token account.
/// 4. `[writable]` Reserve collateral SPL Token mint.
/// 5. `[]` Derived lending market authority ($authority).
/// 6. `[]` Clock sysvar
/// 7. '[]` Token program id
export const depositInstruction = (
liquidityAmount: number | BN,
from: PublicKey, // Liquidity input SPL Token account. $authority can transfer $liquidity_amount
to: PublicKey, // Collateral output SPL Token account,
reserveAuthority: PublicKey,
reserveAccount: PublicKey,
reserveSupply: PublicKey,
collateralMint: PublicKey
): TransactionInstruction => {
const dataLayout = BufferLayout.struct([
BufferLayout.u8("instruction"),
Layout.uint64("liquidityAmount"),
]);
const data = Buffer.alloc(dataLayout.span);
dataLayout.encode(
{
instruction: LendingInstruction.DepositReserveLiquidity,
liquidityAmount: new BN(liquidityAmount),
},
data
);
const keys = [
{ pubkey: from, isSigner: false, isWritable: true },
{ pubkey: to, isSigner: false, isWritable: true },
{ pubkey: reserveAccount, isSigner: false, isWritable: true },
{ pubkey: reserveSupply, isSigner: false, isWritable: true },
{ pubkey: collateralMint, isSigner: false, isWritable: true },
{ pubkey: reserveAuthority, isSigner: false, isWritable: false },
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
];
return new TransactionInstruction({
keys,
programId: LENDING_PROGRAM_ID,
data,
});
};
export const calculateDepositAPY = (reserve: LendingReserve) => {
const totalBorrows = reserve.totalBorrowsWad.div(WAD).toNumber();
const currentUtilization =
totalBorrows / (reserve.totalLiquidity.toNumber() + totalBorrows);
const borrowAPY = calculateBorrowAPY(reserve);
return currentUtilization * borrowAPY;
};

View File

@ -3,3 +3,5 @@ export * from "./reserve";
export * from "./obligation";
export * from "./lending";
export * from "./borrow";
export * from "./deposit";
export * from "./withdraw";

View File

@ -7,7 +7,6 @@ import {
} from "@solana/web3.js";
import BN from "bn.js";
import * as BufferLayout from "buffer-layout";
import { WAD } from "../../constants";
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../../constants/ids";
import * as Layout from "./../../utils/layout";
import { LendingInstruction } from "./lending";
@ -169,119 +168,3 @@ export const initReserveInstruction = (
data,
});
};
/// Deposit liquidity into a reserve. The output is a collateral token representing ownership
/// of the reserve liquidity pool.
///
/// 0. `[writable]` Liquidity input SPL Token account. $authority can transfer $liquidity_amount
/// 1. `[writable]` Collateral output SPL Token account,
/// 2. `[writable]` Reserve account.
/// 3. `[writable]` Reserve liquidity supply SPL Token account.
/// 4. `[writable]` Reserve collateral SPL Token mint.
/// 5. `[]` Derived lending market authority ($authority).
/// 6. `[]` Clock sysvar
/// 7. '[]` Token program id
export const depositInstruction = (
liquidityAmount: number | BN,
from: PublicKey, // Liquidity input SPL Token account. $authority can transfer $liquidity_amount
to: PublicKey, // Collateral output SPL Token account,
reserveAuthority: PublicKey,
reserveAccount: PublicKey,
reserveSupply: PublicKey,
collateralMint: PublicKey
): TransactionInstruction => {
const dataLayout = BufferLayout.struct([
BufferLayout.u8("instruction"),
Layout.uint64("liquidityAmount"),
]);
const data = Buffer.alloc(dataLayout.span);
dataLayout.encode(
{
instruction: LendingInstruction.DepositReserveLiquidity,
liquidityAmount: new BN(liquidityAmount),
},
data
);
const keys = [
{ pubkey: from, isSigner: false, isWritable: true },
{ pubkey: to, isSigner: false, isWritable: true },
{ pubkey: reserveAccount, isSigner: false, isWritable: true },
{ pubkey: reserveSupply, isSigner: false, isWritable: true },
{ pubkey: collateralMint, isSigner: false, isWritable: true },
{ pubkey: reserveAuthority, isSigner: false, isWritable: false },
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
];
return new TransactionInstruction({
keys,
programId: LENDING_PROGRAM_ID,
data,
});
};
export const withdrawInstruction = (
collateralAmount: number | BN,
from: PublicKey, // Collateral input SPL Token account. $authority can transfer $liquidity_amount
to: PublicKey, // Liquidity output SPL Token account,
reserveAccount: PublicKey,
collateralMint: PublicKey,
reserveSupply: PublicKey,
authority: PublicKey
): TransactionInstruction => {
const dataLayout = BufferLayout.struct([
BufferLayout.u8("instruction"),
Layout.uint64("collateralAmount"),
]);
const data = Buffer.alloc(dataLayout.span);
dataLayout.encode(
{
instruction: LendingInstruction.WithdrawReserveLiquidity,
collateralAmount: new BN(collateralAmount),
},
data
);
const keys = [
{ pubkey: from, isSigner: false, isWritable: true },
{ pubkey: to, isSigner: false, isWritable: true },
{ pubkey: reserveAccount, isSigner: false, isWritable: true },
{ pubkey: collateralMint, isSigner: false, isWritable: true },
{ pubkey: reserveSupply, isSigner: false, isWritable: true },
{ pubkey: authority, isSigner: false, isWritable: false },
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
];
return new TransactionInstruction({
keys,
programId: LENDING_PROGRAM_ID,
data,
});
};
export const calculateBorrowAPY = (reserve: LendingReserve) => {
const totalBorrows = reserve.totalBorrowsWad.div(WAD).toNumber();
const currentUtilization =
totalBorrows / (reserve.totalLiquidity.toNumber() + totalBorrows);
const optimalUtilization = reserve.config.optimalUtilizationRate;
let borrowAPY;
if (currentUtilization < optimalUtilization) {
const normalized_factor = currentUtilization / optimalUtilization;
const optimalBorrowRate = reserve.config.optimalBorrowRate / 100;
const minBorrowRate = reserve.config.minBorrowRate / 100;
borrowAPY =
normalized_factor * (optimalBorrowRate - minBorrowRate) + minBorrowRate;
} else {
const normalized_factor =
(currentUtilization - optimalUtilization) / (100 - optimalUtilization);
const optimalBorrowRate = reserve.config.optimalBorrowRate / 100;
const maxBorrowRate = reserve.config.maxBorrowRate / 100;
borrowAPY =
normalized_factor * (maxBorrowRate - optimalBorrowRate) +
optimalBorrowRate;
}
return borrowAPY;
};

View File

@ -0,0 +1,50 @@
import {
PublicKey,
SYSVAR_CLOCK_PUBKEY,
TransactionInstruction,
} from "@solana/web3.js";
import BN from "bn.js";
import * as BufferLayout from "buffer-layout";
import { LENDING_PROGRAM_ID, TOKEN_PROGRAM_ID } from "../../constants/ids";
import * as Layout from "./../../utils/layout";
import { LendingInstruction } from "./lending";
export const withdrawInstruction = (
collateralAmount: number | BN,
from: PublicKey, // Collateral input SPL Token account. $authority can transfer $liquidity_amount
to: PublicKey, // Liquidity output SPL Token account,
reserveAccount: PublicKey,
collateralMint: PublicKey,
reserveSupply: PublicKey,
authority: PublicKey
): TransactionInstruction => {
const dataLayout = BufferLayout.struct([
BufferLayout.u8("instruction"),
Layout.uint64("collateralAmount"),
]);
const data = Buffer.alloc(dataLayout.span);
dataLayout.encode(
{
instruction: LendingInstruction.WithdrawReserveLiquidity,
collateralAmount: new BN(collateralAmount),
},
data
);
const keys = [
{ pubkey: from, isSigner: false, isWritable: true },
{ pubkey: to, isSigner: false, isWritable: true },
{ pubkey: reserveAccount, isSigner: false, isWritable: true },
{ pubkey: collateralMint, isSigner: false, isWritable: true },
{ pubkey: reserveSupply, isSigner: false, isWritable: true },
{ pubkey: authority, isSigner: false, isWritable: false },
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
];
return new TransactionInstruction({
keys,
programId: LENDING_PROGRAM_ID,
data,
});
};

View File

@ -1,4 +1,5 @@
import React from "react";
import { LABELS } from "../../constants";
import { useLendingReserves } from "../../hooks";
import { BorrowItem } from "./item";
import "./itemStyle.less";
@ -8,10 +9,10 @@ export const BorrowView = () => {
return (
<div className="flexColumn">
<div className="borrow-item deposit-header">
<div>Asset</div>
<div>Available fro you</div>
<div>APY</div>
<div>Action</div>
<div>{LABELS.TABLE_TITLE_ASSET}</div>
<div>{LABELS.TABLE_TITLE_MAX_BORROW}</div>
<div>{LABELS.TABLE_TITLE_APR}</div>
<div>{LABELS.TABLE_TITLE_ACTION}</div>
</div>
{reserveAccounts.map((account) => (
<BorrowItem reserve={account.info} address={account.pubkey} />

View File

@ -1,6 +1,6 @@
import React, { useMemo } from "react";
import { useTokenName } from "../../hooks";
import { calculateBorrowAPY, LendingReserve } from "../../models/lending";
import { calculateBorrowAPY, calculateDepositAPY, LendingReserve } from "../../models/lending";
import { TokenIcon } from "../../components/TokenIcon";
import {
wadToLamports,
@ -36,6 +36,12 @@ export const LendingReserveItem = (props: {
props.reserve,
]);
const depositAPY = useMemo(() => calculateDepositAPY(props.reserve), [
props.reserve,
]);
const marketSize = totalLiquidity+totalBorrows;
return (
<Link to={`/reserve/${props.address.toBase58()}`}>
<Card>
@ -44,14 +50,18 @@ export const LendingReserveItem = (props: {
<TokenIcon mintAddress={props.reserve.liquidityMint} />
{name}
</span>
<div>
{formatNumber.format(totalLiquidity+totalBorrows)} {name}
<div title={marketSize.toString()}>
{formatNumber.format(marketSize)} {name}
</div>
<div>
<div title={totalBorrows.toString()}>
{formatNumber.format(totalBorrows)} {name}
</div>
<div>--</div>
<div>{formatPct.format(borrowAPY)}</div>
<div title={depositAPY.toString()}>
{formatPct.format(depositAPY)}
</div>
<div title={borrowAPY.toString()}>
{formatPct.format(borrowAPY)}
</div>
</div>
</Card>
</Link>