import { useState, useEffect, useRef } from 'react' import { Input, Radio, Switch, Select } from 'antd' import xw from 'xwind' import styled from '@emotion/styled' import useMarket from '../hooks/useMarket' import useWallet from '../hooks/useWallet' import useIpAddress from '../hooks/useIpAddress' import useConnection from '../hooks/useConnection' import { PublicKey } from '@solana/web3.js' import { IDS } from '@blockworks-foundation/mango-client' import { notify } from '../utils/notifications' import { placeAndSettle } from '../utils/mango' import { calculateMarketPrice, getDecimalCount } from '../utils' import FloatingElement from './FloatingElement' import { roundToDecimal } from '../utils/index' import useMarginAccount from '../hooks/useMarginAcccount' import useMangoStore from '../stores/useMangoStore' const SellButton = styled.button` margin: 20px 0px 0px 0px; background: #e54033; border-color: #e54033; ` const BuyButton = styled.button` margin: 20px 0px 0px 0px; color: #141026; background: #9bd104; border-color: #9bd104; ` export default function TradeForm({ setChangeOrderRef, }: { setChangeOrderRef?: ( ref: ({ size, price }: { size?: number; price?: number }) => void ) => void }) { console.log('reloading trade form') const [side, setSide] = useState<'buy' | 'sell'>('buy') const { baseCurrency, quoteCurrency, market } = useMarket() const address = market?.publicKey const { wallet, connected } = useWallet() const { connection, cluster } = useConnection() const { marginAccount, mangoGroup } = useMarginAccount() const tradeForm = useMangoStore((s) => s.tradeForm) const orderBookRef = useRef(useMangoStore.getState().market.orderBook) const orderbook = orderBookRef.current[0] useEffect( () => useMangoStore.subscribe( (orderBook) => (orderBookRef.current = orderBook as any[]), (state) => state.market.orderBook ), [] ) const markPriceRef = useRef(useMangoStore.getState().market.markPrice) const markPrice = markPriceRef.current useEffect( () => useMangoStore.subscribe( (markPrice) => (markPriceRef.current = markPrice as number), (state) => state.market.markPrice ), [] ) const { ipAllowed } = useIpAddress() const [postOnly, setPostOnly] = useState(false) const [ioc, setIoc] = useState(false) const [baseSize, setBaseSize] = useState(undefined) const [quoteSize, setQuoteSize] = useState(undefined) const [price, setPrice] = useState(undefined) const [submitting, setSubmitting] = useState(false) const [tradeType, setTradeType] = useState('Limit') const sizeDecimalCount = market?.minOrderSize && getDecimalCount(market.minOrderSize) const priceDecimalCount = market?.tickSize && getDecimalCount(market.tickSize) useEffect(() => { setChangeOrderRef && setChangeOrderRef(doChangeOrder) // eslint-disable-next-line react-hooks/exhaustive-deps }, [setChangeOrderRef]) useEffect(() => { if (!price && markPrice && tradeType !== 'Market') { setPrice(markPrice) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [price, baseSize, quoteSize]) // Set the price from the balance comp useEffect(() => { if (tradeForm.currency) { if (tradeForm.currency === baseCurrency) { // onSetBaseSize(size.size); } else { // onSetQuoteSize(size.size); } } }, [tradeForm]) const onSetBaseSize = (baseSize: number | undefined) => { setBaseSize(baseSize) if (!baseSize) { setQuoteSize(undefined) return } const usePrice = price || markPrice if (!usePrice) { setQuoteSize(undefined) return } const rawQuoteSize = baseSize * usePrice const quoteSize = baseSize && roundToDecimal(rawQuoteSize, sizeDecimalCount) setQuoteSize(quoteSize) } const onSetQuoteSize = (quoteSize: number | undefined) => { setQuoteSize(quoteSize) if (!quoteSize) { setBaseSize(undefined) return } const usePrice = price || markPrice if (!usePrice) { setBaseSize(undefined) return } const rawBaseSize = quoteSize / usePrice const baseSize = quoteSize && roundToDecimal(rawBaseSize, sizeDecimalCount) setBaseSize(baseSize) } const doChangeOrder = ({ size, price, }: { size?: number price?: number }) => { const formattedSize = size && roundToDecimal(size, sizeDecimalCount) const formattedPrice = price && roundToDecimal(price, priceDecimalCount) formattedSize && onSetBaseSize(formattedSize) formattedPrice && setPrice(formattedPrice) } const postOnChange = (checked) => { if (checked) { setIoc(false) } setPostOnly(checked) } const iocOnChange = (checked) => { if (checked) { setPostOnly(false) } setIoc(checked) } async function onSubmit() { if (!price && tradeType === 'Limit') { console.warn('Missing price') notify({ message: 'Missing price', type: 'error', }) return } else if (!baseSize) { console.warn('Missing size') notify({ message: 'Missing size', type: 'error', }) return } console.log('checking if we can call place order', { mangoGroup, address, marginAccount, market, }) if (!mangoGroup || !address || !marginAccount || !market) return setSubmitting(true) try { let calculatedPrice if (tradeType === 'Market') { calculatedPrice = side === 'buy' ? calculateMarketPrice(orderbook.asks, tradeForm.size, side) : calculateMarketPrice(orderbook.bids, tradeForm.size, side) } await placeAndSettle( connection, new PublicKey(IDS[cluster].mango_program_id), mangoGroup, // TODO: // @ts-ignore marginAccount, market, wallet, side, calculatedPrice ?? price, baseSize, ioc ? 'ioc' : postOnly ? 'postOnly' : 'limit' ) // refreshCache(tuple('getTokenAccounts', wallet, connected)) setPrice(undefined) onSetBaseSize(undefined) } catch (e) { console.warn(e) notify({ message: 'Error placing order', description: e.message, type: 'error', }) } finally { setSubmitting(false) } } const handleTradeTypeChange = (tradeType) => { setTradeType(tradeType) if (tradeType === 'Market') { setIoc(true) setPrice(undefined) } else { const limitPrice = side === 'buy' ? orderbook.asks[0][0] : orderbook.bids[0][0] setPrice(limitPrice) setIoc(false) } } return (
setSide(e.target.value)} value={side} buttonStyle="solid" style={{ marginBottom: 8, width: '100%', }} > BUY SELL Price
} suffix={ {quoteCurrency} } value={price} type="number" step={market?.tickSize || 1} onChange={(e) => setPrice(parseFloat(e.target.value))} disabled={tradeType === 'Market'} /> Size} suffix={ {baseCurrency} } value={baseSize} type="number" step={market?.minOrderSize || 1} onChange={(e) => onSetBaseSize(parseFloat(e.target.value))} /> {quoteCurrency} } value={quoteSize} type="number" step={market?.minOrderSize || 1} onChange={(e) => onSetQuoteSize(parseFloat(e.target.value))} /> {/* {connected && marginInfo.prices.length ? ( ) : null} */} {tradeType !== 'Market' ? (
{'POST '} {'IOC '}
) : null}
{ipAllowed ? ( side === 'buy' ? ( {connected ? `Buy ${baseCurrency}` : 'CONNECT WALLET TO TRADE'} ) : ( {connected ? `Sell ${baseCurrency}` : 'CONNECT WALLET TO TRADE'} ) ) : ( )}
) }