mars-v2-frontend/src/utils/accounts.ts
Bob van der Helm 117de1e3e5
Add perps balances table (#714)
* Add perps balances table

* fix: updated svg

---------

Co-authored-by: Linkie Link <linkielink.dev@gmail.com>
2024-01-08 10:14:32 +01:00

334 lines
11 KiB
TypeScript

import BigNumber from 'bignumber.js'
import { BN_ZERO } from 'constants/math'
import { ORACLE_DENOM } from 'constants/oracle'
import { BNCoin } from 'types/classes/BNCoin'
import { VaultPosition } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { byDenom } from 'utils/array'
import { BN } from 'utils/helpers'
import { convertApyToApr } from 'utils/parsers'
export const calculateAccountBalanceValue = (
account: Account | AccountChange,
prices: BNCoin[],
assets: Asset[],
): BigNumber => {
const depositsValue = calculateAccountValue('deposits', account, prices, assets)
const lendsValue = calculateAccountValue('lends', account, prices, assets)
const debtsValue = calculateAccountValue('debts', account, prices, assets)
const vaultsValue = calculateAccountValue('vaults', account, prices, assets)
return depositsValue.plus(lendsValue).plus(vaultsValue).minus(debtsValue)
}
export const getAccountPositionValues = (
account: Account | AccountChange,
prices: BNCoin[],
assets: Asset[],
) => {
const deposits = calculateAccountValue('deposits', account, prices, assets)
const lends = calculateAccountValue('lends', account, prices, assets)
const debts = calculateAccountValue('debts', account, prices, assets)
const vaults = calculateAccountValue('vaults', account, prices, assets)
return [deposits, lends, debts, vaults]
}
export const calculateAccountValue = (
type: 'deposits' | 'lends' | 'debts' | 'vaults',
account: Account | AccountChange,
prices: BNCoin[],
assets: Asset[],
): BigNumber => {
if (!account[type] || !prices) return BN_ZERO
if (type === 'vaults') {
return (
account.vaults?.reduce((acc, vaultPosition) => {
return acc
.plus(vaultPosition.values.primary)
.plus(vaultPosition.values.secondary)
.plus(vaultPosition.values.unlocking)
.plus(vaultPosition.values.unlocked)
}, BN_ZERO) || BN_ZERO
)
}
return account[type]?.reduce((acc, position) => {
const asset = assets.find(byDenom(position.denom))
if (!asset) return acc
const price = prices.find((price) => price.denom === position.denom)?.amount ?? 0
const amount = BN(position.amount).shiftedBy(-asset.decimals)
const positionValue = amount.multipliedBy(price)
return acc.plus(positionValue)
}, BN_ZERO)
}
export const calculateAccountApr = (
account: Account,
borrowAssetsData: BorrowMarketTableData[],
lendingAssetsData: LendingMarketTableData[],
prices: BNCoin[],
hlsStrategies: HLSStrategy[],
assets: Asset[],
isHls?: boolean,
): BigNumber => {
const depositValue = calculateAccountValue('deposits', account, prices, assets)
const lendsValue = calculateAccountValue('lends', account, prices, assets)
const vaultsValue = calculateAccountValue('vaults', account, prices, assets)
const debtsValue = calculateAccountValue('debts', account, prices, assets)
const totalValue = depositValue.plus(lendsValue).plus(vaultsValue)
const totalNetValue = totalValue.minus(debtsValue)
if (totalNetValue.isLessThanOrEqualTo(0)) return BN_ZERO
const { vaults, lends, debts, deposits } = account
let totalDepositsInterestValue = BN_ZERO
let totalLendsInterestValue = BN_ZERO
let totalVaultsInterestValue = BN_ZERO
let totalDebtInterestValue = BN_ZERO
if (isHls) {
deposits?.forEach((deposit) => {
const asset = assets.find(byDenom(deposit.denom))
if (!asset) return BN_ZERO
const price = prices.find(byDenom(deposit.denom))?.amount ?? 0
const amount = BN(deposit.amount).shiftedBy(-asset.decimals)
const apy =
hlsStrategies.find((strategy) => strategy.denoms.deposit === deposit.denom)?.apy || 0
const positionInterest = amount
.multipliedBy(price)
.multipliedBy(convertApyToApr(apy, 365))
.dividedBy(100)
totalDepositsInterestValue = totalDepositsInterestValue.plus(positionInterest)
})
}
lends?.forEach((lend) => {
const asset = assets.find(byDenom(lend.denom))
if (!asset) return BN_ZERO
const price = prices.find(byDenom(lend.denom))?.amount ?? 0
const amount = BN(lend.amount).shiftedBy(-asset.decimals)
const apy = lendingAssetsData.find((lendingAsset) => lendingAsset.asset.denom === lend.denom)
?.apy.deposit
if (!apy) return
const positionInterest = amount
.multipliedBy(price)
.multipliedBy(convertApyToApr(apy, 365))
.dividedBy(100)
totalLendsInterestValue = totalLendsInterestValue.plus(positionInterest)
})
vaults?.forEach((vault) => {
const lockedValue = vault.values.primary.plus(vault.values.secondary)
const positionInterest = lockedValue.multipliedBy(vault?.apr ?? 0).dividedBy(100)
totalVaultsInterestValue = totalVaultsInterestValue.plus(positionInterest)
})
debts?.forEach((debt) => {
const asset = assets.find(byDenom(debt.denom))
if (!asset) return BN_ZERO
const price = prices.find(byDenom(debt.denom))?.amount ?? 0
const amount = BN(debt.amount).shiftedBy(-asset.decimals)
const apy = borrowAssetsData.find((borrowAsset) => borrowAsset.asset.denom === debt.denom)?.apy
.borrow
if (!apy) return
const positionInterest = amount
.multipliedBy(price)
.multipliedBy(convertApyToApr(apy, 365))
.dividedBy(100)
totalDebtInterestValue = totalDebtInterestValue.plus(positionInterest)
})
const totalInterestValue = totalLendsInterestValue
.plus(totalVaultsInterestValue)
.minus(totalDebtInterestValue)
.plus(totalDepositsInterestValue)
return totalInterestValue.dividedBy(totalNetValue).times(100)
}
export function calculateAccountLeverage(account: Account, prices: BNCoin[], assets: Asset[]) {
const [deposits, lends, debts, vaults] = getAccountPositionValues(account, prices, assets)
const netValue = deposits.plus(lends).plus(vaults).minus(debts)
return debts.dividedBy(netValue).plus(1)
}
export function getAmount(denom: string, coins: Coin[]): BigNumber {
return BN(coins.find((asset) => asset.denom === denom)?.amount ?? 0)
}
export function accumulateAmounts(denom: string, coins: BNCoin[]): BigNumber {
return coins.reduce((acc, coin) => acc.plus(getAmount(denom, [coin.toCoin()])), BN_ZERO)
}
// TODO: 📈 Add correct type mapping
export function convertAccountToPositions(account: Account): PositionsWithoutPerps {
return {
account_id: account.id,
debts: account.debts.map((debt) => ({
shares: '0', // This is not needed, but required by the contract
amount: debt.amount.toString(),
denom: debt.denom,
})),
deposits: account.deposits.map((deposit) => deposit.toCoin()),
lends: account.lends.map((lend) => ({
shares: '0', // This is not needed, but required by the contract
amount: lend.amount.toString(),
denom: lend.denom,
})),
vaults: account.vaults.map(
(vault) =>
({
vault: {
address: vault.address,
},
amount: {
locking: {
locked: vault.amounts.locked.toString(),
unlocking: [
{
id: 0,
coin: { amount: vault.amounts.unlocking.toString(), denom: vault.denoms.lp },
},
],
},
},
}) as VaultPosition,
),
}
}
export function cloneAccount(account: Account): Account {
return {
id: account.id,
kind: account.kind,
debts: account.debts.map(
(debt) =>
new BNCoin({
amount: debt.amount.toString(),
denom: debt.denom,
}),
),
deposits: account.deposits.map((deposit) => new BNCoin(deposit.toCoin())),
lends: account.lends.map(
(lend) =>
new BNCoin({
amount: lend.amount.toString(),
denom: lend.denom,
}),
),
vaults: account.vaults.map((vault) => ({
...vault,
amounts: {
locked: vault.amounts.locked,
unlocking: vault.amounts.unlocking,
unlocked: vault.amounts.unlocked,
primary: vault.amounts.primary,
secondary: vault.amounts.secondary,
},
values: {
primary: vault.values.primary,
secondary: vault.values.secondary,
unlocking: vault.values.unlocking,
unlocked: vault.values.unlocked,
},
})),
// TODO: 📈Add correct type mapping
perps: account.perps,
}
}
export function removeDepositsAndLends(account: Account, denom: string) {
const deposits = account.deposits.filter((deposit) => deposit.denom !== denom)
const lends = account.lends.filter((lend) => lend.denom !== denom)
deposits.push(BNCoin.fromDenomAndBigNumber(denom, BN_ZERO))
lends.push(BNCoin.fromDenomAndBigNumber(denom, BN_ZERO))
return {
...account,
deposits,
lends,
}
}
export function getMergedBalancesForAsset(account: Account, assets: Asset[]) {
const balances: BNCoin[] = []
assets.forEach((asset) => {
const balance = accumulateAmounts(asset.denom, [...account.deposits, ...account.lends])
balances.push(BNCoin.fromDenomAndBigNumber(asset.denom, balance))
})
return balances
}
export function computeHealthGaugePercentage(health: number) {
const ATTENTION_CUTOFF = 10
const HEALTHY_CUTOFF = 30
const HEALTHY_BAR_SIZE = 55
const UNHEALTHY_BAR_SIZE = 21
const GAP_SIZE = 3
if (health > HEALTHY_CUTOFF) {
const basePercentage = 100 - HEALTHY_BAR_SIZE
const additionalPercentage =
((health - HEALTHY_CUTOFF) / (100 - HEALTHY_CUTOFF)) * HEALTHY_BAR_SIZE
return 100 - (basePercentage + additionalPercentage + GAP_SIZE)
}
if (health > ATTENTION_CUTOFF) {
const basePercentage = UNHEALTHY_BAR_SIZE
const additionalPercentage =
((health - ATTENTION_CUTOFF) / (HEALTHY_CUTOFF - ATTENTION_CUTOFF)) * UNHEALTHY_BAR_SIZE
return 100 - (basePercentage + additionalPercentage + GAP_SIZE)
}
return 100 - (health / ATTENTION_CUTOFF) * UNHEALTHY_BAR_SIZE
}
export function getAccountSummaryStats(
account: Account,
prices: BNCoin[],
borrowAssets: BorrowMarketTableData[],
lendingAssets: LendingMarketTableData[],
hlsStrategies: HLSStrategy[],
assets: Asset[],
isHls?: boolean,
) {
const [deposits, lends, debts, vaults] = getAccountPositionValues(account, prices, assets)
const positionValue = deposits.plus(lends).plus(vaults)
const apr = calculateAccountApr(
account,
borrowAssets,
lendingAssets,
prices,
hlsStrategies,
assets,
isHls,
)
const leverage = calculateAccountLeverage(account, prices, assets)
return {
positionValue: BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, positionValue),
debts: BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, debts),
netWorth: BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, positionValue.minus(debts)),
apr,
leverage,
}
}
export function isAccountEmpty(account: Account) {
return (
account.vaults.length === 0 &&
account.lends.length === 0 &&
account.debts.length === 0 &&
account.deposits.length === 0
)
}