Mp 3367 staking interactions (#613)
* ♻️ Refactor borrowRate to be in full numbers * ✨Enter into HLS Staking strategy * ✨HLS Staking deposited table + Portfolio pages * tidy: refactored the masks for HealthBar --------- Co-authored-by: Linkie Link <linkielink.dev@gmail.com>
This commit is contained in:
parent
dd29f17a42
commit
d2afe06b16
@ -4,16 +4,20 @@ import getDepositedVaults from 'api/vaults/getDepositedVaults'
|
|||||||
import { BNCoin } from 'types/classes/BNCoin'
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
import { Positions } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
|
import { Positions } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
|
||||||
|
|
||||||
export default async function getAccount(accountIdAndKind: AccountIdAndKind): Promise<Account> {
|
export default async function getAccount(accountId?: string): Promise<Account> {
|
||||||
|
if (!accountId) return new Promise((_, reject) => reject('No account ID found'))
|
||||||
|
|
||||||
const creditManagerQueryClient = await getCreditManagerQueryClient()
|
const creditManagerQueryClient = await getCreditManagerQueryClient()
|
||||||
|
|
||||||
const accountPosition: Positions = await cacheFn(
|
const accountPosition: Positions = await cacheFn(
|
||||||
() => creditManagerQueryClient.positions({ accountId: accountIdAndKind.id }),
|
() => creditManagerQueryClient.positions({ accountId: accountId }),
|
||||||
positionsCache,
|
positionsCache,
|
||||||
`account/${accountIdAndKind.id}`,
|
`account/${accountId}`,
|
||||||
)
|
)
|
||||||
|
|
||||||
const depositedVaults = await getDepositedVaults(accountIdAndKind.id, accountPosition)
|
const accountKind = await creditManagerQueryClient.accountKind({ accountId: accountId })
|
||||||
|
|
||||||
|
const depositedVaults = await getDepositedVaults(accountId, accountPosition)
|
||||||
|
|
||||||
if (accountPosition) {
|
if (accountPosition) {
|
||||||
return {
|
return {
|
||||||
@ -22,7 +26,7 @@ export default async function getAccount(accountIdAndKind: AccountIdAndKind): Pr
|
|||||||
lends: accountPosition.lends.map((lend) => new BNCoin(lend)),
|
lends: accountPosition.lends.map((lend) => new BNCoin(lend)),
|
||||||
deposits: accountPosition.deposits.map((deposit) => new BNCoin(deposit)),
|
deposits: accountPosition.deposits.map((deposit) => new BNCoin(deposit)),
|
||||||
vaults: depositedVaults,
|
vaults: depositedVaults,
|
||||||
kind: accountIdAndKind.kind,
|
kind: accountKind,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
41
src/api/hls/getHLSStakingAccounts.ts
Normal file
41
src/api/hls/getHLSStakingAccounts.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import getHLSStakingAssets from 'api/hls/getHLSStakingAssets'
|
||||||
|
import getPrices from 'api/prices/getPrices'
|
||||||
|
import getAccounts from 'api/wallets/getAccounts'
|
||||||
|
import { calculateAccountLeverage, getAccountPositionValues, isAccountEmpty } from 'utils/accounts'
|
||||||
|
|
||||||
|
export default async function getHLSStakingAccounts(
|
||||||
|
address?: string,
|
||||||
|
): Promise<HLSAccountWithStrategy[]> {
|
||||||
|
const accounts = await getAccounts('high_levered_strategy', address)
|
||||||
|
const activeAccounts = accounts.filter((account) => !isAccountEmpty(account))
|
||||||
|
const hlsStrategies = await getHLSStakingAssets()
|
||||||
|
const prices = await getPrices()
|
||||||
|
const hlsAccountsWithStrategy: HLSAccountWithStrategy[] = []
|
||||||
|
|
||||||
|
activeAccounts.forEach((account) => {
|
||||||
|
if (account.deposits.length === 0 || account.debts.length === 0) return
|
||||||
|
|
||||||
|
const strategy = hlsStrategies.find(
|
||||||
|
(strategy) =>
|
||||||
|
strategy.denoms.deposit === account.deposits.at(0).denom &&
|
||||||
|
strategy.denoms.borrow === account.debts.at(0).denom,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!strategy) return
|
||||||
|
|
||||||
|
const [deposits, lends, debts, vaults] = getAccountPositionValues(account, prices)
|
||||||
|
|
||||||
|
hlsAccountsWithStrategy.push({
|
||||||
|
...account,
|
||||||
|
strategy,
|
||||||
|
values: {
|
||||||
|
net: deposits.minus(debts),
|
||||||
|
debt: debts,
|
||||||
|
total: deposits,
|
||||||
|
},
|
||||||
|
leverage: calculateAccountLeverage(account, prices).toNumber(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return hlsAccountsWithStrategy
|
||||||
|
}
|
@ -1,14 +1,18 @@
|
|||||||
import { getParamsQueryClient } from 'api/cosmwasm-client'
|
import { getParamsQueryClient } from 'api/cosmwasm-client'
|
||||||
import getAssetParams from 'api/params/getAssetParams'
|
import getAssetParams from 'api/params/getAssetParams'
|
||||||
|
import { getStakingAssets } from 'utils/assets'
|
||||||
import { BN } from 'utils/helpers'
|
import { BN } from 'utils/helpers'
|
||||||
import { resolveHLSStrategies } from 'utils/resolvers'
|
import { resolveHLSStrategies } from 'utils/resolvers'
|
||||||
|
|
||||||
export default async function getHLSStakingAssets() {
|
export default async function getHLSStakingAssets() {
|
||||||
|
const stakingAssetDenoms = getStakingAssets().map((asset) => asset.denom)
|
||||||
const assetParams = await getAssetParams()
|
const assetParams = await getAssetParams()
|
||||||
const client = await getParamsQueryClient()
|
const HLSAssets = assetParams
|
||||||
const HLSAssets = assetParams.filter((asset) => asset.credit_manager.hls)
|
.filter((asset) => stakingAssetDenoms.includes(asset.denom))
|
||||||
|
.filter((asset) => asset.credit_manager.hls)
|
||||||
const strategies = resolveHLSStrategies('coin', HLSAssets)
|
const strategies = resolveHLSStrategies('coin', HLSAssets)
|
||||||
|
|
||||||
|
const client = await getParamsQueryClient()
|
||||||
const depositCaps$ = strategies.map((strategy) =>
|
const depositCaps$ = strategies.map((strategy) =>
|
||||||
client.totalDeposit({ denom: strategy.denoms.deposit }),
|
client.totalDeposit({ denom: strategy.denoms.deposit }),
|
||||||
)
|
)
|
||||||
|
@ -41,7 +41,7 @@ export default async function getVaults(): Promise<Vault[]> {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
apy: apr ? convertAprToApy(apr.apr, 365) : null,
|
apy: apr ? convertAprToApy(apr.apr, 365) : null,
|
||||||
apr: apr ? apr.apr / 100 : null,
|
apr: apr ? apr.apr : null,
|
||||||
ltv: {
|
ltv: {
|
||||||
max: Number(vaultConfig.max_loan_to_value),
|
max: Number(vaultConfig.max_loan_to_value),
|
||||||
liq: Number(vaultConfig.liquidation_threshold),
|
liq: Number(vaultConfig.liquidation_threshold),
|
||||||
|
@ -8,7 +8,7 @@ export default async function getAccounts(kind: AccountKind, address?: string):
|
|||||||
|
|
||||||
const $accounts = accountIdsAndKinds
|
const $accounts = accountIdsAndKinds
|
||||||
.filter((a) => a.kind === kind)
|
.filter((a) => a.kind === kind)
|
||||||
.map((account) => getAccount(account))
|
.map((account) => getAccount(account.id))
|
||||||
|
|
||||||
const accounts = await Promise.all($accounts).then((accounts) => accounts)
|
const accounts = await Promise.all($accounts).then((accounts) => accounts)
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ export default function useAccountBalanceData(props: Props) {
|
|||||||
const prevDebt = updatedAccount
|
const prevDebt = updatedAccount
|
||||||
? account?.debts.find((position) => position.denom === debt.denom)
|
? account?.debts.find((position) => position.denom === debt.denom)
|
||||||
: debt
|
: debt
|
||||||
return getAssetAccountBalanceRow('borrowing', asset, prices, debt, apy * -100, prevDebt)
|
return getAssetAccountBalanceRow('borrowing', asset, prices, debt, apy, prevDebt)
|
||||||
})
|
})
|
||||||
return [...deposits, ...lends, ...vaults, ...debts]
|
return [...deposits, ...lends, ...vaults, ...debts]
|
||||||
}, [prices, account, updatedAccount, borrowingData, lendingData])
|
}, [prices, account, updatedAccount, borrowingData, lendingData])
|
||||||
|
@ -21,7 +21,7 @@ interface Props {
|
|||||||
|
|
||||||
export default function AccountStats(props: Props) {
|
export default function AccountStats(props: Props) {
|
||||||
const { accountId, isActive, setShowMenu } = props
|
const { accountId, isActive, setShowMenu } = props
|
||||||
const { data: account } = useAccount('default', accountId)
|
const { data: account } = useAccount(accountId)
|
||||||
const { data: prices } = usePrices()
|
const { data: prices } = usePrices()
|
||||||
const positionBalance = useMemo(
|
const positionBalance = useMemo(
|
||||||
() => (!account ? null : calculateAccountBalanceValue(account, prices)),
|
() => (!account ? null : calculateAccountBalanceValue(account, prices)),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
|
import HealthIcon from 'components/Account/Health/HealthIcon'
|
||||||
import HealthTooltip from 'components/Account/Health/HealthTooltip'
|
import HealthTooltip from 'components/Account/Health/HealthTooltip'
|
||||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||||
import { LocalStorageKeys } from 'constants/localStorageKeys'
|
import { LocalStorageKeys } from 'constants/localStorageKeys'
|
||||||
@ -9,12 +10,15 @@ import useLocalStorage from 'hooks/useLocalStorage'
|
|||||||
import { getHealthIndicatorColors } from 'utils/healthIndicator'
|
import { getHealthIndicatorColors } from 'utils/healthIndicator'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
className?: string
|
||||||
|
hasLabel?: boolean
|
||||||
health: number
|
health: number
|
||||||
healthFactor: number
|
healthFactor: number
|
||||||
|
height?: string
|
||||||
|
iconClassName?: string
|
||||||
updatedHealth?: number
|
updatedHealth?: number
|
||||||
updatedHealthFactor?: number
|
updatedHealthFactor?: number
|
||||||
hasLabel?: boolean
|
showIcon?: boolean
|
||||||
className?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateHealth(health: number): number {
|
function calculateHealth(health: number): number {
|
||||||
@ -36,6 +40,9 @@ export default function HealthBar({
|
|||||||
healthFactor = 0,
|
healthFactor = 0,
|
||||||
updatedHealthFactor = 0,
|
updatedHealthFactor = 0,
|
||||||
className,
|
className,
|
||||||
|
height = '4',
|
||||||
|
iconClassName = 'w-5',
|
||||||
|
showIcon = false,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [reduceMotion] = useLocalStorage<boolean>(
|
const [reduceMotion] = useLocalStorage<boolean>(
|
||||||
LocalStorageKeys.REDUCE_MOTION,
|
LocalStorageKeys.REDUCE_MOTION,
|
||||||
@ -57,59 +64,81 @@ export default function HealthBar({
|
|||||||
health={isUpdated ? updatedHealth : health}
|
health={isUpdated ? updatedHealth : health}
|
||||||
healthFactor={isUpdated ? updatedHealthFactor : healthFactor}
|
healthFactor={isUpdated ? updatedHealthFactor : healthFactor}
|
||||||
>
|
>
|
||||||
<div className={classNames('flex w-full', className)}>
|
<>
|
||||||
<svg version='1.1' xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 184 4'>
|
{showIcon && (
|
||||||
|
<HealthIcon
|
||||||
|
health={health}
|
||||||
|
isLoading={healthFactor === 0}
|
||||||
|
className={classNames('mr-2', iconClassName)}
|
||||||
|
colorClass='text-white'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className={classNames('flex w-full', 'rounded-full overflow-hidden', className)}>
|
||||||
|
<svg
|
||||||
|
version='1.1'
|
||||||
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
x='0px'
|
||||||
|
y='0px'
|
||||||
|
viewBox={`0 0 184 ${height}`}
|
||||||
|
>
|
||||||
<mask id='healthBarMask'>
|
<mask id='healthBarMask'>
|
||||||
<path fill='#FFFFFF' d='M0,2c0-1.1,0.9-2,2-2h41.6v4H2C0.9,4,0,3.1,0,2z' />
|
<rect fill='#FFFFFF' x='46' width='47' height={height} />
|
||||||
<rect x='46' fill='#FFFFFF' width='47' height='4' />
|
<rect fill='#FFFFFF' width='43.6' height={height} />
|
||||||
<path fill='#FFFFFF' d='M95.5,0H182c1.1,0,2,0.9,2,2s-0.9,2-2,2H95.5V0z' />
|
<rect fill='#FFFFFF' x='95.5' width='88.5' height={height} />
|
||||||
</mask>
|
</mask>
|
||||||
<mask id='backgroundHealthBarMask'>
|
<mask id='backgroundHealthBarMask'>
|
||||||
<rect x='62.1' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='0' y='0' width='6.4' height='{height}' />
|
||||||
<rect x='48' fill='white' width='2' height='4' />
|
<rect fill='#FFFFFF' x='8.9' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='57.3' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='13.7' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='52.5' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='18.5' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='66.9' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='23.3' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='86.1' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='28.1' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='81.3' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='32.9' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='71.7' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='37.7' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='90.9' fill='white' width='2.1' height='4' />
|
<rect fill='#FFFFFF' x='42.5' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='76.5' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='47.3' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='119.2' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='52.1' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='143.2' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='56.9' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='138.4' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='61.7' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='133.6' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='66.5' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='124' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='71.3' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='100' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='76.1' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='104.8' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='80.9' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='109.6' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='85.7' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='114.4' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='90.5' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='128.8' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='95.3' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='172' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='100.1' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='176.8' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='104.9' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='95.5' fill='white' width='2.1' height='4' />
|
<rect fill='#FFFFFF' x='109.7' y='0' width='2.4' height='{height}' />
|
||||||
<path fill='white' d='M182,0h-0.4v4h0.4c1.1,0,2-0.9,2-2S183.1,0,182,0z' />
|
<rect fill='#FFFFFF' x='114.5' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='162.4' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='119.2' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='152.8' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='124' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='157.6' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='128.8' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='167.2' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='133.6' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='148' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='138.4' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='17.2' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='143.2' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='12.4' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='148' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='3.1' fill='white' width='2.1' height='4' />
|
<rect fill='#FFFFFF' x='152.8' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='7.6' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='157.6' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='22' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='162.4' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='41.2' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='167.2' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='36.4' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='172' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='26.8' fill='white' width='2.4' height='4' />
|
<rect fill='#FFFFFF' x='176.8' y='0' width='2.4' height='{height}' />
|
||||||
<path fill='white' d='M0.7,0.5C0.3,0.9,0,1.4,0,2s0.3,1.1,0.7,1.5V0.5z' />
|
<rect fill='#FFFFFF' x='181.6' y='0' width='2.4' height='{height}' />
|
||||||
<rect x='31.6' fill='white' width='2.4' height='4' />
|
|
||||||
</mask>
|
</mask>
|
||||||
<rect className='fill-white/10' width='184' height='4' mask='url(#healthBarMask)' />
|
|
||||||
<rect
|
<rect
|
||||||
className={classNames(backgroundColor, !reduceMotion && 'transition-all duration-500')}
|
className='fill-white/10'
|
||||||
|
width='184'
|
||||||
|
height={height}
|
||||||
|
mask='url(#healthBarMask)'
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
className={classNames(
|
||||||
|
backgroundColor,
|
||||||
|
!reduceMotion && 'transition-all duration-500',
|
||||||
|
)}
|
||||||
width={isUpdated && isIncrease ? updatedWidth : width}
|
width={isUpdated && isIncrease ? updatedWidth : width}
|
||||||
height='4'
|
height={height}
|
||||||
mask={isUpdated ? 'url(#backgroundHealthBarMask)' : 'url(#healthBarMask)'}
|
mask={isUpdated ? 'url(#backgroundHealthBarMask)' : 'url(#healthBarMask)'}
|
||||||
/>
|
/>
|
||||||
{isUpdated && (
|
{isUpdated && (
|
||||||
@ -119,12 +148,13 @@ export default function HealthBar({
|
|||||||
!reduceMotion && 'transition-all duration-500',
|
!reduceMotion && 'transition-all duration-500',
|
||||||
)}
|
)}
|
||||||
width={isUpdated && !isIncrease ? updatedWidth : width}
|
width={isUpdated && !isIncrease ? updatedWidth : width}
|
||||||
height='4'
|
height={height}
|
||||||
mask='url(#healthBarMask)'
|
mask='url(#healthBarMask)'
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
</HealthTooltip>
|
</HealthTooltip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ export default function HealthIcon(props: Props) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!isLoading && health === 0 ? (
|
{!isLoading && health === 0 ? (
|
||||||
<ExclamationMarkCircled className='w-5 text-loss animate-pulse' />
|
<ExclamationMarkCircled className={classNames('w-5 text-loss animate-pulse', className)} />
|
||||||
) : (
|
) : (
|
||||||
<Heart
|
<Heart
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
@ -17,7 +17,7 @@ export default function BorrowRate(props: Props) {
|
|||||||
return (
|
return (
|
||||||
<FormattedNumber
|
<FormattedNumber
|
||||||
className='justify-end text-xs'
|
className='justify-end text-xs'
|
||||||
amount={props.borrowRate * 100}
|
amount={props.borrowRate}
|
||||||
options={{ minDecimals: 2, maxDecimals: 2, suffix: '%' }}
|
options={{ minDecimals: 2, maxDecimals: 2, suffix: '%' }}
|
||||||
animate
|
animate
|
||||||
/>
|
/>
|
||||||
|
38
src/components/DepositCapCell.tsx
Normal file
38
src/components/DepositCapCell.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { FormattedNumber } from 'components/FormattedNumber'
|
||||||
|
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||||
|
import { VAULT_DEPOSIT_BUFFER } from 'constants/vaults'
|
||||||
|
import { getAssetByDenom } from 'utils/assets'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
depositCap: DepositCap
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DepositCapCell(props: Props) {
|
||||||
|
const percent = props.depositCap.used
|
||||||
|
.dividedBy(props.depositCap.max.multipliedBy(VAULT_DEPOSIT_BUFFER))
|
||||||
|
.multipliedBy(100)
|
||||||
|
.integerValue()
|
||||||
|
|
||||||
|
const decimals = getAssetByDenom(props.depositCap.denom)?.decimals ?? 6
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TitleAndSubCell
|
||||||
|
title={
|
||||||
|
<FormattedNumber
|
||||||
|
amount={props.depositCap.max.toNumber()}
|
||||||
|
options={{ minDecimals: 2, abbreviated: true, decimals }}
|
||||||
|
className='text-xs'
|
||||||
|
animate
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
sub={
|
||||||
|
<FormattedNumber
|
||||||
|
amount={percent.toNumber()}
|
||||||
|
options={{ minDecimals: 2, maxDecimals: 2, suffix: '% Filled' }}
|
||||||
|
className='text-xs'
|
||||||
|
animate
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
@ -1,10 +1,7 @@
|
|||||||
import { Row } from '@tanstack/react-table'
|
import { Row } from '@tanstack/react-table'
|
||||||
|
|
||||||
import { FormattedNumber } from 'components/FormattedNumber'
|
import DepositCapCell from 'components/DepositCapCell'
|
||||||
import Loading from 'components/Loading'
|
import Loading from 'components/Loading'
|
||||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
|
||||||
import { VAULT_DEPOSIT_BUFFER } from 'constants/vaults'
|
|
||||||
import { getAssetByDenom } from 'utils/assets'
|
|
||||||
|
|
||||||
export const DEPOSIT_CAP_META = { accessorKey: 'cap', header: 'Deposit Cap' }
|
export const DEPOSIT_CAP_META = { accessorKey: 'cap', header: 'Deposit Cap' }
|
||||||
|
|
||||||
@ -27,31 +24,5 @@ export default function DepositCap(props: Props) {
|
|||||||
|
|
||||||
if (props.isLoading) return <Loading />
|
if (props.isLoading) return <Loading />
|
||||||
|
|
||||||
const percent = vault.cap.used
|
return <DepositCapCell depositCap={vault.cap} />
|
||||||
.dividedBy(vault.cap.max.multipliedBy(VAULT_DEPOSIT_BUFFER))
|
|
||||||
.multipliedBy(100)
|
|
||||||
.integerValue()
|
|
||||||
|
|
||||||
const decimals = getAssetByDenom(vault.cap.denom)?.decimals ?? 6
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TitleAndSubCell
|
|
||||||
title={
|
|
||||||
<FormattedNumber
|
|
||||||
amount={vault.cap.max.toNumber()}
|
|
||||||
options={{ minDecimals: 2, abbreviated: true, decimals }}
|
|
||||||
className='text-xs'
|
|
||||||
animate
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
sub={
|
|
||||||
<FormattedNumber
|
|
||||||
amount={percent.toNumber()}
|
|
||||||
options={{ minDecimals: 2, maxDecimals: 2, suffix: '% Filled' }}
|
|
||||||
className='text-xs'
|
|
||||||
animate
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ export default function VaultCard(props: Props) {
|
|||||||
abbreviated: true,
|
abbreviated: true,
|
||||||
decimals: getAssetByDenom(props.vault.cap.denom)?.decimals,
|
decimals: getAssetByDenom(props.vault.cap.denom)?.decimals,
|
||||||
})}
|
})}
|
||||||
sub={'Depo. Cap'}
|
sub={'Deposit Cap'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ActionButton onClick={openVaultModal} color='secondary' text='Deposit' className='w-full' />
|
<ActionButton onClick={openVaultModal} color='secondary' text='Deposit' className='w-full' />
|
||||||
|
@ -14,7 +14,7 @@ export default function Apr(props: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AssetRate
|
<AssetRate
|
||||||
rate={convertAprToApy((props.marketLiquidityRate ?? 0) * 100, 365)}
|
rate={convertAprToApy(props.marketLiquidityRate ?? 0, 365)}
|
||||||
isEnabled={props.borrowEnabled}
|
isEnabled={props.borrowEnabled}
|
||||||
className='justify-end text-xs'
|
className='justify-end text-xs'
|
||||||
type='apy'
|
type='apy'
|
||||||
|
@ -7,7 +7,7 @@ import { demagnify } from 'utils/formatters'
|
|||||||
|
|
||||||
export const DEPOSIT_CAP_META = {
|
export const DEPOSIT_CAP_META = {
|
||||||
accessorKey: 'marketDepositCap',
|
accessorKey: 'marketDepositCap',
|
||||||
header: 'Depo. Cap',
|
header: 'Deposit Cap',
|
||||||
id: 'marketDepositCap',
|
id: 'marketDepositCap',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
src/components/HLS/HLSTag.tsx
Normal file
11
src/components/HLS/HLSTag.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import Text from 'components/Text'
|
||||||
|
|
||||||
|
export default function HLSTag() {
|
||||||
|
return (
|
||||||
|
<Text tag='span' className='rounded-sm gradient-hls px-2 font-bold py-0.5 ml-2' size='xs'>
|
||||||
|
HLS
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
24
src/components/HLS/Staking/ActiveStakingAccounts.tsx
Normal file
24
src/components/HLS/Staking/ActiveStakingAccounts.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { NAME_META } from 'components/HLS/Farm/Table/Columns/Name'
|
||||||
|
import useDepositedColumns from 'components/HLS/Staking/Table/Columns/useDepositedColumns'
|
||||||
|
import Table from 'components/Table'
|
||||||
|
import useHLSStakingAccounts from 'hooks/useHLSStakingAccounts'
|
||||||
|
import useStore from 'store'
|
||||||
|
|
||||||
|
const title = 'Active Strategies'
|
||||||
|
|
||||||
|
export default function ActiveStakingAccounts() {
|
||||||
|
const address = useStore((s) => s.address)
|
||||||
|
const columns = useDepositedColumns({ isLoading: false })
|
||||||
|
const { data: hlsStakingAccounts } = useHLSStakingAccounts(address)
|
||||||
|
|
||||||
|
if (!hlsStakingAccounts.length) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
title={title}
|
||||||
|
columns={columns}
|
||||||
|
data={hlsStakingAccounts}
|
||||||
|
initialSorting={[{ id: NAME_META.id, desc: true }]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
@ -6,7 +6,7 @@ import Table from 'components/Table'
|
|||||||
import useHLSStakingAssets from 'hooks/useHLSStakingAssets'
|
import useHLSStakingAssets from 'hooks/useHLSStakingAssets'
|
||||||
import { getEnabledMarketAssets } from 'utils/assets'
|
import { getEnabledMarketAssets } from 'utils/assets'
|
||||||
|
|
||||||
const title = 'Available HLS Staking'
|
const title = 'Available Strategies'
|
||||||
|
|
||||||
function Content() {
|
function Content() {
|
||||||
const assets = getEnabledMarketAssets()
|
const assets = getEnabledMarketAssets()
|
||||||
|
30
src/components/HLS/Staking/Table/Columns/Account.tsx
Normal file
30
src/components/HLS/Staking/Table/Columns/Account.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import HealthBar from 'components/Account/Health/HealthBar'
|
||||||
|
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||||
|
import useHealthComputer from 'hooks/useHealthComputer'
|
||||||
|
|
||||||
|
export const ACCOUNT_META = { id: 'account', header: 'Account', accessorKey: 'id' }
|
||||||
|
interface Props {
|
||||||
|
account: HLSAccountWithStrategy
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Name(props: Props) {
|
||||||
|
const { health, healthFactor } = useHealthComputer(props.account)
|
||||||
|
return (
|
||||||
|
<TitleAndSubCell
|
||||||
|
className=''
|
||||||
|
title={`Account ${props.account.id}`}
|
||||||
|
sub={
|
||||||
|
<HealthBar
|
||||||
|
health={health}
|
||||||
|
healthFactor={healthFactor}
|
||||||
|
className=''
|
||||||
|
showIcon
|
||||||
|
height='10'
|
||||||
|
iconClassName='mr-0.5 w-3'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
22
src/components/HLS/Staking/Table/Columns/ActiveApy.tsx
Normal file
22
src/components/HLS/Staking/Table/Columns/ActiveApy.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Row } from '@tanstack/react-table'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||||
|
|
||||||
|
export const ACTIVE_APY_META = { header: 'APY', accessorKey: 'strategy' }
|
||||||
|
|
||||||
|
export const activeApySortingFn = (
|
||||||
|
a: Row<HLSAccountWithStrategy>,
|
||||||
|
b: Row<HLSAccountWithStrategy>,
|
||||||
|
): number => {
|
||||||
|
// TODO: Properly implement this
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
account: HLSAccountWithStrategy
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ActiveAPY(props: Props) {
|
||||||
|
return <TitleAndSubCell title={'-'} sub={'-%/day'} />
|
||||||
|
}
|
25
src/components/HLS/Staking/Table/Columns/DebtValue.tsx
Normal file
25
src/components/HLS/Staking/Table/Columns/DebtValue.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Row } from '@tanstack/react-table'
|
||||||
|
|
||||||
|
import DisplayCurrency from 'components/DisplayCurrency'
|
||||||
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
|
|
||||||
|
export const DEBT_VAL_META = { header: 'Debt Value', accessorKey: 'values.debt' }
|
||||||
|
interface Props {
|
||||||
|
account: HLSAccountWithStrategy
|
||||||
|
}
|
||||||
|
|
||||||
|
export function debtValueSorting(
|
||||||
|
a: Row<HLSAccountWithStrategy>,
|
||||||
|
b: Row<HLSAccountWithStrategy>,
|
||||||
|
): number {
|
||||||
|
return a.original.values.debt.minus(b.original.values.debt).toNumber()
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DebtValue(props: Props) {
|
||||||
|
return (
|
||||||
|
<DisplayCurrency
|
||||||
|
coin={BNCoin.fromDenomAndBigNumber('usd', props.account.values.debt)}
|
||||||
|
className='text-xs'
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
23
src/components/HLS/Staking/Table/Columns/DepositCap.tsx
Normal file
23
src/components/HLS/Staking/Table/Columns/DepositCap.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Row } from '@tanstack/react-table'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import DepositCapCell from 'components/DepositCapCell'
|
||||||
|
|
||||||
|
export const CAP_META = { header: 'Cap', accessorKey: 'strategy.depositCap' }
|
||||||
|
|
||||||
|
export const depositCapSortingFn = (
|
||||||
|
a: Row<HLSAccountWithStrategy>,
|
||||||
|
b: Row<HLSAccountWithStrategy>,
|
||||||
|
): number => {
|
||||||
|
const depositCapA = a.original.strategy.depositCap.max
|
||||||
|
const depositCapB = b.original.strategy.depositCap.max
|
||||||
|
return depositCapA.minus(depositCapB).toNumber()
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
account: HLSAccountWithStrategy
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Name(props: Props) {
|
||||||
|
return <DepositCapCell depositCap={props.account.strategy.depositCap} />
|
||||||
|
}
|
25
src/components/HLS/Staking/Table/Columns/Leverage.tsx
Normal file
25
src/components/HLS/Staking/Table/Columns/Leverage.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Row } from '@tanstack/react-table'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { FormattedNumber } from 'components/FormattedNumber'
|
||||||
|
|
||||||
|
export const LEV_META = { accessorKey: 'leverage ', header: 'Leverage' }
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
account: HLSAccountWithStrategy
|
||||||
|
}
|
||||||
|
|
||||||
|
export function leverageSortingFn(a: Row<HLSAccountWithStrategy>, b: Row<HLSAccountWithStrategy>) {
|
||||||
|
return a.original.leverage - b.original.leverage
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MaxLeverage(props: Props) {
|
||||||
|
return (
|
||||||
|
<FormattedNumber
|
||||||
|
amount={props.account.leverage}
|
||||||
|
options={{ minDecimals: 2, maxDecimals: 2, suffix: 'x' }}
|
||||||
|
className='text-xs'
|
||||||
|
animate
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
14
src/components/HLS/Staking/Table/Columns/Manage.tsx
Normal file
14
src/components/HLS/Staking/Table/Columns/Manage.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import Button from 'components/Button'
|
||||||
|
|
||||||
|
export const MANAGE_META = { id: 'manage' }
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
account: HLSAccountWithStrategy
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Manage(props: Props) {
|
||||||
|
// TODO: Impelement dropdown
|
||||||
|
return <Button text='Manage' color='tertiary' />
|
||||||
|
}
|
@ -5,7 +5,7 @@ import Loading from 'components/Loading'
|
|||||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||||
import { getAssetByDenom } from 'utils/assets'
|
import { getAssetByDenom } from 'utils/assets'
|
||||||
|
|
||||||
export const NAME_META = { id: 'name', header: 'Vault', accessorKey: 'denoms.deposit' }
|
export const NAME_META = { id: 'name', header: 'Strategy', accessorKey: 'strategy.denoms.deposit' }
|
||||||
interface Props {
|
interface Props {
|
||||||
strategy: HLSStrategy
|
strategy: HLSStrategy
|
||||||
}
|
}
|
||||||
@ -21,8 +21,8 @@ export default function Name(props: Props) {
|
|||||||
{depositAsset && borrowAsset ? (
|
{depositAsset && borrowAsset ? (
|
||||||
<TitleAndSubCell
|
<TitleAndSubCell
|
||||||
className='ml-2 mr-2 text-left'
|
className='ml-2 mr-2 text-left'
|
||||||
title={`${depositAsset.symbol}/${borrowAsset.symbol}`}
|
title={`${depositAsset.symbol} - ${borrowAsset.symbol}`}
|
||||||
sub='Staking'
|
sub='Via MARS'
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Loading />
|
<Loading />
|
||||||
|
25
src/components/HLS/Staking/Table/Columns/NetValue.tsx
Normal file
25
src/components/HLS/Staking/Table/Columns/NetValue.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Row } from '@tanstack/react-table'
|
||||||
|
|
||||||
|
import DisplayCurrency from 'components/DisplayCurrency'
|
||||||
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
|
|
||||||
|
export const NET_VAL_META = { header: 'Net Value', accessorKey: 'values.net' }
|
||||||
|
interface Props {
|
||||||
|
account: HLSAccountWithStrategy
|
||||||
|
}
|
||||||
|
|
||||||
|
export function netValueSorting(
|
||||||
|
a: Row<HLSAccountWithStrategy>,
|
||||||
|
b: Row<HLSAccountWithStrategy>,
|
||||||
|
): number {
|
||||||
|
return a.original.values.net.minus(b.original.values.net).toNumber()
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function NetValue(props: Props) {
|
||||||
|
return (
|
||||||
|
<DisplayCurrency
|
||||||
|
coin={BNCoin.fromDenomAndBigNumber('usd', props.account.values.net)}
|
||||||
|
className='text-xs'
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
25
src/components/HLS/Staking/Table/Columns/PositionValue.tsx
Normal file
25
src/components/HLS/Staking/Table/Columns/PositionValue.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Row } from '@tanstack/react-table'
|
||||||
|
|
||||||
|
import DisplayCurrency from 'components/DisplayCurrency'
|
||||||
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
|
|
||||||
|
export const POS_VAL_META = { header: 'Pos. Value', accessorKey: 'values.total' }
|
||||||
|
interface Props {
|
||||||
|
account: HLSAccountWithStrategy
|
||||||
|
}
|
||||||
|
|
||||||
|
export function positionValueSorting(
|
||||||
|
a: Row<HLSAccountWithStrategy>,
|
||||||
|
b: Row<HLSAccountWithStrategy>,
|
||||||
|
): number {
|
||||||
|
return a.original.values.total.minus(b.original.values.total).toNumber()
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PositionValue(props: Props) {
|
||||||
|
return (
|
||||||
|
<DisplayCurrency
|
||||||
|
coin={BNCoin.fromDenomAndBigNumber('usd', props.account.values.total)}
|
||||||
|
className='text-xs'
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
import { ColumnDef } from '@tanstack/react-table'
|
||||||
|
import React, { useMemo } from 'react'
|
||||||
|
|
||||||
|
import Account, { ACCOUNT_META } from 'components/HLS/Staking/Table/Columns/Account'
|
||||||
|
import ActiveApy, {
|
||||||
|
ACTIVE_APY_META,
|
||||||
|
activeApySortingFn,
|
||||||
|
} from 'components/HLS/Staking/Table/Columns/ActiveApy'
|
||||||
|
import DebtValue, {
|
||||||
|
DEBT_VAL_META,
|
||||||
|
debtValueSorting,
|
||||||
|
} from 'components/HLS/Staking/Table/Columns/DebtValue'
|
||||||
|
import DepositCap, {
|
||||||
|
CAP_META,
|
||||||
|
depositCapSortingFn,
|
||||||
|
} from 'components/HLS/Staking/Table/Columns/DepositCap'
|
||||||
|
import Leverage, {
|
||||||
|
LEV_META,
|
||||||
|
leverageSortingFn,
|
||||||
|
} from 'components/HLS/Staking/Table/Columns/Leverage'
|
||||||
|
import Manage, { MANAGE_META } from 'components/HLS/Staking/Table/Columns/Manage'
|
||||||
|
import Name, { NAME_META } from 'components/HLS/Staking/Table/Columns/Name'
|
||||||
|
import NetValue, {
|
||||||
|
NET_VAL_META,
|
||||||
|
netValueSorting,
|
||||||
|
} from 'components/HLS/Staking/Table/Columns/NetValue'
|
||||||
|
import PositionValue, {
|
||||||
|
POS_VAL_META,
|
||||||
|
positionValueSorting,
|
||||||
|
} from 'components/HLS/Staking/Table/Columns/PositionValue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isLoading: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useDepositedColumns(props: Props) {
|
||||||
|
return useMemo<ColumnDef<HLSAccountWithStrategy>[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
...NAME_META,
|
||||||
|
cell: ({ row }) => <Name strategy={row.original.strategy} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...ACCOUNT_META,
|
||||||
|
cell: ({ row }) => <Account account={row.original} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...LEV_META,
|
||||||
|
cell: ({ row }) => <Leverage account={row.original} />,
|
||||||
|
sortingFn: leverageSortingFn,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...POS_VAL_META,
|
||||||
|
cell: ({ row }) => <PositionValue account={row.original} />,
|
||||||
|
sortingFn: positionValueSorting,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...NET_VAL_META,
|
||||||
|
cell: ({ row }) => <NetValue account={row.original} />,
|
||||||
|
sortingFn: netValueSorting,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...DEBT_VAL_META,
|
||||||
|
cell: ({ row }) => <DebtValue account={row.original} />,
|
||||||
|
sortingFn: debtValueSorting,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...CAP_META,
|
||||||
|
cell: ({ row }) => <DepositCap account={row.original} />,
|
||||||
|
sortingFn: depositCapSortingFn,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...ACTIVE_APY_META,
|
||||||
|
cell: ({ row }) => <ActiveApy account={row.original} />,
|
||||||
|
sortingFn: activeApySortingFn,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...MANAGE_META,
|
||||||
|
cell: ({ row }) => <Manage account={row.original} />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
}
|
@ -30,7 +30,7 @@ export default function useAssetTableColumns(isBorrow: boolean) {
|
|||||||
const borrowAsset = row.original.asset as BorrowAsset
|
const borrowAsset = row.original.asset as BorrowAsset
|
||||||
const showRate = !borrowAsset?.borrowRate
|
const showRate = !borrowAsset?.borrowRate
|
||||||
const rate = isBorrow ? market?.borrowRate : market?.liquidityRate
|
const rate = isBorrow ? market?.borrowRate : market?.liquidityRate
|
||||||
const apy = convertAprToApy((rate ?? 0) * 100, 365)
|
const apy = convertAprToApy(rate ?? 0, 365)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex items-center'>
|
<div className='flex items-center'>
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
|
|
||||||
import Button from 'components/Button'
|
|
||||||
import { ArrowRight } from 'components/Icons'
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
hlsAccounts: AccountIdAndKind[]
|
|
||||||
onClickBtn: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ChooseAccount(props: Props) {
|
|
||||||
return (
|
|
||||||
<div className='p-4'>
|
|
||||||
<Button onClick={props.onClickBtn} text='Continue' rightIcon={<ArrowRight />} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
@ -65,6 +65,7 @@ export default function useAccordionItems(props: Props) {
|
|||||||
onChangeAmount={props.onChangeDebt}
|
onChangeAmount={props.onChangeDebt}
|
||||||
onClickBtn={() => props.toggleIsOpen(2)}
|
onClickBtn={() => props.toggleIsOpen(2)}
|
||||||
max={props.maxBorrowAmount}
|
max={props.maxBorrowAmount}
|
||||||
|
positionValue={props.positionValue}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
renderSubTitle: () => (
|
renderSubTitle: () => (
|
||||||
@ -78,7 +79,7 @@ export default function useAccordionItems(props: Props) {
|
|||||||
toggleOpen: props.toggleIsOpen,
|
toggleOpen: props.toggleIsOpen,
|
||||||
},
|
},
|
||||||
...[
|
...[
|
||||||
props.hlsAccounts.length > 2
|
props.emptyHlsAccounts.length > 0
|
||||||
? {
|
? {
|
||||||
title: 'Select HLS Account',
|
title: 'Select HLS Account',
|
||||||
renderContent: () => (
|
renderContent: () => (
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
import { useCallback } from 'react'
|
import { useCallback, useMemo } from 'react'
|
||||||
|
|
||||||
|
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||||
|
import { LocalStorageKeys } from 'constants/localStorageKeys'
|
||||||
import useDepositHlsVault from 'hooks/useDepositHlsVault'
|
import useDepositHlsVault from 'hooks/useDepositHlsVault'
|
||||||
|
import useHealthComputer from 'hooks/useHealthComputer'
|
||||||
|
import useLocalStorage from 'hooks/useLocalStorage'
|
||||||
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
|
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
|
||||||
import { BN } from 'utils/helpers'
|
import useStore from 'store'
|
||||||
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
|
import { Action } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
borrowAsset: Asset
|
borrowAsset: Asset
|
||||||
@ -12,6 +18,8 @@ interface Props {
|
|||||||
|
|
||||||
export default function useVaultController(props: Props) {
|
export default function useVaultController(props: Props) {
|
||||||
const { collateralAsset, borrowAsset, selectedAccount } = props
|
const { collateralAsset, borrowAsset, selectedAccount } = props
|
||||||
|
const [slippage] = useLocalStorage<number>(LocalStorageKeys.SLIPPAGE, DEFAULT_SETTINGS.slippage)
|
||||||
|
const addToStakingStrategy = useStore((s) => s.addToStakingStrategy)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
leverage,
|
leverage,
|
||||||
@ -25,17 +33,66 @@ export default function useVaultController(props: Props) {
|
|||||||
borrowDenom: borrowAsset.denom,
|
borrowDenom: borrowAsset.denom,
|
||||||
})
|
})
|
||||||
|
|
||||||
const actions = []
|
const depositCoin = useMemo(
|
||||||
|
() => BNCoin.fromDenomAndBigNumber(collateralAsset.denom, depositAmount),
|
||||||
|
[collateralAsset.denom, depositAmount],
|
||||||
|
)
|
||||||
|
|
||||||
const { updatedAccount, simulateVaultDeposit } = useUpdatedAccount(selectedAccount)
|
const borrowCoin = useMemo(
|
||||||
|
() => BNCoin.fromDenomAndBigNumber(borrowAsset.denom, borrowAmount),
|
||||||
|
[borrowAsset.denom, borrowAmount],
|
||||||
|
)
|
||||||
|
|
||||||
const execute = () => null
|
const actions: Action[] = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
deposit: depositCoin.toCoin(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
borrow: borrowCoin.toCoin(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
swap_exact_in: {
|
||||||
|
denom_out: collateralAsset.denom,
|
||||||
|
slippage: slippage.toString(),
|
||||||
|
coin_in: BNCoin.fromDenomAndBigNumber(borrowAsset.denom, borrowAmount).toActionCoin(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[borrowAmount, borrowAsset.denom, borrowCoin, collateralAsset.denom, depositCoin, slippage],
|
||||||
|
)
|
||||||
|
|
||||||
|
const { updatedAccount, addDeposits } = useUpdatedAccount(selectedAccount)
|
||||||
|
const { computeMaxBorrowAmount } = useHealthComputer(updatedAccount)
|
||||||
|
|
||||||
|
const maxBorrowAmount = useMemo(() => {
|
||||||
|
// TODO: Perhaps we need a specific target for this -> target = swap
|
||||||
|
return computeMaxBorrowAmount(props.borrowAsset.denom, 'deposit')
|
||||||
|
}, [computeMaxBorrowAmount, props.borrowAsset.denom])
|
||||||
|
|
||||||
|
const execute = useCallback(() => {
|
||||||
|
addToStakingStrategy({
|
||||||
|
actions,
|
||||||
|
accountId: selectedAccount.id,
|
||||||
|
borrowCoin: BNCoin.fromDenomAndBigNumber(borrowAsset.denom, borrowAmount),
|
||||||
|
depositCoin: BNCoin.fromDenomAndBigNumber(collateralAsset.denom, depositAmount),
|
||||||
|
})
|
||||||
|
}, [
|
||||||
|
actions,
|
||||||
|
addToStakingStrategy,
|
||||||
|
borrowAmount,
|
||||||
|
borrowAsset.denom,
|
||||||
|
collateralAsset.denom,
|
||||||
|
depositAmount,
|
||||||
|
selectedAccount.id,
|
||||||
|
])
|
||||||
|
|
||||||
const onChangeCollateral = useCallback(
|
const onChangeCollateral = useCallback(
|
||||||
(amount: BigNumber) => {
|
(amount: BigNumber) => {
|
||||||
setDepositAmount(amount)
|
setDepositAmount(amount)
|
||||||
|
addDeposits([BNCoin.fromDenomAndBigNumber(collateralAsset.denom, amount)])
|
||||||
},
|
},
|
||||||
[setDepositAmount],
|
[addDeposits, collateralAsset.denom, setDepositAmount],
|
||||||
)
|
)
|
||||||
|
|
||||||
const onChangeDebt = useCallback(
|
const onChangeDebt = useCallback(
|
||||||
@ -50,7 +107,7 @@ export default function useVaultController(props: Props) {
|
|||||||
depositAmount,
|
depositAmount,
|
||||||
execute,
|
execute,
|
||||||
leverage,
|
leverage,
|
||||||
maxBorrowAmount: BN(0),
|
maxBorrowAmount,
|
||||||
onChangeCollateral,
|
onChangeCollateral,
|
||||||
onChangeDebt,
|
onChangeDebt,
|
||||||
positionValue,
|
positionValue,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import DoubleLogo from 'components/DoubleLogo'
|
import DoubleLogo from 'components/DoubleLogo'
|
||||||
|
import HLSTag from 'components/HLS/HLSTag'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import { getAssetByDenom } from 'utils/assets'
|
import { getAssetByDenom } from 'utils/assets'
|
||||||
|
|
||||||
@ -19,9 +20,7 @@ export default function Header(props: Props) {
|
|||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<DoubleLogo primaryDenom={props.primaryDenom} secondaryDenom={props.secondaryDenom} />
|
<DoubleLogo primaryDenom={props.primaryDenom} secondaryDenom={props.secondaryDenom} />
|
||||||
<Text>{`${primaryAsset.symbol} - ${secondaryAsset.symbol}`}</Text>
|
<Text>{`${primaryAsset.symbol} - ${secondaryAsset.symbol}`}</Text>
|
||||||
<Text className='rounded-sm gradient-hls px-2 font-bold py-0.5' size='xs'>
|
<HLSTag />
|
||||||
HLS
|
|
||||||
</Text>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ interface Props {
|
|||||||
max: BigNumber
|
max: BigNumber
|
||||||
onChangeAmount: (amount: BigNumber) => void
|
onChangeAmount: (amount: BigNumber) => void
|
||||||
onClickBtn: () => void
|
onClickBtn: () => void
|
||||||
|
positionValue: BigNumber
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Leverage(props: Props) {
|
export default function Leverage(props: Props) {
|
||||||
@ -23,7 +24,7 @@ export default function Leverage(props: Props) {
|
|||||||
onChange={props.onChangeAmount}
|
onChange={props.onChangeAmount}
|
||||||
maxText='Max borrow'
|
maxText='Max borrow'
|
||||||
/>
|
/>
|
||||||
<LeverageSummary asset={props.asset} />
|
<LeverageSummary asset={props.asset} positionValue={props.positionValue} />
|
||||||
<Button onClick={props.onClickBtn} text='Continue' rightIcon={<ArrowRight />} />
|
<Button onClick={props.onClickBtn} text='Continue' rightIcon={<ArrowRight />} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -2,14 +2,19 @@ import React, { useMemo } from 'react'
|
|||||||
|
|
||||||
import { FormattedNumber } from 'components/FormattedNumber'
|
import { FormattedNumber } from 'components/FormattedNumber'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
|
import useBorrowAsset from 'hooks/useBorrowAsset'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
asset: Asset
|
asset: Asset
|
||||||
|
positionValue: BigNumber
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LeverageSummary(props: Props) {
|
export default function LeverageSummary(props: Props) {
|
||||||
|
const borrowAsset = useBorrowAsset(props.asset.denom)
|
||||||
|
|
||||||
const items: { title: string; amount: number; options: FormatOptions }[] = useMemo(() => {
|
const items: { title: string; amount: number; options: FormatOptions }[] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
|
// TODO: Get APY numbers
|
||||||
{
|
{
|
||||||
title: 'APY',
|
title: 'APY',
|
||||||
amount: 0,
|
amount: 0,
|
||||||
@ -17,16 +22,16 @@ export default function LeverageSummary(props: Props) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: `Borrow APR ${props.asset.symbol}`,
|
title: `Borrow APR ${props.asset.symbol}`,
|
||||||
amount: 0,
|
amount: borrowAsset?.borrowRate || 0,
|
||||||
options: { suffix: '%', minDecimals: 1, maxDecimals: 1 },
|
options: { suffix: '%', minDecimals: 2, maxDecimals: 2 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Total Position Size',
|
title: 'Total Position Size',
|
||||||
amount: 0,
|
amount: props.positionValue.toNumber(),
|
||||||
options: { prefix: '$' },
|
options: { prefix: '$' },
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}, [props.asset.symbol])
|
}, [borrowAsset?.borrowRate, props.asset.symbol, props.positionValue])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='grid grid-cols-2 gap-2'>
|
<div className='grid grid-cols-2 gap-2'>
|
||||||
|
@ -48,6 +48,14 @@ export default function YourPosition(props: Props) {
|
|||||||
className='text-white/60 place-self-end text-xs'
|
className='text-white/60 place-self-end text-xs'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className='flex justify-between mb-2'>
|
||||||
|
<Text className='text-white/60 text-xs'>Leverage</Text>
|
||||||
|
<FormattedNumber
|
||||||
|
amount={props.leverage}
|
||||||
|
options={{ suffix: 'x' }}
|
||||||
|
className='text-white/60 place-self-end text-xs'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className='flex justify-between'>
|
<div className='flex justify-between'>
|
||||||
<Text className='text-xs group/apytooltip' tag='span'>
|
<Text className='text-xs group/apytooltip' tag='span'>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
@ -13,7 +13,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Content(props: Props) {
|
function Content(props: Props) {
|
||||||
const { data: account } = useAccount('high_levered_strategy', props.accountId, true)
|
const { data: account } = useAccount(props.accountId, true)
|
||||||
|
|
||||||
const { data } = useBorrowMarketAssetsTableData(false)
|
const { data } = useBorrowMarketAssetsTableData(false)
|
||||||
const borrowAssets = useMemo(() => data?.allAssets || [], [data])
|
const borrowAssets = useMemo(() => data?.allAssets || [], [data])
|
||||||
|
@ -17,7 +17,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Content(props: Props) {
|
function Content(props: Props) {
|
||||||
const { data: account } = useAccount('default', props.accountId, true)
|
const { data: account } = useAccount(props.accountId, true)
|
||||||
const { data: prices } = usePrices()
|
const { data: prices } = usePrices()
|
||||||
const { health, healthFactor } = useHealthComputer(account)
|
const { health, healthFactor } = useHealthComputer(account)
|
||||||
const { data } = useBorrowMarketAssetsTableData(false)
|
const { data } = useBorrowMarketAssetsTableData(false)
|
||||||
@ -80,6 +80,7 @@ function Content(props: Props) {
|
|||||||
health={health}
|
health={health}
|
||||||
healthFactor={healthFactor}
|
healthFactor={healthFactor}
|
||||||
title={`Credit Account ${props.accountId}`}
|
title={`Credit Account ${props.accountId}`}
|
||||||
|
accountId={props.accountId}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -88,7 +89,12 @@ export default function Summary(props: Props) {
|
|||||||
return (
|
return (
|
||||||
<Suspense
|
<Suspense
|
||||||
fallback={
|
fallback={
|
||||||
<Skeleton health={0} healthFactor={0} title={`Credit Account ${props.accountId}`} />
|
<Skeleton
|
||||||
|
health={0}
|
||||||
|
healthFactor={0}
|
||||||
|
title={`Credit Account ${props.accountId}`}
|
||||||
|
accountId={props.accountId}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Content {...props} />
|
<Content {...props} />
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import HealthBar from 'components/Account/Health/HealthBar'
|
import HealthBar from 'components/Account/Health/HealthBar'
|
||||||
import HealthIcon from 'components/Account/Health/HealthIcon'
|
|
||||||
import Card from 'components/Card'
|
import Card from 'components/Card'
|
||||||
|
import HLSTag from 'components/HLS/HLSTag'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||||
|
|
||||||
@ -12,6 +12,7 @@ interface Props {
|
|||||||
healthFactor: number
|
healthFactor: number
|
||||||
accountId: string
|
accountId: string
|
||||||
isCurrent?: boolean
|
isCurrent?: boolean
|
||||||
|
isHls?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Skeleton(props: Props) {
|
export default function Skeleton(props: Props) {
|
||||||
@ -19,7 +20,10 @@ export default function Skeleton(props: Props) {
|
|||||||
return (
|
return (
|
||||||
<Card className='p-4 bg-white/5'>
|
<Card className='p-4 bg-white/5'>
|
||||||
<div className='flex items-center justify-between'>
|
<div className='flex items-center justify-between'>
|
||||||
<Text>Credit Account {accountId}</Text>
|
<Text>
|
||||||
|
Credit Account {accountId}
|
||||||
|
{props.isHls && <HLSTag />}
|
||||||
|
</Text>
|
||||||
<Text size='xs' className='text-white/60'>
|
<Text size='xs' className='text-white/60'>
|
||||||
{isCurrent && '(current)'}
|
{isCurrent && '(current)'}
|
||||||
</Text>
|
</Text>
|
||||||
@ -30,13 +34,7 @@ export default function Skeleton(props: Props) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className='flex gap-1 mt-6'>
|
<div className='flex gap-1 mt-6'>
|
||||||
<HealthIcon
|
<HealthBar health={health} healthFactor={healthFactor} showIcon />
|
||||||
isLoading={healthFactor === 0}
|
|
||||||
health={health}
|
|
||||||
className='w-5'
|
|
||||||
colorClass='text-white/60'
|
|
||||||
/>
|
|
||||||
<HealthBar health={health} healthFactor={healthFactor} />
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
|
@ -27,7 +27,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function PortfolioCard(props: Props) {
|
export default function PortfolioCard(props: Props) {
|
||||||
const { data: account } = useAccount('default', props.accountId)
|
const { data: account } = useAccount(props.accountId)
|
||||||
const { health, healthFactor } = useHealthComputer(account)
|
const { health, healthFactor } = useHealthComputer(account)
|
||||||
const { address: urlAddress } = useParams()
|
const { address: urlAddress } = useParams()
|
||||||
const { data: prices } = usePrices()
|
const { data: prices } = usePrices()
|
||||||
@ -110,6 +110,7 @@ export default function PortfolioCard(props: Props) {
|
|||||||
healthFactor={healthFactor}
|
healthFactor={healthFactor}
|
||||||
accountId={props.accountId}
|
accountId={props.accountId}
|
||||||
isCurrent={props.accountId === currentAccountId}
|
isCurrent={props.accountId === currentAccountId}
|
||||||
|
isHls={account.kind === 'high_levered_strategy'}
|
||||||
/>
|
/>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
)
|
)
|
||||||
|
@ -91,5 +91,5 @@ export default function PortfolioSummary() {
|
|||||||
|
|
||||||
if (!walletAddress && !urlAddress) return null
|
if (!walletAddress && !urlAddress) return null
|
||||||
|
|
||||||
return <SummarySkeleton title='Portfolio Summary' stats={stats} />
|
return <SummarySkeleton title='Portfolio Summary' stats={stats} accountId='' />
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,11 @@ import React from 'react'
|
|||||||
import HealthBar from 'components/Account/Health/HealthBar'
|
import HealthBar from 'components/Account/Health/HealthBar'
|
||||||
import HealthIcon from 'components/Account/Health/HealthIcon'
|
import HealthIcon from 'components/Account/Health/HealthIcon'
|
||||||
import Card from 'components/Card'
|
import Card from 'components/Card'
|
||||||
|
import HLSTag from 'components/HLS/HLSTag'
|
||||||
import Loading from 'components/Loading'
|
import Loading from 'components/Loading'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||||
|
import useAccount from 'hooks/useAccount'
|
||||||
import { DEFAULT_PORTFOLIO_STATS } from 'utils/constants'
|
import { DEFAULT_PORTFOLIO_STATS } from 'utils/constants'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -13,16 +15,21 @@ interface Props {
|
|||||||
health?: number
|
health?: number
|
||||||
healthFactor?: number
|
healthFactor?: number
|
||||||
title: string
|
title: string
|
||||||
|
accountId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SummarySkeleton(props: Props) {
|
export default function SummarySkeleton(props: Props) {
|
||||||
const { health, healthFactor, title } = props
|
const { health, healthFactor, title } = props
|
||||||
const stats = props.stats || DEFAULT_PORTFOLIO_STATS
|
const stats = props.stats || DEFAULT_PORTFOLIO_STATS
|
||||||
|
const { data: account } = useAccount(props.accountId, false)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col w-full gap-8'>
|
<div className='flex flex-col w-full gap-8'>
|
||||||
<div className='flex justify-between'>
|
<div className='flex justify-between'>
|
||||||
|
<div className='flex items-center'>
|
||||||
<Text size='2xl'>{title}</Text>
|
<Text size='2xl'>{title}</Text>
|
||||||
|
{account?.kind === 'high_levered_strategy' && <HLSTag />}
|
||||||
|
</div>
|
||||||
{health !== undefined && healthFactor !== undefined && (
|
{health !== undefined && healthFactor !== undefined && (
|
||||||
<div className='flex gap-1 max-w-[300px] flex-grow'>
|
<div className='flex gap-1 max-w-[300px] flex-grow'>
|
||||||
<HealthIcon isLoading={healthFactor === 0} health={health} className='w-5' />
|
<HealthIcon isLoading={healthFactor === 0} health={health} className='w-5' />
|
||||||
|
@ -80,7 +80,7 @@ export default function Option(props: Props) {
|
|||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
<AssetRate
|
<AssetRate
|
||||||
rate={convertAprToApy((marketAsset?.borrowRate ?? 0) * 100, 365)}
|
rate={convertAprToApy(marketAsset?.borrowRate ?? 0, 365)}
|
||||||
isEnabled={marketAsset?.borrowEnabled ?? false}
|
isEnabled={marketAsset?.borrowEnabled ?? false}
|
||||||
className='col-span-2 text-white/50'
|
className='col-span-2 text-white/50'
|
||||||
type='apy'
|
type='apy'
|
||||||
|
@ -45,7 +45,7 @@ export default function Row<T>(props: Props<T>) {
|
|||||||
key={cell.id}
|
key={cell.id}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
isSymbolOrName ? 'text-left' : 'text-right',
|
isSymbolOrName ? 'text-left' : 'text-right',
|
||||||
props.spacingClassName ?? 'p-4',
|
props.spacingClassName ?? 'px-3 py-4',
|
||||||
borderClasses,
|
borderClasses,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
@ -79,10 +79,10 @@ export default function Table<T>(props: Props<T>) {
|
|||||||
'align-center',
|
'align-center',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className='w-6 h-6 text-white'>
|
<span className='w-5 h-5 text-white'>
|
||||||
{header.column.getCanSort()
|
{header.column.getCanSort()
|
||||||
? {
|
? {
|
||||||
asc: <SortAsc />,
|
asc: <SortAsc size={16} />,
|
||||||
desc: <SortDesc />,
|
desc: <SortDesc />,
|
||||||
false: <SortNone />,
|
false: <SortNone />,
|
||||||
}[header.column.getIsSorted() as string] ?? null
|
}[header.column.getIsSorted() as string] ?? null
|
||||||
@ -90,8 +90,8 @@ export default function Table<T>(props: Props<T>) {
|
|||||||
</span>
|
</span>
|
||||||
<Text
|
<Text
|
||||||
tag='span'
|
tag='span'
|
||||||
size='sm'
|
size='xs'
|
||||||
className='flex items-center font-normal text-white/70'
|
className='flex items-center font-normal text-white/60'
|
||||||
>
|
>
|
||||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -58,6 +58,7 @@ export const ASSETS: Asset[] = [
|
|||||||
isDisplayCurrency: ENV.NETWORK !== NETWORK.TESTNET,
|
isDisplayCurrency: ENV.NETWORK !== NETWORK.TESTNET,
|
||||||
isAutoLendEnabled: false,
|
isAutoLendEnabled: false,
|
||||||
poolId: 803,
|
poolId: 803,
|
||||||
|
isStaking: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
symbol: 'WBTC.axl',
|
symbol: 'WBTC.axl',
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
|
|
||||||
import getAccount from 'api/accounts/getAccount'
|
import getAccount from 'api/accounts/getAccount'
|
||||||
import { AccountKind } from 'types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types'
|
|
||||||
|
|
||||||
export default function useAccount(kind: AccountKind, accountId?: string, suspense?: boolean) {
|
export default function useAccount(accountId?: string, suspense?: boolean) {
|
||||||
return useSWR(`account${accountId}`, () => getAccount({ id: accountId || '', kind }), {
|
return useSWR(`account${accountId}`, () => getAccount(accountId), {
|
||||||
suspense: suspense,
|
suspense: suspense,
|
||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
})
|
})
|
||||||
|
11
src/hooks/useHLSStakingAccounts.tsx
Normal file
11
src/hooks/useHLSStakingAccounts.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import useSWR from 'swr'
|
||||||
|
|
||||||
|
import getHLSStakingAccounts from 'api/hls/getHLSStakingAccounts'
|
||||||
|
|
||||||
|
export default function useHLSStakingAccounts(address?: string) {
|
||||||
|
return useSWR(`${address}/hlsStakingAccounts`, () => getHLSStakingAccounts(address), {
|
||||||
|
fallbackData: [],
|
||||||
|
suspense: true,
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
})
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import Tab from 'components/Earn/Tab'
|
import Tab from 'components/Earn/Tab'
|
||||||
|
import ActiveStakingAccounts from 'components/HLS/Staking/ActiveStakingAccounts'
|
||||||
import AvailableHlsStakingAssets from 'components/HLS/Staking/AvailableHLSStakingAssets'
|
import AvailableHlsStakingAssets from 'components/HLS/Staking/AvailableHLSStakingAssets'
|
||||||
import HLSStakingIntro from 'components/HLS/Staking/HLSStakingIntro'
|
import HLSStakingIntro from 'components/HLS/Staking/HLSStakingIntro'
|
||||||
import MigrationBanner from 'components/MigrationBanner'
|
import MigrationBanner from 'components/MigrationBanner'
|
||||||
@ -11,6 +12,7 @@ export default function HLSStakingPage() {
|
|||||||
<Tab tabs={HLS_TABS} activeTabIdx={1} />
|
<Tab tabs={HLS_TABS} activeTabIdx={1} />
|
||||||
<HLSStakingIntro />
|
<HLSStakingIntro />
|
||||||
<AvailableHlsStakingAssets />
|
<AvailableHlsStakingAssets />
|
||||||
|
<ActiveStakingAccounts />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { MsgExecuteContract } from '@delphi-labs/shuttle-react'
|
import { MsgExecuteContract } from '@delphi-labs/shuttle-react'
|
||||||
|
import BigNumber from 'bignumber.js'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { isMobile } from 'react-device-detect'
|
import { isMobile } from 'react-device-detect'
|
||||||
import { GetState, SetState } from 'zustand'
|
import { GetState, SetState } from 'zustand'
|
||||||
|
|
||||||
import { ENV } from 'constants/env'
|
import { ENV } from 'constants/env'
|
||||||
|
import { BN_ZERO } from 'constants/math'
|
||||||
import { Store } from 'store'
|
import { Store } from 'store'
|
||||||
import { BNCoin } from 'types/classes/BNCoin'
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
import { ExecuteMsg as AccountNftExecuteMsg } from 'types/generated/mars-account-nft/MarsAccountNft.types'
|
import { ExecuteMsg as AccountNftExecuteMsg } from 'types/generated/mars-account-nft/MarsAccountNft.types'
|
||||||
@ -121,6 +123,7 @@ export default function createBroadcastSlice(
|
|||||||
coins: changes.deposits?.map((debt) => debt.toCoin()) ?? [],
|
coins: changes.deposits?.map((debt) => debt.toCoin()) ?? [],
|
||||||
text: action === 'vaultCreate' ? 'Created a Vault Position' : 'Added to Vault Position',
|
text: action === 'vaultCreate' ? 'Created a Vault Position' : 'Added to Vault Position',
|
||||||
})
|
})
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
set({ toast })
|
set({ toast })
|
||||||
@ -156,6 +159,41 @@ export default function createBroadcastSlice(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
toast: null,
|
toast: null,
|
||||||
|
addToStakingStrategy: async (options: {
|
||||||
|
accountId: string
|
||||||
|
actions: Action[]
|
||||||
|
depositCoin: BNCoin
|
||||||
|
borrowCoin: BNCoin
|
||||||
|
}) => {
|
||||||
|
const msg: CreditManagerExecuteMsg = {
|
||||||
|
update_credit_account: {
|
||||||
|
account_id: options.accountId,
|
||||||
|
actions: options.actions,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = get().executeMsg({
|
||||||
|
messages: [
|
||||||
|
generateExecutionMessage(get().address, ENV.ADDRESS_CREDIT_MANAGER, msg, [
|
||||||
|
options.depositCoin.toCoin(),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const swapOptions = { denomOut: options.depositCoin.denom, coinIn: options.borrowCoin }
|
||||||
|
|
||||||
|
get().setToast({
|
||||||
|
response,
|
||||||
|
options: {
|
||||||
|
action: 'hls-staking',
|
||||||
|
accountId: options.accountId,
|
||||||
|
changes: { deposits: [options.depositCoin], debts: [options.borrowCoin] },
|
||||||
|
},
|
||||||
|
swapOptions,
|
||||||
|
})
|
||||||
|
|
||||||
|
return response.then((response) => !!response.result)
|
||||||
|
},
|
||||||
borrow: async (options: { accountId: string; coin: BNCoin; borrowToWallet: boolean }) => {
|
borrow: async (options: { accountId: string; coin: BNCoin; borrowToWallet: boolean }) => {
|
||||||
const borrowAction: Action = { borrow: options.coin.toCoin() }
|
const borrowAction: Action = { borrow: options.coin.toCoin() }
|
||||||
const withdrawAction: Action = { withdraw: options.coin.toActionCoin() }
|
const withdrawAction: Action = { withdraw: options.coin.toActionCoin() }
|
||||||
@ -687,12 +725,23 @@ export default function createBroadcastSlice(
|
|||||||
getSingleValueFromBroadcastResult(response.result, 'wasm', 'token_id') ?? undefined
|
getSingleValueFromBroadcastResult(response.result, 'wasm', 'token_id') ?? undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toast.options.action === 'swap' && toast.swapOptions) {
|
if (toast.swapOptions) {
|
||||||
const coinOut = getTokenOutFromSwapResponse(response, toast.swapOptions.denomOut)
|
const coinOut = getTokenOutFromSwapResponse(response, toast.swapOptions.denomOut)
|
||||||
const successMessage = `Swapped ${formatAmountWithSymbol(
|
|
||||||
|
if (toast.options.action === 'swap') {
|
||||||
|
toast.options.message = `Swapped ${formatAmountWithSymbol(
|
||||||
toast.swapOptions.coinIn.toCoin(),
|
toast.swapOptions.coinIn.toCoin(),
|
||||||
)} for ${formatAmountWithSymbol(coinOut)}`
|
)} for ${formatAmountWithSymbol(coinOut)}`
|
||||||
toast.options.message = successMessage
|
}
|
||||||
|
|
||||||
|
if (toast.options.action === 'hls-staking') {
|
||||||
|
const depositAmount: BigNumber = toast.options.changes?.deposits?.length
|
||||||
|
? toast.options.changes.deposits[0].amount
|
||||||
|
: BN_ZERO
|
||||||
|
|
||||||
|
coinOut.amount = depositAmount.plus(coinOut.amount).toFixed(0)
|
||||||
|
toast.options.message = `Added ${formatAmountWithSymbol(coinOut)}`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleResponseMessages({
|
handleResponseMessages({
|
||||||
|
10
src/types/interfaces/account.d.ts
vendored
10
src/types/interfaces/account.d.ts
vendored
@ -29,3 +29,13 @@ interface AccountIdAndKind {
|
|||||||
id: string
|
id: string
|
||||||
kind: AccountKind
|
kind: AccountKind
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface HLSAccountWithStrategy extends Account {
|
||||||
|
leverage: number
|
||||||
|
strategy: HLSStrategy
|
||||||
|
values: {
|
||||||
|
net: BigNumber
|
||||||
|
debt: BigNumber
|
||||||
|
total: BigNumber
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1
src/types/interfaces/asset.d.ts
vendored
1
src/types/interfaces/asset.d.ts
vendored
@ -54,6 +54,7 @@ interface Asset {
|
|||||||
pythPriceFeedId?: string
|
pythPriceFeedId?: string
|
||||||
forceFetchPrice?: boolean
|
forceFetchPrice?: boolean
|
||||||
testnetDenom?: string
|
testnetDenom?: string
|
||||||
|
isStaking?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PseudoAsset {
|
interface PseudoAsset {
|
||||||
|
7
src/types/interfaces/store/broadcast.d.ts
vendored
7
src/types/interfaces/store/broadcast.d.ts
vendored
@ -71,6 +71,7 @@ interface HandleResponseProps {
|
|||||||
| 'unlock'
|
| 'unlock'
|
||||||
| 'swap'
|
| 'swap'
|
||||||
| 'oracle'
|
| 'oracle'
|
||||||
|
| 'hls-staking'
|
||||||
lend?: boolean
|
lend?: boolean
|
||||||
accountId?: string
|
accountId?: string
|
||||||
changes?: { debts?: BNCoin[]; deposits?: BNCoin[]; lends?: BNCoin[] }
|
changes?: { debts?: BNCoin[]; deposits?: BNCoin[]; lends?: BNCoin[] }
|
||||||
@ -79,6 +80,12 @@ interface HandleResponseProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface BroadcastSlice {
|
interface BroadcastSlice {
|
||||||
|
addToStakingStrategy: (options: {
|
||||||
|
accountId: string
|
||||||
|
actions: Action[]
|
||||||
|
depositCoin: BNCoin
|
||||||
|
borrowCoin: BNCoin
|
||||||
|
}) => Promise<boolean>
|
||||||
borrow: (options: {
|
borrow: (options: {
|
||||||
accountId: string
|
accountId: string
|
||||||
coin: BNCoin
|
coin: BNCoin
|
||||||
|
@ -89,13 +89,13 @@ export const calculateAccountApr = (
|
|||||||
const apr =
|
const apr =
|
||||||
lendingAssetsData.find((lendingAsset) => lendingAsset.asset.denom === lend.denom)
|
lendingAssetsData.find((lendingAsset) => lendingAsset.asset.denom === lend.denom)
|
||||||
?.marketLiquidityRate ?? 0
|
?.marketLiquidityRate ?? 0
|
||||||
const positionInterest = amount.multipliedBy(price).multipliedBy(apr)
|
const positionInterest = amount.multipliedBy(price).multipliedBy(apr).dividedBy(100)
|
||||||
totalLendsInterestValue = totalLendsInterestValue.plus(positionInterest)
|
totalLendsInterestValue = totalLendsInterestValue.plus(positionInterest)
|
||||||
})
|
})
|
||||||
|
|
||||||
vaults?.forEach((vault) => {
|
vaults?.forEach((vault) => {
|
||||||
const lockedValue = vault.values.primary.plus(vault.values.secondary)
|
const lockedValue = vault.values.primary.plus(vault.values.secondary)
|
||||||
const positionInterest = lockedValue.multipliedBy(vault?.apr ?? 0)
|
const positionInterest = lockedValue.multipliedBy(vault?.apr ?? 0).dividedBy(100)
|
||||||
totalVaultsInterestValue = totalVaultsInterestValue.plus(positionInterest)
|
totalVaultsInterestValue = totalVaultsInterestValue.plus(positionInterest)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -107,15 +107,19 @@ export const calculateAccountApr = (
|
|||||||
const apy =
|
const apy =
|
||||||
borrowAssetsData.find((borrowAsset) => borrowAsset.asset.denom === debt.denom)?.borrowRate ??
|
borrowAssetsData.find((borrowAsset) => borrowAsset.asset.denom === debt.denom)?.borrowRate ??
|
||||||
0
|
0
|
||||||
const positionInterest = amount.multipliedBy(price).multipliedBy(convertApyToApr(apy, 365))
|
const positionInterest = amount
|
||||||
|
.multipliedBy(price)
|
||||||
|
.multipliedBy(convertApyToApr(apy, 365))
|
||||||
|
.dividedBy(100)
|
||||||
|
|
||||||
totalDebtInterestValue = totalDebtInterestValue.plus(positionInterest)
|
totalDebtInterestValue = totalDebtInterestValue.plus(positionInterest)
|
||||||
})
|
})
|
||||||
|
|
||||||
const totalInterstValue = totalLendsInterestValue
|
const totalInterestValue = totalLendsInterestValue
|
||||||
.plus(totalVaultsInterestValue)
|
.plus(totalVaultsInterestValue)
|
||||||
.minus(totalDebtInterestValue)
|
.minus(totalDebtInterestValue)
|
||||||
|
|
||||||
return totalInterstValue.dividedBy(totalNetValue).times(100)
|
return totalInterestValue.dividedBy(totalNetValue).times(100)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calculateAccountLeverage(account: Account, prices: BNCoin[]) {
|
export function calculateAccountLeverage(account: Account, prices: BNCoin[]) {
|
||||||
|
@ -43,3 +43,7 @@ export function getLendEnabledAssets() {
|
|||||||
export function getBorrowEnabledAssets() {
|
export function getBorrowEnabledAssets() {
|
||||||
return ASSETS.filter((asset) => asset.isBorrowEnabled)
|
return ASSETS.filter((asset) => asset.isBorrowEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getStakingAssets() {
|
||||||
|
return ASSETS.filter((asset) => asset.isStaking)
|
||||||
|
}
|
||||||
|
@ -148,7 +148,7 @@ export function formatLeverage(leverage: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function formatPercent(percent: number | string, minDecimals?: number) {
|
export function formatPercent(percent: number | string, minDecimals?: number) {
|
||||||
return formatValue(+percent * 100, {
|
return formatValue(+percent, {
|
||||||
minDecimals: minDecimals ?? 0,
|
minDecimals: minDecimals ?? 0,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
})
|
})
|
||||||
@ -209,6 +209,5 @@ export function getCoinAmount(denom: string, value: BigNumber, prices: BNCoin[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function convertLiquidityRateToAPR(rate: number) {
|
export function convertLiquidityRateToAPR(rate: number) {
|
||||||
const rateMulHundred = rate * 100
|
return rate >= 0.01 ? rate : 0.0
|
||||||
return rateMulHundred >= 0.01 ? rateMulHundred : 0.0
|
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,8 @@ export const convertAprToApy = (apr: number, numberOfCompoundingPeriods: number)
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const convertApyToApr = (apy: number, numberOfCompoundingPeriods: number): number => {
|
export const convertApyToApr = (apy: number, numberOfCompoundingPeriods: number): number => {
|
||||||
const periodicRate = (1 + apy) ** (1 / numberOfCompoundingPeriods) - 1
|
const periodicRate = (1 + apy / 100) ** (1 / numberOfCompoundingPeriods) - 1
|
||||||
return periodicRate * numberOfCompoundingPeriods
|
return periodicRate * numberOfCompoundingPeriods * 100
|
||||||
}
|
}
|
||||||
|
|
||||||
export const combineBNCoins = (coins: BNCoin[]): BNCoin[] => {
|
export const combineBNCoins = (coins: BNCoin[]): BNCoin[] => {
|
||||||
|
@ -13,7 +13,7 @@ export function resolveMarketResponse(
|
|||||||
): Market {
|
): Market {
|
||||||
return {
|
return {
|
||||||
denom: marketResponse.denom,
|
denom: marketResponse.denom,
|
||||||
borrowRate: Number(marketResponse.borrow_rate),
|
borrowRate: Number(marketResponse.borrow_rate) * 100,
|
||||||
debtTotalScaled: marketResponse.debt_total_scaled,
|
debtTotalScaled: marketResponse.debt_total_scaled,
|
||||||
collateralTotalScaled: marketResponse.collateral_total_scaled,
|
collateralTotalScaled: marketResponse.collateral_total_scaled,
|
||||||
depositEnabled: assetParamsResponse.red_bank.deposit_enabled,
|
depositEnabled: assetParamsResponse.red_bank.deposit_enabled,
|
||||||
@ -24,7 +24,7 @@ export function resolveMarketResponse(
|
|||||||
max: BN(assetParamsResponse.deposit_cap),
|
max: BN(assetParamsResponse.deposit_cap),
|
||||||
},
|
},
|
||||||
maxLtv: Number(assetParamsResponse.max_loan_to_value),
|
maxLtv: Number(assetParamsResponse.max_loan_to_value),
|
||||||
liquidityRate: Number(marketResponse.liquidity_rate),
|
liquidityRate: Number(marketResponse.liquidity_rate) * 100,
|
||||||
liquidationThreshold: Number(assetParamsResponse.liquidation_threshold),
|
liquidationThreshold: Number(assetParamsResponse.liquidation_threshold),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,8 +60,8 @@ export function resolveHLSStrategies(
|
|||||||
maxLeverage: getLeverageFromLTV(+asset.credit_manager.hls!.max_loan_to_value),
|
maxLeverage: getLeverageFromLTV(+asset.credit_manager.hls!.max_loan_to_value),
|
||||||
maxLTV: +asset.credit_manager.hls!.max_loan_to_value,
|
maxLTV: +asset.credit_manager.hls!.max_loan_to_value,
|
||||||
denoms: {
|
denoms: {
|
||||||
deposit: correlatedDenom,
|
deposit: asset.denom,
|
||||||
borrow: asset.denom,
|
borrow: correlatedDenom,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user