185 lines
6.1 KiB
TypeScript
185 lines
6.1 KiB
TypeScript
|
import { PerpMarket } from '@blockworks-foundation/mango-v4'
|
||
|
import SideBadge from '@components/shared/SideBadge'
|
||
|
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
|
||
|
import { LinkIcon, NoSymbolIcon } from '@heroicons/react/20/solid'
|
||
|
import { useWallet } from '@solana/wallet-adapter-react'
|
||
|
import { PublicKey } from '@solana/web3.js'
|
||
|
import mangoStore from '@store/mangoStore'
|
||
|
import useMangoAccount from 'hooks/useMangoAccount'
|
||
|
import useSelectedMarket from 'hooks/useSelectedMarket'
|
||
|
import { useTranslation } from 'next-i18next'
|
||
|
import { useMemo } from 'react'
|
||
|
import { formatDecimal } from 'utils/numbers'
|
||
|
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 = (mangoAccountPk: PublicKey, event: any) => {
|
||
|
console.log('event: ', event)
|
||
|
|
||
|
const maker = event.maker.toString() === mangoAccountPk.toString()
|
||
|
const orderId = maker ? event.makerOrderId : event.takerOrderId
|
||
|
const value = event.quantity * event.price
|
||
|
const feeRate = maker
|
||
|
? event.makerFee.val.toNumber()
|
||
|
: event.takerFee.val.toNumber()
|
||
|
const side = maker ? reverseSide(event.takerSide) : event.takerSide
|
||
|
|
||
|
return {
|
||
|
...event,
|
||
|
key: orderId?.toString(),
|
||
|
liquidity: maker ? 'Maker' : 'Taker',
|
||
|
size: event.size,
|
||
|
price: event.price,
|
||
|
value,
|
||
|
feeCost: (feeRate * value).toFixed(4),
|
||
|
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 = (mangoAccountPk: PublicKey, tradeHistory: any[]) => {
|
||
|
return tradeHistory
|
||
|
.flat()
|
||
|
.map((event) => {
|
||
|
if (event.eventFlags || event.nativeQuantityPaid) {
|
||
|
return parsedSerumEvent(event)
|
||
|
} else if (event.maker) {
|
||
|
return parsedPerpEvent(mangoAccountPk, event)
|
||
|
} else {
|
||
|
return event
|
||
|
}
|
||
|
})
|
||
|
.sort(byTimestamp)
|
||
|
}
|
||
|
|
||
|
const TradeHistory = () => {
|
||
|
const { t } = useTranslation(['common', 'trade'])
|
||
|
const { connected } = useWallet()
|
||
|
const { selectedMarket } = useSelectedMarket()
|
||
|
const { mangoAccount } = useMangoAccount()
|
||
|
const fills = mangoStore((s) => s.selectedMarket.fills)
|
||
|
|
||
|
const openOrderOwner = useMemo(() => {
|
||
|
if (!mangoAccount || !selectedMarket) return
|
||
|
|
||
|
if (selectedMarket instanceof PerpMarket) {
|
||
|
return mangoAccount.publicKey
|
||
|
} else {
|
||
|
return mangoAccount.getSerum3OoAccount(selectedMarket.marketIndex).address
|
||
|
}
|
||
|
}, [mangoAccount, selectedMarket])
|
||
|
|
||
|
const tradeHistoryFromEventQueue = useMemo(() => {
|
||
|
if (!mangoAccount || !selectedMarket) return []
|
||
|
|
||
|
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(mangoAccount.publicKey, mangoAccountFills)
|
||
|
}, [selectedMarket, mangoAccount, openOrderOwner])
|
||
|
|
||
|
if (!selectedMarket) return null
|
||
|
|
||
|
return connected ? (
|
||
|
tradeHistoryFromEventQueue.length ? (
|
||
|
<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>
|
||
|
</TrHead>
|
||
|
</thead>
|
||
|
<tbody>
|
||
|
{tradeHistoryFromEventQueue.map((trade: any) => {
|
||
|
return (
|
||
|
<TrBody key={`${trade.marketIndex}`} className="my-1 p-2">
|
||
|
<Td className="">
|
||
|
<TableMarketName market={selectedMarket} />
|
||
|
</Td>
|
||
|
<Td className="text-right">
|
||
|
<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">
|
||
|
${trade.value.toFixed(2)}
|
||
|
</Td>
|
||
|
<Td className="text-right font-mono">{trade.feeCost}</Td>
|
||
|
</TrBody>
|
||
|
)
|
||
|
})}
|
||
|
</tbody>
|
||
|
</Table>
|
||
|
<div className="flex flex-col items-center justify-center py-8 px-20">
|
||
|
<p className="mt-1 rounded border border-th-bkg-3 p-2">
|
||
|
During the Mango V4 alpha, only your most recent trades will be
|
||
|
displayed. Full trade history will be available after the alpha
|
||
|
phase is complete.
|
||
|
</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
) : (
|
||
|
<div className="flex flex-col items-center justify-center py-8 px-20">
|
||
|
<NoSymbolIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||
|
<p>No trade history for {selectedMarket?.name}</p>
|
||
|
<p className="mt-4 rounded border border-th-bkg-3 p-2">
|
||
|
During the Mango V4 alpha, only your recent trades will be displayed.
|
||
|
Full trade history will be available after the alpha phase is
|
||
|
complete.
|
||
|
</p>
|
||
|
</div>
|
||
|
)
|
||
|
) : (
|
||
|
<div className="flex flex-col items-center p-8">
|
||
|
<LinkIcon className="mb-2 h-6 w-6 text-th-fgd-4" />
|
||
|
<p>{t('trade:connect-positions')}</p>
|
||
|
</div>
|
||
|
)
|
||
|
}
|
||
|
|
||
|
export default TradeHistory
|