MP-2803: created fund already funded account flow (#350)

This commit is contained in:
Linkie Link 2023-08-08 15:57:53 +02:00 committed by GitHub
parent d43437e440
commit f4fc2dcfcc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 197 additions and 125 deletions

View File

@ -5,13 +5,12 @@ import Button from 'components/Button'
import Card from 'components/Card'
import FullOverlayContent from 'components/FullOverlayContent'
import { Plus } from 'components/Icons'
import SwitchWithLabel from 'components/SwitchWithLabel'
import SwitchAutoLend from 'components/Switch/SwitchAutoLend'
import Text from 'components/Text'
import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider'
import WalletBridges from 'components/Wallet/WalletBridges'
import { BN_ZERO } from 'constants/math'
import useAccounts from 'hooks/useAccounts'
import useAutoLendEnabledAccountIds from 'hooks/useAutoLendEnabledAccountIds'
import useCurrentAccount from 'hooks/useCurrentAccount'
import useToggle from 'hooks/useToggle'
import useWalletBalances from 'hooks/useWalletBalances'
@ -34,8 +33,6 @@ export default function AccountFund() {
const [fundingAssets, setFundingAssets] = useState<BNCoin[]>([])
const { data: walletBalances } = useWalletBalances(address)
const baseAsset = getBaseAsset()
const { autoLendEnabledAccountIds, toggleAutoLend } = useAutoLendEnabledAccountIds()
const isAutoLendEnabled = autoLendEnabledAccountIds.includes(accountId ?? '0')
const hasAssetSelected = fundingAssets.length > 0
const hasFundingAssets =
fundingAssets.length > 0 && fundingAssets.every((a) => a.toCoin().amount !== '0')
@ -88,7 +85,7 @@ export default function AccountFund() {
const updateFundingAssets = useCallback(
(amount: BigNumber, denom: string) => {
const assetToUpdate = fundingAssets.find((asset) => asset.denom === denom)
const assetToUpdate = fundingAssets.find(byDenom(denom))
if (assetToUpdate) {
assetToUpdate.amount = amount
setFundingAssets([...fundingAssets.filter((a) => a.denom !== denom), assetToUpdate])
@ -116,21 +113,21 @@ export default function AccountFund() {
copy='In order to start trading with this account, you need to deposit funds.'
docs='fund'
>
<Card className='w-full bg-white/5 p-6'>
<Card className='w-full p-6 bg-white/5'>
{!hasAssetSelected && <Text>Please select an asset.</Text>}
{selectedDenoms.map((denom) => {
const asset = getAssetByDenom(denom) as Asset
{fundingAssets.map((coin) => {
const asset = getAssetByDenom(coin.denom) as Asset
const balance = walletBalances.find(byDenom(asset.denom))?.amount ?? '0'
const balance = walletBalances.find(byDenom(coin.denom))?.amount ?? '0'
return (
<div
key={asset.symbol}
className='w-full rounded-base border border-white/20 bg-white/5 p-4'
className='w-full p-4 border rounded-base border-white/20 bg-white/5'
>
<TokenInputWithSlider
asset={asset}
onChange={(amount) => updateFundingAssets(amount, asset.denom)}
amount={BN_ZERO}
amount={coin.amount ?? BN_ZERO}
max={BN(balance)}
balances={walletBalances}
maxText='Max'
@ -140,24 +137,19 @@ export default function AccountFund() {
})}
<Button
className='mt-4 w-full'
className='w-full mt-4'
text='Select assets'
color='tertiary'
rightIcon={<Plus />}
iconClassName='w-3'
onClick={handleSelectAssetsClick}
/>
<div className='mt-4 border border-transparent border-t-white/10 pt-4'>
<SwitchWithLabel
name='isLending'
label='Lend assets to earn yield'
value={isAutoLendEnabled}
onChange={() => toggleAutoLend(selectedAccountId)}
tooltip={`Fund your account and lend assets effortlessly! By lending, you'll earn attractive interest (APY) without impacting your LTV. It's a win-win situation - don't miss out on this easy opportunity to grow your holdings!`}
/>
</div>
<SwitchAutoLend
className='pt-4 mt-4 border border-transparent border-t-white/10'
accountId={selectedAccountId}
/>
<Button
className='mt-4 w-full'
className='w-full mt-4'
text='Fund account'
color='tertiary'
disabled={!hasFundingAssets}

View File

@ -8,9 +8,8 @@ import Button from 'components/Button'
import Card from 'components/Card'
import { ArrowCircledTopRight, ArrowDownLine, ArrowUpLine, TrashBin } from 'components/Icons'
import Radio from 'components/Radio'
import SwitchWithLabel from 'components/SwitchWithLabel'
import SwitchAutoLend from 'components/Switch/SwitchAutoLend'
import Text from 'components/Text'
import useAutoLendEnabledAccountIds from 'hooks/useAutoLendEnabledAccountIds'
import useCurrentAccount from 'hooks/useCurrentAccount'
import usePrices from 'hooks/usePrices'
import useStore from 'store'
@ -32,7 +31,6 @@ export default function AccountList(props: Props) {
const { pathname } = useLocation()
const { address } = useParams()
const { data: prices } = usePrices()
const { autoLendEnabledAccountIds, toggleAutoLend } = useAutoLendEnabledAccountIds()
const account = useCurrentAccount()
const accountId = account?.id
@ -52,11 +50,10 @@ export default function AccountList(props: Props) {
if (!props.accounts?.length) return null
return (
<div className='flex w-full flex-wrap p-4'>
<div className='flex flex-wrap w-full p-4'>
{props.accounts.map((account) => {
const positionBalance = calculateAccountValue('deposits', account, prices)
const isActive = accountId === account.id
const isAutoLendEnabled = autoLendEnabledAccountIds.includes(account.id)
return (
<div key={account.id} id={`account-${account.id}`} className='w-full pt-4'>
@ -81,7 +78,7 @@ export default function AccountList(props: Props) {
>
{isActive ? (
<>
<div className='w-full border border-transparent border-b-white/20 p-4'>
<div className='w-full p-4 border border-transparent border-b-white/20'>
<AccountStats account={account} />
</div>
<div className='grid grid-flow-row grid-cols-2 gap-4 p-4'>
@ -122,15 +119,10 @@ export default function AccountList(props: Props) {
text='Transfer'
onClick={() => {}}
/>
<div className='col-span-2 border border-transparent border-t-white/10 pt-4'>
<SwitchWithLabel
name='isLending'
label='Lend assets to earn yield'
value={isAutoLendEnabled}
onChange={() => toggleAutoLend(account.id)}
tooltip={`Fund your account and lend assets effortlessly! By lending, you'll earn attractive interest (APY) without impacting your LTV. It's a win-win situation - don't miss out on this easy opportunity to grow your holdings!`}
/>
</div>
<SwitchAutoLend
className='col-span-2 pt-4 border border-transparent border-t-white/10'
accountId={account.id}
/>
</div>
</>
) : (

View File

@ -4,16 +4,17 @@ import AccountComposition from 'components/Account/AccountComposition'
import AccountHealth from 'components/Account/AccountHealth'
import Card from 'components/Card'
import DisplayCurrency from 'components/DisplayCurrency'
import { ArrowChartLineUp } from 'components/Icons'
import Text from 'components/Text'
import { BN_ZERO } from 'constants/math'
import { ORACLE_DENOM } from 'constants/oracle'
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
import useHealthComputer from 'hooks/useHealthComputer'
import useIsOpenArray from 'hooks/useIsOpenArray'
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
import usePrices from 'hooks/usePrices'
import { BNCoin } from 'types/classes/BNCoin'
import { calculateAccountValue } from 'utils/accounts'
import { formatHealth } from 'utils/formatters'
import { BN } from 'utils/helpers'
interface Props {
account?: Account
@ -32,6 +33,9 @@ export default function AccountSummary(props: Props) {
useLendingMarketAssetsTableData()
const borrowAssetsData = [...borrowAvailableAssets, ...accountBorrowedAssets]
const lendingAssetsData = [...lendingAvailableAssets, ...accountLentAssets]
const { health } = useHealthComputer(props.account)
const healthFactor = BN(100).minus(formatHealth(health)).toNumber()
if (!props.account) return null
return (
@ -44,13 +48,7 @@ export default function AccountSummary(props: Props) {
/>
</Item>
<Item>
<span className='flex h-4 w-4 items-center'>
<ArrowChartLineUp />
</span>
<Text size='sm'>4.5x</Text>
</Item>
<Item>
<AccountHealth health={80} />
<AccountHealth health={healthFactor} />
</Item>
</Card>
<Accordion
@ -89,7 +87,7 @@ export default function AccountSummary(props: Props) {
function Item(props: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className='flex flex-1 items-center justify-center gap-1 border-r border-r-white/10 px-2 py-2'
className='flex items-center justify-center flex-1 gap-1 px-2 py-2 border-r border-r-white/10'
{...props}
>
{props.children}

View File

@ -24,12 +24,15 @@ interface Props {
export default function AssetSelectTable(props: Props) {
const defaultSelected = useMemo(() => {
const assets = props.assets as BorrowAsset[]
return assets.reduce((acc, asset, index) => {
if (props.selectedDenoms?.includes(asset.denom)) {
acc[index] = true
}
return acc
}, {} as { [key: number]: boolean })
return assets.reduce(
(acc, asset, index) => {
if (props.selectedDenoms?.includes(asset.denom)) {
acc[index] = true
}
return acc
},
{} as { [key: number]: boolean },
)
}, [props.selectedDenoms, props.assets])
const [sorting, setSorting] = useState<SortingState>([{ id: 'symbol', desc: false }])
const [selected, setSelected] = useState<RowSelectionState>(defaultSelected)

View File

@ -154,7 +154,7 @@ function BorrowModal(props: Props) {
headerClassName='gradient-header pl-2 pr-2.5 py-2.5 border-b-white/5 border-b'
contentClassName='flex flex-col'
>
<div className='flex gap-3 border-b border-white/5 px-6 py-4 gradient-header'>
<div className='flex gap-3 px-6 py-4 border-b border-white/5 gradient-header'>
<TitleAndSubCell
title={formatPercent(modal?.marketData.borrowRate || '0')}
sub={'Borrow rate'}
@ -173,12 +173,12 @@ function BorrowModal(props: Props) {
sub={'Liquidity available'}
/>
</div>
<div className='flex flex-1 items-start gap-6 p-6'>
<div className='flex items-start flex-1 gap-6 p-6'>
<Card
className='flex flex-1 bg-white/5 p-4'
className='flex flex-1 p-4 bg-white/5'
contentClassName='gap-6 flex flex-col justify-between h-full min-h-[380px]'
>
<div className='flex w-full flex-wrap'>
<div className='flex flex-wrap w-full'>
<TokenInputWithSlider
asset={asset}
onChange={setAmount}
@ -189,8 +189,8 @@ function BorrowModal(props: Props) {
disabled={isConfirming}
/>
<Divider className='my-6' />
<div className='flex flex-1 flex-wrap'>
<Text className='mb-1 w-full'>Receive funds to Wallet</Text>
<div className='flex flex-wrap flex-1'>
<Text className='w-full mb-1'>Receive funds to Wallet</Text>
<Text size='xs' className='text-white/50'>
Your borrowed funds will directly go to your wallet
</Text>

View File

@ -1,17 +1,20 @@
import BigNumber from 'bignumber.js'
import { useState } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import Button from 'components/Button'
import { ArrowRight } from 'components/Icons'
import { ArrowRight, Plus } from 'components/Icons'
import SwitchAutoLend from 'components/Switch/SwitchAutoLend'
import Text from 'components/Text'
import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider'
import { ASSETS } from 'constants/assets'
import WalletBridges from 'components/Wallet/WalletBridges'
import { BN_ZERO } from 'constants/math'
import useToggle from 'hooks/useToggle'
import useWalletBalances from 'hooks/useWalletBalances'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { getAmount } from 'utils/accounts'
import { byDenom } from 'utils/array'
import { getAssetByDenom, getBaseAsset } from 'utils/assets'
import { hardcodedFee } from 'utils/constants'
import { BN } from 'utils/helpers'
interface Props {
account: Account
@ -20,68 +23,123 @@ interface Props {
export default function FundAccount(props: Props) {
const { account, setChange } = props
const accountId = account.id
const address = useStore((s) => s.address)
const deposit = useStore((s) => s.deposit)
const balances = useStore((s) => s.balances)
const defaultAsset = ASSETS.find(byDenom(balances[0].denom)) ?? ASSETS[0]
const [isConfirming, setIsConfirming] = useToggle()
const [currentAsset, setCurrentAsset] = useState(defaultAsset)
const [amount, setAmount] = useState(BN_ZERO)
const depositAmount = BN_ZERO.plus(amount)
const max = getAmount(currentAsset.denom, balances ?? [])
const walletAssetModal = useStore((s) => s.walletAssetsModal)
const [isFunding, setIsFunding] = useToggle(false)
const [fundingAssets, setFundingAssets] = useState<BNCoin[]>([])
const { data: walletBalances } = useWalletBalances(address)
const baseAsset = getBaseAsset()
const hasAssetSelected = fundingAssets.length > 0
const hasFundingAssets =
fundingAssets.length > 0 && fundingAssets.every((a) => a.toCoin().amount !== '0')
function onChangeAmount(val: BigNumber) {
setAmount(val)
setChange({
deposits: [
{
amount: depositAmount.toString(),
denom: currentAsset.denom,
},
],
})
}
const baseBalance = useMemo(
() => walletBalances.find(byDenom(baseAsset.denom))?.amount ?? '0',
[walletBalances, baseAsset],
)
function resetState() {
setCurrentAsset(defaultAsset)
setAmount(BN_ZERO)
setChange(undefined)
}
const selectedDenoms = useMemo(() => {
return walletAssetModal?.selectedDenoms ?? []
}, [walletAssetModal?.selectedDenoms])
async function onConfirm() {
setIsConfirming(true)
const handleClick = useCallback(async () => {
setIsFunding(true)
if (!accountId) return
const result = await deposit({
fee: hardcodedFee,
accountId: account.id,
coins: [BNCoin.fromDenomAndBigNumber(currentAsset.denom, amount)],
accountId,
coins: fundingAssets,
})
setIsFunding(false)
if (result) useStore.setState({ focusComponent: null, walletAssetsModal: null })
}, [fundingAssets, accountId, setIsFunding, deposit])
setIsConfirming(false)
if (result) {
resetState()
useStore.setState({ fundAndWithdrawModal: null })
const handleSelectAssetsClick = useCallback(() => {
useStore.setState({
walletAssetsModal: {
isOpen: true,
selectedDenoms,
},
})
}, [selectedDenoms])
useEffect(() => {
const currentSelectedDenom = fundingAssets.map((asset) => asset.denom)
if (
selectedDenoms.every((denom) => currentSelectedDenom.includes(denom)) &&
selectedDenoms.length === currentSelectedDenom.length
)
return
const newFundingAssets = selectedDenoms.map((denom) =>
BNCoin.fromDenomAndBigNumber(denom, BN(fundingAssets.find(byDenom(denom))?.amount ?? '0')),
)
setFundingAssets(newFundingAssets)
}, [selectedDenoms, fundingAssets])
const updateFundingAssets = useCallback(
(amount: BigNumber, denom: string) => {
const assetToUpdate = fundingAssets.find(byDenom(denom))
if (assetToUpdate) {
assetToUpdate.amount = amount
setFundingAssets([...fundingAssets.filter((a) => a.denom !== denom), assetToUpdate])
}
setChange({ deposits: fundingAssets })
},
[fundingAssets, setChange],
)
useEffect(() => {
if (BN(baseBalance).isLessThan(hardcodedFee.amount[0].amount)) {
useStore.setState({ focusComponent: <WalletBridges /> })
}
}
}, [baseBalance])
return (
<>
<TokenInputWithSlider
asset={currentAsset}
onChange={onChangeAmount}
onChangeAsset={setCurrentAsset}
amount={amount}
max={max}
className='w-full'
balances={balances}
hasSelect
maxText='Max'
disabled={isConfirming}
/>
<div className='flex flex-wrap items-start'>
{!hasAssetSelected && <Text>Please select an asset.</Text>}
{fundingAssets.map((coin) => {
const asset = getAssetByDenom(coin.denom) as Asset
const balance = walletBalances.find(byDenom(coin.denom))?.amount ?? '0'
return (
<TokenInputWithSlider
key={coin.denom}
asset={asset}
onChange={(amount) => updateFundingAssets(amount, asset.denom)}
amount={coin.amount ?? BN_ZERO}
max={BN(balance)}
balances={walletBalances}
maxText='Max'
className='w-full mb-4'
/>
)
})}
<Button
className='w-full mt-4'
text='Select assets'
color='tertiary'
rightIcon={<Plus />}
iconClassName='w-3'
onClick={handleSelectAssetsClick}
/>
<SwitchAutoLend
className='pt-4 mt-4 border border-transparent border-t-white/10'
accountId={accountId}
/>
</div>
<Button
onClick={onConfirm}
showProgressIndicator={isConfirming}
className='w-full'
text={'Fund'}
className='w-full mt-4'
text='Fund account'
rightIcon={<ArrowRight />}
disabled={!hasFundingAssets}
showProgressIndicator={isFunding}
onClick={handleClick}
/>
</>
)

View File

@ -9,14 +9,15 @@ import Slider from 'components/Slider'
import Text from 'components/Text'
import TokenInput from 'components/TokenInput'
import { BN_ZERO } from 'constants/math'
import useHealthComputer from 'hooks/useHealthComputer'
import useMarketAssets from 'hooks/useMarketAssets'
import usePrices from 'hooks/usePrices'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { Action } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { byDenom } from 'utils/array'
import { findCoinByDenom, getAssetByDenom } from 'utils/assets'
import { formatPercent } from 'utils/formatters'
import useHealthComputer from 'hooks/useHealthComputer'
export interface VaultBorrowingsProps {
updatedAccount: Account
@ -154,12 +155,10 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
}
return (
<div className='flex flex-1 flex-col gap-4 p-4'>
<div className='flex flex-col flex-1 gap-4 p-4'>
{props.borrowings.map((coin) => {
const asset = getAssetByDenom(coin.denom)
const maxAmount = maxBorrowAmounts.find(
(maxAmount) => maxAmount.denom === coin.denom,
)?.amount
const maxAmount = maxBorrowAmounts.find(byDenom(coin.denom))?.amount
if (!asset || !maxAmount)
return <React.Fragment key={`input-${coin.denom}`}></React.Fragment>
return (

View File

@ -10,13 +10,13 @@ import Slider from 'components/Slider'
import Switch from 'components/Switch'
import Text from 'components/Text'
import TokenInput from 'components/TokenInput'
import { BN_ZERO } from 'constants/math'
import usePrice from 'hooks/usePrice'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { getAmount } from 'utils/accounts'
import { BN } from 'utils/helpers'
import { findCoinByDenom } from 'utils/assets'
import { BN_ZERO } from 'constants/math'
import { BN } from 'utils/helpers'
interface Props {
deposits: BNCoin[]

View File

@ -34,9 +34,9 @@ export default function WalletAssetsModalContent(props: Props) {
)
}, [assets, searchString])
const currentSelectedDenom = useStore((s) => s.walletAssetsModal?.selectedDenoms)
const currentSelectedDenom = useStore((s) => s.walletAssetsModal?.selectedDenoms ?? [])
const [selectedDenoms, setSelectedDenoms] = useState<string[]>(
currentSelectedDenom?.filter((denom) => filteredAssets.findIndex(byDenom(denom))) || [],
currentSelectedDenom.filter((denom) => filteredAssets.findIndex(byDenom(denom)) || []),
)
const onChangeSelect = useCallback(
@ -49,7 +49,7 @@ export default function WalletAssetsModalContent(props: Props) {
return (
<>
<div className='border-b border-white/5 bg-white/10 px-4 py-3'>
<div className='px-4 py-3 border-b border-white/5 bg-white/10'>
<SearchBar
value={searchString}
placeholder={`Search for e.g. "ETH" or "Ethereum"`}

View File

@ -0,0 +1,27 @@
import classNames from 'classnames'
import SwitchWithLabel from 'components/Switch/SwitchWithLabel'
import useAutoLendEnabledAccountIds from 'hooks/useAutoLendEnabledAccountIds'
interface Props {
accountId: string
className?: string
}
export default function SwitchAutoLend(props: Props) {
const { accountId, className } = props
const { autoLendEnabledAccountIds, toggleAutoLend } = useAutoLendEnabledAccountIds()
const isAutoLendEnabled = autoLendEnabledAccountIds.includes(accountId)
return (
<div className={classNames('w-full', className)}>
<SwitchWithLabel
name='isLending'
label='Lend assets to earn yield'
value={isAutoLendEnabled}
onChange={() => toggleAutoLend(accountId)}
tooltip={`Fund your account and lend assets effortlessly! By lending, you'll earn attractive interest (APY) without impacting your LTV. It's a win-win situation - don't miss out on this easy opportunity to grow your holdings!`}
/>
</div>
)
}

View File

@ -4,7 +4,8 @@
@font-face {
font-family: Inter;
src: url('../fonts/Inter-ExtraLight.woff2') format('woff2'),
src:
url('../fonts/Inter-ExtraLight.woff2') format('woff2'),
url('../fonts/Inter-ExtraLight.woff') format('woff');
font-weight: 300;
font-style: normal;
@ -13,7 +14,8 @@
@font-face {
font-family: Inter;
src: url('../fonts/Inter-Regular.woff2') format('woff2'),
src:
url('../fonts/Inter-Regular.woff2') format('woff2'),
url('../fonts/Inter-Regular.woff') format('woff');
font-weight: 400;
font-style: normal;
@ -22,7 +24,8 @@
@font-face {
font-family: Inter;
src: url('../fonts/Inter-SemiBold.woff2') format('woff2'),
src:
url('../fonts/Inter-SemiBold.woff2') format('woff2'),
url('../fonts/Inter-SemiBold.woff') format('woff');
font-weight: 600;
font-style: normal;