MP-2894: withdraw from account (#339)

This commit is contained in:
Linkie Link 2023-08-04 15:10:30 +02:00 committed by GitHub
parent 103c8bed9a
commit 267b968c4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 429 additions and 272 deletions

View File

@ -132,7 +132,7 @@ export default function AccountBalancesTable(props: Props) {
return (
<FormattedNumber
className='text-right text-xs'
amount={Number(BN(amount).abs().toPrecision(2))}
amount={Number(BN(amount).abs())}
options={{ maxDecimals: 2, abbreviated: true }}
animate
/>

View File

@ -1,5 +1,6 @@
import BigNumber from 'bignumber.js'
import classNames from 'classnames'
import { useMemo } from 'react'
import DisplayCurrency from 'components/DisplayCurrency'
import { FormattedNumber } from 'components/FormattedNumber'
@ -13,9 +14,8 @@ import {
calculateAccountApr,
calculateAccountBalanceValue,
calculateAccountBorrowRate,
calculateAccountDebtValue,
calculateAccountDepositsValue,
calculateAccountPnL,
getAccountPositionValues,
} from 'utils/accounts'
interface Props {
@ -28,24 +28,37 @@ interface ItemProps {
current: BigNumber
change: BigNumber
className?: string
isBadIncrease?: boolean
isDecrease?: boolean
isPercentage?: boolean
}
export default function AccountComposition(props: Props) {
const { account, change } = props
const { data: prices } = usePrices()
const balance = calculateAccountDepositsValue(props.account, prices)
const balanceChange = props.change ? calculateAccountDepositsValue(props.change, prices) : BN_ZERO
const debtBalance = calculateAccountDebtValue(props.account, prices)
const debtBalanceChange = props.change ? calculateAccountDebtValue(props.change, prices) : BN_ZERO
const totalBalance = calculateAccountBalanceValue(props.account, prices)
const totalBalanceChange = props.change
? calculateAccountBalanceValue(props.change, prices)
: BN_ZERO
const apr = calculateAccountApr(props.account, prices)
const aprChange = props.change ? calculateAccountPnL(props.change, prices) : BN_ZERO
const borrowRate = calculateAccountBorrowRate(props.account, prices)
const borrowRateChange = props.change ? calculateAccountPnL(props.change, prices) : BN_ZERO
const [depositsBalance, lendsBalance, debtsBalance] = useMemo(
() => getAccountPositionValues(account, prices),
[account, prices],
)
const [depositsBalanceChange, lendsBalanceChange, debtsBalanceChange] = useMemo(
() => (change ? getAccountPositionValues(change, prices) : [BN_ZERO, BN_ZERO, BN_ZERO]),
[change, prices],
)
const totalBalance = useMemo(
() => calculateAccountBalanceValue(account, prices),
[account, prices],
)
const totalBalanceChange = useMemo(
() => (change ? calculateAccountBalanceValue(change, prices) : BN_ZERO),
[change, prices],
)
const balance = depositsBalance.plus(lendsBalance)
const balanceChange = depositsBalanceChange.plus(lendsBalanceChange)
const apr = calculateAccountApr(account, prices)
const aprChange = change ? calculateAccountPnL(change, prices) : BN_ZERO
const borrowRate = calculateAccountBorrowRate(account, prices)
const borrowRateChange = change ? calculateAccountPnL(change, prices) : BN_ZERO
return (
<div className='w-full flex-wrap p-4'>
@ -57,10 +70,10 @@ export default function AccountComposition(props: Props) {
/>
<Item
title='Total Debt'
current={debtBalance}
change={debtBalance.plus(debtBalanceChange)}
current={debtsBalance}
change={debtsBalance.plus(debtsBalanceChange)}
className='pb-3'
isBadIncrease
isDecrease
/>
<Item
title='Total Balance'
@ -74,16 +87,16 @@ export default function AccountComposition(props: Props) {
current={borrowRate}
change={borrowRate.plus(borrowRateChange)}
isPercentage
isBadIncrease
isDecrease
/>
</div>
)
}
function Item(props: ItemProps) {
const increase = props.isBadIncrease
? props.current.isGreaterThan(props.change)
: props.current.isLessThan(props.change)
const { current, change } = props
const increase = props.isDecrease ? current.isGreaterThan(change) : current.isLessThan(change)
return (
<div className={classNames('flex w-full flex-nowrap', props.className)}>
<div className='flex flex-shrink items-center'>
@ -94,32 +107,32 @@ function Item(props: ItemProps) {
<div className='flex flex-1 items-center justify-end gap-2'>
{props.isPercentage ? (
<FormattedNumber
amount={props.current.toNumber()}
amount={current.toNumber()}
options={{ suffix: '%', minDecimals: 2, maxDecimals: 2 }}
className='text-sm'
animate
/>
) : (
<DisplayCurrency
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, props.current)}
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, current)}
className='text-sm'
/>
)}
{!props.current.isEqualTo(props.change) && (
{!current.isEqualTo(change) && (
<>
<span className={classNames('w-3', increase ? 'text-profit' : 'text-loss')}>
<ArrowRight />
</span>
{props.isPercentage ? (
<FormattedNumber
amount={props.change.toNumber()}
amount={change.toNumber()}
options={{ suffix: '%', minDecimals: 2, maxDecimals: 2 }}
className={classNames('text-sm', increase ? 'text-profit' : 'text-loss')}
animate
/>
) : (
<DisplayCurrency
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, props.change)}
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, change)}
className={classNames('text-sm', increase ? 'text-profit' : 'text-loss')}
/>
)}

View File

@ -7,7 +7,7 @@ import FullOverlayContent from 'components/FullOverlayContent'
import { Plus } from 'components/Icons'
import SwitchWithLabel from 'components/SwitchWithLabel'
import Text from 'components/Text'
import TokenInputWithSlider from 'components/TokenInputWithSlider'
import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider'
import WalletBridges from 'components/Wallet/WalletBridges'
import { BN_ZERO } from 'constants/math'
import useAccounts from 'hooks/useAccounts'
@ -16,6 +16,7 @@ import useCurrentAccount from 'hooks/useCurrentAccount'
import useToggle from 'hooks/useToggle'
import useWalletBalances from 'hooks/useWalletBalances'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { byDenom } from 'utils/array'
import { getAssetByDenom, getBaseAsset } from 'utils/assets'
import { hardcodedFee } from 'utils/constants'
@ -30,13 +31,14 @@ export default function AccountFund() {
const currentAccount = useCurrentAccount()
const [isFunding, setIsFunding] = useToggle(false)
const [selectedAccountId, setSelectedAccountId] = useState<null | string>(null)
const [fundingAssets, setFundingAssets] = useState<Coin[]>([])
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.amount !== '0')
const hasFundingAssets =
fundingAssets.length > 0 && fundingAssets.every((a) => a.toCoin().amount !== '0')
const baseBalance = useMemo(
() => walletBalances.find(byDenom(baseAsset.denom))?.amount ?? '0',
@ -77,10 +79,9 @@ export default function AccountFund() {
)
return
const newFundingAssets = selectedDenoms.map((denom) => ({
denom,
amount: fundingAssets.find((asset) => asset.denom === denom)?.amount ?? '0',
}))
const newFundingAssets = selectedDenoms.map((denom) =>
BNCoin.fromDenomAndBigNumber(denom, BN(fundingAssets.find(byDenom(denom))?.amount ?? '0')),
)
setFundingAssets(newFundingAssets)
}, [selectedDenoms, fundingAssets])
@ -89,7 +90,7 @@ export default function AccountFund() {
(amount: BigNumber, denom: string) => {
const assetToUpdate = fundingAssets.find((asset) => asset.denom === denom)
if (assetToUpdate) {
assetToUpdate.amount = amount.toString()
assetToUpdate.amount = amount
setFundingAssets([...fundingAssets.filter((a) => a.denom !== denom), assetToUpdate])
}
},

View File

@ -14,7 +14,7 @@ import useAutoLendEnabledAccountIds from 'hooks/useAutoLendEnabledAccountIds'
import useCurrentAccount from 'hooks/useCurrentAccount'
import usePrices from 'hooks/usePrices'
import useStore from 'store'
import { calculateAccountDepositsValue } from 'utils/accounts'
import { calculateAccountValue } from 'utils/accounts'
import { getPage, getRoute } from 'utils/route'
interface Props {
@ -54,7 +54,7 @@ export default function AccountList(props: Props) {
return (
<div className='flex w-full flex-wrap p-4'>
{props.accounts.map((account) => {
const positionBalance = calculateAccountDepositsValue(account, prices)
const positionBalance = calculateAccountValue('deposits', account, prices)
const isActive = accountId === account.id
const isAutoLendEnabled = autoLendEnabledAccountIds.includes(account.id)

View File

@ -4,7 +4,7 @@ import { ORACLE_DENOM } from 'constants/oracle'
import useHealthComputer from 'hooks/useHealthComputer'
import usePrices from 'hooks/usePrices'
import { BNCoin } from 'types/classes/BNCoin'
import { calculateAccountDepositsValue } from 'utils/accounts'
import { calculateAccountValue } from 'utils/accounts'
import { formatHealth } from 'utils/formatters'
import { BN } from 'utils/helpers'
@ -14,7 +14,7 @@ interface Props {
export default function AccountStats(props: Props) {
const { data: prices } = usePrices()
const positionBalance = calculateAccountDepositsValue(props.account, prices)
const positionBalance = calculateAccountValue('deposits', props.account, prices)
const { health } = useHealthComputer(props.account)
const healthFactor = BN(100).minus(formatHealth(health)).toNumber()
return (

View File

@ -13,7 +13,7 @@ import useIsOpenArray from 'hooks/useIsOpenArray'
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
import usePrices from 'hooks/usePrices'
import { BNCoin } from 'types/classes/BNCoin'
import { calculateAccountDepositsValue } from 'utils/accounts'
import { calculateAccountValue } from 'utils/accounts'
interface Props {
account?: Account
@ -24,7 +24,7 @@ export default function AccountSummary(props: Props) {
const [isOpen, toggleOpen] = useIsOpenArray(2, true)
const { data: prices } = usePrices()
const accountBalance = props.account
? calculateAccountDepositsValue(props.account, prices)
? calculateAccountValue('deposits', props.account, prices)
: BN_ZERO
const { availableAssets: borrowAvailableAssets, accountBorrowedAssets } =
useBorrowMarketAssetsTableData()

View File

@ -8,10 +8,10 @@ import Divider from 'components/Divider'
import { ArrowRight } from 'components/Icons'
import Modal from 'components/Modal'
import Text from 'components/Text'
import TokenInputWithSlider from 'components/TokenInputWithSlider'
import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider'
import { BN_ZERO } from 'constants/math'
import { byDenom } from 'utils/array'
import { BN } from 'utils/helpers'
import { BN_ZERO } from 'constants/math'
interface Props {
asset: Asset

View File

@ -24,15 +24,12 @@ 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

@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'
import BigNumber from 'bignumber.js'
import { useEffect, useState } from 'react'
import AccountSummary from 'components/Account/AccountSummary'
import AssetImage from 'components/AssetImage'
@ -11,17 +11,17 @@ import Modal from 'components/Modal'
import Switch from 'components/Switch'
import Text from 'components/Text'
import TitleAndSubCell from 'components/TitleAndSubCell'
import TokenInputWithSlider from 'components/TokenInputWithSlider'
import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider'
import { ASSETS } from 'constants/assets'
import { BN_ZERO } from 'constants/math'
import useCurrentAccount from 'hooks/useCurrentAccount'
import useHealthComputer from 'hooks/useHealthComputer'
import useToggle from 'hooks/useToggle'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { hardcodedFee } from 'utils/constants'
import { formatPercent, formatValue } from 'utils/formatters'
import { BN } from 'utils/helpers'
import useHealthComputer from 'hooks/useHealthComputer'
import { BN_ZERO } from 'constants/math'
function getDebtAmount(modal: BorrowModal | null) {
return BN((modal?.marketData as BorrowMarketTableData)?.debt ?? 0).toString()

View File

@ -0,0 +1,88 @@
import BigNumber from 'bignumber.js'
import { useState } from 'react'
import Button from 'components/Button'
import { ArrowRight } from 'components/Icons'
import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider'
import { ASSETS } from 'constants/assets'
import { BN_ZERO } from 'constants/math'
import useToggle from 'hooks/useToggle'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { getAmount } from 'utils/accounts'
import { byDenom } from 'utils/array'
import { hardcodedFee } from 'utils/constants'
interface Props {
account: Account
setChange: (change: AccountChange | undefined) => void
}
export default function FundAccount(props: Props) {
const { account, setChange } = props
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 ?? [])
function onChangeAmount(val: BigNumber) {
setAmount(val)
setChange({
deposits: [
{
amount: depositAmount.toString(),
denom: currentAsset.denom,
},
],
})
}
function resetState() {
setCurrentAsset(defaultAsset)
setAmount(BN_ZERO)
setChange(undefined)
}
async function onConfirm() {
setIsConfirming(true)
const result = await deposit({
fee: hardcodedFee,
accountId: account.id,
coins: [BNCoin.fromDenomAndBigNumber(currentAsset.denom, amount)],
})
setIsConfirming(false)
if (result) {
resetState()
useStore.setState({ fundAndWithdrawModal: null })
}
}
return (
<>
<TokenInputWithSlider
asset={currentAsset}
onChange={onChangeAmount}
onChangeAsset={setCurrentAsset}
amount={amount}
max={max}
className='w-full'
balances={balances}
hasSelect
maxText='Max'
disabled={isConfirming}
/>
<Button
onClick={onConfirm}
showProgressIndicator={isConfirming}
className='w-full'
text={'Fund'}
rightIcon={<ArrowRight />}
/>
</>
)
}

View File

@ -1,17 +1,9 @@
import BigNumber from 'bignumber.js'
import { useState } from 'react'
import AccountSummary from 'components/Account/AccountSummary'
import Button from 'components/Button'
import Card from 'components/Card'
import Divider from 'components/Divider'
import { ArrowRight } from 'components/Icons'
import TokenInputWithSlider from 'components/TokenInputWithSlider'
import useToggle from 'hooks/useToggle'
import useStore from 'store'
import { getAmount } from 'utils/accounts'
import { hardcodedFee } from 'utils/constants'
import { BN_ZERO } from 'constants/math'
import FundAccount from 'components/Modals/FundWithdraw/FundAccount'
import WithdrawFromAccount from 'components/Modals/FundWithdraw/WithdrawFromAccount'
interface Props {
account: Account
@ -19,103 +11,22 @@ interface Props {
}
export default function FundWithdrawModalContent(props: Props) {
const baseCurrency = useStore((s) => s.baseCurrency)
const withdraw = useStore((s) => s.withdraw)
const deposit = useStore((s) => s.deposit)
const balances = useStore((s) => s.balances)
const [isConfirming, setIsConfirming] = useToggle()
const [currentAsset, setCurrentAsset] = useState(baseCurrency)
const [amount, setAmount] = useState(BN_ZERO)
const { account, isFunding } = props
const [change, setChange] = useState<AccountChange | undefined>()
const max = props.isFunding
? getAmount(currentAsset.denom, balances ?? [])
: props.account
? getAmount(currentAsset.denom, props.account.deposits)
: BN_ZERO
function onChangeAmount(val: BigNumber) {
setAmount(val)
setChange({
deposits: [
{
amount: props.isFunding
? BN_ZERO.plus(amount).toString()
: BN_ZERO.minus(amount).toString(),
denom: currentAsset.denom,
},
],
})
}
function resetState() {
setCurrentAsset(baseCurrency)
setAmount(BN_ZERO)
setChange(undefined)
}
async function onConfirm() {
setIsConfirming(true)
let result
if (props.isFunding) {
result = await deposit({
fee: hardcodedFee,
accountId: props.account.id,
coins: [
{
denom: currentAsset.denom,
amount: amount.toString(),
},
],
})
} else {
result = await withdraw({
fee: hardcodedFee,
accountId: props.account.id,
coins: [
{
denom: currentAsset.denom,
amount: amount.toString(),
},
],
})
}
setIsConfirming(false)
if (result) {
resetState()
useStore.setState({ fundAndWithdrawModal: null })
}
}
return (
<div className='flex flex-1 items-start gap-6 p-6'>
<Card
className='flex flex-1 bg-white/5 p-4'
contentClassName='gap-6 flex flex-col justify-between h-full'
contentClassName='gap-6 flex flex-col justify-between h-full min-h-[380px] '
>
<TokenInputWithSlider
asset={currentAsset}
onChange={onChangeAmount}
onChangeAsset={setCurrentAsset}
amount={amount}
max={max}
balances={props.isFunding ? balances : props.account.deposits}
accountId={!props.isFunding ? props.account.id : undefined}
hasSelect
maxText='Max'
disabled={isConfirming}
/>
<Divider />
<Button
onClick={onConfirm}
showProgressIndicator={isConfirming}
className='w-full'
text={props.isFunding ? 'Fund' : 'Withdraw'}
rightIcon={<ArrowRight />}
/>
{isFunding ? (
<FundAccount account={account} setChange={setChange} />
) : (
<WithdrawFromAccount account={account} setChange={setChange} />
)}
</Card>
<AccountSummary account={props.account} change={change} />
<AccountSummary account={account} change={change} />
</div>
)
}

View File

@ -0,0 +1,129 @@
import BigNumber from 'bignumber.js'
import { useEffect, useState } from 'react'
import Button from 'components/Button'
import Divider from 'components/Divider'
import { ArrowRight } from 'components/Icons'
import Switch from 'components/Switch'
import Text from 'components/Text'
import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider'
import { ASSETS } from 'constants/assets'
import { BN_ZERO } from 'constants/math'
import useHealthComputer from 'hooks/useHealthComputer'
import useToggle from 'hooks/useToggle'
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { byDenom } from 'utils/array'
import { hardcodedFee } from 'utils/constants'
interface Props {
account: Account
setChange: (change: AccountChange | undefined) => void
}
export default function WithdrawFromAccount(props: Props) {
const { account, setChange } = props
const defaultAsset = ASSETS.find(byDenom(account.deposits[0].denom)) ?? ASSETS[0]
const withdraw = useStore((s) => s.withdraw)
const [withdrawWithBorrowing, setWithdrawWithBorrowing] = useToggle()
const [isConfirming, setIsConfirming] = useToggle()
const [currentAsset, setCurrentAsset] = useState(defaultAsset)
const [amount, setAmount] = useState(BN_ZERO)
const { updatedAccount, removeDepositByDenom } = useUpdatedAccount(account)
const { computeMaxWithdrawAmount } = useHealthComputer(account)
const { computeMaxBorrowAmount } = useHealthComputer(updatedAccount)
const maxWithdrawAmount = computeMaxWithdrawAmount(currentAsset.denom)
const maxWithdrawWithBorrowAmount = computeMaxBorrowAmount(currentAsset.denom, 'wallet').plus(
maxWithdrawAmount,
)
const isWithinBalance = amount.isLessThan(maxWithdrawAmount)
const depositAmount = BN_ZERO.minus(isWithinBalance ? amount : maxWithdrawAmount)
const debtAmount = isWithinBalance ? BN_ZERO : amount.minus(maxWithdrawAmount)
const max = withdrawWithBorrowing ? maxWithdrawWithBorrowAmount : maxWithdrawAmount
function onChangeAmount(val: BigNumber) {
setAmount(val)
setChange({
deposits: [
{
amount: depositAmount.toString(),
denom: currentAsset.denom,
},
],
debts: [{ amount: debtAmount.toString(), denom: currentAsset.denom }],
})
}
function resetState() {
setCurrentAsset(defaultAsset)
setAmount(BN_ZERO)
setChange(undefined)
}
async function onConfirm() {
setIsConfirming(true)
const result = await withdraw({
fee: hardcodedFee,
accountId: account.id,
coins: [BNCoin.fromDenomAndBigNumber(currentAsset.denom, amount)],
borrow: debtAmount.isZero()
? []
: [BNCoin.fromDenomAndBigNumber(currentAsset.denom, debtAmount)],
})
setIsConfirming(false)
if (result) {
resetState()
useStore.setState({ fundAndWithdrawModal: null })
}
}
useEffect(() => {
removeDepositByDenom(currentAsset.denom)
}, [currentAsset.denom, removeDepositByDenom])
return (
<>
<div className='flex w-full flex-wrap'>
<TokenInputWithSlider
asset={currentAsset}
onChange={onChangeAmount}
onChangeAsset={setCurrentAsset}
amount={amount}
max={max}
className='w-full'
balances={account.deposits}
accountId={account.id}
hasSelect={account.deposits.length > 1}
maxText='Max'
disabled={isConfirming}
/>
<Divider className='my-6' />
<div className='flex w-full flex-wrap'>
<div className='flex flex-1 flex-wrap'>
<Text className='mb-1 w-full'>Withdraw with borrowing</Text>
<Text size='xs' className='text-white/50'>
Borrow assets from your credit account to withdraw to your wallet
</Text>
</div>
<div className='flex flex-wrap items-center justify-end'>
<Switch
name='borrow-to-wallet'
checked={withdrawWithBorrowing}
onChange={setWithdrawWithBorrowing}
disabled={isConfirming}
/>
</div>
</div>
</div>
<Button
onClick={onConfirm}
showProgressIndicator={isConfirming}
className='w-full'
text={'Withdraw'}
rightIcon={<ArrowRight />}
/>
</>
)
}

View File

@ -157,8 +157,9 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
<div className='flex flex-1 flex-col 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(
(maxAmount) => maxAmount.denom === coin.denom,
)?.amount
if (!asset || !maxAmount)
return <React.Fragment key={`input-${coin.denom}`}></React.Fragment>
return (

View File

@ -1,4 +1,3 @@
import BigNumber from 'bignumber.js'
import { useCallback, useEffect, useState } from 'react'
import Accordion from 'components/Accordion'
@ -7,10 +6,10 @@ import VaultBorrowings from 'components/Modals/Vault/VaultBorrowings'
import VaultBorrowingsSubTitle from 'components/Modals/Vault/VaultBorrowingsSubTitle'
import VaultDeposit from 'components/Modals/Vault/VaultDeposits'
import VaultDepositSubTitle from 'components/Modals/Vault/VaultDepositsSubTitle'
import useIsOpenArray from 'hooks/useIsOpenArray'
import useDepositVault from 'hooks/broadcast/useDepositVault'
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
import { BN_ZERO } from 'constants/math'
import useDepositVault from 'hooks/broadcast/useDepositVault'
import useIsOpenArray from 'hooks/useIsOpenArray'
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
interface Props {
vault: Vault | DepositedVault

View File

@ -75,9 +75,10 @@ export default function Option(props: Props) {
<Text size='sm' className='col-span-2 text-white/50'>
{formatValue(5, { maxDecimals: 2, minDecimals: 0, prefix: 'APY ', suffix: '%' })}
</Text>
<Text size='sm' className='col-span-2 text-right text-white/50'>
<DisplayCurrency coin={new BNCoin({ denom: asset.denom, amount: balance })} />
</Text>
<DisplayCurrency
className='col-span-2 text-right text-sm text-white/50'
coin={new BNCoin({ denom: asset.denom, amount: balance })}
/>
</div>
)
}

View File

@ -150,7 +150,7 @@ export default function Slider(props: Props) {
{(showTooltip || isDragging) && (
<div className='absolute -top-8 left-1/2 -translate-x-1/2 rounded-xs bg-martian-red px-2 py-[2px] text-xs'>
<OverlayMark className='absolute -bottom-2 left-1/2 -z-1 h-2 -translate-x-1/2 text-martian-red' />
{props.value}%
{props.value.toFixed(0)}%
</div>
)}
</div>

View File

@ -1,10 +1,10 @@
import BigNumber from 'bignumber.js'
import { useState } from 'react'
import { useEffect, useState } from 'react'
import Slider from 'components/Slider'
import TokenInput from 'components/TokenInput'
import { BN } from 'utils/helpers'
import { BN_ZERO } from 'constants/math'
import { BN } from 'utils/helpers'
interface Props {
amount: BigNumber
@ -44,6 +44,13 @@ export default function TokenInputWithSlider(props: Props) {
props.onChangeAsset(newAsset)
}
useEffect(() => {
const newAmount = props.amount.isLessThan(props.max) ? props.amount : props.max
const newPercentage = newAmount.dividedBy(props.max).multipliedBy(100).toNumber()
if (!amount.isEqualTo(newAmount)) setAmount(newAmount)
if (percentage !== newPercentage) setPercentage(newPercentage)
}, [props.max, props.amount, amount, percentage])
return (
<div className={props.className}>
<TokenInput

View File

@ -43,66 +43,57 @@ export default function useHealthComputer(account?: Account) {
const vaultPositionValues = useMemo(() => {
if (!account?.vaults) return null
return account.vaults.reduce(
(prev, curr) => {
const baseCoinPrice = prices.find((price) => price.denom === curr.denoms.lp)?.amount || 0
prev[curr.address] = {
base_coin: {
amount: '0', // Not used by healthcomputer
denom: curr.denoms.lp,
value: curr.amounts.unlocking.times(baseCoinPrice).integerValue().toString(),
},
vault_coin: {
amount: '0', // Not used by healthcomputer
denom: curr.denoms.vault,
value: curr.values.primary
.div(baseCurrencyPrice)
.plus(curr.values.secondary.div(baseCurrencyPrice))
.integerValue()
.toString(),
},
}
return prev
},
{} as { [key: string]: VaultPositionValue },
)
return account.vaults.reduce((prev, curr) => {
const baseCoinPrice = prices.find((price) => price.denom === curr.denoms.lp)?.amount || 0
prev[curr.address] = {
base_coin: {
amount: '0', // Not used by healthcomputer
denom: curr.denoms.lp,
value: curr.amounts.unlocking.times(baseCoinPrice).integerValue().toString(),
},
vault_coin: {
amount: '0', // Not used by healthcomputer
denom: curr.denoms.vault,
value: curr.values.primary
.div(baseCurrencyPrice)
.plus(curr.values.secondary.div(baseCurrencyPrice))
.integerValue()
.toString(),
},
}
return prev
}, {} as { [key: string]: VaultPositionValue })
}, [account?.vaults, prices, baseCurrencyPrice])
const priceData = useMemo(() => {
const baseCurrencyPrice =
prices.find((price) => price.denom === baseCurrency.denom)?.amount || 0
return prices.reduce(
(prev, curr) => {
prev[curr.denom] = curr.amount.div(baseCurrencyPrice).decimalPlaces(18).toString()
return prev
},
{} as { [key: string]: string },
)
return prices.reduce((prev, curr) => {
prev[curr.denom] = curr.amount.div(baseCurrencyPrice).decimalPlaces(18).toString()
return prev
}, {} as { [key: string]: string })
}, [prices, baseCurrency.denom])
const denomsData = useMemo(
() =>
assetParams.reduce(
(prev, curr) => {
const params: AssetParamsBaseForAddr = {
...curr,
// The following overrides are required as testnet is 'broken' and new contracts are not updated yet
// These overrides are not used by the healthcomputer internally, so they're not important anyways.
protocol_liquidation_fee: '1',
liquidation_bonus: {
max_lb: '1',
min_lb: '1',
slope: '1',
starting_lb: '1',
},
}
prev[params.denom] = params
assetParams.reduce((prev, curr) => {
const params: AssetParamsBaseForAddr = {
...curr,
// The following overrides are required as testnet is 'broken' and new contracts are not updated yet
// These overrides are not used by the healthcomputer internally, so they're not important anyways.
protocol_liquidation_fee: '1',
liquidation_bonus: {
max_lb: '1',
min_lb: '1',
slope: '1',
starting_lb: '1',
},
}
prev[params.denom] = params
return prev
},
{} as { [key: string]: AssetParamsBaseForAddr },
),
return prev
}, {} as { [key: string]: AssetParamsBaseForAddr }),
[assetParams],
)
@ -112,13 +103,10 @@ export default function useHealthComputer(account?: Account) {
const vaultPositionDenoms = positions.vaults.map((vault) => vault.vault.address)
return vaultConfigs
.filter((config) => vaultPositionDenoms.includes(config.addr))
.reduce(
(prev, curr) => {
prev[curr.addr] = curr
return prev
},
{} as { [key: string]: VaultConfigBaseForString },
)
.reduce((prev, curr) => {
prev[curr.addr] = curr
return prev
}, {} as { [key: string]: VaultConfigBaseForString })
}, [vaultConfigs, positions])
const healthComputer: HealthComputer | null = useMemo(() => {

View File

@ -1,7 +1,7 @@
import { useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { BNCoin } from 'types/classes/BNCoin'
import { addCoins, addValueToVaults, removeCoins } from 'hooks/useUpdatedAccount/functions'
import { BNCoin } from 'types/classes/BNCoin'
import { cloneAccount } from 'utils/accounts'
export interface VaultValue {
@ -17,6 +17,16 @@ export function useUpdatedAccount(account: Account) {
const [removedDebt, removeDebt] = useState<BNCoin[]>([])
const [addedVaultValues, addVaultValues] = useState<VaultValue[]>([])
const removeDepositByDenom = useCallback(
(denom: string) => {
const deposit = account.deposits.find((deposit) => deposit.denom === denom)
if (deposit) {
removeDeposits([...removedDeposits, deposit])
}
},
[account, removedDeposits],
)
useEffect(() => {
async function updateAccount() {
const accountCopy = cloneAccount(account)
@ -35,6 +45,7 @@ export function useUpdatedAccount(account: Account) {
updatedAccount,
addDeposits,
removeDeposits,
removeDepositByDenom,
addDebt,
removeDebt,
addVaultValues,

View File

@ -147,24 +147,26 @@ export default function createBroadcastSlice(
return !!response.result
},
deposit: async (options: { fee: StdFee; accountId: string; coins: Coin[] }) => {
deposit: async (options: { fee: StdFee; accountId: string; coins: BNCoin[] }) => {
const msg: CreditManagerExecuteMsg = {
update_credit_account: {
account_id: options.accountId,
actions: options.coins.map((coin) => ({
deposit: coin,
deposit: coin.toCoin(),
})),
},
}
const funds = options.coins.map((coin) => coin.toCoin())
const response = await get().executeMsg({
messages: [
generateExecutionMessage(get().address, ENV.ADDRESS_CREDIT_MANAGER, msg, options.coins),
],
messages: [generateExecutionMessage(get().address, ENV.ADDRESS_CREDIT_MANAGER, msg, funds)],
fee: options.fee,
})
const depositString = options.coins.map((coin) => formatAmountWithSymbol(coin)).join('and ')
const depositString = options.coins
.map((coin) => formatAmountWithSymbol(coin.toCoin()))
.join('and ')
handleResponseMessages(response, `Deposited ${depositString} to Account ${options.accountId}`)
return !!response.result
},
@ -247,13 +249,23 @@ export default function createBroadcastSlice(
handleResponseMessages(response, `Deposited into vault`)
return !!response.result
},
withdraw: async (options: { fee: StdFee; accountId: string; coins: Coin[] }) => {
withdraw: async (options: {
fee: StdFee
accountId: string
coins: BNCoin[]
borrow: BNCoin[]
}) => {
const withdrawActions = options.coins.map((coin) => ({
withdraw: coin.toCoin(),
}))
const borrowActions = options.borrow.map((coin) => ({
borrow: coin.toCoin(),
}))
const msg: CreditManagerExecuteMsg = {
update_credit_account: {
account_id: options.accountId,
actions: options.coins.map((coin) => ({
withdraw: coin,
})),
actions: [...borrowActions, ...withdrawActions],
},
}
@ -262,7 +274,9 @@ export default function createBroadcastSlice(
fee: options.fee,
})
const withdrawString = options.coins.map((coin) => formatAmountWithSymbol(coin)).join('and ')
const withdrawString = options.coins
.map((coin) => formatAmountWithSymbol(coin.toCoin()))
.join('and ')
handleResponseMessages(
response,
`Withdrew ${withdrawString} from Account ${options.accountId}`,

View File

@ -4,8 +4,7 @@
@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;
@ -14,8 +13,7 @@
@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;
@ -24,8 +22,7 @@
@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;

View File

@ -16,7 +16,7 @@ interface BroadcastSlice {
}) => Promise<boolean>
createAccount: (options: { fee: StdFee }) => Promise<string | null>
deleteAccount: (options: { fee: StdFee; accountId: string; lends: BNCoin[] }) => Promise<boolean>
deposit: (options: { fee: StdFee; accountId: string; coins: Coin[] }) => Promise<boolean>
deposit: (options: { fee: StdFee; accountId: string; coins: BNCoin[] }) => Promise<boolean>
unlock: (options: {
fee: StdFee
accountId: string
@ -33,7 +33,12 @@ interface BroadcastSlice {
accountId: string
actions: Action[]
}) => Promise<boolean>
withdraw: (options: { fee: StdFee; accountId: string; coins: Coin[] }) => Promise<boolean>
withdraw: (options: {
fee: StdFee
accountId: string
coins: BNCoin[]
borrow: BNCoin[]
}) => Promise<boolean>
lend: (options: {
fee: StdFee
accountId: string

View File

@ -13,39 +13,34 @@ export const calculateAccountBalanceValue = (
account: Account | AccountChange,
prices: BNCoin[],
): BigNumber => {
const totalDepositValue = calculateAccountDepositsValue(account, prices)
const totalDebtValue = calculateAccountDebtValue(account, prices)
const depositsValue = calculateAccountValue('deposits', account, prices)
const lendsValue = calculateAccountValue('lends', account, prices)
const debtsValue = calculateAccountValue('debts', account, prices)
return totalDepositValue.minus(totalDebtValue)
return depositsValue.plus(lendsValue).minus(debtsValue)
}
export const calculateAccountDepositsValue = (
export const getAccountPositionValues = (account: Account | AccountChange, prices: BNCoin[]) => {
const deposits = calculateAccountValue('deposits', account, prices)
const lends = calculateAccountValue('lends', account, prices)
const debts = calculateAccountValue('debts', account, prices)
return [deposits, lends, debts]
}
export const calculateAccountValue = (
type: 'deposits' | 'lends' | 'debts',
account: Account | AccountChange,
prices: BNCoin[],
): BigNumber => {
if (!account.deposits) return BN_ZERO
return account.deposits.reduce((acc, deposit) => {
const asset = getAssetByDenom(deposit.denom)
if (!account[type]) return BN_ZERO
return account[type]?.reduce((acc, position) => {
const asset = getAssetByDenom(position.denom)
if (!asset) return acc
const price = prices.find((price) => price.denom === deposit.denom)?.amount ?? 0
const amount = BN(deposit.amount).shiftedBy(-asset.decimals)
const depositValue = amount.multipliedBy(price)
return acc.plus(depositValue)
}, BN_ZERO)
}
export const calculateAccountDebtValue = (
account: Account | AccountChange,
prices: BNCoin[],
): BigNumber => {
if (!account.debts) return BN_ZERO
return account.debts.reduce((acc, debt) => {
const asset = getAssetByDenom(debt.denom)
if (!asset) return acc
const price = prices.find((price) => price.denom === debt.denom)?.amount ?? 0
const debtAmount = BN(debt.amount).shiftedBy(-asset.decimals)
const debtValue = debtAmount.multipliedBy(price)
return acc.plus(debtValue)
const price = prices.find((price) => price.denom === position.denom)?.amount ?? 0
const amount = BN(position.amount).shiftedBy(-asset.decimals)
const positionValue = amount.multipliedBy(price)
return acc.plus(positionValue)
}, BN_ZERO)
}
@ -105,7 +100,7 @@ export function convertAccountToPositions(account: Account): Positions {
],
},
},
}) as VaultPosition,
} as VaultPosition),
),
}
}