From 267b968c4a6d974c4a0dae5c1e7bfcc2315f8a6f Mon Sep 17 00:00:00 2001 From: Linkie Link Date: Fri, 4 Aug 2023 15:10:30 +0200 Subject: [PATCH] MP-2894: withdraw from account (#339) --- .../Account/AccountBalancesTable.tsx | 2 +- src/components/Account/AccountComposition.tsx | 67 +++++---- src/components/Account/AccountFund.tsx | 17 +-- src/components/Account/AccountList.tsx | 4 +- src/components/Account/AccountStats.tsx | 4 +- src/components/Account/AccountSummary.tsx | 4 +- .../Modals/AssetAmountSelectActionModal.tsx | 4 +- .../Modals/AssetsSelect/AssetSelectTable.tsx | 15 +- src/components/Modals/BorrowModal.tsx | 8 +- .../Modals/FundWithdraw/FundAccount.tsx | 88 ++++++++++++ .../FundAndWithdrawModalContent.tsx | 109 ++------------- .../FundWithdraw/WithdrawFromAccount.tsx | 129 ++++++++++++++++++ .../Modals/Vault/VaultBorrowings.tsx | 5 +- .../Modals/Vault/VaultModalContent.tsx | 7 +- src/components/Select/Option.tsx | 7 +- src/components/Slider.tsx | 2 +- .../{ => TokenInput}/TokenInputWithSlider.tsx | 11 +- .../{TokenInput.tsx => TokenInput/index.tsx} | 0 src/hooks/useHealthComputer.tsx | 100 ++++++-------- src/hooks/useUpdatedAccount/index.ts | 15 +- src/store/slices/broadcast.ts | 36 +++-- src/styles/globals.css | 9 +- src/types/interfaces/store/broadcast.d.ts | 9 +- src/utils/accounts.ts | 49 +++---- 24 files changed, 429 insertions(+), 272 deletions(-) create mode 100644 src/components/Modals/FundWithdraw/FundAccount.tsx create mode 100644 src/components/Modals/FundWithdraw/WithdrawFromAccount.tsx rename src/components/{ => TokenInput}/TokenInputWithSlider.tsx (81%) rename src/components/{TokenInput.tsx => TokenInput/index.tsx} (100%) diff --git a/src/components/Account/AccountBalancesTable.tsx b/src/components/Account/AccountBalancesTable.tsx index fee8e7fe..82bb465c 100644 --- a/src/components/Account/AccountBalancesTable.tsx +++ b/src/components/Account/AccountBalancesTable.tsx @@ -132,7 +132,7 @@ export default function AccountBalancesTable(props: Props) { return ( diff --git a/src/components/Account/AccountComposition.tsx b/src/components/Account/AccountComposition.tsx index 97e65c8e..9bddd236 100644 --- a/src/components/Account/AccountComposition.tsx +++ b/src/components/Account/AccountComposition.tsx @@ -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 (
@@ -57,10 +70,10 @@ export default function AccountComposition(props: Props) { />
) } 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 (
@@ -94,32 +107,32 @@ function Item(props: ItemProps) {
{props.isPercentage ? ( ) : ( )} - {!props.current.isEqualTo(props.change) && ( + {!current.isEqualTo(change) && ( <> {props.isPercentage ? ( ) : ( )} diff --git a/src/components/Account/AccountFund.tsx b/src/components/Account/AccountFund.tsx index e4077e54..c8bfa6d2 100644 --- a/src/components/Account/AccountFund.tsx +++ b/src/components/Account/AccountFund.tsx @@ -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) - const [fundingAssets, setFundingAssets] = useState([]) + const [fundingAssets, setFundingAssets] = useState([]) 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]) } }, diff --git a/src/components/Account/AccountList.tsx b/src/components/Account/AccountList.tsx index c2a969fe..2f86cf90 100644 --- a/src/components/Account/AccountList.tsx +++ b/src/components/Account/AccountList.tsx @@ -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 (
{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) diff --git a/src/components/Account/AccountStats.tsx b/src/components/Account/AccountStats.tsx index fe444e29..d02d9346 100644 --- a/src/components/Account/AccountStats.tsx +++ b/src/components/Account/AccountStats.tsx @@ -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 ( diff --git a/src/components/Account/AccountSummary.tsx b/src/components/Account/AccountSummary.tsx index 9839a138..7ee8f67a 100644 --- a/src/components/Account/AccountSummary.tsx +++ b/src/components/Account/AccountSummary.tsx @@ -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() diff --git a/src/components/Modals/AssetAmountSelectActionModal.tsx b/src/components/Modals/AssetAmountSelectActionModal.tsx index 06ce9b0d..27217f93 100644 --- a/src/components/Modals/AssetAmountSelectActionModal.tsx +++ b/src/components/Modals/AssetAmountSelectActionModal.tsx @@ -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 diff --git a/src/components/Modals/AssetsSelect/AssetSelectTable.tsx b/src/components/Modals/AssetsSelect/AssetSelectTable.tsx index ac004205..3a76bffa 100644 --- a/src/components/Modals/AssetsSelect/AssetSelectTable.tsx +++ b/src/components/Modals/AssetsSelect/AssetSelectTable.tsx @@ -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([{ id: 'symbol', desc: false }]) const [selected, setSelected] = useState(defaultSelected) diff --git a/src/components/Modals/BorrowModal.tsx b/src/components/Modals/BorrowModal.tsx index 9dcf8cc7..bbf67ab5 100644 --- a/src/components/Modals/BorrowModal.tsx +++ b/src/components/Modals/BorrowModal.tsx @@ -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() diff --git a/src/components/Modals/FundWithdraw/FundAccount.tsx b/src/components/Modals/FundWithdraw/FundAccount.tsx new file mode 100644 index 00000000..b8d3867c --- /dev/null +++ b/src/components/Modals/FundWithdraw/FundAccount.tsx @@ -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 ( + <> + +
) } diff --git a/src/components/Modals/FundWithdraw/WithdrawFromAccount.tsx b/src/components/Modals/FundWithdraw/WithdrawFromAccount.tsx new file mode 100644 index 00000000..42eaa920 --- /dev/null +++ b/src/components/Modals/FundWithdraw/WithdrawFromAccount.tsx @@ -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 ( + <> +
+ 1} + maxText='Max' + disabled={isConfirming} + /> + +
+
+ Withdraw with borrowing + + Borrow assets from your credit account to withdraw to your wallet + +
+
+ +
+
+
+
diff --git a/src/components/TokenInputWithSlider.tsx b/src/components/TokenInput/TokenInputWithSlider.tsx similarity index 81% rename from src/components/TokenInputWithSlider.tsx rename to src/components/TokenInput/TokenInputWithSlider.tsx index a713d4ec..16d6820d 100644 --- a/src/components/TokenInputWithSlider.tsx +++ b/src/components/TokenInput/TokenInputWithSlider.tsx @@ -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 (
{ 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(() => { diff --git a/src/hooks/useUpdatedAccount/index.ts b/src/hooks/useUpdatedAccount/index.ts index fa50d7b5..34be021d 100644 --- a/src/hooks/useUpdatedAccount/index.ts +++ b/src/hooks/useUpdatedAccount/index.ts @@ -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([]) const [addedVaultValues, addVaultValues] = useState([]) + 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, diff --git a/src/store/slices/broadcast.ts b/src/store/slices/broadcast.ts index 34102f42..619b9cf8 100644 --- a/src/store/slices/broadcast.ts +++ b/src/store/slices/broadcast.ts @@ -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}`, diff --git a/src/styles/globals.css b/src/styles/globals.css index 3e611736..a6115bb5 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -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; diff --git a/src/types/interfaces/store/broadcast.d.ts b/src/types/interfaces/store/broadcast.d.ts index fc719240..e952ec31 100644 --- a/src/types/interfaces/store/broadcast.d.ts +++ b/src/types/interfaces/store/broadcast.d.ts @@ -16,7 +16,7 @@ interface BroadcastSlice { }) => Promise createAccount: (options: { fee: StdFee }) => Promise deleteAccount: (options: { fee: StdFee; accountId: string; lends: BNCoin[] }) => Promise - deposit: (options: { fee: StdFee; accountId: string; coins: Coin[] }) => Promise + deposit: (options: { fee: StdFee; accountId: string; coins: BNCoin[] }) => Promise unlock: (options: { fee: StdFee accountId: string @@ -33,7 +33,12 @@ interface BroadcastSlice { accountId: string actions: Action[] }) => Promise - withdraw: (options: { fee: StdFee; accountId: string; coins: Coin[] }) => Promise + withdraw: (options: { + fee: StdFee + accountId: string + coins: BNCoin[] + borrow: BNCoin[] + }) => Promise lend: (options: { fee: StdFee accountId: string diff --git a/src/utils/accounts.ts b/src/utils/accounts.ts index 47e5df0f..a0fe4b18 100644 --- a/src/utils/accounts.ts +++ b/src/utils/accounts.ts @@ -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), ), } }