* Add perps balances table * fix: updated svg --------- Co-authored-by: Linkie Link <linkielink.dev@gmail.com>
334 lines
11 KiB
TypeScript
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
|
|
)
|
|
}
|