add close all perp positions
This commit is contained in:
parent
559170bded
commit
cfe4b8f0dc
|
@ -0,0 +1,25 @@
|
||||||
|
const NukeIcon = ({ className }: { className?: string }) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className={`${className}`}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 32 32"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<g clipPath="url(#a)">
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M16 32c8.8366 0 16-7.1634 16-16 0-8.83656-7.1634-16-16-16C7.16344 0 0 7.16344 0 16c0 8.8366 7.16344 16 16 16Zm.026-20.9524c.7047 0 1.3722.1482 1.9656.4077.2967.1482.6675 0 .8159-.2595l3.3748-5.85549c.1854-.33355.0742-.74121-.2596-.88945-3.5973-1.85302-8.0105-2.00126-11.8675-.03706-.33375.1853-.445.59296-.25957.92651l3.44897 5.81849c.1484.2965.5192.4076.8159.2594.5934-.2224 1.2609-.3706 1.9655-.3706Zm-5.5628 4.9661H3.67652c-.37086 0-.66755.2965-.66755.6671.11126 1.9642.66755 3.9654 1.70595 5.7814 1.07549 1.853 2.52184 3.3354 4.22779 4.4472.29668.1853.74171.0741.89006-.2224l3.44893-5.8925c.1484-.2965.1113-.6301-.1483-.8524-1.0755-.7783-1.8172-2.0013-2.0026-3.3725-.0371-.2965-.3338-.5559-.6676-.5559Zm10.4953.5559c-.1855 1.3712-.9272 2.5942-2.0027 3.3725-.2596.1853-.3338.5559-.1483.8524l3.3748 5.8555c.1854.3335.5934.4447.9271.2224 3.5603-2.2978 5.6371-6.152 5.8967-10.1546.0371-.3706-.2967-.667-.6676-.667h-6.7496c-.3338-.0371-.5934.2223-.6304.5188ZM16.026 19.312c1.8434 0 3.3377-1.4933 3.3377-3.3354 0-1.8421-1.4943-3.3354-3.3377-3.3354s-3.3377 1.4933-3.3377 3.3354c0 1.8421 1.4943 3.3354 3.3377 3.3354Z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="a">
|
||||||
|
<path d="M0 0h32v32H0z" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NukeIcon
|
|
@ -0,0 +1,128 @@
|
||||||
|
import { FunctionComponent, useCallback, useState } from 'react'
|
||||||
|
import mangoStore from '@store/mangoStore'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
import Modal from '@components/shared/Modal'
|
||||||
|
import Button, { LinkButton } from '@components/shared/Button'
|
||||||
|
import { notify } from 'utils/notifications'
|
||||||
|
import Loading from '@components/shared/Loading'
|
||||||
|
import { isMangoError } from 'types'
|
||||||
|
import { ModalProps } from 'types/modal'
|
||||||
|
import useMangoGroup from 'hooks/useMangoGroup'
|
||||||
|
import useOpenPerpPositions from 'hooks/useOpenPerpPositions'
|
||||||
|
import { floorToDecimal, getDecimalCount } from 'utils/numbers'
|
||||||
|
import MarketLogos from './MarketLogos'
|
||||||
|
import PerpSideBadge from './PerpSideBadge'
|
||||||
|
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||||
|
|
||||||
|
const CloseAllPositionsModal: FunctionComponent<ModalProps> = ({
|
||||||
|
onClose,
|
||||||
|
isOpen,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation(['common', 'trade'])
|
||||||
|
const [submitting, setSubmitting] = useState(false)
|
||||||
|
const openPerpPositions = useOpenPerpPositions()
|
||||||
|
const { group } = useMangoGroup()
|
||||||
|
|
||||||
|
const handleCloseAll = useCallback(async () => {
|
||||||
|
const client = mangoStore.getState().client
|
||||||
|
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||||
|
const actions = mangoStore.getState().actions
|
||||||
|
|
||||||
|
if (!group || !mangoAccount) {
|
||||||
|
notify({
|
||||||
|
title: 'Something went wrong. Try again later',
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setSubmitting(true)
|
||||||
|
try {
|
||||||
|
const maxSlippage = 0.025
|
||||||
|
const { signature: tx } = await client.perpCloseAll(
|
||||||
|
group,
|
||||||
|
mangoAccount,
|
||||||
|
maxSlippage,
|
||||||
|
)
|
||||||
|
actions.fetchOpenOrders()
|
||||||
|
notify({
|
||||||
|
type: 'success',
|
||||||
|
title: 'Transaction successful',
|
||||||
|
txid: tx,
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
if (isMangoError(e)) {
|
||||||
|
notify({
|
||||||
|
title: 'There was an issue.',
|
||||||
|
description: e.message,
|
||||||
|
txid: e?.txid,
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
console.error('Place trade error:', e)
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false)
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
}, [group, onClose])
|
||||||
|
|
||||||
|
if (!group) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal onClose={onClose} isOpen={isOpen}>
|
||||||
|
<h3 className="mb-2 text-center">{t('trade:close-all-positions')}</h3>
|
||||||
|
<div className="pb-6 text-th-fgd-3">{t('trade:price-expect')}</div>
|
||||||
|
<div className="border-b border-th-bkg-3">
|
||||||
|
{openPerpPositions.map((position, i) => {
|
||||||
|
const market = group.getPerpMarketByMarketIndex(position.marketIndex)
|
||||||
|
const basePosition = position.getBasePositionUi(market)
|
||||||
|
const floorBasePosition = floorToDecimal(
|
||||||
|
basePosition,
|
||||||
|
getDecimalCount(market.minOrderSize),
|
||||||
|
).toNumber()
|
||||||
|
|
||||||
|
if (!basePosition) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-between border-t border-th-bkg-3 py-3"
|
||||||
|
key={market.name + i}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<MarketLogos market={market} />
|
||||||
|
<p className="mr-2">{market.name}</p>
|
||||||
|
<PerpSideBadge basePosition={basePosition} />
|
||||||
|
</div>
|
||||||
|
<p className="font-mono text-th-fgd-2">
|
||||||
|
<FormatNumericValue value={Math.abs(floorBasePosition)} />
|
||||||
|
<span className="mx-1 text-th-bkg-4">|</span>
|
||||||
|
<FormatNumericValue
|
||||||
|
value={Math.abs(floorBasePosition * market.uiPrice)}
|
||||||
|
isUsd
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
className="mb-4 mt-6 flex w-full items-center justify-center"
|
||||||
|
onClick={handleCloseAll}
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
{submitting ? (
|
||||||
|
<Loading />
|
||||||
|
) : (
|
||||||
|
<span>{t('trade:close-all-positions')}</span>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
<LinkButton
|
||||||
|
className="inline-flex w-full items-center justify-center"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
{t('cancel')}
|
||||||
|
</LinkButton>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CloseAllPositionsModal
|
|
@ -26,11 +26,14 @@ import { Disclosure, Transition } from '@headlessui/react'
|
||||||
import useOpenPerpPositions from 'hooks/useOpenPerpPositions'
|
import useOpenPerpPositions from 'hooks/useOpenPerpPositions'
|
||||||
import PnlTooltipContent from '@components/shared/PnlTooltipContent'
|
import PnlTooltipContent from '@components/shared/PnlTooltipContent'
|
||||||
import PerpSideBadge from './PerpSideBadge'
|
import PerpSideBadge from './PerpSideBadge'
|
||||||
|
import CloseAllPositionsModal from './CloseAllPositionsModal'
|
||||||
|
import NukeIcon from '@components/icons/NukeIcon'
|
||||||
|
|
||||||
const PerpPositions = () => {
|
const PerpPositions = () => {
|
||||||
const { t } = useTranslation(['common', 'trade'])
|
const { t } = useTranslation(['common', 'trade'])
|
||||||
const { group } = useMangoGroup()
|
const { group } = useMangoGroup()
|
||||||
const [showMarketCloseModal, setShowMarketCloseModal] = useState(false)
|
const [showMarketCloseModal, setShowMarketCloseModal] = useState(false)
|
||||||
|
const [showCloseAllModal, setShowCloseAllModal] = useState(false)
|
||||||
const [positionToClose, setPositionToClose] = useState<PerpPosition | null>(
|
const [positionToClose, setPositionToClose] = useState<PerpPosition | null>(
|
||||||
null,
|
null,
|
||||||
)
|
)
|
||||||
|
@ -147,7 +150,22 @@ const PerpPositions = () => {
|
||||||
</div>
|
</div>
|
||||||
</Th>
|
</Th>
|
||||||
<Th className="text-right">{t('trade:unrealized-pnl')}</Th>
|
<Th className="text-right">{t('trade:unrealized-pnl')}</Th>
|
||||||
{!isUnownedAccount ? <Th /> : null}
|
{!isUnownedAccount ? (
|
||||||
|
<Th>
|
||||||
|
{openPerpPositions?.length > 1 ? (
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<NukeIcon className="mr-1.5 h-4 w-4 text-th-active" />
|
||||||
|
<LinkButton
|
||||||
|
onClick={() => setShowCloseAllModal(true)}
|
||||||
|
>
|
||||||
|
{t('trade:close-all')}
|
||||||
|
</LinkButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</Th>
|
||||||
|
) : null}
|
||||||
</TrHead>
|
</TrHead>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -300,7 +318,7 @@ const PerpPositions = () => {
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => showClosePositionModal(position)}
|
onClick={() => showClosePositionModal(position)}
|
||||||
>
|
>
|
||||||
Close
|
{t('close')}
|
||||||
</Button>
|
</Button>
|
||||||
<IconButton
|
<IconButton
|
||||||
hideBg
|
hideBg
|
||||||
|
@ -613,14 +631,26 @@ const PerpPositions = () => {
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-2 mt-3 flex space-x-3">
|
<div className="col-span-2 mt-3 flex space-x-3">
|
||||||
<Button
|
<Button
|
||||||
className="w-1/2"
|
className="w-full text-xs sm:text-sm"
|
||||||
secondary
|
secondary
|
||||||
onClick={() => showClosePositionModal(position)}
|
onClick={() => showClosePositionModal(position)}
|
||||||
>
|
>
|
||||||
{t('trade:close-position')}
|
{t('close')}
|
||||||
</Button>
|
</Button>
|
||||||
|
{openPerpPositions?.length > 1 ? (
|
||||||
|
<Button
|
||||||
|
className="w-full text-xs sm:text-sm"
|
||||||
|
secondary
|
||||||
|
onClick={() => setShowCloseAllModal(true)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<NukeIcon className="mr-2 h-4 w-4 flex-shrink-0 text-th-active" />
|
||||||
|
{t('trade:close-all')}
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
<Button
|
<Button
|
||||||
className="w-1/2"
|
className="w-full text-xs sm:text-sm"
|
||||||
secondary
|
secondary
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handleShowShare(openPerpPositions[i])
|
handleShowShare(openPerpPositions[i])
|
||||||
|
@ -734,6 +764,12 @@ const PerpPositions = () => {
|
||||||
position={positionToClose}
|
position={positionToClose}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
{showCloseAllModal ? (
|
||||||
|
<CloseAllPositionsModal
|
||||||
|
isOpen={showCloseAllModal}
|
||||||
|
onClose={() => setShowCloseAllModal(false)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
"cancel-all": "Cancel All",
|
"cancel-all": "Cancel All",
|
||||||
"cancel-order": "Cancel Order",
|
"cancel-order": "Cancel Order",
|
||||||
"cancel-order-error": "Failed to cancel order",
|
"cancel-order-error": "Failed to cancel order",
|
||||||
|
"close-all": "Close All",
|
||||||
|
"close-all-positions": "Close All Positions",
|
||||||
"close-confirm": "Market close your {{config_name}} position",
|
"close-confirm": "Market close your {{config_name}} position",
|
||||||
"close-position": "Close Position",
|
"close-position": "Close Position",
|
||||||
"connect-orders": "Connect to view your open orders",
|
"connect-orders": "Connect to view your open orders",
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
"cancel-all": "Cancel All",
|
"cancel-all": "Cancel All",
|
||||||
"cancel-order": "Cancel Order",
|
"cancel-order": "Cancel Order",
|
||||||
"cancel-order-error": "Failed to cancel order",
|
"cancel-order-error": "Failed to cancel order",
|
||||||
|
"close-all": "Close All",
|
||||||
|
"close-all-positions": "Close All Positions",
|
||||||
"close-confirm": "Market close your {{config_name}} position",
|
"close-confirm": "Market close your {{config_name}} position",
|
||||||
"close-position": "Close Position",
|
"close-position": "Close Position",
|
||||||
"connect-orders": "Connect to view your open orders",
|
"connect-orders": "Connect to view your open orders",
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
"cancel-all": "Cancel All",
|
"cancel-all": "Cancel All",
|
||||||
"cancel-order": "Cancel Order",
|
"cancel-order": "Cancel Order",
|
||||||
"cancel-order-error": "Failed to cancel order",
|
"cancel-order-error": "Failed to cancel order",
|
||||||
|
"close-all": "Close All",
|
||||||
|
"close-all-positions": "Close All Positions",
|
||||||
"close-confirm": "Market close your {{config_name}} position",
|
"close-confirm": "Market close your {{config_name}} position",
|
||||||
"close-position": "Close Position",
|
"close-position": "Close Position",
|
||||||
"connect-orders": "Connect to view your open orders",
|
"connect-orders": "Connect to view your open orders",
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
"cancel-all": "Cancel All",
|
"cancel-all": "Cancel All",
|
||||||
"cancel-order": "Cancel Order",
|
"cancel-order": "Cancel Order",
|
||||||
"cancel-order-error": "取消订单失败",
|
"cancel-order-error": "取消订单失败",
|
||||||
|
"close-all": "Close All",
|
||||||
|
"close-all-positions": "Close All Positions",
|
||||||
"close-confirm": "市场平仓 {{config_name}}",
|
"close-confirm": "市场平仓 {{config_name}}",
|
||||||
"close-position": "平仓",
|
"close-position": "平仓",
|
||||||
"connect-orders": "连接以查看未结订单",
|
"connect-orders": "连接以查看未结订单",
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
"cancel-all": "Cancel All",
|
"cancel-all": "Cancel All",
|
||||||
"cancel-order": "Cancel Order",
|
"cancel-order": "Cancel Order",
|
||||||
"cancel-order-error": "取消訂單失敗",
|
"cancel-order-error": "取消訂單失敗",
|
||||||
|
"close-all": "Close All",
|
||||||
|
"close-all-positions": "Close All Positions",
|
||||||
"close-confirm": "市場平倉 {{config_name}}",
|
"close-confirm": "市場平倉 {{config_name}}",
|
||||||
"close-position": "平倉",
|
"close-position": "平倉",
|
||||||
"connect-orders": "連接以查看未結訂單",
|
"connect-orders": "連接以查看未結訂單",
|
||||||
|
|
Loading…
Reference in New Issue