diff --git a/components/swap/BuyTokenInput.tsx b/components/swap/BuyTokenInput.tsx
index 30e00ee9..178df008 100644
--- a/components/swap/BuyTokenInput.tsx
+++ b/components/swap/BuyTokenInput.tsx
@@ -14,6 +14,7 @@ import { OUTPUT_TOKEN_DEFAULT } from 'utils/constants'
import { NUMBER_FORMAT_CLASSNAMES } from './MarketSwapForm'
import InlineNotification from '@components/shared/InlineNotification'
import useMangoAccount from 'hooks/useMangoAccount'
+import { SwapFormTokenListType } from './SwapFormTokenList'
const BuyTokenInput = ({
error,
@@ -25,7 +26,7 @@ const BuyTokenInput = ({
error?: string
handleAmountOutChange: (e: NumberFormatValues, info: SourceInfo) => void
loading?: boolean
- setShowTokenSelect: Dispatch>
+ setShowTokenSelect: Dispatch>
handleRepay?: (amountOut: string) => void
}) => {
const { t } = useTranslation('common')
diff --git a/components/swap/LimitSwapForm.tsx b/components/swap/LimitSwapForm.tsx
index ef94b595..8449b600 100644
--- a/components/swap/LimitSwapForm.tsx
+++ b/components/swap/LimitSwapForm.tsx
@@ -26,8 +26,8 @@ import SwapSlider from './SwapSlider'
import PercentageSelectButtons from './PercentageSelectButtons'
import { floorToDecimal, formatCurrencyValue } from 'utils/numbers'
import { withValueLimit } from './MarketSwapForm'
-import SellTokenInput from './SellTokenInput'
-import BuyTokenInput from './BuyTokenInput'
+import ReduceInputTokenInput from './ReduceInputTokenInput'
+import ReduceOutputTokenInput from './ReduceOutputTokenInput'
import { notify } from 'utils/notifications'
import * as sentry from '@sentry/nextjs'
import { isMangoError } from 'types'
@@ -45,12 +45,13 @@ import DepositWithdrawModal from '@components/modals/DepositWithdrawModal'
import useRemainingBorrowsInPeriod from 'hooks/useRemainingBorrowsInPeriod'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
+import { SwapFormTokenListType } from './SwapFormTokenList'
dayjs.extend(relativeTime)
type LimitSwapFormProps = {
- showTokenSelect: 'input' | 'output' | undefined
- setShowTokenSelect: Dispatch>
+ showTokenSelect: SwapFormTokenListType
+ setShowTokenSelect: Dispatch>
}
type LimitSwapForm = {
@@ -230,12 +231,20 @@ const LimitSwapForm = ({
return triggerDifference
}, [quotePrice, triggerPrice])
- const handleTokenSelect = (type: 'input' | 'output') => {
+ const handleTokenSelect = (type: SwapFormTokenListType) => {
setShowTokenSelect(type)
setFormErrors({})
setTriggerPrice('')
}
+ useLayoutEffect(() => {
+ if (!mangoAccount || !inputBank) {
+ return
+ }
+ const inputPos = mangoAccount.getTokenBalanceUi(inputBank)
+ setAnimateSwitchArrow(() => (inputPos > 0 ? 0 : 1))
+ }, [inputBank, mangoAccount])
+
const hasBorrowToRepay = useMemo(() => {
if (
// orderType !== OrderTypes.REPAY_BORROW ||
@@ -480,47 +489,6 @@ const LimitSwapForm = ({
[amountInFormValue, flipPrices, setFormErrors, setTriggerPrice],
)
- const handleSwitchTokens = useCallback(() => {
- if (!inputBank || !outputBank) return
- setFormErrors({})
- set((s) => {
- s.swap.inputBank = outputBank
- s.swap.outputBank = inputBank
- })
- const multiplier = getOrderTypeMultiplier(orderType, flipPrices)
- const price = flipPrices
- ? floorToDecimal(
- (inputBank.uiPrice / outputBank.uiPrice) * multiplier,
- outputBank.mintDecimals,
- ).toString()
- : floorToDecimal(
- (outputBank.uiPrice / inputBank.uiPrice) * multiplier,
- inputBank.mintDecimals,
- ).toString()
- setTriggerPrice(price)
-
- if (amountInAsDecimal?.gt(0)) {
- const amountOut = getAmountOut(
- amountInAsDecimal.toString(),
- flipPrices,
- price,
- )
- setAmountOutFormValue(amountOut.toString())
- }
- setAnimateSwitchArrow(
- (prevanimateSwitchArrow) => prevanimateSwitchArrow + 1,
- )
- }, [
- amountInAsDecimal,
- flipPrices,
- inputBank,
- orderType,
- outputBank,
- setAmountInFormValue,
- setFormErrors,
- triggerPrice,
- ])
-
const handlePlaceStopLoss = useCallback(async () => {
const invalidFields = isFormValid({
amountIn: amountInAsDecimal.toNumber(),
@@ -760,11 +728,11 @@ const LimitSwapForm = ({
return (
<>
- handleTokenSelect('input')}
+ setShowTokenSelect={() => handleTokenSelect('reduce-input')}
handleMax={handleMax}
isTriggerOrder
/>
@@ -867,24 +835,19 @@ const LimitSwapForm = ({
) : null}
- handleTokenSelect('output')}
+ setShowTokenSelect={() => handleTokenSelect('reduce-output')}
handleRepay={
// orderType === OrderTypes.REPAY_BORROW ?
handleRepay
diff --git a/components/swap/MarketSwapForm.tsx b/components/swap/MarketSwapForm.tsx
index a39d3476..033ac93a 100644
--- a/components/swap/MarketSwapForm.tsx
+++ b/components/swap/MarketSwapForm.tsx
@@ -38,11 +38,12 @@ import useRemainingBorrowsInPeriod from 'hooks/useRemainingBorrowsInPeriod'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { formatCurrencyValue } from 'utils/numbers'
+import { SwapFormTokenListType } from './SwapFormTokenList'
dayjs.extend(relativeTime)
type MarketSwapFormProps = {
- setShowTokenSelect: Dispatch>
+ setShowTokenSelect: Dispatch>
}
const MAX_DIGITS = 11
diff --git a/components/swap/MaxSwapAmount.tsx b/components/swap/MaxSwapAmount.tsx
index ebf06675..484a4505 100644
--- a/components/swap/MaxSwapAmount.tsx
+++ b/components/swap/MaxSwapAmount.tsx
@@ -3,22 +3,20 @@ import mangoStore from '@store/mangoStore'
import Decimal from 'decimal.js'
import { useTranslation } from 'next-i18next'
import { floorToDecimal } from 'utils/numbers'
-import { useTokenMax } from './useTokenMax'
+import { TokenMaxResults } from './useTokenMax'
const MaxSwapAmount = ({
setAmountIn,
useMargin,
+ maxAmount,
}: {
setAmountIn: (x: string) => void
useMargin: boolean
+ maxAmount: (useMargin: boolean) => TokenMaxResults
}) => {
const { t } = useTranslation('common')
const mangoAccountLoading = mangoStore((s) => s.mangoAccount.initialLoad)
- const {
- amount: tokenMax,
- amountWithBorrow,
- decimals,
- } = useTokenMax(useMargin)
+ const { amount: tokenMax, amountWithBorrow, decimals } = maxAmount(useMargin)
if (mangoAccountLoading) return null
diff --git a/components/swap/ReduceInputTokenInput.tsx b/components/swap/ReduceInputTokenInput.tsx
new file mode 100644
index 00000000..28cf2cb1
--- /dev/null
+++ b/components/swap/ReduceInputTokenInput.tsx
@@ -0,0 +1,149 @@
+import TokenSelect from './TokenSelect'
+import NumberFormat, {
+ NumberFormatValues,
+ SourceInfo,
+} from 'react-number-format'
+import { formatCurrencyValue } from 'utils/numbers'
+import { useTranslation } from 'react-i18next'
+import { Dispatch, SetStateAction, useMemo } from 'react'
+import mangoStore from '@store/mangoStore'
+import useMangoGroup from 'hooks/useMangoGroup'
+import { INPUT_TOKEN_DEFAULT } from 'utils/constants'
+import { NUMBER_FORMAT_CLASSNAMES, withValueLimit } from './MarketSwapForm'
+import MaxSwapAmount from './MaxSwapAmount'
+import useUnownedAccount from 'hooks/useUnownedAccount'
+import InlineNotification from '@components/shared/InlineNotification'
+import useMangoAccount from 'hooks/useMangoAccount'
+import { toUiDecimalsForQuote } from '@blockworks-foundation/mango-v4'
+import { SwapFormTokenListType } from './SwapFormTokenList'
+import { TokenMaxResults } from './useTokenMax'
+import Decimal from 'decimal.js'
+
+const useAbsInputPosition = (): TokenMaxResults => {
+ const { mangoAccount } = useMangoAccount()
+ const { inputBank } = mangoStore((s) => s.swap)
+
+ if (!mangoAccount || !inputBank) {
+ return {
+ amount: new Decimal(0),
+ amountWithBorrow: new Decimal(0),
+ decimals: 6,
+ }
+ }
+
+ const amount = new Decimal(
+ Math.abs(mangoAccount.getTokenBalanceUi(inputBank)),
+ )
+ return {
+ decimals: inputBank.mintDecimals,
+ amount: amount,
+ amountWithBorrow: amount,
+ }
+}
+
+const ReduceInputTokenInput = ({
+ handleAmountInChange,
+ setShowTokenSelect,
+ handleMax,
+ className,
+ error,
+ isTriggerOrder,
+}: {
+ handleAmountInChange: (e: NumberFormatValues, info: SourceInfo) => void
+ setShowTokenSelect: Dispatch>
+ handleMax: (amountIn: string) => void
+ className?: string
+ error?: string
+ isTriggerOrder?: boolean
+}) => {
+ const { t } = useTranslation('common')
+ const { mangoAccountAddress } = useMangoAccount()
+ const { group } = useMangoGroup()
+ const { isUnownedAccount } = useUnownedAccount()
+ const {
+ margin: useMargin,
+ inputBank,
+ amountIn: amountInFormValue,
+ } = mangoStore((s) => s.swap)
+
+ const freeCollateral = useMemo(() => {
+ const group = mangoStore.getState().group
+ const mangoAccount = mangoStore.getState().mangoAccount.current
+ return group && mangoAccount
+ ? toUiDecimalsForQuote(mangoAccount.getCollateralValue(group))
+ : 10
+ }, [mangoAccountAddress])
+
+ return (
+
+
+
{t('reduce')}
+ {!isUnownedAccount ? (
+
handleMax(v)}
+ maxAmount={useAbsInputPosition}
+ />
+ ) : null}
+
+
+
+
+
+
+ {!isNaN(Number(amountInFormValue)) ? (
+
+ {inputBank
+ ? formatCurrencyValue(
+ inputBank.uiPrice * Number(amountInFormValue),
+ )
+ : '–'}
+
+ ) : null}
+
+ {mangoAccountAddress && freeCollateral <= 0 ? (
+
+
+
+ ) : null}
+ {error ? (
+
+
+
+ ) : null}
+
+ )
+}
+
+export default ReduceInputTokenInput
diff --git a/components/swap/ReduceOutputTokenInput.tsx b/components/swap/ReduceOutputTokenInput.tsx
new file mode 100644
index 00000000..15736ecb
--- /dev/null
+++ b/components/swap/ReduceOutputTokenInput.tsx
@@ -0,0 +1,127 @@
+import MaxAmountButton from '@components/shared/MaxAmountButton'
+import TokenSelect from './TokenSelect'
+import Loading from '@components/shared/Loading'
+import NumberFormat, {
+ NumberFormatValues,
+ SourceInfo,
+} from 'react-number-format'
+import { floorToDecimal, formatCurrencyValue } from 'utils/numbers'
+import { useTranslation } from 'react-i18next'
+import { Dispatch, SetStateAction, useMemo } from 'react'
+import mangoStore from '@store/mangoStore'
+import useMangoGroup from 'hooks/useMangoGroup'
+import { OUTPUT_TOKEN_DEFAULT } from 'utils/constants'
+import { NUMBER_FORMAT_CLASSNAMES } from './MarketSwapForm'
+import InlineNotification from '@components/shared/InlineNotification'
+import useMangoAccount from 'hooks/useMangoAccount'
+import { SwapFormTokenListType } from './SwapFormTokenList'
+
+const ReduceOutputTokenInput = ({
+ error,
+ handleAmountOutChange,
+ loading,
+ setShowTokenSelect,
+ handleRepay,
+}: {
+ error?: string
+ handleAmountOutChange: (e: NumberFormatValues, info: SourceInfo) => void
+ loading?: boolean
+ setShowTokenSelect: Dispatch>
+ handleRepay?: (amountOut: string) => void
+}) => {
+ const { t } = useTranslation('common')
+ const { mangoAccount } = useMangoAccount()
+ const { group } = useMangoGroup()
+ const {
+ inputBank,
+ outputBank,
+ amountOut: amountOutFormValue,
+ } = mangoStore((s) => s.swap)
+
+ const reducingLong =
+ mangoAccount && inputBank
+ ? mangoAccount.getTokenBalanceUi(inputBank) > 0
+ : false
+
+ const outputTokenBalanceBorrow = useMemo(() => {
+ if (!outputBank || !mangoAccount) return 0
+ const balance = mangoAccount.getTokenBalanceUi(outputBank)
+ const roundedBalance = floorToDecimal(
+ balance,
+ outputBank.mintDecimals,
+ ).toNumber()
+ return balance && balance < 0 ? Math.abs(roundedBalance).toString() : 0
+ }, [mangoAccount, outputBank])
+
+ return (
+
+
+
+ {reducingLong ? t('producing') : t('by-selling')}
+
+ {handleRepay && outputTokenBalanceBorrow ? (
+
handleRepay(outputTokenBalanceBorrow)}
+ value={outputTokenBalanceBorrow}
+ />
+ ) : null}
+
+
+
+
+
+ {loading ? (
+
+
+
+ ) : (
+ <>
+
+ {!isNaN(Number(amountOutFormValue)) ? (
+
+ {outputBank
+ ? formatCurrencyValue(
+ outputBank.uiPrice * Number(amountOutFormValue),
+ )
+ : '–'}
+
+ ) : null}
+ >
+ )}
+
+ {error ? (
+
+
+
+ ) : null}
+
+ )
+}
+
+export default ReduceOutputTokenInput
diff --git a/components/swap/SellTokenInput.tsx b/components/swap/SellTokenInput.tsx
index 05f89189..abd4517a 100644
--- a/components/swap/SellTokenInput.tsx
+++ b/components/swap/SellTokenInput.tsx
@@ -15,6 +15,8 @@ import useUnownedAccount from 'hooks/useUnownedAccount'
import InlineNotification from '@components/shared/InlineNotification'
import useMangoAccount from 'hooks/useMangoAccount'
import { toUiDecimalsForQuote } from '@blockworks-foundation/mango-v4'
+import { SwapFormTokenListType } from './SwapFormTokenList'
+import { useTokenMax } from './useTokenMax'
const SellTokenInput = ({
handleAmountInChange,
@@ -25,7 +27,7 @@ const SellTokenInput = ({
isTriggerOrder,
}: {
handleAmountInChange: (e: NumberFormatValues, info: SourceInfo) => void
- setShowTokenSelect: Dispatch>
+ setShowTokenSelect: Dispatch>
handleMax: (amountIn: string) => void
className?: string
error?: string
@@ -59,6 +61,7 @@ const SellTokenInput = ({
handleMax(v)}
+ maxAmount={useTokenMax}
/>
) : null}
diff --git a/components/swap/SwapForm.tsx b/components/swap/SwapForm.tsx
index 7fae0043..269f9727 100644
--- a/components/swap/SwapForm.tsx
+++ b/components/swap/SwapForm.tsx
@@ -20,13 +20,15 @@ import LimitSwapForm from './LimitSwapForm'
import Switch from '@components/forms/Switch'
import useLocalStorageState from 'hooks/useLocalStorageState'
import { useIsWhiteListed } from 'hooks/useIsWhiteListed'
+import { SwapFormTokenListType } from './SwapFormTokenList'
const set = mangoStore.getState().set
const SwapForm = () => {
const { t } = useTranslation(['common', 'swap', 'trade'])
const { data: isWhiteListed } = useIsWhiteListed()
- const [showTokenSelect, setShowTokenSelect] = useState<'input' | 'output'>()
+ const [showTokenSelect, setShowTokenSelect] =
+ useState()
const [showSettings, setShowSettings] = useState(false)
const [swapOrLimit, setSwapOrLimit] = useState('swap')
const [, setSavedSwapMargin] = useLocalStorageState(
@@ -149,7 +151,7 @@ const SwapForm = () => {
setShowTokenSelect(undefined)}
onTokenSelect={
- showTokenSelect === 'input'
+ showTokenSelect === 'input' || showTokenSelect === 'reduce-input'
? handleTokenInSelect
: handleTokenOutSelect
}
diff --git a/components/swap/SwapFormTokenList.tsx b/components/swap/SwapFormTokenList.tsx
index e00a2d3e..d87e8f57 100644
--- a/components/swap/SwapFormTokenList.tsx
+++ b/components/swap/SwapFormTokenList.tsx
@@ -15,6 +15,13 @@ import { formatTokenSymbol } from 'utils/tokens'
import TokenLogo from '@components/shared/TokenLogo'
import Input from '@components/forms/Input'
+export type SwapFormTokenListType =
+ | 'input'
+ | 'output'
+ | 'reduce-input'
+ | 'reduce-output'
+ | undefined
+
const generateSearchTerm = (item: Token, searchValue: string) => {
const normalizedSearchValue = searchValue.toLowerCase()
const values = `${item.symbol} ${item.name}`.toLowerCase()
@@ -50,7 +57,7 @@ const TokenItem = ({
token: TokenInfoWithAmounts
onSubmit: (x: string) => void
useMargin: boolean
- type: 'input' | 'output' | undefined
+ type: SwapFormTokenListType
}) => {
const { t } = useTranslation('trade')
const { address, symbol, name } = token
@@ -92,7 +99,7 @@ const TokenItem = ({
- {type === 'input' &&
+ {(type === 'input' || type === 'reduce-input') &&
token.amount &&
token.amountWithBorrow &&
token.decimals ? (
@@ -128,7 +135,7 @@ const SwapFormTokenList = ({
}: {
onClose: () => void
onTokenSelect: (x: string) => void
- type: 'input' | 'output' | undefined
+ type: SwapFormTokenListType
useMargin: boolean
}) => {
const { t } = useTranslation(['common', 'search', 'swap'])
@@ -177,6 +184,37 @@ const SwapFormTokenList = ({
: Number(b.amount) - Number(a.amount),
)
+ return filteredSortedTokens
+ } else if (
+ mangoTokens?.length &&
+ group &&
+ mangoAccount &&
+ outputBank &&
+ inputBank &&
+ type === 'reduce-input'
+ ) {
+ const filteredSortedTokens = mangoTokens
+ .map((token) => {
+ const tokenBank = group.getFirstBankByMint(
+ new PublicKey(token.address),
+ )
+ const uiAmount = mangoAccount.getTokenBalanceUi(tokenBank)
+ const uiDollarValue = uiAmount * tokenBank.uiPrice
+ console.log(tokenBank)
+ return {
+ ...token,
+ amount: new Decimal(uiAmount),
+ amountWithBorrow: new Decimal(uiAmount),
+ absDollarValue: Math.abs(uiDollarValue),
+ decimals: inputBank.mintDecimals,
+ }
+ })
+ .filter(
+ (token) =>
+ token.symbol !== outputBank?.name && token.absDollarValue > 0.0001,
+ )
+ .sort((a, b) => b.absDollarValue - a.absDollarValue)
+
return filteredSortedTokens
} else if (mangoTokens?.length) {
const filteredTokens = mangoTokens
@@ -212,6 +250,8 @@ const SwapFormTokenList = ({
? t('swap:you-sell')
: type === 'output'
? t('swap:you-buy')
+ : type === 'reduce-input'
+ ? t('swap:you-reduce')
: ''}
>
- type: 'input' | 'output'
+ showTokenList: Dispatch>
+ type: SwapFormTokenListType
}
const TokenSelect = ({ bank, showTokenList, type }: TokenSelectProps) => {
const { group } = useMangoGroup()
+ const { mangoAccount } = useMangoAccount()
if (!group) return null
+ let posType = ''
+ if (type === 'reduce-input' && mangoAccount && bank) {
+ const uiPos = mangoAccount.getTokenBalanceUi(bank)
+ if (uiPos > 0) {
+ posType = 'long'
+ } else if (uiPos < 0) {
+ posType = 'short'
+ }
+ }
+
return (