From 1ecd80dac22b12980d0765700647733ee5d31a03 Mon Sep 17 00:00:00 2001
From: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com>
Date: Fri, 10 Nov 2023 17:20:48 +0100
Subject: [PATCH] Hls leverage (#628)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* ✨Add basic modal for HLS staking
* ✨UI components for Manage
* ✨All Manage actions (except change lev)
* 🐛hls intro icons + checkbox, hide repay when no debt, clickable dropdown
* fix build
* ✨finish all actiosn for HLS staking
* 🐛clean up tooltip props
---
src/api/hls/getHLSStakingAssets.ts | 1 +
.../HLS/Staking/Table/Columns/Manage.tsx | 4 +-
src/components/Header/DesktopHeader.tsx | 2 +-
.../Modals/HLS/Deposit/Leverage.tsx | 14 +-
src/components/Modals/HLS/Deposit/index.tsx | 41 ++--
.../Modals/HLS/Deposit/useAccordionItems.tsx | 3 +
.../HLS/Deposit/useStakingController.tsx | 3 +-
.../Modals/HLS/Manage/ChangeLeverage.tsx | 116 ++++++++-
src/components/Modals/HLS/Manage/Deposit.tsx | 2 +-
src/components/Modals/HLS/Manage/index.tsx | 4 +-
src/components/Modals/HLS/index.tsx | 9 +-
.../Modals/Vault/VaultBorrowings.tsx | 4 +-
src/components/Routes.tsx | 7 +-
src/components/Slider.tsx | 205 ----------------
src/components/Slider/LeverageLabel.tsx | 26 ++
src/components/Slider/Mark.tsx | 23 ++
src/components/Slider/Track.tsx | 32 +++
src/components/Slider/index.tsx | 222 ++++++++++++++++++
.../TokenInput/TokenInputWithSlider.tsx | 5 +
src/components/Tooltip/index.tsx | 3 +-
src/hooks/useUpdatedAccount/index.ts | 18 +-
src/pages/HLSStakingPage.tsx | 4 +-
src/store/slices/broadcast.ts | 27 ++-
src/types/interfaces/store/broadcast.d.ts | 6 +-
src/utils/actions.ts | 68 ++++++
tailwind.config.js | 16 ++
26 files changed, 610 insertions(+), 255 deletions(-)
delete mode 100644 src/components/Slider.tsx
create mode 100644 src/components/Slider/LeverageLabel.tsx
create mode 100644 src/components/Slider/Mark.tsx
create mode 100644 src/components/Slider/Track.tsx
create mode 100644 src/components/Slider/index.tsx
create mode 100644 src/utils/actions.ts
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/components/HLS/Staking/Table/Columns/Manage.tsx b/src/components/HLS/Staking/Table/Columns/Manage.tsx
index 640b230a..94141d19 100644
--- a/src/components/HLS/Staking/Table/Columns/Manage.tsx
+++ b/src/components/HLS/Staking/Table/Columns/Manage.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useMemo } from 'react'
+import React, { useCallback, useMemo } from 'react'
import DropDownButton from 'components/Button/DropDownButton'
import { ArrowDownLine, Cross, HandCoins, Plus, Scale } from 'components/Icons'
@@ -61,7 +61,7 @@ export default function Manage(props: Props) {
onClick: () => closeHlsStakingPosition({ accountId: props.account.id, actions }),
},
],
- [actions, closeHlsStakingPosition, openModal, props.account.id, hasNoDebt],
+ [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/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..e076caa0 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],
)
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 (
+