diff --git a/__tests__/components/Modals/vault/VaultBorrowings.test.tsx b/__tests__/components/Modals/vault/VaultBorrowings.test.tsx index 0c597050..2189ebc9 100644 --- a/__tests__/components/Modals/vault/VaultBorrowings.test.tsx +++ b/__tests__/components/Modals/vault/VaultBorrowings.test.tsx @@ -1,12 +1,12 @@ import { render } from '@testing-library/react' -import { ASSETS } from 'constants/assets' -import { BN } from 'utils/helpers' -import useStore from 'store' import DisplayCurrency from 'components/DisplayCurrency' import VaultBorrowings, { VaultBorrowingsProps } from 'components/Modals/Vault/VaultBorrowings' +import { ASSETS } from 'constants/assets' import { TESTNET_VAULTS_META_DATA } from 'constants/vaults' +import useStore from 'store' import { BNCoin } from 'types/classes/BNCoin' +import { BN } from 'utils/helpers' jest.mock('hooks/usePrices', () => jest.fn(() => ({ @@ -58,6 +58,7 @@ describe('', () => { deposits: [], onChangeBorrowings: jest.fn(), depositActions: [], + depositCapReachedCoins: [], } beforeAll(() => { diff --git a/src/api/incentives/calculateAssetIncentivesApy.ts b/src/api/incentives/calculateAssetIncentivesApy.ts index bee28a91..5babe85c 100644 --- a/src/api/incentives/calculateAssetIncentivesApy.ts +++ b/src/api/incentives/calculateAssetIncentivesApy.ts @@ -33,9 +33,7 @@ export default async function calculateAssetIncentivesApy( const annualEmission = totalActiveEmissionValue.multipliedBy(SECONDS_IN_A_YEAR) const totalAnnualReturnsValue = annualEmission.plus(marketReturns) - const incentivesApy = totalAnnualReturnsValue.dividedBy(marketLiquidityValue).multipliedBy(100) - - return incentivesApy + return totalAnnualReturnsValue.dividedBy(marketLiquidityValue).multipliedBy(100) } catch (ex) { console.error(ex) return null diff --git a/src/api/markets/getMarket.ts b/src/api/markets/getMarket.ts index 623c3525..c0e76da7 100644 --- a/src/api/markets/getMarket.ts +++ b/src/api/markets/getMarket.ts @@ -3,15 +3,16 @@ import { getParamsQueryClient, getRedBankQueryClient } from 'api/cosmwasm-client export default async function getMarket(denom: string): Promise { try { - const redbankClient = await getRedBankQueryClient() + const redBankClient = await getRedBankQueryClient() const paramsClient = await getParamsQueryClient() - const [market, assetParams] = await Promise.all([ - redbankClient.market({ denom }), + const [market, assetParams, assetCap] = await Promise.all([ + redBankClient.market({ denom }), paramsClient.assetParams({ denom }), + paramsClient.totalDeposit({ denom }), ]) - return resolveMarketResponse(market, assetParams) + return resolveMarketResponse(market, assetParams, assetCap) } catch (ex) { throw ex } diff --git a/src/api/markets/getMarketUnderlyingLiquidityAmount.ts b/src/api/markets/getMarketUnderlyingLiquidityAmount.ts index d5b3efb7..0ec4fb42 100644 --- a/src/api/markets/getMarketUnderlyingLiquidityAmount.ts +++ b/src/api/markets/getMarketUnderlyingLiquidityAmount.ts @@ -3,12 +3,10 @@ import { getRedBankQueryClient } from 'api/cosmwasm-client' export default async function getUnderlyingLiquidityAmount(market: Market): Promise { try { const client = await getRedBankQueryClient() - const marketLiquidityAmount: string = await client.underlyingLiquidityAmount({ + return await client.underlyingLiquidityAmount({ denom: market.denom, amountScaled: market.collateralTotalScaled, }) - - return marketLiquidityAmount } catch (ex) { throw ex } diff --git a/src/api/markets/getMarkets.ts b/src/api/markets/getMarkets.ts index aa448ba7..c24256e1 100644 --- a/src/api/markets/getMarkets.ts +++ b/src/api/markets/getMarkets.ts @@ -4,23 +4,31 @@ import iterateContractQuery from 'utils/iterateContractQuery' import { byDenom } from 'utils/array' import { resolveMarketResponse } from 'utils/resolvers' import { Market as RedBankMarket } from 'types/generated/mars-red-bank/MarsRedBank.types' -import { AssetParamsBaseForAddr as AssetParams } from 'types/generated/mars-params/MarsParams.types' +import { + AssetParamsBaseForAddr as AssetParams, + TotalDepositResponse, +} from 'types/generated/mars-params/MarsParams.types' export default async function getMarkets(): Promise { try { - const redbankClient = await getRedBankQueryClient() + const redBankClient = await getRedBankQueryClient() const paramsClient = await getParamsQueryClient() const enabledAssets = getEnabledMarketAssets() - const [markets, assetParams] = await Promise.all([ - iterateContractQuery(redbankClient.markets), + const capQueries = enabledAssets.map((asset) => + paramsClient.totalDeposit({ denom: asset.denom }), + ) + const [markets, assetParams, assetCaps] = await Promise.all([ + iterateContractQuery(redBankClient.markets), iterateContractQuery(paramsClient.allAssetParams), + Promise.all(capQueries), ]) return enabledAssets.map((asset) => resolveMarketResponse( markets.find(byDenom(asset.denom)) as RedBankMarket, assetParams.find(byDenom(asset.denom)) as AssetParams, + assetCaps.find(byDenom(asset.denom)) as TotalDepositResponse, ), ) } catch (ex) { diff --git a/src/components/Account/AccountFund.tsx b/src/components/Account/AccountFund.tsx index 07745ff5..71d4f515 100644 --- a/src/components/Account/AccountFund.tsx +++ b/src/components/Account/AccountFund.tsx @@ -1,8 +1,9 @@ -import { useCallback, useEffect, useMemo, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' import { useParams } from 'react-router-dom' import Button from 'components/Button' import Card from 'components/Card' +import DepositCapMessage from 'components/DepositCapMessage' import FullOverlayContent from 'components/FullOverlayContent' import { Plus } from 'components/Icons' import SwitchAutoLend from 'components/Switch/SwitchAutoLend' @@ -12,6 +13,7 @@ import WalletBridges from 'components/Wallet/WalletBridges' import { BN_ZERO } from 'constants/math' import useAccounts from 'hooks/useAccounts' import useCurrentAccount from 'hooks/useCurrentAccount' +import useMarketAssets from 'hooks/useMarketAssets' import useToggle from 'hooks/useToggle' import useWalletBalances from 'hooks/useWalletBalances' import useStore from 'store' @@ -19,6 +21,7 @@ import { BNCoin } from 'types/classes/BNCoin' import { byDenom } from 'utils/array' import { getAssetByDenom, getBaseAsset } from 'utils/assets' import { defaultFee } from 'utils/constants' +import { getCapLeftWithBuffer } from 'utils/generic' import { BN } from 'utils/helpers' export default function AccountFund() { @@ -36,6 +39,7 @@ export default function AccountFund() { const hasAssetSelected = fundingAssets.length > 0 const hasFundingAssets = fundingAssets.length > 0 && fundingAssets.every((a) => a.toCoin().amount !== '0') + const { data: marketAssets } = useMarketAssets() const baseBalance = useMemo( () => walletBalances.find(byDenom(baseAsset.denom))?.amount ?? '0', @@ -84,16 +88,15 @@ export default function AccountFund() { 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]) - } - }, - [fundingAssets], - ) + const updateFundingAssets = useCallback((amount: BigNumber, denom: string) => { + setFundingAssets((fundingAssets) => { + const updateIdx = fundingAssets.findIndex(byDenom(denom)) + if (updateIdx === -1) return fundingAssets + + fundingAssets[updateIdx].amount = amount + return [...fundingAssets] + }) + }, []) useEffect(() => { if (BN(baseBalance).isLessThan(defaultFee.amount[0].amount)) { @@ -106,6 +109,21 @@ export default function AccountFund() { setSelectedAccountId(currentAccount?.id ?? accountId) }, [accounts, selectedAccountId, accountId, currentAccount]) + const depositCapReachedCoins = useMemo(() => { + const depositCapReachedCoins: BNCoin[] = [] + fundingAssets.forEach((asset) => { + const marketAsset = marketAssets.find(byDenom(asset.denom)) + if (!marketAsset) return + + const capLeft = getCapLeftWithBuffer(marketAsset.cap) + + if (asset.amount.isLessThanOrEqualTo(capLeft)) return + + depositCapReachedCoins.push(BNCoin.fromDenomAndBigNumber(asset.denom, capLeft)) + }) + return depositCapReachedCoins + }, [fundingAssets, marketAssets]) + if (!selectedAccountId) return null return ( @@ -147,6 +165,12 @@ export default function AccountFund() { onClick={handleSelectAssetsClick} disabled={isFunding} /> + 0} showProgressIndicator={isFunding} onClick={handleClick} size='lg' diff --git a/src/components/DepositCapMessage.tsx b/src/components/DepositCapMessage.tsx new file mode 100644 index 00000000..4ae8cebd --- /dev/null +++ b/src/components/DepositCapMessage.tsx @@ -0,0 +1,50 @@ +import classNames from 'classnames' +import { HTMLAttributes } from 'react' + +import { FormattedNumber } from 'components/FormattedNumber' +import { InfoCircle } from 'components/Icons' +import Text from 'components/Text' +import { BNCoin } from 'types/classes/BNCoin' +import { getAssetByDenom } from 'utils/assets' + +interface Props extends HTMLAttributes { + action: 'buy' | 'deposit' | 'fund' + coins: BNCoin[] + showIcon?: boolean +} + +export default function DepositCapMessage(props: Props) { + if (!props.coins.length) return null + + return ( +
+ {props.showIcon && } +
+ Deposit Cap Reached + {`Unfortunately you're not able to ${ + props.action + } more than the following amount${props.coins.length > 1 ? 's' : ''}:`} + {props.coins.map((coin) => { + const asset = getAssetByDenom(coin.denom) + + if (!asset) return null + + return ( +
+ Cap Left: + +
+ ) + })} +
+
+ ) +} diff --git a/src/components/DisplayCurrency.tsx b/src/components/DisplayCurrency.tsx index 7b21254c..cca97117 100644 --- a/src/components/DisplayCurrency.tsx +++ b/src/components/DisplayCurrency.tsx @@ -1,5 +1,5 @@ -import { useMemo } from 'react' import classNames from 'classnames' +import { useMemo } from 'react' import { FormattedNumber } from 'components/FormattedNumber' import { DEFAULT_SETTINGS } from 'constants/defaultSettings' @@ -14,7 +14,7 @@ interface Props { coin: BNCoin className?: string isApproximation?: boolean - parantheses?: boolean + parentheses?: boolean } export default function DisplayCurrency(props: Props) { @@ -43,7 +43,7 @@ export default function DisplayCurrency(props: Props) { - +
{status && } {status === VaultStatus.ACTIVE && } diff --git a/src/components/Earn/Farm/VaultTable.tsx b/src/components/Earn/Farm/VaultTable.tsx index 08c98bea..5c8867e6 100644 --- a/src/components/Earn/Farm/VaultTable.tsx +++ b/src/components/Earn/Farm/VaultTable.tsx @@ -182,6 +182,7 @@ export const VaultTable = (props: Props) => { accessorKey: 'ltv.max', header: 'Max LTV', cell: ({ row }) => { + if (props.isLoading) return return {formatPercent(row.original.ltv.max)} }, }, @@ -189,6 +190,7 @@ export const VaultTable = (props: Props) => { accessorKey: 'ltv.liq', header: 'Liq. LTV', cell: ({ row }) => { + if (props.isLoading) return return {formatPercent(row.original.ltv.liq)} }, }, diff --git a/src/components/Header/DesktopHeader.tsx b/src/components/Header/DesktopHeader.tsx index 0c329c15..bb67c799 100644 --- a/src/components/Header/DesktopHeader.tsx +++ b/src/components/Header/DesktopHeader.tsx @@ -9,11 +9,11 @@ import Settings from 'components/Settings' import Wallet from 'components/Wallet' import useStore from 'store' -export const menuTree: { page: Page; label: string }[] = [ - { page: 'trade', label: 'Trade' }, - { page: 'lend', label: 'Earn' }, - { page: 'borrow', label: 'Borrow' }, - { page: 'portfolio', label: 'Portfolio' }, +export const menuTree: { pages: Page[]; label: string }[] = [ + { pages: ['trade'], label: 'Trade' }, + { pages: ['lend', 'farm'], label: 'Earn' }, + { pages: ['borrow'], label: 'Borrow' }, + { pages: ['portfolio'], label: 'Portfolio' }, ] export default function DesktopHeader() { diff --git a/src/components/Modals/BorrowModal.tsx b/src/components/Modals/BorrowModal.tsx index 141abc58..95807e92 100644 --- a/src/components/Modals/BorrowModal.tsx +++ b/src/components/Modals/BorrowModal.tsx @@ -207,7 +207,7 @@ function BorrowModal(props: Props) { asset.denom, modal.marketData?.liquidity?.amount ?? BN_ZERO, )} - parantheses + parentheses />
diff --git a/src/components/Modals/FundWithdraw/FundAccount.tsx b/src/components/Modals/FundWithdraw/FundAccount.tsx index c937eeee..4d34c148 100644 --- a/src/components/Modals/FundWithdraw/FundAccount.tsx +++ b/src/components/Modals/FundWithdraw/FundAccount.tsx @@ -1,6 +1,7 @@ -import { useCallback, useEffect, useMemo, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' import Button from 'components/Button' +import DepositCapMessage from 'components/DepositCapMessage' import { ArrowRight, Plus } from 'components/Icons' import SwitchAutoLend from 'components/Switch/SwitchAutoLend' import Text from 'components/Text' @@ -8,6 +9,7 @@ import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider' import WalletBridges from 'components/Wallet/WalletBridges' import { BN_ZERO } from 'constants/math' import useAutoLendEnabledAccountIds from 'hooks/useAutoLendEnabledAccountIds' +import useMarketAssets from 'hooks/useMarketAssets' import useToggle from 'hooks/useToggle' import { useUpdatedAccount } from 'hooks/useUpdatedAccount' import useWalletBalances from 'hooks/useWalletBalances' @@ -16,6 +18,7 @@ import { BNCoin } from 'types/classes/BNCoin' import { byDenom } from 'utils/array' import { getAssetByDenom, getBaseAsset } from 'utils/assets' import { defaultFee } from 'utils/constants' +import { getCapLeftWithBuffer } from 'utils/generic' import { BN } from 'utils/helpers' interface Props { @@ -38,7 +41,7 @@ export default function FundAccount(props: Props) { const { autoLendEnabledAccountIds } = useAutoLendEnabledAccountIds() const isAutoLendEnabled = autoLendEnabledAccountIds.includes(accountId) const { simulateDeposits } = useUpdatedAccount(account) - + const { data: marketAssets } = useMarketAssets() const baseBalance = useMemo( () => walletBalances.find(byDenom(baseAsset.denom))?.amount ?? '0', [walletBalances, baseAsset], @@ -87,16 +90,15 @@ export default function FundAccount(props: Props) { 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]) - } - }, - [fundingAssets], - ) + const updateFundingAssets = useCallback((amount: BigNumber, denom: string) => { + setFundingAssets((fundingAssets) => { + const updateIdx = fundingAssets.findIndex(byDenom(denom)) + if (updateIdx === -1) return fundingAssets + + fundingAssets[updateIdx].amount = amount + return [...fundingAssets] + }) + }, []) useEffect(() => { simulateDeposits(isAutoLendEnabled ? 'lend' : 'deposit', fundingAssets) @@ -108,6 +110,21 @@ export default function FundAccount(props: Props) { } }, [baseBalance]) + const depositCapReachedCoins = useMemo(() => { + const depositCapReachedCoins: BNCoin[] = [] + fundingAssets.forEach((asset) => { + const marketAsset = marketAssets.find(byDenom(asset.denom)) + if (!marketAsset) return + + const capLeft = getCapLeftWithBuffer(marketAsset.cap) + + if (asset.amount.isLessThanOrEqualTo(capLeft)) return + + depositCapReachedCoins.push(BNCoin.fromDenomAndBigNumber(asset.denom, capLeft)) + }) + return depositCapReachedCoins + }, [fundingAssets, marketAssets]) + return ( <>
@@ -139,6 +156,12 @@ export default function FundAccount(props: Props) { onClick={handleSelectAssetsClick} disabled={isFunding} /> + } - disabled={!hasFundingAssets} + disabled={!hasFundingAssets || depositCapReachedCoins.length > 0} showProgressIndicator={isFunding} onClick={handleClick} /> diff --git a/src/components/Modals/Vault/VaultBorrowings.tsx b/src/components/Modals/Vault/VaultBorrowings.tsx index a3b269b5..9215e0fb 100644 --- a/src/components/Modals/Vault/VaultBorrowings.tsx +++ b/src/components/Modals/Vault/VaultBorrowings.tsx @@ -2,6 +2,7 @@ import BigNumber from 'bignumber.js' import React, { useEffect, useMemo, useState } from 'react' import Button from 'components/Button' +import DepositCapMessage from 'components/DepositCapMessage' import DisplayCurrency from 'components/DisplayCurrency' import Divider from 'components/Divider' import { ArrowRight, ExclamationMarkCircled } from 'components/Icons' @@ -27,6 +28,7 @@ export interface VaultBorrowingsProps { vault: Vault depositActions: Action[] onChangeBorrowings: (borrowings: BNCoin[]) => void + depositCapReachedCoins: BNCoin[] } export default function VaultBorrowings(props: VaultBorrowingsProps) { @@ -187,12 +189,21 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
)} +