import HideMangoAccount from '@components/account/HideMangoAccount' import MangoAccountSizeModal from '@components/modals/MangoAccountSizeModal' import Button, { IconButton, LinkButton } from '@components/shared/Button' import TokenLogo from '@components/shared/TokenLogo' import Tooltip from '@components/shared/Tooltip' import MarketLogos from '@components/trade/MarketLogos' import { Disclosure } from '@headlessui/react' import { ChevronDownIcon, DocumentDuplicateIcon, PencilIcon, SquaresPlusIcon, TrashIcon, UserPlusIcon, } from '@heroicons/react/20/solid' import useMangoAccount from 'hooks/useMangoAccount' import useMangoAccountAccounts from 'hooks/useMangoAccountAccounts' import useMangoGroup from 'hooks/useMangoGroup' import { useTranslation } from 'next-i18next' import { useCallback, useMemo, useState } from 'react' import { MAX_ACCOUNTS, PRIVACY_MODE } from 'utils/constants' import mangoStore from '@store/mangoStore' import { PublicKey } from '@solana/web3.js' import { notify } from 'utils/notifications' import { isMangoError } from 'types' import { getMaxWithdrawForBank } from '@components/swap/useTokenMax' import Decimal from 'decimal.js' import { formatTokenSymbol } from 'utils/tokens' import { handleCancelAll } from '@components/swap/SwapTriggerOrders' import { handleCopyAddress } from '@components/account/AccountActions' import AccountNameModal from '@components/modals/AccountNameModal' import DelegateModal, { DEFAULT_DELEGATE, } from '@components/modals/DelegateModal' import { abbreviateAddress } from 'utils/formatting' import CloseAccountModal from '@components/modals/CloseAccountModal' import useLocalStorageState from 'hooks/useLocalStorageState' import Switch from '@components/forms/Switch' import useUnownedAccount from 'hooks/useUnownedAccount' import { useWallet } from '@solana/wallet-adapter-react' import CreateAccountForm from '@components/account/CreateAccountForm' import ConnectEmptyState from '@components/shared/ConnectEmptyState' import { Serum3Market } from '@blockworks-foundation/mango-v4' const CLOSE_WRAPPER_CLASSNAMES = 'mb-4 flex flex-col md:flex-row md:items-center md:justify-between rounded-md bg-th-bkg-2 px-4 py-3' const SLOT_ROW_CLASSNAMES = 'flex items-center justify-between border-t border-th-bkg-3 py-3' const ROW_BUTTON_CLASSNAMES = 'flex items-center justify-between border-t border-th-bkg-3 py-4 w-full md:px-4 focus:outline-none md:hover:bg-th-bkg-2' const AccountSettings = () => { const { t } = useTranslation(['common', 'settings', 'trade']) const { mangoAccount, mangoAccountAddress } = useMangoAccount() const { group } = useMangoGroup() const { isDelegatedAccount, isUnownedAccount } = useUnownedAccount() const { connected } = useWallet() const [showAccountSizeModal, setShowAccountSizeModal] = useState(false) const [showEditAccountModal, setShowEditAccountModal] = useState(false) const [showCloseAccountModal, setShowCloseAccountModal] = useState(false) const [privacyMode, setPrivacyMode] = useLocalStorageState(PRIVACY_MODE) const [showDelegateModal, setShowDelegateModal] = useState(false) const [submitting, setSubmitting] = useState(false) const [cancelTcs, setCancelTcs] = useState('') const { usedTokens, usedSerum3, usedPerps, usedPerpOo, usedTcs, emptySerum3, emptyPerps, totalTokens, totalSerum3, totalPerps, totalPerpOpenOrders, isAccountFull, } = useMangoAccountAccounts() const tokenStatus = useMemo(() => { const mangoAccount = mangoStore.getState().mangoAccount.current if (!group || !mangoAccount || !usedTokens.length) return [] const tokens = [] for (const token of usedTokens) { const bank = group.getFirstBankByTokenIndex(token.tokenIndex) const tokenMax = getMaxWithdrawForBank(group, bank, mangoAccount) const balance = mangoAccount.getTokenBalanceUi(bank) const isClosable = tokenMax.eq(new Decimal(balance)) && !token.inUseCount tokens.push({ isClosable, balance, tokenIndex: token.tokenIndex }) } return tokens }, [group, mangoAccountAddress, usedTokens]) const handleCloseToken = useCallback( async (tokenMint: PublicKey) => { const client = mangoStore.getState().client const group = mangoStore.getState().group const actions = mangoStore.getState().actions if (!mangoAccount || !group) return setSubmitting(true) try { const { signature: tx, slot } = await client.tokenWithdrawAllDepositForMint( group, mangoAccount, tokenMint, ) notify({ title: 'Transaction confirmed', type: 'success', txid: tx, }) await actions.reloadMangoAccount(slot) setSubmitting(false) } catch (e) { console.error(e) setSubmitting(false) if (!isMangoError(e)) return notify({ title: 'Transaction failed', description: e.message, txid: e?.txid, type: 'error', }) } }, [mangoAccount, setSubmitting], ) const handleCloseSerumOos = useCallback( async (market: Serum3Market) => { const client = mangoStore.getState().client const group = mangoStore.getState().group const actions = mangoStore.getState().actions if (!mangoAccount || !group) return setSubmitting(true) try { const ix = await client.serum3CloseOpenOrdersIx( group, mangoAccount, market.serumMarketExternal, ) const tx = await client.sendAndConfirmTransaction([ix]) 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', }) } finally { setSubmitting(false) } }, [mangoAccount], ) const handleClosePerpAccounts = useCallback( async (marketIndex: number) => { const client = mangoStore.getState().client const group = mangoStore.getState().group const actions = mangoStore.getState().actions if (!mangoAccount || !group) return setSubmitting(true) try { const ix = await client.perpDeactivatePositionIx( group, mangoAccount, marketIndex, ) const tx = await client.sendAndConfirmTransaction([ix]) 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', }) } finally { setSubmitting(false) } }, [mangoAccount], ) return mangoAccount && group && !isDelegatedAccount && !isUnownedAccount ? (
{mangoAccount.publicKey.toString()}
{t('settings:privacy-mode')}
{t('tokens')}
{t('settings:slots-used', { used: usedTokens.length, total: totalTokens.length, type: t('tokens').toLowerCase(), })}
{t('settings:close-token-positions-desc')}
{i + 1}.
{tokenBank.name}
{status?.balance}
{t('settings:close-instructions')}
{t('notifications:empty-state-title')}...
)}{t('settings:spot-markets')}
{t('settings:slots-used', { used: usedSerum3.length, total: totalSerum3.length, type: t('settings:spot-markets').toLowerCase(), })}
{t('settings:close-spot-oo-desc')}
{i + 1}.
{market.name}
{t('notifications:empty-state-title')}...
)}{t('settings:perp-markets')}
{t('settings:slots-used', { used: usedPerps.length, total: totalPerps.length, type: t('settings:perp-positions').toLowerCase(), })}
{t('settings:close-perp-desc')}
{i + 1}.
{market.name}
{t('notifications:empty-state-title')}...
)}{t('settings:perp-open-orders')}
{t('settings:slots-used', { used: usedPerpOo.length, total: totalPerpOpenOrders.length, type: t('settings:perp-open-orders').toLowerCase(), })}
{i + 1}.
{market.name}
{t('notifications:empty-state-title')}...
)}{t('trade:trigger-orders')}
{t('settings:trigger-orders-used', { orders: usedTcs.length, })}
{i + 1}.
{pair}
{t('notifications:empty-state-title')}...
)}{t('settings:account-settings-unowned')}
{t('settings:account-settings-delegated')}