mars-v2-frontend/src/hooks/useHealthComputer.tsx
Bob van der Helm 0325e311cb
Mp 3360 create vault position (#607)
* 🔧 Small fixes

*  Deposit into HLS Vault + Groudnwork for HLS Staking

* Adjusted according to feedback

* Adjusted according to feedback
2023-11-02 10:40:34 +01:00

206 lines
6.1 KiB
TypeScript

import { useCallback, useEffect, useMemo, useState } from 'react'
import { BN_ZERO } from 'constants/math'
import useAssetParams from 'hooks/useAssetParams'
import usePrices from 'hooks/usePrices'
import useVaultConfigs from 'hooks/useVaultConfigs'
import {
Positions,
VaultPositionValue,
} from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { VaultConfigBaseForString } from 'types/generated/mars-params/MarsParams.types'
import {
AssetParamsBaseForAddr,
HealthComputer,
} from 'types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types'
import { convertAccountToPositions } from 'utils/accounts'
import { getAssetByDenom } from 'utils/assets'
import { LTV_BUFFER } from 'utils/constants'
import {
BorrowTarget,
compute_health_js,
max_borrow_estimate_js,
max_swap_estimate_js,
max_withdraw_estimate_js,
SwapKind,
} from 'utils/health_computer'
import { BN } from 'utils/helpers' // Pyth returns prices with up to 32 decimals. Javascript only supports 18 decimals. So we need to scale by 14 t
// Pyth returns prices with up to 32 decimals. Javascript only supports 18 decimals. So we need to scale by 14 t
// avoid "too many decimals" errors.
const VALUE_SCALE_FACTOR = 14
export default function useHealthComputer(account?: Account) {
const { data: prices } = usePrices()
const { data: assetParams } = useAssetParams()
const { data: vaultConfigs } = useVaultConfigs()
const [healthFactor, setHealthFactor] = useState(0)
const positions: Positions | null = useMemo(() => {
if (!account) return null
return convertAccountToPositions(account)
}, [account])
const vaultPositionValues = useMemo(() => {
if (!account?.vaults) return null
return account.vaults.reduce(
(prev, curr) => {
const baseCoinPrice = prices.find((price) => price.denom === curr.denoms.lp)?.amount || 0
prev[curr.address] = {
base_coin: {
amount: '0', // Not used by healthcomputer
denom: curr.denoms.lp,
value: curr.amounts.unlocking.times(baseCoinPrice).integerValue().toString(),
},
vault_coin: {
amount: '0', // Not used by healthcomputer
denom: curr.denoms.vault,
value: curr.values.primary
.plus(curr.values.secondary)
.plus(curr.values.unlocking)
.plus(curr.values.unlocked)
.shiftedBy(VALUE_SCALE_FACTOR + 6) // Need to scale additional 6 to correct for uusd values
.integerValue()
.toString(),
},
}
return prev
},
{} as { [key: string]: VaultPositionValue },
)
}, [account?.vaults, prices])
const priceData = useMemo(() => {
return prices.reduce(
(prev, curr) => {
const decimals = getAssetByDenom(curr.denom)?.decimals || 6
// The HealthComputer needs prices expressed per 1 amount. So we need to correct here for any additional decimals.
prev[curr.denom] = curr.amount
.shiftedBy(VALUE_SCALE_FACTOR)
.shiftedBy(-decimals + 6)
.toString()
return prev
},
{} as { [key: string]: string },
)
}, [prices])
const denomsData = useMemo(
() =>
assetParams.reduce(
(prev, curr) => {
prev[curr.denom] = curr
return prev
},
{} as { [key: string]: AssetParamsBaseForAddr },
),
[assetParams],
)
const vaultConfigsData = useMemo(() => {
if (!vaultConfigs.length) return null
return vaultConfigs.reduce(
(prev, curr) => {
prev[curr.addr] = curr
return prev
},
{} as { [key: string]: VaultConfigBaseForString },
)
}, [vaultConfigs])
const healthComputer: HealthComputer | null = useMemo(() => {
if (
!account ||
!positions ||
!vaultPositionValues ||
!vaultConfigsData ||
Object.keys(denomsData).length === 0 ||
Object.keys(priceData).length === 0 ||
positions.vaults.length !== Object.keys(vaultPositionValues).length
)
return null
return {
denoms_data: { params: denomsData, prices: priceData },
vaults_data: {
vault_configs: vaultConfigsData,
vault_values: vaultPositionValues,
},
positions: positions,
kind: account.kind,
}
}, [account, positions, vaultPositionValues, vaultConfigsData, denomsData, priceData])
useEffect(() => {
if (!healthComputer) return
try {
setHealthFactor(Number(compute_health_js(healthComputer).max_ltv_health_factor) || 10)
} catch (err) {
console.error(err)
}
}, [healthComputer])
const computeMaxBorrowAmount = useCallback(
(denom: string, target: BorrowTarget) => {
if (!healthComputer) return BN_ZERO
try {
return BN(max_borrow_estimate_js(healthComputer, denom, target))
.multipliedBy(LTV_BUFFER)
.integerValue()
} catch (err) {
console.error(err)
return BN_ZERO
}
},
[healthComputer],
)
const computeMaxWithdrawAmount = useCallback(
(denom: string) => {
if (!healthComputer) return BN_ZERO
try {
return BN(max_withdraw_estimate_js(healthComputer, denom))
} catch (err) {
console.error(err)
return BN_ZERO
}
},
[healthComputer],
)
const computeMaxSwapAmount = useCallback(
(from: string, to: string, kind: SwapKind) => {
if (!healthComputer) return BN_ZERO
try {
return BN(max_swap_estimate_js(healthComputer, from, to, kind))
} catch {
return BN_ZERO
}
},
[healthComputer],
)
const health = useMemo(() => {
const convertedHealth = BN(Math.log(healthFactor))
.dividedBy(Math.log(3.5))
.multipliedBy(100)
.integerValue()
.toNumber()
if (convertedHealth > 100) return 100
if (convertedHealth === 0 && healthFactor > 1) return 1
if (convertedHealth < 0) return 0
return convertedHealth
}, [healthFactor])
return {
health,
healthFactor,
computeMaxBorrowAmount,
computeMaxWithdrawAmount,
computeMaxSwapAmount,
}
}