mars-v2-frontend/hooks/useCalculateMaxWithdrawAmount.tsx
Gustavo Mauricio 8bde02c2e9
MP-1705: Borrow limit (#48)
* update borrow funds hooks using credit manager client

* update calculate max borrow and withdraw hooks to consider future debt
2022-11-07 17:14:16 +00:00

127 lines
4.0 KiB
TypeScript

import { useCallback, useMemo } from 'react'
import BigNumber from 'bignumber.js'
import { getTokenDecimals } from 'utils/tokens'
import useCreditAccountPositions from './useCreditAccountPositions'
import useCreditManagerStore from 'stores/useCreditManagerStore'
import useTokenPrices from './useTokenPrices'
import useMarkets from './useMarkets'
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 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 || !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 isCapacityHigherThanBalance = BigNumber(maxAmountCapacity).gt(tokenAmountInCreditAccount)
if (!borrow && isCapacityHigherThanBalance) {
return BigNumber(tokenAmountInCreditAccount)
.div(10 ** tokenDecimals)
.toNumber()
}
return BigNumber(maxAmountCapacity)
.div(10 ** tokenDecimals)
.decimalPlaces(tokenDecimals)
.toNumber()
}, [
borrow,
denom,
getTokenValue,
marketsData,
positionsData,
tokenAmountInCreditAccount,
tokenDecimals,
tokenPrices,
])
return maxAmount
}
export default useCalculateMaxWithdrawAmount