diff --git a/package.json b/package.json index 32b508cf..3a7d0b90 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mars-v2-frontend", - "version": "2.0.3", + "version": "2.0.4", "private": true, "scripts": { "build": "yarn validate-env && next build", @@ -24,7 +24,7 @@ "dependencies": { "@cosmjs/cosmwasm-stargate": "^0.31.1", "@delphi-labs/shuttle-react": "^3.10.0", - "@keplr-wallet/cosmos": "^0.12.39", + "@keplr-wallet/cosmos": "^0.12.42", "@sentry/nextjs": "^7.74.0", "@splinetool/react-spline": "^2.2.6", "@splinetool/runtime": "^0.9.482", diff --git a/public/images/tokens/akt.svg b/public/images/tokens/akt.svg new file mode 100644 index 00000000..0d58ae4d --- /dev/null +++ b/public/images/tokens/akt.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/images/tokens/atom.svg b/public/images/tokens/atom.svg index ed377e8b..9db9255b 100644 --- a/public/images/tokens/atom.svg +++ b/public/images/tokens/atom.svg @@ -1,44 +1,30 @@ - - cosmos-atom-logo - - - - - - - - - + + + + + + + + + + diff --git a/public/images/tokens/axl.svg b/public/images/tokens/axl.svg index 69026781..4a79129b 100644 --- a/public/images/tokens/axl.svg +++ b/public/images/tokens/axl.svg @@ -1,15 +1,10 @@ - - + - - diff --git a/public/images/tokens/axlusdc.svg b/public/images/tokens/axlusdc.svg index fe97b87a..525a1573 100644 --- a/public/images/tokens/axlusdc.svg +++ b/public/images/tokens/axlusdc.svg @@ -1,11 +1,35 @@ - - - - - - - - - - + + + + + + + + + + + diff --git a/public/images/tokens/axlwbtc.svg b/public/images/tokens/axlwbtc.svg index 04f937ab..8f0e4c11 100644 --- a/public/images/tokens/axlwbtc.svg +++ b/public/images/tokens/axlwbtc.svg @@ -1,23 +1,47 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/public/images/tokens/axlweth.svg b/public/images/tokens/axlweth.svg index 3698ec59..4f2cd6ca 100644 --- a/public/images/tokens/axlweth.svg +++ b/public/images/tokens/axlweth.svg @@ -1,18 +1,31 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/public/images/tokens/dydx.svg b/public/images/tokens/dydx.svg new file mode 100644 index 00000000..9b3c2831 --- /dev/null +++ b/public/images/tokens/dydx.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + diff --git a/public/images/tokens/inj.svg b/public/images/tokens/inj.svg new file mode 100644 index 00000000..73b0ab30 --- /dev/null +++ b/public/images/tokens/inj.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/public/images/tokens/statom.svg b/public/images/tokens/statom.svg index 73781bbe..80c18af9 100644 --- a/public/images/tokens/statom.svg +++ b/public/images/tokens/statom.svg @@ -1,38 +1,40 @@ - - - - + + + + diff --git a/public/images/tokens/stosmo.svg b/public/images/tokens/stosmo.svg new file mode 100644 index 00000000..cd1ed2d0 --- /dev/null +++ b/public/images/tokens/stosmo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/images/tokens/tia.svg b/public/images/tokens/tia.svg new file mode 100644 index 00000000..0a831bb5 --- /dev/null +++ b/public/images/tokens/tia.svg @@ -0,0 +1,46 @@ + + + + diff --git a/public/images/tokens/usdt.svg b/public/images/tokens/usdt.svg new file mode 100644 index 00000000..f90ac0ec --- /dev/null +++ b/public/images/tokens/usdt.svg @@ -0,0 +1,10 @@ + + + + diff --git a/src/api/hls/getHLSStakingAssets.ts b/src/api/hls/getHLSStakingAssets.ts index 8f2ad39a..41aa22c8 100644 --- a/src/api/hls/getHLSStakingAssets.ts +++ b/src/api/hls/getHLSStakingAssets.ts @@ -26,6 +26,7 @@ export default async function getHLSStakingAssets() { used: BN(depositCap.amount), max: BN(depositCap.cap), }, + apy: 18, // TODO: Actually implement the APY here! } as HLSStrategy }) }) diff --git a/src/api/prices/getMarsPrice.ts b/src/api/prices/getMarsPrice.ts index f9eb3656..906ad8df 100644 --- a/src/api/prices/getMarsPrice.ts +++ b/src/api/prices/getMarsPrice.ts @@ -1,13 +1,10 @@ -import { ASSETS, MARS_MAINNET_DENOM } from 'constants/assets' -import { bySymbol } from 'utils/array' import getPoolPrice from 'api/prices/getPoolPrice' +import { ASSETS } from 'constants/assets' +import { bySymbol } from 'utils/array' async function getMarsPrice() { - const marsAsset = { - ...(ASSETS.find(bySymbol('MARS')) as Asset), - denom: MARS_MAINNET_DENOM, - } - + const marsAsset = ASSETS.find(bySymbol('MARS')) + if (!marsAsset) return 0 return await getPoolPrice(marsAsset) } diff --git a/src/api/prices/getPoolPrice.ts b/src/api/prices/getPoolPrice.ts index 22913890..6481d6b5 100644 --- a/src/api/prices/getPoolPrice.ts +++ b/src/api/prices/getPoolPrice.ts @@ -41,7 +41,21 @@ const getAssetRate = async (asset: Asset) => { `poolPrices/${(asset.poolId || 0).toString()}`, 60, ) - return calculateSpotPrice(response.pool.pool_assets, asset) + const pool = response.pool + const poolAssets: PoolAsset[] = pool.scaling_factor_controller + ? [ + { + token: pool.pool_liquidity[0], + weight: 1, + }, + { + token: pool.pool_liquidity[1], + weight: 1, + }, + ] + : pool.pool_assets + + return calculateSpotPrice(poolAssets, asset) } const calculateSpotPrice = (poolAssets: PoolAsset[], asset: Asset): [BigNumber, PoolAsset] => { diff --git a/src/components/Borrow/Table/Columns/BorrowRate.tsx b/src/components/Borrow/Table/Columns/BorrowRate.tsx index 44cccfa6..4aaff79a 100644 --- a/src/components/Borrow/Table/Columns/BorrowRate.tsx +++ b/src/components/Borrow/Table/Columns/BorrowRate.tsx @@ -1,9 +1,7 @@ -import React from 'react' - import { FormattedNumber } from 'components/FormattedNumber' import Loading from 'components/Loading' -export const BORROW_RATE_META = { accessorKey: 'borrowRate', header: 'Borrow Rate' } +export const BORROW_RATE_META = { accessorKey: 'borrowRate', header: 'Borrow Rate APY' } interface Props { borrowRate: number | null diff --git a/src/components/HLS/Staking/Table/Columns/Manage.tsx b/src/components/HLS/Staking/Table/Columns/Manage.tsx index ca4c3c85..94141d19 100644 --- a/src/components/HLS/Staking/Table/Columns/Manage.tsx +++ b/src/components/HLS/Staking/Table/Columns/Manage.tsx @@ -61,7 +61,7 @@ export default function Manage(props: Props) { onClick: () => closeHlsStakingPosition({ accountId: props.account.id, actions }), }, ], - [actions, closeHlsStakingPosition, openModal, props.account.id], + [actions, closeHlsStakingPosition, hasNoDebt, openModal, props.account.id], ) return diff --git a/src/components/Header/DesktopHeader.tsx b/src/components/Header/DesktopHeader.tsx index ba6be5a5..367c9b0a 100644 --- a/src/components/Header/DesktopHeader.tsx +++ b/src/components/Header/DesktopHeader.tsx @@ -17,7 +17,7 @@ export const menuTree: { pages: Page[]; label: string }[] = [ { pages: ['lend', 'farm'], label: 'Earn' }, { pages: ['borrow'], label: 'Borrow' }, { pages: ['portfolio'], label: 'Portfolio' }, - ...(ENABLE_HLS ? [{ pages: ['hls-farm', 'hls-staking'] as Page[], label: 'High Leverage' }] : []), + ...(ENABLE_HLS ? [{ pages: ['hls-staking'] as Page[], label: 'High Leverage' }] : []), ] export default function DesktopHeader() { diff --git a/src/components/Modals/BorrowModal.tsx b/src/components/Modals/BorrowModal.tsx index a94b8c85..78df7e06 100644 --- a/src/components/Modals/BorrowModal.tsx +++ b/src/components/Modals/BorrowModal.tsx @@ -202,7 +202,7 @@ function BorrowModal(props: Props) {
{totalDebt.isGreaterThan(0) && ( <> diff --git a/src/components/Modals/HLS/Deposit/Leverage.tsx b/src/components/Modals/HLS/Deposit/Leverage.tsx index 3c1f4f02..105fdd01 100644 --- a/src/components/Modals/HLS/Deposit/Leverage.tsx +++ b/src/components/Modals/HLS/Deposit/Leverage.tsx @@ -12,20 +12,28 @@ interface Props { onChangeAmount: (amount: BigNumber) => void onClickBtn: () => void positionValue: BigNumber + leverage: number + maxLeverage: number } export default function Leverage(props: Props) { return ( -
+
- -
) } diff --git a/src/components/Modals/HLS/Deposit/index.tsx b/src/components/Modals/HLS/Deposit/index.tsx index 1a619753..176f802a 100644 --- a/src/components/Modals/HLS/Deposit/index.tsx +++ b/src/components/Modals/HLS/Deposit/index.tsx @@ -16,6 +16,7 @@ interface Props { borrowAsset: Asset collateralAsset: Asset vaultAddress: string | null + strategy?: HLSStrategy } export default function Controller(props: Props) { @@ -46,19 +47,24 @@ export default function Controller(props: Props) { /> ) - return ( - - ) + if (props.strategy) { + return ( + + ) + } + + return null } interface ContentProps { @@ -120,7 +126,11 @@ function Vault(props: VaultContentProps) { return } -function StakingContent(props: ContentProps) { +interface StakingContentProps extends ContentProps { + strategy: HLSStrategy +} + +function StakingContent(props: StakingContentProps) { const { depositAmount, onChangeCollateral, @@ -152,10 +162,11 @@ function StakingContent(props: ContentProps) { positionValue, selectedAccount: props.selectedAccount, setSelectedAccount: props.setSelectedAccount, + strategy: props.strategy, toggleIsOpen: props.toggleIsOpen, updatedAccount, maxBorrowAmount, - apy: 0, // TODO: Implement APY + apy: props.strategy.apy || 0, // TODO: Implement APY walletCollateralAsset: props.walletCollateralAsset, }) diff --git a/src/components/Modals/HLS/Deposit/useAccordionItems.tsx b/src/components/Modals/HLS/Deposit/useAccordionItems.tsx index c93ccf77..f36db15b 100644 --- a/src/components/Modals/HLS/Deposit/useAccordionItems.tsx +++ b/src/components/Modals/HLS/Deposit/useAccordionItems.tsx @@ -29,6 +29,7 @@ interface Props { positionValue: BigNumber selectedAccount: Account | null setSelectedAccount: (account: Account) => void + strategy?: HLSStrategy toggleIsOpen: (index: number) => void updatedAccount: Account | undefined walletCollateralAsset: Coin | undefined @@ -64,12 +65,14 @@ export default function useAccordionItems(props: Props) { title: 'Leverage', renderContent: () => ( props.toggleIsOpen(2)} max={props.maxBorrowAmount} positionValue={props.positionValue} + maxLeverage={props.strategy?.maxLeverage || 1} /> ), renderSubTitle: () => ( diff --git a/src/components/Modals/HLS/Deposit/useStakingController.tsx b/src/components/Modals/HLS/Deposit/useStakingController.tsx index bcc0e0de..7a54ba32 100644 --- a/src/components/Modals/HLS/Deposit/useStakingController.tsx +++ b/src/components/Modals/HLS/Deposit/useStakingController.tsx @@ -23,8 +23,6 @@ export default function useStakingController(props: Props) { setBorrowAmount, borrowAmount, positionValue, - borrowCoin, - depositCoin, actions, } = useDepositHlsVault({ collateralDenom: collateralAsset.denom, @@ -40,6 +38,7 @@ export default function useStakingController(props: Props) { }, [computeMaxBorrowAmount, props.borrowAsset.denom]) const execute = useCallback(() => { + useStore.setState({ hlsModal: null }) addToStakingStrategy({ actions, accountId: selectedAccount.id, diff --git a/src/components/Modals/HLS/Manage/ChangeLeverage.tsx b/src/components/Modals/HLS/Manage/ChangeLeverage.tsx index 6cb40d3f..e9734118 100644 --- a/src/components/Modals/HLS/Manage/ChangeLeverage.tsx +++ b/src/components/Modals/HLS/Manage/ChangeLeverage.tsx @@ -1,10 +1,120 @@ +import React, { useCallback, useMemo, useState } from 'react' + +import Button from 'components/Button' +import LeverageSummary from 'components/Modals/HLS/Deposit/LeverageSummary' +import TokenInputWithSlider from 'components/TokenInput/TokenInputWithSlider' +import { DEFAULT_SETTINGS } from 'constants/defaultSettings' +import { LocalStorageKeys } from 'constants/localStorageKeys' +import { BN_ZERO } from 'constants/math' +import useHealthComputer from 'hooks/useHealthComputer' +import useLocalStorage from 'hooks/useLocalStorage' +import usePrices from 'hooks/usePrices' +import { useUpdatedAccount } from 'hooks/useUpdatedAccount' +import useStore from 'store' +import { BNCoin } from 'types/classes/BNCoin' +import { getAccountPositionValues } from 'utils/accounts' +import { getHlsStakingChangeLevActions } from 'utils/actions' +import { byDenom } from 'utils/array' + interface Props { - account: Account + account: HLSAccountWithStrategy action: HlsStakingManageAction borrowAsset: Asset collateralAsset: Asset } -export default function Repay(props: Props) { - return <> +export default function ChangeLeverage(props: Props) { + const { data: prices } = usePrices() + const [slippage] = useLocalStorage(LocalStorageKeys.SLIPPAGE, DEFAULT_SETTINGS.slippage) + const { updatedAccount, simulateHlsStakingDeposit, simulateHlsStakingWithdraw, leverage } = + useUpdatedAccount(props.account) + + const changeHlsStakingLeverage = useStore((s) => s.changeHlsStakingLeverage) + const { computeMaxBorrowAmount } = useHealthComputer(props.account) + const previousDebt: BigNumber = useMemo( + () => props.account.debts.find(byDenom(props.borrowAsset.denom))?.amount || BN_ZERO, + [props.account.debts, props.borrowAsset.denom], + ) + + const [currentDebt, setAmount] = useState(previousDebt) + const maxBorrowAmount = useMemo(() => { + return computeMaxBorrowAmount(props.borrowAsset.denom, 'deposit').plus(previousDebt) + }, [computeMaxBorrowAmount, previousDebt, props.borrowAsset.denom]) + + const onChangeAmount = useCallback( + (currentDebt: BigNumber) => { + setAmount(currentDebt) + if (currentDebt.isLessThan(previousDebt)) { + simulateHlsStakingWithdraw( + props.collateralAsset.denom, + props.borrowAsset.denom, + previousDebt.minus(currentDebt), + ) + } else { + simulateHlsStakingDeposit( + BNCoin.fromDenomAndBigNumber(props.collateralAsset.denom, BN_ZERO), + BNCoin.fromDenomAndBigNumber(props.borrowAsset.denom, currentDebt.minus(previousDebt)), + ) + } + }, + [ + previousDebt, + props.borrowAsset.denom, + props.collateralAsset.denom, + simulateHlsStakingDeposit, + simulateHlsStakingWithdraw, + ], + ) + + const positionValue = useMemo(() => { + const [deposits, lends, debts, vaults] = getAccountPositionValues( + updatedAccount || props.account, + prices, + ) + + return deposits.plus(lends).plus(debts).plus(vaults) + }, [prices, props.account, updatedAccount]) + + const handleOnClick = useCallback(() => { + useStore.setState({ hlsManageModal: null }) + if (currentDebt.isEqualTo(previousDebt)) return + const actions = getHlsStakingChangeLevActions( + previousDebt, + currentDebt, + props.collateralAsset.denom, + props.borrowAsset.denom, + slippage, + prices, + ) + changeHlsStakingLeverage({ accountId: props.account.id, actions }) + }, [ + currentDebt, + changeHlsStakingLeverage, + previousDebt, + prices, + props.account.id, + props.borrowAsset.denom, + props.collateralAsset.denom, + slippage, + ]) + + return ( + <> + +
+ +
+ + ) } diff --git a/src/components/Modals/HLS/Manage/Deposit.tsx b/src/components/Modals/HLS/Manage/Deposit.tsx index 33c8c445..b4f329ad 100644 --- a/src/components/Modals/HLS/Manage/Deposit.tsx +++ b/src/components/Modals/HLS/Manage/Deposit.tsx @@ -88,7 +88,7 @@ export default function Deposit(props: Props) { const actions = useDepositActions({ depositCoin, borrowCoin }) const currentDebt: BigNumber = useMemo( - () => props.account.debts.find(byDenom(props.borrowAsset.denom)).amount || BN_ZERO, + () => props.account.debts.find(byDenom(props.borrowAsset.denom))?.amount || BN_ZERO, [props.account.debts, props.borrowAsset.denom], ) @@ -185,7 +185,7 @@ export default function Deposit(props: Props) { maxText='In Wallet' /> -
+
Keep leverage diff --git a/src/components/Modals/HLS/Manage/index.tsx b/src/components/Modals/HLS/Manage/index.tsx index d7a5be43..329fd5e3 100644 --- a/src/components/Modals/HLS/Manage/index.tsx +++ b/src/components/Modals/HLS/Manage/index.tsx @@ -20,7 +20,7 @@ export default function HlsManageModalController() { return ( + ) return null @@ -37,6 +42,7 @@ export default function HlsModalController() { interface Props { primaryAsset: Asset secondaryAsset: Asset + strategy?: HLSStrategy vaultAddress: string | null } @@ -57,6 +63,7 @@ function HlsModal(props: Props) { collateralAsset={props.primaryAsset} borrowAsset={props.secondaryAsset} vaultAddress={props.vaultAddress} + strategy={props.strategy} /> ) diff --git a/src/components/Modals/Vault/VaultBorrowings.tsx b/src/components/Modals/Vault/VaultBorrowings.tsx index 923f14e4..8faac873 100644 --- a/src/components/Modals/Vault/VaultBorrowings.tsx +++ b/src/components/Modals/Vault/VaultBorrowings.tsx @@ -6,7 +6,7 @@ import DepositCapMessage from 'components/DepositCapMessage' import DisplayCurrency from 'components/DisplayCurrency' import Divider from 'components/Divider' import { ArrowRight, ExclamationMarkCircled } from 'components/Icons' -import Slider from 'components/Slider' +import Index from 'components/Slider' import Text from 'components/Text' import TokenInput from 'components/TokenInput' import { BN_ZERO } from 'constants/math' @@ -192,7 +192,7 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) { /> ) })} - {props.borrowings.length === 1 && } + {props.borrowings.length === 1 && } {props.borrowings.length === 0 && (
diff --git a/src/components/Routes.tsx b/src/components/Routes.tsx index df26dd94..74b64b7c 100644 --- a/src/components/Routes.tsx +++ b/src/components/Routes.tsx @@ -3,7 +3,6 @@ import { Navigate, Outlet, Route, Routes as RoutesWrapper } from 'react-router-d import Layout from 'pages/_layout' import BorrowPage from 'pages/BorrowPage' import FarmPage from 'pages/FarmPage' -import HLSFarmPage from 'pages/HLSFarmPage' import HLSStakingPage from 'pages/HLSStakingPage' import LendPage from 'pages/LendPage' import MobilePage from 'pages/MobilePage' @@ -28,7 +27,7 @@ export default function Routes() { } /> } /> } /> - } /> + {/*} />*/} } /> } /> @@ -36,11 +35,11 @@ export default function Routes() { } /> } /> } /> + } /> } /> - } /> - } /> + {/*} />*/} } /> } /> diff --git a/src/components/Slider.tsx b/src/components/Slider.tsx deleted file mode 100644 index 8b2208d3..00000000 --- a/src/components/Slider.tsx +++ /dev/null @@ -1,205 +0,0 @@ -import classNames from 'classnames' -import { ChangeEvent, useRef, useState } from 'react' -import Draggable from 'react-draggable' - -import { OverlayMark } from 'components/Icons/index' -import useToggle from 'hooks/useToggle' - -type Props = { - value: number - onChange: (value: number) => void - className?: string - disabled?: boolean -} - -export default function Slider(props: Props) { - const [showTooltip, setShowTooltip] = useToggle() - const [sliderRect, setSliderRect] = useState({ width: 0, left: 0, right: 0 }) - const ref = useRef(null) - const nodeRef = useRef(null) - const [isDragging, setIsDragging] = useToggle() - - function handleSliderRect() { - const leftCap = ref.current?.getBoundingClientRect().left ?? 0 - const rightCap = ref.current?.getBoundingClientRect().right ?? 0 - const newSliderWidth = ref.current?.getBoundingClientRect().width ?? 0 - - if ( - sliderRect.width !== newSliderWidth || - leftCap !== sliderRect.left || - rightCap !== sliderRect.right - ) { - setSliderRect({ - left: leftCap, - right: rightCap, - width: newSliderWidth, - }) - } - } - - function handleDrag(e: any) { - if (!isDragging) { - setIsDragging(true) - } - - const current: number = e.clientX - - if (current < sliderRect.left) { - props.onChange(0) - return - } - - if (current > sliderRect.right) { - props.onChange(100) - return - } - - const value = Math.round(((current - sliderRect.left) / sliderRect.width) * 100) - - if (value !== props.value) { - props.onChange(value) - } - } - - function handleSliderClick(e: ChangeEvent) { - props.onChange(Number(e.target.value)) - } - - function handleShowTooltip() { - setShowTooltip(true) - } - - function handleHideTooltip() { - setShowTooltip(false) - } - - const DraggableElement: any = Draggable - - return ( -
- -
- - - - - - - - - -
- {!props.disabled && ( -
- setIsDragging(false)} - position={{ x: (sliderRect.width / 100) * props.value, y: 0 }} - > -
-
- {(showTooltip || isDragging) && ( -
- - {props.value.toFixed(0)}% -
- )} -
- -
- )} -
- ) -} - -interface MarkProps { - value: number - sliderValue: number - onClick: (value: number) => void - disabled?: boolean -} - -function Mark(props: MarkProps) { - return ( - - ) -} - -interface TrackProps { - maxValue: number - sliderValue: number -} - -function Track(props: TrackProps) { - const minValue = props.maxValue - 21 - let percentage = 0 - - if (props.sliderValue >= props.maxValue) percentage = 100 - - if (props.sliderValue > minValue && props.sliderValue < props.maxValue) { - percentage = ((props.sliderValue - minValue) / (props.maxValue - minValue)) * 100 - } - - return ( -
-
-
-
- ) -} diff --git a/src/components/Slider/LeverageLabel.tsx b/src/components/Slider/LeverageLabel.tsx new file mode 100644 index 00000000..3611429f --- /dev/null +++ b/src/components/Slider/LeverageLabel.tsx @@ -0,0 +1,26 @@ +import classNames from 'classnames' + +import Text from 'components/Text' + +interface Props { + className?: string + decimals: number + leverage: number + style?: {} +} + +export default function LeverageLabel(props: Props) { + return ( +
+
+ {props.leverage.toFixed(props.decimals)}x +
+ ) +} diff --git a/src/components/Slider/Mark.tsx b/src/components/Slider/Mark.tsx new file mode 100644 index 00000000..28b10d00 --- /dev/null +++ b/src/components/Slider/Mark.tsx @@ -0,0 +1,23 @@ +import classNames from 'classnames' + +interface Props { + disabled?: boolean + onClick: (value: number) => void + sliderValue: number + style?: {} + value: number +} + +export default function Mark(props: Props) { + return ( +