mars-v2-frontend/hooks/useAccountStats.tsx
Gustavo Mauricio 56bfea8ed0
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
2022-11-09 13:47:36 +01:00

255 lines
7.0 KiB
TypeScript

import BigNumber from 'bignumber.js'
import { useMemo } from 'react'
import useCreditManagerStore from 'stores/useCreditManagerStore'
import useCreditAccountPositions from './useCreditAccountPositions'
import useMarkets from './useMarkets'
import useTokenPrices from './useTokenPrices'
// displaying 3 levels of risk based on the weighted average of liquidation LTVs
// 0.85 -> 25% risk
// 0.65 - 0.85 -> 50% risk
// < 0.65 -> 100% risk
const getRiskFromAverageLiquidationLTVs = (value: number) => {
if (value >= 0.85) return 0.25
if (value > 0.65) return 0.5
return 1
}
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()
const currentStats = useMemo(() => {
if (!marketsData || !tokenPrices || !positionsData) return null
const assets = positionsData.coins.map((coin) => {
const market = marketsData[coin.denom]
return {
amount: coin.amount,
denom: coin.denom,
liquidationThreshold: market.liquidation_threshold,
basePrice: tokenPrices[coin.denom],
}
})
const debts = positionsData.debts.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,
}
}, [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