Mp 3360 create vault position (#607)

* 🔧 Small fixes

*  Deposit into HLS Vault + Groudnwork for HLS Staking

* Adjusted according to feedback

* Adjusted according to feedback
This commit is contained in:
Bob van der Helm 2023-11-02 10:40:34 +01:00 committed by GitHub
parent f38399606b
commit 0325e311cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 821 additions and 217 deletions

View File

@ -1,7 +1,7 @@
import { Suspense, useMemo } from 'react' import { Suspense, useMemo } from 'react'
import { NAME_META } from 'components/Earn/Farm/Table/Columns/Name' import { NAME_META } from 'components/HLS/Farm/Table/Columns/Name'
import useAvailableColumns from 'components/Earn/Farm/Table/Columns/useAvailableColumns' import useAvailableColumns from 'components/HLS/Farm/Table/Columns/useAvailableColumns'
import Table from 'components/Table' import Table from 'components/Table'
import { ENV } from 'constants/env' import { ENV } from 'constants/env'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
@ -38,20 +38,22 @@ function Fallback() {
const columns = useAvailableColumns({ isLoading: true }) const columns = useAvailableColumns({ isLoading: true })
const vaults = ENV.NETWORK === NETWORK.TESTNET ? TESTNET_VAULTS_META_DATA : VAULTS_META_DATA const vaults = ENV.NETWORK === NETWORK.TESTNET ? TESTNET_VAULTS_META_DATA : VAULTS_META_DATA
const mockVaults: Vault[] = vaults.map((vault) => ({ const mockVaults: Vault[] = vaults
...vault, .filter((v) => v.isHls)
apy: null, .map((vault) => ({
apr: null, ...vault,
ltv: { apy: null,
max: 0, apr: null,
liq: 0, ltv: {
}, max: 0,
cap: { liq: 0,
denom: 'denom', },
used: BN_ZERO, cap: {
max: BN_ZERO, denom: 'denom',
}, used: BN_ZERO,
})) max: BN_ZERO,
},
}))
return ( return (
<Table <Table
title={title} title={title}

View File

@ -13,12 +13,13 @@ import useStore from 'store'
export const DEPOSIT_META = { accessorKey: 'deposit', header: 'Deposit' } export const DEPOSIT_META = { accessorKey: 'deposit', header: 'Deposit' }
interface Props { interface Props {
vault: Vault
isLoading: boolean isLoading: boolean
strategy?: HLSStrategy
vault?: Vault
} }
export default function Deposit(props: Props) { export default function Deposit(props: Props) {
const { vault } = props const { strategy, vault } = props
const [showHlsInfo, setShowHlsInfo] = useLocalStorage<boolean>( const [showHlsInfo, setShowHlsInfo] = useLocalStorage<boolean>(
LocalStorageKeys.HLS_INFORMATION, LocalStorageKeys.HLS_INFORMATION,
@ -27,7 +28,12 @@ export default function Deposit(props: Props) {
const { open: openAlertDialog, close } = useAlertDialog() const { open: openAlertDialog, close } = useAlertDialog()
const enterVaultHandler = useCallback(() => { const openHlsModal = useCallback(
() => useStore.setState({ hlsModal: { strategy, vault } }),
[strategy, vault],
)
const handleOnClick = useCallback(() => {
if (!showHlsInfo) { if (!showHlsInfo) {
openHlsModal() openHlsModal()
return return
@ -72,17 +78,13 @@ export default function Deposit(props: Props) {
onClick: (isChecked: boolean) => setShowHlsInfo(!isChecked), onClick: (isChecked: boolean) => setShowHlsInfo(!isChecked),
}, },
}) })
}, [close, openAlertDialog, setShowHlsInfo, showHlsInfo]) }, [close, openAlertDialog, openHlsModal, setShowHlsInfo, showHlsInfo])
function openHlsModal() {
useStore.setState({ hlsModal: { vault } })
}
if (props.isLoading) return <Loading /> if (props.isLoading) return <Loading />
return ( return (
<div className='flex items-center justify-end'> <div className='flex items-center justify-end'>
<ActionButton onClick={enterVaultHandler} color='tertiary' text='Deposit' /> <ActionButton onClick={handleOnClick} color='tertiary' text='Deposit' />
</div> </div>
) )
} }

View File

@ -2,6 +2,8 @@ import React from 'react'
import TitleAndSubCell from 'components/TitleAndSubCell' import TitleAndSubCell from 'components/TitleAndSubCell'
export const NAME_META = { id: 'name', accessorKey: 'denoms.primary', header: 'Name' }
interface Props { interface Props {
strategy: HLSStrategy strategy: HLSStrategy
} }

View File

@ -1,3 +1,45 @@
export default function AvailableHlsStakingAssets() { import { Suspense } from 'react'
return null
import { NAME_META } from 'components/HLS/Farm/Table/Columns/Name'
import useAvailableColumns from 'components/HLS/Staking/Table/Columns/useAvailableColumns'
import Table from 'components/Table'
import useHLSStakingAssets from 'hooks/useHLSStakingAssets'
import { getEnabledMarketAssets } from 'utils/assets'
const title = 'Available HLS Staking'
function Content() {
const assets = getEnabledMarketAssets()
const { data: hlsStrategies } = useHLSStakingAssets()
const columns = useAvailableColumns({ isLoading: false })
return (
<Table
title={title}
columns={columns}
data={hlsStrategies}
initialSorting={[{ id: NAME_META.id, desc: true }]}
/>
)
}
export default function AvailableHlsVaults() {
return (
<Suspense fallback={<Fallback />}>
<Content />
</Suspense>
)
}
function Fallback() {
const columns = useAvailableColumns({ isLoading: true })
return (
<Table
title={title}
columns={columns}
data={[]}
initialSorting={[{ id: NAME_META.id, desc: true }]}
/>
)
} }

View File

@ -0,0 +1,23 @@
import React from 'react'
import { FormattedNumber } from 'components/FormattedNumber'
import Loading from 'components/Loading'
export const LTV_MAX_META = { accessorKey: 'maxLTV', header: 'Max LTV' }
interface Props {
strategy: HLSStrategy
isLoading: boolean
}
export default function MaxLtv(props: Props) {
const { strategy } = props
if (props.isLoading) return <Loading />
return (
<FormattedNumber
amount={strategy.maxLTV * 100}
options={{ minDecimals: 0, maxDecimals: 0, suffix: '%' }}
className='text-xs'
animate
/>
)
}

View File

@ -0,0 +1,20 @@
import React from 'react'
import { FormattedNumber } from 'components/FormattedNumber'
export const MAX_LEV_META = { accessorKey: 'maxLeverage', header: 'Max Leverage' }
interface Props {
strategy: HLSStrategy
}
export default function MaxLeverage(props: Props) {
return (
<FormattedNumber
amount={props.strategy.maxLeverage}
options={{ minDecimals: 2, maxDecimals: 2, suffix: 'x' }}
className='text-xs'
animate
/>
)
}

View File

@ -0,0 +1,32 @@
import React from 'react'
import DoubleLogo from 'components/DoubleLogo'
import Loading from 'components/Loading'
import TitleAndSubCell from 'components/TitleAndSubCell'
import { getAssetByDenom } from 'utils/assets'
export const NAME_META = { id: 'name', header: 'Vault', accessorKey: 'denoms.deposit' }
interface Props {
strategy: HLSStrategy
}
export default function Name(props: Props) {
const { strategy } = props
const depositAsset = getAssetByDenom(props.strategy.denoms.deposit)
const borrowAsset = getAssetByDenom(props.strategy.denoms.borrow)
return (
<div className='flex'>
<DoubleLogo primaryDenom={strategy.denoms.deposit} secondaryDenom={strategy.denoms.borrow} />
{depositAsset && borrowAsset ? (
<TitleAndSubCell
className='ml-2 mr-2 text-left'
title={`${depositAsset.symbol}/${borrowAsset.symbol}`}
sub='Staking'
/>
) : (
<Loading />
)}
</div>
)
}

View File

@ -0,0 +1,39 @@
import { ColumnDef } from '@tanstack/react-table'
import React, { useMemo } from 'react'
import Deposit, { DEPOSIT_META } from 'components/HLS/Farm/Table/Columns/Deposit'
import MaxLeverage, { MAX_LEV_META } from 'components/HLS/Staking/Table/Columns/MaxLeverage'
import MaxLTV, { LTV_MAX_META } from 'components/HLS/Staking/Table/Columns/MaxLTV'
import Name, { NAME_META } from 'components/HLS/Staking/Table/Columns/Name'
interface Props {
isLoading: boolean
}
export default function useAvailableColumns(props: Props) {
return useMemo<ColumnDef<HLSStrategy>[]>(
() => [
{
...NAME_META,
cell: ({ row }) => <Name strategy={row.original as HLSStrategy} />,
},
{
...MAX_LEV_META,
cell: ({ row }) => <MaxLeverage strategy={row.original} />,
},
{
...LTV_MAX_META,
cell: ({ row }) => (
<MaxLTV strategy={row.original as HLSStrategy} isLoading={props.isLoading} />
),
},
{
...DEPOSIT_META,
cell: ({ row }) => (
<Deposit strategy={row.original as HLSStrategy} isLoading={props.isLoading} />
),
},
],
[props.isLoading],
)
}

View File

@ -1,142 +0,0 @@
import React, { useMemo, useState } from 'react'
import Accordion from 'components/Accordion'
import { Item } from 'components/AccordionContent'
import CreateAccount from 'components/Modals/HLS/CreateAccount'
import Leverage from 'components/Modals/HLS/Leverage'
import ProvideCollateral from 'components/Modals/HLS/ProvideCollateral'
import SelectAccount from 'components/Modals/HLS/SelectAccount'
import { CollateralSubTitle, LeverageSubTitle, SubTitle } from 'components/Modals/HLS/SubTitles'
import Summary from 'components/Modals/HLS/Summary'
import { BN_ZERO } from 'constants/math'
import useAccounts from 'hooks/useAccounts'
import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance'
import useDepositHlsVault from 'hooks/useDepositHlsVault'
import useIsOpenArray from 'hooks/useIsOpenArray'
import useStore from 'store'
import { getAssetByDenom } from 'utils/assets'
import { BN } from 'utils/helpers'
interface Props {
vault: Vault
}
export default function Content(props: Props) {
const [selectedAccount, setSelectedAccount] = useState<Account | null>(null)
const [isOpen, toggleIsOpen] = useIsOpenArray(4, false)
const address = useStore((s) => s.address)
const { data: hlsAccounts } = useAccounts('high_levered_strategy', address)
const collateralAsset = getAssetByDenom(props.vault.denoms.primary)
const borrowAsset = getAssetByDenom(props.vault.denoms.secondary)
const walletCollateralAsset = useCurrentWalletBalance(props.vault.denoms.primary)
const { setDepositAmount, depositAmount, setBorrowAmount, borrowAmount, positionValue } =
useDepositHlsVault({
vault: props.vault,
})
const items: Item[] = useMemo(() => {
if (!collateralAsset || !borrowAsset) return []
return [
{
title: 'Provide Collateral',
renderContent: () => (
<ProvideCollateral
amount={depositAmount}
onChangeAmount={setDepositAmount}
asset={collateralAsset}
onClickBtn={() => toggleIsOpen(1)}
max={BN(walletCollateralAsset?.amount || 0)}
/>
),
renderSubTitle: () => (
<CollateralSubTitle
isOpen={isOpen[0]}
amount={depositAmount}
denom={collateralAsset.denom}
/>
),
isOpen: isOpen[0],
toggleOpen: toggleIsOpen,
},
{
title: 'Leverage',
renderContent: () => (
<Leverage
amount={depositAmount}
asset={borrowAsset}
// TODO: Get max borrow amount
max={BN_ZERO}
onChangeAmount={setDepositAmount}
onClickBtn={() => toggleIsOpen(2)}
/>
),
renderSubTitle: () => (
// TODO: Add leverage
<LeverageSubTitle leverage={1.1} isOpen={isOpen[1]} positionValue={positionValue} />
),
isOpen: isOpen[1],
toggleOpen: toggleIsOpen,
},
...[
hlsAccounts.length > 2
? {
title: 'Select HLS Account',
renderContent: () => (
<SelectAccount
selectedAccount={selectedAccount}
onChangeSelected={setSelectedAccount}
hlsAccounts={hlsAccounts}
onClickBtn={() => toggleIsOpen(3)}
/>
),
renderSubTitle: () =>
selectedAccount && !isOpen[2] ? (
<SubTitle text={`Account ${selectedAccount.id}`} />
) : null,
isOpen: isOpen[2],
toggleOpen: toggleIsOpen,
}
: {
title: 'Create HLS Account',
renderContent: () => <CreateAccount />,
renderSubTitle: () => null,
isOpen: isOpen[2],
toggleOpen: toggleIsOpen,
},
],
{
title: 'Summary',
renderContent: () => (
<Summary
depositAmount={depositAmount}
borrowAmount={borrowAmount}
positionValue={positionValue}
vault={props.vault}
onClickBtn={() => {
// TODO: Implement tx execution
}}
/>
),
renderSubTitle: () => null,
isOpen: isOpen[3],
toggleOpen: toggleIsOpen,
},
]
}, [
collateralAsset,
borrowAsset,
isOpen,
toggleIsOpen,
hlsAccounts,
depositAmount,
setDepositAmount,
walletCollateralAsset?.amount,
selectedAccount,
borrowAmount,
positionValue,
props.vault,
])
return <Accordion items={items} />
}

View File

@ -0,0 +1,168 @@
import React, { useMemo, useState } from 'react'
import Accordion from 'components/Accordion'
import useStakingController from 'components/Modals/HLS/Content//useStakingController'
import useVaultController from 'components/Modals/HLS/Content//useVaultController'
import useAccordionItems from 'components/Modals/HLS/Content/useAccordionItems'
import { EMPTY_ACCOUNT_HLS } from 'constants/accounts'
import useAccounts from 'hooks/useAccounts'
import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance'
import useIsOpenArray from 'hooks/useIsOpenArray'
import useVault from 'hooks/useVault'
import useStore from 'store'
import { isAccountEmpty } from 'utils/accounts'
import { getAssetByDenom } from 'utils/assets'
interface Props {
borrowDenom: string
collateralDenom: string
vaultAddress: string | null
}
export default function Controller(props: Props) {
const collateralAsset = getAssetByDenom(props.collateralDenom)
const borrowAsset = getAssetByDenom(props.borrowDenom)
const [selectedAccount, setSelectedAccount] = useState<Account>(EMPTY_ACCOUNT_HLS)
const [isOpen, toggleIsOpen] = useIsOpenArray(4, false)
const address = useStore((s) => s.address)
const { data: hlsAccounts } = useAccounts('high_levered_strategy', address)
const emptyHlsAccounts = useMemo(
() => hlsAccounts.filter((account) => isAccountEmpty(account)),
[hlsAccounts],
)
const walletCollateralAsset = useCurrentWalletBalance(props.collateralDenom)
const vault = useVault(props.vaultAddress || '')
if (!collateralAsset || !borrowAsset) return null
if (vault)
return (
<Vault
walletCollateralAsset={walletCollateralAsset}
vault={vault}
collateralAsset={collateralAsset}
borrowAsset={borrowAsset}
emptyHlsAccounts={emptyHlsAccounts}
hlsAccounts={hlsAccounts}
isOpen={isOpen}
selectedAccount={selectedAccount}
setSelectedAccount={setSelectedAccount}
toggleIsOpen={toggleIsOpen}
/>
)
return (
<StakingContent
walletCollateralAsset={walletCollateralAsset}
collateralAsset={collateralAsset}
borrowAsset={borrowAsset}
emptyHlsAccounts={emptyHlsAccounts}
hlsAccounts={hlsAccounts}
isOpen={isOpen}
selectedAccount={selectedAccount}
setSelectedAccount={setSelectedAccount}
toggleIsOpen={toggleIsOpen}
/>
)
}
interface ContentProps {
borrowAsset: Asset
collateralAsset: Asset
emptyHlsAccounts: Account[]
hlsAccounts: Account[]
isOpen: boolean[]
selectedAccount: Account
setSelectedAccount: (account: Account) => void
toggleIsOpen: (index: number) => void
walletCollateralAsset: Coin | undefined
}
interface VaultContentProps extends ContentProps {
vault: Vault
}
function Vault(props: VaultContentProps) {
const {
borrowAmount,
depositAmount,
execute,
leverage,
maxBorrowAmount,
onChangeCollateral,
onChangeDebt,
positionValue,
updatedAccount,
} = useVaultController({
vault: props.vault,
collateralAsset: props.collateralAsset,
borrowAsset: props.borrowAsset,
selectedAccount: props.selectedAccount,
})
const items = useAccordionItems({
apy: props.vault.apy || 0,
borrowAmount,
borrowAsset: props.borrowAsset,
collateralAsset: props.collateralAsset,
depositAmount,
emptyHlsAccounts: props.emptyHlsAccounts,
execute,
hlsAccounts: props.hlsAccounts,
isOpen: props.isOpen,
leverage,
maxBorrowAmount,
onChangeCollateral,
onChangeDebt,
positionValue,
selectedAccount: props.selectedAccount,
setSelectedAccount: props.setSelectedAccount,
toggleIsOpen: props.toggleIsOpen,
updatedAccount,
walletCollateralAsset: props.walletCollateralAsset,
})
return <Accordion className='h-[546px] overflow-y-scroll scrollbar-hide' items={items} />
}
function StakingContent(props: ContentProps) {
const {
depositAmount,
onChangeCollateral,
updatedAccount,
borrowAmount,
onChangeDebt,
leverage,
maxBorrowAmount,
positionValue,
execute,
} = useStakingController({
collateralAsset: props.collateralAsset,
borrowAsset: props.borrowAsset,
selectedAccount: props.selectedAccount,
})
const items = useAccordionItems({
borrowAmount,
borrowAsset: props.borrowAsset,
collateralAsset: props.collateralAsset,
depositAmount,
emptyHlsAccounts: props.emptyHlsAccounts,
execute,
hlsAccounts: props.hlsAccounts,
isOpen: props.isOpen,
leverage,
onChangeCollateral,
onChangeDebt,
positionValue,
selectedAccount: props.selectedAccount,
setSelectedAccount: props.setSelectedAccount,
toggleIsOpen: props.toggleIsOpen,
updatedAccount,
maxBorrowAmount,
apy: 0, // TODO: Implement APY
walletCollateralAsset: props.walletCollateralAsset,
})
return <Accordion className='h-[546px] overflow-y-scroll scrollbar-hide' items={items} />
}

View File

@ -0,0 +1,127 @@
import React, { useMemo } from 'react'
import CreateAccount from 'components/Modals/HLS/CreateAccount'
import Leverage from 'components/Modals/HLS/Leverage'
import ProvideCollateral from 'components/Modals/HLS/ProvideCollateral'
import SelectAccount from 'components/Modals/HLS/SelectAccount'
import { CollateralSubTitle, LeverageSubTitle, SubTitle } from 'components/Modals/HLS/SubTitles'
import Summary from 'components/Modals/HLS/Summary'
import { BN } from 'utils/helpers'
interface Props {
apy: number
borrowAmount: BigNumber
borrowAsset: Asset
collateralAsset: Asset
depositAmount: BigNumber
emptyHlsAccounts: Account[]
execute: () => void
hlsAccounts: Account[]
isOpen: boolean[]
leverage: number
maxBorrowAmount: BigNumber
onChangeCollateral: (amount: BigNumber) => void
onChangeDebt: (amount: BigNumber) => void
positionValue: BigNumber
selectedAccount: Account | null
setSelectedAccount: (account: Account) => void
toggleIsOpen: (index: number) => void
updatedAccount: Account | undefined
walletCollateralAsset: Coin | undefined
}
export default function useAccordionItems(props: Props) {
return useMemo(() => {
return [
{
title: 'Provide Collateral',
renderContent: () => (
<ProvideCollateral
amount={props.depositAmount}
onChangeAmount={props.onChangeCollateral}
asset={props.collateralAsset}
onClickBtn={() => props.toggleIsOpen(1)}
// TODO: Add check for deposit cap
max={BN(props.walletCollateralAsset?.amount || 0)}
/>
),
renderSubTitle: () => (
<CollateralSubTitle
isOpen={props.isOpen[0]}
amount={props.depositAmount}
denom={props.collateralAsset.denom}
/>
),
isOpen: props.isOpen[0],
toggleOpen: props.toggleIsOpen,
},
{
title: 'Leverage',
renderContent: () => (
<Leverage
amount={props.borrowAmount}
asset={props.borrowAsset}
onChangeAmount={props.onChangeDebt}
onClickBtn={() => props.toggleIsOpen(2)}
max={props.maxBorrowAmount}
/>
),
renderSubTitle: () => (
<LeverageSubTitle
leverage={props.leverage}
isOpen={props.isOpen[1]}
positionValue={props.positionValue}
/>
),
isOpen: props.isOpen[1],
toggleOpen: props.toggleIsOpen,
},
...[
props.hlsAccounts.length > 2
? {
title: 'Select HLS Account',
renderContent: () => (
<SelectAccount
selectedAccount={props.selectedAccount}
onChangeSelected={props.setSelectedAccount}
hlsAccounts={props.emptyHlsAccounts}
onClickBtn={() => props.toggleIsOpen(3)}
/>
),
renderSubTitle: () =>
props.selectedAccount && !props.isOpen[2] ? (
<SubTitle text={`Account ${props.selectedAccount.id}`} />
) : null,
isOpen: props.isOpen[2],
toggleOpen: props.toggleIsOpen,
}
: {
title: 'Create HLS Account',
renderContent: () => <CreateAccount />,
renderSubTitle: () => null,
isOpen: props.isOpen[2],
toggleOpen: props.toggleIsOpen,
},
],
{
title: 'Summary',
renderContent: () => (
<Summary
depositAmount={props.depositAmount}
borrowAmount={props.borrowAmount}
leverage={props.leverage}
positionValue={props.positionValue}
collateralAsset={props.collateralAsset}
borrowAsset={props.borrowAsset}
apy={props.apy}
onClickBtn={props.execute}
/>
),
renderSubTitle: () => null,
isOpen: props.isOpen[3],
toggleOpen: props.toggleIsOpen,
},
]
}, [props])
}

View File

@ -0,0 +1,59 @@
import { useCallback } from 'react'
import useDepositHlsVault from 'hooks/useDepositHlsVault'
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
import { BN } from 'utils/helpers'
interface Props {
borrowAsset: Asset
collateralAsset: Asset
selectedAccount: Account
}
export default function useVaultController(props: Props) {
const { collateralAsset, borrowAsset, selectedAccount } = props
const {
leverage,
setDepositAmount,
depositAmount,
setBorrowAmount,
borrowAmount,
positionValue,
} = useDepositHlsVault({
collateralDenom: collateralAsset.denom,
borrowDenom: borrowAsset.denom,
})
const actions = []
const { updatedAccount, simulateVaultDeposit } = useUpdatedAccount(selectedAccount)
const execute = () => null
const onChangeCollateral = useCallback(
(amount: BigNumber) => {
setDepositAmount(amount)
},
[setDepositAmount],
)
const onChangeDebt = useCallback(
(amount: BigNumber) => {
setBorrowAmount(amount)
},
[setBorrowAmount],
)
return {
borrowAmount,
depositAmount,
execute,
leverage,
maxBorrowAmount: BN(0),
onChangeCollateral,
onChangeDebt,
positionValue,
updatedAccount,
}
}

View File

@ -0,0 +1,128 @@
import { useCallback, useMemo } from 'react'
import useDepositVault from 'hooks/broadcast/useDepositVault'
import useDepositHlsVault from 'hooks/useDepositHlsVault'
import useHealthComputer from 'hooks/useHealthComputer'
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
interface Props {
borrowAsset: Asset
collateralAsset: Asset
selectedAccount: Account
vault: Vault
}
export default function useVaultController(props: Props) {
const { vault, collateralAsset, borrowAsset, selectedAccount } = props
const depositIntoVault = useStore((s) => s.depositIntoVault)
const {
leverage,
setDepositAmount,
depositAmount,
setBorrowAmount,
borrowAmount,
positionValue,
} = useDepositHlsVault({
collateralDenom: collateralAsset.denom,
borrowDenom: borrowAsset.denom,
})
const { actions } = useDepositVault({
vault,
reclaims: [],
deposits: [BNCoin.fromDenomAndBigNumber(collateralAsset.denom, depositAmount)],
borrowings: [BNCoin.fromDenomAndBigNumber(borrowAsset.denom, borrowAmount)],
kind: 'high_levered_strategy',
})
const { updatedAccount, simulateVaultDeposit } = useUpdatedAccount(selectedAccount)
const { computeMaxBorrowAmount } = useHealthComputer(updatedAccount)
const maxBorrowAmount = useMemo(
// TODO: Check that the amount is actually the HLS amount
// TODO: Add check for market liquidity
// TODO: Add check for deposit cap
() => {
return computeMaxBorrowAmount(props.borrowAsset.denom, {
vault: { address: props.vault?.address },
}).plus(borrowAmount)
},
[borrowAmount, computeMaxBorrowAmount, props.borrowAsset.denom, props.vault?.address],
)
const execute = useCallback(() => {
depositIntoVault({
accountId: selectedAccount.id,
actions,
deposits: [BNCoin.fromDenomAndBigNumber(collateralAsset.denom, depositAmount)],
borrowings: [BNCoin.fromDenomAndBigNumber(borrowAsset.denom, borrowAmount)],
isCreate: true,
kind: 'high_levered_strategy',
})
useStore.setState({ hlsModal: null })
}, [
actions,
borrowAmount,
depositAmount,
depositIntoVault,
borrowAsset.denom,
collateralAsset.denom,
selectedAccount.id,
])
const onChangeCollateral = useCallback(
(amount: BigNumber) => {
setDepositAmount(amount)
simulateVaultDeposit(
vault.address,
[BNCoin.fromDenomAndBigNumber(collateralAsset.denom, amount)],
[BNCoin.fromDenomAndBigNumber(borrowAsset.denom, borrowAmount)],
)
},
[
borrowAmount,
borrowAsset,
collateralAsset,
vault.address,
setDepositAmount,
simulateVaultDeposit,
],
)
const onChangeDebt = useCallback(
(amount: BigNumber) => {
setBorrowAmount(amount)
simulateVaultDeposit(
vault.address,
[BNCoin.fromDenomAndBigNumber(collateralAsset.denom, depositAmount)],
[BNCoin.fromDenomAndBigNumber(borrowAsset.denom, amount)],
)
},
[
borrowAsset,
collateralAsset,
depositAmount,
vault.address,
setBorrowAmount,
simulateVaultDeposit,
],
)
return {
borrowAmount,
depositAmount,
execute,
leverage,
maxBorrowAmount,
onChangeCollateral,
onChangeDebt,
positionValue,
updatedAccount,
}
}

View File

@ -16,12 +16,12 @@ export default function Header(props: Props) {
if (!primaryAsset || !secondaryAsset) return null if (!primaryAsset || !secondaryAsset) return null
return ( return (
<span 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'> <Text className='rounded-sm gradient-hls px-2 font-bold py-0.5' size='xs'>
HLS HLS
</Text> </Text>
</span> </div>
) )
} }

View File

@ -21,6 +21,7 @@ export default function Leverage(props: Props) {
asset={props.asset} asset={props.asset}
max={props.max} max={props.max}
onChange={props.onChangeAmount} onChange={props.onChangeAmount}
maxText='Max borrow'
/> />
<LeverageSummary asset={props.asset} /> <LeverageSummary asset={props.asset} />
<Button onClick={props.onClickBtn} text='Continue' rightIcon={<ArrowRight />} /> <Button onClick={props.onClickBtn} text='Continue' rightIcon={<ArrowRight />} />

View File

@ -29,11 +29,15 @@ export default function LeverageSummary(props: Props) {
}, [props.asset.symbol]) }, [props.asset.symbol])
return ( return (
<div className='grid grid-cols-2'> <div className='grid grid-cols-2 gap-2'>
{items.map((item) => ( {items.map((item) => (
<React.Fragment key={item.title}> <React.Fragment key={item.title}>
<Text className='text-white/60'>{item.title}</Text> <Text className='text-white/60 text-xs'>{item.title}</Text>
<FormattedNumber className='place-self-end' amount={item.amount} options={item.options} /> <FormattedNumber
className='place-self-end text-xs'
amount={item.amount}
options={item.options}
/>
</React.Fragment> </React.Fragment>
))} ))}
</div> </div>

View File

@ -3,7 +3,7 @@ import React from 'react'
import DisplayCurrency from 'components/DisplayCurrency' import DisplayCurrency from 'components/DisplayCurrency'
import Text from 'components/Text' import Text from 'components/Text'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { formatAmountWithSymbol } from 'utils/formatters' import { formatAmountWithSymbol, formatLeverage } from 'utils/formatters'
interface SubTitleProps { interface SubTitleProps {
text: string text: string
@ -46,7 +46,7 @@ export function LeverageSubTitle(props: LeveragedSubTitleProps) {
return ( return (
<> <>
<SubTitle text={`${props.leverage}x • Total Position Value `} /> <SubTitle text={`${formatLeverage(props.leverage)} • Total Position Value `} />
<DisplayCurrency <DisplayCurrency
coin={BNCoin.fromDenomAndBigNumber('usd', props.positionValue)} coin={BNCoin.fromDenomAndBigNumber('usd', props.positionValue)}
className='text-white/60 text-xs inline' className='text-white/60 text-xs inline'

View File

@ -16,7 +16,7 @@ export default function AprBreakdown(props: Props) {
<FormattedNumber <FormattedNumber
amount={item.amount} amount={item.amount}
className='text-sm' className='text-sm'
options={{ suffix: '%', maxDecimals: 2, minDecimals: 0 }} options={{ suffix: '%', maxDecimals: 2, minDecimals: 2 }}
/> />
</div> </div>
))} ))}

View File

@ -6,31 +6,32 @@ import AssetSummary from 'components/Modals/HLS/Summary/AssetSummary'
import YourPosition from 'components/Modals/HLS/Summary/YourPosition' import YourPosition from 'components/Modals/HLS/Summary/YourPosition'
import useBorrowAsset from 'hooks/useBorrowAsset' import useBorrowAsset from 'hooks/useBorrowAsset'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { getAssetByDenom } from 'utils/assets'
interface Props { interface Props {
apy: number
borrowAmount: BigNumber borrowAmount: BigNumber
borrowAsset: Asset
collateralAsset: Asset
depositAmount: BigNumber depositAmount: BigNumber
leverage: number
onClickBtn: () => void onClickBtn: () => void
positionValue: BigNumber positionValue: BigNumber
vault: Vault
} }
export default function Summary(props: Props) { export default function Summary(props: Props) {
const collateralAsset = getAssetByDenom(props.vault.denoms.primary) const borrowAsset = useBorrowAsset(props.borrowAsset.denom)
const borrowAsset = useBorrowAsset(props.vault.denoms.secondary)
if (!collateralAsset || !borrowAsset) return null if (!borrowAsset) return null
return ( return (
<div className='p-4 flex flex-col gap-4'> <div className='p-4 flex flex-col gap-4'>
<AssetSummary asset={collateralAsset} amount={props.depositAmount} /> <AssetSummary asset={props.collateralAsset} amount={props.depositAmount} />
<AssetSummary asset={borrowAsset} amount={props.borrowAmount} isBorrow /> <AssetSummary asset={borrowAsset} amount={props.borrowAmount} isBorrow />
<YourPosition <YourPosition
positionValue={BNCoin.fromDenomAndBigNumber('usd', props.positionValue)} positionValue={BNCoin.fromDenomAndBigNumber('usd', props.positionValue)}
baseApy={props.vault.apy || 0} baseApy={props.apy || 0}
borrowRate={borrowAsset.borrowRate || 0} borrowRate={borrowAsset.borrowRate || 0}
leverage={3.5} leverage={props.leverage}
/> />
<Button <Button
onClick={props.onClickBtn} onClick={props.onClickBtn}

View File

@ -8,13 +8,30 @@ import useStore from 'store'
export default function HlsModalController() { export default function HlsModalController() {
const modal = useStore((s) => s.hlsModal) const modal = useStore((s) => s.hlsModal)
if (!modal?.vault) return null if (modal?.vault)
return (
<HlsModal
collateralDenom={modal.vault.denoms.primary}
borrowDenom={modal.vault.denoms.secondary}
vaultAddress={modal.vault.address}
/>
)
if (modal?.strategy)
return (
<HlsModal
collateralDenom={modal.strategy.denoms.deposit}
borrowDenom={modal.strategy.denoms.borrow}
vaultAddress={null}
/>
)
return <HlsModal vault={modal.vault} /> return null
} }
interface Props { interface Props {
vault: Vault borrowDenom: string
collateralDenom: string
vaultAddress: string | null
} }
function HlsModal(props: Props) { function HlsModal(props: Props) {
@ -24,18 +41,17 @@ function HlsModal(props: Props) {
return ( return (
<Modal <Modal
header={ header={<Header primaryDenom={props.collateralDenom} secondaryDenom={props.borrowDenom} />}
<Header
primaryDenom={props.vault.denoms.primary}
secondaryDenom={props.vault.denoms.secondary}
/>
}
headerClassName='gradient-header pl-2 pr-2.5 py-3 border-b-white/5 border-b' headerClassName='gradient-header pl-2 pr-2.5 py-3 border-b-white/5 border-b'
contentClassName='flex flex-col p-6 h-full overflow-y-scroll scrollbar-hide' contentClassName='flex flex-col p-6'
modalClassName='max-w-modal-md h-[min(80%,600px)]' modalClassName='max-w-modal-md'
onClose={handleClose} onClose={handleClose}
> >
<Content vault={props.vault} /> <Content
collateralDenom={props.collateralDenom}
borrowDenom={props.borrowDenom}
vaultAddress={props.vaultAddress}
/>
</Modal> </Modal>
) )
} }

View File

@ -168,6 +168,7 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
deposits: props.deposits, deposits: props.deposits,
borrowings: props.borrowings, borrowings: props.borrowings,
isCreate: vaultModal.isCreate, isCreate: vaultModal.isCreate,
kind: 'default',
}) })
useStore.setState({ vaultModal: null }) useStore.setState({ vaultModal: null })
} }

View File

@ -49,6 +49,7 @@ export default function VaultModalContent(props: Props) {
reclaims: removedLends, reclaims: removedLends,
deposits: removedDeposits, deposits: removedDeposits,
borrowings: addedDebts, borrowings: addedDebts,
kind: 'default',
}) })
const depositCapReachedCoins = useMemo(() => { const depositCapReachedCoins = useMemo(() => {

13
src/constants/accounts.ts Normal file
View File

@ -0,0 +1,13 @@
export const EMPTY_ACCOUNT: Account = {
id: '',
kind: 'default',
debts: [],
deposits: [],
lends: [],
vaults: [],
}
export const EMPTY_ACCOUNT_HLS: Account = {
...EMPTY_ACCOUNT,
kind: 'high_levered_strategy',
}

View File

@ -87,6 +87,7 @@ export const VAULTS_META_DATA: VaultMetaData[] = [
secondary: 'ATOM', secondary: 'ATOM',
}, },
isFeatured: false, isFeatured: false,
isHls: true,
}, },
{ {
address: 'osmo185gqewrlde8vrqw7j8lpad67v8jfrx9u7770k9q87tqqecctp5tq50wt2c', address: 'osmo185gqewrlde8vrqw7j8lpad67v8jfrx9u7770k9q87tqqecctp5tq50wt2c',

View File

@ -6,7 +6,7 @@ import useAutoLend from 'hooks/useAutoLend'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/useLocalStorage'
import usePrices from 'hooks/usePrices' import usePrices from 'hooks/usePrices'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { Action } from 'types/generated/mars-credit-manager/MarsCreditManager.types' import { AccountKind, Action } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { getLendEnabledAssets } from 'utils/assets' import { getLendEnabledAssets } from 'utils/assets'
import { import {
getEnterVaultActions, getEnterVaultActions,
@ -19,6 +19,7 @@ interface Props {
reclaims: BNCoin[] reclaims: BNCoin[]
deposits: BNCoin[] deposits: BNCoin[]
borrowings: BNCoin[] borrowings: BNCoin[]
kind: AccountKind
} }
export default function useDepositVault(props: Props): { export default function useDepositVault(props: Props): {
@ -47,6 +48,14 @@ export default function useDepositVault(props: Props): {
[props.vault, deposits, borrowings, reclaims, prices, slippage], [props.vault, deposits, borrowings, reclaims, prices, slippage],
) )
const depositActions: Action[] = useMemo(() => {
if (props.kind === 'default') return []
return deposits.map((bnCoin) => ({
deposit: bnCoin.toCoin(),
}))
}, [deposits, props.kind])
const reclaimActions: Action[] = useMemo(() => { const reclaimActions: Action[] = useMemo(() => {
return reclaims.map((bnCoin) => ({ return reclaims.map((bnCoin) => ({
reclaim: bnCoin.toActionCoin(), reclaim: bnCoin.toActionCoin(),
@ -71,7 +80,7 @@ export default function useDepositVault(props: Props): {
}, [props.vault, primaryCoin, secondaryCoin, slippage]) }, [props.vault, primaryCoin, secondaryCoin, slippage])
const lendActions: Action[] = useMemo(() => { const lendActions: Action[] = useMemo(() => {
if (!isAutoLend) return [] if (!isAutoLend || props.kind === 'high_levered_strategy') return []
const denoms = [props.vault.denoms.primary, props.vault.denoms.secondary] const denoms = [props.vault.denoms.primary, props.vault.denoms.secondary]
const denomsForLend = getLendEnabledAssets() const denomsForLend = getLendEnabledAssets()
@ -84,17 +93,37 @@ export default function useDepositVault(props: Props): {
amount: 'account_balance', amount: 'account_balance',
}, },
})) }))
}, [isAutoLend, props.vault.denoms.primary, props.vault.denoms.secondary]) }, [isAutoLend, props.kind, props.vault.denoms.primary, props.vault.denoms.secondary])
const refundActions: Action[] = useMemo(() => {
if (props.kind === 'default') return []
return [
{
refund_all_coin_balances: {},
},
]
}, [props.kind])
const actions = useMemo(() => { const actions = useMemo(() => {
return [ return [
...depositActions,
...reclaimActions, ...reclaimActions,
...borrowActions, ...borrowActions,
...swapActions, ...swapActions,
...enterVaultActions, ...enterVaultActions,
...lendActions, ...lendActions,
...refundActions,
] ]
}, [reclaimActions, borrowActions, swapActions, enterVaultActions, lendActions]) }, [
depositActions,
reclaimActions,
borrowActions,
swapActions,
enterVaultActions,
lendActions,
refundActions,
])
return { return {
actions, actions,

View File

@ -3,10 +3,11 @@ import { useMemo, useState } from 'react'
import { BN_ZERO } from 'constants/math' import { BN_ZERO } from 'constants/math'
import usePrices from 'hooks/usePrices' import usePrices from 'hooks/usePrices'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { getValueFromBNCoins } from 'utils/helpers' import { getCoinValue } from 'utils/formatters'
interface Props { interface Props {
vault: Vault borrowDenom: string
collateralDenom: string
} }
export default function useDepositHlsVault(props: Props) { export default function useDepositHlsVault(props: Props) {
const { data: prices } = usePrices() const { data: prices } = usePrices()
@ -14,17 +15,21 @@ export default function useDepositHlsVault(props: Props) {
const [depositAmount, setDepositAmount] = useState<BigNumber>(BN_ZERO) const [depositAmount, setDepositAmount] = useState<BigNumber>(BN_ZERO)
const [borrowAmount, setBorrowAmount] = useState<BigNumber>(BN_ZERO) const [borrowAmount, setBorrowAmount] = useState<BigNumber>(BN_ZERO)
const positionValue = useMemo(() => { const { positionValue, leverage } = useMemo(() => {
if (!prices.length) return BN_ZERO const collateralValue = getCoinValue(
BNCoin.fromDenomAndBigNumber(props.collateralDenom, depositAmount),
return getValueFromBNCoins(
[
BNCoin.fromDenomAndBigNumber(props.vault.denoms.primary, depositAmount),
BNCoin.fromDenomAndBigNumber(props.vault.denoms.secondary, borrowAmount),
],
prices, prices,
) )
}, [prices, depositAmount, borrowAmount]) const borrowValue = getCoinValue(
BNCoin.fromDenomAndBigNumber(props.borrowDenom, borrowAmount),
prices,
)
return {
positionValue: collateralValue.plus(borrowValue),
leverage: borrowValue.dividedBy(collateralValue).plus(1).toNumber() || 1,
}
}, [borrowAmount, depositAmount, prices, props.collateralDenom, props.borrowDenom])
return { return {
setDepositAmount, setDepositAmount,
@ -32,5 +37,6 @@ export default function useDepositHlsVault(props: Props) {
setBorrowAmount, setBorrowAmount,
borrowAmount, borrowAmount,
positionValue, positionValue,
leverage,
} }
} }

View File

@ -10,7 +10,6 @@ import {
} from 'types/generated/mars-credit-manager/MarsCreditManager.types' } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { VaultConfigBaseForString } from 'types/generated/mars-params/MarsParams.types' import { VaultConfigBaseForString } from 'types/generated/mars-params/MarsParams.types'
import { import {
AccountKind,
AssetParamsBaseForAddr, AssetParamsBaseForAddr,
HealthComputer, HealthComputer,
} from 'types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types' } from 'types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types'
@ -114,6 +113,7 @@ export default function useHealthComputer(account?: Account) {
const healthComputer: HealthComputer | null = useMemo(() => { const healthComputer: HealthComputer | null = useMemo(() => {
if ( if (
!account ||
!positions || !positions ||
!vaultPositionValues || !vaultPositionValues ||
!vaultConfigsData || !vaultConfigsData ||
@ -130,9 +130,9 @@ export default function useHealthComputer(account?: Account) {
vault_values: vaultPositionValues, vault_values: vaultPositionValues,
}, },
positions: positions, positions: positions,
kind: 'default' as AccountKind, kind: account.kind,
} }
}, [priceData, denomsData, vaultConfigsData, vaultPositionValues, positions]) }, [account, positions, vaultPositionValues, vaultConfigsData, denomsData, priceData])
useEffect(() => { useEffect(() => {
if (!healthComputer) return if (!healthComputer) return

9
src/hooks/useVault.tsx Normal file
View File

@ -0,0 +1,9 @@
import useVaults from 'hooks/useVaults'
export default function useVault(address: string) {
const { data: vaults } = useVaults(false)
if (!vaults?.length) return null
return vaults.find((v) => v.address === address) ?? null
}

View File

@ -423,6 +423,7 @@ export default function createBroadcastSlice(
deposits: BNCoin[] deposits: BNCoin[]
borrowings: BNCoin[] borrowings: BNCoin[]
isCreate: boolean isCreate: boolean
kind: AccountKind
}) => { }) => {
const msg: CreditManagerExecuteMsg = { const msg: CreditManagerExecuteMsg = {
update_credit_account: { update_credit_account: {
@ -432,7 +433,14 @@ export default function createBroadcastSlice(
} }
const response = get().executeMsg({ const response = get().executeMsg({
messages: [generateExecutionMessage(get().address, ENV.ADDRESS_CREDIT_MANAGER, msg, [])], messages: [
generateExecutionMessage(
get().address,
ENV.ADDRESS_CREDIT_MANAGER,
msg,
options.kind === 'default' ? [] : options.deposits.map((coin) => coin.toCoin()),
),
],
}) })
const depositedCoins = getVaultDepositCoinsFromActions(options.actions) const depositedCoins = getVaultDepositCoinsFromActions(options.actions)

View File

@ -96,6 +96,7 @@ interface BroadcastSlice {
deposits: BNCoin[] deposits: BNCoin[]
borrowings: BNCoin[] borrowings: BNCoin[]
isCreate: boolean isCreate: boolean
kind: import('types/generated/mars-rover-health-types/MarsRoverHealthTypes.types').AccountKind
}) => Promise<boolean> }) => Promise<boolean>
executeMsg: (options: { messages: MsgExecuteContract[] }) => Promise<BroadcastResult> executeMsg: (options: { messages: MsgExecuteContract[] }) => Promise<BroadcastResult>
lend: (options: { accountId: string; coin: BNCoin; isMax?: boolean }) => Promise<boolean> lend: (options: { accountId: string; coin: BNCoin; isMax?: boolean }) => Promise<boolean>

View File

@ -70,5 +70,6 @@ interface WalletAssetModal {
} }
interface HlsModal { interface HlsModal {
vault: Vault strategy?: HLSStrategy
vault?: Vault
} }

View File

@ -19,6 +19,7 @@ interface VaultMetaData {
secondary: string secondary: string
} }
isFeatured?: boolean isFeatured?: boolean
isHls?: boolean
} }
interface VaultInfo { interface VaultInfo {

View File

@ -272,3 +272,12 @@ export function getAccountSummaryStats(
leverage, leverage,
} }
} }
export function isAccountEmpty(account: Account) {
return (
account.vaults.length === 0 &&
account.lends.length === 0 &&
account.debts.length === 0 &&
account.deposits.length === 0
)
}