/* eslint-disable @typescript-eslint/no-explicit-any */ import { EXPLORERS } from '@components/settings/PreferredExplorerSettings' 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, TableDateDisplay, 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, NoSymbolIcon } from '@heroicons/react/20/solid' import { useWallet } from '@solana/wallet-adapter-react' import mangoStore from '@store/mangoStore' 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 { PAGINATION_PAGE_LENGTH, PREFERRED_EXPLORER_KEY } from 'utils/constants' import { formatNumericValue } from 'utils/numbers' import { breakpoints } from 'utils/theme' 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, { minimumSignificantDigits: 1, maximumSignificantDigits: 3, }) } export const getFee = (activity: any, mangoAccountAddress: string) => { const { activity_type } = activity let fee = { value: '0', symbol: '' } if (activity_type === 'swap') { const { loan_origination_fee, swap_in_symbol } = activity.activity_details fee = loan_origination_fee ? { value: formatFee(loan_origination_fee), symbol: swap_in_symbol } : { value: '0', symbol: '' } } if (activity_type === 'perp_trade') { const { maker_fee, taker_fee, maker, price, quantity } = activity.activity_details const value = price * quantity fee = { value: formatFee( mangoAccountAddress === maker ? maker_fee * value : taker_fee * value, ), symbol: 'USDC', } } if (activity_type === 'openbook_trade') { 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 } export const getCreditAndDebit = ( activity: any, mangoAccountAddress: string, ) => { const { activity_type } = activity let credit = { value: '0', symbol: '' } let debit = { value: '0', symbol: '' } if (activity_type === 'liquidate_token_with_token') { const { side, liab_amount, liab_symbol, asset_amount, asset_symbol } = activity.activity_details if (side === 'liqee') { credit = { value: formatNumericValue(liab_amount), symbol: liab_symbol } debit = { value: formatNumericValue(asset_amount), symbol: asset_symbol, } } else { credit = { value: formatNumericValue(asset_amount), symbol: asset_symbol, } 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 } debit = { value: '0', symbol: '' } } if (activity_type === 'withdraw') { const { symbol, quantity } = activity.activity_details credit = { value: '0', symbol: '' } debit = { value: formatNumericValue(quantity * -1), symbol } } if (activity_type === 'swap') { const { swap_in_amount, swap_in_symbol, swap_out_amount, swap_out_symbol } = activity.activity_details credit = { value: formatNumericValue(swap_out_amount), symbol: swap_out_symbol, } debit = { value: formatNumericValue(swap_in_amount * -1), symbol: swap_in_symbol, } } if (activity_type === 'perp_trade') { const { maker_fee, perp_market_name, price, quantity, taker, taker_fee, taker_side, } = activity.activity_details const side = taker === mangoAccountAddress ? taker_side : taker_side === 'bid' ? 'ask' : 'bid' const feeRatio = taker === mangoAccountAddress ? taker_fee : maker_fee const notional = quantity * price const fee = feeRatio * notional if (side === 'bid') { credit = { value: quantity, symbol: perp_market_name } debit = { value: formatNumericValue((notional + fee) * -1), symbol: 'USDC', } } else { credit = { value: formatNumericValue(notional - fee), symbol: 'USDC', } debit = { value: `-${quantity}`, symbol: perp_market_name } } } if (activity_type === 'openbook_trade') { const { side, price, size, base_symbol, quote_symbol } = activity.activity_details credit = side === 'buy' ? { value: formatNumericValue(size), symbol: base_symbol } : { value: formatNumericValue(size * price), symbol: quote_symbol } debit = side === 'buy' ? { value: formatNumericValue(size * price * -1), symbol: quote_symbol, } : { value: formatNumericValue(size * -1), symbol: base_symbol } } return { credit, debit } } export const getValue = (activity: any, mangoAccountAddress: string) => { const { activity_type } = activity let value = 0 if (activity_type === 'liquidate_token_with_token') { 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 (base_transfer > 0) { if (side === 'liqee') { value = Math.abs(quote_transfer) } else { value = Math.abs(base_transfer * price) } } else { if (side === 'liqee') { value = Math.abs(base_transfer * price) } else { value = Math.abs(quote_transfer) } } } if (activity_type === 'deposit' || activity_type === 'withdraw') { const { usd_equivalent } = activity.activity_details value = activity_type === 'withdraw' ? usd_equivalent * -1 : usd_equivalent } if (activity_type === 'swap') { const { swap_out_amount, swap_out_price_usd } = activity.activity_details value = swap_out_amount * swap_out_price_usd } if (activity_type === 'perp_trade') { const { price, quantity, taker, taker_fee, maker_fee } = activity.activity_details const isTaker = taker === mangoAccountAddress const feeRatio = isTaker ? taker_fee : maker_fee const notional = quantity * price const fee = feeRatio * notional value = isTaker ? notional + fee : notional - fee } if (activity_type === 'openbook_trade') { const { price, size } = activity.activity_details value = price * size } return value } 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) const queryParams = mangoStore((s) => s.activityFeed.queryParams) const [offset, setOffset] = useState(0) const { connected } = useWallet() const [preferredExplorer] = useLocalStorageState( PREFERRED_EXPLORER_KEY, EXPLORERS[0], ) const { width } = useViewport() const showTableView = width ? width > breakpoints.md : false const handleShowMore = useCallback(() => { const set = mangoStore.getState().set set((s) => { s.activityFeed.loading = true }) if (!mangoAccountAddress) return setOffset(offset + PAGINATION_PAGE_LENGTH) actions.fetchActivityFeed( mangoAccountAddress, offset + PAGINATION_PAGE_LENGTH, queryParams, ) }, [actions, offset, queryParams, mangoAccountAddress]) return mangoAccountAddress && (activityFeed.length || loadActivityFeed) ? ( <> {showTableView ? ( {activityFeed.map((activity, index: number) => { const { activity_type, block_datetime } = activity const { signature } = activity.activity_details const isOpenbook = activity_type === 'openbook_trade' const amounts = getCreditAndDebit(activity, mangoAccountAddress) const value = getValue(activity, mangoAccountAddress) const fee = getFee(activity, mangoAccountAddress) const isExpandable = isLiquidationActivityFeedItem(activity) || isPerpTradeActivityFeedItem(activity) || isSpotTradeActivityFeedItem(activity) return isExpandable ? ( {({ open }) => ( <> {isLiquidationActivityFeedItem(activity) ? ( ) : isPerpTradeActivityFeedItem(activity) ? ( ) : isSpotTradeActivityFeedItem(activity) ? ( ) : null} )} ) : ( ) })}
{t('date')} {t('activity:activity')} {t('activity:credit')} {t('activity:debit')} {t('fee')} {t('value')} {t('explorer')}
) : (
{activityFeed.map((activity: any, index: number) => ( ))}
)} {loadActivityFeed ? (
{[...Array(4)].map((x, i) => (
))}
) : null} {activityFeed.length && activityFeed.length % PAGINATION_PAGE_LENGTH === 0 ? (
{t('show-more')}
) : null} ) : mangoAccountAddress || connected ? (

{t('activity:no-activity')}

) : (
) } export default ActivityFeedTable interface SharedTableBodyProps { block_datetime: string activity_type: string amounts: { credit: { value: string; symbol: string } debit: { value: string; symbol: string } } isExpandable: boolean fee: { value: string; symbol: string } isOpenbook: boolean value: number } const SharedTableBody = ({ block_datetime, activity_type, amounts, isExpandable, fee, isOpenbook, value, }: SharedTableBodyProps) => { const { t } = useTranslation('activity') return ( <> {t(`activity:${activity_type}`)} {amounts.credit.value}{' '} {amounts.credit.symbol} {amounts.debit.value}{' '} {amounts.debit.symbol} {fee.value}{' '} {fee.symbol} = 0 ? 'text-th-up' : 'text-th-down' }`} > {value > 0 && activity_type !== 'swap' && !isOpenbook && !isExpandable ? '+' : ''} ) } const MobileActivityFeedItem = ({ activity, getValue, }: { activity: any getValue: (a: any, m: string) => number }) => { const { t } = useTranslation(['common', 'activity']) const [preferredExplorer] = useLocalStorageState( PREFERRED_EXPLORER_KEY, EXPLORERS[0], ) const { mangoAccountAddress } = useMangoAccount() const { activity_type, block_datetime } = activity const { signature } = activity.activity_details const isSwap = activity_type === 'swap' const value = getValue(activity, mangoAccountAddress) const isExpandable = isLiquidationActivityFeedItem(activity) || isPerpTradeActivityFeedItem(activity) || isSpotTradeActivityFeedItem(activity) const isPerpTaker = isPerpTradeActivityFeedItem(activity) && activity.activity_details.taker === mangoAccountAddress const perpTradeSide = isPerpTaker ? activity.activity_details.taker_side : activity.activity_details.taker_side === 'bid' ? 'ask' : 'bid' return (
{isExpandable ? ( {({ open }) => ( <>

{t(`activity:${activity_type}`)}

{isLiquidationActivityFeedItem(activity) ? (

) : isPerpTradeActivityFeedItem(activity) ? (

{activity.activity_details.quantity} {activity.activity_details.perp_market_name} {' '}

) : isSpotTradeActivityFeedItem(activity) ? (

{activity.activity_details.size} {`${activity.activity_details.base_symbol}/${activity.activity_details.quote_symbol}`} {' '}

) : null}
{isLiquidationActivityFeedItem(activity) ? ( ) : isPerpTradeActivityFeedItem(activity) ? ( ) : isSpotTradeActivityFeedItem(activity) ? ( ) : null}
)}
) : (

{t(`activity:${activity_type}`)}

{isSwap ? ( <> {activity.activity_details.swap_in_symbol} for {activity.activity_details.swap_out_symbol} ) : ( <> {activity.activity_details.quantity} {activity.activity_details.symbol} )}

)}
) }