mars-v2-frontend/hooks/useCalculateMaxTradeAmount.tsx
Linkie Link c4f8f4eab0
Mp 1757 wallet connect (#66)
* MP-1757: implemented the WalletProvider and connect buttons

* tidy: tidy up the search

* MP-1691: moved modals outside of the DOM

* MP-1691: changed CreditManager into AccountDetails

* fix: fixed the naming

* MP-1691: UX approvements

* MP-1691: global confirm and delete modal added

* fix: merged the credit-account and wallet branch

* MP-1757: added the status store

* fix: updated the store interaction

* MP-1757: major cleanup of stores

* tidy: format
2022-12-08 21:14:38 +01:00

145 lines
4.9 KiB
TypeScript

import BigNumber from 'bignumber.js'
import { useCallback, useMemo } from 'react'
import { useAccountDetailsStore } from 'stores'
import useCreditAccountPositions from './useCreditAccountPositions'
import useMarkets from './useMarkets'
import useRedbankBalances from './useRedbankBalances'
import useTokenPrices from './useTokenPrices'
const getApproximateHourlyInterest = (amount: string, borrowAPY: string) => {
const hourlyAPY = BigNumber(borrowAPY).div(24 * 365)
return hourlyAPY.times(amount).toNumber()
}
// max trade amount doesnt consider wallet balance as its not relevant
// the entire token balance within the wallet will always be able to be fully swapped
const useCalculateMaxTradeAmount = (tokenIn: string, tokenOut: string, isMargin: boolean) => {
const selectedAccount = useAccountDetailsStore((s) => s.selectedAccount)
const { data: positionsData } = useCreditAccountPositions(selectedAccount ?? '')
const { data: marketsData } = useMarkets()
const { data: tokenPrices } = useTokenPrices()
const { data: redbankBalances } = useRedbankBalances()
const accountAmount = useMemo(() => {
return BigNumber(
positionsData?.coins?.find((coin) => coin.denom === tokenIn)?.amount ?? 0,
).toNumber()
}, [positionsData, tokenIn])
const getTokenValue = useCallback(
(amount: string, denom: string) => {
if (!tokenPrices) return 0
return BigNumber(amount).times(tokenPrices[denom]).toNumber()
},
[tokenPrices],
)
return useMemo(() => {
if (!marketsData || !tokenPrices || !positionsData || !redbankBalances || !tokenIn || !tokenOut)
return 0
const totalWeightedPositions = positionsData.coins.reduce((acc, coin) => {
const tokenWeightedValue = BigNumber(getTokenValue(coin.amount, coin.denom)).times(
Number(marketsData[coin.denom].max_loan_to_value),
)
return tokenWeightedValue.plus(acc).toNumber()
}, 0)
// 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 tokenOutLTV = Number(marketsData[tokenOut].max_loan_to_value)
const tokenInLTV = Number(marketsData[tokenIn].max_loan_to_value)
// if the target token ltv higher, the full amount will always be able to be swapped
if (tokenOutLTV < tokenInLTV) {
// in theory, the most you can swap from x to y while keeping an health factor of 1
const maxSwapValue = BigNumber(totalLiabilitiesValue)
.minus(totalWeightedPositions)
.dividedBy(tokenOutLTV - tokenInLTV)
const maxSwapAmount = maxSwapValue.div(tokenPrices[tokenIn]).decimalPlaces(0)
// if the swappable amount is lower than the account amount, any further calculations are irrelevant
if (maxSwapAmount.isLessThanOrEqualTo(accountAmount)) return maxSwapAmount.toNumber()
}
// if margin is disabled, the max swap amount is capped at the account amount
if (!isMargin) {
return accountAmount
}
const estimatedTokenOutAmount = BigNumber(accountAmount).times(
tokenPrices[tokenIn] / tokenPrices[tokenOut],
)
let positionsCoins = [...positionsData.coins]
// if the target token is not in the account, add it to the positions
if (!positionsCoins.find((coin) => coin.denom === tokenOut)) {
positionsCoins.push({
amount: '0',
denom: tokenOut,
})
}
// calculate weighted positions assuming the initial swap is made
const totalWeightedPositionsAfterSwap = positionsCoins
.filter((coin) => coin.denom !== tokenIn)
.reduce((acc, coin) => {
const coinAmount =
coin.denom === tokenOut
? BigNumber(coin.amount).plus(estimatedTokenOutAmount).toString()
: coin.amount
const tokenWeightedValue = BigNumber(getTokenValue(coinAmount, coin.denom)).times(
Number(marketsData[coin.denom].max_loan_to_value),
)
return tokenWeightedValue.plus(acc).toNumber()
}, 0)
const maxBorrowValue =
totalWeightedPositionsAfterSwap === 0
? 0
: BigNumber(totalWeightedPositionsAfterSwap)
.minus(totalLiabilitiesValue)
.dividedBy(1 - tokenOutLTV)
.toNumber()
const maxBorrowAmount = BigNumber(maxBorrowValue).dividedBy(tokenPrices[tokenIn]).toNumber()
return BigNumber(accountAmount).plus(maxBorrowAmount).decimalPlaces(0).toNumber()
}, [
accountAmount,
getTokenValue,
isMargin,
marketsData,
positionsData,
redbankBalances,
tokenIn,
tokenOut,
tokenPrices,
])
}
export default useCalculateMaxTradeAmount