Merge branch 'main' of github.com:blockworks-foundation/mango-v4-ui
This commit is contained in:
commit
c0e378b65a
|
@ -122,7 +122,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
|
|||
</div>
|
||||
{/* note: overflow-x-hidden below prevents position sticky from working in activity feed */}
|
||||
<div
|
||||
className={`w-full overflow-x-hidden transition-all duration-${sideBarAnimationDuration} ease-in-out ${
|
||||
className={`w-full transition-all duration-${sideBarAnimationDuration} ease-in-out ${
|
||||
isCollapsed ? 'md:pl-[64px]' : 'pl-[200px]'
|
||||
}`}
|
||||
>
|
||||
|
|
|
@ -11,7 +11,6 @@ import useOpenPerpPositions from 'hooks/useOpenPerpPositions'
|
|||
import HistoryTabs from './HistoryTabs'
|
||||
import ManualRefresh from '@components/shared/ManualRefresh'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import SwapTriggerOrders from '@components/swap/SwapTriggerOrders'
|
||||
import AccountOverview from './AccountOverview'
|
||||
import AccountOrders from './AccountOrders'
|
||||
|
||||
|
@ -88,8 +87,6 @@ const TabContent = ({ activeTab }: { activeTab: string }) => {
|
|||
return <PerpPositions />
|
||||
case 'trade:orders':
|
||||
return <AccountOrders />
|
||||
case 'trade:trigger-orders':
|
||||
return <SwapTriggerOrders />
|
||||
case 'trade:unsettled':
|
||||
return (
|
||||
<UnsettledTrades
|
||||
|
|
|
@ -171,12 +171,15 @@ const ModifyTvOrderModal = ({
|
|||
/>
|
||||
</div>
|
||||
<div className="mb-6">
|
||||
<MaxSizeButton
|
||||
minOrderDecimals={minOrderDecimals}
|
||||
tickDecimals={tickDecimals}
|
||||
useMargin={savedCheckboxSettings.margin}
|
||||
large
|
||||
/>
|
||||
<div className="mb-2 mt-3 flex items-center justify-between">
|
||||
<p className="text-th-fgd-3">{t('trade:size')}</p>
|
||||
<MaxSizeButton
|
||||
minOrderDecimals={minOrderDecimals}
|
||||
tickDecimals={tickDecimals}
|
||||
useMargin={savedCheckboxSettings.margin}
|
||||
large
|
||||
/>
|
||||
</div>
|
||||
<NumberFormat
|
||||
name="size"
|
||||
id="size"
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import { useMemo } from 'react'
|
||||
|
||||
const TradePriceDifference = ({
|
||||
currentPrice,
|
||||
newPrice,
|
||||
}: {
|
||||
currentPrice: number | undefined
|
||||
newPrice: number | undefined
|
||||
}) => {
|
||||
const priceDifference = useMemo(() => {
|
||||
if (!currentPrice || !newPrice) return 0
|
||||
const difference = ((newPrice - currentPrice) / currentPrice) * 100
|
||||
return difference
|
||||
}, [currentPrice, newPrice])
|
||||
|
||||
return (
|
||||
<p
|
||||
className={`font-mono text-xs ${
|
||||
priceDifference >= 0 ? 'text-th-up' : 'text-th-down'
|
||||
}`}
|
||||
>
|
||||
{priceDifference
|
||||
? (priceDifference > 0 ? '+' : '') + priceDifference.toFixed(2)
|
||||
: '0.00'}
|
||||
%
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
export default TradePriceDifference
|
|
@ -14,7 +14,7 @@ import { NUMBER_FORMAT_CLASSNAMES } from './MarketSwapForm'
|
|||
import InlineNotification from '@components/shared/InlineNotification'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import { SwapFormTokenListType } from './SwapFormTokenList'
|
||||
import { getInputTokenBalance } from './TriggerSwapForm'
|
||||
import { getTokenBalance } from './TriggerSwapForm'
|
||||
|
||||
const ReduceOutputTokenInput = ({
|
||||
error,
|
||||
|
@ -38,7 +38,7 @@ const ReduceOutputTokenInput = ({
|
|||
|
||||
const reducingLong = useMemo(() => {
|
||||
if (!inputBank || !mangoAccountAddress) return false
|
||||
const inputBalance = getInputTokenBalance(inputBank)
|
||||
const inputBalance = getTokenBalance(inputBank)
|
||||
return inputBalance > 0
|
||||
}, [inputBank, mangoAccountAddress])
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import MarketSwapForm from './MarketSwapForm'
|
|||
import Switch from '@components/forms/Switch'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import { SwapFormTokenListType } from './SwapFormTokenList'
|
||||
import { TriggerOrderTypes } from 'types'
|
||||
import { SwapTypes } from 'types'
|
||||
import TriggerSwapForm from './TriggerSwapForm'
|
||||
import WalletSwapForm from './WalletSwapForm'
|
||||
import TabButtons from '@components/shared/TabButtons'
|
||||
|
@ -101,7 +101,7 @@ const SwapForm = () => {
|
|||
}, [groupLoaded, query])
|
||||
|
||||
const handleSwapOrTrigger = useCallback(
|
||||
(orderType: TriggerOrderTypes) => {
|
||||
(orderType: SwapTypes) => {
|
||||
set((state) => {
|
||||
state.swap.swapOrTrigger = orderType
|
||||
if (orderType !== 'swap' && outputBank?.name === OUTPUT_TOKEN_DEFAULT) {
|
||||
|
|
|
@ -14,7 +14,7 @@ import FormatNumericValue from '@components/shared/FormatNumericValue'
|
|||
import { formatTokenSymbol } from 'utils/tokens'
|
||||
import TokenLogo from '@components/shared/TokenLogo'
|
||||
import Input from '@components/forms/Input'
|
||||
import { getInputTokenBalance } from './TriggerSwapForm'
|
||||
import { getTokenBalance } from './TriggerSwapForm'
|
||||
import { walletBalanceForToken } from '@components/DepositForm'
|
||||
import TokenReduceOnlyDesc from '@components/shared/TokenReduceOnlyDesc'
|
||||
import PopularSwapTokens from './PopularSwapTokens'
|
||||
|
@ -333,7 +333,7 @@ const SwapFormTokenList = ({
|
|||
return t('swap:reduce-position')
|
||||
} else {
|
||||
if (!mangoAccountAddress || !inputBank) return ''
|
||||
const uiPos = getInputTokenBalance(inputBank)
|
||||
const uiPos = getTokenBalance(inputBank)
|
||||
if (uiPos > 0) {
|
||||
return t('swap:reduce-position-buy')
|
||||
} else if (uiPos < 0) {
|
||||
|
|
|
@ -33,6 +33,56 @@ import { Disclosure, Transition } from '@headlessui/react'
|
|||
import SheenLoader from '@components/shared/SheenLoader'
|
||||
import { formatTokenSymbol } from 'utils/tokens'
|
||||
|
||||
export const handleCancelTriggerOrder = async (
|
||||
id: BN,
|
||||
setCancelId?: (id: string) => void,
|
||||
) => {
|
||||
try {
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
const actions = mangoStore.getState().actions
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
|
||||
if (!mangoAccount || !group) return
|
||||
if (setCancelId) {
|
||||
setCancelId(id.toString())
|
||||
}
|
||||
|
||||
try {
|
||||
const { signature: tx, slot } = await client.tokenConditionalSwapCancel(
|
||||
group,
|
||||
mangoAccount,
|
||||
id,
|
||||
)
|
||||
notify({
|
||||
title: 'Transaction confirmed',
|
||||
type: 'success',
|
||||
txid: tx,
|
||||
noSound: true,
|
||||
})
|
||||
actions.fetchGroup()
|
||||
await actions.reloadMangoAccount(slot)
|
||||
} catch (e) {
|
||||
console.error('failed to cancel swap order', e)
|
||||
sentry.captureException(e)
|
||||
if (isMangoError(e)) {
|
||||
notify({
|
||||
title: 'Transaction failed',
|
||||
description: e.message,
|
||||
txid: e?.txid,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('failed to cancel trigger order', e)
|
||||
} finally {
|
||||
if (setCancelId) {
|
||||
setCancelId('')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const handleCancelAll = async (
|
||||
setCancelId: (id: '' | 'all') => void,
|
||||
) => {
|
||||
|
@ -162,49 +212,6 @@ const SwapOrders = () => {
|
|||
sortConfig,
|
||||
} = useSortableData(formattedTableData())
|
||||
|
||||
const handleCancel = async (id: BN) => {
|
||||
try {
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
const actions = mangoStore.getState().actions
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
|
||||
if (!mangoAccount || !group) return
|
||||
setCancelId(id.toString())
|
||||
|
||||
try {
|
||||
const { signature: tx, slot } = await client.tokenConditionalSwapCancel(
|
||||
group,
|
||||
mangoAccount,
|
||||
id,
|
||||
)
|
||||
notify({
|
||||
title: 'Transaction confirmed',
|
||||
type: 'success',
|
||||
txid: tx,
|
||||
noSound: true,
|
||||
})
|
||||
actions.fetchGroup()
|
||||
await actions.reloadMangoAccount(slot)
|
||||
} catch (e) {
|
||||
console.error('failed to cancel swap order', e)
|
||||
sentry.captureException(e)
|
||||
if (isMangoError(e)) {
|
||||
notify({
|
||||
title: 'Transaction failed',
|
||||
description: e.message,
|
||||
txid: e?.txid,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('failed to cancel trigger order', e)
|
||||
} finally {
|
||||
setCancelId('')
|
||||
}
|
||||
}
|
||||
|
||||
return orders.length ? (
|
||||
showTableView ? (
|
||||
<Table>
|
||||
|
@ -358,7 +365,9 @@ const SwapOrders = () => {
|
|||
disabled={
|
||||
cancelId === data.id.toString() || cancelId === 'all'
|
||||
}
|
||||
onClick={() => handleCancel(data.id)}
|
||||
onClick={() =>
|
||||
handleCancelTriggerOrder(data.id, setCancelId)
|
||||
}
|
||||
size="small"
|
||||
>
|
||||
{cancelId === data.id.toString() || cancelId === 'all' ? (
|
||||
|
@ -495,7 +504,11 @@ const SwapOrders = () => {
|
|||
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">{t('cancel')}</p>
|
||||
<LinkButton onClick={() => handleCancel(data.id)}>
|
||||
<LinkButton
|
||||
onClick={() =>
|
||||
handleCancelTriggerOrder(data.id, setCancelId)
|
||||
}
|
||||
>
|
||||
{cancelId === data.id.toString() ? (
|
||||
<SheenLoader className="mt-1">
|
||||
<div className="h-3.5 w-20 bg-th-bkg-2" />
|
||||
|
|
|
@ -27,9 +27,6 @@ import {
|
|||
import { withValueLimit } from './MarketSwapForm'
|
||||
import ReduceInputTokenInput from './ReduceInputTokenInput'
|
||||
import ReduceOutputTokenInput from './ReduceOutputTokenInput'
|
||||
import { notify } from 'utils/notifications'
|
||||
import * as sentry from '@sentry/nextjs'
|
||||
import { isMangoError } from 'types'
|
||||
import Button, { LinkButton } from '@components/shared/Button'
|
||||
import Loading from '@components/shared/Loading'
|
||||
import TokenLogo from '@components/shared/TokenLogo'
|
||||
|
@ -49,6 +46,8 @@ import Tooltip from '@components/shared/Tooltip'
|
|||
import Link from 'next/link'
|
||||
import useTokenPositionsFull from 'hooks/useTokenPositionsFull'
|
||||
import TopBarStore from '@store/topBarStore'
|
||||
import { TriggerOrderTypes, handlePlaceTriggerOrder } from 'utils/tradeForm'
|
||||
import TradePriceDifference from '@components/shared/TradePriceDifference'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
|
@ -69,19 +68,14 @@ type TriggerSwapForm = {
|
|||
|
||||
type FormErrors = Partial<Record<keyof TriggerSwapForm, string>>
|
||||
|
||||
enum OrderTypes {
|
||||
STOP_LOSS = 'trade:stop-loss',
|
||||
TAKE_PROFIT = 'trade:take-profit',
|
||||
}
|
||||
|
||||
const ORDER_TYPES = [OrderTypes.STOP_LOSS, OrderTypes.TAKE_PROFIT]
|
||||
const ORDER_TYPES = [TriggerOrderTypes.STOP_LOSS, TriggerOrderTypes.TAKE_PROFIT]
|
||||
|
||||
const set = mangoStore.getState().set
|
||||
|
||||
export const getInputTokenBalance = (inputBank: Bank | undefined) => {
|
||||
export const getTokenBalance = (bank: Bank | undefined) => {
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
if (!inputBank || !mangoAccount) return 0
|
||||
const balance = mangoAccount.getTokenBalanceUi(inputBank)
|
||||
if (!bank || !mangoAccount) return 0
|
||||
const balance = mangoAccount.getTokenBalanceUi(bank)
|
||||
return balance
|
||||
}
|
||||
|
||||
|
@ -93,13 +87,13 @@ const getOutputTokenBalance = (outputBank: Bank | undefined) => {
|
|||
}
|
||||
|
||||
const getOrderTypeMultiplier = (
|
||||
orderType: OrderTypes,
|
||||
orderType: TriggerOrderTypes,
|
||||
flipPrices: boolean,
|
||||
reducingShort: boolean,
|
||||
) => {
|
||||
if (orderType === OrderTypes.STOP_LOSS) {
|
||||
if (orderType === TriggerOrderTypes.STOP_LOSS) {
|
||||
return reducingShort ? (flipPrices ? 0.9 : 1.1) : flipPrices ? 1.1 : 0.9
|
||||
} else if (orderType === OrderTypes.TAKE_PROFIT) {
|
||||
} else if (orderType === TriggerOrderTypes.TAKE_PROFIT) {
|
||||
return reducingShort ? (flipPrices ? 1.1 : 0.9) : flipPrices ? 0.9 : 1.1
|
||||
} else {
|
||||
return 1
|
||||
|
@ -114,7 +108,6 @@ const TriggerSwapForm = ({
|
|||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const { ipAllowed, ipCountry } = useIpAddress()
|
||||
const { setShowSettingsModal } = TopBarStore()
|
||||
// const [triggerPrice, setTriggerPrice] = useState('')
|
||||
const [orderType, setOrderType] = useState(ORDER_TYPES[0])
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [swapFormSizeUi] = useLocalStorageState(SIZE_INPUT_UI_KEY, 'slider')
|
||||
|
@ -185,7 +178,7 @@ const TriggerSwapForm = ({
|
|||
|
||||
const isReducingShort = useMemo(() => {
|
||||
if (!mangoAccountAddress || !inputBank) return false
|
||||
const inputBalance = getInputTokenBalance(inputBank)
|
||||
const inputBalance = getTokenBalance(inputBank)
|
||||
return inputBalance < 0
|
||||
}, [inputBank, mangoAccountAddress])
|
||||
|
||||
|
@ -235,14 +228,6 @@ const TriggerSwapForm = ({
|
|||
}
|
||||
}, [flipPrices, orderType, isReducingShort])
|
||||
|
||||
const triggerPriceDifference = useMemo(() => {
|
||||
if (!quotePrice) return 0
|
||||
const triggerDifference = triggerPrice
|
||||
? ((parseFloat(triggerPrice) - quotePrice) / quotePrice) * 100
|
||||
: 0
|
||||
return triggerDifference
|
||||
}, [quotePrice, triggerPrice])
|
||||
|
||||
const handleTokenSelect = (type: SwapFormTokenListType) => {
|
||||
setShowTokenSelect(type)
|
||||
setFormErrors({})
|
||||
|
@ -284,7 +269,7 @@ const TriggerSwapForm = ({
|
|||
'triggerPrice',
|
||||
]
|
||||
const triggerPriceNumber = parseFloat(form.triggerPrice)
|
||||
const inputTokenBalance = getInputTokenBalance(inputBank)
|
||||
const inputTokenBalance = getTokenBalance(inputBank)
|
||||
const shouldFlip = flipPrices !== isReducingShort
|
||||
for (const key of requiredFields) {
|
||||
const value = form[key] as string
|
||||
|
@ -292,24 +277,20 @@ const TriggerSwapForm = ({
|
|||
invalidFields[key] = t('settings:error-required-field')
|
||||
}
|
||||
}
|
||||
if (orderType === OrderTypes.STOP_LOSS) {
|
||||
if (orderType === TriggerOrderTypes.STOP_LOSS) {
|
||||
if (shouldFlip && triggerPriceNumber <= quotePrice) {
|
||||
invalidFields.triggerPrice =
|
||||
'Trigger price must be above oracle price'
|
||||
invalidFields.triggerPrice = t('trade:error-trigger-above')
|
||||
}
|
||||
if (!shouldFlip && triggerPriceNumber >= quotePrice) {
|
||||
invalidFields.triggerPrice =
|
||||
'Trigger price must be below oracle price'
|
||||
invalidFields.triggerPrice = t('trade:error-trigger-below')
|
||||
}
|
||||
}
|
||||
if (orderType === OrderTypes.TAKE_PROFIT) {
|
||||
if (orderType === TriggerOrderTypes.TAKE_PROFIT) {
|
||||
if (shouldFlip && triggerPriceNumber >= quotePrice) {
|
||||
invalidFields.triggerPrice =
|
||||
'Trigger price must be below oracle price'
|
||||
invalidFields.triggerPrice = t('trade:error-trigger-below')
|
||||
}
|
||||
if (!shouldFlip && triggerPriceNumber <= quotePrice) {
|
||||
invalidFields.triggerPrice =
|
||||
'Trigger price must be above oracle price'
|
||||
invalidFields.triggerPrice = t('trade:error-trigger-above')
|
||||
}
|
||||
}
|
||||
if (form.amountIn > Math.abs(inputTokenBalance)) {
|
||||
|
@ -457,123 +438,6 @@ const TriggerSwapForm = ({
|
|||
[amountInFormValue, flipPrices, setFormErrors],
|
||||
)
|
||||
|
||||
const handlePlaceStopLoss = useCallback(async () => {
|
||||
const invalidFields = isFormValid({
|
||||
amountIn: amountInAsDecimal.toNumber(),
|
||||
triggerPrice,
|
||||
})
|
||||
if (Object.keys(invalidFields).length) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
const actions = mangoStore.getState().actions
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const inputBank = mangoStore.getState().swap.inputBank
|
||||
const outputBank = mangoStore.getState().swap.outputBank
|
||||
|
||||
if (!mangoAccount || !group || !inputBank || !outputBank || !triggerPrice)
|
||||
return
|
||||
setSubmitting(true)
|
||||
|
||||
const amountIn = amountInAsDecimal.toNumber()
|
||||
|
||||
const isReduceLong = !isReducingShort
|
||||
|
||||
try {
|
||||
let tx
|
||||
if (orderType === OrderTypes.STOP_LOSS) {
|
||||
if (isReduceLong) {
|
||||
tx = await client.tcsStopLossOnDeposit(
|
||||
group,
|
||||
mangoAccount,
|
||||
inputBank,
|
||||
outputBank,
|
||||
parseFloat(triggerPrice),
|
||||
flipPrices,
|
||||
amountIn,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
} else {
|
||||
tx = await client.tcsStopLossOnBorrow(
|
||||
group,
|
||||
mangoAccount,
|
||||
outputBank,
|
||||
inputBank,
|
||||
parseFloat(triggerPrice),
|
||||
!flipPrices,
|
||||
amountIn,
|
||||
null,
|
||||
true,
|
||||
null,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (orderType === OrderTypes.TAKE_PROFIT) {
|
||||
if (isReduceLong) {
|
||||
tx = await client.tcsTakeProfitOnDeposit(
|
||||
group,
|
||||
mangoAccount,
|
||||
inputBank,
|
||||
outputBank,
|
||||
parseFloat(triggerPrice),
|
||||
flipPrices,
|
||||
amountIn,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
} else {
|
||||
tx = await client.tcsTakeProfitOnBorrow(
|
||||
group,
|
||||
mangoAccount,
|
||||
outputBank,
|
||||
inputBank,
|
||||
parseFloat(triggerPrice),
|
||||
!flipPrices,
|
||||
amountIn,
|
||||
null,
|
||||
true,
|
||||
null,
|
||||
)
|
||||
}
|
||||
}
|
||||
notify({
|
||||
title: 'Transaction confirmed',
|
||||
type: 'success',
|
||||
txid: tx?.signature,
|
||||
noSound: true,
|
||||
})
|
||||
actions.fetchGroup()
|
||||
await actions.reloadMangoAccount(tx?.slot)
|
||||
} catch (e) {
|
||||
console.error('onSwap error: ', e)
|
||||
sentry.captureException(e)
|
||||
if (isMangoError(e)) {
|
||||
notify({
|
||||
title: 'Transaction failed',
|
||||
description: e.message,
|
||||
txid: e?.txid,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Swap error:', e)
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
}, [
|
||||
flipPrices,
|
||||
orderType,
|
||||
quotePrice,
|
||||
triggerPrice,
|
||||
amountInAsDecimal,
|
||||
amountOutFormValue,
|
||||
isReducingShort,
|
||||
])
|
||||
|
||||
const orderDescription = useMemo(() => {
|
||||
if (
|
||||
!amountInFormValue ||
|
||||
|
@ -611,7 +475,7 @@ const TriggerSwapForm = ({
|
|||
// xor of two flip flags
|
||||
const shouldFlip = flipPrices !== isReducingShort
|
||||
const orderTypeString =
|
||||
orderType === OrderTypes.STOP_LOSS
|
||||
orderType === TriggerOrderTypes.STOP_LOSS
|
||||
? shouldFlip
|
||||
? t('trade:rises-to')
|
||||
: t('trade:falls-to')
|
||||
|
@ -675,7 +539,7 @@ const TriggerSwapForm = ({
|
|||
const handleOrderTypeChange = useCallback(
|
||||
(type: string) => {
|
||||
setFormErrors({})
|
||||
const newType = type as OrderTypes
|
||||
const newType = type as TriggerOrderTypes
|
||||
setOrderType(newType)
|
||||
const triggerMultiplier = getOrderTypeMultiplier(
|
||||
newType,
|
||||
|
@ -700,7 +564,27 @@ const TriggerSwapForm = ({
|
|||
[flipPrices, quotePrice, setFormErrors, isReducingShort],
|
||||
)
|
||||
|
||||
const onClick = !connected ? connect : handlePlaceStopLoss
|
||||
const handleOrder = () => {
|
||||
const invalidFields = isFormValid({
|
||||
amountIn: amountInAsDecimal.toNumber(),
|
||||
triggerPrice,
|
||||
})
|
||||
if (Object.keys(invalidFields).length) {
|
||||
return
|
||||
}
|
||||
handlePlaceTriggerOrder(
|
||||
inputBank,
|
||||
outputBank,
|
||||
amountInAsDecimal.toNumber(),
|
||||
triggerPrice,
|
||||
orderType,
|
||||
isReducingShort,
|
||||
flipPrices,
|
||||
setSubmitting,
|
||||
)
|
||||
}
|
||||
|
||||
const onClick = !connected ? connect : handleOrder
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -789,17 +673,10 @@ const TriggerSwapForm = ({
|
|||
<div className="col-span-1">
|
||||
<div className="mb-2 flex items-end justify-between">
|
||||
<p className="text-th-fgd-2">{t('trade:trigger-price')}</p>
|
||||
<p
|
||||
className={`font-mono text-xs ${
|
||||
triggerPriceDifference >= 0 ? 'text-th-up' : 'text-th-down'
|
||||
}`}
|
||||
>
|
||||
{triggerPriceDifference
|
||||
? (triggerPriceDifference > 0 ? '+' : '') +
|
||||
triggerPriceDifference.toFixed(2)
|
||||
: '0.00'}
|
||||
%
|
||||
</p>
|
||||
<TradePriceDifference
|
||||
currentPrice={quotePrice}
|
||||
newPrice={parseFloat(triggerPrice)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="relative w-full">
|
||||
|
|
|
@ -29,7 +29,12 @@ import * as sentry from '@sentry/nextjs'
|
|||
|
||||
import { notify } from 'utils/notifications'
|
||||
import SpotSlider from './SpotSlider'
|
||||
import { calculateLimitPriceForMarketOrder } from 'utils/tradeForm'
|
||||
import {
|
||||
OrderTypes,
|
||||
TriggerOrderTypes,
|
||||
calculateLimitPriceForMarketOrder,
|
||||
handlePlaceTriggerOrder,
|
||||
} from 'utils/tradeForm'
|
||||
import Image from 'next/legacy/image'
|
||||
import { QuestionMarkCircleIcon } from '@heroicons/react/20/solid'
|
||||
import Loading from '@components/shared/Loading'
|
||||
|
@ -66,6 +71,10 @@ import SecondaryConnectButton from '@components/shared/SecondaryConnectButton'
|
|||
import useRemainingBorrowsInPeriod from 'hooks/useRemainingBorrowsInPeriod'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import Select from '@components/forms/Select'
|
||||
import TriggerOrderMaxButton from './TriggerOrderMaxButton'
|
||||
import TradePriceDifference from '@components/shared/TradePriceDifference'
|
||||
import { getTokenBalance } from '@components/swap/TriggerSwapForm'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
|
@ -88,12 +97,22 @@ export const DEFAULT_CHECKBOX_SETTINGS = {
|
|||
margin: true,
|
||||
}
|
||||
|
||||
type TradeForm = {
|
||||
baseSize: number
|
||||
orderType: OrderTypes | TriggerOrderTypes
|
||||
price: string | undefined
|
||||
side: 'buy' | 'sell'
|
||||
}
|
||||
|
||||
type FormErrors = Partial<Record<keyof TradeForm, string>>
|
||||
|
||||
const AdvancedTradeForm = () => {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const { t } = useTranslation(['common', 'settings', 'swap', 'trade'])
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const tradeForm = mangoStore((s) => s.tradeForm)
|
||||
const themeData = mangoStore((s) => s.themeData)
|
||||
const [placingOrder, setPlacingOrder] = useState(false)
|
||||
const [formErrors, setFormErrors] = useState<FormErrors>({})
|
||||
const [tradeFormSizeUi] = useLocalStorageState(SIZE_INPUT_UI_KEY, 'slider')
|
||||
const [savedCheckboxSettings, setSavedCheckboxSettings] =
|
||||
useLocalStorageState(TRADE_CHECKBOXES_KEY, DEFAULT_CHECKBOX_SETTINGS)
|
||||
|
@ -108,6 +127,7 @@ const AdvancedTradeForm = () => {
|
|||
price: oraclePrice,
|
||||
baseLogoURI,
|
||||
baseSymbol,
|
||||
quoteBank,
|
||||
quoteLogoURI,
|
||||
quoteSymbol,
|
||||
serumOrPerpMarket,
|
||||
|
@ -115,11 +135,22 @@ const AdvancedTradeForm = () => {
|
|||
const { remainingBorrowsInPeriod, timeToNextPeriod } =
|
||||
useRemainingBorrowsInPeriod()
|
||||
|
||||
const setTradeType = useCallback((tradeType: 'Limit' | 'Market') => {
|
||||
set((s) => {
|
||||
s.tradeForm.tradeType = tradeType
|
||||
})
|
||||
}, [])
|
||||
const baseBank = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
if (!group || !selectedMarket || selectedMarket instanceof PerpMarket)
|
||||
return
|
||||
const bank = group.getFirstBankByTokenIndex(selectedMarket.baseTokenIndex)
|
||||
return bank
|
||||
}, [selectedMarket])
|
||||
|
||||
const setTradeType = useCallback(
|
||||
(tradeType: OrderTypes | TriggerOrderTypes) => {
|
||||
set((s) => {
|
||||
s.tradeForm.tradeType = tradeType
|
||||
})
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
const handlePriceChange = useCallback(
|
||||
(e: NumberFormatValues, info: SourceInfo) => {
|
||||
|
@ -132,6 +163,7 @@ const AdvancedTradeForm = () => {
|
|||
).toString()
|
||||
}
|
||||
})
|
||||
setFormErrors({})
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
@ -152,6 +184,7 @@ const AdvancedTradeForm = () => {
|
|||
s.tradeForm.quoteSize = ''
|
||||
}
|
||||
})
|
||||
setFormErrors({})
|
||||
},
|
||||
[oraclePrice],
|
||||
)
|
||||
|
@ -172,6 +205,7 @@ const AdvancedTradeForm = () => {
|
|||
s.tradeForm.baseSize = ''
|
||||
}
|
||||
})
|
||||
setFormErrors({})
|
||||
},
|
||||
[oraclePrice],
|
||||
)
|
||||
|
@ -232,6 +266,7 @@ const AdvancedTradeForm = () => {
|
|||
set((s) => {
|
||||
s.tradeForm.side = side
|
||||
})
|
||||
setFormErrors({})
|
||||
}, [])
|
||||
|
||||
const handleSetMargin = useCallback(
|
||||
|
@ -248,6 +283,8 @@ const AdvancedTradeForm = () => {
|
|||
if (
|
||||
!group ||
|
||||
!mangoAccount ||
|
||||
!baseBank ||
|
||||
!quoteBank ||
|
||||
!tradePrice ||
|
||||
!(selectedMarket instanceof Serum3Market)
|
||||
) {
|
||||
|
@ -255,11 +292,8 @@ const AdvancedTradeForm = () => {
|
|||
}
|
||||
|
||||
const isBuySide = side === 'buy'
|
||||
const tokenIndex =
|
||||
selectedMarket[isBuySide ? 'quoteTokenIndex' : 'baseTokenIndex']
|
||||
const balance = mangoAccount.getTokenBalanceUi(
|
||||
group.getFirstBankByTokenIndex(tokenIndex),
|
||||
)
|
||||
const balanceBank = isBuySide ? quoteBank : baseBank
|
||||
const balance = mangoAccount.getTokenBalanceUi(balanceBank)
|
||||
const max = Math.max(balance, 0)
|
||||
|
||||
const sizeToCompare = isBuySide ? quoteSize : baseSize
|
||||
|
@ -292,6 +326,8 @@ const AdvancedTradeForm = () => {
|
|||
})
|
||||
},
|
||||
[
|
||||
baseBank,
|
||||
quoteBank,
|
||||
mangoAccount,
|
||||
oraclePrice,
|
||||
savedCheckboxSettings,
|
||||
|
@ -316,25 +352,19 @@ const AdvancedTradeForm = () => {
|
|||
}, [serumOrPerpMarket])
|
||||
|
||||
const isMarketEnabled = useMemo(() => {
|
||||
const group = mangoStore.getState().group
|
||||
const { group } = mangoStore.getState()
|
||||
if (!selectedMarket || !group) return false
|
||||
if (selectedMarket instanceof PerpMarket) {
|
||||
return selectedMarket.oracleLastUpdatedSlot !== 0
|
||||
} else if (selectedMarket instanceof Serum3Market) {
|
||||
const baseBank = group.getFirstBankByTokenIndex(
|
||||
selectedMarket.baseTokenIndex,
|
||||
)
|
||||
const quoteBank = group.getFirstBankByTokenIndex(
|
||||
selectedMarket.quoteTokenIndex,
|
||||
)
|
||||
return (
|
||||
baseBank.oracleLastUpdatedSlot !== 0 &&
|
||||
(quoteBank.name == 'USDC'
|
||||
baseBank?.oracleLastUpdatedSlot !== 0 &&
|
||||
(quoteBank?.name == 'USDC'
|
||||
? true
|
||||
: quoteBank.oracleLastUpdatedSlot !== 0)
|
||||
: quoteBank?.oracleLastUpdatedSlot !== 0)
|
||||
)
|
||||
}
|
||||
}, [selectedMarket])
|
||||
}, [baseBank, quoteBank, selectedMarket])
|
||||
|
||||
/*
|
||||
* Updates the limit price on page load
|
||||
|
@ -376,12 +406,56 @@ const AdvancedTradeForm = () => {
|
|||
}
|
||||
}, [oraclePrice, selectedMarket, tickDecimals, tradeForm])
|
||||
|
||||
const handlePlaceOrder = useCallback(async () => {
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
const isTriggerOrder =
|
||||
tradeForm.tradeType === TriggerOrderTypes.STOP_LOSS ||
|
||||
tradeForm.tradeType === TriggerOrderTypes.TAKE_PROFIT
|
||||
|
||||
// default to correct side for trigger orders
|
||||
useEffect(() => {
|
||||
if (isTriggerOrder) {
|
||||
const balance = getTokenBalance(baseBank)
|
||||
set((state) => {
|
||||
if (balance > 0) {
|
||||
state.tradeForm.side = 'sell'
|
||||
} else {
|
||||
state.tradeForm.side = 'buy'
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [isTriggerOrder])
|
||||
|
||||
// set default trigger price
|
||||
useEffect(() => {
|
||||
if (isTriggerOrder) {
|
||||
let triggerPrice = oraclePrice
|
||||
if (tradeForm.tradeType === TriggerOrderTypes.STOP_LOSS) {
|
||||
if (tradeForm.side === 'buy') {
|
||||
triggerPrice = oraclePrice * 1.1
|
||||
} else {
|
||||
triggerPrice = oraclePrice * 0.9
|
||||
}
|
||||
} else {
|
||||
if (tradeForm.side === 'buy') {
|
||||
triggerPrice = oraclePrice * 0.9
|
||||
} else {
|
||||
triggerPrice = oraclePrice * 1.1
|
||||
}
|
||||
}
|
||||
set((state) => {
|
||||
state.tradeForm.price = floorToDecimal(
|
||||
triggerPrice,
|
||||
tickDecimals,
|
||||
).toFixed()
|
||||
})
|
||||
}
|
||||
}, [isTriggerOrder, tickDecimals, tradeForm.side, tradeForm.tradeType])
|
||||
|
||||
const handleStandardOrder = useCallback(async () => {
|
||||
const { client } = mangoStore.getState()
|
||||
const { group } = mangoStore.getState()
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const tradeForm = mangoStore.getState().tradeForm
|
||||
const actions = mangoStore.getState().actions
|
||||
const { tradeForm } = mangoStore.getState()
|
||||
const { actions } = mangoStore.getState()
|
||||
const selectedMarket = mangoStore.getState().selectedMarket.current
|
||||
|
||||
if (!group || !mangoAccount) return
|
||||
|
@ -486,32 +560,68 @@ const AdvancedTradeForm = () => {
|
|||
}
|
||||
}, [])
|
||||
|
||||
const handleTriggerOrder = useCallback(() => {
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const { baseSize, price, side, tradeType } = mangoStore.getState().tradeForm
|
||||
const invalidFields = isFormValid({
|
||||
baseSize: parseFloat(baseSize),
|
||||
price: price,
|
||||
orderType: tradeType,
|
||||
side,
|
||||
})
|
||||
if (Object.keys(invalidFields).length) {
|
||||
return
|
||||
}
|
||||
if (!mangoAccount || !baseBank || !price) return
|
||||
const isReducingShort = mangoAccount.getTokenBalanceUi(baseBank) < 0
|
||||
const orderType = tradeType as TriggerOrderTypes
|
||||
handlePlaceTriggerOrder(
|
||||
baseBank,
|
||||
quoteBank,
|
||||
Number(baseSize),
|
||||
price,
|
||||
orderType,
|
||||
isReducingShort,
|
||||
false,
|
||||
setPlacingOrder,
|
||||
)
|
||||
}, [baseBank, quoteBank, setPlacingOrder])
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
isTriggerOrder ? handleTriggerOrder() : handleStandardOrder()
|
||||
},
|
||||
[isTriggerOrder],
|
||||
)
|
||||
|
||||
const sideNames = useMemo(() => {
|
||||
return selectedMarket instanceof PerpMarket
|
||||
? [t('trade:long'), t('trade:short')]
|
||||
: [t('buy'), t('sell')]
|
||||
}, [selectedMarket, t])
|
||||
|
||||
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
handlePlaceOrder()
|
||||
}
|
||||
|
||||
const balanceBank = useMemo(() => {
|
||||
const { group } = mangoStore.getState()
|
||||
if (
|
||||
!group ||
|
||||
!selectedMarket ||
|
||||
selectedMarket instanceof PerpMarket ||
|
||||
!savedCheckboxSettings.margin
|
||||
!savedCheckboxSettings.margin ||
|
||||
isTriggerOrder
|
||||
)
|
||||
return
|
||||
if (tradeForm.side === 'buy') {
|
||||
return group.getFirstBankByTokenIndex(selectedMarket.quoteTokenIndex)
|
||||
return quoteBank
|
||||
} else {
|
||||
return group.getFirstBankByTokenIndex(selectedMarket.baseTokenIndex)
|
||||
return baseBank
|
||||
}
|
||||
}, [savedCheckboxSettings, selectedMarket, tradeForm.side])
|
||||
}, [
|
||||
baseBank,
|
||||
quoteBank,
|
||||
savedCheckboxSettings,
|
||||
selectedMarket,
|
||||
tradeForm.side,
|
||||
isTriggerOrder,
|
||||
])
|
||||
|
||||
// check if the borrowed amount exceeds the net borrow limit in the current period
|
||||
const borrowExceedsLimitInPeriod = useMemo(() => {
|
||||
|
@ -525,11 +635,77 @@ const AdvancedTradeForm = () => {
|
|||
return borrowAmountNotional > remainingBorrowsInPeriod
|
||||
}, [balanceBank, mangoAccount, remainingBorrowsInPeriod, tradeForm])
|
||||
|
||||
const disabled =
|
||||
(connected && (!tradeForm.baseSize || !tradeForm.price)) ||
|
||||
!serumOrPerpMarket ||
|
||||
parseFloat(tradeForm.baseSize) < serumOrPerpMarket.minOrderSize ||
|
||||
!isMarketEnabled
|
||||
const orderTypes = useMemo(() => {
|
||||
const orderTypesArray = Object.values(OrderTypes)
|
||||
if (!selectedMarket || selectedMarket instanceof PerpMarket)
|
||||
return orderTypesArray
|
||||
const baseBalance = floorToDecimal(
|
||||
getTokenBalance(baseBank),
|
||||
minOrderDecimals,
|
||||
).toNumber()
|
||||
const triggerOrderTypesArray = Object.values(TriggerOrderTypes)
|
||||
return Math.abs(baseBalance) > 0
|
||||
? [...orderTypesArray, ...triggerOrderTypesArray]
|
||||
: orderTypesArray
|
||||
}, [baseBank, minOrderDecimals, selectedMarket])
|
||||
|
||||
const isFormValid = useCallback(
|
||||
(form: TradeForm) => {
|
||||
const { baseSize, price, orderType, side } = form
|
||||
const invalidFields: FormErrors = {}
|
||||
setFormErrors({})
|
||||
const requiredFields: (keyof TradeForm)[] = ['baseSize', 'price']
|
||||
const priceNumber = price ? parseFloat(price) : 0
|
||||
const baseTokenBalance = getTokenBalance(baseBank)
|
||||
const isReducingShort = baseTokenBalance < 0
|
||||
for (const key of requiredFields) {
|
||||
const value = form[key] as string
|
||||
if (!value) {
|
||||
invalidFields[key] = t('settings:error-required-field')
|
||||
}
|
||||
}
|
||||
if (orderType === TriggerOrderTypes.STOP_LOSS) {
|
||||
if (isReducingShort && priceNumber <= oraclePrice) {
|
||||
invalidFields.price = t('trade:error-trigger-above')
|
||||
}
|
||||
if (!isReducingShort && priceNumber >= oraclePrice) {
|
||||
invalidFields.price = t('trade:error-trigger-below')
|
||||
}
|
||||
}
|
||||
if (orderType === TriggerOrderTypes.TAKE_PROFIT) {
|
||||
if (isReducingShort && priceNumber >= oraclePrice) {
|
||||
invalidFields.price = t('trade:error-trigger-below')
|
||||
}
|
||||
if (!isReducingShort && priceNumber <= oraclePrice) {
|
||||
invalidFields.price = t('trade:error-trigger-above')
|
||||
}
|
||||
}
|
||||
if (side === 'buy' && !isReducingShort && isTriggerOrder) {
|
||||
invalidFields.baseSize = t('trade:error-no-short')
|
||||
}
|
||||
if (side === 'sell' && isReducingShort && isTriggerOrder) {
|
||||
invalidFields.baseSize = t('trade:error-no-long')
|
||||
}
|
||||
if (baseSize > Math.abs(baseTokenBalance) && isTriggerOrder) {
|
||||
invalidFields.baseSize = t('swap:insufficient-balance', {
|
||||
symbol: baseBank?.name,
|
||||
})
|
||||
}
|
||||
if (baseSize < minOrderSize) {
|
||||
invalidFields.baseSize = t('trade:min-order-size-error', {
|
||||
minSize: minOrderSize,
|
||||
symbol: baseSymbol,
|
||||
})
|
||||
}
|
||||
if (Object.keys(invalidFields).length) {
|
||||
setFormErrors(invalidFields)
|
||||
}
|
||||
return invalidFields
|
||||
},
|
||||
[baseBank, isTriggerOrder, minOrderSize, oraclePrice, setFormErrors],
|
||||
)
|
||||
|
||||
const disabled = !serumOrPerpMarket || !isMarketEnabled
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -547,11 +723,30 @@ const AdvancedTradeForm = () => {
|
|||
</div>
|
||||
<div className="mt-1 px-2 md:mt-3 md:px-4">
|
||||
<p className="mb-2 text-xs">{t('trade:order-type')}</p>
|
||||
<ButtonGroup
|
||||
activeValue={tradeForm.tradeType}
|
||||
onChange={(tab: 'Limit' | 'Market') => setTradeType(tab)}
|
||||
values={['Limit', 'Market']}
|
||||
/>
|
||||
{selectedMarket instanceof PerpMarket ? (
|
||||
<ButtonGroup
|
||||
activeValue={tradeForm.tradeType}
|
||||
onChange={(tab: OrderTypes | TriggerOrderTypes) =>
|
||||
setTradeType(tab)
|
||||
}
|
||||
values={orderTypes}
|
||||
/>
|
||||
) : (
|
||||
<Select
|
||||
value={t(tradeForm.tradeType)}
|
||||
onChange={(type: OrderTypes | TriggerOrderTypes) =>
|
||||
setTradeType(type)
|
||||
}
|
||||
>
|
||||
{orderTypes.map((type) => (
|
||||
<Select.Option key={type} value={type}>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
{t(type)}
|
||||
</div>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
</div>
|
||||
{tradeForm.tradeType === 'Market' &&
|
||||
selectedMarket instanceof Serum3Market ? (
|
||||
|
@ -560,12 +755,20 @@ const AdvancedTradeForm = () => {
|
|||
<>
|
||||
<form onSubmit={(e) => handleSubmit(e)}>
|
||||
<div className="mt-3 px-3 md:px-4">
|
||||
{tradeForm.tradeType === 'Limit' ? (
|
||||
{tradeForm.tradeType === 'Limit' || isTriggerOrder ? (
|
||||
<>
|
||||
<div className="mb-2 mt-3 flex items-center justify-between">
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{t('trade:limit-price')}
|
||||
{isTriggerOrder
|
||||
? t('trade:trigger-price')
|
||||
: t('trade:limit-price')}
|
||||
</p>
|
||||
{tradeForm.price ? (
|
||||
<TradePriceDifference
|
||||
currentPrice={oraclePrice}
|
||||
newPrice={parseFloat(tradeForm.price)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="relative">
|
||||
{quoteLogoURI ? (
|
||||
|
@ -597,13 +800,33 @@ const AdvancedTradeForm = () => {
|
|||
/>
|
||||
<div className={INPUT_SUFFIX_CLASSNAMES}>{quoteSymbol}</div>
|
||||
</div>
|
||||
{formErrors.price ? (
|
||||
<div className="mt-1">
|
||||
<InlineNotification
|
||||
type="error"
|
||||
desc={formErrors.price}
|
||||
hideBorder
|
||||
hidePadding
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
<MaxSizeButton
|
||||
minOrderDecimals={minOrderDecimals}
|
||||
tickDecimals={tickDecimals}
|
||||
useMargin={savedCheckboxSettings.margin}
|
||||
/>
|
||||
<div className="mb-2 mt-3 flex items-center justify-between">
|
||||
<p className="text-xs text-th-fgd-3">{t('trade:size')}</p>
|
||||
{!isTriggerOrder ? (
|
||||
<MaxSizeButton
|
||||
minOrderDecimals={minOrderDecimals}
|
||||
tickDecimals={tickDecimals}
|
||||
useMargin={savedCheckboxSettings.margin}
|
||||
/>
|
||||
) : (
|
||||
<TriggerOrderMaxButton
|
||||
minOrderDecimals={minOrderDecimals}
|
||||
tickDecimals={tickDecimals}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<div className="relative">
|
||||
<NumberFormat
|
||||
|
@ -664,16 +887,11 @@ const AdvancedTradeForm = () => {
|
|||
/>
|
||||
<div className={INPUT_SUFFIX_CLASSNAMES}>{quoteSymbol}</div>
|
||||
</div>
|
||||
{minOrderSize &&
|
||||
tradeForm.baseSize &&
|
||||
parseFloat(tradeForm.baseSize) < minOrderSize ? (
|
||||
{formErrors.baseSize ? (
|
||||
<div className="mt-1">
|
||||
<InlineNotification
|
||||
type="error"
|
||||
desc={t('trade:min-order-size-error', {
|
||||
minSize: minOrderSize,
|
||||
symbol: baseSymbol,
|
||||
})}
|
||||
desc={formErrors.baseSize}
|
||||
hideBorder
|
||||
hidePadding
|
||||
/>
|
||||
|
@ -689,12 +907,14 @@ const AdvancedTradeForm = () => {
|
|||
tickDecimals={tickDecimals}
|
||||
step={tradeForm.side === 'buy' ? tickSize : minOrderSize}
|
||||
useMargin={savedCheckboxSettings.margin}
|
||||
isTriggerOrder
|
||||
/>
|
||||
) : (
|
||||
<SpotButtonGroup
|
||||
minOrderDecimals={minOrderDecimals}
|
||||
tickDecimals={tickDecimals}
|
||||
useMargin={savedCheckboxSettings.margin}
|
||||
isTriggerOrder
|
||||
/>
|
||||
)
|
||||
) : tradeFormSizeUi === 'slider' ? (
|
||||
|
@ -746,7 +966,8 @@ const AdvancedTradeForm = () => {
|
|||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{selectedMarket instanceof Serum3Market ? (
|
||||
{isTriggerOrder ? null : selectedMarket instanceof
|
||||
Serum3Market ? (
|
||||
<div className="mt-4" id="trade-step-eight">
|
||||
<Tooltip
|
||||
className="hidden md:block"
|
||||
|
@ -838,7 +1059,7 @@ const AdvancedTradeForm = () => {
|
|||
</form>
|
||||
{tradeForm.tradeType === 'Market' &&
|
||||
selectedMarket instanceof PerpMarket ? (
|
||||
<div className="mb-4 px-3 md:px-4">
|
||||
<div className="mb-4 px-4">
|
||||
<InlineNotification
|
||||
type="warning"
|
||||
desc={t('trade:price-expect')}
|
||||
|
@ -860,6 +1081,44 @@ const AdvancedTradeForm = () => {
|
|||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{isTriggerOrder ? (
|
||||
<div className="mb-4 px-4">
|
||||
<InlineNotification
|
||||
desc={
|
||||
<div>
|
||||
<span className="mr-1">{t('swap:trigger-beta')}</span>
|
||||
<Tooltip
|
||||
content={
|
||||
<ul className="ml-4 list-disc space-y-2">
|
||||
<li>
|
||||
Trigger orders on long-tail assets could be
|
||||
susceptible to oracle manipulation.
|
||||
</li>
|
||||
<li>
|
||||
Trigger orders rely on a sufficient amount of well
|
||||
collateralized liquidators.
|
||||
</li>
|
||||
<li>
|
||||
The slippage on existing orders could be
|
||||
higher/lower than what's estimated.
|
||||
</li>
|
||||
<li>
|
||||
The amount of tokens used to fill your order can
|
||||
vary and depends on the final execution price.
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
>
|
||||
<span className="tooltip-underline whitespace-nowrap">
|
||||
{t('swap:important-info')}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
}
|
||||
type="info"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<TradeSummary balanceBank={balanceBank} mangoAccount={mangoAccount} />
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -115,20 +115,15 @@ const MaxSizeButton = ({
|
|||
}, [perpMax, spotMax, selectedMarket, price, side, tradeType])
|
||||
|
||||
return (
|
||||
<div className="mb-2 mt-3 flex items-center justify-between">
|
||||
<p className={`${large ? 'text-sm' : 'text-xs'} text-th-fgd-3`}>
|
||||
{t('trade:size')}
|
||||
</p>
|
||||
<FadeInFadeOut show={!!price && !isUnownedAccount && connected}>
|
||||
<MaxAmountButton
|
||||
className={large ? 'text-sm' : 'text-xs'}
|
||||
decimals={minOrderDecimals}
|
||||
label={t('max')}
|
||||
onClick={handleMax}
|
||||
value={maxAmount}
|
||||
/>
|
||||
</FadeInFadeOut>
|
||||
</div>
|
||||
<FadeInFadeOut show={!!price && !isUnownedAccount && connected}>
|
||||
<MaxAmountButton
|
||||
className={large ? 'text-sm' : 'text-xs'}
|
||||
decimals={minOrderDecimals}
|
||||
label={t('max')}
|
||||
onClick={handleMax}
|
||||
value={maxAmount}
|
||||
/>
|
||||
</FadeInFadeOut>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ import isEqual from 'lodash/isEqual'
|
|||
import { useViewport } from 'hooks/useViewport'
|
||||
import TokenLogo from '@components/shared/TokenLogo'
|
||||
import MarketLogos from './MarketLogos'
|
||||
import { OrderTypes } from 'utils/tradeForm'
|
||||
|
||||
const sizeCompacter = Intl.NumberFormat('en', {
|
||||
maximumFractionDigits: 6,
|
||||
|
@ -698,12 +699,6 @@ const OrderbookRow = ({
|
|||
return floorToDecimal(sizeToShow, decimals)
|
||||
}, [minOrderSizeDecimals, price, size, sizeInBase, tickSizeDecimals])
|
||||
|
||||
// const formattedSize = useMemo(() => {
|
||||
// return minOrderSize && !isNaN(size)
|
||||
// ? floorToDecimal(size, getDecimalCount(minOrderSize))
|
||||
// : new Decimal(size ?? -1)
|
||||
// }, [size, minOrderSize, sizeInBase, tickSize])
|
||||
|
||||
const formattedPrice = useMemo(() => {
|
||||
return tickSizeDecimals && !isNaN(price)
|
||||
? floorToDecimal(price, tickSizeDecimals)
|
||||
|
@ -714,7 +709,7 @@ const OrderbookRow = ({
|
|||
const set = mangoStore.getState().set
|
||||
set((state) => {
|
||||
state.tradeForm.price = formattedPrice.toFixed()
|
||||
state.tradeForm.tradeType = 'Limit'
|
||||
state.tradeForm.tradeType = OrderTypes.LIMIT
|
||||
if (state.tradeForm.baseSize) {
|
||||
const quoteSize = floorToDecimal(
|
||||
formattedPrice.mul(new Decimal(state.tradeForm.baseSize)),
|
||||
|
|
|
@ -151,6 +151,7 @@ const PerpPositions = () => {
|
|||
</Th>
|
||||
<Th className="text-right">{t('trade:unrealized-pnl')}</Th>
|
||||
<Th className="text-right">ROE</Th>
|
||||
<Th className="text-right">{t('funding')}</Th>
|
||||
{!isUnownedAccount ? (
|
||||
<Th>
|
||||
{openPerpPositions?.length > 1 ? (
|
||||
|
@ -192,6 +193,8 @@ const PerpPositions = () => {
|
|||
position.cumulativePnlOverPositionLifetimeUi(market)
|
||||
const unrealizedPnl = position.getUnRealizedPnlUi(market)
|
||||
const realizedPnl = position.getRealizedPnlUi()
|
||||
const positionFunding =
|
||||
position.getCumulativeFundingUi(market)
|
||||
const roe =
|
||||
(unrealizedPnl / (Math.abs(basePosition) * avgEntryPrice)) *
|
||||
100
|
||||
|
@ -320,6 +323,19 @@ const PerpPositions = () => {
|
|||
<FormatNumericValue value={roe} decimals={2} />%
|
||||
</span>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
<span
|
||||
className={
|
||||
positionFunding >= 0 ? 'text-th-up' : 'text-th-down'
|
||||
}
|
||||
>
|
||||
<FormatNumericValue
|
||||
value={positionFunding}
|
||||
decimals={2}
|
||||
isUsd
|
||||
/>
|
||||
</span>
|
||||
</Td>
|
||||
{!isUnownedAccount ? (
|
||||
<Td>
|
||||
<div className="flex items-center justify-end space-x-4">
|
||||
|
@ -450,6 +466,7 @@ const PerpPositions = () => {
|
|||
group,
|
||||
mangoAccount,
|
||||
)
|
||||
const positionFunding = position.getCumulativeFundingUi(market)
|
||||
const unsettledPnl = position.getUnsettledPnlUi(market)
|
||||
const notional = Math.abs(floorBasePosition) * market._uiPrice
|
||||
return (
|
||||
|
@ -661,6 +678,24 @@ const PerpPositions = () => {
|
|||
<FormatNumericValue value={roe} decimals={2} />%
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{t('funding')}
|
||||
</p>
|
||||
<p
|
||||
className={`font-mono ${
|
||||
positionFunding >= 0
|
||||
? 'text-th-up'
|
||||
: 'text-th-down'
|
||||
}`}
|
||||
>
|
||||
<FormatNumericValue
|
||||
value={positionFunding}
|
||||
decimals={2}
|
||||
isUsd
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-2 mt-3 flex space-x-3">
|
||||
<Button
|
||||
className="w-full text-xs sm:text-sm"
|
||||
|
|
|
@ -2,24 +2,58 @@ import ButtonGroup from '@components/forms/ButtonGroup'
|
|||
import mangoStore from '@store/mangoStore'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { floorToDecimal } from 'utils/numbers'
|
||||
import { useSpotMarketMax } from './SpotSlider'
|
||||
import { PerpMarket } from '@blockworks-foundation/mango-v4'
|
||||
import Decimal from 'decimal.js'
|
||||
|
||||
const SpotButtonGroup = ({
|
||||
minOrderDecimals,
|
||||
tickDecimals,
|
||||
useMargin,
|
||||
isTriggerOrder,
|
||||
}: {
|
||||
minOrderDecimals: number
|
||||
tickDecimals: number
|
||||
useMargin: boolean
|
||||
isTriggerOrder: boolean
|
||||
}) => {
|
||||
const side = mangoStore((s) => s.tradeForm.side)
|
||||
const { side } = mangoStore((s) => s.tradeForm)
|
||||
const { selectedMarket } = useSelectedMarket()
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const [sizePercentage, setSizePercentage] = useState('')
|
||||
const max = useSpotMarketMax(mangoAccount, selectedMarket, side, useMargin)
|
||||
const standardOrderMax = useSpotMarketMax(
|
||||
mangoAccount,
|
||||
selectedMarket,
|
||||
side,
|
||||
useMargin,
|
||||
)
|
||||
|
||||
const max = useMemo(() => {
|
||||
if (!isTriggerOrder) return standardOrderMax
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const { group } = mangoStore.getState()
|
||||
if (
|
||||
!group ||
|
||||
!mangoAccount ||
|
||||
!selectedMarket ||
|
||||
selectedMarket instanceof PerpMarket
|
||||
)
|
||||
return 0
|
||||
const positionBank = group.getFirstBankByTokenIndex(
|
||||
selectedMarket.baseTokenIndex,
|
||||
)
|
||||
let max = 0
|
||||
const balance = mangoAccount.getTokenBalanceUi(positionBank)
|
||||
const roundedBalance = floorToDecimal(balance, minOrderDecimals).toNumber()
|
||||
if (side === 'buy') {
|
||||
max = roundedBalance < 0 ? roundedBalance : 0
|
||||
} else {
|
||||
max = roundedBalance > 0 ? roundedBalance : 0
|
||||
}
|
||||
return Math.abs(max)
|
||||
}, [isTriggerOrder, selectedMarket, side, standardOrderMax])
|
||||
|
||||
const handleSizePercentage = useCallback(
|
||||
(percentage: string) => {
|
||||
|
@ -28,30 +62,46 @@ const SpotButtonGroup = ({
|
|||
const size = max * (Number(percentage) / 100)
|
||||
|
||||
set((s) => {
|
||||
if (s.tradeForm.side === 'buy') {
|
||||
s.tradeForm.quoteSize = floorToDecimal(size, tickDecimals).toString()
|
||||
|
||||
if (Number(s.tradeForm.price)) {
|
||||
s.tradeForm.baseSize = floorToDecimal(
|
||||
size / Number(s.tradeForm.price),
|
||||
minOrderDecimals,
|
||||
).toString()
|
||||
} else {
|
||||
s.tradeForm.baseSize = ''
|
||||
const price = Number(s.tradeForm.price)
|
||||
if (isTriggerOrder) {
|
||||
const baseSize = floorToDecimal(size, minOrderDecimals)
|
||||
s.tradeForm.baseSize = baseSize.toFixed()
|
||||
if (price) {
|
||||
const quoteSize = floorToDecimal(
|
||||
new Decimal(size).mul(price),
|
||||
tickDecimals,
|
||||
)
|
||||
s.tradeForm.quoteSize = quoteSize.toFixed()
|
||||
}
|
||||
} else if (s.tradeForm.side === 'sell') {
|
||||
s.tradeForm.baseSize = floorToDecimal(size, tickDecimals).toString()
|
||||
|
||||
if (Number(s.tradeForm.price)) {
|
||||
} else {
|
||||
if (s.tradeForm.side === 'buy') {
|
||||
s.tradeForm.quoteSize = floorToDecimal(
|
||||
size * Number(s.tradeForm.price),
|
||||
size,
|
||||
tickDecimals,
|
||||
).toString()
|
||||
|
||||
if (price) {
|
||||
s.tradeForm.baseSize = floorToDecimal(
|
||||
size / Number(s.tradeForm.price),
|
||||
minOrderDecimals,
|
||||
).toString()
|
||||
} else {
|
||||
s.tradeForm.baseSize = ''
|
||||
}
|
||||
} else if (s.tradeForm.side === 'sell') {
|
||||
s.tradeForm.baseSize = floorToDecimal(size, tickDecimals).toString()
|
||||
|
||||
if (Number(s.tradeForm.price)) {
|
||||
s.tradeForm.quoteSize = floorToDecimal(
|
||||
size * Number(s.tradeForm.price),
|
||||
tickDecimals,
|
||||
).toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
[minOrderDecimals, tickDecimals, max],
|
||||
[minOrderDecimals, tickDecimals, max, isTriggerOrder],
|
||||
)
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import { MangoAccount, Serum3Market } from '@blockworks-foundation/mango-v4'
|
||||
import {
|
||||
MangoAccount,
|
||||
PerpMarket,
|
||||
Serum3Market,
|
||||
} from '@blockworks-foundation/mango-v4'
|
||||
import LeverageSlider from '@components/shared/LeverageSlider'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
|
@ -60,17 +64,48 @@ const SpotSlider = ({
|
|||
tickDecimals,
|
||||
step,
|
||||
useMargin,
|
||||
isTriggerOrder,
|
||||
}: {
|
||||
minOrderDecimals: number
|
||||
tickDecimals: number
|
||||
step: number
|
||||
useMargin: boolean
|
||||
isTriggerOrder: boolean
|
||||
}) => {
|
||||
const side = mangoStore((s) => s.tradeForm.side)
|
||||
const { baseSize, quoteSize, side } = mangoStore((s) => s.tradeForm)
|
||||
const { selectedMarket, price: marketPrice } = useSelectedMarket()
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const tradeForm = mangoStore((s) => s.tradeForm)
|
||||
const max = useSpotMarketMax(mangoAccount, selectedMarket, side, useMargin)
|
||||
const standardOrderMax = useSpotMarketMax(
|
||||
mangoAccount,
|
||||
selectedMarket,
|
||||
side,
|
||||
useMargin,
|
||||
)
|
||||
|
||||
const max = useMemo(() => {
|
||||
if (!isTriggerOrder) return standardOrderMax
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const { group } = mangoStore.getState()
|
||||
if (
|
||||
!group ||
|
||||
!mangoAccount ||
|
||||
!selectedMarket ||
|
||||
selectedMarket instanceof PerpMarket
|
||||
)
|
||||
return 0
|
||||
const positionBank = group.getFirstBankByTokenIndex(
|
||||
selectedMarket.baseTokenIndex,
|
||||
)
|
||||
let max = 0
|
||||
const balance = mangoAccount.getTokenBalanceUi(positionBank)
|
||||
const roundedBalance = floorToDecimal(balance, minOrderDecimals).toNumber()
|
||||
if (side === 'buy') {
|
||||
max = roundedBalance < 0 ? roundedBalance : 0
|
||||
} else {
|
||||
max = roundedBalance > 0 ? roundedBalance : 0
|
||||
}
|
||||
return Math.abs(max)
|
||||
}, [isTriggerOrder, selectedMarket, side, standardOrderMax])
|
||||
|
||||
const handleSlide = useCallback(
|
||||
(val: string) => {
|
||||
|
@ -81,41 +116,50 @@ const SpotSlider = ({
|
|||
s.tradeForm.tradeType === 'Market'
|
||||
? marketPrice
|
||||
: Number(s.tradeForm.price)
|
||||
|
||||
if (s.tradeForm.side === 'buy') {
|
||||
if (Number(price)) {
|
||||
const baseSize = floorToDecimal(
|
||||
parseFloat(val) / price,
|
||||
minOrderDecimals,
|
||||
)
|
||||
const quoteSize = floorToDecimal(baseSize.mul(price), tickDecimals)
|
||||
s.tradeForm.baseSize = baseSize.toFixed()
|
||||
s.tradeForm.quoteSize = quoteSize.toFixed()
|
||||
} else {
|
||||
s.tradeForm.baseSize = ''
|
||||
s.tradeForm.quoteSize = val
|
||||
}
|
||||
} else if (s.tradeForm.side === 'sell') {
|
||||
s.tradeForm.baseSize = val
|
||||
if (Number(price)) {
|
||||
s.tradeForm.quoteSize = floorToDecimal(
|
||||
parseFloat(val) * price,
|
||||
tickDecimals,
|
||||
).toFixed()
|
||||
if (isTriggerOrder) {
|
||||
const baseSize = floorToDecimal(parseFloat(val), minOrderDecimals)
|
||||
const quoteSize = floorToDecimal(baseSize.mul(price), tickDecimals)
|
||||
s.tradeForm.baseSize = baseSize.toFixed()
|
||||
s.tradeForm.quoteSize = quoteSize.toFixed()
|
||||
} else {
|
||||
if (s.tradeForm.side === 'buy') {
|
||||
if (Number(price)) {
|
||||
const baseSize = floorToDecimal(
|
||||
parseFloat(val) / price,
|
||||
minOrderDecimals,
|
||||
)
|
||||
const quoteSize = floorToDecimal(
|
||||
baseSize.mul(price),
|
||||
tickDecimals,
|
||||
)
|
||||
s.tradeForm.baseSize = baseSize.toFixed()
|
||||
s.tradeForm.quoteSize = quoteSize.toFixed()
|
||||
} else {
|
||||
s.tradeForm.baseSize = ''
|
||||
s.tradeForm.quoteSize = val
|
||||
}
|
||||
} else if (s.tradeForm.side === 'sell') {
|
||||
s.tradeForm.baseSize = val
|
||||
if (Number(price)) {
|
||||
s.tradeForm.quoteSize = floorToDecimal(
|
||||
parseFloat(val) * price,
|
||||
tickDecimals,
|
||||
).toFixed()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
[marketPrice, minOrderDecimals, tickDecimals],
|
||||
[marketPrice, minOrderDecimals, tickDecimals, isTriggerOrder],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="w-full px-3 md:px-4">
|
||||
<LeverageSlider
|
||||
amount={
|
||||
tradeForm.side === 'buy'
|
||||
? parseFloat(tradeForm.quoteSize)
|
||||
: parseFloat(tradeForm.baseSize)
|
||||
side === 'buy' && !isTriggerOrder
|
||||
? parseFloat(quoteSize)
|
||||
: parseFloat(baseSize)
|
||||
}
|
||||
leverageMax={max}
|
||||
onChange={handleSlide}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { useEffect, useMemo, useState } from 'react'
|
||||
import TabButtons from '@components/shared/TabButtons'
|
||||
import OpenOrders from './OpenOrders'
|
||||
import SwapTradeBalances from '../shared/BalancesTable'
|
||||
import UnsettledTrades from './UnsettledTrades'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
|
@ -12,6 +11,7 @@ import useUnsettledPerpPositions from 'hooks/useUnsettledPerpPositions'
|
|||
import TradeHistory from './TradeHistory'
|
||||
import useOpenPerpPositions from 'hooks/useOpenPerpPositions'
|
||||
import ManualRefresh from '@components/shared/ManualRefresh'
|
||||
import AccountOrders from '@components/account/AccountOrders'
|
||||
|
||||
const TradeInfoTabs = () => {
|
||||
const [selectedTab, setSelectedTab] = useState('balances')
|
||||
|
@ -30,13 +30,22 @@ const TradeInfoTabs = () => {
|
|||
}, [selectedMarketName])
|
||||
|
||||
const tabsWithCount: [string, number][] = useMemo(() => {
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
const unsettledTradeCount =
|
||||
Object.values(unsettledSpotBalances).flat().length +
|
||||
unsettledPerpPositions?.length
|
||||
|
||||
const stopOrdersCount =
|
||||
mangoAccount?.tokenConditionalSwaps.filter((tcs) => tcs.hasData)
|
||||
?.length || 0
|
||||
|
||||
return [
|
||||
['balances', 0],
|
||||
['trade:positions', openPerpPositions.length],
|
||||
['trade:orders', Object.values(openOrders).flat().length],
|
||||
[
|
||||
'trade:orders',
|
||||
Object.values(openOrders).flat().length + stopOrdersCount,
|
||||
],
|
||||
['trade:unsettled', unsettledTradeCount],
|
||||
['trade-history', 0],
|
||||
]
|
||||
|
@ -79,7 +88,7 @@ const TabContent = ({ selectedTab }: { selectedTab: string }) => {
|
|||
case 'balances':
|
||||
return <SwapTradeBalances />
|
||||
case 'trade:orders':
|
||||
return <OpenOrders />
|
||||
return <AccountOrders />
|
||||
case 'trade:unsettled':
|
||||
return (
|
||||
<UnsettledTrades
|
||||
|
|
|
@ -22,7 +22,10 @@ import {
|
|||
} from 'utils/numbers'
|
||||
import { formatTokenSymbol } from 'utils/tokens'
|
||||
import useOpenPerpPositions from 'hooks/useOpenPerpPositions'
|
||||
import { calculateEstPriceForBaseSize } from 'utils/tradeForm'
|
||||
import {
|
||||
TriggerOrderTypes,
|
||||
calculateEstPriceForBaseSize,
|
||||
} from 'utils/tradeForm'
|
||||
|
||||
const TradeSummary = ({
|
||||
balanceBank,
|
||||
|
@ -174,6 +177,10 @@ const TradeSummary = ({
|
|||
)
|
||||
}, [quoteBank, tradeForm])
|
||||
|
||||
const isTriggerOrder =
|
||||
tradeForm.tradeType === TriggerOrderTypes.STOP_LOSS ||
|
||||
tradeForm.tradeType === TriggerOrderTypes.TAKE_PROFIT
|
||||
|
||||
return (
|
||||
<div className="space-y-2 px-3 md:px-4">
|
||||
<div className="flex justify-between text-xs">
|
||||
|
@ -277,7 +284,7 @@ const TradeSummary = ({
|
|||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
{selectedMarket instanceof Serum3Market ? (
|
||||
{selectedMarket instanceof Serum3Market && !isTriggerOrder ? (
|
||||
<div className="flex justify-between text-xs">
|
||||
<p>{t('common:route')}</p>
|
||||
<p className="text-th-fgd-2">Openbook</p>
|
||||
|
|
|
@ -27,11 +27,16 @@ import {
|
|||
PerpOrder,
|
||||
Serum3Market,
|
||||
Serum3Side,
|
||||
TokenConditionalSwap,
|
||||
} from '@blockworks-foundation/mango-v4'
|
||||
import { Order } from '@project-serum/serum/lib/market'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import { formatNumericValue, getDecimalCount } from 'utils/numbers'
|
||||
import {
|
||||
floorToDecimal,
|
||||
formatNumericValue,
|
||||
getDecimalCount,
|
||||
} from 'utils/numbers'
|
||||
import { BN } from '@coral-xyz/anchor'
|
||||
import Datafeed from 'apis/datafeed'
|
||||
// import PerpDatafeed from 'apis/mngo/datafeed'
|
||||
|
@ -43,6 +48,7 @@ import ModifyTvOrderModal from '@components/modals/ModifyTvOrderModal'
|
|||
import { findSerum3MarketPkInOpenOrders } from './OpenOrders'
|
||||
import { Transition } from '@headlessui/react'
|
||||
import useThemeWrapper from 'hooks/useThemeWrapper'
|
||||
import { handleCancelTriggerOrder } from '@components/swap/SwapTriggerOrders'
|
||||
|
||||
export interface ChartContainerProps {
|
||||
container: ChartingLibraryWidgetOptions['container']
|
||||
|
@ -70,6 +76,15 @@ function hexToRgb(hex: string) {
|
|||
: null
|
||||
}
|
||||
|
||||
const getTriggerOrders = () => {
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
if (!mangoAccount) return []
|
||||
const triggerOrders = mangoAccount.tokenConditionalSwaps.filter(
|
||||
(tcs) => tcs.hasData,
|
||||
)
|
||||
return triggerOrders
|
||||
}
|
||||
|
||||
const TradingViewChart = () => {
|
||||
const { t } = useTranslation(['common', 'tv-chart', 'trade'])
|
||||
const { theme } = useThemeWrapper()
|
||||
|
@ -146,9 +161,10 @@ const TradingViewChart = () => {
|
|||
tvWidgetRef.current.activeChart().resolution(),
|
||||
() => {
|
||||
if (showOrderLinesLocalStorage) {
|
||||
const openOrders = mangoStore.getState().mangoAccount.openOrders
|
||||
const { openOrders } = mangoStore.getState().mangoAccount
|
||||
deleteLines()
|
||||
drawLinesForMarket(openOrders)
|
||||
const triggerOrders = getTriggerOrders()
|
||||
drawLinesForMarket(openOrders, triggerOrders)
|
||||
}
|
||||
return
|
||||
},
|
||||
|
@ -297,6 +313,7 @@ const TradingViewChart = () => {
|
|||
order.size,
|
||||
minOrderDecimals,
|
||||
)
|
||||
const sideColor = isLong ? COLORS.UP[theme] : COLORS.DOWN[theme]
|
||||
if (!tvWidgetRef?.current?.chart()) return
|
||||
return (
|
||||
tvWidgetRef.current
|
||||
|
@ -363,20 +380,126 @@ const TradingViewChart = () => {
|
|||
// ? `${order.orderType} Order #: ${order.orderId}`
|
||||
// : `Order #: ${order.orderId}`
|
||||
// )
|
||||
.setBodyTextColor(isLong ? COLORS.UP[theme] : COLORS.DOWN[theme])
|
||||
.setQuantityTextColor(isLong ? COLORS.UP[theme] : COLORS.DOWN[theme])
|
||||
.setBodyTextColor(sideColor)
|
||||
.setQuantityTextColor(sideColor)
|
||||
.setCancelButtonIconColor(COLORS.FGD4[theme])
|
||||
.setBodyBorderColor(isLong ? COLORS.UP[theme] : COLORS.DOWN[theme])
|
||||
.setQuantityBorderColor(
|
||||
isLong ? COLORS.UP[theme] : COLORS.DOWN[theme],
|
||||
)
|
||||
.setCancelButtonBorderColor(
|
||||
isLong ? COLORS.UP[theme] : COLORS.DOWN[theme],
|
||||
)
|
||||
.setBodyBorderColor(sideColor)
|
||||
.setQuantityBorderColor(sideColor)
|
||||
.setCancelButtonBorderColor(sideColor)
|
||||
.setBodyBackgroundColor(COLORS.BKG1[theme])
|
||||
.setQuantityBackgroundColor(COLORS.BKG1[theme])
|
||||
.setCancelButtonBackgroundColor(COLORS.BKG1[theme])
|
||||
.setLineColor(isLong ? COLORS.UP[theme] : COLORS.DOWN[theme])
|
||||
.setLineColor(sideColor)
|
||||
.setLineLength(3)
|
||||
.setLineWidth(1)
|
||||
.setLineStyle(1)
|
||||
)
|
||||
},
|
||||
[
|
||||
cancelPerpOrder,
|
||||
cancelSpotOrder,
|
||||
selectedMarketName,
|
||||
t,
|
||||
theme,
|
||||
getOrderDecimals,
|
||||
],
|
||||
)
|
||||
|
||||
const drawTriggerOrderLine = useCallback(
|
||||
(order: TokenConditionalSwap) => {
|
||||
const { group } = mangoStore.getState()
|
||||
const selectedMarket = mangoStore.getState().selectedMarket.current
|
||||
if (!group) return
|
||||
const buyBank = group.getFirstBankByTokenIndex(order.buyTokenIndex)
|
||||
const sellBank = group.getFirstBankByTokenIndex(order.sellTokenIndex)
|
||||
const maxBuy = floorToDecimal(
|
||||
order.getMaxBuyUi(group),
|
||||
buyBank.mintDecimals,
|
||||
).toNumber()
|
||||
const maxSell = floorToDecimal(
|
||||
order.getMaxSellUi(group),
|
||||
sellBank.mintDecimals,
|
||||
).toNumber()
|
||||
let side: string
|
||||
let orderSizeUi: number
|
||||
if (maxBuy === 0 || maxBuy > maxSell) {
|
||||
orderSizeUi = maxSell
|
||||
side = 'sell'
|
||||
} else {
|
||||
orderSizeUi = maxBuy
|
||||
side = 'buy'
|
||||
}
|
||||
const price = order.getThresholdPriceUi(group)
|
||||
const isReducingShort = side.toLowerCase() === 'buy'
|
||||
let orderType
|
||||
if (selectedMarket && selectedMarket instanceof Serum3Market) {
|
||||
const baseBank =
|
||||
selectedMarket.baseTokenIndex === buyBank.tokenIndex
|
||||
? buyBank
|
||||
: sellBank
|
||||
const quoteBank =
|
||||
selectedMarket.quoteTokenIndex === buyBank.tokenIndex
|
||||
? buyBank
|
||||
: sellBank
|
||||
const currentPrice = baseBank.uiPrice / quoteBank.uiPrice
|
||||
orderType =
|
||||
(isReducingShort && price > currentPrice) ||
|
||||
(!isReducingShort && price < currentPrice)
|
||||
? t('trade:stop-loss')
|
||||
: t('trade:take-profit')
|
||||
}
|
||||
const [
|
||||
// minOrderDecimals,
|
||||
tickSizeDecimals,
|
||||
] = getOrderDecimals()
|
||||
const sideColor = isReducingShort ? COLORS.UP[theme] : COLORS.DOWN[theme]
|
||||
|
||||
if (!tvWidgetRef?.current?.chart()) return
|
||||
return (
|
||||
tvWidgetRef.current
|
||||
.chart()
|
||||
.createOrderLine({ disableUndo: false })
|
||||
.onMove(function (this: IOrderLineAdapter) {
|
||||
tvWidgetRef.current?.showNoticeDialog({
|
||||
title: 'Edit trigger order',
|
||||
body: 'Editing trigger orders is coming soon',
|
||||
callback: () => this.setPrice(price),
|
||||
})
|
||||
})
|
||||
.onCancel(function () {
|
||||
tvWidgetRef.current?.showConfirmDialog({
|
||||
title: t('tv-chart:cancel-order'),
|
||||
body: t('tv-chart:cancel-order-details', {
|
||||
marketName: selectedMarketName,
|
||||
orderSize: orderSizeUi,
|
||||
orderSide: side.toUpperCase(),
|
||||
orderPrice: formatNumericValue(price, tickSizeDecimals),
|
||||
}),
|
||||
callback: (res) => {
|
||||
if (res) {
|
||||
handleCancelTriggerOrder(order.id)
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
.setPrice(price)
|
||||
.setQuantity(orderSizeUi.toString())
|
||||
.setText(orderType ? orderType.toUpperCase() : side.toUpperCase())
|
||||
// .setTooltip(
|
||||
// order.perpTrigger?.clientOrderId
|
||||
// ? `${order.orderType} Order #: ${order.orderId}`
|
||||
// : `Order #: ${order.orderId}`
|
||||
// )
|
||||
.setBodyTextColor(sideColor)
|
||||
.setQuantityTextColor(sideColor)
|
||||
.setCancelButtonIconColor(COLORS.FGD4[theme])
|
||||
.setBodyBorderColor(sideColor)
|
||||
.setQuantityBorderColor(sideColor)
|
||||
.setCancelButtonBorderColor(sideColor)
|
||||
.setBodyBackgroundColor(COLORS.BKG1[theme])
|
||||
.setQuantityBackgroundColor(COLORS.BKG1[theme])
|
||||
.setCancelButtonBackgroundColor(COLORS.BKG1[theme])
|
||||
.setLineColor(sideColor)
|
||||
.setLineLength(3)
|
||||
.setLineWidth(1)
|
||||
.setLineStyle(1)
|
||||
|
@ -393,15 +516,18 @@ const TradingViewChart = () => {
|
|||
)
|
||||
|
||||
const drawLinesForMarket = useCallback(
|
||||
(openOrders: Record<string, Order[] | PerpOrder[]>) => {
|
||||
(
|
||||
openOrders: Record<string, Order[] | PerpOrder[]>,
|
||||
triggerOrders: TokenConditionalSwap[],
|
||||
) => {
|
||||
const set = mangoStore.getState().set
|
||||
const newOrderLines = new Map()
|
||||
const oOrders = Object.entries(openOrders).map(([marketPk, orders]) => ({
|
||||
orders,
|
||||
marketPk,
|
||||
}))
|
||||
const selectedMarket = mangoStore.getState().selectedMarket.current
|
||||
if (oOrders?.length) {
|
||||
const selectedMarket = mangoStore.getState().selectedMarket.current
|
||||
const selectedMarketPk =
|
||||
selectedMarket instanceof Serum3Market
|
||||
? selectedMarket?.serumMarketExternal.toString()
|
||||
|
@ -414,6 +540,24 @@ const TradingViewChart = () => {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (triggerOrders.length && selectedMarket instanceof Serum3Market) {
|
||||
const { baseTokenIndex, quoteTokenIndex } = selectedMarket
|
||||
for (const triggerOrder of triggerOrders) {
|
||||
const { buyTokenIndex, sellTokenIndex } = triggerOrder
|
||||
|
||||
if (
|
||||
(baseTokenIndex === buyTokenIndex ||
|
||||
quoteTokenIndex === buyTokenIndex) &&
|
||||
(baseTokenIndex === sellTokenIndex ||
|
||||
quoteTokenIndex === sellTokenIndex)
|
||||
) {
|
||||
newOrderLines.set(
|
||||
triggerOrder.id.toString(),
|
||||
drawTriggerOrderLine(triggerOrder),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
set((state) => {
|
||||
state.tradingView.orderLines = newOrderLines
|
||||
})
|
||||
|
@ -429,7 +573,8 @@ const TradingViewChart = () => {
|
|||
el.style.color = COLORS.FGD4[theme]
|
||||
} else {
|
||||
const openOrders = mangoStore.getState().mangoAccount.openOrders
|
||||
drawLinesForMarket(openOrders)
|
||||
const triggerOrders = getTriggerOrders()
|
||||
drawLinesForMarket(openOrders, triggerOrders)
|
||||
el.style.color = COLORS.ACTIVE[theme]
|
||||
}
|
||||
},
|
||||
|
@ -438,9 +583,10 @@ const TradingViewChart = () => {
|
|||
|
||||
const closeModifyOrderModal = useCallback(() => {
|
||||
const openOrders = mangoStore.getState().mangoAccount.openOrders
|
||||
const triggerOrders = getTriggerOrders()
|
||||
setOrderToModify(null)
|
||||
deleteLines()
|
||||
drawLinesForMarket(openOrders)
|
||||
drawLinesForMarket(openOrders, triggerOrders)
|
||||
}, [deleteLines, drawLinesForMarket])
|
||||
|
||||
const toggleTradeExecutions = useCallback(
|
||||
|
@ -674,9 +820,14 @@ const TradingViewChart = () => {
|
|||
let subscription
|
||||
if (chartReady && tvWidgetRef?.current) {
|
||||
subscription = mangoStore.subscribe(
|
||||
(state) => state.mangoAccount.openOrders,
|
||||
(openOrders) => {
|
||||
(state) => state.mangoAccount,
|
||||
(account) => {
|
||||
if (showOrderLines) {
|
||||
const openOrders = account.openOrders
|
||||
const triggerOrders =
|
||||
account.current?.tokenConditionalSwaps.filter(
|
||||
(tcs) => tcs.hasData,
|
||||
) || []
|
||||
const orderLines = mangoStore.getState().tradingView.orderLines
|
||||
tvWidgetRef.current?.onChartReady(() => {
|
||||
let matchingOrderLines = 0
|
||||
|
@ -697,6 +848,11 @@ const TradingViewChart = () => {
|
|||
}
|
||||
}
|
||||
})
|
||||
triggerOrders?.forEach((order) => {
|
||||
if (order.id.toString() == key) {
|
||||
matchingOrderLines += 1
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const selectedMarket =
|
||||
|
@ -706,11 +862,29 @@ const TradingViewChart = () => {
|
|||
? selectedMarket?.serumMarketExternal.toString()
|
||||
: selectedMarket?.publicKey.toString()
|
||||
|
||||
let ordersForMarket = 0
|
||||
oOrders?.forEach(({ marketPk, orders }) => {
|
||||
if (marketPk === selectedMarketPk) {
|
||||
openOrdersForMarket = orders.length
|
||||
ordersForMarket = orders.length
|
||||
}
|
||||
})
|
||||
let triggerOrdersForMarket = 0
|
||||
triggerOrders.forEach((order) => {
|
||||
if (selectedMarket instanceof Serum3Market) {
|
||||
const { baseTokenIndex, quoteTokenIndex } = selectedMarket
|
||||
const { buyTokenIndex, sellTokenIndex } = order
|
||||
if (
|
||||
(baseTokenIndex === buyTokenIndex ||
|
||||
quoteTokenIndex === buyTokenIndex) &&
|
||||
(baseTokenIndex === sellTokenIndex ||
|
||||
quoteTokenIndex === sellTokenIndex)
|
||||
) {
|
||||
triggerOrdersForMarket += 1
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
openOrdersForMarket = ordersForMarket + triggerOrdersForMarket
|
||||
|
||||
tvWidgetRef.current?.activeChart().dataReady(() => {
|
||||
if (
|
||||
|
@ -718,7 +892,9 @@ const TradingViewChart = () => {
|
|||
orderLines?.size !== matchingOrderLines
|
||||
) {
|
||||
deleteLines()
|
||||
drawLinesForMarket(openOrders)
|
||||
// might need to change
|
||||
const triggerOrders = getTriggerOrders()
|
||||
drawLinesForMarket(openOrders, triggerOrders)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
import { PerpMarket } from '@blockworks-foundation/mango-v4'
|
||||
import MaxAmountButton from '@components/shared/MaxAmountButton'
|
||||
import { FadeInFadeOut } from '@components/shared/Transitions'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
import useUnownedAccount from 'hooks/useUnownedAccount'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { floorToDecimal } from 'utils/numbers'
|
||||
|
||||
const TriggerOrderMaxButton = ({
|
||||
minOrderDecimals,
|
||||
tickDecimals,
|
||||
large,
|
||||
}: {
|
||||
minOrderDecimals: number
|
||||
tickDecimals: number
|
||||
large?: boolean
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
const { selectedMarket } = useSelectedMarket()
|
||||
const { price, side } = mangoStore((s) => s.tradeForm)
|
||||
const { isUnownedAccount } = useUnownedAccount()
|
||||
const { connected } = useWallet()
|
||||
|
||||
const positionBank = useMemo(() => {
|
||||
const { group } = mangoStore.getState()
|
||||
if (!group || !selectedMarket || selectedMarket instanceof PerpMarket)
|
||||
return
|
||||
const bank = group.getFirstBankByTokenIndex(selectedMarket.baseTokenIndex)
|
||||
return bank
|
||||
}, [selectedMarket])
|
||||
|
||||
const max = useMemo(() => {
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
if (!positionBank || !mangoAccount) return 0
|
||||
let max = 0
|
||||
const balance = mangoAccount.getTokenBalanceUi(positionBank)
|
||||
const roundedBalance = floorToDecimal(balance, minOrderDecimals).toNumber()
|
||||
if (side === 'buy') {
|
||||
max = roundedBalance < 0 ? roundedBalance : 0
|
||||
} else {
|
||||
max = roundedBalance > 0 ? roundedBalance : 0
|
||||
}
|
||||
return max
|
||||
}, [positionBank, side, minOrderDecimals])
|
||||
|
||||
const handleMax = useCallback(() => {
|
||||
const set = mangoStore.getState().set
|
||||
const baseSize = floorToDecimal(Math.abs(max), minOrderDecimals)
|
||||
const quoteSize = price
|
||||
? floorToDecimal(baseSize.mul(price), tickDecimals)
|
||||
: 0
|
||||
set((state) => {
|
||||
state.tradeForm.baseSize = baseSize.toFixed()
|
||||
state.tradeForm.quoteSize = quoteSize.toFixed()
|
||||
})
|
||||
}, [minOrderDecimals, price, tickDecimals])
|
||||
|
||||
return (
|
||||
<FadeInFadeOut show={!!price && !isUnownedAccount && connected}>
|
||||
<MaxAmountButton
|
||||
className={large ? 'text-sm' : 'text-xs'}
|
||||
decimals={minOrderDecimals}
|
||||
label={t('trade:position')}
|
||||
onClick={handleMax}
|
||||
value={max}
|
||||
/>
|
||||
</FadeInFadeOut>
|
||||
)
|
||||
}
|
||||
|
||||
export default TriggerOrderMaxButton
|
|
@ -23,7 +23,7 @@
|
|||
"dependencies": {
|
||||
"@blockworks-foundation/mango-feeds": "0.1.7",
|
||||
"@blockworks-foundation/mango-mints-redemption": "^0.0.10",
|
||||
"@blockworks-foundation/mango-v4": "^0.19.41",
|
||||
"@blockworks-foundation/mango-v4": "0.19.43",
|
||||
"@blockworks-foundation/mango-v4-settings": "0.2.16",
|
||||
"@blockworks-foundation/mangolana": "0.0.1-beta.15",
|
||||
"@headlessui/react": "1.6.6",
|
||||
|
|
|
@ -32,6 +32,7 @@ import DashboardSuggestedValues from '@components/modals/DashboardSuggestedValue
|
|||
import { USDC_MINT } from 'utils/constants'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import useBanks from 'hooks/useBanks'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
|
@ -55,9 +56,34 @@ export async function getStaticProps({ locale }: { locale: string }) {
|
|||
const Dashboard: NextPage = () => {
|
||||
const { group } = useMangoGroup()
|
||||
const connection = mangoStore((s) => s.connection)
|
||||
|
||||
const { banks } = useBanks()
|
||||
const [isOpenSuggestionModal, setIsOpenSuggestionModal] = useState(false)
|
||||
const [priceImpacts, setPriceImapcts] = useState<PriceImpact[]>([])
|
||||
const [stickyIndex, setStickyIndex] = useState(-1)
|
||||
|
||||
const handleScroll = useCallback(() => {
|
||||
for (let i = 0; i < banks.length; i++) {
|
||||
const element = document.getElementById(`parent-item-${i}`)
|
||||
|
||||
if (element) {
|
||||
const rect = element.getBoundingClientRect()
|
||||
|
||||
if (rect.top <= 0) {
|
||||
setStickyIndex(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [banks])
|
||||
|
||||
useEffect(() => {
|
||||
if (banks.length) {
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}
|
||||
}, [banks])
|
||||
|
||||
useEffect(() => {
|
||||
const handleGetPriceImapcts = async () => {
|
||||
|
@ -114,28 +140,35 @@ const Dashboard: NextPage = () => {
|
|||
</div>
|
||||
<h3 className="mb-3 mt-6 text-base text-th-fgd-3">Banks</h3>
|
||||
<div className="border-b border-th-bkg-3">
|
||||
{Array.from(group.banksMapByMint)
|
||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||
.map(([mintAddress, banks]) =>
|
||||
banks.map((bank) => {
|
||||
const mintInfo = group.mintInfosMapByMint.get(
|
||||
bank.mint.toString(),
|
||||
)
|
||||
{banks
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((bank, i) => {
|
||||
const mintInfo = group.mintInfosMapByMint.get(
|
||||
bank.mint.toString(),
|
||||
)
|
||||
|
||||
const formattedBankValues = getFormattedBankValues(
|
||||
group,
|
||||
bank,
|
||||
)
|
||||
const formattedBankValues = getFormattedBankValues(
|
||||
group,
|
||||
bank,
|
||||
)
|
||||
|
||||
return (
|
||||
<Disclosure key={bank.publicKey.toString()}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
return (
|
||||
<Disclosure key={bank.publicKey.toString()}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<div
|
||||
className={`w-full border-t border-th-bkg-3 p-4 md:hover:bg-th-bkg-4 ${
|
||||
open
|
||||
? i === stickyIndex
|
||||
? 'sticky top-0 bg-th-bkg-4'
|
||||
: 'bg-th-bkg-4'
|
||||
: ''
|
||||
}`}
|
||||
id={`parent-item-${i}`}
|
||||
>
|
||||
<Disclosure.Button
|
||||
className="flex w-full items-center justify-between"
|
||||
aria-label="panel"
|
||||
className={`flex w-full items-center justify-between border-t border-th-bkg-3 p-4 md:hover:bg-th-bkg-4 ${
|
||||
open ? 'bg-th-bkg-4' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<TokenLogo bank={bank} />
|
||||
|
@ -149,243 +182,245 @@ const Dashboard: NextPage = () => {
|
|||
} h-5 w-5 text-th-fgd-3`}
|
||||
/>
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel>
|
||||
<KeyValuePair
|
||||
label="Mint"
|
||||
value={
|
||||
<ExplorerLink address={mintAddress} />
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Bank"
|
||||
value={
|
||||
<ExplorerLink
|
||||
address={formattedBankValues.publicKey.toString()}
|
||||
anchorData
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="MintInfo"
|
||||
value={
|
||||
<ExplorerLink
|
||||
address={mintInfo!.publicKey.toString()}
|
||||
anchorData
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Vault"
|
||||
value={
|
||||
<ExplorerLink
|
||||
address={formattedBankValues.vault}
|
||||
anchorData
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Oracle"
|
||||
value={
|
||||
bank.oracleProvider ==
|
||||
OracleProvider.Switchboard ? (
|
||||
<a
|
||||
href={`https://app.switchboard.xyz/solana/mainnet-beta/feed/${bank.oracle.toString()}`}
|
||||
className={`flex items-center break-all text-th-fgd-2 hover:text-th-fgd-3`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{bank.oracle.toString()}
|
||||
<ArrowTopRightOnSquareIcon className="ml-2 h-5 w-5 whitespace-nowrap" />
|
||||
</a>
|
||||
) : (
|
||||
<ExplorerLink
|
||||
address={formattedBankValues.oracle}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Token Index"
|
||||
value={formattedBankValues.tokenIndex}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Mint Decimals"
|
||||
value={formattedBankValues.mintDecimals}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Oracle Price"
|
||||
value={`$${bank.uiPrice}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Stable Price"
|
||||
value={`$${formattedBankValues.stablePrice}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Last stable price updated"
|
||||
value={
|
||||
formattedBankValues.lastStablePriceUpdated
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Stable Price: delay interval"
|
||||
value={`${formattedBankValues.stablePriceModel.delayIntervalSeconds}s`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Stable Price: growth limits"
|
||||
value={`${formattedBankValues.stablePriceGrowthLimitsDelay}% delay / ${formattedBankValues.stablePriceGrowthLimitsStable}% stable`}
|
||||
/>
|
||||
<VaultData bank={bank} />
|
||||
<KeyValuePair
|
||||
label="Loan Fee Rate"
|
||||
value={`${formattedBankValues.loanFeeRate} bps`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Loan origination fee rate"
|
||||
value={`${formattedBankValues.loanOriginationFeeRate} bps`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Collected fees native"
|
||||
value={`${formattedBankValues.collectedFeesNative} ($${formattedBankValues.collectedFeesNativePrice})`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Dust"
|
||||
value={formattedBankValues.dust}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Deposits"
|
||||
value={`${formattedBankValues.deposits} ($${formattedBankValues.depositsPrice})`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Borrows"
|
||||
value={`${formattedBankValues.borrows} ($${formattedBankValues.borrowsPrice})`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Reduce Only"
|
||||
value={`${
|
||||
bank.reduceOnly
|
||||
} (Are deposits reduce only - ${bank.areDepositsReduceOnly()}, Are borrows reduce only - ${bank.areBorrowsReduceOnly()})`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Avg Utilization"
|
||||
value={`${formattedBankValues.avgUtilization}%`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Maint Asset/Liab Weight"
|
||||
value={`${formattedBankValues.maintAssetWeight} /
|
||||
${formattedBankValues.maintLiabWeight}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Init Asset/Liab Weight"
|
||||
value={`${formattedBankValues.initAssetWeight} /
|
||||
${formattedBankValues.initLiabWeight}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Scaled Init Asset/Liab Weight"
|
||||
value={`${formattedBankValues.scaledInitAssetWeight} / ${formattedBankValues.scaledInitLiabWeight}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Deposit weight scale start quote"
|
||||
value={`$${formattedBankValues.depositWeightScaleStartQuote}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Borrow weight scale start quote"
|
||||
value={`$${formattedBankValues.borrowWeightScaleStartQuote}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Rate params"
|
||||
value={
|
||||
<span className="text-right">
|
||||
{`${formattedBankValues.rate0}% @ ${formattedBankValues.util0}% util, `}
|
||||
{`${formattedBankValues.rate1}% @ ${formattedBankValues.util1}% util, `}
|
||||
{`${formattedBankValues.maxRate}% @ 100% util`}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Adjustment factor"
|
||||
value={`${formattedBankValues.adjustmentFactor}%`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Deposit rate"
|
||||
value={`${formattedBankValues.depositRate}%`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Borrow rate"
|
||||
value={`${formattedBankValues.borrowRate}%`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Last index update"
|
||||
value={formattedBankValues.lastIndexUpdate}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Last rates updated"
|
||||
value={formattedBankValues.lastRatesUpdate}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Oracle: Conf Filter"
|
||||
value={`${
|
||||
formattedBankValues.oracleConfFilter
|
||||
}% (Last known confidence ${bank._oracleLastKnownDeviation
|
||||
?.div(bank.price)
|
||||
.mul(I80F48.fromNumber(100))
|
||||
.toNumber()
|
||||
.toFixed(2)}%)`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Oracle: Max Staleness"
|
||||
value={`${bank.oracleConfig.maxStalenessSlots} slots (Last updated slot ${bank._oracleLastUpdatedSlot})`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Group Insurance Fund"
|
||||
value={`${mintInfo!.groupInsuranceFund}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Min vault to deposits ratio"
|
||||
value={`${formattedBankValues.minVaultToDepositsRatio}%`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label={`Net borrows in window (next window starts ${dayjs().to(
|
||||
dayjs().add(
|
||||
bank.getTimeToNextBorrowLimitWindowStartsTs(),
|
||||
'second',
|
||||
),
|
||||
)})`}
|
||||
value={`$${formattedBankValues.minVaultToDepositsRatio} / $${formattedBankValues.netBorrowLimitPerWindowQuote}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Liquidation fee"
|
||||
value={`${formattedBankValues.liquidationFee}%`}
|
||||
/>
|
||||
{bank.mint.toBase58() !== USDC_MINT && (
|
||||
<div className="mb-4 mt-2 flex">
|
||||
<Button
|
||||
className=" ml-auto"
|
||||
onClick={() =>
|
||||
setIsOpenSuggestionModal(true)
|
||||
}
|
||||
</div>
|
||||
<Disclosure.Panel>
|
||||
<KeyValuePair
|
||||
label="Mint"
|
||||
value={
|
||||
<ExplorerLink
|
||||
address={bank.mint.toString()}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Bank"
|
||||
value={
|
||||
<ExplorerLink
|
||||
address={formattedBankValues.publicKey.toString()}
|
||||
anchorData
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="MintInfo"
|
||||
value={
|
||||
<ExplorerLink
|
||||
address={mintInfo!.publicKey.toString()}
|
||||
anchorData
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Vault"
|
||||
value={
|
||||
<ExplorerLink
|
||||
address={formattedBankValues.vault}
|
||||
anchorData
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Oracle"
|
||||
value={
|
||||
bank.oracleProvider ==
|
||||
OracleProvider.Switchboard ? (
|
||||
<a
|
||||
href={`https://app.switchboard.xyz/solana/mainnet-beta/feed/${bank.oracle.toString()}`}
|
||||
className={`flex items-center break-all text-th-fgd-2 hover:text-th-fgd-3`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Check suggested values
|
||||
{isOpenSuggestionModal && (
|
||||
<DashboardSuggestedValues
|
||||
priceImpacts={priceImpacts}
|
||||
group={group}
|
||||
bank={bank}
|
||||
isOpen={isOpenSuggestionModal}
|
||||
onClose={() =>
|
||||
setIsOpenSuggestionModal(false)
|
||||
}
|
||||
></DashboardSuggestedValues>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
)
|
||||
}),
|
||||
)}
|
||||
{bank.oracle.toString()}
|
||||
<ArrowTopRightOnSquareIcon className="ml-2 h-5 w-5 whitespace-nowrap" />
|
||||
</a>
|
||||
) : (
|
||||
<ExplorerLink
|
||||
address={formattedBankValues.oracle}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Token Index"
|
||||
value={formattedBankValues.tokenIndex}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Mint Decimals"
|
||||
value={formattedBankValues.mintDecimals}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Oracle Price"
|
||||
value={`$${bank.uiPrice}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Stable Price"
|
||||
value={`$${formattedBankValues.stablePrice}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Last stable price updated"
|
||||
value={
|
||||
formattedBankValues.lastStablePriceUpdated
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Stable Price: delay interval"
|
||||
value={`${formattedBankValues.stablePriceModel.delayIntervalSeconds}s`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Stable Price: growth limits"
|
||||
value={`${formattedBankValues.stablePriceGrowthLimitsDelay}% delay / ${formattedBankValues.stablePriceGrowthLimitsStable}% stable`}
|
||||
/>
|
||||
<VaultData bank={bank} />
|
||||
<KeyValuePair
|
||||
label="Loan Fee Rate"
|
||||
value={`${formattedBankValues.loanFeeRate} bps`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Loan origination fee rate"
|
||||
value={`${formattedBankValues.loanOriginationFeeRate} bps`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Collected fees native"
|
||||
value={`${formattedBankValues.collectedFeesNative} ($${formattedBankValues.collectedFeesNativePrice})`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Dust"
|
||||
value={formattedBankValues.dust}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Deposits"
|
||||
value={`${formattedBankValues.deposits} ($${formattedBankValues.depositsPrice})`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Borrows"
|
||||
value={`${formattedBankValues.borrows} ($${formattedBankValues.borrowsPrice})`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Reduce Only"
|
||||
value={`${
|
||||
bank.reduceOnly
|
||||
} (Are deposits reduce only - ${bank.areDepositsReduceOnly()}, Are borrows reduce only - ${bank.areBorrowsReduceOnly()})`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Avg Utilization"
|
||||
value={`${formattedBankValues.avgUtilization}%`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Maint Asset/Liab Weight"
|
||||
value={`${formattedBankValues.maintAssetWeight} /
|
||||
${formattedBankValues.maintLiabWeight}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Init Asset/Liab Weight"
|
||||
value={`${formattedBankValues.initAssetWeight} /
|
||||
${formattedBankValues.initLiabWeight}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Scaled Init Asset/Liab Weight"
|
||||
value={`${formattedBankValues.scaledInitAssetWeight} / ${formattedBankValues.scaledInitLiabWeight}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Deposit weight scale start quote"
|
||||
value={`$${formattedBankValues.depositWeightScaleStartQuote}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Borrow weight scale start quote"
|
||||
value={`$${formattedBankValues.borrowWeightScaleStartQuote}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Rate params"
|
||||
value={
|
||||
<span className="text-right">
|
||||
{`${formattedBankValues.rate0}% @ ${formattedBankValues.util0}% util, `}
|
||||
{`${formattedBankValues.rate1}% @ ${formattedBankValues.util1}% util, `}
|
||||
{`${formattedBankValues.maxRate}% @ 100% util`}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Adjustment factor"
|
||||
value={`${formattedBankValues.adjustmentFactor}%`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Deposit rate"
|
||||
value={`${formattedBankValues.depositRate}%`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Borrow rate"
|
||||
value={`${formattedBankValues.borrowRate}%`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Last index update"
|
||||
value={formattedBankValues.lastIndexUpdate}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Last rates updated"
|
||||
value={formattedBankValues.lastRatesUpdate}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Oracle: Conf Filter"
|
||||
value={`${
|
||||
formattedBankValues.oracleConfFilter
|
||||
}% (Last known confidence ${bank._oracleLastKnownDeviation
|
||||
?.div(bank.price)
|
||||
.mul(I80F48.fromNumber(100))
|
||||
.toNumber()
|
||||
.toFixed(2)}%)`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Oracle: Max Staleness"
|
||||
value={`${bank.oracleConfig.maxStalenessSlots} slots (Last updated slot ${bank._oracleLastUpdatedSlot})`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Group Insurance Fund"
|
||||
value={`${mintInfo!.groupInsuranceFund}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Min vault to deposits ratio"
|
||||
value={`${formattedBankValues.minVaultToDepositsRatio}%`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label={`Net borrows in window (next window starts ${dayjs().to(
|
||||
dayjs().add(
|
||||
bank.getTimeToNextBorrowLimitWindowStartsTs(),
|
||||
'second',
|
||||
),
|
||||
)})`}
|
||||
value={`$${formattedBankValues.minVaultToDepositsRatio} / $${formattedBankValues.netBorrowLimitPerWindowQuote}`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Liquidation fee"
|
||||
value={`${formattedBankValues.liquidationFee}%`}
|
||||
/>
|
||||
{bank.mint.toBase58() !== USDC_MINT && (
|
||||
<div className="mb-4 mt-2 flex">
|
||||
<Button
|
||||
className=" ml-auto"
|
||||
onClick={() =>
|
||||
setIsOpenSuggestionModal(true)
|
||||
}
|
||||
>
|
||||
Check suggested values
|
||||
{isOpenSuggestionModal && (
|
||||
<DashboardSuggestedValues
|
||||
priceImpacts={priceImpacts}
|
||||
group={group}
|
||||
bank={bank}
|
||||
isOpen={isOpenSuggestionModal}
|
||||
onClose={() =>
|
||||
setIsOpenSuggestionModal(false)
|
||||
}
|
||||
></DashboardSuggestedValues>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<h3 className="mb-3 mt-6 text-base text-th-fgd-3">
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
"tooltip-favorite-swap-add": "Add pair to favorites",
|
||||
"tooltip-favorite-swap-remove": "Remove pair from favorites",
|
||||
"tooltip-wallet-swap": "When enabled, you'll swap tokens in your wallet. When disabled, you'll swap tokens in your Mango Account.",
|
||||
"trigger-beta": "Trigger orders are in beta. Use with caution.",
|
||||
"trigger-beta": "Trigger orders are in beta",
|
||||
"use-margin": "Allow Margin",
|
||||
"wallet-swap": "Wallet Swap",
|
||||
"warning-no-collateral": "You have no free collateral",
|
||||
|
|
|
@ -24,6 +24,10 @@
|
|||
"current-price": "Current Price",
|
||||
"depth": "Depth",
|
||||
"edit-order": "Edit Order",
|
||||
"error-no-long": "No long position to reduce",
|
||||
"error-no-short": "No borrow position to reduce",
|
||||
"error-trigger-above": "Trigger price must be above oracle price",
|
||||
"error-trigger-below": "Trigger price must be below oracle price",
|
||||
"est-liq-price": "Est. Liq. Price",
|
||||
"est-slippage": "Est. Slippage",
|
||||
"falls-to": "falls below",
|
||||
|
@ -73,6 +77,7 @@
|
|||
"orders": "Orders",
|
||||
"place-order": "Place {{side}} Order",
|
||||
"placing-order": "Placing Order",
|
||||
"position": "Position",
|
||||
"positions": "Positions",
|
||||
"post": "Post",
|
||||
"preview-sound": "Preview Sound",
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
"tooltip-favorite-swap-add": "Add pair to favorites",
|
||||
"tooltip-favorite-swap-remove": "Remove pair from favorites",
|
||||
"tooltip-wallet-swap": "When enabled, you'll swap tokens in your wallet. When disabled, you'll swap tokens in your Mango Account.",
|
||||
"trigger-beta": "Trigger orders are in beta. Use with caution.",
|
||||
"trigger-beta": "Trigger orders are in beta",
|
||||
"use-margin": "Allow Margin",
|
||||
"wallet-swap": "Wallet Swap",
|
||||
"warning-no-collateral": "You have no free collateral",
|
||||
|
|
|
@ -24,6 +24,10 @@
|
|||
"current-price": "Current Price",
|
||||
"depth": "Depth",
|
||||
"edit-order": "Edit Order",
|
||||
"error-no-long": "No long position to reduce",
|
||||
"error-no-short": "No borrow position to reduce",
|
||||
"error-trigger-above": "Trigger price must be above oracle price",
|
||||
"error-trigger-below": "Trigger price must be below oracle price",
|
||||
"est-liq-price": "Est. Liq. Price",
|
||||
"est-slippage": "Est. Slippage",
|
||||
"falls-to": "falls below",
|
||||
|
@ -73,6 +77,7 @@
|
|||
"orders": "Orders",
|
||||
"place-order": "Place {{side}} Order",
|
||||
"placing-order": "Placing Order",
|
||||
"position": "Position",
|
||||
"positions": "Positions",
|
||||
"post": "Post",
|
||||
"preview-sound": "Preview Sound",
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
"tooltip-favorite-swap-add": "Add pair to favorites",
|
||||
"tooltip-favorite-swap-remove": "Remove pair from favorites",
|
||||
"tooltip-wallet-swap": "When enabled, you'll swap tokens in your wallet. When disabled, you'll swap tokens in your Mango Account.",
|
||||
"trigger-beta": "Trigger orders are in beta. Use with caution.",
|
||||
"trigger-beta": "Trigger orders are in beta",
|
||||
"use-margin": "Allow Margin",
|
||||
"wallet-swap": "Wallet Swap",
|
||||
"warning-no-collateral": "You have no free collateral",
|
||||
|
|
|
@ -24,6 +24,10 @@
|
|||
"current-price": "Current Price",
|
||||
"depth": "Depth",
|
||||
"edit-order": "Edit Order",
|
||||
"error-no-long": "No long position to reduce",
|
||||
"error-no-short": "No borrow position to reduce",
|
||||
"error-trigger-above": "Trigger price must be above oracle price",
|
||||
"error-trigger-below": "Trigger price must be below oracle price",
|
||||
"est-liq-price": "Est. Liq. Price",
|
||||
"est-slippage": "Est. Slippage",
|
||||
"falls-to": "falls below",
|
||||
|
@ -73,6 +77,7 @@
|
|||
"orders": "Orders",
|
||||
"place-order": "Place {{side}} Order",
|
||||
"placing-order": "Placing Order",
|
||||
"position": "Position",
|
||||
"positions": "Positions",
|
||||
"post": "Post",
|
||||
"preview-sound": "Preview Sound",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"account-stats": "Account Stats",
|
||||
"account-stats": "帐户统计",
|
||||
"assets": "资产",
|
||||
"assets-liabilities": "资产和债务",
|
||||
"collateral-value": "质押品价值",
|
||||
|
@ -16,7 +16,7 @@
|
|||
"maint-health": "维持健康度",
|
||||
"maint-health-contribution": "维持健康贡献",
|
||||
"maint-health-contributions": "维持健康度",
|
||||
"more-account-stats": "More Account Stats",
|
||||
"more-account-stats": "更多帐户统计",
|
||||
"no-data": "无数据可显示",
|
||||
"no-pnl-history": "无盈亏历史",
|
||||
"pnl-chart": "盈亏图表",
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
"all": "全部",
|
||||
"amount": "数量",
|
||||
"amount-owed": "欠款",
|
||||
"asked-sign-transaction": "You'll be asked to sign a transaction",
|
||||
"asked-sign-transaction": "你会被要求签署交易",
|
||||
"asset-liability-weight": "资产/债务权重",
|
||||
"asset-liability-weight-desc": "资产权重在账户健康计算中对质押品价值进行扣减。资产权重越低,资产对质押品的影响越小。债务权重恰恰相反(在健康计算中增加债务价值)。",
|
||||
"asset-weight": "资产权重",
|
||||
|
@ -42,7 +42,7 @@
|
|||
"close-account": "关户",
|
||||
"close-account-desc": "你确定吗? 关户就无法恢复",
|
||||
"closing-account": "正在关闭帐户...",
|
||||
"close-borrow": "Close {{token}} Borrow",
|
||||
"close-borrow": "结清{{token}}借贷",
|
||||
"collateral-value": "质押品价值",
|
||||
"confirm": "确认",
|
||||
"connect": "连接",
|
||||
|
@ -59,7 +59,7 @@
|
|||
"date": "日期",
|
||||
"date-from": "从",
|
||||
"date-to": "至",
|
||||
"degraded": "Degraded",
|
||||
"degraded": "退化的",
|
||||
"delegate": "委托",
|
||||
"delegate-account": "委托帐户",
|
||||
"delegate-account-info": "帐户委托给: {{delegate}}",
|
||||
|
@ -78,11 +78,11 @@
|
|||
"edit": "编辑",
|
||||
"edit-account": "编辑帐户标签",
|
||||
"edit-profile-image": "切换头像",
|
||||
"enable-notifications": "Enable Notifications",
|
||||
"error-borrow-exceeds-limit": "Maximum borrow for the current period is {{remaining}}. New period starts {{resetTime}}",
|
||||
"error-repay-insufficient-funds": "Not enough {{token}} in your wallet to repay this amount",
|
||||
"enable-notifications": "开启通知",
|
||||
"error-borrow-exceeds-limit": "目前期间的最大借贷为{{remaining}}.新期间从{{resetTime}}开始",
|
||||
"error-repay-insufficient-funds": "钱包里的{{token}}不足归还",
|
||||
"error-token-positions-full": "你帐户的币位已占满",
|
||||
"explore": "Explore",
|
||||
"explore": "探索",
|
||||
"explorer": "浏览器",
|
||||
"fee": "费用",
|
||||
"feedback-survey": "反馈调查",
|
||||
|
@ -123,9 +123,9 @@
|
|||
"new-account-success": "您的新帐户准备好了😎",
|
||||
"new-version": "新版本出来了",
|
||||
"no": "不",
|
||||
"offchain-services": "Offchain Services",
|
||||
"offchain-services": "区块联外的服务",
|
||||
"operational": "运行中",
|
||||
"optional": "可选",
|
||||
"operational": "Operational",
|
||||
"outstanding-balance": "未结余额",
|
||||
"overview": "摘要",
|
||||
"perp": "合约",
|
||||
|
@ -145,7 +145,7 @@
|
|||
"repayment-amount": "还贷额",
|
||||
"risks": "风险",
|
||||
"rolling-change": "24小时变化",
|
||||
"rpc-ping": "与RPC节点的ping时间",
|
||||
"rpc-ping": "RPC Ping",
|
||||
"route": "路线",
|
||||
"save": "存",
|
||||
"select": "选择",
|
||||
|
@ -156,7 +156,7 @@
|
|||
"select-withdraw-token": "选提出币种",
|
||||
"sell": "卖",
|
||||
"settings": "设置",
|
||||
"severly-degraded": "Severly Degraded",
|
||||
"severly-degraded": "退化严重",
|
||||
"show-more": "显示更多",
|
||||
"solana-tps": "Solana TPS",
|
||||
"soon": "等一下",
|
||||
|
@ -170,12 +170,12 @@
|
|||
"token": "币种",
|
||||
"token-collateral-multiplier": "{{token}}质押品乘数",
|
||||
"tokens": "币种",
|
||||
"tooltip-available": "The amount of {{token}} available to borrow",
|
||||
"tooltip-available": "可借的{{token}}数量",
|
||||
"tooltip-borrow-fee": "借贷费用。",
|
||||
"tooltip-borrow-rate": "您将为借入余额支付的可变利率",
|
||||
"tooltip-collateral-value": "您可以交易或借入的美元金额",
|
||||
"tooltip-collateral-weight": "The multiplier applied to the notional value of {{token}} collateral",
|
||||
"tooltip-interest-rates": "The interest rates for depositing (green/left) and borrowing (red/right)",
|
||||
"tooltip-collateral-weight": "适用于 {{token}} 抵押品名义价值的乘数",
|
||||
"tooltip-interest-rates": "存款利率(绿/左)和借贷利率(红/右)",
|
||||
"total": "总计",
|
||||
"total-borrow-value": "总借贷价值",
|
||||
"total-borrows": "总借贷",
|
||||
|
@ -205,7 +205,7 @@
|
|||
"vote": "投票",
|
||||
"yes": "是",
|
||||
"you": "你",
|
||||
"using-ledger": "Using Ledger",
|
||||
"sign-to-in-app-notifications": "Sign to in app notifications"
|
||||
}
|
||||
|
||||
"using-ledger": "正在使用Ledger",
|
||||
"sign-to-in-app-notifications": "登入APP通知"
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"collateral-weight": "Collateral Weight",
|
||||
"gainers": "Gainers",
|
||||
"losers": "Losers",
|
||||
"no-gainers": "No Gainers...",
|
||||
"no-losers": "No Losers...",
|
||||
"recently-listed": "Recently Listed"
|
||||
"collateral-weight": "质押品权重",
|
||||
"gainers": "赢家",
|
||||
"losers": "输家",
|
||||
"no-gainers": "无赢家...",
|
||||
"no-losers": "无输家...",
|
||||
"recently-listed": "最近上架"
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
"base-bank": "基准Bank",
|
||||
"base-mint": "基准铸币",
|
||||
"base-token": "基准币种",
|
||||
"before-listing-1": "你必须存入10,0000 MNGO以上",
|
||||
"before-listing-1": "你必须存入100000 MNGO以上",
|
||||
"before-listing-2": "新币种需要有",
|
||||
"before-listing-3": "是否有足够相对USDC与SOL的流动性?",
|
||||
"before-listing-4": "新币种由DAO确认。过程需要3天的时间。",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"empty-state-title": "这里没什么",
|
||||
"notifications": "通知",
|
||||
"sign-message": "签署讯息",
|
||||
"sign-using-ledger": "Sign with Ledger",
|
||||
"sign-using-ledger": "以Ledger登入",
|
||||
"unauth-desc": "连接钱包而受到通知",
|
||||
"unauth-title": "通知收件匣"
|
||||
}
|
|
@ -23,13 +23,13 @@
|
|||
"chart-right": "图表右",
|
||||
"chinese": "简体中文",
|
||||
"chinese-traditional": "繁体中文",
|
||||
"close-instructions": "Close Instructions",
|
||||
"close-perp": "Close Perp Positions",
|
||||
"close-instructions": "关闭帐户指示",
|
||||
"close-perp": "平仓",
|
||||
"close-perp-desc": "To close unused positions, close the position/s, cancel any open orders and settle any unsettled funding.",
|
||||
"close-spot-oo": "Close Spot Open Orders",
|
||||
"close-spot-oo": "取消现货未结订单",
|
||||
"close-spot-oo-desc": "To close unused markets, cancel your open orders and settle any unsettled balances.",
|
||||
"close-token-positions-desc": "Closing tokens positions can affect your account health. The balance will be withdrawn to your wallet on closing.",
|
||||
"close-unused": "Close Unused",
|
||||
"close-unused": "关闭位用过的币位",
|
||||
"close-unused-desc": "Closing unused slots will refund SOL to your account. Each slot type has different requirements for closure (hover over the buttons for details)",
|
||||
"connect-notifications": "连接钱包来切换通知设定",
|
||||
"custom": "自定",
|
||||
|
@ -84,7 +84,7 @@
|
|||
"percentage": "百分比",
|
||||
"percentage-of-max": "最多之{{size}}%",
|
||||
"perp-open-orders": "合约未结订单",
|
||||
"perp-positions": "合约持仓",
|
||||
"perp-markets": "合约持仓",
|
||||
"preferences": "Preferences",
|
||||
"preferred-explorer": "首选探索器",
|
||||
"privacy": "Privacy",
|
||||
|
@ -109,7 +109,7 @@
|
|||
"solscan": "Solscan",
|
||||
"sounds": "声响",
|
||||
"spanish": "Español",
|
||||
"spot-markets": "现货未结订单",
|
||||
"spot-markets": "现货市场",
|
||||
"swap-success": "换币/交易成功",
|
||||
"swap-trade-size-selector": "换币/交易大小选择器",
|
||||
"telemetry": "Telemetry",
|
||||
|
@ -122,12 +122,12 @@
|
|||
"tooltip-hot-key-percentage-size": "将大小设置为最大杠杆的百分比。",
|
||||
"tooltip-hot-key-price": "将价格设置为预言机价格变化的百分比。",
|
||||
"tooltip-orderbook-bandwidth-saving": "使用链下服务进行挂单簿更新,将数据使用量减少约 1000 倍。如果未结订单在挂单簿中不正确显示,请禁用此选项。",
|
||||
"tooltip-perp-open-orders": "你可以有未结订单的合约市场数量。新帐户的最多为{{max}}",
|
||||
"tooltip-perp-markets": "你可以有持仓的合约市场数量。新帐户的最多为{{max}}",
|
||||
"tooltip-perp-open-orders": "包括所有试场的未结订单的综合数量。新帐户的最多为{{max}}",
|
||||
"tooltip-perp-markets": "你能有持仓的合约市场数量。新帐户的最多为{{max}}",
|
||||
"tooltip-privacy-mode": "Masks the USD values of your account",
|
||||
"tooltip-private-account": "Your account won't show on leaderboards and it won't be viewable via URL",
|
||||
"tooltip-spot-markets": "你能有余额的现货市场数量。新帐户的最多为{{max}}",
|
||||
"tooltip-send-telemetry": "Send anonymous usage statistics to help improve the app",
|
||||
"tooltip-spot-markets": "你可以有未结订单的现货市场数量。新帐户的最多为{{max}}",
|
||||
"tooltip-token-accounts": "你帐户可以持有的币种数量。新帐户的最多为{{max}}",
|
||||
"top-left": "左上",
|
||||
"top-right": "右上",
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
"hourly": "小时",
|
||||
"largest-perp-positions": "最大持仓",
|
||||
"perp-details": "{{market}}统计",
|
||||
"perp-open-interest": "合约未平仓",
|
||||
"perp-open-interest": "合约未平仓量",
|
||||
"perp-volume": "合约交易量",
|
||||
"pnl-liquidation-fee": "正数盈亏清算费用",
|
||||
"settle-pnl-factor": "结清盈亏因素",
|
||||
"show-as-apr": "Show as APR",
|
||||
"show-cumulative": "Show Cumulative",
|
||||
"show-as-apr": "为APR显示",
|
||||
"show-cumulative": "显示累积",
|
||||
"six-hourly": "6-小时",
|
||||
"tooltip-base-liquidation-fee": "当清算者接管持仓时,被清算者将支付此费用。",
|
||||
"tooltip-funding-limits": "资金费用的最高与最低(一天)。",
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"health-impact": "健康影响",
|
||||
"hide-fees": "隐藏费用",
|
||||
"hide-swap-history": "隐藏换币纪录",
|
||||
"important-info": "Important Info",
|
||||
"important-info": "重要资料",
|
||||
"input-reduce-only-warning": "{{symbol}}处于仅减少模式。您可以将余额换成另一个币种",
|
||||
"insufficient-balance": "{{symbol}}余额不够",
|
||||
"insufficient-collateral": "质押品不够",
|
||||
|
@ -34,9 +34,9 @@
|
|||
"rate": "汇率",
|
||||
"receive": "你将获取",
|
||||
"received": "获取",
|
||||
"reduce-position": "Reduce Position",
|
||||
"reduce-position-buy": "Reduce long by buying",
|
||||
"reduce-position-sell": "Reduce short by selling",
|
||||
"reduce-position": "减少持仓",
|
||||
"reduce-position-buy": "买而减少做多持仓",
|
||||
"reduce-position-sell": "卖而减少做空持仓",
|
||||
"review-swap": "检视换币",
|
||||
"route-info": "路线细节",
|
||||
"show-fees": "显示费用",
|
||||
|
@ -55,7 +55,7 @@
|
|||
"tooltip-favorite-swap-add": "Add pair to favorites",
|
||||
"tooltip-favorite-swap-remove": "Remove pair from favorites",
|
||||
"tooltip-wallet-swap": "When enabled, you'll swap tokens in your wallet. When disabled, you'll swap tokens in your Mango Account.",
|
||||
"trigger-beta": "Trigger orders are in beta. Use with caution.",
|
||||
"trigger-beta": "触发订单还在开发中。请小心一点。",
|
||||
"use-margin": "允许杠杆",
|
||||
"wallet-swap": "Wallet Swap",
|
||||
"warning-no-collateral": "你没有可用质押品",
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
"oracle-confidence": "预言机可信度",
|
||||
"oracle-staleness": "预言机不新鲜性",
|
||||
"parameters": "参数",
|
||||
"rate-curve": "Interest Rate Curve",
|
||||
"rate-curve": "利率曲线",
|
||||
"token-stats": "币种细节",
|
||||
"token-fees-collected": "收取的币种费用",
|
||||
"token-not-found": "查不到币种",
|
||||
|
|
|
@ -11,19 +11,23 @@
|
|||
"cancel-all": "取消全部",
|
||||
"cancel-order": "取消订单",
|
||||
"cancel-order-error": "取消订单失败",
|
||||
"close-all": "Close All",
|
||||
"close-all-positions": "Close All Positions",
|
||||
"close-all": "全部平仓",
|
||||
"close-all-positions": "全部平仓",
|
||||
"close-confirm": "市场平仓 {{config_name}}",
|
||||
"close-position": "平仓",
|
||||
"connect-orders": "连接以查看未结订单",
|
||||
"connect-positions": "连接以查看持仓",
|
||||
"connect-trade-history": "连接以查看交易历史",
|
||||
"connect-trigger-orders": "Connect to view your trigger orders",
|
||||
"connect-trigger-orders": "连接以查看触发订单",
|
||||
"connect-unsettled": "连接以查看未结清余额",
|
||||
"copy-and-share": "将图片复制到剪贴版",
|
||||
"current-price": "目前价格",
|
||||
"depth": "深度",
|
||||
"edit-order": "编辑订单",
|
||||
"error-no-long": "No long position to reduce",
|
||||
"error-no-short": "No borrow position to reduce",
|
||||
"error-trigger-above": "Trigger price must be above oracle price",
|
||||
"error-trigger-below": "Trigger price must be below oracle price",
|
||||
"est-liq-price": "预计清算价格",
|
||||
"est-slippage": "预计下滑",
|
||||
"falls-to": "下降至",
|
||||
|
@ -54,15 +58,15 @@
|
|||
"min-order-size-error": "订单的最小数量是 {{minSize}} {{symbol}}",
|
||||
"more-details": "看细节",
|
||||
"no-balances": "无余额",
|
||||
"no-borrows": "Borrow Disabled",
|
||||
"no-markets-found": "No markets found...",
|
||||
"no-borrows": "借贷已停用",
|
||||
"no-markets-found": "无市场",
|
||||
"no-orders": "无订单",
|
||||
"no-positions": "无持仓",
|
||||
"no-trigger-orders": "No trigger orders",
|
||||
"no-trigger-orders": "无触发订单",
|
||||
"no-unsettled": "无未结清余额",
|
||||
"notional": "名义",
|
||||
"notional-volume": "名义交易量($)",
|
||||
"open-interest": "未平仓",
|
||||
"open-interest": "未平仓量",
|
||||
"oracle": "预言机",
|
||||
"oracle-not-updated": "此预言机最近没更新。",
|
||||
"oracle-not-updated-warning": "有该币种持仓的账户,操作将会失败。",
|
||||
|
@ -73,10 +77,11 @@
|
|||
"orders": "订单",
|
||||
"place-order": "下{{side}}单",
|
||||
"placing-order": "正在下单",
|
||||
"position": "Position",
|
||||
"positions": "持仓",
|
||||
"post": "Post",
|
||||
"preview-sound": "声音预览",
|
||||
"price-expect": "您收到的价格可能与您预期有差异,并且无法保证完全执行。为了您的安全,最大滑点保持为2.5%。超过2.5%滑点的部分不会被平仓。",
|
||||
"price-expect": "The execution price may be worse than you expect and full execution is not guaranteed. Your order will be filled up to a max slippage of 2.5%",
|
||||
"price-provided-by": "预言机来自",
|
||||
"quote": "计价",
|
||||
"realized-pnl": "已实现的盈亏",
|
||||
|
@ -93,7 +98,6 @@
|
|||
"show-asks": "显示要价",
|
||||
"show-bids": "显示出价",
|
||||
"side": "方向",
|
||||
"trigger-order-desc-with-borrow": "{{action}} {{amount}} {{symbol}} if the oracle price {{orderType}} {{triggerPrice}} {{priceUnit}}. You'll borrow ~{{borrowAmount}} {{quoteSymbol}} to execute this trade",
|
||||
"size": "数量",
|
||||
"spread": "差价",
|
||||
"stable-price": "稳定价格",
|
||||
|
@ -111,11 +115,11 @@
|
|||
"tooltip-insured": "如果发生破产,{{tokenOrMarket}}损失是否可以从保险基金中归还",
|
||||
"tooltip-ioc": "IOC交易若不吃单就会被取消。任何无法立刻成交的部分将被取消",
|
||||
"tooltip-post": "Post交易若不挂单就会被取消。",
|
||||
"tooltip-size-base-quote": "Show size in {{token}}",
|
||||
"tooltip-private-counterparty": "Counterparty has Private Account enabled",
|
||||
"tooltip-size-base-quote": "以{{token}}显示数量",
|
||||
"tooltip-private-counterparty": "交易对手已启用私人帐户",
|
||||
"tooltip-slippage": "当前价格与您的交易将执行的价格之间的差值的估计",
|
||||
"tooltip-stable-price": "稳定价格用于一个安全机制。此机制可以限制用户在预言机价格快速波动时下风险高的订单",
|
||||
"tooltip-view-counterparty": "View counterparty {{pk}}",
|
||||
"tooltip-view-counterparty": "查看交易对手 {{pk}}",
|
||||
"tooltip-volume-alert": "交易量警报设定",
|
||||
"total-pnl": "总盈亏",
|
||||
"trade-sounds-tooltip": "为每笔新交易播放警报声音",
|
||||
|
@ -124,10 +128,11 @@
|
|||
"trigger-order": "触发订单",
|
||||
"trigger-order-desc": "{{amount}} {{symbol}} 若预言机价格{{orderType}} {{triggerPrice}} {{priceUnit}}",
|
||||
"trigger-orders": "触发订单",
|
||||
"trigger-order-desc-with-borrow": "{{action}} {{amount}} {{symbol}} if the oracle price {{orderType}} {{triggerPrice}} {{priceUnit}}. You' ll borrow ~{{borrowAmount}} {{quoteSymbol}} to execute this trade",
|
||||
"tweet-position": "分享至Twitter",
|
||||
"unrealized-pnl": "未实现盈亏",
|
||||
"unsettled": "未结清",
|
||||
"view-counterparty": "View counterparty {{pk}}",
|
||||
"view-counterparty": "查看交易对手 {{pk}}",
|
||||
"volume-alert": "交易量警报",
|
||||
"volume-alert-desc": "交易量超过警报设定时播放声音"
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"account-stats": "Account Stats",
|
||||
"account-stats": "帳戶統計",
|
||||
"assets": "資產",
|
||||
"assets-liabilities": "資產和債務",
|
||||
"collateral-value": "質押品價值",
|
||||
|
@ -16,7 +16,7 @@
|
|||
"maint-health": "維持健康度",
|
||||
"maint-health-contribution": "維持健康貢獻",
|
||||
"maint-health-contributions": "維持健康度",
|
||||
"more-account-stats": "More Account Stats",
|
||||
"more-account-stats": "更多帳戶統計",
|
||||
"no-data": "無數據可顯示",
|
||||
"no-pnl-history": "無盈虧歷史",
|
||||
"pnl-chart": "盈虧圖表",
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
"all": "全部",
|
||||
"amount": "數量",
|
||||
"amount-owed": "欠款",
|
||||
"asked-sign-transaction": "You'll be asked to sign a transaction",
|
||||
"asked-sign-transaction": "你會被要求簽署交易",
|
||||
"asset-liability-weight": "資產/債務權重",
|
||||
"asset-liability-weight-desc": "資產權重在賬戶健康計算中對質押品價值進行扣減。資產權重越低,資產對質押品的影響越小。債務權重恰恰相反(在健康計算中增加債務價值)。",
|
||||
"asset-weight": "資產權重",
|
||||
|
@ -42,7 +42,7 @@
|
|||
"close-account": "關戶",
|
||||
"close-account-desc": "你確定嗎? 關戶就無法恢復",
|
||||
"closing-account": "正在關閉帳戶...",
|
||||
"close-borrow": "Close {{token}} Borrow",
|
||||
"close-borrow": "結清{{token}}借貸",
|
||||
"collateral-value": "質押品價值",
|
||||
"confirm": "確認",
|
||||
"connect": "連接",
|
||||
|
@ -59,7 +59,7 @@
|
|||
"date": "日期",
|
||||
"date-from": "從",
|
||||
"date-to": "至",
|
||||
"degraded": "Degraded",
|
||||
"degraded": "退化的",
|
||||
"delegate": "委託",
|
||||
"delegate-account": "委託帳戶",
|
||||
"delegate-account-info": "帳戶委託給: {{delegate}}",
|
||||
|
@ -78,11 +78,11 @@
|
|||
"edit": "編輯",
|
||||
"edit-account": "編輯帳戶標籤",
|
||||
"edit-profile-image": "切換頭像",
|
||||
"enable-notifications": "Enable Notifications",
|
||||
"error-borrow-exceeds-limit": "Maximum borrow for the current period is {{remaining}}. New period starts {{resetTime}}",
|
||||
"error-repay-insufficient-funds": "Not enough {{token}} in your wallet to repay this amount",
|
||||
"enable-notifications": "開啟通知",
|
||||
"error-borrow-exceeds-limit": "目前期間的最大借貸為{{remaining}}.新期間從{{resetTime}}開始",
|
||||
"error-repay-insufficient-funds": "錢包裡的{{token}}不足歸還",
|
||||
"error-token-positions-full": "你帳戶的幣位已占滿",
|
||||
"explore": "Explore",
|
||||
"explore": "探索",
|
||||
"explorer": "瀏覽器",
|
||||
"fee": "費用",
|
||||
"feedback-survey": "反饋調查",
|
||||
|
@ -123,8 +123,8 @@
|
|||
"new-account-success": "您的新帳戶準備好了😎",
|
||||
"new-version": "新版本出來了",
|
||||
"no": "不",
|
||||
"offchain-services": "Offchain Services",
|
||||
"operational": "Operational",
|
||||
"offchain-services": "區塊聯外的服務",
|
||||
"operational": "運行中",
|
||||
"optional": "可選",
|
||||
"outstanding-balance": "未結餘額",
|
||||
"overview": "摘要",
|
||||
|
@ -145,7 +145,7 @@
|
|||
"repayment-amount": "還貸額",
|
||||
"risks": "風險",
|
||||
"rolling-change": "24小時變化",
|
||||
"rpc-ping": "與RPC節點的ping時間",
|
||||
"rpc-ping": "RPC Ping",
|
||||
"route": "路線",
|
||||
"save": "存",
|
||||
"select": "選擇",
|
||||
|
@ -156,7 +156,7 @@
|
|||
"select-withdraw-token": "選提出幣種",
|
||||
"sell": "賣",
|
||||
"settings": "設置",
|
||||
"severly-degraded": "Severly Degraded",
|
||||
"severly-degraded": "退化嚴重",
|
||||
"show-more": "顯示更多",
|
||||
"solana-tps": "Solana TPS",
|
||||
"soon": "等一下",
|
||||
|
@ -170,12 +170,12 @@
|
|||
"token": "幣種",
|
||||
"token-collateral-multiplier": "{{token}}質押品乘數",
|
||||
"tokens": "幣種",
|
||||
"tooltip-available": "The amount of {{token}} available to borrow",
|
||||
"tooltip-available": "可借的{{token}}數量",
|
||||
"tooltip-borrow-fee": "借貸費用。",
|
||||
"tooltip-borrow-rate": "您將為借入餘額支付的可變利率",
|
||||
"tooltip-collateral-value": "您可以交易或借入的美元金額",
|
||||
"tooltip-collateral-weight": "The multiplier applied to the notional value of {{token}} collateral",
|
||||
"tooltip-interest-rates": "The interest rates for depositing (green/left) and borrowing (red/right)",
|
||||
"tooltip-collateral-weight": "適用於 {{token}} 抵押品名義價值的乘數",
|
||||
"tooltip-interest-rates": "存款利率(綠/左)和借貸利率(紅/右)",
|
||||
"total": "總計",
|
||||
"total-borrow-value": "總借貸價值",
|
||||
"total-borrows": "總借貸",
|
||||
|
@ -205,6 +205,6 @@
|
|||
"vote": "投票",
|
||||
"yes": "是",
|
||||
"you": "你",
|
||||
"using-ledger": "Using Ledger",
|
||||
"sign-to-in-app-notifications": "Sign to in app notifications"
|
||||
"using-ledger": "正在使用Ledger",
|
||||
"sign-to-in-app-notifications": "登入APP通知"
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"collateral-weight": "Collateral Weight",
|
||||
"gainers": "Gainers",
|
||||
"losers": "Losers",
|
||||
"no-gainers": "No Gainers...",
|
||||
"no-losers": "No Losers...",
|
||||
"recently-listed": "Recently Listed"
|
||||
"collateral-weight": "質押品權重",
|
||||
"gainers": "贏家",
|
||||
"losers": "輸家",
|
||||
"no-gainers": "無贏家...",
|
||||
"no-losers": "無輸家...",
|
||||
"recently-listed": "最近上架"
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
"base-bank": "基準Bank",
|
||||
"base-mint": "基準鑄幣",
|
||||
"base-token": "基準幣種",
|
||||
"before-listing-1": "你必須存入10,0000 MNGO以上",
|
||||
"before-listing-1": "你必須存入100000 MNGO以上",
|
||||
"before-listing-2": "新幣種需要有",
|
||||
"before-listing-3": "是否有足夠相對USDC與SOL的流動性?",
|
||||
"before-listing-4": "新幣種由DAO確認。過程需要3天的時間。",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"empty-state-title": "這裡沒什麼",
|
||||
"notifications": "通知",
|
||||
"sign-message": "簽署訊息",
|
||||
"sign-using-ledger": "Sign with Ledger",
|
||||
"sign-using-ledger": "以Ledger登入",
|
||||
"unauth-desc": "連接錢包而受到通知",
|
||||
"unauth-title": "通知收件匣"
|
||||
}
|
|
@ -23,13 +23,13 @@
|
|||
"chart-right": "圖表右",
|
||||
"chinese": "简体中文",
|
||||
"chinese-traditional": "繁體中文",
|
||||
"close-instructions": "Close Instructions",
|
||||
"close-perp": "Close Perp Positions",
|
||||
"close-instructions": "關閉帳戶指示",
|
||||
"close-perp": "平倉",
|
||||
"close-perp-desc": "To close unused positions, close the position/s, cancel any open orders and settle any unsettled funding.",
|
||||
"close-spot-oo": "Close Spot Open Orders",
|
||||
"close-spot-oo": "取消現貨未結訂單",
|
||||
"close-spot-oo-desc": "To close unused markets, cancel your open orders and settle any unsettled balances.",
|
||||
"close-token-positions-desc": "Closing tokens positions can affect your account health. The balance will be withdrawn to your wallet on closing.",
|
||||
"close-unused": "Close Unused",
|
||||
"close-unused": "關閉位用過的幣位",
|
||||
"close-unused-desc": "Closing unused slots will refund SOL to your account. Each slot type has different requirements for closure (hover over the buttons for details)",
|
||||
"connect-notifications": "連接錢包來切換通知設定",
|
||||
"custom": "自定",
|
||||
|
@ -84,7 +84,7 @@
|
|||
"percentage": "百分比",
|
||||
"percentage-of-max": "最多之{{size}}%",
|
||||
"perp-open-orders": "合約未結訂單",
|
||||
"perp-positions": "合約持倉",
|
||||
"perp-markets": "合約市場",
|
||||
"preferences": "Preferences",
|
||||
"preferred-explorer": "首選探索器",
|
||||
"privacy": "Privacy",
|
||||
|
@ -109,7 +109,7 @@
|
|||
"solscan": "Solscan",
|
||||
"sounds": "聲響",
|
||||
"spanish": "Español",
|
||||
"spot-markets": "現貨未結訂單",
|
||||
"spot-markets": "現貨市場",
|
||||
"swap-success": "換幣/交易成功",
|
||||
"swap-trade-size-selector": "換幣/交易大小選擇器",
|
||||
"telemetry": "Telemetry",
|
||||
|
@ -122,12 +122,12 @@
|
|||
"tooltip-hot-key-percentage-size": "將大小設置為最大槓桿的百分比。",
|
||||
"tooltip-hot-key-price": "將價格設置為預言機價格變化的百分比。",
|
||||
"tooltip-orderbook-bandwidth-saving": "使用鏈下服務進行掛單簿更新,將數據使用量減少約 1000 倍。如果未結訂單在掛單簿中不正確顯示,請禁用此選項。",
|
||||
"tooltip-perp-open-orders": "你可以有未結訂單的合約市場數量。新帳戶的最多為{{max}}",
|
||||
"tooltip-perp-markets": "你可以有持倉的合約市場數量。新帳戶的最多為{{max}}",
|
||||
"tooltip-perp-open-orders": "包括所有試場的未結訂單的綜合數量。新帳戶的最多為{{max}}",
|
||||
"tooltip-perp-markets": "你能有持倉的合約市場數量。新帳戶的最多為{{max}}",
|
||||
"tooltip-privacy-mode": "Masks the USD values of your account",
|
||||
"tooltip-private-account": "Your account won't show on leaderboards and it won't be viewable via URL",
|
||||
"tooltip-spot-markets": "你能有餘額的現貨市場數量。新帳戶的最多為{{max}}",
|
||||
"tooltip-send-telemetry": "Send anonymous usage statistics to help improve the app",
|
||||
"tooltip-spot-markets": "你可以有未結訂單的現貨市場數量。新帳戶的最多為{{max}}",
|
||||
"tooltip-token-accounts": "你帳戶可以持有的幣種數量。新帳戶的最多為{{max}}",
|
||||
"top-left": "左上",
|
||||
"top-right": "右上",
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
"hourly": "小時",
|
||||
"largest-perp-positions": "最大持倉",
|
||||
"perp-details": "{{market}}統計",
|
||||
"perp-open-interest": "合約未平倉",
|
||||
"perp-open-interest": "合約未平倉量",
|
||||
"perp-volume": "合約交易量",
|
||||
"pnl-liquidation-fee": "正數盈虧清算費用",
|
||||
"settle-pnl-factor": "結清盈虧因素",
|
||||
"show-as-apr": "Show as APR",
|
||||
"show-cumulative": "Show Cumulative",
|
||||
"show-as-apr": "為APR顯示",
|
||||
"show-cumulative": "顯示累積",
|
||||
"six-hourly": "6-小時",
|
||||
"tooltip-base-liquidation-fee": "當清算者接管持倉時,被清算者將支付此費用。",
|
||||
"tooltip-funding-limits": "資金費用的最高與最低(一天)。",
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"health-impact": "健康影響",
|
||||
"hide-fees": "隱藏費用",
|
||||
"hide-swap-history": "隱藏換幣紀錄",
|
||||
"important-info": "Important Info",
|
||||
"important-info": "重要資料",
|
||||
"input-reduce-only-warning": "{{symbol}}處於僅減少模式。您可以將餘額換成另一個幣種",
|
||||
"insufficient-balance": "{{symbol}}餘額不夠",
|
||||
"insufficient-collateral": "質押品不夠",
|
||||
|
@ -34,9 +34,9 @@
|
|||
"rate": "匯率",
|
||||
"receive": "你將獲取",
|
||||
"received": "獲取",
|
||||
"reduce-position": "Reduce Position",
|
||||
"reduce-position-buy": "Reduce long by buying",
|
||||
"reduce-position-sell": "Reduce short by selling",
|
||||
"reduce-position": "減少持倉",
|
||||
"reduce-position-buy": "買而減少做多持倉",
|
||||
"reduce-position-sell": "賣而減少做空持倉",
|
||||
"review-swap": "檢視換幣",
|
||||
"route-info": "路線細節",
|
||||
"show-fees": "顯示費用",
|
||||
|
@ -55,7 +55,7 @@
|
|||
"tooltip-favorite-swap-add": "Add pair to favorites",
|
||||
"tooltip-favorite-swap-remove": "Remove pair from favorites",
|
||||
"tooltip-wallet-swap": "When enabled, you'll swap tokens in your wallet. When disabled, you'll swap tokens in your Mango Account.",
|
||||
"trigger-beta": "Trigger orders are in beta. Use with caution.",
|
||||
"trigger-beta": "觸發訂單還在開發中。請小心一點。",
|
||||
"use-margin": "允許槓桿",
|
||||
"wallet-swap": "Wallet Swap",
|
||||
"warning-no-collateral": "你沒有可用質押品",
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
"oracle-confidence": "預言機可信度",
|
||||
"oracle-staleness": "預言機不新鮮性",
|
||||
"parameters": "參數",
|
||||
"rate-curve": "Interest Rate Curve",
|
||||
"rate-curve": "利率曲線",
|
||||
"token-stats": "幣種細節",
|
||||
"token-fees-collected": "收取的幣種費用",
|
||||
"token-not-found": "查不到幣種",
|
||||
|
|
|
@ -11,19 +11,23 @@
|
|||
"cancel-all": "取消全部",
|
||||
"cancel-order": "取消訂單",
|
||||
"cancel-order-error": "取消訂單失敗",
|
||||
"close-all": "Close All",
|
||||
"close-all-positions": "Close All Positions",
|
||||
"close-all": "全部平倉",
|
||||
"close-all-positions": "全部平倉",
|
||||
"close-confirm": "市場平倉 {{config_name}}",
|
||||
"close-position": "平倉",
|
||||
"connect-orders": "連接以查看未結訂單",
|
||||
"connect-positions": "連接以查看持倉",
|
||||
"connect-trade-history": "連接以查看交易歷史",
|
||||
"connect-trigger-orders": "Connect to view your trigger orders",
|
||||
"connect-trigger-orders": "連接以查看觸發訂單",
|
||||
"connect-unsettled": "連接以查看未結清餘額",
|
||||
"copy-and-share": "將圖片複製到剪貼版",
|
||||
"current-price": "目前價格",
|
||||
"depth": "深度",
|
||||
"edit-order": "編輯訂單",
|
||||
"error-no-long": "No long position to reduce",
|
||||
"error-no-short": "No borrow position to reduce",
|
||||
"error-trigger-above": "Trigger price must be above oracle price",
|
||||
"error-trigger-below": "Trigger price must be below oracle price",
|
||||
"est-liq-price": "預計清算價格",
|
||||
"est-slippage": "預計下滑",
|
||||
"falls-to": "下降至",
|
||||
|
@ -54,15 +58,15 @@
|
|||
"min-order-size-error": "訂單的最小數量是 {{minSize}} {{symbol}}",
|
||||
"more-details": "看細節",
|
||||
"no-balances": "無餘額",
|
||||
"no-borrows": "Borrow Disabled",
|
||||
"no-markets-found": "No markets found...",
|
||||
"no-borrows": "借貸已停用",
|
||||
"no-markets-found": "無市場",
|
||||
"no-orders": "無訂單",
|
||||
"no-positions": "無持倉",
|
||||
"no-trigger-orders": "No trigger orders",
|
||||
"no-trigger-orders": "無觸發訂單",
|
||||
"no-unsettled": "無未結清餘額",
|
||||
"notional": "名義",
|
||||
"notional-volume": "名義交易量($)",
|
||||
"open-interest": "未平倉",
|
||||
"open-interest": "未平倉量",
|
||||
"oracle": "預言機",
|
||||
"oracle-not-updated": "此預言機最近沒更新。",
|
||||
"oracle-not-updated-warning": "有該幣種持倉的賬戶,操作將會失敗。",
|
||||
|
@ -73,10 +77,11 @@
|
|||
"orders": "訂單",
|
||||
"place-order": "下{{side}}單",
|
||||
"placing-order": "正在下單",
|
||||
"position": "Position",
|
||||
"positions": "持倉",
|
||||
"post": "Post",
|
||||
"preview-sound": "聲音預覽",
|
||||
"price-expect": "您收到的價格可能與您預期有差異,並且無法保證完全執行。為了您的安全,最大滑點保持為 2.5%。超過 2.5%滑點的部分不會被平倉。",
|
||||
"price-expect": "The execution price may be worse than you expect and full execution is not guaranteed. Your order will be filled up to a max slippage of 2.5%",
|
||||
"price-provided-by": "預言機來自",
|
||||
"quote": "計價",
|
||||
"realized-pnl": "已實現的盈虧",
|
||||
|
@ -110,11 +115,11 @@
|
|||
"tooltip-insured": "如果發生破產,{{tokenOrMarket}}損失是否可以從保險基金中歸還",
|
||||
"tooltip-ioc": "IOC交易若不吃單就會被取消。任何無法立刻成交的部分將被取消",
|
||||
"tooltip-post": "Post交易若不掛單就會被取消。",
|
||||
"tooltip-size-base-quote": "Show size in {{token}}",
|
||||
"tooltip-private-counterparty": "Counterparty has Private Account enabled",
|
||||
"tooltip-size-base-quote": "以{{token}}顯示數量",
|
||||
"tooltip-private-counterparty": "交易對手已啟用私人帳戶",
|
||||
"tooltip-slippage": "當前價格與您的交易將執行的價格之間的差值的估計",
|
||||
"tooltip-stable-price": "穩定價格用於一個安全機制。此機制可以限制用戶在預言機價格快速波動時下風險高的訂單",
|
||||
"tooltip-view-counterparty": "View counterparty {{pk}}",
|
||||
"tooltip-view-counterparty": "查看交易對手 {{pk}}",
|
||||
"tooltip-volume-alert": "交易量警報設定",
|
||||
"total-pnl": "總盈虧",
|
||||
"trade-sounds-tooltip": "為每筆新交易播放警報聲音",
|
||||
|
@ -127,7 +132,7 @@
|
|||
"tweet-position": "分享至Twitter",
|
||||
"unrealized-pnl": "未實現盈虧",
|
||||
"unsettled": "未結清",
|
||||
"view-counterparty": "View counterparty {{pk}}",
|
||||
"view-counterparty": "查看交易對手 {{pk}}",
|
||||
"volume-alert": "交易量警報",
|
||||
"volume-alert-desc": "交易量超過警報設定時播放聲音"
|
||||
}
|
|
@ -59,7 +59,6 @@ import {
|
|||
SpotBalances,
|
||||
SpotTradeHistory,
|
||||
SwapHistoryItem,
|
||||
TotalInterestDataItem,
|
||||
TradeForm,
|
||||
TokenStatsItem,
|
||||
NFT,
|
||||
|
@ -69,7 +68,7 @@ import {
|
|||
ThemeData,
|
||||
PositionStat,
|
||||
OrderbookTooltip,
|
||||
TriggerOrderTypes,
|
||||
SwapTypes,
|
||||
} from 'types'
|
||||
import spotBalancesUpdater from './spotBalancesUpdater'
|
||||
import { PerpMarket } from '@blockworks-foundation/mango-v4/'
|
||||
|
@ -88,6 +87,7 @@ import mapValues from 'lodash/mapValues'
|
|||
import groupBy from 'lodash/groupBy'
|
||||
import sampleSize from 'lodash/sampleSize'
|
||||
import { fetchTokenStatsData, processTokenStatsData } from 'apis/mngo'
|
||||
import { OrderTypes, TriggerOrderTypes } from 'utils/tradeForm'
|
||||
|
||||
const GROUP = new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX')
|
||||
|
||||
|
@ -146,7 +146,7 @@ export const DEFAULT_TRADE_FORM: TradeForm = {
|
|||
price: undefined,
|
||||
baseSize: '',
|
||||
quoteSize: '',
|
||||
tradeType: 'Limit',
|
||||
tradeType: OrderTypes.LIMIT,
|
||||
triggerPrice: '',
|
||||
postOnly: false,
|
||||
ioc: false,
|
||||
|
@ -241,7 +241,7 @@ export type MangoStore = {
|
|||
amountIn: string
|
||||
amountOut: string
|
||||
flipPrices: boolean
|
||||
swapOrTrigger: TriggerOrderTypes
|
||||
swapOrTrigger: SwapTypes
|
||||
triggerPrice: string
|
||||
}
|
||||
set: (x: (x: MangoStore) => void) => void
|
||||
|
|
|
@ -11,6 +11,7 @@ import { JsonMetadata } from '@metaplex-foundation/js'
|
|||
import { Event } from '@project-serum/serum/lib/queue'
|
||||
import { PublicKey } from '@solana/web3.js'
|
||||
import { formatTradeHistory } from 'hooks/useTradeHistory'
|
||||
import { OrderTypes, TriggerOrderTypes } from 'utils/tradeForm'
|
||||
|
||||
export type EmptyObject = { [K in keyof never]?: never }
|
||||
export interface OrderbookL2 {
|
||||
|
@ -441,7 +442,7 @@ export interface TradeForm {
|
|||
price: string | undefined
|
||||
baseSize: string
|
||||
quoteSize: string
|
||||
tradeType: 'Market' | 'Limit'
|
||||
tradeType: OrderTypes | TriggerOrderTypes
|
||||
triggerPrice?: string
|
||||
postOnly: boolean
|
||||
ioc: boolean
|
||||
|
@ -553,4 +554,4 @@ export interface FilledOrder {
|
|||
quantity: number
|
||||
}
|
||||
|
||||
export type TriggerOrderTypes = 'swap' | 'trade:trigger-order'
|
||||
export type SwapTypes = 'swap' | 'trade:trigger-order'
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import { OrderbookL2 } from 'types'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { OrderbookL2, isMangoError } from 'types'
|
||||
import { notify } from './notifications'
|
||||
import * as sentry from '@sentry/nextjs'
|
||||
import { Bank } from '@blockworks-foundation/mango-v4'
|
||||
|
||||
export const calculateLimitPriceForMarketOrder = (
|
||||
orderBook: OrderbookL2,
|
||||
|
@ -83,3 +87,124 @@ export const calculateSlippage = (
|
|||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
export const handlePlaceTriggerOrder = async (
|
||||
positionBank: Bank | undefined,
|
||||
quoteBank: Bank | undefined,
|
||||
amountIn: number,
|
||||
triggerPrice: string,
|
||||
orderType: TriggerOrderTypes,
|
||||
isReducingShort: boolean,
|
||||
flipPrices: boolean,
|
||||
setSubmitting: (s: boolean) => void,
|
||||
) => {
|
||||
try {
|
||||
const client = mangoStore.getState().client
|
||||
const group = mangoStore.getState().group
|
||||
const actions = mangoStore.getState().actions
|
||||
const mangoAccount = mangoStore.getState().mangoAccount.current
|
||||
// const inputBank = mangoStore.getState().swap.inputBank
|
||||
// const outputBank = mangoStore.getState().swap.outputBank
|
||||
|
||||
if (!mangoAccount || !group || !positionBank || !quoteBank || !triggerPrice)
|
||||
return
|
||||
setSubmitting(true)
|
||||
|
||||
// const amountIn = amountInAsDecimal.toNumber()
|
||||
|
||||
const isReduceLong = !isReducingShort
|
||||
|
||||
try {
|
||||
let tx
|
||||
if (orderType === TriggerOrderTypes.STOP_LOSS) {
|
||||
if (isReduceLong) {
|
||||
tx = await client.tcsStopLossOnDeposit(
|
||||
group,
|
||||
mangoAccount,
|
||||
positionBank,
|
||||
quoteBank,
|
||||
parseFloat(triggerPrice),
|
||||
flipPrices,
|
||||
amountIn,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
} else {
|
||||
tx = await client.tcsStopLossOnBorrow(
|
||||
group,
|
||||
mangoAccount,
|
||||
quoteBank,
|
||||
positionBank,
|
||||
parseFloat(triggerPrice),
|
||||
!flipPrices,
|
||||
amountIn,
|
||||
null,
|
||||
true,
|
||||
null,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (orderType === TriggerOrderTypes.TAKE_PROFIT) {
|
||||
if (isReduceLong) {
|
||||
tx = await client.tcsTakeProfitOnDeposit(
|
||||
group,
|
||||
mangoAccount,
|
||||
positionBank,
|
||||
quoteBank,
|
||||
parseFloat(triggerPrice),
|
||||
flipPrices,
|
||||
amountIn,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
} else {
|
||||
tx = await client.tcsTakeProfitOnBorrow(
|
||||
group,
|
||||
mangoAccount,
|
||||
quoteBank,
|
||||
positionBank,
|
||||
parseFloat(triggerPrice),
|
||||
!flipPrices,
|
||||
amountIn,
|
||||
null,
|
||||
true,
|
||||
null,
|
||||
)
|
||||
}
|
||||
}
|
||||
notify({
|
||||
title: 'Transaction confirmed',
|
||||
type: 'success',
|
||||
txid: tx?.signature,
|
||||
noSound: true,
|
||||
})
|
||||
actions.fetchGroup()
|
||||
await actions.reloadMangoAccount(tx?.slot)
|
||||
} catch (e) {
|
||||
console.error('onSwap error: ', e)
|
||||
sentry.captureException(e)
|
||||
if (isMangoError(e)) {
|
||||
notify({
|
||||
title: 'Transaction failed',
|
||||
description: e.message,
|
||||
txid: e?.txid,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Swap error:', e)
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
export enum OrderTypes {
|
||||
LIMIT = 'Limit',
|
||||
MARKET = 'Market',
|
||||
}
|
||||
|
||||
export enum TriggerOrderTypes {
|
||||
STOP_LOSS = 'trade:stop-loss',
|
||||
TAKE_PROFIT = 'trade:take-profit',
|
||||
}
|
||||
|
|
|
@ -50,10 +50,10 @@
|
|||
bn.js "^5.2.1"
|
||||
eslint-config-prettier "^9.0.0"
|
||||
|
||||
"@blockworks-foundation/mango-v4@^0.19.41":
|
||||
version "0.19.41"
|
||||
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4/-/mango-v4-0.19.41.tgz#d4f88aebe5509cacf5569e6b3b7665bc9053c33f"
|
||||
integrity sha512-q3rXSgql8lcYtCbtvIb5VPHjkcQUEbnmMQ4p6D81X+wVE3sBKNOpXiq2uQNEVJRBsWKzz/Ea1W9jM6siq7bpWA==
|
||||
"@blockworks-foundation/mango-v4@0.19.43":
|
||||
version "0.19.43"
|
||||
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-v4/-/mango-v4-0.19.43.tgz#82c2df9c63204e31c19473672329cba43ea40fbc"
|
||||
integrity sha512-son/flq/mvngtBppKbxXfNIq/Z4L7mp6zOP5k8cD11rVlZ3wmb3jfBkKqocgL3IqnRIWj3cL9R6dM37tqaFZzw==
|
||||
dependencies:
|
||||
"@blockworks-foundation/mango-v4-settings" "^0.2.16"
|
||||
"@coral-xyz/anchor" "^0.28.1-beta.2"
|
||||
|
|
Loading…
Reference in New Issue