diff --git a/components/forms/Select.tsx b/components/forms/Select.tsx index 7884b656..cd45a072 100644 --- a/components/forms/Select.tsx +++ b/components/forms/Select.tsx @@ -1,10 +1,14 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { Listbox } from '@headlessui/react' import { ChevronDownIcon } from '@heroicons/react/20/solid' import { ReactNode } from 'react' +import { TradeForm } from 'types' -interface SelectProps { - value: string | ReactNode - onChange: (x: string) => void +type Values = TradeForm['tradeType'] | ReactNode + +interface SelectProps { + value: T | string + onChange: (x: T) => void children: ReactNode className?: string buttonClassName?: string @@ -13,7 +17,7 @@ interface SelectProps { disabled?: boolean } -const Select = ({ +const Select = ({ value, onChange, children, @@ -22,7 +26,7 @@ const Select = ({ dropdownPanelClassName, placeholder = 'Select', disabled = false, -}: SelectProps) => { +}: SelectProps) => { return (
@@ -40,7 +44,7 @@ const Select = ({ {placeholder} )} diff --git a/components/shared/BalancesTable.tsx b/components/shared/BalancesTable.tsx index daa39dfe..99baaf6b 100644 --- a/components/shared/BalancesTable.tsx +++ b/components/shared/BalancesTable.tsx @@ -236,7 +236,7 @@ const Balance = ({ bank }: { bank: BankWithBalance }) => { if (!group || !selectedMarket) return let price: number - if (tradeForm.tradeType === 'Market') { + if (tradeForm.tradeType === 'market') { const orderbook = mangoStore.getState().selectedMarket.orderbook const side = (balance > 0 && type === 'quote') || (balance < 0 && type === 'base') diff --git a/components/shared/SuccessParticles.tsx b/components/shared/SuccessParticles.tsx index b5079975..5e69702e 100644 --- a/components/shared/SuccessParticles.tsx +++ b/components/shared/SuccessParticles.tsx @@ -24,7 +24,7 @@ const SuccessParticles = () => { const tokenMint = mangoStore.getState().swap.outputBank?.mint.toString() return mangoTokens.find((t) => t.address === tokenMint)?.logoURI } - if (showForTrade && tradeType === 'Market') { + if (showForTrade && tradeType === 'market') { const market = mangoStore.getState().selectedMarket.current const side = mangoStore.getState().tradeForm.side if (market instanceof Serum3Market) { diff --git a/components/swap/SwapForm.tsx b/components/swap/SwapForm.tsx index 4b976932..f1a25cfa 100644 --- a/components/swap/SwapForm.tsx +++ b/components/swap/SwapForm.tsx @@ -79,6 +79,8 @@ const SwapForm = () => { const [showConfirm, setShowConfirm] = useState(false) const [orderType, setOrderType] = useState(ORDER_TYPES[0]) const [activeTab, setActiveTab] = useState('swap') + const [limitPrice, setLimitPrice] = useState('') + const [triggerPrice, setTriggerPrice] = useState('') const { group } = useMangoGroup() const [swapFormSizeUi] = useLocalStorageState(SIZE_INPUT_UI_KEY, 'slider') const { ipAllowed, ipCountry } = useIpAddress() @@ -187,6 +189,22 @@ const SwapForm = () => { [swapMode, setAmountInFormValue] ) + const handleLimitPrice = useCallback( + (e: NumberFormatValues, info: SourceInfo) => { + if (info.source !== 'event') return + setLimitPrice(e.value) + }, + [setLimitPrice] + ) + + const handleTriggerPrice = useCallback( + (e: NumberFormatValues, info: SourceInfo) => { + if (info.source !== 'event') return + setTriggerPrice(e.value) + }, + [setTriggerPrice] + ) + const handleAmountOutChange = useCallback( (e: NumberFormatValues, info: SourceInfo) => { if (info.source !== 'event') return @@ -411,28 +429,28 @@ const SwapForm = () => { ))}
-
-

- {orderType === 'trade:limit' - ? t('trade:limit-price') - : t('trade:trigger-price')} -

- -
- {orderType === 'trade:stop-limit' ? ( + {orderType !== 'trade:limit' ? ( +
+

+ {t('trade:trigger-price')} +

+ +
+ ) : null} + {orderType !== 'trade:stop-market' ? (

{t('trade:limit-price')}

{ thousandSeparator="," allowNegative={false} isNumericString={true} - decimalScale={inputBank?.mintDecimals || 6} - name="amountIn" - id="amountIn" + decimalScale={outputBank?.mintDecimals || 6} + name="limitPrice" + id="limitPrice" className="h-10 w-full rounded-lg bg-th-input-bkg p-3 text-right font-mono text-sm text-th-fgd-1 focus:border-th-fgd-4 focus:outline-none md:hover:border-th-input-border-hover md:hover:focus-visible:bg-th-bkg-3" placeholder="0.00" - value={amountInFormValue} - onValueChange={handleAmountInChange} + value={limitPrice} + onValueChange={handleLimitPrice} isAllowed={withValueLimit} />
diff --git a/components/trade/AdvancedTradeForm.tsx b/components/trade/AdvancedTradeForm.tsx index 15cd99de..9c42cf55 100644 --- a/components/trade/AdvancedTradeForm.tsx +++ b/components/trade/AdvancedTradeForm.tsx @@ -46,7 +46,6 @@ import useSelectedMarket from 'hooks/useSelectedMarket' import { floorToDecimal, getDecimalCount } from 'utils/numbers' import LogoWithFallback from '@components/shared/LogoWithFallback' import useIpAddress from 'hooks/useIpAddress' -import ButtonGroup from '@components/forms/ButtonGroup' import TradeSummary from './TradeSummary' import useMangoAccount from 'hooks/useMangoAccount' import MaxSizeButton from './MaxSizeButton' @@ -54,8 +53,9 @@ import { INITIAL_SOUND_SETTINGS } from '@components/settings/SoundSettings' import { Howl } from 'howler' import { useWallet } from '@solana/wallet-adapter-react' import { useEnhancedWallet } from '@components/wallet/EnhancedWalletProvider' -import { isMangoError } from 'types' +import { TradeForm, isMangoError } from 'types' import InlineNotification from '@components/shared/InlineNotification' +import Select from '@components/forms/Select' const set = mangoStore.getState().set @@ -76,6 +76,13 @@ export const DEFAULT_CHECKBOX_SETTINGS = { margin: false, } +const ORDER_TYPES = [ + 'trade:limit', + 'market', + 'trade:stop-market', + 'trade:stop-limit', +] + const AdvancedTradeForm = () => { const { t } = useTranslation(['common', 'trade']) const { mangoAccount } = useMangoAccount() @@ -101,7 +108,7 @@ const AdvancedTradeForm = () => { serumOrPerpMarket, } = useSelectedMarket() - const setTradeType = useCallback((tradeType: 'Limit' | 'Market') => { + const setTradeType = useCallback((tradeType: TradeForm['tradeType']) => { set((s) => { s.tradeForm.tradeType = tradeType }) @@ -122,12 +129,22 @@ const AdvancedTradeForm = () => { [] ) + const handleTriggerPriceChange = useCallback( + (e: NumberFormatValues, info: SourceInfo) => { + if (info.source !== 'event') return + set((s) => { + s.tradeForm.triggerPrice = e.value + }) + }, + [] + ) + const handleBaseSizeChange = useCallback( (e: NumberFormatValues, info: SourceInfo) => { if (info.source !== 'event') return set((s) => { const price = - s.tradeForm.tradeType === 'Market' + s.tradeForm.tradeType === 'market' ? oraclePrice : Number(s.tradeForm.price) @@ -147,7 +164,7 @@ const AdvancedTradeForm = () => { if (info.source !== 'event') return set((s) => { const price = - s.tradeForm.tradeType === 'Market' + s.tradeForm.tradeType === 'market' ? oraclePrice : Number(s.tradeForm.price) @@ -229,7 +246,7 @@ const AdvancedTradeForm = () => { const { group } = mangoStore.getState() const { tradeType, side, price, baseSize, quoteSize } = tradeForm - const tradePrice = tradeType === 'Market' ? oraclePrice : price + const tradePrice = tradeType === 'market' ? oraclePrice : price if ( !group || @@ -342,7 +359,7 @@ const AdvancedTradeForm = () => { useEffect(() => { const group = mangoStore.getState().group if ( - tradeForm.tradeType === 'Market' && + tradeForm.tradeType === 'market' && oraclePrice && selectedMarket && group @@ -375,7 +392,7 @@ const AdvancedTradeForm = () => { try { const baseSize = Number(tradeForm.baseSize) let price = Number(tradeForm.price) - if (tradeForm.tradeType === 'Market') { + if (tradeForm.tradeType === 'market') { const orderbook = mangoStore.getState().selectedMarket.orderbook price = calculateLimitPriceForMarketOrder( orderbook, @@ -387,7 +404,7 @@ const AdvancedTradeForm = () => { if (selectedMarket instanceof Serum3Market) { const spotOrderType = tradeForm.ioc ? Serum3OrderType.immediateOrCancel - : tradeForm.postOnly && tradeForm.tradeType !== 'Market' + : tradeForm.postOnly && tradeForm.tradeType !== 'market' ? Serum3OrderType.postOnly : Serum3OrderType.limit const tx = await client.serum3PlaceOrder( @@ -416,7 +433,7 @@ const AdvancedTradeForm = () => { }) } else if (selectedMarket instanceof PerpMarket) { const perpOrderType = - tradeForm.tradeType === 'Market' + tradeForm.tradeType === 'market' ? PerpOrderType.market : tradeForm.ioc ? PerpOrderType.immediateOrCancel @@ -499,15 +516,55 @@ const AdvancedTradeForm = () => {

{t('trade:order-type')}

- setTradeType(tab)} - values={['Limit', 'Market']} - /> +
handleSubmit(e)}>
- {tradeForm.tradeType === 'Limit' ? ( + {tradeForm.tradeType.includes('stop') ? ( + <> +
+

+ {t('trade:trigger-price')} +

+
+
+ {quoteLogoURI ? ( +
+ +
+ ) : ( +
+ +
+ )} + +
{quoteSymbol}
+
+ + ) : null} + {tradeForm.tradeType.includes('limit') ? ( <>

@@ -650,7 +707,7 @@ const AdvancedTradeForm = () => { )}

- {tradeForm.tradeType === 'Limit' ? ( + {tradeForm.tradeType === 'trade:limit' ? (
{ )}
- {tradeForm.tradeType === 'Market' ? ( + {tradeForm.tradeType === 'market' ? (
= ({ const maxSlippage = 0.025 // const perpOrderType = - // tradeForm.tradeType === 'Market' + // tradeForm.tradeType === 'market' // ? PerpOrderType.market // : tradeForm.ioc // ? PerpOrderType.immediateOrCancel diff --git a/components/trade/MaxSizeButton.tsx b/components/trade/MaxSizeButton.tsx index 412d71f8..07c8ff9c 100644 --- a/components/trade/MaxSizeButton.tsx +++ b/components/trade/MaxSizeButton.tsx @@ -63,7 +63,7 @@ const MaxSizeButton = ({ const set = mangoStore.getState().set set((state) => { if (side === 'buy') { - if (tradeType === 'Market' || !price) { + if (tradeType === 'market' || !price) { const baseSize = floorToDecimal(max / oraclePrice, minOrderDecimals) const quoteSize = floorToDecimal(max, tickDecimals) state.tradeForm.baseSize = baseSize.toFixed() @@ -79,7 +79,7 @@ const MaxSizeButton = ({ } } else { const baseSize = floorToDecimal(max, minOrderDecimals) - if (tradeType === 'Market' || !price) { + if (tradeType === 'market' || !price) { const quoteSize = floorToDecimal( baseSize.mul(oraclePrice), tickDecimals @@ -106,7 +106,7 @@ const MaxSizeButton = ({ const maxAmount = useMemo(() => { const max = selectedMarket instanceof Serum3Market ? spotMax : perpMax || 0 - const tradePrice = tradeType === 'Market' ? oraclePrice : Number(price) + const tradePrice = tradeType === 'market' ? oraclePrice : Number(price) if (side === 'buy') { return max / tradePrice } else { diff --git a/components/trade/Orderbook.tsx b/components/trade/Orderbook.tsx index d2681588..b74ac4ff 100644 --- a/components/trade/Orderbook.tsx +++ b/components/trade/Orderbook.tsx @@ -849,7 +849,10 @@ const OrderbookRow = ({ const set = mangoStore.getState().set set((state) => { state.tradeForm.price = formattedPrice.toFixed() - if (state.tradeForm.baseSize && state.tradeForm.tradeType === 'Limit') { + if ( + state.tradeForm.baseSize && + state.tradeForm.tradeType.includes('limit') + ) { const quoteSize = floorToDecimal( formattedPrice.mul(new Decimal(state.tradeForm.baseSize)), getDecimalCount(tickSize) diff --git a/components/trade/PerpPositions.tsx b/components/trade/PerpPositions.tsx index eefa0575..14a2ac11 100644 --- a/components/trade/PerpPositions.tsx +++ b/components/trade/PerpPositions.tsx @@ -50,7 +50,7 @@ const PerpPositions = () => { const set = mangoStore.getState().set let price = Number(tradeForm.price) - if (tradeForm.tradeType === 'Market') { + if (tradeForm.tradeType === 'market') { const orderbook = mangoStore.getState().selectedMarket.orderbook price = calculateLimitPriceForMarketOrder( orderbook, diff --git a/components/trade/PerpSlider.tsx b/components/trade/PerpSlider.tsx index ce926678..22424e02 100644 --- a/components/trade/PerpSlider.tsx +++ b/components/trade/PerpSlider.tsx @@ -55,7 +55,7 @@ const PerpSlider = ({ set((s) => { const price = - s.tradeForm.tradeType === 'Market' + s.tradeForm.tradeType === 'market' ? marketPrice : Number(s.tradeForm.price) diff --git a/components/trade/Slippage.tsx b/components/trade/Slippage.tsx index 5e396e43..7b5da73b 100644 --- a/components/trade/Slippage.tsx +++ b/components/trade/Slippage.tsx @@ -15,7 +15,7 @@ const Slippage = () => { const slippage = useMemo(() => { try { - if (tradeForm.tradeType === 'Market' && markPrice && selectedMarket) { + if (tradeForm.tradeType === 'market' && markPrice && selectedMarket) { const orderbook = mangoStore.getState().selectedMarket.orderbook return calculateSlippage( orderbook, diff --git a/components/trade/SpotSlider.tsx b/components/trade/SpotSlider.tsx index 5d0c37d0..9e4ab3c9 100644 --- a/components/trade/SpotSlider.tsx +++ b/components/trade/SpotSlider.tsx @@ -78,7 +78,7 @@ const SpotSlider = ({ set((s) => { const price = - s.tradeForm.tradeType === 'Market' + s.tradeForm.tradeType === 'market' ? marketPrice : Number(s.tradeForm.price) diff --git a/store/mangoStore.ts b/store/mangoStore.ts index 451efde4..cb5807a2 100644 --- a/store/mangoStore.ts +++ b/store/mangoStore.ts @@ -118,7 +118,8 @@ export const DEFAULT_TRADE_FORM: TradeForm = { price: undefined, baseSize: '', quoteSize: '', - tradeType: 'Limit', + tradeType: 'trade:limit', + triggerPrice: '', postOnly: false, ioc: false, reduceOnly: false, diff --git a/types/index.ts b/types/index.ts index 8dffa84e..61678980 100644 --- a/types/index.ts +++ b/types/index.ts @@ -358,7 +358,8 @@ export interface TradeForm { price: string | undefined baseSize: string quoteSize: string - tradeType: 'Market' | 'Limit' + tradeType: 'market' | 'trade:limit' | 'trade:stop-limit' | 'trade:stop-market' + triggerPrice?: string postOnly: boolean ioc: boolean reduceOnly: boolean