Merge branch 'main' into accept-terms
This commit is contained in:
commit
08fcae739c
|
@ -28,6 +28,7 @@ import HealthHeart from './account/HealthHeart'
|
|||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { IconButton } from './shared/Button'
|
||||
import LeaderboardIcon from './icons/LeaderboardIcon'
|
||||
|
||||
const SideNav = ({ collapsed }: { collapsed: boolean }) => {
|
||||
const { t } = useTranslation(['common', 'search'])
|
||||
|
@ -122,14 +123,15 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
|
|||
icon={<EllipsisHorizontalIcon className="h-5 w-5" />}
|
||||
title={t('more')}
|
||||
>
|
||||
{/* <MenuItem
|
||||
active={pathname === '/fees'}
|
||||
collapsed={false}
|
||||
icon={<ReceiptTaxIcon className="h-5 w-5" />}
|
||||
title={t('fees')}
|
||||
pagePath="/fees"
|
||||
hideIconBg
|
||||
/> */}
|
||||
<MenuItem
|
||||
active={pathname === '/leaderboard'}
|
||||
collapsed={false}
|
||||
icon={<LeaderboardIcon className="h-5 w-5" />}
|
||||
title={t('leaderboard')}
|
||||
pagePath="/leaderboard"
|
||||
hideIconBg
|
||||
showTooltip={false}
|
||||
/>
|
||||
<MenuItem
|
||||
active={pathname === '/search'}
|
||||
collapsed={false}
|
||||
|
|
|
@ -126,13 +126,17 @@ const AccountPage = () => {
|
|||
data: fundingData,
|
||||
isLoading: loadingFunding,
|
||||
isFetching: fetchingFunding,
|
||||
} = useQuery(['funding'], () => fetchFundingTotals(mangoAccountAddress), {
|
||||
cacheTime: 1000 * 60 * 10,
|
||||
staleTime: 1000 * 60,
|
||||
retry: 3,
|
||||
refetchOnWindowFocus: false,
|
||||
enabled: !!mangoAccountAddress,
|
||||
})
|
||||
} = useQuery(
|
||||
['funding', mangoAccountAddress],
|
||||
() => fetchFundingTotals(mangoAccountAddress),
|
||||
{
|
||||
cacheTime: 1000 * 60 * 10,
|
||||
staleTime: 1000 * 60,
|
||||
retry: 3,
|
||||
refetchOnWindowFocus: false,
|
||||
enabled: !!mangoAccountAddress,
|
||||
}
|
||||
)
|
||||
|
||||
const oneDayPerformanceData: PerformanceDataItem[] | [] = useMemo(() => {
|
||||
if (!performanceData || !performanceData.length) return []
|
||||
|
|
|
@ -1,133 +1,39 @@
|
|||
import { EXPLORERS } from '@components/settings/PreferredExplorerSettings'
|
||||
import { IconButton } from '@components/shared/Button'
|
||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||
import { ArrowLeftIcon } from '@heroicons/react/20/solid'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import dayjs from 'dayjs'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Image from 'next/legacy/image'
|
||||
import { useState } from 'react'
|
||||
import { PREFERRED_EXPLORER_KEY } from 'utils/constants'
|
||||
import { useEffect, useState } from 'react'
|
||||
import ActivityFeedTable from './ActivityFeedTable'
|
||||
import { LiquidationActivity } from 'types'
|
||||
import LiquidationDetails from './LiquidationDetails'
|
||||
|
||||
const ActivityFeed = () => {
|
||||
const activityFeed = mangoStore((s) => s.activityFeed.feed)
|
||||
const [showActivityDetail, setShowActivityDetail] =
|
||||
useState<LiquidationActivity>()
|
||||
const [scrollPosition, setScrollPosition] = useState(0)
|
||||
|
||||
const handleShowActivityDetails = (activity: LiquidationActivity) => {
|
||||
setShowActivityDetail(activity)
|
||||
setScrollPosition(window.scrollY)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (scrollPosition && !showActivityDetail) {
|
||||
window.scroll(0, scrollPosition)
|
||||
}
|
||||
}, [scrollPosition, showActivityDetail])
|
||||
|
||||
return !showActivityDetail ? (
|
||||
<ActivityFeedTable
|
||||
activityFeed={activityFeed}
|
||||
handleShowActivityDetails={handleShowActivityDetails}
|
||||
/>
|
||||
) : (
|
||||
<ActivityDetails
|
||||
activity={showActivityDetail}
|
||||
setShowActivityDetail={setShowActivityDetail}
|
||||
/>
|
||||
<div className="px-6">
|
||||
<LiquidationDetails
|
||||
activity={showActivityDetail}
|
||||
setShowActivityDetail={setShowActivityDetail}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ActivityFeed
|
||||
|
||||
const ActivityDetails = ({
|
||||
activity,
|
||||
setShowActivityDetail,
|
||||
}: {
|
||||
activity: LiquidationActivity
|
||||
setShowActivityDetail: (x: LiquidationActivity | undefined) => void
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'activity', 'settings'])
|
||||
const [preferredExplorer] = useLocalStorageState(
|
||||
PREFERRED_EXPLORER_KEY,
|
||||
EXPLORERS[0]
|
||||
)
|
||||
const { block_datetime, activity_type } = activity
|
||||
const {
|
||||
asset_amount,
|
||||
asset_price,
|
||||
asset_symbol,
|
||||
liab_amount,
|
||||
liab_price,
|
||||
liab_symbol,
|
||||
signature,
|
||||
} = activity.activity_details
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center p-6">
|
||||
<IconButton
|
||||
className="mr-4"
|
||||
onClick={() => setShowActivityDetail(undefined)}
|
||||
>
|
||||
<ArrowLeftIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
<h2 className="text-lg">{t('activity:liquidation-details')}</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 px-6 md:grid-cols-2">
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 text-sm">{t('date')}</p>
|
||||
<p className="text-th-fgd-1">
|
||||
{dayjs(block_datetime).format('ddd D MMM')}
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{dayjs(block_datetime).format('h:mma')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 text-sm">{t('activity:liquidation-type')}</p>
|
||||
<p className="text-th-fgd-1">
|
||||
{activity_type === 'liquidate_token_with_token'
|
||||
? t('spot')
|
||||
: t('perp')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 text-sm">{t('activity:asset-liquidated')}</p>
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
<FormatNumericValue value={asset_amount} />{' '}
|
||||
<span className="font-body">{asset_symbol}</span>
|
||||
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}
|
||||
<FormatNumericValue value={asset_price} isUsd />
|
||||
</p>
|
||||
<p className="font-mono text-xs text-th-fgd-3">
|
||||
<FormatNumericValue value={asset_price * asset_amount} isUsd />
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 text-sm">{t('activity:asset-returned')}</p>
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
<FormatNumericValue value={liab_amount} />{' '}
|
||||
<span className="font-body">{liab_symbol}</span>
|
||||
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}
|
||||
<FormatNumericValue value={liab_price} isUsd />
|
||||
</p>
|
||||
<p className="font-mono text-xs text-th-fgd-3">
|
||||
<FormatNumericValue value={liab_price * liab_amount} isUsd />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-3 mt-8 flex justify-center border-y border-th-bkg-3 py-3">
|
||||
<a
|
||||
className="default-transition flex items-center text-th-fgd-2 hover:text-th-fgd-3"
|
||||
href={`${preferredExplorer.url}${signature}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/explorer-logos/${preferredExplorer.name}.png`}
|
||||
/>
|
||||
<span className="ml-2 text-base">{t('view-transaction')}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { EXPLORERS } from '@components/settings/PreferredExplorerSettings'
|
||||
import { IconButton, LinkButton } from '@components/shared/Button'
|
||||
import { LinkButton } from '@components/shared/Button'
|
||||
import ConnectEmptyState from '@components/shared/ConnectEmptyState'
|
||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||
import SheenLoader from '@components/shared/SheenLoader'
|
||||
import SideBadge from '@components/shared/SideBadge'
|
||||
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import { Disclosure, Transition } from '@headlessui/react'
|
||||
import PerpSideBadge from '@components/trade/PerpSideBadge'
|
||||
import { Transition } from '@headlessui/react'
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
|
@ -22,11 +22,12 @@ import useMangoAccount from 'hooks/useMangoAccount'
|
|||
import { useViewport } from 'hooks/useViewport'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Image from 'next/legacy/image'
|
||||
import { Fragment, useCallback, useState } from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { ActivityFeed, isLiquidationFeedItem, LiquidationActivity } from 'types'
|
||||
import { PAGINATION_PAGE_LENGTH, PREFERRED_EXPLORER_KEY } from 'utils/constants'
|
||||
import { formatNumericValue } from 'utils/numbers'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import LiquidationDetails from './LiquidationDetails'
|
||||
|
||||
const formatFee = (value: number) => {
|
||||
return value.toLocaleString(undefined, {
|
||||
|
@ -55,6 +56,43 @@ const getFee = (activity: any, mangoAccountAddress: string) => {
|
|||
const { fee_cost, quote_symbol } = activity.activity_details
|
||||
fee = { value: fee_cost, symbol: quote_symbol }
|
||||
}
|
||||
if (activity_type === 'liquidate_token_with_token') {
|
||||
const { side, liab_amount, liab_symbol, asset_amount, asset_price } =
|
||||
activity.activity_details
|
||||
if (side === 'liqee') {
|
||||
fee = {
|
||||
value: formatNumericValue(
|
||||
Math.abs(liab_amount) - Math.abs(asset_amount * asset_price)
|
||||
),
|
||||
symbol: liab_symbol,
|
||||
}
|
||||
} else {
|
||||
fee = {
|
||||
value: formatNumericValue(
|
||||
Math.abs(asset_amount * asset_price) - Math.abs(liab_amount)
|
||||
),
|
||||
symbol: liab_symbol,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (activity_type === 'liquidate_perp_base_position_or_positive_pnl') {
|
||||
const { base_transfer, price, quote_transfer } = activity.activity_details
|
||||
if (base_transfer > 0) {
|
||||
fee = {
|
||||
value: formatNumericValue(
|
||||
Math.abs(base_transfer * price) - Math.abs(quote_transfer)
|
||||
),
|
||||
symbol: 'USDC',
|
||||
}
|
||||
} else {
|
||||
fee = {
|
||||
value: formatNumericValue(
|
||||
Math.abs(quote_transfer) - Math.abs(base_transfer * price)
|
||||
),
|
||||
symbol: 'USDC',
|
||||
}
|
||||
}
|
||||
}
|
||||
return fee
|
||||
}
|
||||
|
||||
|
@ -79,6 +117,23 @@ const getCreditAndDebit = (activity: any) => {
|
|||
debit = { value: formatNumericValue(liab_amount), symbol: liab_symbol }
|
||||
}
|
||||
}
|
||||
if (activity_type === 'liquidate_perp_base_position_or_positive_pnl') {
|
||||
const { base_transfer, perp_market_name, quote_transfer } =
|
||||
activity.activity_details
|
||||
if (base_transfer > 0) {
|
||||
credit = {
|
||||
value: formatNumericValue(base_transfer),
|
||||
symbol: perp_market_name,
|
||||
}
|
||||
debit = { value: formatNumericValue(quote_transfer), symbol: 'USDC' }
|
||||
} else {
|
||||
credit = { value: formatNumericValue(quote_transfer), symbol: 'USDC' }
|
||||
debit = {
|
||||
value: formatNumericValue(base_transfer),
|
||||
symbol: perp_market_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (activity_type === 'deposit') {
|
||||
const { symbol, quantity } = activity.activity_details
|
||||
credit = { value: formatNumericValue(quantity), symbol }
|
||||
|
@ -131,12 +186,24 @@ const getValue = (activity: any) => {
|
|||
const { activity_type } = activity
|
||||
let value = 0
|
||||
if (activity_type === 'liquidate_token_with_token') {
|
||||
const { side, liab_amount, liab_price, asset_amount, asset_price } =
|
||||
const { asset_amount, asset_price } = activity.activity_details
|
||||
value = Math.abs(asset_amount * asset_price)
|
||||
}
|
||||
if (activity_type === 'liquidate_perp_base_position_or_positive_pnl') {
|
||||
const { base_transfer, price, quote_transfer, side } =
|
||||
activity.activity_details
|
||||
if (side === 'liqee') {
|
||||
value = asset_amount * asset_price
|
||||
if (base_transfer > 0) {
|
||||
if (side === 'liqee') {
|
||||
value = Math.abs(quote_transfer)
|
||||
} else {
|
||||
value = Math.abs(base_transfer * price)
|
||||
}
|
||||
} else {
|
||||
value = liab_amount * liab_price
|
||||
if (side === 'liqee') {
|
||||
value = Math.abs(base_transfer * price)
|
||||
} else {
|
||||
value = Math.abs(quote_transfer)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (activity_type === 'deposit' || activity_type === 'withdraw') {
|
||||
|
@ -218,8 +285,6 @@ const ActivityFeedTable = ({
|
|||
{activityFeed.map((activity, index: number) => {
|
||||
const { activity_type, block_datetime } = activity
|
||||
const { signature } = activity.activity_details
|
||||
const isLiquidation =
|
||||
activity_type === 'liquidate_token_with_token'
|
||||
const isOpenbook = activity_type === 'openbook_trade'
|
||||
const amounts = getCreditAndDebit(activity)
|
||||
const value = getValue(activity)
|
||||
|
@ -228,7 +293,7 @@ const ActivityFeedTable = ({
|
|||
<TrBody
|
||||
key={signature + index}
|
||||
className={`default-transition text-sm hover:bg-th-bkg-2 ${
|
||||
isLiquidation ? 'cursor-pointer' : ''
|
||||
isLiquidationFeedItem(activity) ? 'cursor-pointer' : ''
|
||||
}`}
|
||||
onClick={
|
||||
isLiquidationFeedItem(activity)
|
||||
|
@ -271,7 +336,8 @@ const ActivityFeedTable = ({
|
|||
className={`text-right font-mono ${
|
||||
activity_type === 'swap' ||
|
||||
activity_type === 'perp_trade' ||
|
||||
isOpenbook
|
||||
isOpenbook ||
|
||||
isLiquidationFeedItem(activity)
|
||||
? 'text-th-fgd-2'
|
||||
: value >= 0
|
||||
? 'text-th-up'
|
||||
|
@ -281,13 +347,14 @@ const ActivityFeedTable = ({
|
|||
{value > 0 &&
|
||||
activity_type !== 'swap' &&
|
||||
activity_type !== 'perp_trade' &&
|
||||
!isOpenbook
|
||||
!isOpenbook &&
|
||||
!isLiquidationFeedItem(activity)
|
||||
? '+'
|
||||
: ''}
|
||||
<FormatNumericValue value={value} isUsd />
|
||||
</Td>
|
||||
<Td>
|
||||
{activity_type !== 'liquidate_token_with_token' ? (
|
||||
{!isLiquidationFeedItem(activity) ? (
|
||||
<div className="flex items-center justify-end">
|
||||
<Tooltip
|
||||
content={`View on ${t(
|
||||
|
@ -345,7 +412,7 @@ const ActivityFeedTable = ({
|
|||
{activityFeed.length &&
|
||||
activityFeed.length % PAGINATION_PAGE_LENGTH === 0 ? (
|
||||
<div className="flex justify-center py-6">
|
||||
<LinkButton onClick={handleShowMore}>Show More</LinkButton>
|
||||
<LinkButton onClick={handleShowMore}>{t('show-more')}</LinkButton>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
|
@ -371,120 +438,146 @@ const MobileActivityFeedItem = ({
|
|||
getValue: (x: any) => number
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'activity'])
|
||||
const [expandActivityDetails, setExpandActivityDetails] = useState(false)
|
||||
const [preferredExplorer] = useLocalStorageState(
|
||||
PREFERRED_EXPLORER_KEY,
|
||||
EXPLORERS[0]
|
||||
)
|
||||
const { activity_type, block_datetime } = activity
|
||||
const { signature } = activity.activity_details
|
||||
const isLiquidation = activity_type === 'liquidate_token_with_token'
|
||||
const isSwap = activity_type === 'swap'
|
||||
const isOpenbook = activity_type === 'openbook_trade'
|
||||
const isPerp = activity_type === 'perp_trade'
|
||||
const value = getValue(activity)
|
||||
|
||||
return (
|
||||
<div key={signature} className="border-b border-th-bkg-3 p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-th-fgd-1">
|
||||
{dayjs(block_datetime).format('ddd D MMM')}
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{dayjs(block_datetime).format('h:mma')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div key={signature} className="border-b border-th-bkg-3">
|
||||
{isLiquidationFeedItem(activity) ? (
|
||||
<Disclosure>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button className="w-full p-4 text-left focus:outline-none">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-th-fgd-1">
|
||||
{dayjs(block_datetime).format('ddd D MMM')}
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{dayjs(block_datetime).format('h:mma')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-6 pr-2">
|
||||
<div>
|
||||
<p className="text-right text-xs">
|
||||
{t(`activity:${activity_type}`)}
|
||||
</p>
|
||||
<p className="text-right font-mono text-sm text-th-fgd-1">
|
||||
<FormatNumericValue value={value} isUsd />
|
||||
</p>
|
||||
</div>
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
open ? 'rotate-180' : 'rotate-360'
|
||||
} h-6 w-6 flex-shrink-0 text-th-fgd-3`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Disclosure.Button>
|
||||
<Transition
|
||||
enter="transition ease-in duration-200"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
>
|
||||
<Disclosure.Panel>
|
||||
<div className="border-t border-th-bkg-3 px-4 py-4">
|
||||
<LiquidationDetails activity={activity} />
|
||||
</div>
|
||||
</Disclosure.Panel>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
) : (
|
||||
<div className="flex items-center justify-between p-4">
|
||||
<div>
|
||||
<p className="text-right text-xs">
|
||||
{t(`activity:${activity_type}`)}
|
||||
<p className="text-sm text-th-fgd-1">
|
||||
{dayjs(block_datetime).format('ddd D MMM')}
|
||||
</p>
|
||||
<p className="text-right font-mono text-sm text-th-fgd-1">
|
||||
{isLiquidation ? (
|
||||
<FormatNumericValue value={value} isUsd />
|
||||
) : isSwap ? (
|
||||
<>
|
||||
<span className="mr-1">
|
||||
<FormatNumericValue
|
||||
value={activity.activity_details.swap_in_amount}
|
||||
decimals={6}
|
||||
/>
|
||||
</span>
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{activity.activity_details.swap_in_symbol}
|
||||
</span>
|
||||
<span className="mx-1 font-body text-th-fgd-3">for</span>
|
||||
<span className="mr-1">
|
||||
<FormatNumericValue
|
||||
value={activity.activity_details.swap_out_amount}
|
||||
decimals={6}
|
||||
/>
|
||||
</span>
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{activity.activity_details.swap_out_symbol}
|
||||
</span>
|
||||
</>
|
||||
) : isPerp ? (
|
||||
<>
|
||||
<span className="mr-1">
|
||||
{activity.activity_details.quantity}
|
||||
</span>
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{activity.activity_details.perp_market_name}
|
||||
</span>
|
||||
<span className="font-body">
|
||||
{' '}
|
||||
<PerpSideBadge
|
||||
basePosition={
|
||||
activity.activity_details.taker_side === 'bid' ? 1 : -1
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</>
|
||||
) : isOpenbook ? (
|
||||
<>
|
||||
{/* <span
|
||||
className={`mr-1 font-body ${
|
||||
activity.activity_details.side === 'buy'
|
||||
? 'text-th-up'
|
||||
: 'text-th-down'
|
||||
}`}
|
||||
>
|
||||
{activity.activity_details.side === 'buy' ? 'BUY' : 'SELL'}
|
||||
</span> */}
|
||||
<span className="mr-1">{activity.activity_details.size}</span>
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{activity.activity_details.base_symbol}
|
||||
</span>
|
||||
<span className="font-body">
|
||||
{' '}
|
||||
<SideBadge side={activity.activity_details.side} />
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="mr-1">
|
||||
{activity.activity_details.quantity}
|
||||
</span>
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{activity.activity_details.symbol}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{dayjs(block_datetime).format('h:mma')}
|
||||
</p>
|
||||
</div>
|
||||
{isLiquidation ? (
|
||||
<IconButton
|
||||
onClick={() => setExpandActivityDetails((prev) => !prev)}
|
||||
>
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
expandActivityDetails ? 'rotate-180' : 'rotate-360'
|
||||
} h-6 w-6 flex-shrink-0 text-th-fgd-1`}
|
||||
/>
|
||||
</IconButton>
|
||||
) : (
|
||||
<div className="flex items-center space-x-4">
|
||||
<div>
|
||||
<p className="text-right text-xs">
|
||||
{t(`activity:${activity_type}`)}
|
||||
</p>
|
||||
<p className="text-right font-mono text-sm text-th-fgd-1">
|
||||
{isSwap ? (
|
||||
<>
|
||||
<span className="mr-1">
|
||||
<FormatNumericValue
|
||||
value={activity.activity_details.swap_in_amount}
|
||||
decimals={6}
|
||||
/>
|
||||
</span>
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{activity.activity_details.swap_in_symbol}
|
||||
</span>
|
||||
<span className="mx-1 font-body text-th-fgd-3">for</span>
|
||||
<span className="mr-1">
|
||||
<FormatNumericValue
|
||||
value={activity.activity_details.swap_out_amount}
|
||||
decimals={6}
|
||||
/>
|
||||
</span>
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{activity.activity_details.swap_out_symbol}
|
||||
</span>
|
||||
</>
|
||||
) : isPerp ? (
|
||||
<>
|
||||
<span className="mr-1">
|
||||
{activity.activity_details.quantity}
|
||||
</span>
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{activity.activity_details.perp_market_name}
|
||||
</span>
|
||||
<span className="font-body">
|
||||
{' '}
|
||||
<PerpSideBadge
|
||||
basePosition={
|
||||
activity.activity_details.taker_side === 'bid'
|
||||
? 1
|
||||
: -1
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</>
|
||||
) : isOpenbook ? (
|
||||
<>
|
||||
<span className="mr-1">
|
||||
{activity.activity_details.size}
|
||||
</span>
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{activity.activity_details.base_symbol}
|
||||
</span>
|
||||
<span className="font-body">
|
||||
{' '}
|
||||
<SideBadge side={activity.activity_details.side} />
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="mr-1">
|
||||
{activity.activity_details.quantity}
|
||||
</span>
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{activity.activity_details.symbol}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href={`${preferredExplorer.url}${signature}`}
|
||||
target="_blank"
|
||||
|
@ -499,89 +592,9 @@ const MobileActivityFeedItem = ({
|
|||
/>
|
||||
</div>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Transition
|
||||
appear={true}
|
||||
show={expandActivityDetails}
|
||||
as={Fragment}
|
||||
enter="transition ease-in duration-200"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition ease-out"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="mt-4 grid grid-cols-2 gap-4 border-t border-th-bkg-3 pt-4">
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 text-sm">{t('activity:asset-liquidated')}</p>
|
||||
<p className="font-mono text-sm text-th-fgd-1">
|
||||
<FormatNumericValue
|
||||
value={activity.activity_details.asset_amount}
|
||||
/>{' '}
|
||||
<span className="font-body">
|
||||
{activity.activity_details.asset_symbol}
|
||||
</span>
|
||||
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}
|
||||
<FormatNumericValue
|
||||
value={activity.activity_details.asset_price}
|
||||
isUsd
|
||||
/>
|
||||
</p>
|
||||
<p className="font-mono text-xs text-th-fgd-3">
|
||||
<FormatNumericValue
|
||||
value={
|
||||
activity.activity_details.asset_price *
|
||||
activity.activity_details.asset_amount
|
||||
}
|
||||
isUsd
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 text-sm">{t('activity:asset-returned')}</p>
|
||||
<p className="font-mono text-sm text-th-fgd-1">
|
||||
<FormatNumericValue
|
||||
value={activity.activity_details.liab_amount}
|
||||
/>{' '}
|
||||
<span className="font-body">
|
||||
{activity.activity_details.liab_symbol}
|
||||
</span>
|
||||
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}
|
||||
<FormatNumericValue
|
||||
value={activity.activity_details.liab_price}
|
||||
isUsd
|
||||
/>
|
||||
</p>
|
||||
<p className="font-mono text-xs text-th-fgd-3">
|
||||
<FormatNumericValue
|
||||
value={
|
||||
activity.activity_details.liab_price *
|
||||
activity.activity_details.liab_amount
|
||||
}
|
||||
isUsd
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-2 flex justify-center pt-3">
|
||||
<a
|
||||
className="default-transition flex items-center text-sm text-th-fgd-1 hover:text-th-fgd-3"
|
||||
href={`${preferredExplorer.url}${signature}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/explorer-logos/${preferredExplorer.name}.png`}
|
||||
/>
|
||||
<span className="ml-2 text-base">{t('view-transaction')}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ const DEFAULT_ADVANCED_FILTERS = {
|
|||
const DEFAULT_PARAMS = [
|
||||
'deposit',
|
||||
'perp_trade',
|
||||
'liquidate_perp_base_position_or_positive_pnl',
|
||||
'liquidate_token_with_token',
|
||||
'openbook_trade',
|
||||
'swap',
|
||||
|
@ -68,7 +69,7 @@ const ActivityFilters = () => {
|
|||
}, [advancedFilters])
|
||||
|
||||
const queryParams = useMemo(() => {
|
||||
return !params.length || params.length === 6
|
||||
return !params.length || params.length === 7
|
||||
? advancedParamsString
|
||||
: `&activity-type=${params.toString()}${advancedParamsString}`
|
||||
}, [advancedParamsString, params])
|
||||
|
|
|
@ -26,12 +26,17 @@ const AssetsLiabilities = ({ isMobile }: { isMobile: boolean }) => {
|
|||
if (!group || !mangoAccount) return [0, 0, 0, 0]
|
||||
const assets = toUiDecimalsForQuote(mangoAccount.getAssetsValue(group))
|
||||
const liabs = toUiDecimalsForQuote(mangoAccount.getLiabsValue(group))
|
||||
const assetsRatio = (assets / (assets + liabs)) * 100
|
||||
const liabsRatio = 100 - assetsRatio
|
||||
let assetsRatio = 0
|
||||
let liabsRatio = 0
|
||||
if (assets && liabs) {
|
||||
assetsRatio = (assets / (assets + liabs)) * 100
|
||||
liabsRatio = 100 - assetsRatio
|
||||
}
|
||||
return [assets, assetsRatio, liabs, liabsRatio]
|
||||
}, [group, mangoAccount])
|
||||
|
||||
const chartData = useMemo(() => {
|
||||
if (!assetsValue && !liabsValue) return []
|
||||
return [
|
||||
{ name: 'assets', value: assetsValue },
|
||||
{ name: 'liabilities', value: liabsValue },
|
||||
|
@ -45,7 +50,7 @@ const AssetsLiabilities = ({ isMobile }: { isMobile: boolean }) => {
|
|||
|
||||
return (
|
||||
<div className="flex flex-col items-center pt-4 md:flex-row md:space-x-4">
|
||||
{mangoAccount ? (
|
||||
{mangoAccount && chartData.length ? (
|
||||
<PieChart width={size} height={size}>
|
||||
<Pie
|
||||
cursor="pointer"
|
||||
|
|
|
@ -0,0 +1,245 @@
|
|||
import { EXPLORERS } from '@components/settings/PreferredExplorerSettings'
|
||||
import { IconButton } from '@components/shared/Button'
|
||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||
import { ArrowLeftIcon } from '@heroicons/react/20/solid'
|
||||
import dayjs from 'dayjs'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Image from 'next/image'
|
||||
import { useMemo } from 'react'
|
||||
import {
|
||||
isPerpLiquidation,
|
||||
LiquidationActivity,
|
||||
SpotOrPerpLiquidationItem,
|
||||
} from 'types'
|
||||
import { PREFERRED_EXPLORER_KEY } from 'utils/constants'
|
||||
|
||||
const LiquidationDetails = ({
|
||||
activity,
|
||||
setShowActivityDetail,
|
||||
}: {
|
||||
activity: LiquidationActivity
|
||||
setShowActivityDetail?: (x: LiquidationActivity | undefined) => void
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'activity', 'settings'])
|
||||
const [preferredExplorer] = useLocalStorageState(
|
||||
PREFERRED_EXPLORER_KEY,
|
||||
EXPLORERS[0]
|
||||
)
|
||||
const { block_datetime } = activity
|
||||
|
||||
const getAssetLiquidatedReturned = (details: SpotOrPerpLiquidationItem) => {
|
||||
const assets = {
|
||||
liquidated: { amount: 0, symbol: '', value: 0 },
|
||||
returned: { amount: 0, symbol: '', value: 0 },
|
||||
}
|
||||
if (isPerpLiquidation(details)) {
|
||||
const { base_transfer, perp_market_name, price, quote_transfer } = details
|
||||
const isLiquidatorBase = base_transfer > 0 ? 1 : -1
|
||||
const isLiquidatorQuote = base_transfer > 0 ? -1 : 1
|
||||
const liquidatedAmount = Math.abs(base_transfer)
|
||||
const returnedAmount = Math.abs(quote_transfer)
|
||||
assets.liquidated.amount = liquidatedAmount
|
||||
assets.liquidated.value = liquidatedAmount * price * isLiquidatorBase
|
||||
assets.liquidated.symbol = perp_market_name
|
||||
assets.returned.amount = returnedAmount
|
||||
assets.returned.value = returnedAmount * isLiquidatorQuote
|
||||
assets.returned.symbol = 'USDC'
|
||||
} else {
|
||||
const {
|
||||
liab_amount,
|
||||
liab_price,
|
||||
liab_symbol,
|
||||
asset_amount,
|
||||
asset_price,
|
||||
asset_symbol,
|
||||
} = details
|
||||
assets.liquidated.amount = Math.abs(asset_amount)
|
||||
assets.liquidated.symbol = asset_symbol
|
||||
assets.liquidated.value = asset_amount * asset_price
|
||||
assets.returned.amount = Math.abs(liab_amount)
|
||||
assets.returned.symbol = liab_symbol
|
||||
assets.returned.value = liab_amount * liab_price
|
||||
}
|
||||
return assets
|
||||
}
|
||||
|
||||
const [
|
||||
assetLiquidated,
|
||||
assetReturned,
|
||||
assetLiquidatedSymbol,
|
||||
assetReturnedSymbol,
|
||||
liquidatedValue,
|
||||
returnedValue,
|
||||
fee,
|
||||
] = useMemo(() => {
|
||||
if (!activity) return [0, 0, '', '', 0, 0, 0]
|
||||
const values = getAssetLiquidatedReturned(activity.activity_details)
|
||||
const isNegativeFee = activity.activity_details.side === 'liqee' ? -1 : 1
|
||||
const fee =
|
||||
(Math.abs(values.liquidated.value) - Math.abs(values.returned.value)) *
|
||||
isNegativeFee
|
||||
return [
|
||||
values.liquidated.amount,
|
||||
values.returned.amount,
|
||||
values.liquidated.symbol,
|
||||
values.returned.symbol,
|
||||
values.liquidated.value,
|
||||
values.returned.value,
|
||||
fee,
|
||||
]
|
||||
}, [activity])
|
||||
|
||||
return (
|
||||
<div className="md:pb-10">
|
||||
{setShowActivityDetail ? (
|
||||
<div className="flex items-center py-6">
|
||||
<IconButton
|
||||
className="mr-4"
|
||||
onClick={() => setShowActivityDetail(undefined)}
|
||||
size="small"
|
||||
>
|
||||
<ArrowLeftIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
<h2 className="text-lg">{t('activity:liquidation-details')}</h2>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{isPerpLiquidation(activity.activity_details) ? (
|
||||
<>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 text-sm">{t('date')}</p>
|
||||
<p className="text-th-fgd-1">
|
||||
{dayjs(block_datetime).format('ddd D MMM')}
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{dayjs(block_datetime).format('h:mma')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 text-sm">{t('activity:liquidation-type')}</p>
|
||||
<p className="text-th-fgd-1">{t('perp')}</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 text-sm">{t('activity:asset-liquidated')}</p>
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
<FormatNumericValue value={assetLiquidated} />{' '}
|
||||
<span className="font-body">{assetLiquidatedSymbol}</span>
|
||||
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}
|
||||
<FormatNumericValue
|
||||
value={activity.activity_details.price}
|
||||
isUsd
|
||||
/>
|
||||
</p>
|
||||
<p className="font-mono text-xs text-th-fgd-3">
|
||||
<FormatNumericValue value={liquidatedValue} isUsd />
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 text-sm">{t('activity:asset-returned')}</p>
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
<FormatNumericValue value={assetReturned} />{' '}
|
||||
<span className="font-body">{assetReturnedSymbol}</span>
|
||||
</p>
|
||||
<p className="font-mono text-xs text-th-fgd-3">
|
||||
<FormatNumericValue value={returnedValue} isUsd />
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 text-sm">{t('date')}</p>
|
||||
<p className="text-th-fgd-1">
|
||||
{dayjs(block_datetime).format('ddd D MMM')}
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{dayjs(block_datetime).format('h:mma')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 text-sm">{t('activity:liquidation-type')}</p>
|
||||
<p className="text-th-fgd-1">{t('spot')}</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 text-sm">{t('activity:asset-liquidated')}</p>
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
<FormatNumericValue value={assetLiquidated} />{' '}
|
||||
<span className="font-body">{assetLiquidatedSymbol}</span>
|
||||
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}
|
||||
<FormatNumericValue
|
||||
value={activity.activity_details.asset_price}
|
||||
isUsd
|
||||
/>
|
||||
</p>
|
||||
<p className="font-mono text-xs text-th-fgd-3">
|
||||
<FormatNumericValue value={liquidatedValue} isUsd />
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 text-sm">{t('activity:asset-returned')}</p>
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
<FormatNumericValue value={assetReturned} />{' '}
|
||||
<span className="font-body">{assetReturnedSymbol}</span>
|
||||
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}
|
||||
<FormatNumericValue
|
||||
value={activity.activity_details.liab_price}
|
||||
isUsd
|
||||
/>
|
||||
</p>
|
||||
<p className="font-mono text-xs text-th-fgd-3">
|
||||
<FormatNumericValue value={returnedValue} isUsd />
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 text-sm">{t('activity:liquidation-fee')}</p>
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
<FormatNumericValue value={fee} isUsd />
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 text-sm">{t('activity:liquidation-side')}</p>
|
||||
<p className="text-th-fgd-1">
|
||||
{activity.activity_details.side === 'liqor'
|
||||
? t('activity:liquidator')
|
||||
: t('activity:liquidated')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 text-sm">{t('activity:counterparty')}</p>
|
||||
<a
|
||||
className="text-sm"
|
||||
href={`/?address=${activity.activity_details.counterparty}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('activity:view-account')}
|
||||
</a>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 text-sm">{t('transaction')}</p>
|
||||
<a
|
||||
className="default-transition flex items-center text-th-fgd-2 hover:text-th-fgd-3"
|
||||
href={`${preferredExplorer.url}${activity.activity_details.signature}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/explorer-logos/${preferredExplorer.name}.png`}
|
||||
/>
|
||||
<span className="ml-2 text-sm">
|
||||
{t(`settings:${preferredExplorer.name}`)}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LiquidationDetails
|
|
@ -0,0 +1,14 @@
|
|||
const LeaderboardIcon = ({ className }: { className?: string }) => {
|
||||
return (
|
||||
<svg
|
||||
className={`${className}`}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M6.55423 13.2164L7.61683 13.5408C7.80664 13.8247 7.99644 14.0679 8.18597 14.2704C8.86894 15 9.66603 15.4867 10.6148 15.7703V16.4595C10.6148 17.7162 9.66603 18.7705 8.48964 18.7705H8.26198C8.07218 18.7705 7.92049 18.9325 7.92049 19.1353V20.6352C7.92049 20.838 8.07218 21 8.26198 21H15.7761C15.9659 21 16.1176 20.838 16.1176 20.6352V19.1353C16.1176 18.9325 15.9659 18.7705 15.7761 18.7705H15.5484C14.3721 18.7705 13.4233 17.7569 13.4233 16.5002V15.8111C14.3721 15.5679 15.1692 15.0815 15.8521 14.3111C16.0419 14.1084 16.1936 13.8652 16.4213 13.5815L17.4839 13.2976C20.1403 12.5273 22 9.93272 22 7.0137V5.7974C22 5.39187 21.6964 5.02705 21.2789 5.02705H18.1668L18.1671 3.77035C18.1671 3.36482 17.8635 3 17.446 3H6.55429C6.17468 3 5.83317 3.32436 5.83317 3.77035V5.0678H2.72112C2.34151 5.0678 2 5.39217 2 5.83815V6.9733C2.03812 9.89196 3.85963 12.4865 6.55423 13.2164ZM18.0911 7.05412H20.0645C20.0267 8.838 19.0019 10.419 17.4841 11.1082C17.9772 9.44596 18.0911 7.78371 18.0911 7.05412ZM5.87112 7.05412C5.90898 7.82447 5.98495 9.44596 6.47839 11.1082C4.96052 10.4191 3.93573 8.838 3.89791 7.05412H5.87112Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default LeaderboardIcon
|
|
@ -0,0 +1,135 @@
|
|||
import ButtonGroup from '@components/forms/ButtonGroup'
|
||||
import { LinkButton } from '@components/shared/Button'
|
||||
import SheenLoader from '@components/shared/SheenLoader'
|
||||
import { NoSymbolIcon } from '@heroicons/react/20/solid'
|
||||
import { useInfiniteQuery } from '@tanstack/react-query'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { EmptyObject } from 'types'
|
||||
import { MANGO_DATA_API_URL } from 'utils/constants'
|
||||
import LeaderboardTable from './LeaderboardTable'
|
||||
|
||||
export interface LeaderboardRes {
|
||||
date_hour: string
|
||||
mango_account: string
|
||||
pnl: number
|
||||
profile_image_url: string | null
|
||||
profile_name: string
|
||||
start_date_hour: string
|
||||
trader_category: string
|
||||
wallet_pk: string
|
||||
}
|
||||
|
||||
type DaysToShow = '1DAY' | '1WEEK' | 'ALLTIME'
|
||||
|
||||
const isLeaderboard = (
|
||||
response: null | EmptyObject | LeaderboardRes[]
|
||||
): response is LeaderboardRes[] => {
|
||||
if (response && Array.isArray(response)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const fetchLeaderboard = async (
|
||||
daysToShow: DaysToShow,
|
||||
offset = 0
|
||||
): Promise<Array<LeaderboardRes>> => {
|
||||
const data = await fetch(
|
||||
`${MANGO_DATA_API_URL}/leaderboard-pnl?over_period=${daysToShow}&offset=${offset}`
|
||||
)
|
||||
const parsedData: null | EmptyObject | LeaderboardRes[] = await data.json()
|
||||
|
||||
let leaderboardData
|
||||
if (isLeaderboard(parsedData)) {
|
||||
leaderboardData = parsedData
|
||||
}
|
||||
|
||||
return leaderboardData ?? []
|
||||
}
|
||||
|
||||
const LeaderboardPage = () => {
|
||||
const { t } = useTranslation(['common', 'leaderboard'])
|
||||
const [daysToShow, setDaysToShow] = useState<DaysToShow>('ALLTIME')
|
||||
|
||||
const { data, isLoading, isFetching, isFetchingNextPage, fetchNextPage } =
|
||||
useInfiniteQuery(
|
||||
['leaderboard', daysToShow],
|
||||
({ pageParam }) => fetchLeaderboard(daysToShow, pageParam),
|
||||
{
|
||||
cacheTime: 1000 * 60 * 10,
|
||||
staleTime: 1000 * 60 * 5,
|
||||
retry: 3,
|
||||
refetchOnWindowFocus: false,
|
||||
keepPreviousData: true,
|
||||
getNextPageParam: (_lastPage, pages) => pages.length * 20,
|
||||
}
|
||||
)
|
||||
|
||||
const leaderboardData = useMemo(() => {
|
||||
if (data?.pages.length) {
|
||||
return data.pages.flat()
|
||||
}
|
||||
return []
|
||||
}, [data, daysToShow])
|
||||
|
||||
const handleDaysToShow = (days: DaysToShow) => {
|
||||
setDaysToShow(days)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4 md:p-10 lg:px-0">
|
||||
<div className="grid grid-cols-12">
|
||||
<div className="col-span-12 lg:col-span-8 lg:col-start-3">
|
||||
<div className="mb-6 flex flex-col md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<h1 className="mb-2">{t('leaderboard')}</h1>
|
||||
<p className="mb-4 md:mb-0">
|
||||
{t('leaderboard:leaderboard-desc')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full md:w-48">
|
||||
<ButtonGroup
|
||||
activeValue={daysToShow}
|
||||
disabled={isLoading}
|
||||
onChange={(v) => handleDaysToShow(v)}
|
||||
names={['24h', '7d', '30d', t('all')]}
|
||||
values={['1DAY', '1WEEK', 'ALLTIME']}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{leaderboardData.length ? (
|
||||
<LeaderboardTable
|
||||
data={leaderboardData}
|
||||
loading={isFetching && !isFetchingNextPage}
|
||||
/>
|
||||
) : !isFetching && !isLoading ? (
|
||||
<div className="flex flex-col items-center rounded-md border border-th-bkg-3 p-4">
|
||||
<NoSymbolIcon className="mb-1 h-7 w-7 text-th-fgd-4" />
|
||||
<p>{t('leaderboard:leaderboard-unavailable')}</p>
|
||||
</div>
|
||||
) : null}
|
||||
{isLoading || isFetchingNextPage ? (
|
||||
<div className="mt-2 space-y-2">
|
||||
{[...Array(20)].map((x, i) => (
|
||||
<SheenLoader className="flex flex-1" key={i}>
|
||||
<div className="h-16 w-full rounded-md bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{leaderboardData.length && leaderboardData.length < 100 ? (
|
||||
<LinkButton
|
||||
className="mx-auto mt-6"
|
||||
onClick={() => fetchNextPage()}
|
||||
>
|
||||
{t('show-more')}
|
||||
</LinkButton>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LeaderboardPage
|
|
@ -0,0 +1,107 @@
|
|||
import MedalIcon from '@components/icons/MedalIcon'
|
||||
import ProfileImage from '@components/profile/ProfileImage'
|
||||
import SheenLoader from '@components/shared/SheenLoader'
|
||||
import { ChevronRightIcon } from '@heroicons/react/20/solid'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { formatCurrencyValue } from 'utils/numbers'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import { LeaderboardRes } from './LeaderboardPage'
|
||||
|
||||
const LeaderboardTable = ({
|
||||
data,
|
||||
loading,
|
||||
}: {
|
||||
data: LeaderboardRes[]
|
||||
loading: boolean
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{/* <div className="grid grid-cols-12 px-4 pb-2">
|
||||
<div className="col-span-2 md:col-span-1">
|
||||
<p className="text-xs text-th-fgd-4">{t('rank')}</p>
|
||||
</div>
|
||||
<div className="col-span-4 md:col-span-5">
|
||||
<p className="text-xs text-th-fgd-4">{t('trader')}</p>
|
||||
</div>
|
||||
<div className="col-span-5 flex justify-end">
|
||||
<p className="text-xs text-th-fgd-4">{t('pnl')}</p>
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="space-y-2">
|
||||
{data.map((d, i) => (
|
||||
<LeaderboardRow
|
||||
item={d}
|
||||
loading={loading}
|
||||
rank={i + 1}
|
||||
key={d.mango_account + d.pnl + i}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default LeaderboardTable
|
||||
|
||||
const LeaderboardRow = ({
|
||||
item,
|
||||
loading,
|
||||
rank,
|
||||
}: {
|
||||
item: LeaderboardRes
|
||||
loading?: boolean
|
||||
rank: number
|
||||
}) => {
|
||||
const { profile_name, profile_image_url, mango_account, pnl, wallet_pk } =
|
||||
item
|
||||
const { width } = useViewport()
|
||||
const isMobile = width ? width < breakpoints.md : false
|
||||
|
||||
return !loading ? (
|
||||
<a
|
||||
className="default-transition flex w-full items-center justify-between rounded-md border border-th-bkg-3 px-3 py-3 md:px-4 md:hover:bg-th-bkg-2"
|
||||
href={`/?address=${mango_account}`}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="relative flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-th-bkg-3 md:mr-2">
|
||||
<p
|
||||
className={`relative z-10 font-bold ${
|
||||
rank < 4 ? 'text-th-bkg-1' : 'text-th-fgd-3'
|
||||
}`}
|
||||
>
|
||||
{rank}
|
||||
</p>
|
||||
{rank < 4 ? (
|
||||
<MedalIcon className="absolute shadow-md" rank={rank} />
|
||||
) : null}
|
||||
</div>
|
||||
<ProfileImage
|
||||
imageSize={isMobile ? '32' : '40'}
|
||||
imageUrl={profile_image_url}
|
||||
placeholderSize={isMobile ? '20' : '24'}
|
||||
/>
|
||||
<div className="text-left">
|
||||
<p className="capitalize text-th-fgd-2 md:text-base">
|
||||
{profile_name ||
|
||||
wallet_pk.slice(0, 4) + '...' + wallet_pk.slice(-4)}
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-4">
|
||||
Acc: {mango_account.slice(0, 4) + '...' + mango_account.slice(-4)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="mr-3 text-right font-mono md:text-base">
|
||||
{formatCurrencyValue(pnl, 2)}
|
||||
</span>
|
||||
<ChevronRightIcon className="h-5 w-5 text-th-fgd-3" />
|
||||
</div>
|
||||
</a>
|
||||
) : (
|
||||
<SheenLoader className="flex flex-1">
|
||||
<div className="h-16 w-full rounded-md bg-th-bkg-2" />
|
||||
</SheenLoader>
|
||||
)
|
||||
}
|
|
@ -18,6 +18,7 @@ import {
|
|||
BanknotesIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import SolanaTps from '@components/SolanaTps'
|
||||
import LeaderboardIcon from '@components/icons/LeaderboardIcon'
|
||||
|
||||
const StyledBarItemLabel = ({
|
||||
children,
|
||||
|
@ -105,7 +106,7 @@ const MoreMenuPanel = ({
|
|||
const { t } = useTranslation(['common', 'search'])
|
||||
return (
|
||||
<div
|
||||
className={`fixed bottom-0 z-30 h-96 w-full overflow-hidden rounded-t-3xl bg-th-bkg-2 px-4 transition duration-300 ease-in-out ${
|
||||
className={`fixed bottom-0 z-30 h-[420px] w-full overflow-hidden rounded-t-3xl bg-th-bkg-2 px-4 transition duration-300 ease-in-out ${
|
||||
showPanel ? 'translate-y-0' : 'translate-y-full'
|
||||
}`}
|
||||
>
|
||||
|
@ -129,6 +130,11 @@ const MoreMenuPanel = ({
|
|||
path="/stats"
|
||||
icon={<ChartBarIcon className="h-5 w-5" />}
|
||||
/>
|
||||
<MoreMenuItem
|
||||
title={t('leaderboard')}
|
||||
path="/leaderboard"
|
||||
icon={<LeaderboardIcon className="h-5 w-5" />}
|
||||
/>
|
||||
<MoreMenuItem
|
||||
title={t('search:search-accounts')}
|
||||
path="/search"
|
||||
|
@ -171,17 +177,17 @@ const MoreMenuItem = ({
|
|||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center space-x-3">
|
||||
{icon}
|
||||
<span className="ml-1.5">{title}</span>
|
||||
<span>{title}</span>
|
||||
</div>
|
||||
<ChevronRightIcon className="h-5 w-5" />
|
||||
</a>
|
||||
) : (
|
||||
<Link href={path} shallow={true} className={classNames}>
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center space-x-3">
|
||||
{icon}
|
||||
<span className="ml-1.5">{title}</span>
|
||||
<span>{title}</span>
|
||||
</div>
|
||||
<ChevronRightIcon className="h-5 w-5" />
|
||||
</Link>
|
||||
|
|
|
@ -9,7 +9,7 @@ const ProfileImage = ({
|
|||
}: {
|
||||
imageSize: string
|
||||
placeholderSize: string
|
||||
imageUrl?: string
|
||||
imageUrl?: string | null
|
||||
isOwnerProfile?: boolean
|
||||
}) => {
|
||||
const profile = mangoStore((s) => s.profile.details)
|
||||
|
|
|
@ -10,7 +10,10 @@ import Change from '../shared/Change'
|
|||
import MarketLogos from '@components/trade/MarketLogos'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
|
||||
import { usePerpFundingRate } from '@components/trade/PerpFundingRate'
|
||||
import {
|
||||
formatFunding,
|
||||
usePerpFundingRate,
|
||||
} from '@components/trade/PerpFundingRate'
|
||||
import { IconButton } from '@components/shared/Button'
|
||||
import { ChevronRightIcon } from '@heroicons/react/20/solid'
|
||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||
|
@ -95,7 +98,7 @@ const PerpMarketsTable = ({
|
|||
(r) => r.market_index === market.perpMarketIndex
|
||||
)
|
||||
fundingRate = marketRate
|
||||
? `${marketRate.funding_rate_hourly.toFixed(4)}%`
|
||||
? `${formatFunding.format(marketRate.funding_rate_hourly)}`
|
||||
: '–'
|
||||
} else {
|
||||
fundingRate = '–'
|
||||
|
|
|
@ -74,7 +74,11 @@ const TokenPage = () => {
|
|||
|
||||
const bankName = useMemo(() => {
|
||||
if (!token) return
|
||||
return token === 'WBTC' ? 'wBTC (Portal)' : token.toString()
|
||||
return token === 'WBTC'
|
||||
? 'wBTC (Portal)'
|
||||
: token === 'ETH'
|
||||
? 'ETH (Portal)'
|
||||
: token.toString()
|
||||
}, [token])
|
||||
|
||||
const bank = useMemo(() => {
|
||||
|
|
|
@ -22,9 +22,9 @@ type TopTokenAccount = {
|
|||
token_index: number
|
||||
value: number
|
||||
wallet_pk: string
|
||||
profile_image_url: string
|
||||
profile_name: string
|
||||
trader_category: string
|
||||
profile_image_url?: string
|
||||
profile_name?: string
|
||||
trader_category?: string
|
||||
}
|
||||
|
||||
const fetchTopTokenAccounts = async (tokenIndex: number) => {
|
||||
|
@ -44,37 +44,37 @@ const fetchTopTokenAccounts = async (tokenIndex: number) => {
|
|||
TopDepositorBorrower[]
|
||||
] = await Promise.all([depositsResponse.json(), borrowsResponse.json()])
|
||||
|
||||
const depositorProfilesResponse = await Promise.all(
|
||||
depositsData.map((r: TopDepositorBorrower) =>
|
||||
fetch(
|
||||
`${MANGO_DATA_API_URL}/user-data/profile-details?wallet-pk=${r.wallet_pk}`
|
||||
)
|
||||
)
|
||||
)
|
||||
// const depositorProfilesResponse = await Promise.all(
|
||||
// depositsData.map((r: TopDepositorBorrower) =>
|
||||
// fetch(
|
||||
// `${MANGO_DATA_API_URL}/user-data/profile-details?wallet-pk=${r.wallet_pk}`
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
|
||||
const depositorProfilesData = await Promise.all(
|
||||
depositorProfilesResponse.map((d) => d.json())
|
||||
)
|
||||
// const depositorProfilesData = await Promise.all(
|
||||
// depositorProfilesResponse.map((d) => d.json())
|
||||
// )
|
||||
|
||||
const borrowerProfilesResponse = await Promise.all(
|
||||
borrowsData.map((r: TopDepositorBorrower) =>
|
||||
fetch(
|
||||
`${MANGO_DATA_API_URL}/user-data/profile-details?wallet-pk=${r.wallet_pk}`
|
||||
)
|
||||
)
|
||||
)
|
||||
// const borrowerProfilesResponse = await Promise.all(
|
||||
// borrowsData.map((r: TopDepositorBorrower) =>
|
||||
// fetch(
|
||||
// `${MANGO_DATA_API_URL}/user-data/profile-details?wallet-pk=${r.wallet_pk}`
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
|
||||
const borrowerProfilesData = await Promise.all(
|
||||
borrowerProfilesResponse.map((d) => d.json())
|
||||
)
|
||||
// const borrowerProfilesData = await Promise.all(
|
||||
// borrowerProfilesResponse.map((d) => d.json())
|
||||
// )
|
||||
|
||||
return [
|
||||
depositsData
|
||||
.map((data, i) => ({ ...data, ...depositorProfilesData[i] }))
|
||||
// .map((data, i) => ({ ...data, ...depositorProfilesData[i] }))
|
||||
.slice(0, 10)
|
||||
.filter((d) => d.value > 0),
|
||||
borrowsData
|
||||
.map((data, i) => ({ ...data, ...borrowerProfilesData[i] }))
|
||||
// .map((data, i) => ({ ...data, ...borrowerProfilesData[i] }))
|
||||
.slice(0, 10)
|
||||
.filter((d) => d.value < 0),
|
||||
]
|
||||
|
|
|
@ -30,7 +30,7 @@ export const usePerpFundingRate = () => {
|
|||
return Array.isArray(res?.data) ? res : { isSuccess: false, data: null }
|
||||
}
|
||||
|
||||
const formatFunding = Intl.NumberFormat('en', {
|
||||
export const formatFunding = Intl.NumberFormat('en', {
|
||||
minimumSignificantDigits: 1,
|
||||
maximumSignificantDigits: 2,
|
||||
style: 'percent',
|
||||
|
|
|
@ -60,7 +60,7 @@ export const ConnectWalletButton: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<div className="absolute top-1/2 right-0 z-10 h-full -translate-y-1/2">
|
||||
<div className="absolute top-1/2 right-0 z-20 h-full -translate-y-1/2">
|
||||
<WalletSelect />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -233,7 +233,15 @@ const Dashboard: NextPage = () => {
|
|||
/>
|
||||
<KeyValuePair
|
||||
label="Collected fees native"
|
||||
value={bank.collectedFeesNative.toNumber()}
|
||||
value={`${toUiDecimals(
|
||||
bank.collectedFeesNative.toNumber(),
|
||||
bank.mintDecimals
|
||||
).toFixed(2)} ($${(
|
||||
toUiDecimals(
|
||||
bank.collectedFeesNative.toNumber(),
|
||||
bank.mintDecimals
|
||||
) * bank.uiPrice
|
||||
).toFixed(2)})`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Dust"
|
||||
|
@ -241,21 +249,35 @@ const Dashboard: NextPage = () => {
|
|||
/>
|
||||
<KeyValuePair
|
||||
label="Deposits"
|
||||
value={toUiDecimals(
|
||||
value={`${toUiDecimals(
|
||||
bank.indexedDeposits
|
||||
.mul(bank.depositIndex)
|
||||
.toNumber(),
|
||||
bank.mintDecimals
|
||||
)}
|
||||
)} ($${(
|
||||
toUiDecimals(
|
||||
bank.indexedDeposits
|
||||
.mul(bank.depositIndex)
|
||||
.toNumber(),
|
||||
bank.mintDecimals
|
||||
) * bank.uiPrice
|
||||
).toFixed(2)})`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Borrows"
|
||||
value={toUiDecimals(
|
||||
value={`${toUiDecimals(
|
||||
bank.indexedBorrows
|
||||
.mul(bank.borrowIndex)
|
||||
.toNumber(),
|
||||
bank.mintDecimals
|
||||
)}
|
||||
)} ($${(
|
||||
toUiDecimals(
|
||||
bank.indexedBorrows
|
||||
.mul(bank.borrowIndex)
|
||||
.toNumber(),
|
||||
bank.mintDecimals
|
||||
) * bank.uiPrice
|
||||
).toFixed(2)})`}
|
||||
/>
|
||||
<KeyValuePair
|
||||
label="Avg Utilization"
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import LeaderboardPage from '@components/leaderboard/LeaderboardPage'
|
||||
import type { NextPage } from 'next'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
||||
export async function getStaticProps({ locale }: { locale: string }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, [
|
||||
'common',
|
||||
'leaderboard',
|
||||
'profile',
|
||||
'search',
|
||||
])),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const Leaderboard: NextPage = () => {
|
||||
return (
|
||||
<div className="pb-16 md:pb-0">
|
||||
<LeaderboardPage />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Leaderboard
|
|
@ -6,16 +6,22 @@
|
|||
"asset-liquidated": "Asset Liquidated",
|
||||
"asset-returned": "Asset Returned",
|
||||
"connect-activity": "Connect to view your account activity",
|
||||
"counterparty": "Counterparty",
|
||||
"credit": "Credit",
|
||||
"debit": "Debit",
|
||||
"deposit": "Deposit",
|
||||
"deposits": "Deposits",
|
||||
"filter-results": "Filter",
|
||||
"liquidate_perp_base_position_or_positive_pnl": "Perp Liquidation",
|
||||
"liquidate_token_with_token": "Spot Liquidation",
|
||||
"liquidated": "Liquidated",
|
||||
"liquidation": "Liquidation",
|
||||
"liquidation-fee": "Liquidation Fee",
|
||||
"liquidation-type": "Liquidation Type",
|
||||
"liquidation-side": "Liquidation Side",
|
||||
"liquidations": "Liquidations",
|
||||
"liquidation-details": "Liquidation Details",
|
||||
"liquidate_token_with_token": "Spot Liquidation",
|
||||
"liquidator": "Liquidator",
|
||||
"no-activity": "No account activity",
|
||||
"openbook_trade": "Spot Trade",
|
||||
"perps": "Perps",
|
||||
|
@ -30,6 +36,7 @@
|
|||
"update": "Update",
|
||||
"value-from": "Value From",
|
||||
"value-to": "Value To",
|
||||
"view-account": "View Account",
|
||||
"withdraw": "Withdraw",
|
||||
"withdrawals": "Withdrawals"
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
"actions": "Actions",
|
||||
"add-new-account": "Add New Account",
|
||||
"agree-and-continue": "Agree and Continue",
|
||||
"all": "All",
|
||||
"amount": "Amount",
|
||||
"amount-owed": "Amount Owed",
|
||||
"asset-liability-weight": "Asset/Liability Weights",
|
||||
|
@ -83,6 +84,7 @@
|
|||
"insufficient-sol": "Solana requires 0.04454 SOL rent to create a Mango Account. This will be returned if you close your account.",
|
||||
"interest-earned": "Interest Earned",
|
||||
"interest-earned-paid": "Interest Earned",
|
||||
"leaderboard": "Leaderboard",
|
||||
"learn": "Learn",
|
||||
"leverage": "Leverage",
|
||||
"liability-weight": "Liability Weight",
|
||||
|
@ -126,6 +128,7 @@
|
|||
"select-withdraw-token": "Select Withdraw Token",
|
||||
"sell": "Sell",
|
||||
"settings": "Settings",
|
||||
"show-more": "Show More",
|
||||
"show-zero-balances": "Show Zero Balances",
|
||||
"solana-tps": "Solana TPS",
|
||||
"spot": "Spot",
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"leaderboard-desc": "Top 100 Mango traders by total PnL (spot and perp)",
|
||||
"leaderboard-unavailable": "Leaderboard unavailable"
|
||||
}
|
|
@ -6,16 +6,22 @@
|
|||
"asset-liquidated": "Asset Liquidated",
|
||||
"asset-returned": "Asset Returned",
|
||||
"connect-activity": "Connect to view your account activity",
|
||||
"counterparty": "Counterparty",
|
||||
"credit": "Credit",
|
||||
"debit": "Debit",
|
||||
"deposit": "Deposit",
|
||||
"deposits": "Deposits",
|
||||
"filter-results": "Filter",
|
||||
"liquidate_perp_base_position_or_positive_pnl": "Perp Liquidation",
|
||||
"liquidate_token_with_token": "Spot Liquidation",
|
||||
"liquidated": "Liquidated",
|
||||
"liquidation": "Liquidation",
|
||||
"liquidation-fee": "Liquidation Fee",
|
||||
"liquidation-type": "Liquidation Type",
|
||||
"liquidation-side": "Liquidation Side",
|
||||
"liquidations": "Liquidations",
|
||||
"liquidation-details": "Liquidation Details",
|
||||
"liquidate_token_with_token": "Spot Liquidation",
|
||||
"liquidator": "Liquidator",
|
||||
"no-activity": "No account activity",
|
||||
"openbook_trade": "Spot Trade",
|
||||
"perps": "Perps",
|
||||
|
@ -30,6 +36,7 @@
|
|||
"update": "Update",
|
||||
"value-from": "Value From",
|
||||
"value-to": "Value To",
|
||||
"view-account": "View Account",
|
||||
"withdraw": "Withdraw",
|
||||
"withdrawals": "Withdrawals"
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
"actions": "Actions",
|
||||
"add-new-account": "Add New Account",
|
||||
"agree-and-continue": "Agree and Continue",
|
||||
"all": "All",
|
||||
"amount": "Amount",
|
||||
"amount-owed": "Amount Owed",
|
||||
"asset-liability-weight": "Asset/Liability Weights",
|
||||
|
@ -83,6 +84,7 @@
|
|||
"insufficient-sol": "Solana requires 0.04454 SOL rent to create a Mango Account. This will be returned if you close your account.",
|
||||
"interest-earned": "Interest Earned",
|
||||
"interest-earned-paid": "Interest Earned",
|
||||
"leaderboard": "Leaderboard",
|
||||
"learn": "Learn",
|
||||
"leverage": "Leverage",
|
||||
"liability-weight": "Liability Weight",
|
||||
|
@ -126,6 +128,7 @@
|
|||
"select-withdraw-token": "Select Withdraw Token",
|
||||
"sell": "Sell",
|
||||
"settings": "Settings",
|
||||
"show-more": "Show More",
|
||||
"show-zero-balances": "Show Zero Balances",
|
||||
"solana-tps": "Solana TPS",
|
||||
"spot": "Spot",
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"leaderboard-desc": "Top 100 Mango traders by total PnL (spot and perp)",
|
||||
"leaderboard-unavailable": "Leaderboard unavailable"
|
||||
}
|
|
@ -6,16 +6,22 @@
|
|||
"asset-liquidated": "Asset Liquidated",
|
||||
"asset-returned": "Asset Returned",
|
||||
"connect-activity": "Connect to view your account activity",
|
||||
"counterparty": "Counterparty",
|
||||
"credit": "Credit",
|
||||
"debit": "Debit",
|
||||
"deposit": "Deposit",
|
||||
"deposits": "Deposits",
|
||||
"filter-results": "Filter",
|
||||
"liquidate_perp_base_position_or_positive_pnl": "Perp Liquidation",
|
||||
"liquidate_token_with_token": "Spot Liquidation",
|
||||
"liquidated": "Liquidated",
|
||||
"liquidation": "Liquidation",
|
||||
"liquidation-fee": "Liquidation Fee",
|
||||
"liquidation-type": "Liquidation Type",
|
||||
"liquidation-side": "Liquidation Side",
|
||||
"liquidations": "Liquidations",
|
||||
"liquidation-details": "Liquidation Details",
|
||||
"liquidate_token_with_token": "Spot Liquidation",
|
||||
"liquidator": "Liquidator",
|
||||
"no-activity": "No account activity",
|
||||
"openbook_trade": "Spot Trade",
|
||||
"perps": "Perps",
|
||||
|
@ -30,6 +36,7 @@
|
|||
"update": "Update",
|
||||
"value-from": "Value From",
|
||||
"value-to": "Value To",
|
||||
"view-account": "View Account",
|
||||
"withdraw": "Withdraw",
|
||||
"withdrawals": "Withdrawals"
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
"actions": "Actions",
|
||||
"add-new-account": "Add New Account",
|
||||
"agree-and-continue": "Agree and Continue",
|
||||
"all": "All",
|
||||
"amount": "Amount",
|
||||
"amount-owed": "Amount Owed",
|
||||
"asset-liability-weight": "Asset/Liability Weights",
|
||||
|
@ -83,6 +84,7 @@
|
|||
"insufficient-sol": "Solana requires 0.04454 SOL rent to create a Mango Account. This will be returned if you close your account.",
|
||||
"interest-earned": "Interest Earned",
|
||||
"interest-earned-paid": "Interest Earned",
|
||||
"leaderboard": "Leaderboard",
|
||||
"learn": "Learn",
|
||||
"leverage": "Leverage",
|
||||
"liability-weight": "Liability Weight",
|
||||
|
@ -126,6 +128,7 @@
|
|||
"select-withdraw-token": "Select Withdraw Token",
|
||||
"sell": "Sell",
|
||||
"settings": "Settings",
|
||||
"show-more": "Show More",
|
||||
"show-zero-balances": "Show Zero Balances",
|
||||
"solana-tps": "Solana TPS",
|
||||
"spot": "Spot",
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"leaderboard-desc": "Top 100 Mango traders by total PnL (spot and perp)",
|
||||
"leaderboard-unavailable": "Leaderboard unavailable"
|
||||
}
|
|
@ -6,16 +6,22 @@
|
|||
"asset-liquidated": "Asset Liquidated",
|
||||
"asset-returned": "Asset Returned",
|
||||
"connect-activity": "Connect to view your account activity",
|
||||
"counterparty": "Counterparty",
|
||||
"credit": "Credit",
|
||||
"debit": "Debit",
|
||||
"deposit": "Deposit",
|
||||
"deposits": "Deposits",
|
||||
"filter-results": "Filter",
|
||||
"liquidate_perp_base_position_or_positive_pnl": "Perp Liquidation",
|
||||
"liquidate_token_with_token": "Spot Liquidation",
|
||||
"liquidated": "Liquidated",
|
||||
"liquidation": "Liquidation",
|
||||
"liquidation-fee": "Liquidation Fee",
|
||||
"liquidation-type": "Liquidation Type",
|
||||
"liquidation-side": "Liquidation Side",
|
||||
"liquidations": "Liquidations",
|
||||
"liquidation-details": "Liquidation Details",
|
||||
"liquidate_token_with_token": "Spot Liquidation",
|
||||
"liquidator": "Liquidator",
|
||||
"no-activity": "No account activity",
|
||||
"openbook_trade": "Spot Trade",
|
||||
"perps": "Perps",
|
||||
|
@ -30,6 +36,7 @@
|
|||
"update": "Update",
|
||||
"value-from": "Value From",
|
||||
"value-to": "Value To",
|
||||
"view-account": "View Account",
|
||||
"withdraw": "Withdraw",
|
||||
"withdrawals": "Withdrawals"
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
"actions": "Actions",
|
||||
"add-new-account": "Add New Account",
|
||||
"agree-and-continue": "Agree and Continue",
|
||||
"all": "All",
|
||||
"amount": "Amount",
|
||||
"amount-owed": "Amount Owed",
|
||||
"asset-liability-weight": "Asset/Liability Weights",
|
||||
|
@ -83,6 +84,7 @@
|
|||
"insufficient-sol": "Solana requires 0.04454 SOL rent to create a Mango Account. This will be returned if you close your account.",
|
||||
"interest-earned": "Interest Earned",
|
||||
"interest-earned-paid": "Interest Earned",
|
||||
"leaderboard": "Leaderboard",
|
||||
"learn": "Learn",
|
||||
"leverage": "Leverage",
|
||||
"liability-weight": "Liability Weight",
|
||||
|
@ -126,6 +128,7 @@
|
|||
"select-withdraw-token": "Select Withdraw Token",
|
||||
"sell": "Sell",
|
||||
"settings": "Settings",
|
||||
"show-more": "Show More",
|
||||
"show-zero-balances": "Show Zero Balances",
|
||||
"solana-tps": "Solana TPS",
|
||||
"spot": "Spot",
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"leaderboard-desc": "Top 100 Mango traders by total PnL (spot and perp)",
|
||||
"leaderboard-unavailable": "Leaderboard unavailable"
|
||||
}
|
|
@ -6,16 +6,22 @@
|
|||
"asset-liquidated": "Asset Liquidated",
|
||||
"asset-returned": "Asset Returned",
|
||||
"connect-activity": "Connect to view your account activity",
|
||||
"counterparty": "Counterparty",
|
||||
"credit": "Credit",
|
||||
"debit": "Debit",
|
||||
"deposit": "Deposit",
|
||||
"deposits": "Deposits",
|
||||
"filter-results": "Filter",
|
||||
"liquidate_perp_base_position_or_positive_pnl": "Perp Liquidation",
|
||||
"liquidate_token_with_token": "Spot Liquidation",
|
||||
"liquidated": "Liquidated",
|
||||
"liquidation": "Liquidation",
|
||||
"liquidation-fee": "Liquidation Fee",
|
||||
"liquidation-type": "Liquidation Type",
|
||||
"liquidation-side": "Liquidation Side",
|
||||
"liquidations": "Liquidations",
|
||||
"liquidation-details": "Liquidation Details",
|
||||
"liquidate_token_with_token": "Spot Liquidation",
|
||||
"liquidator": "Liquidator",
|
||||
"no-activity": "No account activity",
|
||||
"openbook_trade": "Spot Trade",
|
||||
"perps": "Perps",
|
||||
|
@ -30,6 +36,7 @@
|
|||
"update": "Update",
|
||||
"value-from": "Value From",
|
||||
"value-to": "Value To",
|
||||
"view-account": "View Account",
|
||||
"withdraw": "Withdraw",
|
||||
"withdrawals": "Withdrawals"
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
"actions": "Actions",
|
||||
"add-new-account": "Add New Account",
|
||||
"agree-and-continue": "Agree and Continue",
|
||||
"all": "All",
|
||||
"amount": "Amount",
|
||||
"amount-owed": "Amount Owed",
|
||||
"asset-liability-weight": "Asset/Liability Weights",
|
||||
|
@ -83,6 +84,7 @@
|
|||
"insufficient-sol": "Solana requires 0.04454 SOL rent to create a Mango Account. This will be returned if you close your account.",
|
||||
"interest-earned": "Interest Earned",
|
||||
"interest-earned-paid": "Interest Earned",
|
||||
"leaderboard": "Leaderboard",
|
||||
"learn": "Learn",
|
||||
"leverage": "Leverage",
|
||||
"liability-weight": "Liability Weight",
|
||||
|
@ -126,6 +128,7 @@
|
|||
"select-withdraw-token": "Select Withdraw Token",
|
||||
"sell": "Sell",
|
||||
"settings": "Settings",
|
||||
"show-more": "Show More",
|
||||
"show-zero-balances": "Show Zero Balances",
|
||||
"solana-tps": "Solana TPS",
|
||||
"spot": "Spot",
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"leaderboard-desc": "Top 100 Mango traders by total PnL (spot and perp)",
|
||||
"leaderboard-unavailable": "Leaderboard unavailable"
|
||||
}
|
|
@ -132,22 +132,42 @@ export interface DepositWithdrawFeedItem {
|
|||
wallet_pk: string
|
||||
}
|
||||
|
||||
export interface LiquidationFeedItem {
|
||||
export interface SpotLiquidationFeedItem {
|
||||
asset_amount: number
|
||||
asset_price: number
|
||||
asset_symbol: string
|
||||
block_datetime: string
|
||||
counterparty: string
|
||||
liab_amount: number
|
||||
liab_price: number
|
||||
liab_symbol: string
|
||||
mango_account: string
|
||||
mango_group: string
|
||||
side: string
|
||||
side: 'liqor' | 'liqee'
|
||||
signature: string
|
||||
}
|
||||
|
||||
export interface PerpLiquidationFeedItem {
|
||||
base_transfer: -0.5
|
||||
block_datetime: string
|
||||
counterparty: string
|
||||
mango_account: string
|
||||
mango_group: string
|
||||
perp_market_name: string
|
||||
pnl_settle_limit_transfer: number
|
||||
pnl_transfer: number
|
||||
price: number
|
||||
quote_transfer: number
|
||||
side: 'liqor' | 'liqee'
|
||||
signature: string
|
||||
}
|
||||
|
||||
export type SpotOrPerpLiquidationItem =
|
||||
| SpotLiquidationFeedItem
|
||||
| PerpLiquidationFeedItem
|
||||
|
||||
export interface LiquidationActivity {
|
||||
activity_details: LiquidationFeedItem
|
||||
activity_details: SpotOrPerpLiquidationItem
|
||||
block_datetime: string
|
||||
activity_type: string
|
||||
symbol: string
|
||||
|
@ -162,6 +182,15 @@ export function isLiquidationFeedItem(
|
|||
return false
|
||||
}
|
||||
|
||||
export function isPerpLiquidation(
|
||||
activityDetails: SpotOrPerpLiquidationItem
|
||||
): activityDetails is PerpLiquidationFeedItem {
|
||||
if ((activityDetails as PerpLiquidationFeedItem).base_transfer) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export interface SwapHistoryItem {
|
||||
block_datetime: string
|
||||
mango_account: string
|
||||
|
@ -202,7 +231,8 @@ export type ActivityFeed = {
|
|||
symbol: string
|
||||
activity_details:
|
||||
| DepositWithdrawFeedItem
|
||||
| LiquidationFeedItem
|
||||
| SpotLiquidationFeedItem
|
||||
| PerpLiquidationFeedItem
|
||||
| SwapHistoryItem
|
||||
| PerpTradeHistory
|
||||
| SpotTradeHistory
|
||||
|
|
Loading…
Reference in New Issue