Merge pull request #182 from blockworks-foundation/trade-hot-keys
Trade Hot Keys
This commit is contained in:
commit
f6b3d2c5e7
|
@ -0,0 +1,494 @@
|
||||||
|
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 } 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'
|
||||||
|
import Tooltip from '@components/shared/Tooltip'
|
||||||
|
import { KeyIcon, TrashIcon } from '@heroicons/react/20/solid'
|
||||||
|
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { ModalProps } from 'types/modal'
|
||||||
|
import { HOT_KEYS_KEY } from 'utils/constants'
|
||||||
|
|
||||||
|
export type HotKey = {
|
||||||
|
ioc: boolean
|
||||||
|
keySequence: string
|
||||||
|
margin: boolean
|
||||||
|
orderSide: 'buy' | 'sell'
|
||||||
|
orderSizeType: 'percentage' | 'notional'
|
||||||
|
orderSize: string
|
||||||
|
orderType: 'limit' | 'market'
|
||||||
|
orderPrice: string
|
||||||
|
postOnly: boolean
|
||||||
|
reduceOnly: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const HotKeysSettings = () => {
|
||||||
|
const { t } = useTranslation(['common', 'settings', 'trade'])
|
||||||
|
const [hotKeys, setHotKeys] = useLocalStorageState(HOT_KEYS_KEY, [])
|
||||||
|
const [showHotKeyModal, setShowHotKeyModal] = useState(false)
|
||||||
|
|
||||||
|
const handleDeleteKey = (key: string) => {
|
||||||
|
const newKeys = hotKeys.filter((hk: HotKey) => hk.keySequence !== key)
|
||||||
|
setHotKeys([...newKeys])
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="mb-4 flex items-center justify-between">
|
||||||
|
<div className="pr-6">
|
||||||
|
<h2 className="mb-1 text-base">{t('settings:hot-keys')}</h2>
|
||||||
|
<p>{t('settings:hot-keys-desc')}</p>
|
||||||
|
</div>
|
||||||
|
{hotKeys.length ? (
|
||||||
|
<Button
|
||||||
|
className="whitespace-nowrap"
|
||||||
|
disabled={hotKeys.length >= 20}
|
||||||
|
onClick={() => setShowHotKeyModal(true)}
|
||||||
|
secondary
|
||||||
|
>
|
||||||
|
{t('settings:new-hot-key')}
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
{hotKeys.length === 20 ? (
|
||||||
|
<div className="mb-4">
|
||||||
|
<InlineNotification
|
||||||
|
type="warning"
|
||||||
|
desc={t('settings:error-key-limit-reached')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{hotKeys.length ? (
|
||||||
|
<Table>
|
||||||
|
<thead>
|
||||||
|
<TrHead>
|
||||||
|
<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('settings:options')}</Th>
|
||||||
|
<Th />
|
||||||
|
</TrHead>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{hotKeys.map((hk: HotKey) => {
|
||||||
|
const {
|
||||||
|
keySequence,
|
||||||
|
orderSide,
|
||||||
|
orderPrice,
|
||||||
|
orderSize,
|
||||||
|
orderSizeType,
|
||||||
|
orderType,
|
||||||
|
ioc,
|
||||||
|
margin,
|
||||||
|
reduceOnly,
|
||||||
|
postOnly,
|
||||||
|
} = hk
|
||||||
|
const size =
|
||||||
|
orderSizeType === 'percentage'
|
||||||
|
? t('settings:percentage-of-max', { size: orderSize })
|
||||||
|
: `$${orderSize}`
|
||||||
|
const price = orderPrice
|
||||||
|
? `${orderPrice}% ${
|
||||||
|
orderSide === 'buy'
|
||||||
|
? t('settings:below')
|
||||||
|
: t('settings:above')
|
||||||
|
} oracle`
|
||||||
|
: t('trade:market')
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
margin: margin,
|
||||||
|
IOC: ioc,
|
||||||
|
post: postOnly,
|
||||||
|
reduce: reduceOnly,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TrBody key={keySequence} className="text-right">
|
||||||
|
<Td className="text-left">{keySequence}</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' ? ', ' : ''}${t(
|
||||||
|
`trade:${e[0]}`
|
||||||
|
)}`
|
||||||
|
: ''
|
||||||
|
})}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => handleDeleteKey(keySequence)}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<TrashIcon className="h-4 w-4" />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
</TrBody>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
) : (
|
||||||
|
<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('settings:no-hot-keys')}</p>
|
||||||
|
<Button onClick={() => setShowHotKeyModal(true)}>
|
||||||
|
<div className="flex items-center">
|
||||||
|
{t('settings:new-hot-key')}
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{showHotKeyModal ? (
|
||||||
|
<HotKeyModal
|
||||||
|
isOpen={showHotKeyModal}
|
||||||
|
onClose={() => setShowHotKeyModal(false)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HotKeysSettings
|
||||||
|
|
||||||
|
type FormErrors = Partial<Record<keyof HotKeyForm, string>>
|
||||||
|
|
||||||
|
type HotKeyForm = {
|
||||||
|
baseKey: string
|
||||||
|
triggerKey: string
|
||||||
|
price: string
|
||||||
|
side: 'buy' | 'sell'
|
||||||
|
size: string
|
||||||
|
sizeType: 'percentage' | 'notional'
|
||||||
|
orderType: 'limit' | 'market'
|
||||||
|
ioc: boolean
|
||||||
|
post: boolean
|
||||||
|
margin: boolean
|
||||||
|
reduce: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_FORM_VALUES: HotKeyForm = {
|
||||||
|
baseKey: 'shift',
|
||||||
|
triggerKey: '',
|
||||||
|
price: '',
|
||||||
|
side: 'buy',
|
||||||
|
size: '',
|
||||||
|
sizeType: 'percentage',
|
||||||
|
orderType: 'limit',
|
||||||
|
ioc: false,
|
||||||
|
post: false,
|
||||||
|
margin: false,
|
||||||
|
reduce: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
const HotKeyModal = ({ isOpen, onClose }: ModalProps) => {
|
||||||
|
const { t } = useTranslation(['common', 'settings', 'trade'])
|
||||||
|
const [hotKeys, setHotKeys] = useLocalStorageState<HotKey[]>(HOT_KEYS_KEY, [])
|
||||||
|
const [hotKeyForm, setHotKeyForm] = useState<HotKeyForm>({
|
||||||
|
...DEFAULT_FORM_VALUES,
|
||||||
|
})
|
||||||
|
const [formErrors, setFormErrors] = useState<FormErrors>({})
|
||||||
|
|
||||||
|
const handleSetForm = (propertyName: string, value: string | boolean) => {
|
||||||
|
setFormErrors({})
|
||||||
|
setHotKeyForm((prevState) => ({ ...prevState, [propertyName]: value }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePostOnlyChange = (postOnly: boolean) => {
|
||||||
|
if (postOnly) {
|
||||||
|
handleSetForm('ioc', !postOnly)
|
||||||
|
}
|
||||||
|
handleSetForm('post', postOnly)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleIocChange = (ioc: boolean) => {
|
||||||
|
if (ioc) {
|
||||||
|
handleSetForm('post', !ioc)
|
||||||
|
}
|
||||||
|
handleSetForm('ioc', ioc)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFormValid = (form: HotKeyForm) => {
|
||||||
|
const invalidFields: FormErrors = {}
|
||||||
|
setFormErrors({})
|
||||||
|
const triggerKey: (keyof HotKeyForm)[] = ['triggerKey']
|
||||||
|
const requiredFields: (keyof HotKeyForm)[] = ['size', 'price', 'triggerKey']
|
||||||
|
const numberFields: (keyof HotKeyForm)[] = ['size', 'price']
|
||||||
|
const alphanumericRegex = /^[a-zA-Z0-9]+$/
|
||||||
|
for (const key of triggerKey) {
|
||||||
|
const value = form[key] as string
|
||||||
|
if (value.length > 1) {
|
||||||
|
invalidFields[key] = t('settings:error-too-many-characters')
|
||||||
|
}
|
||||||
|
if (!alphanumericRegex.test(value)) {
|
||||||
|
invalidFields[key] = t('settings:error-alphanumeric-only')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const key of requiredFields) {
|
||||||
|
const value = form[key] as string
|
||||||
|
if (!value) {
|
||||||
|
if (hotKeyForm.orderType === 'market') {
|
||||||
|
if (key !== 'price') {
|
||||||
|
invalidFields[key] = t('settings:error-required-field')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
invalidFields[key] = t('settings:error-required-field')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const key of numberFields) {
|
||||||
|
const value = form[key] as string
|
||||||
|
if (value) {
|
||||||
|
if (isNaN(parseFloat(value))) {
|
||||||
|
invalidFields[key] = t('settings:error-must-be-number')
|
||||||
|
}
|
||||||
|
if (parseFloat(value) < 0) {
|
||||||
|
invalidFields[key] = t('settings:error-must-be-above-zero')
|
||||||
|
}
|
||||||
|
if (parseFloat(value) > 100) {
|
||||||
|
if (key === 'price') {
|
||||||
|
invalidFields[key] = t('settings:error-must-be-below-100')
|
||||||
|
} else {
|
||||||
|
if (hotKeyForm.sizeType === 'percentage') {
|
||||||
|
invalidFields[key] = t('settings:error-must-be-below-100')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const newKeySequence = `${form.baseKey}+${form.triggerKey}`
|
||||||
|
const keyExists = hotKeys.find((k) => k.keySequence === newKeySequence)
|
||||||
|
if (keyExists) {
|
||||||
|
invalidFields.triggerKey = t('settings:error-key-in-use')
|
||||||
|
}
|
||||||
|
if (Object.keys(invalidFields).length) {
|
||||||
|
setFormErrors(invalidFields)
|
||||||
|
}
|
||||||
|
return invalidFields
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
const invalidFields = isFormValid(hotKeyForm)
|
||||||
|
if (Object.keys(invalidFields).length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const newHotKey = {
|
||||||
|
keySequence: `${hotKeyForm.baseKey}+${hotKeyForm.triggerKey}`,
|
||||||
|
orderSide: hotKeyForm.side,
|
||||||
|
orderSizeType: hotKeyForm.sizeType,
|
||||||
|
orderSize: hotKeyForm.size,
|
||||||
|
orderType: hotKeyForm.orderType,
|
||||||
|
orderPrice: hotKeyForm.price,
|
||||||
|
ioc: hotKeyForm.ioc,
|
||||||
|
margin: hotKeyForm.margin,
|
||||||
|
postOnly: hotKeyForm.post,
|
||||||
|
reduceOnly: hotKeyForm.reduce,
|
||||||
|
}
|
||||||
|
setHotKeys([...hotKeys, newHotKey])
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} onClose={onClose}>
|
||||||
|
<>
|
||||||
|
<h2 className="mb-4 text-center">{t('settings:new-hot-key')}</h2>
|
||||||
|
<div className="mb-4">
|
||||||
|
<Label text={t('settings:base-key')} />
|
||||||
|
<ButtonGroup
|
||||||
|
activeValue={hotKeyForm.baseKey}
|
||||||
|
onChange={(key) => handleSetForm('baseKey', key)}
|
||||||
|
values={['shift', 'ctrl', 'option']}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<Label text={t('settings:trigger-key')} />
|
||||||
|
<Input
|
||||||
|
hasError={formErrors.triggerKey !== undefined}
|
||||||
|
type="text"
|
||||||
|
value={hotKeyForm.triggerKey}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleSetForm('triggerKey', e.target.value.toLowerCase())
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{formErrors.triggerKey ? (
|
||||||
|
<div className="mt-1">
|
||||||
|
<InlineNotification
|
||||||
|
type="error"
|
||||||
|
desc={formErrors.triggerKey}
|
||||||
|
hideBorder
|
||||||
|
hidePadding
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<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('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('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">
|
||||||
|
<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"
|
||||||
|
value={hotKeyForm.size}
|
||||||
|
onChange={(e) => handleSetForm('size', e.target.value)}
|
||||||
|
suffix={hotKeyForm.sizeType === 'percentage' ? '%' : 'USD'}
|
||||||
|
/>
|
||||||
|
{formErrors.size ? (
|
||||||
|
<div className="mt-1">
|
||||||
|
<InlineNotification
|
||||||
|
type="error"
|
||||||
|
desc={formErrors.size}
|
||||||
|
hideBorder
|
||||||
|
hidePadding
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
{hotKeyForm.orderType === 'limit' ? (
|
||||||
|
<div className="w-full">
|
||||||
|
<Tooltip content={t('settings:tooltip-hot-key-price')}>
|
||||||
|
<Label className="tooltip-underline" text={t('price')} />
|
||||||
|
</Tooltip>
|
||||||
|
<Input
|
||||||
|
hasError={formErrors.price !== undefined}
|
||||||
|
type="text"
|
||||||
|
value={hotKeyForm.price}
|
||||||
|
onChange={(e) => handleSetForm('price', e.target.value)}
|
||||||
|
placeholder="e.g. 1%"
|
||||||
|
suffix="%"
|
||||||
|
/>
|
||||||
|
{formErrors.price ? (
|
||||||
|
<div className="mt-1">
|
||||||
|
<InlineNotification
|
||||||
|
type="error"
|
||||||
|
desc={formErrors.price}
|
||||||
|
hideBorder
|
||||||
|
hidePadding
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap md:flex-nowrap">
|
||||||
|
{hotKeyForm.orderType === 'limit' ? (
|
||||||
|
<div className="flex">
|
||||||
|
<div className="mr-3 mt-4" id="trade-step-six">
|
||||||
|
<Tooltip
|
||||||
|
className="hidden md:block"
|
||||||
|
delay={100}
|
||||||
|
content={t('trade:tooltip-post')}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={hotKeyForm.post}
|
||||||
|
onChange={(e) => handlePostOnlyChange(e.target.checked)}
|
||||||
|
>
|
||||||
|
{t('trade:post')}
|
||||||
|
</Checkbox>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div className="mr-3 mt-4" id="trade-step-seven">
|
||||||
|
<Tooltip
|
||||||
|
className="hidden md:block"
|
||||||
|
delay={100}
|
||||||
|
content={t('trade:tooltip-ioc')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center text-xs text-th-fgd-3">
|
||||||
|
<Checkbox
|
||||||
|
checked={hotKeyForm.ioc}
|
||||||
|
onChange={(e) => handleIocChange(e.target.checked)}
|
||||||
|
>
|
||||||
|
IOC
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div className="mt-4 mr-3" id="trade-step-eight">
|
||||||
|
<Tooltip
|
||||||
|
className="hidden md:block"
|
||||||
|
delay={100}
|
||||||
|
content={t('trade:tooltip-enable-margin')}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={hotKeyForm.margin}
|
||||||
|
onChange={(e) => handleSetForm('margin', e.target.checked)}
|
||||||
|
>
|
||||||
|
{t('trade:margin')}
|
||||||
|
</Checkbox>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div className="mr-3 mt-4">
|
||||||
|
<Tooltip
|
||||||
|
className="hidden md:block"
|
||||||
|
delay={100}
|
||||||
|
content={
|
||||||
|
'Reduce will only decrease the size of an open position. This is often used for closing a position.'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="flex items-center text-xs text-th-fgd-3">
|
||||||
|
<Checkbox
|
||||||
|
checked={hotKeyForm.reduce}
|
||||||
|
onChange={(e) => handleSetForm('reduce', e.target.checked)}
|
||||||
|
>
|
||||||
|
{t('trade:reduce-only')}
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button className="mt-6 w-full" onClick={handleSave}>
|
||||||
|
{t('settings:save-hot-key')}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
|
@ -42,7 +42,7 @@ const NotificationSettings = () => {
|
||||||
<h2 className="text-base">{t('settings:notifications')}</h2>
|
<h2 className="text-base">{t('settings:notifications')}</h2>
|
||||||
</div>
|
</div>
|
||||||
{isAuth ? (
|
{isAuth ? (
|
||||||
<div className="flex items-center justify-between border-t border-th-bkg-3 p-4">
|
<div className="flex items-center justify-between border-y border-th-bkg-3 p-4">
|
||||||
<p>{t('settings:limit-order-filled')}</p>
|
<p>{t('settings:limit-order-filled')}</p>
|
||||||
<Switch
|
<Switch
|
||||||
checked={!!data?.fillsNotifications}
|
checked={!!data?.fillsNotifications}
|
||||||
|
@ -55,7 +55,7 @@ const NotificationSettings = () => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="mb-8 rounded-lg border border-th-bkg-3 p-6">
|
<div className="rounded-lg border border-th-bkg-3 p-6">
|
||||||
{connected ? (
|
{connected ? (
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<BellIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
<BellIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
|
import { useViewport } from 'hooks/useViewport'
|
||||||
import AnimationSettings from './AnimationSettings'
|
import AnimationSettings from './AnimationSettings'
|
||||||
import DisplaySettings from './DisplaySettings'
|
import DisplaySettings from './DisplaySettings'
|
||||||
|
import HotKeysSettings from './HotKeysSettings'
|
||||||
import NotificationSettings from './NotificationSettings'
|
import NotificationSettings from './NotificationSettings'
|
||||||
import PreferredExplorerSettings from './PreferredExplorerSettings'
|
import PreferredExplorerSettings from './PreferredExplorerSettings'
|
||||||
import RpcSettings from './RpcSettings'
|
import RpcSettings from './RpcSettings'
|
||||||
import SoundSettings from './SoundSettings'
|
import SoundSettings from './SoundSettings'
|
||||||
|
import { breakpoints } from 'utils/theme'
|
||||||
|
|
||||||
const SettingsPage = () => {
|
const SettingsPage = () => {
|
||||||
|
const { width } = useViewport()
|
||||||
|
const isMobile = width ? width < breakpoints.lg : false
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-12">
|
<div className="grid grid-cols-12">
|
||||||
<div className="col-span-12 border-b border-th-bkg-3 lg:col-span-10 lg:col-start-2 xl:col-span-8 xl:col-start-3">
|
<div className="col-span-12 border-b border-th-bkg-3 lg:col-span-10 lg:col-start-2 xl:col-span-8 xl:col-start-3">
|
||||||
|
@ -14,9 +19,14 @@ const SettingsPage = () => {
|
||||||
<div className="col-span-12 border-b border-th-bkg-3 pt-8 lg:col-span-10 lg:col-start-2 xl:col-span-8 xl:col-start-3">
|
<div className="col-span-12 border-b border-th-bkg-3 pt-8 lg:col-span-10 lg:col-start-2 xl:col-span-8 xl:col-start-3">
|
||||||
<DisplaySettings />
|
<DisplaySettings />
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-12 border-b border-th-bkg-3 pt-8 lg:col-span-10 lg:col-start-2 xl:col-span-8 xl:col-start-3">
|
<div className="col-span-12 pt-8 lg:col-span-10 lg:col-start-2 xl:col-span-8 xl:col-start-3">
|
||||||
<NotificationSettings />
|
<NotificationSettings />
|
||||||
</div>
|
</div>
|
||||||
|
{!isMobile ? (
|
||||||
|
<div className="col-span-12 pt-8 lg:col-span-10 lg:col-start-2 xl:col-span-8 xl:col-start-3">
|
||||||
|
<HotKeysSettings />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
<div className="col-span-12 border-b border-th-bkg-3 pt-8 lg:col-span-10 lg:col-start-2 xl:col-span-8 xl:col-start-3">
|
<div className="col-span-12 border-b border-th-bkg-3 pt-8 lg:col-span-10 lg:col-start-2 xl:col-span-8 xl:col-start-3">
|
||||||
<AnimationSettings />
|
<AnimationSettings />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -59,7 +59,7 @@ import InlineNotification from '@components/shared/InlineNotification'
|
||||||
|
|
||||||
const set = mangoStore.getState().set
|
const set = mangoStore.getState().set
|
||||||
|
|
||||||
const successSound = new Howl({
|
export const successSound = new Howl({
|
||||||
src: ['/sounds/swap-success.mp3'],
|
src: ['/sounds/swap-success.mp3'],
|
||||||
volume: 0.5,
|
volume: 0.5,
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,6 +16,7 @@ import OrderbookAndTrades from './OrderbookAndTrades'
|
||||||
import FavoriteMarketsBar from './FavoriteMarketsBar'
|
import FavoriteMarketsBar from './FavoriteMarketsBar'
|
||||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||||
import { TRADE_LAYOUT_KEY } from 'utils/constants'
|
import { TRADE_LAYOUT_KEY } from 'utils/constants'
|
||||||
|
import TradeHotKeys from './TradeHotKeys'
|
||||||
|
|
||||||
export type TradeLayout =
|
export type TradeLayout =
|
||||||
| 'chartLeft'
|
| 'chartLeft'
|
||||||
|
@ -206,7 +207,7 @@ const TradeAdvancedPage = () => {
|
||||||
return showMobileView ? (
|
return showMobileView ? (
|
||||||
<MobileTradeAdvancedPage />
|
<MobileTradeAdvancedPage />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<TradeHotKeys>
|
||||||
<FavoriteMarketsBar />
|
<FavoriteMarketsBar />
|
||||||
<ResponsiveGridLayout
|
<ResponsiveGridLayout
|
||||||
onBreakpointChange={(bp) => console.log('bp: ', bp)}
|
onBreakpointChange={(bp) => console.log('bp: ', bp)}
|
||||||
|
@ -262,7 +263,7 @@ const TradeAdvancedPage = () => {
|
||||||
{/* {!tourSettings?.trade_tour_seen && isOnboarded && connected ? (
|
{/* {!tourSettings?.trade_tour_seen && isOnboarded && connected ? (
|
||||||
<TradeOnboardingTour />
|
<TradeOnboardingTour />
|
||||||
) : null} */}
|
) : null} */}
|
||||||
</>
|
</TradeHotKeys>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,393 @@
|
||||||
|
import {
|
||||||
|
Group,
|
||||||
|
MangoAccount,
|
||||||
|
PerpMarket,
|
||||||
|
PerpOrderSide,
|
||||||
|
PerpOrderType,
|
||||||
|
Serum3Market,
|
||||||
|
Serum3OrderType,
|
||||||
|
Serum3SelfTradeBehavior,
|
||||||
|
Serum3Side,
|
||||||
|
} from '@blockworks-foundation/mango-v4'
|
||||||
|
import { HotKey } from '@components/settings/HotKeysSettings'
|
||||||
|
import mangoStore from '@store/mangoStore'
|
||||||
|
import { ReactNode, useCallback } from 'react'
|
||||||
|
import Hotkeys from 'react-hot-keys'
|
||||||
|
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'
|
||||||
|
import { successSound } from './AdvancedTradeForm'
|
||||||
|
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 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
|
||||||
|
|
||||||
|
const calcBaseSize = (
|
||||||
|
orderDetails: HotKey,
|
||||||
|
maxSize: number,
|
||||||
|
market: PerpMarket | Market,
|
||||||
|
oraclePrice: number,
|
||||||
|
quoteTokenIndex: number,
|
||||||
|
group: Group,
|
||||||
|
limitPrice?: number
|
||||||
|
) => {
|
||||||
|
const { orderSize, orderSide, orderSizeType, orderType } = orderDetails
|
||||||
|
let baseSize: number
|
||||||
|
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 {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 { 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 [soundSettings] = useLocalStorageState(
|
||||||
|
SOUND_SETTINGS_KEY,
|
||||||
|
INITIAL_SOUND_SETTINGS
|
||||||
|
)
|
||||||
|
|
||||||
|
const handlePlaceOrder = useCallback(
|
||||||
|
async (hkOrder: HotKey) => {
|
||||||
|
const client = mangoStore.getState().client
|
||||||
|
const group = mangoStore.getState().group
|
||||||
|
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||||
|
const actions = mangoStore.getState().actions
|
||||||
|
const selectedMarket = mangoStore.getState().selectedMarket.current
|
||||||
|
const {
|
||||||
|
ioc,
|
||||||
|
orderPrice,
|
||||||
|
orderSide,
|
||||||
|
orderType,
|
||||||
|
postOnly,
|
||||||
|
reduceOnly,
|
||||||
|
margin,
|
||||||
|
} = hkOrder
|
||||||
|
|
||||||
|
if (!group || !mangoAccount || !serumOrPerpMarket || !selectedMarket)
|
||||||
|
return
|
||||||
|
try {
|
||||||
|
const orderMax =
|
||||||
|
serumOrPerpMarket instanceof PerpMarket
|
||||||
|
? calcPerpMax(mangoAccount, selectedMarket, orderSide)
|
||||||
|
: calcSpotMarketMax(mangoAccount, selectedMarket, orderSide, margin)
|
||||||
|
const quoteTokenIndex =
|
||||||
|
selectedMarket instanceof PerpMarket
|
||||||
|
? 0
|
||||||
|
: selectedMarket.quoteTokenIndex
|
||||||
|
let baseSize: number
|
||||||
|
let price: number
|
||||||
|
if (orderType === 'market') {
|
||||||
|
baseSize = calcBaseSize(
|
||||||
|
hkOrder,
|
||||||
|
orderMax,
|
||||||
|
serumOrPerpMarket,
|
||||||
|
oraclePrice,
|
||||||
|
quoteTokenIndex,
|
||||||
|
group
|
||||||
|
)
|
||||||
|
const orderbook = mangoStore.getState().selectedMarket.orderbook
|
||||||
|
price = calculateLimitPriceForMarketOrder(
|
||||||
|
orderbook,
|
||||||
|
baseSize,
|
||||||
|
orderSide
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// change in price from oracle for limit order
|
||||||
|
const priceChange = (Number(orderPrice) / 100) * oraclePrice
|
||||||
|
// subtract price change for buy limit, add for sell limit
|
||||||
|
const rawPrice =
|
||||||
|
orderSide === 'buy'
|
||||||
|
? oraclePrice - priceChange
|
||||||
|
: oraclePrice + priceChange
|
||||||
|
price = floorToDecimal(
|
||||||
|
rawPrice,
|
||||||
|
getDecimalCount(serumOrPerpMarket.tickSize)
|
||||||
|
).toNumber()
|
||||||
|
baseSize = calcBaseSize(
|
||||||
|
hkOrder,
|
||||||
|
orderMax,
|
||||||
|
serumOrPerpMarket,
|
||||||
|
oraclePrice,
|
||||||
|
quoteTokenIndex,
|
||||||
|
group,
|
||||||
|
price
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
: postOnly && orderType !== 'market'
|
||||||
|
? Serum3OrderType.postOnly
|
||||||
|
: Serum3OrderType.limit
|
||||||
|
const tx = await client.serum3PlaceOrder(
|
||||||
|
group,
|
||||||
|
mangoAccount,
|
||||||
|
selectedMarket.serumMarketExternal,
|
||||||
|
orderSide === 'buy' ? Serum3Side.bid : Serum3Side.ask,
|
||||||
|
price,
|
||||||
|
baseSize,
|
||||||
|
Serum3SelfTradeBehavior.decrementTake,
|
||||||
|
spotOrderType,
|
||||||
|
Date.now(),
|
||||||
|
10
|
||||||
|
)
|
||||||
|
actions.fetchOpenOrders(true)
|
||||||
|
set((s) => {
|
||||||
|
s.successAnimation.trade = true
|
||||||
|
})
|
||||||
|
if (soundSettings['swap-success']) {
|
||||||
|
successSound.play()
|
||||||
|
}
|
||||||
|
notify({
|
||||||
|
type: 'success',
|
||||||
|
title: 'Transaction successful',
|
||||||
|
txid: tx,
|
||||||
|
})
|
||||||
|
} else if (selectedMarket instanceof PerpMarket) {
|
||||||
|
const perpOrderType =
|
||||||
|
orderType === 'market'
|
||||||
|
? PerpOrderType.market
|
||||||
|
: ioc
|
||||||
|
? PerpOrderType.immediateOrCancel
|
||||||
|
: postOnly
|
||||||
|
? PerpOrderType.postOnly
|
||||||
|
: PerpOrderType.limit
|
||||||
|
console.log('perpOrderType', perpOrderType)
|
||||||
|
|
||||||
|
const tx = await client.perpPlaceOrder(
|
||||||
|
group,
|
||||||
|
mangoAccount,
|
||||||
|
selectedMarket.perpMarketIndex,
|
||||||
|
orderSide === 'buy' ? PerpOrderSide.bid : PerpOrderSide.ask,
|
||||||
|
price,
|
||||||
|
Math.abs(baseSize),
|
||||||
|
undefined, // maxQuoteQuantity
|
||||||
|
Date.now(),
|
||||||
|
perpOrderType,
|
||||||
|
selectedMarket.reduceOnly || reduceOnly,
|
||||||
|
undefined,
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
actions.fetchOpenOrders(true)
|
||||||
|
set((s) => {
|
||||||
|
s.successAnimation.trade = true
|
||||||
|
})
|
||||||
|
if (soundSettings['swap-success']) {
|
||||||
|
successSound.play()
|
||||||
|
}
|
||||||
|
notify({
|
||||||
|
type: 'success',
|
||||||
|
title: 'Transaction successful',
|
||||||
|
txid: tx,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Place trade error:', e)
|
||||||
|
if (!isMangoError(e)) return
|
||||||
|
notify({
|
||||||
|
title: 'There was an issue.',
|
||||||
|
description: e.message,
|
||||||
|
txid: e?.txid,
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[serumOrPerpMarket]
|
||||||
|
)
|
||||||
|
|
||||||
|
const onKeyDown = useCallback(
|
||||||
|
(keyName: string) => {
|
||||||
|
const orderDetails = hotKeys.find(
|
||||||
|
(hk: HotKey) => hk.keySequence === keyName
|
||||||
|
)
|
||||||
|
if (orderDetails) {
|
||||||
|
handlePlaceOrder(orderDetails)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[handlePlaceOrder, hotKeys]
|
||||||
|
)
|
||||||
|
|
||||||
|
const showHotKeys =
|
||||||
|
hotKeys.length &&
|
||||||
|
asPath.includes('/trade') &&
|
||||||
|
mangoAccountAddress &&
|
||||||
|
!isUnownedAccount
|
||||||
|
|
||||||
|
return showHotKeys ? (
|
||||||
|
<Hotkeys
|
||||||
|
keyName={hotKeys.map((k: HotKey) => k.keySequence).toString()}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Hotkeys>
|
||||||
|
) : (
|
||||||
|
<>{children}</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TradeHotKeys
|
|
@ -65,6 +65,7 @@
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-flip-numbers": "3.0.5",
|
"react-flip-numbers": "3.0.5",
|
||||||
"react-grid-layout": "1.3.4",
|
"react-grid-layout": "1.3.4",
|
||||||
|
"react-hot-keys": "2.7.2",
|
||||||
"react-nice-dates": "3.1.0",
|
"react-nice-dates": "3.1.0",
|
||||||
"react-number-format": "4.9.2",
|
"react-number-format": "4.9.2",
|
||||||
"react-tsparticles": "2.2.4",
|
"react-tsparticles": "2.2.4",
|
||||||
|
|
|
@ -17,6 +17,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
|
||||||
'profile',
|
'profile',
|
||||||
'search',
|
'search',
|
||||||
'settings',
|
'settings',
|
||||||
|
'trade',
|
||||||
])),
|
])),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
{
|
{
|
||||||
|
"above": "Above",
|
||||||
"animations": "Animations",
|
"animations": "Animations",
|
||||||
|
"at": "at",
|
||||||
"avocado": "Avocado",
|
"avocado": "Avocado",
|
||||||
"banana": "Banana",
|
"banana": "Banana",
|
||||||
|
"base-key": "Base Key",
|
||||||
|
"below": "Below",
|
||||||
"blueberry": "Blueberry",
|
"blueberry": "Blueberry",
|
||||||
"bottom-left": "Bottom-Left",
|
"bottom-left": "Bottom-Left",
|
||||||
"bottom-right": "Bottom-Right",
|
"bottom-right": "Bottom-Right",
|
||||||
|
@ -16,18 +20,40 @@
|
||||||
"custom": "Custom",
|
"custom": "Custom",
|
||||||
"dark": "Dark",
|
"dark": "Dark",
|
||||||
"display": "Display",
|
"display": "Display",
|
||||||
|
"error-alphanumeric-only": "Alphanumeric characters only",
|
||||||
|
"error-key-in-use": "Hot key already in use. Choose a unique key",
|
||||||
|
"error-key-limit-reached": "You've reached the maximum number of hot keys",
|
||||||
|
"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",
|
"english": "English",
|
||||||
"high-contrast": "High Contrast",
|
"high-contrast": "High Contrast",
|
||||||
|
"hot-keys": "Hot Keys",
|
||||||
|
"hot-keys-desc": "Use hot keys to place orders on the trade page. They execute on the market you're viewing and are not market specific.",
|
||||||
|
"key-sequence": "Key Sequence",
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
"light": "Light",
|
"light": "Light",
|
||||||
"lychee": "Lychee",
|
"lychee": "Lychee",
|
||||||
"mango": "Mango",
|
"mango": "Mango",
|
||||||
"mango-classic": "Mango Classic",
|
"mango-classic": "Mango Classic",
|
||||||
"medium": "Medium",
|
"medium": "Medium",
|
||||||
|
"new-hot-key": "New Hot Key",
|
||||||
|
"no-hot-keys": "Create your first hot key",
|
||||||
"notification-position": "Notification Position",
|
"notification-position": "Notification Position",
|
||||||
|
"notional": "Notional",
|
||||||
"number-scroll": "Number Scroll",
|
"number-scroll": "Number Scroll",
|
||||||
"olive": "Olive",
|
"olive": "Olive",
|
||||||
|
"options": "Options",
|
||||||
|
"oracle": "Oracle",
|
||||||
"orderbook-flash": "Orderbook Flash",
|
"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",
|
"preferred-explorer": "Preferred Explorer",
|
||||||
"recent-trades": "Recent Trades",
|
"recent-trades": "Recent Trades",
|
||||||
"rpc": "RPC",
|
"rpc": "RPC",
|
||||||
|
@ -35,6 +61,7 @@
|
||||||
"rpc-url": "Enter RPC URL",
|
"rpc-url": "Enter RPC URL",
|
||||||
"russian": "Русский",
|
"russian": "Русский",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
|
"save-hot-key": "Save Hot Key",
|
||||||
"slider": "Slider",
|
"slider": "Slider",
|
||||||
"solana-beach": "Solana Beach",
|
"solana-beach": "Solana Beach",
|
||||||
"solana-explorer": "Solana Explorer",
|
"solana-explorer": "Solana Explorer",
|
||||||
|
@ -45,6 +72,9 @@
|
||||||
"swap-success": "Swap/Trade Success",
|
"swap-success": "Swap/Trade Success",
|
||||||
"swap-trade-size-selector": "Swap/Trade Size Selector",
|
"swap-trade-size-selector": "Swap/Trade Size Selector",
|
||||||
"theme": "Theme",
|
"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-left": "Top-Left",
|
||||||
"top-right": "Top-Right",
|
"top-right": "Top-Right",
|
||||||
"trade-layout": "Trade Layout",
|
"trade-layout": "Trade Layout",
|
||||||
|
@ -52,6 +82,7 @@
|
||||||
"transaction-success": "Transaction Success",
|
"transaction-success": "Transaction Success",
|
||||||
"trade-chart": "Trade Chart",
|
"trade-chart": "Trade Chart",
|
||||||
"trading-view": "Trading View",
|
"trading-view": "Trading View",
|
||||||
|
"trigger-key": "Trigger Key",
|
||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
"limit-order-filled": "Limit Order Fills",
|
"limit-order-filled": "Limit Order Fills",
|
||||||
"orderbook-bandwidth-saving": "Orderbook Bandwidth Saving",
|
"orderbook-bandwidth-saving": "Orderbook Bandwidth Saving",
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
"maker": "Maker",
|
"maker": "Maker",
|
||||||
"maker-fee": "Maker Fee",
|
"maker-fee": "Maker Fee",
|
||||||
"margin": "Margin",
|
"margin": "Margin",
|
||||||
|
"market": "Market",
|
||||||
"market-details": "{{market}} Market Details",
|
"market-details": "{{market}} Market Details",
|
||||||
"max-leverage": "Max Leverage",
|
"max-leverage": "Max Leverage",
|
||||||
"min-order-size": "Min Order Size",
|
"min-order-size": "Min Order Size",
|
||||||
|
@ -65,6 +66,7 @@
|
||||||
"price-provided-by": "Oracle by",
|
"price-provided-by": "Oracle by",
|
||||||
"quote": "Quote",
|
"quote": "Quote",
|
||||||
"realized-pnl": "Realized PnL",
|
"realized-pnl": "Realized PnL",
|
||||||
|
"reduce": "Reduce",
|
||||||
"reduce-only": "Reduce Only",
|
"reduce-only": "Reduce Only",
|
||||||
"sells": "Sells",
|
"sells": "Sells",
|
||||||
"settle-funds": "Settle Funds",
|
"settle-funds": "Settle Funds",
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
{
|
{
|
||||||
|
"above": "Above",
|
||||||
"animations": "Animations",
|
"animations": "Animations",
|
||||||
|
"at": "at",
|
||||||
"avocado": "Avocado",
|
"avocado": "Avocado",
|
||||||
"banana": "Banana",
|
"banana": "Banana",
|
||||||
|
"base-key": "Base Key",
|
||||||
|
"below": "Below",
|
||||||
"blueberry": "Blueberry",
|
"blueberry": "Blueberry",
|
||||||
"bottom-left": "Bottom-Left",
|
"bottom-left": "Bottom-Left",
|
||||||
"bottom-right": "Bottom-Right",
|
"bottom-right": "Bottom-Right",
|
||||||
|
@ -16,18 +20,40 @@
|
||||||
"custom": "Custom",
|
"custom": "Custom",
|
||||||
"dark": "Dark",
|
"dark": "Dark",
|
||||||
"display": "Display",
|
"display": "Display",
|
||||||
|
"error-alphanumeric-only": "Alphanumeric characters only",
|
||||||
|
"error-key-in-use": "Hot key already in use. Choose a unique key",
|
||||||
|
"error-key-limit-reached": "You've reached the maximum number of hot keys",
|
||||||
|
"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",
|
"english": "English",
|
||||||
"high-contrast": "High Contrast",
|
"high-contrast": "High Contrast",
|
||||||
|
"hot-keys": "Hot Keys",
|
||||||
|
"hot-keys-desc": "Use hot keys to place orders on the trade page. They execute on the market you're viewing and are not market specific.",
|
||||||
|
"key-sequence": "Key Sequence",
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
"light": "Light",
|
"light": "Light",
|
||||||
"lychee": "Lychee",
|
"lychee": "Lychee",
|
||||||
"mango": "Mango",
|
"mango": "Mango",
|
||||||
"mango-classic": "Mango Classic",
|
"mango-classic": "Mango Classic",
|
||||||
"medium": "Medium",
|
"medium": "Medium",
|
||||||
|
"new-hot-key": "New Hot Key",
|
||||||
|
"no-hot-keys": "Create your first hot key",
|
||||||
"notification-position": "Notification Position",
|
"notification-position": "Notification Position",
|
||||||
|
"notional": "Notional",
|
||||||
"number-scroll": "Number Scroll",
|
"number-scroll": "Number Scroll",
|
||||||
"olive": "Olive",
|
"olive": "Olive",
|
||||||
|
"options": "Options",
|
||||||
|
"oracle": "Oracle",
|
||||||
"orderbook-flash": "Orderbook Flash",
|
"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",
|
"preferred-explorer": "Preferred Explorer",
|
||||||
"recent-trades": "Recent Trades",
|
"recent-trades": "Recent Trades",
|
||||||
"rpc": "RPC",
|
"rpc": "RPC",
|
||||||
|
@ -35,6 +61,7 @@
|
||||||
"rpc-url": "Enter RPC URL",
|
"rpc-url": "Enter RPC URL",
|
||||||
"russian": "Русский",
|
"russian": "Русский",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
|
"save-hot-key": "Save Hot Key",
|
||||||
"slider": "Slider",
|
"slider": "Slider",
|
||||||
"solana-beach": "Solana Beach",
|
"solana-beach": "Solana Beach",
|
||||||
"solana-explorer": "Solana Explorer",
|
"solana-explorer": "Solana Explorer",
|
||||||
|
@ -45,6 +72,9 @@
|
||||||
"swap-success": "Swap/Trade Success",
|
"swap-success": "Swap/Trade Success",
|
||||||
"swap-trade-size-selector": "Swap/Trade Size Selector",
|
"swap-trade-size-selector": "Swap/Trade Size Selector",
|
||||||
"theme": "Theme",
|
"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-left": "Top-Left",
|
||||||
"top-right": "Top-Right",
|
"top-right": "Top-Right",
|
||||||
"trade-layout": "Trade Layout",
|
"trade-layout": "Trade Layout",
|
||||||
|
@ -52,6 +82,7 @@
|
||||||
"transaction-success": "Transaction Success",
|
"transaction-success": "Transaction Success",
|
||||||
"trade-chart": "Trade Chart",
|
"trade-chart": "Trade Chart",
|
||||||
"trading-view": "Trading View",
|
"trading-view": "Trading View",
|
||||||
|
"trigger-key": "Trigger Key",
|
||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
"limit-order-filled": "Limit Order Fills",
|
"limit-order-filled": "Limit Order Fills",
|
||||||
"orderbook-bandwidth-saving": "Orderbook Bandwidth Saving",
|
"orderbook-bandwidth-saving": "Orderbook Bandwidth Saving",
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
"maker": "Maker",
|
"maker": "Maker",
|
||||||
"maker-fee": "Maker Fee",
|
"maker-fee": "Maker Fee",
|
||||||
"margin": "Margin",
|
"margin": "Margin",
|
||||||
|
"market": "Market",
|
||||||
"market-details": "{{market}} Market Details",
|
"market-details": "{{market}} Market Details",
|
||||||
"max-leverage": "Max Leverage",
|
"max-leverage": "Max Leverage",
|
||||||
"min-order-size": "Min Order Size",
|
"min-order-size": "Min Order Size",
|
||||||
|
@ -65,6 +66,7 @@
|
||||||
"price-provided-by": "Oracle by",
|
"price-provided-by": "Oracle by",
|
||||||
"quote": "Quote",
|
"quote": "Quote",
|
||||||
"realized-pnl": "Realized PnL",
|
"realized-pnl": "Realized PnL",
|
||||||
|
"reduce": "Reduce",
|
||||||
"reduce-only": "Reduce Only",
|
"reduce-only": "Reduce Only",
|
||||||
"sells": "Sells",
|
"sells": "Sells",
|
||||||
"settle-funds": "Settle Funds",
|
"settle-funds": "Settle Funds",
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
{
|
{
|
||||||
|
"above": "Above",
|
||||||
"animations": "Animations",
|
"animations": "Animations",
|
||||||
|
"at": "at",
|
||||||
"avocado": "Avocado",
|
"avocado": "Avocado",
|
||||||
"banana": "Banana",
|
"banana": "Banana",
|
||||||
|
"base-key": "Base Key",
|
||||||
|
"below": "Below",
|
||||||
"blueberry": "Blueberry",
|
"blueberry": "Blueberry",
|
||||||
"bottom-left": "Bottom-Left",
|
"bottom-left": "Bottom-Left",
|
||||||
"bottom-right": "Bottom-Right",
|
"bottom-right": "Bottom-Right",
|
||||||
|
@ -16,18 +20,40 @@
|
||||||
"custom": "Custom",
|
"custom": "Custom",
|
||||||
"dark": "Dark",
|
"dark": "Dark",
|
||||||
"display": "Display",
|
"display": "Display",
|
||||||
|
"error-alphanumeric-only": "Alphanumeric characters only",
|
||||||
|
"error-key-in-use": "Hot key already in use. Choose a unique key",
|
||||||
|
"error-key-limit-reached": "You've reached the maximum number of hot keys",
|
||||||
|
"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",
|
"english": "English",
|
||||||
"high-contrast": "High Contrast",
|
"high-contrast": "High Contrast",
|
||||||
|
"hot-keys": "Hot Keys",
|
||||||
|
"hot-keys-desc": "Use hot keys to place orders on the trade page. They execute on the market you're viewing and are not market specific.",
|
||||||
|
"key-sequence": "Key Sequence",
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
"light": "Light",
|
"light": "Light",
|
||||||
"lychee": "Lychee",
|
"lychee": "Lychee",
|
||||||
"mango": "Mango",
|
"mango": "Mango",
|
||||||
"mango-classic": "Mango Classic",
|
"mango-classic": "Mango Classic",
|
||||||
"medium": "Medium",
|
"medium": "Medium",
|
||||||
|
"new-hot-key": "New Hot Key",
|
||||||
|
"no-hot-keys": "Create your first hot key",
|
||||||
"notification-position": "Notification Position",
|
"notification-position": "Notification Position",
|
||||||
|
"notional": "Notional",
|
||||||
"number-scroll": "Number Scroll",
|
"number-scroll": "Number Scroll",
|
||||||
"olive": "Olive",
|
"olive": "Olive",
|
||||||
|
"options": "Options",
|
||||||
|
"oracle": "Oracle",
|
||||||
"orderbook-flash": "Orderbook Flash",
|
"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",
|
"preferred-explorer": "Preferred Explorer",
|
||||||
"recent-trades": "Recent Trades",
|
"recent-trades": "Recent Trades",
|
||||||
"rpc": "RPC",
|
"rpc": "RPC",
|
||||||
|
@ -35,6 +61,7 @@
|
||||||
"rpc-url": "Enter RPC URL",
|
"rpc-url": "Enter RPC URL",
|
||||||
"russian": "Русский",
|
"russian": "Русский",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
|
"save-hot-key": "Save Hot Key",
|
||||||
"slider": "Slider",
|
"slider": "Slider",
|
||||||
"solana-beach": "Solana Beach",
|
"solana-beach": "Solana Beach",
|
||||||
"solana-explorer": "Solana Explorer",
|
"solana-explorer": "Solana Explorer",
|
||||||
|
@ -45,6 +72,9 @@
|
||||||
"swap-success": "Swap/Trade Success",
|
"swap-success": "Swap/Trade Success",
|
||||||
"swap-trade-size-selector": "Swap/Trade Size Selector",
|
"swap-trade-size-selector": "Swap/Trade Size Selector",
|
||||||
"theme": "Theme",
|
"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-left": "Top-Left",
|
||||||
"top-right": "Top-Right",
|
"top-right": "Top-Right",
|
||||||
"trade-layout": "Trade Layout",
|
"trade-layout": "Trade Layout",
|
||||||
|
@ -52,6 +82,7 @@
|
||||||
"transaction-success": "Transaction Success",
|
"transaction-success": "Transaction Success",
|
||||||
"trade-chart": "Trade Chart",
|
"trade-chart": "Trade Chart",
|
||||||
"trading-view": "Trading View",
|
"trading-view": "Trading View",
|
||||||
|
"trigger-key": "Trigger Key",
|
||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
"limit-order-filled": "Limit Order Fills",
|
"limit-order-filled": "Limit Order Fills",
|
||||||
"orderbook-bandwidth-saving": "Orderbook Bandwidth Saving",
|
"orderbook-bandwidth-saving": "Orderbook Bandwidth Saving",
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
"maker": "Maker",
|
"maker": "Maker",
|
||||||
"maker-fee": "Maker Fee",
|
"maker-fee": "Maker Fee",
|
||||||
"margin": "Margin",
|
"margin": "Margin",
|
||||||
|
"market": "Market",
|
||||||
"market-details": "{{market}} Market Details",
|
"market-details": "{{market}} Market Details",
|
||||||
"max-leverage": "Max Leverage",
|
"max-leverage": "Max Leverage",
|
||||||
"min-order-size": "Min Order Size",
|
"min-order-size": "Min Order Size",
|
||||||
|
@ -65,6 +66,7 @@
|
||||||
"price-provided-by": "Oracle by",
|
"price-provided-by": "Oracle by",
|
||||||
"quote": "Quote",
|
"quote": "Quote",
|
||||||
"realized-pnl": "Realized PnL",
|
"realized-pnl": "Realized PnL",
|
||||||
|
"reduce": "Reduce",
|
||||||
"reduce-only": "Reduce Only",
|
"reduce-only": "Reduce Only",
|
||||||
"sells": "Sells",
|
"sells": "Sells",
|
||||||
"settle-funds": "Settle Funds",
|
"settle-funds": "Settle Funds",
|
||||||
|
|
|
@ -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,8 +20,20 @@
|
||||||
"custom": "自定",
|
"custom": "自定",
|
||||||
"dark": "暗",
|
"dark": "暗",
|
||||||
"display": "显示",
|
"display": "显示",
|
||||||
|
"error-alphanumeric-only": "Alphanumeric characters only",
|
||||||
|
"error-key-in-use": "Hot key already in use. Choose a unique key",
|
||||||
|
"error-key-limit-reached": "You've reached the maximum number of hot keys",
|
||||||
|
"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",
|
"english": "English",
|
||||||
"high-contrast": "高对比度",
|
"high-contrast": "高对比度",
|
||||||
|
"hot-keys": "Hot Keys",
|
||||||
|
"hot-keys-desc": "Use hot keys to place orders on the trade page. They execute on the market you're viewing and are not market specific.",
|
||||||
|
"key-sequence": "Key Sequence",
|
||||||
"language": "语言",
|
"language": "语言",
|
||||||
"light": "光",
|
"light": "光",
|
||||||
"limit-order-filled": "限价单成交",
|
"limit-order-filled": "限价单成交",
|
||||||
|
@ -25,11 +41,21 @@
|
||||||
"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",
|
||||||
"notifications": "通知",
|
"notifications": "通知",
|
||||||
"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",
|
"rpc": "RPC",
|
||||||
|
@ -37,6 +63,7 @@
|
||||||
"rpc-url": "输入RPC URL",
|
"rpc-url": "输入RPC URL",
|
||||||
"russian": "Русский",
|
"russian": "Русский",
|
||||||
"save": "存",
|
"save": "存",
|
||||||
|
"save-hot-key": "Save Hot Key",
|
||||||
"sign-to-notifications": "登录通知中心以更改设置",
|
"sign-to-notifications": "登录通知中心以更改设置",
|
||||||
"slider": "滑块",
|
"slider": "滑块",
|
||||||
"solana-beach": "Solana Beach",
|
"solana-beach": "Solana Beach",
|
||||||
|
@ -48,11 +75,15 @@
|
||||||
"swap-success": "换币/交易成功",
|
"swap-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-chart": "交易图表",
|
"trade-chart": "交易图表",
|
||||||
"trade-layout": "交易布局",
|
"trade-layout": "交易布局",
|
||||||
"trading-view": "Trading View",
|
"trading-view": "Trading View",
|
||||||
|
"trigger-key": "Trigger Key",
|
||||||
"transaction-fail": "交易失败",
|
"transaction-fail": "交易失败",
|
||||||
"transaction-success": "交易成功",
|
"transaction-success": "交易成功",
|
||||||
"orderbook-bandwidth-saving": "Orderbook Bandwidth Saving",
|
"orderbook-bandwidth-saving": "Orderbook Bandwidth Saving",
|
||||||
|
|
|
@ -31,14 +31,15 @@
|
||||||
"insured": "{{token}} Insured",
|
"insured": "{{token}} Insured",
|
||||||
"last-updated": "Last updated",
|
"last-updated": "Last updated",
|
||||||
"limit": "Limit",
|
"limit": "Limit",
|
||||||
"limit-price": "Limit Price",
|
"limit-price": "限价价格",
|
||||||
"long": "Long",
|
"long": "做多",
|
||||||
"maker": "Maker",
|
"maker": "挂单者",
|
||||||
|
"margin": "保证金",
|
||||||
|
"market": "Market",
|
||||||
|
"market-details": "{{market}}市场细节",
|
||||||
|
"max-leverage": "最多杠杆",
|
||||||
|
"min-order-size": "最小订单量",
|
||||||
"maker-fee": "Maker Fee",
|
"maker-fee": "Maker Fee",
|
||||||
"margin": "Margin",
|
|
||||||
"market-details": "{{market}} Market Details",
|
|
||||||
"max-leverage": "Max Leverage",
|
|
||||||
"min-order-size": "Min Order Size",
|
|
||||||
"min-order-size-error": "Min order size is {{minSize}} {{symbol}}",
|
"min-order-size-error": "Min order size is {{minSize}} {{symbol}}",
|
||||||
"more-details": "More Details",
|
"more-details": "More Details",
|
||||||
"no-balances": "No balances",
|
"no-balances": "No balances",
|
||||||
|
@ -60,25 +61,25 @@
|
||||||
"placing-order": "Placing Order",
|
"placing-order": "Placing Order",
|
||||||
"positions": "Positions",
|
"positions": "Positions",
|
||||||
"post": "Post",
|
"post": "Post",
|
||||||
"preview-sound": "Preview Sound",
|
"preview-sound": "声音预览",
|
||||||
"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-expect": "您收到的价格可能与您预期有差异,并且无法保证完全执行。为了您的安全,最大滑点保持为 2.5%。超过 2.5%滑点的部分不会被平仓。",
|
||||||
"price-provided-by": "Oracle by",
|
"price-provided-by": "语言机来自",
|
||||||
"quote": "Quote",
|
"quote": "计价",
|
||||||
"realized-pnl": "Realized PnL",
|
"reduce": "Reduce",
|
||||||
"reduce-only": "Reduce Only",
|
"reduce-only": "限减少",
|
||||||
"sells": "Sells",
|
"sells": "卖单",
|
||||||
"settle-funds": "Settle Funds",
|
"settle-funds": "借清资金",
|
||||||
"settle-funds-error": "Failed to settle funds",
|
"settle-funds-error": "借清出错",
|
||||||
"short": "Short",
|
"short": "做空",
|
||||||
"show-asks": "Show Asks",
|
"show-asks": "显示要价",
|
||||||
"show-bids": "Show Bids",
|
"show-bids": "显示出价",
|
||||||
"side": "Side",
|
"side": "方向",
|
||||||
"size": "Size",
|
"size": "數量",
|
||||||
"spread": "Spread",
|
"spread": "差價",
|
||||||
"stable-price": "Stable Price",
|
"stable-price": "穩定價格",
|
||||||
"taker": "Taker",
|
"taker": "吃單者",
|
||||||
|
"tick-size": "波動單位",
|
||||||
"taker-fee": "Taker Fee",
|
"taker-fee": "Taker Fee",
|
||||||
"tick-size": "Tick Size",
|
|
||||||
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this trade. The current {{token}} variable borrow rate is {{rate}}%",
|
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this trade. The current {{token}} variable borrow rate is {{rate}}%",
|
||||||
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this trade. The current {{token}} variable borrow rate is {{rate}}%",
|
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this trade. The current {{token}} variable borrow rate is {{rate}}%",
|
||||||
"tooltip-enable-margin": "Enable spot margin for this trade",
|
"tooltip-enable-margin": "Enable spot margin for this 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,8 +20,20 @@
|
||||||
"custom": "自定",
|
"custom": "自定",
|
||||||
"dark": "暗",
|
"dark": "暗",
|
||||||
"display": "顯示",
|
"display": "顯示",
|
||||||
|
"error-alphanumeric-only": "Alphanumeric characters only",
|
||||||
|
"error-key-in-use": "Hot key already in use. Choose a unique key",
|
||||||
|
"error-key-limit-reached": "You've reached the maximum number of hot keys",
|
||||||
|
"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",
|
"english": "English",
|
||||||
"high-contrast": "高對比度",
|
"high-contrast": "高對比度",
|
||||||
|
"hot-keys": "Hot Keys",
|
||||||
|
"hot-keys-desc": "Use hot keys to place orders on the trade page. They execute on the market you're viewing and are not market specific.",
|
||||||
|
"key-sequence": "Key Sequence",
|
||||||
"language": "語言",
|
"language": "語言",
|
||||||
"light": "光",
|
"light": "光",
|
||||||
"limit-order-filled": "限价单成交",
|
"limit-order-filled": "限价单成交",
|
||||||
|
@ -25,11 +41,21 @@
|
||||||
"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",
|
||||||
"notifications": "通知",
|
"notifications": "通知",
|
||||||
"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",
|
"rpc": "RPC",
|
||||||
|
@ -37,6 +63,7 @@
|
||||||
"rpc-url": "輸入RPC URL",
|
"rpc-url": "輸入RPC URL",
|
||||||
"russian": "Русский",
|
"russian": "Русский",
|
||||||
"save": "存",
|
"save": "存",
|
||||||
|
"save-hot-key": "Save Hot Key",
|
||||||
"sign-to-notifications": "登录通知中心以更改设置",
|
"sign-to-notifications": "登录通知中心以更改设置",
|
||||||
"slider": "滑塊",
|
"slider": "滑塊",
|
||||||
"solana-beach": "Solana Beach",
|
"solana-beach": "Solana Beach",
|
||||||
|
@ -48,11 +75,15 @@
|
||||||
"swap-success": "換幣/交易成功",
|
"swap-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-chart": "交易圖表",
|
"trade-chart": "交易圖表",
|
||||||
"trade-layout": "交易佈局",
|
"trade-layout": "交易佈局",
|
||||||
"trading-view": "Trading View",
|
"trading-view": "Trading View",
|
||||||
|
"trigger-key": "Trigger Key",
|
||||||
"transaction-fail": "交易失敗",
|
"transaction-fail": "交易失敗",
|
||||||
"transaction-success": "交易成功",
|
"transaction-success": "交易成功",
|
||||||
"orderbook-bandwidth-saving": "Orderbook Bandwidth Saving",
|
"orderbook-bandwidth-saving": "Orderbook Bandwidth Saving",
|
||||||
|
|
|
@ -31,14 +31,15 @@
|
||||||
"insured": "{{token}} Insured",
|
"insured": "{{token}} Insured",
|
||||||
"last-updated": "Last updated",
|
"last-updated": "Last updated",
|
||||||
"limit": "Limit",
|
"limit": "Limit",
|
||||||
"limit-price": "Limit Price",
|
"limit-price": "限價價格",
|
||||||
"long": "Long",
|
"long": "做多",
|
||||||
"maker": "Maker",
|
"maker": "掛單者",
|
||||||
"maker-fee": "Maker Fee",
|
"maker-fee": "掛單者 Fee",
|
||||||
"margin": "Margin",
|
"margin": "保證金",
|
||||||
"market-details": "{{market}} Market Details",
|
"market": "Market",
|
||||||
"max-leverage": "Max Leverage",
|
"market-details": "{{market}}市場細節",
|
||||||
"min-order-size": "Min Order Size",
|
"max-leverage": "最多槓桿",
|
||||||
|
"min-order-size": "最小訂單量",
|
||||||
"min-order-size-error": "Min order size is {{minSize}} {{symbol}}",
|
"min-order-size-error": "Min order size is {{minSize}} {{symbol}}",
|
||||||
"more-details": "More Details",
|
"more-details": "More Details",
|
||||||
"no-balances": "No balances",
|
"no-balances": "No balances",
|
||||||
|
@ -60,25 +61,26 @@
|
||||||
"placing-order": "Placing Order",
|
"placing-order": "Placing Order",
|
||||||
"positions": "Positions",
|
"positions": "Positions",
|
||||||
"post": "Post",
|
"post": "Post",
|
||||||
"preview-sound": "Preview Sound",
|
"preview-sound": "聲音預覽",
|
||||||
"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-expect": "您收到的價格可能與您預期有差異,並且無法保證完全執行。為了您的安全,最大滑點保持為 2.5%。超過 2.5%滑點的部分不會被平倉。",
|
||||||
"price-provided-by": "Oracle by",
|
"price-provided-by": "語言機來自",
|
||||||
"quote": "Quote",
|
"quote": "計價",
|
||||||
|
"reduce": "Reduce",
|
||||||
|
"reduce-only": "限減少",
|
||||||
|
"sells": "賣單",
|
||||||
|
"settle-funds": "借清資金",
|
||||||
|
"settle-funds-error": "借清出錯",
|
||||||
|
"short": "做空",
|
||||||
|
"show-asks": "顯示要價",
|
||||||
|
"show-bids": "顯示出價",
|
||||||
|
"side": "方向",
|
||||||
|
"size": "數量",
|
||||||
|
"spread": "差價",
|
||||||
|
"stable-price": "穩定價格",
|
||||||
|
"taker": "吃單者",
|
||||||
|
"taker-fee": "吃單者 Fee",
|
||||||
|
"tick-size": "波動單位",
|
||||||
"realized-pnl": "Realized PnL",
|
"realized-pnl": "Realized PnL",
|
||||||
"reduce-only": "Reduce Only",
|
|
||||||
"sells": "Sells",
|
|
||||||
"settle-funds": "Settle Funds",
|
|
||||||
"settle-funds-error": "Failed to settle funds",
|
|
||||||
"short": "Short",
|
|
||||||
"show-asks": "Show Asks",
|
|
||||||
"show-bids": "Show Bids",
|
|
||||||
"side": "Side",
|
|
||||||
"size": "Size",
|
|
||||||
"spread": "Spread",
|
|
||||||
"stable-price": "Stable Price",
|
|
||||||
"taker": "Taker",
|
|
||||||
"taker-fee": "Taker Fee",
|
|
||||||
"tick-size": "Tick Size",
|
|
||||||
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this trade. The current {{token}} variable borrow rate is {{rate}}%",
|
"tooltip-borrow-balance": "You'll use your {{balance}} {{token}} balance and borrow {{borrowAmount}} {{token}} to execute this trade. The current {{token}} variable borrow rate is {{rate}}%",
|
||||||
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this trade. The current {{token}} variable borrow rate is {{rate}}%",
|
"tooltip-borrow-no-balance": "You'll borrow {{borrowAmount}} {{token}} to execute this trade. The current {{token}} variable borrow rate is {{rate}}%",
|
||||||
"tooltip-enable-margin": "Enable spot margin for this trade",
|
"tooltip-enable-margin": "Enable spot margin for this trade",
|
||||||
|
|
|
@ -59,6 +59,8 @@ export const STATS_TAB_KEY = 'activeStatsTab-0.1'
|
||||||
|
|
||||||
export const USE_ORDERBOOK_FEED_KEY = 'useOrderbookFeed-0.1'
|
export const USE_ORDERBOOK_FEED_KEY = 'useOrderbookFeed-0.1'
|
||||||
|
|
||||||
|
export const HOT_KEYS_KEY = 'hotKeys-0.1'
|
||||||
|
|
||||||
// Unused
|
// Unused
|
||||||
export const PROFILE_CATEGORIES = [
|
export const PROFILE_CATEGORIES = [
|
||||||
'borrower',
|
'borrower',
|
||||||
|
|
13
yarn.lock
13
yarn.lock
|
@ -5379,6 +5379,11 @@ hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
react-is "^16.7.0"
|
react-is "^16.7.0"
|
||||||
|
|
||||||
|
hotkeys-js@^3.8.1:
|
||||||
|
version "3.10.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/hotkeys-js/-/hotkeys-js-3.10.2.tgz#cf52661904f5a13a973565cb97085fea2f5ae257"
|
||||||
|
integrity sha512-Z6vLmJTYzkbZZXlBkhrYB962Q/rZGc/WHQiyEGu9ZZVF7bAeFDjjDa31grWREuw9Ygb4zmlov2bTkPYqj0aFnQ==
|
||||||
|
|
||||||
howler@2.2.3:
|
howler@2.2.3:
|
||||||
version "2.2.3"
|
version "2.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/howler/-/howler-2.2.3.tgz#a2eff9b08b586798e7a2ee17a602a90df28715da"
|
resolved "https://registry.yarnpkg.com/howler/-/howler-2.2.3.tgz#a2eff9b08b586798e7a2ee17a602a90df28715da"
|
||||||
|
@ -7139,6 +7144,14 @@ react-grid-layout@1.3.4:
|
||||||
react-draggable "^4.0.0"
|
react-draggable "^4.0.0"
|
||||||
react-resizable "^3.0.4"
|
react-resizable "^3.0.4"
|
||||||
|
|
||||||
|
react-hot-keys@2.7.2:
|
||||||
|
version "2.7.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-hot-keys/-/react-hot-keys-2.7.2.tgz#7d2b02b7e2cf69182ea71ca01885446ebfae01d2"
|
||||||
|
integrity sha512-Z7eSh7SU6s52+zP+vkfFoNk0x4kgEmnwqDiyACKv53crK2AZ7FUaBLnf+vxLor3dvtId9murLmKOsrJeYgeHWw==
|
||||||
|
dependencies:
|
||||||
|
hotkeys-js "^3.8.1"
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
|
||||||
react-i18next@^11.18.0:
|
react-i18next@^11.18.0:
|
||||||
version "11.18.6"
|
version "11.18.6"
|
||||||
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.18.6.tgz#e159c2960c718c1314f1e8fcaa282d1c8b167887"
|
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.18.6.tgz#e159c2960c718c1314f1e8fcaa282d1c8b167887"
|
||||||
|
|
Loading…
Reference in New Issue