Hls leverage (#628)
* ✨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
This commit is contained in:
parent
cc3b0eee20
commit
1ecd80dac2
@ -26,6 +26,7 @@ export default async function getHLSStakingAssets() {
|
|||||||
used: BN(depositCap.amount),
|
used: BN(depositCap.amount),
|
||||||
max: BN(depositCap.cap),
|
max: BN(depositCap.cap),
|
||||||
},
|
},
|
||||||
|
apy: 18, // TODO: Actually implement the APY here!
|
||||||
} as HLSStrategy
|
} as HLSStrategy
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useMemo } from 'react'
|
import React, { useCallback, useMemo } from 'react'
|
||||||
|
|
||||||
import DropDownButton from 'components/Button/DropDownButton'
|
import DropDownButton from 'components/Button/DropDownButton'
|
||||||
import { ArrowDownLine, Cross, HandCoins, Plus, Scale } from 'components/Icons'
|
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 }),
|
onClick: () => closeHlsStakingPosition({ accountId: props.account.id, actions }),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[actions, closeHlsStakingPosition, openModal, props.account.id, hasNoDebt],
|
[actions, closeHlsStakingPosition, hasNoDebt, openModal, props.account.id],
|
||||||
)
|
)
|
||||||
|
|
||||||
return <DropDownButton items={ITEMS} text='Manage' color='tertiary' />
|
return <DropDownButton items={ITEMS} text='Manage' color='tertiary' />
|
||||||
|
@ -17,7 +17,7 @@ export const menuTree: { pages: Page[]; label: string }[] = [
|
|||||||
{ pages: ['lend', 'farm'], label: 'Earn' },
|
{ pages: ['lend', 'farm'], label: 'Earn' },
|
||||||
{ pages: ['borrow'], label: 'Borrow' },
|
{ pages: ['borrow'], label: 'Borrow' },
|
||||||
{ pages: ['portfolio'], label: 'Portfolio' },
|
{ 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() {
|
export default function DesktopHeader() {
|
||||||
|
@ -12,20 +12,28 @@ interface Props {
|
|||||||
onChangeAmount: (amount: BigNumber) => void
|
onChangeAmount: (amount: BigNumber) => void
|
||||||
onClickBtn: () => void
|
onClickBtn: () => void
|
||||||
positionValue: BigNumber
|
positionValue: BigNumber
|
||||||
|
leverage: number
|
||||||
|
maxLeverage: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Leverage(props: Props) {
|
export default function Leverage(props: Props) {
|
||||||
return (
|
return (
|
||||||
<div className='p-4 flex-col gap-6 flex'>
|
<div className='flex-col gap-6 flex justify-between h-full p-4'>
|
||||||
<TokenInputWithSlider
|
<TokenInputWithSlider
|
||||||
amount={props.amount}
|
amount={props.amount}
|
||||||
asset={props.asset}
|
asset={props.asset}
|
||||||
max={props.max}
|
max={props.max}
|
||||||
onChange={props.onChangeAmount}
|
onChange={props.onChangeAmount}
|
||||||
maxText='Max borrow'
|
maxText='Max borrow'
|
||||||
|
leverage={{
|
||||||
|
current: props.leverage,
|
||||||
|
max: props.maxLeverage,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
<div className='flex flex-col gap-6'>
|
||||||
<LeverageSummary asset={props.asset} positionValue={props.positionValue} />
|
<LeverageSummary asset={props.asset} positionValue={props.positionValue} />
|
||||||
<Button onClick={props.onClickBtn} text='Continue' rightIcon={<ArrowRight />} />
|
<Button onClick={props.onClickBtn} text='Continue' rightIcon={<ArrowRight />} />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ interface Props {
|
|||||||
borrowAsset: Asset
|
borrowAsset: Asset
|
||||||
collateralAsset: Asset
|
collateralAsset: Asset
|
||||||
vaultAddress: string | null
|
vaultAddress: string | null
|
||||||
|
strategy?: HLSStrategy
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Controller(props: Props) {
|
export default function Controller(props: Props) {
|
||||||
@ -46,6 +47,7 @@ export default function Controller(props: Props) {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (props.strategy) {
|
||||||
return (
|
return (
|
||||||
<StakingContent
|
<StakingContent
|
||||||
walletCollateralAsset={walletCollateralAsset}
|
walletCollateralAsset={walletCollateralAsset}
|
||||||
@ -57,10 +59,14 @@ export default function Controller(props: Props) {
|
|||||||
selectedAccount={selectedAccount}
|
selectedAccount={selectedAccount}
|
||||||
setSelectedAccount={setSelectedAccount}
|
setSelectedAccount={setSelectedAccount}
|
||||||
toggleIsOpen={toggleIsOpen}
|
toggleIsOpen={toggleIsOpen}
|
||||||
|
strategy={props.strategy}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
interface ContentProps {
|
interface ContentProps {
|
||||||
borrowAsset: Asset
|
borrowAsset: Asset
|
||||||
collateralAsset: Asset
|
collateralAsset: Asset
|
||||||
@ -120,7 +126,11 @@ function Vault(props: VaultContentProps) {
|
|||||||
return <Accordion className='h-[546px] overflow-y-scroll scrollbar-hide' items={items} />
|
return <Accordion className='h-[546px] overflow-y-scroll scrollbar-hide' items={items} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function StakingContent(props: ContentProps) {
|
interface StakingContentProps extends ContentProps {
|
||||||
|
strategy: HLSStrategy
|
||||||
|
}
|
||||||
|
|
||||||
|
function StakingContent(props: StakingContentProps) {
|
||||||
const {
|
const {
|
||||||
depositAmount,
|
depositAmount,
|
||||||
onChangeCollateral,
|
onChangeCollateral,
|
||||||
@ -152,10 +162,11 @@ function StakingContent(props: ContentProps) {
|
|||||||
positionValue,
|
positionValue,
|
||||||
selectedAccount: props.selectedAccount,
|
selectedAccount: props.selectedAccount,
|
||||||
setSelectedAccount: props.setSelectedAccount,
|
setSelectedAccount: props.setSelectedAccount,
|
||||||
|
strategy: props.strategy,
|
||||||
toggleIsOpen: props.toggleIsOpen,
|
toggleIsOpen: props.toggleIsOpen,
|
||||||
updatedAccount,
|
updatedAccount,
|
||||||
maxBorrowAmount,
|
maxBorrowAmount,
|
||||||
apy: 0, // TODO: Implement APY
|
apy: props.strategy.apy || 0, // TODO: Implement APY
|
||||||
walletCollateralAsset: props.walletCollateralAsset,
|
walletCollateralAsset: props.walletCollateralAsset,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ interface Props {
|
|||||||
positionValue: BigNumber
|
positionValue: BigNumber
|
||||||
selectedAccount: Account | null
|
selectedAccount: Account | null
|
||||||
setSelectedAccount: (account: Account) => void
|
setSelectedAccount: (account: Account) => void
|
||||||
|
strategy?: HLSStrategy
|
||||||
toggleIsOpen: (index: number) => void
|
toggleIsOpen: (index: number) => void
|
||||||
updatedAccount: Account | undefined
|
updatedAccount: Account | undefined
|
||||||
walletCollateralAsset: Coin | undefined
|
walletCollateralAsset: Coin | undefined
|
||||||
@ -64,12 +65,14 @@ export default function useAccordionItems(props: Props) {
|
|||||||
title: 'Leverage',
|
title: 'Leverage',
|
||||||
renderContent: () => (
|
renderContent: () => (
|
||||||
<Leverage
|
<Leverage
|
||||||
|
leverage={props.leverage}
|
||||||
amount={props.borrowAmount}
|
amount={props.borrowAmount}
|
||||||
asset={props.borrowAsset}
|
asset={props.borrowAsset}
|
||||||
onChangeAmount={props.onChangeDebt}
|
onChangeAmount={props.onChangeDebt}
|
||||||
onClickBtn={() => props.toggleIsOpen(2)}
|
onClickBtn={() => props.toggleIsOpen(2)}
|
||||||
max={props.maxBorrowAmount}
|
max={props.maxBorrowAmount}
|
||||||
positionValue={props.positionValue}
|
positionValue={props.positionValue}
|
||||||
|
maxLeverage={props.strategy?.maxLeverage || 1}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
renderSubTitle: () => (
|
renderSubTitle: () => (
|
||||||
|
@ -23,8 +23,6 @@ export default function useStakingController(props: Props) {
|
|||||||
setBorrowAmount,
|
setBorrowAmount,
|
||||||
borrowAmount,
|
borrowAmount,
|
||||||
positionValue,
|
positionValue,
|
||||||
borrowCoin,
|
|
||||||
depositCoin,
|
|
||||||
actions,
|
actions,
|
||||||
} = useDepositHlsVault({
|
} = useDepositHlsVault({
|
||||||
collateralDenom: collateralAsset.denom,
|
collateralDenom: collateralAsset.denom,
|
||||||
@ -40,6 +38,7 @@ export default function useStakingController(props: Props) {
|
|||||||
}, [computeMaxBorrowAmount, props.borrowAsset.denom])
|
}, [computeMaxBorrowAmount, props.borrowAsset.denom])
|
||||||
|
|
||||||
const execute = useCallback(() => {
|
const execute = useCallback(() => {
|
||||||
|
useStore.setState({ hlsModal: null })
|
||||||
addToStakingStrategy({
|
addToStakingStrategy({
|
||||||
actions,
|
actions,
|
||||||
accountId: selectedAccount.id,
|
accountId: selectedAccount.id,
|
||||||
|
@ -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 {
|
interface Props {
|
||||||
account: Account
|
account: HLSAccountWithStrategy
|
||||||
action: HlsStakingManageAction
|
action: HlsStakingManageAction
|
||||||
borrowAsset: Asset
|
borrowAsset: Asset
|
||||||
collateralAsset: Asset
|
collateralAsset: Asset
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Repay(props: Props) {
|
export default function ChangeLeverage(props: Props) {
|
||||||
return <></>
|
const { data: prices } = usePrices()
|
||||||
|
const [slippage] = useLocalStorage<number>(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 (
|
||||||
|
<>
|
||||||
|
<TokenInputWithSlider
|
||||||
|
amount={currentDebt}
|
||||||
|
asset={props.borrowAsset}
|
||||||
|
max={maxBorrowAmount}
|
||||||
|
onChange={onChangeAmount}
|
||||||
|
maxText='Max borrow'
|
||||||
|
leverage={{
|
||||||
|
current: leverage,
|
||||||
|
max: props.account.strategy.maxLeverage,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className='flex flex-col gap-6'>
|
||||||
|
<LeverageSummary asset={props.borrowAsset} positionValue={positionValue} />
|
||||||
|
<Button onClick={handleOnClick} text='Confirm' />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ export default function Deposit(props: Props) {
|
|||||||
const actions = useDepositActions({ depositCoin, borrowCoin })
|
const actions = useDepositActions({ depositCoin, borrowCoin })
|
||||||
|
|
||||||
const currentDebt: BigNumber = useMemo(
|
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],
|
[props.account.debts, props.borrowAsset.denom],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ export default function HlsManageModalController() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<HlsModal
|
<HlsModal
|
||||||
account={account}
|
account={{ ...account, strategy: modal.staking.strategy } as HLSAccountWithStrategy}
|
||||||
action={modal.staking.action}
|
action={modal.staking.action}
|
||||||
collateralAsset={collateralAsset}
|
collateralAsset={collateralAsset}
|
||||||
borrowAsset={borrowAsset}
|
borrowAsset={borrowAsset}
|
||||||
@ -29,7 +29,7 @@ export default function HlsManageModalController() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
account: Account
|
account: HLSAccountWithStrategy
|
||||||
action: HlsStakingManageAction
|
action: HlsStakingManageAction
|
||||||
borrowAsset: Asset
|
borrowAsset: Asset
|
||||||
collateralAsset: Asset
|
collateralAsset: Asset
|
||||||
|
@ -28,7 +28,12 @@ export default function HlsModalController() {
|
|||||||
)
|
)
|
||||||
if (modal?.strategy)
|
if (modal?.strategy)
|
||||||
return (
|
return (
|
||||||
<HlsModal primaryAsset={primaryAsset} secondaryAsset={secondaryAsset} vaultAddress={null} />
|
<HlsModal
|
||||||
|
primaryAsset={primaryAsset}
|
||||||
|
secondaryAsset={secondaryAsset}
|
||||||
|
strategy={modal.strategy}
|
||||||
|
vaultAddress={null}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
return null
|
return null
|
||||||
@ -37,6 +42,7 @@ export default function HlsModalController() {
|
|||||||
interface Props {
|
interface Props {
|
||||||
primaryAsset: Asset
|
primaryAsset: Asset
|
||||||
secondaryAsset: Asset
|
secondaryAsset: Asset
|
||||||
|
strategy?: HLSStrategy
|
||||||
vaultAddress: string | null
|
vaultAddress: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,6 +63,7 @@ function HlsModal(props: Props) {
|
|||||||
collateralAsset={props.primaryAsset}
|
collateralAsset={props.primaryAsset}
|
||||||
borrowAsset={props.secondaryAsset}
|
borrowAsset={props.secondaryAsset}
|
||||||
vaultAddress={props.vaultAddress}
|
vaultAddress={props.vaultAddress}
|
||||||
|
strategy={props.strategy}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
|
@ -6,7 +6,7 @@ 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'
|
||||||
import Slider from 'components/Slider'
|
import Index from 'components/Slider'
|
||||||
import Text from 'components/Text'
|
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'
|
||||||
@ -192,7 +192,7 @@ export default function VaultBorrowings(props: VaultBorrowingsProps) {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
{props.borrowings.length === 1 && <Slider onChange={onChangeSlider} value={percentage} />}
|
{props.borrowings.length === 1 && <Index onChange={onChangeSlider} value={percentage} />}
|
||||||
{props.borrowings.length === 0 && (
|
{props.borrowings.length === 0 && (
|
||||||
<div className='flex items-center gap-4 py-2'>
|
<div className='flex items-center gap-4 py-2'>
|
||||||
<div className='w-4'>
|
<div className='w-4'>
|
||||||
|
@ -3,7 +3,6 @@ import { Navigate, Outlet, Route, Routes as RoutesWrapper } from 'react-router-d
|
|||||||
import Layout from 'pages/_layout'
|
import Layout from 'pages/_layout'
|
||||||
import BorrowPage from 'pages/BorrowPage'
|
import BorrowPage from 'pages/BorrowPage'
|
||||||
import FarmPage from 'pages/FarmPage'
|
import FarmPage from 'pages/FarmPage'
|
||||||
import HLSFarmPage from 'pages/HLSFarmPage'
|
|
||||||
import HLSStakingPage from 'pages/HLSStakingPage'
|
import HLSStakingPage from 'pages/HLSStakingPage'
|
||||||
import LendPage from 'pages/LendPage'
|
import LendPage from 'pages/LendPage'
|
||||||
import MobilePage from 'pages/MobilePage'
|
import MobilePage from 'pages/MobilePage'
|
||||||
@ -28,7 +27,7 @@ export default function Routes() {
|
|||||||
<Route path='/portfolio' element={<PortfolioPage />} />
|
<Route path='/portfolio' element={<PortfolioPage />} />
|
||||||
<Route path='/mobile' element={<MobilePage />} />
|
<Route path='/mobile' element={<MobilePage />} />
|
||||||
<Route path='/hls-staking' element={<HLSStakingPage />} />
|
<Route path='/hls-staking' element={<HLSStakingPage />} />
|
||||||
<Route path='/hls-farm' element={<HLSFarmPage />} />
|
{/*<Route path='/hls-farm' element={<HLSFarmPage />} />*/}
|
||||||
<Route path='/' element={<TradePage />} />
|
<Route path='/' element={<TradePage />} />
|
||||||
<Route path='/wallets/:address'>
|
<Route path='/wallets/:address'>
|
||||||
<Route path='trade' element={<TradePage />} />
|
<Route path='trade' element={<TradePage />} />
|
||||||
@ -36,11 +35,11 @@ export default function Routes() {
|
|||||||
<Route path='lend' element={<LendPage />} />
|
<Route path='lend' element={<LendPage />} />
|
||||||
<Route path='borrow' element={<BorrowPage />} />
|
<Route path='borrow' element={<BorrowPage />} />
|
||||||
<Route path='portfolio' element={<PortfolioPage />} />
|
<Route path='portfolio' element={<PortfolioPage />} />
|
||||||
|
<Route path='hls-staking' element={<HLSStakingPage />} />
|
||||||
<Route path='portfolio/:accountId'>
|
<Route path='portfolio/:accountId'>
|
||||||
<Route path='' element={<PortfolioAccountPage />} />
|
<Route path='' element={<PortfolioAccountPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path='hls-staking' element={<HLSStakingPage />} />
|
{/*<Route path='hls-farm' element={<HLSFarmPage />} />*/}
|
||||||
<Route path='hls-farm' element={<HLSFarmPage />} />
|
|
||||||
<Route path='' element={<TradePage />} />
|
<Route path='' element={<TradePage />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path='*' element={<Navigate to='/' />} />
|
<Route path='*' element={<Navigate to='/' />} />
|
||||||
|
@ -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<HTMLDivElement>(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<HTMLInputElement>) {
|
|
||||||
props.onChange(Number(e.target.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleShowTooltip() {
|
|
||||||
setShowTooltip(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleHideTooltip() {
|
|
||||||
setShowTooltip(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
const DraggableElement: any = Draggable
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={ref}
|
|
||||||
className={classNames(
|
|
||||||
'relative min-h-3 w-full transition-opacity',
|
|
||||||
props.className,
|
|
||||||
props.disabled && 'pointer-events-none',
|
|
||||||
)}
|
|
||||||
onMouseEnter={handleSliderRect}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type='range'
|
|
||||||
value={props.value}
|
|
||||||
onChange={handleSliderClick}
|
|
||||||
onMouseDown={handleShowTooltip}
|
|
||||||
className='absolute z-2 w-full hover:cursor-pointer appearance-none bg-transparent [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:appearance-none'
|
|
||||||
/>
|
|
||||||
<div className='absolute flex items-center w-full gap-1'>
|
|
||||||
<Mark
|
|
||||||
onClick={props.onChange}
|
|
||||||
value={0}
|
|
||||||
sliderValue={props.value}
|
|
||||||
disabled={props.disabled}
|
|
||||||
/>
|
|
||||||
<Track maxValue={23} sliderValue={props.value} />
|
|
||||||
<Mark
|
|
||||||
onClick={props.onChange}
|
|
||||||
value={25}
|
|
||||||
sliderValue={props.value}
|
|
||||||
disabled={props.disabled}
|
|
||||||
/>
|
|
||||||
<Track maxValue={48} sliderValue={props.value} />
|
|
||||||
<Mark
|
|
||||||
onClick={props.onChange}
|
|
||||||
value={50}
|
|
||||||
sliderValue={props.value}
|
|
||||||
disabled={props.disabled}
|
|
||||||
/>
|
|
||||||
<Track maxValue={73} sliderValue={props.value} />
|
|
||||||
<Mark
|
|
||||||
onClick={props.onChange}
|
|
||||||
value={75}
|
|
||||||
sliderValue={props.value}
|
|
||||||
disabled={props.disabled}
|
|
||||||
/>
|
|
||||||
<Track maxValue={98} sliderValue={props.value} />
|
|
||||||
<Mark
|
|
||||||
onClick={props.onChange}
|
|
||||||
value={100}
|
|
||||||
sliderValue={props.value}
|
|
||||||
disabled={props.disabled}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{!props.disabled && (
|
|
||||||
<div onMouseEnter={handleShowTooltip} onMouseLeave={handleHideTooltip}>
|
|
||||||
<DraggableElement
|
|
||||||
nodeRef={nodeRef}
|
|
||||||
axis='x'
|
|
||||||
grid={[sliderRect.width / 100, 0]}
|
|
||||||
bounds={{ left: 0, right: sliderRect.width }}
|
|
||||||
positionOffset={{ x: (props.value / 100) * -12, y: 0 }}
|
|
||||||
onDrag={handleDrag}
|
|
||||||
onStop={() => setIsDragging(false)}
|
|
||||||
position={{ x: (sliderRect.width / 100) * props.value, y: 0 }}
|
|
||||||
>
|
|
||||||
<div ref={nodeRef} className='absolute z-20 leading-3'>
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
'z-20 h-3 w-3 rotate-45 hover:cursor-pointer rounded-xs border-[2px] border-white bg-martian-red'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{(showTooltip || isDragging) && (
|
|
||||||
<div className='absolute -top-8 left-1/2 -translate-x-1/2 rounded-xs bg-martian-red px-2 py-[2px] text-xs'>
|
|
||||||
<OverlayMark className='absolute h-2 -translate-x-1/2 -bottom-2 left-1/2 -z-1 text-martian-red' />
|
|
||||||
{props.value.toFixed(0)}%
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</DraggableElement>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MarkProps {
|
|
||||||
value: number
|
|
||||||
sliderValue: number
|
|
||||||
onClick: (value: number) => void
|
|
||||||
disabled?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
function Mark(props: MarkProps) {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
onClick={() => props.onClick(props.value)}
|
|
||||||
className={`z-20 h-3 w-3 rotate-45 rounded-xs border-[1px] border-white/20 hover:border-[2px] hover:border-white ${
|
|
||||||
props.sliderValue >= props.value && !props.disabled
|
|
||||||
? 'bg-martian-red hover:border-white'
|
|
||||||
: 'bg-grey-medium'
|
|
||||||
}`}
|
|
||||||
disabled={props.disabled}
|
|
||||||
></button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<div className='relative flex-1 h-1 overflow-hidden bg-transparent rounded-sm'>
|
|
||||||
<div className='absolute h-3 z-1 bg-martian-red ' style={{ width: `${percentage}%` }} />
|
|
||||||
<div className='absolute w-full h-3 bg-white/20' />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
26
src/components/Slider/LeverageLabel.tsx
Normal file
26
src/components/Slider/LeverageLabel.tsx
Normal file
@ -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 (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'flex flex-col gap-1 items-center',
|
||||||
|
'transition-opacity duration-100',
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
style={props.style}
|
||||||
|
>
|
||||||
|
<div className={classNames('h-2.5 w-[1px] border-[0.5px] border-white/20')} />
|
||||||
|
<Text className='text-xs text-white/50'>{props.leverage.toFixed(props.decimals)}x</Text>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
23
src/components/Slider/Mark.tsx
Normal file
23
src/components/Slider/Mark.tsx
Normal file
@ -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 (
|
||||||
|
<button
|
||||||
|
onClick={() => props.onClick(props.value)}
|
||||||
|
className={classNames(
|
||||||
|
'z-20 h-2 w-2 rotate-45 rounded-xs border-[1px] border-white/20 hover:border-[1px] hover:border-white',
|
||||||
|
(props.sliderValue < props.value || props.disabled) && '!bg-grey-medium',
|
||||||
|
)}
|
||||||
|
style={props.style}
|
||||||
|
disabled={props.disabled}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
32
src/components/Slider/Track.tsx
Normal file
32
src/components/Slider/Track.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
bg: string
|
||||||
|
maxValue: number
|
||||||
|
sliderValue: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Track(props: Props) {
|
||||||
|
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 (
|
||||||
|
<div className='relative flex-1 h-1 bg-white/20 rounded-sm w-1'>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'h-1 z-1 rounded-sm w-1',
|
||||||
|
'before:absolute',
|
||||||
|
'before:top-0 before:bottom-0 before:right-0 before:left-0',
|
||||||
|
percentage > 0 && props.bg,
|
||||||
|
percentage > 0 && 'slider-mask',
|
||||||
|
)}
|
||||||
|
style={{ width: `${percentage}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
222
src/components/Slider/index.tsx
Normal file
222
src/components/Slider/index.tsx
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
import classNames from 'classnames'
|
||||||
|
import { ChangeEvent, useRef, useState } from 'react'
|
||||||
|
import Draggable from 'react-draggable'
|
||||||
|
|
||||||
|
import { OverlayMark } from 'components/Icons'
|
||||||
|
import LeverageLabel from 'components/Slider/LeverageLabel'
|
||||||
|
import Mark from 'components/Slider/Mark'
|
||||||
|
import Track from 'components/Slider/Track'
|
||||||
|
import useToggle from 'hooks/useToggle'
|
||||||
|
|
||||||
|
const colors = {
|
||||||
|
'1': '#897E83',
|
||||||
|
'2': '#BD8898',
|
||||||
|
'3': '#DB83A5',
|
||||||
|
'4': '#B5469B',
|
||||||
|
'5': '#920D92',
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
value: number
|
||||||
|
onChange: (value: number) => void
|
||||||
|
leverage?: {
|
||||||
|
current: number
|
||||||
|
max: number
|
||||||
|
}
|
||||||
|
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<HTMLDivElement>(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<HTMLInputElement>) {
|
||||||
|
props.onChange(Number(e.target.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleShowTooltip() {
|
||||||
|
setShowTooltip(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleHideTooltip() {
|
||||||
|
setShowTooltip(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActiveIndex() {
|
||||||
|
if (props.value >= 100) return '5'
|
||||||
|
if (props.value >= 75) return '4'
|
||||||
|
if (props.value >= 50) return '3'
|
||||||
|
if (props.value >= 25) return '2'
|
||||||
|
return '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
const DraggableElement: any = Draggable
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={classNames(
|
||||||
|
'relative min-h-3 w-full transition-opacity',
|
||||||
|
props.className,
|
||||||
|
props.disabled && 'pointer-events-none',
|
||||||
|
)}
|
||||||
|
onMouseEnter={handleSliderRect}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type='range'
|
||||||
|
value={props.value}
|
||||||
|
onChange={handleSliderClick}
|
||||||
|
onMouseDown={handleShowTooltip}
|
||||||
|
className='absolute z-2 w-full hover:cursor-pointer appearance-none bg-transparent [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:appearance-none'
|
||||||
|
/>
|
||||||
|
<div className='absolute flex items-center w-full gap-1.5'>
|
||||||
|
<Mark
|
||||||
|
onClick={props.onChange}
|
||||||
|
value={0}
|
||||||
|
sliderValue={props.value}
|
||||||
|
disabled={props.disabled}
|
||||||
|
style={{ backgroundColor: colors['1'] }}
|
||||||
|
/>
|
||||||
|
<Track maxValue={23} sliderValue={props.value} bg='before:gradient-slider-1' />
|
||||||
|
<Mark
|
||||||
|
onClick={props.onChange}
|
||||||
|
value={25}
|
||||||
|
sliderValue={props.value}
|
||||||
|
disabled={props.disabled}
|
||||||
|
style={{ backgroundColor: colors['2'] }}
|
||||||
|
/>
|
||||||
|
<Track maxValue={48} sliderValue={props.value} bg='before:gradient-slider-2' />
|
||||||
|
<Mark
|
||||||
|
onClick={props.onChange}
|
||||||
|
value={50}
|
||||||
|
sliderValue={props.value}
|
||||||
|
disabled={props.disabled}
|
||||||
|
style={{ backgroundColor: colors['3'] }}
|
||||||
|
/>
|
||||||
|
<Track maxValue={73} sliderValue={props.value} bg='before:gradient-slider-3' />
|
||||||
|
<Mark
|
||||||
|
onClick={props.onChange}
|
||||||
|
value={75}
|
||||||
|
sliderValue={props.value}
|
||||||
|
disabled={props.disabled}
|
||||||
|
style={{ backgroundColor: colors['4'] }}
|
||||||
|
/>
|
||||||
|
<Track maxValue={98} sliderValue={props.value} bg='before:gradient-slider-4' />
|
||||||
|
<Mark
|
||||||
|
onClick={props.onChange}
|
||||||
|
value={100}
|
||||||
|
sliderValue={props.value}
|
||||||
|
disabled={props.disabled}
|
||||||
|
style={{ backgroundColor: colors['5'] }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{!props.disabled && (
|
||||||
|
<div onMouseEnter={handleShowTooltip} onMouseLeave={handleHideTooltip}>
|
||||||
|
<DraggableElement
|
||||||
|
nodeRef={nodeRef}
|
||||||
|
axis='x'
|
||||||
|
grid={[sliderRect.width / 100, 0]}
|
||||||
|
bounds={{ left: 0, right: sliderRect.width }}
|
||||||
|
positionOffset={{ x: (props.value / 100) * -12, y: 0 }}
|
||||||
|
onDrag={handleDrag}
|
||||||
|
onStop={() => setIsDragging(false)}
|
||||||
|
position={{ x: (sliderRect.width / 100) * props.value, y: -2 }}
|
||||||
|
>
|
||||||
|
<div ref={nodeRef} className='absolute z-20 leading-3'>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'z-20 h-3 w-3 rotate-45 hover:cursor-pointer rounded-xs border-[2px] border-white',
|
||||||
|
)}
|
||||||
|
style={{ background: colors[getActiveIndex()] }}
|
||||||
|
/>
|
||||||
|
{props.leverage ? (
|
||||||
|
<div className='pt-2.5'>
|
||||||
|
<LeverageLabel
|
||||||
|
leverage={props.leverage.current}
|
||||||
|
decimals={1}
|
||||||
|
className={props.leverage.current >= 10 ? '-translate-x-2' : '-translate-x-1'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
(showTooltip || isDragging) && (
|
||||||
|
<div className='absolute -top-8 left-1/2 -translate-x-1/2 rounded-xs bg-fuchsia px-2 py-[2px] text-xs'>
|
||||||
|
<OverlayMark
|
||||||
|
className={classNames(
|
||||||
|
'absolute h-2 -translate-x-1/2 -bottom-2 left-1/2 -z-1 text-fuchsia',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{props.value.toFixed(0)}%
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</DraggableElement>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{props.leverage && (
|
||||||
|
<div className='pt-2 flex justify-between'>
|
||||||
|
<LeverageLabel
|
||||||
|
leverage={1}
|
||||||
|
decimals={0}
|
||||||
|
className='-translate-x-0.5'
|
||||||
|
style={{ opacity: props.value < 5 ? 0 : 1 }}
|
||||||
|
/>
|
||||||
|
<LeverageLabel
|
||||||
|
leverage={props.leverage.max || 1}
|
||||||
|
decimals={0}
|
||||||
|
className='translate-x-1.5'
|
||||||
|
style={{ opacity: props.value > 95 ? 0 : 1 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -19,6 +19,10 @@ interface Props {
|
|||||||
hasSelect?: boolean
|
hasSelect?: boolean
|
||||||
maxText?: string
|
maxText?: string
|
||||||
onChangeAsset?: (asset: Asset) => void
|
onChangeAsset?: (asset: Asset) => void
|
||||||
|
leverage?: {
|
||||||
|
current: number
|
||||||
|
max: number
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TokenInputWithSlider(props: Props) {
|
export default function TokenInputWithSlider(props: Props) {
|
||||||
@ -71,6 +75,7 @@ export default function TokenInputWithSlider(props: Props) {
|
|||||||
value={percentage || 0}
|
value={percentage || 0}
|
||||||
onChange={(value) => onChangeSlider(value)}
|
onChange={(value) => onChangeSlider(value)}
|
||||||
disabled={props.disabled}
|
disabled={props.disabled}
|
||||||
|
leverage={props.leverage}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -44,7 +44,8 @@ export const Tooltip = (props: Props) => {
|
|||||||
className={props.contentClassName}
|
className={props.contentClassName}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{...props}
|
onClickOutside={props.onClickOutside}
|
||||||
|
visible={props.visible}
|
||||||
>
|
>
|
||||||
{props.children ? (
|
{props.children ? (
|
||||||
<span
|
<span
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
import useVaults from 'hooks/useVaults'
|
import useVaults from 'hooks/useVaults'
|
||||||
import useStore from 'store'
|
import useStore from 'store'
|
||||||
import { BNCoin } from 'types/classes/BNCoin'
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
import { cloneAccount } from 'utils/accounts'
|
import { calculateAccountLeverage, cloneAccount } from 'utils/accounts'
|
||||||
import { byDenom } from 'utils/array'
|
import { byDenom } from 'utils/array'
|
||||||
import { getCoinAmount, getCoinValue } from 'utils/formatters'
|
import { getCoinAmount, getCoinValue } from 'utils/formatters'
|
||||||
import { getValueFromBNCoins } from 'utils/helpers'
|
import { getValueFromBNCoins } from 'utils/helpers'
|
||||||
@ -30,6 +30,7 @@ export function useUpdatedAccount(account?: Account) {
|
|||||||
const [updatedAccount, setUpdatedAccount] = useState<Account | undefined>(
|
const [updatedAccount, setUpdatedAccount] = useState<Account | undefined>(
|
||||||
account ? cloneAccount(account) : undefined,
|
account ? cloneAccount(account) : undefined,
|
||||||
)
|
)
|
||||||
|
|
||||||
const [slippage] = useLocalStorage<number>(LocalStorageKeys.SLIPPAGE, DEFAULT_SETTINGS.slippage)
|
const [slippage] = useLocalStorage<number>(LocalStorageKeys.SLIPPAGE, DEFAULT_SETTINGS.slippage)
|
||||||
const [addedDeposits, addDeposits] = useState<BNCoin[]>([])
|
const [addedDeposits, addDeposits] = useState<BNCoin[]>([])
|
||||||
const [removedDeposits, removeDeposits] = useState<BNCoin[]>([])
|
const [removedDeposits, removeDeposits] = useState<BNCoin[]>([])
|
||||||
@ -39,7 +40,7 @@ export function useUpdatedAccount(account?: Account) {
|
|||||||
const [addedLends, addLends] = useState<BNCoin[]>([])
|
const [addedLends, addLends] = useState<BNCoin[]>([])
|
||||||
const [removedLends, removeLends] = useState<BNCoin[]>([])
|
const [removedLends, removeLends] = useState<BNCoin[]>([])
|
||||||
const [addedTrades, addTrades] = useState<BNCoin[]>([])
|
const [addedTrades, addTrades] = useState<BNCoin[]>([])
|
||||||
|
const [leverage, setLeverage] = useState<number>(0)
|
||||||
const removeDepositAndLendsByDenom = useCallback(
|
const removeDepositAndLendsByDenom = useCallback(
|
||||||
(denom: string) => {
|
(denom: string) => {
|
||||||
if (!account) return
|
if (!account) return
|
||||||
@ -165,6 +166,16 @@ export function useUpdatedAccount(account?: Account) {
|
|||||||
[prices],
|
[prices],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const simulateHlsStakingWithdraw = useCallback(
|
||||||
|
(collateralDenom: string, debtDenom: string, repayAmount: BigNumber) => {
|
||||||
|
const repayValue = getCoinValue(BNCoin.fromDenomAndBigNumber(debtDenom, repayAmount), prices)
|
||||||
|
const removeDepositAmount = getCoinAmount(collateralDenom, repayValue, prices)
|
||||||
|
removeDeposits([BNCoin.fromDenomAndBigNumber(collateralDenom, removeDepositAmount)])
|
||||||
|
removeDebts([BNCoin.fromDenomAndBigNumber(debtDenom, repayAmount)])
|
||||||
|
},
|
||||||
|
[prices],
|
||||||
|
)
|
||||||
|
|
||||||
const simulateVaultDeposit = useCallback(
|
const simulateVaultDeposit = useCallback(
|
||||||
(address: string, coins: BNCoin[], borrowCoins: BNCoin[]) => {
|
(address: string, coins: BNCoin[], borrowCoins: BNCoin[]) => {
|
||||||
if (!account) return
|
if (!account) return
|
||||||
@ -205,6 +216,7 @@ export function useUpdatedAccount(account?: Account) {
|
|||||||
accountCopy.lends = addCoins(addedLends, [...accountCopy.lends])
|
accountCopy.lends = addCoins(addedLends, [...accountCopy.lends])
|
||||||
accountCopy.lends = removeCoins(removedLends, [...accountCopy.lends])
|
accountCopy.lends = removeCoins(removedLends, [...accountCopy.lends])
|
||||||
setUpdatedAccount(accountCopy)
|
setUpdatedAccount(accountCopy)
|
||||||
|
setLeverage(calculateAccountLeverage(accountCopy, prices).toNumber())
|
||||||
useStore.setState({ updatedAccount: accountCopy })
|
useStore.setState({ updatedAccount: accountCopy })
|
||||||
|
|
||||||
return () => useStore.setState({ updatedAccount: undefined })
|
return () => useStore.setState({ updatedAccount: undefined })
|
||||||
@ -235,12 +247,14 @@ export function useUpdatedAccount(account?: Account) {
|
|||||||
addedDeposits,
|
addedDeposits,
|
||||||
addedDebts,
|
addedDebts,
|
||||||
addedLends,
|
addedLends,
|
||||||
|
leverage,
|
||||||
removedDeposits,
|
removedDeposits,
|
||||||
removedDebts,
|
removedDebts,
|
||||||
removedLends,
|
removedLends,
|
||||||
simulateBorrow,
|
simulateBorrow,
|
||||||
simulateDeposits,
|
simulateDeposits,
|
||||||
simulateHlsStakingDeposit,
|
simulateHlsStakingDeposit,
|
||||||
|
simulateHlsStakingWithdraw,
|
||||||
simulateLending,
|
simulateLending,
|
||||||
simulateRepay,
|
simulateRepay,
|
||||||
simulateTrade,
|
simulateTrade,
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
import Tab from 'components/Earn/Tab'
|
|
||||||
import ActiveStakingAccounts from 'components/HLS/Staking/ActiveStakingAccounts'
|
import ActiveStakingAccounts from 'components/HLS/Staking/ActiveStakingAccounts'
|
||||||
import AvailableHlsStakingAssets from 'components/HLS/Staking/AvailableHLSStakingAssets'
|
import AvailableHlsStakingAssets from 'components/HLS/Staking/AvailableHLSStakingAssets'
|
||||||
import HLSStakingIntro from 'components/HLS/Staking/HLSStakingIntro'
|
import HLSStakingIntro from 'components/HLS/Staking/HLSStakingIntro'
|
||||||
import MigrationBanner from 'components/MigrationBanner'
|
import MigrationBanner from 'components/MigrationBanner'
|
||||||
import { HLS_TABS } from 'constants/pages'
|
|
||||||
|
|
||||||
export default function HLSStakingPage() {
|
export default function HLSStakingPage() {
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-wrap w-full gap-6'>
|
<div className='flex flex-wrap w-full gap-6'>
|
||||||
<MigrationBanner />
|
<MigrationBanner />
|
||||||
<Tab tabs={HLS_TABS} activeTabIdx={1} />
|
{/*<Tab tabs={HLS_TABS} activeTabIdx={1} />*/}
|
||||||
<HLSStakingIntro />
|
<HLSStakingIntro />
|
||||||
<AvailableHlsStakingAssets />
|
<AvailableHlsStakingAssets />
|
||||||
<ActiveStakingAccounts />
|
<ActiveStakingAccounts />
|
||||||
|
@ -232,6 +232,28 @@ export default function createBroadcastSlice(
|
|||||||
|
|
||||||
return response.then((response) => !!response.result)
|
return response.then((response) => !!response.result)
|
||||||
},
|
},
|
||||||
|
changeHlsStakingLeverage: async (options: { accountId: string; actions: Action[] }) => {
|
||||||
|
const msg: CreditManagerExecuteMsg = {
|
||||||
|
update_credit_account: {
|
||||||
|
account_id: options.accountId,
|
||||||
|
actions: options.actions,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = get().executeMsg({
|
||||||
|
messages: [generateExecutionMessage(get().address, ENV.ADDRESS_CREDIT_MANAGER, msg, [])],
|
||||||
|
})
|
||||||
|
|
||||||
|
get().setToast({
|
||||||
|
response,
|
||||||
|
options: {
|
||||||
|
action: 'deposit',
|
||||||
|
message: `Changed Leverage`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return response.then((response) => !!response.result)
|
||||||
|
},
|
||||||
closeHlsStakingPosition: async (options: { accountId: string; actions: Action[] }) => {
|
closeHlsStakingPosition: async (options: { accountId: string; actions: Action[] }) => {
|
||||||
const msg: CreditManagerExecuteMsg = {
|
const msg: CreditManagerExecuteMsg = {
|
||||||
update_credit_account: {
|
update_credit_account: {
|
||||||
@ -252,10 +274,7 @@ export default function createBroadcastSlice(
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const response_1 = await response
|
return response.then((response) => !!response.result)
|
||||||
return response_1.result
|
|
||||||
? getSingleValueFromBroadcastResult(response_1.result, 'wasm', 'token_id')
|
|
||||||
: null
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createAccount: async (accountKind: AccountKind) => {
|
createAccount: async (accountKind: AccountKind) => {
|
||||||
|
6
src/types/interfaces/store/broadcast.d.ts
vendored
6
src/types/interfaces/store/broadcast.d.ts
vendored
@ -91,11 +91,9 @@ interface BroadcastSlice {
|
|||||||
coin: BNCoin
|
coin: BNCoin
|
||||||
borrowToWallet: boolean
|
borrowToWallet: boolean
|
||||||
}) => Promise<boolean>
|
}) => Promise<boolean>
|
||||||
|
changeHlsStakingLeverage: (options: { accountId: string; actions: Action[] }) => Promise<boolean>
|
||||||
claimRewards: (options: { accountId: string }) => ExecutableTx
|
claimRewards: (options: { accountId: string }) => ExecutableTx
|
||||||
closeHlsStakingPosition: (options: {
|
closeHlsStakingPosition: (options: { accountId: string; actions: Action[] }) => Promise<boolean>
|
||||||
accountId: string
|
|
||||||
actions: Action[]
|
|
||||||
}) => Promise<string | null>
|
|
||||||
createAccount: (
|
createAccount: (
|
||||||
accountKind: import('types/generated/mars-rover-health-types/MarsRoverHealthTypes.types').AccountKind,
|
accountKind: import('types/generated/mars-rover-health-types/MarsRoverHealthTypes.types').AccountKind,
|
||||||
) => Promise<string | null>
|
) => Promise<string | null>
|
||||||
|
68
src/utils/actions.ts
Normal file
68
src/utils/actions.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { BN_ZERO } from 'constants/math'
|
||||||
|
import { BNCoin } from 'types/classes/BNCoin'
|
||||||
|
import { Action } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
|
||||||
|
import { getCoinAmount, getCoinValue } from 'utils/formatters'
|
||||||
|
|
||||||
|
export function getHlsStakingChangeLevActions(
|
||||||
|
previousAmount: BigNumber,
|
||||||
|
currentAmount: BigNumber,
|
||||||
|
collateralDenom: string,
|
||||||
|
borrowDenom: string,
|
||||||
|
slippage: number,
|
||||||
|
prices: BNCoin[],
|
||||||
|
): Action[] {
|
||||||
|
let actions: Action[] = []
|
||||||
|
|
||||||
|
if (currentAmount.isLessThan(previousAmount)) {
|
||||||
|
const debtValue = getCoinValue(
|
||||||
|
BNCoin.fromDenomAndBigNumber(borrowDenom, previousAmount.minus(currentAmount)),
|
||||||
|
prices,
|
||||||
|
)
|
||||||
|
const collateralAmount = getCoinAmount(collateralDenom, debtValue, prices)
|
||||||
|
|
||||||
|
actions = [
|
||||||
|
{
|
||||||
|
swap_exact_in: {
|
||||||
|
coin_in: BNCoin.fromDenomAndBigNumber(collateralDenom, collateralAmount).toActionCoin(),
|
||||||
|
denom_out: borrowDenom,
|
||||||
|
slippage: slippage.toString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
repay: {
|
||||||
|
coin: BNCoin.fromDenomAndBigNumber(
|
||||||
|
borrowDenom,
|
||||||
|
previousAmount
|
||||||
|
.minus(currentAmount)
|
||||||
|
.times(1 - slippage)
|
||||||
|
.integerValue(),
|
||||||
|
).toActionCoin(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
withdraw: BNCoin.fromDenomAndBigNumber(borrowDenom, BN_ZERO).toActionCoin(true),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
actions = [
|
||||||
|
{
|
||||||
|
borrow: BNCoin.fromDenomAndBigNumber(
|
||||||
|
borrowDenom,
|
||||||
|
currentAmount.minus(previousAmount),
|
||||||
|
).toCoin(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
swap_exact_in: {
|
||||||
|
denom_out: collateralDenom,
|
||||||
|
coin_in: BNCoin.fromDenomAndBigNumber(
|
||||||
|
borrowDenom,
|
||||||
|
currentAmount.minus(previousAmount),
|
||||||
|
).toActionCoin(true),
|
||||||
|
slippage: slippage.toString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions
|
||||||
|
}
|
@ -88,6 +88,7 @@ module.exports = {
|
|||||||
chart: '#220e1d',
|
chart: '#220e1d',
|
||||||
error: '#F04438',
|
error: '#F04438',
|
||||||
'error-bg': '#FDA29B',
|
'error-bg': '#FDA29B',
|
||||||
|
fuchsia: '#B7439F',
|
||||||
green: '#039855',
|
green: '#039855',
|
||||||
grey: '#908e91',
|
grey: '#908e91',
|
||||||
'grey-dark': '#1a1c25',
|
'grey-dark': '#1a1c25',
|
||||||
@ -352,6 +353,18 @@ module.exports = {
|
|||||||
'.gradient-secondary-to-primary': {
|
'.gradient-secondary-to-primary': {
|
||||||
background: 'linear-gradient(180deg, #926AC8 100%, #7F78E8 0%)',
|
background: 'linear-gradient(180deg, #926AC8 100%, #7F78E8 0%)',
|
||||||
},
|
},
|
||||||
|
'.gradient-slider-1': {
|
||||||
|
background: 'linear-gradient(to right, #8D7F85, #B78796)',
|
||||||
|
},
|
||||||
|
'.gradient-slider-2': {
|
||||||
|
background: 'linear-gradient(to right, #C08899, #E08AA6)',
|
||||||
|
},
|
||||||
|
'.gradient-slider-3': {
|
||||||
|
background: 'linear-gradient(to right, #D97FA4, #B84A9C)',
|
||||||
|
},
|
||||||
|
'.gradient-slider-4': {
|
||||||
|
background: 'linear-gradient(to right, #961293, #B3419B)',
|
||||||
|
},
|
||||||
'.gradient-tooltip': {
|
'.gradient-tooltip': {
|
||||||
background:
|
background:
|
||||||
'linear-gradient(77.47deg, rgba(20, 24, 57, 0.9) 11.58%, rgba(34, 16, 57, 0.9) 93.89%)',
|
'linear-gradient(77.47deg, rgba(20, 24, 57, 0.9) 11.58%, rgba(34, 16, 57, 0.9) 93.89%)',
|
||||||
@ -364,6 +377,9 @@ module.exports = {
|
|||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
fontFeatureSettings: '"tnum" on',
|
fontFeatureSettings: '"tnum" on',
|
||||||
},
|
},
|
||||||
|
'.slider-mask': {
|
||||||
|
mask: 'linear-gradient(#fff 0 0)',
|
||||||
|
},
|
||||||
'.text-3xs': { fontSize: '9px', lineHeight: '12px' },
|
'.text-3xs': { fontSize: '9px', lineHeight: '12px' },
|
||||||
'.text-3xs-caps': {
|
'.text-3xs-caps': {
|
||||||
fontSize: '9px',
|
fontSize: '9px',
|
||||||
|
Loading…
Reference in New Issue
Block a user