Update account stats on pending action (#51)

* feat: update account stats on pending action

* lift repay amount with buffer calculation to repay modal

* refactor navigation component to properly format account stats

* update repay funds hook using credit manager client

* consider market liquidity when calculating max withdraw amount

* refactor useMaxBorrowAmount hook to be consistent with withdraw

* format

* remove duplicated import statements
This commit is contained in:
Gustavo Mauricio 2022-11-09 12:47:36 +00:00 committed by GitHub
parent 27cdd1c954
commit 56bfea8ed0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 487 additions and 139 deletions

View File

@ -12,12 +12,18 @@ import useCalculateMaxBorrowAmount from 'hooks/useCalculateMaxBorrowAmount'
import useMarkets from 'hooks/useMarkets'
import useTokenPrices from 'hooks/useTokenPrices'
import { formatCurrency } from 'utils/formatters'
import useCreditAccountPositions from 'hooks/useCreditAccountPositions'
import useCreditManagerStore from 'stores/useCreditManagerStore'
import useAccountStats, { AccountStatsAction } from 'hooks/useAccountStats'
import { chain } from 'utils/chains'
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
import Button from './Button'
import ProgressBar from './ProgressBar'
import SemiCircleProgress from './SemiCircleProgress'
import ContainerSecondary from './ContainerSecondary'
import Spinner from './Spinner'
import Tooltip from './Tooltip'
import Button from './Button'
type Props = {
show: boolean
@ -29,21 +35,46 @@ const BorrowModal = ({ show, onClose, tokenDenom }: Props) => {
const [amount, setAmount] = useState(0)
const [isBorrowToCreditAccount, setIsBorrowToCreditAccount] = useState(false)
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
const { data: positionsData, isLoading: isLoadingPositions } = useCreditAccountPositions(
selectedAccount ?? '',
)
const { actions, borrowAmount } = useMemo(() => {
const borrowAmount = BigNumber(amount)
.times(10 ** getTokenDecimals(tokenDenom))
.toNumber()
const withdrawAmount = isBorrowToCreditAccount ? 0 : borrowAmount
return {
borrowAmount,
withdrawAmount,
actions: [
{
type: 'borrow',
amount: borrowAmount,
denom: tokenDenom,
},
{
type: 'withdraw',
amount: withdrawAmount,
denom: tokenDenom,
},
] as AccountStatsAction[],
}
}, [amount, isBorrowToCreditAccount, tokenDenom])
const accountStats = useAccountStats(actions)
const tokenSymbol = getTokenSymbol(tokenDenom)
const { mutate, isLoading } = useBorrowFunds(
BigNumber(amount)
.times(10 ** getTokenDecimals(tokenDenom))
.toNumber(),
tokenDenom,
!isBorrowToCreditAccount,
{
onSuccess: () => {
onClose()
toast.success(`${amount} ${tokenSymbol} successfully Borrowed`)
},
const { mutate, isLoading } = useBorrowFunds(borrowAmount, tokenDenom, !isBorrowToCreditAccount, {
onSuccess: () => {
onClose()
toast.success(`${amount} ${tokenSymbol} successfully Borrowed`)
},
)
})
const { data: tokenPrices } = useTokenPrices()
const { data: balancesData } = useAllBalances()
@ -94,6 +125,17 @@ const BorrowModal = ({ show, onClose, tokenDenom }: Props) => {
setAmount(0)
}
const getTokenTotalUSDValue = (amount: string, denom: string) => {
// early return if prices are not fetched yet
if (!tokenPrices) return 0
return (
BigNumber(amount)
.div(10 ** getTokenDecimals(denom))
.toNumber() * tokenPrices[denom]
)
}
return (
<Transition appear show={show} as={React.Fragment}>
<Dialog as='div' className='relative z-10' onClose={onClose}>
@ -120,25 +162,13 @@ const BorrowModal = ({ show, onClose, tokenDenom }: Props) => {
leaveFrom='opacity-100 scale-100'
leaveTo='opacity-0 scale-95'
>
<Dialog.Panel className='flex min-h-[520px] w-full max-w-3xl transform overflow-hidden rounded-2xl bg-[#585A74] align-middle shadow-xl transition-all'>
<Dialog.Panel className='flex w-full max-w-3xl transform overflow-hidden rounded-2xl bg-[#585A74] align-middle shadow-xl transition-all'>
{isLoading && (
<div className='absolute inset-0 z-40 grid place-items-center bg-black/50'>
<Spinner />
</div>
)}
<div className='flex flex-1 flex-col items-start justify-between bg-[#4A4C60] p-6'>
<div>
<p className='text-bold mb-3 text-xs uppercase text-white/50'>About</p>
<h4 className='mb-4 text-xl leading-8'>
Bringing the next generation of video creation to the Metaverse.
<br />
Powered by deep-learning.
</h4>
</div>
<Image src='/logo.svg' alt='mars' width={150} height={50} />
</div>
<div className='flex flex-1 flex-col p-4'>
<Dialog.Title as='h3' className='mb-4 text-center font-medium'>
Borrow {tokenSymbol}
@ -219,6 +249,111 @@ const BorrowModal = ({ show, onClose, tokenDenom }: Props) => {
Borrow
</Button>
</div>
<div className='flex w-1/2 flex-col justify-center bg-[#4A4C60] p-4'>
<p className='text-bold mb-3 text-xs uppercase text-white/50'>About</p>
<h4 className='mb-4 text-xl'>Subaccount {selectedAccount}</h4>
<div className='mb-2 rounded-md border border-white/20 p-3'>
{accountStats && (
<div className='flex items-center gap-x-3'>
<p className='flex-1 text-xs'>
{formatCurrency(
BigNumber(accountStats.netWorth)
.dividedBy(10 ** chain.stakeCurrency.coinDecimals)
.toNumber(),
)}
</p>
{/* TOOLTIP */}
<div title={`${String(accountStats.currentLeverage.toFixed(1))}x`}>
<SemiCircleProgress
value={accountStats.currentLeverage / accountStats.maxLeverage}
label='Lvg'
/>
</div>
<SemiCircleProgress value={accountStats.risk} label='Risk' />
<ProgressBar value={accountStats.health} />
</div>
)}
</div>
<div className='mb-2 rounded-md border border-white/20 p-3 text-sm'>
<div className='mb-1 flex justify-between'>
<div>Total Position:</div>
<div className='font-semibold'>
{formatCurrency(
BigNumber(accountStats?.totalPosition ?? 0)
.dividedBy(10 ** chain.stakeCurrency.coinDecimals)
.toNumber(),
)}
</div>
</div>
<div className='flex justify-between'>
<div>Total Liabilities:</div>
<div className='font-semibold'>
{formatCurrency(
BigNumber(accountStats?.totalDebt ?? 0)
.dividedBy(10 ** chain.stakeCurrency.coinDecimals)
.toNumber(),
)}
</div>
</div>
</div>
<div className='rounded-md border border-white/20 p-3'>
<h4 className='mb-2 font-bold'>Balances</h4>
{isLoadingPositions ? (
<div>Loading...</div>
) : (
<table className='w-full border-separate border-spacing-1'>
<thead className='text-left text-xs font-semibold'>
<tr>
<th>Asset</th>
<th>Value</th>
<th>Size</th>
<th className='text-right'>APY</th>
</tr>
</thead>
<tbody>
{positionsData?.coins.map((coin) => (
<tr key={coin.denom} className='text-xs text-white/50'>
<td>{getTokenSymbol(coin.denom)}</td>
<td>
{formatCurrency(getTokenTotalUSDValue(coin.amount, coin.denom))}
</td>
<td>
{BigNumber(coin.amount)
.div(10 ** getTokenDecimals(coin.denom))
.toNumber()
.toLocaleString(undefined, {
maximumFractionDigits: getTokenDecimals(coin.denom),
})}
</td>
<td className='text-right'>-</td>
</tr>
))}
{positionsData?.debts.map((coin) => (
<tr key={coin.denom} className='text-xs text-red-500'>
<td className='text-white/50'>{getTokenSymbol(coin.denom)}</td>
<td>
-{formatCurrency(getTokenTotalUSDValue(coin.amount, coin.denom))}
</td>
<td>
-
{BigNumber(coin.amount)
.div(10 ** getTokenDecimals(coin.denom))
.toNumber()
.toLocaleString(undefined, {
maximumFractionDigits: 6,
})}
</td>
<td className='text-right'>
-{(Number(marketsData?.[coin.denom].borrow_rate) * 100).toFixed(1)}%
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>

View File

@ -12,8 +12,8 @@ import useCreditManagerStore from 'stores/useCreditManagerStore'
import useWalletStore from 'stores/useWalletStore'
import { formatCurrency } from 'utils/formatters'
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
import Button from '../Button'
import { chain } from 'utils/chains'
import Button from 'components/Button'
const CreditManager = () => {
const [showFundWalletModal, setShowFundWalletModal] = useState(false)
@ -78,11 +78,23 @@ const CreditManager = () => {
<ContainerSecondary className='mb-2 text-sm'>
<div className='mb-1 flex justify-between'>
<div>Total Position:</div>
<div className='font-semibold'>{formatCurrency(accountStats?.totalPosition ?? 0)}</div>
<div className='font-semibold'>
{formatCurrency(
BigNumber(accountStats?.totalPosition ?? 0)
.dividedBy(10 ** chain.stakeCurrency.coinDecimals)
.toNumber(),
)}
</div>
</div>
<div className='flex justify-between'>
<div>Total Liabilities:</div>
<div className='font-semibold'>{formatCurrency(accountStats?.totalDebt ?? 0)}</div>
<div className='font-semibold'>
{formatCurrency(
BigNumber(accountStats?.totalDebt ?? 0)
.dividedBy(10 ** chain.stakeCurrency.coinDecimals)
.toNumber(),
)}
</div>
</div>
</ContainerSecondary>
<ContainerSecondary>

View File

@ -4,6 +4,7 @@ import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import React, { useMemo } from 'react'
import BigNumber from 'bignumber.js'
import ArrowRightLine from 'components/Icons/arrow-right-line.svg'
import ProgressBar from 'components/ProgressBar'
@ -17,6 +18,7 @@ import useCreditAccounts from 'hooks/useCreditAccounts'
import useCreditManagerStore from 'stores/useCreditManagerStore'
import useWalletStore from 'stores/useWalletStore'
import { formatCurrency } from 'utils/formatters'
import { chain } from 'utils/chains'
import Button from './Button'
import SemiCircleProgress from './SemiCircleProgress'
@ -81,7 +83,13 @@ const Navigation = () => {
<div className='flex items-center gap-4'>
{accountStats && (
<>
<p>{formatCurrency(accountStats.netWorth)}</p>
<p>
{formatCurrency(
BigNumber(accountStats.netWorth)
.dividedBy(10 ** chain.stakeCurrency.coinDecimals)
.toNumber(),
)}
</p>
{/* TOOLTIP */}
<div title={`${String(accountStats.currentLeverage.toFixed(1))}x`}>
<SemiCircleProgress

View File

@ -18,6 +18,9 @@ import Button from './Button'
import ContainerSecondary from './ContainerSecondary'
import Spinner from './Spinner'
// 0.001% buffer / slippage to avoid repay action from not fully repaying the debt amount
const REPAY_BUFFER = 1.00001
type Props = {
show: boolean
onClose: () => void
@ -37,6 +40,8 @@ const RepayModal = ({ show, onClose, tokenDenom }: Props) => {
positionsData?.debts.find((coin) => coin.denom === tokenDenom)?.amount ?? 0
return BigNumber(tokenDebtAmount)
.times(REPAY_BUFFER)
.decimalPlaces(0)
.div(10 ** getTokenDecimals(tokenDenom))
.toNumber()
}, [positionsData, tokenDenom])

View File

@ -6,7 +6,6 @@ import { toast } from 'react-toastify'
import Slider from 'components/Slider'
import useWithdrawFunds from 'hooks/mutations/useWithdrawFunds'
import useAccountStats from 'hooks/useAccountStats'
import useAllBalances from 'hooks/useAllBalances'
import useCalculateMaxWithdrawAmount from 'hooks/useCalculateMaxWithdrawAmount'
import useCreditAccountPositions from 'hooks/useCreditAccountPositions'
@ -15,12 +14,14 @@ import useTokenPrices from 'hooks/useTokenPrices'
import useCreditManagerStore from 'stores/useCreditManagerStore'
import { formatCurrency } from 'utils/formatters'
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
import useAccountStats, { AccountStatsAction } from 'hooks/useAccountStats'
import { chain } from 'utils/chains'
import Button from './Button'
import ContainerSecondary from './ContainerSecondary'
import ProgressBar from './ProgressBar'
import SemiCircleProgress from './SemiCircleProgress'
import Spinner from './Spinner'
import SemiCircleProgress from './SemiCircleProgress'
import ProgressBar from './ProgressBar'
import ContainerSecondary from './ContainerSecondary'
import Button from './Button'
const WithdrawModal = ({ show, onClose }: any) => {
const [amount, setAmount] = useState(0)
@ -35,7 +36,6 @@ const WithdrawModal = ({ show, onClose }: any) => {
const { data: balancesData } = useAllBalances()
const { data: tokenPrices } = useTokenPrices()
const { data: marketsData } = useMarkets()
const accountStats = useAccountStats()
const selectedTokenSymbol = getTokenSymbol(selectedToken)
const selectedTokenDecimals = getTokenDecimals(selectedToken)
@ -46,7 +46,7 @@ const WithdrawModal = ({ show, onClose }: any) => {
.toNumber()
}, [positionsData, selectedTokenDecimals, selectedToken])
const { borrowAmount, withdrawAmount } = useMemo(() => {
const { actions, borrowAmount, withdrawAmount } = useMemo(() => {
const borrowAmount =
amount > tokenAmountInCreditAccount
? BigNumber(amount)
@ -62,8 +62,22 @@ const WithdrawModal = ({ show, onClose }: any) => {
return {
borrowAmount,
withdrawAmount,
actions: [
{
type: 'borrow',
amount: borrowAmount,
denom: selectedToken,
},
{
type: 'withdraw',
amount: withdrawAmount,
denom: selectedToken,
},
] as AccountStatsAction[],
}
}, [amount, selectedTokenDecimals, tokenAmountInCreditAccount])
}, [amount, selectedToken, selectedTokenDecimals, tokenAmountInCreditAccount])
const accountStats = useAccountStats(actions)
const { mutate, isLoading } = useWithdrawFunds(withdrawAmount, borrowAmount, selectedToken, {
onSuccess: () => {
@ -234,7 +248,13 @@ const WithdrawModal = ({ show, onClose }: any) => {
<div className='mb-2 rounded-md border border-white/20 p-3'>
{accountStats && (
<div className='flex items-center gap-x-3'>
<p>{formatCurrency(accountStats.netWorth)}</p>
<p className='flex-1 text-xs'>
{formatCurrency(
BigNumber(accountStats.netWorth)
.dividedBy(10 ** chain.stakeCurrency.coinDecimals)
.toNumber(),
)}
</p>
{/* TOOLTIP */}
<div title={`${String(accountStats.currentLeverage.toFixed(1))}x`}>
<SemiCircleProgress
@ -251,13 +271,21 @@ const WithdrawModal = ({ show, onClose }: any) => {
<div className='mb-1 flex justify-between'>
<div>Total Position:</div>
<div className='font-semibold'>
{formatCurrency(accountStats?.totalPosition ?? 0)}
{formatCurrency(
BigNumber(accountStats?.totalPosition ?? 0)
.dividedBy(10 ** chain.stakeCurrency.coinDecimals)
.toNumber(),
)}
</div>
</div>
<div className='flex justify-between'>
<div>Total Liabilities:</div>
<div className='font-semibold'>
{formatCurrency(accountStats?.totalDebt ?? 0)}
{formatCurrency(
BigNumber(accountStats?.totalDebt ?? 0)
.dividedBy(10 ** chain.stakeCurrency.coinDecimals)
.toNumber(),
)}
</div>
</div>
</div>

View File

@ -1,5 +1,4 @@
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
import BigNumber from 'bignumber.js'
import { useMemo } from 'react'
import { toast } from 'react-toastify'
@ -8,64 +7,51 @@ import useCreditManagerStore from 'stores/useCreditManagerStore'
import useWalletStore from 'stores/useWalletStore'
import { queryKeys } from 'types/query-keys-factory'
import { hardcodedFee } from 'utils/contants'
import { getTokenDecimals } from 'utils/tokens'
// 0.001% buffer / slippage to avoid repay action from not fully repaying the debt amount
const REPAY_BUFFER = 1.00001
const useRepayFunds = (
amount: number,
denom: string,
options: Omit<UseMutationOptions, 'onError'>,
) => {
const signingClient = useWalletStore((s) => s.signingClient)
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
const creditManagerClient = useWalletStore((s) => s.clients.creditManager)
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount ?? '')
const address = useWalletStore((s) => s.address)
const queryClient = useQueryClient()
const adjustedAmount = BigNumber(amount).times(REPAY_BUFFER).decimalPlaces(0).toString()
const executeMsg = useMemo(() => {
return {
update_credit_account: {
account_id: selectedAccount,
actions: [
{
deposit: {
denom: denom,
amount: adjustedAmount,
},
},
{
repay: {
denom: denom,
amount: adjustedAmount,
},
},
],
const actions = useMemo(() => {
return [
{
deposit: {
denom: denom,
amount: String(amount),
},
},
}
}, [adjustedAmount, denom, selectedAccount])
{
repay: {
denom: denom,
amount: String(amount),
},
},
]
}, [amount, denom])
return useMutation(
async () =>
await signingClient?.execute(
address,
contractAddresses.creditManager,
executeMsg,
await creditManagerClient?.updateCreditAccount(
{ accountId: selectedAccount, actions },
hardcodedFee,
undefined,
[
{
denom,
amount: adjustedAmount,
amount: String(amount),
},
],
),
{
onSettled: () => {
queryClient.invalidateQueries(queryKeys.creditAccountsPositions(selectedAccount ?? ''))
queryClient.invalidateQueries(queryKeys.creditAccountsPositions(selectedAccount))
queryClient.invalidateQueries(queryKeys.tokenBalance(address, denom))
queryClient.invalidateQueries(queryKeys.allBalances(address))
queryClient.invalidateQueries(queryKeys.redbankBalances())

View File

@ -2,7 +2,6 @@ import BigNumber from 'bignumber.js'
import { useMemo } from 'react'
import useCreditManagerStore from 'stores/useCreditManagerStore'
import { getTokenDecimals } from 'utils/tokens'
import useCreditAccountPositions from './useCreditAccountPositions'
import useMarkets from './useMarkets'
@ -18,59 +17,104 @@ const getRiskFromAverageLiquidationLTVs = (value: number) => {
return 1
}
const useAccountStats = () => {
type Asset = {
amount: string
denom: string
liquidationThreshold: string
basePrice: number
}
type Debt = {
amount: string
denom: string
basePrice: number
}
const calculateStatsFromAccountPositions = (assets: Asset[], debts: Debt[]) => {
const totalPosition = assets.reduce((acc, asset) => {
const tokenTotalValue = BigNumber(asset.amount).times(asset.basePrice)
return BigNumber(tokenTotalValue).plus(acc).toNumber()
}, 0)
const totalDebt = debts.reduce((acc, debt) => {
const tokenTotalValue = BigNumber(debt.amount).times(debt.basePrice)
return BigNumber(tokenTotalValue).plus(acc).toNumber()
}, 0)
const totalWeightedPositions = assets.reduce((acc, asset) => {
const assetWeightedValue = BigNumber(asset.amount)
.times(asset.basePrice)
.times(asset.liquidationThreshold)
return assetWeightedValue.plus(acc).toNumber()
}, 0)
const netWorth = BigNumber(totalPosition).minus(totalDebt).toNumber()
const liquidationLTVsWeightedAverage = BigNumber(totalWeightedPositions)
.div(totalPosition)
.toNumber()
const maxLeverage = BigNumber(1)
.div(BigNumber(1).minus(liquidationLTVsWeightedAverage))
.toNumber()
const currentLeverage = BigNumber(totalPosition).div(netWorth).toNumber()
const health = BigNumber(1).minus(BigNumber(currentLeverage).div(maxLeverage)).toNumber() || 1
const risk = liquidationLTVsWeightedAverage
? getRiskFromAverageLiquidationLTVs(liquidationLTVsWeightedAverage)
: 0
return {
health,
maxLeverage,
currentLeverage,
netWorth,
risk,
totalPosition,
totalDebt,
}
}
export type AccountStatsAction = {
type: 'borrow' | 'repay' | 'deposit' | 'withdraw'
amount: number
denom: string
}
const useAccountStats = (actions?: AccountStatsAction[]) => {
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
const { data: positionsData } = useCreditAccountPositions(selectedAccount ?? '')
const { data: marketsData } = useMarkets()
const { data: tokenPrices } = useTokenPrices()
return useMemo(() => {
const currentStats = useMemo(() => {
if (!marketsData || !tokenPrices || !positionsData) return null
const getTokenTotalUSDValue = (amount: string, denom: string) => {
// early return if prices are not fetched yet
if (!tokenPrices) return 0
const assets = positionsData.coins.map((coin) => {
const market = marketsData[coin.denom]
return BigNumber(amount)
.div(10 ** getTokenDecimals(denom))
.times(tokenPrices[denom])
.toNumber()
}
return {
amount: coin.amount,
denom: coin.denom,
liquidationThreshold: market.liquidation_threshold,
basePrice: tokenPrices[coin.denom],
}
})
const totalPosition = positionsData.coins.reduce((acc, coin) => {
const tokenTotalValue = getTokenTotalUSDValue(coin.amount, coin.denom)
return BigNumber(tokenTotalValue).plus(acc).toNumber()
}, 0)
const debts = positionsData.debts.map((debt) => {
return {
amount: debt.amount,
denom: debt.denom,
basePrice: tokenPrices[debt.denom],
}
})
const totalDebt = positionsData.debts.reduce((acc, coin) => {
const tokenTotalValue = getTokenTotalUSDValue(coin.amount, coin.denom)
return BigNumber(tokenTotalValue).plus(acc).toNumber()
}, 0)
const totalWeightedPositions = positionsData.coins.reduce((acc, coin) => {
const tokenWeightedValue = BigNumber(getTokenTotalUSDValue(coin.amount, coin.denom)).times(
Number(marketsData[coin.denom].liquidation_threshold),
)
return tokenWeightedValue.plus(acc).toNumber()
}, 0)
const netWorth = BigNumber(totalPosition).minus(totalDebt).toNumber()
const liquidationLTVsWeightedAverage = BigNumber(totalWeightedPositions)
.div(totalPosition)
.toNumber()
const maxLeverage = BigNumber(1)
.div(BigNumber(1).minus(liquidationLTVsWeightedAverage))
.toNumber()
const currentLeverage = BigNumber(totalPosition).div(netWorth).toNumber()
const health = BigNumber(1).minus(BigNumber(currentLeverage).div(maxLeverage)).toNumber() || 1
const risk = liquidationLTVsWeightedAverage
? getRiskFromAverageLiquidationLTVs(liquidationLTVsWeightedAverage)
: 0
const { health, maxLeverage, currentLeverage, netWorth, risk, totalPosition, totalDebt } =
calculateStatsFromAccountPositions(assets, debts)
return {
health,
@ -82,6 +126,129 @@ const useAccountStats = () => {
totalDebt,
}
}, [marketsData, positionsData, tokenPrices])
const actionResultStats = useMemo(() => {
if (!actions) return null
if (!tokenPrices || !marketsData || !positionsData) return null
let resultPositionsCoins = positionsData.coins.map((coin) => ({ ...coin }))
let resultPositionsDebts = positionsData.debts.map((debt) => {
const { shares, ...otherProps } = debt
return { ...otherProps }
})
actions.forEach((action) => {
if (action.amount === 0) return
if (action.type === 'deposit') {
const index = resultPositionsCoins.findIndex((coin) => coin.denom === action.denom)
if (index !== -1) {
resultPositionsCoins[index].amount = BigNumber(resultPositionsCoins[index].amount)
.plus(action.amount)
.toFixed()
} else {
resultPositionsCoins.push({
amount: action.amount.toString(),
denom: action.denom,
})
}
return
}
if (action.type === 'withdraw') {
const index = resultPositionsCoins.findIndex((coin) => coin.denom === action.denom)
if (index !== -1) {
resultPositionsCoins[index].amount = BigNumber(resultPositionsCoins[index].amount)
.minus(action.amount)
.toFixed()
} else {
throw new Error('Cannot withdraw more than you have')
}
return
}
if (action.type === 'borrow') {
const indexDebts = resultPositionsDebts.findIndex((coin) => coin.denom === action.denom)
const indexPositions = resultPositionsCoins.findIndex((coin) => coin.denom === action.denom)
if (indexDebts !== -1) {
resultPositionsDebts[indexDebts].amount = BigNumber(
resultPositionsDebts[indexDebts].amount,
)
.plus(action.amount)
.toFixed()
} else {
resultPositionsDebts.push({
amount: action.amount.toString(),
denom: action.denom,
})
}
if (indexPositions !== -1) {
resultPositionsCoins[indexPositions].amount = BigNumber(
resultPositionsCoins[indexPositions].amount,
)
.plus(action.amount)
.toFixed()
} else {
resultPositionsCoins.push({
amount: action.amount.toString(),
denom: action.denom,
})
}
return
}
// TODO: repay
if (action.type === 'repay') {
const index = resultPositionsDebts.findIndex((coin) => coin.denom === action.denom)
resultPositionsDebts[index].amount = BigNumber(resultPositionsDebts[index].amount)
.minus(action.amount)
.toFixed()
return
}
})
const assets = resultPositionsCoins.map((coin) => {
const market = marketsData[coin.denom]
return {
amount: coin.amount,
denom: coin.denom,
liquidationThreshold: market.liquidation_threshold,
basePrice: tokenPrices[coin.denom],
}
})
const debts = resultPositionsDebts.map((debt) => {
return {
amount: debt.amount,
denom: debt.denom,
basePrice: tokenPrices[debt.denom],
}
})
const { health, maxLeverage, currentLeverage, netWorth, risk, totalPosition, totalDebt } =
calculateStatsFromAccountPositions(assets, debts)
return {
health,
maxLeverage,
currentLeverage,
netWorth,
risk,
totalPosition,
totalDebt,
}
}, [actions, marketsData, positionsData, tokenPrices])
return actionResultStats || currentStats
}
export default useAccountStats

View File

@ -62,32 +62,31 @@ const useCalculateMaxBorrowAmount = (denom: string, isUnderCollateralized: boole
const borrowTokenPrice = tokenPrices[denom]
const tokenDecimals = getTokenDecimals(denom)
let maxValue
let maxAmountCapacity
if (isUnderCollateralized) {
// MAX TO CREDIT ACCOUNT
maxValue = BigNumber(totalLiabilitiesValue)
maxAmountCapacity = BigNumber(totalLiabilitiesValue)
.minus(totalWeightedPositions)
.div(borrowTokenPrice * Number(marketsData[denom].max_loan_to_value) - borrowTokenPrice)
.div(10 ** tokenDecimals)
.decimalPlaces(tokenDecimals)
.toNumber()
} else {
// MAX TO WALLET
maxValue = BigNumber(totalWeightedPositions)
maxAmountCapacity = BigNumber(totalWeightedPositions)
.minus(totalLiabilitiesValue)
.div(borrowTokenPrice)
.div(10 ** tokenDecimals)
.decimalPlaces(tokenDecimals)
.toNumber()
}
const marketLiquidity = BigNumber(redbankBalances?.[denom] ?? 0)
.div(10 ** getTokenDecimals(denom))
const marketLiquidity = redbankBalances?.[denom] ?? 0
const maxBorrowAmount = maxAmountCapacity.gt(marketLiquidity)
? marketLiquidity
: maxAmountCapacity.gt(0)
? maxAmountCapacity
: 0
return BigNumber(maxBorrowAmount)
.dividedBy(10 ** tokenDecimals)
.decimalPlaces(tokenDecimals)
.toNumber()
if (marketLiquidity < maxValue) return marketLiquidity
return maxValue > 0 ? maxValue : 0
}, [
denom,
getTokenValue,

View File

@ -7,6 +7,7 @@ import { getTokenDecimals } from 'utils/tokens'
import useCreditAccountPositions from './useCreditAccountPositions'
import useMarkets from './useMarkets'
import useTokenPrices from './useTokenPrices'
import useRedbankBalances from './useRedbankBalances'
const getApproximateHourlyInterest = (amount: string, borrowAPY: string) => {
const hourlyAPY = BigNumber(borrowAPY).div(24 * 365)
@ -20,6 +21,7 @@ const useCalculateMaxWithdrawAmount = (denom: string, borrow: boolean) => {
const { data: positionsData } = useCreditAccountPositions(selectedAccount ?? '')
const { data: marketsData } = useMarkets()
const { data: tokenPrices } = useTokenPrices()
const { data: redbankBalances } = useRedbankBalances()
const tokenDecimals = getTokenDecimals(denom)
@ -37,7 +39,7 @@ const useCalculateMaxWithdrawAmount = (denom: string, borrow: boolean) => {
}, [denom, positionsData])
const maxAmount = useMemo(() => {
if (!marketsData || !tokenPrices || !positionsData || !denom) return 0
if (!marketsData || !tokenPrices || !positionsData || !redbankBalances || !denom) return 0
const hasDebt = positionsData.debts.length > 0
@ -98,15 +100,20 @@ const useCalculateMaxWithdrawAmount = (denom: string, borrow: boolean) => {
maxAmountCapacity = maxAmountCapacity < 0 ? 0 : maxAmountCapacity
}
const isCapacityHigherThanBalance = BigNumber(maxAmountCapacity).gt(tokenAmountInCreditAccount)
const marketLiquidity = redbankBalances?.[denom] ?? 0
if (!borrow && isCapacityHigherThanBalance) {
const maxWithdrawAmount = BigNumber(maxAmountCapacity).gt(marketLiquidity)
? marketLiquidity
: maxAmountCapacity
const isMaxAmountHigherThanBalance = BigNumber(maxWithdrawAmount).gt(tokenAmountInCreditAccount)
if (!borrow && isMaxAmountHigherThanBalance) {
return BigNumber(tokenAmountInCreditAccount)
.div(10 ** tokenDecimals)
.toNumber()
}
return BigNumber(maxAmountCapacity)
return BigNumber(maxWithdrawAmount)
.div(10 ** tokenDecimals)
.decimalPlaces(tokenDecimals)
.toNumber()
@ -116,6 +123,7 @@ const useCalculateMaxWithdrawAmount = (denom: string, borrow: boolean) => {
getTokenValue,
marketsData,
positionsData,
redbankBalances,
tokenAmountInCreditAccount,
tokenDecimals,
tokenPrices,