mango-v4-ui/components/trade/TradeHistory.tsx

269 lines
8.4 KiB
TypeScript
Raw Normal View History

2022-12-19 09:28:24 -08:00
import { I80F48, PerpMarket } from '@blockworks-foundation/mango-v4'
2022-12-19 09:12:03 -08:00
import SideBadge from '@components/shared/SideBadge'
import {
Table,
TableDateDisplay,
Td,
Th,
TrBody,
TrHead,
} from '@components/shared/TableElements'
2023-01-03 03:06:37 -08:00
import { NoSymbolIcon } from '@heroicons/react/20/solid'
2023-01-06 16:26:06 -08:00
import { PublicKey } from '@solana/web3.js'
2022-12-19 09:12:03 -08:00
import mangoStore from '@store/mangoStore'
import useMangoAccount from 'hooks/useMangoAccount'
import useSelectedMarket from 'hooks/useSelectedMarket'
2022-12-20 20:48:41 -08:00
import { useViewport } from 'hooks/useViewport'
2022-12-19 09:12:03 -08:00
import { useMemo } from 'react'
import { formatDecimal } from 'utils/numbers'
2022-12-20 20:48:41 -08:00
import { breakpoints } from 'utils/theme'
2022-12-19 09:12:03 -08:00
import TableMarketName from './TableMarketName'
const byTimestamp = (a: any, b: any) => {
return (
new Date(b.loadTimestamp || b.timestamp * 1000).getTime() -
new Date(a.loadTimestamp || a.timestamp * 1000).getTime()
)
}
const reverseSide = (side: string) => (side === 'buy' ? 'sell' : 'buy')
const parsedPerpEvent = (mangoAccountAddress: string, event: any) => {
const maker = event.maker.toString() === mangoAccountAddress
2022-12-19 09:12:03 -08:00
const orderId = maker ? event.makerOrderId : event.takerOrderId
const value = event.quantity * event.price
const feeRate = maker
2022-12-19 09:28:24 -08:00
? new I80F48(event.makerFee.val)
: new I80F48(event.takerFee.val)
2022-12-19 09:12:03 -08:00
const side = maker ? reverseSide(event.takerSide) : event.takerSide
return {
...event,
key: orderId?.toString(),
liquidity: maker ? 'Maker' : 'Taker',
size: event.size,
price: event.price,
value,
2022-12-19 09:28:24 -08:00
feeCost: (feeRate.toNumber() * value).toFixed(4),
2022-12-19 09:12:03 -08:00
side,
marketName: event.marketName,
}
}
const parsedSerumEvent = (event: any) => {
let liquidity
if (event.eventFlags) {
liquidity = event?.eventFlags?.maker ? 'Maker' : 'Taker'
} else {
liquidity = event.maker ? 'Maker' : 'Taker'
}
return {
...event,
liquidity,
key: `${event.maker}-${event.price}`,
value: event.price * event.size,
side: event.side,
marketName: event.marketName,
}
}
const formatTradeHistory = (
mangoAccountAddress: string,
tradeHistory: any[]
) => {
2022-12-19 09:12:03 -08:00
return tradeHistory
.flat()
.map((event) => {
if (event.eventFlags || event.nativeQuantityPaid) {
return parsedSerumEvent(event)
} else if (event.maker) {
return parsedPerpEvent(mangoAccountAddress, event)
2022-12-19 09:12:03 -08:00
} else {
return event
}
})
.sort(byTimestamp)
}
const TradeHistory = () => {
2023-01-06 16:26:06 -08:00
const group = mangoStore.getState().group
2022-12-19 09:12:03 -08:00
const { selectedMarket } = useSelectedMarket()
const { mangoAccount, mangoAccountAddress } = useMangoAccount()
2022-12-19 09:12:03 -08:00
const fills = mangoStore((s) => s.selectedMarket.fills)
2023-01-06 16:26:06 -08:00
const tradeHistory = mangoStore((s) => s.mangoAccount.tradeHistory)
2022-12-20 20:48:41 -08:00
const { width } = useViewport()
const showTableView = width ? width > breakpoints.md : false
2022-12-19 09:12:03 -08:00
const openOrderOwner = useMemo(() => {
if (!mangoAccount || !selectedMarket) return
2023-01-06 16:26:06 -08:00
if (selectedMarket instanceof PerpMarket) {
return mangoAccount.publicKey
} else {
try {
return mangoAccount.getSerum3OoAccount(selectedMarket.marketIndex)
.address
2023-01-06 16:26:06 -08:00
} catch {
console.warn(
'Unable to find OO account for mkt index',
selectedMarket.marketIndex
)
}
2022-12-19 09:12:03 -08:00
}
}, [mangoAccount, selectedMarket])
2023-01-06 16:26:06 -08:00
const eventQueueFillsForAccount = useMemo(() => {
if (!mangoAccountAddress || !selectedMarket) return []
2022-12-19 09:12:03 -08:00
const mangoAccountFills = fills
.filter((fill: any) => {
if (fill.openOrders) {
// handles serum event queue for spot trades
return openOrderOwner ? fill.openOrders.equals(openOrderOwner) : false
} else {
// handles mango event queue for perp trades
return (
fill.taker.equals(openOrderOwner) ||
fill.maker.equals(openOrderOwner)
)
}
})
.map((fill: any) => ({ ...fill, marketName: selectedMarket.name }))
return formatTradeHistory(mangoAccountAddress, mangoAccountFills)
}, [selectedMarket, mangoAccountAddress, openOrderOwner, fills])
2022-12-19 09:12:03 -08:00
2023-01-06 16:26:06 -08:00
const combinedTradeHistory = useMemo(() => {
let newFills = []
if (eventQueueFillsForAccount?.length) {
console.log('eventQueueFillsForAccount', eventQueueFillsForAccount)
newFills = eventQueueFillsForAccount.filter((fill) => {
return !tradeHistory.find((t) => {
if (t.order_id) {
return t.order_id === fill.orderId?.toString()
}
// else {
// return t.seq_num === fill.seqNum?.toString()
// }
})
})
}
return [...newFills, ...tradeHistory]
}, [eventQueueFillsForAccount, tradeHistory])
console.log('trade history', tradeHistory)
if (!selectedMarket || !group) return null
2022-12-19 09:12:03 -08:00
2023-01-06 16:26:06 -08:00
return mangoAccount && combinedTradeHistory.length ? (
showTableView ? (
<div>
<Table>
<thead>
<TrHead>
<Th className="text-left">Market</Th>
<Th className="text-right">Side</Th>
<Th className="text-right">Size</Th>
<Th className="text-right">Price</Th>
<Th className="text-right">Value</Th>
<Th className="text-right">Fee</Th>
2023-01-06 16:26:06 -08:00
<Th className="text-right">Time</Th>
</TrHead>
</thead>
<tbody>
2023-01-06 16:26:06 -08:00
{combinedTradeHistory.map((trade: any) => {
let market
if ('market' in trade) {
market = group.getSerum3MarketByExternalMarket(
new PublicKey(trade.market)
)
} else {
market = selectedMarket
}
let makerTaker = trade.liquidity
if ('maker' in trade) {
makerTaker = trade.maker ? 'Maker' : 'Taker'
}
return (
2023-01-06 16:26:06 -08:00
<TrBody
key={`${trade.signature || trade.marketIndex}${trade.size}`}
className="my-1 p-2"
>
<Td className="">
2023-01-06 16:26:06 -08:00
<TableMarketName market={market} />
</Td>
<Td className="text-right">
2022-12-20 20:48:41 -08:00
<SideBadge side={trade.side} />
</Td>
<Td className="text-right font-mono">{trade.size}</Td>
<Td className="text-right font-mono">
{formatDecimal(trade.price)}
</Td>
<Td className="text-right font-mono">
2023-01-06 16:26:06 -08:00
{trade.price * trade.size}
</Td>
<Td className="text-right">
2023-01-06 16:26:06 -08:00
<span className="font-mono">
{trade.fee_cost || trade.feeCost}
</span>
<p className="font-body text-xs text-th-fgd-4">
{makerTaker}
</p>
</Td>
2023-01-06 16:26:06 -08:00
<Td className="whitespace-nowrap text-right">
{trade.block_datetime ? (
<TableDateDisplay
2023-01-06 16:26:06 -08:00
date={trade.block_datetime}
showSeconds
/>
2023-01-06 16:26:06 -08:00
) : (
'Recent'
)}
</Td>
</TrBody>
)
})}
</tbody>
</Table>
</div>
) : (
<div>
2023-01-06 16:26:06 -08:00
{eventQueueFillsForAccount.map((trade: any) => {
return (
<div
className="flex items-center justify-between border-b border-th-bkg-3 p-4"
key={`${trade.marketIndex}`}
>
<div>
<TableMarketName market={selectedMarket} />
<div className="mt-1 flex items-center space-x-1">
<SideBadge side={trade.side} />
<p className="text-th-fgd-4">
<span className="font-mono text-th-fgd-3">
{trade.size}
</span>
{' for '}
<span className="font-mono text-th-fgd-3">
{formatDecimal(trade.price)}
</span>
</p>
</div>
</div>
<p className="font-mono">${trade.value.toFixed(2)}</p>
</div>
)
})}
2022-12-19 09:12:03 -08:00
</div>
)
) : (
<div className="flex flex-col items-center justify-center px-6 pb-8 pt-4">
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
2023-01-06 16:26:06 -08:00
<p>No trade history</p>
2022-12-19 09:12:03 -08:00
</div>
)
}
export default TradeHistory