Extend perps (#721)
This commit is contained in:
parent
14d09409f9
commit
bfd03d66a4
@ -1,5 +1,6 @@
|
||||
import { cacheFn, oraclePriceCache } from 'api/cache'
|
||||
import { getOracleQueryClient } from 'api/cosmwasm-client'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
import { PRICE_ORACLE_DECIMALS } from 'constants/query'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { PriceResponse } from 'types/generated/mars-oracle-osmosis/MarsOracleOsmosis.types'
|
||||
@ -27,7 +28,7 @@ export default async function getOraclePrices(
|
||||
const decimalDiff = asset.decimals - PRICE_ORACLE_DECIMALS
|
||||
return BNCoin.fromDenomAndBigNumber(
|
||||
asset.denom,
|
||||
BN(priceResponse.price).shiftedBy(decimalDiff),
|
||||
BN(priceResponse?.price ?? BN_ZERO).shiftedBy(decimalDiff),
|
||||
)
|
||||
})
|
||||
} catch (ex) {
|
||||
|
@ -18,6 +18,7 @@ interface Props {
|
||||
parentheses?: boolean
|
||||
showZero?: boolean
|
||||
options?: FormatOptions
|
||||
isProfitOrLoss?: boolean
|
||||
}
|
||||
|
||||
export default function DisplayCurrency(props: Props) {
|
||||
@ -34,10 +35,10 @@ export default function DisplayCurrency(props: Props) {
|
||||
|
||||
const isUSD = displayCurrencyAsset.id === 'USD'
|
||||
|
||||
const amount = useMemo(() => {
|
||||
const [amount, absoluteAmount] = useMemo(() => {
|
||||
const coinValue = getCoinValue(props.coin, prices, assets)
|
||||
|
||||
if (displayCurrency === ORACLE_DENOM) return coinValue.toNumber()
|
||||
if (displayCurrency === ORACLE_DENOM) return [coinValue.toNumber(), coinValue.abs().toNumber()]
|
||||
|
||||
const displayDecimals = displayCurrencyAsset.decimals
|
||||
const displayPrice = getCoinValue(
|
||||
@ -46,10 +47,14 @@ export default function DisplayCurrency(props: Props) {
|
||||
assets,
|
||||
)
|
||||
|
||||
return coinValue.div(displayPrice).toNumber()
|
||||
const amount = coinValue.div(displayPrice).toNumber()
|
||||
|
||||
return [amount, Math.abs(amount)]
|
||||
}, [assets, displayCurrency, displayCurrencyAsset.decimals, prices, props.coin])
|
||||
|
||||
const isLessThanACent = (isUSD && amount < 0.01 && amount > 0) || (amount === 0 && props.showZero)
|
||||
const isLessThanACent =
|
||||
(isUSD && absoluteAmount < 0.01 && absoluteAmount > 0) ||
|
||||
(absoluteAmount === 0 && props.showZero)
|
||||
const smallerThanPrefix = isLessThanACent ? '< ' : ''
|
||||
|
||||
const prefix = isUSD
|
||||
@ -64,8 +69,10 @@ export default function DisplayCurrency(props: Props) {
|
||||
className={classNames(
|
||||
props.className,
|
||||
props.parentheses && 'before:content-["("] after:content-[")"]',
|
||||
props.isProfitOrLoss && (amount < 0 ? 'text-error' : amount === 0 ? '' : 'text-success'),
|
||||
props.isProfitOrLoss && amount < 0 && 'before:content-["-"]',
|
||||
)}
|
||||
amount={isLessThanACent ? 0.01 : amount}
|
||||
amount={isLessThanACent ? 0.01 : absoluteAmount}
|
||||
options={{
|
||||
minDecimals: isUSD ? 2 : 0,
|
||||
maxDecimals: 2,
|
||||
|
@ -35,7 +35,6 @@ export default function ChainSelect() {
|
||||
client: undefined,
|
||||
address: undefined,
|
||||
userDomain: undefined,
|
||||
accounts: null,
|
||||
balances: [],
|
||||
})
|
||||
navigate(getRoute(getPage(pathname), searchParams))
|
||||
|
31
src/components/Perps/BalancesTable/Columns/Leverage.tsx
Normal file
31
src/components/Perps/BalancesTable/Columns/Leverage.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import Text from 'components/Text'
|
||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||
|
||||
export const LEVERAGE_META = {
|
||||
accessorKey: 'leverage',
|
||||
header: () => (
|
||||
<div className='flex flex-col gap-1'>
|
||||
<Text size='xs'>Liquidation Price</Text>
|
||||
<Text size='xs' className='text-white/40'>
|
||||
Leverage
|
||||
</Text>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
type Props = {
|
||||
liquidationPrice: BigNumber
|
||||
leverage: number
|
||||
}
|
||||
|
||||
export default function Leverage(props: Props) {
|
||||
return (
|
||||
<TitleAndSubCell
|
||||
title={
|
||||
<FormattedNumber amount={props.liquidationPrice.toNumber()} options={{ prefix: '$' }} />
|
||||
}
|
||||
sub={<FormattedNumber amount={props.leverage} options={{ suffix: 'x' }} />}
|
||||
/>
|
||||
)
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
import classNames from 'classnames'
|
||||
|
||||
import Text from 'components/Text'
|
||||
|
||||
export const PERP_TYPE_META = { accessorKey: 'type', header: 'Side' }
|
||||
|
||||
type Props = {
|
||||
type: PerpsType
|
||||
}
|
||||
|
||||
export default function PerpType(props: Props) {
|
||||
return (
|
||||
<Text
|
||||
size='xs'
|
||||
className={classNames(
|
||||
'capitalize px-1 py-0.5 rounded-sm inline',
|
||||
props.type === 'short' && 'text-error bg-error/20',
|
||||
props.type === 'long' && 'text-success bg-success/20',
|
||||
)}
|
||||
>
|
||||
{props.type}
|
||||
</Text>
|
||||
)
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import classNames from 'classnames'
|
||||
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import Text from 'components/Text'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
|
||||
export const PNL_META = { accessorKey: 'pnl', header: 'Total PnL', id: 'pnl' }
|
||||
@ -10,19 +11,38 @@ type Props = {
|
||||
}
|
||||
|
||||
export default function PnL(props: Props) {
|
||||
const isNegative = props.pnl.amount.isNegative()
|
||||
return (
|
||||
<span
|
||||
className={classNames(
|
||||
'text-xs',
|
||||
isNegative ? 'text-error' : props.pnl.amount.isZero() ? '' : 'text-success',
|
||||
)}
|
||||
<Tooltip
|
||||
content={
|
||||
<PnLTooltip
|
||||
realized={BNCoin.fromDenomAndBigNumber('uusd', BN_ZERO)}
|
||||
unrealized={props.pnl}
|
||||
/>
|
||||
}
|
||||
type='info'
|
||||
underline
|
||||
>
|
||||
{isNegative ? '-' : props.pnl.amount.isZero() ? '' : '+'}
|
||||
<DisplayCurrency
|
||||
className='inline'
|
||||
coin={BNCoin.fromDenomAndBigNumber(props.pnl.denom, props.pnl.amount.abs())}
|
||||
/>
|
||||
</span>
|
||||
<DisplayCurrency className='inline text-xs' coin={props.pnl} isProfitOrLoss />
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
type PnLTooltipProps = {
|
||||
realized: BNCoin
|
||||
unrealized: BNCoin
|
||||
}
|
||||
|
||||
function PnLTooltip(props: PnLTooltipProps) {
|
||||
return (
|
||||
<div className='flex flex-col gap-2 w-full'>
|
||||
{[props.realized, props.unrealized].map((coin, i) => (
|
||||
<div key={i} className='flex w-full text-white/60 space-between items-center gap-8'>
|
||||
<Text className='mr-auto' size='sm'>
|
||||
{i === 0 ? 'Realized' : 'Unrealized'} PnL
|
||||
</Text>
|
||||
<DisplayCurrency coin={coin} className='self-end text-end' isProfitOrLoss />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
import classNames from 'classnames'
|
||||
|
||||
import Text from 'components/Text'
|
||||
|
||||
export const PERP_TYPE_META = { accessorKey: 'tradeDirection', header: 'Side' }
|
||||
|
||||
type Props = {
|
||||
tradeDirection: TradeDirection
|
||||
}
|
||||
|
||||
export default function TradeDirection(props: Props) {
|
||||
const { tradeDirection } = props
|
||||
return (
|
||||
<Text
|
||||
size='xs'
|
||||
className={classNames(
|
||||
'capitalize px-1 py-0.5 rounded-sm inline',
|
||||
tradeDirection === 'short' && 'text-error bg-error/20',
|
||||
tradeDirection === 'long' && 'text-success bg-success/20',
|
||||
)}
|
||||
>
|
||||
{tradeDirection}
|
||||
</Text>
|
||||
)
|
||||
}
|
@ -2,11 +2,14 @@ import { ColumnDef } from '@tanstack/react-table'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import EntryPrice, { ENTRY_PRICE_META } from 'components/Perps/BalancesTable/Columns/EntryPrice'
|
||||
import Leverage, { LEVERAGE_META } from 'components/Perps/BalancesTable/Columns/Leverage'
|
||||
import Manage, { MANAGE_META } from 'components/Perps/BalancesTable/Columns/Manage'
|
||||
import { PERP_NAME_META, PerpName } from 'components/Perps/BalancesTable/Columns/PerpName'
|
||||
import PerpType, { PERP_TYPE_META } from 'components/Perps/BalancesTable/Columns/PerpType'
|
||||
import PnL, { PNL_META } from 'components/Perps/BalancesTable/Columns/PnL'
|
||||
import Size, { SIZE_META } from 'components/Perps/BalancesTable/Columns/Size'
|
||||
import TradeDirection, {
|
||||
PERP_TYPE_META,
|
||||
} from 'components/Perps/BalancesTable/Columns/TradeDirection'
|
||||
import { PerpPositionRow } from 'components/Perps/BalancesTable/usePerpsBalancesData'
|
||||
|
||||
export default function usePerpsBalancesTable() {
|
||||
@ -18,12 +21,21 @@ export default function usePerpsBalancesTable() {
|
||||
},
|
||||
{
|
||||
...PERP_TYPE_META,
|
||||
cell: ({ row }) => <PerpType type={row.original.type} />,
|
||||
cell: ({ row }) => <TradeDirection tradeDirection={row.original.tradeDirection} />,
|
||||
},
|
||||
{
|
||||
...SIZE_META,
|
||||
cell: ({ row }) => <Size size={row.original.size} asset={row.original.asset} />,
|
||||
},
|
||||
{
|
||||
...LEVERAGE_META,
|
||||
cell: ({ row }) => (
|
||||
<Leverage
|
||||
liquidationPrice={row.original.liquidationPrice}
|
||||
leverage={row.original.leverage}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
...ENTRY_PRICE_META,
|
||||
cell: ({ row }) => (
|
||||
|
@ -1,34 +1,48 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
import useAllAssets from 'hooks/assets/useAllAssets'
|
||||
import usePerpsEnabledAssets from 'hooks/assets/usePerpsEnabledAssets'
|
||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||
import usePrices from 'hooks/usePrices'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { getAccountNetValue } from 'utils/accounts'
|
||||
import { byDenom } from 'utils/array'
|
||||
import { demagnify } from 'utils/formatters'
|
||||
|
||||
export default function usePerpsBalancesTable() {
|
||||
const currentAccount = useCurrentAccount()
|
||||
const perpAssets = usePerpsEnabledAssets()
|
||||
const allAssets = useAllAssets()
|
||||
const { data: prices } = usePrices()
|
||||
|
||||
return useMemo<PerpPositionRow[]>(() => {
|
||||
if (!currentAccount) return []
|
||||
|
||||
const netValue = getAccountNetValue(currentAccount, prices, allAssets)
|
||||
|
||||
return currentAccount.perps.map((position) => {
|
||||
const asset = perpAssets.find(byDenom(position.denom))
|
||||
const price = prices.find(byDenom(position.denom))?.amount ?? BN_ZERO
|
||||
const asset = perpAssets.find(byDenom(position.denom))!
|
||||
return {
|
||||
asset,
|
||||
type: position.type,
|
||||
tradeDirection: position.tradeDirection,
|
||||
size: position.size,
|
||||
pnl: position.pnl,
|
||||
entryPrice: position.entryPrice,
|
||||
liquidationPrice: position.entryPrice, // TODO: 📈 Get actual liquidation price from HC
|
||||
leverage: price.times(demagnify(position.size, asset)).div(netValue).plus(1).toNumber(),
|
||||
} as PerpPositionRow
|
||||
})
|
||||
}, [currentAccount, perpAssets])
|
||||
}, [allAssets, currentAccount, perpAssets, prices])
|
||||
}
|
||||
|
||||
export type PerpPositionRow = {
|
||||
asset: Asset
|
||||
type: 'long' | 'short'
|
||||
tradeDirection: TradeDirection
|
||||
size: BigNumber
|
||||
pnl: BNCoin
|
||||
entryPrice: BigNumber
|
||||
liquidationPrice: BigNumber
|
||||
leverage: number
|
||||
}
|
||||
|
@ -1,14 +1,15 @@
|
||||
import Button from 'components/Button'
|
||||
|
||||
const LEVERAGE_PRESETS = [1, 2, 3, 5, 10]
|
||||
|
||||
export function LeverageButtons() {
|
||||
return (
|
||||
<div className='flex justify-between'>
|
||||
{LEVERAGE_PRESETS.map((leverage) => (
|
||||
<Button key={leverage} color='tertiary' className='w-12'>
|
||||
<button
|
||||
key={leverage}
|
||||
className='w-12 !border:none bg-white/10 rounded-sm py-1 text-xs hover:bg-white/20'
|
||||
>
|
||||
{leverage}x
|
||||
</Button>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useState } from 'react'
|
||||
|
||||
import Button from 'components/Button'
|
||||
import Card from 'components/Card'
|
||||
import { DirectionSelect } from 'components/DirectionSelect'
|
||||
import { LeverageButtons } from 'components/Perps/Module/LeverageButtons'
|
||||
import { Or } from 'components/Perps/Module/Or'
|
||||
import PerpsSummary from 'components/Perps/Module/Summary'
|
||||
import RangeInput from 'components/RangeInput'
|
||||
import { Spacer } from 'components/Spacer'
|
||||
import Text from 'components/Text'
|
||||
@ -12,50 +11,35 @@ import AssetSelectorPerps from 'components/Trade/TradeModule/AssetSelector/Asset
|
||||
import AssetAmountInput from 'components/Trade/TradeModule/SwapForm/AssetAmountInput'
|
||||
import OrderTypeSelector from 'components/Trade/TradeModule/SwapForm/OrderTypeSelector'
|
||||
import { AvailableOrderType } from 'components/Trade/TradeModule/SwapForm/OrderTypeSelector/types'
|
||||
import { TradeDirectionSelector } from 'components/TradeDirectionSelector'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
import useBaseAsset from 'hooks/assets/useBasetAsset'
|
||||
import usePerpsAsset from 'hooks/perps/usePerpsAsset'
|
||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||
import useStore from 'store'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
export function PerpsModule() {
|
||||
const [selectedOrderType, setSelectedOrderType] = useState<AvailableOrderType>('Market')
|
||||
const [selectedOrderDirection, setSelectedOrderDirection] = useState<OrderDirection>('long')
|
||||
const baseAsset = useBaseAsset()
|
||||
const [tradeDirection, setTradeDirection] = useState<TradeDirection>('long')
|
||||
const { perpsAsset } = usePerpsAsset()
|
||||
const openPerpPosition = useStore((s) => s.openPerpPosition)
|
||||
const currentAccount = useCurrentAccount()
|
||||
|
||||
const onConfirm = useCallback(async () => {
|
||||
if (!currentAccount) return
|
||||
await openPerpPosition({
|
||||
accountId: currentAccount.id,
|
||||
coin: BNCoin.fromDenomAndBigNumber(perpsAsset.denom, BN(1000)),
|
||||
})
|
||||
}, [currentAccount, openPerpPosition, perpsAsset.denom])
|
||||
const [amount, setAmount] = useState<BigNumber>(BN_ZERO)
|
||||
|
||||
if (!perpsAsset) return null
|
||||
|
||||
return (
|
||||
<Card
|
||||
contentClassName='px-4 gap-5 flex flex-col'
|
||||
contentClassName='px-4 gap-5 flex flex-col h-full pb-4'
|
||||
title={<AssetSelectorPerps asset={perpsAsset} />}
|
||||
className='mb-4'
|
||||
className='mb-4 h-full'
|
||||
>
|
||||
<OrderTypeSelector selected={selectedOrderType} onChange={setSelectedOrderType} />
|
||||
|
||||
<DirectionSelect
|
||||
direction={selectedOrderDirection}
|
||||
onChangeDirection={setSelectedOrderDirection}
|
||||
/>
|
||||
<TradeDirectionSelector direction={tradeDirection} onChangeDirection={setTradeDirection} />
|
||||
<AssetAmountInput
|
||||
label='Amount'
|
||||
max={BN_ZERO}
|
||||
amount={BN_ZERO}
|
||||
setAmount={() => {}}
|
||||
asset={baseAsset}
|
||||
max={BN(1000)} // TODO: Implement max calculation
|
||||
amount={amount}
|
||||
setAmount={setAmount}
|
||||
asset={perpsAsset}
|
||||
maxButtonLabel='Max:'
|
||||
disabled={false}
|
||||
/>
|
||||
@ -64,7 +48,7 @@ export function PerpsModule() {
|
||||
<RangeInput max={0} value={0} onChange={() => {}} />
|
||||
<LeverageButtons />
|
||||
<Spacer />
|
||||
<Button onClick={onConfirm}>{selectedOrderDirection} ETH</Button>
|
||||
<PerpsSummary amount={amount} tradeDirection={tradeDirection} asset={perpsAsset} />
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
47
src/components/Perps/Module/Summary.tsx
Normal file
47
src/components/Perps/Module/Summary.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import { useCallback } from 'react'
|
||||
|
||||
import ActionButton from 'components/Button/ActionButton'
|
||||
import SummaryLine from 'components/SummaryLine'
|
||||
import Text from 'components/Text'
|
||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||
import useStore from 'store'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
|
||||
type Props = {
|
||||
amount: BigNumber
|
||||
tradeDirection: TradeDirection
|
||||
asset: Asset
|
||||
}
|
||||
|
||||
export default function PerpsSummary(props: Props) {
|
||||
const openPerpPosition = useStore((s) => s.openPerpPosition)
|
||||
const currentAccount = useCurrentAccount()
|
||||
|
||||
const onConfirm = useCallback(async () => {
|
||||
if (!currentAccount) return
|
||||
await openPerpPosition({
|
||||
accountId: currentAccount.id,
|
||||
coin: BNCoin.fromDenomAndBigNumber(
|
||||
props.asset.denom,
|
||||
props.amount.times(props.tradeDirection === 'short' ? -1 : 1),
|
||||
),
|
||||
})
|
||||
}, [currentAccount, openPerpPosition, props.amount, props.asset.denom, props.tradeDirection])
|
||||
|
||||
return (
|
||||
<div className='border border-white/10 rounded-sm bg-white/5'>
|
||||
<div className='py-4 px-3 flex flex-col gap-1'>
|
||||
<Text size='xs' className='font-bold mb-2'>
|
||||
Summary
|
||||
</Text>
|
||||
<SummaryLine label='Expected Price'>Something</SummaryLine>
|
||||
<SummaryLine label='Fees'>Something</SummaryLine>
|
||||
<SummaryLine label='Total'>Something</SummaryLine>
|
||||
</div>
|
||||
<ActionButton onClick={onConfirm} className='w-full py-2.5'>
|
||||
<span className='capitalize mr-1'>{props.tradeDirection}</span>
|
||||
{props.asset.symbol}
|
||||
</ActionButton>
|
||||
</div>
|
||||
)
|
||||
}
|
18
src/components/SummaryLine.tsx
Normal file
18
src/components/SummaryLine.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import classNames from 'classnames'
|
||||
import React from 'react'
|
||||
|
||||
const infoLineClasses = 'flex flex-row justify-between flex-1 mb-1 text-xs text-white'
|
||||
|
||||
interface SummaryLineProps {
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
label: string
|
||||
}
|
||||
export default function SummaryLine(props: SummaryLineProps) {
|
||||
return (
|
||||
<div className={classNames(infoLineClasses, props.className)}>
|
||||
<span className='opacity-40'>{props.label}</span>
|
||||
<span>{props.children}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -16,7 +16,7 @@ export default function TooltipContent(props: Props) {
|
||||
<div>
|
||||
<div
|
||||
className={classNames(
|
||||
'flex max-w-[320px] flex-1 gap-2 rounded-sm py-1 px-2 text-sm shadow-tooltip backdrop-blur-lg',
|
||||
'flex max-w-[320px] flex-1 gap-2 rounded-sm p-3 text-sm shadow-tooltip backdrop-blur-lg',
|
||||
props.type === 'info' && 'bg-white/20',
|
||||
props.type === 'warning' && 'bg-warning',
|
||||
props.type === 'error' && 'bg-error',
|
||||
|
@ -51,7 +51,7 @@ export const Tooltip = (props: Props) => {
|
||||
<span
|
||||
className={classNames(
|
||||
props.underline &&
|
||||
'border-b-1 hover:cursor-pointer border border-x-0 border-t-0 border-dashed border-white/50 hover:border-transparent',
|
||||
'border-b-1 hover:cursor-pointer border border-x-0 border-t-0 border-dashed border-white/20 pb-1',
|
||||
!reduceMotion && 'transition-all',
|
||||
props.className,
|
||||
)}
|
||||
|
@ -7,6 +7,7 @@ import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import Divider from 'components/Divider'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { ChevronDown } from 'components/Icons'
|
||||
import SummaryLine from 'components/SummaryLine'
|
||||
import Text from 'components/Text'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { LocalStorageKeys } from 'constants/localStorageKeys'
|
||||
@ -36,11 +37,9 @@ interface Props {
|
||||
sellAsset: Asset
|
||||
showProgressIndicator: boolean
|
||||
isAdvanced?: boolean
|
||||
direction?: OrderDirection
|
||||
direction?: TradeDirection
|
||||
}
|
||||
|
||||
const infoLineClasses = 'flex flex-row justify-between flex-1 mb-1 text-xs text-white'
|
||||
|
||||
export default function TradeSummary(props: Props) {
|
||||
const {
|
||||
buyAsset,
|
||||
@ -87,7 +86,7 @@ export default function TradeSummary(props: Props) {
|
||||
}, [assets, route, sellAsset.symbol])
|
||||
|
||||
const buttonText = useMemo(() => {
|
||||
if (!isAdvanced && direction === 'sell') return `Sell ${sellAsset.symbol}`
|
||||
if (!isAdvanced && direction === 'short') return `Sell ${sellAsset.symbol}`
|
||||
return route.length ? `Buy ${buyAsset.symbol}` : 'No route found'
|
||||
}, [buyAsset.symbol, route, sellAsset.symbol, isAdvanced, direction])
|
||||
|
||||
@ -189,17 +188,3 @@ export default function TradeSummary(props: Props) {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface SummaryLineProps {
|
||||
children: React.ReactNode
|
||||
label: string
|
||||
className?: string
|
||||
}
|
||||
function SummaryLine(props: SummaryLineProps) {
|
||||
return (
|
||||
<div className={classNames(infoLineClasses, props.className)}>
|
||||
<span className='opacity-40'>{props.label}</span>
|
||||
<span>{props.children}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import estimateExactIn from 'api/swap/estimateExactIn'
|
||||
import AvailableLiquidityMessage from 'components/AvailableLiquidityMessage'
|
||||
import DepositCapMessage from 'components/DepositCapMessage'
|
||||
import { DirectionSelect } from 'components/DirectionSelect'
|
||||
import Divider from 'components/Divider'
|
||||
import RangeInput from 'components/RangeInput'
|
||||
import Text from 'components/Text'
|
||||
@ -16,6 +15,7 @@ import MarginToggle from 'components/Trade/TradeModule/SwapForm/MarginToggle'
|
||||
import OrderTypeSelector from 'components/Trade/TradeModule/SwapForm/OrderTypeSelector'
|
||||
import { AvailableOrderType } from 'components/Trade/TradeModule/SwapForm/OrderTypeSelector/types'
|
||||
import TradeSummary from 'components/Trade/TradeModule/SwapForm/TradeSummary'
|
||||
import { TradeDirectionSelector } from 'components/TradeDirectionSelector'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { LocalStorageKeys } from 'constants/localStorageKeys'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
@ -52,14 +52,15 @@ export default function SwapForm(props: Props) {
|
||||
const swap = useStore((s) => s.swap)
|
||||
const [slippage] = useLocalStorage(LocalStorageKeys.SLIPPAGE, DEFAULT_SETTINGS.slippage)
|
||||
const { computeMaxSwapAmount } = useHealthComputer(account)
|
||||
const [orderDirection, setOrderDirection] = useState<OrderDirection>('buy')
|
||||
const [tradeDirection, setTradeDirection] = useState<TradeDirection>('long')
|
||||
const { data: borrowAssets } = useMarketBorrowings()
|
||||
const { data: marketAssets } = useMarketAssets()
|
||||
|
||||
const [inputAsset, outputAsset] = useMemo(() => {
|
||||
if (isAdvanced) return [sellAsset, buyAsset]
|
||||
if (orderDirection === 'buy') return [sellAsset, buyAsset]
|
||||
if (tradeDirection === 'long') return [sellAsset, buyAsset]
|
||||
return [buyAsset, sellAsset]
|
||||
}, [buyAsset, sellAsset, orderDirection, isAdvanced])
|
||||
}, [buyAsset, sellAsset, tradeDirection, isAdvanced])
|
||||
const { data: route, isLoading: isRouteLoading } = useSwapRoute(
|
||||
inputAsset.denom,
|
||||
outputAsset.denom,
|
||||
@ -246,7 +247,7 @@ export default function SwapForm(props: Props) {
|
||||
useEffect(() => {
|
||||
onChangeOutputAmount(BN_ZERO)
|
||||
onChangeInputAmount(BN_ZERO)
|
||||
}, [orderDirection, onChangeOutputAmount, onChangeInputAmount])
|
||||
}, [tradeDirection, onChangeOutputAmount, onChangeInputAmount])
|
||||
|
||||
useEffect(() => {
|
||||
setOutputAssetAmount(BN_ZERO)
|
||||
@ -375,9 +376,9 @@ export default function SwapForm(props: Props) {
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<DirectionSelect
|
||||
direction={orderDirection}
|
||||
onChangeDirection={setOrderDirection}
|
||||
<TradeDirectionSelector
|
||||
direction={tradeDirection}
|
||||
onChangeDirection={setTradeDirection}
|
||||
asset={buyAsset}
|
||||
/>
|
||||
<AssetAmountInput
|
||||
@ -460,7 +461,7 @@ export default function SwapForm(props: Props) {
|
||||
sellAmount={inputAssetAmount}
|
||||
buyAmount={outputAssetAmount}
|
||||
isAdvanced={isAdvanced}
|
||||
direction={orderDirection}
|
||||
direction={tradeDirection}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
@ -1,28 +1,27 @@
|
||||
import classNames from 'classnames'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import Text from 'components/Text'
|
||||
|
||||
interface Props {
|
||||
direction: OrderDirection
|
||||
onChangeDirection: (direction: OrderDirection) => void
|
||||
direction: TradeDirection
|
||||
onChangeDirection: (direction: TradeDirection) => void
|
||||
asset?: Asset
|
||||
}
|
||||
|
||||
export function DirectionSelect(props: Props) {
|
||||
const hasAsset = props.asset
|
||||
const directions: OrderDirection[] = hasAsset ? ['buy', 'sell'] : ['long', 'short']
|
||||
export function TradeDirectionSelector(props: Props) {
|
||||
return (
|
||||
<div className='flex rounded-sm bg-black/20'>
|
||||
<Direction
|
||||
onClick={() => props.onChangeDirection(directions[0])}
|
||||
direction={directions[0]}
|
||||
isActive={props.direction === directions[0]}
|
||||
onClick={() => props.onChangeDirection('long')}
|
||||
direction={'long'}
|
||||
isActive={props.direction === 'long'}
|
||||
asset={props.asset}
|
||||
/>
|
||||
<Direction
|
||||
onClick={() => props.onChangeDirection(directions[1])}
|
||||
direction={directions[1]}
|
||||
isActive={props.direction === directions[1]}
|
||||
onClick={() => props.onChangeDirection('short')}
|
||||
direction={'short'}
|
||||
isActive={props.direction === 'short'}
|
||||
asset={props.asset}
|
||||
/>
|
||||
</div>
|
||||
@ -30,13 +29,22 @@ export function DirectionSelect(props: Props) {
|
||||
}
|
||||
|
||||
interface DirectionProps {
|
||||
direction: 'long' | 'short' | 'buy' | 'sell'
|
||||
direction: TradeDirection
|
||||
isActive: boolean
|
||||
onClick: () => void
|
||||
asset?: Asset
|
||||
}
|
||||
function Direction(props: DirectionProps) {
|
||||
const classString = props.direction === 'long' || props.direction === 'buy' ? 'success' : 'error'
|
||||
const classString = props.direction === 'long' ? 'success' : 'error'
|
||||
|
||||
const label = useMemo(() => {
|
||||
if (props.asset) {
|
||||
return props.direction === 'long' ? `Buy ${props.asset.symbol}` : `Sell ${props.asset.symbol}`
|
||||
} else {
|
||||
return props.direction === 'long' ? 'Long' : 'Short'
|
||||
}
|
||||
}, [props.asset, props.direction])
|
||||
|
||||
return (
|
||||
<button
|
||||
className={classNames(
|
||||
@ -52,7 +60,7 @@ function Direction(props: DirectionProps) {
|
||||
props.isActive ? `text-${classString}` : 'text-white/20',
|
||||
)}
|
||||
>
|
||||
{props.asset ? `${props.direction} ${props.asset.symbol}` : props.direction}
|
||||
{label}
|
||||
</Text>
|
||||
</button>
|
||||
)
|
@ -74,7 +74,6 @@ export default function WalletConnectedButton() {
|
||||
client: undefined,
|
||||
address: undefined,
|
||||
userDomain: undefined,
|
||||
accounts: null,
|
||||
balances: [],
|
||||
focusComponent: null,
|
||||
})
|
||||
|
@ -72,7 +72,6 @@ export default function WalletConnecting(props: Props) {
|
||||
client: undefined,
|
||||
address: undefined,
|
||||
userDomain: undefined,
|
||||
accounts: null,
|
||||
focusComponent: {
|
||||
component: (
|
||||
<WalletSelect
|
||||
@ -104,7 +103,6 @@ export default function WalletConnecting(props: Props) {
|
||||
client: undefined,
|
||||
address: undefined,
|
||||
userDomain: undefined,
|
||||
accounts: null,
|
||||
focusComponent: {
|
||||
component: <WalletSelect />,
|
||||
},
|
||||
@ -137,7 +135,6 @@ export default function WalletConnecting(props: Props) {
|
||||
client: undefined,
|
||||
address: undefined,
|
||||
userDomain: undefined,
|
||||
accounts: null,
|
||||
focusComponent: {
|
||||
component: (
|
||||
<WalletSelect
|
||||
@ -173,7 +170,6 @@ export default function WalletConnecting(props: Props) {
|
||||
client: undefined,
|
||||
address: undefined,
|
||||
userDomain: undefined,
|
||||
accounts: null,
|
||||
focusComponent: {
|
||||
component: <WalletSelect />,
|
||||
},
|
||||
|
@ -27,7 +27,6 @@ export default function Wallet() {
|
||||
client: undefined,
|
||||
address: undefined,
|
||||
userDomain: undefined,
|
||||
accounts: null,
|
||||
balances: [],
|
||||
focusComponent: null,
|
||||
})
|
||||
|
@ -7,7 +7,7 @@ export default function useAccount(accountId?: string, suspense?: boolean) {
|
||||
const chainConfig = useChainConfig()
|
||||
|
||||
return useSWR(
|
||||
`chains/${chainConfig.id}/accounts/${accountId}`,
|
||||
accountId && `chains/${chainConfig.id}/accounts/${accountId}`,
|
||||
() => getAccount(chainConfig, accountId),
|
||||
{
|
||||
suspense: suspense,
|
||||
|
@ -2,7 +2,6 @@ import useSWR from 'swr'
|
||||
|
||||
import getAccounts from 'api/wallets/getAccounts'
|
||||
import useChainConfig from 'hooks/useChainConfig'
|
||||
import useStore from 'store'
|
||||
import { AccountKind } from 'types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types'
|
||||
|
||||
export default function useAccounts(kind: AccountKind, address?: string, suspense = true) {
|
||||
@ -15,13 +14,6 @@ export default function useAccounts(kind: AccountKind, address?: string, suspens
|
||||
suspense: suspense,
|
||||
fallbackData: [],
|
||||
revalidateOnFocus: false,
|
||||
onSuccess: (accounts) => {
|
||||
if (kind === 'high_levered_strategy') {
|
||||
useStore.setState({ hlsAccounts: accounts })
|
||||
return
|
||||
}
|
||||
useStore.setState({ accounts: accounts })
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import useAccounts from 'hooks/accounts/useAccounts'
|
||||
import useAutoLendEnabledAccountIds from 'hooks/localStorage/useAutoLendEnabledAccountIds'
|
||||
import useCurrentAccount from 'hooks/useCurrentAccount'
|
||||
import useStore from 'store'
|
||||
|
||||
export default function useAutoLend(): {
|
||||
autoLendEnabledAccountIds: string[]
|
||||
@ -10,7 +10,7 @@ export default function useAutoLend(): {
|
||||
setAutoLendOnAllAccounts: (lendAssets: boolean) => void
|
||||
enableAutoLendAccountId: (accountId: string) => void
|
||||
} {
|
||||
const accounts = useStore((s) => s.accounts)
|
||||
const { data: accounts } = useAccounts('default', undefined, false)
|
||||
const currentAccount = useCurrentAccount()
|
||||
const [autoLendEnabledAccountIds, setAutoLendEnabledAccountIds] = useAutoLendEnabledAccountIds()
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import useAccounts from 'hooks/accounts/useAccounts'
|
||||
import useAccountId from 'hooks/useAccountId'
|
||||
import useStore from 'store'
|
||||
|
||||
export default function useCurrentAccount(): Account | undefined {
|
||||
const accountId = useAccountId()
|
||||
const { data: accounts } = useAccounts('default', undefined, false)
|
||||
|
||||
const accounts = useStore((s) => s.accounts)
|
||||
return accounts?.find((account) => account.id === accountId)
|
||||
}
|
||||
|
@ -5,15 +5,13 @@ import { PerpsPositions } from 'components/Perps/PerpsPositions'
|
||||
|
||||
export default function PerpsPage() {
|
||||
return (
|
||||
<div className='flex flex-col w-full h-full gap-4'>
|
||||
<div className='grid w-full grid-cols-[auto_346px] gap-4'>
|
||||
<div className='flex flex-col gap-4'>
|
||||
<PerpsInfo />
|
||||
<PerpsChart />
|
||||
</div>
|
||||
<div className='grid grid-cols-[auto_376px] grid-rows-[min-content_auto_auto] w-full gap-4'>
|
||||
<PerpsInfo />
|
||||
<div className='h-full w-[376px] row-span-3'>
|
||||
<PerpsModule />
|
||||
<PerpsPositions />
|
||||
</div>
|
||||
<PerpsChart />
|
||||
<PerpsPositions />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ export default function createBroadcastSlice(
|
||||
case 'open-perp':
|
||||
toast.content.push({
|
||||
coins: changes.deposits?.map((deposit) => deposit.toCoin()) ?? [],
|
||||
text: 'Opened perp position',
|
||||
text: 'Market order executed',
|
||||
})
|
||||
break
|
||||
case 'close-perp':
|
||||
|
7
src/types/interfaces/perps.d.ts
vendored
7
src/types/interfaces/perps.d.ts
vendored
@ -1,8 +1,6 @@
|
||||
const BNCoin = import('types/classes/BNCoin').BNCoin
|
||||
|
||||
type OrderDirection = PerpsType | ('buy' | 'sell')
|
||||
|
||||
type PerpsType = 'long' | 'short'
|
||||
type TradeDirection = 'long' | 'short'
|
||||
|
||||
// TODO: 📈Remove this type when healthcomputer is implemented
|
||||
type PositionsWithoutPerps = Omit<
|
||||
@ -13,7 +11,6 @@ type PositionsWithoutPerps = Omit<
|
||||
type PerpsPosition = {
|
||||
denom: string
|
||||
baseDenom: string
|
||||
type: PerpsType
|
||||
tradeDirection: TradeDirection
|
||||
size: BigNumber
|
||||
// closingFee: BNCoin
|
||||
}
|
||||
|
2
src/types/interfaces/store/common.d.ts
vendored
2
src/types/interfaces/store/common.d.ts
vendored
@ -1,5 +1,4 @@
|
||||
interface CommonSlice {
|
||||
accounts: Account[] | null
|
||||
address?: string
|
||||
chainConfig: ChainConfig
|
||||
userDomain?: {
|
||||
@ -7,7 +6,6 @@ interface CommonSlice {
|
||||
domain_full: string
|
||||
}
|
||||
balances: Coin[]
|
||||
hlsAccounts: Account[] | null
|
||||
client?: WalletClient
|
||||
isOpen: boolean
|
||||
selectedAccount: string | null
|
||||
|
@ -331,3 +331,8 @@ export function isAccountEmpty(account: Account) {
|
||||
account.deposits.length === 0
|
||||
)
|
||||
}
|
||||
|
||||
export function getAccountNetValue(account: Account, prices: BNCoin[], assets: Asset[]) {
|
||||
const [deposits, lends, debts, vaults] = getAccountPositionValues(account, prices, assets)
|
||||
return deposits.plus(lends).plus(vaults).minus(debts)
|
||||
}
|
||||
|
@ -82,8 +82,8 @@ export function resolvePerpsPositions(perpPositions: Positions['perps']): PerpsP
|
||||
return {
|
||||
denom: position.denom,
|
||||
baseDenom: position.base_denom,
|
||||
size: BN(position.size as any),
|
||||
type: BN(position.size as any).isNegative() ? 'short' : 'long',
|
||||
size: BN(position.size as any).abs(),
|
||||
tradeDirection: BN(position.size as any).isNegative() ? 'short' : 'long',
|
||||
closingFee: BNCoin.fromCoin(position.pnl.coins.closing_fee),
|
||||
pnl: getPnlCoin(position.pnl.coins.pnl, position.base_denom),
|
||||
entryPrice: BN(position.entry_price),
|
||||
@ -94,7 +94,9 @@ export function resolvePerpsPositions(perpPositions: Positions['perps']): PerpsP
|
||||
function getPnlCoin(pnl: PnL, denom: string): BNCoin {
|
||||
let amount = BN_ZERO
|
||||
|
||||
if ('loss' in (pnl as { loss: Coin })) {
|
||||
if (pnl === 'break_even') return BNCoin.fromDenomAndBigNumber(denom, amount)
|
||||
|
||||
if ('loss' in (pnl as any)) {
|
||||
amount = BN((pnl as any).loss.amount).times(-1)
|
||||
} else if ('profit' in (pnl as { profit: Coin })) {
|
||||
amount = BN((pnl as any).profit.amount)
|
||||
|
Loading…
Reference in New Issue
Block a user