2f7b266e6b
* MP-1674: replaced the logo and added dekstop only nav * MP-1677: borrowCapacity implemented into the SubAccount Nav * MP-1677: adjusted the SubAccount navigation * M1677: fixed the button and SearchInput component * MP-1674: fixed the NavLink component * MP-1674: fixed the SubAccount navigation * tidy: cleaning up the trading view * MP-1674: added withdraw and funding functions * MP-1674: worked on the AccountStats * MP-1671: modal work * MP-1647: improvised CreditAccount expander * tidy: fixed the page structure * MP-1758: finished the SearchInput layout * MP-1759: updated the semicircle graphs * MP-1759: SemiCircle to Gauge * fix: implemented animated numbers * tidy: refactor * MP-1759: added Tooltip to the Gauge * fix: replace animate={true} with animate * fix: fixed the Gauge timing * fix: updated the BorrowCapacity styles * fix: renamed SubAccount to Account * fix: Text should not be a button, Button should be * tidy: format * fix: Text no clicky * fix: replaced all the Text appearances with onClick
265 lines
7.2 KiB
TypeScript
265 lines
7.2 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 =
|
|
totalWeightedPositions === 0
|
|
? 0
|
|
: BigNumber(totalWeightedPositions).div(totalPosition).toNumber()
|
|
|
|
const maxLeverage = BigNumber(1)
|
|
.div(BigNumber(1).minus(liquidationLTVsWeightedAverage))
|
|
.toNumber()
|
|
const currentLeverage = netWorth === 0 ? 0 : BigNumber(totalPosition).div(netWorth).toNumber()
|
|
const health =
|
|
maxLeverage === 0
|
|
? 1
|
|
: 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,
|
|
assets,
|
|
debts,
|
|
}
|
|
}, [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
|
|
.filter((coin) => coin.amount !== '0')
|
|
.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,
|
|
assets,
|
|
debts,
|
|
}
|
|
}, [actions, marketsData, positionsData, tokenPrices])
|
|
|
|
return actionResultStats || currentStats
|
|
}
|
|
|
|
export default useAccountStats
|