import { useState, useEffect, useRef } from 'react' import styled from '@emotion/styled' import useMarket from '../hooks/useMarket' 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 useMangoStore from '../stores/useMangoStore' import Button from './Button' import TradeType from './TradeType' import Input from './Input' import Switch from './Switch' const StyledRightInput = styled(Input)` border-left: 1px solid transparent; ` export default function TradeForm() { const { baseCurrency, quoteCurrency, market, marketAddress } = useMarket() const set = useMangoStore((s) => s.set) const connected = useMangoStore((s) => s.wallet.connected) const actions = useMangoStore((s) => s.actions) const { connection, cluster } = useConnection() const { side, baseSize, quoteSize, price, tradeType } = useMangoStore( (s) => s.tradeForm ) const { ipAllowed } = useIpAddress() const [postOnly, setPostOnly] = useState(false) const [ioc, setIoc] = useState(false) const [submitting, setSubmitting] = useState(false) const orderBookRef = useRef(useMangoStore.getState().selectedMarket.orderBook) const orderbook = orderBookRef.current[0] useEffect( () => useMangoStore.subscribe( (orderBook) => (orderBookRef.current = orderBook as any[]), (state) => state.selectedMarket.orderBook ), [] ) const setSide = (side) => set((s) => { s.tradeForm.side = side }) const setBaseSize = (baseSize) => set((s) => { s.tradeForm.baseSize = parseFloat(baseSize) }) const setQuoteSize = (quoteSize) => set((s) => { s.tradeForm.quoteSize = parseFloat(quoteSize) }) const setPrice = (price) => set((s) => { s.tradeForm.price = parseFloat(price) }) const setTradeType = (type) => set((s) => { s.tradeForm.tradeType = type }) const markPriceRef = useRef(useMangoStore.getState().selectedMarket.markPrice) const markPrice = markPriceRef.current useEffect( () => useMangoStore.subscribe( (markPrice) => (markPriceRef.current = markPrice as number), (state) => state.selectedMarket.markPrice ), [] ) const sizeDecimalCount = market?.minOrderSize && getDecimalCount(market.minOrderSize) // const priceDecimalCount = market?.tickSize && getDecimalCount(market.tickSize) const onSetPrice = (price: number | '') => { setPrice(price) if (!price) return if (baseSize) { onSetBaseSize(baseSize) } } const onSetBaseSize = (baseSize: number | '') => { setBaseSize(baseSize) if (!baseSize) { setQuoteSize('') return } const usePrice = Number(price) || markPrice if (!usePrice) { setQuoteSize('') return } const rawQuoteSize = baseSize * usePrice const quoteSize = baseSize && roundToDecimal(rawQuoteSize, sizeDecimalCount) setQuoteSize(quoteSize) } const onSetQuoteSize = (quoteSize: number | '') => { setQuoteSize(quoteSize) if (!quoteSize) { setBaseSize('') return } if (!Number(price) && tradeType === 'Limit') { setBaseSize('') return } const usePrice = Number(price) || markPrice const rawBaseSize = quoteSize / usePrice const baseSize = quoteSize && roundToDecimal(rawBaseSize, sizeDecimalCount) setBaseSize(baseSize) } 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 } const marginAccount = useMangoStore.getState().selectedMarginAccount.current const mangoGroup = useMangoStore.getState().selectedMangoGroup.current const wallet = useMangoStore.getState().wallet.current if (!mangoGroup || !marketAddress || !marginAccount || !market) return setSubmitting(true) try { let calculatedPrice if (tradeType === 'Market') { calculatedPrice = side === 'buy' ? calculateMarketPrice(orderbook.asks, baseSize, side) : calculateMarketPrice(orderbook.bids, baseSize, side) } await placeAndSettle( connection, new PublicKey(IDS[cluster].mango_program_id), mangoGroup, marginAccount, market, wallet, side, calculatedPrice ?? price, baseSize, ioc ? 'ioc' : postOnly ? 'postOnly' : 'limit' ) console.log('Successfully placed trade!') setPrice('') onSetBaseSize('') actions.fetchMarginAccounts() } catch (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('') } else { const limitPrice = side === 'buy' ? orderbook.asks[0][0] : orderbook.bids[0][0] setPrice(limitPrice) setIoc(false) } } const disabledTradeButton = (!price && tradeType === 'Limit') || !baseSize || !connected || submitting return (
onSetPrice(parseFloat(e.target.value))} value={price} disabled={tradeType === 'Market'} prefix={'Price'} suffix={quoteCurrency} className="rounded-r-none" wrapperClassName="w-3/5" /> onSetBaseSize(parseFloat(e.target.value))} value={baseSize} className="rounded-r-none" wrapperClassName="w-3/5" prefix={'Size'} suffix={baseCurrency} /> onSetQuoteSize(parseFloat(e.target.value))} value={quoteSize} className="rounded-l-none" wrapperClassName="w-2/5" suffix={quoteCurrency} /> {tradeType !== 'Market' ? (
POST
IOC
) : null}
{ipAllowed ? ( connected ? ( side === 'buy' ? ( ) : ( ) ) : ( <> {/*
*/} ) ) : ( )}
) }