Merge branch 'main' into feature/kline-charts-spot
This commit is contained in:
commit
cc67329be9
|
@ -1,7 +1,7 @@
|
|||
export const fetchChartData = async (
|
||||
baseTokenId: string | undefined,
|
||||
quoteTokenId: string | undefined,
|
||||
daysToShow: number
|
||||
daysToShow: string
|
||||
) => {
|
||||
console.log('fetching chart:', baseTokenId, quoteTokenId)
|
||||
|
||||
|
|
|
@ -26,13 +26,13 @@ const AccountChart = ({
|
|||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const [daysToShow, setDaysToShow] = useState<number>(1)
|
||||
const [daysToShow, setDaysToShow] = useState<string>('1')
|
||||
const loading = mangoStore((s) => s.mangoAccount.stats.performance.loading)
|
||||
|
||||
const handleDaysToShow = async (days: number) => {
|
||||
const handleDaysToShow = async (days: string) => {
|
||||
await actions.fetchAccountPerformance(
|
||||
mangoAccount.publicKey.toString(),
|
||||
days
|
||||
parseInt(days)
|
||||
)
|
||||
setDaysToShow(days)
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@ import Input from '@components/forms/Input'
|
|||
import Label from '@components/forms/Label'
|
||||
import MultiSelectDropdown from '@components/forms/MultiSelectDropdown'
|
||||
import { EXPLORERS } from '@components/settings/PreferredExplorerSettings'
|
||||
import Button, { IconButton, LinkButton } from '@components/shared/Button'
|
||||
import Modal from '@components/shared/Modal'
|
||||
import Button, { IconButton } from '@components/shared/Button'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import { Disclosure, Transition } from '@headlessui/react'
|
||||
import {
|
||||
AdjustmentsVerticalIcon,
|
||||
ArrowLeftIcon,
|
||||
ArrowPathIcon,
|
||||
ChevronDownIcon,
|
||||
|
@ -18,18 +18,17 @@ import dayjs from 'dayjs'
|
|||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Image from 'next/legacy/image'
|
||||
import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { PREFERRED_EXPLORER_KEY } from 'utils/constants'
|
||||
import { formatDecimal, formatFixedDecimals } from 'utils/numbers'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import ActivityFeedTable from './ActivityFeedTable'
|
||||
|
||||
interface Filters {
|
||||
deposit: boolean
|
||||
liquidate_token_with_token: boolean
|
||||
perp_trade: boolean
|
||||
swap: boolean
|
||||
withdraw: boolean
|
||||
}
|
||||
|
@ -45,6 +44,7 @@ interface AdvancedFilters {
|
|||
const DEFAULT_FILTERS = {
|
||||
deposit: true,
|
||||
liquidate_token_with_token: true,
|
||||
perp_trade: true,
|
||||
swap: true,
|
||||
withdraw: true,
|
||||
}
|
||||
|
@ -61,6 +61,7 @@ const DEFAULT_PARAMS = [
|
|||
'deposit',
|
||||
'liquidate_token_with_token',
|
||||
'swap',
|
||||
'perp_trade',
|
||||
'withdraw',
|
||||
]
|
||||
|
||||
|
@ -111,7 +112,7 @@ const ActivityFeed = () => {
|
|||
}, [advancedFilters])
|
||||
|
||||
const queryParams = useMemo(() => {
|
||||
return params.length === 3
|
||||
return params.length === 5
|
||||
? advancedParamsString
|
||||
: `&activity-type=${params.toString()}${advancedParamsString}`
|
||||
}, [advancedParamsString, params])
|
||||
|
@ -123,6 +124,7 @@ const ActivityFeed = () => {
|
|||
setFilters={setFilters}
|
||||
updateFilters={updateFilters}
|
||||
params={queryParams}
|
||||
setParams={setParams}
|
||||
advancedFilters={advancedFilters}
|
||||
setAdvancedFilters={setAdvancedFilters}
|
||||
/>
|
||||
|
@ -147,6 +149,7 @@ const ActivityFilters = ({
|
|||
setFilters,
|
||||
updateFilters,
|
||||
params,
|
||||
setParams,
|
||||
advancedFilters,
|
||||
setAdvancedFilters,
|
||||
}: {
|
||||
|
@ -154,6 +157,7 @@ const ActivityFilters = ({
|
|||
setFilters: (x: Filters) => void
|
||||
updateFilters: (e: ChangeEvent<HTMLInputElement>, filter: string) => void
|
||||
params: string
|
||||
setParams: (x: string[]) => void
|
||||
advancedFilters: AdvancedFilters
|
||||
setAdvancedFilters: (x: AdvancedFilters) => void
|
||||
}) => {
|
||||
|
@ -161,10 +165,6 @@ const ActivityFilters = ({
|
|||
const actions = mangoStore((s) => s.actions)
|
||||
const loadActivityFeed = mangoStore((s) => s.activityFeed.loading)
|
||||
const { mangoAccount } = useMangoAccount()
|
||||
const [showAdvancedFiltersModal, setShowAdvancedFiltersModal] =
|
||||
useState(false)
|
||||
const { width } = useViewport()
|
||||
const isMobile = width ? width < breakpoints.lg : false
|
||||
const [showMobileFilters, setShowMobileFilters] = useState(false)
|
||||
const [hasFilters, setHasFilters] = useState(false)
|
||||
|
||||
|
@ -197,80 +197,16 @@ const ActivityFilters = ({
|
|||
await actions.fetchActivityFeed(mangoAccount.publicKey.toString())
|
||||
setAdvancedFilters(DEFAULT_ADVANCED_FILTERS)
|
||||
setFilters(DEFAULT_FILTERS)
|
||||
setParams(DEFAULT_PARAMS)
|
||||
}
|
||||
}, [actions])
|
||||
|
||||
const handleUpdateModalResults = () => {
|
||||
handleUpdateResults()
|
||||
setShowAdvancedFiltersModal(false)
|
||||
}
|
||||
|
||||
const handleUpdateMobileResults = () => {
|
||||
handleUpdateResults()
|
||||
setShowMobileFilters(false)
|
||||
}
|
||||
|
||||
return mangoAccount ? (
|
||||
!isMobile ? (
|
||||
<>
|
||||
<div className="flex items-center justify-between border-b border-th-bkg-3 bg-th-bkg-2 pl-6">
|
||||
<h3 className="flex items-center whitespace-nowrap pr-6 text-sm">
|
||||
{t('activity:filter-results')}
|
||||
</h3>
|
||||
<ActivityTypeFiltersForm
|
||||
filters={filters}
|
||||
updateFilters={updateFilters}
|
||||
/>
|
||||
<div className="flex h-12 items-center justify-between border-l border-th-bkg-4 p-6">
|
||||
<LinkButton
|
||||
className="whitespace-nowrap text-sm"
|
||||
onClick={() => setShowAdvancedFiltersModal(true)}
|
||||
>
|
||||
{t('activity:advanced-filters')}
|
||||
</LinkButton>
|
||||
{hasFilters ? (
|
||||
<Tooltip content={t('activity:reset-filters')}>
|
||||
<IconButton
|
||||
className={`ml-4 ${loadActivityFeed ? 'animate-spin' : ''}`}
|
||||
onClick={() => handleResetFilters()}
|
||||
size="small"
|
||||
>
|
||||
<ArrowPathIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
<Button
|
||||
className="rounded-none"
|
||||
size="large"
|
||||
onClick={handleUpdateResults}
|
||||
>
|
||||
{t('activity:update')}
|
||||
</Button>
|
||||
</div>
|
||||
{showAdvancedFiltersModal ? (
|
||||
<Modal
|
||||
isOpen={showAdvancedFiltersModal}
|
||||
onClose={() => setShowAdvancedFiltersModal(false)}
|
||||
>
|
||||
<h2 className="mb-2 text-center">
|
||||
{t('activity:advanced-filters')}
|
||||
</h2>
|
||||
<AdvancedFiltersForm
|
||||
advancedFilters={advancedFilters}
|
||||
setAdvancedFilters={setAdvancedFilters}
|
||||
/>
|
||||
<Button
|
||||
className="w-full"
|
||||
size="large"
|
||||
onClick={handleUpdateModalResults}
|
||||
>
|
||||
{t('activity:update')}
|
||||
</Button>
|
||||
</Modal>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<Disclosure>
|
||||
<div className="relative">
|
||||
{hasFilters ? (
|
||||
|
@ -289,15 +225,17 @@ const ActivityFilters = ({
|
|||
<div
|
||||
onClick={() => setShowMobileFilters(!showMobileFilters)}
|
||||
role="button"
|
||||
className={`default-transition w-full border-b border-th-bkg-3 bg-th-bkg-2 p-4 hover:bg-th-bkg-3`}
|
||||
className={`default-transition w-full border-b border-th-bkg-3 bg-th-bkg-2 p-4 hover:bg-th-bkg-3 md:px-6`}
|
||||
>
|
||||
<Disclosure.Button
|
||||
className={`flex h-full w-full items-center justify-between rounded-none`}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<AdjustmentsVerticalIcon className="h-5 w-5 text-th-fgd-4" />
|
||||
<span className="font-bold text-th-fgd-1">
|
||||
{t('activity:filter-results')}
|
||||
</span>
|
||||
|
||||
</div>
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
showMobileFilters ? 'rotate-180' : 'rotate-360'
|
||||
|
@ -329,7 +267,7 @@ const ActivityFilters = ({
|
|||
setAdvancedFilters={setAdvancedFilters}
|
||||
/>
|
||||
<Button
|
||||
className="w-full"
|
||||
className="w-full md:w-auto"
|
||||
size="large"
|
||||
onClick={handleUpdateMobileResults}
|
||||
>
|
||||
|
@ -338,7 +276,6 @@ const ActivityFilters = ({
|
|||
</Disclosure.Panel>
|
||||
</Transition>
|
||||
</Disclosure>
|
||||
)
|
||||
) : null
|
||||
}
|
||||
|
||||
|
@ -351,7 +288,7 @@ const ActivityTypeFiltersForm = ({
|
|||
}) => {
|
||||
const { t } = useTranslation('activity')
|
||||
return (
|
||||
<div className="flex w-full flex-col space-y-2 md:flex-row md:space-y-0">
|
||||
<div className="flex w-full flex-col space-y-3 md:flex-row md:space-y-0">
|
||||
<div className="flex h-8 flex-1 items-center lg:h-12 lg:border-l lg:border-th-bkg-4 lg:p-4">
|
||||
<Checkbox
|
||||
checked={filters.deposit}
|
||||
|
@ -376,6 +313,14 @@ const ActivityTypeFiltersForm = ({
|
|||
<span className="text-sm">{t('swaps')}</span>
|
||||
</Checkbox>
|
||||
</div>
|
||||
<div className="flex h-8 flex-1 items-center lg:h-12 lg:border-l lg:border-th-bkg-4 lg:p-4">
|
||||
<Checkbox
|
||||
checked={filters.perp_trade}
|
||||
onChange={(e) => updateFilters(e, 'perp_trade')}
|
||||
>
|
||||
<span className="text-sm">{t('perps')}</span>
|
||||
</Checkbox>
|
||||
</div>
|
||||
<div className="flex h-8 flex-1 items-center lg:h-12 lg:border-l lg:border-th-bkg-4 lg:p-4">
|
||||
<Checkbox
|
||||
checked={filters.liquidate_token_with_token}
|
||||
|
|
|
@ -102,6 +102,11 @@ const ActivityFeedTable = ({
|
|||
symbol: swap_in_symbol,
|
||||
}
|
||||
}
|
||||
if (activity_type === 'perp_trade') {
|
||||
const { perp_market, price, quantity } = activity.activity_details
|
||||
credit = { value: quantity, symbol: perp_market }
|
||||
debit = { value: formatDecimal(quantity * price * -1), symbol: 'USDC' }
|
||||
}
|
||||
return { credit, debit }
|
||||
}
|
||||
|
||||
|
@ -134,6 +139,11 @@ const ActivityFeedTable = ({
|
|||
(swap_in_amount + loan_origination_fee) * swap_in_price_usd -
|
||||
swap_out_amount * swap_out_price_usd
|
||||
}
|
||||
if (activity_type === 'perp_trade') {
|
||||
const { maker_fee, price, quantity, taker_fee } =
|
||||
activity.activity_details
|
||||
value = (quantity * price + maker_fee + taker_fee) * -1
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
|
@ -309,6 +319,7 @@ const MobileActivityFeedItem = ({
|
|||
const { signature } = activity.activity_details
|
||||
const isLiquidation = activity_type === 'liquidate_token_with_token'
|
||||
const isSwap = activity_type === 'swap'
|
||||
const isPerp = activity_type === 'perp_trade'
|
||||
const activityName = isLiquidation ? 'liquidation' : activity_type
|
||||
const value = getValue(activity)
|
||||
return (
|
||||
|
@ -352,6 +363,26 @@ const MobileActivityFeedItem = ({
|
|||
{activity.activity_details.swap_out_symbol}
|
||||
</span>
|
||||
</>
|
||||
) : isPerp ? (
|
||||
<>
|
||||
<span
|
||||
className={`mr-1 font-body ${
|
||||
activity.activity_details.taker_side === 'bid'
|
||||
? 'text-th-green'
|
||||
: 'text-th-red'
|
||||
}`}
|
||||
>
|
||||
{activity.activity_details.taker_side === 'bid'
|
||||
? 'BUY'
|
||||
: 'SELL'}
|
||||
</span>
|
||||
<span className="mr-1">
|
||||
{activity.activity_details.quantity}
|
||||
</span>
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{activity.activity_details.perp_market}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="mr-1">
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { FunctionComponent } from 'react'
|
||||
|
||||
interface ChartRangeButtonsProps {
|
||||
activeValue: number
|
||||
activeValue: string
|
||||
className?: string
|
||||
onChange: (x: number) => void
|
||||
values: Array<number>
|
||||
onChange: (x: string) => void
|
||||
values: Array<string>
|
||||
names?: Array<string>
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ const ChartRangeButtons: FunctionComponent<ChartRangeButtonsProps> = ({
|
|||
<div className="relative flex">
|
||||
{activeValue && values.includes(activeValue) ? (
|
||||
<div
|
||||
className={`default-transition absolute left-0 top-0 h-full transform rounded-md bg-th-bkg-2`}
|
||||
className={`default-transition absolute left-0 top-0 h-full transform rounded-md bg-th-bkg-3`}
|
||||
style={{
|
||||
transform: `translateX(${
|
||||
values.findIndex((v) => v === activeValue) * 100
|
||||
|
|
|
@ -30,11 +30,11 @@ dayjs.extend(relativeTime)
|
|||
|
||||
interface DetailedAreaChartProps {
|
||||
data: any[]
|
||||
daysToShow: number
|
||||
daysToShow: string
|
||||
hideChange?: boolean
|
||||
hideChart?: () => void
|
||||
loading?: boolean
|
||||
setDaysToShow: (x: number) => void
|
||||
setDaysToShow: (x: string) => void
|
||||
tickFormat?: (x: any) => string
|
||||
title?: string
|
||||
xKey: string
|
||||
|
@ -51,7 +51,7 @@ export const formatDateAxis = (date: string, days: number) => {
|
|||
|
||||
const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
|
||||
data,
|
||||
daysToShow = 1,
|
||||
daysToShow = '1',
|
||||
hideChange,
|
||||
hideChart,
|
||||
loading,
|
||||
|
@ -183,7 +183,7 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
|
|||
<ChartRangeButtons
|
||||
activeValue={daysToShow}
|
||||
names={['24H', '7D', '30D']}
|
||||
values={[1, 7, 30]}
|
||||
values={['1', '7', '30']}
|
||||
onChange={(v) => setDaysToShow(v)}
|
||||
/>
|
||||
</div>
|
||||
|
@ -252,7 +252,9 @@ const DetailedAreaChart: FunctionComponent<DetailedAreaChartProps> = ({
|
|||
fontSize: 10,
|
||||
}}
|
||||
tickLine={false}
|
||||
tickFormatter={(d) => formatDateAxis(d, daysToShow)}
|
||||
tickFormatter={(d) =>
|
||||
formatDateAxis(d, parseInt(daysToShow))
|
||||
}
|
||||
/>
|
||||
<YAxis
|
||||
axisLine={false}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import { StarIcon } from '@heroicons/react/24/outline'
|
||||
import { StarIcon as FilledStarIcon } from '@heroicons/react/20/solid'
|
||||
import { PerpMarket, Serum3Market } from '@blockworks-foundation/mango-v4'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import { FAVORITE_MARKETS_KEY } from 'utils/constants'
|
||||
|
||||
const FavoriteMarketButton = ({
|
||||
market,
|
||||
}: {
|
||||
market: PerpMarket | Serum3Market
|
||||
}) => {
|
||||
const [favoriteMarkets, setFavoriteMarkets] = useLocalStorageState(
|
||||
FAVORITE_MARKETS_KEY,
|
||||
[]
|
||||
)
|
||||
|
||||
const addToFavorites = (marketName: string) => {
|
||||
const newFavorites: any = [...favoriteMarkets, marketName]
|
||||
setFavoriteMarkets(newFavorites)
|
||||
}
|
||||
|
||||
const removeFromFavorites = (marketName: string) => {
|
||||
setFavoriteMarkets(favoriteMarkets.filter((m: string) => m !== marketName))
|
||||
}
|
||||
|
||||
return favoriteMarkets.find(
|
||||
(marketName: string) => marketName === market.name
|
||||
) ? (
|
||||
<button
|
||||
className="default-transition flex items-center justify-center text-th-primary md:hover:text-th-fgd-3"
|
||||
onClick={() => removeFromFavorites(market.name)}
|
||||
>
|
||||
<FilledStarIcon className="h-5 w-5" />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="default-transition flex items-center justify-center text-th-fgd-4 md:hover:text-th-primary"
|
||||
onClick={() => addToFavorites(market.name)}
|
||||
>
|
||||
<StarIcon className="h-5 w-5" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default FavoriteMarketButton
|
|
@ -76,7 +76,7 @@ const SwapTokenChart = () => {
|
|||
const [baseTokenId, setBaseTokenId] = useState(inputCoingeckoId)
|
||||
const [quoteTokenId, setQuoteTokenId] = useState(outputCoingeckoId)
|
||||
const [mouseData, setMouseData] = useState<any>(null)
|
||||
const [daysToShow, setDaysToShow] = useState(1)
|
||||
const [daysToShow, setDaysToShow] = useState('1')
|
||||
const { theme } = useTheme()
|
||||
const [animationSettings] = useLocalStorageState(
|
||||
ANIMATION_SETTINGS_KEY,
|
||||
|
@ -245,7 +245,7 @@ const SwapTokenChart = () => {
|
|||
<ChartRangeButtons
|
||||
activeValue={daysToShow}
|
||||
names={['24H', '7D', '30D']}
|
||||
values={[1, 7, 30]}
|
||||
values={['1', '7', '30']}
|
||||
onChange={(v) => setDaysToShow(v)}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,115 +1,13 @@
|
|||
import { PerpMarket } from '@blockworks-foundation/mango-v4'
|
||||
import Change from '@components/shared/Change'
|
||||
import TabUnderline from '@components/shared/TabUnderline'
|
||||
import { Popover } from '@headlessui/react'
|
||||
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useCoingecko } from 'hooks/useCoingecko'
|
||||
import useOraclePrice from 'hooks/useOraclePrice'
|
||||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Link from 'next/link'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { DEFAULT_MARKET_NAME } from 'utils/constants'
|
||||
import { useMemo } from 'react'
|
||||
import { formatFixedDecimals } from 'utils/numbers'
|
||||
import MarketLogos from './MarketLogos'
|
||||
|
||||
const MarketSelectDropdown = () => {
|
||||
const { selectedMarket } = useSelectedMarket()
|
||||
const serumMarkets = mangoStore((s) => s.serumMarkets)
|
||||
const perpMarkets = mangoStore((s) => s.perpMarkets)
|
||||
const [activeTab, setActiveTab] = useState('perp')
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
{({ open }) => (
|
||||
<div
|
||||
className="relative flex flex-col overflow-visible"
|
||||
id="trade-step-one"
|
||||
>
|
||||
<Popover.Button className="default-transition flex w-full items-center justify-between hover:text-th-primary">
|
||||
<>
|
||||
{selectedMarket ? <MarketLogos market={selectedMarket} /> : null}
|
||||
</>
|
||||
<div className="text-xl font-bold text-th-fgd-1 md:text-base">
|
||||
{selectedMarket?.name || DEFAULT_MARKET_NAME}
|
||||
</div>
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
open ? 'rotate-180' : 'rotate-360'
|
||||
} mt-0.5 ml-2 h-6 w-6 flex-shrink-0 text-th-fgd-3`}
|
||||
/>
|
||||
</Popover.Button>
|
||||
<Popover.Panel className="absolute -left-5 top-[46px] z-50 mr-4 w-screen bg-th-bkg-2 pb-2 pt-4 sm:w-56 md:top-[37px]">
|
||||
<TabUnderline
|
||||
activeValue={activeTab}
|
||||
onChange={(v) => setActiveTab(v)}
|
||||
small
|
||||
values={['perp', 'spot']}
|
||||
/>
|
||||
{activeTab === 'spot'
|
||||
? serumMarkets?.length
|
||||
? serumMarkets.map((m) => {
|
||||
return (
|
||||
<Link
|
||||
href={{
|
||||
pathname: '/trade',
|
||||
query: { name: m.name },
|
||||
}}
|
||||
key={m.publicKey.toString()}
|
||||
shallow={true}
|
||||
>
|
||||
<div className="default-transition flex items-center py-2 px-4 hover:cursor-pointer hover:bg-th-bkg-2">
|
||||
<MarketLogos market={m} />
|
||||
<span
|
||||
className={
|
||||
m.name === selectedMarket?.name
|
||||
? 'text-th-primary'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{m.name}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
})
|
||||
: null
|
||||
: null}
|
||||
{activeTab === 'perp'
|
||||
? perpMarkets?.length
|
||||
? perpMarkets.map((m) => {
|
||||
return (
|
||||
<Link
|
||||
href={{
|
||||
pathname: '/trade',
|
||||
query: { name: m.name },
|
||||
}}
|
||||
key={m.publicKey.toString()}
|
||||
shallow={true}
|
||||
>
|
||||
<div className="default-transition flex items-center py-2 px-4 hover:cursor-pointer hover:bg-th-bkg-2">
|
||||
<MarketLogos market={m} />
|
||||
<span
|
||||
className={
|
||||
m.name === selectedMarket?.name
|
||||
? 'text-th-primary'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{m.name}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
})
|
||||
: null
|
||||
: null}
|
||||
</Popover.Panel>
|
||||
</div>
|
||||
)}
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
import MarketSelectDropdown from './MarketSelectDropdown'
|
||||
import PerpFundingRate from './PerpFundingRate'
|
||||
|
||||
const AdvancedMarketHeader = () => {
|
||||
const { t } = useTranslation(['common', 'trade'])
|
||||
|
@ -154,14 +52,13 @@ const AdvancedMarketHeader = () => {
|
|||
<div className="ml-6 flex-col">
|
||||
<div className="text-xs text-th-fgd-4">{t('rolling-change')}</div>
|
||||
<Change change={change} size="small" />
|
||||
{/* <div
|
||||
className={`font-mono text-xs ${
|
||||
change < 0 ? 'text-th-red' : 'text-th-gree'
|
||||
}`}
|
||||
>
|
||||
{isNaN(change) ? '0.00' : change.toFixed(2)}%
|
||||
</div> */}
|
||||
</div>
|
||||
{selectedMarket instanceof PerpMarket ? (
|
||||
<div className="ml-6 flex-col">
|
||||
<div className="text-xs text-th-fgd-4">Funding Rate</div>
|
||||
<PerpFundingRate />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
import { Transition } from '@headlessui/react'
|
||||
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 ? (
|
||||
<Transition
|
||||
className="flex items-center space-x-4 overflow-hidden border-b border-th-bkg-3 px-6"
|
||||
show={!!favoriteMarkets.length}
|
||||
enter="transition-all ease-in duration-200"
|
||||
enterFrom="opacity-0 h-0"
|
||||
enterTo="opacity-100 h-8"
|
||||
leave="transition-all ease-out duration-200"
|
||||
leaveFrom="opacity-100 h-8"
|
||||
leaveTo="opacity-0 h-0"
|
||||
>
|
||||
<StarIcon className="h-4 w-4 text-th-fgd-4" />
|
||||
{favoriteMarkets.map((mkt: string) => {
|
||||
// const change24h = marketsInfo?.find((m) => m.name === mkt)?.change24h
|
||||
const isPerp = mkt.includes('PERP')
|
||||
let market
|
||||
if (isPerp) {
|
||||
market = group?.getPerpMarketByName(mkt)
|
||||
} else {
|
||||
market = group?.getSerum3MarketByName(mkt)
|
||||
}
|
||||
return (
|
||||
<Link href={`/trade?name=${mkt}`} key={mkt} shallow={true}>
|
||||
<div
|
||||
className={`default-transition flex items-center whitespace-nowrap py-1 text-xs hover:text-th-primary hover:opacity-100 ${
|
||||
asPath.includes(mkt) ||
|
||||
(asPath === '/trade' &&
|
||||
selectedMarket &&
|
||||
selectedMarket.name === mkt)
|
||||
? 'text-th-primary'
|
||||
: 'text-th-fgd-1 opacity-60'
|
||||
}`}
|
||||
>
|
||||
{market ? <MarketLogos market={market} small /> : null}
|
||||
<span className="mb-0 mr-1.5 text-xs">{mkt}</span>
|
||||
{/* {change24h ? (
|
||||
<div
|
||||
className={`text-xs ${
|
||||
change24h
|
||||
? change24h >= 0
|
||||
? 'text-th-green'
|
||||
: 'text-th-red'
|
||||
: 'text-th-fgd-4'
|
||||
}`}
|
||||
>
|
||||
{`${(change24h * 100).toFixed(1)}%`}
|
||||
</div>
|
||||
) : null} */}
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</Transition>
|
||||
) : null
|
||||
}
|
||||
|
||||
export default FavoriteMarketsBar
|
|
@ -5,7 +5,13 @@ import Image from 'next/legacy/image'
|
|||
import { useMemo } from 'react'
|
||||
import useMangoGroup from 'hooks/useMangoGroup'
|
||||
|
||||
const MarketLogos = ({ market }: { market: Serum3Market | PerpMarket }) => {
|
||||
const MarketLogos = ({
|
||||
market,
|
||||
small,
|
||||
}: {
|
||||
market: Serum3Market | PerpMarket
|
||||
small?: boolean
|
||||
}) => {
|
||||
const { group } = useMangoGroup()
|
||||
const { mangoTokens } = useJupiterMints()
|
||||
|
||||
|
@ -39,8 +45,14 @@ const MarketLogos = ({ market }: { market: Serum3Market | PerpMarket }) => {
|
|||
|
||||
return (
|
||||
<div
|
||||
className={`relative mr-1.5 h-5 ${
|
||||
market instanceof Serum3Market ? 'w-[34px]' : 'w-[20px]'
|
||||
className={`relative mr-1.5 ${small ? 'h-4' : 'h-5'} ${
|
||||
market instanceof Serum3Market
|
||||
? small
|
||||
? 'w-[27px]'
|
||||
: 'w-[34px]'
|
||||
: small
|
||||
? 'w-[16px]'
|
||||
: 'w-[20px]'
|
||||
}`}
|
||||
>
|
||||
<div className="absolute left-0 top-0">
|
||||
|
@ -48,12 +60,14 @@ const MarketLogos = ({ market }: { market: Serum3Market | PerpMarket }) => {
|
|||
<Image
|
||||
alt=""
|
||||
className="z-10 rounded-full drop-shadow-md"
|
||||
width="20"
|
||||
height="20"
|
||||
width={small ? '16' : '20'}
|
||||
height={small ? '16' : '20'}
|
||||
src={logos.baseLogoURI}
|
||||
/>
|
||||
) : (
|
||||
<QuestionMarkCircleIcon className="h-5 w-5 text-th-fgd-3" />
|
||||
<QuestionMarkCircleIcon
|
||||
className={`${small ? 'h-4 w-4' : 'h-5 w-5'} text-th-fgd-3`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="absolute right-0 top-0">
|
||||
|
@ -61,12 +75,14 @@ const MarketLogos = ({ market }: { market: Serum3Market | PerpMarket }) => {
|
|||
<Image
|
||||
alt=""
|
||||
className="rounded-full opacity-60"
|
||||
width="20"
|
||||
height="20"
|
||||
width={small ? '16' : '20'}
|
||||
height={small ? '16' : '20'}
|
||||
src={logos.quoteLogoURI}
|
||||
/>
|
||||
) : market instanceof PerpMarket ? null : (
|
||||
<QuestionMarkCircleIcon className="h-5 w-5 text-th-fgd-3" />
|
||||
<QuestionMarkCircleIcon
|
||||
className={`${small ? 'h-4 w-4' : 'h-5 w-5'} text-th-fgd-3`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
import ChartRangeButtons from '@components/shared/ChartRangeButtons'
|
||||
import FavoriteMarketButton from '@components/shared/FavoriteMarketButton'
|
||||
import TabUnderline from '@components/shared/TabUnderline'
|
||||
import { Popover } from '@headlessui/react'
|
||||
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
import Link from 'next/link'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { DEFAULT_MARKET_NAME } from 'utils/constants'
|
||||
import MarketLogos from './MarketLogos'
|
||||
|
||||
const MarketSelectDropdown = () => {
|
||||
const { selectedMarket } = useSelectedMarket()
|
||||
const serumMarkets = mangoStore((s) => s.serumMarkets)
|
||||
const perpMarkets = mangoStore((s) => s.perpMarkets)
|
||||
const [activeTab, setActiveTab] = useState('perp')
|
||||
const [spotBaseFilter, setSpotBaseFilter] = useState('All')
|
||||
|
||||
const spotBaseTokens: string[] = useMemo(() => {
|
||||
if (serumMarkets.length) {
|
||||
const baseTokens: string[] = []
|
||||
serumMarkets.map((m) => {
|
||||
const base = m.name.split('/')[1]
|
||||
if (!baseTokens.includes(base)) {
|
||||
baseTokens.push(base)
|
||||
}
|
||||
})
|
||||
return baseTokens
|
||||
}
|
||||
return []
|
||||
}, [serumMarkets])
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
{({ open }) => (
|
||||
<div
|
||||
className="relative flex flex-col overflow-visible"
|
||||
id="trade-step-one"
|
||||
>
|
||||
<Popover.Button className="default-transition flex w-full items-center justify-between hover:text-th-primary">
|
||||
<>
|
||||
{selectedMarket ? <MarketLogos market={selectedMarket} /> : null}
|
||||
</>
|
||||
<div className="text-xl font-bold text-th-fgd-1 md:text-base">
|
||||
{selectedMarket?.name || DEFAULT_MARKET_NAME}
|
||||
</div>
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
open ? 'rotate-180' : 'rotate-360'
|
||||
} mt-0.5 ml-2 h-6 w-6 flex-shrink-0 text-th-fgd-3`}
|
||||
/>
|
||||
</Popover.Button>
|
||||
<Popover.Panel className="absolute -left-5 top-[46px] z-50 mr-4 w-screen bg-th-bkg-2 pb-2 pt-4 sm:w-72 md:top-[37px]">
|
||||
<TabUnderline
|
||||
activeValue={activeTab}
|
||||
onChange={(v) => setActiveTab(v)}
|
||||
small
|
||||
values={['perp', 'spot']}
|
||||
/>
|
||||
{activeTab === 'spot' ? (
|
||||
serumMarkets?.length ? (
|
||||
<>
|
||||
<div className="mb-2 w-56 px-4">
|
||||
<ChartRangeButtons
|
||||
activeValue={spotBaseFilter}
|
||||
values={['All', ...spotBaseTokens]}
|
||||
onChange={(v) => setSpotBaseFilter(v)}
|
||||
/>
|
||||
</div>
|
||||
{serumMarkets
|
||||
.filter((mkt) => {
|
||||
if (spotBaseFilter === 'All') {
|
||||
return mkt
|
||||
} else {
|
||||
return mkt.name.split('/')[1] === spotBaseFilter
|
||||
}
|
||||
})
|
||||
.map((m) => {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center justify-between py-2 px-4"
|
||||
key={m.publicKey.toString()}
|
||||
>
|
||||
<Link
|
||||
href={{
|
||||
pathname: '/trade',
|
||||
query: { name: m.name },
|
||||
}}
|
||||
shallow={true}
|
||||
>
|
||||
<div className="default-transition flex items-center hover:cursor-pointer hover:text-th-primary">
|
||||
<MarketLogos market={m} />
|
||||
<span
|
||||
className={
|
||||
m.name === selectedMarket?.name
|
||||
? 'text-th-primary'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{m.name}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
<FavoriteMarketButton market={m} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
) : null
|
||||
) : null}
|
||||
{activeTab === 'perp'
|
||||
? perpMarkets?.length
|
||||
? perpMarkets.map((m) => {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center justify-between py-2 px-4"
|
||||
key={m.publicKey.toString()}
|
||||
>
|
||||
<Link
|
||||
href={{
|
||||
pathname: '/trade',
|
||||
query: { name: m.name },
|
||||
}}
|
||||
shallow={true}
|
||||
>
|
||||
<div className="default-transition flex items-center hover:cursor-pointer hover:bg-th-bkg-2">
|
||||
<MarketLogos market={m} />
|
||||
<span
|
||||
className={
|
||||
m.name === selectedMarket?.name
|
||||
? 'text-th-primary'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{m.name}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
<FavoriteMarketButton market={m} />
|
||||
</div>
|
||||
)
|
||||
})
|
||||
: null
|
||||
: null}
|
||||
</Popover.Panel>
|
||||
</div>
|
||||
)}
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
export default MarketSelectDropdown
|
|
@ -25,19 +25,26 @@ import {
|
|||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
import { INITIAL_ANIMATION_SETTINGS } from '@components/settings/AnimationSettings'
|
||||
|
||||
function decodeBookL2(
|
||||
export const decodeBookL2 = (book: SpotOrderBook | BookSide): number[][] => {
|
||||
const depth = 40
|
||||
if (book instanceof SpotOrderBook) {
|
||||
book.getL2(depth).map(([price, size]) => [price, size])
|
||||
} else if (book instanceof BookSide) {
|
||||
return book.getL2Ui(depth)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
function decodeBook(
|
||||
client: MangoClient,
|
||||
market: Market | PerpMarket,
|
||||
accInfo: AccountInfo<Buffer>,
|
||||
side: 'bids' | 'asks'
|
||||
): number[][] {
|
||||
if (market && accInfo?.data) {
|
||||
const depth = 40
|
||||
): SpotOrderBook | BookSide {
|
||||
if (market instanceof Market) {
|
||||
const book = SpotOrderBook.decode(market, accInfo.data)
|
||||
return book.getL2(depth).map(([price, size]) => [price, size])
|
||||
} else if (market instanceof PerpMarket) {
|
||||
// FIXME: Review the null being passed here
|
||||
return book
|
||||
} else {
|
||||
const decodedAcc = client.program.coder.accounts.decode(
|
||||
'bookSide',
|
||||
accInfo.data
|
||||
|
@ -48,11 +55,9 @@ function decodeBookL2(
|
|||
side === 'bids' ? BookSideType.bids : BookSideType.asks,
|
||||
decodedAcc
|
||||
)
|
||||
return book.getL2Ui(depth)
|
||||
return book
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
// export function decodeBook(
|
||||
// market: Market,
|
||||
|
@ -327,14 +332,10 @@ const Orderbook = () => {
|
|||
if (bidsPk) {
|
||||
connection.getAccountInfo(bidsPk).then((info) => {
|
||||
if (!info) return
|
||||
const decodedBook = decodeBook(client, market, info, 'bids')
|
||||
set((state) => {
|
||||
// state.accountInfos[bidsPk.toString()] = info
|
||||
state.selectedMarket.orderbook.bids = decodeBookL2(
|
||||
client,
|
||||
market,
|
||||
info,
|
||||
'bids'
|
||||
)
|
||||
state.selectedMarket.bidsAccount = decodedBook
|
||||
state.selectedMarket.orderbook.bids = decodeBookL2(decodedBook)
|
||||
})
|
||||
})
|
||||
console.log('bidsPk', bidsPk)
|
||||
|
@ -347,15 +348,10 @@ const Orderbook = () => {
|
|||
previousBidInfo.lamports !== info.lamports
|
||||
) {
|
||||
previousBidInfo = info
|
||||
// info['parsed'] = decodeBook(serum3MarketExternal, info)
|
||||
const decodedBook = decodeBook(client, market, info, 'bids')
|
||||
set((state) => {
|
||||
// state.accountInfos[bidsPk.toString()] = info
|
||||
state.selectedMarket.orderbook.bids = decodeBookL2(
|
||||
client,
|
||||
market,
|
||||
info,
|
||||
'bids'
|
||||
)
|
||||
state.selectedMarket.bidsAccount = decodedBook
|
||||
state.selectedMarket.orderbook.bids = decodeBookL2(decodedBook)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -367,14 +363,10 @@ const Orderbook = () => {
|
|||
if (asksPk) {
|
||||
connection.getAccountInfo(asksPk).then((info) => {
|
||||
if (!info) return
|
||||
const decodedBook = decodeBook(client, market, info, 'asks')
|
||||
set((state) => {
|
||||
// state.accountInfos[bidsPk.toString()] = info
|
||||
state.selectedMarket.orderbook.asks = decodeBookL2(
|
||||
client,
|
||||
market,
|
||||
info,
|
||||
'bids'
|
||||
)
|
||||
state.selectedMarket.asksAccount = decodedBook
|
||||
state.selectedMarket.orderbook.asks = decodeBookL2(decodedBook)
|
||||
})
|
||||
})
|
||||
askSubscriptionId = connection.onAccountChange(
|
||||
|
@ -386,15 +378,10 @@ const Orderbook = () => {
|
|||
previousAskInfo.lamports !== info.lamports
|
||||
) {
|
||||
previousAskInfo = info
|
||||
// info['parsed'] = decodeBook(serum3MarketExternal, info)
|
||||
const decodedBook = decodeBook(client, market, info, 'asks')
|
||||
set((state) => {
|
||||
// state.accountInfos[asksPk.toString()] = info
|
||||
state.selectedMarket.orderbook.asks = decodeBookL2(
|
||||
client,
|
||||
market,
|
||||
info,
|
||||
'asks'
|
||||
)
|
||||
state.selectedMarket.asksAccount = decodedBook
|
||||
state.selectedMarket.orderbook.asks = decodeBookL2(decodedBook)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import { BookSide, PerpMarket } from '@blockworks-foundation/mango-v4'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||||
|
||||
const PerpFundingRate = () => {
|
||||
const { selectedMarket } = useSelectedMarket()
|
||||
const bids = mangoStore((s) => s.selectedMarket.bidsAccount)
|
||||
const asks = mangoStore((s) => s.selectedMarket.asksAccount)
|
||||
|
||||
return (
|
||||
<div className="font-mono text-xs text-th-fgd-2">
|
||||
{selectedMarket instanceof PerpMarket &&
|
||||
bids instanceof BookSide &&
|
||||
asks instanceof BookSide
|
||||
? selectedMarket.getCurrentFundingRate(bids, asks)
|
||||
: '-'}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PerpFundingRate
|
|
@ -14,6 +14,7 @@ import MobileTradeAdvancedPage from './MobileTradeAdvancedPage'
|
|||
import OrderbookAndTrades from './OrderbookAndTrades'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import TradeOnboardingTour from '@components/tours/TradeOnboardingTour'
|
||||
import FavoriteMarketsBar from './FavoriteMarketsBar'
|
||||
|
||||
const TradingViewChart = dynamic(() => import('./TradingViewChart'), {
|
||||
ssr: false,
|
||||
|
@ -149,6 +150,7 @@ const TradeAdvancedPage = () => {
|
|||
<MobileTradeAdvancedPage />
|
||||
) : (
|
||||
<>
|
||||
<FavoriteMarketsBar />
|
||||
<ResponsiveGridLayout
|
||||
// layouts={savedLayouts ? savedLayouts : defaultLayouts}
|
||||
layouts={defaultLayouts}
|
||||
|
|
|
@ -86,7 +86,7 @@ const Token: NextPage = () => {
|
|||
const { isLoading: loadingPrices, data: coingeckoPrices } = useCoingecko()
|
||||
const [chartData, setChartData] = useState<{ prices: any[] } | null>(null)
|
||||
const [loadChartData, setLoadChartData] = useState(true)
|
||||
const [daysToShow, setDaysToShow] = useState<number>(1)
|
||||
const [daysToShow, setDaysToShow] = useState<string>('1')
|
||||
const [animationSettings] = useLocalStorageState(
|
||||
ANIMATION_SETTINGS_KEY,
|
||||
INITIAL_ANIMATION_SETTINGS
|
||||
|
@ -177,11 +177,11 @@ const Token: NextPage = () => {
|
|||
} = coingeckoData ? coingeckoData.market_data : DEFAULT_COINGECKO_VALUES
|
||||
|
||||
const loadingChart = useMemo(() => {
|
||||
return daysToShow == 1 ? loadingPrices : loadChartData
|
||||
return daysToShow == '1' ? loadingPrices : loadChartData
|
||||
}, [loadChartData, loadingPrices])
|
||||
|
||||
const coingeckoTokenPrices = useMemo(() => {
|
||||
if (daysToShow === 1 && coingeckoPrices.length && bank) {
|
||||
if (daysToShow === '1' && coingeckoPrices.length && bank) {
|
||||
const tokenPriceData = coingeckoPrices.find(
|
||||
(asset) => asset.symbol === bank.name
|
||||
)
|
||||
|
@ -197,8 +197,8 @@ const Token: NextPage = () => {
|
|||
return []
|
||||
}, [coingeckoPrices, bank, daysToShow, chartData, loadingChart])
|
||||
|
||||
const handleDaysToShow = async (days: number) => {
|
||||
if (days !== 1) {
|
||||
const handleDaysToShow = async (days: string) => {
|
||||
if (days !== '1') {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://api.coingecko.com/api/v3/coins/${coingeckoId}/market_chart?vs_currency=usd&days=${days}`
|
||||
|
@ -406,12 +406,12 @@ const Token: NextPage = () => {
|
|||
<ChartRangeButtons
|
||||
activeValue={daysToShow}
|
||||
names={['24H', '7D', '30D']}
|
||||
values={[1, 7, 30]}
|
||||
values={['1', '7', '30']}
|
||||
onChange={(v) => handleDaysToShow(v)}
|
||||
/>
|
||||
</div>
|
||||
<PriceChart
|
||||
daysToShow={daysToShow}
|
||||
daysToShow={parseInt(daysToShow)}
|
||||
prices={coingeckoTokenPrices}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
"liquidation": "Liquidation",
|
||||
"liquidations": "Liquidations",
|
||||
"liquidation-details": "Liquidation Details",
|
||||
"perps": "Perps",
|
||||
"perp_trade": "Perp",
|
||||
"reset-filters": "Reset Filters",
|
||||
"select-tokens": "Select Tokens",
|
||||
"swap": "Swap",
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
"liquidation": "Liquidation",
|
||||
"liquidations": "Liquidations",
|
||||
"liquidation-details": "Liquidation Details",
|
||||
"perps": "Perps",
|
||||
"perp_trade": "Perp",
|
||||
"reset-filters": "Reset Filters",
|
||||
"select-tokens": "Select Tokens",
|
||||
"swap": "Swap",
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
"liquidation": "Liquidation",
|
||||
"liquidations": "Liquidations",
|
||||
"liquidation-details": "Liquidation Details",
|
||||
"perps": "Perps",
|
||||
"perp_trade": "Perp",
|
||||
"reset-filters": "Reset Filters",
|
||||
"select-tokens": "Select Tokens",
|
||||
"swap": "Swap",
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
"liquidation": "Liquidation",
|
||||
"liquidations": "Liquidations",
|
||||
"liquidation-details": "Liquidation Details",
|
||||
"perps": "Perps",
|
||||
"perp_trade": "Perp",
|
||||
"reset-filters": "Reset Filters",
|
||||
"select-tokens": "Select Tokens",
|
||||
"swap": "Swap",
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
"liquidation": "Liquidation",
|
||||
"liquidations": "Liquidations",
|
||||
"liquidation-details": "Liquidation Details",
|
||||
"perps": "Perps",
|
||||
"perp_trade": "Perp",
|
||||
"reset-filters": "Reset Filters",
|
||||
"select-tokens": "Select Tokens",
|
||||
"swap": "Swap",
|
||||
|
|
|
@ -5,6 +5,7 @@ import { subscribeWithSelector } from 'zustand/middleware'
|
|||
import { AnchorProvider, 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 as SpotOrderBook } from '@project-serum/serum'
|
||||
import { Wallet as WalletAdapter } from '@solana/wallet-adapter-react'
|
||||
import {
|
||||
MangoClient,
|
||||
|
@ -15,6 +16,7 @@ import {
|
|||
Bank,
|
||||
PerpOrder,
|
||||
PerpPosition,
|
||||
BookSide,
|
||||
} from '@blockworks-foundation/mango-v4'
|
||||
|
||||
import EmptyWallet from '../utils/wallet'
|
||||
|
@ -194,6 +196,8 @@ export type MangoStore = {
|
|||
name: string
|
||||
current: Serum3Market | PerpMarket | undefined
|
||||
fills: any
|
||||
bidsAccount: BookSide | SpotOrderBook | undefined
|
||||
asksAccount: BookSide | SpotOrderBook | undefined
|
||||
orderbook: Orderbook
|
||||
}
|
||||
serumMarkets: Serum3Market[]
|
||||
|
@ -295,6 +299,8 @@ const mangoStore = create<MangoStore>()(
|
|||
name: DEFAULT_MARKET_NAME,
|
||||
current: undefined,
|
||||
fills: [],
|
||||
bidsAccount: undefined,
|
||||
asksAccount: undefined,
|
||||
orderbook: {
|
||||
bids: [],
|
||||
asks: [],
|
||||
|
|
|
@ -48,3 +48,5 @@ export const GRID_LAYOUT_KEY = 'savedLayouts-0.1'
|
|||
export const NOTIFICATION_POSITION_KEY = 'notificationPosition'
|
||||
|
||||
export const MIN_SOL_BALANCE = 0.04
|
||||
|
||||
export const FAVORITE_MARKETS_KEY = 'favoriteMarkets'
|
||||
|
|
Loading…
Reference in New Issue