Merge branch 'align-advanced-trade-form' into max/stoploss
This commit is contained in:
commit
82a3dcebb6
|
@ -1,758 +0,0 @@
|
||||||
import { useState, useEffect, useRef } from 'react'
|
|
||||||
import styled from '@emotion/styled'
|
|
||||||
import useIpAddress from '../hooks/useIpAddress'
|
|
||||||
import {
|
|
||||||
getTokenBySymbol,
|
|
||||||
PerpMarket,
|
|
||||||
} from '@blockworks-foundation/mango-client'
|
|
||||||
import { notify } from '../utils/notifications'
|
|
||||||
import { calculateTradePrice, getDecimalCount } from '../utils'
|
|
||||||
import { floorToDecimal } from '../utils/index'
|
|
||||||
import useMangoStore from '../stores/useMangoStore'
|
|
||||||
import Button from './Button'
|
|
||||||
import TradeType from './TradeType'
|
|
||||||
import Input from './Input'
|
|
||||||
import Switch from './Switch'
|
|
||||||
import { Market } from '@project-serum/serum'
|
|
||||||
import Big from 'big.js'
|
|
||||||
import MarketFee from './MarketFee'
|
|
||||||
import LeverageSlider from './LeverageSlider'
|
|
||||||
import Loading from './Loading'
|
|
||||||
import Tooltip from './Tooltip'
|
|
||||||
import { useViewport } from '../hooks/useViewport'
|
|
||||||
import { breakpoints } from './TradePageGrid'
|
|
||||||
import OrderSideTabs from './OrderSideTabs'
|
|
||||||
import { ElementTitle } from './styles'
|
|
||||||
|
|
||||||
const StyledRightInput = styled(Input)`
|
|
||||||
border-left: 1px solid transparent;
|
|
||||||
`
|
|
||||||
|
|
||||||
export default function AdvancedTradeForm({ initLeverage }) {
|
|
||||||
const set = useMangoStore((s) => s.set)
|
|
||||||
const { ipAllowed } = useIpAddress()
|
|
||||||
const connected = useMangoStore((s) => s.wallet.connected)
|
|
||||||
const actions = useMangoStore((s) => s.actions)
|
|
||||||
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
|
||||||
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
|
|
||||||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
|
||||||
const mangoClient = useMangoStore((s) => s.connection.client)
|
|
||||||
const market = useMangoStore((s) => s.selectedMarket.current)
|
|
||||||
const isPerpMarket = market instanceof PerpMarket
|
|
||||||
const [reduceOnly, setReduceOnly] = useState(false)
|
|
||||||
|
|
||||||
const {
|
|
||||||
side,
|
|
||||||
baseSize,
|
|
||||||
quoteSize,
|
|
||||||
price,
|
|
||||||
tradeType,
|
|
||||||
triggerPrice,
|
|
||||||
triggerCondition,
|
|
||||||
} = useMangoStore((s) => s.tradeForm)
|
|
||||||
const isLimitOrder = ['Limit', 'Stop Limit', 'Take Profit Limit'].includes(
|
|
||||||
tradeType
|
|
||||||
)
|
|
||||||
const isMarketOrder = ['Market', 'Stop Loss', 'Take Profit'].includes(
|
|
||||||
tradeType
|
|
||||||
)
|
|
||||||
const isTriggerOrder = [
|
|
||||||
'Stop Loss',
|
|
||||||
'Stop Limit',
|
|
||||||
'Take Profit',
|
|
||||||
'Take Profit Limit',
|
|
||||||
].includes(tradeType)
|
|
||||||
|
|
||||||
const { width } = useViewport()
|
|
||||||
const isMobile = width ? width < breakpoints.sm : false
|
|
||||||
|
|
||||||
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
|
|
||||||
useEffect(
|
|
||||||
() =>
|
|
||||||
useMangoStore.subscribe(
|
|
||||||
// @ts-ignore
|
|
||||||
(orderBook) => (orderBookRef.current = orderBook),
|
|
||||||
(state) => state.selectedMarket.orderBook
|
|
||||||
),
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (tradeType === 'Market') {
|
|
||||||
set((s) => {
|
|
||||||
s.tradeForm.price = ''
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [tradeType, set])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let condition
|
|
||||||
switch (tradeType) {
|
|
||||||
case 'Stop Loss':
|
|
||||||
case 'Stop Limit':
|
|
||||||
condition = side == 'buy' ? 'above' : 'below'
|
|
||||||
break
|
|
||||||
case 'Take Profit':
|
|
||||||
case 'Take Profit Limit':
|
|
||||||
condition = side == 'buy' ? 'below' : 'above'
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if (condition) {
|
|
||||||
set((s) => {
|
|
||||||
s.tradeForm.triggerCondition = condition
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [set, tradeType, side])
|
|
||||||
|
|
||||||
const setSide = (side) => {
|
|
||||||
set((s) => {
|
|
||||||
s.tradeForm.side = side
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const setBaseSize = (baseSize) =>
|
|
||||||
set((s) => {
|
|
||||||
if (!Number.isNaN(parseFloat(baseSize))) {
|
|
||||||
s.tradeForm.baseSize = parseFloat(baseSize)
|
|
||||||
} else {
|
|
||||||
s.tradeForm.baseSize = baseSize
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const setQuoteSize = (quoteSize) =>
|
|
||||||
set((s) => {
|
|
||||||
if (!Number.isNaN(parseFloat(quoteSize))) {
|
|
||||||
s.tradeForm.quoteSize = parseFloat(quoteSize)
|
|
||||||
} else {
|
|
||||||
s.tradeForm.quoteSize = quoteSize
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const setPrice = (price) =>
|
|
||||||
set((s) => {
|
|
||||||
if (!Number.isNaN(parseFloat(price))) {
|
|
||||||
s.tradeForm.price = parseFloat(price)
|
|
||||||
} else {
|
|
||||||
s.tradeForm.price = price
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const setTradeType = (type) => {
|
|
||||||
set((s) => {
|
|
||||||
s.tradeForm.tradeType = type
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const setTriggerPrice = (price) => {
|
|
||||||
set((s) => {
|
|
||||||
if (!Number.isNaN(parseFloat(price))) {
|
|
||||||
s.tradeForm.triggerPrice = parseFloat(price)
|
|
||||||
} else {
|
|
||||||
s.tradeForm.triggerPrice = price
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (isMarketOrder) {
|
|
||||||
onSetPrice(price)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const markPriceRef = useRef(useMangoStore.getState().selectedMarket.markPrice)
|
|
||||||
const markPrice = markPriceRef.current
|
|
||||||
useEffect(
|
|
||||||
() =>
|
|
||||||
useMangoStore.subscribe(
|
|
||||||
(markPrice) => (markPriceRef.current = markPrice as number),
|
|
||||||
(state) => state.selectedMarket.markPrice
|
|
||||||
),
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
|
|
||||||
let minOrderSize = '0'
|
|
||||||
if (market instanceof Market && market.minOrderSize) {
|
|
||||||
minOrderSize = market.minOrderSize.toString()
|
|
||||||
} else if (market instanceof PerpMarket) {
|
|
||||||
const baseDecimals = getTokenBySymbol(
|
|
||||||
groupConfig,
|
|
||||||
marketConfig.baseSymbol
|
|
||||||
).decimals
|
|
||||||
minOrderSize = new Big(market.baseLotSize)
|
|
||||||
.div(new Big(10).pow(baseDecimals))
|
|
||||||
.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
const sizeDecimalCount = getDecimalCount(minOrderSize)
|
|
||||||
|
|
||||||
let tickSize = 1
|
|
||||||
if (market instanceof Market) {
|
|
||||||
tickSize = market.tickSize
|
|
||||||
} else if (isPerpMarket) {
|
|
||||||
const baseDecimals = getTokenBySymbol(
|
|
||||||
groupConfig,
|
|
||||||
marketConfig.baseSymbol
|
|
||||||
).decimals
|
|
||||||
const quoteDecimals = getTokenBySymbol(
|
|
||||||
groupConfig,
|
|
||||||
groupConfig.quoteSymbol
|
|
||||||
).decimals
|
|
||||||
|
|
||||||
const nativeToUi = new Big(10).pow(baseDecimals - quoteDecimals)
|
|
||||||
const lotsToNative = new Big(market.quoteLotSize).div(
|
|
||||||
new Big(market.baseLotSize)
|
|
||||||
)
|
|
||||||
tickSize = lotsToNative.mul(nativeToUi).toNumber()
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSetPrice = (price: number | '') => {
|
|
||||||
setPrice(price)
|
|
||||||
if (!price) return
|
|
||||||
if (baseSize) {
|
|
||||||
onSetBaseSize(baseSize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSetBaseSize = (baseSize: number | '') => {
|
|
||||||
const { price } = useMangoStore.getState().tradeForm
|
|
||||||
setBaseSize(baseSize)
|
|
||||||
if (!baseSize) {
|
|
||||||
setQuoteSize('')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const usePrice = Number(price) || markPrice
|
|
||||||
if (!usePrice) {
|
|
||||||
setQuoteSize('')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const rawQuoteSize = baseSize * usePrice
|
|
||||||
setQuoteSize(rawQuoteSize.toFixed(6))
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSetQuoteSize = (quoteSize: number | '') => {
|
|
||||||
setQuoteSize(quoteSize)
|
|
||||||
if (!quoteSize) {
|
|
||||||
setBaseSize('')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Number(price) && isLimitOrder) {
|
|
||||||
setBaseSize('')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const usePrice = Number(price) || markPrice
|
|
||||||
const rawBaseSize = quoteSize / usePrice
|
|
||||||
const baseSize = quoteSize && floorToDecimal(rawBaseSize, sizeDecimalCount)
|
|
||||||
setBaseSize(baseSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onTradeTypeChange = (tradeType) => {
|
|
||||||
setTradeType(tradeType)
|
|
||||||
if (['Market', 'Stop Loss', 'Take Profit'].includes(tradeType)) {
|
|
||||||
setIoc(true)
|
|
||||||
if (isTriggerOrder) {
|
|
||||||
setPrice(triggerPrice)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const priceOnBook = side === 'buy' ? orderbook?.asks : orderbook?.bids
|
|
||||||
if (priceOnBook && priceOnBook.length > 0 && priceOnBook[0].length > 0) {
|
|
||||||
setPrice(priceOnBook[0][0])
|
|
||||||
}
|
|
||||||
setIoc(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const postOnChange = (checked) => {
|
|
||||||
if (checked) {
|
|
||||||
setIoc(false)
|
|
||||||
}
|
|
||||||
setPostOnly(checked)
|
|
||||||
}
|
|
||||||
const iocOnChange = (checked) => {
|
|
||||||
if (checked) {
|
|
||||||
setPostOnly(false)
|
|
||||||
}
|
|
||||||
setIoc(checked)
|
|
||||||
}
|
|
||||||
const reduceOnChange = (checked) => {
|
|
||||||
if (checked) {
|
|
||||||
setReduceOnly(false)
|
|
||||||
}
|
|
||||||
setReduceOnly(checked)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onSubmit() {
|
|
||||||
if (!price && isLimitOrder) {
|
|
||||||
notify({
|
|
||||||
title: 'Missing price',
|
|
||||||
type: 'error',
|
|
||||||
})
|
|
||||||
return
|
|
||||||
} else if (!baseSize) {
|
|
||||||
notify({
|
|
||||||
title: 'Missing size',
|
|
||||||
type: 'error',
|
|
||||||
})
|
|
||||||
return
|
|
||||||
} else if (!triggerPrice && isTriggerOrder) {
|
|
||||||
notify({
|
|
||||||
title: 'Missing trigger price',
|
|
||||||
type: 'error',
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const mangoAccount = useMangoStore.getState().selectedMangoAccount.current
|
|
||||||
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
|
|
||||||
const { askInfo, bidInfo } = useMangoStore.getState().selectedMarket
|
|
||||||
const wallet = useMangoStore.getState().wallet.current
|
|
||||||
|
|
||||||
if (!wallet || !mangoGroup || !mangoAccount || !market) return
|
|
||||||
setSubmitting(true)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const orderPrice = calculateTradePrice(
|
|
||||||
tradeType,
|
|
||||||
orderbook,
|
|
||||||
baseSize,
|
|
||||||
side,
|
|
||||||
price,
|
|
||||||
triggerPrice
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!orderPrice) {
|
|
||||||
notify({
|
|
||||||
title: 'Price not available',
|
|
||||||
description: 'Please try again',
|
|
||||||
type: 'error',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const orderType = ioc ? 'ioc' : postOnly ? 'postOnly' : 'limit'
|
|
||||||
let txid
|
|
||||||
if (market instanceof Market) {
|
|
||||||
txid = await mangoClient.placeSpotOrder2(
|
|
||||||
mangoGroup,
|
|
||||||
mangoAccount,
|
|
||||||
mangoGroup.mangoCache,
|
|
||||||
market,
|
|
||||||
wallet,
|
|
||||||
side,
|
|
||||||
orderPrice,
|
|
||||||
baseSize,
|
|
||||||
orderType
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
if (isTriggerOrder) {
|
|
||||||
txid = await mangoClient.addPerpTriggerOrder(
|
|
||||||
mangoGroup,
|
|
||||||
mangoAccount,
|
|
||||||
market,
|
|
||||||
wallet,
|
|
||||||
isMarketOrder ? 'market' : orderType,
|
|
||||||
side,
|
|
||||||
orderPrice,
|
|
||||||
baseSize,
|
|
||||||
triggerCondition,
|
|
||||||
Number(triggerPrice)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
txid = await mangoClient.placePerpOrder(
|
|
||||||
mangoGroup,
|
|
||||||
mangoAccount,
|
|
||||||
mangoGroup.mangoCache,
|
|
||||||
market,
|
|
||||||
wallet,
|
|
||||||
side,
|
|
||||||
orderPrice,
|
|
||||||
baseSize,
|
|
||||||
isMarketOrder ? 'market' : orderType,
|
|
||||||
Date.now(),
|
|
||||||
side === 'buy' ? askInfo : bidInfo, // book side used for ConsumeEvents
|
|
||||||
reduceOnly
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
notify({ title: 'Successfully placed trade', txid })
|
|
||||||
setPrice('')
|
|
||||||
onSetBaseSize('')
|
|
||||||
} catch (e) {
|
|
||||||
notify({
|
|
||||||
title: 'Error placing order',
|
|
||||||
description: e.message,
|
|
||||||
txid: e.txid,
|
|
||||||
type: 'error',
|
|
||||||
})
|
|
||||||
console.error(e)
|
|
||||||
} finally {
|
|
||||||
// TODO: should be removed, main issue are newly created OO accounts
|
|
||||||
// await sleep(600)
|
|
||||||
actions.reloadMangoAccount()
|
|
||||||
actions.loadMarketFills()
|
|
||||||
setSubmitting(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const disabledTradeButton =
|
|
||||||
(!price && isLimitOrder) ||
|
|
||||||
!baseSize ||
|
|
||||||
!connected ||
|
|
||||||
submitting ||
|
|
||||||
!mangoAccount
|
|
||||||
|
|
||||||
return !isMobile ? (
|
|
||||||
<div className={!connected ? 'fliter blur-sm' : 'flex flex-col h-full'}>
|
|
||||||
<ElementTitle>
|
|
||||||
{marketConfig.name}
|
|
||||||
<span className="border border-th-primary ml-2 px-1 py-0.5 rounded text-xs text-th-primary">
|
|
||||||
{initLeverage}x
|
|
||||||
</span>
|
|
||||||
</ElementTitle>
|
|
||||||
<OrderSideTabs onChange={setSide} side={side} />
|
|
||||||
<Input.Group className="mt-4">
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step={tickSize}
|
|
||||||
onChange={(e) => onSetPrice(e.target.value)}
|
|
||||||
value={price}
|
|
||||||
disabled={isMarketOrder}
|
|
||||||
prefix={'Price'}
|
|
||||||
suffix={groupConfig.quoteSymbol}
|
|
||||||
className="rounded-r-none"
|
|
||||||
wrapperClassName="w-3/5"
|
|
||||||
/>
|
|
||||||
<TradeType
|
|
||||||
onChange={onTradeTypeChange}
|
|
||||||
value={tradeType}
|
|
||||||
offerTriggers={isPerpMarket}
|
|
||||||
className="hover:border-th-primary flex-grow"
|
|
||||||
/>
|
|
||||||
</Input.Group>
|
|
||||||
{isTriggerOrder && (
|
|
||||||
<Input.Group className="mt-4">
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step={tickSize}
|
|
||||||
onChange={(e) => setTriggerPrice(e.target.value)}
|
|
||||||
value={triggerPrice}
|
|
||||||
prefix={'Trigger Price'}
|
|
||||||
suffix={groupConfig.quoteSymbol}
|
|
||||||
className="rounded-r-none"
|
|
||||||
// wrapperClassName="w-3/5"
|
|
||||||
/>
|
|
||||||
</Input.Group>
|
|
||||||
)}
|
|
||||||
<Input.Group className="mt-4">
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step={minOrderSize}
|
|
||||||
onChange={(e) => onSetBaseSize(e.target.value)}
|
|
||||||
value={baseSize}
|
|
||||||
className="rounded-r-none"
|
|
||||||
wrapperClassName="w-3/5"
|
|
||||||
prefixClassName="w-12"
|
|
||||||
prefix={'Size'}
|
|
||||||
suffix={marketConfig.baseSymbol}
|
|
||||||
/>
|
|
||||||
<StyledRightInput
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step={minOrderSize}
|
|
||||||
onChange={(e) => onSetQuoteSize(e.target.value)}
|
|
||||||
value={quoteSize}
|
|
||||||
className="rounded-l-none"
|
|
||||||
wrapperClassName="w-2/5"
|
|
||||||
suffix={groupConfig.quoteSymbol}
|
|
||||||
/>
|
|
||||||
</Input.Group>
|
|
||||||
<LeverageSlider
|
|
||||||
onChange={(e) => onSetBaseSize(e)}
|
|
||||||
value={baseSize ? baseSize : 0}
|
|
||||||
step={parseFloat(minOrderSize)}
|
|
||||||
disabled={false}
|
|
||||||
side={side}
|
|
||||||
decimalCount={sizeDecimalCount}
|
|
||||||
price={calculateTradePrice(
|
|
||||||
tradeType,
|
|
||||||
orderbook,
|
|
||||||
baseSize ? baseSize : 0,
|
|
||||||
side,
|
|
||||||
price,
|
|
||||||
triggerPrice
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<div className="flex mt-2">
|
|
||||||
{isLimitOrder ? (
|
|
||||||
<>
|
|
||||||
<div className="mr-4">
|
|
||||||
<Tooltip
|
|
||||||
delay={250}
|
|
||||||
placement="left"
|
|
||||||
content="Post only orders are guaranteed to be the maker order or else it will be canceled."
|
|
||||||
>
|
|
||||||
<Switch checked={postOnly} onChange={postOnChange}>
|
|
||||||
POST
|
|
||||||
</Switch>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
<div className="mr-4">
|
|
||||||
<Tooltip
|
|
||||||
delay={250}
|
|
||||||
placement="left"
|
|
||||||
content="Immediate or cancel orders are guaranteed to be the taker or it will be canceled."
|
|
||||||
>
|
|
||||||
<Switch checked={ioc} onChange={iocOnChange}>
|
|
||||||
IOC
|
|
||||||
</Switch>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
{marketConfig.kind === 'perp' && !isTriggerOrder ? (
|
|
||||||
<div>
|
|
||||||
<Tooltip
|
|
||||||
delay={250}
|
|
||||||
placement="left"
|
|
||||||
content="Reduce only orders will only reduce your overall position."
|
|
||||||
>
|
|
||||||
<Switch checked={reduceOnly} onChange={reduceOnChange}>
|
|
||||||
Reduce Only
|
|
||||||
</Switch>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
<div className={`flex py-4`}>
|
|
||||||
{ipAllowed ? (
|
|
||||||
<Button
|
|
||||||
disabled={disabledTradeButton}
|
|
||||||
onClick={onSubmit}
|
|
||||||
className={`${
|
|
||||||
!disabledTradeButton
|
|
||||||
? 'bg-th-bkg-2 border border-th-green hover:border-th-green-dark'
|
|
||||||
: 'border border-th-bkg-4'
|
|
||||||
} text-th-green hover:text-th-fgd-1 hover:bg-th-green-dark flex-grow`}
|
|
||||||
>
|
|
||||||
{submitting ? (
|
|
||||||
<div className="w-full">
|
|
||||||
<Loading className="mx-auto" />
|
|
||||||
</div>
|
|
||||||
) : side.toLowerCase() === 'buy' ? (
|
|
||||||
market instanceof PerpMarket ? (
|
|
||||||
`${baseSize > 0 ? 'Long ' + baseSize : 'Long '} ${
|
|
||||||
marketConfig.name
|
|
||||||
}`
|
|
||||||
) : (
|
|
||||||
`${baseSize > 0 ? 'Buy ' + baseSize : 'Buy '} ${
|
|
||||||
marketConfig.baseSymbol
|
|
||||||
}`
|
|
||||||
)
|
|
||||||
) : market instanceof PerpMarket ? (
|
|
||||||
`${baseSize > 0 ? 'Short ' + baseSize : 'Short '} ${
|
|
||||||
marketConfig.name
|
|
||||||
}`
|
|
||||||
) : (
|
|
||||||
`${baseSize > 0 ? 'Sell ' + baseSize : 'Sell '} ${
|
|
||||||
marketConfig.baseSymbol
|
|
||||||
}`
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button disabled className="flex-grow">
|
|
||||||
<span>Country Not Allowed</span>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex text-xs text-th-fgd-4 px-6 mt-2.5">
|
|
||||||
<MarketFee />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex flex-col h-full">
|
|
||||||
<div className={`flex pb-3 text-base text-th-fgd-4`}>
|
|
||||||
<button
|
|
||||||
onClick={() => setSide('buy')}
|
|
||||||
className={`flex-1 outline-none focus:outline-none`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`hover:text-th-green pb-1 transition-colors duration-500
|
|
||||||
${
|
|
||||||
side === 'buy'
|
|
||||||
? `text-th-green hover:text-th-green border-b-2 border-th-green`
|
|
||||||
: undefined
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Buy
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setSide('sell')}
|
|
||||||
className={`flex-1 outline-none focus:outline-none`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`hover:text-th-red pb-1 transition-colors duration-500
|
|
||||||
${
|
|
||||||
side === 'sell'
|
|
||||||
? `text-th-red hover:text-th-red border-b-2 border-th-red`
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
Sell
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="pb-3">
|
|
||||||
<label className="block mb-1 text-th-fgd-3 text-xs">Price</label>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step={tickSize}
|
|
||||||
onChange={(e) => onSetPrice(e.target.value)}
|
|
||||||
value={price}
|
|
||||||
disabled={tradeType === 'Market'}
|
|
||||||
suffix={
|
|
||||||
<img
|
|
||||||
src={`/assets/icons/${groupConfig.quoteSymbol.toLowerCase()}.svg`}
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between pb-3">
|
|
||||||
<label className="text-th-fgd-3 text-xs">Type</label>
|
|
||||||
<TradeType
|
|
||||||
onChange={onTradeTypeChange}
|
|
||||||
value={tradeType}
|
|
||||||
className=""
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label className="block mb-1 text-th-fgd-3 text-xs">Size</label>
|
|
||||||
<div className="grid grid-cols-2 grid-rows-1 gap-2">
|
|
||||||
<div className="col-span-1">
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step={minOrderSize}
|
|
||||||
onChange={(e) => onSetBaseSize(e.target.value)}
|
|
||||||
value={baseSize}
|
|
||||||
suffix={
|
|
||||||
<img
|
|
||||||
src={`/assets/icons/${marketConfig.baseSymbol.toLowerCase()}.svg`}
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-1">
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step={minOrderSize}
|
|
||||||
onChange={(e) => onSetQuoteSize(e.target.value)}
|
|
||||||
value={quoteSize}
|
|
||||||
suffix={
|
|
||||||
<img
|
|
||||||
src={`/assets/icons/${groupConfig.quoteSymbol.toLowerCase()}.svg`}
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<LeverageSlider
|
|
||||||
onChange={(e) => onSetBaseSize(e)}
|
|
||||||
value={baseSize ? baseSize : 0}
|
|
||||||
step={parseFloat(minOrderSize)}
|
|
||||||
disabled={false}
|
|
||||||
side={side}
|
|
||||||
decimalCount={sizeDecimalCount}
|
|
||||||
price={calculateTradePrice(
|
|
||||||
tradeType,
|
|
||||||
orderbook,
|
|
||||||
baseSize ? baseSize : 0,
|
|
||||||
side,
|
|
||||||
price,
|
|
||||||
triggerPrice
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{tradeType !== 'Market' ? (
|
|
||||||
<div className="flex mt-2">
|
|
||||||
<Switch checked={postOnly} onChange={postOnChange}>
|
|
||||||
POST
|
|
||||||
</Switch>
|
|
||||||
<div className="ml-4">
|
|
||||||
<Switch checked={ioc} onChange={iocOnChange}>
|
|
||||||
IOC
|
|
||||||
</Switch>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<div className={`flex py-4`}>
|
|
||||||
{ipAllowed ? (
|
|
||||||
side === 'buy' ? (
|
|
||||||
<Button
|
|
||||||
disabled={disabledTradeButton}
|
|
||||||
onClick={onSubmit}
|
|
||||||
className={`${
|
|
||||||
!disabledTradeButton
|
|
||||||
? 'bg-th-bkg-2 border border-th-green hover:border-th-green-dark'
|
|
||||||
: 'border border-th-bkg-4'
|
|
||||||
} text-th-green hover:text-th-fgd-1 hover:bg-th-green-dark flex-grow`}
|
|
||||||
>
|
|
||||||
{submitting ? (
|
|
||||||
<div className="w-full">
|
|
||||||
<Loading className="mx-auto" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
`${baseSize > 0 ? 'Buy ' + baseSize : 'Buy '} ${
|
|
||||||
marketConfig.name.includes('PERP')
|
|
||||||
? marketConfig.name
|
|
||||||
: marketConfig.baseSymbol
|
|
||||||
}`
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
disabled={disabledTradeButton}
|
|
||||||
onClick={onSubmit}
|
|
||||||
className={`${
|
|
||||||
!disabledTradeButton
|
|
||||||
? 'bg-th-bkg-2 border border-th-red hover:border-th-red-dark'
|
|
||||||
: 'border border-th-bkg-4'
|
|
||||||
} text-th-red hover:text-th-fgd-1 hover:bg-th-red-dark flex-grow`}
|
|
||||||
>
|
|
||||||
{submitting ? (
|
|
||||||
<div className="w-full">
|
|
||||||
<Loading className="mx-auto" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
`${baseSize > 0 ? 'Sell ' + baseSize : 'Sell '} ${
|
|
||||||
marketConfig.name.includes('PERP')
|
|
||||||
? marketConfig.name
|
|
||||||
: marketConfig.baseSymbol
|
|
||||||
}`
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<Button disabled className="flex-grow">
|
|
||||||
<span>Country Not Allowed</span>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex text-xs text-th-fgd-4 px-6 mt-2.5">
|
|
||||||
<MarketFee />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,5 +1,3 @@
|
||||||
// import { useViewport } from '../hooks/useViewport'
|
|
||||||
// import { breakpoints } from './TradePageGrid'
|
|
||||||
import { FunctionComponent } from 'react'
|
import { FunctionComponent } from 'react'
|
||||||
|
|
||||||
interface ButtonGroupProps {
|
interface ButtonGroupProps {
|
||||||
|
@ -15,8 +13,6 @@ const ButtonGroup: FunctionComponent<ButtonGroupProps> = ({
|
||||||
values,
|
values,
|
||||||
onChange,
|
onChange,
|
||||||
}) => {
|
}) => {
|
||||||
// const { width } = useViewport()
|
|
||||||
// const isMobile = width ? width < breakpoints.sm : false
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-th-bkg-3 rounded-md">
|
<div className="bg-th-bkg-3 rounded-md">
|
||||||
<div className="flex relative">
|
<div className="flex relative">
|
||||||
|
@ -37,7 +33,7 @@ const ButtonGroup: FunctionComponent<ButtonGroupProps> = ({
|
||||||
${
|
${
|
||||||
v === activeValue
|
v === activeValue
|
||||||
? `text-th-primary`
|
? `text-th-primary`
|
||||||
: `text-th-fgd-1 opacity-50 hover:opacity-100`
|
: `text-th-fgd-1 opacity-70 hover:opacity-100`
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
key={`${v}${i}`}
|
key={`${v}${i}`}
|
||||||
|
|
|
@ -15,19 +15,20 @@ const HiddenCheckbox = styled.input`
|
||||||
width: 1px;
|
width: 1px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const Checkbox = ({ checked, ...props }) => (
|
const Checkbox = ({ checked, children, ...props }) => (
|
||||||
<>
|
<label className="cursor-pointer flex items-center">
|
||||||
<HiddenCheckbox checked={checked} {...props} type="checkbox" />
|
<HiddenCheckbox checked={checked} {...props} type="checkbox" />
|
||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
checked ? 'bg-th-fgd-4' : 'bg-th-bkg-4'
|
checked ? 'border-th-primary' : 'border-th-fgd-4'
|
||||||
} cursor-pointer default-transition inline-block rounded h-4 w-4`}
|
} border cursor-pointer default-transition flex items-center justify-center rounded h-4 w-4`}
|
||||||
>
|
>
|
||||||
<CheckIcon
|
<CheckIcon
|
||||||
className={`${checked ? 'block' : 'hidden'} h-4 w-4 text-th-primary`}
|
className={`${checked ? 'block' : 'hidden'} h-4 w-4 text-th-primary`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
<span className="ml-2 text-xs text-th-fgd-3">{children}</span>
|
||||||
|
</label>
|
||||||
)
|
)
|
||||||
|
|
||||||
export default Checkbox
|
export default Checkbox
|
||||||
|
|
|
@ -29,9 +29,11 @@ export const FlipCardInner = styled.div<any>`
|
||||||
`
|
`
|
||||||
|
|
||||||
export const FlipCardFront = styled.div`
|
export const FlipCardFront = styled.div`
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
@media screen and (min-width: 768px) {
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const FlipCardBack = styled.div`
|
export const FlipCardBack = styled.div`
|
||||||
|
@ -45,5 +47,4 @@ export const StyledFloatingElement = styled(FloatingElement)`
|
||||||
animation: ${css`
|
animation: ${css`
|
||||||
${fadeIn} 1s linear
|
${fadeIn} 1s linear
|
||||||
`};
|
`};
|
||||||
overflow: hidden;
|
|
||||||
`
|
`
|
||||||
|
|
|
@ -41,7 +41,7 @@ const FloatingElement: FunctionComponent<FloatingElementProps> = ({
|
||||||
const wallet = useMangoStore((s) => s.wallet.current)
|
const wallet = useMangoStore((s) => s.wallet.current)
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`thin-scroll bg-th-bkg-2 rounded-lg p-2.5 sm:p-4 overflow-auto overflow-x-hidden relative ${className}`}
|
className={`thin-scroll bg-th-bkg-2 rounded-lg p-2.5 md:p-4 overflow-auto overflow-x-hidden relative ${className}`}
|
||||||
>
|
>
|
||||||
{!connected && showConnect ? (
|
{!connected && showConnect ? (
|
||||||
<div className="absolute top-0 left-0 w-full h-full z-10">
|
<div className="absolute top-0 left-0 w-full h-full z-10">
|
||||||
|
|
|
@ -30,8 +30,7 @@ const Input = ({
|
||||||
<div className={`flex relative ${wrapperClassName}`}>
|
<div className={`flex relative ${wrapperClassName}`}>
|
||||||
{prefix ? (
|
{prefix ? (
|
||||||
<div
|
<div
|
||||||
className={`flex items-center justify-end p-2 border border-r-0
|
className={`absolute left-2 top-1/2 transform -translate-y-1/2 ${prefixClassName}`}
|
||||||
border-th-fgd-4 bg-th-bkg-2 text-xs rounded-md rounded-r-none text-right ${prefixClassName}`}
|
|
||||||
>
|
>
|
||||||
{prefix}
|
{prefix}
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,7 +39,7 @@ const Input = ({
|
||||||
type={type}
|
type={type}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
className={`${className} pb-px px-2 flex-1 bg-th-bkg-1 rounded-md h-10 text-th-fgd-1 w-full
|
className={`${className} bg-th-bkg-1 pb-px px-2 flex-1 rounded-md h-10 text-th-fgd-1 w-full
|
||||||
border ${
|
border ${
|
||||||
error ? 'border-th-red' : 'border-th-fgd-4'
|
error ? 'border-th-red' : 'border-th-fgd-4'
|
||||||
} default-transition hover:border-th-primary
|
} default-transition hover:border-th-primary
|
||||||
|
@ -50,7 +49,7 @@ const Input = ({
|
||||||
? 'bg-th-bkg-3 cursor-not-allowed hover:border-th-fgd-4 text-th-fgd-3'
|
? 'bg-th-bkg-3 cursor-not-allowed hover:border-th-fgd-4 text-th-fgd-3'
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
${prefix ? 'rounded-l-none' : ''}`}
|
${prefix ? 'pl-7' : ''}`}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -18,9 +18,6 @@ const Switch: FunctionComponent<SwitchProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`flex items-center ${className}`}>
|
<div className={`flex items-center ${className}`}>
|
||||||
<span className="mr-1">
|
|
||||||
<span className="">{children}</span>
|
|
||||||
</span>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`${
|
className={`${
|
||||||
|
@ -41,6 +38,9 @@ const Switch: FunctionComponent<SwitchProps> = ({
|
||||||
shadow transform ring-0 transition ease-in-out duration-200`}
|
shadow transform ring-0 transition ease-in-out duration-200`}
|
||||||
></span>
|
></span>
|
||||||
</button>
|
</button>
|
||||||
|
<span className="ml-2">
|
||||||
|
<span className="">{children}</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import FloatingElement from '../components/FloatingElement'
|
||||||
import Orderbook from '../components/Orderbook'
|
import Orderbook from '../components/Orderbook'
|
||||||
import AccountInfo from './AccountInfo'
|
import AccountInfo from './AccountInfo'
|
||||||
import UserMarketInfo from './UserMarketInfo'
|
import UserMarketInfo from './UserMarketInfo'
|
||||||
import TradeForm from './TradeForm'
|
import TradeForm from './trade_form/TradeForm'
|
||||||
import UserInfo from './UserInfo'
|
import UserInfo from './UserInfo'
|
||||||
import RecentMarketTrades from './RecentMarketTrades'
|
import RecentMarketTrades from './RecentMarketTrades'
|
||||||
import useMangoStore from '../stores/useMangoStore'
|
import useMangoStore from '../stores/useMangoStore'
|
||||||
|
|
|
@ -1,97 +0,0 @@
|
||||||
import { Listbox } from '@headlessui/react'
|
|
||||||
import styled from '@emotion/styled'
|
|
||||||
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/solid'
|
|
||||||
import { useViewport } from '../hooks/useViewport'
|
|
||||||
import { breakpoints } from './TradePageGrid'
|
|
||||||
|
|
||||||
const StyledListbox = styled(Listbox.Button)`
|
|
||||||
border-left: 1px solid transparent;
|
|
||||||
`
|
|
||||||
|
|
||||||
const TradeType = ({
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
offerTriggers = false,
|
|
||||||
className = '',
|
|
||||||
}) => {
|
|
||||||
const { width } = useViewport()
|
|
||||||
const isMobile = width ? width < breakpoints.sm : false
|
|
||||||
|
|
||||||
const TRADE_TYPES = ['Limit', 'Market']
|
|
||||||
if (offerTriggers)
|
|
||||||
TRADE_TYPES.push(
|
|
||||||
'Stop Loss',
|
|
||||||
'Stop Limit',
|
|
||||||
'Take Profit',
|
|
||||||
'Take Profit Limit'
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`relative ${className}`}>
|
|
||||||
{!isMobile ? (
|
|
||||||
<Listbox value={value} onChange={onChange}>
|
|
||||||
{({ open }) => (
|
|
||||||
<>
|
|
||||||
<StyledListbox
|
|
||||||
className={`font-normal h-full w-full bg-th-bkg-1 border border-th-fgd-4 hover:border-th-primary rounded rounded-l-none focus:outline-none focus:border-th-primary`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`flex items-center justify-between space-x-4 pl-2 pr-1`}
|
|
||||||
>
|
|
||||||
<span>{value}</span>
|
|
||||||
{open ? (
|
|
||||||
<ChevronUpIcon className={`h-5 w-5 mr-1 text-th-primary`} />
|
|
||||||
) : (
|
|
||||||
<ChevronDownIcon
|
|
||||||
className={`h-5 w-5 mr-1 text-th-primary`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</StyledListbox>
|
|
||||||
{open ? (
|
|
||||||
<Listbox.Options
|
|
||||||
static
|
|
||||||
className={`z-20 w-full p-1 absolute left-0 mt-1 bg-th-bkg-1 origin-top-left divide-y divide-th-bkg-3 shadow-lg outline-none rounded-md`}
|
|
||||||
>
|
|
||||||
{TRADE_TYPES.map((type) => (
|
|
||||||
<Listbox.Option key={type} value={type}>
|
|
||||||
{({ selected }) => (
|
|
||||||
<div
|
|
||||||
className={`p-2 hover:bg-th-bkg-2 hover:cursor-pointer tracking-wider ${
|
|
||||||
selected && `text-th-primary`
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{type}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Listbox.Option>
|
|
||||||
))}
|
|
||||||
</Listbox.Options>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Listbox>
|
|
||||||
) : (
|
|
||||||
<div className="flex">
|
|
||||||
{TRADE_TYPES.slice(0, 2).map((tradeType) => (
|
|
||||||
<div
|
|
||||||
key={tradeType}
|
|
||||||
className={`px-2 py-2 ml-2 rounded-md cursor-pointer default-transition bg-th-bkg-4
|
|
||||||
${
|
|
||||||
value === tradeType
|
|
||||||
? `ring-1 ring-inset ring-th-primary text-th-primary`
|
|
||||||
: `text-th-fgd-1 opacity-50 hover:opacity-100`
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
onClick={() => onChange(tradeType)}
|
|
||||||
>
|
|
||||||
{tradeType}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TradeType
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { Disclosure } from '@headlessui/react'
|
import { Disclosure } from '@headlessui/react'
|
||||||
import dynamic from 'next/dynamic'
|
import dynamic from 'next/dynamic'
|
||||||
import { XIcon } from '@heroicons/react/outline'
|
import { XIcon } from '@heroicons/react/outline'
|
||||||
import useMangoStore from '../../stores/useMangoStore'
|
import useMangoStore from '../../stores/useMangoStore'
|
||||||
import { PerpMarket } from '@blockworks-foundation/mango-client'
|
import { getWeights, PerpMarket } from '@blockworks-foundation/mango-client'
|
||||||
import { CandlesIcon } from '../icons'
|
import { CandlesIcon } from '../icons'
|
||||||
import SwipeableTabs from './SwipeableTabs'
|
import SwipeableTabs from './SwipeableTabs'
|
||||||
import TradeForm from '../TradeForm'
|
import AdvancedTradeForm from '../trade_form/AdvancedTradeForm'
|
||||||
import Orderbook from '../Orderbook'
|
import Orderbook from '../Orderbook'
|
||||||
import MarketBalances from '../MarketBalances'
|
import MarketBalances from '../MarketBalances'
|
||||||
import MarketDetails from '../MarketDetails'
|
import MarketDetails from '../MarketDetails'
|
||||||
|
@ -25,6 +25,7 @@ const MobileTradePage = () => {
|
||||||
const [viewIndex, setViewIndex] = useState(0)
|
const [viewIndex, setViewIndex] = useState(0)
|
||||||
const selectedMarket = useMangoStore((s) => s.selectedMarket.current)
|
const selectedMarket = useMangoStore((s) => s.selectedMarket.current)
|
||||||
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
|
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
|
||||||
|
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||||
const connected = useMangoStore((s) => s.wallet.connected)
|
const connected = useMangoStore((s) => s.wallet.connected)
|
||||||
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
||||||
const baseSymbol = marketConfig.baseSymbol
|
const baseSymbol = marketConfig.baseSymbol
|
||||||
|
@ -34,6 +35,15 @@ const MobileTradePage = () => {
|
||||||
setViewIndex(index)
|
setViewIndex(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const initLeverage = useMemo(() => {
|
||||||
|
if (!mangoGroup || !marketConfig) return 1
|
||||||
|
|
||||||
|
const ws = getWeights(mangoGroup, marketConfig.marketIndex, 'Init')
|
||||||
|
const w =
|
||||||
|
marketConfig.kind === 'perp' ? ws.perpAssetWeight : ws.spotAssetWeight
|
||||||
|
return Math.round((100 * -1) / (w.toNumber() - 1)) / 100
|
||||||
|
}, [mangoGroup, marketConfig])
|
||||||
|
|
||||||
const TABS =
|
const TABS =
|
||||||
selectedMarket instanceof PerpMarket
|
selectedMarket instanceof PerpMarket
|
||||||
? ['Trade', 'Details', 'Position', 'Orders']
|
? ['Trade', 'Details', 'Position', 'Orders']
|
||||||
|
@ -59,6 +69,9 @@ const MobileTradePage = () => {
|
||||||
{isPerpMarket ? 'PERP' : groupConfig.quoteSymbol}
|
{isPerpMarket ? 'PERP' : groupConfig.quoteSymbol}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<span className="border border-th-primary ml-2 px-1 py-0.5 rounded text-xs text-th-primary">
|
||||||
|
{initLeverage}x
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Disclosure>
|
<Disclosure>
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
|
@ -72,7 +85,7 @@ const MobileTradePage = () => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Disclosure.Button>
|
</Disclosure.Button>
|
||||||
<Disclosure.Panel className="pt-3">
|
<Disclosure.Panel>
|
||||||
<div className="bg-th-bkg-2 h-96 mb-2 p-2 rounded-lg">
|
<div className="bg-th-bkg-2 h-96 mb-2 p-2 rounded-lg">
|
||||||
<TVChartContainer />
|
<TVChartContainer />
|
||||||
</div>
|
</div>
|
||||||
|
@ -90,7 +103,7 @@ const MobileTradePage = () => {
|
||||||
<div>
|
<div>
|
||||||
<div className="bg-th-bkg-2 grid grid-cols-12 grid-rows-1 gap-4 mb-2 px-2 py-3 rounded-lg">
|
<div className="bg-th-bkg-2 grid grid-cols-12 grid-rows-1 gap-4 mb-2 px-2 py-3 rounded-lg">
|
||||||
<div className="col-span-7">
|
<div className="col-span-7">
|
||||||
<TradeForm />
|
<AdvancedTradeForm />
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-5">
|
<div className="col-span-5">
|
||||||
<Orderbook depth={8} />
|
<Orderbook depth={8} />
|
||||||
|
|
|
@ -0,0 +1,789 @@
|
||||||
|
import { useMemo, useState, useEffect, useRef } from 'react'
|
||||||
|
import useIpAddress from '../../hooks/useIpAddress'
|
||||||
|
import {
|
||||||
|
getMarketIndexBySymbol,
|
||||||
|
getTokenBySymbol,
|
||||||
|
I80F48,
|
||||||
|
nativeI80F48ToUi,
|
||||||
|
PerpMarket,
|
||||||
|
} from '@blockworks-foundation/mango-client'
|
||||||
|
import { notify } from '../../utils/notifications'
|
||||||
|
import { calculateTradePrice, getDecimalCount } from '../../utils'
|
||||||
|
import { floorToDecimal } from '../../utils/index'
|
||||||
|
import useMangoStore from '../../stores/useMangoStore'
|
||||||
|
import Button from '../Button'
|
||||||
|
import TradeType from './TradeType'
|
||||||
|
import Input from '../Input'
|
||||||
|
import { Market } from '@project-serum/serum'
|
||||||
|
import Big from 'big.js'
|
||||||
|
import MarketFee from '../MarketFee'
|
||||||
|
import Loading from '../Loading'
|
||||||
|
import Tooltip from '../Tooltip'
|
||||||
|
import OrderSideTabs from './OrderSideTabs'
|
||||||
|
import { ElementTitle } from '../styles'
|
||||||
|
import ButtonGroup from '../ButtonGroup'
|
||||||
|
import SlippageWarning from './SlippageWarning'
|
||||||
|
import Checkbox from '../Checkbox'
|
||||||
|
import { useViewport } from '../../hooks/useViewport'
|
||||||
|
import { breakpoints } from '../TradePageGrid'
|
||||||
|
|
||||||
|
interface AdvancedTradeFormProps {
|
||||||
|
initLeverage?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AdvancedTradeForm({
|
||||||
|
initLeverage,
|
||||||
|
}: AdvancedTradeFormProps) {
|
||||||
|
const set = useMangoStore((s) => s.set)
|
||||||
|
const { ipAllowed } = useIpAddress()
|
||||||
|
const connected = useMangoStore((s) => s.wallet.connected)
|
||||||
|
const actions = useMangoStore((s) => s.actions)
|
||||||
|
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
|
||||||
|
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
|
||||||
|
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||||
|
const mangoClient = useMangoStore((s) => s.connection.client)
|
||||||
|
const market = useMangoStore((s) => s.selectedMarket.current)
|
||||||
|
const isPerpMarket = market instanceof PerpMarket
|
||||||
|
const [reduceOnly, setReduceOnly] = useState(false)
|
||||||
|
const [spotMargin, setSpotMargin] = useState(false)
|
||||||
|
const [positionSizePercent, setPositionSizePercent] = useState('')
|
||||||
|
|
||||||
|
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||||
|
const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache)
|
||||||
|
const marketIndex = getMarketIndexBySymbol(
|
||||||
|
groupConfig,
|
||||||
|
marketConfig.baseSymbol
|
||||||
|
)
|
||||||
|
const { width } = useViewport()
|
||||||
|
const isMobile = width ? width < breakpoints.sm : false
|
||||||
|
|
||||||
|
const {
|
||||||
|
side,
|
||||||
|
baseSize,
|
||||||
|
quoteSize,
|
||||||
|
price,
|
||||||
|
tradeType,
|
||||||
|
triggerPrice,
|
||||||
|
triggerCondition,
|
||||||
|
} = useMangoStore((s) => s.tradeForm)
|
||||||
|
const isLimitOrder = ['Limit', 'Stop Limit', 'Take Profit Limit'].includes(
|
||||||
|
tradeType
|
||||||
|
)
|
||||||
|
const isMarketOrder = ['Market', 'Stop Loss', 'Take Profit'].includes(
|
||||||
|
tradeType
|
||||||
|
)
|
||||||
|
const isTriggerOrder = [
|
||||||
|
'Stop Loss',
|
||||||
|
'Stop Limit',
|
||||||
|
'Take Profit',
|
||||||
|
'Take Profit Limit',
|
||||||
|
].includes(tradeType)
|
||||||
|
|
||||||
|
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
|
||||||
|
useEffect(
|
||||||
|
() =>
|
||||||
|
useMangoStore.subscribe(
|
||||||
|
// @ts-ignore
|
||||||
|
(orderBook) => (orderBookRef.current = orderBook),
|
||||||
|
(state) => state.selectedMarket.orderBook
|
||||||
|
),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tradeType === 'Market') {
|
||||||
|
set((s) => {
|
||||||
|
s.tradeForm.price = ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [tradeType, set])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let condition
|
||||||
|
switch (tradeType) {
|
||||||
|
case 'Stop Loss':
|
||||||
|
case 'Stop Limit':
|
||||||
|
condition = side == 'buy' ? 'above' : 'below'
|
||||||
|
break
|
||||||
|
case 'Take Profit':
|
||||||
|
case 'Take Profit Limit':
|
||||||
|
condition = side == 'buy' ? 'below' : 'above'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (condition) {
|
||||||
|
set((s) => {
|
||||||
|
s.tradeForm.triggerCondition = condition
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [set, tradeType, side])
|
||||||
|
|
||||||
|
const { max, deposits, borrows, spotMax } = useMemo(() => {
|
||||||
|
if (!mangoAccount) return { max: 0 }
|
||||||
|
const priceOrDefault = price
|
||||||
|
? I80F48.fromNumber(price)
|
||||||
|
: mangoGroup.getPrice(marketIndex, mangoCache)
|
||||||
|
|
||||||
|
const token =
|
||||||
|
side === 'buy'
|
||||||
|
? getTokenBySymbol(groupConfig, 'USDC')
|
||||||
|
: getTokenBySymbol(groupConfig, marketConfig.baseSymbol)
|
||||||
|
const tokenIndex = mangoGroup.getTokenIndex(token.mintKey)
|
||||||
|
|
||||||
|
const availableBalance = floorToDecimal(
|
||||||
|
nativeI80F48ToUi(
|
||||||
|
mangoAccount.getAvailableBalance(mangoGroup, mangoCache, tokenIndex),
|
||||||
|
token.decimals
|
||||||
|
).toNumber(),
|
||||||
|
token.decimals
|
||||||
|
)
|
||||||
|
|
||||||
|
const spotMax =
|
||||||
|
side === 'buy'
|
||||||
|
? availableBalance / priceOrDefault.toNumber()
|
||||||
|
: availableBalance
|
||||||
|
|
||||||
|
const {
|
||||||
|
max: maxQuote,
|
||||||
|
deposits,
|
||||||
|
borrows,
|
||||||
|
} = mangoAccount.getMaxLeverageForMarket(
|
||||||
|
mangoGroup,
|
||||||
|
mangoCache,
|
||||||
|
marketIndex,
|
||||||
|
market,
|
||||||
|
side,
|
||||||
|
priceOrDefault
|
||||||
|
)
|
||||||
|
|
||||||
|
if (maxQuote.toNumber() <= 0) return { max: 0 }
|
||||||
|
// multiply the maxQuote by a scaler value to account for
|
||||||
|
// srm fees or rounding issues in getMaxLeverageForMarket
|
||||||
|
const maxScaler = market instanceof PerpMarket ? 0.99 : 0.95
|
||||||
|
const scaledMax = price
|
||||||
|
? (maxQuote.toNumber() * maxScaler) / price
|
||||||
|
: (maxQuote.toNumber() * maxScaler) /
|
||||||
|
mangoGroup.getPrice(marketIndex, mangoCache).toNumber()
|
||||||
|
|
||||||
|
return { max: scaledMax, deposits, borrows, spotMax }
|
||||||
|
}, [mangoAccount, mangoGroup, mangoCache, marketIndex, market, side, price])
|
||||||
|
|
||||||
|
const onChangeSide = (side) => {
|
||||||
|
setPositionSizePercent('')
|
||||||
|
set((s) => {
|
||||||
|
s.tradeForm.side = side
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const setBaseSize = (baseSize) =>
|
||||||
|
set((s) => {
|
||||||
|
if (!Number.isNaN(parseFloat(baseSize))) {
|
||||||
|
s.tradeForm.baseSize = parseFloat(baseSize)
|
||||||
|
} else {
|
||||||
|
s.tradeForm.baseSize = baseSize
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const setQuoteSize = (quoteSize) =>
|
||||||
|
set((s) => {
|
||||||
|
if (!Number.isNaN(parseFloat(quoteSize))) {
|
||||||
|
s.tradeForm.quoteSize = parseFloat(quoteSize)
|
||||||
|
} else {
|
||||||
|
s.tradeForm.quoteSize = quoteSize
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const setPrice = (price) =>
|
||||||
|
set((s) => {
|
||||||
|
if (!Number.isNaN(parseFloat(price))) {
|
||||||
|
s.tradeForm.price = parseFloat(price)
|
||||||
|
} else {
|
||||||
|
s.tradeForm.price = price
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const setTradeType = (type) => {
|
||||||
|
set((s) => {
|
||||||
|
s.tradeForm.tradeType = type
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const setTriggerPrice = (price) => {
|
||||||
|
set((s) => {
|
||||||
|
if (!Number.isNaN(parseFloat(price))) {
|
||||||
|
s.tradeForm.triggerPrice = parseFloat(price)
|
||||||
|
} else {
|
||||||
|
s.tradeForm.triggerPrice = price
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (isMarketOrder) {
|
||||||
|
onSetPrice(price)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const markPriceRef = useRef(useMangoStore.getState().selectedMarket.markPrice)
|
||||||
|
const markPrice = markPriceRef.current
|
||||||
|
useEffect(
|
||||||
|
() =>
|
||||||
|
useMangoStore.subscribe(
|
||||||
|
(markPrice) => (markPriceRef.current = markPrice as number),
|
||||||
|
(state) => state.selectedMarket.markPrice
|
||||||
|
),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
let minOrderSize = '0'
|
||||||
|
if (market instanceof Market && market.minOrderSize) {
|
||||||
|
minOrderSize = market.minOrderSize.toString()
|
||||||
|
} else if (market instanceof PerpMarket) {
|
||||||
|
const baseDecimals = getTokenBySymbol(
|
||||||
|
groupConfig,
|
||||||
|
marketConfig.baseSymbol
|
||||||
|
).decimals
|
||||||
|
minOrderSize = new Big(market.baseLotSize)
|
||||||
|
.div(new Big(10).pow(baseDecimals))
|
||||||
|
.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
const sizeDecimalCount = getDecimalCount(minOrderSize)
|
||||||
|
|
||||||
|
let tickSize = 1
|
||||||
|
if (market instanceof Market) {
|
||||||
|
tickSize = market.tickSize
|
||||||
|
} else if (isPerpMarket) {
|
||||||
|
const baseDecimals = getTokenBySymbol(
|
||||||
|
groupConfig,
|
||||||
|
marketConfig.baseSymbol
|
||||||
|
).decimals
|
||||||
|
const quoteDecimals = getTokenBySymbol(
|
||||||
|
groupConfig,
|
||||||
|
groupConfig.quoteSymbol
|
||||||
|
).decimals
|
||||||
|
|
||||||
|
const nativeToUi = new Big(10).pow(baseDecimals - quoteDecimals)
|
||||||
|
const lotsToNative = new Big(market.quoteLotSize).div(
|
||||||
|
new Big(market.baseLotSize)
|
||||||
|
)
|
||||||
|
tickSize = lotsToNative.mul(nativeToUi).toNumber()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSetPrice = (price: number | '') => {
|
||||||
|
setPrice(price)
|
||||||
|
if (!price) return
|
||||||
|
if (baseSize) {
|
||||||
|
onSetBaseSize(baseSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSetBaseSize = (baseSize: number | '') => {
|
||||||
|
const { price } = useMangoStore.getState().tradeForm
|
||||||
|
setBaseSize(baseSize)
|
||||||
|
if (!baseSize) {
|
||||||
|
setQuoteSize('')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const usePrice = Number(price) || markPrice
|
||||||
|
if (!usePrice) {
|
||||||
|
setQuoteSize('')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const rawQuoteSize = baseSize * usePrice
|
||||||
|
setQuoteSize(rawQuoteSize.toFixed(6))
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSetQuoteSize = (quoteSize: number | '') => {
|
||||||
|
setQuoteSize(quoteSize)
|
||||||
|
if (!quoteSize) {
|
||||||
|
setBaseSize('')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Number(price) && isLimitOrder) {
|
||||||
|
setBaseSize('')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const usePrice = Number(price) || markPrice
|
||||||
|
const rawBaseSize = quoteSize / usePrice
|
||||||
|
const baseSize = quoteSize && floorToDecimal(rawBaseSize, sizeDecimalCount)
|
||||||
|
setBaseSize(baseSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onTradeTypeChange = (tradeType) => {
|
||||||
|
setTradeType(tradeType)
|
||||||
|
if (['Market', 'Stop Loss', 'Take Profit'].includes(tradeType)) {
|
||||||
|
setIoc(true)
|
||||||
|
if (isTriggerOrder) {
|
||||||
|
setPrice(triggerPrice)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const priceOnBook = side === 'buy' ? orderbook?.asks : orderbook?.bids
|
||||||
|
if (priceOnBook && priceOnBook.length > 0 && priceOnBook[0].length > 0) {
|
||||||
|
setPrice(priceOnBook[0][0])
|
||||||
|
}
|
||||||
|
setIoc(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const postOnChange = (checked) => {
|
||||||
|
if (checked) {
|
||||||
|
setIoc(false)
|
||||||
|
}
|
||||||
|
setPostOnly(checked)
|
||||||
|
}
|
||||||
|
const iocOnChange = (checked) => {
|
||||||
|
if (checked) {
|
||||||
|
setPostOnly(false)
|
||||||
|
}
|
||||||
|
setIoc(checked)
|
||||||
|
}
|
||||||
|
const reduceOnChange = (checked) => {
|
||||||
|
if (checked) {
|
||||||
|
setReduceOnly(false)
|
||||||
|
}
|
||||||
|
setReduceOnly(checked)
|
||||||
|
}
|
||||||
|
const marginOnChange = (checked) => {
|
||||||
|
setPositionSizePercent('')
|
||||||
|
setSpotMargin(checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSetPositionSize = (percent) => {
|
||||||
|
setPositionSizePercent(percent)
|
||||||
|
const baseSizeMax =
|
||||||
|
spotMargin || marketConfig.kind === 'perp' ? max : spotMax
|
||||||
|
const baseSize = baseSizeMax * (parseInt(percent) / 100)
|
||||||
|
const step = parseFloat(minOrderSize)
|
||||||
|
const roundedSize = (Math.round(baseSize / step) * step).toFixed(
|
||||||
|
sizeDecimalCount
|
||||||
|
)
|
||||||
|
setBaseSize(parseFloat(roundedSize))
|
||||||
|
const usePrice = Number(price) || markPrice
|
||||||
|
if (!usePrice) {
|
||||||
|
setQuoteSize('')
|
||||||
|
}
|
||||||
|
const rawQuoteSize = parseFloat(roundedSize) * usePrice
|
||||||
|
setQuoteSize(rawQuoteSize.toFixed(6))
|
||||||
|
}
|
||||||
|
|
||||||
|
const percentToClose = (size, total) => {
|
||||||
|
return (size / total) * 100
|
||||||
|
}
|
||||||
|
|
||||||
|
const roundedDeposits = parseFloat(deposits?.toFixed(sizeDecimalCount))
|
||||||
|
const roundedBorrows = parseFloat(borrows?.toFixed(sizeDecimalCount))
|
||||||
|
|
||||||
|
const closeDepositString =
|
||||||
|
percentToClose(baseSize, roundedDeposits) > 100
|
||||||
|
? `100% close position and open a ${(+baseSize - roundedDeposits).toFixed(
|
||||||
|
sizeDecimalCount
|
||||||
|
)} ${marketConfig.baseSymbol} short`
|
||||||
|
: `${percentToClose(baseSize, roundedDeposits).toFixed(
|
||||||
|
0
|
||||||
|
)}% close position`
|
||||||
|
|
||||||
|
const closeBorrowString =
|
||||||
|
percentToClose(baseSize, roundedBorrows) > 100
|
||||||
|
? `100% close position and open a ${(+baseSize - roundedBorrows).toFixed(
|
||||||
|
sizeDecimalCount
|
||||||
|
)} ${marketConfig.baseSymbol} short`
|
||||||
|
: `${percentToClose(baseSize, roundedBorrows).toFixed(0)}% close position`
|
||||||
|
|
||||||
|
async function onSubmit() {
|
||||||
|
if (!price && isLimitOrder) {
|
||||||
|
notify({
|
||||||
|
title: 'Missing price',
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
} else if (!baseSize) {
|
||||||
|
notify({
|
||||||
|
title: 'Missing size',
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
} else if (!triggerPrice && isTriggerOrder) {
|
||||||
|
notify({
|
||||||
|
title: 'Missing trigger price',
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const mangoAccount = useMangoStore.getState().selectedMangoAccount.current
|
||||||
|
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
|
||||||
|
const { askInfo, bidInfo } = useMangoStore.getState().selectedMarket
|
||||||
|
const wallet = useMangoStore.getState().wallet.current
|
||||||
|
|
||||||
|
if (!wallet || !mangoGroup || !mangoAccount || !market) return
|
||||||
|
setSubmitting(true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const orderPrice = calculateTradePrice(
|
||||||
|
tradeType,
|
||||||
|
orderbook,
|
||||||
|
baseSize,
|
||||||
|
side,
|
||||||
|
price,
|
||||||
|
triggerPrice
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!orderPrice) {
|
||||||
|
notify({
|
||||||
|
title: 'Price not available',
|
||||||
|
description: 'Please try again',
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderType = ioc ? 'ioc' : postOnly ? 'postOnly' : 'limit'
|
||||||
|
let txid
|
||||||
|
if (market instanceof Market) {
|
||||||
|
txid = await mangoClient.placeSpotOrder2(
|
||||||
|
mangoGroup,
|
||||||
|
mangoAccount,
|
||||||
|
mangoGroup.mangoCache,
|
||||||
|
market,
|
||||||
|
wallet,
|
||||||
|
side,
|
||||||
|
orderPrice,
|
||||||
|
baseSize,
|
||||||
|
orderType
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
if (isTriggerOrder) {
|
||||||
|
txid = await mangoClient.addPerpTriggerOrder(
|
||||||
|
mangoGroup,
|
||||||
|
mangoAccount,
|
||||||
|
market,
|
||||||
|
wallet,
|
||||||
|
isMarketOrder ? 'market' : orderType,
|
||||||
|
side,
|
||||||
|
orderPrice,
|
||||||
|
baseSize,
|
||||||
|
triggerCondition,
|
||||||
|
Number(triggerPrice)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
txid = await mangoClient.placePerpOrder(
|
||||||
|
mangoGroup,
|
||||||
|
mangoAccount,
|
||||||
|
mangoGroup.mangoCache,
|
||||||
|
market,
|
||||||
|
wallet,
|
||||||
|
side,
|
||||||
|
orderPrice,
|
||||||
|
baseSize,
|
||||||
|
isMarketOrder ? 'market' : orderType,
|
||||||
|
Date.now(),
|
||||||
|
side === 'buy' ? askInfo : bidInfo, // book side used for ConsumeEvents
|
||||||
|
reduceOnly
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notify({ title: 'Successfully placed trade', txid })
|
||||||
|
setPrice('')
|
||||||
|
onSetBaseSize('')
|
||||||
|
} catch (e) {
|
||||||
|
notify({
|
||||||
|
title: 'Error placing order',
|
||||||
|
description: e.message,
|
||||||
|
txid: e.txid,
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
console.error(e)
|
||||||
|
} finally {
|
||||||
|
// TODO: should be removed, main issue are newly created OO accounts
|
||||||
|
// await sleep(600)
|
||||||
|
actions.reloadMangoAccount()
|
||||||
|
actions.loadMarketFills()
|
||||||
|
setSubmitting(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const roundedMax = (
|
||||||
|
Math.round(max / parseFloat(minOrderSize)) * parseFloat(minOrderSize)
|
||||||
|
).toFixed(sizeDecimalCount)
|
||||||
|
|
||||||
|
const sizeTooLarge =
|
||||||
|
spotMargin || marketConfig.kind === 'perp'
|
||||||
|
? baseSize > roundedMax
|
||||||
|
: baseSize > spotMax
|
||||||
|
|
||||||
|
const disabledTradeButton =
|
||||||
|
(!price && isLimitOrder) ||
|
||||||
|
!baseSize ||
|
||||||
|
!connected ||
|
||||||
|
submitting ||
|
||||||
|
!mangoAccount ||
|
||||||
|
sizeTooLarge
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col h-full">
|
||||||
|
<ElementTitle className="hidden md:flex">
|
||||||
|
{marketConfig.name}
|
||||||
|
<span className="border border-th-primary ml-2 px-1 py-0.5 rounded text-xs text-th-primary">
|
||||||
|
{initLeverage}x
|
||||||
|
</span>
|
||||||
|
</ElementTitle>
|
||||||
|
<OrderSideTabs onChange={onChangeSide} side={side} />
|
||||||
|
<div className="grid grid-cols-12 md:gap-2 text-left">
|
||||||
|
<div className="col-span-12 md:col-span-2 flex items-center">
|
||||||
|
<label className="text-xxs md:text-xs text-th-fgd-3">Type</label>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-12 md:col-span-10 pb-2 md:pb-0">
|
||||||
|
<TradeType
|
||||||
|
onChange={onTradeTypeChange}
|
||||||
|
value={tradeType}
|
||||||
|
offerTriggers={isPerpMarket}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-12 md:col-span-2 flex items-center">
|
||||||
|
<label className="text-xxs md:text-xs text-th-fgd-3">Price</label>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-12 md:col-span-10 pb-2 md:pb-0">
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step={tickSize}
|
||||||
|
onChange={(e) => onSetPrice(e.target.value)}
|
||||||
|
value={price}
|
||||||
|
disabled={isMarketOrder}
|
||||||
|
placeholder={tradeType === 'Market' ? markPrice : null}
|
||||||
|
prefix={
|
||||||
|
<img
|
||||||
|
src={`/assets/icons/${groupConfig.quoteSymbol.toLowerCase()}.svg`}
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{isTriggerOrder && (
|
||||||
|
<>
|
||||||
|
<div className="col-span-12 md:col-span-2 flex items-center">
|
||||||
|
<label className="text-xxs md:text-xs text-th-fgd-3">
|
||||||
|
Trigger Price
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-12 md:col-span-10 pb-2 md:pb-0">
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step={tickSize}
|
||||||
|
onChange={(e) => setTriggerPrice(e.target.value)}
|
||||||
|
value={triggerPrice}
|
||||||
|
prefix={
|
||||||
|
<img
|
||||||
|
src={`/assets/icons/${groupConfig.quoteSymbol.toLowerCase()}.svg`}
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<div className="col-span-12 md:col-span-2 flex items-center">
|
||||||
|
<label className="text-xxs md:text-xs text-th-fgd-3">Size</label>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-12 md:col-span-10 pb-2 md:pb-0">
|
||||||
|
<Input.Group className="-mb-1">
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step={minOrderSize}
|
||||||
|
onChange={(e) => onSetBaseSize(e.target.value)}
|
||||||
|
value={baseSize}
|
||||||
|
wrapperClassName="mr-0.5 w-1/2"
|
||||||
|
prefix={
|
||||||
|
<img
|
||||||
|
src={`/assets/icons/${marketConfig.baseSymbol.toLowerCase()}.svg`}
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step={minOrderSize}
|
||||||
|
onChange={(e) => onSetQuoteSize(e.target.value)}
|
||||||
|
value={quoteSize}
|
||||||
|
wrapperClassName="ml-0.5 w-1/2"
|
||||||
|
prefix={
|
||||||
|
<img
|
||||||
|
src={`/assets/icons/${groupConfig.quoteSymbol.toLowerCase()}.svg`}
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Input.Group>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-12 md:col-span-10 md:col-start-3">
|
||||||
|
<ButtonGroup
|
||||||
|
activeValue={positionSizePercent}
|
||||||
|
onChange={(p) => handleSetPositionSize(p)}
|
||||||
|
unit="%"
|
||||||
|
values={
|
||||||
|
isMobile
|
||||||
|
? ['10', '25', '50', '75']
|
||||||
|
: ['10', '25', '50', '75', '100']
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{marketConfig.kind === 'perp' ? (
|
||||||
|
side === 'sell' ? (
|
||||||
|
roundedDeposits > 0 ? (
|
||||||
|
<div className="text-th-fgd-3 text-xs tracking-normal mt-2">
|
||||||
|
<span>{closeDepositString}</span>
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
) : roundedBorrows > 0 ? (
|
||||||
|
<div className="text-th-fgd-3 text-xs tracking-normal mt-2">
|
||||||
|
<span>{closeBorrowString}</span>
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
) : null}
|
||||||
|
<div className="sm:flex">
|
||||||
|
{isLimitOrder ? (
|
||||||
|
<div className="flex">
|
||||||
|
<div className="mr-4 mt-4">
|
||||||
|
<Tooltip
|
||||||
|
className="hidden md:block"
|
||||||
|
delay={250}
|
||||||
|
placement="left"
|
||||||
|
content="Post only orders are guaranteed to be the maker order or else it will be canceled."
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={postOnly}
|
||||||
|
onChange={(e) => postOnChange(e.target.checked)}
|
||||||
|
>
|
||||||
|
POST
|
||||||
|
</Checkbox>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div className="mr-4 mt-4">
|
||||||
|
<Tooltip
|
||||||
|
className="hidden md:block"
|
||||||
|
delay={250}
|
||||||
|
placement="left"
|
||||||
|
content="Immediate or cancel orders are guaranteed to be the taker or it will be canceled."
|
||||||
|
>
|
||||||
|
<div className="flex items-center text-th-fgd-3 text-xs">
|
||||||
|
<Checkbox
|
||||||
|
checked={ioc}
|
||||||
|
onChange={(e) => iocOnChange(e.target.checked)}
|
||||||
|
>
|
||||||
|
IOC
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{marketConfig.kind === 'perp' && !isTriggerOrder ? (
|
||||||
|
<div className="mt-4">
|
||||||
|
<Tooltip
|
||||||
|
className="hidden md:block"
|
||||||
|
delay={250}
|
||||||
|
placement="left"
|
||||||
|
content="Reduce only orders will only reduce your overall position."
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={reduceOnly}
|
||||||
|
onChange={(e) => reduceOnChange(e.target.checked)}
|
||||||
|
>
|
||||||
|
Reduce Only
|
||||||
|
</Checkbox>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{marketConfig.kind === 'spot' ? (
|
||||||
|
<div className="mt-4">
|
||||||
|
<Tooltip
|
||||||
|
delay={250}
|
||||||
|
placement="left"
|
||||||
|
content="Enable spot margin for this trade"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={spotMargin}
|
||||||
|
onChange={(e) => marginOnChange(e.target.checked)}
|
||||||
|
>
|
||||||
|
Margin
|
||||||
|
</Checkbox>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div className={`flex pt-4`}>
|
||||||
|
{ipAllowed ? (
|
||||||
|
<Button
|
||||||
|
disabled={disabledTradeButton}
|
||||||
|
onClick={onSubmit}
|
||||||
|
className={`bg-th-bkg-2 border ${
|
||||||
|
!disabledTradeButton
|
||||||
|
? side === 'buy'
|
||||||
|
? 'border-th-green hover:border-th-green-dark text-th-green hover:bg-th-green-dark'
|
||||||
|
: 'border-th-red hover:border-th-red-dark text-th-red hover:bg-th-red-dark'
|
||||||
|
: 'border border-th-bkg-4'
|
||||||
|
} hover:text-th-fgd-1 flex-grow`}
|
||||||
|
>
|
||||||
|
{submitting ? (
|
||||||
|
<div className="w-full">
|
||||||
|
<Loading className="mx-auto" />
|
||||||
|
</div>
|
||||||
|
) : sizeTooLarge ? (
|
||||||
|
'Size Too Large'
|
||||||
|
) : side === 'buy' ? (
|
||||||
|
market instanceof PerpMarket ? (
|
||||||
|
<>
|
||||||
|
{baseSize > 0 ? 'Long ' + baseSize : 'Long '}{' '}
|
||||||
|
<span className="whitespace-nowrap">
|
||||||
|
{marketConfig.name}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
`${baseSize > 0 ? 'Buy ' + baseSize : 'Buy '} ${
|
||||||
|
marketConfig.baseSymbol
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
) : market instanceof PerpMarket ? (
|
||||||
|
sizeTooLarge ? (
|
||||||
|
'Size Too Large'
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{baseSize > 0 ? 'Short ' + baseSize : 'Short '}{' '}
|
||||||
|
<span className="whitespace-nowrap">
|
||||||
|
{marketConfig.name}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
`${baseSize > 0 ? 'Sell ' + baseSize : 'Sell '} ${
|
||||||
|
marketConfig.baseSymbol
|
||||||
|
}`
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button disabled className="flex-grow">
|
||||||
|
<span>Country Not Allowed</span>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{tradeType === 'Market' ? (
|
||||||
|
<div className="col-span-12 md:col-span-10 md:col-start-3 pt-2">
|
||||||
|
<SlippageWarning slippage={0.2} />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div className="flex text-xs text-th-fgd-4 pt-4">
|
||||||
|
<MarketFee />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,19 +1,21 @@
|
||||||
import { FunctionComponent } from 'react'
|
import { FunctionComponent } from 'react'
|
||||||
import { PerpMarket } from '@blockworks-foundation/mango-client'
|
import { PerpMarket } from '@blockworks-foundation/mango-client'
|
||||||
import useMangoStore from '../stores/useMangoStore'
|
import useMangoStore from '../../stores/useMangoStore'
|
||||||
|
|
||||||
interface OrderSideTabsProps {
|
interface OrderSideTabsProps {
|
||||||
|
isSimpleForm?: boolean
|
||||||
onChange: (x) => void
|
onChange: (x) => void
|
||||||
side: string
|
side: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const OrderSideTabs: FunctionComponent<OrderSideTabsProps> = ({
|
const OrderSideTabs: FunctionComponent<OrderSideTabsProps> = ({
|
||||||
side,
|
isSimpleForm,
|
||||||
onChange,
|
onChange,
|
||||||
|
side,
|
||||||
}) => {
|
}) => {
|
||||||
const market = useMangoStore((s) => s.selectedMarket.current)
|
const market = useMangoStore((s) => s.selectedMarket.current)
|
||||||
return (
|
return (
|
||||||
<div className={`border-b border-th-fgd-4 mb-4 relative`}>
|
<div className={`border-b border-th-fgd-4 mb-2 md:mb-4 relative`}>
|
||||||
<div
|
<div
|
||||||
className={`absolute ${
|
className={`absolute ${
|
||||||
side === 'buy'
|
side === 'buy'
|
||||||
|
@ -24,7 +26,7 @@ const OrderSideTabs: FunctionComponent<OrderSideTabsProps> = ({
|
||||||
<nav className="-mb-px flex" aria-label="Tabs">
|
<nav className="-mb-px flex" aria-label="Tabs">
|
||||||
<button
|
<button
|
||||||
onClick={() => onChange('buy')}
|
onClick={() => onChange('buy')}
|
||||||
className={`cursor-pointer default-transition flex font-semibold items-center justify-center p-2 relative text-base w-1/2 whitespace-nowrap hover:opacity-100
|
className={`cursor-pointer default-transition flex font-semibold items-center justify-center pb-2 md:py-2 relative text-base w-1/2 whitespace-nowrap hover:opacity-100
|
||||||
${
|
${
|
||||||
side === 'buy'
|
side === 'buy'
|
||||||
? `text-th-green`
|
? `text-th-green`
|
||||||
|
@ -32,11 +34,11 @@ const OrderSideTabs: FunctionComponent<OrderSideTabsProps> = ({
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{market instanceof PerpMarket ? 'Long' : 'Buy'}
|
{market instanceof PerpMarket && isSimpleForm ? 'Long' : 'Buy'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => onChange('sell')}
|
onClick={() => onChange('sell')}
|
||||||
className={`cursor-pointer default-transition flex font-semibold items-center justify-center p-2 relative text-base w-1/2 whitespace-nowrap hover:opacity-100
|
className={`cursor-pointer default-transition flex font-semibold items-center justify-center pb-2 relative text-base w-1/2 whitespace-nowrap hover:opacity-100
|
||||||
${
|
${
|
||||||
side === 'sell'
|
side === 'sell'
|
||||||
? `text-th-red`
|
? `text-th-red`
|
||||||
|
@ -44,7 +46,7 @@ const OrderSideTabs: FunctionComponent<OrderSideTabsProps> = ({
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{market instanceof PerpMarket ? 'Short' : 'Sell'}
|
{market instanceof PerpMarket && isSimpleForm ? 'Short' : 'Sell'}
|
||||||
</button>
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
|
@ -1,30 +1,27 @@
|
||||||
import { useState, useEffect, useRef, useMemo } from 'react'
|
import { useState, useEffect, useRef, useMemo } from 'react'
|
||||||
import useIpAddress from '../hooks/useIpAddress'
|
import useIpAddress from '../../hooks/useIpAddress'
|
||||||
import {
|
import {
|
||||||
getTokenBySymbol,
|
getTokenBySymbol,
|
||||||
getMarketIndexBySymbol,
|
getMarketIndexBySymbol,
|
||||||
I80F48,
|
I80F48,
|
||||||
PerpMarket,
|
PerpMarket,
|
||||||
} from '@blockworks-foundation/mango-client'
|
} from '@blockworks-foundation/mango-client'
|
||||||
import { notify } from '../utils/notifications'
|
import { notify } from '../../utils/notifications'
|
||||||
import { calculateTradePrice, getDecimalCount, sleep } from '../utils'
|
import { calculateTradePrice, getDecimalCount, sleep } from '../../utils'
|
||||||
import { floorToDecimal } from '../utils/index'
|
import { floorToDecimal } from '../../utils/index'
|
||||||
import useMangoStore from '../stores/useMangoStore'
|
import useMangoStore from '../../stores/useMangoStore'
|
||||||
import Button from './Button'
|
import Button from '../Button'
|
||||||
import TradeType from './TradeType'
|
import Input from '../Input'
|
||||||
import Input from './Input'
|
|
||||||
import Switch from './Switch'
|
|
||||||
import { Market } from '@project-serum/serum'
|
import { Market } from '@project-serum/serum'
|
||||||
import Big from 'big.js'
|
import Big from 'big.js'
|
||||||
import MarketFee from './MarketFee'
|
import MarketFee from '../MarketFee'
|
||||||
import LeverageSlider from './LeverageSlider'
|
import Loading from '../Loading'
|
||||||
import Loading from './Loading'
|
import { ElementTitle } from '../styles'
|
||||||
import { useViewport } from '../hooks/useViewport'
|
import ButtonGroup from '../ButtonGroup'
|
||||||
import { breakpoints } from './TradePageGrid'
|
import Checkbox from '../Checkbox'
|
||||||
import { ElementTitle } from './styles'
|
|
||||||
import ButtonGroup from './ButtonGroup'
|
|
||||||
import Checkbox from './Checkbox'
|
|
||||||
import OrderSideTabs from './OrderSideTabs'
|
import OrderSideTabs from './OrderSideTabs'
|
||||||
|
import Tooltip from '../Tooltip'
|
||||||
|
import SlippageWarning from './SlippageWarning'
|
||||||
|
|
||||||
export default function SimpleTradeForm({ initLeverage }) {
|
export default function SimpleTradeForm({ initLeverage }) {
|
||||||
const set = useMangoStore((s) => s.set)
|
const set = useMangoStore((s) => s.set)
|
||||||
|
@ -42,10 +39,8 @@ export default function SimpleTradeForm({ initLeverage }) {
|
||||||
marketConfig.baseSymbol
|
marketConfig.baseSymbol
|
||||||
)
|
)
|
||||||
const market = useMangoStore((s) => s.selectedMarket.current)
|
const market = useMangoStore((s) => s.selectedMarket.current)
|
||||||
const { side, baseSize, quoteSize, price, stopPrice, tradeType } =
|
const { side, baseSize, quoteSize, price, triggerPrice, tradeType } =
|
||||||
useMangoStore((s) => s.tradeForm)
|
useMangoStore((s) => s.tradeForm)
|
||||||
const { width } = useViewport()
|
|
||||||
const isMobile = width ? width < breakpoints.sm : false
|
|
||||||
|
|
||||||
const [postOnly, setPostOnly] = useState(false)
|
const [postOnly, setPostOnly] = useState(false)
|
||||||
const [ioc, setIoc] = useState(false)
|
const [ioc, setIoc] = useState(false)
|
||||||
|
@ -54,6 +49,8 @@ export default function SimpleTradeForm({ initLeverage }) {
|
||||||
const [showStopForm, setShowStopForm] = useState(false)
|
const [showStopForm, setShowStopForm] = useState(false)
|
||||||
const [showTakeProfitForm, setShowTakeProfitForm] = useState(false)
|
const [showTakeProfitForm, setShowTakeProfitForm] = useState(false)
|
||||||
const [stopSizePercent, setStopSizePercent] = useState('5%')
|
const [stopSizePercent, setStopSizePercent] = useState('5%')
|
||||||
|
const [reduceOnly, setReduceOnly] = useState(false)
|
||||||
|
const [spotMargin, setSpotMargin] = useState(false)
|
||||||
|
|
||||||
const orderBookRef = useRef(useMangoStore.getState().selectedMarket.orderBook)
|
const orderBookRef = useRef(useMangoStore.getState().selectedMarket.orderBook)
|
||||||
const orderbook = orderBookRef.current
|
const orderbook = orderBookRef.current
|
||||||
|
@ -67,6 +64,12 @@ export default function SimpleTradeForm({ initLeverage }) {
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tradeType !== 'Market' && tradeType !== 'Limit') {
|
||||||
|
setTradeType('Limit')
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tradeType === 'Market') {
|
if (tradeType === 'Market') {
|
||||||
set((s) => {
|
set((s) => {
|
||||||
|
@ -110,9 +113,9 @@ export default function SimpleTradeForm({ initLeverage }) {
|
||||||
const setStopPrice = (price) =>
|
const setStopPrice = (price) =>
|
||||||
set((s) => {
|
set((s) => {
|
||||||
if (!Number.isNaN(parseFloat(price))) {
|
if (!Number.isNaN(parseFloat(price))) {
|
||||||
s.tradeForm.stopPrice = parseFloat(price)
|
s.tradeForm.triggerPrice = parseFloat(price)
|
||||||
} else {
|
} else {
|
||||||
s.tradeForm.stopPrice = price
|
s.tradeForm.triggerPrice = price
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -237,6 +240,12 @@ export default function SimpleTradeForm({ initLeverage }) {
|
||||||
}
|
}
|
||||||
setIoc(checked)
|
setIoc(checked)
|
||||||
}
|
}
|
||||||
|
const reduceOnChange = (checked) => {
|
||||||
|
if (checked) {
|
||||||
|
setReduceOnly(false)
|
||||||
|
}
|
||||||
|
setReduceOnly(checked)
|
||||||
|
}
|
||||||
|
|
||||||
async function onSubmit() {
|
async function onSubmit() {
|
||||||
if (!price && tradeType === 'Limit') {
|
if (!price && tradeType === 'Limit') {
|
||||||
|
@ -406,16 +415,16 @@ export default function SimpleTradeForm({ initLeverage }) {
|
||||||
(side === 'sell' && baseSize === roundedDeposits) ||
|
(side === 'sell' && baseSize === roundedDeposits) ||
|
||||||
(side === 'buy' && baseSize === roundedBorrows)
|
(side === 'buy' && baseSize === roundedBorrows)
|
||||||
|
|
||||||
return !isMobile ? (
|
return (
|
||||||
<div className={!connected ? 'fliter blur-sm' : 'flex flex-col h-full'}>
|
<div className="flex flex-col h-full">
|
||||||
<ElementTitle>
|
<ElementTitle>
|
||||||
{marketConfig.name}
|
{marketConfig.name}
|
||||||
<span className="border border-th-primary ml-2 px-1 py-0.5 rounded text-xs text-th-primary">
|
<span className="border border-th-primary ml-2 px-1 py-0.5 rounded text-xs text-th-primary">
|
||||||
{initLeverage}x
|
{initLeverage}x
|
||||||
</span>
|
</span>
|
||||||
</ElementTitle>
|
</ElementTitle>
|
||||||
<OrderSideTabs onChange={setSide} side={side} />
|
<OrderSideTabs isSimpleForm onChange={setSide} side={side} />
|
||||||
<div className="grid grid-cols-12 gap-2">
|
<div className="grid grid-cols-12 gap-2 text-left">
|
||||||
<div className="col-span-2 flex items-center">
|
<div className="col-span-2 flex items-center">
|
||||||
<label className="text-xs text-th-fgd-3">Type</label>
|
<label className="text-xs text-th-fgd-3">Type</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -437,7 +446,7 @@ export default function SimpleTradeForm({ initLeverage }) {
|
||||||
onChange={(e) => onSetPrice(e.target.value)}
|
onChange={(e) => onSetPrice(e.target.value)}
|
||||||
value={price}
|
value={price}
|
||||||
disabled={tradeType === 'Market'}
|
disabled={tradeType === 'Market'}
|
||||||
placeholder={tradeType === 'Market' ? 'Market Price' : null}
|
placeholder={tradeType === 'Market' ? markPrice : null}
|
||||||
prefix={
|
prefix={
|
||||||
<img
|
<img
|
||||||
src={`/assets/icons/${groupConfig.quoteSymbol.toLowerCase()}.svg`}
|
src={`/assets/icons/${groupConfig.quoteSymbol.toLowerCase()}.svg`}
|
||||||
|
@ -484,7 +493,7 @@ export default function SimpleTradeForm({ initLeverage }) {
|
||||||
/>
|
/>
|
||||||
</Input.Group>
|
</Input.Group>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-10 col-start-3">
|
<div className="col-span-10 col-start-3 pb-2">
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
activeValue={positionSizePercent}
|
activeValue={positionSizePercent}
|
||||||
onChange={(p) => handleSetPositionSize(p)}
|
onChange={(p) => handleSetPositionSize(p)}
|
||||||
|
@ -500,31 +509,33 @@ export default function SimpleTradeForm({ initLeverage }) {
|
||||||
<span>{roundedBorrows > 0 ? closeBorrowString : null}</span>
|
<span>{roundedBorrows > 0 ? closeBorrowString : null}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex items-center">
|
<div className="flex items-center space-x-1">
|
||||||
{!hideProfitStop ? (
|
{!hideProfitStop ? (
|
||||||
<div className="pr-4 pt-3">
|
<div
|
||||||
<label className="cursor-pointer flex items-center">
|
className={`${
|
||||||
<Checkbox
|
showStopForm ? 'bg-th-bkg-4' : 'bg-th-bkg-3'
|
||||||
checked={showStopForm}
|
} mt-2 p-2 rounded-md w-1/2`}
|
||||||
onChange={(e) => setShowStopForm(e.target.checked)}
|
>
|
||||||
/>
|
<Checkbox
|
||||||
<span className="ml-2 text-xs text-th-fgd-3">
|
checked={showStopForm}
|
||||||
Set Stop Loss
|
onChange={(e) => setShowStopForm(e.target.checked)}
|
||||||
</span>
|
>
|
||||||
</label>
|
Set Stop Loss
|
||||||
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{!hideProfitStop ? (
|
{!hideProfitStop ? (
|
||||||
<div className="pt-3">
|
<div
|
||||||
<label className="cursor-pointer flex items-center">
|
className={`${
|
||||||
<Checkbox
|
showTakeProfitForm ? 'bg-th-bkg-4' : 'bg-th-bkg-3'
|
||||||
checked={showTakeProfitForm}
|
} mt-2 p-2 rounded-md w-1/2`}
|
||||||
onChange={(e) => setShowTakeProfitForm(e.target.checked)}
|
>
|
||||||
/>
|
<Checkbox
|
||||||
<span className="ml-2 text-xs text-th-fgd-3">
|
checked={showTakeProfitForm}
|
||||||
Set Take Profit
|
onChange={(e) => setShowTakeProfitForm(e.target.checked)}
|
||||||
</span>
|
>
|
||||||
</label>
|
Set Take Profit
|
||||||
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
@ -540,7 +551,7 @@ export default function SimpleTradeForm({ initLeverage }) {
|
||||||
min="0"
|
min="0"
|
||||||
step={tickSize}
|
step={tickSize}
|
||||||
onChange={(e) => setStopPrice(e.target.value)}
|
onChange={(e) => setStopPrice(e.target.value)}
|
||||||
value={stopPrice}
|
value={triggerPrice}
|
||||||
prefix={
|
prefix={
|
||||||
<img
|
<img
|
||||||
src={`/assets/icons/${groupConfig.quoteSymbol.toLowerCase()}.svg`}
|
src={`/assets/icons/${groupConfig.quoteSymbol.toLowerCase()}.svg`}
|
||||||
|
@ -550,7 +561,7 @@ export default function SimpleTradeForm({ initLeverage }) {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-10 col-start-3">
|
<div className="col-span-10 col-start-3 pb-2">
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
activeValue={stopSizePercent}
|
activeValue={stopSizePercent}
|
||||||
onChange={(p) => setStopSizePercent(p)}
|
onChange={(p) => setStopSizePercent(p)}
|
||||||
|
@ -563,7 +574,7 @@ export default function SimpleTradeForm({ initLeverage }) {
|
||||||
<>
|
<>
|
||||||
<div className="col-span-2 flex items-center">
|
<div className="col-span-2 flex items-center">
|
||||||
<label className="text-left text-xs text-th-fgd-3">
|
<label className="text-left text-xs text-th-fgd-3">
|
||||||
Take Profit Price
|
Profit Price
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-10 -mb-1">
|
<div className="col-span-10 -mb-1">
|
||||||
|
@ -572,7 +583,7 @@ export default function SimpleTradeForm({ initLeverage }) {
|
||||||
min="0"
|
min="0"
|
||||||
step={tickSize}
|
step={tickSize}
|
||||||
onChange={(e) => setStopPrice(e.target.value)}
|
onChange={(e) => setStopPrice(e.target.value)}
|
||||||
value={stopPrice}
|
value={triggerPrice}
|
||||||
prefix={
|
prefix={
|
||||||
<img
|
<img
|
||||||
src={`/assets/icons/${groupConfig.quoteSymbol.toLowerCase()}.svg`}
|
src={`/assets/icons/${groupConfig.quoteSymbol.toLowerCase()}.svg`}
|
||||||
|
@ -582,7 +593,7 @@ export default function SimpleTradeForm({ initLeverage }) {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-10 col-start-3">
|
<div className="col-span-10 col-start-3 pb-2">
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
activeValue={stopSizePercent}
|
activeValue={stopSizePercent}
|
||||||
onChange={(p) => setStopSizePercent(p)}
|
onChange={(p) => setStopSizePercent(p)}
|
||||||
|
@ -591,7 +602,71 @@ export default function SimpleTradeForm({ initLeverage }) {
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
<div className={`col-span-10 col-start-3 flex py-3`}>
|
<div className="col-span-10 col-start-3 flex">
|
||||||
|
{tradeType === 'Limit' ? (
|
||||||
|
<>
|
||||||
|
<div className="mr-4">
|
||||||
|
<Tooltip
|
||||||
|
delay={250}
|
||||||
|
placement="left"
|
||||||
|
content="Post only orders are guaranteed to be the maker order or else it will be canceled."
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={postOnly}
|
||||||
|
onChange={(e) => postOnChange(e.target.checked)}
|
||||||
|
>
|
||||||
|
POST
|
||||||
|
</Checkbox>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div className="mr-4">
|
||||||
|
<Tooltip
|
||||||
|
delay={250}
|
||||||
|
placement="left"
|
||||||
|
content="Immediate or cancel orders are guaranteed to be the taker or it will be canceled."
|
||||||
|
>
|
||||||
|
<div className="flex items-center text-th-fgd-3 text-xs">
|
||||||
|
<Checkbox
|
||||||
|
checked={ioc}
|
||||||
|
onChange={(e) => iocOnChange(e.target.checked)}
|
||||||
|
>
|
||||||
|
IOC
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
{marketConfig.kind === 'perp' ? (
|
||||||
|
<Tooltip
|
||||||
|
delay={250}
|
||||||
|
placement="left"
|
||||||
|
content="Reduce only orders will only reduce your overall position."
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={reduceOnly}
|
||||||
|
onChange={(e) => reduceOnChange(e.target.checked)}
|
||||||
|
>
|
||||||
|
Reduce Only
|
||||||
|
</Checkbox>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
|
{marketConfig.kind === 'spot' ? (
|
||||||
|
<Tooltip
|
||||||
|
delay={250}
|
||||||
|
placement="left"
|
||||||
|
content="Enable spot margin for this trade"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={spotMargin}
|
||||||
|
onChange={(e) => setSpotMargin(e.target.checked)}
|
||||||
|
>
|
||||||
|
Margin
|
||||||
|
</Checkbox>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div className={`col-span-10 col-start-3 flex pt-2`}>
|
||||||
{ipAllowed ? (
|
{ipAllowed ? (
|
||||||
<Button
|
<Button
|
||||||
disabled={disabledTradeButton}
|
disabled={disabledTradeButton}
|
||||||
|
@ -632,214 +707,14 @@ export default function SimpleTradeForm({ initLeverage }) {
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!showStopForm ? (
|
{tradeType === 'Market' ? (
|
||||||
<div className="col-span-10 col-start-3 flex text-xs text-th-fgd-4">
|
<div className="col-span-10 col-start-3">
|
||||||
<MarketFee />
|
<SlippageWarning slippage={0.2} />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
<div className="col-span-10 col-start-3 flex pt-2 text-xs text-th-fgd-4">
|
||||||
{/* <LeverageSlider
|
<MarketFee />
|
||||||
onChange={(e) => onSetBaseSize(e)}
|
|
||||||
value={baseSize ? baseSize : 0}
|
|
||||||
step={parseFloat(minOrderSize)}
|
|
||||||
disabled={false}
|
|
||||||
side={side}
|
|
||||||
decimalCount={sizeDecimalCount}
|
|
||||||
price={calculateTradePrice(
|
|
||||||
tradeType,
|
|
||||||
orderbook,
|
|
||||||
baseSize ? baseSize : 0,
|
|
||||||
side,
|
|
||||||
price
|
|
||||||
)}
|
|
||||||
/> */}
|
|
||||||
{/* {tradeType !== 'Market' ? (
|
|
||||||
<div className="flex mt-2">
|
|
||||||
<Switch checked={postOnly} onChange={postOnChange}>
|
|
||||||
POST
|
|
||||||
</Switch>
|
|
||||||
<div className="ml-4">
|
|
||||||
<Switch checked={ioc} onChange={iocOnChange}>
|
|
||||||
IOC
|
|
||||||
</Switch>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : null} */}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex flex-col h-full">
|
|
||||||
<div className={`flex pb-3 text-base text-th-fgd-4`}>
|
|
||||||
<button
|
|
||||||
onClick={() => setSide('buy')}
|
|
||||||
className={`flex-1 outline-none focus:outline-none`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`hover:text-th-green pb-1 transition-colors duration-500
|
|
||||||
${
|
|
||||||
side === 'buy'
|
|
||||||
? `text-th-green hover:text-th-green border-b-2 border-th-green`
|
|
||||||
: undefined
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{market instanceof PerpMarket ? 'Long' : 'Buy'}
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setSide('sell')}
|
|
||||||
className={`flex-1 outline-none focus:outline-none`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`hover:text-th-red pb-1 transition-colors duration-500
|
|
||||||
${
|
|
||||||
side === 'sell'
|
|
||||||
? `text-th-red hover:text-th-red border-b-2 border-th-red`
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
{market instanceof PerpMarket ? 'Short' : 'Sell'}
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="pb-3">
|
|
||||||
<label className="block mb-1 text-th-fgd-3 text-xs">Price</label>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step={tickSize}
|
|
||||||
onChange={(e) => onSetPrice(e.target.value)}
|
|
||||||
value={price}
|
|
||||||
disabled={tradeType === 'Market'}
|
|
||||||
suffix={
|
|
||||||
<img
|
|
||||||
src={`/assets/icons/${groupConfig.quoteSymbol.toLowerCase()}.svg`}
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between pb-3">
|
|
||||||
<label className="text-th-fgd-3 text-xs">Type</label>
|
|
||||||
<TradeType onChange={onTradeTypeChange} value={tradeType} />
|
|
||||||
</div>
|
|
||||||
<label className="block mb-1 text-th-fgd-3 text-xs">Size</label>
|
|
||||||
<div className="grid grid-cols-2 grid-rows-1 gap-2">
|
|
||||||
<div className="col-span-1">
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step={minOrderSize}
|
|
||||||
onChange={(e) => onSetBaseSize(e.target.value)}
|
|
||||||
value={baseSize}
|
|
||||||
suffix={
|
|
||||||
<img
|
|
||||||
src={`/assets/icons/${marketConfig.baseSymbol.toLowerCase()}.svg`}
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-1">
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step={minOrderSize}
|
|
||||||
onChange={(e) => onSetQuoteSize(e.target.value)}
|
|
||||||
value={quoteSize}
|
|
||||||
suffix={
|
|
||||||
<img
|
|
||||||
src={`/assets/icons/${groupConfig.quoteSymbol.toLowerCase()}.svg`}
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<LeverageSlider
|
|
||||||
onChange={(e) => onSetBaseSize(e)}
|
|
||||||
value={baseSize ? baseSize : 0}
|
|
||||||
step={parseFloat(minOrderSize)}
|
|
||||||
disabled={false}
|
|
||||||
side={side}
|
|
||||||
decimalCount={sizeDecimalCount}
|
|
||||||
price={calculateTradePrice(
|
|
||||||
tradeType,
|
|
||||||
orderbook,
|
|
||||||
baseSize ? baseSize : 0,
|
|
||||||
side,
|
|
||||||
price
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{tradeType !== 'Market' ? (
|
|
||||||
<div className="flex mt-2">
|
|
||||||
<Switch checked={postOnly} onChange={postOnChange}>
|
|
||||||
POST
|
|
||||||
</Switch>
|
|
||||||
<div className="ml-4">
|
|
||||||
<Switch checked={ioc} onChange={iocOnChange}>
|
|
||||||
IOC
|
|
||||||
</Switch>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<div className={`flex py-4`}>
|
|
||||||
{ipAllowed ? (
|
|
||||||
side === 'buy' ? (
|
|
||||||
<Button
|
|
||||||
disabled={disabledTradeButton}
|
|
||||||
onClick={onSubmit}
|
|
||||||
className={`${
|
|
||||||
!disabledTradeButton
|
|
||||||
? 'bg-th-bkg-2 border border-th-green hover:border-th-green-dark'
|
|
||||||
: 'border border-th-bkg-4'
|
|
||||||
} text-th-green hover:text-th-fgd-1 hover:bg-th-green-dark flex-grow`}
|
|
||||||
>
|
|
||||||
{submitting ? (
|
|
||||||
<div className="w-full">
|
|
||||||
<Loading className="mx-auto" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
`${baseSize > 0 ? 'Buy ' + baseSize : 'Buy '} ${
|
|
||||||
marketConfig.name.includes('PERP')
|
|
||||||
? marketConfig.name
|
|
||||||
: marketConfig.baseSymbol
|
|
||||||
}`
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
disabled={disabledTradeButton}
|
|
||||||
onClick={onSubmit}
|
|
||||||
className={`${
|
|
||||||
!disabledTradeButton
|
|
||||||
? 'bg-th-bkg-2 border border-th-red hover:border-th-red-dark'
|
|
||||||
: 'border border-th-bkg-4'
|
|
||||||
} text-th-red hover:text-th-fgd-1 hover:bg-th-red-dark flex-grow`}
|
|
||||||
>
|
|
||||||
{submitting ? (
|
|
||||||
<div className="w-full">
|
|
||||||
<Loading className="mx-auto" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
`${baseSize > 0 ? 'Sell ' + baseSize : 'Sell '} ${
|
|
||||||
marketConfig.name.includes('PERP')
|
|
||||||
? marketConfig.name
|
|
||||||
: marketConfig.baseSymbol
|
|
||||||
}`
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<Button disabled className="flex-grow">
|
|
||||||
<span>Country Not Allowed</span>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex text-xs text-th-fgd-4 px-6 mt-2.5">
|
|
||||||
<MarketFee />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
|
@ -0,0 +1,17 @@
|
||||||
|
const SlippageWarning = ({ slippage }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`font-bold ${
|
||||||
|
slippage <= 0.5
|
||||||
|
? 'text-th-green'
|
||||||
|
: slippage > 0.5 && slippage <= 2
|
||||||
|
? 'text-th-orange'
|
||||||
|
: 'text-th-red'
|
||||||
|
} pt-2 rounded-md text-center text-th-fgd-3 text-xs`}
|
||||||
|
>
|
||||||
|
{slippage}% expected slippage
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SlippageWarning
|
|
@ -1,7 +1,7 @@
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { SwitchHorizontalIcon } from '@heroicons/react/outline'
|
import { SwitchHorizontalIcon } from '@heroicons/react/outline'
|
||||||
import { getWeights } from '@blockworks-foundation/mango-client'
|
import { getWeights } from '@blockworks-foundation/mango-client'
|
||||||
import useMangoStore from '../stores/useMangoStore'
|
import useMangoStore from '../../stores/useMangoStore'
|
||||||
import AdvancedTradeForm from './AdvancedTradeForm'
|
import AdvancedTradeForm from './AdvancedTradeForm'
|
||||||
import SimpleTradeForm from './SimpleTradeForm'
|
import SimpleTradeForm from './SimpleTradeForm'
|
||||||
import {
|
import {
|
||||||
|
@ -10,12 +10,13 @@ import {
|
||||||
FlipCardFront,
|
FlipCardFront,
|
||||||
FlipCardInner,
|
FlipCardInner,
|
||||||
StyledFloatingElement,
|
StyledFloatingElement,
|
||||||
} from './FlipCard'
|
} from '../FlipCard'
|
||||||
|
|
||||||
export default function TradeForm() {
|
export default function TradeForm() {
|
||||||
const [showAdvancedFrom, setShowAdvancedForm] = useState(true)
|
const [showAdvancedFrom, setShowAdvancedForm] = useState(true)
|
||||||
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
|
const marketConfig = useMangoStore((s) => s.selectedMarket.config)
|
||||||
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
|
||||||
|
const connected = useMangoStore((s) => s.wallet.connected)
|
||||||
|
|
||||||
const handleFormChange = () => {
|
const handleFormChange = () => {
|
||||||
setShowAdvancedForm(!showAdvancedFrom)
|
setShowAdvancedForm(!showAdvancedFrom)
|
||||||
|
@ -35,26 +36,33 @@ export default function TradeForm() {
|
||||||
<FlipCardInner flip={showAdvancedFrom}>
|
<FlipCardInner flip={showAdvancedFrom}>
|
||||||
{showAdvancedFrom ? (
|
{showAdvancedFrom ? (
|
||||||
<FlipCardFront>
|
<FlipCardFront>
|
||||||
<StyledFloatingElement className="h-full">
|
<StyledFloatingElement
|
||||||
<button
|
className="h-full px-1 py-0 md:px-4 md:py-4"
|
||||||
onClick={handleFormChange}
|
showConnect
|
||||||
className="absolute flex items-center justify-center right-4 top-4 rounded-full bg-th-bkg-3 w-8 h-8 hover:text-th-primary focus:outline-none"
|
>
|
||||||
>
|
<div className={`${!connected ? 'filter blur-sm' : ''}`}>
|
||||||
<SwitchHorizontalIcon className="w-5 h-5" />
|
<button
|
||||||
</button>
|
onClick={handleFormChange}
|
||||||
<AdvancedTradeForm initLeverage={initLeverage} />
|
className="absolute hidden md:flex items-center justify-center right-4 rounded-full bg-th-bkg-3 w-8 h-8 hover:text-th-primary focus:outline-none"
|
||||||
|
>
|
||||||
|
<SwitchHorizontalIcon className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
<AdvancedTradeForm initLeverage={initLeverage} />
|
||||||
|
</div>
|
||||||
</StyledFloatingElement>
|
</StyledFloatingElement>
|
||||||
</FlipCardFront>
|
</FlipCardFront>
|
||||||
) : (
|
) : (
|
||||||
<FlipCardBack>
|
<FlipCardBack>
|
||||||
<StyledFloatingElement className="h-full">
|
<StyledFloatingElement className="h-full px-1 md:px-4" showConnect>
|
||||||
<button
|
<div className={`${!connected ? 'filter blur-sm' : ''}`}>
|
||||||
onClick={handleFormChange}
|
<button
|
||||||
className="absolute flex items-center justify-center right-4 top-4 rounded-full bg-th-bkg-3 w-8 h-8 hover:text-th-primary focus:outline-none"
|
onClick={handleFormChange}
|
||||||
>
|
className="absolute flex items-center justify-center right-4 rounded-full bg-th-bkg-3 w-8 h-8 hover:text-th-primary focus:outline-none"
|
||||||
<SwitchHorizontalIcon className="w-5 h-5" />
|
>
|
||||||
</button>
|
<SwitchHorizontalIcon className="w-5 h-5" />
|
||||||
<SimpleTradeForm initLeverage={initLeverage} />
|
</button>
|
||||||
|
<SimpleTradeForm initLeverage={initLeverage} />
|
||||||
|
</div>
|
||||||
</StyledFloatingElement>
|
</StyledFloatingElement>
|
||||||
</FlipCardBack>
|
</FlipCardBack>
|
||||||
)}
|
)}
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { Listbox } from '@headlessui/react'
|
||||||
|
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/solid'
|
||||||
|
|
||||||
|
const TradeType = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
offerTriggers = false,
|
||||||
|
className = '',
|
||||||
|
}) => {
|
||||||
|
const TRADE_TYPES = ['Limit', 'Market']
|
||||||
|
if (offerTriggers)
|
||||||
|
TRADE_TYPES.push(
|
||||||
|
'Stop Loss',
|
||||||
|
'Stop Limit',
|
||||||
|
'Take Profit',
|
||||||
|
'Take Profit Limit'
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`relative ${className}`}>
|
||||||
|
<Listbox value={value} onChange={onChange}>
|
||||||
|
{({ open }) => (
|
||||||
|
<>
|
||||||
|
<Listbox.Button
|
||||||
|
className={`font-normal h-full w-full bg-th-bkg-1 border border-th-fgd-4 p-2 hover:border-th-primary rounded-md focus:outline-none focus:border-th-primary`}
|
||||||
|
>
|
||||||
|
<div className={`flex items-center justify-between space-x-4`}>
|
||||||
|
<span>{value}</span>
|
||||||
|
{open ? (
|
||||||
|
<ChevronUpIcon className={`h-5 w-5 mr-1 text-th-primary`} />
|
||||||
|
) : (
|
||||||
|
<ChevronDownIcon className={`h-5 w-5 mr-1 text-th-primary`} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Listbox.Button>
|
||||||
|
{open ? (
|
||||||
|
<Listbox.Options
|
||||||
|
static
|
||||||
|
className={`z-20 w-full p-1 absolute left-0 mt-1 bg-th-bkg-1 origin-top-left divide-y divide-th-bkg-3 shadow-lg outline-none rounded-md text-left`}
|
||||||
|
>
|
||||||
|
{TRADE_TYPES.map((type) => (
|
||||||
|
<Listbox.Option key={type} value={type}>
|
||||||
|
{({ selected }) => (
|
||||||
|
<div
|
||||||
|
className={`p-2 hover:bg-th-bkg-2 hover:cursor-pointer tracking-wider ${
|
||||||
|
selected && `text-th-primary`
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{type}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Listbox.Option>
|
||||||
|
))}
|
||||||
|
</Listbox.Options>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Listbox>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TradeType
|
|
@ -1,8 +1,8 @@
|
||||||
import { Listbox } from '@headlessui/react'
|
import { Listbox } from '@headlessui/react'
|
||||||
import styled from '@emotion/styled'
|
import styled from '@emotion/styled'
|
||||||
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/solid'
|
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/solid'
|
||||||
import { useViewport } from '../hooks/useViewport'
|
import { useViewport } from '../../hooks/useViewport'
|
||||||
import { breakpoints } from './TradePageGrid'
|
import { breakpoints } from '../TradePageGrid'
|
||||||
|
|
||||||
const StyledListbox = styled(Listbox.Button)`
|
const StyledListbox = styled(Listbox.Button)`
|
||||||
border-right: 1px solid transparent;
|
border-right: 1px solid transparent;
|
||||||
|
@ -61,8 +61,8 @@ const TriggerType = ({ value, onChange, className = '' }) => {
|
||||||
</Listbox>
|
</Listbox>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
{TRIGGER_TYPES.map((triggerType) => {
|
{TRIGGER_TYPES.map((triggerType, i) => (
|
||||||
;<div
|
<div
|
||||||
className={`px-2 py-1 ml-2 rounded-md cursor-pointer default-transition bg-th-bkg-4
|
className={`px-2 py-1 ml-2 rounded-md cursor-pointer default-transition bg-th-bkg-4
|
||||||
${
|
${
|
||||||
value === triggerType
|
value === triggerType
|
||||||
|
@ -70,11 +70,12 @@ const TriggerType = ({ value, onChange, className = '' }) => {
|
||||||
: `text-th-fgd-1 opacity-50 hover:opacity-100`
|
: `text-th-fgd-1 opacity-50 hover:opacity-100`
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
|
key={`${triggerType}${i}`}
|
||||||
onClick={() => onChange(triggerType)}
|
onClick={() => onChange(triggerType)}
|
||||||
>
|
>
|
||||||
{triggerType}
|
{triggerType}
|
||||||
</div>
|
</div>
|
||||||
})}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
|
@ -10,10 +10,10 @@ import useMangoStore from '../stores/useMangoStore'
|
||||||
import { copyToClipboard } from '../utils'
|
import { copyToClipboard } from '../utils'
|
||||||
import PageBodyContainer from '../components/PageBodyContainer'
|
import PageBodyContainer from '../components/PageBodyContainer'
|
||||||
import TopBar from '../components/TopBar'
|
import TopBar from '../components/TopBar'
|
||||||
import AccountOrders from '../components/account-page/AccountOrders'
|
import AccountOrders from '../components/account_page/AccountOrders'
|
||||||
import AccountHistory from '../components/account-page/AccountHistory'
|
import AccountHistory from '../components/account_page/AccountHistory'
|
||||||
import AccountsModal from '../components/AccountsModal'
|
import AccountsModal from '../components/AccountsModal'
|
||||||
import AccountOverview from '../components/account-page/AccountOverview'
|
import AccountOverview from '../components/account_page/AccountOverview'
|
||||||
import AccountNameModal from '../components/AccountNameModal'
|
import AccountNameModal from '../components/AccountNameModal'
|
||||||
import Button from '../components/Button'
|
import Button from '../components/Button'
|
||||||
import EmptyState from '../components/EmptyState'
|
import EmptyState from '../components/EmptyState'
|
||||||
|
|
|
@ -5,7 +5,7 @@ import PageBodyContainer from '../components/PageBodyContainer'
|
||||||
import TopBar from '../components/TopBar'
|
import TopBar from '../components/TopBar'
|
||||||
import EmptyState from '../components/EmptyState'
|
import EmptyState from '../components/EmptyState'
|
||||||
import AccountsModal from '../components/AccountsModal'
|
import AccountsModal from '../components/AccountsModal'
|
||||||
import AccountBorrows from '../components/account-page/AccountBorrows'
|
import AccountBorrows from '../components/account_page/AccountBorrows'
|
||||||
import Loading from '../components/Loading'
|
import Loading from '../components/Loading'
|
||||||
|
|
||||||
export default function Borrow() {
|
export default function Borrow() {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import TopBar from '../components/TopBar'
|
import TopBar from '../components/TopBar'
|
||||||
import PageBodyContainer from '../components/PageBodyContainer'
|
import PageBodyContainer from '../components/PageBodyContainer'
|
||||||
import StatsTotals from '../components/stats-page/StatsTotals'
|
import StatsTotals from '../components/stats_page/StatsTotals'
|
||||||
import StatsAssets from '../components/stats-page/StatsAssets'
|
import StatsAssets from '../components/stats_page/StatsAssets'
|
||||||
import StatsPerps from '../components/stats-page/StatsPerps'
|
import StatsPerps from '../components/stats_page/StatsPerps'
|
||||||
import useMangoStats from '../hooks/useMangoStats'
|
import useMangoStats from '../hooks/useMangoStats'
|
||||||
import Swipeable from '../components/mobile/Swipeable'
|
import Swipeable from '../components/mobile/Swipeable'
|
||||||
import SwipeableTabs from '../components/mobile/SwipeableTabs'
|
import SwipeableTabs from '../components/mobile/SwipeableTabs'
|
||||||
|
|
Loading…
Reference in New Issue