225 lines
7.6 KiB
TypeScript
225 lines
7.6 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import SwapHistoryTable from '../swap/SwapHistoryTable'
|
|
import TradeHistory from '@components/trade/TradeHistory'
|
|
import mangoStore from '@store/mangoStore'
|
|
import useMangoAccount from 'hooks/useMangoAccount'
|
|
import ActivityFeedTable, {
|
|
getCreditAndDebit,
|
|
getFee,
|
|
getValue,
|
|
} from './ActivityFeedTable'
|
|
import { handleExport } from 'utils/export'
|
|
import { LinkButton } from '@components/shared/Button'
|
|
import Loading from '@components/shared/Loading'
|
|
import { ArrowDownTrayIcon } from '@heroicons/react/20/solid'
|
|
import {
|
|
countLeadingZeros,
|
|
floorToDecimal,
|
|
formatCurrencyValue,
|
|
formatNumericValue,
|
|
} from 'utils/numbers'
|
|
import { formatTokenSymbol } from 'utils/tokens'
|
|
import { fetchTradeHistory, parseApiTradeHistory } from 'hooks/useTradeHistory'
|
|
import { PublicKey } from '@solana/web3.js'
|
|
import { PerpMarket } from '@blockworks-foundation/mango-v4'
|
|
import useUnownedAccount from 'hooks/useUnownedAccount'
|
|
import SecondaryTabBar from '@components/shared/SecondaryTabBar'
|
|
import ActivityFilters from './ActivityFilters'
|
|
import { useTranslation } from 'react-i18next'
|
|
|
|
const TABS = ['activity:activity-feed', 'activity:swaps', 'activity:trades']
|
|
|
|
const HistoryTabs = () => {
|
|
const { t } = useTranslation(['common', 'account', 'activity', 'trade'])
|
|
const [activeTab, setActiveTab] = useState<string>('activity:activity-feed')
|
|
const actions = mangoStore((s) => s.actions)
|
|
const { mangoAccountAddress } = useMangoAccount()
|
|
const { isUnownedAccount } = useUnownedAccount()
|
|
const [loadExportData, setLoadExportData] = useState(false)
|
|
|
|
useEffect(() => {
|
|
if (actions && mangoAccountAddress) {
|
|
actions.fetchActivityFeed(mangoAccountAddress)
|
|
}
|
|
}, [actions, mangoAccountAddress])
|
|
|
|
const exportActivityDataToCSV = async () => {
|
|
setLoadExportData(true)
|
|
await actions.fetchActivityFeed(mangoAccountAddress, 0, '', 10000)
|
|
const activityFeed = mangoStore.getState().activityFeed.feed
|
|
const dataToExport = activityFeed.map((row) => {
|
|
const { activity_type, block_datetime } = row
|
|
const { signature } = row.activity_details
|
|
const amounts = getCreditAndDebit(row, mangoAccountAddress)
|
|
const value = getValue(row, mangoAccountAddress)
|
|
const fee = getFee(row, mangoAccountAddress)
|
|
const timestamp = new Date(block_datetime)
|
|
return {
|
|
date: `${timestamp.toLocaleDateString()} ${timestamp.toLocaleTimeString()}`,
|
|
activity: t(`activity:${activity_type}`),
|
|
credit: `${amounts.credit.value} ${amounts.credit.symbol}`,
|
|
debit: `${amounts.debit.value} ${amounts.debit.symbol}`,
|
|
fee: `${fee.value} ${fee.symbol}`,
|
|
value: value.toFixed(3),
|
|
signature,
|
|
}
|
|
})
|
|
|
|
const title = `${mangoAccountAddress}-Activity-${new Date().toLocaleDateString()}`
|
|
|
|
handleExport(dataToExport, title)
|
|
setLoadExportData(false)
|
|
}
|
|
|
|
const exportSwapsDataToCSV = async () => {
|
|
setLoadExportData(true)
|
|
await actions.fetchSwapHistory(mangoAccountAddress, 0, 0, 10000)
|
|
const swapHistory = mangoStore.getState().mangoAccount.swapHistory.data
|
|
const dataToExport = swapHistory.map((row) => {
|
|
const {
|
|
block_datetime,
|
|
signature,
|
|
swap_in_amount,
|
|
swap_in_loan_origination_fee,
|
|
swap_in_symbol,
|
|
swap_out_amount,
|
|
loan,
|
|
loan_origination_fee,
|
|
swap_out_price_usd,
|
|
swap_out_symbol,
|
|
} = row
|
|
const borrowAmount =
|
|
loan > 0 ? `${floorToDecimal(loan, countLeadingZeros(loan) + 2)}` : 0
|
|
const borrowFee =
|
|
swap_in_loan_origination_fee > 0
|
|
? swap_in_loan_origination_fee.toFixed(4)
|
|
: loan_origination_fee > 0
|
|
? loan_origination_fee.toFixed(4)
|
|
: 0
|
|
|
|
const inSymbol = formatTokenSymbol(swap_in_symbol)
|
|
const outSymbol = formatTokenSymbol(swap_out_symbol)
|
|
|
|
const inDecimals = countLeadingZeros(swap_in_amount) + 2
|
|
const outDecimals = countLeadingZeros(swap_out_amount) + 2
|
|
const timestamp = new Date(block_datetime)
|
|
|
|
return {
|
|
date: `${timestamp.toLocaleDateString()} ${timestamp.toLocaleTimeString()}`,
|
|
paid: `${formatNumericValue(swap_in_amount, inDecimals)} ${inSymbol}`,
|
|
received: `${formatNumericValue(
|
|
swap_out_amount,
|
|
outDecimals,
|
|
)} ${outSymbol}`,
|
|
value: formatCurrencyValue(swap_out_price_usd * swap_out_amount),
|
|
borrow: `${borrowAmount} ${inSymbol}`,
|
|
['borrow fee']: `${borrowFee} ${inSymbol}`,
|
|
signature,
|
|
}
|
|
})
|
|
|
|
const title = `${mangoAccountAddress}-Swaps-${new Date().toLocaleDateString()}`
|
|
|
|
handleExport(dataToExport, title)
|
|
setLoadExportData(false)
|
|
}
|
|
|
|
const exportTradesDataToCSV = async () => {
|
|
setLoadExportData(true)
|
|
const group = mangoStore.getState().group
|
|
if (!group) return
|
|
const tradeHistory = await fetchTradeHistory(mangoAccountAddress, 0, 10000)
|
|
const dataToExport = tradeHistory.map((row) => {
|
|
const trade = parseApiTradeHistory(mangoAccountAddress, row)
|
|
const timestamp = new Date(trade.block_datetime)
|
|
let market
|
|
if ('market' in trade) {
|
|
market = group.getSerum3MarketByExternalMarket(
|
|
new PublicKey(trade.market),
|
|
)
|
|
} else if ('perp_market' in trade) {
|
|
market = group.getPerpMarketByMarketIndex(trade.market_index)
|
|
}
|
|
const { side, price, size, feeCost, liquidity, signature } = trade
|
|
return {
|
|
date: `${timestamp.toLocaleDateString()} ${timestamp.toLocaleTimeString()}`,
|
|
market: market?.name,
|
|
side:
|
|
market instanceof PerpMarket
|
|
? side === 'buy'
|
|
? t('trade:long')
|
|
: t('trade:short')
|
|
: t(side),
|
|
size: size,
|
|
price: price,
|
|
value: (price * size).toFixed(2),
|
|
fee: formatNumericValue(feeCost),
|
|
type: liquidity,
|
|
signature,
|
|
}
|
|
})
|
|
|
|
const title = `${mangoAccountAddress}-Trades-${new Date().toLocaleDateString()}`
|
|
|
|
handleExport(dataToExport, title)
|
|
setLoadExportData(false)
|
|
}
|
|
|
|
const handleExportData = (dataType: string) => {
|
|
if (dataType === 'activity:activity-feed') {
|
|
exportActivityDataToCSV()
|
|
} else if (dataType === 'activity:swaps') {
|
|
exportSwapsDataToCSV()
|
|
} else {
|
|
exportTradesDataToCSV()
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div className="relative">
|
|
<SecondaryTabBar
|
|
activeTab={activeTab}
|
|
setActiveTab={setActiveTab}
|
|
tabs={TABS}
|
|
/>
|
|
<div className="flex w-full items-center justify-end border-b border-th-bkg-3 py-3 pr-4 sm:-mt-14 sm:h-14 sm:border-b-0 sm:py-0 md:pr-6">
|
|
{!isUnownedAccount ? (
|
|
<LinkButton
|
|
className="flex items-center font-normal no-underline"
|
|
disabled={loadExportData}
|
|
onClick={() => handleExportData(activeTab)}
|
|
>
|
|
<span className="mr-2">
|
|
{t('account:export', { dataType: t(activeTab) })}
|
|
</span>
|
|
{loadExportData ? (
|
|
<Loading />
|
|
) : (
|
|
<ArrowDownTrayIcon className="h-5 w-5" />
|
|
)}
|
|
</LinkButton>
|
|
) : null}
|
|
{activeTab === 'activity:activity-feed' ? <ActivityFilters /> : null}
|
|
</div>
|
|
</div>
|
|
<TabContent activeTab={activeTab} />
|
|
</>
|
|
)
|
|
}
|
|
|
|
const TabContent = ({ activeTab }: { activeTab: string }) => {
|
|
switch (activeTab) {
|
|
case 'activity:activity-feed':
|
|
return <ActivityFeedTable />
|
|
case 'activity:swaps':
|
|
return <SwapHistoryTable />
|
|
case 'activity:trades':
|
|
return <TradeHistory />
|
|
default:
|
|
return <ActivityFeedTable />
|
|
}
|
|
}
|
|
|
|
export default HistoryTabs
|