import React, { useEffect, useMemo, useState } from 'react' import Modal from './Modal' import Input from './Input' import { ElementTitle } from './styles' import useMangoStore from '../stores/useMangoStore' import useMarketList from '../hooks/useMarketList' import { floorToDecimal, tokenPrecision, displayDepositsForMarginAccount, } from '../utils/index' import useConnection from '../hooks/useConnection' import { borrowAndWithdraw, withdraw } from '../utils/mango' import Loading from './Loading' import Slider from './Slider' import Button, { LinkButton } from './Button' import { notify } from '../utils/notifications' import Switch from './Switch' import Tooltip from './Tooltip' import { ExclamationCircleIcon, InformationCircleIcon, } from '@heroicons/react/outline' import { ChevronLeftIcon, ChevronDownIcon, ChevronUpIcon, } from '@heroicons/react/solid' import { Disclosure } from '@headlessui/react' import { PublicKey } from '@solana/web3.js' import { MarginAccount, uiToNative } from '@blockworks-foundation/mango-client' import Select from './Select' const WithdrawModal = ({ isOpen, onClose }) => { const [withdrawTokenSymbol, setWithdrawTokenSymbol] = useState('') const [inputAmount, setInputAmount] = useState(0) const [invalidAmountMessage, setInvalidAmountMessage] = useState('') const [maxAmount, setMaxAmount] = useState(0) const [submitting, setSubmitting] = useState(false) const [includeBorrow, setIncludeBorrow] = useState(false) const [simulation, setSimulation] = useState(null) const [showSimulation, setShowSimulation] = useState(false) const [sliderPercentage, setSliderPercentage] = useState(0) const [maxButtonTransition, setMaxButtonTransition] = useState(false) const { getTokenIndex, symbols } = useMarketList() const { connection, programId } = useConnection() const walletAccounts = useMangoStore((s) => s.wallet.balances) const prices = useMangoStore((s) => s.selectedMangoGroup.prices) const selectedMangoGroup = useMangoStore((s) => s.selectedMangoGroup.current) const selectedMarginAccount = useMangoStore( (s) => s.selectedMarginAccount.current ) const actions = useMangoStore((s) => s.actions) const withdrawAccounts = useMemo( () => walletAccounts.filter((acc) => Object.values(symbols).includes(acc.account.mint.toString()) ), [symbols, walletAccounts] ) const [selectedAccount, setSelectedAccount] = useState(withdrawAccounts[0]) const tokenIndex = useMemo( () => getTokenIndex(symbols[withdrawTokenSymbol]), [withdrawTokenSymbol, getTokenIndex] ) const DECIMALS = { BTC: 6, ETH: 5, SOL: 3, SRM: 2, USDC: 2, } useEffect(() => { if (!selectedMangoGroup || !selectedMarginAccount || !withdrawTokenSymbol) return const mintDecimals = selectedMangoGroup.mintDecimals[tokenIndex] const groupIndex = selectedMangoGroup.indexes[tokenIndex] const deposits = selectedMarginAccount.getUiDeposit( selectedMangoGroup, tokenIndex ) const borrows = selectedMarginAccount.getUiBorrow( selectedMangoGroup, tokenIndex ) const currentAssetsVal = selectedMarginAccount.getAssetsVal(selectedMangoGroup, prices) - getMaxForSelectedAsset() * prices[tokenIndex] const currentLiabs = selectedMarginAccount.getLiabsVal( selectedMangoGroup, prices ) // multiply by 0.99 and subtract 0.01 to account for rounding issues const liabsAvail = (currentAssetsVal / 1.2 - currentLiabs) * 0.99 - 0.01 // calculate max withdraw amount const amountToWithdraw = includeBorrow ? liabsAvail / prices[tokenIndex] + getMaxForSelectedAsset() : getMaxForSelectedAsset() if (amountToWithdraw > 0) { setMaxAmount(amountToWithdraw) } else { setMaxAmount(0) } // simulate change to deposits & borrow based on input amount const newDeposit = Math.max(0, deposits - inputAmount) const newBorrows = borrows + Math.max(0, inputAmount - deposits) // clone MarginAccount and arrays to not modify selectedMarginAccount const simulation = new MarginAccount(null, selectedMarginAccount) simulation.deposits = [...selectedMarginAccount.deposits] simulation.borrows = [...selectedMarginAccount.borrows] // update with simulated values simulation.deposits[tokenIndex] = uiToNative(newDeposit, mintDecimals).toNumber() / groupIndex.deposit simulation.borrows[tokenIndex] = uiToNative(newBorrows, mintDecimals).toNumber() / groupIndex.borrow const equity = simulation.computeValue(selectedMangoGroup, prices) const assetsVal = simulation.getAssetsVal(selectedMangoGroup, prices) const liabsVal = simulation.getLiabsVal(selectedMangoGroup, prices) const collateralRatio = simulation.getCollateralRatio( selectedMangoGroup, prices ) const leverage = 1 / Math.max(0, collateralRatio - 1) setSimulation({ equity, assetsVal, liabsVal, collateralRatio, leverage, }) }, [ includeBorrow, inputAmount, prices, tokenIndex, selectedMarginAccount, selectedMangoGroup, ]) const handleWithdraw = () => { setSubmitting(true) const marginAccount = useMangoStore.getState().selectedMarginAccount.current const mangoGroup = useMangoStore.getState().selectedMangoGroup.current const wallet = useMangoStore.getState().wallet.current if (!marginAccount || !mangoGroup) return if (!includeBorrow) { withdraw( connection, new PublicKey(programId), mangoGroup, marginAccount, wallet, selectedAccount.account.mint, selectedAccount.publicKey, Number(inputAmount) ) .then((_transSig: string) => { setSubmitting(false) actions.fetchMangoGroup() actions.fetchMarginAccounts() actions.fetchWalletBalances() onClose() }) .catch((err) => { setSubmitting(false) console.warn('Error withdrawing:', err) notify({ message: 'Could not perform withdraw', txid: err.txid, type: 'error', }) onClose() }) } else { borrowAndWithdraw( connection, new PublicKey(programId), mangoGroup, marginAccount, wallet, selectedAccount.account.mint, selectedAccount.publicKey, Number(inputAmount) ) .then((_transSig: string) => { setSubmitting(false) actions.fetchMangoGroup() actions.fetchMarginAccounts() actions.fetchWalletBalances() onClose() }) .catch((err) => { setSubmitting(false) console.warn('Error borrowing and withdrawing:', err) notify({ message: 'Could not perform borrow and withdraw', description: `${err}`, txid: err.txid, type: 'error', }) onClose() }) } } const handleSetSelectedAsset = (symbol) => { setInputAmount(0) setSliderPercentage(0) setWithdrawTokenSymbol(symbol) } const getMaxForSelectedAsset = () => { return displayDepositsForMarginAccount( selectedMarginAccount, selectedMangoGroup, tokenIndex ) } const getBorrowAmount = () => { const tokenBalance = getMaxForSelectedAsset() const borrowAmount = inputAmount - tokenBalance return borrowAmount > 0 ? borrowAmount : 0 } const getAccountStatusColor = ( collateralRatio: number, isRisk?: boolean, isStatus?: boolean ) => { if (collateralRatio < 1.25) { return isRisk ? (