merge main

This commit is contained in:
saml33 2023-02-15 10:41:47 +11:00
commit 066fd9b364
57 changed files with 1366 additions and 843 deletions

View File

@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
yarn format && yarn typecheck && yarn lint --quiet
yarn typecheck && yarn lint --quiet && yarn format

View File

@ -53,26 +53,16 @@ const HydrateStore = () => {
// watch selected Mango Account for changes
useEffect(() => {
const client = mangoStore.getState().client
if (!mangoAccountPk) return
const subscriptionId = connection.onAccountChange(
mangoAccountPk,
async (info, context) => {
if (info?.lamports === 0) return
const lastSeenSlot = mangoStore.getState().mangoAccount.lastSlot
// const mangoAccountLastUpdated = new Date(
// mangoStore.getState().mangoAccount.lastUpdatedAt
// )
const mangoAccount = mangoStore.getState().mangoAccount.current
if (!mangoAccount) return
// const newUpdatedAt = new Date()
// const timeDiff =
// mangoAccountLastUpdated.getTime() - newUpdatedAt.getTime()
// only updated mango account if it's been more than 1 second since last update
// if (Math.abs(timeDiff) >= 500 && context.slot > lastSeenSlot) {
if (context.slot > lastSeenSlot) {
const decodedMangoAccount = client.program.coder.accounts.decode(
'mangoAccount',
@ -83,10 +73,8 @@ const HydrateStore = () => {
decodedMangoAccount
)
await newMangoAccount.reloadSerum3OpenOrders(client)
actions.fetchOpenOrders()
// newMangoAccount.spotOpenOrdersAccounts =
// mangoAccount.spotOpenOrdersAccounts
// newMangoAccount.advancedOrders = mangoAccount.advancedOrders
actions.fetchOpenOrders(newMangoAccount)
set((s) => {
s.mangoAccount.current = newMangoAccount
s.mangoAccount.lastSlot = context.slot

View File

@ -34,6 +34,7 @@ import BankAmountWithValue from './shared/BankAmountWithValue'
import useBanksWithBalances, {
BankWithBalance,
} from 'hooks/useBanksWithBalances'
import useUnownedAccount from 'hooks/useUnownedAccount'
const TokenList = () => {
const { t } = useTranslation(['common', 'token', 'trade'])
@ -399,7 +400,7 @@ const ActionsMenu = ({
const router = useRouter()
const { mangoTokens } = useJupiterMints()
const spotMarkets = mangoStore((s) => s.serumMarkets)
const { connected } = useWallet()
const isUnownedAccount = useUnownedAccount()
const spotMarket = useMemo(() => {
return spotMarkets.find((m) => {
@ -477,7 +478,7 @@ const ActionsMenu = ({
return (
<>
{mangoAccount && !connected ? null : (
{isUnownedAccount ? null : (
<IconDropMenu
icon={<EllipsisHorizontalIcon className="h-5 w-5" />}
postion="leftBottom"

View File

@ -24,6 +24,7 @@ import DepositWithdrawModal from './modals/DepositWithdrawModal'
import { useViewport } from 'hooks/useViewport'
import { breakpoints } from 'utils/theme'
import AccountsButton from './AccountsButton'
import useUnownedAccount from 'hooks/useUnownedAccount'
// import ThemeSwitcher from './ThemeSwitcher'
const TopBar = () => {
@ -41,6 +42,7 @@ const TopBar = () => {
const { query } = router
const { width } = useViewport()
const isMobile = width ? width < breakpoints.sm : false
const isUnownedAccount = useUnownedAccount()
const handleCloseSetup = useCallback(() => {
setShowUserSetup(false)
@ -77,7 +79,7 @@ const TopBar = () => {
</div>
) : null}
<img
className="mr-4 ml-2 h-8 w-auto md:hidden"
className="mr-4 h-8 w-auto md:hidden"
src="/logos/logo-mark.svg"
alt="next"
/>
@ -112,7 +114,7 @@ const TopBar = () => {
{/* <div className="px-3 md:px-4">
<ThemeSwitcher />
</div> */}
{(mangoAccount && !connected) || (!connected && isMobile) ? null : (
{isUnownedAccount || (!connected && isMobile) ? null : (
<Button
onClick={() => handleDepositWithdrawModal('deposit')}
secondary

View File

@ -23,6 +23,7 @@ import { useWallet } from '@solana/wallet-adapter-react'
import CreateAccountModal from '@components/modals/CreateAccountModal'
import { Popover, Transition } from '@headlessui/react'
import ActionsLinkButton from './ActionsLinkButton'
import useUnownedAccount from 'hooks/useUnownedAccount'
export const handleCopyAddress = (
mangoAccount: MangoAccount,
@ -37,7 +38,7 @@ export const handleCopyAddress = (
const AccountActions = () => {
const { t } = useTranslation(['common', 'close-account'])
const { mangoAccount } = useMangoAccount()
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
const [showCloseAccountModal, setShowCloseAccountModal] = useState(false)
const [showEditAccountModal, setShowEditAccountModal] = useState(false)
const [showBorrowModal, setShowBorrowModal] = useState(false)
@ -45,9 +46,10 @@ const AccountActions = () => {
const [showDelegateModal, setShowDelegateModal] = useState(false)
const [showCreateAccountModal, setShowCreateAccountModal] = useState(false)
const { connected } = useWallet()
const isUnownedAccount = useUnownedAccount()
const handleBorrowModal = () => {
if (!connected || mangoAccount) {
if (mangoAccountAddress || !connected) {
setShowBorrowModal(true)
} else {
setShowCreateAccountModal(true)
@ -56,11 +58,11 @@ const AccountActions = () => {
return (
<>
{mangoAccount && !connected ? null : (
{isUnownedAccount ? null : (
<div className="flex items-center space-x-2">
<Button
className="flex w-1/3 items-center justify-center md:w-auto"
disabled={!mangoAccount}
disabled={!mangoAccountAddress}
onClick={() => setShowRepayModal(true)}
secondary
>

View File

@ -16,7 +16,11 @@ const SimpleAreaChart = dynamic(
import { COLORS } from '../../styles/colors'
import { useTheme } from 'next-themes'
import { IconButton } from '../shared/Button'
import { ArrowsPointingOutIcon, ChartBarIcon } from '@heroicons/react/20/solid'
import {
ArrowsPointingOutIcon,
CalendarIcon,
ChartBarIcon,
} from '@heroicons/react/20/solid'
import { Transition } from '@headlessui/react'
import AccountTabs from './AccountTabs'
import SheenLoader from '../shared/SheenLoader'
@ -28,7 +32,7 @@ import {
ANIMATION_SETTINGS_KEY,
// IS_ONBOARDED_KEY
} from 'utils/constants'
// import { useWallet } from '@solana/wallet-adapter-react'
import { useWallet } from '@solana/wallet-adapter-react'
import useLocalStorageState from 'hooks/useLocalStorageState'
// import AccountOnboardingTour from '@components/tours/AccountOnboardingTour'
import dayjs from 'dayjs'
@ -42,7 +46,7 @@ import HealthBar from './HealthBar'
const AccountPage = () => {
const { t } = useTranslation(['common', 'account'])
// const { connected } = useWallet()
const { connected } = useWallet()
const { group } = useMangoGroup()
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
const actions = mangoStore.getState().actions
@ -69,8 +73,7 @@ const AccountPage = () => {
)
useEffect(() => {
if (mangoAccountAddress) {
console.log('fired')
if (mangoAccountAddress || (!mangoAccountAddress && connected)) {
actions.fetchAccountPerformance(mangoAccountAddress, 31)
actions.fetchAccountInterestTotals(mangoAccountAddress)
}
@ -442,7 +445,11 @@ const AccountPage = () => {
{mangoAccountAddress ? (
<div className="flex items-center space-x-3">
{performanceData.length > 4 ? (
<Tooltip content={t('account:pnl-chart')} delay={250}>
<Tooltip
className="hidden md:block"
content={t('account:pnl-chart')}
delay={250}
>
<IconButton
className="text-th-fgd-3"
hideBg
@ -452,15 +459,19 @@ const AccountPage = () => {
</IconButton>
</Tooltip>
) : null}
{/* <Tooltip content={t('account:pnl-history')} delay={250}>
<Tooltip
className="hidden md:block"
content={t('account:pnl-history')}
delay={250}
>
<IconButton
className="text-th-fgd-3"
hideBg
onClick={() => setShowPnlHistory(true)}
>
<ClockIcon className="h-5 w-5" />
<CalendarIcon className="h-5 w-5" />
</IconButton>
</Tooltip> */}
</Tooltip>
</div>
) : null}
</div>
@ -491,7 +502,11 @@ const AccountPage = () => {
</p>
</Tooltip>
{interestTotalValue > 1 || interestTotalValue < -1 ? (
<Tooltip content="Cumulative Interest Chart" delay={250}>
<Tooltip
className="hidden md:block"
content="Cumulative Interest Chart"
delay={250}
>
<IconButton
className="text-th-fgd-3"
hideBg

View File

@ -124,7 +124,10 @@ const ActivityFilters = () => {
<Disclosure>
<div className="flex items-center">
{hasFilters ? (
<Tooltip content={t('activity:reset-filters')}>
<Tooltip
className="hidden md:block"
content={t('activity:reset-filters')}
>
<IconButton
className={`${loadActivityFeed ? 'animate-spin' : ''}`}
onClick={() => handleResetFilters()}

View File

@ -201,27 +201,23 @@ const YourBorrowsTable = ({ banks }: { banks: BankWithBalance[] }) => {
</p>
</div>
<div className="flex space-x-2">
<Tooltip content={`${t('repay')} ${bank.name}`}>
<IconButton
onClick={() =>
handleShowActionModals(bank.name, 'repay')
}
size="medium"
>
<ArrowDownRightIcon className="h-5 w-5" />
</IconButton>
</Tooltip>
<Tooltip content={`${t('borrow')} ${bank.name}`}>
<IconButton
disabled={available.eq(0)}
onClick={() =>
handleShowActionModals(bank.name, 'borrow')
}
size="medium"
>
<ArrowUpLeftIcon className="h-5 w-5" />
</IconButton>
</Tooltip>
<IconButton
onClick={() =>
handleShowActionModals(bank.name, 'repay')
}
size="medium"
>
<ArrowDownRightIcon className="h-5 w-5" />
</IconButton>
<IconButton
disabled={available.eq(0)}
onClick={() =>
handleShowActionModals(bank.name, 'borrow')
}
size="medium"
>
<ArrowUpLeftIcon className="h-5 w-5" />
</IconButton>
</div>
</div>
</div>

View File

@ -75,7 +75,7 @@ const CloseAccountModal = ({ isOpen, onClose }: ModalProps) => {
}
const fetchTotalAccountSOL = useCallback(async () => {
if (!mangoAccount) {
if (!mangoAccount?.current) {
return
}
const accountKeys = [

View File

@ -193,7 +193,11 @@ const MangoAccountsListModal = ({
</div>
</button>
<div className="flex h-full items-center justify-center rounded-md rounded-l-none bg-th-bkg-3">
<Tooltip content={t('copy-address')} delay={250}>
<Tooltip
className="hidden md:block"
content={t('copy-address')}
delay={250}
>
<IconButton
className="text-th-fgd-3"
onClick={() =>

View File

@ -33,7 +33,7 @@ const PnlHistoryModal = ({
useEffect(() => {
if (mangoAccountAddress) {
actions.fetchAccountPerformance(mangoAccountAddress, 30)
actions.fetchAccountPerformance(mangoAccountAddress, 31)
}
}, [actions, mangoAccountAddress])
@ -41,8 +41,10 @@ const PnlHistoryModal = ({
if (!performanceData.length) return []
const dailyPnl = performanceData.filter((d: PerformanceDataItem) => {
const date = new Date(d.time)
return date.getHours() === 0
const startTime = new Date().getTime() - 30 * 86400000
const dataDate = new Date(d.time)
const dataTime = dataDate.getTime()
return dataTime >= startTime && dataDate.getHours() === 0
})
return dailyPnl.length

View File

@ -107,7 +107,7 @@ const DisplaySettings = () => {
/>
</div>
</div> */}
<div className="flex flex-col border-t border-th-bkg-3 py-4 md:flex-row md:items-center md:justify-between md:px-4">
<div className="hidden border-t border-th-bkg-3 py-4 md:flex md:flex-row md:items-center md:justify-between md:px-4">
<p className="mb-2 md:mb-0">{t('settings:notification-position')}</p>
<div className="w-full min-w-[140px] md:w-auto">
<Select

View File

@ -20,12 +20,12 @@ import { Table, Td, Th, TrBody, TrHead } from './TableElements'
import useSelectedMarket from 'hooks/useSelectedMarket'
import ConnectEmptyState from './ConnectEmptyState'
import { useWallet } from '@solana/wallet-adapter-react'
import Decimal from 'decimal.js'
import FormatNumericValue from './FormatNumericValue'
import BankAmountWithValue from './BankAmountWithValue'
import useBanksWithBalances, {
BankWithBalance,
} from 'hooks/useBanksWithBalances'
import useUnownedAccount from 'hooks/useUnownedAccount'
const BalancesTable = () => {
const { t } = useTranslation(['common', 'trade'])
@ -195,6 +195,7 @@ export default BalancesTable
const Balance = ({ bank }: { bank: BankWithBalance }) => {
const { selectedMarket } = useSelectedMarket()
const { asPath } = useRouter()
const isUnownedAccount = useUnownedAccount()
const tokenBank = bank.bank
@ -288,37 +289,43 @@ const Balance = ({ bank }: { bank: BankWithBalance }) => {
}
}, [tokenBank, selectedMarket])
console.log(tokenBank.name, ' balance', new Decimal(balance).toFixed())
if (!balance) return <p className="flex justify-end">0</p>
return (
<p className="flex justify-end">
{asPath.includes('/trade') && isBaseOrQuote ? (
<LinkButton
className="font-normal underline-offset-4"
onClick={() =>
handleTradeFormBalanceClick(Math.abs(balance), isBaseOrQuote)
}
>
{!isUnownedAccount ? (
asPath.includes('/trade') && isBaseOrQuote ? (
<LinkButton
className="font-normal underline-offset-4"
onClick={() =>
handleTradeFormBalanceClick(Math.abs(balance), isBaseOrQuote)
}
>
<FormatNumericValue
value={balance}
decimals={tokenBank.mintDecimals}
/>
</LinkButton>
) : asPath.includes('/swap') ? (
<LinkButton
className="font-normal underline-offset-4"
onClick={() =>
handleSwapFormBalanceClick(
Number(formatNumericValue(balance, tokenBank.mintDecimals))
)
}
>
<FormatNumericValue
value={balance}
decimals={tokenBank.mintDecimals}
/>
</LinkButton>
) : (
<FormatNumericValue
value={balance}
decimals={tokenBank.mintDecimals}
/>
</LinkButton>
) : asPath.includes('/swap') ? (
<LinkButton
className="font-normal underline-offset-4"
onClick={() =>
handleSwapFormBalanceClick(
Number(formatNumericValue(balance, tokenBank.mintDecimals))
)
}
>
<FormatNumericValue
value={balance}
decimals={tokenBank.mintDecimals}
/>
</LinkButton>
)
) : (
<FormatNumericValue value={balance} decimals={tokenBank.mintDecimals} />
)}

View File

@ -363,12 +363,14 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
domain
? domain
: ([dataMin, dataMax]) => {
const difference =
Math.abs(dataMax) - Math.abs(dataMin)
if (difference < 0.1) {
const difference = dataMax - dataMin
if (difference < 0.01) {
return [dataMin - 0.001, dataMax + 0.001]
} else if (difference < 0.1) {
return [dataMin - 0.01, dataMax + 0.01]
} else if (difference < 1) {
return [dataMin - 0.1, dataMax + 0.1]
return [dataMin - 0.1, dataMax + 0.11]
} else if (difference < 10) {
return [dataMin - 1, dataMax + 1]
} else {

View File

@ -1,4 +1,4 @@
import { Fragment, useCallback, useEffect, useMemo, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import {
CheckCircleIcon,
ArrowTopRightOnSquareIcon,
@ -70,31 +70,31 @@ const NotificationList = () => {
const reversedNotifications = [...notifications].reverse()
const position: string = useMemo(() => {
switch (notificationPosition) {
case 'bottom-left':
return 'bottom-0 left-0'
case 'bottom-right':
return 'bottom-0 right-0'
case 'top-left':
return 'top-0 left-0'
case 'top-right':
return 'top-0 right-0'
const getPosition = (position: string) => {
const sharedClasses =
'pointer-events-none fixed z-50 flex items-end p-4 text-th-fgd-1 md:p-6'
switch (position) {
case 'Bottom-Left':
return 'flex-col bottom-0 left-0 ' + sharedClasses
case 'Bottom-Right':
return 'flex-col w-full bottom-0 right-0 ' + sharedClasses
case 'Top-Left':
return 'flex-col-reverse top-0 left-0 ' + sharedClasses
case 'Top-Right':
return 'flex-col-reverse w-full top-0 right-0 ' + sharedClasses
default:
return 'bottom-0 left-0'
return 'flex-col bottom-0 left-0 ' + sharedClasses
}
}, [notificationPosition])
}
useEffect(() => setMounted(true), [])
if (!mounted) return null
return (
<div
className={`pointer-events-none fixed z-50 flex w-full flex-col items-end space-y-2 p-4 text-th-fgd-1 md:w-auto md:p-6 ${position}`}
>
<div className={`${getPosition(notificationPosition)}`}>
{notifications.filter((n) => n.show).length > 1 ? (
<button
className="default-transition pointer-events-auto flex items-center rounded bg-th-bkg-3 px-2 py-1 text-xs text-th-fgd-3 md:hover:bg-th-bkg-4"
className="default-transition pointer-events-auto my-1 flex items-center rounded bg-th-bkg-3 px-2 py-1 text-xs text-th-fgd-3 md:hover:bg-th-bkg-4"
onClick={clearAll}
>
<XMarkIcon className="mr-1 h-3.5 w-3.5" />
@ -111,7 +111,7 @@ const NotificationList = () => {
const Notification = ({ notification }: { notification: Notification }) => {
const [notificationPosition] = useLocalStorageState(
NOTIFICATION_POSITION_KEY,
'bottom-left'
'Bottom-Left'
)
const [preferredExplorer] = useLocalStorageState(
PREFERRED_EXPLORER_KEY,
@ -179,17 +179,7 @@ const Notification = ({ notification }: { notification: Notification }) => {
}
})
const {
enterFromClass,
enterToClass,
leaveFromClass,
leaveToClass,
}: {
enterFromClass: string
enterToClass: string
leaveFromClass: string
leaveToClass: string
} = useMemo(() => {
const getTransformClasses = (position: string) => {
const fromLeft = {
enterFromClass: 'md:-translate-x-48',
enterToClass: 'md:translate-x-100',
@ -202,31 +192,39 @@ const Notification = ({ notification }: { notification: Notification }) => {
leaveFromClass: 'md:translate-x-0',
leaveToClass: 'md:translate-x-48',
}
switch (notificationPosition) {
case 'bottom-left':
switch (position) {
case 'Bottom-Left':
return fromLeft
case 'bottom-right':
case 'Bottom-Right':
return fromRight
case 'top-left':
case 'Top-Left':
return fromLeft
case 'top-right':
case 'Top-Right':
return fromRight
default:
return fromLeft
}
}, [notificationPosition])
}
return (
<Transition
className="my-1 w-full md:w-auto"
show={show}
as={Fragment}
appear={true}
enter="ease-out duration-500 transition"
enterFrom={`-translate-y-2 opacity-0 md:translate-y-0 ${enterFromClass}`}
enterTo={`translate-y-0 opacity-100 ${enterToClass}`}
enterFrom={`-translate-y-2 opacity-0 md:translate-y-0 ${
getTransformClasses(notificationPosition).enterFromClass
}`}
enterTo={`translate-y-0 opacity-100 ${
getTransformClasses(notificationPosition).enterToClass
}`}
leave="ease-in duration-200 transition"
leaveFrom={`translate-y-0 ${leaveFromClass}`}
leaveTo={`-translate-y-2 md:translate-y-0 ${leaveToClass}`}
leaveFrom={`translate-y-0 ${
getTransformClasses(notificationPosition).leaveFromClass
}`}
leaveTo={`-translate-y-2 md:translate-y-0 ${
getTransformClasses(notificationPosition).leaveToClass
}`}
>
<div
className={`pointer-events-auto w-full rounded-md border bg-th-bkg-2 shadow-lg md:w-auto ${

View File

@ -43,8 +43,11 @@ const SimpleAreaChart = ({
<XAxis dataKey={xKey} hide />
<YAxis
domain={([dataMin, dataMax]) => {
const difference = Math.abs(dataMax) - Math.abs(dataMin)
if (difference < 0.1) {
const difference = dataMax - dataMin
if (difference < 0.01) {
return [dataMin - 0.001, dataMax + 0.001]
} else if (difference < 0.1) {
return [dataMin - 0.01, dataMax + 0.01]
} else if (difference < 1) {
return [dataMin - 0.1, dataMax + 0.11]

View File

@ -10,10 +10,7 @@ import SpotMarketsTable from './SpotMarketsTable'
import TokenStats from './TokenStats'
// const TABS = ['tokens', 'perp', 'spot', 'mango']
const TABS =
process.env.NEXT_PUBLIC_SHOW_PERPS === 'true'
? ['tokens', 'perp', 'spot', 'mango']
: ['tokens', 'spot', 'mango']
const TABS = ['tokens', 'perp', 'spot', 'mango']
const StatsPage = () => {
const [activeTab, setActiveTab] = useState('tokens')

View File

@ -48,6 +48,7 @@ import useIpAddress from 'hooks/useIpAddress'
import { useEnhancedWallet } from '@components/wallet/EnhancedWalletProvider'
import SwapSettings from './SwapSettings'
import InlineNotification from '@components/shared/InlineNotification'
import useUnownedAccount from 'hooks/useUnownedAccount'
const MAX_DIGITS = 11
export const withValueLimit = (values: NumberFormatValues): boolean => {
@ -69,6 +70,7 @@ const SwapForm = () => {
const { group } = useMangoGroup()
const [swapFormSizeUi] = useLocalStorageState(SIZE_INPUT_UI_KEY, 'slider')
const { ipAllowed, ipCountry } = useIpAddress()
const isUnownedAccount = useUnownedAccount()
const {
margin: useMargin,
@ -327,10 +329,12 @@ const SwapForm = () => {
</div>
<div className="mb-2 flex items-end justify-between">
<p className="text-th-fgd-2 lg:text-base">{t('swap:pay')}</p>
<MaxSwapAmount
useMargin={useMargin}
setAmountIn={(v) => setAmountInFormValue(v, true)}
/>
{!isUnownedAccount ? (
<MaxSwapAmount
useMargin={useMargin}
setAmountIn={(v) => setAmountInFormValue(v, true)}
/>
) : null}
</div>
<div className="mb-3 grid grid-cols-2" id="swap-step-two">
<div className="col-span-1 rounded-lg rounded-r-none border border-r-0 border-th-input-border bg-th-input-bkg">

View File

@ -164,8 +164,6 @@ const fetchJupiterTransaction = async (
const filtered_jup_ixs = ixs
.filter((ix) => !isSetupIx(ix.programId))
.filter((ix) => !isDuplicateAta(ix))
console.log('ixs: ', ixs)
console.log('filtered ixs: ', filtered_jup_ixs)
return [filtered_jup_ixs, alts]
}

View File

@ -103,7 +103,13 @@ export const getTokenInMax = (
}
}
export const useTokenMax = (useMargin = true) => {
interface TokenMaxResults {
amount: Decimal
amountWithBorrow: Decimal
decimals: number
}
export const useTokenMax = (useMargin = true): TokenMaxResults => {
const { mangoAccount } = useMangoAccount()
const { group } = useMangoGroup()
const inputBank = mangoStore((s) => s.swap.inputBank)

View File

@ -9,7 +9,7 @@ import { useCoingecko } from 'hooks/useCoingecko'
import parse from 'html-react-parser'
import { useTranslation } from 'next-i18next'
import dynamic from 'next/dynamic'
import { useLayoutEffect, useMemo, useRef, useState } from 'react'
import { useMemo, useState } from 'react'
const PriceChart = dynamic(() => import('@components/token/PriceChart'), {
ssr: false,
})
@ -48,14 +48,6 @@ const CoingeckoStats = ({
const [chartData, setChartData] = useState<{ prices: any[] } | null>(null)
const [loadChartData, setLoadChartData] = useState(true)
const { isLoading: loadingPrices, data: coingeckoPrices } = useCoingecko()
const descWidthRef = useRef<any>(null)
const [width, setWidth] = useState<number>(0)
useLayoutEffect(() => {
if (!descWidthRef.current) return
setWidth(descWidthRef.current.clientWidth)
}, [])
const handleDaysToShow = async (days: string) => {
if (days !== '1') {
@ -109,21 +101,26 @@ const CoingeckoStats = ({
return []
}, [coingeckoPrices, bank, daysToShow, chartData, loadingChart])
const truncateDescription = (desc: string) =>
desc.substring(0, (desc + ' ').lastIndexOf(' ', 144))
const description = useMemo(() => {
const desc = coingeckoData?.description?.en
if (!desc) return ''
return showFullDesc
? coingeckoData.description.en
: truncateDescription(coingeckoData.description.en)
}, [coingeckoData, showFullDesc])
return (
<>
{coingeckoData?.description?.en?.length ? (
{description ? (
<div className="border-b border-th-bkg-3 py-4 px-6">
<h2 className="mb-1 text-xl">About {bank.name}</h2>
<div className="flex items-end">
<p
className={`${
showFullDesc ? 'h-full' : 'h-5'
} max-w-[720px] overflow-hidden`}
ref={descWidthRef}
>
{parse(coingeckoData.description.en)}
</p>
{width === 720 ? (
<p className="max-w-[720px]">{parse(description)}</p>
{coingeckoData.description.en.length > description.length ||
showFullDesc ? (
<span
className="default-transition ml-4 flex cursor-pointer items-end font-normal underline hover:text-th-fgd-2 md:hover:no-underline"
onClick={() => setShowFullDesc(!showFullDesc)}

View File

@ -3,7 +3,7 @@ import { useTheme } from 'next-themes'
import { useMemo } from 'react'
import { Area, AreaChart, ResponsiveContainer, XAxis, YAxis } from 'recharts'
import { COLORS } from 'styles/colors'
import { formatYAxis } from 'utils/formatting'
import { formatCurrencyValue } from 'utils/numbers'
const PriceChart = ({
prices,
@ -19,8 +19,8 @@ const PriceChart = ({
}, [prices])
return (
<div className="relative -mt-1 h-96 w-auto">
<div className="-mx-6 mt-6 h-full px-10">
<div className="relative -mt-1 h-72 w-auto md:h-96">
<div className="mt-6 h-full">
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={prices}>
<defs>
@ -71,8 +71,9 @@ const PriceChart = ({
fill: 'var(--fgd-4)',
fontSize: 10,
}}
tickFormatter={(x) => formatYAxis(x)}
tickFormatter={(x) => formatCurrencyValue(x)}
tickLine={false}
width={prices[0][1] < 0.00001 ? 100 : 60}
/>
</AreaChart>
</ResponsiveContainer>

View File

@ -55,7 +55,7 @@ const AdvancedMarketHeader = ({
return (
<div className="flex flex-col bg-th-bkg-1 md:h-12 md:flex-row md:items-center">
<div className=" w-full px-5 md:w-auto md:py-0 md:pr-6 lg:pb-0">
<div className="w-full px-4 md:w-auto md:px-6 md:py-0 lg:pb-0">
<MarketSelectDropdown />
</div>
<div className="border-t border-th-bkg-3 py-2 px-5 md:border-t-0 md:py-0 md:px-0">

View File

@ -537,6 +537,7 @@ const AdvancedTradeForm = () => {
{selectedMarket instanceof Serum3Market ? (
<div className="mt-4" id="trade-step-eight">
<Tooltip
className="hidden md:block"
delay={250}
placement="left"
content={t('trade:tooltip-enable-margin')}
@ -594,7 +595,9 @@ const AdvancedTradeForm = () => {
) : (
<div className="flex items-center space-x-2">
<Loading />
<span>{t('trade:placing-order')}</span>
<span className="hidden sm:block">
{t('trade:placing-order')}
</span>
</div>
)}
</Button>

View File

@ -3,24 +3,20 @@ import { StarIcon } from '@heroicons/react/20/solid'
import useLocalStorageState from 'hooks/useLocalStorageState'
import useMangoGroup from 'hooks/useMangoGroup'
import useSelectedMarket from 'hooks/useSelectedMarket'
import { useViewport } from 'hooks/useViewport'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { FAVORITE_MARKETS_KEY } from 'utils/constants'
import { breakpoints } from 'utils/theme'
import MarketLogos from './MarketLogos'
const FavoriteMarketsBar = () => {
const [favoriteMarkets] = useLocalStorageState(FAVORITE_MARKETS_KEY, [])
const { width } = useViewport()
const isMobile = width ? width < breakpoints.sm : false
const { asPath } = useRouter()
const { selectedMarket } = useSelectedMarket()
const { group } = useMangoGroup()
return !isMobile ? (
return (
<Transition
className="flex items-center space-x-4 overflow-hidden border-b border-th-bkg-3 py-1 px-6"
className="hide-scroll flex items-center space-x-2 overflow-x-auto border-b border-th-bkg-3 bg-th-bkg-2 py-1 px-4 md:space-x-4 md:px-6"
show={!!favoriteMarkets.length}
enter="transition-all ease-in duration-200"
enterFrom="opacity-0 h-0"
@ -29,7 +25,7 @@ const FavoriteMarketsBar = () => {
leaveFrom="opacity-100 h-8"
leaveTo="opacity-0 h-0"
>
<StarIcon className="h-4 w-4 text-th-fgd-4" />
<StarIcon className="h-4 w-4 flex-shrink-0 text-th-fgd-4" />
{favoriteMarkets.map((mkt: string) => {
// const change24h = marketsInfo?.find((m) => m.name === mkt)?.change24h
const isPerp = mkt.includes('PERP')
@ -71,7 +67,7 @@ const FavoriteMarketsBar = () => {
)
})}
</Transition>
) : null
)
}
export default FavoriteMarketsBar

View File

@ -10,8 +10,7 @@ import { useMemo, useState } from 'react'
import { DEFAULT_MARKET_NAME } from 'utils/constants'
import MarketLogos from './MarketLogos'
const isTesting = process.env.NEXT_PUBLIC_SHOW_PERPS === 'true'
const TAB_VALUES = isTesting ? ['spot', 'perp'] : ['spot']
const TAB_VALUES = ['spot', 'perp']
const MarketSelectDropdown = () => {
const { selectedMarket } = useSelectedMarket()
@ -21,11 +20,13 @@ const MarketSelectDropdown = () => {
const [spotBaseFilter, setSpotBaseFilter] = useState('All')
const perpMarkets = useMemo(() => {
return allPerpMarkets.filter(
(p) =>
p.publicKey.toString() !==
'9Y8paZ5wUpzLFfQuHz8j2RtPrKsDtHx9sbgFmWb5abCw'
)
return allPerpMarkets
.filter(
(p) =>
p.publicKey.toString() !==
'9Y8paZ5wUpzLFfQuHz8j2RtPrKsDtHx9sbgFmWb5abCw'
)
.sort((a, b) => a.name.localeCompare(b.name))
}, [allPerpMarkets])
const spotBaseTokens: string[] = useMemo(() => {
@ -87,6 +88,7 @@ const MarketSelectDropdown = () => {
return mkt.name.split('/')[1] === spotBaseFilter
}
})
.sort((a, b) => a.name.localeCompare(b.name))
.map((m) => {
return (
<div

View File

@ -4,6 +4,7 @@ import { FadeInFadeOut } from '@components/shared/Transitions'
import mangoStore from '@store/mangoStore'
import useMangoAccount from 'hooks/useMangoAccount'
import useSelectedMarket from 'hooks/useSelectedMarket'
import useUnownedAccount from 'hooks/useUnownedAccount'
import { useTranslation } from 'next-i18next'
import { useCallback, useMemo } from 'react'
import { formatNumericValue } from 'utils/numbers'
@ -28,6 +29,7 @@ const MaxSizeButton = ({
side,
useMargin
)
const isUnownedAccount = useUnownedAccount()
const perpMax = useMemo(() => {
const group = mangoStore.getState().group
@ -108,7 +110,7 @@ const MaxSizeButton = ({
return (
<div className="mb-2 mt-3 flex items-center justify-between">
<p className="text-xs text-th-fgd-3">{t('trade:size')}</p>
<FadeInFadeOut show={!!price}>
<FadeInFadeOut show={!!price && !isUnownedAccount}>
<MaxAmountButton
className="text-xs"
decimals={minOrderDecimals}

View File

@ -7,12 +7,16 @@ import TabButtons from '@components/shared/TabButtons'
import { TABS } from './OrderbookAndTrades'
import RecentTrades from './RecentTrades'
import TradingChartContainer from './TradingChartContainer'
import FavoriteMarketsBar from './FavoriteMarketsBar'
const MobileTradeAdvancedPage = () => {
const [activeTab, setActiveTab] = useState('trade:book')
const [showChart, setShowChart] = useState(false)
return (
<div className="grid grid-cols-2 sm:grid-cols-3">
<div className="col-span-2 sm:col-span-3">
<FavoriteMarketsBar />
</div>
<div className="col-span-2 border-b border-th-bkg-3 sm:col-span-3">
<AdvancedMarketHeader
showChart={showChart}
@ -36,18 +40,10 @@ const MobileTradeAdvancedPage = () => {
fillWidth
/>
</div>
<div
className={`h-full ${
activeTab === 'trade:book' ? 'visible' : 'hidden'
}`}
>
<div className={activeTab === 'trade:book' ? 'visible' : 'hidden'}>
<Orderbook />
</div>
<div
className={`h-full ${
activeTab === 'trade:trades' ? 'visible' : 'hidden'
}`}
>
<div className={activeTab === 'trade:trades' ? 'visible' : 'hidden'}>
<RecentTrades />
</div>
</div>

View File

@ -27,6 +27,7 @@ import { useWallet } from '@solana/wallet-adapter-react'
import { PublicKey } from '@solana/web3.js'
import mangoStore from '@store/mangoStore'
import useMangoAccount from 'hooks/useMangoAccount'
import useUnownedAccount from 'hooks/useUnownedAccount'
import { useViewport } from 'hooks/useViewport'
import { useTranslation } from 'next-i18next'
import { ChangeEvent, useCallback, useState } from 'react'
@ -49,6 +50,7 @@ const OpenOrders = () => {
const showTableView = width ? width > breakpoints.md : false
const { mangoAccountAddress } = useMangoAccount()
const { connected } = useWallet()
const isUnownedAccount = useUnownedAccount()
const findSerum3MarketPkInOpenOrders = (o: Order): string | undefined => {
const openOrders = mangoStore.getState().mangoAccount.openOrders
@ -237,7 +239,9 @@ const OpenOrders = () => {
<Th className="w-[16.67%] text-right">{t('trade:size')}</Th>
<Th className="w-[16.67%] text-right">{t('price')}</Th>
<Th className="w-[16.67%] text-right">{t('value')}</Th>
<Th className="w-[16.67%] text-right"></Th>
{!isUnownedAccount ? (
<Th className="w-[16.67%] text-right" />
) : null}
</TrHead>
</thead>
<tbody>
@ -248,21 +252,14 @@ const OpenOrders = () => {
let market: PerpMarket | Serum3Market
let tickSize: number
let minOrderSize: number
let quoteSymbol
if (o instanceof PerpOrder) {
market = group.getPerpMarketByMarketIndex(o.perpMarketIndex)
quoteSymbol = group.getFirstBankByTokenIndex(
market.settleTokenIndex
).name
tickSize = market.tickSize
minOrderSize = market.minOrderSize
} else {
market = group.getSerum3MarketByExternalMarket(
new PublicKey(marketPk)
)
quoteSymbol = group.getFirstBankByTokenIndex(
market!.quoteTokenIndex
).name
const serumMarket = group.getSerum3ExternalMarket(
market.serumMarketExternal
)
@ -283,21 +280,16 @@ const OpenOrders = () => {
{modifyOrderId !== o.orderId.toString() ? (
<>
<Td className="w-[16.67%] text-right font-mono">
{o.size.toLocaleString(undefined, {
maximumFractionDigits:
getDecimalCount(minOrderSize),
})}
<FormatNumericValue
value={o.size}
decimals={getDecimalCount(minOrderSize)}
/>
</Td>
<Td className="w-[16.67%] whitespace-nowrap text-right">
<span className="font-mono">
{o.price.toLocaleString(undefined, {
minimumFractionDigits: getDecimalCount(tickSize),
maximumFractionDigits: getDecimalCount(tickSize),
})}{' '}
<span className="font-body text-th-fgd-4">
{quoteSymbol}
</span>
</span>
<Td className="w-[16.67%] whitespace-nowrap text-right font-mono">
<FormatNumericValue
value={o.price}
decimals={getDecimalCount(tickSize)}
/>
</Td>
</>
) : (
@ -325,63 +317,61 @@ const OpenOrders = () => {
</Td>
</>
)}
<Td className="w-[16.67%] text-right">
<FormatNumericValue
value={o.size * o.price}
decimals={2}
isUsd
/>
<Td className="w-[16.67%] text-right font-mono">
<FormatNumericValue value={o.size * o.price} isUsd />
</Td>
<Td className="w-[16.67%]">
<div className="flex justify-end space-x-2">
{modifyOrderId !== o.orderId.toString() ? (
<>
<IconButton
onClick={() => showEditOrderForm(o, tickSize)}
size="small"
>
<PencilIcon className="h-4 w-4" />
</IconButton>
<Tooltip content={t('cancel')}>
{!isUnownedAccount ? (
<Td className="w-[16.67%]">
<div className="flex justify-end space-x-2">
{modifyOrderId !== o.orderId.toString() ? (
<>
<IconButton
disabled={cancelId === o.orderId.toString()}
onClick={() =>
o instanceof PerpOrder
? handleCancelPerpOrder(o)
: handleCancelSerumOrder(o)
}
onClick={() => showEditOrderForm(o, tickSize)}
size="small"
>
{cancelId === o.orderId.toString() ? (
<PencilIcon className="h-4 w-4" />
</IconButton>
<Tooltip content={t('cancel')}>
<IconButton
disabled={cancelId === o.orderId.toString()}
onClick={() =>
o instanceof PerpOrder
? handleCancelPerpOrder(o)
: handleCancelSerumOrder(o)
}
size="small"
>
{cancelId === o.orderId.toString() ? (
<Loading className="h-4 w-4" />
) : (
<TrashIcon className="h-4 w-4" />
)}
</IconButton>
</Tooltip>
</>
) : (
<>
<IconButton
onClick={() => modifyOrder(o)}
size="small"
>
{loadingModifyOrder ? (
<Loading className="h-4 w-4" />
) : (
<TrashIcon className="h-4 w-4" />
<CheckIcon className="h-4 w-4" />
)}
</IconButton>
</Tooltip>
</>
) : (
<>
<IconButton
onClick={() => modifyOrder(o)}
size="small"
>
{loadingModifyOrder ? (
<Loading className="h-4 w-4" />
) : (
<CheckIcon className="h-4 w-4" />
)}
</IconButton>
<IconButton
onClick={cancelEditOrderForm}
size="small"
>
<XMarkIcon className="h-4 w-4" />
</IconButton>
</>
)}
</div>
</Td>
<IconButton
onClick={cancelEditOrderForm}
size="small"
>
<XMarkIcon className="h-4 w-4" />
</IconButton>
</>
)}
</div>
</Td>
) : null}
</TrBody>
)
})
@ -397,24 +387,14 @@ const OpenOrders = () => {
let market: PerpMarket | Serum3Market
let tickSize: number
let minOrderSize: number
let quoteSymbol: string
let baseSymbol: string
if (o instanceof PerpOrder) {
market = group.getPerpMarketByMarketIndex(o.perpMarketIndex)
baseSymbol = market.name.split('-')[0]
quoteSymbol = group.getFirstBankByTokenIndex(
market.settleTokenIndex
).name
tickSize = market.tickSize
minOrderSize = market.minOrderSize
} else {
market = group.getSerum3MarketByExternalMarket(
new PublicKey(marketPk)
)
baseSymbol = market.name.split('/')[0]
quoteSymbol = group.getFirstBankByTokenIndex(
market!.quoteTokenIndex
).name
const serumMarket = group.getSerum3ExternalMarket(
market.serumMarketExternal
)
@ -433,20 +413,18 @@ const OpenOrders = () => {
<SideBadge side={o.side} />
<p className="text-th-fgd-4">
<span className="font-mono text-th-fgd-3">
{o.size.toLocaleString(undefined, {
maximumFractionDigits:
getDecimalCount(minOrderSize),
})}
</span>{' '}
{baseSymbol}
{' for '}
<FormatNumericValue
value={o.size}
decimals={getDecimalCount(minOrderSize)}
/>
</span>
{' at '}
<span className="font-mono text-th-fgd-3">
{o.price.toLocaleString(undefined, {
minimumFractionDigits: getDecimalCount(tickSize),
maximumFractionDigits: getDecimalCount(tickSize),
})}
</span>{' '}
{quoteSymbol}
<FormatNumericValue
value={o.price}
decimals={getDecimalCount(tickSize)}
/>
</span>
</p>
</div>
) : (
@ -477,7 +455,7 @@ const OpenOrders = () => {
</div>
)}
</div>
{connected ? (
{!isUnownedAccount ? (
<div className="flex items-center space-x-3 pl-8">
<div className="flex items-center space-x-2">
{modifyOrderId !== o.orderId.toString() ? (

View File

@ -437,6 +437,7 @@ const Orderbook = () => {
<div className="flex items-center justify-between border-b border-th-bkg-3 px-4 py-2">
<div id="trade-step-three" className="flex items-center space-x-1.5">
<Tooltip
className="hidden md:block"
content={showBuys ? t('trade:hide-bids') : t('trade:show-bids')}
placement="bottom"
>
@ -451,6 +452,7 @@ const Orderbook = () => {
</button>
</Tooltip>
<Tooltip
className="hidden md:block"
content={showSells ? t('trade:hide-asks') : t('trade:show-asks')}
placement="bottom"
>
@ -464,7 +466,11 @@ const Orderbook = () => {
<OrderbookIcon className="h-4 w-4" side="sell" />
</button>
</Tooltip>
<Tooltip content={'Reset and center orderbook'} placement="bottom">
<Tooltip
className="hidden md:block"
content={'Reset and center orderbook'}
placement="bottom"
>
<button
className={`rounded ${
showSells ? 'bg-th-bkg-3' : 'bg-th-bkg-2'
@ -477,7 +483,12 @@ const Orderbook = () => {
</div>
{market ? (
<div id="trade-step-four">
<Tooltip content={t('trade:grouping')} placement="left" delay={250}>
<Tooltip
className="hidden md:block"
content={t('trade:grouping')}
placement="left"
delay={250}
>
<GroupSize
tickSize={market.tickSize}
onChange={onGroupSizeChange}

View File

@ -9,9 +9,12 @@ import mangoStore from '@store/mangoStore'
import useMangoAccount from 'hooks/useMangoAccount'
import useMangoGroup from 'hooks/useMangoGroup'
import useSelectedMarket from 'hooks/useSelectedMarket'
import useUnownedAccount from 'hooks/useUnownedAccount'
import { useViewport } from 'hooks/useViewport'
import { useTranslation } from 'next-i18next'
import { useCallback, useState } from 'react'
import { floorToDecimal, getDecimalCount } from 'utils/numbers'
import { breakpoints } from 'utils/theme'
import { calculateLimitPriceForMarketOrder } from 'utils/tradeForm'
import MarketCloseModal from './MarketCloseModal'
import PerpSideBadge from './PerpSideBadge'
@ -28,6 +31,9 @@ const PerpPositions = () => {
const { selectedMarket } = useSelectedMarket()
const { connected } = useWallet()
const { mangoAccountAddress } = useMangoAccount()
const isUnownedAccount = useUnownedAccount()
const { width } = useViewport()
const showTableView = width ? width > breakpoints.md : false
const handlePositionClick = (positionSize: number, market: PerpMarket) => {
const tradeForm = mangoStore.getState().tradeForm
@ -72,53 +78,152 @@ const PerpPositions = () => {
)
return mangoAccountAddress && openPerpPositions.length ? (
<>
<div className="thin-scroll overflow-x-auto">
<Table>
<thead>
<TrHead>
<Th className="text-left">{t('market')}</Th>
<Th className="text-right">{t('trade:side')}</Th>
<Th className="text-right">{t('trade:size')}</Th>
<Th className="text-right">{t('trade:notional')}</Th>
<Th className="text-right">{t('trade:entry-price')}</Th>
<Th className="text-right">{`${t('trade:unsettled')} ${t(
'pnl'
)}`}</Th>
<Th className="text-right">{t('pnl')}</Th>
<Th />
</TrHead>
</thead>
<tbody>
{openPerpPositions.map((position) => {
const market = group.getPerpMarketByMarketIndex(
position.marketIndex
)
const basePosition = position.getBasePositionUi(market)
const floorBasePosition = floorToDecimal(
basePosition,
getDecimalCount(market.minOrderSize)
).toNumber()
const isSelectedMarket =
selectedMarket instanceof PerpMarket &&
selectedMarket.perpMarketIndex === position.marketIndex
showTableView ? (
<>
<div className="thin-scroll overflow-x-auto">
<Table>
<thead>
<TrHead>
<Th className="text-left">{t('market')}</Th>
<Th className="text-right">{t('trade:side')}</Th>
<Th className="text-right">{t('trade:size')}</Th>
<Th className="text-right">{t('trade:notional')}</Th>
<Th className="text-right">{t('trade:entry-price')}</Th>
<Th className="text-right">{`${t('trade:unsettled')} ${t(
'pnl'
)}`}</Th>
<Th className="text-right">{t('pnl')}</Th>
{!isUnownedAccount ? <Th /> : null}
</TrHead>
</thead>
<tbody>
{openPerpPositions.map((position) => {
const market = group.getPerpMarketByMarketIndex(
position.marketIndex
)
const basePosition = position.getBasePositionUi(market)
const floorBasePosition = floorToDecimal(
basePosition,
getDecimalCount(market.minOrderSize)
).toNumber()
const isSelectedMarket =
selectedMarket instanceof PerpMarket &&
selectedMarket.perpMarketIndex === position.marketIndex
if (!basePosition) return null
if (!basePosition) return null
const unsettledPnl = position.getUnsettledPnlUi(market)
const cummulativePnl =
position.cumulativePnlOverPositionLifetimeUi(market)
const unsettledPnl = position.getUnsettledPnlUi(market)
const cummulativePnl =
position.cumulativePnlOverPositionLifetimeUi(market)
return (
<TrBody key={`${position.marketIndex}`} className="my-1 p-2">
<Td>
<TableMarketName market={market} />
</Td>
<Td className="text-right">
<PerpSideBadge basePosition={basePosition} />
</Td>
<Td className="text-right font-mono">
<p className="flex justify-end">
return (
<TrBody key={`${position.marketIndex}`} className="my-1 p-2">
<Td>
<TableMarketName market={market} />
</Td>
<Td className="text-right">
<PerpSideBadge basePosition={basePosition} />
</Td>
<Td className="text-right font-mono">
<p className="flex justify-end">
{isSelectedMarket ? (
<LinkButton
onClick={() =>
handlePositionClick(floorBasePosition, market)
}
>
<FormatNumericValue
value={Math.abs(basePosition)}
decimals={getDecimalCount(market.minOrderSize)}
/>
</LinkButton>
) : (
<FormatNumericValue
value={Math.abs(basePosition)}
decimals={getDecimalCount(market.minOrderSize)}
/>
)}
</p>
</Td>
<Td className="text-right font-mono">
<FormatNumericValue
value={Math.abs(floorBasePosition) * market._uiPrice}
isUsd
/>
</Td>
<Td className="text-right font-mono">
<FormatNumericValue
value={position.getAverageEntryPriceUi(market)}
decimals={getDecimalCount(market.tickSize)}
isUsd
/>
</Td>
<Td className={`text-right font-mono`}>
<FormatNumericValue
value={unsettledPnl}
decimals={market.baseDecimals}
/>
</Td>
<Td
className={`text-right font-mono ${
cummulativePnl > 0 ? 'text-th-up' : 'text-th-down'
}`}
>
<FormatNumericValue value={cummulativePnl} isUsd />
</Td>
{!isUnownedAccount ? (
<Td className={`text-right`}>
<Button
className="text-xs"
secondary
size="small"
onClick={() => showClosePositionModal(position)}
>
Close
</Button>
</Td>
) : null}
</TrBody>
)
})}
</tbody>
</Table>
</div>
{showMarketCloseModal && positionToClose ? (
<MarketCloseModal
isOpen={showMarketCloseModal}
onClose={hideClosePositionModal}
position={positionToClose}
/>
) : null}
</>
) : (
<>
{openPerpPositions.map((position) => {
const market = group.getPerpMarketByMarketIndex(position.marketIndex)
const basePosition = position.getBasePositionUi(market)
const floorBasePosition = floorToDecimal(
basePosition,
getDecimalCount(market.minOrderSize)
).toNumber()
const isSelectedMarket =
selectedMarket instanceof PerpMarket &&
selectedMarket.perpMarketIndex === position.marketIndex
if (!basePosition) return null
const cummulativePnl =
position.cumulativePnlOverPositionLifetimeUi(market)
return (
<div
className="flex items-center justify-between border-b border-th-bkg-3 p-4"
key={`${position.marketIndex}`}
>
<div>
<TableMarketName market={market} />
<div className="mt-1 flex items-center space-x-1">
<PerpSideBadge basePosition={basePosition} />
<p className="text-th-fgd-4">
<span className="font-mono text-th-fgd-3">
{isSelectedMarket ? (
<LinkButton
onClick={() =>
@ -136,58 +241,42 @@ const PerpPositions = () => {
decimals={getDecimalCount(market.minOrderSize)}
/>
)}
</p>
</Td>
<Td className="text-right font-mono">
<FormatNumericValue
value={Math.abs(floorBasePosition) * market._uiPrice}
isUsd
/>
</Td>
<Td className="text-right font-mono">
<FormatNumericValue
value={position.getAverageEntryPriceUi(market)}
decimals={getDecimalCount(market.tickSize)}
isUsd
/>
</Td>
<Td className={`text-right font-mono`}>
<FormatNumericValue
value={unsettledPnl}
decimals={market.baseDecimals}
/>
</Td>
<Td
className={`text-right font-mono ${
cummulativePnl > 0 ? 'text-th-up' : 'text-th-down'
}`}
</span>
{' at '}
<span className="font-mono text-th-fgd-3">
<FormatNumericValue
value={position.getAverageEntryPriceUi(market)}
decimals={getDecimalCount(market.tickSize)}
isUsd
/>
</span>
</p>
</div>
</div>
<div className="flex items-center space-x-3">
<div
className={`text-right font-mono ${
cummulativePnl > 0 ? 'text-th-up' : 'text-th-down'
}`}
>
<FormatNumericValue value={cummulativePnl} isUsd />
</div>
{!isUnownedAccount ? (
<Button
className="text-xs"
secondary
size="small"
onClick={() => showClosePositionModal(position)}
>
<FormatNumericValue value={cummulativePnl} isUsd />
</Td>
<Td className={`text-right`}>
<Button
className="text-xs"
secondary
size="small"
onClick={() => showClosePositionModal(position)}
>
Close
</Button>
</Td>
</TrBody>
)
})}
</tbody>
</Table>
</div>
{showMarketCloseModal && positionToClose ? (
<MarketCloseModal
isOpen={showMarketCloseModal}
onClose={hideClosePositionModal}
position={positionToClose}
/>
) : null}
</>
Close
</Button>
) : null}
</div>
</div>
)
})}
</>
)
) : mangoAccountAddress || connected ? (
<div className="flex flex-col items-center p-8">
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />

View File

@ -16,6 +16,7 @@ import TradeVolumeAlertModal, {
DEFAULT_VOLUME_ALERT_SETTINGS,
} from '@components/modals/TradeVolumeAlertModal'
import dayjs from 'dayjs'
import { PerpMarket } from '@blockworks-foundation/mango-v4'
const volumeAlertSound = new Howl({
src: ['/sounds/trade-buy.mp3'],
@ -46,17 +47,29 @@ const RecentTrades = () => {
useEffect(() => {
if (!fills.length) return
const latesetFill = fills[0]
if (!latestFillId) {
setLatestFillId(fills[0].orderId.toString())
const fillId =
selectedMarket instanceof PerpMarket
? latesetFill.takerClientOrderId
: latesetFill.orderId
setLatestFillId(fillId.toString())
}
}, [fills])
useInterval(() => {
if (!soundSettings['recent-trades'] || !quoteBank) return
setLatestFillId(fills[0].orderId.toString())
const fillsLimitIndex = fills.findIndex(
(f) => f.orderId.toString() === latestFillId
)
if (!soundSettings['recent-trades'] || !quoteBank || !fills.length) return
const latesetFill = fills[0]
const fillId =
selectedMarket instanceof PerpMarket
? latesetFill.takerClientOrderId
: latesetFill.orderId
setLatestFillId(fillId.toString())
const fillsLimitIndex = fills.findIndex((f) => {
const id =
selectedMarket instanceof PerpMarket ? f.takerClientOrderId : f.orderId
return id.toString() === fillId.toString()
})
const newFillsVolumeValue = fills
.slice(0, fillsLimitIndex)
.reduce((a, c) => a + c.size * c.price, 0)
@ -107,7 +120,7 @@ const RecentTrades = () => {
const vol = fills.reduce(
(a: { buys: number; sells: number }, c: any) => {
if (c.side === 'buy' || c.takerSide === 1) {
if (c.side === 'buy' || c.takerSide === 0) {
a.buys = a.buys + c.size
} else {
a.sells = a.sells + c.size
@ -124,7 +137,11 @@ const RecentTrades = () => {
<>
<div className="thin-scroll h-full overflow-y-scroll">
<div className="flex items-center justify-between border-b border-th-bkg-3 py-1 px-2">
<Tooltip content={t('trade:tooltip-volume-alert')} delay={250}>
<Tooltip
className="hidden md:block"
content={t('trade:tooltip-volume-alert')}
delay={250}
>
<IconButton
onClick={() => setShowVolumeAlertModal(true)}
size="small"

View File

@ -340,7 +340,7 @@ const TradeHistory = () => {
<span className="font-mono text-th-fgd-2">
{trade.size}
</span>
{' for '}
{' at '}
<span className="font-mono text-th-fgd-2">
<FormatNumericValue value={trade.price} />
</span>

View File

@ -1,10 +1,11 @@
import { useEffect, useRef, useMemo, useState } from 'react'
import { useEffect, useRef, useMemo, useState, useCallback } from 'react'
import { useTheme } from 'next-themes'
import {
widget,
ChartingLibraryWidgetOptions,
IChartingLibraryWidget,
ResolutionString,
IOrderLineAdapter,
EntityId,
} from '@public/charting_library'
import mangoStore from '@store/mangoStore'
@ -12,15 +13,30 @@ import { useViewport } from 'hooks/useViewport'
import {
CHART_DATA_FEED,
DEFAULT_MARKET_NAME,
SHOW_ORDER_LINES_KEY,
SHOW_STABLE_PRICE_KEY,
} from 'utils/constants'
import { breakpoints } from 'utils/theme'
import { COLORS } from 'styles/colors'
import useLocalStorageState from 'hooks/useLocalStorageState'
import { useTranslation } from 'next-i18next'
import useStablePrice from 'hooks/useStablePrice'
import { notify } from 'utils/notifications'
import {
PerpMarket,
PerpOrder,
PerpOrderType,
Serum3Market,
Serum3OrderType,
Serum3SelfTradeBehavior,
Serum3Side,
} 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 { BN } from '@project-serum/anchor'
import SpotDatafeed from 'apis/birdeye/datafeed'
import PerpDatafeed from 'apis/mngo/datafeed'
import useStablePrice from 'hooks/useStablePrice'
export interface ChartContainerProps {
container: ChartingLibraryWidgetOptions['container']
@ -49,12 +65,17 @@ function hexToRgb(hex: string) {
}
const TradingViewChart = () => {
const { t } = useTranslation('tv-chart')
const { t } = useTranslation(['tv-chart', 'trade'])
const { theme } = useTheme()
const { width } = useViewport()
const [chartReady, setChartReady] = useState(false)
const [spotOrPerp, setSpotOrPerp] = useState('spot')
const [showOrderLinesLocalStorage, toggleShowOrderLinesLocalStorage] =
useLocalStorageState(SHOW_ORDER_LINES_KEY, true)
const [showOrderLines, toggleShowOrderLines] = useState(
showOrderLinesLocalStorage
)
const [showStablePriceLocalStorage, toggleShowStablePriceLocalStorage] =
useLocalStorageState(SHOW_STABLE_PRICE_KEY, false)
const [showStablePrice, toggleShowStablePrice] = useState(
@ -151,6 +172,96 @@ const TradingViewChart = () => {
}
}, [selectedMarket, chartReady])
useEffect(() => {
if (
selectedMarketName?.toLowerCase().includes('perp') &&
spotOrPerp !== 'perp'
) {
setSpotOrPerp('perp')
} else if (
!selectedMarketName?.toLowerCase().includes('perp') &&
spotOrPerp !== 'spot'
) {
setSpotOrPerp('spot')
}
}, [selectedMarketName, spotOrPerp])
useEffect(() => {
if (window) {
const widgetOptions: ChartingLibraryWidgetOptions = {
// debug: true,
symbol: selectedMarket,
// BEWARE: no trailing slash is expected in feed URL
// tslint:disable-next-line:no-any
datafeed: spotOrPerp === 'spot' ? SpotDatafeed : PerpDatafeed,
interval:
defaultProps.interval as ChartingLibraryWidgetOptions['interval'],
container:
defaultProps.container as ChartingLibraryWidgetOptions['container'],
library_path: defaultProps.libraryPath as string,
locale: 'en',
enabled_features: ['hide_left_toolbar_by_default'],
disabled_features: [
'use_localstorage_for_settings',
'timeframes_toolbar',
isMobile ? 'left_toolbar' : '',
'show_logo_on_all_charts',
'caption_buttons_text_if_possible',
'header_settings',
// 'header_chart_type',
'header_compare',
'compare_symbol',
'header_screenshot',
// 'header_widget_dom_node',
// 'header_widget',
'header_saveload',
'header_undo_redo',
'header_interval_dialog_button',
'show_interval_dialog_on_key_press',
'header_symbol_search',
'popup_hints',
],
fullscreen: defaultProps.fullscreen,
autosize: defaultProps.autosize,
studies_overrides: defaultProps.studiesOverrides,
theme:
theme === 'Light' || theme === 'Banana' || theme === 'Lychee'
? 'Light'
: 'Dark',
custom_css_url: '/styles/tradingview.css',
loading_screen: {
backgroundColor: COLORS.BKG1[theme],
},
overrides: {
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
...chartStyleOverrides,
},
}
const tvWidget = new widget(widgetOptions)
tvWidgetRef.current = tvWidget
tvWidgetRef.current.onChartReady(function () {
createOLButton()
createStablePriceButton()
if (showOrderLines) {
const openOrders = mangoStore.getState().mangoAccount.openOrders
deleteLines()
drawLinesForMarket(openOrders)
}
if (showStablePrice && stablePrice) {
const set = mangoStore.getState().set
set((s) => {
s.tradingView.stablePriceLine = drawStablePriceLine(stablePrice)
})
}
setChartReady(true)
})
//eslint-disable-next-line
}
}, [theme, isMobile, defaultProps, spotOrPerp])
const createStablePriceButton = () => {
const button = tvWidgetRef?.current?.createButton()
if (!button) {
@ -242,108 +353,425 @@ const TradingViewChart = () => {
})
}
useEffect(() => {
if (
selectedMarketName?.toLowerCase().includes('perp') &&
spotOrPerp !== 'perp'
) {
setSpotOrPerp('perp')
} else if (
!selectedMarketName?.toLowerCase().includes('perp') &&
spotOrPerp !== 'spot'
) {
setSpotOrPerp('spot')
const createOLButton = () => {
const button = tvWidgetRef?.current?.createButton()
if (!button) {
return
}
}, [selectedMarketName, spotOrPerp])
button.textContent = 'OL'
if (showOrderLinesLocalStorage) {
button.style.color = COLORS.ACTIVE[theme]
} else {
button.style.color = COLORS.FGD4[theme]
}
button.setAttribute('title', t('tv-chart:toggle-order-line'))
button.addEventListener('click', toggleOrderLines)
}
function toggleOrderLines(this: HTMLElement) {
toggleShowOrderLines((prevState: boolean) => !prevState)
if (this.style.color === hexToRgb(COLORS.ACTIVE[theme])) {
deleteLines()
this.style.color = COLORS.FGD4[theme]
} else {
const openOrders = mangoStore.getState().mangoAccount.openOrders
drawLinesForMarket(openOrders)
this.style.color = COLORS.ACTIVE[theme]
}
}
useEffect(() => {
if (window) {
const widgetOptions: ChartingLibraryWidgetOptions = {
// debug: true,
symbol: selectedMarket,
// BEWARE: no trailing slash is expected in feed URL
// tslint:disable-next-line:no-any
datafeed: spotOrPerp === 'spot' ? SpotDatafeed : PerpDatafeed,
interval:
defaultProps.interval as ChartingLibraryWidgetOptions['interval'],
container:
defaultProps.container as ChartingLibraryWidgetOptions['container'],
library_path: defaultProps.libraryPath as string,
locale: 'en',
enabled_features: ['hide_left_toolbar_by_default'],
disabled_features: [
'use_localstorage_for_settings',
'timeframes_toolbar',
isMobile ? 'left_toolbar' : '',
'show_logo_on_all_charts',
'caption_buttons_text_if_possible',
'header_settings',
// 'header_chart_type',
'header_compare',
'compare_symbol',
'header_screenshot',
// 'header_widget_dom_node',
// 'header_widget',
'header_saveload',
'header_undo_redo',
'header_interval_dialog_button',
'show_interval_dialog_on_key_press',
'header_symbol_search',
'popup_hints',
],
fullscreen: defaultProps.fullscreen,
autosize: defaultProps.autosize,
studies_overrides: defaultProps.studiesOverrides,
theme:
theme === 'Light' || theme === 'Banana' || theme === 'Lychee'
? 'Light'
: 'Dark',
custom_css_url: '/styles/tradingview.css',
loading_screen: {
backgroundColor:
theme === 'Dark'
? COLORS.BKG1.Dark
: theme === 'Light'
? COLORS.BKG1.Light
: theme === 'Mango Classic'
? COLORS.BKG1['Mango Classic']
: theme === 'Medium'
? COLORS.BKG1.Medium
: theme === 'Avocado'
? COLORS.BKG1.Avocado
: theme === 'Blueberry'
? COLORS.BKG1.Blueberry
: theme === 'Banana'
? COLORS.BKG1.Banana
: theme === 'Lychee'
? COLORS.BKG1.Lychee
: theme === 'Olive'
? COLORS.BKG1.Olive
: COLORS.BKG1['High Contrast'],
},
overrides: {
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
if (showOrderLines !== showOrderLinesLocalStorage) {
toggleShowOrderLinesLocalStorage(showOrderLines)
}
}, [showOrderLines])
...chartStyleOverrides,
},
}
// update order lines if a user's open orders change
useEffect(() => {
let subscription
if (chartReady && tvWidgetRef?.current) {
subscription = mangoStore.subscribe(
(state) => state.mangoAccount.openOrders,
(openOrders) => {
const orderLines = mangoStore.getState().tradingView.orderLines
tvWidgetRef.current?.onChartReady(() => {
let matchingOrderLines = 0
let openOrdersForMarket = 0
const tvWidget = new widget(widgetOptions)
tvWidgetRef.current = tvWidget
const oOrders = Object.entries(openOrders).map(
([marketPk, orders]) => ({
orders,
marketPk,
})
)
tvWidgetRef.current.onChartReady(function () {
if (showStablePrice && stablePrice) {
const set = mangoStore.getState().set
set((s) => {
s.tradingView.stablePriceLine = drawStablePriceLine(stablePrice)
for (const [key] of orderLines) {
oOrders?.forEach(({ orders }) => {
for (const order of orders) {
if (order.orderId == key) {
matchingOrderLines += 1
}
}
})
}
const selectedMarket = mangoStore.getState().selectedMarket.current
const selectedMarketPk =
selectedMarket instanceof Serum3Market
? selectedMarket?.serumMarketExternal.toString()
: selectedMarket?.publicKey.toString()
oOrders?.forEach(({ marketPk, orders }) => {
if (marketPk === selectedMarketPk) {
openOrdersForMarket = orders.length
}
})
tvWidgetRef.current?.activeChart().dataReady(() => {
if (
(showOrderLines &&
matchingOrderLines !== openOrdersForMarket) ||
orderLines?.size !== matchingOrderLines
) {
deleteLines()
drawLinesForMarket(openOrders)
}
})
})
}
createStablePriceButton()
setChartReady(true)
})
//eslint-disable-next-line
)
}
}, [theme, isMobile, defaultProps, spotOrPerp])
return subscription
}, [chartReady, showOrderLines])
const drawLinesForMarket = (
openOrders: Record<string, Order[] | PerpOrder[]>
) => {
const set = mangoStore.getState().set
const newOrderLines = new Map()
const oOrders = Object.entries(openOrders).map(([marketPk, orders]) => ({
orders,
marketPk,
}))
if (oOrders?.length) {
const selectedMarket = mangoStore.getState().selectedMarket.current
const selectedMarketPk =
selectedMarket instanceof Serum3Market
? selectedMarket?.serumMarketExternal.toString()
: selectedMarket?.publicKey.toString()
for (const { orders, marketPk } of oOrders) {
if (marketPk === selectedMarketPk) {
for (const order of orders) {
newOrderLines.set(order.orderId.toString(), drawLine(order))
}
}
}
}
set((state) => {
state.tradingView.orderLines = newOrderLines
})
}
const deleteLines = () => {
const set = mangoStore.getState().set
const orderLines = mangoStore.getState().tradingView.orderLines
if (orderLines.size > 0) {
orderLines?.forEach((value: IOrderLineAdapter, key: string | BN) => {
orderLines.get(key)?.remove()
})
set((state) => {
state.tradingView.orderLines = new Map()
})
}
}
function getOrderDecimals() {
const selectedMarket = mangoStore.getState().selectedMarket.current
let minOrderDecimals = 4
let tickSizeDecimals = 2
if (!selectedMarket) return [minOrderDecimals, tickSizeDecimals]
if (selectedMarket instanceof PerpMarket) {
minOrderDecimals = getDecimalCount(selectedMarket.minOrderSize)
tickSizeDecimals = getDecimalCount(selectedMarket.tickSize)
} else {
const group = mangoStore.getState().group
const market = group?.getSerum3ExternalMarket(
selectedMarket.serumMarketExternal
)
if (market) {
minOrderDecimals = getDecimalCount(market.minOrderSize)
tickSizeDecimals = getDecimalCount(market.tickSize)
}
}
return [minOrderDecimals, tickSizeDecimals]
}
function drawLine(order: Order | PerpOrder) {
const side =
typeof order.side === 'string'
? t(order.side)
: 'bid' in order.side
? t('trade:long')
: t('trade:short')
const isLong = side === 'buy' || side === 'long'
const isShort = side === 'sell' || side === 'short'
const [minOrderDecimals, tickSizeDecimals] = getOrderDecimals()
const orderSizeUi: string = formatNumericValue(order.size, minOrderDecimals)
if (!tvWidgetRef?.current?.chart()) return
return (
tvWidgetRef.current
.chart()
.createOrderLine({ disableUndo: false })
.onMove(function (this: IOrderLineAdapter) {
const currentOrderPrice = order.price
const updatedOrderPrice = this.getPrice()
const selectedMarketPrice =
mangoStore.getState().selectedMarket.markPrice
if (
(isLong && updatedOrderPrice > 1.05 * selectedMarketPrice) ||
(isShort && updatedOrderPrice < 0.95 * selectedMarketPrice)
) {
tvWidgetRef.current?.showNoticeDialog({
title: t('tv-chart:outside-range'),
body:
t('tv-chart:slippage-warning', {
updatedOrderPrice: updatedOrderPrice,
aboveBelow:
side == 'buy' || side === 'long' ? t('above') : t('below'),
selectedMarketPrice: selectedMarketPrice,
}) +
'<p><p>' +
t('tv-chart:slippage-accept'),
callback: () => {
this.setPrice(currentOrderPrice)
},
})
} else {
tvWidgetRef.current?.showConfirmDialog({
title: t('tv-chart:modify-order'),
body: t('tv-chart:modify-order-details', {
marketName: selectedMarketName,
orderSize: orderSizeUi,
orderSide: side.toUpperCase(),
currentOrderPrice: formatNumericValue(
currentOrderPrice,
tickSizeDecimals
),
updatedOrderPrice: formatNumericValue(
updatedOrderPrice,
tickSizeDecimals
),
}),
callback: (res) => {
if (res) {
modifyOrder(order, updatedOrderPrice)
} else {
this.setPrice(currentOrderPrice)
}
},
})
}
})
.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(order.price, tickSizeDecimals),
}),
callback: (res) => {
if (res) {
if (order instanceof PerpOrder) {
cancelPerpOrder(order)
} else {
cancelSpotOrder(order)
}
}
},
})
})
.setPrice(order.price)
.setQuantity(orderSizeUi)
.setText(side.toUpperCase())
// .setTooltip(
// order.perpTrigger?.clientOrderId
// ? `${order.orderType} Order #: ${order.orderId}`
// : `Order #: ${order.orderId}`
// )
.setBodyTextColor(isLong ? COLORS.UP[theme] : COLORS.DOWN[theme])
.setQuantityTextColor(isLong ? COLORS.UP[theme] : COLORS.DOWN[theme])
.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]
)
.setBodyBackgroundColor(COLORS.BKG1[theme])
.setQuantityBackgroundColor(COLORS.BKG1[theme])
.setCancelButtonBackgroundColor(COLORS.BKG1[theme])
.setLineColor(isLong ? COLORS.UP[theme] : COLORS.DOWN[theme])
.setLineLength(3)
.setLineWidth(1)
.setLineStyle(1)
)
}
const modifyOrder = useCallback(
async (o: PerpOrder | Order, price: number) => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const mangoAccount = mangoStore.getState().mangoAccount.current
const actions = mangoStore.getState().actions
const baseSize = o.size
if (!group || !mangoAccount) return
try {
let tx = ''
if (o instanceof PerpOrder) {
tx = await client.modifyPerpOrder(
group,
mangoAccount,
o.perpMarketIndex,
o.orderId,
o.side,
price,
Math.abs(baseSize),
undefined, // maxQuoteQuantity
Date.now(),
PerpOrderType.limit,
undefined,
undefined
)
} else {
const marketPk = findSerum3MarketPkInOpenOrders(o)
if (!marketPk) return
const market = group.getSerum3MarketByExternalMarket(
new PublicKey(marketPk)
)
tx = await client.modifySerum3Order(
group,
o.orderId,
mangoAccount,
market.serumMarketExternal,
o.side === 'buy' ? Serum3Side.bid : Serum3Side.ask,
price,
baseSize,
Serum3SelfTradeBehavior.decrementTake,
Serum3OrderType.limit,
Date.now(),
10
)
}
actions.fetchOpenOrders()
notify({
type: 'success',
title: 'Transaction successful',
txid: tx,
})
} catch (e: any) {
console.error('Error canceling', e)
notify({
title: 'Unable to modify order',
description: e.message,
txid: e.txid,
type: 'error',
})
}
},
[t]
)
const cancelSpotOrder = useCallback(
async (o: Order) => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const mangoAccount = mangoStore.getState().mangoAccount.current
const actions = mangoStore.getState().actions
if (!group || !mangoAccount) return
const marketPk = findSerum3MarketPkInOpenOrders(o)
if (!marketPk) return
const market = group.getSerum3MarketByExternalMarket(
new PublicKey(marketPk)
)
try {
const tx = await client.serum3CancelOrder(
group,
mangoAccount,
market!.serumMarketExternal,
o.side === 'buy' ? Serum3Side.bid : Serum3Side.ask,
o.orderId
)
actions.fetchOpenOrders()
notify({
type: 'success',
title: 'Transaction successful',
txid: tx,
})
} catch (e: any) {
console.error('Error canceling', e)
notify({
title: t('trade:cancel-order-error'),
description: e.message,
txid: e.txid,
type: 'error',
})
}
},
[t]
)
const cancelPerpOrder = useCallback(
async (o: PerpOrder) => {
const client = mangoStore.getState().client
const group = mangoStore.getState().group
const mangoAccount = mangoStore.getState().mangoAccount.current
const actions = mangoStore.getState().actions
if (!group || !mangoAccount) return
try {
const tx = await client.perpCancelOrder(
group,
mangoAccount,
o.perpMarketIndex,
o.orderId
)
actions.fetchOpenOrders()
notify({
type: 'success',
title: 'Transaction successful',
txid: tx,
})
} catch (e: any) {
console.error('Error canceling', e)
notify({
title: t('trade:cancel-order-error'),
description: e.message,
txid: e.txid,
type: 'error',
})
}
},
[t]
)
const findSerum3MarketPkInOpenOrders = (o: Order): string | undefined => {
const openOrders = mangoStore.getState().mangoAccount.openOrders
let foundedMarketPk: string | undefined = undefined
for (const [marketPk, orders] of Object.entries(openOrders)) {
for (const order of orders) {
if (order.orderId.eq(o.orderId)) {
foundedMarketPk = marketPk
break
}
}
if (foundedMarketPk) {
break
}
}
return foundedMarketPk
}
return (
<div id={defaultProps.container as string} className="tradingview-chart" />

View File

@ -17,6 +17,7 @@ import useMangoAccount from 'hooks/useMangoAccount'
import { useWallet } from '@solana/wallet-adapter-react'
import ConnectEmptyState from '@components/shared/ConnectEmptyState'
import FormatNumericValue from '@components/shared/FormatNumericValue'
import useUnownedAccount from 'hooks/useUnownedAccount'
const UnsettledTrades = ({
unsettledSpotBalances,
@ -32,6 +33,7 @@ const UnsettledTrades = ({
const showTableView = width ? width > breakpoints.md : false
const { mangoAccountAddress } = useMangoAccount()
const { connected } = useWallet()
const isUnownedAccount = useUnownedAccount()
const handleSettleSerumFunds = useCallback(async (mktAddress: string) => {
const client = mangoStore.getState().client
@ -136,7 +138,9 @@ const UnsettledTrades = ({
<TrHead>
<Th className="bg-th-bkg-1 text-left">{t('market')}</Th>
<Th className="bg-th-bkg-1 text-right">{t('trade:amount')}</Th>
<Th className="bg-th-bkg-1 text-right" />
{!isUnownedAccount ? (
<Th className="bg-th-bkg-1 text-right" />
) : null}
</TrHead>
</thead>
<tbody>
@ -156,34 +160,40 @@ const UnsettledTrades = ({
<div className="flex justify-end">
{unsettledSpotBalances[mktAddress].base ? (
<div>
{unsettledSpotBalances[mktAddress].base}{' '}
<FormatNumericValue
value={unsettledSpotBalances[mktAddress].base}
/>{' '}
<span className="font-body text-th-fgd-4">{base}</span>
</div>
) : null}
{unsettledSpotBalances[mktAddress].quote ? (
<div className="ml-4">
{unsettledSpotBalances[mktAddress].quote}{' '}
<FormatNumericValue
value={unsettledSpotBalances[mktAddress].quote}
/>{' '}
<span className="font-body text-th-fgd-4">{quote}</span>
</div>
) : null}
</div>
</Td>
<Td>
<div className="flex justify-end">
<Tooltip content={t('trade:settle-funds')}>
<IconButton
onClick={() => handleSettleSerumFunds(mktAddress)}
size="small"
>
{settleMktAddress === mktAddress ? (
<Loading className="h-4 w-4" />
) : (
<CheckIcon className="h-4 w-4" />
)}
</IconButton>
</Tooltip>
</div>
</Td>
{!isUnownedAccount ? (
<Td>
<div className="flex justify-end">
<Tooltip content={t('trade:settle-funds')}>
<IconButton
onClick={() => handleSettleSerumFunds(mktAddress)}
size="small"
>
{settleMktAddress === mktAddress ? (
<Loading className="h-4 w-4" />
) : (
<CheckIcon className="h-4 w-4" />
)}
</IconButton>
</Tooltip>
</div>
</Td>
) : null}
</TrBody>
)
})}
@ -203,29 +213,64 @@ const UnsettledTrades = ({
/>{' '}
<span className="font-body text-th-fgd-4">USDC</span>
</Td>
<Td>
<div className="flex justify-end">
<Tooltip content={t('trade:settle-funds')}>
<IconButton
onClick={() => handleSettlePerpFunds(market)}
size="small"
>
{settleMktAddress === market.publicKey.toString() ? (
<Loading className="h-4 w-4" />
) : (
<CheckIcon className="h-4 w-4" />
)}
</IconButton>
</Tooltip>
</div>
</Td>
{!isUnownedAccount ? (
<Td>
<div className="flex justify-end">
<Tooltip content={t('trade:settle-funds')}>
<IconButton
onClick={() => handleSettlePerpFunds(market)}
size="small"
>
{settleMktAddress === market.publicKey.toString() ? (
<Loading className="h-4 w-4" />
) : (
<CheckIcon className="h-4 w-4" />
)}
</IconButton>
</Tooltip>
</div>
</Td>
) : null}
</TrBody>
)
})}
</tbody>
</Table>
) : (
<div className="pb-20">
<div>
{unsettledPerpPositions.map((position) => {
const market = group.getPerpMarketByMarketIndex(position.marketIndex)
return (
<div
key={position.marketIndex}
className="flex items-center justify-between border-b border-th-bkg-3 p-4"
>
<TableMarketName market={market} />
<div className="flex items-center space-x-3">
<div>
<FormatNumericValue
value={position.getUnsettledPnlUi(market)}
decimals={market.baseDecimals}
/>{' '}
<span className="font-body text-th-fgd-4">USDC</span>
</div>
{!isUnownedAccount ? (
<IconButton
onClick={() => handleSettlePerpFunds(market)}
size="medium"
>
{settleMktAddress === market.publicKey.toString() ? (
<Loading className="h-4 w-4" />
) : (
<CheckIcon className="h-4 w-4" />
)}
</IconButton>
) : null}
</div>
</div>
)
})}
{Object.entries(unsettledSpotBalances).map(([mktAddress]) => {
const market = group.getSerum3MarketByExternalMarket(
new PublicKey(mktAddress)
@ -242,26 +287,32 @@ const UnsettledTrades = ({
<div className="flex items-center space-x-3">
{unsettledSpotBalances[mktAddress].base ? (
<span className="font-mono text-sm">
{unsettledSpotBalances[mktAddress].base}{' '}
<FormatNumericValue
value={unsettledSpotBalances[mktAddress].base}
/>{' '}
<span className="font-body text-th-fgd-4">{base}</span>
</span>
) : null}
{unsettledSpotBalances[mktAddress].quote ? (
<span className="font-mono text-sm">
{unsettledSpotBalances[mktAddress].quote}{' '}
<FormatNumericValue
value={unsettledSpotBalances[mktAddress].quote}
/>{' '}
<span className="font-body text-th-fgd-4">{quote}</span>
</span>
) : null}
<IconButton
onClick={() => handleSettleSerumFunds(mktAddress)}
size="medium"
>
{settleMktAddress === mktAddress ? (
<Loading className="h-4 w-4" />
) : (
<CheckIcon className="h-4 w-4" />
)}
</IconButton>
{!isUnownedAccount ? (
<IconButton
onClick={() => handleSettleSerumFunds(mktAddress)}
size="medium"
>
{settleMktAddress === mktAddress ? (
<Loading className="h-4 w-4" />
) : (
<CheckIcon className="h-4 w-4" />
)}
</IconButton>
) : null}
</div>
</div>
)

View File

@ -27,7 +27,7 @@ export const ConnectWalletButton: React.FC = () => {
disabled={!groupLoaded}
className={` text-white focus:outline-none disabled:cursor-wait disabled:opacity-25`}
>
<div className="relative flex h-16 w-44 bg-th-bkg-2 py-2 before:absolute before:inset-0 before:bg-gradient-to-r before:from-transparent before:via-th-bkg-4 before:to-transparent before:opacity-0 hover:overflow-hidden hover:before:-translate-x-full hover:before:animate-[shimmer_0.75s_normal] hover:before:opacity-100">
<div className="relative flex h-16 w-44 bg-th-bkg-3 py-2 before:absolute before:inset-0 before:bg-gradient-to-r before:from-transparent before:via-th-bkg-4 before:to-transparent before:opacity-0 hover:overflow-hidden hover:before:-translate-x-full hover:before:animate-[shimmer_0.75s_normal] hover:before:opacity-100">
<div className="default-transition relative z-10 flex h-full items-center justify-center space-x-3 px-4">
{connecting ? (
<Loading className="h-[28px] w-[28px]" />

View File

@ -0,0 +1,17 @@
import { useWallet } from '@solana/wallet-adapter-react'
import { useMemo } from 'react'
import useMangoAccount from './useMangoAccount'
const useUnownedAccount = () => {
const { connected } = useWallet()
const { mangoAccountAddress } = useMangoAccount()
const isUnownedAccount = useMemo(() => {
if (connected) return false
return mangoAccountAddress && !connected
}, [connected, mangoAccountAddress])
return isUnownedAccount
}
export default useUnownedAccount

View File

@ -230,13 +230,6 @@ const Dashboard: NextPage = () => {
label="Init Asset/Liab Weight"
value={`${bank.initAssetWeight.toFixed(2)}/
${bank.initLiabWeight.toFixed(2)}`}
/>
<KeyValuePair
label="Scaled Init Asset/Liab Weight"
value={`${bank
.scaledInitAssetWeight()
.toFixed(4)}/
${bank.scaledInitLiabWeight().toFixed(4)}`}
/>
<KeyValuePair
label="Deposit weight scale start quote"

View File

@ -1,13 +1,15 @@
import ExplorerLink from '@components/shared/ExplorerLink'
import useMangoGroup from 'hooks/useMangoGroup'
import type { NextPage } from 'next'
import { ReactNode } from 'react'
import { ReactNode, useCallback, useEffect, useState } from 'react'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import useMangoAccount from 'hooks/useMangoAccount'
import {
toUiDecimalsForQuote,
HealthType,
PerpOrder,
} from '@blockworks-foundation/mango-v4'
import mangoStore from '@store/mangoStore'
export async function getStaticProps({ locale }: { locale: string }) {
return {
@ -22,6 +24,29 @@ const Dashboard: NextPage = () => {
// const { mangoTokens } = useJupiterMints()
// const client = mangoStore(s => s.client)
const { mangoAccount } = useMangoAccount()
const client = mangoStore((s) => s.client)
const [openOrders, setOpenOrders] = useState<Record<string, PerpOrder[]>>()
useEffect(() => {
if (mangoAccount) {
loadOpenOrders()
}
}, [mangoAccount])
const loadOpenOrders = useCallback(async () => {
if (!mangoAccount || !group) return
const openOrders: Record<string, any> = {}
for (const perpOrder of mangoAccount.perpOrdersActive()) {
const market = group.getPerpMarketByMarketIndex(perpOrder.orderMarket)
const orders = await mangoAccount.loadPerpOpenOrdersForMarket(
client,
group,
perpOrder.orderMarket
)
openOrders[market.publicKey.toString()] = orders
}
setOpenOrders(openOrders)
}, [mangoAccount, client, group])
return (
<div className="grid grid-cols-12">
@ -222,6 +247,49 @@ const Dashboard: NextPage = () => {
</div>
)
})}
<h3 className="mt-4">Perp Open Orders</h3>
{openOrders
? Object.entries(openOrders).map(
([marketAddress, openOrders]) => {
return (
<div key={marketAddress} className="mt-4">
<KeyValuePair
label="Market Address"
value={<ExplorerLink address={marketAddress} />}
/>
{openOrders.map((openOrder) => {
return (
<div
key={`${openOrder.orderId}${openOrder.side}${openOrder.seqNum}`}
className="mt-4 rounded border border-th-bkg-3 px-2"
>
<KeyValuePair
label="Side"
value={
openOrder.side
? 'bid' in openOrder.side
? 'Bid'
: 'Ask'
: null
}
/>
<KeyValuePair
label="Price"
value={openOrder.price}
/>
<KeyValuePair
label="Size"
value={openOrder.size}
/>
</div>
)
})}
</div>
)
}
)
: null}
</div>
) : (
'Loading'

View File

@ -14,5 +14,6 @@
"settle-balances": "Settle all balances",
"transaction-confirmed": "Transaction Confirmed",
"withdraw-assets-worth": "Withdraw assets worth {{value}}",
"you-must": "Before you can close your account"
"you-must": "Before you can close your account",
"account-closed": "Account Closed 👋"
}

View File

@ -22,6 +22,7 @@
"instantaneous-funding": "Instantaneous Funding",
"interval-seconds": "Interval (seconds)",
"limit-price": "Limit Price",
"long": "Long",
"margin": "Margin",
"no-balances": "No balances",
"no-orders": "No open orders",
@ -46,6 +47,7 @@
"sells": "Sells",
"settle-funds": "Settle Funds",
"settle-funds-error": "Failed to settle funds",
"short": "Short",
"show-asks": "Show Asks",
"show-bids": "Show Bids",
"side": "Side",

View File

@ -1,14 +1,15 @@
{
"advanced-order": "Advanced Order Type",
"advanced-order-details": "Advanced order types in the chart window may only be cancelled. If new conditions are required, please cancel this order and use the Advanced Trade Form.",
"cancel-order": "Cancel Your Order?",
"cancel-order-details": "Would you like to cancel your order for {{orderSize}} {{baseSymbol}} {{orderSide}} at ${{orderPrice}}?",
"modify-order": "Modify Your Order?",
"modify-order-details": "Would you like to change your order from a {{orderSize}} {{baseSymbol}} {{orderSide}} at ${{currentOrderPrice}} to a {{orderSize}} {{baseSymbol}} LIMIT {{orderSide}} at ${{updatedOrderPrice}}?",
"advanced-order-details": "Advanced order types in the chart window may only be cancelled. If new conditions are required, cancel this order and use the trade order form.",
"cancel-order": "Cancel Order",
"cancel-order-details": "Cancel your order for {{orderSide}} {{orderSize}} {{marketName}} at {{orderPrice}}",
"modify-order": "Modify Order",
"modify-order-details": "Edit your {{marketName}} order from {{orderSide}} {{orderSize}} at {{currentOrderPrice}} to {{orderSide}} {{orderSize}} at {{updatedOrderPrice}}",
"order-details": " ({{orderType}} {{orderSide}}) if price is {{triggerCondition}} {{triggerPrice}}",
"outside-range": "Order Price Outside Range",
"slippage-accept": "Please use the trade input form if you wish to accept the potential slippage.",
"slippage-warning": "Your order price ({{updatedOrderPrice}}) is greater than 5% {{aboveBelow}} the current market price ({{selectedMarketPrice}}) indicating you might incur significant slippage.",
"toggle-stable-price": "Toggle stable price line",
"slippage-accept": "Use the trade order form if you wish to accept the potential slippage.",
"slippage-warning": "{{updatedOrderPrice}} is greater than 5% {{aboveBelow}} the current market price of {{selectedMarketPrice}}. Executing this trade could incur significant slippage.",
"toggle-order-line": "Toggle order line visibility",
"toggle-stable-price": "Toggle stable price line"
"toggle-trade-executions": "Toggle trade execution visibility"
}

View File

@ -14,5 +14,6 @@
"settle-balances": "Settle all balances",
"transaction-confirmed": "Transaction Confirmed",
"withdraw-assets-worth": "Withdraw assets worth {{value}}",
"you-must": "Before you can close your account"
"you-must": "Before you can close your account",
"account-closed": "Account Closed 👋"
}

View File

@ -22,6 +22,7 @@
"instantaneous-funding": "Instantaneous Funding",
"interval-seconds": "Interval (seconds)",
"limit-price": "Limit Price",
"long": "Long",
"margin": "Margin",
"no-balances": "No balances",
"no-orders": "No open orders",
@ -46,6 +47,7 @@
"sells": "Sells",
"settle-funds": "Settle Funds",
"settle-funds-error": "Failed to settle funds",
"short": "Short",
"show-asks": "Show Asks",
"show-bids": "Show Bids",
"side": "Side",

View File

@ -1,14 +1,15 @@
{
"advanced-order": "Advanced Order Type",
"advanced-order-details": "Advanced order types in the chart window may only be cancelled. If new conditions are required, please cancel this order and use the Advanced Trade Form.",
"cancel-order": "Cancel Your Order?",
"cancel-order-details": "Would you like to cancel your order for {{orderSize}} {{baseSymbol}} {{orderSide}} at ${{orderPrice}}?",
"modify-order": "Modify Your Order?",
"modify-order-details": "Would you like to change your order from a {{orderSize}} {{baseSymbol}} {{orderSide}} at ${{currentOrderPrice}} to a {{orderSize}} {{baseSymbol}} LIMIT {{orderSide}} at ${{updatedOrderPrice}}?",
"advanced-order-details": "Advanced order types in the chart window may only be cancelled. If new conditions are required, cancel this order and use the trade order form.",
"cancel-order": "Cancel Order",
"cancel-order-details": "Cancel your order for {{orderSide}} {{orderSize}} {{marketName}} at {{orderPrice}}",
"modify-order": "Modify Order",
"modify-order-details": "Edit your {{marketName}} order from {{orderSide}} {{orderSize}} at {{currentOrderPrice}} to {{orderSide}} {{orderSize}} at {{updatedOrderPrice}}",
"order-details": " ({{orderType}} {{orderSide}}) if price is {{triggerCondition}} {{triggerPrice}}",
"outside-range": "Order Price Outside Range",
"slippage-accept": "Please use the trade input form if you wish to accept the potential slippage.",
"slippage-warning": "Your order price ({{updatedOrderPrice}}) is greater than 5% {{aboveBelow}} the current market price ({{selectedMarketPrice}}) indicating you might incur significant slippage.",
"toggle-stable-price": "Toggle stable price line",
"slippage-accept": "Use the trade order form if you wish to accept the potential slippage.",
"slippage-warning": "{{updatedOrderPrice}} is greater than 5% {{aboveBelow}} the current market price of {{selectedMarketPrice}}. Executing this trade could incur significant slippage.",
"toggle-order-line": "Toggle order line visibility",
"toggle-stable-price": "Toggle stable price line"
"toggle-trade-executions": "Toggle trade execution visibility"
}

View File

@ -14,5 +14,6 @@
"settle-balances": "Settle all balances",
"transaction-confirmed": "Transaction Confirmed",
"withdraw-assets-worth": "Withdraw assets worth {{value}}",
"you-must": "Before you can close your account"
"you-must": "Before you can close your account",
"account-closed": "Account Closed 👋"
}

View File

@ -22,6 +22,7 @@
"instantaneous-funding": "Instantaneous Funding",
"interval-seconds": "Interval (seconds)",
"limit-price": "Limit Price",
"long": "Long",
"margin": "Margin",
"no-balances": "No balances",
"no-orders": "No open orders",
@ -46,6 +47,7 @@
"sells": "Sells",
"settle-funds": "Settle Funds",
"settle-funds-error": "Failed to settle funds",
"short": "Short",
"show-asks": "Show Asks",
"show-bids": "Show Bids",
"side": "Side",

View File

@ -1,14 +1,15 @@
{
"advanced-order": "高級訂單類型",
"advanced-order-details": "在圖表窗口中高級訂單類型只能取消。如果需要新條件,請取消此訂單並使用高級交易表格進行。",
"cancel-order": "取消訂單嗎?",
"cancel-order-details": "您確定要取消{{orderSize}} {{baseSymbol}} {{orderSide}} 價格${{orderPrice}}的掛單嗎?",
"modify-order": "改您的訂單嗎?",
"modify-order-details": "您確定要把{{orderSize}} {{baseSymbol}}{{orderSide}} 價格${{currentOrderPrice}}的掛單改成{{orderSize}} {{baseSymbol}}限價{{orderSide}} 價格${{updatedOrderPrice}}嗎?",
"order-details": "({{orderType}}{{orderSide}})若價格{{triggerCondition}}{{triggerPrice}}",
"outside-range": "訂單價格在範圍之外",
"slippage-accept": "若您接受潛在的下滑請使用交易表格進行。",
"slippage-warning": "您的訂單價格({{updatedOrderPrice}})多餘5%{{aboveBelow}}市場價格({{selectedMarketPrice}})表是您也許遭受可觀的下滑。",
"toggle-order-line": "切換訂單線可見性",
"toggle-stable-price": "Toggle stable price line"
"advanced-order": "Advanced Order Type",
"advanced-order-details": "Advanced order types in the chart window may only be cancelled. If new conditions are required, cancel this order and use the trade order form.",
"cancel-order": "Cancel Order",
"cancel-order-details": "Cancel your order for {{orderSide}} {{orderSize}} {{marketName}} at {{orderPrice}}",
"modify-order": "Modify Order",
"modify-order-details": "Edit your {{marketName}} order from {{orderSide}} {{orderSize}} at {{currentOrderPrice}} to {{orderSide}} {{orderSize}} at {{updatedOrderPrice}}",
"order-details": " ({{orderType}} {{orderSide}}) if price is {{triggerCondition}} {{triggerPrice}}",
"outside-range": "Order Price Outside Range",
"toggle-stable-price": "Toggle stable price line",
"slippage-accept": "Use the trade order form if you wish to accept the potential slippage.",
"slippage-warning": "{{updatedOrderPrice}} is greater than 5% {{aboveBelow}} the current market price of {{selectedMarketPrice}}. Executing this trade could incur significant slippage.",
"toggle-order-line": "Toggle order line visibility",
"toggle-trade-executions": "Toggle trade execution visibility"
}

View File

@ -14,5 +14,6 @@
"settle-balances": "Settle all balances",
"transaction-confirmed": "Transaction Confirmed",
"withdraw-assets-worth": "Withdraw assets worth {{value}}",
"you-must": "Before you can close your account"
"you-must": "Before you can close your account",
"account-closed": "Account Closed 👋"
}

View File

@ -22,6 +22,7 @@
"instantaneous-funding": "Instantaneous Funding",
"interval-seconds": "Interval (seconds)",
"limit-price": "Limit Price",
"long": "Long",
"margin": "Margin",
"no-balances": "No balances",
"no-orders": "No open orders",
@ -45,6 +46,7 @@
"sells": "Sells",
"settle-funds": "Settle Funds",
"settle-funds-error": "Failed to settle funds",
"short": "Short",
"show-asks": "Show Asks",
"show-bids": "Show Bids",
"side": "Side",

View File

@ -10,5 +10,6 @@
"slippage-accept": "若您接受潜在的下滑请使用交易表格进行。",
"slippage-warning": "您的订单价格({{updatedOrderPrice}})多余5%{{aboveBelow}}市场价格({{selectedMarketPrice}})表是您也许遭受可观的下滑。",
"toggle-order-line": "切换订单线可见性",
"toggle-stable-price": "Toggle stable price line"
"toggle-stable-price": "Toggle stable price line",
"toggle-trade-executions": "Toggle trade execution visibility"
}

View File

@ -14,5 +14,6 @@
"settle-balances": "Settle all balances",
"transaction-confirmed": "Transaction Confirmed",
"withdraw-assets-worth": "Withdraw assets worth {{value}}",
"you-must": "Before you can close your account"
"you-must": "Before you can close your account",
"account-closed": "Account Closed 👋"
}

View File

@ -22,6 +22,7 @@
"instantaneous-funding": "Instantaneous Funding",
"interval-seconds": "Interval (seconds)",
"limit-price": "Limit Price",
"long": "Long",
"margin": "Margin",
"no-balances": "No balances",
"no-orders": "No open orders",
@ -45,6 +46,7 @@
"sells": "Sells",
"settle-funds": "Settle Funds",
"settle-funds-error": "Failed to settle funds",
"short": "Short",
"show-asks": "Show Asks",
"show-bids": "Show Bids",
"side": "Side",

View File

@ -10,5 +10,6 @@
"slippage-accept": "若您接受潛在的下滑請使用交易表格進行。",
"slippage-warning": "您的訂單價格({{updatedOrderPrice}})多餘5%{{aboveBelow}}市場價格({{selectedMarketPrice}})表是您也許遭受可觀的下滑。",
"toggle-order-line": "切換訂單線可見性",
"toggle-stable-price": "Toggle stable price line"
"toggle-stable-price": "Toggle stable price line",
"toggle-trade-executions": "Toggle trade execution visibility"
}

View File

@ -2,7 +2,7 @@ import dayjs from 'dayjs'
import produce from 'immer'
import create from 'zustand'
import { subscribeWithSelector } from 'zustand/middleware'
import { AnchorProvider, Wallet, web3 } from '@project-serum/anchor'
import { AnchorProvider, BN, Wallet, web3 } from '@project-serum/anchor'
import { Connection, Keypair, PublicKey } from '@solana/web3.js'
import { OpenOrders, Order } from '@project-serum/serum/lib/market'
import { Orderbook } from '@project-serum/serum'
@ -49,7 +49,10 @@ import spotBalancesUpdater from './spotBalancesUpdater'
import { PerpMarket } from '@blockworks-foundation/mango-v4/'
import perpPositionsUpdater from './perpPositionsUpdater'
import { DEFAULT_PRIORITY_FEE } from '@components/settings/RpcSettings'
import { EntityId } from '@public/charting_library/charting_library'
import {
EntityId,
IOrderLineAdapter,
} from '@public/charting_library/charting_library'
const GROUP = new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX')
@ -95,6 +98,8 @@ const initMangoClient = (
})
}
let mangoGroupRetryAttempt = 0
export interface TotalInterestDataItem {
borrow_interest: number
deposit_interest: number
@ -332,6 +337,7 @@ export type MangoStore = {
tradeForm: TradeForm
tradingView: {
stablePriceLine: Map<string, EntityId> | undefined
orderLines: Map<string | BN, IOrderLineAdapter>
}
wallet: {
tokens: TokenAccount[]
@ -479,6 +485,7 @@ const mangoStore = create<MangoStore>()(
tradeForm: DEFAULT_TRADE_FORM,
tradingView: {
stablePriceLine: new Map(),
orderLines: new Map(),
},
wallet: {
tokens: [],
@ -603,7 +610,7 @@ const mangoStore = create<MangoStore>()(
state.activityFeed.feed = combinedFeed
})
} catch (e) {
console.log('Failed to fetch account activity feed', e)
console.error('Failed to fetch account activity feed', e)
} finally {
set((state) => {
state.activityFeed.loading = false
@ -656,9 +663,15 @@ const mangoStore = create<MangoStore>()(
)
}
})
mangoGroupRetryAttempt = 0
} catch (e) {
notify({ type: 'info', title: 'Unable to refresh data' })
console.error('Error fetching group', e)
if (mangoGroupRetryAttempt < 2) {
// get().actions.fetchGroup()
mangoGroupRetryAttempt++
} else {
notify({ type: 'info', title: 'Unable to refresh data' })
console.error('Error fetching group', e)
}
}
},
reloadMangoAccount: async () => {
@ -813,8 +826,10 @@ const mangoStore = create<MangoStore>()(
mangoAccount.serum3Active().length &&
Object.keys(openOrders).length
) {
serumOpenOrderAccounts =
await mangoAccount.loadSerum3OpenOrdersAccounts(client)
serumOpenOrderAccounts = Array.from(
mangoAccount.serum3OosMapByMarketIndex.values()
)
await mangoAccount.loadSerum3OpenOrdersAccounts(client)
}
for (const perpOrder of mangoAccount.perpOrdersActive()) {

View File

@ -47,6 +47,8 @@ export const PRIORITY_FEE_KEY = 'priorityFeeKey-0.1'
export const SHOW_STABLE_PRICE_KEY = 'showStablePriceKey-0.1'
export const SHOW_ORDER_LINES_KEY = 'showOrderLines-0.1'
// Unused
export const PROFILE_CATEGORIES = [
'borrower',

313
yarn.lock
View File

@ -24,12 +24,12 @@
"@blockworks-foundation/mango-v4@https://github.com/blockworks-foundation/mango-v4.git#ts-client":
version "0.4.3"
resolved "https://github.com/blockworks-foundation/mango-v4.git#35763da947e3b15175dcee5c81633e409803b2f7"
resolved "https://github.com/blockworks-foundation/mango-v4.git#c0af34311455de5685387a80812a938215025a6f"
dependencies:
"@project-serum/anchor" "^0.25.0"
"@project-serum/serum" "^0.13.65"
"@pythnetwork/client" "~2.8.0"
"@solana/spl-token" "0.2.0"
"@pythnetwork/client" "~2.14.0"
"@solana/spl-token" "0.3.7"
"@solana/web3.js" "^1.63.1"
"@switchboard-xyz/sbv2-lite" "^0.1.6"
big.js "^6.1.1"
@ -37,7 +37,6 @@
bs58 "^5.0.0"
cross-fetch "^3.1.5"
dotenv "^16.0.3"
ftx-api "^1.1.13"
node-kraken-api "^2.2.2"
"@blocto/sdk@^0.2.21":
@ -50,6 +49,35 @@
eip1193-provider "^1.0.1"
js-sha3 "^0.8.0"
"@coral-xyz/anchor@^0.26.0":
version "0.26.0"
resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.26.0.tgz#c8e4f7177e93441afd030f22d777d54d0194d7d1"
integrity sha512-PxRl+wu5YyptWiR9F2MBHOLLibm87Z4IMUBPreX+DYBtPM+xggvcPi0KAN7+kIL4IrIhXI8ma5V0MCXxSN1pHg==
dependencies:
"@coral-xyz/borsh" "^0.26.0"
"@solana/web3.js" "^1.68.0"
base64-js "^1.5.1"
bn.js "^5.1.2"
bs58 "^4.0.1"
buffer-layout "^1.2.2"
camelcase "^6.3.0"
cross-fetch "^3.1.5"
crypto-hash "^1.3.0"
eventemitter3 "^4.0.7"
js-sha256 "^0.9.0"
pako "^2.0.3"
snake-case "^3.0.4"
superstruct "^0.15.4"
toml "^3.0.0"
"@coral-xyz/borsh@^0.26.0":
version "0.26.0"
resolved "https://registry.yarnpkg.com/@coral-xyz/borsh/-/borsh-0.26.0.tgz#d054f64536d824634969e74138f9f7c52bbbc0d5"
integrity sha512-uCZ0xus0CszQPHYfWAqKS5swS1UxvePu83oOF+TWpUkedsNlg6p2p4azxZNSSqwXb9uXMFgxhuMBX9r3Xoi0vQ==
dependencies:
bn.js "^5.1.2"
buffer-layout "^1.2.0"
"@eslint/eslintrc@^1.2.1":
version "1.4.1"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.4.1.tgz#af58772019a2d271b7e2d4c23ff4ddcba3ccfb3e"
@ -84,18 +112,6 @@
resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-2.1.0.tgz#66aff77094dc3080bd5df44ec63881f2676eb020"
integrity sha512-i1BpaNDVLJdRBEKeJWkVO6tYX6DMFBuwMhSuWqLsY4ufeTKGVuV5rBsUhxPayXqnnWHgXUAmWK16H/ykO5Wj4Q==
"@hapi/hoek@^9.0.0":
version "9.3.0"
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb"
integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==
"@hapi/topo@^5.0.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012"
integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==
dependencies:
"@hapi/hoek" "^9.0.0"
"@headlessui/react@1.6.6":
version "1.6.6"
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.6.6.tgz#3073c066b85535c9d28783da0a4d9288b5354d0c"
@ -547,11 +563,12 @@
bs58 "^4.0.1"
eventemitter3 "^4.0.7"
"@pythnetwork/client@~2.8.0":
version "2.8.0"
resolved "https://registry.yarnpkg.com/@pythnetwork/client/-/client-2.8.0.tgz#1b764d710f17c2f3cd3c9ba0c4ae74d26e72b01b"
integrity sha512-YqqZSDDsEApC/F4H5ejcl8OCr7gsWv0noTK8YABT8pCtFRTQxXBP0r2S8KQMaIw1x97Mue/jkYybdMeFQzPjCw==
"@pythnetwork/client@~2.14.0":
version "2.14.0"
resolved "https://registry.yarnpkg.com/@pythnetwork/client/-/client-2.14.0.tgz#0c12a7e1bcc66ff198fdb64c003b8d4a24431efc"
integrity sha512-tFLGnuIBjlzDa8TrJULzJIdykketGXDJZtO+8+i4XO9l2uOKXzxt+pjt05ng5B9iY63FzJqgAkawT/O3V0NAdQ==
dependencies:
"@coral-xyz/anchor" "^0.26.0"
buffer "^6.0.1"
"@react-native-async-storage/async-storage@^1.17.7":
@ -566,23 +583,6 @@
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.4.tgz#0c8b74c50f29ee44f423f7416829c0bf8bb5eb27"
integrity sha512-LwzQKA4vzIct1zNZzBmRKI9QuNpLgTQMEjsQLf3BXuGYb3QPTP4Yjf6mkdX+X1mYttZ808QpOwAzZjv28kq7DA==
"@sideway/address@^4.1.3":
version "4.1.4"
resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0"
integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==
dependencies:
"@hapi/hoek" "^9.0.0"
"@sideway/formula@^3.0.0":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f"
integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==
"@sideway/pinpoint@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df"
integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==
"@socket.io/component-emitter@~3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"
@ -612,16 +612,6 @@
"@solana/wallet-adapter-base" "^0.9.17"
js-base64 "^3.7.2"
"@solana/buffer-layout-utils@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@solana/buffer-layout-utils/-/buffer-layout-utils-0.2.0.tgz#b45a6cab3293a2eb7597cceb474f229889d875ca"
integrity sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g==
dependencies:
"@solana/buffer-layout" "^4.0.0"
"@solana/web3.js" "^1.32.0"
bigint-buffer "^1.1.5"
bignumber.js "^9.0.1"
"@solana/buffer-layout@^4.0.0":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz#b996235eaec15b1e0b5092a8ed6028df77fa6c15"
@ -629,15 +619,14 @@
dependencies:
buffer "~6.0.3"
"@solana/spl-token@0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.2.0.tgz#329bb6babb5de0f9c40035ddb1657f01a8347acd"
integrity sha512-RWcn31OXtdqIxmkzQfB2R+WpsJOVS6rKuvpxJFjvik2LyODd+WN58ZP3Rpjpro03fscGAkzlFuP3r42doRJgyQ==
"@solana/spl-token@0.3.7":
version "0.3.7"
resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.3.7.tgz#6f027f9ad8e841f792c32e50920d9d2e714fc8da"
integrity sha512-bKGxWTtIw6VDdCBngjtsGlKGLSmiu/8ghSt/IOYJV24BsymRbgq7r12GToeetpxmPaZYLddKwAz7+EwprLfkfg==
dependencies:
"@solana/buffer-layout" "^4.0.0"
"@solana/buffer-layout-utils" "^0.2.0"
"@solana/web3.js" "^1.32.0"
start-server-and-test "^1.14.0"
buffer "^6.0.3"
"@solana/spl-token@^0.1.6", "@solana/spl-token@^0.1.8":
version "0.1.8"
@ -1091,7 +1080,7 @@
"@wallet-standard/app" "^1.0.0"
"@wallet-standard/base" "^1.0.0"
"@solana/web3.js@^1.17.0", "@solana/web3.js@^1.21.0", "@solana/web3.js@^1.31.0", "@solana/web3.js@^1.32.0", "@solana/web3.js@^1.36.0", "@solana/web3.js@^1.44.3", "@solana/web3.js@^1.63.1":
"@solana/web3.js@^1.17.0", "@solana/web3.js@^1.21.0", "@solana/web3.js@^1.31.0", "@solana/web3.js@^1.36.0", "@solana/web3.js@^1.44.3", "@solana/web3.js@^1.63.1", "@solana/web3.js@^1.68.0":
version "1.73.2"
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.73.2.tgz#4b30cd402b35733dae3a7d0b638be26a7742b395"
integrity sha512-9WACF8W4Nstj7xiDw3Oom22QmrhBh0VyZyZ7JvvG3gOxLWLlX3hvm5nPVJOGcCE/9fFavBbCUb5A6CIuvMGdoA==
@ -2219,21 +2208,13 @@ axios@1.2.4:
form-data "^4.0.0"
proxy-from-env "^1.1.0"
axios@^0.21.0, axios@^0.21.4:
axios@^0.21.0:
version "0.21.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
dependencies:
follow-redirects "^1.14.0"
axios@^0.27.2:
version "0.27.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972"
integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==
dependencies:
follow-redirects "^1.14.9"
form-data "^4.0.0"
axobject-query@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
@ -2349,11 +2330,6 @@ blakejs@^1.1.0:
resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814"
integrity sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==
bluebird@3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.8, bn.js@^4.11.9:
version "4.12.0"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
@ -2589,6 +2565,11 @@ camelcase@^5.0.0, camelcase@^5.3.1:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
camelcase@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001406, caniuse-lite@^1.0.30001426:
version "1.0.30001448"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001448.tgz#ca7550b1587c92a392a2b377cd9c508b3b4395bf"
@ -2628,11 +2609,6 @@ chalk@^4.0.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
check-more-types@2.24.0:
version "2.24.0"
resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600"
integrity sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==
chokidar@^3.5.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
@ -2827,7 +2803,7 @@ cross-fetch@^3.1.4, cross-fetch@^3.1.5:
dependencies:
node-fetch "2.6.7"
cross-spawn@^7.0.2, cross-spawn@^7.0.3:
cross-spawn@^7.0.2:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
@ -2966,7 +2942,7 @@ dayjs@1.11.3:
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.3.tgz#4754eb694a624057b9ad2224b67b15d552589258"
integrity sha512-xxwlswWOlGhzgQ4TKzASQkUhqERI3egRNqgV4ScR8wlANA/A9tZ7miXa44vTTKEq5l7vWoL5G57bG3zA+Kow0A==
debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@ -3186,11 +3162,6 @@ drbg.js@^1.0.1:
create-hash "^1.1.2"
create-hmac "^1.1.4"
duplexer@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==
ecc-jsbn@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
@ -3654,19 +3625,6 @@ ethereumjs-util@^7.1.5:
ethereum-cryptography "^0.1.3"
rlp "^2.2.4"
event-stream@=3.3.4:
version "3.3.4"
resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571"
integrity sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==
dependencies:
duplexer "~0.1.1"
from "~0"
map-stream "~0.1.0"
pause-stream "0.0.11"
split "0.3"
stream-combiner "~0.0.4"
through "~2.3.1"
eventemitter3@^4.0.0, eventemitter3@^4.0.1, eventemitter3@^4.0.4, eventemitter3@^4.0.7:
version "4.0.7"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
@ -3685,21 +3643,6 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
md5.js "^1.3.4"
safe-buffer "^5.1.1"
execa@5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
dependencies:
cross-spawn "^7.0.3"
get-stream "^6.0.0"
human-signals "^2.1.0"
is-stream "^2.0.0"
merge-stream "^2.0.0"
npm-run-path "^4.0.1"
onetime "^5.1.2"
signal-exit "^3.0.3"
strip-final-newline "^2.0.0"
exenv@^1.2.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d"
@ -3841,7 +3784,7 @@ flatted@^3.1.0:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3"
integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==
follow-redirects@^1.14.0, follow-redirects@^1.14.9, follow-redirects@^1.15.0:
follow-redirects@^1.14.0, follow-redirects@^1.15.0:
version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
@ -3881,11 +3824,6 @@ fraction.js@^4.2.0:
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==
from@~0:
version "0.1.7"
resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe"
integrity sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==
fs-minipass@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
@ -3903,15 +3841,6 @@ fsevents@~2.3.2:
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
ftx-api@^1.1.13:
version "1.1.14"
resolved "https://registry.yarnpkg.com/ftx-api/-/ftx-api-1.1.14.tgz#14457c3927da448468bf7b1c2e4909bfbf668919"
integrity sha512-p6HPgqBiS02RSvHUB8vFRc1XDv5uEBclsuDXoX2AonPEVZySOt0Tp7V+P4R8xlbyohDa3UN6D54dm5C94P/unQ==
dependencies:
axios "^0.21.4"
isomorphic-ws "^4.0.1"
ws "^7.4.0"
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
@ -3965,11 +3894,6 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3:
has "^1.0.3"
has-symbols "^1.0.3"
get-stream@^6.0.0:
version "6.0.1"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
get-symbol-description@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6"
@ -4215,11 +4139,6 @@ https-proxy-agent@^5.0.0:
agent-base "6"
debug "4"
human-signals@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
humanize-ms@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed"
@ -4448,7 +4367,7 @@ is-shared-array-buffer@^1.0.2:
dependencies:
call-bind "^1.0.2"
is-stream@^2.0.0, is-stream@^2.0.1:
is-stream@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
@ -4554,17 +4473,6 @@ jmespath@^0.15.0:
resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217"
integrity sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w==
joi@^17.7.0:
version "17.7.0"
resolved "https://registry.yarnpkg.com/joi/-/joi-17.7.0.tgz#591a33b1fe1aca2bc27f290bcad9b9c1c570a6b3"
integrity sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==
dependencies:
"@hapi/hoek" "^9.0.0"
"@hapi/topo" "^5.0.0"
"@sideway/address" "^4.1.3"
"@sideway/formula" "^3.0.0"
"@sideway/pinpoint" "^2.0.0"
joycon@^2.2.5:
version "2.2.5"
resolved "https://registry.yarnpkg.com/joycon/-/joycon-2.2.5.tgz#8d4cf4cbb2544d7b7583c216fcdfec19f6be1615"
@ -4722,11 +4630,6 @@ language-tags@^1.0.5:
dependencies:
language-subtag-registry "~0.3.2"
lazy-ass@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513"
integrity sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==
leven@2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580"
@ -4817,11 +4720,6 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
map-stream@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194"
integrity sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==
md5.js@^1.3.4:
version "1.3.5"
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
@ -4843,11 +4741,6 @@ merge-options@^3.0.4:
dependencies:
is-plain-obj "^2.1.0"
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
merge2@^1.3.0, merge2@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
@ -4881,11 +4774,6 @@ mime-types@^2.1.12, mime-types@~2.1.19:
dependencies:
mime-db "1.52.0"
mimic-fn@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
@ -4903,7 +4791,7 @@ minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2:
dependencies:
brace-expansion "^1.1.7"
minimist@^1.2.0, minimist@^1.2.6, minimist@^1.2.7:
minimist@^1.2.0, minimist@^1.2.6:
version "1.2.7"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
@ -5116,13 +5004,6 @@ npm-normalize-package-bin@^3.0.0:
resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.0.tgz#6097436adb4ef09e2628b59a7882576fe53ce485"
integrity sha512-g+DPQSkusnk7HYXr75NtzkIP4+N81i3RPsGFidF3DzHd9MT9wWngmqoeg/fnHFz5MNdtG4w03s+QnhewSLTT2Q==
npm-run-path@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
dependencies:
path-key "^3.0.0"
npmlog@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
@ -5228,13 +5109,6 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0:
dependencies:
wrappy "1"
onetime@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
dependencies:
mimic-fn "^2.1.0"
optionator@^0.9.1:
version "0.9.1"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
@ -5318,7 +5192,7 @@ path-is-absolute@^1.0.0:
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
path-key@^3.0.0, path-key@^3.1.0:
path-key@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
@ -5333,13 +5207,6 @@ path-type@^4.0.0:
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
pause-stream@0.0.11:
version "0.0.11"
resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445"
integrity sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==
dependencies:
through "~2.3"
pbkdf2@^3.0.17, pbkdf2@^3.0.3:
version "3.1.2"
resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075"
@ -5553,13 +5420,6 @@ proxy-from-env@^1.1.0:
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
ps-tree@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/ps-tree/-/ps-tree-1.2.0.tgz#5e7425b89508736cdd4f2224d028f7bb3f722ebd"
integrity sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==
dependencies:
event-stream "=3.3.4"
psl@^1.1.28:
version "1.9.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
@ -6059,13 +5919,6 @@ rxjs@6, rxjs@^6.6.3:
dependencies:
tslib "^1.9.0"
rxjs@^7.8.0:
version "7.8.0"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4"
integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==
dependencies:
tslib "^2.1.0"
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
@ -6202,7 +6055,7 @@ side-channel@^1.0.4:
get-intrinsic "^1.0.2"
object-inspect "^1.9.0"
signal-exit@^3.0.0, signal-exit@^3.0.3, signal-exit@^3.0.7:
signal-exit@^3.0.0, signal-exit@^3.0.7:
version "3.0.7"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
@ -6263,13 +6116,6 @@ split2@^3.1.1:
dependencies:
readable-stream "^3.0.0"
split@0.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f"
integrity sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==
dependencies:
through "2"
sshpk@^1.7.0:
version "1.17.0"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5"
@ -6285,20 +6131,6 @@ sshpk@^1.7.0:
safer-buffer "^2.0.2"
tweetnacl "~0.14.0"
start-server-and-test@^1.14.0:
version "1.15.3"
resolved "https://registry.yarnpkg.com/start-server-and-test/-/start-server-and-test-1.15.3.tgz#33bb6465ff4d5e3a476337512a7e0d737a5ef026"
integrity sha512-4GqkqghvUR9cJ8buvtgkyT0AHgVwCJ5EN8eDEhe9grTChGwWUxGm2nqfSeE9+0PZkLRdFqcwTwxVHe1y3ViutQ==
dependencies:
arg "^5.0.2"
bluebird "3.7.2"
check-more-types "2.24.0"
debug "4.3.4"
execa "5.1.1"
lazy-ass "1.6.0"
ps-tree "1.2.0"
wait-on "7.0.1"
stream-browserify@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f"
@ -6307,13 +6139,6 @@ stream-browserify@^3.0.0:
inherits "~2.0.4"
readable-stream "^3.5.0"
stream-combiner@~0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14"
integrity sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==
dependencies:
duplexer "~0.1.1"
strict-uri-encode@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
@ -6418,11 +6243,6 @@ strip-bom@^3.0.0:
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==
strip-final-newline@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
@ -6528,7 +6348,7 @@ text-table@^0.2.0:
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
through@2, "through@>=2.2.7 <3", through@~2.3, through@~2.3.1:
"through@>=2.2.7 <3":
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==
@ -6595,7 +6415,7 @@ tslib@^1.8.1, tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0:
tslib@^2.0.3, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
@ -7080,17 +6900,6 @@ void-elements@3.1.0:
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==
wait-on@7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-7.0.1.tgz#5cff9f8427e94f4deacbc2762e6b0a489b19eae9"
integrity sha512-9AnJE9qTjRQOlTZIldAaf/da2eW0eSRSgcqq85mXQja/DW3MriHxkpODDSUEg+Gri/rKEcXUZHe+cevvYItaog==
dependencies:
axios "^0.27.2"
joi "^17.7.0"
lodash "^4.17.21"
minimist "^1.2.7"
rxjs "^7.8.0"
walktour@5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/walktour/-/walktour-5.1.1.tgz#951b5bce2abed0ae4209dc74d4d79f252a85e813"
@ -7213,9 +7022,9 @@ ws@^7.2.0, ws@^7.4.0, ws@^7.4.5, ws@^7.5.1:
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
ws@^8.5.0:
version "8.12.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.12.0.tgz#485074cc392689da78e1828a9ff23585e06cddd8"
integrity sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==
version "8.12.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.12.1.tgz#c51e583d79140b5e42e39be48c934131942d4a8f"
integrity sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==
ws@~8.2.3:
version "8.2.3"