Mp 3214 calculating account apr (#391)
This commit is contained in:
parent
160f8da6aa
commit
997171a185
@ -1,4 +1,3 @@
|
||||
import { render } from '@testing-library/react'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { ChevronDown, ChevronRight } from 'components/Icons'
|
||||
@ -38,7 +37,7 @@ export default function AccordionContent(props: Props) {
|
||||
</Text>
|
||||
</div>
|
||||
<div className='block pr-1 group-[[open]]:hidden'>
|
||||
{isOpen ? <ChevronDown /> : <ChevronRight />}
|
||||
{isOpen ? <ChevronDown className='h-1.5' /> : <ChevronRight className='w-1.5' />}
|
||||
</div>
|
||||
</div>
|
||||
{isOpen && <div className='bg-white/5 transition-[padding]'>{renderContent()}</div>}
|
||||
|
@ -118,7 +118,7 @@ export default function AccountBalancesTable(props: Props) {
|
||||
denom: row.original.denom,
|
||||
amount: row.original.amount.toString(),
|
||||
})
|
||||
return <DisplayCurrency coin={coin} className='text-right text-xs' />
|
||||
return <DisplayCurrency coin={coin} className='text-xs text-right' />
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -132,7 +132,7 @@ export default function AccountBalancesTable(props: Props) {
|
||||
)
|
||||
return (
|
||||
<FormattedNumber
|
||||
className='text-right text-xs'
|
||||
className='text-xs text-right'
|
||||
amount={Number(BN(amount).abs())}
|
||||
options={{ maxDecimals: 2, abbreviated: true }}
|
||||
animate
|
||||
@ -145,6 +145,8 @@ export default function AccountBalancesTable(props: Props) {
|
||||
accessorKey: 'apy',
|
||||
header: 'APY',
|
||||
cell: ({ row }) => {
|
||||
if (row.original.apy === 0)
|
||||
return <span className='w-full text-xs text-center'>–</span>
|
||||
return (
|
||||
<FormattedNumber
|
||||
className='text-xs'
|
||||
@ -193,7 +195,7 @@ export default function AccountBalancesTable(props: Props) {
|
||||
'align-center',
|
||||
)}
|
||||
>
|
||||
<span className='h-6 w-6 text-white'>
|
||||
<span className='w-6 h-6 text-white'>
|
||||
{header.column.getCanSort()
|
||||
? {
|
||||
asc: <SortAsc />,
|
||||
|
@ -8,13 +8,13 @@ import { ArrowRight } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
import { ORACLE_DENOM } from 'constants/oracle'
|
||||
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
|
||||
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
|
||||
import usePrices from 'hooks/usePrices'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import {
|
||||
calculateAccountApr,
|
||||
calculateAccountBalanceValue,
|
||||
calculateAccountBorrowRate,
|
||||
calculateAccountPnL,
|
||||
getAccountPositionValues,
|
||||
} from 'utils/accounts'
|
||||
|
||||
@ -35,6 +35,18 @@ interface ItemProps {
|
||||
export default function AccountComposition(props: Props) {
|
||||
const { account, change } = props
|
||||
const { data: prices } = usePrices()
|
||||
const { availableAssets: borrowAvailableAssets, accountBorrowedAssets } =
|
||||
useBorrowMarketAssetsTableData()
|
||||
const { availableAssets: lendingAvailableAssets, accountLentAssets } =
|
||||
useLendingMarketAssetsTableData()
|
||||
const borrowAssetsData = useMemo(
|
||||
() => [...borrowAvailableAssets, ...accountBorrowedAssets],
|
||||
[borrowAvailableAssets, accountBorrowedAssets],
|
||||
)
|
||||
const lendingAssetsData = useMemo(
|
||||
() => [...lendingAvailableAssets, ...accountLentAssets],
|
||||
[lendingAvailableAssets, accountLentAssets],
|
||||
)
|
||||
|
||||
const [depositsBalance, lendsBalance, debtsBalance] = useMemo(
|
||||
() => getAccountPositionValues(account, prices),
|
||||
@ -52,16 +64,28 @@ export default function AccountComposition(props: Props) {
|
||||
() => (change ? calculateAccountBalanceValue(change, prices) : BN_ZERO),
|
||||
[change, prices],
|
||||
)
|
||||
|
||||
const balance = depositsBalance.plus(lendsBalance)
|
||||
const balanceChange = depositsBalanceChange.plus(lendsBalanceChange)
|
||||
const apr = calculateAccountApr(account, prices)
|
||||
const aprChange = change ? calculateAccountPnL(change, prices) : BN_ZERO
|
||||
const borrowRate = calculateAccountBorrowRate(account, prices)
|
||||
const borrowRateChange = change ? calculateAccountPnL(change, prices) : BN_ZERO
|
||||
const apr = useMemo(
|
||||
() => calculateAccountApr(account, totalBalance, borrowAssetsData, lendingAssetsData, prices),
|
||||
[account, totalBalance, borrowAssetsData, lendingAssetsData, prices],
|
||||
)
|
||||
const aprChange = useMemo(
|
||||
() =>
|
||||
change
|
||||
? calculateAccountApr(
|
||||
change,
|
||||
totalBalance.plus(totalBalanceChange),
|
||||
borrowAssetsData,
|
||||
lendingAssetsData,
|
||||
prices,
|
||||
)
|
||||
: BN_ZERO,
|
||||
[change, totalBalance, totalBalanceChange, borrowAssetsData, lendingAssetsData, prices],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className='w-full flex-wrap p-4'>
|
||||
<div className='flex-wrap w-full p-4'>
|
||||
<Item
|
||||
title='Total Position Value'
|
||||
current={balance}
|
||||
@ -79,16 +103,9 @@ export default function AccountComposition(props: Props) {
|
||||
title='Total Balance'
|
||||
current={totalBalance}
|
||||
change={totalBalance.plus(totalBalanceChange)}
|
||||
className='border border-transparent border-y-white/20 py-3 font-bold'
|
||||
className='py-3 font-bold border border-transparent border-y-white/20'
|
||||
/>
|
||||
<Item title='APR' current={apr} change={apr.plus(aprChange)} className='py-3' isPercentage />
|
||||
<Item
|
||||
title='Borrow Rate'
|
||||
current={borrowRate}
|
||||
change={borrowRate.plus(borrowRateChange)}
|
||||
isPercentage
|
||||
isDecrease
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -99,12 +116,12 @@ function Item(props: ItemProps) {
|
||||
|
||||
return (
|
||||
<div className={classNames('flex w-full flex-nowrap', props.className)}>
|
||||
<div className='flex flex-shrink items-center'>
|
||||
<div className='flex items-center flex-shrink'>
|
||||
<Text size='sm' className='text-white/60'>
|
||||
{props.title}
|
||||
</Text>
|
||||
</div>
|
||||
<div className='flex flex-1 items-center justify-end gap-2'>
|
||||
<div className='flex items-center justify-end flex-1 gap-2'>
|
||||
{props.isPercentage ? (
|
||||
<FormattedNumber
|
||||
amount={current.toNumber()}
|
||||
|
@ -7,13 +7,18 @@ import { Gauge } from 'components/Gauge'
|
||||
import { ArrowRight, Heart } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import { ORACLE_DENOM } from 'constants/oracle'
|
||||
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
|
||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||
import useHealthComputer from 'hooks/useHealthComputer'
|
||||
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
|
||||
import usePrices from 'hooks/usePrices'
|
||||
import useStore from 'store'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { calculateAccountBalanceValue, calculateAccountLeverage } from 'utils/accounts'
|
||||
import { formatLeverage } from 'utils/formatters'
|
||||
import {
|
||||
calculateAccountApr,
|
||||
calculateAccountBalanceValue,
|
||||
calculateAccountLeverage,
|
||||
} from 'utils/accounts'
|
||||
|
||||
export default function AccountDetailsController() {
|
||||
const account = useCurrentAccount()
|
||||
@ -34,22 +39,44 @@ function AccountDetails(props: Props) {
|
||||
const { health } = useHealthComputer(props.account)
|
||||
const { health: updatedHealth } = useHealthComputer(updatedAccount)
|
||||
const { data: prices } = usePrices()
|
||||
const accountBalanceValue = calculateAccountBalanceValue(
|
||||
updatedAccount ? updatedAccount : props.account,
|
||||
prices,
|
||||
const accountBalanceValue = useMemo(
|
||||
() => calculateAccountBalanceValue(updatedAccount ? updatedAccount : props.account, prices),
|
||||
[updatedAccount, props.account, prices],
|
||||
)
|
||||
const coin = BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, accountBalanceValue)
|
||||
const leverage = useMemo(
|
||||
() => calculateAccountLeverage(updatedAccount ? updatedAccount : props.account, prices),
|
||||
[props.account, updatedAccount, prices],
|
||||
)
|
||||
|
||||
const { availableAssets: borrowAvailableAssets, accountBorrowedAssets } =
|
||||
useBorrowMarketAssetsTableData()
|
||||
const { availableAssets: lendingAvailableAssets, accountLentAssets } =
|
||||
useLendingMarketAssetsTableData()
|
||||
const borrowAssetsData = useMemo(
|
||||
() => [...borrowAvailableAssets, ...accountBorrowedAssets],
|
||||
[borrowAvailableAssets, accountBorrowedAssets],
|
||||
)
|
||||
const lendingAssetsData = useMemo(
|
||||
() => [...lendingAvailableAssets, ...accountLentAssets],
|
||||
[lendingAvailableAssets, accountLentAssets],
|
||||
)
|
||||
const apr = useMemo(
|
||||
() =>
|
||||
calculateAccountApr(
|
||||
props.account,
|
||||
accountBalanceValue,
|
||||
borrowAssetsData,
|
||||
lendingAssetsData,
|
||||
prices,
|
||||
),
|
||||
[props.account, accountBalanceValue, borrowAssetsData, lendingAssetsData, prices],
|
||||
)
|
||||
return (
|
||||
<div
|
||||
data-testid='account-details'
|
||||
className='w-16 rounded-base border border-white/20 bg-white/5 backdrop-blur-sticky'
|
||||
className='w-16 border rounded-base border-white/20 bg-white/5 backdrop-blur-sticky'
|
||||
>
|
||||
<div className='flex w-full flex-wrap justify-center py-4'>
|
||||
<div className='flex flex-wrap justify-center w-full py-4'>
|
||||
<Gauge tooltip='Health Factor' percentage={health} icon={<Heart />} />
|
||||
<Text size='2xs' className='mb-0.5 mt-1 w-full text-center text-white/50'>
|
||||
Health
|
||||
@ -77,19 +104,35 @@ function AccountDetails(props: Props) {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='w-full border-t border-white/20 py-4'>
|
||||
<div className='w-full py-4 border-t border-white/20'>
|
||||
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50'>
|
||||
Leverage
|
||||
</Text>
|
||||
<Text size='xs' className='text-center'>
|
||||
{formatLeverage(leverage.toNumber())}
|
||||
<Text size='2xs' className='text-center'>
|
||||
<FormattedNumber
|
||||
className={'w-full text-center text-2xs'}
|
||||
amount={leverage.toNumber()}
|
||||
options={{ maxDecimals: 2, minDecimals: 2, suffix: 'x' }}
|
||||
animate
|
||||
/>
|
||||
</Text>
|
||||
</div>
|
||||
<div className='w-full border-t border-white/20 py-4'>
|
||||
<div className='w-full py-4 border-t border-white/20'>
|
||||
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50'>
|
||||
Net worth
|
||||
</Text>
|
||||
<DisplayCurrency coin={coin} className='w-full truncate text-center text-2xs ' />
|
||||
<DisplayCurrency coin={coin} className='w-full text-center truncate text-2xs ' />
|
||||
</div>
|
||||
<div className='w-full py-4 border-t border-white/20'>
|
||||
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50'>
|
||||
APR
|
||||
</Text>
|
||||
<FormattedNumber
|
||||
className={'w-full text-center text-2xs'}
|
||||
amount={apr.toNumber()}
|
||||
options={{ maxDecimals: 2, minDecimals: 2, suffix: '%' }}
|
||||
animate
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -2,6 +2,7 @@ import classNames from 'classnames'
|
||||
|
||||
import { Heart } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
@ -18,7 +19,7 @@ export default function AccountHealth(props: Props) {
|
||||
|
||||
return (
|
||||
<div className={classNames('flex items-center gap-1.5', props.classNames)}>
|
||||
<div className='flex h-4 w-3 flex-auto'>
|
||||
<div className='flex flex-auto w-3 h-4'>
|
||||
<Heart />
|
||||
</div>
|
||||
{props.hasLabel && (
|
||||
@ -27,32 +28,34 @@ export default function AccountHealth(props: Props) {
|
||||
</Text>
|
||||
)}
|
||||
<div className='flex flex-shrink'>
|
||||
<svg width='100%' viewBox='0 0 53 4' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<rect width='53' height='4' rx='2' fill='white' fillOpacity='0.1' />
|
||||
<rect
|
||||
width={healthBarWidth}
|
||||
height='4'
|
||||
rx='2'
|
||||
fill='url(#bar)'
|
||||
style={{
|
||||
transition: reduceMotion ? 'none' : 'width 1s ease',
|
||||
}}
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id='bar'
|
||||
x1='0%'
|
||||
y1='0%'
|
||||
x2='100%'
|
||||
y2='0%'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
>
|
||||
<stop stopColor='#FF645F' />
|
||||
<stop offset='0.5' stopColor='#FFB1AE' />
|
||||
<stop offset='1' stopColor='#FFD5D3' />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<Tooltip content={`Account Health: ${props.health}%`} type='info'>
|
||||
<svg width='100%' viewBox='0 0 53 4' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<rect width='53' height='4' rx='2' fill='white' fillOpacity='0.1' />
|
||||
<rect
|
||||
width={healthBarWidth}
|
||||
height='4'
|
||||
rx='2'
|
||||
fill='url(#bar)'
|
||||
style={{
|
||||
transition: reduceMotion ? 'none' : 'width 1s ease',
|
||||
}}
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id='bar'
|
||||
x1='0%'
|
||||
y1='0%'
|
||||
x2='100%'
|
||||
y2='0%'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
>
|
||||
<stop stopColor='#FF645F' />
|
||||
<stop offset='0.5' stopColor='#FFB1AE' />
|
||||
<stop offset='1' stopColor='#FFD5D3' />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -13,7 +13,7 @@ import Text from 'components/Text'
|
||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||
import usePrices from 'hooks/usePrices'
|
||||
import useStore from 'store'
|
||||
import { calculateAccountValue } from 'utils/accounts'
|
||||
import { calculateAccountBalanceValue } from 'utils/accounts'
|
||||
import { getPage, getRoute } from 'utils/route'
|
||||
|
||||
interface Props {
|
||||
@ -52,7 +52,7 @@ export default function AccountList(props: Props) {
|
||||
return (
|
||||
<div className='flex flex-wrap w-full p-4'>
|
||||
{props.accounts.map((account) => {
|
||||
const positionBalance = calculateAccountValue('deposits', account, prices)
|
||||
const positionBalance = calculateAccountBalanceValue(account, prices)
|
||||
const isActive = accountId === account.id
|
||||
|
||||
return (
|
||||
|
@ -1,5 +1,5 @@
|
||||
import classNames from 'classnames'
|
||||
import { Suspense } from 'react'
|
||||
import { Suspense, useMemo } from 'react'
|
||||
|
||||
import AccountBalancesTable from 'components/Account/AccountBalancesTable'
|
||||
import AccountComposition from 'components/Account/AccountComposition'
|
||||
@ -18,13 +18,19 @@ function Content() {
|
||||
useBorrowMarketAssetsTableData()
|
||||
const { availableAssets: lendingAvailableAssets, accountLentAssets } =
|
||||
useLendingMarketAssetsTableData()
|
||||
const borrowAssetsData = [...borrowAvailableAssets, ...accountBorrowedAssets]
|
||||
const lendingAssetsData = [...lendingAvailableAssets, ...accountLentAssets]
|
||||
const borrowAssetsData = useMemo(
|
||||
() => [...borrowAvailableAssets, ...accountBorrowedAssets],
|
||||
[borrowAvailableAssets, accountBorrowedAssets],
|
||||
)
|
||||
const lendingAssetsData = useMemo(
|
||||
() => [...lendingAvailableAssets, ...accountLentAssets],
|
||||
[lendingAvailableAssets, accountLentAssets],
|
||||
)
|
||||
|
||||
if (!address) {
|
||||
return (
|
||||
<Card
|
||||
className='h-fit w-full justify-center bg-white/5'
|
||||
className='justify-center w-full h-fit bg-white/5'
|
||||
title='Portfolio'
|
||||
contentClassName='px-4 py-6'
|
||||
>
|
||||
@ -40,9 +46,9 @@ function Content() {
|
||||
className={classNames('grid w-full grid-cols-1 gap-4', 'md:grid-cols-2', 'lg:grid-cols-3')}
|
||||
>
|
||||
{account.map((account: Account, index: number) => (
|
||||
<Card className='h-fit w-full bg-white/5' title={`Account ${account.id}`} key={index}>
|
||||
<Card className='w-full h-fit bg-white/5' title={`Account ${account.id}`} key={index}>
|
||||
<AccountComposition account={account} />
|
||||
<Text className='mt-3 w-full bg-white/10 px-4 py-2 text-white/40'>Balances</Text>
|
||||
<Text className='w-full px-4 py-2 mt-3 bg-white/10 text-white/40'>Balances</Text>
|
||||
<AccountBalancesTable
|
||||
account={account}
|
||||
borrowingData={borrowAssetsData}
|
||||
@ -61,12 +67,12 @@ function Fallback() {
|
||||
className={classNames('grid w-full grid-cols-1 gap-4', 'md:grid-cols-2', 'lg:grid-cols-3')}
|
||||
>
|
||||
{Array.from({ length: cardCount }, (_, i) => (
|
||||
<Card key={i} className='h-fit w-full bg-white/5' title='Account' contentClassName='py-6'>
|
||||
<Card key={i} className='w-full h-fit bg-white/5' title='Account' contentClassName='py-6'>
|
||||
<div className='p-4'>
|
||||
<Loading className='h-4 w-50' />
|
||||
</div>
|
||||
<Text className='mt-3 w-full bg-white/10 px-4 py-2 text-white/40'>Balances</Text>
|
||||
<Loading className='h-4 w-full' />
|
||||
<Text className='w-full px-4 py-2 mt-3 bg-white/10 text-white/40'>Balances</Text>
|
||||
<Loading className='w-full h-4' />
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
@ -1,12 +1,16 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import AccountHealth from 'components/Account/AccountHealth'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { ArrowChartLineUp } from 'components/Icons'
|
||||
import { ORACLE_DENOM } from 'constants/oracle'
|
||||
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
|
||||
import useHealthComputer from 'hooks/useHealthComputer'
|
||||
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
|
||||
import usePrices from 'hooks/usePrices'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { calculateAccountValue } from 'utils/accounts'
|
||||
import { formatHealth } from 'utils/formatters'
|
||||
import { BN } from 'utils/helpers'
|
||||
import { calculateAccountApr, calculateAccountBalanceValue } from 'utils/accounts'
|
||||
|
||||
interface Props {
|
||||
account: Account
|
||||
@ -14,17 +18,50 @@ interface Props {
|
||||
|
||||
export default function AccountStats(props: Props) {
|
||||
const { data: prices } = usePrices()
|
||||
const positionBalance = calculateAccountValue('deposits', props.account, prices)
|
||||
const positionBalance = useMemo(
|
||||
() => calculateAccountBalanceValue(props.account, prices),
|
||||
[props.account, prices],
|
||||
)
|
||||
const { health } = useHealthComputer(props.account)
|
||||
const healthFactor = BN(100).minus(formatHealth(health)).toNumber()
|
||||
const { availableAssets: borrowAvailableAssets, accountBorrowedAssets } =
|
||||
useBorrowMarketAssetsTableData()
|
||||
const { availableAssets: lendingAvailableAssets, accountLentAssets } =
|
||||
useLendingMarketAssetsTableData()
|
||||
const borrowAssetsData = useMemo(
|
||||
() => [...borrowAvailableAssets, ...accountBorrowedAssets],
|
||||
[borrowAvailableAssets, accountBorrowedAssets],
|
||||
)
|
||||
const lendingAssetsData = useMemo(
|
||||
() => [...lendingAvailableAssets, ...accountLentAssets],
|
||||
[lendingAvailableAssets, accountLentAssets],
|
||||
)
|
||||
const apr = useMemo(
|
||||
() =>
|
||||
calculateAccountApr(
|
||||
props.account,
|
||||
positionBalance,
|
||||
borrowAssetsData,
|
||||
lendingAssetsData,
|
||||
prices,
|
||||
),
|
||||
[props.account, positionBalance, borrowAssetsData, lendingAssetsData, prices],
|
||||
)
|
||||
return (
|
||||
<div className='w-full flex-wrap'>
|
||||
<div className='flex-wrap w-full'>
|
||||
<DisplayCurrency
|
||||
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, positionBalance)}
|
||||
className='w-full text-xl'
|
||||
/>
|
||||
<div className='mt-1 flex w-full items-center'>
|
||||
<AccountHealth health={healthFactor} classNames='w-[140px]' hasLabel />
|
||||
<div className='flex items-center justify-between w-full mt-1'>
|
||||
<span className='flex flex-wrap'>
|
||||
<ArrowChartLineUp className='w-4 mr-1' />
|
||||
<FormattedNumber
|
||||
className='text-xs text-white/70'
|
||||
amount={apr.toNumber()}
|
||||
options={{ prefix: 'APR: ', suffix: '%', minDecimals: 2, maxDecimals: 2 }}
|
||||
/>
|
||||
</span>
|
||||
<AccountHealth health={health} classNames='w-[140px]' hasLabel />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -1,9 +1,13 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import Accordion from 'components/Accordion'
|
||||
import AccountBalancesTable from 'components/Account/AccountBalancesTable'
|
||||
import AccountComposition from 'components/Account/AccountComposition'
|
||||
import AccountHealth from 'components/Account/AccountHealth'
|
||||
import Card from 'components/Card'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import Text from 'components/Text'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
import { ORACLE_DENOM } from 'constants/oracle'
|
||||
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
|
||||
@ -12,9 +16,7 @@ import useIsOpenArray from 'hooks/useIsOpenArray'
|
||||
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
|
||||
import usePrices from 'hooks/usePrices'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { calculateAccountValue } from 'utils/accounts'
|
||||
import { formatHealth } from 'utils/formatters'
|
||||
import { BN } from 'utils/helpers'
|
||||
import { calculateAccountBalanceValue, calculateAccountLeverage } from 'utils/accounts'
|
||||
|
||||
interface Props {
|
||||
account?: Account
|
||||
@ -24,31 +26,49 @@ interface Props {
|
||||
export default function AccountSummary(props: Props) {
|
||||
const [isOpen, toggleOpen] = useIsOpenArray(2, true)
|
||||
const { data: prices } = usePrices()
|
||||
const accountBalance = props.account
|
||||
? calculateAccountValue('deposits', props.account, prices)
|
||||
: BN_ZERO
|
||||
const accountBalance = useMemo(
|
||||
() => (props.account ? calculateAccountBalanceValue(props.account, prices) : BN_ZERO),
|
||||
[props.account, prices],
|
||||
)
|
||||
const { availableAssets: borrowAvailableAssets, accountBorrowedAssets } =
|
||||
useBorrowMarketAssetsTableData()
|
||||
const { availableAssets: lendingAvailableAssets, accountLentAssets } =
|
||||
useLendingMarketAssetsTableData()
|
||||
const borrowAssetsData = [...borrowAvailableAssets, ...accountBorrowedAssets]
|
||||
const lendingAssetsData = [...lendingAvailableAssets, ...accountLentAssets]
|
||||
const borrowAssetsData = useMemo(
|
||||
() => [...borrowAvailableAssets, ...accountBorrowedAssets],
|
||||
[borrowAvailableAssets, accountBorrowedAssets],
|
||||
)
|
||||
const lendingAssetsData = useMemo(
|
||||
() => [...lendingAvailableAssets, ...accountLentAssets],
|
||||
[lendingAvailableAssets, accountLentAssets],
|
||||
)
|
||||
|
||||
const { health } = useHealthComputer(props.account)
|
||||
const healthFactor = BN(100).minus(formatHealth(health)).toNumber()
|
||||
const leverage = useMemo(
|
||||
() => (props.account ? calculateAccountLeverage(props.account, prices) : BN_ZERO),
|
||||
[props.account, prices],
|
||||
)
|
||||
if (!props.account) return null
|
||||
|
||||
return (
|
||||
<div className='h-[546px] min-w-[345px] basis-[345px] overflow-y-scroll scrollbar-hide'>
|
||||
<Card className='mb-4 h-min min-w-fit bg-white/10' contentClassName='flex'>
|
||||
<Item>
|
||||
<Item title='Networth'>
|
||||
<DisplayCurrency
|
||||
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, accountBalance)}
|
||||
className='text-sm'
|
||||
className='text-xs'
|
||||
/>
|
||||
</Item>
|
||||
<Item>
|
||||
<AccountHealth health={healthFactor} />
|
||||
<Item title='Leverage'>
|
||||
<FormattedNumber
|
||||
className='text-xs'
|
||||
amount={leverage.toNumber()}
|
||||
options={{ minDecimals: 2, maxDecimals: 2, suffix: 'x' }}
|
||||
animate
|
||||
/>
|
||||
</Item>
|
||||
<Item title='Account Health'>
|
||||
<AccountHealth health={health} />
|
||||
</Item>
|
||||
</Card>
|
||||
<Accordion
|
||||
@ -87,9 +107,12 @@ export default function AccountSummary(props: Props) {
|
||||
function Item(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className='flex items-center justify-center flex-1 gap-1 px-2 py-2 border-r border-r-white/10'
|
||||
className='flex flex-wrap items-center gap-1 px-4 py-2 border-r border-r-white/10'
|
||||
{...props}
|
||||
>
|
||||
<Text size='xs' className='w-full text-white/50'>
|
||||
{props.title}
|
||||
</Text>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
|
@ -35,6 +35,7 @@ export const FormattedNumber = React.memo(
|
||||
if (
|
||||
(prevAmountRef.current === props.amount && props.amount === 0) ||
|
||||
!props.animate ||
|
||||
prevAmountRef.current === 0 ||
|
||||
reduceMotion
|
||||
)
|
||||
return (
|
||||
|
@ -10,8 +10,8 @@ import classNames from 'classnames'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { SortAsc, SortDesc, SortNone } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import useAssetTableColumns from 'components/Modals/AssetsSelect/useAssetTableColumns'
|
||||
import Text from 'components/Text'
|
||||
import useStore from 'store'
|
||||
import { byDenom } from 'utils/array'
|
||||
|
||||
@ -97,7 +97,7 @@ export default function AssetSelectTable(props: Props) {
|
||||
'align-center',
|
||||
)}
|
||||
>
|
||||
<span className='h-6 w-6 text-white'>
|
||||
<span className='w-6 h-6 text-white'>
|
||||
{header.column.getCanSort()
|
||||
? {
|
||||
asc: <SortAsc />,
|
||||
|
@ -38,13 +38,13 @@ export default function useAssetTableColumns() {
|
||||
header: (data) => {
|
||||
const tableData = data.table.options.data as AssetTableRow[]
|
||||
const assetData = tableData.length && (tableData[0].asset as BorrowAsset)
|
||||
if (assetData && assetData.borrowRate !== null) return 'Borrow Rate'
|
||||
if (assetData && assetData?.borrowRate) return 'Borrow Rate'
|
||||
return 'Balance'
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const asset = row.original.asset as BorrowAsset
|
||||
const balance = row.original.balance
|
||||
if (asset.borrowRate !== null)
|
||||
if (asset?.borrowRate)
|
||||
return (
|
||||
<Text size='sm' className='mb-0.5 text-white'>
|
||||
{formatPercent(asset.borrowRate ?? 0)}
|
||||
|
@ -7,6 +7,7 @@ import Text from 'components/Text'
|
||||
import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider'
|
||||
import WalletBridges from 'components/Wallet/WalletBridges'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
import useAutoLendEnabledAccountIds from 'hooks/useAutoLendEnabledAccountIds'
|
||||
import useToggle from 'hooks/useToggle'
|
||||
import useWalletBalances from 'hooks/useWalletBalances'
|
||||
import useStore from 'store'
|
||||
@ -34,6 +35,8 @@ export default function FundAccount(props: Props) {
|
||||
const hasAssetSelected = fundingAssets.length > 0
|
||||
const hasFundingAssets =
|
||||
fundingAssets.length > 0 && fundingAssets.every((a) => a.toCoin().amount !== '0')
|
||||
const { autoLendEnabledAccountIds } = useAutoLendEnabledAccountIds()
|
||||
const isAutoLendEnabled = autoLendEnabledAccountIds.includes(accountId)
|
||||
|
||||
const baseBalance = useMemo(
|
||||
() => walletBalances.find(byDenom(baseAsset.denom))?.amount ?? '0',
|
||||
@ -60,6 +63,7 @@ export default function FundAccount(props: Props) {
|
||||
walletAssetsModal: {
|
||||
isOpen: true,
|
||||
selectedDenoms,
|
||||
isBorrow: false,
|
||||
},
|
||||
})
|
||||
}, [selectedDenoms])
|
||||
@ -87,13 +91,17 @@ export default function FundAccount(props: Props) {
|
||||
if (assetToUpdateIdx > -1) {
|
||||
prevAssets[assetToUpdateIdx].amount = amount
|
||||
}
|
||||
setChange({ deposits: prevAssets })
|
||||
setChange({ [isAutoLendEnabled ? "lends" : "deposits"]: prevAssets })
|
||||
return prevAssets
|
||||
})
|
||||
},
|
||||
[setChange],
|
||||
[setChange, isAutoLendEnabled],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setChange({ [isAutoLendEnabled ? "lends" : "deposits"]: fundingAssets })
|
||||
}, [isAutoLendEnabled, fundingAssets, setChange])
|
||||
|
||||
useEffect(() => {
|
||||
if (BN(baseBalance).isLessThan(defaultFee.amount[0].amount)) {
|
||||
useStore.setState({ focusComponent: { component: <WalletBridges /> } })
|
||||
|
@ -15,9 +15,9 @@ export default function FundWithdrawModalContent(props: Props) {
|
||||
const [change, setChange] = useState<AccountChange | undefined>()
|
||||
|
||||
return (
|
||||
<div className='flex flex-1 items-start gap-6 p-6'>
|
||||
<div className='flex items-start flex-1 gap-6 p-6'>
|
||||
<Card
|
||||
className='flex flex-1 bg-white/5 p-4'
|
||||
className='flex flex-1 p-4 bg-white/5'
|
||||
contentClassName='gap-6 flex flex-col justify-between h-full min-h-[380px]'
|
||||
>
|
||||
{isFunding ? (
|
||||
|
@ -67,7 +67,7 @@ export default function TokenInputWithSlider(props: Props) {
|
||||
accountId={props.accountId}
|
||||
/>
|
||||
<Slider
|
||||
value={percentage}
|
||||
value={percentage || 0}
|
||||
onChange={(value) => onChangeSlider(value)}
|
||||
disabled={props.disabled}
|
||||
/>
|
||||
|
@ -1,7 +1,9 @@
|
||||
import Card from 'components/Card'
|
||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import AccountBalancesTable from 'components/Account/AccountBalancesTable'
|
||||
import Card from 'components/Card'
|
||||
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
|
||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
|
||||
|
||||
export default function AccountDetailsCard() {
|
||||
@ -10,8 +12,14 @@ export default function AccountDetailsCard() {
|
||||
useBorrowMarketAssetsTableData()
|
||||
const { availableAssets: lendingAvailableAssets, accountLentAssets } =
|
||||
useLendingMarketAssetsTableData()
|
||||
const borrowAssetsData = [...borrowAvailableAssets, ...accountBorrowedAssets]
|
||||
const lendingAssetsData = [...lendingAvailableAssets, ...accountLentAssets]
|
||||
const borrowAssetsData = useMemo(
|
||||
() => [...borrowAvailableAssets, ...accountBorrowedAssets],
|
||||
[borrowAvailableAssets, accountBorrowedAssets],
|
||||
)
|
||||
const lendingAssetsData = useMemo(
|
||||
() => [...lendingAvailableAssets, ...accountLentAssets],
|
||||
[lendingAvailableAssets, accountLentAssets],
|
||||
)
|
||||
|
||||
const tabs = (
|
||||
<div className={className.tabWrapper}>
|
||||
|
1
src/types/interfaces/store/modals.d.ts
vendored
1
src/types/interfaces/store/modals.d.ts
vendored
@ -57,4 +57,5 @@ interface UnlockModal {
|
||||
interface WalletAssetModal {
|
||||
isOpen?: boolean
|
||||
selectedDenoms: string[]
|
||||
isBorrow?: boolean
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
} from 'types/generated/mars-credit-manager/MarsCreditManager.types'
|
||||
import { getAssetByDenom } from 'utils/assets'
|
||||
import { BN } from 'utils/helpers'
|
||||
import { convertApyToApr } from 'utils/parsers'
|
||||
|
||||
export const calculateAccountBalanceValue = (
|
||||
account: Account | AccountChange,
|
||||
@ -34,7 +35,7 @@ export const calculateAccountValue = (
|
||||
account: Account | AccountChange,
|
||||
prices: BNCoin[],
|
||||
): BigNumber => {
|
||||
if (!account[type]) return BN_ZERO
|
||||
if (!account[type] || !prices) return BN_ZERO
|
||||
|
||||
if (type === 'vaults') {
|
||||
return (
|
||||
@ -56,18 +57,62 @@ export const calculateAccountValue = (
|
||||
}, BN_ZERO)
|
||||
}
|
||||
|
||||
export const calculateAccountPnL = (
|
||||
account: Account | AccountChange,
|
||||
prices: BNCoin[],
|
||||
): BigNumber => {
|
||||
return BN_ZERO
|
||||
}
|
||||
|
||||
export const calculateAccountApr = (
|
||||
account: Account | AccountChange,
|
||||
totalValue: BigNumber,
|
||||
borrowAssetsData: BorrowMarketTableData[],
|
||||
lendingAssetsData: LendingMarketTableData[],
|
||||
prices: BNCoin[],
|
||||
): BigNumber => {
|
||||
return BN_ZERO
|
||||
if (totalValue.isZero()) return BN_ZERO
|
||||
const { vaults, lends, debts } = account
|
||||
|
||||
let totalLendsInterestValue = BN_ZERO
|
||||
let totalVaultsInterestValue = BN_ZERO
|
||||
let totalDeptsInterestValue = BN_ZERO
|
||||
|
||||
lends?.forEach((lend) => {
|
||||
const asset = getAssetByDenom(lend.denom)
|
||||
if (!asset) return BN_ZERO
|
||||
const price = prices.find((price) => price.denom === lend.denom)?.amount ?? 0
|
||||
const amount = BN(lend.amount).shiftedBy(-asset.decimals)
|
||||
const apr =
|
||||
lendingAssetsData.find((lendingAsset) => lendingAsset.asset.denom === lend.denom)
|
||||
?.marketLiquidityRate ?? 0
|
||||
const positionInterest = amount.multipliedBy(price).multipliedBy(apr)
|
||||
totalLendsInterestValue = totalLendsInterestValue.plus(positionInterest)
|
||||
})
|
||||
|
||||
vaults?.forEach((vault) => {
|
||||
const asset = getAssetByDenom(vault.denoms.lp)
|
||||
if (!asset) return BN_ZERO
|
||||
const price = prices.find((price) => price.denom === vault.denoms.lp)?.amount ?? 0
|
||||
const amount = BN(vault.amounts.locked).shiftedBy(-asset.decimals)
|
||||
const positionInterest = amount
|
||||
.multipliedBy(price)
|
||||
.multipliedBy(convertApyToApr(vault?.apy ?? 0, 365))
|
||||
totalVaultsInterestValue = totalVaultsInterestValue.plus(positionInterest)
|
||||
})
|
||||
|
||||
debts?.forEach((debt) => {
|
||||
const asset = getAssetByDenom(debt.denom)
|
||||
if (!asset) return BN_ZERO
|
||||
const price = prices.find((price) => price.denom === debt.denom)?.amount ?? 0
|
||||
const amount = BN(debt.amount).shiftedBy(-asset.decimals)
|
||||
const apr =
|
||||
borrowAssetsData.find((borrowAsset) => borrowAsset.asset.denom === debt.denom)
|
||||
?.marketLiquidityRate ?? 0
|
||||
const positionInterest = amount.multipliedBy(price).multipliedBy(apr)
|
||||
totalDeptsInterestValue = totalDeptsInterestValue.plus(positionInterest)
|
||||
})
|
||||
|
||||
const totalPositiveInterestValue = totalLendsInterestValue
|
||||
.plus(totalVaultsInterestValue)
|
||||
.minus(totalDeptsInterestValue)
|
||||
|
||||
const totalApr = totalPositiveInterestValue.dividedBy(totalValue).times(100)
|
||||
|
||||
return totalApr
|
||||
}
|
||||
|
||||
export const calculateAccountBorrowRate = (
|
||||
|
@ -16,6 +16,12 @@ export const convertAprToApy = (apr: number, numberOfCompoundingPeriods: number)
|
||||
return ((1 + apr / 100 / numberOfCompoundingPeriods) ** numberOfCompoundingPeriods - 1) * 100
|
||||
}
|
||||
|
||||
export const convertApyToApr = (apy: number, numberOfCompoundingPeriods: number): number => {
|
||||
return (
|
||||
(Math.pow(1 + apy / 100, numberOfCompoundingPeriods) - 1) * 100 * numberOfCompoundingPeriods
|
||||
)
|
||||
}
|
||||
|
||||
export const combineBNCoins = (coins: BNCoin[]): BNCoin[] => {
|
||||
const combinedMap: { [key: string]: number } = {}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user