56bfea8ed0
* 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
136 lines
4.4 KiB
TypeScript
136 lines
4.4 KiB
TypeScript
import BigNumber from 'bignumber.js'
|
|
import { useCallback, useMemo } from 'react'
|
|
|
|
import useCreditManagerStore from 'stores/useCreditManagerStore'
|
|
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)
|
|
|
|
return hourlyAPY.times(amount).toNumber()
|
|
}
|
|
|
|
const useCalculateMaxWithdrawAmount = (denom: string, borrow: boolean) => {
|
|
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
|
|
|
|
const { data: positionsData } = useCreditAccountPositions(selectedAccount ?? '')
|
|
const { data: marketsData } = useMarkets()
|
|
const { data: tokenPrices } = useTokenPrices()
|
|
const { data: redbankBalances } = useRedbankBalances()
|
|
|
|
const tokenDecimals = getTokenDecimals(denom)
|
|
|
|
const getTokenValue = useCallback(
|
|
(amount: string, denom: string) => {
|
|
if (!tokenPrices) return 0
|
|
|
|
return BigNumber(amount).times(tokenPrices[denom]).toNumber()
|
|
},
|
|
[tokenPrices],
|
|
)
|
|
|
|
const tokenAmountInCreditAccount = useMemo(() => {
|
|
return positionsData?.coins.find((coin) => coin.denom === denom)?.amount ?? 0
|
|
}, [denom, positionsData])
|
|
|
|
const maxAmount = useMemo(() => {
|
|
if (!marketsData || !tokenPrices || !positionsData || !redbankBalances || !denom) return 0
|
|
|
|
const hasDebt = positionsData.debts.length > 0
|
|
|
|
const borrowTokenPrice = tokenPrices[denom]
|
|
const borrowTokenMaxLTV = Number(marketsData[denom].max_loan_to_value)
|
|
|
|
// approximate debt value in an hour timespan to avoid throwing on smart contract level
|
|
// due to debt interest being applied
|
|
const totalLiabilitiesValue = positionsData?.debts.reduce((acc, coin) => {
|
|
const estimatedInterestAmount = getApproximateHourlyInterest(
|
|
coin.amount,
|
|
marketsData[coin.denom].borrow_rate,
|
|
)
|
|
const tokenDebtValue = BigNumber(getTokenValue(coin.amount, coin.denom)).plus(
|
|
estimatedInterestAmount,
|
|
)
|
|
|
|
return tokenDebtValue.plus(acc).toNumber()
|
|
}, 0)
|
|
|
|
const positionsWeightedAverageWithoutAsset = positionsData?.coins.reduce((acc, coin) => {
|
|
if (coin.denom === denom) return acc
|
|
|
|
const tokenWeightedValue = BigNumber(getTokenValue(coin.amount, coin.denom)).times(
|
|
Number(marketsData[coin.denom].max_loan_to_value),
|
|
)
|
|
|
|
return tokenWeightedValue.plus(acc).toNumber()
|
|
}, 0)
|
|
|
|
const isHealthyAfterFullWithdraw = !hasDebt
|
|
? true
|
|
: positionsWeightedAverageWithoutAsset / totalLiabilitiesValue > 1
|
|
|
|
let maxAmountCapacity = 0
|
|
|
|
if (isHealthyAfterFullWithdraw) {
|
|
const maxBorrow = BigNumber(positionsWeightedAverageWithoutAsset)
|
|
.minus(totalLiabilitiesValue)
|
|
.div(borrowTokenPrice)
|
|
|
|
maxAmountCapacity = maxBorrow
|
|
.plus(tokenAmountInCreditAccount)
|
|
.decimalPlaces(tokenDecimals)
|
|
.toNumber()
|
|
} else {
|
|
const requiredCollateral = BigNumber(totalLiabilitiesValue)
|
|
.minus(positionsWeightedAverageWithoutAsset)
|
|
.dividedBy(borrowTokenPrice * borrowTokenMaxLTV)
|
|
|
|
maxAmountCapacity = BigNumber(tokenAmountInCreditAccount)
|
|
.minus(requiredCollateral)
|
|
.decimalPlaces(tokenDecimals)
|
|
.toNumber()
|
|
|
|
// required collateral might be greater than the amount in the credit account which will result
|
|
// in a negative max amount capacity
|
|
maxAmountCapacity = maxAmountCapacity < 0 ? 0 : maxAmountCapacity
|
|
}
|
|
|
|
const marketLiquidity = redbankBalances?.[denom] ?? 0
|
|
|
|
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(maxWithdrawAmount)
|
|
.div(10 ** tokenDecimals)
|
|
.decimalPlaces(tokenDecimals)
|
|
.toNumber()
|
|
}, [
|
|
borrow,
|
|
denom,
|
|
getTokenValue,
|
|
marketsData,
|
|
positionsData,
|
|
redbankBalances,
|
|
tokenAmountInCreditAccount,
|
|
tokenDecimals,
|
|
tokenPrices,
|
|
])
|
|
|
|
return maxAmount
|
|
}
|
|
|
|
export default useCalculateMaxWithdrawAmount
|