Merge pull request #115 from blockworks-foundation/expand-perp-activity
expand perp trade activity feed details
This commit is contained in:
commit
55adbe7602
|
@ -1,39 +0,0 @@
|
|||
import mangoStore from '@store/mangoStore'
|
||||
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}
|
||||
/>
|
||||
) : (
|
||||
<div className="px-6">
|
||||
<LiquidationDetails
|
||||
activity={showActivityDetail}
|
||||
setShowActivityDetail={setShowActivityDetail}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ActivityFeed
|
|
@ -9,11 +9,7 @@ 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 {
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
NoSymbolIcon,
|
||||
} from '@heroicons/react/20/solid'
|
||||
import { ChevronDownIcon, NoSymbolIcon } from '@heroicons/react/20/solid'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import dayjs from 'dayjs'
|
||||
|
@ -23,16 +19,17 @@ import { useViewport } from 'hooks/useViewport'
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import Image from 'next/legacy/image'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { ActivityFeed, isLiquidationFeedItem, LiquidationActivity } from 'types'
|
||||
import { isLiquidationFeedItem, isPerpTradeFeedItem } 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'
|
||||
import PerpTradeDetails from './PerpTradeDetails'
|
||||
|
||||
const formatFee = (value: number) => {
|
||||
export const formatFee = (value: number) => {
|
||||
return value.toLocaleString(undefined, {
|
||||
minimumSignificantDigits: 1,
|
||||
maximumSignificantDigits: 2,
|
||||
maximumSignificantDigits: 3,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -259,14 +256,9 @@ const getValue = (activity: any, mangoAccountAddress: string) => {
|
|||
return value
|
||||
}
|
||||
|
||||
const ActivityFeedTable = ({
|
||||
activityFeed,
|
||||
handleShowActivityDetails,
|
||||
}: {
|
||||
activityFeed: ActivityFeed[]
|
||||
handleShowActivityDetails: (x: LiquidationActivity) => void
|
||||
}) => {
|
||||
const ActivityFeedTable = () => {
|
||||
const { t } = useTranslation(['common', 'activity'])
|
||||
const activityFeed = mangoStore((s) => s.activityFeed.feed)
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const actions = mangoStore((s) => s.actions)
|
||||
const loadActivityFeed = mangoStore((s) => s.activityFeed.loading)
|
||||
|
@ -323,100 +315,118 @@ const ActivityFeedTable = ({
|
|||
const amounts = getCreditAndDebit(activity, mangoAccountAddress)
|
||||
const value = getValue(activity, mangoAccountAddress)
|
||||
const fee = getFee(activity, mangoAccountAddress)
|
||||
const isExpandable =
|
||||
isLiquidationFeedItem(activity) || isPerpTradeFeedItem(activity)
|
||||
return (
|
||||
<TrBody
|
||||
key={signature + index}
|
||||
className={`default-transition text-sm hover:bg-th-bkg-2 ${
|
||||
isLiquidationFeedItem(activity) ? 'cursor-pointer' : ''
|
||||
}`}
|
||||
onClick={
|
||||
isLiquidationFeedItem(activity)
|
||||
? () => handleShowActivityDetails(activity)
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<Td>
|
||||
<p className="font-body">
|
||||
{dayjs(block_datetime).format('ddd D MMM')}
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{dayjs(block_datetime).format('h:mma')}
|
||||
</p>
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
{t(`activity:${activity_type}`)}
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
{amounts.credit.value}{' '}
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{amounts.credit.symbol}
|
||||
</span>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
{amounts.debit.value}{' '}
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{amounts.debit.symbol}
|
||||
</span>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
{fee.value}{' '}
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{fee.symbol}
|
||||
</span>
|
||||
</Td>
|
||||
<Td
|
||||
className={`text-right font-mono ${
|
||||
activity_type === 'swap' ||
|
||||
activity_type === 'perp_trade' ||
|
||||
isOpenbook ||
|
||||
isLiquidationFeedItem(activity)
|
||||
? 'text-th-fgd-2'
|
||||
: value >= 0
|
||||
? 'text-th-up'
|
||||
: 'text-th-down'
|
||||
}`}
|
||||
>
|
||||
{value > 0 &&
|
||||
activity_type !== 'swap' &&
|
||||
activity_type !== 'perp_trade' &&
|
||||
!isOpenbook &&
|
||||
!isLiquidationFeedItem(activity)
|
||||
? '+'
|
||||
: ''}
|
||||
<FormatNumericValue value={value} isUsd />
|
||||
</Td>
|
||||
<Td>
|
||||
{!isLiquidationFeedItem(activity) ? (
|
||||
<div className="flex items-center justify-end">
|
||||
<Tooltip
|
||||
content={`View on ${t(
|
||||
`settings:${preferredExplorer.name}`
|
||||
)}`}
|
||||
placement="top-end"
|
||||
<Disclosure key={`${signature}${index}`}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button
|
||||
as={TrBody}
|
||||
className={`default-transition text-sm ${
|
||||
isExpandable
|
||||
? 'cursor-pointer md:hover:bg-th-bkg-2'
|
||||
: 'pointer-events-none'
|
||||
}`}
|
||||
>
|
||||
<Td>
|
||||
<p className="font-body">
|
||||
{dayjs(block_datetime).format('ddd D MMM')}
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
{dayjs(block_datetime).format('h:mma')}
|
||||
</p>
|
||||
</Td>
|
||||
<Td className="text-right">
|
||||
{t(`activity:${activity_type}`)}
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
{amounts.credit.value}{' '}
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{amounts.credit.symbol}
|
||||
</span>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
{amounts.debit.value}{' '}
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{amounts.debit.symbol}
|
||||
</span>
|
||||
</Td>
|
||||
<Td className="text-right font-mono">
|
||||
{fee.value}{' '}
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{fee.symbol}
|
||||
</span>
|
||||
</Td>
|
||||
<Td
|
||||
className={`text-right font-mono ${
|
||||
activity_type === 'swap' ||
|
||||
isOpenbook ||
|
||||
isExpandable
|
||||
? 'text-th-fgd-2'
|
||||
: value >= 0
|
||||
? 'text-th-up'
|
||||
: 'text-th-down'
|
||||
}`}
|
||||
>
|
||||
<a
|
||||
href={`${preferredExplorer.url}${signature}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div className="h-6 w-6">
|
||||
<Image
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
src={`/explorer-logos/${preferredExplorer.name}.png`}
|
||||
{value > 0 &&
|
||||
activity_type !== 'swap' &&
|
||||
!isOpenbook &&
|
||||
!isExpandable
|
||||
? '+'
|
||||
: ''}
|
||||
<FormatNumericValue value={value} isUsd />
|
||||
</Td>
|
||||
<Td>
|
||||
{!isExpandable ? (
|
||||
<div className="flex items-center justify-end">
|
||||
<Tooltip
|
||||
content={`View on ${t(
|
||||
`settings:${preferredExplorer.name}`
|
||||
)}`}
|
||||
placement="top-end"
|
||||
>
|
||||
<a
|
||||
href={`${preferredExplorer.url}${signature}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div className="h-6 w-6">
|
||||
<Image
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
src={`/explorer-logos/${preferredExplorer.name}.png`}
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-end">
|
||||
<ChevronDownIcon
|
||||
className={`h-6 w-6 text-th-fgd-3 ${
|
||||
open ? 'rotate-180' : 'rotate-360'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-end">
|
||||
<ChevronRightIcon className="h-6 w-6 text-th-fgd-3" />
|
||||
</div>
|
||||
)}
|
||||
</Td>
|
||||
</TrBody>
|
||||
)}
|
||||
</Td>
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel as={TrBody}>
|
||||
{isLiquidationFeedItem(activity) ? (
|
||||
<td className="p-6" colSpan={7}>
|
||||
<LiquidationDetails activity={activity} />
|
||||
</td>
|
||||
) : isPerpTradeFeedItem(activity) ? (
|
||||
<td className="p-6" colSpan={7}>
|
||||
<PerpTradeDetails activity={activity} />
|
||||
</td>
|
||||
) : null}
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
|
@ -481,10 +491,22 @@ const MobileActivityFeedItem = ({
|
|||
const isOpenbook = activity_type === 'openbook_trade'
|
||||
const isPerp = activity_type === 'perp_trade'
|
||||
const value = getValue(activity, mangoAccountAddress)
|
||||
const isExpandable =
|
||||
isLiquidationFeedItem(activity) || isPerpTradeFeedItem(activity)
|
||||
|
||||
const isPerpTaker =
|
||||
isPerpTradeFeedItem(activity) &&
|
||||
activity.activity_details.taker === mangoAccountAddress
|
||||
|
||||
const perpTradeSide = isPerpTaker
|
||||
? activity.activity_details.taker_side
|
||||
: activity.activity_details.taker_side === 'bid'
|
||||
? 'ask'
|
||||
: 'bid'
|
||||
|
||||
return (
|
||||
<div key={signature} className="border-b border-th-bkg-3">
|
||||
{isLiquidationFeedItem(activity) ? (
|
||||
{isExpandable ? (
|
||||
<Disclosure>
|
||||
{({ open }) => (
|
||||
<>
|
||||
|
@ -503,9 +525,26 @@ const MobileActivityFeedItem = ({
|
|||
<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>
|
||||
{isLiquidationFeedItem(activity) ? (
|
||||
<p className="text-right font-mono text-sm text-th-fgd-1">
|
||||
<FormatNumericValue value={value} isUsd />
|
||||
</p>
|
||||
) : (
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
<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={perpTradeSide === 'bid' ? 1 : -1}
|
||||
/>
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
|
@ -522,7 +561,11 @@ const MobileActivityFeedItem = ({
|
|||
>
|
||||
<Disclosure.Panel>
|
||||
<div className="border-t border-th-bkg-3 px-4 py-4">
|
||||
<LiquidationDetails activity={activity} />
|
||||
{isLiquidationFeedItem(activity) ? (
|
||||
<LiquidationDetails activity={activity} />
|
||||
) : (
|
||||
<PerpTradeDetails activity={activity} />
|
||||
)}
|
||||
</div>
|
||||
</Disclosure.Panel>
|
||||
</Transition>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import SwapHistoryTable from '../swap/SwapHistoryTable'
|
||||
import ActivityFeed from './ActivityFeed'
|
||||
import TradeHistory from '@components/trade/TradeHistory'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import ActivityFilters from './ActivityFilters'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import ActivityFeedTable from './ActivityFeedTable'
|
||||
|
||||
const TABS = ['activity:activity-feed', 'activity:swaps', 'activity:trades']
|
||||
|
||||
|
@ -49,13 +49,13 @@ const HistoryTabs = () => {
|
|||
const TabContent = ({ activeTab }: { activeTab: string }) => {
|
||||
switch (activeTab) {
|
||||
case 'activity:activity-feed':
|
||||
return <ActivityFeed />
|
||||
return <ActivityFeedTable />
|
||||
case 'activity:swaps':
|
||||
return <SwapHistoryTable />
|
||||
case 'activity:trades':
|
||||
return <TradeHistory />
|
||||
default:
|
||||
return <ActivityFeed />
|
||||
return <ActivityFeedTable />
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
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'
|
||||
|
@ -16,17 +13,14 @@ 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 = {
|
||||
|
@ -91,152 +85,148 @@ const LiquidationDetails = ({
|
|||
}, [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 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 font-body text-sm text-th-fgd-3">
|
||||
{t('activity:liquidation-type')}
|
||||
</p>
|
||||
<p className="font-body text-th-fgd-1">{t('perp')}</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 font-body text-sm text-th-fgd-3">
|
||||
{t('activity:asset-liquidated')}
|
||||
</p>
|
||||
<p className="text-th-fgd-1">
|
||||
<FormatNumericValue value={assetLiquidated} />{' '}
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{assetLiquidatedSymbol}
|
||||
</span>
|
||||
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}
|
||||
<FormatNumericValue
|
||||
value={activity.activity_details.price}
|
||||
isUsd
|
||||
/>
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
<FormatNumericValue value={liquidatedValue} isUsd />
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 font-body text-sm text-th-fgd-3">
|
||||
{t('activity:asset-returned')}
|
||||
</p>
|
||||
<p className="text-th-fgd-1">
|
||||
<FormatNumericValue value={assetReturned} />{' '}
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{assetReturnedSymbol}
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-xs text-th-fgd-3">
|
||||
<FormatNumericValue value={returnedValue} isUsd />
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 font-body text-sm text-th-fgd-3">
|
||||
{t('activity:liquidation-type')}
|
||||
</p>
|
||||
<p className="font-body text-th-fgd-1">{t('spot')}</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 font-body text-sm text-th-fgd-3">
|
||||
{t('activity:asset-liquidated')}
|
||||
</p>
|
||||
<p className="text-th-fgd-1">
|
||||
<FormatNumericValue value={assetLiquidated} />{' '}
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{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="text-xs text-th-fgd-3">
|
||||
<FormatNumericValue value={liquidatedValue} isUsd />
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 font-body text-sm text-th-fgd-3">
|
||||
{t('activity:asset-returned')}
|
||||
</p>
|
||||
<p className="text-th-fgd-1">
|
||||
<FormatNumericValue value={assetReturned} />{' '}
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{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="text-xs text-th-fgd-3">
|
||||
<FormatNumericValue value={returnedValue} isUsd />
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 font-body text-sm text-th-fgd-3">
|
||||
{t('activity:liquidation-fee')}
|
||||
</p>
|
||||
<p className="text-th-fgd-1">
|
||||
<FormatNumericValue value={fee} isUsd />
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 font-body text-sm text-th-fgd-3">
|
||||
{t('activity:liquidation-side')}
|
||||
</p>
|
||||
<p className="font-body 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 font-body text-sm text-th-fgd-3">
|
||||
{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 font-body text-sm text-th-fgd-3">
|
||||
{t('transaction')}
|
||||
</p>
|
||||
<a
|
||||
className="flex items-center"
|
||||
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>
|
||||
)
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
import { EXPLORERS } from '@components/settings/PreferredExplorerSettings'
|
||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import PerpSideBadge from '@components/trade/PerpSideBadge'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Image from 'next/image'
|
||||
import { PerpTradeActivity } from 'types'
|
||||
import { PREFERRED_EXPLORER_KEY } from 'utils/constants'
|
||||
import { getDecimalCount } from 'utils/numbers'
|
||||
import { formatFee } from './ActivityFeedTable'
|
||||
|
||||
const PerpTradeDetails = ({ activity }: { activity: PerpTradeActivity }) => {
|
||||
const { t } = useTranslation(['common', 'activity', 'settings', 'trade'])
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const [preferredExplorer] = useLocalStorageState(
|
||||
PREFERRED_EXPLORER_KEY,
|
||||
EXPLORERS[0]
|
||||
)
|
||||
const {
|
||||
maker,
|
||||
maker_fee,
|
||||
perp_market_name,
|
||||
price,
|
||||
quantity,
|
||||
signature,
|
||||
taker,
|
||||
taker_fee,
|
||||
taker_side,
|
||||
} = activity.activity_details
|
||||
|
||||
const isTaker = taker === mangoAccountAddress
|
||||
|
||||
const side = isTaker ? taker_side : taker_side === 'bid' ? 'ask' : 'bid'
|
||||
|
||||
const notional = quantity * price
|
||||
|
||||
const fee = isTaker ? taker_fee * notional : maker_fee * notional
|
||||
|
||||
const totalPrice = (notional + fee) / quantity
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 font-body text-sm text-th-fgd-3">
|
||||
{t('trade:side')}
|
||||
</p>
|
||||
<PerpSideBadge basePosition={side === 'bid' ? 1 : -1} />
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 font-body text-sm text-th-fgd-3">
|
||||
{t('trade:size')}
|
||||
</p>
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
{quantity}{' '}
|
||||
<span className="font-body text-th-fgd-3">{perp_market_name}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 font-body text-sm text-th-fgd-3">
|
||||
{t('activity:execution-price')}
|
||||
</p>
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
<FormatNumericValue value={price} decimals={getDecimalCount(price)} />{' '}
|
||||
<span className="font-body text-th-fgd-3">USDC</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 font-body text-sm text-th-fgd-3">{t('value')}</p>
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
<FormatNumericValue value={notional} isUsd />
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 font-body text-sm text-th-fgd-3">{t('fee')}</p>
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
{formatFee(fee)} <span className="font-body text-th-fgd-3">USDC</span>
|
||||
</p>
|
||||
<p className="font-body text-xs text-th-fgd-3">
|
||||
{isTaker ? t('trade:taker') : t('trade:maker')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<Tooltip content={t('activity:net-price-desc')} placement="top-start">
|
||||
<p className="tooltip-underline mb-0.5 font-body text-sm text-th-fgd-3">
|
||||
{t('activity:net-price')}
|
||||
</p>
|
||||
</Tooltip>
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
<FormatNumericValue
|
||||
value={totalPrice}
|
||||
decimals={Math.max(getDecimalCount(price), 3)}
|
||||
/>{' '}
|
||||
<span className="font-body text-th-fgd-3">USDC</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 font-body text-sm text-th-fgd-3">
|
||||
{t('activity:counterparty')}
|
||||
</p>
|
||||
<a
|
||||
className="text-sm"
|
||||
href={`/?address=${isTaker ? maker : taker}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('activity:view-account')}
|
||||
</a>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 font-body text-sm text-th-fgd-3">
|
||||
{t('transaction')}
|
||||
</p>
|
||||
<a
|
||||
className="default-transition flex items-center"
|
||||
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-sm">
|
||||
{t(`settings:${preferredExplorer.name}`)}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PerpTradeDetails
|
|
@ -6,13 +6,13 @@ type SideBadgeProps = {
|
|||
|
||||
const SideBadge: FunctionComponent<SideBadgeProps> = ({ side }) => {
|
||||
if (side !== 'buy' && side !== 'sell') {
|
||||
return <div>Unknown</div>
|
||||
return <span>Unknown</span>
|
||||
}
|
||||
|
||||
const isBid = side === 'buy'
|
||||
|
||||
return (
|
||||
<div
|
||||
<span
|
||||
className={`inline-block rounded uppercase ${
|
||||
isBid
|
||||
? 'text-th-up md:border md:border-th-up'
|
||||
|
@ -21,7 +21,7 @@ const SideBadge: FunctionComponent<SideBadgeProps> = ({ side }) => {
|
|||
uppercase md:-my-0.5 md:px-1.5 md:py-0.5 md:text-xs`}
|
||||
>
|
||||
{isBid ? 'Buy' : 'Sell'}
|
||||
</div>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ const PerpSideBadge = ({ basePosition }: { basePosition: number }) => {
|
|||
return (
|
||||
<>
|
||||
{basePosition !== 0 ? (
|
||||
<div
|
||||
<span
|
||||
className={`inline-block rounded uppercase ${
|
||||
basePosition > 0
|
||||
? 'text-th-up md:border md:border-th-up'
|
||||
|
@ -11,7 +11,7 @@ const PerpSideBadge = ({ basePosition }: { basePosition: number }) => {
|
|||
uppercase md:-my-0.5 md:px-1.5 md:py-0.5 md:text-xs`}
|
||||
>
|
||||
{basePosition > 0 ? 'Long' : 'Short'}
|
||||
</div>
|
||||
</span>
|
||||
) : (
|
||||
'--'
|
||||
)}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"debit": "Debit",
|
||||
"deposit": "Deposit",
|
||||
"deposits": "Deposits",
|
||||
"execution-price": "Execution Price",
|
||||
"filter-results": "Filter",
|
||||
"liquidate_perp_base_position_or_positive_pnl": "Perp Liquidation",
|
||||
"liquidate_token_with_token": "Spot Liquidation",
|
||||
|
@ -22,8 +23,11 @@
|
|||
"liquidations": "Liquidations",
|
||||
"liquidation-details": "Liquidation Details",
|
||||
"liquidator": "Liquidator",
|
||||
"net-price": "Net Price",
|
||||
"net-price-desc": "The trade price inclusive of fees",
|
||||
"no-activity": "No account activity",
|
||||
"openbook_trade": "Spot Trade",
|
||||
"perp-details": "Perp Trade Details",
|
||||
"perps": "Perps",
|
||||
"perp_trade": "Perp Trade",
|
||||
"reset-filters": "Reset Filters",
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"debit": "Debit",
|
||||
"deposit": "Deposit",
|
||||
"deposits": "Deposits",
|
||||
"execution-price": "Execution Price",
|
||||
"filter-results": "Filter",
|
||||
"liquidate_perp_base_position_or_positive_pnl": "Perp Liquidation",
|
||||
"liquidate_token_with_token": "Spot Liquidation",
|
||||
|
@ -22,8 +23,11 @@
|
|||
"liquidations": "Liquidations",
|
||||
"liquidation-details": "Liquidation Details",
|
||||
"liquidator": "Liquidator",
|
||||
"net-price": "Net Price",
|
||||
"net-price-desc": "The trade price inclusive of fees",
|
||||
"no-activity": "No account activity",
|
||||
"openbook_trade": "Spot Trade",
|
||||
"perp-details": "Perp Trade Details",
|
||||
"perps": "Perps",
|
||||
"perp_trade": "Perp Trade",
|
||||
"reset-filters": "Reset Filters",
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"debit": "Debit",
|
||||
"deposit": "Deposit",
|
||||
"deposits": "Deposits",
|
||||
"execution-price": "Execution Price",
|
||||
"filter-results": "Filter",
|
||||
"liquidate_perp_base_position_or_positive_pnl": "Perp Liquidation",
|
||||
"liquidate_token_with_token": "Spot Liquidation",
|
||||
|
@ -22,8 +23,11 @@
|
|||
"liquidations": "Liquidations",
|
||||
"liquidation-details": "Liquidation Details",
|
||||
"liquidator": "Liquidator",
|
||||
"net-price": "Net Price",
|
||||
"net-price-desc": "The trade price inclusive of fees",
|
||||
"no-activity": "No account activity",
|
||||
"openbook_trade": "Spot Trade",
|
||||
"perp-details": "Perp Trade Details",
|
||||
"perps": "Perps",
|
||||
"perp_trade": "Perp Trade",
|
||||
"reset-filters": "Reset Filters",
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"debit": "Debit",
|
||||
"deposit": "Deposit",
|
||||
"deposits": "Deposits",
|
||||
"execution-price": "Execution Price",
|
||||
"filter-results": "Filter",
|
||||
"liquidate_perp_base_position_or_positive_pnl": "Perp Liquidation",
|
||||
"liquidate_token_with_token": "Spot Liquidation",
|
||||
|
@ -22,8 +23,11 @@
|
|||
"liquidations": "Liquidations",
|
||||
"liquidation-details": "Liquidation Details",
|
||||
"liquidator": "Liquidator",
|
||||
"net-price": "Net Price",
|
||||
"net-price-desc": "The trade price inclusive of fees",
|
||||
"no-activity": "No account activity",
|
||||
"openbook_trade": "Spot Trade",
|
||||
"perp-details": "Perp Trade Details",
|
||||
"perps": "Perps",
|
||||
"perp_trade": "Perp Trade",
|
||||
"reset-filters": "Reset Filters",
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"debit": "Debit",
|
||||
"deposit": "Deposit",
|
||||
"deposits": "Deposits",
|
||||
"execution-price": "Execution Price",
|
||||
"filter-results": "Filter",
|
||||
"liquidate_perp_base_position_or_positive_pnl": "Perp Liquidation",
|
||||
"liquidate_token_with_token": "Spot Liquidation",
|
||||
|
@ -22,8 +23,11 @@
|
|||
"liquidations": "Liquidations",
|
||||
"liquidation-details": "Liquidation Details",
|
||||
"liquidator": "Liquidator",
|
||||
"net-price": "Net Price",
|
||||
"net-price-desc": "The trade price inclusive of fees",
|
||||
"no-activity": "No account activity",
|
||||
"openbook_trade": "Spot Trade",
|
||||
"perp-details": "Perp Trade Details",
|
||||
"perps": "Perps",
|
||||
"perp_trade": "Perp Trade",
|
||||
"reset-filters": "Reset Filters",
|
||||
|
|
|
@ -132,6 +132,26 @@ export interface DepositWithdrawFeedItem {
|
|||
wallet_pk: string
|
||||
}
|
||||
|
||||
export interface PerpTradeFeedItem {
|
||||
block_datetime: string
|
||||
maker: string
|
||||
maker_fee: number
|
||||
maker_order_id: string | null
|
||||
market_index: number
|
||||
perp_market: string
|
||||
perp_market_name: string
|
||||
price: number
|
||||
quantity: number
|
||||
seq_num: number
|
||||
signature: number
|
||||
slot: number
|
||||
taker: string
|
||||
taker_client_order_id: string | null
|
||||
taker_fee: number
|
||||
taker_order_id: string | null
|
||||
taker_side: string
|
||||
}
|
||||
|
||||
export interface SpotLiquidationFeedItem {
|
||||
asset_amount: number
|
||||
asset_price: number
|
||||
|
@ -173,6 +193,13 @@ export interface LiquidationActivity {
|
|||
symbol: string
|
||||
}
|
||||
|
||||
export interface PerpTradeActivity {
|
||||
activity_details: PerpTradeFeedItem
|
||||
block_datetime: string
|
||||
activity_type: string
|
||||
symbol: string
|
||||
}
|
||||
|
||||
export function isLiquidationFeedItem(
|
||||
item: ActivityFeed
|
||||
): item is LiquidationActivity {
|
||||
|
@ -182,6 +209,15 @@ export function isLiquidationFeedItem(
|
|||
return false
|
||||
}
|
||||
|
||||
export function isPerpTradeFeedItem(
|
||||
item: ActivityFeed
|
||||
): item is PerpTradeActivity {
|
||||
if (item.activity_type === 'perp_trade') {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function isPerpLiquidation(
|
||||
activityDetails: SpotOrPerpLiquidationItem
|
||||
): activityDetails is PerpLiquidationFeedItem {
|
||||
|
@ -234,7 +270,7 @@ export type ActivityFeed = {
|
|||
| SpotLiquidationFeedItem
|
||||
| PerpLiquidationFeedItem
|
||||
| SwapHistoryItem
|
||||
| PerpTradeHistory
|
||||
| PerpTradeFeedItem
|
||||
| SpotTradeHistory
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue