Mp 2880 modifying farm position (#272)

* added correct resolving of account positions

* solve rendering bug for lp amount

* bugfix: add slippage to minlpamount

* fix DisplayCurrency to accept only BNCoin

* bugfix: remove prices from store

* add basic depostied vaults table

* Farm: Added deposited table

* finish deposited table, remove featured vaults:

* enable deposit more for vaults

* use controller for vault modal

* small fixes and polishing of add deposit

* fix tests, run format

* removed empty deposited table

---------

Co-authored-by: Linkie Link <linkielink.dev@gmail.com>
This commit is contained in:
Bob van der Helm 2023-06-29 13:12:11 +02:00 committed by GitHub
parent 697e83b7cb
commit 999bad4059
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 182 additions and 108 deletions

View File

@ -22,7 +22,7 @@ jest.mock('hooks/useMarketAssets', () =>
})), })),
) )
jest.mock('hooks/broadcast/useDepositVault', () => jest.fn(() => ({}))) jest.mock('hooks/broadcast/useDepositVault', () => jest.fn(() => ({ actions: [] })))
jest.mock('components/DisplayCurrency') jest.mock('components/DisplayCurrency')

View File

@ -1,3 +1,4 @@
import { render } from '@testing-library/react'
import classNames from 'classnames' import classNames from 'classnames'
import { ChevronDown, ChevronRight } from 'components/Icons' import { ChevronDown, ChevronRight } from 'components/Icons'
@ -12,14 +13,12 @@ export interface Item {
title: string title: string
renderContent: () => React.ReactNode renderContent: () => React.ReactNode
isOpen?: boolean isOpen?: boolean
subTitle?: string | React.ReactNode renderSubTitle: () => React.ReactNode
toggleOpen: (index: number) => void toggleOpen: (index: number) => void
} }
export default function AccordionContent(props: Props) { export default function AccordionContent(props: Props) {
const { title, renderContent, isOpen, subTitle, toggleOpen } = props.item const { title, renderContent, isOpen, renderSubTitle, toggleOpen } = props.item
const shouldShowSubTitle = subTitle && !isOpen
return ( return (
<div key={title} className='group border-b-white/10 [&:not(:last-child)]:border-b'> <div key={title} className='group border-b-white/10 [&:not(:last-child)]:border-b'>
@ -34,11 +33,9 @@ export default function AccordionContent(props: Props) {
> >
<div> <div>
<Text>{title}</Text> <Text>{title}</Text>
{shouldShowSubTitle && ( <Text size='xs' className='mt-1 text-white/60'>
<Text size='xs' className='mt-1 text-white/60'> {renderSubTitle()}
{subTitle} </Text>
</Text>
)}
</div> </div>
<div className='block pr-1 group-[[open]]:hidden'> <div className='block pr-1 group-[[open]]:hidden'>
{isOpen ? <ChevronDown /> : <ChevronRight />} {isOpen ? <ChevronDown /> : <ChevronRight />}

View File

@ -54,6 +54,7 @@ export default function AccountSummary(props: Props) {
) : null, ) : null,
isOpen: isOpen[0], isOpen: isOpen[0],
toggleOpen: (index: number) => toggleOpen(index), toggleOpen: (index: number) => toggleOpen(index),
renderSubTitle: () => <></>,
}, },
{ {
title: 'Balances', title: 'Balances',
@ -61,6 +62,7 @@ export default function AccountSummary(props: Props) {
props.account ? <AccountBalancesTable data={props.account} /> : null, props.account ? <AccountBalancesTable data={props.account} /> : null,
isOpen: isOpen[1], isOpen: isOpen[1],
toggleOpen: (index: number) => toggleOpen(index), toggleOpen: (index: number) => toggleOpen(index),
renderSubTitle: () => <></>,
}, },
]} ]}
allowMultipleOpen allowMultipleOpen

View File

@ -1,6 +1,6 @@
import { Row } from '@tanstack/react-table' import { Row } from '@tanstack/react-table'
import Button from 'components/Button'
import Button from 'components/Button'
import { LockUnlocked, Plus } from 'components/Icons' import { LockUnlocked, Plus } from 'components/Icons'
import useStore from 'store' import useStore from 'store'
@ -19,6 +19,16 @@ export default function VaultExpanded(props: Props) {
}) })
} }
function depositMoreHandler() {
useStore.setState({
vaultModal: {
vault: props.row.original,
isDeposited: true,
selectedBorrowDenoms: [props.row.original.denoms.secondary],
},
})
}
let isDeposited: boolean = false let isDeposited: boolean = false
if ((props.row.original as DepositedVault)?.amounts) { if ((props.row.original as DepositedVault)?.amounts) {
isDeposited = true isDeposited = true
@ -39,7 +49,11 @@ export default function VaultExpanded(props: Props) {
<div className='align-center flex justify-end gap-3 p-4'> <div className='align-center flex justify-end gap-3 p-4'>
{isDeposited ? ( {isDeposited ? (
<> <>
<Button color='secondary' leftIcon={<Plus className='w-3' />}> <Button
onClick={depositMoreHandler}
color='secondary'
leftIcon={<Plus className='w-3' />}
>
Deposit more Deposit more
</Button> </Button>
<Button color='tertiary' leftIcon={<LockUnlocked />}> <Button color='tertiary' leftIcon={<LockUnlocked />}>

View File

@ -76,3 +76,21 @@ function Fallback() {
return <VaultTable data={mockVaults} isLoading /> return <VaultTable data={mockVaults} isLoading />
} }
export function AvailableVaults() {
return (
<Card className='h-fit w-full bg-white/5' title='Available vaults'>
<Suspense fallback={<Fallback />}>
<Content type='available' />
</Suspense>
</Card>
)
}
export function DepositedVaults() {
return (
<Suspense fallback={null}>
<Content type='deposited' />
</Suspense>
)
}

View File

@ -1,4 +1,4 @@
import VaultModal from 'components/Modals/Vault/VaultModal' import VaultModal from 'components/Modals/Vault'
import BorrowModal from 'components/Modals/Borrow/BorrowModal' import BorrowModal from 'components/Modals/Borrow/BorrowModal'
import FundAndWithdrawModal from 'components/Modals/FundWithdraw/FundAndWithdrawModal' import FundAndWithdrawModal from 'components/Modals/FundWithdraw/FundAndWithdrawModal'
import AddVaultBorrowAssetsModal from 'components/Modals/AddVaultAssets/AddVaultBorrowAssetsModal' import AddVaultBorrowAssetsModal from 'components/Modals/AddVaultAssets/AddVaultBorrowAssetsModal'
@ -11,7 +11,6 @@ export default function ModalsContainer() {
<VaultModal /> <VaultModal />
<BorrowModal /> <BorrowModal />
<FundAndWithdrawModal /> <FundAndWithdrawModal />
<VaultModal />
<AddVaultBorrowAssetsModal /> <AddVaultBorrowAssetsModal />
<UnlockModal /> <UnlockModal />
<LendAndReclaimModalController /> <LendAndReclaimModalController />

View File

@ -40,6 +40,7 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
const baseCurrency = useStore((s) => s.baseCurrency) const baseCurrency = useStore((s) => s.baseCurrency)
const vaultModal = useStore((s) => s.vaultModal) const vaultModal = useStore((s) => s.vaultModal)
const depositIntoVault = useStore((s) => s.depositIntoVault) const depositIntoVault = useStore((s) => s.depositIntoVault)
const [isConfirming, setIsConfirming] = useState(false)
const { actions: depositActions, fee: depositFee } = useDepositVault({ const { actions: depositActions, fee: depositFee } = useDepositVault({
vault: props.vault, vault: props.vault,
@ -149,8 +150,17 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
}) })
} }
function onConfirm() { async function onConfirm() {
depositIntoVault({ fee: depositFee, accountId: props.account.id, actions: depositActions }) setIsConfirming(true)
const isSuccess = await depositIntoVault({
fee: depositFee,
accountId: props.account.id,
actions: depositActions,
})
setIsConfirming(false)
if (isSuccess) {
useStore.setState({ vaultModal: null })
}
} }
return ( return (
@ -207,7 +217,14 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
) )
})} })}
</div> </div>
<Button onClick={onConfirm} color='primary' text='Deposit' rightIcon={<ArrowRight />} /> <Button
onClick={onConfirm}
color='primary'
text='Deposit'
rightIcon={<ArrowRight />}
showProgressIndicator={isConfirming}
disabled={!depositActions.length}
/>
</div> </div>
) )
} }

View File

@ -50,12 +50,12 @@ export default function VaultDeposit(props: Props) {
[primaryValue, secondaryValue], [primaryValue, secondaryValue],
) )
const primaryValuePercentage = useMemo( const primaryValuePercentage = useMemo(() => {
() => primaryValue.div(totalValue).times(100).decimalPlaces(2).toNumber() || 50, const value = primaryValue.div(totalValue).times(100).decimalPlaces(2).toNumber()
[primaryValue, totalValue], return isNaN(value) ? 50 : value
) }, [primaryValue, totalValue])
const secondaryValuePercentage = useMemo( const secondaryValuePercentage = useMemo(
() => new BigNumber(100).minus(primaryValuePercentage).decimalPlaces(2).toNumber() || 50, () => new BigNumber(100).minus(primaryValuePercentage).integerValue(2).toNumber() ?? 50,
[primaryValuePercentage], [primaryValuePercentage],
) )

View File

@ -1,52 +0,0 @@
import VaultLogo from 'components/Earn/vault/VaultLogo'
import Modal from 'components/Modal'
import Text from 'components/Text'
import { ASSETS } from 'constants/assets'
import useCurrentAccount from 'hooks/useCurrentAccount'
import useStore from 'store'
import { CircularProgress } from 'components/CircularProgress'
import VaultModalContent from 'components/Modals/Vault/VaultModalContent'
export default function VaultModal() {
const currentAccount = useCurrentAccount()
const modal = useStore((s) => s.vaultModal)
const primaryAsset =
ASSETS.find((asset) => asset.denom === modal?.vault.denoms.primary) ?? ASSETS[0]
const secondaryAsset =
ASSETS.find((asset) => asset.denom === modal?.vault.denoms.secondary) ?? ASSETS[0]
const hasValidData = primaryAsset && currentAccount && secondaryAsset
function onClose() {
useStore.setState({ vaultModal: null })
}
return (
<Modal
open={!!(modal && hasValidData)}
onClose={onClose}
header={
modal && (
<span className='flex items-center gap-4 px-4'>
<VaultLogo vault={modal.vault} />
<Text>{`${modal.vault.symbols.primary} - ${modal.vault.symbols.secondary}`}</Text>
</span>
)
}
headerClassName='gradient-header pl-2 pr-2.5 py-2.5 border-b-white/5 border-b'
contentClassName='flex flex-col'
>
{modal?.vault && currentAccount ? (
<VaultModalContent
vault={modal.vault}
primaryAsset={primaryAsset}
secondaryAsset={secondaryAsset}
account={currentAccount}
/>
) : (
<CircularProgress />
)}
</Modal>
)
}

View File

@ -13,10 +13,11 @@ import VaultDepositSubTitle from 'components/Modals/Vault/VaultDepositsSubTitle'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
interface Props { interface Props {
vault: Vault vault: Vault | DepositedVault
primaryAsset: Asset primaryAsset: Asset
secondaryAsset: Asset secondaryAsset: Asset
account: Account account: Account
isDeposited?: boolean
} }
export default function VaultModalContent(props: Props) { export default function VaultModalContent(props: Props) {
@ -55,6 +56,31 @@ export default function VaultModalContent(props: Props) {
[setIsCustomRatio], [setIsCustomRatio],
) )
function getDepositSubTitle() {
if (isOpen[0] && props.isDeposited)
return 'The amounts you enter below will be added to your current position.'
if (isOpen[0]) return null
return (
<VaultDepositSubTitle
primaryAmount={primaryAmount}
secondaryAmount={secondaryAmount}
primaryAsset={props.primaryAsset}
secondaryAsset={props.secondaryAsset}
/>
)
}
function getBorrowingsSubTitle() {
if (isOpen[1] && props.isDeposited)
return 'The amounts you enter below will be added to your current position.'
if (isOpen[1]) return null
return <VaultBorrowingsSubTitle borrowings={borrowings} />
}
return ( return (
<div className='flex flex-grow items-start gap-6 p-6'> <div className='flex flex-grow items-start gap-6 p-6'>
<Accordion <Accordion
@ -76,14 +102,7 @@ export default function VaultModalContent(props: Props) {
/> />
), ),
title: 'Deposit', title: 'Deposit',
subTitle: ( renderSubTitle: getDepositSubTitle,
<VaultDepositSubTitle
primaryAmount={primaryAmount}
secondaryAmount={secondaryAmount}
primaryAsset={props.primaryAsset}
secondaryAsset={props.secondaryAsset}
/>
),
isOpen: isOpen[0], isOpen: isOpen[0],
toggleOpen: (index: number) => toggleOpen(index), toggleOpen: (index: number) => toggleOpen(index),
}, },
@ -102,7 +121,7 @@ export default function VaultModalContent(props: Props) {
/> />
), ),
title: 'Borrow', title: 'Borrow',
subTitle: <VaultBorrowingsSubTitle borrowings={borrowings} />, renderSubTitle: getBorrowingsSubTitle,
isOpen: isOpen[1], isOpen: isOpen[1],
toggleOpen: (index: number) => toggleOpen(index), toggleOpen: (index: number) => toggleOpen(index),
}, },

View File

@ -0,0 +1,61 @@
import VaultLogo from 'components/Earn/vault/VaultLogo'
import Modal from 'components/Modal'
import Text from 'components/Text'
import { ASSETS } from 'constants/assets'
import useCurrentAccount from 'hooks/useCurrentAccount'
import useStore from 'store'
import VaultModalContent from 'components/Modals/Vault/VaultModalContent'
export default function VaultModalController() {
const currentAccount = useCurrentAccount()
const modal = useStore((s) => s.vaultModal)
const primaryAsset = ASSETS.find((asset) => asset.denom === modal?.vault.denoms.primary)
const secondaryAsset = ASSETS.find((asset) => asset.denom === modal?.vault.denoms.secondary)
if (!modal || !currentAccount || !primaryAsset || !secondaryAsset) return null
return (
<VaultModal
currentAccount={currentAccount}
modal={modal}
primaryAsset={primaryAsset}
secondaryAsset={secondaryAsset}
/>
)
}
interface Props {
currentAccount: Account
modal: VaultModal
primaryAsset: Asset
secondaryAsset: Asset
}
function VaultModal(props: Props) {
function onClose() {
useStore.setState({ vaultModal: null })
}
return (
<Modal
open={true}
onClose={onClose}
header={
<span className='flex items-center gap-4 px-4'>
<VaultLogo vault={props.modal.vault} />
<Text>{`${props.modal.vault.symbols.primary} - ${props.modal.vault.symbols.secondary}`}</Text>
</span>
}
headerClassName='gradient-header pl-2 pr-2.5 py-2.5 border-b-white/5 border-b'
contentClassName='flex flex-col'
>
<VaultModalContent
vault={props.modal.vault}
primaryAsset={props.primaryAsset}
secondaryAsset={props.secondaryAsset}
account={props.currentAccount}
isDeposited={props.modal.isDeposited}
/>
</Modal>
)
}

View File

@ -157,6 +157,7 @@ export default function NumberInput(props: Props) {
props.className, props.className,
)} )}
style={props.style} style={props.style}
placeholder='0'
/> />
) )
} }

View File

@ -24,36 +24,31 @@ export default function useDepositVault(props: Props): { actions: Action[]; fee:
const { data: prices } = usePrices() const { data: prices } = usePrices()
const slippage = useStore((s) => s.slippage) const slippage = useStore((s) => s.slippage)
const borrowings: BNCoin[] = useMemo(
() => props.borrowings.filter((borrowing) => borrowing.amount.gt(0)),
[props.borrowings],
)
const deposits: BNCoin[] = useMemo(
() => props.deposits.filter((deposit) => deposit.amount.gt(0)),
[props.deposits],
)
const debouncedGetMinLpToReceive = useMemo(() => debounce(getMinLpToReceive, 500), []) const debouncedGetMinLpToReceive = useMemo(() => debounce(getMinLpToReceive, 500), [])
const { primaryCoin, secondaryCoin, totalValue } = useMemo( const { primaryCoin, secondaryCoin, totalValue } = useMemo(
() => () => getVaultDepositCoinsAndValue(props.vault, deposits, borrowings, prices),
getVaultDepositCoinsAndValue( [deposits, borrowings, props.vault, prices],
props.vault,
props.deposits.filter((borrowing) => borrowing.amount.gt(0)),
props.borrowings.filter((borrowing) => borrowing.amount.gt(0)),
prices,
),
[props.deposits, props.borrowings, props.vault, prices],
) )
const borrowActions: Action[] = useMemo(() => { const borrowActions: Action[] = useMemo(() => {
return props.borrowings.map((bnCoin) => ({ return borrowings.map((bnCoin) => ({
borrow: bnCoin.toCoin(), borrow: bnCoin.toCoin(),
})) }))
}, [props.borrowings]) }, [borrowings])
const swapActions: Action[] = useMemo( const swapActions: Action[] = useMemo(
() => () => getVaultSwapActions(props.vault, deposits, borrowings, prices, slippage, totalValue),
getVaultSwapActions( [totalValue, prices, props.vault, deposits, borrowings, slippage],
props.vault,
props.deposits.filter((borrowing) => borrowing.amount.gt(0)),
props.borrowings.filter((borrowing) => borrowing.amount.gt(0)),
prices,
slippage,
totalValue,
),
[totalValue, prices, props.vault, props.deposits, props.borrowings, slippage],
) )
useMemo(async () => { useMemo(async () => {
@ -77,6 +72,8 @@ export default function useDepositVault(props: Props): { actions: Action[]; fee:
]) ])
const enterVaultActions: Action[] = useMemo(() => { const enterVaultActions: Action[] = useMemo(() => {
if (primaryCoin.amount.isZero() || secondaryCoin.amount.isZero()) return []
return getEnterVaultActions(props.vault, primaryCoin, secondaryCoin, minLpToReceive) return getEnterVaultActions(props.vault, primaryCoin, secondaryCoin, minLpToReceive)
}, [props.vault, primaryCoin, secondaryCoin, minLpToReceive]) }, [props.vault, primaryCoin, secondaryCoin, minLpToReceive])

View File

@ -1,13 +1,13 @@
import Tab from 'components/Earn/Tab' import Tab from 'components/Earn/Tab'
import Vaults from 'components/Earn/vault/Vaults' import { AvailableVaults, DepositedVaults } from 'components/Earn/vault/Vaults'
export default function FarmPage() { export default function FarmPage() {
return ( return (
<> <>
<Tab isFarm /> <Tab isFarm />
{/* <FeaturedVaults /> */} {/* <FeaturedVaults /> */}
<Vaults type='deposited' /> <DepositedVaults />
<Vaults type='available' /> <AvailableVaults />
</> </>
) )
} }

View File

@ -23,7 +23,8 @@ interface BorrowModal {
} }
interface VaultModal { interface VaultModal {
vault: Vault vault: Vault | DepositedVault
isDeposited?: boolean
selectedBorrowDenoms: string[] selectedBorrowDenoms: string[]
} }