allow users to manually close unused spot oo accounts and perp positions
This commit is contained in:
parent
2e73510e26
commit
c5cf3f0ecb
|
@ -1,22 +1,38 @@
|
||||||
import MangoAccountSizeModal from '@components/modals/MangoAccountSizeModal'
|
import MangoAccountSizeModal from '@components/modals/MangoAccountSizeModal'
|
||||||
|
import ActionsLinkButton from '@components/account/ActionsLinkButton'
|
||||||
import { LinkButton } from '@components/shared/Button'
|
import { LinkButton } from '@components/shared/Button'
|
||||||
import TokenLogo from '@components/shared/TokenLogo'
|
import TokenLogo from '@components/shared/TokenLogo'
|
||||||
import Tooltip from '@components/shared/Tooltip'
|
import Tooltip from '@components/shared/Tooltip'
|
||||||
import MarketLogos from '@components/trade/MarketLogos'
|
import MarketLogos from '@components/trade/MarketLogos'
|
||||||
import { Disclosure } from '@headlessui/react'
|
import { Disclosure, Popover, Transition } from '@headlessui/react'
|
||||||
import { ChevronDownIcon, SquaresPlusIcon } from '@heroicons/react/20/solid'
|
import {
|
||||||
|
ChevronDownIcon,
|
||||||
|
MinusCircleIcon,
|
||||||
|
SquaresPlusIcon,
|
||||||
|
} from '@heroicons/react/20/solid'
|
||||||
|
import { PublicKey, TransactionInstruction } from '@solana/web3.js'
|
||||||
|
import mangoStore from '@store/mangoStore'
|
||||||
import useMangoAccount from 'hooks/useMangoAccount'
|
import useMangoAccount from 'hooks/useMangoAccount'
|
||||||
import useMangoAccountAccounts, {
|
import useMangoAccountAccounts, {
|
||||||
getAvaialableAccountsColor,
|
getAvaialableAccountsColor,
|
||||||
} from 'hooks/useMangoAccountAccounts'
|
} from 'hooks/useMangoAccountAccounts'
|
||||||
import useMangoGroup from 'hooks/useMangoGroup'
|
import useMangoGroup from 'hooks/useMangoGroup'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
import { useState } from 'react'
|
import { Fragment, useState } from 'react'
|
||||||
import { MAX_ACCOUNTS } from 'utils/constants'
|
import { MAX_ACCOUNTS } from 'utils/constants'
|
||||||
|
import { isMangoError } from 'types'
|
||||||
|
import { notify } from 'utils/notifications'
|
||||||
|
|
||||||
|
enum CLOSE_TYPE {
|
||||||
|
TOKEN,
|
||||||
|
PERP,
|
||||||
|
SERUMOO,
|
||||||
|
PERPOO,
|
||||||
|
}
|
||||||
|
|
||||||
const AccountSettings = () => {
|
const AccountSettings = () => {
|
||||||
const { t } = useTranslation(['common', 'settings'])
|
const { t } = useTranslation(['common', 'settings'])
|
||||||
const { mangoAccountAddress } = useMangoAccount()
|
const { mangoAccountAddress, mangoAccount } = useMangoAccount()
|
||||||
const { group } = useMangoGroup()
|
const { group } = useMangoGroup()
|
||||||
const [showAccountSizeModal, setShowAccountSizeModal] = useState(false)
|
const [showAccountSizeModal, setShowAccountSizeModal] = useState(false)
|
||||||
const {
|
const {
|
||||||
|
@ -24,6 +40,10 @@ const AccountSettings = () => {
|
||||||
usedSerum3,
|
usedSerum3,
|
||||||
usedPerps,
|
usedPerps,
|
||||||
usedPerpOo,
|
usedPerpOo,
|
||||||
|
// emptyTokens,
|
||||||
|
emptySerum3,
|
||||||
|
emptyPerps,
|
||||||
|
// emptyPerpOo,
|
||||||
totalTokens,
|
totalTokens,
|
||||||
totalSerum3,
|
totalSerum3,
|
||||||
totalPerps,
|
totalPerps,
|
||||||
|
@ -31,6 +51,57 @@ const AccountSettings = () => {
|
||||||
isAccountFull,
|
isAccountFull,
|
||||||
} = useMangoAccountAccounts()
|
} = useMangoAccountAccounts()
|
||||||
|
|
||||||
|
const handleCloseSlots = async (closeType: CLOSE_TYPE) => {
|
||||||
|
const client = mangoStore.getState().client
|
||||||
|
const group = mangoStore.getState().group
|
||||||
|
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||||
|
const actions = mangoStore.getState().actions
|
||||||
|
if (!mangoAccount || !group) return
|
||||||
|
try {
|
||||||
|
let ixs: TransactionInstruction[] = []
|
||||||
|
if (closeType == CLOSE_TYPE.TOKEN) {
|
||||||
|
// No instruction yet
|
||||||
|
} else if (closeType === CLOSE_TYPE.PERP) {
|
||||||
|
ixs = await Promise.all(
|
||||||
|
emptyPerps.map((p) =>
|
||||||
|
client.perpDeactivatePositionIx(group, mangoAccount, p.marketIndex),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else if (closeType === CLOSE_TYPE.SERUMOO) {
|
||||||
|
ixs = await Promise.all(
|
||||||
|
emptySerum3.map((s) =>
|
||||||
|
client.serum3CloseOpenOrdersIx(
|
||||||
|
group,
|
||||||
|
mangoAccount,
|
||||||
|
new PublicKey(s),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else if (closeType === CLOSE_TYPE.PERPOO) {
|
||||||
|
// No instruction yet
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ixs.length === 0) return
|
||||||
|
const tx = await client.sendAndConfirmTransaction(ixs)
|
||||||
|
|
||||||
|
notify({
|
||||||
|
title: 'Transaction confirmed',
|
||||||
|
type: 'success',
|
||||||
|
txid: tx.signature,
|
||||||
|
})
|
||||||
|
await actions.reloadMangoAccount()
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
if (!isMangoError(e)) return
|
||||||
|
notify({
|
||||||
|
title: 'Transaction failed',
|
||||||
|
description: e.message,
|
||||||
|
txid: e?.txid,
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return mangoAccountAddress && group ? (
|
return mangoAccountAddress && group ? (
|
||||||
<>
|
<>
|
||||||
<h2 className="mb-4 text-base">{t('account')}</h2>
|
<h2 className="mb-4 text-base">{t('account')}</h2>
|
||||||
|
@ -45,6 +116,67 @@ const AccountSettings = () => {
|
||||||
{t('settings:increase-account-size')}
|
{t('settings:increase-account-size')}
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
) : null}
|
) : null}
|
||||||
|
{emptySerum3.length > 0 || emptyPerps.length > 0 ? (
|
||||||
|
<Popover className="relative sm:w-1/3 md:w-auto">
|
||||||
|
{({ open }) => (
|
||||||
|
<>
|
||||||
|
<Popover.Button
|
||||||
|
className={`w-full focus:outline-none`}
|
||||||
|
as="div"
|
||||||
|
>
|
||||||
|
<LinkButton className="flex items-center">
|
||||||
|
<MinusCircleIcon className="mr-1.5 h-4 w-4" />
|
||||||
|
{t('settings:close-unused-slots')}
|
||||||
|
</LinkButton>
|
||||||
|
</Popover.Button>
|
||||||
|
<Transition
|
||||||
|
appear={true}
|
||||||
|
show={open}
|
||||||
|
as={Fragment}
|
||||||
|
enter="transition ease-in duration-75"
|
||||||
|
enterFrom="opacity-0 nice scale-75"
|
||||||
|
enterTo="opacity-100 scale-100"
|
||||||
|
leave="transition ease-out duration-100"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
>
|
||||||
|
<Popover.Panel className="absolute right-0 top-10 mt-1 space-y-2 rounded-md bg-th-bkg-2 px-4 py-2.5">
|
||||||
|
{/* <ActionsLinkButton
|
||||||
|
disabled={emptyTokens.length === 0}
|
||||||
|
mangoAccount={mangoAccount!}
|
||||||
|
onClick={() => console.log('handle close tokens')}
|
||||||
|
>
|
||||||
|
<span className="ml-2">{'Close unused token positions'}</span>
|
||||||
|
</ActionsLinkButton> */}
|
||||||
|
<ActionsLinkButton
|
||||||
|
disabled={emptySerum3.length === 0}
|
||||||
|
mangoAccount={mangoAccount!}
|
||||||
|
onClick={() => handleCloseSlots(CLOSE_TYPE.SERUMOO)}
|
||||||
|
>
|
||||||
|
<span className="ml-2">
|
||||||
|
{t('settings:close-spot-oo')}
|
||||||
|
</span>
|
||||||
|
</ActionsLinkButton>
|
||||||
|
<ActionsLinkButton
|
||||||
|
disabled={emptyPerps.length === 0}
|
||||||
|
mangoAccount={mangoAccount!}
|
||||||
|
onClick={() => handleCloseSlots(CLOSE_TYPE.PERP)}
|
||||||
|
>
|
||||||
|
<span className="ml-2">{t('settings:close-perp')}</span>
|
||||||
|
</ActionsLinkButton>
|
||||||
|
{/* <ActionsLinkButton
|
||||||
|
disabled={emptyPerpOo.length === 0}
|
||||||
|
mangoAccount={mangoAccount!}
|
||||||
|
onClick={() => console.log('close perp oos')}
|
||||||
|
>
|
||||||
|
<span className="ml-2">{'Close unused perp OOs'}</span>
|
||||||
|
</ActionsLinkButton> */}
|
||||||
|
</Popover.Panel>
|
||||||
|
</Transition>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Popover>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<Disclosure>
|
<Disclosure>
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
|
|
|
@ -8,6 +8,9 @@ import {
|
||||||
TokenPosition,
|
TokenPosition,
|
||||||
} from '@blockworks-foundation/mango-v4'
|
} from '@blockworks-foundation/mango-v4'
|
||||||
import { MAX_ACCOUNTS } from 'utils/constants'
|
import { MAX_ACCOUNTS } from 'utils/constants'
|
||||||
|
import useBanksWithBalances from './useBanksWithBalances'
|
||||||
|
import { OpenOrders } from '@project-serum/serum'
|
||||||
|
import { BN } from '@coral-xyz/anchor'
|
||||||
|
|
||||||
export const getAvaialableAccountsColor = (used: number, total: number) => {
|
export const getAvaialableAccountsColor = (used: number, total: number) => {
|
||||||
const remaining = total - used
|
const remaining = total - used
|
||||||
|
@ -44,11 +47,22 @@ const getIsAccountSizeFull = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function useMangoAccountAccounts() {
|
export default function useMangoAccountAccounts() {
|
||||||
const { mangoAccountAddress } = useMangoAccount()
|
const { mangoAccountAddress, mangoAccount } = useMangoAccount()
|
||||||
|
const banks = useBanksWithBalances()
|
||||||
|
|
||||||
|
const [
|
||||||
|
usedTokens,
|
||||||
|
usedSerum3,
|
||||||
|
usedPerps,
|
||||||
|
usedPerpOo,
|
||||||
|
emptyTokens,
|
||||||
|
emptySerum3,
|
||||||
|
emptyPerps,
|
||||||
|
emptyPerpOo,
|
||||||
|
] = useMemo(() => {
|
||||||
|
if (!mangoAccountAddress || !mangoAccount)
|
||||||
|
return [[], [], [], [], [], [], [], []]
|
||||||
|
|
||||||
const [usedTokens, usedSerum3, usedPerps, usedPerpOo] = useMemo(() => {
|
|
||||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
|
||||||
if (!mangoAccountAddress || !mangoAccount) return [[], [], [], []]
|
|
||||||
const { tokens, serum3, perps, perpOpenOrders } = mangoAccount
|
const { tokens, serum3, perps, perpOpenOrders } = mangoAccount
|
||||||
const usedTokens: TokenPosition[] = tokens.filter((t) => t.inUseCount)
|
const usedTokens: TokenPosition[] = tokens.filter((t) => t.inUseCount)
|
||||||
const usedSerum3: Serum3Orders[] = serum3.filter(
|
const usedSerum3: Serum3Orders[] = serum3.filter(
|
||||||
|
@ -60,8 +74,48 @@ export default function useMangoAccountAccounts() {
|
||||||
const usedPerpOo: PerpOo[] = perpOpenOrders.filter(
|
const usedPerpOo: PerpOo[] = perpOpenOrders.filter(
|
||||||
(p) => p.orderMarket !== 65535,
|
(p) => p.orderMarket !== 65535,
|
||||||
)
|
)
|
||||||
return [usedTokens, usedSerum3, usedPerps, usedPerpOo]
|
|
||||||
}, [mangoAccountAddress])
|
// const emptyPerpOo = [] // No instruction for closing perp oo
|
||||||
|
const emptyTokens = usedTokens.filter((t) => {
|
||||||
|
const bank = banks.find((b) => b.bank.tokenIndex === t.tokenIndex)
|
||||||
|
if (!bank) return false
|
||||||
|
return t.inUseCount && bank.balance === 0 && bank.borrowedAmount === 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const emptyPerps = usedPerps.filter(
|
||||||
|
(p) =>
|
||||||
|
p.asksBaseLots.isZero() &&
|
||||||
|
p.bidsBaseLots.isZero() &&
|
||||||
|
p.takerBaseLots.isZero &&
|
||||||
|
p.takerQuoteLots.isZero() &&
|
||||||
|
p.basePositionLots.isZero() &&
|
||||||
|
p.quotePositionNative.isZero(),
|
||||||
|
)
|
||||||
|
|
||||||
|
const usedOpenOrders = usedSerum3
|
||||||
|
.map((s) => mangoAccount.serum3OosMapByMarketIndex.get(s.marketIndex))
|
||||||
|
.filter((o) => o !== undefined) as OpenOrders[]
|
||||||
|
const maxFreeSlotBits = new BN(2).pow(new BN(128)).sub(new BN(1)) // 2^128 - 1
|
||||||
|
const emptySerum3 = usedOpenOrders
|
||||||
|
.filter(
|
||||||
|
(o) =>
|
||||||
|
o.baseTokenTotal.isZero() &&
|
||||||
|
o.quoteTokenTotal.isZero() &&
|
||||||
|
o.freeSlotBits.eq(maxFreeSlotBits),
|
||||||
|
)
|
||||||
|
.map((f) => f.market)
|
||||||
|
|
||||||
|
return [
|
||||||
|
usedTokens,
|
||||||
|
usedSerum3,
|
||||||
|
usedPerps,
|
||||||
|
usedPerpOo,
|
||||||
|
emptyTokens,
|
||||||
|
emptySerum3,
|
||||||
|
emptyPerps,
|
||||||
|
[],
|
||||||
|
]
|
||||||
|
}, [mangoAccountAddress, mangoAccount])
|
||||||
|
|
||||||
const [totalTokens, totalSerum3, totalPerps, totalPerpOpenOrders] =
|
const [totalTokens, totalSerum3, totalPerps, totalPerpOpenOrders] =
|
||||||
useMemo(() => {
|
useMemo(() => {
|
||||||
|
@ -73,7 +127,7 @@ export default function useMangoAccountAccounts() {
|
||||||
const totalPerps = perps
|
const totalPerps = perps
|
||||||
const totalPerpOpenOrders = perpOpenOrders
|
const totalPerpOpenOrders = perpOpenOrders
|
||||||
return [totalTokens, totalSerum3, totalPerps, totalPerpOpenOrders]
|
return [totalTokens, totalSerum3, totalPerps, totalPerpOpenOrders]
|
||||||
}, [mangoAccountAddress])
|
}, [mangoAccountAddress, mangoAccount])
|
||||||
|
|
||||||
// const [availableTokens, availableSerum3, availablePerps, availablePerpOo] =
|
// const [availableTokens, availableSerum3, availablePerps, availablePerpOo] =
|
||||||
// useMemo(() => {
|
// useMemo(() => {
|
||||||
|
@ -99,6 +153,10 @@ export default function useMangoAccountAccounts() {
|
||||||
usedSerum3,
|
usedSerum3,
|
||||||
usedPerps,
|
usedPerps,
|
||||||
usedPerpOo,
|
usedPerpOo,
|
||||||
|
emptyTokens,
|
||||||
|
emptySerum3,
|
||||||
|
emptyPerps,
|
||||||
|
emptyPerpOo,
|
||||||
totalTokens,
|
totalTokens,
|
||||||
totalSerum3,
|
totalSerum3,
|
||||||
totalPerps,
|
totalPerps,
|
||||||
|
|
|
@ -19,6 +19,9 @@
|
||||||
"chart-right": "Chart Right",
|
"chart-right": "Chart Right",
|
||||||
"chinese": "简体中文",
|
"chinese": "简体中文",
|
||||||
"chinese-traditional": "繁體中文",
|
"chinese-traditional": "繁體中文",
|
||||||
|
"close-unused-slots": "Close Unused Slots",
|
||||||
|
"close-spot-oo": "Close Spot Open Orders",
|
||||||
|
"close-perp": "Close Perp Positions",
|
||||||
"connect-notifications": "Connect to update your notification settings",
|
"connect-notifications": "Connect to update your notification settings",
|
||||||
"custom": "Custom",
|
"custom": "Custom",
|
||||||
"dark": "Dark",
|
"dark": "Dark",
|
||||||
|
|
|
@ -19,6 +19,9 @@
|
||||||
"chart-right": "Chart Right",
|
"chart-right": "Chart Right",
|
||||||
"chinese": "简体中文",
|
"chinese": "简体中文",
|
||||||
"chinese-traditional": "繁體中文",
|
"chinese-traditional": "繁體中文",
|
||||||
|
"close-unused-slots": "Close Unused Slots",
|
||||||
|
"close-spot-oo": "Close Spot Open Orders",
|
||||||
|
"close-perp": "Close Perp Positions",
|
||||||
"connect-notifications": "Connect to update your notification settings",
|
"connect-notifications": "Connect to update your notification settings",
|
||||||
"custom": "Custom",
|
"custom": "Custom",
|
||||||
"dark": "Dark",
|
"dark": "Dark",
|
||||||
|
|
|
@ -19,6 +19,9 @@
|
||||||
"chart-right": "Chart Right",
|
"chart-right": "Chart Right",
|
||||||
"chinese": "简体中文",
|
"chinese": "简体中文",
|
||||||
"chinese-traditional": "繁體中文",
|
"chinese-traditional": "繁體中文",
|
||||||
|
"close-unused-slots": "Close Unused Slots",
|
||||||
|
"close-spot-oo": "Close Spot Open Orders",
|
||||||
|
"close-perp": "Close Perp Positions",
|
||||||
"connect-notifications": "Connect to update your notification settings",
|
"connect-notifications": "Connect to update your notification settings",
|
||||||
"custom": "Custom",
|
"custom": "Custom",
|
||||||
"dark": "Dark",
|
"dark": "Dark",
|
||||||
|
|
|
@ -19,6 +19,9 @@
|
||||||
"chart-right": "图表右",
|
"chart-right": "图表右",
|
||||||
"chinese": "简体中文",
|
"chinese": "简体中文",
|
||||||
"chinese-traditional": "繁體中文",
|
"chinese-traditional": "繁體中文",
|
||||||
|
"close-unused-slots": "Close Unused Slots",
|
||||||
|
"close-spot-oo": "Close Spot Open Orders",
|
||||||
|
"close-perp": "Close Perp Positions",
|
||||||
"connect-notifications": "Connect to update your notification settings",
|
"connect-notifications": "Connect to update your notification settings",
|
||||||
"custom": "自定",
|
"custom": "自定",
|
||||||
"dark": "暗",
|
"dark": "暗",
|
||||||
|
|
|
@ -19,6 +19,9 @@
|
||||||
"chart-right": "圖表右",
|
"chart-right": "圖表右",
|
||||||
"chinese": "简体中文",
|
"chinese": "简体中文",
|
||||||
"chinese-traditional": "繁體中文",
|
"chinese-traditional": "繁體中文",
|
||||||
|
"close-unused-slots": "Close Unused Slots",
|
||||||
|
"close-spot-oo": "Close Spot Open Orders",
|
||||||
|
"close-perp": "Close Perp Positions",
|
||||||
"connect-notifications": "連接錢包來切換通知設定",
|
"connect-notifications": "連接錢包來切換通知設定",
|
||||||
"custom": "自定",
|
"custom": "自定",
|
||||||
"dark": "暗",
|
"dark": "暗",
|
||||||
|
|
Loading…
Reference in New Issue