add more details to activity feed spot trades
This commit is contained in:
parent
83d8f9d9db
commit
df12bc0195
|
@ -19,19 +19,23 @@ import PerpSideBadge from '@components/trade/PerpSideBadge'
|
|||
import { ChevronDownIcon, NoSymbolIcon } from '@heroicons/react/20/solid'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import dayjs from 'dayjs'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import useMangoAccount from 'hooks/useMangoAccount'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Image from 'next/legacy/image'
|
||||
import { useCallback, useState } from 'react'
|
||||
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'
|
||||
import LiquidationActivityDetails from './LiquidationActivityDetails'
|
||||
import PerpTradeActivityDetails from './PerpTradeActivityDetails'
|
||||
import {
|
||||
isLiquidationActivityFeedItem,
|
||||
isPerpTradeActivityFeedItem,
|
||||
isSpotTradeActivityFeedItem,
|
||||
} from 'types'
|
||||
import SpotTradeActivityDetails from './SpotTradeActivityDetails'
|
||||
|
||||
export const formatFee = (value: number) => {
|
||||
return value.toLocaleString(undefined, {
|
||||
|
@ -326,7 +330,9 @@ const ActivityFeedTable = () => {
|
|||
const value = getValue(activity, mangoAccountAddress)
|
||||
const fee = getFee(activity, mangoAccountAddress)
|
||||
const isExpandable =
|
||||
isLiquidationFeedItem(activity) || isPerpTradeFeedItem(activity)
|
||||
isLiquidationActivityFeedItem(activity) ||
|
||||
isPerpTradeActivityFeedItem(activity) ||
|
||||
isSpotTradeActivityFeedItem(activity)
|
||||
return isExpandable ? (
|
||||
<Disclosure key={`${signature}${index}`}>
|
||||
{({ open }) => (
|
||||
|
@ -355,13 +361,17 @@ const ActivityFeedTable = () => {
|
|||
</Td>
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel as={TrBody}>
|
||||
{isLiquidationFeedItem(activity) ? (
|
||||
{isLiquidationActivityFeedItem(activity) ? (
|
||||
<td className="p-6" colSpan={7}>
|
||||
<LiquidationDetails activity={activity} />
|
||||
<LiquidationActivityDetails activity={activity} />
|
||||
</td>
|
||||
) : isPerpTradeFeedItem(activity) ? (
|
||||
) : isPerpTradeActivityFeedItem(activity) ? (
|
||||
<td className="p-6" colSpan={7}>
|
||||
<PerpTradeDetails activity={activity} />
|
||||
<PerpTradeActivityDetails activity={activity} />
|
||||
</td>
|
||||
) : isSpotTradeActivityFeedItem(activity) ? (
|
||||
<td className="p-6" colSpan={7}>
|
||||
<SpotTradeActivityDetails activity={activity} />
|
||||
</td>
|
||||
) : null}
|
||||
</Disclosure.Panel>
|
||||
|
@ -525,14 +535,14 @@ const MobileActivityFeedItem = ({
|
|||
const { activity_type, block_datetime } = activity
|
||||
const { signature } = activity.activity_details
|
||||
const isSwap = activity_type === 'swap'
|
||||
const isOpenbook = activity_type === 'openbook_trade'
|
||||
const isPerp = activity_type === 'perp_trade'
|
||||
const value = getValue(activity, mangoAccountAddress)
|
||||
const isExpandable =
|
||||
isLiquidationFeedItem(activity) || isPerpTradeFeedItem(activity)
|
||||
isLiquidationActivityFeedItem(activity) ||
|
||||
isPerpTradeActivityFeedItem(activity) ||
|
||||
isSpotTradeActivityFeedItem(activity)
|
||||
|
||||
const isPerpTaker =
|
||||
isPerpTradeFeedItem(activity) &&
|
||||
isPerpTradeActivityFeedItem(activity) &&
|
||||
activity.activity_details.taker === mangoAccountAddress
|
||||
|
||||
const perpTradeSide = isPerpTaker
|
||||
|
@ -550,23 +560,18 @@ const MobileActivityFeedItem = ({
|
|||
<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>
|
||||
<TableDateDisplay date={block_datetime} showSeconds />
|
||||
</div>
|
||||
<div className="flex items-center space-x-6 pr-2">
|
||||
<div>
|
||||
<p className="text-right text-xs">
|
||||
{t(`activity:${activity_type}`)}
|
||||
</p>
|
||||
{isLiquidationFeedItem(activity) ? (
|
||||
{isLiquidationActivityFeedItem(activity) ? (
|
||||
<p className="text-right font-mono text-sm text-th-fgd-1">
|
||||
<FormatNumericValue value={value} isUsd />
|
||||
</p>
|
||||
) : (
|
||||
) : isPerpTradeActivityFeedItem(activity) ? (
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
<span className="mr-1">
|
||||
{activity.activity_details.quantity}
|
||||
|
@ -581,7 +586,20 @@ const MobileActivityFeedItem = ({
|
|||
/>
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
) : isSpotTradeActivityFeedItem(activity) ? (
|
||||
<p className="font-mono text-th-fgd-1">
|
||||
<span className="mr-1">
|
||||
{activity.activity_details.size}
|
||||
</span>
|
||||
<span className="font-body text-th-fgd-3">
|
||||
{`${activity.activity_details.base_symbol}/${activity.activity_details.quote_symbol}`}
|
||||
</span>
|
||||
<span className="font-body">
|
||||
{' '}
|
||||
<SideBadge side={activity.activity_details.side} />
|
||||
</span>
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
<ChevronDownIcon
|
||||
className={`${
|
||||
|
@ -598,11 +616,13 @@ const MobileActivityFeedItem = ({
|
|||
>
|
||||
<Disclosure.Panel>
|
||||
<div className="border-t border-th-bkg-3 px-4 py-4">
|
||||
{isLiquidationFeedItem(activity) ? (
|
||||
<LiquidationDetails activity={activity} />
|
||||
) : (
|
||||
<PerpTradeDetails activity={activity} />
|
||||
)}
|
||||
{isLiquidationActivityFeedItem(activity) ? (
|
||||
<LiquidationActivityDetails activity={activity} />
|
||||
) : isPerpTradeActivityFeedItem(activity) ? (
|
||||
<PerpTradeActivityDetails activity={activity} />
|
||||
) : isSpotTradeActivityFeedItem(activity) ? (
|
||||
<SpotTradeActivityDetails activity={activity} />
|
||||
) : null}
|
||||
</div>
|
||||
</Disclosure.Panel>
|
||||
</Transition>
|
||||
|
@ -612,12 +632,7 @@ const MobileActivityFeedItem = ({
|
|||
) : (
|
||||
<div className="flex items-center justify-between p-4">
|
||||
<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>
|
||||
<TableDateDisplay date={block_datetime} showSeconds />
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div>
|
||||
|
@ -647,38 +662,6 @@ const MobileActivityFeedItem = ({
|
|||
{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">
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
import { PREFERRED_EXPLORER_KEY } from 'utils/constants'
|
||||
import { abbreviateAddress } from 'utils/formatting'
|
||||
|
||||
const LiquidationDetails = ({
|
||||
const LiquidationActivityDetails = ({
|
||||
activity,
|
||||
}: {
|
||||
activity: LiquidationActivity
|
||||
|
@ -240,4 +240,4 @@ const LiquidationDetails = ({
|
|||
)
|
||||
}
|
||||
|
||||
export default LiquidationDetails
|
||||
export default LiquidationActivityDetails
|
|
@ -13,8 +13,13 @@ import { PREFERRED_EXPLORER_KEY } from 'utils/constants'
|
|||
import { abbreviateAddress } from 'utils/formatting'
|
||||
import { getDecimalCount } from 'utils/numbers'
|
||||
import { formatFee } from './ActivityFeedTable'
|
||||
import Decimal from 'decimal.js'
|
||||
|
||||
const PerpTradeDetails = ({ activity }: { activity: PerpTradeActivity }) => {
|
||||
const PerpTradeActivityDetails = ({
|
||||
activity,
|
||||
}: {
|
||||
activity: PerpTradeActivity
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'activity', 'settings', 'trade'])
|
||||
const { mangoAccountAddress } = useMangoAccount()
|
||||
const [preferredExplorer] = useLocalStorageState(
|
||||
|
@ -37,16 +42,21 @@ const PerpTradeDetails = ({ activity }: { activity: PerpTradeActivity }) => {
|
|||
|
||||
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
|
||||
const quantityDecimal = new Decimal(quantity)
|
||||
const notional = quantityDecimal.mul(new Decimal(price))
|
||||
const fee = isTaker
|
||||
? notional.mul(new Decimal(taker_fee))
|
||||
: notional.mul(new Decimal(maker_fee))
|
||||
const totalPrice = notional.plus(fee).div(quantityDecimal)
|
||||
|
||||
const counterpartyPk = isTaker ? maker : taker
|
||||
|
||||
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('market')}</p>
|
||||
<p className="font-body text-th-fgd-1">{perp_market_name}</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 font-body text-sm text-th-fgd-3">
|
||||
{t('trade:side')}
|
||||
|
@ -80,7 +90,8 @@ const PerpTradeDetails = ({ activity }: { activity: PerpTradeActivity }) => {
|
|||
<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>
|
||||
{formatFee(fee.toNumber())}{' '}
|
||||
<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')}
|
||||
|
@ -141,4 +152,4 @@ const PerpTradeDetails = ({ activity }: { activity: PerpTradeActivity }) => {
|
|||
)
|
||||
}
|
||||
|
||||
export default PerpTradeDetails
|
||||
export default PerpTradeActivityDetails
|
|
@ -0,0 +1,123 @@
|
|||
import { EXPLORERS } from '@components/settings/PreferredExplorerSettings'
|
||||
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
||||
import Tooltip from '@components/shared/Tooltip'
|
||||
import useLocalStorageState from 'hooks/useLocalStorageState'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Image from 'next/image'
|
||||
import { SpotTradeActivity } from 'types'
|
||||
import { PREFERRED_EXPLORER_KEY } from 'utils/constants'
|
||||
import { getDecimalCount } from 'utils/numbers'
|
||||
import { formatFee } from './ActivityFeedTable'
|
||||
import SideBadge from '@components/shared/SideBadge'
|
||||
import Decimal from 'decimal.js'
|
||||
|
||||
const SpotTradeActivityDetails = ({
|
||||
activity,
|
||||
}: {
|
||||
activity: SpotTradeActivity
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'activity', 'settings', 'trade'])
|
||||
const [preferredExplorer] = useLocalStorageState(
|
||||
PREFERRED_EXPLORER_KEY,
|
||||
EXPLORERS[0],
|
||||
)
|
||||
const {
|
||||
base_symbol,
|
||||
fee_cost,
|
||||
maker,
|
||||
price,
|
||||
quote_symbol,
|
||||
side,
|
||||
signature,
|
||||
size,
|
||||
} = activity.activity_details
|
||||
|
||||
const sizeDecimal = new Decimal(size)
|
||||
const notional = sizeDecimal.mul(new Decimal(price))
|
||||
const totalPrice = notional.plus(new Decimal(fee_cost)).div(sizeDecimal)
|
||||
|
||||
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('market')}</p>
|
||||
<p className="font-body text-th-fgd-1">{`${base_symbol}/${quote_symbol}`}</p>
|
||||
</div>
|
||||
<div className="col-span-1">
|
||||
<p className="mb-0.5 font-body text-sm text-th-fgd-3">
|
||||
{t('trade:side')}
|
||||
</p>
|
||||
<SideBadge side={side} />
|
||||
</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">
|
||||
{size} <span className="font-body text-th-fgd-3">{base_symbol}</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} />{' '}
|
||||
<span className="font-body text-th-fgd-3">{quote_symbol}</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_cost)}{' '}
|
||||
<span className="font-body text-th-fgd-3">{quote_symbol}</span>
|
||||
</p>
|
||||
<p className="font-body text-xs text-th-fgd-3">
|
||||
{maker ? t('trade:maker') : t('trade:taker')}
|
||||
</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">{quote_symbol}</span>
|
||||
</p>
|
||||
</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}${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 SpotTradeActivityDetails
|
|
@ -188,7 +188,7 @@ export interface DepositWithdrawFeedItem {
|
|||
wallet_pk: string
|
||||
}
|
||||
|
||||
export interface PerpTradeFeedItem {
|
||||
interface PerpTradeActivityFeedItem {
|
||||
block_datetime: string
|
||||
maker: string
|
||||
maker_fee: number
|
||||
|
@ -208,6 +208,28 @@ export interface PerpTradeFeedItem {
|
|||
taker_side: string
|
||||
}
|
||||
|
||||
interface SpotTradeActivityFeedItem {
|
||||
base_symbol: string
|
||||
bid: boolean
|
||||
block_datetime: string
|
||||
client_order_id: string
|
||||
fee_cost: number
|
||||
fee_tier: number
|
||||
instruction_num: number
|
||||
maker: boolean
|
||||
mango_account: string
|
||||
market: string
|
||||
open_orders: string
|
||||
open_orders_owner: string
|
||||
order_id: string
|
||||
price: number
|
||||
quote_symbol: string
|
||||
referrer_rebate: null
|
||||
side: 'buy' | 'sell'
|
||||
signature: string
|
||||
size: number
|
||||
}
|
||||
|
||||
export interface SpotLiquidationFeedItem {
|
||||
asset_amount: number
|
||||
asset_price: number
|
||||
|
@ -250,13 +272,20 @@ export interface LiquidationActivity {
|
|||
}
|
||||
|
||||
export interface PerpTradeActivity {
|
||||
activity_details: PerpTradeFeedItem
|
||||
activity_details: PerpTradeActivityFeedItem
|
||||
block_datetime: string
|
||||
activity_type: string
|
||||
symbol: string
|
||||
}
|
||||
|
||||
export function isLiquidationFeedItem(
|
||||
export interface SpotTradeActivity {
|
||||
activity_details: SpotTradeActivityFeedItem
|
||||
block_datetime: string
|
||||
activity_type: string
|
||||
symbol: string
|
||||
}
|
||||
|
||||
export function isLiquidationActivityFeedItem(
|
||||
item: ActivityFeed,
|
||||
): item is LiquidationActivity {
|
||||
if (item.activity_type.includes('liquidate')) {
|
||||
|
@ -265,7 +294,7 @@ export function isLiquidationFeedItem(
|
|||
return false
|
||||
}
|
||||
|
||||
export function isPerpTradeFeedItem(
|
||||
export function isPerpTradeActivityFeedItem(
|
||||
item: ActivityFeed,
|
||||
): item is PerpTradeActivity {
|
||||
if (item.activity_type === 'perp_trade') {
|
||||
|
@ -274,6 +303,15 @@ export function isPerpTradeFeedItem(
|
|||
return false
|
||||
}
|
||||
|
||||
export function isSpotTradeActivityFeedItem(
|
||||
item: ActivityFeed,
|
||||
): item is SpotTradeActivity {
|
||||
if (item.activity_type === 'openbook_trade') {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function isPerpLiquidation(
|
||||
activityDetails: SpotOrPerpLiquidationItem,
|
||||
): activityDetails is PerpLiquidationFeedItem {
|
||||
|
@ -343,7 +381,7 @@ export type ActivityFeed = {
|
|||
| SpotLiquidationFeedItem
|
||||
| PerpLiquidationFeedItem
|
||||
| SwapHistoryItem
|
||||
| PerpTradeFeedItem
|
||||
| PerpTradeActivityFeedItem
|
||||
| SpotTradeHistory
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue