Mp 3270 deposit cap messages (#416)
* Add Deposit cap for AssetSelector * Add DepositCap component for Trade * DepositCap to Vault and Fund * DepositCap to Vault and Fund * Small bugixes * formatting fixes * formatting fixes * formatting fixes * formatting fixes * run format
This commit is contained in:
parent
28ccf8ba84
commit
d87fbd2a15
@ -1,12 +1,12 @@
|
|||||||
import { render } from '@testing-library/react'
|
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 DisplayCurrency from 'components/DisplayCurrency'
|
||||||
import VaultBorrowings, { VaultBorrowingsProps } from 'components/Modals/Vault/VaultBorrowings'
|
import VaultBorrowings, { VaultBorrowingsProps } from 'components/Modals/Vault/VaultBorrowings'
|
||||||
|
import { ASSETS } from 'constants/assets'
|
||||||
import { TESTNET_VAULTS_META_DATA } from 'constants/vaults'
|
import { TESTNET_VAULTS_META_DATA } from 'constants/vaults'
|
||||||
|
import useStore from 'store'
|
||||||
import { BNCoin } from 'types/classes/BNCoin'
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
|
import { BN } from 'utils/helpers'
|
||||||
|
|
||||||
jest.mock('hooks/usePrices', () =>
|
jest.mock('hooks/usePrices', () =>
|
||||||
jest.fn(() => ({
|
jest.fn(() => ({
|
||||||
@ -58,6 +58,7 @@ describe('<VaultBorrowings />', () => {
|
|||||||
deposits: [],
|
deposits: [],
|
||||||
onChangeBorrowings: jest.fn(),
|
onChangeBorrowings: jest.fn(),
|
||||||
depositActions: [],
|
depositActions: [],
|
||||||
|
depositCapReachedCoins: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
|
@ -33,9 +33,7 @@ export default async function calculateAssetIncentivesApy(
|
|||||||
const annualEmission = totalActiveEmissionValue.multipliedBy(SECONDS_IN_A_YEAR)
|
const annualEmission = totalActiveEmissionValue.multipliedBy(SECONDS_IN_A_YEAR)
|
||||||
|
|
||||||
const totalAnnualReturnsValue = annualEmission.plus(marketReturns)
|
const totalAnnualReturnsValue = annualEmission.plus(marketReturns)
|
||||||
const incentivesApy = totalAnnualReturnsValue.dividedBy(marketLiquidityValue).multipliedBy(100)
|
return totalAnnualReturnsValue.dividedBy(marketLiquidityValue).multipliedBy(100)
|
||||||
|
|
||||||
return incentivesApy
|
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
console.error(ex)
|
console.error(ex)
|
||||||
return null
|
return null
|
||||||
|
@ -3,15 +3,16 @@ import { getParamsQueryClient, getRedBankQueryClient } from 'api/cosmwasm-client
|
|||||||
|
|
||||||
export default async function getMarket(denom: string): Promise<Market> {
|
export default async function getMarket(denom: string): Promise<Market> {
|
||||||
try {
|
try {
|
||||||
const redbankClient = await getRedBankQueryClient()
|
const redBankClient = await getRedBankQueryClient()
|
||||||
const paramsClient = await getParamsQueryClient()
|
const paramsClient = await getParamsQueryClient()
|
||||||
|
|
||||||
const [market, assetParams] = await Promise.all([
|
const [market, assetParams, assetCap] = await Promise.all([
|
||||||
redbankClient.market({ denom }),
|
redBankClient.market({ denom }),
|
||||||
paramsClient.assetParams({ denom }),
|
paramsClient.assetParams({ denom }),
|
||||||
|
paramsClient.totalDeposit({ denom }),
|
||||||
])
|
])
|
||||||
|
|
||||||
return resolveMarketResponse(market, assetParams)
|
return resolveMarketResponse(market, assetParams, assetCap)
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
throw ex
|
throw ex
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,10 @@ import { getRedBankQueryClient } from 'api/cosmwasm-client'
|
|||||||
export default async function getUnderlyingLiquidityAmount(market: Market): Promise<string> {
|
export default async function getUnderlyingLiquidityAmount(market: Market): Promise<string> {
|
||||||
try {
|
try {
|
||||||
const client = await getRedBankQueryClient()
|
const client = await getRedBankQueryClient()
|
||||||
const marketLiquidityAmount: string = await client.underlyingLiquidityAmount({
|
return await client.underlyingLiquidityAmount({
|
||||||
denom: market.denom,
|
denom: market.denom,
|
||||||
amountScaled: market.collateralTotalScaled,
|
amountScaled: market.collateralTotalScaled,
|
||||||
})
|
})
|
||||||
|
|
||||||
return marketLiquidityAmount
|
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
throw ex
|
throw ex
|
||||||
}
|
}
|
||||||
|
@ -4,23 +4,31 @@ import iterateContractQuery from 'utils/iterateContractQuery'
|
|||||||
import { byDenom } from 'utils/array'
|
import { byDenom } from 'utils/array'
|
||||||
import { resolveMarketResponse } from 'utils/resolvers'
|
import { resolveMarketResponse } from 'utils/resolvers'
|
||||||
import { Market as RedBankMarket } from 'types/generated/mars-red-bank/MarsRedBank.types'
|
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<Market[]> {
|
export default async function getMarkets(): Promise<Market[]> {
|
||||||
try {
|
try {
|
||||||
const redbankClient = await getRedBankQueryClient()
|
const redBankClient = await getRedBankQueryClient()
|
||||||
const paramsClient = await getParamsQueryClient()
|
const paramsClient = await getParamsQueryClient()
|
||||||
|
|
||||||
const enabledAssets = getEnabledMarketAssets()
|
const enabledAssets = getEnabledMarketAssets()
|
||||||
const [markets, assetParams] = await Promise.all([
|
const capQueries = enabledAssets.map((asset) =>
|
||||||
iterateContractQuery(redbankClient.markets),
|
paramsClient.totalDeposit({ denom: asset.denom }),
|
||||||
|
)
|
||||||
|
const [markets, assetParams, assetCaps] = await Promise.all([
|
||||||
|
iterateContractQuery(redBankClient.markets),
|
||||||
iterateContractQuery(paramsClient.allAssetParams),
|
iterateContractQuery(paramsClient.allAssetParams),
|
||||||
|
Promise.all(capQueries),
|
||||||
])
|
])
|
||||||
|
|
||||||
return enabledAssets.map((asset) =>
|
return enabledAssets.map((asset) =>
|
||||||
resolveMarketResponse(
|
resolveMarketResponse(
|
||||||
markets.find(byDenom(asset.denom)) as RedBankMarket,
|
markets.find(byDenom(asset.denom)) as RedBankMarket,
|
||||||
assetParams.find(byDenom(asset.denom)) as AssetParams,
|
assetParams.find(byDenom(asset.denom)) as AssetParams,
|
||||||
|
assetCaps.find(byDenom(asset.denom)) as TotalDepositResponse,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
|
@ -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 { useParams } from 'react-router-dom'
|
||||||
|
|
||||||
import Button from 'components/Button'
|
import Button from 'components/Button'
|
||||||
import Card from 'components/Card'
|
import Card from 'components/Card'
|
||||||
|
import DepositCapMessage from 'components/DepositCapMessage'
|
||||||
import FullOverlayContent from 'components/FullOverlayContent'
|
import FullOverlayContent from 'components/FullOverlayContent'
|
||||||
import { Plus } from 'components/Icons'
|
import { Plus } from 'components/Icons'
|
||||||
import SwitchAutoLend from 'components/Switch/SwitchAutoLend'
|
import SwitchAutoLend from 'components/Switch/SwitchAutoLend'
|
||||||
@ -12,6 +13,7 @@ import WalletBridges from 'components/Wallet/WalletBridges'
|
|||||||
import { BN_ZERO } from 'constants/math'
|
import { BN_ZERO } from 'constants/math'
|
||||||
import useAccounts from 'hooks/useAccounts'
|
import useAccounts from 'hooks/useAccounts'
|
||||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||||
|
import useMarketAssets from 'hooks/useMarketAssets'
|
||||||
import useToggle from 'hooks/useToggle'
|
import useToggle from 'hooks/useToggle'
|
||||||
import useWalletBalances from 'hooks/useWalletBalances'
|
import useWalletBalances from 'hooks/useWalletBalances'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
@ -19,6 +21,7 @@ import { BNCoin } from 'types/classes/BNCoin'
|
|||||||
import { byDenom } from 'utils/array'
|
import { byDenom } from 'utils/array'
|
||||||
import { getAssetByDenom, getBaseAsset } from 'utils/assets'
|
import { getAssetByDenom, getBaseAsset } from 'utils/assets'
|
||||||
import { defaultFee } from 'utils/constants'
|
import { defaultFee } from 'utils/constants'
|
||||||
|
import { getCapLeftWithBuffer } from 'utils/generic'
|
||||||
import { BN } from 'utils/helpers'
|
import { BN } from 'utils/helpers'
|
||||||
|
|
||||||
export default function AccountFund() {
|
export default function AccountFund() {
|
||||||
@ -36,6 +39,7 @@ export default function AccountFund() {
|
|||||||
const hasAssetSelected = fundingAssets.length > 0
|
const hasAssetSelected = fundingAssets.length > 0
|
||||||
const hasFundingAssets =
|
const hasFundingAssets =
|
||||||
fundingAssets.length > 0 && fundingAssets.every((a) => a.toCoin().amount !== '0')
|
fundingAssets.length > 0 && fundingAssets.every((a) => a.toCoin().amount !== '0')
|
||||||
|
const { data: marketAssets } = useMarketAssets()
|
||||||
|
|
||||||
const baseBalance = useMemo(
|
const baseBalance = useMemo(
|
||||||
() => walletBalances.find(byDenom(baseAsset.denom))?.amount ?? '0',
|
() => walletBalances.find(byDenom(baseAsset.denom))?.amount ?? '0',
|
||||||
@ -84,16 +88,15 @@ export default function AccountFund() {
|
|||||||
setFundingAssets(newFundingAssets)
|
setFundingAssets(newFundingAssets)
|
||||||
}, [selectedDenoms, fundingAssets])
|
}, [selectedDenoms, fundingAssets])
|
||||||
|
|
||||||
const updateFundingAssets = useCallback(
|
const updateFundingAssets = useCallback((amount: BigNumber, denom: string) => {
|
||||||
(amount: BigNumber, denom: string) => {
|
setFundingAssets((fundingAssets) => {
|
||||||
const assetToUpdate = fundingAssets.find(byDenom(denom))
|
const updateIdx = fundingAssets.findIndex(byDenom(denom))
|
||||||
if (assetToUpdate) {
|
if (updateIdx === -1) return fundingAssets
|
||||||
assetToUpdate.amount = amount
|
|
||||||
setFundingAssets([...fundingAssets.filter((a) => a.denom !== denom), assetToUpdate])
|
fundingAssets[updateIdx].amount = amount
|
||||||
}
|
return [...fundingAssets]
|
||||||
},
|
})
|
||||||
[fundingAssets],
|
}, [])
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (BN(baseBalance).isLessThan(defaultFee.amount[0].amount)) {
|
if (BN(baseBalance).isLessThan(defaultFee.amount[0].amount)) {
|
||||||
@ -106,6 +109,21 @@ export default function AccountFund() {
|
|||||||
setSelectedAccountId(currentAccount?.id ?? accountId)
|
setSelectedAccountId(currentAccount?.id ?? accountId)
|
||||||
}, [accounts, selectedAccountId, accountId, currentAccount])
|
}, [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
|
if (!selectedAccountId) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -147,6 +165,12 @@ export default function AccountFund() {
|
|||||||
onClick={handleSelectAssetsClick}
|
onClick={handleSelectAssetsClick}
|
||||||
disabled={isFunding}
|
disabled={isFunding}
|
||||||
/>
|
/>
|
||||||
|
<DepositCapMessage
|
||||||
|
action='fund'
|
||||||
|
coins={depositCapReachedCoins}
|
||||||
|
className='pr-4 py-2 mt-4'
|
||||||
|
showIcon
|
||||||
|
/>
|
||||||
<SwitchAutoLend
|
<SwitchAutoLend
|
||||||
className='pt-4 mt-4 border border-transparent border-t-white/10'
|
className='pt-4 mt-4 border border-transparent border-t-white/10'
|
||||||
accountId={selectedAccountId}
|
accountId={selectedAccountId}
|
||||||
@ -155,7 +179,7 @@ export default function AccountFund() {
|
|||||||
className='w-full mt-4'
|
className='w-full mt-4'
|
||||||
text='Fund account'
|
text='Fund account'
|
||||||
color='tertiary'
|
color='tertiary'
|
||||||
disabled={!hasFundingAssets}
|
disabled={!hasFundingAssets || depositCapReachedCoins.length > 0}
|
||||||
showProgressIndicator={isFunding}
|
showProgressIndicator={isFunding}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
size='lg'
|
size='lg'
|
||||||
|
50
src/components/DepositCapMessage.tsx
Normal file
50
src/components/DepositCapMessage.tsx
Normal file
@ -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<HTMLDivElement> {
|
||||||
|
action: 'buy' | 'deposit' | 'fund'
|
||||||
|
coins: BNCoin[]
|
||||||
|
showIcon?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DepositCapMessage(props: Props) {
|
||||||
|
if (!props.coins.length) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames('flex items-start', props.className)}>
|
||||||
|
{props.showIcon && <InfoCircle width={26} className='mr-5' />}
|
||||||
|
<div className='flex-col gap-2 flex'>
|
||||||
|
<Text size='sm'>Deposit Cap Reached</Text>
|
||||||
|
<Text size='xs' className='text-white/40'>{`Unfortunately you're not able to ${
|
||||||
|
props.action
|
||||||
|
} more than the following amount${props.coins.length > 1 ? 's' : ''}:`}</Text>
|
||||||
|
{props.coins.map((coin) => {
|
||||||
|
const asset = getAssetByDenom(coin.denom)
|
||||||
|
|
||||||
|
if (!asset) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={coin.denom} className='flex gap-1'>
|
||||||
|
<Text size='xs'>Cap Left:</Text>
|
||||||
|
<FormattedNumber
|
||||||
|
amount={coin.amount.toNumber()}
|
||||||
|
options={{
|
||||||
|
abbreviated: true,
|
||||||
|
decimals: asset.decimals,
|
||||||
|
suffix: ` ${asset.symbol}`,
|
||||||
|
}}
|
||||||
|
className='text-white/60 text-xs'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { useMemo } from 'react'
|
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
import { FormattedNumber } from 'components/FormattedNumber'
|
import { FormattedNumber } from 'components/FormattedNumber'
|
||||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||||
@ -14,7 +14,7 @@ interface Props {
|
|||||||
coin: BNCoin
|
coin: BNCoin
|
||||||
className?: string
|
className?: string
|
||||||
isApproximation?: boolean
|
isApproximation?: boolean
|
||||||
parantheses?: boolean
|
parentheses?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DisplayCurrency(props: Props) {
|
export default function DisplayCurrency(props: Props) {
|
||||||
@ -43,7 +43,7 @@ export default function DisplayCurrency(props: Props) {
|
|||||||
<FormattedNumber
|
<FormattedNumber
|
||||||
className={classNames(
|
className={classNames(
|
||||||
props.className,
|
props.className,
|
||||||
props.parantheses && 'before:content-["("] after:content-[")"]',
|
props.parentheses && 'before:content-["("] after:content-[")"]',
|
||||||
)}
|
)}
|
||||||
amount={convertToDisplayAmount(props.coin, displayCurrency, prices).toNumber()}
|
amount={convertToDisplayAmount(props.coin, displayCurrency, prices).toNumber()}
|
||||||
options={{
|
options={{
|
||||||
|
@ -107,7 +107,7 @@ export default function VaultExpanded(props: Props) {
|
|||||||
!isExpanded && props.row.toggleExpanded()
|
!isExpanded && props.row.toggleExpanded()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<td colSpan={!status ? 5 : 6}>
|
<td colSpan={!status ? 7 : 8}>
|
||||||
<div className='align-center flex justify-end gap-3 p-4'>
|
<div className='align-center flex justify-end gap-3 p-4'>
|
||||||
{status && <DepositMoreButton />}
|
{status && <DepositMoreButton />}
|
||||||
{status === VaultStatus.ACTIVE && <UnlockButton />}
|
{status === VaultStatus.ACTIVE && <UnlockButton />}
|
||||||
|
@ -182,6 +182,7 @@ export const VaultTable = (props: Props) => {
|
|||||||
accessorKey: 'ltv.max',
|
accessorKey: 'ltv.max',
|
||||||
header: 'Max LTV',
|
header: 'Max LTV',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
if (props.isLoading) return <Loading />
|
||||||
return <Text className='text-xs'>{formatPercent(row.original.ltv.max)}</Text>
|
return <Text className='text-xs'>{formatPercent(row.original.ltv.max)}</Text>
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -189,6 +190,7 @@ export const VaultTable = (props: Props) => {
|
|||||||
accessorKey: 'ltv.liq',
|
accessorKey: 'ltv.liq',
|
||||||
header: 'Liq. LTV',
|
header: 'Liq. LTV',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
if (props.isLoading) return <Loading />
|
||||||
return <Text className='text-xs'>{formatPercent(row.original.ltv.liq)}</Text>
|
return <Text className='text-xs'>{formatPercent(row.original.ltv.liq)}</Text>
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -9,11 +9,11 @@ import Settings from 'components/Settings'
|
|||||||
import Wallet from 'components/Wallet'
|
import Wallet from 'components/Wallet'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
|
|
||||||
export const menuTree: { page: Page; label: string }[] = [
|
export const menuTree: { pages: Page[]; label: string }[] = [
|
||||||
{ page: 'trade', label: 'Trade' },
|
{ pages: ['trade'], label: 'Trade' },
|
||||||
{ page: 'lend', label: 'Earn' },
|
{ pages: ['lend', 'farm'], label: 'Earn' },
|
||||||
{ page: 'borrow', label: 'Borrow' },
|
{ pages: ['borrow'], label: 'Borrow' },
|
||||||
{ page: 'portfolio', label: 'Portfolio' },
|
{ pages: ['portfolio'], label: 'Portfolio' },
|
||||||
]
|
]
|
||||||
|
|
||||||
export default function DesktopHeader() {
|
export default function DesktopHeader() {
|
||||||
|
@ -207,7 +207,7 @@ function BorrowModal(props: Props) {
|
|||||||
asset.denom,
|
asset.denom,
|
||||||
modal.marketData?.liquidity?.amount ?? BN_ZERO,
|
modal.marketData?.liquidity?.amount ?? BN_ZERO,
|
||||||
)}
|
)}
|
||||||
parantheses
|
parentheses
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Text size='xs' className='text-white/50' tag='span'>
|
<Text size='xs' className='text-white/50' tag='span'>
|
||||||
|
@ -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 Button from 'components/Button'
|
||||||
|
import DepositCapMessage from 'components/DepositCapMessage'
|
||||||
import { ArrowRight, Plus } from 'components/Icons'
|
import { ArrowRight, Plus } from 'components/Icons'
|
||||||
import SwitchAutoLend from 'components/Switch/SwitchAutoLend'
|
import SwitchAutoLend from 'components/Switch/SwitchAutoLend'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
@ -8,6 +9,7 @@ import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider'
|
|||||||
import WalletBridges from 'components/Wallet/WalletBridges'
|
import WalletBridges from 'components/Wallet/WalletBridges'
|
||||||
import { BN_ZERO } from 'constants/math'
|
import { BN_ZERO } from 'constants/math'
|
||||||
import useAutoLendEnabledAccountIds from 'hooks/useAutoLendEnabledAccountIds'
|
import useAutoLendEnabledAccountIds from 'hooks/useAutoLendEnabledAccountIds'
|
||||||
|
import useMarketAssets from 'hooks/useMarketAssets'
|
||||||
import useToggle from 'hooks/useToggle'
|
import useToggle from 'hooks/useToggle'
|
||||||
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
|
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
|
||||||
import useWalletBalances from 'hooks/useWalletBalances'
|
import useWalletBalances from 'hooks/useWalletBalances'
|
||||||
@ -16,6 +18,7 @@ import { BNCoin } from 'types/classes/BNCoin'
|
|||||||
import { byDenom } from 'utils/array'
|
import { byDenom } from 'utils/array'
|
||||||
import { getAssetByDenom, getBaseAsset } from 'utils/assets'
|
import { getAssetByDenom, getBaseAsset } from 'utils/assets'
|
||||||
import { defaultFee } from 'utils/constants'
|
import { defaultFee } from 'utils/constants'
|
||||||
|
import { getCapLeftWithBuffer } from 'utils/generic'
|
||||||
import { BN } from 'utils/helpers'
|
import { BN } from 'utils/helpers'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -38,7 +41,7 @@ export default function FundAccount(props: Props) {
|
|||||||
const { autoLendEnabledAccountIds } = useAutoLendEnabledAccountIds()
|
const { autoLendEnabledAccountIds } = useAutoLendEnabledAccountIds()
|
||||||
const isAutoLendEnabled = autoLendEnabledAccountIds.includes(accountId)
|
const isAutoLendEnabled = autoLendEnabledAccountIds.includes(accountId)
|
||||||
const { simulateDeposits } = useUpdatedAccount(account)
|
const { simulateDeposits } = useUpdatedAccount(account)
|
||||||
|
const { data: marketAssets } = useMarketAssets()
|
||||||
const baseBalance = useMemo(
|
const baseBalance = useMemo(
|
||||||
() => walletBalances.find(byDenom(baseAsset.denom))?.amount ?? '0',
|
() => walletBalances.find(byDenom(baseAsset.denom))?.amount ?? '0',
|
||||||
[walletBalances, baseAsset],
|
[walletBalances, baseAsset],
|
||||||
@ -87,16 +90,15 @@ export default function FundAccount(props: Props) {
|
|||||||
setFundingAssets(newFundingAssets)
|
setFundingAssets(newFundingAssets)
|
||||||
}, [selectedDenoms, fundingAssets])
|
}, [selectedDenoms, fundingAssets])
|
||||||
|
|
||||||
const updateFundingAssets = useCallback(
|
const updateFundingAssets = useCallback((amount: BigNumber, denom: string) => {
|
||||||
(amount: BigNumber, denom: string) => {
|
setFundingAssets((fundingAssets) => {
|
||||||
const assetToUpdate = fundingAssets.find(byDenom(denom))
|
const updateIdx = fundingAssets.findIndex(byDenom(denom))
|
||||||
if (assetToUpdate) {
|
if (updateIdx === -1) return fundingAssets
|
||||||
assetToUpdate.amount = amount
|
|
||||||
setFundingAssets([...fundingAssets.filter((a) => a.denom !== denom), assetToUpdate])
|
fundingAssets[updateIdx].amount = amount
|
||||||
}
|
return [...fundingAssets]
|
||||||
},
|
})
|
||||||
[fundingAssets],
|
}, [])
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
simulateDeposits(isAutoLendEnabled ? 'lend' : 'deposit', fundingAssets)
|
simulateDeposits(isAutoLendEnabled ? 'lend' : 'deposit', fundingAssets)
|
||||||
@ -108,6 +110,21 @@ export default function FundAccount(props: Props) {
|
|||||||
}
|
}
|
||||||
}, [baseBalance])
|
}, [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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='flex flex-wrap items-start'>
|
<div className='flex flex-wrap items-start'>
|
||||||
@ -139,6 +156,12 @@ export default function FundAccount(props: Props) {
|
|||||||
onClick={handleSelectAssetsClick}
|
onClick={handleSelectAssetsClick}
|
||||||
disabled={isFunding}
|
disabled={isFunding}
|
||||||
/>
|
/>
|
||||||
|
<DepositCapMessage
|
||||||
|
action='fund'
|
||||||
|
coins={depositCapReachedCoins}
|
||||||
|
className='pr-4 py-2 mt-4'
|
||||||
|
showIcon
|
||||||
|
/>
|
||||||
<SwitchAutoLend
|
<SwitchAutoLend
|
||||||
className='pt-4 mt-4 border border-transparent border-t-white/10'
|
className='pt-4 mt-4 border border-transparent border-t-white/10'
|
||||||
accountId={accountId}
|
accountId={accountId}
|
||||||
@ -148,7 +171,7 @@ export default function FundAccount(props: Props) {
|
|||||||
className='w-full mt-4'
|
className='w-full mt-4'
|
||||||
text='Fund account'
|
text='Fund account'
|
||||||
rightIcon={<ArrowRight />}
|
rightIcon={<ArrowRight />}
|
||||||
disabled={!hasFundingAssets}
|
disabled={!hasFundingAssets || depositCapReachedCoins.length > 0}
|
||||||
showProgressIndicator={isFunding}
|
showProgressIndicator={isFunding}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
/>
|
/>
|
||||||
|
@ -2,6 +2,7 @@ import BigNumber from 'bignumber.js'
|
|||||||
import React, { useEffect, useMemo, useState } from 'react'
|
import React, { useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
import Button from 'components/Button'
|
import Button from 'components/Button'
|
||||||
|
import DepositCapMessage from 'components/DepositCapMessage'
|
||||||
import DisplayCurrency from 'components/DisplayCurrency'
|
import DisplayCurrency from 'components/DisplayCurrency'
|
||||||
import Divider from 'components/Divider'
|
import Divider from 'components/Divider'
|
||||||
import { ArrowRight, ExclamationMarkCircled } from 'components/Icons'
|
import { ArrowRight, ExclamationMarkCircled } from 'components/Icons'
|
||||||
@ -27,6 +28,7 @@ export interface VaultBorrowingsProps {
|
|||||||
vault: Vault
|
vault: Vault
|
||||||
depositActions: Action[]
|
depositActions: Action[]
|
||||||
onChangeBorrowings: (borrowings: BNCoin[]) => void
|
onChangeBorrowings: (borrowings: BNCoin[]) => void
|
||||||
|
depositCapReachedCoins: BNCoin[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function VaultBorrowings(props: VaultBorrowingsProps) {
|
export default function VaultBorrowings(props: VaultBorrowingsProps) {
|
||||||
@ -187,12 +189,21 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
|
|||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
text='Select borrow assets +'
|
text='Select borrow assets +'
|
||||||
color='tertiary'
|
color='tertiary'
|
||||||
onClick={addAsset}
|
onClick={addAsset}
|
||||||
disabled={isConfirming}
|
disabled={isConfirming}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<DepositCapMessage
|
||||||
|
action='deposit'
|
||||||
|
coins={props.depositCapReachedCoins}
|
||||||
|
className='px-4 y-2'
|
||||||
|
showIcon
|
||||||
|
/>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
<div className='flex flex-col gap-2'>
|
<div className='flex flex-col gap-2'>
|
||||||
<div className='flex justify-between'>
|
<div className='flex justify-between'>
|
||||||
@ -221,7 +232,7 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
|
|||||||
text='Deposit'
|
text='Deposit'
|
||||||
rightIcon={<ArrowRight />}
|
rightIcon={<ArrowRight />}
|
||||||
showProgressIndicator={isConfirming}
|
showProgressIndicator={isConfirming}
|
||||||
disabled={!props.depositActions.length}
|
disabled={!props.depositActions.length || props.depositCapReachedCoins.length > 0}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import BigNumber from 'bignumber.js'
|
import BigNumber from 'bignumber.js'
|
||||||
import { useMemo, useState } from 'react'
|
|
||||||
|
|
||||||
import Button from 'components/Button'
|
import Button from 'components/Button'
|
||||||
|
import DepositCapMessage from 'components/DepositCapMessage'
|
||||||
import DisplayCurrency from 'components/DisplayCurrency'
|
import DisplayCurrency from 'components/DisplayCurrency'
|
||||||
import Divider from 'components/Divider'
|
import Divider from 'components/Divider'
|
||||||
import { Gauge } from 'components/Gauge'
|
import { Gauge } from 'components/Gauge'
|
||||||
@ -12,6 +12,7 @@ import Text from 'components/Text'
|
|||||||
import TokenInput from 'components/TokenInput'
|
import TokenInput from 'components/TokenInput'
|
||||||
import { BN_ZERO } from 'constants/math'
|
import { BN_ZERO } from 'constants/math'
|
||||||
import usePrice from 'hooks/usePrice'
|
import usePrice from 'hooks/usePrice'
|
||||||
|
import { useMemo, useState } from 'react'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
import { BNCoin } from 'types/classes/BNCoin'
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
import { accumulateAmounts } from 'utils/accounts'
|
import { accumulateAmounts } from 'utils/accounts'
|
||||||
@ -27,6 +28,7 @@ interface Props {
|
|||||||
onChangeDeposits: (deposits: BNCoin[]) => void
|
onChangeDeposits: (deposits: BNCoin[]) => void
|
||||||
onChangeIsCustomRatio: (isCustomRatio: boolean) => void
|
onChangeIsCustomRatio: (isCustomRatio: boolean) => void
|
||||||
toggleOpen: (index: number) => void
|
toggleOpen: (index: number) => void
|
||||||
|
depositCapReachedCoins: BNCoin[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function VaultDeposit(props: Props) {
|
export default function VaultDeposit(props: Props) {
|
||||||
@ -158,7 +160,7 @@ export default function VaultDeposit(props: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col'>
|
<div className='flex flex-col'>
|
||||||
<div className='flex gap-4 p-4'>
|
<div className='flex gap-4 pl-3 p-4'>
|
||||||
<div className='flex flex-col items-center justify-between gap-1 pb-[30px] pt-2'>
|
<div className='flex flex-col items-center justify-between gap-1 pb-[30px] pt-2'>
|
||||||
<Gauge
|
<Gauge
|
||||||
percentage={primaryValuePercentage}
|
percentage={primaryValuePercentage}
|
||||||
@ -202,6 +204,14 @@ export default function VaultDeposit(props: Props) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<DepositCapMessage
|
||||||
|
action='deposit'
|
||||||
|
coins={props.depositCapReachedCoins}
|
||||||
|
className='px-4 "y-2'
|
||||||
|
showIcon
|
||||||
|
/>
|
||||||
|
|
||||||
<div className='flex flex-col gap-6 p-4 pt-2'>
|
<div className='flex flex-col gap-6 p-4 pt-2'>
|
||||||
{disableInput ? (
|
{disableInput ? (
|
||||||
<div>
|
<div>
|
||||||
@ -240,4 +250,4 @@ export default function VaultDeposit(props: Props) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
import Accordion from 'components/Accordion'
|
import Accordion from 'components/Accordion'
|
||||||
import AccountSummary from 'components/Account/AccountSummary'
|
import AccountSummary from 'components/Account/AccountSummary'
|
||||||
@ -8,10 +8,14 @@ import VaultDeposit from 'components/Modals/Vault/VaultDeposits'
|
|||||||
import VaultDepositSubTitle from 'components/Modals/Vault/VaultDepositsSubTitle'
|
import VaultDepositSubTitle from 'components/Modals/Vault/VaultDepositsSubTitle'
|
||||||
import { BN_ZERO } from 'constants/math'
|
import { BN_ZERO } from 'constants/math'
|
||||||
import useDepositVault from 'hooks/broadcast/useDepositVault'
|
import useDepositVault from 'hooks/broadcast/useDepositVault'
|
||||||
|
import useDisplayAsset from 'hooks/useDisplayAsset'
|
||||||
import useIsOpenArray from 'hooks/useIsOpenArray'
|
import useIsOpenArray from 'hooks/useIsOpenArray'
|
||||||
|
import usePrices from 'hooks/usePrices'
|
||||||
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
|
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
|
||||||
import { BNCoin } from 'types/classes/BNCoin'
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
import { byDenom } from 'utils/array'
|
import { byDenom } from 'utils/array'
|
||||||
|
import { convertToDisplayAmount, magnify } from 'utils/formatters'
|
||||||
|
import { getCapLeftWithBuffer } from 'utils/generic'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
vault: Vault | DepositedVault
|
vault: Vault | DepositedVault
|
||||||
@ -29,14 +33,14 @@ export default function VaultModalContent(props: Props) {
|
|||||||
removedDeposits,
|
removedDeposits,
|
||||||
removedLends,
|
removedLends,
|
||||||
removeLends,
|
removeLends,
|
||||||
updatedAccount,
|
|
||||||
addVaultValues,
|
addVaultValues,
|
||||||
} = useUpdatedAccount(props.account)
|
} = useUpdatedAccount(props.account)
|
||||||
|
|
||||||
|
const { data: prices } = usePrices()
|
||||||
const [isOpen, toggleOpen] = useIsOpenArray(2, false)
|
const [isOpen, toggleOpen] = useIsOpenArray(2, false)
|
||||||
const [isCustomRatio, setIsCustomRatio] = useState(false)
|
const [isCustomRatio, setIsCustomRatio] = useState(false)
|
||||||
const [selectedCoins, setSelectedCoins] = useState<BNCoin[]>([])
|
const [selectedCoins, setSelectedCoins] = useState<BNCoin[]>([])
|
||||||
|
const displayAsset = useDisplayAsset()
|
||||||
const { actions: depositActions, totalValue } = useDepositVault({
|
const { actions: depositActions, totalValue } = useDepositVault({
|
||||||
vault: props.vault,
|
vault: props.vault,
|
||||||
reclaims: removedLends,
|
reclaims: removedLends,
|
||||||
@ -44,6 +48,24 @@ export default function VaultModalContent(props: Props) {
|
|||||||
borrowings: addedDebts,
|
borrowings: addedDebts,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const depositCapReachedCoins = useMemo(() => {
|
||||||
|
const capLeft = getCapLeftWithBuffer(props.vault.cap)
|
||||||
|
|
||||||
|
if (totalValue.isGreaterThan(capLeft)) {
|
||||||
|
const amount = magnify(
|
||||||
|
convertToDisplayAmount(
|
||||||
|
BNCoin.fromDenomAndBigNumber(props.vault.cap.denom, capLeft),
|
||||||
|
displayAsset.denom,
|
||||||
|
prices,
|
||||||
|
).toString(),
|
||||||
|
displayAsset,
|
||||||
|
)
|
||||||
|
|
||||||
|
return [BNCoin.fromDenomAndBigNumber(displayAsset.denom, amount)]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}, [displayAsset, prices, props.vault.cap, totalValue])
|
||||||
|
|
||||||
const handleDepositSelect = useCallback(
|
const handleDepositSelect = useCallback(
|
||||||
(selectedCoins: BNCoin[]) => {
|
(selectedCoins: BNCoin[]) => {
|
||||||
const reclaims: BNCoin[] = []
|
const reclaims: BNCoin[] = []
|
||||||
@ -134,6 +156,7 @@ export default function VaultModalContent(props: Props) {
|
|||||||
toggleOpen={toggleOpen}
|
toggleOpen={toggleOpen}
|
||||||
isCustomRatio={isCustomRatio}
|
isCustomRatio={isCustomRatio}
|
||||||
onChangeIsCustomRatio={onChangeIsCustomRatio}
|
onChangeIsCustomRatio={onChangeIsCustomRatio}
|
||||||
|
depositCapReachedCoins={depositCapReachedCoins}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
title: 'Deposit',
|
title: 'Deposit',
|
||||||
@ -151,6 +174,7 @@ export default function VaultModalContent(props: Props) {
|
|||||||
onChangeBorrowings={addDebts}
|
onChangeBorrowings={addDebts}
|
||||||
vault={props.vault}
|
vault={props.vault}
|
||||||
depositActions={depositActions}
|
depositActions={depositActions}
|
||||||
|
depositCapReachedCoins={depositCapReachedCoins}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
title: 'Borrow',
|
title: 'Borrow',
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useParams } from 'react-router-dom'
|
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
import { useParams } from 'react-router-dom'
|
||||||
|
|
||||||
import { menuTree } from 'components/Header/DesktopHeader'
|
import { menuTree } from 'components/Header/DesktopHeader'
|
||||||
import { Logo } from 'components/Icons'
|
import { Logo } from 'components/Icons'
|
||||||
@ -11,8 +11,8 @@ export default function DesktopNavigation() {
|
|||||||
const { address, accountId } = useParams()
|
const { address, accountId } = useParams()
|
||||||
const focusComponent = useStore((s) => s.focusComponent)
|
const focusComponent = useStore((s) => s.focusComponent)
|
||||||
|
|
||||||
function getIsActive(href: string) {
|
function getIsActive(pages: string[]) {
|
||||||
return location.pathname.includes(href)
|
return pages.some((page) => location.pathname.includes(page))
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -31,8 +31,8 @@ export default function DesktopNavigation() {
|
|||||||
{menuTree.map((item, index) => (
|
{menuTree.map((item, index) => (
|
||||||
<NavLink
|
<NavLink
|
||||||
key={index}
|
key={index}
|
||||||
href={getRoute(item.page, address, accountId)}
|
href={getRoute(item.pages[0], address, accountId)}
|
||||||
isActive={getIsActive(item.page)}
|
isActive={getIsActive(item.pages)}
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
@ -12,12 +12,10 @@ export const NavLink = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
to={props.href}
|
to={props.href}
|
||||||
className={({ isActive }) =>
|
className={classNames(
|
||||||
classNames(
|
'text-sm font-semibold hover:text-white active:text-white',
|
||||||
'text-sm font-semibold hover:text-white active:text-white',
|
props.isActive ? 'pointer-events-none text-white' : 'text-white/60',
|
||||||
isActive ? 'pointer-events-none text-white' : 'text-white/60',
|
)}
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -10,6 +10,7 @@ import { BNCoin } from 'types/classes/BNCoin'
|
|||||||
interface Props {
|
interface Props {
|
||||||
asset: Asset
|
asset: Asset
|
||||||
onSelectAsset: (asset: Asset) => void
|
onSelectAsset: (asset: Asset) => void
|
||||||
|
depositCap?: DepositCap
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AssetItem(props: Props) {
|
export default function AssetItem(props: Props) {
|
||||||
@ -40,11 +41,24 @@ export default function AssetItem(props: Props) {
|
|||||||
{asset.isFavorite ? <StarFilled width={16} /> : <StarOutlined width={16} />}
|
{asset.isFavorite ? <StarFilled width={16} /> : <StarOutlined width={16} />}
|
||||||
</div>
|
</div>
|
||||||
<AssetImage asset={asset} size={24} />
|
<AssetImage asset={asset} size={24} />
|
||||||
<Text size='sm' className='text-left'>
|
<div className='flex-col'>
|
||||||
{asset.name}
|
<div className='flex gap-1'>
|
||||||
</Text>
|
<Text size='sm' className='text-left'>
|
||||||
<div className='rounded-sm bg-white/20 px-[6px] py-[2px]'>
|
{asset.name}
|
||||||
<Text size='xs'>{asset.symbol}</Text>
|
</Text>
|
||||||
|
<div className='rounded-sm bg-white/20 px-[6px] py-[2px]'>
|
||||||
|
<Text size='xs'>{asset.symbol}</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{props.depositCap && (
|
||||||
|
<div className='flex gap-1'>
|
||||||
|
<span className='text-left text-white/60 text-xs'>Cap Left: </span>
|
||||||
|
<DisplayCurrency
|
||||||
|
className='text-left text-white/60 text-xs'
|
||||||
|
coin={BNCoin.fromDenomAndBigNumber(props.depositCap.denom, props.depositCap.max)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DisplayCurrency
|
<DisplayCurrency
|
||||||
|
@ -3,6 +3,8 @@ import classNames from 'classnames'
|
|||||||
import { ChevronDown } from 'components/Icons'
|
import { ChevronDown } from 'components/Icons'
|
||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import AssetItem from 'components/Trade/TradeModule/AssetSelector/AssetItem'
|
import AssetItem from 'components/Trade/TradeModule/AssetSelector/AssetItem'
|
||||||
|
import useMarketAssets from 'hooks/useMarketAssets'
|
||||||
|
import { byDenom } from 'utils/array'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
type: 'buy' | 'sell'
|
type: 'buy' | 'sell'
|
||||||
@ -13,6 +15,8 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function AssetList(props: Props) {
|
export default function AssetList(props: Props) {
|
||||||
|
const { data: marketAssets } = useMarketAssets()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<button
|
<button
|
||||||
@ -34,6 +38,9 @@ export default function AssetList(props: Props) {
|
|||||||
key={`${props.type}-${asset.symbol}`}
|
key={`${props.type}-${asset.symbol}`}
|
||||||
asset={asset}
|
asset={asset}
|
||||||
onSelectAsset={props.onChangeAsset}
|
onSelectAsset={props.onChangeAsset}
|
||||||
|
depositCap={
|
||||||
|
props.type === 'buy' ? marketAssets?.find(byDenom(asset.denom))?.cap : undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -4,8 +4,8 @@ import { InfoCircle } from 'components/Icons'
|
|||||||
import Text from 'components/Text'
|
import Text from 'components/Text'
|
||||||
import { Tooltip } from 'components/Tooltip'
|
import { Tooltip } from 'components/Tooltip'
|
||||||
import { ORDER_TYPE_TABS } from 'components/Trade/TradeModule/SwapForm/OrderTypeSelector/constants'
|
import { ORDER_TYPE_TABS } from 'components/Trade/TradeModule/SwapForm/OrderTypeSelector/constants'
|
||||||
import ConditionalWrapper from 'hocs/ConditionalWrapper'
|
|
||||||
import { AvailableOrderType } from 'components/Trade/TradeModule/SwapForm/OrderTypeSelector/types'
|
import { AvailableOrderType } from 'components/Trade/TradeModule/SwapForm/OrderTypeSelector/types'
|
||||||
|
import ConditionalWrapper from 'hocs/ConditionalWrapper'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
selected: AvailableOrderType
|
selected: AvailableOrderType
|
||||||
@ -46,9 +46,9 @@ export default function OrderTypeSelector(props: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const className = {
|
const className = {
|
||||||
wrapper: 'flex flex-1 flex-row px-3 pt-3',
|
wrapper: 'flex flex-1 flex-row px-3 pt-4',
|
||||||
tab: 'mr-4 pb-2 cursor-pointer select-none flex flex-row',
|
tab: 'mr-4 pb-2 cursor-pointer select-none flex flex-row',
|
||||||
selectedTab: 'border-b-2 border-pink border-solid',
|
selectedTab: 'border-b-2 border-pink border-solid',
|
||||||
disabledTab: 'opacity-50 pointer-events-none',
|
disabledTab: 'opacity-20 pointer-events-none',
|
||||||
infoCircle: 'w-4 h-4 ml-2 mt-1',
|
infoCircle: 'w-4 h-4 ml-2 mt-1',
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import debounce from 'lodash.debounce'
|
|||||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
import estimateExactIn from 'api/swap/estimateExactIn'
|
import estimateExactIn from 'api/swap/estimateExactIn'
|
||||||
|
import DepositCapMessage from 'components/DepositCapMessage'
|
||||||
import Divider from 'components/Divider'
|
import Divider from 'components/Divider'
|
||||||
import RangeInput from 'components/RangeInput'
|
import RangeInput from 'components/RangeInput'
|
||||||
import AssetAmountInput from 'components/Trade/TradeModule/SwapForm/AssetAmountInput'
|
import AssetAmountInput from 'components/Trade/TradeModule/SwapForm/AssetAmountInput'
|
||||||
@ -16,6 +17,7 @@ import useAutoLendEnabledAccountIds from 'hooks/useAutoLendEnabledAccountIds'
|
|||||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||||
import useHealthComputer from 'hooks/useHealthComputer'
|
import useHealthComputer from 'hooks/useHealthComputer'
|
||||||
import useLocalStorage from 'hooks/useLocalStorage'
|
import useLocalStorage from 'hooks/useLocalStorage'
|
||||||
|
import useMarketAssets from 'hooks/useMarketAssets'
|
||||||
import useMarketBorrowings from 'hooks/useMarketBorrowings'
|
import useMarketBorrowings from 'hooks/useMarketBorrowings'
|
||||||
import usePrices from 'hooks/usePrices'
|
import usePrices from 'hooks/usePrices'
|
||||||
import useToggle from 'hooks/useToggle'
|
import useToggle from 'hooks/useToggle'
|
||||||
@ -24,6 +26,7 @@ import useStore from 'store'
|
|||||||
import { BNCoin } from 'types/classes/BNCoin'
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
import { byDenom } from 'utils/array'
|
import { byDenom } from 'utils/array'
|
||||||
import { defaultFee } from 'utils/constants'
|
import { defaultFee } from 'utils/constants'
|
||||||
|
import { getCapLeftWithBuffer } from 'utils/generic'
|
||||||
import { asyncThrottle, BN } from 'utils/helpers'
|
import { asyncThrottle, BN } from 'utils/helpers'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -39,6 +42,7 @@ export default function SwapForm(props: Props) {
|
|||||||
const [slippage] = useLocalStorage(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage)
|
const [slippage] = useLocalStorage(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage)
|
||||||
const { computeMaxSwapAmount } = useHealthComputer(account)
|
const { computeMaxSwapAmount } = useHealthComputer(account)
|
||||||
const { data: borrowAssets } = useMarketBorrowings()
|
const { data: borrowAssets } = useMarketBorrowings()
|
||||||
|
const { data: marketAssets } = useMarketAssets()
|
||||||
|
|
||||||
const [isMarginChecked, setMarginChecked] = useToggle()
|
const [isMarginChecked, setMarginChecked] = useToggle()
|
||||||
const [buyAssetAmount, setBuyAssetAmount] = useState(BN_ZERO)
|
const [buyAssetAmount, setBuyAssetAmount] = useState(BN_ZERO)
|
||||||
@ -58,6 +62,19 @@ export default function SwapForm(props: Props) {
|
|||||||
[borrowAssets, sellAsset.denom],
|
[borrowAssets, sellAsset.denom],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const depositCapReachedCoins: BNCoin[] = useMemo(() => {
|
||||||
|
const buyMarketAsset = marketAssets.find(byDenom(buyAsset.denom))
|
||||||
|
|
||||||
|
if (!buyMarketAsset) return []
|
||||||
|
|
||||||
|
const depositCapLeft = getCapLeftWithBuffer(buyMarketAsset.cap)
|
||||||
|
if (buyAssetAmount.isGreaterThan(depositCapLeft)) {
|
||||||
|
return [BNCoin.fromDenomAndBigNumber(buyAsset.denom, depositCapLeft)]
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}, [marketAssets, buyAsset.denom, buyAssetAmount])
|
||||||
|
|
||||||
const onChangeSellAmount = useCallback(
|
const onChangeSellAmount = useCallback(
|
||||||
(amount: BigNumber) => {
|
(amount: BigNumber) => {
|
||||||
setSellAssetAmount(amount)
|
setSellAssetAmount(amount)
|
||||||
@ -233,59 +250,59 @@ export default function SwapForm(props: Props) {
|
|||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
<OrderTypeSelector selected={selectedOrderType} onChange={setSelectedOrderType} />
|
<OrderTypeSelector selected={selectedOrderType} onChange={setSelectedOrderType} />
|
||||||
<AssetAmountInput
|
<div className='flex flex-col gap-6 px-3 mt-6'>
|
||||||
label='Buy'
|
<AssetAmountInput
|
||||||
max={maxBuyableAmountEstimation}
|
label='Buy'
|
||||||
amount={buyAssetAmount}
|
max={maxBuyableAmountEstimation}
|
||||||
setAmount={onChangeBuyAmount}
|
amount={buyAssetAmount}
|
||||||
asset={buyAsset}
|
setAmount={onChangeBuyAmount}
|
||||||
assetUSDValue={buyAssetValue}
|
asset={buyAsset}
|
||||||
maxButtonLabel='Max Amount:'
|
assetUSDValue={buyAssetValue}
|
||||||
containerClassName='mx-3 my-6'
|
maxButtonLabel='Max Amount:'
|
||||||
disabled={isConfirming}
|
disabled={isConfirming}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RangeInput
|
<RangeInput
|
||||||
wrapperClassName='p-4'
|
disabled={isConfirming || maxBuyableAmountEstimation.isZero()}
|
||||||
disabled={isConfirming || maxBuyableAmountEstimation.isZero()}
|
onChange={handleRangeInputChange}
|
||||||
onChange={handleRangeInputChange}
|
value={buyAssetAmount.shiftedBy(-buyAsset.decimals).toNumber()}
|
||||||
value={buyAssetAmount.shiftedBy(-buyAsset.decimals).toNumber()}
|
max={maxBuyableAmountEstimation.shiftedBy(-buyAsset.decimals).toNumber()}
|
||||||
max={maxBuyableAmountEstimation.shiftedBy(-buyAsset.decimals).toNumber()}
|
marginThreshold={
|
||||||
marginThreshold={
|
isMarginChecked
|
||||||
isMarginChecked
|
? buySideMarginThreshold.shiftedBy(-buyAsset.decimals).toNumber()
|
||||||
? buySideMarginThreshold.shiftedBy(-buyAsset.decimals).toNumber()
|
: undefined
|
||||||
: undefined
|
}
|
||||||
}
|
/>
|
||||||
/>
|
|
||||||
|
|
||||||
<AssetAmountInput
|
<DepositCapMessage action='buy' coins={depositCapReachedCoins} className='p-4 bg-white/5' />
|
||||||
label='Sell'
|
|
||||||
max={maxSellAmount}
|
|
||||||
amount={sellAssetAmount}
|
|
||||||
setAmount={onChangeSellAmount}
|
|
||||||
assetUSDValue={sellAssetValue}
|
|
||||||
asset={sellAsset}
|
|
||||||
maxButtonLabel='Balance:'
|
|
||||||
containerClassName='mx-3'
|
|
||||||
disabled={isConfirming}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TradeSummary
|
<AssetAmountInput
|
||||||
containerClassName='m-3 mt-10'
|
label='Sell'
|
||||||
buyAsset={buyAsset}
|
max={maxSellAmount}
|
||||||
sellAsset={sellAsset}
|
amount={sellAssetAmount}
|
||||||
borrowRate={borrowAsset?.borrowRate}
|
setAmount={onChangeSellAmount}
|
||||||
buyAction={handleBuyClick}
|
assetUSDValue={sellAssetValue}
|
||||||
buyButtonDisabled={sellAssetAmount.isZero()}
|
asset={sellAsset}
|
||||||
showProgressIndicator={isConfirming}
|
maxButtonLabel='Balance:'
|
||||||
isMargin={isMarginChecked}
|
disabled={isConfirming}
|
||||||
borrowAmount={
|
/>
|
||||||
sellAssetAmount.isGreaterThan(sellSideMarginThreshold)
|
|
||||||
? sellAssetAmount.minus(sellSideMarginThreshold)
|
<TradeSummary
|
||||||
: BN_ZERO
|
buyAsset={buyAsset}
|
||||||
}
|
sellAsset={sellAsset}
|
||||||
estimatedFee={estimatedFee}
|
borrowRate={borrowAsset?.borrowRate}
|
||||||
/>
|
buyAction={handleBuyClick}
|
||||||
|
buyButtonDisabled={sellAssetAmount.isZero() || depositCapReachedCoins.length > 0}
|
||||||
|
showProgressIndicator={isConfirming}
|
||||||
|
isMargin={isMarginChecked}
|
||||||
|
borrowAmount={
|
||||||
|
sellAssetAmount.isGreaterThan(sellSideMarginThreshold)
|
||||||
|
? sellAssetAmount.minus(sellSideMarginThreshold)
|
||||||
|
: BN_ZERO
|
||||||
|
}
|
||||||
|
estimatedFee={estimatedFee}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
14
src/hooks/useDisplayAsset.tsx
Normal file
14
src/hooks/useDisplayAsset.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { ASSETS } from 'constants/assets'
|
||||||
|
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||||
|
import { DISPLAY_CURRENCY_KEY } from 'constants/localStore'
|
||||||
|
import useLocalStorage from 'hooks/useLocalStorage'
|
||||||
|
import { byDenom } from 'utils/array'
|
||||||
|
|
||||||
|
export default function useDisplayAsset() {
|
||||||
|
const [displayCurrency] = useLocalStorage<string>(
|
||||||
|
DISPLAY_CURRENCY_KEY,
|
||||||
|
DEFAULT_SETTINGS.displayCurrency,
|
||||||
|
)
|
||||||
|
|
||||||
|
return ASSETS.find(byDenom(displayCurrency)) ?? ASSETS[0]
|
||||||
|
}
|
@ -15,6 +15,7 @@ import {
|
|||||||
HealthComputer,
|
HealthComputer,
|
||||||
} from 'types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types'
|
} from 'types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types'
|
||||||
import { convertAccountToPositions } from 'utils/accounts'
|
import { convertAccountToPositions } from 'utils/accounts'
|
||||||
|
import { LTV_BUFFER } from 'utils/constants'
|
||||||
import {
|
import {
|
||||||
BorrowTarget,
|
BorrowTarget,
|
||||||
compute_health_js,
|
compute_health_js,
|
||||||
@ -24,7 +25,6 @@ import {
|
|||||||
SwapKind,
|
SwapKind,
|
||||||
} from 'utils/health_computer'
|
} from 'utils/health_computer'
|
||||||
import { BN } from 'utils/helpers'
|
import { BN } from 'utils/helpers'
|
||||||
import { LTV_BUFFER } from 'utils/constants'
|
|
||||||
|
|
||||||
export default function useHealthComputer(account?: Account) {
|
export default function useHealthComputer(account?: Account) {
|
||||||
const { data: prices } = usePrices()
|
const { data: prices } = usePrices()
|
||||||
|
@ -23,7 +23,7 @@ function useLendingMarketAssetsTableData(): {
|
|||||||
const accountLentAssets: LendingMarketTableData[] = [],
|
const accountLentAssets: LendingMarketTableData[] = [],
|
||||||
availableAssets: LendingMarketTableData[] = []
|
availableAssets: LendingMarketTableData[] = []
|
||||||
|
|
||||||
markets.forEach(({ denom, depositCap, liquidityRate, liquidationThreshold, maxLtv }) => {
|
markets.forEach(({ denom, cap, liquidityRate, liquidationThreshold, maxLtv }) => {
|
||||||
const asset = getAssetByDenom(denom) as Asset
|
const asset = getAssetByDenom(denom) as Asset
|
||||||
const marketDepositAmount = BN(marketDeposits.find(byDenom(denom))?.amount ?? 0)
|
const marketDepositAmount = BN(marketDeposits.find(byDenom(denom))?.amount ?? 0)
|
||||||
const marketLiquidityAmount = BN(marketLiquidities.find(byDenom(denom))?.amount ?? 0)
|
const marketLiquidityAmount = BN(marketLiquidities.find(byDenom(denom))?.amount ?? 0)
|
||||||
@ -38,7 +38,7 @@ function useLendingMarketAssetsTableData(): {
|
|||||||
accountLentValue,
|
accountLentValue,
|
||||||
accountLentAmount,
|
accountLentAmount,
|
||||||
marketLiquidityAmount,
|
marketLiquidityAmount,
|
||||||
marketDepositCap: BN(depositCap),
|
marketDepositCap: cap.max,
|
||||||
marketLiquidityRate: liquidityRate,
|
marketLiquidityRate: liquidityRate,
|
||||||
marketLiquidationThreshold: liquidationThreshold,
|
marketLiquidationThreshold: liquidationThreshold,
|
||||||
marketMaxLtv: maxLtv,
|
marketMaxLtv: maxLtv,
|
||||||
|
4
src/types/interfaces/asset.d.ts
vendored
4
src/types/interfaces/asset.d.ts
vendored
@ -49,10 +49,6 @@ interface PseudoAsset {
|
|||||||
symbol: string
|
symbol: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OtherAsset extends Omit<Asset, 'symbol'> {
|
|
||||||
symbol: 'MARS'
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BorrowAsset extends Asset {
|
interface BorrowAsset extends Asset {
|
||||||
borrowRate: number | null
|
borrowRate: number | null
|
||||||
liquidity: {
|
liquidity: {
|
||||||
|
2
src/types/interfaces/market.d.ts
vendored
2
src/types/interfaces/market.d.ts
vendored
@ -5,7 +5,7 @@ interface Market {
|
|||||||
collateralTotalScaled: string
|
collateralTotalScaled: string
|
||||||
depositEnabled: boolean
|
depositEnabled: boolean
|
||||||
borrowEnabled: boolean
|
borrowEnabled: boolean
|
||||||
depositCap: string
|
cap: DepositCap
|
||||||
maxLtv: number
|
maxLtv: number
|
||||||
liquidityRate: number
|
liquidityRate: number
|
||||||
liquidationThreshold: number
|
liquidationThreshold: number
|
||||||
|
12
src/types/interfaces/vaults.d.ts
vendored
12
src/types/interfaces/vaults.d.ts
vendored
@ -26,11 +26,7 @@ interface VaultInfo {
|
|||||||
max: number
|
max: number
|
||||||
liq: number
|
liq: number
|
||||||
}
|
}
|
||||||
cap: {
|
cap: DepositCap
|
||||||
denom: string
|
|
||||||
used: BigNumber
|
|
||||||
max: BigNumber
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface VaultConfig extends VaultMetaData, VaultInfo {}
|
interface VaultConfig extends VaultMetaData, VaultInfo {}
|
||||||
@ -74,3 +70,9 @@ interface VaultPositionFlatAmounts {
|
|||||||
unlocking: BigNumber
|
unlocking: BigNumber
|
||||||
unlocked: BigNumber
|
unlocked: BigNumber
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DepositCap {
|
||||||
|
denom: string
|
||||||
|
used: BigNumber
|
||||||
|
max: BigNumber
|
||||||
|
}
|
||||||
|
@ -11,3 +11,5 @@ export const defaultFee: StdFee = {
|
|||||||
export const SECONDS_IN_A_YEAR = 31540000
|
export const SECONDS_IN_A_YEAR = 31540000
|
||||||
|
|
||||||
export const LTV_BUFFER = 0.01
|
export const LTV_BUFFER = 0.01
|
||||||
|
|
||||||
|
export const DEPOSIT_CAP_BUFFER = 0.999
|
||||||
|
5
src/utils/generic.ts
Normal file
5
src/utils/generic.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { DEPOSIT_CAP_BUFFER } from 'utils/constants'
|
||||||
|
|
||||||
|
export function getCapLeftWithBuffer(cap: DepositCap) {
|
||||||
|
return cap.max.minus(cap.used).times(DEPOSIT_CAP_BUFFER).integerValue()
|
||||||
|
}
|
@ -1,9 +1,14 @@
|
|||||||
|
import {
|
||||||
|
AssetParamsBaseForAddr as AssetParams,
|
||||||
|
TotalDepositResponse,
|
||||||
|
} from 'types/generated/mars-params/MarsParams.types'
|
||||||
import { Market as RedBankMarket } from 'types/generated/mars-red-bank/MarsRedBank.types'
|
import { Market as RedBankMarket } from 'types/generated/mars-red-bank/MarsRedBank.types'
|
||||||
import { AssetParamsBaseForAddr as AssetParams } from 'types/generated/mars-params/MarsParams.types'
|
import { BN } from 'utils/helpers'
|
||||||
|
|
||||||
export function resolveMarketResponse(
|
export function resolveMarketResponse(
|
||||||
marketResponse: RedBankMarket,
|
marketResponse: RedBankMarket,
|
||||||
assetParamsResponse: AssetParams,
|
assetParamsResponse: AssetParams,
|
||||||
|
assetCapResponse: TotalDepositResponse,
|
||||||
): Market {
|
): Market {
|
||||||
return {
|
return {
|
||||||
denom: marketResponse.denom,
|
denom: marketResponse.denom,
|
||||||
@ -12,7 +17,11 @@ export function resolveMarketResponse(
|
|||||||
collateralTotalScaled: marketResponse.collateral_total_scaled,
|
collateralTotalScaled: marketResponse.collateral_total_scaled,
|
||||||
depositEnabled: assetParamsResponse.red_bank.deposit_enabled,
|
depositEnabled: assetParamsResponse.red_bank.deposit_enabled,
|
||||||
borrowEnabled: assetParamsResponse.red_bank.borrow_enabled,
|
borrowEnabled: assetParamsResponse.red_bank.borrow_enabled,
|
||||||
depositCap: assetParamsResponse.deposit_cap,
|
cap: {
|
||||||
|
denom: assetCapResponse.denom,
|
||||||
|
used: BN(assetCapResponse.amount),
|
||||||
|
max: BN(assetParamsResponse.deposit_cap),
|
||||||
|
},
|
||||||
maxLtv: Number(assetParamsResponse.max_loan_to_value),
|
maxLtv: Number(assetParamsResponse.max_loan_to_value),
|
||||||
liquidityRate: Number(marketResponse.liquidity_rate),
|
liquidityRate: Number(marketResponse.liquidity_rate),
|
||||||
liquidationThreshold: Number(assetParamsResponse.liquidation_threshold),
|
liquidationThreshold: Number(assetParamsResponse.liquidation_threshold),
|
||||||
|
Loading…
Reference in New Issue
Block a user