From 6b86879fade264bcd293d7b71fe64e8e36af9272 Mon Sep 17 00:00:00 2001 From: bwvdhelm <34470358+bobthebuidlr@users.noreply.github.com> Date: Mon, 13 Mar 2023 23:13:10 +0100 Subject: [PATCH] releave v.1.4.0 --- .../common/Containers/CommonContainer.tsx | 3 +- .../DisplayCurrency/DisplayCurrency.tsx | 11 +- src/components/common/Footer/Footer.tsx | 4 +- .../Header/IncentivesButton.module.scss | 4 + .../common/Header/IncentivesButton.tsx | 16 +- .../common/Header/Settings.module.scss | 17 +- src/components/common/Header/Settings.tsx | 72 ++++- .../common/TextTooltip/TextTooltip.tsx | 10 +- src/components/common/Tutorial/Tutorial.tsx | 2 +- .../ActionsTooltip/components/EditContent.tsx | 17 +- .../components/UnlockContent.tsx | 23 +- .../ActiveVaultsTableMobile.tsx | 13 +- .../useActiveVaultsColumns.tsx | 54 +++- .../AvailableVaultsTableMobile.tsx | 20 +- .../useAvailableVaultsColumns.tsx | 20 +- .../BreakdownGraph/BreakdownGraph.tsx | 14 +- .../BreakdownTable/BreakdownTable.module.scss | 4 + .../BreakdownTable/BreakdownTable.tsx | 42 ++- .../PositionInput/BorrowInput/BorrowInput.tsx | 102 +++++- .../LeverageSlider/LeverageSlider.tsx | 9 +- .../PositionInput/PositionInput.module.scss | 2 +- .../fields/PositionInput/PositionInput.tsx | 81 +++-- .../TokenInput/TokenInput.module.scss | 1 + .../PositionInput/TokenInput/TokenInput.tsx | 6 +- .../fields/RepayInput/RepayInput.tsx | 47 ++- .../redbank/AssetTable/AssetTable.tsx | 6 + .../redbank/AssetTable/MetricsRow.tsx | 18 +- .../BorrowColumns/useBorrowColumns.tsx | 36 +-- .../useDepositColumns.module.scss | 9 + .../DepositColumns/useDepositColumns.tsx | 75 +++-- .../RedbankNotConnected.tsx | 2 + src/configs/osmo-test-4.ts | 54 +--- src/configs/osmosis-1.ts | 22 +- src/constants/appConstants.ts | 1 + src/constants/defaults.ts | 7 +- .../fields/getClosePositionActions.ts | 9 +- src/functions/fields/getCoinFromPosition.ts | 2 +- src/functions/fields/getLeverageFromValues.ts | 14 +- src/functions/fields/getLiqBorrowValue.ts | 9 +- src/functions/fields/getMaxAllowedLeverage.ts | 2 +- src/functions/fields/getMaxBorrowValue.ts | 5 +- .../fields/test/numberFunctions.test.ts | 15 +- src/functions/updateExchangeRate.ts | 3 +- src/hooks/queries/index.ts | 1 + src/hooks/queries/useDepositAndDebt.tsx | 2 + src/hooks/queries/useEditPosition.tsx | 35 +- src/hooks/queries/useRepayPosition.tsx | 21 +- src/hooks/queries/useSpotPrice.tsx | 26 +- src/hooks/queries/useUsdPrice.tsx | 46 +++ src/images/statom.svg | 38 +++ src/mocks/index.ts | 1 + src/mocks/position.ts | 7 +- src/mocks/redBankAssets.ts | 6 +- src/mocks/redBankData.ts | 150 +++++++++ .../farm/vault/[address]/edit/EditVault.tsx | 21 +- .../farm/vault/[address]/repay/RepayVault.tsx | 7 +- src/store/slices/common.ts | 5 + src/store/slices/oracles.ts | 24 +- src/store/slices/redBank.ts | 2 + src/store/slices/vaults.ts | 58 +++- src/styles/_assets.module.scss | 1 + src/styles/_master.scss | 302 +++++++----------- src/types/enums/queryKeys.ts | 3 +- src/types/interfaces/asset.d.ts | 6 +- src/types/interfaces/fields.d.ts | 7 +- src/types/interfaces/networkConfig.d.ts | 15 +- src/types/interfaces/redbank.d.ts | 10 +- 67 files changed, 1165 insertions(+), 512 deletions(-) create mode 100644 src/hooks/queries/useUsdPrice.tsx create mode 100644 src/images/statom.svg create mode 100644 src/mocks/redBankData.ts diff --git a/src/components/common/Containers/CommonContainer.tsx b/src/components/common/Containers/CommonContainer.tsx index b957dfe..f2c91a1 100644 --- a/src/components/common/Containers/CommonContainer.tsx +++ b/src/components/common/Containers/CommonContainer.tsx @@ -7,7 +7,7 @@ import { WalletConnectionStatus, } from '@marsprotocol/wallet-connector' import { useQueryClient } from '@tanstack/react-query' -import { MARS_SYMBOL, USDC_SYMBOL } from 'constants/appConstants' +import { MARS_SYMBOL } from 'constants/appConstants' import { IS_TESTNET } from 'constants/env' import { useBlockHeight, @@ -155,7 +155,6 @@ export const CommonContainer = ({ children }: CommonContainerProps) => { useUserDebt() useMarsOracle() useSpotPrice(MARS_SYMBOL) - useSpotPrice(USDC_SYMBOL) useDepositAndDebt() useRedBank() diff --git a/src/components/common/DisplayCurrency/DisplayCurrency.tsx b/src/components/common/DisplayCurrency/DisplayCurrency.tsx index 7585758..1368c00 100644 --- a/src/components/common/DisplayCurrency/DisplayCurrency.tsx +++ b/src/components/common/DisplayCurrency/DisplayCurrency.tsx @@ -1,5 +1,6 @@ import { Coin } from '@cosmjs/stargate' import { AnimatedNumber } from 'components/common' +import { useEffect, useState } from 'react' import useStore from 'store' interface Props { @@ -20,7 +21,15 @@ export const DisplayCurrency = ({ const networkConfig = useStore((s) => s.networkConfig) const convertToDisplayCurrency = useStore((s) => s.convertToDisplayCurrency) const amount = convertToDisplayCurrency(coin) - const displayCurrency = networkConfig?.displayCurrency + const [displayCurrency, setDisplayCurrency] = useState( + networkConfig?.displayCurrency, + ) + + useEffect(() => { + if (!networkConfig) return + if (displayCurrency.denom !== networkConfig?.displayCurrency.denom) + setDisplayCurrency(networkConfig?.displayCurrency) + }, [networkConfig?.displayCurrency, displayCurrency.denom, networkConfig]) if (!displayCurrency) return null diff --git a/src/components/common/Footer/Footer.tsx b/src/components/common/Footer/Footer.tsx index 8d3ad20..46fe14f 100644 --- a/src/components/common/Footer/Footer.tsx +++ b/src/components/common/Footer/Footer.tsx @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next' import useStore from 'store' import { DocURL } from 'types/enums/docURL' -import { version } from '../../../../package.json' +import packageInfo from '../../../../package.json' import styles from './Footer.module.scss' export const Footer = () => { @@ -211,7 +211,7 @@ export const Footer = () => {
-

Mars Protocol v{version}

+

Mars Protocol v{packageInfo.version}

diff --git a/src/components/common/Header/IncentivesButton.module.scss b/src/components/common/Header/IncentivesButton.module.scss index 4e36b5f..dfb00fb 100644 --- a/src/components/common/Header/IncentivesButton.module.scss +++ b/src/components/common/Header/IncentivesButton.module.scss @@ -18,6 +18,10 @@ outline: none; background: $alphaBlack10; + .marsAmount { + margin: 0 space(1); + } + svg { margin-top: space(-0.5); height: rem-calc(19); diff --git a/src/components/common/Header/IncentivesButton.tsx b/src/components/common/Header/IncentivesButton.tsx index 19801e6..2f90886 100644 --- a/src/components/common/Header/IncentivesButton.tsx +++ b/src/components/common/Header/IncentivesButton.tsx @@ -153,13 +153,15 @@ export const IncentivesButton = () => { }} > - + + + {MARS_SYMBOL} + {showDetails && ( diff --git a/src/components/common/Header/Settings.module.scss b/src/components/common/Header/Settings.module.scss index 5fabebf..2f7f98e 100644 --- a/src/components/common/Header/Settings.module.scss +++ b/src/components/common/Header/Settings.module.scss @@ -43,6 +43,17 @@ flex: 0 0 100%; flex-wrap: wrap; display: flex; + flex-direction: column; + + .select { + border-radius: $borderRadiusS; + border: rem-calc(1) solid $alphaBlack20; + @include padding(1, 2); + color: $colorSecondaryDark; + background-color: $fontColorLightPrimary; + outline: none; + width: 100%; + } .name { @include margin(1, 0); @@ -62,12 +73,16 @@ } } + &.reduceMotion { + flex-direction: row; + } + .content { @include margin(1, 0); display: flex; flex-wrap: wrap; flex: 1; - justify-content: flex-end; + justify-content: space-between; gap: space(2); .button { diff --git a/src/components/common/Header/Settings.tsx b/src/components/common/Header/Settings.tsx index b280da9..19bbda8 100644 --- a/src/components/common/Header/Settings.tsx +++ b/src/components/common/Header/Settings.tsx @@ -1,4 +1,5 @@ import { useWalletManager, WalletConnectionStatus } from '@marsprotocol/wallet-connector' +import { useQueryClient } from '@tanstack/react-query' import BigNumber from 'bignumber.js' import classNames from 'classnames' import { Button, NumberInput, SVG, Toggle, Tooltip } from 'components/common' @@ -12,15 +13,39 @@ import styles from './Settings.module.scss' export const Settings = () => { const { t } = useTranslation() const inputPlaceholder = '...' - + const queryClient = useQueryClient() const slippages = [0.02, 0.03] const [showDetails, setShowDetails] = useState(false) const slippage = useStore((s) => s.slippage) + const networkConfig = useStore((s) => s.networkConfig) + const baseCurrency = useStore((s) => s.baseCurrency) + const whitelistedAssets = useStore((s) => s.whitelistedAssets) + const otherAssets = useStore((s) => s.otherAssets) + const assets: Asset[] = [...whitelistedAssets, ...otherAssets] const [customSlippage, setCustomSlippage] = useState(inputPlaceholder) const [inputRef, setInputRef] = useState>() const [isCustom, setIsCustom] = useState(false) const enableAnimations = useStore((s) => s.enableAnimations) const { status } = useWalletManager() + const exchangeRates = useStore((s) => s.exchangeRates) + const [displayCurrency, setDisplayCurrency] = useState(() => { + const currency = { + denom: baseCurrency.denom, + prefix: '', + suffix: baseCurrency.symbol, + decimals: 2, + } + const currentCurrency = assets.find( + (asset) => asset.denom === networkConfig?.displayCurrency.denom, + ) + + if (currentCurrency) { + currency.denom = currentCurrency.denom + currency.prefix = '' + currency.suffix = currentCurrency.symbol + } + return currency + }) const onInputChange = (value: number) => { setCustomSlippage(value.toString()) @@ -57,6 +82,24 @@ export const Settings = () => { localStorage.setItem('enableAnimations', reduce ? 'false' : 'true') } + const changeDisplayCurrency = (denom: string) => { + const selectedAsset = assets.find((asset) => asset.denom === denom) + if (!selectedAsset || !networkConfig || !exchangeRates?.length) return + const newDisplayCurrency = { + denom: selectedAsset.denom, + prefix: '', + suffix: selectedAsset.symbol, + decimals: 2, + } + const exchangeRate = exchangeRates.find((rate) => rate.denom === newDisplayCurrency.denom) + if (!exchangeRate) return + setDisplayCurrency(newDisplayCurrency) + useStore.setState({ networkConfig: { ...networkConfig, displayCurrency: newDisplayCurrency } }) + useStore.setState({ baseToDisplayCurrencyRatio: 1 / Number(exchangeRate.amount) }) + localStorage.setItem('displayCurrency', JSON.stringify(newDisplayCurrency)) + queryClient.invalidateQueries() + } + if (status !== WalletConnectionStatus.Connected) return null return ( @@ -75,7 +118,7 @@ export const Settings = () => {

{t('common.settings')}

-
+
{t('common.reduceMotion')} @@ -88,6 +131,31 @@ export const Settings = () => { />
+
+
+ {t('common.displayCurrency')} + +
+
+ +
+
{FIELDS_FEATURE && ( <> diff --git a/src/components/common/TextTooltip/TextTooltip.tsx b/src/components/common/TextTooltip/TextTooltip.tsx index e2b641e..64d39c1 100644 --- a/src/components/common/TextTooltip/TextTooltip.tsx +++ b/src/components/common/TextTooltip/TextTooltip.tsx @@ -1,4 +1,5 @@ import Tippy from '@tippyjs/react' +import classNames from 'classnames' import styles from './TextTooltip.module.scss' @@ -7,6 +8,7 @@ interface Props { tooltip: string | React.ReactNode hideUnderline?: boolean hideStyling?: boolean + className?: string } export const TextTooltip = (props: Props) => { return ( @@ -23,7 +25,13 @@ export const TextTooltip = (props: Props) => { ) }} > - + {props.text} diff --git a/src/components/common/Tutorial/Tutorial.tsx b/src/components/common/Tutorial/Tutorial.tsx index f0067ca..cb18ed2 100644 --- a/src/components/common/Tutorial/Tutorial.tsx +++ b/src/components/common/Tutorial/Tutorial.tsx @@ -66,7 +66,7 @@ export const Tutorial = (props: Props) => { } const maxLeverage = props.availableVault - ? formatValue(ltvToLeverage(props.availableVault?.ltv.max), 2, 2) + ? formatValue(ltvToLeverage(props.availableVault?.ltv.contract), 2, 2) : '' const content = ( diff --git a/src/components/fields/ActionsTooltip/components/EditContent.tsx b/src/components/fields/ActionsTooltip/components/EditContent.tsx index 2e7ffc3..b9dcce7 100644 --- a/src/components/fields/ActionsTooltip/components/EditContent.tsx +++ b/src/components/fields/ActionsTooltip/components/EditContent.tsx @@ -15,11 +15,16 @@ export const EditContent = (props: Props) => { const { t } = useTranslation() const convertValueToAmount = useStore((s) => s.convertValueToAmount) + const borrowKey = + props.position.borrowDenom === props.vault.denoms.primary + ? 'borrowedPrimary' + : 'borrowedSecondary' + const primaryAmount = props.position.amounts.primary - (props.prevPosition?.amounts.primary || 0) const secondaryAmount = props.position.amounts.secondary - (props.prevPosition?.amounts.secondary || 0) const borrowedAmount = - props.position.amounts.borrowed - (props.prevPosition?.amounts.borrowed || 0) + props.position.amounts[borrowKey] - (props.prevPosition?.amounts[borrowKey] || 0) const depositPrimary = ( { /> ) - const borrowSecondary = ( + const borrow = ( { return (
  • {t('redbank.borrow')} - {borrowSecondary} + {borrow}
  • ) } const getSwapMessage = () => { - const primaryValue = props.position.values.primary - const secondaryValue = props.position.values.secondary + props.position.values.borrowed + const primaryValue = props.position.values.primary + props.position.values.borrowedPrimary + const secondaryValue = props.position.values.secondary + props.position.values.borrowedSecondary const difference = Math.abs(primaryValue - secondaryValue) if (difference < SWAP_THRESHOLD) return null diff --git a/src/components/fields/ActionsTooltip/components/UnlockContent.tsx b/src/components/fields/ActionsTooltip/components/UnlockContent.tsx index 7457524..4ec4fe4 100644 --- a/src/components/fields/ActionsTooltip/components/UnlockContent.tsx +++ b/src/components/fields/ActionsTooltip/components/UnlockContent.tsx @@ -15,14 +15,21 @@ export const UnlockContent = (props: Props) => { <>
  • {t('fields.removeLiquidity', { vault: props.vault.provider })}
  • - {t('redbank.repay')} - + {props.position.borrowDenom && ( + <> + {t('redbank.repay')} + + + )}
  • {t('redbank.withdraw')} diff --git a/src/components/fields/ActiveVaultsTable/ActiveVaultsTableMobile.tsx b/src/components/fields/ActiveVaultsTable/ActiveVaultsTableMobile.tsx index 743589b..34b0899 100644 --- a/src/components/fields/ActiveVaultsTable/ActiveVaultsTableMobile.tsx +++ b/src/components/fields/ActiveVaultsTable/ActiveVaultsTableMobile.tsx @@ -123,7 +123,10 @@ export const ActiveVaultsTableMobile = () => { @@ -136,9 +139,13 @@ export const ActiveVaultsTableMobile = () => {
    { cell: ({ row }) => { const primaryCoin = { denom: row.original.denoms.primary, - amount: row.original.position.amounts.primary.toString(), + amount: ( + row.original.position.amounts.primary + row.original.position.amounts.borrowedPrimary + ).toString(), } const secondaryCoin = { denom: row.original.denoms.secondary, amount: ( - row.original.position.amounts.secondary + row.original.position.amounts.borrowed + row.original.position.amounts.secondary + + row.original.position.amounts.borrowedSecondary ).toString(), } @@ -97,9 +100,13 @@ export const useActiveVaultsColumns = () => { } tooltip={ <> - + {Number(primaryCoin.amount) > 0 && ( + + )}
    - + {Number(secondaryCoin.amount) > 0 && ( + + )} } /> @@ -134,41 +141,50 @@ export const useActiveVaultsColumns = () => { } tooltip={ <> - + {Number(coins[0].amount) > 0 && ( + + )}
    - + {Number(coins[1].amount) > 0 && ( + + )}{' '} } /> ) }, }), - columnHelper.accessor('position.values.borrowed', { + columnHelper.accessor('position.values.borrowedPrimary', { enableSorting: true, header: () => ( ), - cell: (info) => { + cell: ({ row }) => { const borrowAsset = whitelistedAssets.find( - (asset) => asset.denom === info.row.original.denoms.secondary, + (asset) => asset.denom === row.original.position.borrowDenom, ) if (!borrowAsset) return + const borrowKey = + row.original.position.borrowDenom === row.original.denoms.primary + ? 'borrowedPrimary' + : 'borrowedSecondary' + return ( } tooltip={ {

    {formatValue( - ltvToLeverage(row.original.ltv.max), + ltvToLeverage(row.original.ltv.contract), 2, 2, false, @@ -303,7 +319,7 @@ export const useActiveVaultsColumns = () => { ) }, }), - columnHelper.accessor('position.amounts.borrowed', { + columnHelper.accessor('position.amounts.borrowedPrimary', { enableSorting: false, header: () => ( { ), cell: ({ row }) => { const maxBorrowValue = getMaxBorrowValue(row.original, row.original.position) + const borrowKey = + row.original.position.borrowDenom === row.original.denoms.primary + ? 'borrowedPrimary' + : 'borrowedSecondary' + + const liqBorrowValue = getLiqBorrowValue(row.original, row.original.position.values.net) return ( { >

    {availableVaults.map((vault, i) => { - const borrowAsset = redBankAssets.find((asset) => asset.denom === vault.denoms.secondary) - const maxBorrowRate = Number(borrowAsset?.borrowRate ?? 0) * vault.ltv.max + const primaryBorrowAsset = redBankAssets.find( + (asset) => asset.denom === vault.denoms.primary, + ) + const secondaryBorrowAsset = redBankAssets.find( + (asset) => asset.denom === vault.denoms.secondary, + ) + const borrowRate = Math.min( + Number(primaryBorrowAsset?.borrowRate ?? 0), + Number(secondaryBorrowAsset?.borrowRate ?? 0), + ) + const maxBorrowRate = borrowRate * (ltvToLeverage(vault.ltv.contract) - 1) const minAPY = new BigNumber(vault.apy || 0).toNumber() - const leverage = ltvToLeverage(vault.ltv.max) + const leverage = ltvToLeverage(vault.ltv.contract) const maxAPY = new BigNumber(minAPY).times(leverage).decimalPlaces(2).toNumber() - maxBorrowRate const apyDataNoLev = { total: vault.apy || 0, borrow: 0 } @@ -60,7 +69,10 @@ export const AvailableVaultsTableMobile = () => { hideStyling text={} tooltip={ - + } /> diff --git a/src/components/fields/AvailableVaultsTable/useAvailableVaultsColumns.tsx b/src/components/fields/AvailableVaultsTable/useAvailableVaultsColumns.tsx index 4939beb..01595f2 100644 --- a/src/components/fields/AvailableVaultsTable/useAvailableVaultsColumns.tsx +++ b/src/components/fields/AvailableVaultsTable/useAvailableVaultsColumns.tsx @@ -69,7 +69,7 @@ export const useAvailableVaultsColumns = () => { return ( <>

    - {formatValue(ltvToLeverage(row.original.ltv.max), 2, 2, false, false, 'x')} + {formatValue(ltvToLeverage(row.original.ltv.contract), 2, 2, false, false, 'x')}

    {t('global.max_lower')}

    @@ -87,12 +87,20 @@ export const useAvailableVaultsColumns = () => { return } - const maxLeverage = ltvToLeverage(row.original.ltv.max) - const borrowAsset = redBankAssets.find( + const maxLeverage = ltvToLeverage(row.original.ltv.contract) + const primaryBorrowAsset = redBankAssets.find( + (asset) => asset.denom === row.original.denoms.primary, + ) + const secondaryBorrowAsset = redBankAssets.find( (asset) => asset.denom === row.original.denoms.secondary, ) - const maxBorrowRate = - Number(borrowAsset?.borrowRate ?? 0) * (ltvToLeverage(row.original.ltv.max) - 1) + + const borrowRate = Math.min( + Number(primaryBorrowAsset?.borrowRate ?? 0), + Number(secondaryBorrowAsset?.borrowRate ?? 0), + ) + + const maxBorrowRate = borrowRate * (ltvToLeverage(row.original.ltv.contract) - 1) const minAPY = new BigNumber(row.original.apy).toNumber() @@ -128,7 +136,7 @@ export const useAvailableVaultsColumns = () => { /> } tooltip={ - + } /> diff --git a/src/components/fields/Breakdown/BreakdownGraph/BreakdownGraph.tsx b/src/components/fields/Breakdown/BreakdownGraph/BreakdownGraph.tsx index 185c4d0..ba50b6d 100644 --- a/src/components/fields/Breakdown/BreakdownGraph/BreakdownGraph.tsx +++ b/src/components/fields/Breakdown/BreakdownGraph/BreakdownGraph.tsx @@ -18,7 +18,11 @@ export const BreakdownGraph = (props: Props) => { const { t } = useTranslation() const convertToDisplayCurrency = useStore((s) => s.convertToDisplayCurrency) const baseCurrency = useStore((s) => s.baseCurrency) - const yAxisLimit = Math.max(props.position.values.net, props.position.values.borrowed) + const borrowKey = + props.position.borrowDenom === props.vault.denoms.primary + ? 'borrowedPrimary' + : 'borrowedSecondary' + const yAxisLimit = Math.max(props.position.values.net, props.position.values[borrowKey]) const containerClasses = classNames([ styles.container, @@ -40,7 +44,7 @@ export const BreakdownGraph = (props: Props) => { denom: props.vault.denoms.primary, }), convertToDisplayCurrency({ - amount: props.position.values.borrowed.toString(), + amount: props.position.values[borrowKey].toString(), denom: props.vault.denoms.primary, }), ], @@ -123,7 +127,11 @@ export const BreakdownGraph = (props: Props) => {
    diff --git a/src/components/fields/Breakdown/BreakdownTable/BreakdownTable.module.scss b/src/components/fields/Breakdown/BreakdownTable/BreakdownTable.module.scss index b2982c4..613a828 100644 --- a/src/components/fields/Breakdown/BreakdownTable/BreakdownTable.module.scss +++ b/src/components/fields/Breakdown/BreakdownTable/BreakdownTable.module.scss @@ -29,6 +29,10 @@ width: 100%; border-collapse: collapse; + td { + min-width: space(12); + } + tr { td:nth-child(2) { width: 20px; diff --git a/src/components/fields/Breakdown/BreakdownTable/BreakdownTable.tsx b/src/components/fields/Breakdown/BreakdownTable/BreakdownTable.tsx index fd7013e..9cd5b3a 100644 --- a/src/components/fields/Breakdown/BreakdownTable/BreakdownTable.tsx +++ b/src/components/fields/Breakdown/BreakdownTable/BreakdownTable.tsx @@ -47,10 +47,16 @@ export const BreakdownTable = (props: Props) => { const primaryPrice = usePrice(props.vault.denoms.primary) const secondaryPrice = usePrice(props.vault.denoms.secondary) + const primaryRedBankAsset = useRedBankAsset(props.vault.denoms.primary) const secondaryRedBankAsset = useRedBankAsset(props.vault.denoms.secondary) const primaryChange = props.newPosition.amounts.primary - props.prevPosition.amounts.primary const secondaryChange = props.newPosition.amounts.secondary - props.prevPosition.amounts.secondary - const borrowedChange = props.newPosition.amounts.borrowed - props.prevPosition.amounts.borrowed + const borrowKey = + props.newPosition.borrowDenom === props.vault.denoms.primary + ? 'borrowedPrimary' + : 'borrowedSecondary' + const borrowedChange = + props.newPosition.amounts[borrowKey] - props.prevPosition.amounts[borrowKey] const containerClasses = classNames([ props.className, @@ -71,15 +77,15 @@ export const BreakdownTable = (props: Props) => { denom = props.vault.denoms.secondary break case AmountType.DEBT: - amount = props.newPosition.amounts.borrowed + amount = props.newPosition.amounts[borrowKey] denom = props.vault.denoms.secondary break case AmountType.POSITION_PRIMARY: - amount = props.newPosition.amounts.primary + amount = props.newPosition.amounts.primary + props.newPosition.amounts.borrowedPrimary denom = props.vault.denoms.primary break case AmountType.POSITION_SECONDARY: - amount = props.newPosition.amounts.secondary + props.newPosition.amounts.borrowed + amount = props.newPosition.amounts.secondary + props.newPosition.amounts.borrowedSecondary denom = props.vault.denoms.secondary break } @@ -122,7 +128,9 @@ export const BreakdownTable = (props: Props) => { ) } - const getValueText = (type: 'net' | 'borrowed' | 'total') => ( + const getValueText = ( + type: 'primary' | 'secondary' | 'net' | 'borrowedPrimary' | 'borrowedSecondary' | 'total', + ) => ( { ) } - const isReducingDebt = props.newPosition.amounts.borrowed < props.prevPosition.amounts.borrowed + const isReducingDebt = + props.newPosition.amounts[borrowKey] < props.prevPosition.amounts[borrowKey] if (isReducingDebt) { return ( <> @@ -208,8 +217,12 @@ export const BreakdownTable = (props: Props) => { /* APY CALCULATION */ const currentLeverage = props.newPosition.currentLeverage - const trueBorrowRate = - Number(secondaryRedBankAsset?.borrowRate ?? 0) * (Number(currentLeverage) - 1) + const borrowRate = Number( + borrowKey === 'borrowedPrimary' + ? primaryRedBankAsset?.borrowRate + : secondaryRedBankAsset?.borrowRate, + ) + const trueBorrowRate = borrowRate * (Number(currentLeverage) - 1) const apy = (props.vault.apy || 0) * currentLeverage - trueBorrowRate @@ -291,9 +304,11 @@ export const BreakdownTable = (props: Props) => { {t('common.debt')} {getTokenBalance(AmountType.DEBT)} - {secondaryAsset?.symbol} + + {borrowKey === 'borrowedPrimary' ? primaryAsset?.symbol : secondaryAsset?.symbol} + {getChangeText(borrowedChange, 'secondary', true)} - {getValueText('borrowed')} + {getValueText(borrowKey)} @@ -320,11 +335,14 @@ export const BreakdownTable = (props: Props) => { {!props.isRepay &&
    {getWarningMessage()}
    } + diff --git a/src/components/fields/PositionInput/BorrowInput/BorrowInput.tsx b/src/components/fields/PositionInput/BorrowInput/BorrowInput.tsx index d1cbd4b..931c410 100644 --- a/src/components/fields/PositionInput/BorrowInput/BorrowInput.tsx +++ b/src/components/fields/PositionInput/BorrowInput/BorrowInput.tsx @@ -2,6 +2,7 @@ import classNames from 'classnames' import { Tutorial } from 'components/common' import { TokenInput } from 'components/fields' import { FIELDS_TUTORIAL_KEY } from 'constants/appConstants' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import useStore from 'store' @@ -9,33 +10,106 @@ import styles from './BorrowInput.module.scss' interface Props { vault: Vault - borrowAmount: number + borrowedPrimaryAmount: number + borrowedSecondaryAmount: number maxAmount: number - onChange: (amount: number) => void + prevPosition?: Position + onChangePrimary: (amount: number) => void + onChangeSecondary: (amount: number) => void } export const BorrowInput = (props: Props) => { const { t } = useTranslation() const redBankAssets = useStore((s) => s.redBankAssets) const showTutorial = !localStorage.getItem(FIELDS_TUTORIAL_KEY) - const asset = redBankAssets.find((asset) => asset.denom === props.vault.denoms.secondary) + const primaryAsset = redBankAssets.find((asset) => asset.denom === props.vault.denoms.primary) + const secondaryAsset = redBankAssets.find((asset) => asset.denom === props.vault.denoms.secondary) const containerClasses = classNames([styles.container]) - const input: Input = { - visible: true, + const [cachedPrimaryAmount, setCachedPrimaryAmount] = useState(props.borrowedPrimaryAmount) + const [cachedSecondaryAmount, setCachedSecondaryAmount] = useState(props.borrowedSecondaryAmount) + + const primaryInputVisisble = + props.borrowedPrimaryAmount > 0 || + props.prevPosition?.borrowDenom === props.vault.denoms.primary + ? true + : false + + const [primaryInput, setPrimaryInput] = useState({ + visible: primaryInputVisisble, + denom: props.vault.denoms.primary, + symbol: props.vault.symbols.primary, + }) + + const [secondaryInput, setSecondaryInput] = useState({ + visible: !primaryInputVisisble, denom: props.vault.denoms.secondary, symbol: props.vault.symbols.secondary, + }) + + const selectInput = (symbol: string) => { + if (symbol === primaryInput.symbol) { + primaryInput.visible = true + secondaryInput.visible = false + setCachedSecondaryAmount(props.borrowedSecondaryAmount) + props.onChangePrimary(cachedPrimaryAmount) + } else { + primaryInput.visible = false + secondaryInput.visible = true + setCachedPrimaryAmount(props.borrowedPrimaryAmount) + props.onChangeSecondary(cachedSecondaryAmount) + } + + setPrimaryInput({ ...primaryInput }) + setSecondaryInput({ ...secondaryInput }) + } + + const onChangePrimary = (amount: number) => { + setCachedPrimaryAmount(amount) + props.onChangePrimary(amount) + } + + const onChangeSecondary = (amount: number) => { + setCachedSecondaryAmount(amount) + props.onChangeSecondary(amount) } const tokenInput = ( - + <> + {primaryInput.visible && ( + + )} + {secondaryInput.visible && ( + + )} + ) return ( diff --git a/src/components/fields/PositionInput/LeverageSlider/LeverageSlider.tsx b/src/components/fields/PositionInput/LeverageSlider/LeverageSlider.tsx index cc3c4aa..28bc85d 100644 --- a/src/components/fields/PositionInput/LeverageSlider/LeverageSlider.tsx +++ b/src/components/fields/PositionInput/LeverageSlider/LeverageSlider.tsx @@ -2,7 +2,7 @@ import BigNumber from 'bignumber.js' import classNames from 'classnames' import { InputSlider, Tutorial } from 'components/common' import { FIELDS_TUTORIAL_KEY } from 'constants/appConstants' -import { formatValue, ltvToLeverage } from 'libs/parse' +import { formatValue, leverageToLtv, ltvToLeverage } from 'libs/parse' import React from 'react' import { useTranslation } from 'react-i18next' import colors from 'styles/_assets.module.scss' @@ -29,7 +29,7 @@ export const LeverageSlider = (props: Props) => { isLeverage value={props.leverage} maxValue={props.leverageMax} - leverageMax={ltvToLeverage(props.vault.ltv.max)} + leverageMax={ltvToLeverage(props.vault.ltv.contract)} leverageLimit={props.leverageLimit} onChange={props.onChange} sliderColor={colors.secondary} @@ -51,8 +51,11 @@ export const LeverageSlider = (props: Props) => { ) : ( <>{slider} )} + {leverageToLtv(props.leverage) > props.vault.ltv.max && ( + {t('fields.messages.closeToMaxLtv')} + )} {props.leverage >= props.leverageLimit && - props.leverageLimit < ltvToLeverage(props.vault.ltv.max) && ( + props.leverageLimit < ltvToLeverage(props.vault.ltv.contract) && ( {t('fields.messages.unableToIncreaseLeverage', { leverage: formatValue(props.leverageLimit, 2, 2), diff --git a/src/components/fields/PositionInput/PositionInput.module.scss b/src/components/fields/PositionInput/PositionInput.module.scss index 770e296..c8d3cab 100644 --- a/src/components/fields/PositionInput/PositionInput.module.scss +++ b/src/components/fields/PositionInput/PositionInput.module.scss @@ -54,6 +54,6 @@ @media only screen and (min-width: $bpMediumHigh) { .grid { grid-template-areas: 'supply leverage borrow'; - grid-template-columns: auto 2fr 1fr; + grid-auto-columns: auto; } } diff --git a/src/components/fields/PositionInput/PositionInput.tsx b/src/components/fields/PositionInput/PositionInput.tsx index a08ee98..5154449 100644 --- a/src/components/fields/PositionInput/PositionInput.tsx +++ b/src/components/fields/PositionInput/PositionInput.tsx @@ -20,7 +20,9 @@ export const PositionInput = (props: Props) => { const marketAssetLiquidity = useStore((s) => s.marketAssetLiquidity) const convertToBaseCurrency = useStore((s) => s.convertToBaseCurrency) const convertValueToAmount = useStore((s) => s.convertValueToAmount) - const [maxAllowedLeverage, setMaxAllowedLeverage] = useState(ltvToLeverage(props.vault.ltv.max)) + const [maxAllowedLeverage, setMaxAllowedLeverage] = useState( + ltvToLeverage(props.vault.ltv.contract), + ) const tutorialStep = useStore((s) => s.tutorialSteps['fields']) const showTutorial = !localStorage.getItem(FIELDS_TUTORIAL_KEY) @@ -33,13 +35,26 @@ export const PositionInput = (props: Props) => { denom: props.vault.denoms.secondary, amount: position.amounts.secondary.toString(), }) - const borrowValue = convertToBaseCurrency({ - denom: props.vault.denoms.secondary, - amount: position.amounts.borrowed.toString(), + const borrowedPrimaryValue = convertToBaseCurrency({ + denom: props.vault.denoms.primary, + amount: position.amounts.borrowedPrimary.toString(), }) - position.values.borrowed = borrowValue - position.values.total = borrowValue + primaryValue + secondaryValue + const borrowedSecondaryValue = convertToBaseCurrency({ + denom: props.vault.denoms.secondary, + amount: position.amounts.borrowedSecondary.toString(), + }) + position.values.borrowedPrimary = borrowedPrimaryValue + position.values.borrowedSecondary = borrowedSecondaryValue + position.values.total = + borrowedPrimaryValue + borrowedSecondaryValue + primaryValue + secondaryValue position.values.net = primaryValue + secondaryValue + + if (borrowedPrimaryValue > 0) { + props.position.borrowDenom = props.vault.denoms.primary + } else if (borrowedSecondaryValue > 0) { + props.position.borrowDenom = props.vault.denoms.secondary + } + return position } @@ -53,20 +68,27 @@ export const PositionInput = (props: Props) => { computeBorrowAmount() } - const onBorrowChange = (amount: number) => { + const onBorrowChange = (amount: number, type: 'primary' | 'secondary') => { + props.position.borrowDenom = + type === 'primary' ? props.vault.denoms.primary : props.vault.denoms.secondary const borrowValue = convertToBaseCurrency({ - denom: props.vault.denoms.secondary, + denom: props.position.borrowDenom, amount: amount.toString(), }) - props.position.amounts.borrowed = amount - props.position.values.borrowed = borrowValue + const borrowKey = type === 'primary' ? 'borrowedPrimary' : 'borrowedSecondary' + const secondaryBorrowKey = type !== 'primary' ? 'borrowedPrimary' : 'borrowedSecondary' + props.position.amounts[borrowKey] = amount + props.position.amounts[secondaryBorrowKey] = 0 + props.position.values[borrowKey] = borrowValue + props.position.values[secondaryBorrowKey] = 0 props.position.currentLeverage = getLeverageFromValues(props.position.values) props.setPosition({ ...updateValues(props.position) }) + computeBorrowAmount() } const computeMaxAllowedLeverage = () => { const maxBorrowValue = convertToBaseCurrency({ - denom: props.vault.denoms.secondary, + denom: props.position.borrowDenom || props.vault.denoms.secondary, amount: computeMaxBorrowAmount().toString(), }) @@ -76,16 +98,20 @@ export const PositionInput = (props: Props) => { props.position.values.primary, props.position.values.secondary, ) - : ltvToLeverage(props.vault.ltv.max) + : ltvToLeverage(props.vault.ltv.contract) } const computeBorrowAmount = (leverage?: number) => { const supplyBorrowRatio = leverage ? leverage - 1 : props.position.currentLeverage - 1 const supplyValue = props.position.values.primary + props.position.values.secondary const borrowValue = new BigNumber(supplyValue).times(supplyBorrowRatio).toNumber() + const borrowDenom = props.position.borrowDenom + ? props.position.borrowDenom + : props.vault.denoms.secondary + const borrowAmount = Math.floor( convertValueToAmount({ - denom: props.vault.denoms.secondary, + denom: borrowDenom, amount: borrowValue.toString(), }), ) @@ -97,12 +123,15 @@ export const PositionInput = (props: Props) => { : props.position.currentLeverage const maxAllowedLeverage = computeMaxAllowedLeverage() + const borrowKey = + borrowDenom === props.vault.denoms.primary ? 'borrowedPrimary' : 'borrowedSecondary' + if (targetLeverage > maxAllowedLeverage) { const borrowAmount = computeMaxBorrowAmount() - props.position.amounts.borrowed = borrowAmount + props.position.amounts[borrowKey] = borrowAmount props.position.currentLeverage = maxAllowedLeverage } else { - props.position.amounts.borrowed = borrowAmount + props.position.amounts[borrowKey] = borrowAmount props.position.currentLeverage = targetLeverage } @@ -111,25 +140,28 @@ export const PositionInput = (props: Props) => { } const computeMaxBorrowAmount = () => { + const borrowDenom = props.position.borrowDenom || props.vault.denoms.secondary + const maxAmount = Math.floor( convertValueToAmount({ - denom: props.vault.denoms.secondary, + denom: borrowDenom, amount: getMaxBorrowValue(props.vault, props.position).toString(), }), ) const marketLiquidity = Number( - marketAssetLiquidity.find((market) => market.denom === props.vault.denoms.secondary) - ?.amount || 0, + marketAssetLiquidity.find((market) => market.denom === borrowDenom)?.amount || 0, ) + const borrowKey = + borrowDenom === props.vault.denoms.primary ? 'borrowedPrimary' : 'borrowedSecondary' const maxBorrowAmount = Math.min( maxAmount, - (props.prevPosition?.amounts.borrowed || 0) + marketLiquidity, + (props.prevPosition?.amounts[borrowKey] || 0) + marketLiquidity, ) if (props.prevPosition) { - return Math.max(maxBorrowAmount, props.prevPosition.amounts.borrowed) + return Math.max(maxBorrowAmount, props.prevPosition.amounts[borrowKey]) } return maxBorrowAmount @@ -154,7 +186,7 @@ export const PositionInput = (props: Props) => { leverage={props.position.currentLeverage || 1} leverageLimit={maxAllowedLeverage} leverageMax={Math.max( - ltvToLeverage(props.vault.ltv.max), + ltvToLeverage(props.vault.ltv.contract), props.prevPosition?.currentLeverage || 1, )} onChange={computeBorrowAmount} @@ -162,10 +194,13 @@ export const PositionInput = (props: Props) => { onBorrowChange(amount, 'primary')} + onChangeSecondary={(amount) => onBorrowChange(amount, 'secondary')} maxAmount={computeMaxBorrowAmount()} vault={props.vault} + prevPosition={props.prevPosition} />
    diff --git a/src/components/fields/PositionInput/TokenInput/TokenInput.module.scss b/src/components/fields/PositionInput/TokenInput/TokenInput.module.scss index a11c9b7..bd75cb2 100644 --- a/src/components/fields/PositionInput/TokenInput/TokenInput.module.scss +++ b/src/components/fields/PositionInput/TokenInput/TokenInput.module.scss @@ -5,6 +5,7 @@ flex-direction: column; gap: space(2); flex: 1; + min-width: rem-calc(160); .input { display: flex; diff --git a/src/components/fields/PositionInput/TokenInput/TokenInput.tsx b/src/components/fields/PositionInput/TokenInput/TokenInput.tsx index 4e7e0c7..d32ec76 100644 --- a/src/components/fields/PositionInput/TokenInput/TokenInput.tsx +++ b/src/components/fields/PositionInput/TokenInput/TokenInput.tsx @@ -16,6 +16,7 @@ interface Props { maxAmount?: number maxAmountLabel: string borrowRate?: number + disableGasWarning?: boolean onChange: (amount: number) => void onSelect?: (denom: string) => void } @@ -62,7 +63,10 @@ export const TokenInput = (props: Props) => { 10 ** asset.decimals const showGasWarning = - props.maxAmount && props.amount >= props.maxAmount && asset.denom === baseCurrency.denom + props.maxAmount && + props.amount >= props.maxAmount && + asset.denom === baseCurrency.denom && + !props.disableGasWarning return (
    diff --git a/src/components/fields/RepayInput/RepayInput.tsx b/src/components/fields/RepayInput/RepayInput.tsx index d6b6cab..5144e2d 100644 --- a/src/components/fields/RepayInput/RepayInput.tsx +++ b/src/components/fields/RepayInput/RepayInput.tsx @@ -19,18 +19,28 @@ interface Props { export const RepayInput = (props: Props) => { const { t } = useTranslation() const convertToBaseCurrency = useStore((s) => s.convertToBaseCurrency) - const debtAmount = props.prevPosition.amounts.borrowed + const borrowKey = + props.position.borrowDenom === props.vault.denoms.primary + ? 'borrowedPrimary' + : 'borrowedSecondary' + const supplyKey = borrowKey === 'borrowedPrimary' ? 'primary' : 'secondary' + const debtAmount = props.prevPosition.amounts[borrowKey] const userBalances = useStore((s) => s.userBalances) + const borrowSymbol = + props.position.borrowDenom === props.vault.denoms.primary + ? props.vault.symbols.primary + : props.vault.symbols.secondary + const walletBalance = Number( (findByDenom(userBalances, props.vault.denoms.secondary) as Coin)?.amount || 0, ) const maxRepayAmount = Math.min(walletBalance, debtAmount) - const amount = props.prevPosition.amounts.borrowed - props.position.amounts.borrowed + const amount = props.prevPosition.amounts[borrowKey] - props.position.amounts[borrowKey] const maxValue = Math.max( getLeverageFromValues(props.prevPosition.values), - ltvToLeverage(props.vault.ltv.max), + ltvToLeverage(props.vault.ltv.contract), ) const updateValues = (position: Position) => { @@ -42,21 +52,29 @@ export const RepayInput = (props: Props) => { denom: props.vault.denoms.secondary, amount: position.amounts.secondary.toString(), }) - const borrowValue = convertToBaseCurrency({ - denom: props.vault.denoms.secondary, - amount: position.amounts.borrowed.toString(), + const borrowedPrimaryValue = convertToBaseCurrency({ + denom: props.vault.denoms.primary, + amount: position.amounts.borrowedPrimary.toString(), }) + const borrowedSecondaryValue = convertToBaseCurrency({ + denom: props.vault.denoms.secondary, + amount: position.amounts.borrowedSecondary.toString(), + }) + position.values.primary = primaryValue position.values.secondary = secondaryValue - position.values.borrowed = borrowValue - position.values.total = borrowValue + primaryValue + secondaryValue + position.values.borrowedPrimary = borrowedPrimaryValue + position.values.borrowedSecondary = borrowedSecondaryValue + position.values.total = + borrowedPrimaryValue + borrowedSecondaryValue + primaryValue + secondaryValue position.values.net = primaryValue + secondaryValue position.currentLeverage = getLeverageFromValues(position.values) + return position } const handleChange = (amount: number) => { - props.position.amounts.borrowed = props.prevPosition.amounts.borrowed - amount - props.position.amounts.secondary = props.prevPosition.amounts.secondary + amount + props.position.amounts[borrowKey] = props.prevPosition.amounts[borrowKey] - amount + props.position.amounts[supplyKey] = props.prevPosition.amounts[supplyKey] + amount props.setPosition({ ...updateValues(props.position) }) } @@ -66,21 +84,22 @@ export const RepayInput = (props: Props) => {

    {t('fields.repayDebt')}

    {t('fields.repayingDebtFromWallet')} diff --git a/src/components/redbank/AssetTable/AssetTable.tsx b/src/components/redbank/AssetTable/AssetTable.tsx index f62b65f..9abc5c5 100644 --- a/src/components/redbank/AssetTable/AssetTable.tsx +++ b/src/components/redbank/AssetTable/AssetTable.tsx @@ -91,6 +91,12 @@ export const AssetTable = ({ data, columns, type, disabled = false }: Props) => {table.getRowModel().rows.map((row) => { + if ( + (type === 'deposit' && !row.original.depositEnabled) || + (type === 'borrow' && !row.original.borrowEnabled) + ) + return null + if (row.depth === 1) { return ( diff --git a/src/components/redbank/AssetTable/MetricsRow.tsx b/src/components/redbank/AssetTable/MetricsRow.tsx index b097dc6..8d06995 100644 --- a/src/components/redbank/AssetTable/MetricsRow.tsx +++ b/src/components/redbank/AssetTable/MetricsRow.tsx @@ -86,14 +86,16 @@ export const MetricsRow = ({ row, type }: Props) => { valueClass='s' labelClass='xs faded' /> - + {row.original.borrowEnabled && ( + + )}
    diff --git a/src/components/redbank/BorrowColumns/useBorrowColumns.tsx b/src/components/redbank/BorrowColumns/useBorrowColumns.tsx index 24ee30e..ca89631 100644 --- a/src/components/redbank/BorrowColumns/useBorrowColumns.tsx +++ b/src/components/redbank/BorrowColumns/useBorrowColumns.tsx @@ -70,17 +70,15 @@ export const useBorrowColumns = () => { header: () => ( ), - cell: (info) => { - return ( - - ) - }, + cell: (info) => ( + + ), }), columnHelper.accessor('marketLiquidity', { enableSorting: enableSorting, @@ -100,15 +98,13 @@ export const useBorrowColumns = () => { }), columnHelper.display({ id: 'actions', - cell: ({ row }) => { - return ( -