Perps account preview (#750)

* fix: fixed the Liquidation Price inside the TradeSummary

* feat: added account preview

* tidy: refactor

* feat: added HLS intro

* fix: closing the wallet select focusComponent

* fix: added perps position update to edit as well

* fix: fix update perp

* fix: fail catch

* fix: implemented suggest changes

* tidy: fix

* fix: unfix

* fix: created helper function

* tidy: console.log
This commit is contained in:
Linkie Link 2024-01-30 16:18:54 +01:00 committed by GitHub
parent 3123220fee
commit c4a2a7d913
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 200 additions and 32 deletions

View File

@ -26,8 +26,16 @@ const mapErrorMessages = (providerId: string, errorMessage: string, name: string
}
export default function WalletConnecting(props: Props) {
const { connect, mobileConnect, simulate, sign, broadcast, mobileProviders, extensionProviders } =
useShuttle()
const {
connect,
mobileConnect,
simulate,
sign,
broadcast,
mobileProviders,
extensionProviders,
disconnect,
} = useShuttle()
const providers = useMemo(
() => [...mobileProviders, ...extensionProviders],
[mobileProviders, extensionProviders],
@ -85,6 +93,9 @@ export default function WalletConnecting(props: Props) {
}}
/>
),
onClose: () => {
disconnect({ chainId: chainConfig.id })
},
},
})
}
@ -92,7 +103,17 @@ export default function WalletConnecting(props: Props) {
}
if (!isConnecting) handleConnectAsync()
},
[isConnecting, client, setIsConnecting, connect, chainConfig, broadcast, sign, simulate],
[
isConnecting,
client,
setIsConnecting,
connect,
chainConfig,
broadcast,
sign,
simulate,
disconnect,
],
)
const handleMobileConnect = useCallback(
@ -105,6 +126,9 @@ export default function WalletConnecting(props: Props) {
userDomain: undefined,
focusComponent: {
component: <WalletSelect />,
onClose: () => {
disconnect({ chainId: chainConfig.id })
},
},
})
return
@ -144,6 +168,9 @@ export default function WalletConnecting(props: Props) {
}}
/>
),
onClose: () => {
disconnect({ chainId: chainConfig.id })
},
},
})
}
@ -161,6 +188,7 @@ export default function WalletConnecting(props: Props) {
broadcast,
sign,
simulate,
disconnect,
],
)
@ -172,6 +200,9 @@ export default function WalletConnecting(props: Props) {
userDomain: undefined,
focusComponent: {
component: <WalletSelect />,
onClose: () => {
disconnect({ chainId: chainConfig.id })
},
},
})
return
@ -188,7 +219,15 @@ export default function WalletConnecting(props: Props) {
return
}
handleConnect(provider.id)
}, [handleConnect, isConnecting, providerId, providers, handleMobileConnect])
}, [
handleConnect,
isConnecting,
providerId,
providers,
handleMobileConnect,
disconnect,
chainConfig.id,
])
return (
<FullOverlayContent

View File

@ -58,7 +58,6 @@ export function getVaultAccountBalanceRow(
}
export function getAmountChangeColor(type: PositionType, amount: BigNumber) {
if (type === 'perp') return ''
if (type === 'borrow') {
if (amount.isGreaterThan(0)) return 'text-loss'
if (amount.isLessThan(0)) return 'text-profit'

View File

@ -16,7 +16,7 @@ interface Props {
}
function LabelAndValue(props: { label: string; children: ReactNode; className?: string }) {
const { label, children, className } = props
const { label, children } = props
return (
<div className='flex items-center justify-between'>
@ -58,6 +58,7 @@ function TooltipContent(props: Props) {
export default function Asset(props: Props) {
const { row } = props
return (
<Tooltip content={<TooltipContent row={row} />} type='info'>
<Text size='xs' className='flex items-center gap-1 no-wrap group/asset hover:cursor-help'>

View File

@ -38,7 +38,6 @@ export default function AccountSummary(props: Props) {
const { data: prices } = usePrices()
const assets = useAllAssets()
const updatedAccount = useStore((s) => s.updatedAccount)
const chainConfig = useStore((s) => s.chainConfig)
const accountBalance = useMemo(
() =>
props.account
@ -104,7 +103,7 @@ export default function AccountSummary(props: Props) {
renderSubTitle: () => <></>,
},
]
if (chainConfig.perps)
if (props.account.perps.length > 0)
itemsArray.push({
title: 'Perp Positions',
renderContent: () =>
@ -122,7 +121,6 @@ export default function AccountSummary(props: Props) {
borrowAssetsData,
lendingAssetsData,
props.isHls,
chainConfig.perps,
handleToggle,
accountSummaryTabs,
])

View File

@ -1,16 +1,16 @@
import classNames from 'classnames'
import { isDesktop } from 'react-device-detect'
import { useMemo } from 'react'
import { isDesktop } from 'react-device-detect'
import Wallet from 'components/Wallet'
import AccountMenu from 'components/account/AccountMenu'
import EscButton from 'components/common/Button/EscButton'
import { Coins, CoinsSwap } from 'components/common/Icons'
import Settings from 'components/common/Settings'
import ChainSelect from 'components/header/ChainSelect'
import OracleResyncButton from 'components/header/OracleResyncButton'
import { Coins, CoinsSwap } from 'components/common/Icons'
import DesktopNavigation from 'components/header/navigation/DesktopNavigation'
import RewardsCenter from 'components/header/RewardsCenter'
import Settings from 'components/common/Settings'
import Wallet from 'components/Wallet'
import DesktopNavigation from 'components/header/navigation/DesktopNavigation'
import useAccountId from 'hooks/useAccountId'
import useStore from 'store'
import { WalletID } from 'types/enums/wallet'
@ -83,7 +83,10 @@ export default function DesktopHeader() {
<ChainSelect />
</div>
)}
<EscButton onClick={handleCloseFocusMode} />
<div className='flex gap-4'>
{!address && <ChainSelect />}
<EscButton onClick={handleCloseFocusMode} />
</div>
</div>
) : (
<div className='flex gap-4'>

View File

@ -20,7 +20,7 @@ export default function HLSStakingIntro() {
leftIcon={<PlusSquared />}
onClick={(e) => {
e.preventDefault()
window.open(DocURL.FARM_INTRO_URL, '_blank')
window.open(DocURL.HLS_INTRO_URL, '_blank')
}}
color='secondary'
/>

View File

@ -5,6 +5,7 @@ export const PERP_TYPE_META = { accessorKey: 'tradeDirection', header: 'Side' }
type Props = {
tradeDirection: TradeDirection
className?: string
directionChange?: boolean
}
export default function TradeDirection(props: Props) {

View File

@ -1,5 +1,6 @@
import classNames from 'classnames'
import { useState } from 'react'
import debounce from 'lodash.debounce'
import { useEffect, useMemo, useState } from 'react'
import { Cross } from 'components/common/Icons'
import LeverageSlider from 'components/common/LeverageSlider'
@ -11,12 +12,16 @@ import { Or } from 'components/perps/Module/Or'
import usePerpsManageModule from 'components/perps/Module/PerpsManageModule/usePerpsManageModule'
import PerpsSummary from 'components/perps/Module/Summary'
import AssetAmountInput from 'components/trade/TradeModule/SwapForm/AssetAmountInput'
import useCurrentAccount from 'hooks/accounts/useCurrentAccount'
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
import getPerpsPosition from 'utils/getPerpsPosition'
import { BN } from 'utils/helpers'
export function PerpsManageModule() {
const [tradeDirection, setTradeDirection] = useState<TradeDirection | null>(null)
const [amount, setAmount] = useState<BigNumber | null>(null)
const account = useCurrentAccount()
const { simulatePerps, addedPerps } = useUpdatedAccount(account)
const {
closeManagePerpModule,
previousAmount,
@ -26,6 +31,36 @@ export function PerpsManageModule() {
asset,
} = usePerpsManageModule(amount)
const debouncedUpdateAccount = useMemo(
() =>
debounce((perpsPosition: PerpsPosition) => {
if (
addedPerps &&
perpsPosition.amount === addedPerps.amount &&
perpsPosition.tradeDirection === addedPerps.tradeDirection
)
return
simulatePerps(perpsPosition)
}, 100),
[simulatePerps, addedPerps],
)
useEffect(() => {
const perpsPosition = getPerpsPosition(
asset,
amount ?? previousAmount,
tradeDirection ?? previousTradeDirection,
)
debouncedUpdateAccount(perpsPosition)
}, [
debouncedUpdateAccount,
asset,
amount,
previousAmount,
tradeDirection,
previousTradeDirection,
])
if (!asset) return null
return (
@ -47,7 +82,7 @@ export function PerpsManageModule() {
/>
<AssetAmountInput
label='Amount'
max={BN(100000)} // TODO: Implement max calculation
max={BN(1000000)} // TODO: Implement max calculation
amount={amount ?? previousAmount}
setAmount={setAmount}
asset={asset}

View File

@ -1,4 +1,5 @@
import { useState } from 'react'
import debounce from 'lodash.debounce'
import { useEffect, useMemo, useState } from 'react'
import Card from 'components/common/Card'
import LeverageSlider from 'components/common/LeverageSlider'
@ -13,7 +14,10 @@ import AssetAmountInput from 'components/trade/TradeModule/SwapForm/AssetAmountI
import OrderTypeSelector from 'components/trade/TradeModule/SwapForm/OrderTypeSelector'
import { AvailableOrderType } from 'components/trade/TradeModule/SwapForm/OrderTypeSelector/types'
import { BN_ZERO } from 'constants/math'
import useCurrentAccount from 'hooks/accounts/useCurrentAccount'
import usePerpsAsset from 'hooks/perps/usePerpsAsset'
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
import getPerpsPosition from 'utils/getPerpsPosition'
import { BN } from 'utils/helpers'
export function PerpsModule() {
@ -21,23 +25,43 @@ export function PerpsModule() {
const [tradeDirection, setTradeDirection] = useState<TradeDirection>('long')
const { perpsAsset } = usePerpsAsset()
const [leverage, setLeverage] = useState<number>(1)
const account = useCurrentAccount()
const { simulatePerps, addedPerps } = useUpdatedAccount(account)
const [amount, setAmount] = useState<BigNumber>(BN_ZERO)
const debouncedUpdateAccount = useMemo(
() =>
debounce((perpsPosition: PerpsPosition) => {
if (
addedPerps &&
perpsPosition.amount === addedPerps.amount &&
perpsPosition.tradeDirection === addedPerps.tradeDirection
)
return
simulatePerps(perpsPosition)
}, 100),
[simulatePerps, addedPerps],
)
useEffect(() => {
const perpsPosition = getPerpsPosition(perpsAsset, amount, tradeDirection)
debouncedUpdateAccount(perpsPosition)
}, [debouncedUpdateAccount, amount, perpsAsset, tradeDirection])
if (!perpsAsset) return null
return (
<Card
contentClassName='px-4 gap-5 flex flex-col h-full pb-4'
title={<AssetSelectorPerps asset={perpsAsset} />}
className='mb-4 h-full'
className='h-full mb-4'
>
<OrderTypeSelector selected={selectedOrderType} onChange={setSelectedOrderType} />
<TradeDirectionSelector direction={tradeDirection} onChangeDirection={setTradeDirection} />
<AssetAmountInput
label='Amount'
max={BN(1000)} // TODO: Implement max calculation
max={BN(1000000)} // TODO: Implement max calculation
amount={amount}
setAmount={setAmount}
asset={perpsAsset}

View File

@ -1,5 +1,5 @@
import classNames from 'classnames'
import React, { useMemo } from 'react'
import { useMemo } from 'react'
import ActionButton from 'components/common/Button/ActionButton'
import { CircularProgress } from 'components/common/CircularProgress'
@ -108,7 +108,7 @@ export default function TradeSummary(props: Props) {
<FormattedNumber
className='inline'
amount={liquidationPrice}
options={{ abbreviated: true, prefix: `${props.buyAsset.symbol} = $ ` }}
options={{ abbreviated: true, prefix: `${props.buyAsset.symbol} = $` }}
/>
)}
</div>

View File

@ -1,8 +1,7 @@
import Osmosis1 from 'configs/chains/osmosis/osmosis-1'
import { NETWORK } from 'types/enums/network'
import { ChainInfoID } from 'types/enums/wallet'
import Osmosis1 from './osmosis-1'
const Devnet: ChainConfig = {
...Osmosis1,
id: ChainInfoID.OsmosisDevnet,

View File

@ -33,9 +33,12 @@ function useDisplayCurrencyPrice() {
)
const convertAmount = useCallback(
(asset: Asset, amount: string | number | BigNumber) =>
getConversionRate(asset.denom)?.multipliedBy(BN(amount).shiftedBy(-asset.decimals)) ??
BN_ZERO,
(asset: Asset, amount: string | number | BigNumber) => {
return (
getConversionRate(asset.denom)?.multipliedBy(BN(amount).shiftedBy(-asset.decimals)) ??
BN_ZERO
)
},
[getConversionRate],
)

View File

@ -36,6 +36,27 @@ export function removeCoins(coinsToRemove: BNCoin[], currentCoins: BNCoin[]) {
return currentCoins
}
export function updatePerpsPositions(
currentPositions: PerpsPosition[],
updatedPosition?: PerpsPosition,
): PerpsPosition[] {
if (!updatedPosition) {
return currentPositions ?? []
}
const currentDenoms = currentPositions.map((position) => position.denom)
const index = currentDenoms.indexOf(updatedPosition.denom)
if (index === -1) {
currentPositions.push(updatedPosition)
return currentPositions
}
currentPositions[index].tradeDirection = updatedPosition.tradeDirection
currentPositions[index].amount = updatedPosition.amount
return currentPositions
}
export function addValueToVaults(
vaultValues: VaultValue[],
vaults: DepositedVault[],

View File

@ -11,6 +11,7 @@ import {
addValueToVaults,
getDepositAndLendCoinsToSpend,
removeCoins,
updatePerpsPositions,
} from 'hooks/useUpdatedAccount/functions'
import useVaults from 'hooks/useVaults'
import useStore from 'store'
@ -43,6 +44,7 @@ export function useUpdatedAccount(account?: Account) {
const [addedLends, addLends] = useState<BNCoin[]>([])
const [removedLends, removeLends] = useState<BNCoin[]>([])
const [addedTrades, addTrades] = useState<BNCoin[]>([])
const [addedPerps, addPerps] = useState<PerpsPosition>()
const [leverage, setLeverage] = useState<number>(0)
const removeDepositAndLendsByDenom = useCallback(
@ -241,6 +243,14 @@ export function useUpdatedAccount(account?: Account) {
[account, assets, prices, slippage],
)
const simulatePerps = useCallback(
(position: PerpsPosition) => {
if (!account) return
addPerps(position)
},
[account, addPerps],
)
useEffect(() => {
if (!account) return
@ -252,6 +262,7 @@ export function useUpdatedAccount(account?: Account) {
[...accountCopy.vaults],
availableVaults ?? [],
)
accountCopy.perps = updatePerpsPositions([...accountCopy.perps], addedPerps)
accountCopy.deposits = removeCoins(removedDeposits, [...accountCopy.deposits])
accountCopy.debts = removeCoins(removedDebts, [...accountCopy.debts])
accountCopy.lends = addCoins(addedLends, [...accountCopy.lends])
@ -274,6 +285,7 @@ export function useUpdatedAccount(account?: Account) {
prices,
addedTrades,
assets,
addedPerps,
])
return {
@ -286,10 +298,12 @@ export function useUpdatedAccount(account?: Account) {
addLends,
removeLends,
addVaultValues,
addPerps,
addedDeposits,
addedDebts,
addedLends,
addedTrades,
addedPerps,
leverage,
removedDeposits,
removedDebts,
@ -303,5 +317,6 @@ export function useUpdatedAccount(account?: Account) {
simulateTrade,
simulateVaultDeposit,
simulateWithdraw,
simulatePerps,
}
}

View File

@ -8,6 +8,7 @@ export enum DocURL {
COUNCIL_KEPLR = 'https://wallet.keplr.app/chains/mars-hub?tab=governance',
DOCS_URL = 'https://docs.marsprotocol.io/',
FARM_INTRO_URL = 'https://docs.marsprotocol.io/docs/learn/tutorials/farming/farming-intro',
HLS_INTRO_URL = 'https://docs.marsprotocol.io/docs/learn/mars-v2/high-leveraged-strategies/high-leveraged-strategies-intro',
MANAGE_ACCOUNT_URL = 'https://docs.marsprotocol.io/docs/learn/tutorials/credit-accounts/credit-accounts-intro',
ROVER_INTRO_URL = 'https://docs.marsprotocol.io/docs/learn/mars-v2/credit-accounts',
PRIVACY_POLICY_URL = 'https://docs.marsprotocol.io/docs/overview/legal/privacy-policy',

View File

@ -242,8 +242,14 @@ export function cloneAccount(account: Account): Account {
unlocked: vault.values.unlocked,
},
})),
// TODO: 📈Add correct type mapping
perps: account.perps,
perps: account.perps.map((perpPosition) => ({
...perpPosition,
amount: perpPosition.amount,
closingFee: perpPosition.closingFee,
pnl: perpPosition.pnl,
entryPrice: perpPosition.entryPrice,
tradeDirection: perpPosition.tradeDirection,
})),
}
}

View File

@ -0,0 +1,23 @@
import BigNumber from 'bignumber.js'
import { BN_ONE } from 'constants/math'
import { BNCoin } from 'types/classes/BNCoin'
export default function getPerpsPosition(
asset: Asset,
amount: BigNumber,
tradeDirection: TradeDirection,
) {
const perpsBaseDenom = 'ibc/F91EA2C0A23697A1048E08C2F787E3A58AC6F706A1CD2257A504925158CFC0F3'
const perpsPosition = {
amount,
closingFee: BNCoin.fromDenomAndBigNumber(perpsBaseDenom, BN_ONE),
pnl: BNCoin.fromDenomAndBigNumber(perpsBaseDenom, BN_ONE.negated()),
entryPrice: BN_ONE,
baseDenom: perpsBaseDenom,
denom: asset.denom,
tradeDirection,
}
return perpsPosition
}