errors and translations
This commit is contained in:
parent
6337b696e7
commit
a8fd16eb29
|
@ -2,7 +2,7 @@ import ButtonGroup from '@components/forms/ButtonGroup'
|
|||
import Checkbox from '@components/forms/Checkbox'
|
||||
import Input from '@components/forms/Input'
|
||||
import Label from '@components/forms/Label'
|
||||
import Button, { IconButton, LinkButton } from '@components/shared/Button'
|
||||
import Button, { IconButton } from '@components/shared/Button'
|
||||
import InlineNotification from '@components/shared/InlineNotification'
|
||||
import Modal from '@components/shared/Modal'
|
||||
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
|
||||
|
@ -28,7 +28,7 @@ export type HotKey = {
|
|||
}
|
||||
|
||||
const HotKeysSettings = () => {
|
||||
const { t } = useTranslation('settings')
|
||||
const { t } = useTranslation(['common', 'settings', 'trade'])
|
||||
const [hotKeys, setHotKeys] = useLocalStorageState(HOT_KEYS_KEY, [])
|
||||
const [showHotKeyModal, setShowHotKeyModal] = useState(false)
|
||||
|
||||
|
@ -39,25 +39,27 @@ const HotKeysSettings = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="mb-1 text-base">{t('hot-keys')}</h2>
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="mb-1 text-base">{t('settings:hot-keys')}</h2>
|
||||
<p>{t('settings:hot-keys-desc')}</p>
|
||||
</div>
|
||||
{hotKeys.length ? (
|
||||
<LinkButton onClick={() => setShowHotKeyModal(true)}>
|
||||
{t('create-new-key')}
|
||||
</LinkButton>
|
||||
<Button onClick={() => setShowHotKeyModal(true)} secondary>
|
||||
{t('settings:new-hot-key')}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
<p className="mb-4">{t('hot-keys-desc')}</p>
|
||||
{hotKeys.length ? (
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th className="text-left">{t('key')}</Th>
|
||||
<Th className="text-right">{t('order-type')}</Th>
|
||||
<Th className="text-right">{t('side')}</Th>
|
||||
<Th className="text-right">{t('size')}</Th>
|
||||
<Th className="text-left">{t('settings:key-sequence')}</Th>
|
||||
<Th className="text-right">{t('trade:order-type')}</Th>
|
||||
<Th className="text-right">{t('trade:side')}</Th>
|
||||
<Th className="text-right">{t('trade:size')}</Th>
|
||||
<Th className="text-right">{t('price')}</Th>
|
||||
<Th className="text-right">{t('options')}</Th>
|
||||
<Th className="text-right">{t('settings:options')}</Th>
|
||||
<Th />
|
||||
</TrHead>
|
||||
</thead>
|
||||
|
@ -77,9 +79,15 @@ const HotKeysSettings = () => {
|
|||
} = hk
|
||||
const size =
|
||||
orderSizeType === 'percentage'
|
||||
? `${orderSize}% of max`
|
||||
? t('settings:percentage-of-max', { size: orderSize })
|
||||
: `$${orderSize}`
|
||||
const price = orderPrice ? `${orderPrice}% from oracle` : 'market'
|
||||
const price = orderPrice
|
||||
? `${orderPrice}% ${
|
||||
orderSide === 'buy'
|
||||
? t('settings:below')
|
||||
: t('settings:above')
|
||||
} oracle`
|
||||
: t('trade:market')
|
||||
|
||||
const options = {
|
||||
margin: margin,
|
||||
|
@ -91,14 +99,16 @@ const HotKeysSettings = () => {
|
|||
return (
|
||||
<TrBody key={keySequence} className="text-right">
|
||||
<Td className="text-left">{keySequence}</Td>
|
||||
<Td className="text-right">{orderType}</Td>
|
||||
<Td className="text-right">{orderSide}</Td>
|
||||
<Td className="text-right">{t(`trade:${orderType}`)}</Td>
|
||||
<Td className="text-right">{t(orderSide)}</Td>
|
||||
<Td className="text-right">{size}</Td>
|
||||
<Td className="text-right">{price}</Td>
|
||||
<Td className="text-right">
|
||||
{Object.entries(options).map((e) => {
|
||||
return e[1]
|
||||
? `${e[0] !== 'margin' ? ', ' : ''}${e[0]}`
|
||||
? `${e[0] !== 'margin' ? ', ' : ''}${t(
|
||||
`trade:${e[0]}`
|
||||
)}`
|
||||
: ''
|
||||
})}
|
||||
</Td>
|
||||
|
@ -121,9 +131,11 @@ const HotKeysSettings = () => {
|
|||
<div className="rounded-lg border border-th-bkg-3 p-6">
|
||||
<div className="flex flex-col items-center">
|
||||
<KeyIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||
<p className="mb-4">{t('no-hot-keys')}</p>
|
||||
<p className="mb-4">{t('settings:no-hot-keys')}</p>
|
||||
<Button onClick={() => setShowHotKeyModal(true)}>
|
||||
<div className="flex items-center">{t('create-hot-key')}</div>
|
||||
<div className="flex items-center">
|
||||
{t('settings:new-hot-key')}
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -171,7 +183,7 @@ const DEFAULT_FORM_VALUES: HotKeyForm = {
|
|||
}
|
||||
|
||||
const HotKeyModal = ({ isOpen, onClose }: ModalProps) => {
|
||||
const { t } = useTranslation(['settings', 'trade'])
|
||||
const { t } = useTranslation(['common', 'settings', 'trade'])
|
||||
const [hotKeys, setHotKeys] = useLocalStorageState<HotKey[]>(HOT_KEYS_KEY, [])
|
||||
const [hotKeyForm, setHotKeyForm] = useState<HotKeyForm>({
|
||||
...DEFAULT_FORM_VALUES,
|
||||
|
@ -207,10 +219,10 @@ const HotKeyModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
for (const key of triggerKey) {
|
||||
const value = form[key] as string
|
||||
if (value.length > 1) {
|
||||
invalidFields[key] = t('error-too-many-characters')
|
||||
invalidFields[key] = t('settings:error-too-many-characters')
|
||||
}
|
||||
if (!alphanumericRegex.test(value)) {
|
||||
invalidFields[key] = t('error-alphanumeric-only')
|
||||
invalidFields[key] = t('settings:error-alphanumeric-only')
|
||||
}
|
||||
}
|
||||
for (const key of requiredFields) {
|
||||
|
@ -219,10 +231,10 @@ const HotKeyModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
if (hotKeyForm.orderType === 'market') {
|
||||
console.log(key, invalidFields[key])
|
||||
if (key !== 'price') {
|
||||
invalidFields[key] = t('error-required-field')
|
||||
invalidFields[key] = t('settings:error-required-field')
|
||||
}
|
||||
} else {
|
||||
invalidFields[key] = t('error-required-field')
|
||||
invalidFields[key] = t('settings:error-required-field')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -230,17 +242,17 @@ const HotKeyModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
const value = form[key] as string
|
||||
if (value) {
|
||||
if (isNaN(parseFloat(value))) {
|
||||
invalidFields[key] = t('error-must-be-number')
|
||||
invalidFields[key] = t('settings:error-must-be-number')
|
||||
}
|
||||
if (parseFloat(value) < 0) {
|
||||
invalidFields[key] = t('error-must-be-above-zero')
|
||||
invalidFields[key] = t('settings:error-must-be-above-zero')
|
||||
}
|
||||
if (parseFloat(value) > 100) {
|
||||
if (key === 'price') {
|
||||
invalidFields[key] = t('error-must-be-below-100')
|
||||
invalidFields[key] = t('settings:error-must-be-below-100')
|
||||
} else {
|
||||
if (hotKeyForm.sizeType === 'percentage') {
|
||||
invalidFields[key] = t('error-must-be-below-100')
|
||||
invalidFields[key] = t('settings:error-must-be-below-100')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -276,9 +288,9 @@ const HotKeyModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<>
|
||||
<h2 className="mb-4 text-center">{t('create-hot-key')}</h2>
|
||||
<h2 className="mb-4 text-center">{t('settings:new-hot-key')}</h2>
|
||||
<div className="mb-4">
|
||||
<Label text={t('base-key')} />
|
||||
<Label text={t('settings:base-key')} />
|
||||
<ButtonGroup
|
||||
activeValue={hotKeyForm.baseKey}
|
||||
onChange={(key) => handleSetForm('baseKey', key)}
|
||||
|
@ -286,7 +298,7 @@ const HotKeyModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<Label text={t('trigger-key')} />
|
||||
<Label text={t('settings:trigger-key')} />
|
||||
<Input
|
||||
hasError={formErrors.triggerKey !== undefined}
|
||||
type="text"
|
||||
|
@ -305,32 +317,43 @@ const HotKeyModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
) : null}
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<Label text={t('order-side')} />
|
||||
<Label text={t('settings:order-side')} />
|
||||
<ButtonGroup
|
||||
activeValue={hotKeyForm.side}
|
||||
names={[t('buy'), t('sell')]}
|
||||
onChange={(side) => handleSetForm('side', side)}
|
||||
values={['buy', 'sell']}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<Label text={t('order-type')} />
|
||||
<Label text={t('trade:order-type')} />
|
||||
<ButtonGroup
|
||||
activeValue={hotKeyForm.orderType}
|
||||
names={[t('trade:limit'), t('market')]}
|
||||
onChange={(type) => handleSetForm('orderType', type)}
|
||||
values={['limit', 'market']}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<Label text={t('order-size-type')} />
|
||||
<Label text={t('settings:order-size-type')} />
|
||||
<ButtonGroup
|
||||
activeValue={hotKeyForm.sizeType}
|
||||
names={[t('settings:percentage'), t('settings:notional')]}
|
||||
onChange={(type) => handleSetForm('sizeType', type)}
|
||||
values={['percentage', 'notional']}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-start space-x-4">
|
||||
<div className="w-full">
|
||||
<Label text={t('size')} />
|
||||
<Tooltip
|
||||
content={
|
||||
hotKeyForm.sizeType === 'notional'
|
||||
? t('settings:tooltip-hot-key-notional-size')
|
||||
: t('settings:tooltip-hot-key-percentage-size')
|
||||
}
|
||||
>
|
||||
<Label className="tooltip-underline" text={t('trade:size')} />
|
||||
</Tooltip>
|
||||
<Input
|
||||
hasError={formErrors.size !== undefined}
|
||||
type="text"
|
||||
|
@ -351,7 +374,7 @@ const HotKeyModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
</div>
|
||||
{hotKeyForm.orderType === 'limit' ? (
|
||||
<div className="w-full">
|
||||
<Tooltip content="Set a price as a percentage change from the oracle price">
|
||||
<Tooltip content={t('settings:tooltip-hot-key-price')}>
|
||||
<Label className="tooltip-underline" text={t('price')} />
|
||||
</Tooltip>
|
||||
<Input
|
||||
|
@ -444,7 +467,7 @@ const HotKeyModal = ({ isOpen, onClose }: ModalProps) => {
|
|||
</div>
|
||||
</div>
|
||||
<Button className="mt-6 w-full" onClick={handleSave}>
|
||||
{t('save-hot-key')}
|
||||
{t('settings:save-hot-key')}
|
||||
</Button>
|
||||
</>
|
||||
</Modal>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
Group,
|
||||
MangoAccount,
|
||||
PerpMarket,
|
||||
PerpOrderSide,
|
||||
PerpOrderType,
|
||||
|
@ -10,9 +11,9 @@ import {
|
|||
} from '@blockworks-foundation/mango-v4'
|
||||
import { HotKey } from '@components/settings/HotKeysSettings'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { ReactNode, useCallback } from 'react'
|
||||
import Hotkeys from 'react-hot-keys'
|
||||
import { isMangoError } from 'types'
|
||||
import { GenericMarket, isMangoError } from 'types'
|
||||
import { HOT_KEYS_KEY, SOUND_SETTINGS_KEY } from 'utils/constants'
|
||||
import { notify } from 'utils/notifications'
|
||||
import { calculateLimitPriceForMarketOrder } from 'utils/tradeForm'
|
||||
|
@ -21,11 +22,11 @@ import useLocalStorageState from 'hooks/useLocalStorageState'
|
|||
import { INITIAL_SOUND_SETTINGS } from '@components/settings/SoundSettings'
|
||||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
import { floorToDecimal, getDecimalCount } from 'utils/numbers'
|
||||
import { useSpotMarketMax } from './SpotSlider'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import { Market } from '@project-serum/serum'
|
||||
import { useRouter } from 'next/router'
|
||||
import useUnownedAccount from 'hooks/useUnownedAccount'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
const set = mangoStore.getState().set
|
||||
|
||||
|
@ -39,102 +40,145 @@ const calcBaseSize = (
|
|||
limitPrice?: number
|
||||
) => {
|
||||
const { orderSize, orderSide, orderSizeType, orderType } = orderDetails
|
||||
let quoteSize: number
|
||||
if (!quoteTokenIndex) {
|
||||
quoteSize =
|
||||
orderSizeType === 'percentage'
|
||||
? (Number(orderSize) / 100) * maxSize
|
||||
: Number(orderSize)
|
||||
} else {
|
||||
const quoteBank = group.getFirstBankByTokenIndex(quoteTokenIndex)
|
||||
const quotePrice = quoteBank.uiPrice
|
||||
const orderSizeInQuote = Number(orderSize) / quotePrice
|
||||
quoteSize =
|
||||
orderSizeType === 'percentage'
|
||||
? (orderSizeInQuote / 100) * maxSize
|
||||
: orderSizeInQuote
|
||||
}
|
||||
|
||||
let baseSize: number
|
||||
if (orderType === 'market') {
|
||||
if (orderSide === 'buy') {
|
||||
let quoteSize: number
|
||||
if (orderSide === 'buy') {
|
||||
// assumes USDC = $1 as tokenIndex is 0
|
||||
if (!quoteTokenIndex) {
|
||||
quoteSize =
|
||||
orderSizeType === 'percentage'
|
||||
? (Number(orderSize) / 100) * maxSize
|
||||
: Number(orderSize)
|
||||
} else {
|
||||
// required for non USDC quote tokens
|
||||
const quoteBank = group.getFirstBankByTokenIndex(quoteTokenIndex)
|
||||
const quotePrice = quoteBank.uiPrice
|
||||
const orderSizeInQuote = Number(orderSize) / quotePrice
|
||||
quoteSize =
|
||||
orderSizeType === 'percentage'
|
||||
? (orderSizeInQuote / 100) * maxSize
|
||||
: orderSizeInQuote
|
||||
}
|
||||
if (orderType === 'market') {
|
||||
baseSize = floorToDecimal(
|
||||
quoteSize / oraclePrice,
|
||||
getDecimalCount(market.minOrderSize)
|
||||
).toNumber()
|
||||
} else {
|
||||
if (orderSizeType === 'percentage') {
|
||||
baseSize = floorToDecimal(
|
||||
(Number(orderSize) / 100) * maxSize,
|
||||
getDecimalCount(market.minOrderSize)
|
||||
).toNumber()
|
||||
} else {
|
||||
const price = limitPrice ? limitPrice : 0
|
||||
baseSize = floorToDecimal(
|
||||
quoteSize / price,
|
||||
getDecimalCount(market.minOrderSize)
|
||||
).toNumber()
|
||||
}
|
||||
} else {
|
||||
if (orderSizeType === 'percentage') {
|
||||
baseSize = floorToDecimal(
|
||||
(Number(orderSize) / 100) * maxSize,
|
||||
getDecimalCount(market.minOrderSize)
|
||||
).toNumber()
|
||||
} else {
|
||||
if (orderType === 'market') {
|
||||
baseSize = floorToDecimal(
|
||||
Number(orderSize) / oraclePrice,
|
||||
getDecimalCount(market.minOrderSize)
|
||||
).toNumber()
|
||||
} else {
|
||||
const price = limitPrice ? limitPrice : 0
|
||||
baseSize = floorToDecimal(
|
||||
Number(orderSize) / price,
|
||||
getDecimalCount(market.minOrderSize)
|
||||
).toNumber()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const price = limitPrice ? limitPrice : 0
|
||||
baseSize = floorToDecimal(
|
||||
quoteSize / price,
|
||||
getDecimalCount(market.minOrderSize)
|
||||
).toNumber()
|
||||
}
|
||||
return baseSize
|
||||
}
|
||||
|
||||
const calcSpotMarketMax = (
|
||||
mangoAccount: MangoAccount | undefined,
|
||||
selectedMarket: GenericMarket | undefined,
|
||||
side: string,
|
||||
useMargin: boolean
|
||||
) => {
|
||||
const spotBalances = mangoStore.getState().mangoAccount.spotBalances
|
||||
const group = mangoStore.getState().group
|
||||
if (!mangoAccount || !group || !selectedMarket) return 0
|
||||
if (!(selectedMarket instanceof Serum3Market)) return 0
|
||||
|
||||
let leverageMax = 0
|
||||
let spotMax = 0
|
||||
try {
|
||||
if (side === 'buy') {
|
||||
leverageMax = mangoAccount.getMaxQuoteForSerum3BidUi(
|
||||
group,
|
||||
selectedMarket.serumMarketExternal
|
||||
)
|
||||
const bank = group.getFirstBankByTokenIndex(
|
||||
selectedMarket.quoteTokenIndex
|
||||
)
|
||||
const balance = mangoAccount.getTokenBalanceUi(bank)
|
||||
const unsettled = spotBalances[bank.mint.toString()]?.unsettled || 0
|
||||
spotMax = balance + unsettled
|
||||
} else {
|
||||
leverageMax = mangoAccount.getMaxBaseForSerum3AskUi(
|
||||
group,
|
||||
selectedMarket.serumMarketExternal
|
||||
)
|
||||
const bank = group.getFirstBankByTokenIndex(selectedMarket.baseTokenIndex)
|
||||
const balance = mangoAccount.getTokenBalanceUi(bank)
|
||||
const unsettled = spotBalances[bank.mint.toString()]?.unsettled || 0
|
||||
spotMax = balance + unsettled
|
||||
}
|
||||
return useMargin ? leverageMax : Math.max(spotMax, 0)
|
||||
} catch (e) {
|
||||
console.error('Error calculating max size: ', e)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
const calcPerpMax = (
|
||||
mangoAccount: MangoAccount,
|
||||
selectedMarket: GenericMarket,
|
||||
side: string
|
||||
) => {
|
||||
const group = mangoStore.getState().group
|
||||
if (
|
||||
!mangoAccount ||
|
||||
!group ||
|
||||
!selectedMarket ||
|
||||
selectedMarket instanceof Serum3Market
|
||||
)
|
||||
return 0
|
||||
try {
|
||||
if (side === 'buy') {
|
||||
return mangoAccount.getMaxQuoteForPerpBidUi(
|
||||
group,
|
||||
selectedMarket.perpMarketIndex
|
||||
)
|
||||
} else {
|
||||
return mangoAccount.getMaxBaseForPerpAskUi(
|
||||
group,
|
||||
selectedMarket.perpMarketIndex
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error calculating max leverage: ', e)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
const TradeHotKeys = ({ children }: { children: ReactNode }) => {
|
||||
const {
|
||||
price: oraclePrice,
|
||||
selectedMarket,
|
||||
serumOrPerpMarket,
|
||||
} = useSelectedMarket()
|
||||
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
|
||||
const { t } = useTranslation(['common', 'settings'])
|
||||
const { price: oraclePrice, serumOrPerpMarket } = useSelectedMarket()
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const { isUnownedAccount } = useUnownedAccount()
|
||||
const { asPath } = useRouter()
|
||||
const [hotKeys] = useLocalStorageState(HOT_KEYS_KEY, [])
|
||||
const [placingOrder, setPlacingOrder] = useState(false)
|
||||
const [useMargin, setUseMargin] = useState(false)
|
||||
const [side, setSide] = useState('buy')
|
||||
const [soundSettings] = useLocalStorageState(
|
||||
SOUND_SETTINGS_KEY,
|
||||
INITIAL_SOUND_SETTINGS
|
||||
)
|
||||
const spotMax = useSpotMarketMax(
|
||||
mangoAccount,
|
||||
selectedMarket,
|
||||
side,
|
||||
useMargin
|
||||
)
|
||||
|
||||
const perpMax = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
if (
|
||||
!mangoAccount ||
|
||||
!group ||
|
||||
!selectedMarket ||
|
||||
selectedMarket instanceof Serum3Market
|
||||
)
|
||||
return 0
|
||||
try {
|
||||
if (side === 'buy') {
|
||||
return mangoAccount.getMaxQuoteForPerpBidUi(
|
||||
group,
|
||||
selectedMarket.perpMarketIndex
|
||||
)
|
||||
} else {
|
||||
return mangoAccount.getMaxBaseForPerpAskUi(
|
||||
group,
|
||||
selectedMarket.perpMarketIndex
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error calculating max leverage: ', e)
|
||||
return 0
|
||||
}
|
||||
}, [mangoAccount, side, selectedMarket])
|
||||
|
||||
const handlePlaceOrder = useCallback(
|
||||
async (hkOrder: HotKey) => {
|
||||
|
@ -143,15 +187,23 @@ const TradeHotKeys = ({ children }: { children: ReactNode }) => {
|
|||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const actions = mangoStore.getState().actions
|
||||
const selectedMarket = mangoStore.getState().selectedMarket.current
|
||||
const { ioc, orderPrice, orderSide, orderType, postOnly, reduceOnly } =
|
||||
hkOrder
|
||||
const {
|
||||
ioc,
|
||||
orderPrice,
|
||||
orderSide,
|
||||
orderType,
|
||||
postOnly,
|
||||
reduceOnly,
|
||||
margin,
|
||||
} = hkOrder
|
||||
|
||||
if (!group || !mangoAccount || !serumOrPerpMarket || !selectedMarket)
|
||||
return
|
||||
setPlacingOrder(true)
|
||||
try {
|
||||
const orderMax =
|
||||
serumOrPerpMarket instanceof PerpMarket ? perpMax : spotMax
|
||||
serumOrPerpMarket instanceof PerpMarket
|
||||
? calcPerpMax(mangoAccount, selectedMarket, orderSide)
|
||||
: calcSpotMarketMax(mangoAccount, selectedMarket, orderSide, margin)
|
||||
const quoteTokenIndex =
|
||||
selectedMarket instanceof PerpMarket
|
||||
? 0
|
||||
|
@ -196,6 +248,36 @@ const TradeHotKeys = ({ children }: { children: ReactNode }) => {
|
|||
)
|
||||
}
|
||||
|
||||
// check if size < max
|
||||
if (orderSide === 'buy') {
|
||||
if (baseSize * price > orderMax) {
|
||||
notify({
|
||||
type: 'error',
|
||||
title: t('settings:error-order-exceeds-max'),
|
||||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
console.log(baseSize, orderMax)
|
||||
if (baseSize > orderMax) {
|
||||
notify({
|
||||
type: 'error',
|
||||
title: t('settings:error-order-exceeds-max'),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
notify({
|
||||
type: 'info',
|
||||
title: t('settings:placing-order'),
|
||||
description: `${t(orderSide)} ${baseSize} ${selectedMarket.name} ${
|
||||
orderType === 'limit'
|
||||
? `${t('settings:at')} ${price}`
|
||||
: `${t('settings:at')} ${t('market')}`
|
||||
}`,
|
||||
})
|
||||
|
||||
if (selectedMarket instanceof Serum3Market) {
|
||||
const spotOrderType = ioc
|
||||
? Serum3OrderType.immediateOrCancel
|
||||
|
@ -273,37 +355,23 @@ const TradeHotKeys = ({ children }: { children: ReactNode }) => {
|
|||
txid: e?.txid,
|
||||
type: 'error',
|
||||
})
|
||||
} finally {
|
||||
setPlacingOrder(false)
|
||||
}
|
||||
},
|
||||
[perpMax, serumOrPerpMarket, spotMax]
|
||||
[serumOrPerpMarket]
|
||||
)
|
||||
|
||||
const onKeyDown = useCallback(
|
||||
(keyName: string) => {
|
||||
console.log('sdfsdf')
|
||||
const orderDetails = hotKeys.find(
|
||||
(hk: HotKey) => hk.keySequence === keyName
|
||||
)
|
||||
if (orderDetails) {
|
||||
setUseMargin(orderDetails.margin)
|
||||
setSide(orderDetails.orderSide)
|
||||
handlePlaceOrder(orderDetails)
|
||||
}
|
||||
},
|
||||
[handlePlaceOrder, hotKeys]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (placingOrder) {
|
||||
notify({
|
||||
type: 'success',
|
||||
title: 'Placing order for...',
|
||||
})
|
||||
}
|
||||
}, [placingOrder])
|
||||
|
||||
const showHotKeys =
|
||||
hotKeys.length &&
|
||||
asPath.includes('/trade') &&
|
||||
|
|
|
@ -17,6 +17,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
|
|||
'profile',
|
||||
'search',
|
||||
'settings',
|
||||
'trade',
|
||||
])),
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
{
|
||||
"above": "Above",
|
||||
"animations": "Animations",
|
||||
"at": "at",
|
||||
"avocado": "Avocado",
|
||||
"banana": "Banana",
|
||||
"base-key": "Base Key",
|
||||
"below": "Below",
|
||||
"blueberry": "Blueberry",
|
||||
"bottom-left": "Bottom-Left",
|
||||
"bottom-right": "Bottom-Right",
|
||||
|
@ -16,18 +20,38 @@
|
|||
"custom": "Custom",
|
||||
"dark": "Dark",
|
||||
"display": "Display",
|
||||
"error-alphanumeric-only": "Alphanumeric characters only",
|
||||
"error-must-be-above-zero": "Must be greater than zero",
|
||||
"error-must-be-below-100": "Must be below 100",
|
||||
"error-must-be-number": "Must be a number",
|
||||
"error-order-exceeds-max": "Order exceeds max size",
|
||||
"error-required-field": "This field is required",
|
||||
"error-too-many-characters": "Enter one alphanumeric character",
|
||||
"english": "English",
|
||||
"high-contrast": "High Contrast",
|
||||
"hot-keys": "Hot Keys",
|
||||
"hot-keys-desc": "Create hot keys to place trades",
|
||||
"key-sequence": "Key Sequence",
|
||||
"language": "Language",
|
||||
"light": "Light",
|
||||
"lychee": "Lychee",
|
||||
"mango": "Mango",
|
||||
"mango-classic": "Mango Classic",
|
||||
"medium": "Medium",
|
||||
"new-hot-key": "New Hot Key",
|
||||
"no-hot-keys": "Create your first hot key",
|
||||
"notification-position": "Notification Position",
|
||||
"notional": "Notional",
|
||||
"number-scroll": "Number Scroll",
|
||||
"olive": "Olive",
|
||||
"options": "Options",
|
||||
"oracle": "Oracle",
|
||||
"orderbook-flash": "Orderbook Flash",
|
||||
"order-side": "Order Side",
|
||||
"order-size-type": "Order Size Type",
|
||||
"percentage": "Percentage",
|
||||
"percentage-of-max": "{{size}}% of Max",
|
||||
"placing-order": "Placing Order...",
|
||||
"preferred-explorer": "Preferred Explorer",
|
||||
"recent-trades": "Recent Trades",
|
||||
"rpc": "RPC",
|
||||
|
@ -35,6 +59,7 @@
|
|||
"rpc-url": "Enter RPC URL",
|
||||
"russian": "Русский",
|
||||
"save": "Save",
|
||||
"save-hot-key": "Save Hot Key",
|
||||
"slider": "Slider",
|
||||
"solana-beach": "Solana Beach",
|
||||
"solana-explorer": "Solana Explorer",
|
||||
|
@ -45,6 +70,9 @@
|
|||
"swap-success": "Swap/Trade Success",
|
||||
"swap-trade-size-selector": "Swap/Trade Size Selector",
|
||||
"theme": "Theme",
|
||||
"tooltip-hot-key-notional-size": "Set size as a USD value.",
|
||||
"tooltip-hot-key-percentage-size": "Set size as a percentage of your max leverage.",
|
||||
"tooltip-hot-key-price": "Set a price as a percentage change from the oracle price.",
|
||||
"top-left": "Top-Left",
|
||||
"top-right": "Top-Right",
|
||||
"trade-layout": "Trade Layout",
|
||||
|
@ -52,6 +80,7 @@
|
|||
"transaction-success": "Transaction Success",
|
||||
"trade-chart": "Trade Chart",
|
||||
"trading-view": "Trading View",
|
||||
"trigger-key": "Trigger Key",
|
||||
"notifications": "Notifications",
|
||||
"limit-order-filled": "Limit Order Fills",
|
||||
"orderbook-bandwidth-saving": "Orderbook Bandwidth Saving",
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"maker": "Maker",
|
||||
"maker-fee": "Maker Fee",
|
||||
"margin": "Margin",
|
||||
"market": "Market",
|
||||
"market-details": "{{market}} Market Details",
|
||||
"max-leverage": "Max Leverage",
|
||||
"min-order-size": "Min Order Size",
|
||||
|
@ -63,6 +64,7 @@
|
|||
"price-expect": "The price you receive may be worse than you expect and full execution is not guaranteed. Max slippage is 2.5% for your safety. The part of your position with slippage beyond 2.5% will not be closed.",
|
||||
"price-provided-by": "Oracle by",
|
||||
"quote": "Quote",
|
||||
"reduce": "Reduce",
|
||||
"reduce-only": "Reduce Only",
|
||||
"sells": "Sells",
|
||||
"settle-funds": "Settle Funds",
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"maker": "Maker",
|
||||
"maker-fee": "Maker Fee",
|
||||
"margin": "Margin",
|
||||
"market": "Market",
|
||||
"market-details": "{{market}} Market Details",
|
||||
"max-leverage": "Max Leverage",
|
||||
"min-order-size": "Min Order Size",
|
||||
|
@ -63,6 +64,7 @@
|
|||
"price-expect": "The price you receive may be worse than you expect and full execution is not guaranteed. Max slippage is 2.5% for your safety. The part of your position with slippage beyond 2.5% will not be closed.",
|
||||
"price-provided-by": "Oracle by",
|
||||
"quote": "Quote",
|
||||
"reduce": "Reduce",
|
||||
"reduce-only": "Reduce Only",
|
||||
"sells": "Sells",
|
||||
"settle-funds": "Settle Funds",
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"maker": "Maker",
|
||||
"maker-fee": "Maker Fee",
|
||||
"margin": "Margin",
|
||||
"market": "Market",
|
||||
"market-details": "{{market}} Market Details",
|
||||
"max-leverage": "Max Leverage",
|
||||
"min-order-size": "Min Order Size",
|
||||
|
@ -63,6 +64,7 @@
|
|||
"price-expect": "The price you receive may be worse than you expect and full execution is not guaranteed. Max slippage is 2.5% for your safety. The part of your position with slippage beyond 2.5% will not be closed.",
|
||||
"price-provided-by": "Oracle by",
|
||||
"quote": "Quote",
|
||||
"reduce": "Reduce",
|
||||
"reduce-only": "Reduce Only",
|
||||
"sells": "Sells",
|
||||
"settle-funds": "Settle Funds",
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
"long": "做多",
|
||||
"maker": "挂单者",
|
||||
"margin": "保证金",
|
||||
"market": "Market",
|
||||
"market-details": "{{market}}市场细节",
|
||||
"max-leverage": "最多杠杆",
|
||||
"min-order-size": "最小订单量",
|
||||
|
@ -62,6 +63,7 @@
|
|||
"price-expect": "您收到的价格可能与您预期有差异,并且无法保证完全执行。为了您的安全,最大滑点保持为 2.5%。超过 2.5%滑点的部分不会被平仓。",
|
||||
"price-provided-by": "语言机来自",
|
||||
"quote": "计价",
|
||||
"reduce": "Reduce",
|
||||
"reduce-only": "限减少",
|
||||
"sells": "卖单",
|
||||
"settle-funds": "借清资金",
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"maker": "掛單者",
|
||||
"maker-fee": "掛單者 Fee",
|
||||
"margin": "保證金",
|
||||
"market": "Market",
|
||||
"market-details": "{{market}}市場細節",
|
||||
"max-leverage": "最多槓桿",
|
||||
"min-order-size": "最小訂單量",
|
||||
|
@ -63,6 +64,7 @@
|
|||
"price-expect": "您收到的價格可能與您預期有差異,並且無法保證完全執行。為了您的安全,最大滑點保持為 2.5%。超過 2.5%滑點的部分不會被平倉。",
|
||||
"price-provided-by": "語言機來自",
|
||||
"quote": "計價",
|
||||
"reduce": "Reduce",
|
||||
"reduce-only": "限減少",
|
||||
"sells": "賣單",
|
||||
"settle-funds": "借清資金",
|
||||
|
|
Loading…
Reference in New Issue