Merge branch 'main' into accept-terms

This commit is contained in:
tlrsssss 2023-03-28 15:01:35 -04:00 committed by GitHub
commit 08fcae739c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 2593 additions and 1642 deletions

View File

@ -28,6 +28,7 @@ import HealthHeart from './account/HealthHeart'
import useMangoAccount from 'hooks/useMangoAccount'
import { useTheme } from 'next-themes'
import { IconButton } from './shared/Button'
import LeaderboardIcon from './icons/LeaderboardIcon'
const SideNav = ({ collapsed }: { collapsed: boolean }) => {
const { t } = useTranslation(['common', 'search'])
@ -122,14 +123,15 @@ const SideNav = ({ collapsed }: { collapsed: boolean }) => {
icon={<EllipsisHorizontalIcon className="h-5 w-5" />}
title={t('more')}
>
{/* <MenuItem
active={pathname === '/fees'}
collapsed={false}
icon={<ReceiptTaxIcon className="h-5 w-5" />}
title={t('fees')}
pagePath="/fees"
hideIconBg
/> */}
<MenuItem
active={pathname === '/leaderboard'}
collapsed={false}
icon={<LeaderboardIcon className="h-5 w-5" />}
title={t('leaderboard')}
pagePath="/leaderboard"
hideIconBg
showTooltip={false}
/>
<MenuItem
active={pathname === '/search'}
collapsed={false}

View File

@ -126,13 +126,17 @@ const AccountPage = () => {
data: fundingData,
isLoading: loadingFunding,
isFetching: fetchingFunding,
} = useQuery(['funding'], () => fetchFundingTotals(mangoAccountAddress), {
cacheTime: 1000 * 60 * 10,
staleTime: 1000 * 60,
retry: 3,
refetchOnWindowFocus: false,
enabled: !!mangoAccountAddress,
})
} = useQuery(
['funding', mangoAccountAddress],
() => fetchFundingTotals(mangoAccountAddress),
{
cacheTime: 1000 * 60 * 10,
staleTime: 1000 * 60,
retry: 3,
refetchOnWindowFocus: false,
enabled: !!mangoAccountAddress,
}
)
const oneDayPerformanceData: PerformanceDataItem[] | [] = useMemo(() => {
if (!performanceData || !performanceData.length) return []

View File

@ -1,133 +1,39 @@
import { EXPLORERS } from '@components/settings/PreferredExplorerSettings'
import { IconButton } from '@components/shared/Button'
import FormatNumericValue from '@components/shared/FormatNumericValue'
import { ArrowLeftIcon } from '@heroicons/react/20/solid'
import mangoStore from '@store/mangoStore'
import dayjs from 'dayjs'
import useLocalStorageState from 'hooks/useLocalStorageState'
import { useTranslation } from 'next-i18next'
import Image from 'next/legacy/image'
import { useState } from 'react'
import { PREFERRED_EXPLORER_KEY } from 'utils/constants'
import { useEffect, useState } from 'react'
import ActivityFeedTable from './ActivityFeedTable'
import { LiquidationActivity } from 'types'
import LiquidationDetails from './LiquidationDetails'
const ActivityFeed = () => {
const activityFeed = mangoStore((s) => s.activityFeed.feed)
const [showActivityDetail, setShowActivityDetail] =
useState<LiquidationActivity>()
const [scrollPosition, setScrollPosition] = useState(0)
const handleShowActivityDetails = (activity: LiquidationActivity) => {
setShowActivityDetail(activity)
setScrollPosition(window.scrollY)
}
useEffect(() => {
if (scrollPosition && !showActivityDetail) {
window.scroll(0, scrollPosition)
}
}, [scrollPosition, showActivityDetail])
return !showActivityDetail ? (
<ActivityFeedTable
activityFeed={activityFeed}
handleShowActivityDetails={handleShowActivityDetails}
/>
) : (
<ActivityDetails
activity={showActivityDetail}
setShowActivityDetail={setShowActivityDetail}
/>
<div className="px-6">
<LiquidationDetails
activity={showActivityDetail}
setShowActivityDetail={setShowActivityDetail}
/>
</div>
)
}
export default ActivityFeed
const ActivityDetails = ({
activity,
setShowActivityDetail,
}: {
activity: LiquidationActivity
setShowActivityDetail: (x: LiquidationActivity | undefined) => void
}) => {
const { t } = useTranslation(['common', 'activity', 'settings'])
const [preferredExplorer] = useLocalStorageState(
PREFERRED_EXPLORER_KEY,
EXPLORERS[0]
)
const { block_datetime, activity_type } = activity
const {
asset_amount,
asset_price,
asset_symbol,
liab_amount,
liab_price,
liab_symbol,
signature,
} = activity.activity_details
return (
<div>
<div className="flex items-center p-6">
<IconButton
className="mr-4"
onClick={() => setShowActivityDetail(undefined)}
>
<ArrowLeftIcon className="h-5 w-5" />
</IconButton>
<h2 className="text-lg">{t('activity:liquidation-details')}</h2>
</div>
<div className="grid grid-cols-1 gap-4 px-6 md:grid-cols-2">
<div className="col-span-1">
<p className="mb-0.5 text-sm">{t('date')}</p>
<p className="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>
</div>
<div className="col-span-1">
<p className="mb-0.5 text-sm">{t('activity:liquidation-type')}</p>
<p className="text-th-fgd-1">
{activity_type === 'liquidate_token_with_token'
? t('spot')
: t('perp')}
</p>
</div>
<div className="col-span-1">
<p className="mb-0.5 text-sm">{t('activity:asset-liquidated')}</p>
<p className="font-mono text-th-fgd-1">
<FormatNumericValue value={asset_amount} />{' '}
<span className="font-body">{asset_symbol}</span>
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}
<FormatNumericValue value={asset_price} isUsd />
</p>
<p className="font-mono text-xs text-th-fgd-3">
<FormatNumericValue value={asset_price * asset_amount} isUsd />
</p>
</div>
<div className="col-span-1">
<p className="mb-0.5 text-sm">{t('activity:asset-returned')}</p>
<p className="font-mono text-th-fgd-1">
<FormatNumericValue value={liab_amount} />{' '}
<span className="font-body">{liab_symbol}</span>
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}
<FormatNumericValue value={liab_price} isUsd />
</p>
<p className="font-mono text-xs text-th-fgd-3">
<FormatNumericValue value={liab_price * liab_amount} isUsd />
</p>
</div>
</div>
<div className="col-span-3 mt-8 flex justify-center border-y border-th-bkg-3 py-3">
<a
className="default-transition flex items-center text-th-fgd-2 hover:text-th-fgd-3"
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-base">{t('view-transaction')}</span>
</a>
</div>
</div>
)
}

View File

@ -1,14 +1,14 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { EXPLORERS } from '@components/settings/PreferredExplorerSettings'
import { IconButton, LinkButton } from '@components/shared/Button'
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, 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 { Transition } from '@headlessui/react'
import {
ChevronDownIcon,
ChevronRightIcon,
@ -22,11 +22,12 @@ import useMangoAccount from 'hooks/useMangoAccount'
import { useViewport } from 'hooks/useViewport'
import { useTranslation } from 'next-i18next'
import Image from 'next/legacy/image'
import { Fragment, useCallback, useState } from 'react'
import { useCallback, useState } from 'react'
import { ActivityFeed, isLiquidationFeedItem, LiquidationActivity } 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'
const formatFee = (value: number) => {
return value.toLocaleString(undefined, {
@ -55,6 +56,43 @@ const getFee = (activity: any, mangoAccountAddress: string) => {
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
}
@ -79,6 +117,23 @@ const getCreditAndDebit = (activity: any) => {
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 }
@ -131,12 +186,24 @@ const getValue = (activity: any) => {
const { activity_type } = activity
let value = 0
if (activity_type === 'liquidate_token_with_token') {
const { side, liab_amount, liab_price, asset_amount, asset_price } =
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 (side === 'liqee') {
value = asset_amount * asset_price
if (base_transfer > 0) {
if (side === 'liqee') {
value = Math.abs(quote_transfer)
} else {
value = Math.abs(base_transfer * price)
}
} else {
value = liab_amount * liab_price
if (side === 'liqee') {
value = Math.abs(base_transfer * price)
} else {
value = Math.abs(quote_transfer)
}
}
}
if (activity_type === 'deposit' || activity_type === 'withdraw') {
@ -218,8 +285,6 @@ const ActivityFeedTable = ({
{activityFeed.map((activity, index: number) => {
const { activity_type, block_datetime } = activity
const { signature } = activity.activity_details
const isLiquidation =
activity_type === 'liquidate_token_with_token'
const isOpenbook = activity_type === 'openbook_trade'
const amounts = getCreditAndDebit(activity)
const value = getValue(activity)
@ -228,7 +293,7 @@ const ActivityFeedTable = ({
<TrBody
key={signature + index}
className={`default-transition text-sm hover:bg-th-bkg-2 ${
isLiquidation ? 'cursor-pointer' : ''
isLiquidationFeedItem(activity) ? 'cursor-pointer' : ''
}`}
onClick={
isLiquidationFeedItem(activity)
@ -271,7 +336,8 @@ const ActivityFeedTable = ({
className={`text-right font-mono ${
activity_type === 'swap' ||
activity_type === 'perp_trade' ||
isOpenbook
isOpenbook ||
isLiquidationFeedItem(activity)
? 'text-th-fgd-2'
: value >= 0
? 'text-th-up'
@ -281,13 +347,14 @@ const ActivityFeedTable = ({
{value > 0 &&
activity_type !== 'swap' &&
activity_type !== 'perp_trade' &&
!isOpenbook
!isOpenbook &&
!isLiquidationFeedItem(activity)
? '+'
: ''}
<FormatNumericValue value={value} isUsd />
</Td>
<Td>
{activity_type !== 'liquidate_token_with_token' ? (
{!isLiquidationFeedItem(activity) ? (
<div className="flex items-center justify-end">
<Tooltip
content={`View on ${t(
@ -345,7 +412,7 @@ const ActivityFeedTable = ({
{activityFeed.length &&
activityFeed.length % PAGINATION_PAGE_LENGTH === 0 ? (
<div className="flex justify-center py-6">
<LinkButton onClick={handleShowMore}>Show More</LinkButton>
<LinkButton onClick={handleShowMore}>{t('show-more')}</LinkButton>
</div>
) : null}
</>
@ -371,120 +438,146 @@ const MobileActivityFeedItem = ({
getValue: (x: any) => number
}) => {
const { t } = useTranslation(['common', 'activity'])
const [expandActivityDetails, setExpandActivityDetails] = useState(false)
const [preferredExplorer] = useLocalStorageState(
PREFERRED_EXPLORER_KEY,
EXPLORERS[0]
)
const { activity_type, block_datetime } = activity
const { signature } = activity.activity_details
const isLiquidation = activity_type === 'liquidate_token_with_token'
const isSwap = activity_type === 'swap'
const isOpenbook = activity_type === 'openbook_trade'
const isPerp = activity_type === 'perp_trade'
const value = getValue(activity)
return (
<div key={signature} className="border-b border-th-bkg-3 p-4">
<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>
</div>
<div className="flex items-center space-x-4">
<div key={signature} className="border-b border-th-bkg-3">
{isLiquidationFeedItem(activity) ? (
<Disclosure>
{({ open }) => (
<>
<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>
</div>
<div className="flex items-center space-x-6 pr-2">
<div>
<p className="text-right text-xs">
{t(`activity:${activity_type}`)}
</p>
<p className="text-right font-mono text-sm text-th-fgd-1">
<FormatNumericValue value={value} isUsd />
</p>
</div>
<ChevronDownIcon
className={`${
open ? 'rotate-180' : 'rotate-360'
} h-6 w-6 flex-shrink-0 text-th-fgd-3`}
/>
</div>
</div>
</Disclosure.Button>
<Transition
enter="transition ease-in duration-200"
enterFrom="opacity-0"
enterTo="opacity-100"
>
<Disclosure.Panel>
<div className="border-t border-th-bkg-3 px-4 py-4">
<LiquidationDetails activity={activity} />
</div>
</Disclosure.Panel>
</Transition>
</>
)}
</Disclosure>
) : (
<div className="flex items-center justify-between p-4">
<div>
<p className="text-right text-xs">
{t(`activity:${activity_type}`)}
<p className="text-sm text-th-fgd-1">
{dayjs(block_datetime).format('ddd D MMM')}
</p>
<p className="text-right font-mono text-sm text-th-fgd-1">
{isLiquidation ? (
<FormatNumericValue value={value} isUsd />
) : isSwap ? (
<>
<span className="mr-1">
<FormatNumericValue
value={activity.activity_details.swap_in_amount}
decimals={6}
/>
</span>
<span className="font-body text-th-fgd-3">
{activity.activity_details.swap_in_symbol}
</span>
<span className="mx-1 font-body text-th-fgd-3">for</span>
<span className="mr-1">
<FormatNumericValue
value={activity.activity_details.swap_out_amount}
decimals={6}
/>
</span>
<span className="font-body text-th-fgd-3">
{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 font-body ${
activity.activity_details.side === 'buy'
? 'text-th-up'
: 'text-th-down'
}`}
>
{activity.activity_details.side === 'buy' ? 'BUY' : 'SELL'}
</span> */}
<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">
{activity.activity_details.quantity}
</span>
<span className="font-body text-th-fgd-3">
{activity.activity_details.symbol}
</span>
</>
)}
<p className="text-xs text-th-fgd-3">
{dayjs(block_datetime).format('h:mma')}
</p>
</div>
{isLiquidation ? (
<IconButton
onClick={() => setExpandActivityDetails((prev) => !prev)}
>
<ChevronDownIcon
className={`${
expandActivityDetails ? 'rotate-180' : 'rotate-360'
} h-6 w-6 flex-shrink-0 text-th-fgd-1`}
/>
</IconButton>
) : (
<div className="flex items-center space-x-4">
<div>
<p className="text-right text-xs">
{t(`activity:${activity_type}`)}
</p>
<p className="text-right font-mono text-sm text-th-fgd-1">
{isSwap ? (
<>
<span className="mr-1">
<FormatNumericValue
value={activity.activity_details.swap_in_amount}
decimals={6}
/>
</span>
<span className="font-body text-th-fgd-3">
{activity.activity_details.swap_in_symbol}
</span>
<span className="mx-1 font-body text-th-fgd-3">for</span>
<span className="mr-1">
<FormatNumericValue
value={activity.activity_details.swap_out_amount}
decimals={6}
/>
</span>
<span className="font-body text-th-fgd-3">
{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">
{activity.activity_details.quantity}
</span>
<span className="font-body text-th-fgd-3">
{activity.activity_details.symbol}
</span>
</>
)}
</p>
</div>
<a
href={`${preferredExplorer.url}${signature}`}
target="_blank"
@ -499,89 +592,9 @@ const MobileActivityFeedItem = ({
/>
</div>
</a>
)}
</div>
</div>
<Transition
appear={true}
show={expandActivityDetails}
as={Fragment}
enter="transition ease-in duration-200"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition ease-out"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="mt-4 grid grid-cols-2 gap-4 border-t border-th-bkg-3 pt-4">
<div className="col-span-1">
<p className="mb-0.5 text-sm">{t('activity:asset-liquidated')}</p>
<p className="font-mono text-sm text-th-fgd-1">
<FormatNumericValue
value={activity.activity_details.asset_amount}
/>{' '}
<span className="font-body">
{activity.activity_details.asset_symbol}
</span>
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}
<FormatNumericValue
value={activity.activity_details.asset_price}
isUsd
/>
</p>
<p className="font-mono text-xs text-th-fgd-3">
<FormatNumericValue
value={
activity.activity_details.asset_price *
activity.activity_details.asset_amount
}
isUsd
/>
</p>
</div>
<div className="col-span-1">
<p className="mb-0.5 text-sm">{t('activity:asset-returned')}</p>
<p className="font-mono text-sm text-th-fgd-1">
<FormatNumericValue
value={activity.activity_details.liab_amount}
/>{' '}
<span className="font-body">
{activity.activity_details.liab_symbol}
</span>
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}
<FormatNumericValue
value={activity.activity_details.liab_price}
isUsd
/>
</p>
<p className="font-mono text-xs text-th-fgd-3">
<FormatNumericValue
value={
activity.activity_details.liab_price *
activity.activity_details.liab_amount
}
isUsd
/>
</p>
</div>
<div className="col-span-2 flex justify-center pt-3">
<a
className="default-transition flex items-center text-sm text-th-fgd-1 hover:text-th-fgd-3"
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-base">{t('view-transaction')}</span>
</a>
</div>
</div>
</Transition>
)}
</div>
)
}

View File

@ -39,6 +39,7 @@ const DEFAULT_ADVANCED_FILTERS = {
const DEFAULT_PARAMS = [
'deposit',
'perp_trade',
'liquidate_perp_base_position_or_positive_pnl',
'liquidate_token_with_token',
'openbook_trade',
'swap',
@ -68,7 +69,7 @@ const ActivityFilters = () => {
}, [advancedFilters])
const queryParams = useMemo(() => {
return !params.length || params.length === 6
return !params.length || params.length === 7
? advancedParamsString
: `&activity-type=${params.toString()}${advancedParamsString}`
}, [advancedParamsString, params])

View File

@ -26,12 +26,17 @@ const AssetsLiabilities = ({ isMobile }: { isMobile: boolean }) => {
if (!group || !mangoAccount) return [0, 0, 0, 0]
const assets = toUiDecimalsForQuote(mangoAccount.getAssetsValue(group))
const liabs = toUiDecimalsForQuote(mangoAccount.getLiabsValue(group))
const assetsRatio = (assets / (assets + liabs)) * 100
const liabsRatio = 100 - assetsRatio
let assetsRatio = 0
let liabsRatio = 0
if (assets && liabs) {
assetsRatio = (assets / (assets + liabs)) * 100
liabsRatio = 100 - assetsRatio
}
return [assets, assetsRatio, liabs, liabsRatio]
}, [group, mangoAccount])
const chartData = useMemo(() => {
if (!assetsValue && !liabsValue) return []
return [
{ name: 'assets', value: assetsValue },
{ name: 'liabilities', value: liabsValue },
@ -45,7 +50,7 @@ const AssetsLiabilities = ({ isMobile }: { isMobile: boolean }) => {
return (
<div className="flex flex-col items-center pt-4 md:flex-row md:space-x-4">
{mangoAccount ? (
{mangoAccount && chartData.length ? (
<PieChart width={size} height={size}>
<Pie
cursor="pointer"

View File

@ -0,0 +1,245 @@
import { EXPLORERS } from '@components/settings/PreferredExplorerSettings'
import { IconButton } from '@components/shared/Button'
import FormatNumericValue from '@components/shared/FormatNumericValue'
import { ArrowLeftIcon } from '@heroicons/react/20/solid'
import dayjs from 'dayjs'
import useLocalStorageState from 'hooks/useLocalStorageState'
import { useTranslation } from 'next-i18next'
import Image from 'next/image'
import { useMemo } from 'react'
import {
isPerpLiquidation,
LiquidationActivity,
SpotOrPerpLiquidationItem,
} from 'types'
import { PREFERRED_EXPLORER_KEY } from 'utils/constants'
const LiquidationDetails = ({
activity,
setShowActivityDetail,
}: {
activity: LiquidationActivity
setShowActivityDetail?: (x: LiquidationActivity | undefined) => void
}) => {
const { t } = useTranslation(['common', 'activity', 'settings'])
const [preferredExplorer] = useLocalStorageState(
PREFERRED_EXPLORER_KEY,
EXPLORERS[0]
)
const { block_datetime } = activity
const getAssetLiquidatedReturned = (details: SpotOrPerpLiquidationItem) => {
const assets = {
liquidated: { amount: 0, symbol: '', value: 0 },
returned: { amount: 0, symbol: '', value: 0 },
}
if (isPerpLiquidation(details)) {
const { base_transfer, perp_market_name, price, quote_transfer } = details
const isLiquidatorBase = base_transfer > 0 ? 1 : -1
const isLiquidatorQuote = base_transfer > 0 ? -1 : 1
const liquidatedAmount = Math.abs(base_transfer)
const returnedAmount = Math.abs(quote_transfer)
assets.liquidated.amount = liquidatedAmount
assets.liquidated.value = liquidatedAmount * price * isLiquidatorBase
assets.liquidated.symbol = perp_market_name
assets.returned.amount = returnedAmount
assets.returned.value = returnedAmount * isLiquidatorQuote
assets.returned.symbol = 'USDC'
} else {
const {
liab_amount,
liab_price,
liab_symbol,
asset_amount,
asset_price,
asset_symbol,
} = details
assets.liquidated.amount = Math.abs(asset_amount)
assets.liquidated.symbol = asset_symbol
assets.liquidated.value = asset_amount * asset_price
assets.returned.amount = Math.abs(liab_amount)
assets.returned.symbol = liab_symbol
assets.returned.value = liab_amount * liab_price
}
return assets
}
const [
assetLiquidated,
assetReturned,
assetLiquidatedSymbol,
assetReturnedSymbol,
liquidatedValue,
returnedValue,
fee,
] = useMemo(() => {
if (!activity) return [0, 0, '', '', 0, 0, 0]
const values = getAssetLiquidatedReturned(activity.activity_details)
const isNegativeFee = activity.activity_details.side === 'liqee' ? -1 : 1
const fee =
(Math.abs(values.liquidated.value) - Math.abs(values.returned.value)) *
isNegativeFee
return [
values.liquidated.amount,
values.returned.amount,
values.liquidated.symbol,
values.returned.symbol,
values.liquidated.value,
values.returned.value,
fee,
]
}, [activity])
return (
<div className="md:pb-10">
{setShowActivityDetail ? (
<div className="flex items-center py-6">
<IconButton
className="mr-4"
onClick={() => setShowActivityDetail(undefined)}
size="small"
>
<ArrowLeftIcon className="h-5 w-5" />
</IconButton>
<h2 className="text-lg">{t('activity:liquidation-details')}</h2>
</div>
) : null}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{isPerpLiquidation(activity.activity_details) ? (
<>
<div className="col-span-1">
<p className="mb-0.5 text-sm">{t('date')}</p>
<p className="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>
</div>
<div className="col-span-1">
<p className="mb-0.5 text-sm">{t('activity:liquidation-type')}</p>
<p className="text-th-fgd-1">{t('perp')}</p>
</div>
<div className="col-span-1">
<p className="mb-0.5 text-sm">{t('activity:asset-liquidated')}</p>
<p className="font-mono text-th-fgd-1">
<FormatNumericValue value={assetLiquidated} />{' '}
<span className="font-body">{assetLiquidatedSymbol}</span>
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}
<FormatNumericValue
value={activity.activity_details.price}
isUsd
/>
</p>
<p className="font-mono text-xs text-th-fgd-3">
<FormatNumericValue value={liquidatedValue} isUsd />
</p>
</div>
<div className="col-span-1">
<p className="mb-0.5 text-sm">{t('activity:asset-returned')}</p>
<p className="font-mono text-th-fgd-1">
<FormatNumericValue value={assetReturned} />{' '}
<span className="font-body">{assetReturnedSymbol}</span>
</p>
<p className="font-mono text-xs text-th-fgd-3">
<FormatNumericValue value={returnedValue} isUsd />
</p>
</div>
</>
) : (
<>
<div className="col-span-1">
<p className="mb-0.5 text-sm">{t('date')}</p>
<p className="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>
</div>
<div className="col-span-1">
<p className="mb-0.5 text-sm">{t('activity:liquidation-type')}</p>
<p className="text-th-fgd-1">{t('spot')}</p>
</div>
<div className="col-span-1">
<p className="mb-0.5 text-sm">{t('activity:asset-liquidated')}</p>
<p className="font-mono text-th-fgd-1">
<FormatNumericValue value={assetLiquidated} />{' '}
<span className="font-body">{assetLiquidatedSymbol}</span>
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}
<FormatNumericValue
value={activity.activity_details.asset_price}
isUsd
/>
</p>
<p className="font-mono text-xs text-th-fgd-3">
<FormatNumericValue value={liquidatedValue} isUsd />
</p>
</div>
<div className="col-span-1">
<p className="mb-0.5 text-sm">{t('activity:asset-returned')}</p>
<p className="font-mono text-th-fgd-1">
<FormatNumericValue value={assetReturned} />{' '}
<span className="font-body">{assetReturnedSymbol}</span>
<span className="ml-2 font-body text-th-fgd-3">at</span>{' '}
<FormatNumericValue
value={activity.activity_details.liab_price}
isUsd
/>
</p>
<p className="font-mono text-xs text-th-fgd-3">
<FormatNumericValue value={returnedValue} isUsd />
</p>
</div>
</>
)}
<div className="col-span-1">
<p className="mb-0.5 text-sm">{t('activity:liquidation-fee')}</p>
<p className="font-mono text-th-fgd-1">
<FormatNumericValue value={fee} isUsd />
</p>
</div>
<div className="col-span-1">
<p className="mb-0.5 text-sm">{t('activity:liquidation-side')}</p>
<p className="text-th-fgd-1">
{activity.activity_details.side === 'liqor'
? t('activity:liquidator')
: t('activity:liquidated')}
</p>
</div>
<div className="col-span-1">
<p className="mb-0.5 text-sm">{t('activity:counterparty')}</p>
<a
className="text-sm"
href={`/?address=${activity.activity_details.counterparty}`}
target="_blank"
rel="noopener noreferrer"
>
{t('activity:view-account')}
</a>
</div>
<div className="col-span-1">
<p className="mb-0.5 text-sm">{t('transaction')}</p>
<a
className="default-transition flex items-center text-th-fgd-2 hover:text-th-fgd-3"
href={`${preferredExplorer.url}${activity.activity_details.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>
</div>
)
}
export default LiquidationDetails

View File

@ -0,0 +1,14 @@
const LeaderboardIcon = ({ className }: { className?: string }) => {
return (
<svg
className={`${className}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M6.55423 13.2164L7.61683 13.5408C7.80664 13.8247 7.99644 14.0679 8.18597 14.2704C8.86894 15 9.66603 15.4867 10.6148 15.7703V16.4595C10.6148 17.7162 9.66603 18.7705 8.48964 18.7705H8.26198C8.07218 18.7705 7.92049 18.9325 7.92049 19.1353V20.6352C7.92049 20.838 8.07218 21 8.26198 21H15.7761C15.9659 21 16.1176 20.838 16.1176 20.6352V19.1353C16.1176 18.9325 15.9659 18.7705 15.7761 18.7705H15.5484C14.3721 18.7705 13.4233 17.7569 13.4233 16.5002V15.8111C14.3721 15.5679 15.1692 15.0815 15.8521 14.3111C16.0419 14.1084 16.1936 13.8652 16.4213 13.5815L17.4839 13.2976C20.1403 12.5273 22 9.93272 22 7.0137V5.7974C22 5.39187 21.6964 5.02705 21.2789 5.02705H18.1668L18.1671 3.77035C18.1671 3.36482 17.8635 3 17.446 3H6.55429C6.17468 3 5.83317 3.32436 5.83317 3.77035V5.0678H2.72112C2.34151 5.0678 2 5.39217 2 5.83815V6.9733C2.03812 9.89196 3.85963 12.4865 6.55423 13.2164ZM18.0911 7.05412H20.0645C20.0267 8.838 19.0019 10.419 17.4841 11.1082C17.9772 9.44596 18.0911 7.78371 18.0911 7.05412ZM5.87112 7.05412C5.90898 7.82447 5.98495 9.44596 6.47839 11.1082C4.96052 10.4191 3.93573 8.838 3.89791 7.05412H5.87112Z" />
</svg>
)
}
export default LeaderboardIcon

View File

@ -0,0 +1,135 @@
import ButtonGroup from '@components/forms/ButtonGroup'
import { LinkButton } from '@components/shared/Button'
import SheenLoader from '@components/shared/SheenLoader'
import { NoSymbolIcon } from '@heroicons/react/20/solid'
import { useInfiniteQuery } from '@tanstack/react-query'
import { useTranslation } from 'next-i18next'
import { useMemo, useState } from 'react'
import { EmptyObject } from 'types'
import { MANGO_DATA_API_URL } from 'utils/constants'
import LeaderboardTable from './LeaderboardTable'
export interface LeaderboardRes {
date_hour: string
mango_account: string
pnl: number
profile_image_url: string | null
profile_name: string
start_date_hour: string
trader_category: string
wallet_pk: string
}
type DaysToShow = '1DAY' | '1WEEK' | 'ALLTIME'
const isLeaderboard = (
response: null | EmptyObject | LeaderboardRes[]
): response is LeaderboardRes[] => {
if (response && Array.isArray(response)) {
return true
}
return false
}
const fetchLeaderboard = async (
daysToShow: DaysToShow,
offset = 0
): Promise<Array<LeaderboardRes>> => {
const data = await fetch(
`${MANGO_DATA_API_URL}/leaderboard-pnl?over_period=${daysToShow}&offset=${offset}`
)
const parsedData: null | EmptyObject | LeaderboardRes[] = await data.json()
let leaderboardData
if (isLeaderboard(parsedData)) {
leaderboardData = parsedData
}
return leaderboardData ?? []
}
const LeaderboardPage = () => {
const { t } = useTranslation(['common', 'leaderboard'])
const [daysToShow, setDaysToShow] = useState<DaysToShow>('ALLTIME')
const { data, isLoading, isFetching, isFetchingNextPage, fetchNextPage } =
useInfiniteQuery(
['leaderboard', daysToShow],
({ pageParam }) => fetchLeaderboard(daysToShow, pageParam),
{
cacheTime: 1000 * 60 * 10,
staleTime: 1000 * 60 * 5,
retry: 3,
refetchOnWindowFocus: false,
keepPreviousData: true,
getNextPageParam: (_lastPage, pages) => pages.length * 20,
}
)
const leaderboardData = useMemo(() => {
if (data?.pages.length) {
return data.pages.flat()
}
return []
}, [data, daysToShow])
const handleDaysToShow = (days: DaysToShow) => {
setDaysToShow(days)
}
return (
<div className="p-4 md:p-10 lg:px-0">
<div className="grid grid-cols-12">
<div className="col-span-12 lg:col-span-8 lg:col-start-3">
<div className="mb-6 flex flex-col md:flex-row md:items-center md:justify-between">
<div>
<h1 className="mb-2">{t('leaderboard')}</h1>
<p className="mb-4 md:mb-0">
{t('leaderboard:leaderboard-desc')}
</p>
</div>
<div className="w-full md:w-48">
<ButtonGroup
activeValue={daysToShow}
disabled={isLoading}
onChange={(v) => handleDaysToShow(v)}
names={['24h', '7d', '30d', t('all')]}
values={['1DAY', '1WEEK', 'ALLTIME']}
/>
</div>
</div>
{leaderboardData.length ? (
<LeaderboardTable
data={leaderboardData}
loading={isFetching && !isFetchingNextPage}
/>
) : !isFetching && !isLoading ? (
<div className="flex flex-col items-center rounded-md border border-th-bkg-3 p-4">
<NoSymbolIcon className="mb-1 h-7 w-7 text-th-fgd-4" />
<p>{t('leaderboard:leaderboard-unavailable')}</p>
</div>
) : null}
{isLoading || isFetchingNextPage ? (
<div className="mt-2 space-y-2">
{[...Array(20)].map((x, i) => (
<SheenLoader className="flex flex-1" key={i}>
<div className="h-16 w-full rounded-md bg-th-bkg-2" />
</SheenLoader>
))}
</div>
) : null}
{leaderboardData.length && leaderboardData.length < 100 ? (
<LinkButton
className="mx-auto mt-6"
onClick={() => fetchNextPage()}
>
{t('show-more')}
</LinkButton>
) : null}
</div>
</div>
</div>
)
}
export default LeaderboardPage

View File

@ -0,0 +1,107 @@
import MedalIcon from '@components/icons/MedalIcon'
import ProfileImage from '@components/profile/ProfileImage'
import SheenLoader from '@components/shared/SheenLoader'
import { ChevronRightIcon } from '@heroicons/react/20/solid'
import { useViewport } from 'hooks/useViewport'
import { formatCurrencyValue } from 'utils/numbers'
import { breakpoints } from 'utils/theme'
import { LeaderboardRes } from './LeaderboardPage'
const LeaderboardTable = ({
data,
loading,
}: {
data: LeaderboardRes[]
loading: boolean
}) => {
return (
<>
{/* <div className="grid grid-cols-12 px-4 pb-2">
<div className="col-span-2 md:col-span-1">
<p className="text-xs text-th-fgd-4">{t('rank')}</p>
</div>
<div className="col-span-4 md:col-span-5">
<p className="text-xs text-th-fgd-4">{t('trader')}</p>
</div>
<div className="col-span-5 flex justify-end">
<p className="text-xs text-th-fgd-4">{t('pnl')}</p>
</div>
</div> */}
<div className="space-y-2">
{data.map((d, i) => (
<LeaderboardRow
item={d}
loading={loading}
rank={i + 1}
key={d.mango_account + d.pnl + i}
/>
))}
</div>
</>
)
}
export default LeaderboardTable
const LeaderboardRow = ({
item,
loading,
rank,
}: {
item: LeaderboardRes
loading?: boolean
rank: number
}) => {
const { profile_name, profile_image_url, mango_account, pnl, wallet_pk } =
item
const { width } = useViewport()
const isMobile = width ? width < breakpoints.md : false
return !loading ? (
<a
className="default-transition flex w-full items-center justify-between rounded-md border border-th-bkg-3 px-3 py-3 md:px-4 md:hover:bg-th-bkg-2"
href={`/?address=${mango_account}`}
rel="noopener noreferrer"
target="_blank"
>
<div className="flex items-center space-x-3">
<div className="relative flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-th-bkg-3 md:mr-2">
<p
className={`relative z-10 font-bold ${
rank < 4 ? 'text-th-bkg-1' : 'text-th-fgd-3'
}`}
>
{rank}
</p>
{rank < 4 ? (
<MedalIcon className="absolute shadow-md" rank={rank} />
) : null}
</div>
<ProfileImage
imageSize={isMobile ? '32' : '40'}
imageUrl={profile_image_url}
placeholderSize={isMobile ? '20' : '24'}
/>
<div className="text-left">
<p className="capitalize text-th-fgd-2 md:text-base">
{profile_name ||
wallet_pk.slice(0, 4) + '...' + wallet_pk.slice(-4)}
</p>
<p className="text-xs text-th-fgd-4">
Acc: {mango_account.slice(0, 4) + '...' + mango_account.slice(-4)}
</p>
</div>
</div>
<div className="flex items-center">
<span className="mr-3 text-right font-mono md:text-base">
{formatCurrencyValue(pnl, 2)}
</span>
<ChevronRightIcon className="h-5 w-5 text-th-fgd-3" />
</div>
</a>
) : (
<SheenLoader className="flex flex-1">
<div className="h-16 w-full rounded-md bg-th-bkg-2" />
</SheenLoader>
)
}

View File

@ -18,6 +18,7 @@ import {
BanknotesIcon,
} from '@heroicons/react/20/solid'
import SolanaTps from '@components/SolanaTps'
import LeaderboardIcon from '@components/icons/LeaderboardIcon'
const StyledBarItemLabel = ({
children,
@ -105,7 +106,7 @@ const MoreMenuPanel = ({
const { t } = useTranslation(['common', 'search'])
return (
<div
className={`fixed bottom-0 z-30 h-96 w-full overflow-hidden rounded-t-3xl bg-th-bkg-2 px-4 transition duration-300 ease-in-out ${
className={`fixed bottom-0 z-30 h-[420px] w-full overflow-hidden rounded-t-3xl bg-th-bkg-2 px-4 transition duration-300 ease-in-out ${
showPanel ? 'translate-y-0' : 'translate-y-full'
}`}
>
@ -129,6 +130,11 @@ const MoreMenuPanel = ({
path="/stats"
icon={<ChartBarIcon className="h-5 w-5" />}
/>
<MoreMenuItem
title={t('leaderboard')}
path="/leaderboard"
icon={<LeaderboardIcon className="h-5 w-5" />}
/>
<MoreMenuItem
title={t('search:search-accounts')}
path="/search"
@ -171,17 +177,17 @@ const MoreMenuItem = ({
target="_blank"
rel="noopener noreferrer"
>
<div className="flex items-center">
<div className="flex items-center space-x-3">
{icon}
<span className="ml-1.5">{title}</span>
<span>{title}</span>
</div>
<ChevronRightIcon className="h-5 w-5" />
</a>
) : (
<Link href={path} shallow={true} className={classNames}>
<div className="flex items-center">
<div className="flex items-center space-x-3">
{icon}
<span className="ml-1.5">{title}</span>
<span>{title}</span>
</div>
<ChevronRightIcon className="h-5 w-5" />
</Link>

View File

@ -9,7 +9,7 @@ const ProfileImage = ({
}: {
imageSize: string
placeholderSize: string
imageUrl?: string
imageUrl?: string | null
isOwnerProfile?: boolean
}) => {
const profile = mangoStore((s) => s.profile.details)

View File

@ -10,7 +10,10 @@ import Change from '../shared/Change'
import MarketLogos from '@components/trade/MarketLogos'
import dynamic from 'next/dynamic'
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
import { usePerpFundingRate } from '@components/trade/PerpFundingRate'
import {
formatFunding,
usePerpFundingRate,
} from '@components/trade/PerpFundingRate'
import { IconButton } from '@components/shared/Button'
import { ChevronRightIcon } from '@heroicons/react/20/solid'
import FormatNumericValue from '@components/shared/FormatNumericValue'
@ -95,7 +98,7 @@ const PerpMarketsTable = ({
(r) => r.market_index === market.perpMarketIndex
)
fundingRate = marketRate
? `${marketRate.funding_rate_hourly.toFixed(4)}%`
? `${formatFunding.format(marketRate.funding_rate_hourly)}`
: ''
} else {
fundingRate = ''

View File

@ -74,7 +74,11 @@ const TokenPage = () => {
const bankName = useMemo(() => {
if (!token) return
return token === 'WBTC' ? 'wBTC (Portal)' : token.toString()
return token === 'WBTC'
? 'wBTC (Portal)'
: token === 'ETH'
? 'ETH (Portal)'
: token.toString()
}, [token])
const bank = useMemo(() => {

View File

@ -22,9 +22,9 @@ type TopTokenAccount = {
token_index: number
value: number
wallet_pk: string
profile_image_url: string
profile_name: string
trader_category: string
profile_image_url?: string
profile_name?: string
trader_category?: string
}
const fetchTopTokenAccounts = async (tokenIndex: number) => {
@ -44,37 +44,37 @@ const fetchTopTokenAccounts = async (tokenIndex: number) => {
TopDepositorBorrower[]
] = await Promise.all([depositsResponse.json(), borrowsResponse.json()])
const depositorProfilesResponse = await Promise.all(
depositsData.map((r: TopDepositorBorrower) =>
fetch(
`${MANGO_DATA_API_URL}/user-data/profile-details?wallet-pk=${r.wallet_pk}`
)
)
)
// const depositorProfilesResponse = await Promise.all(
// depositsData.map((r: TopDepositorBorrower) =>
// fetch(
// `${MANGO_DATA_API_URL}/user-data/profile-details?wallet-pk=${r.wallet_pk}`
// )
// )
// )
const depositorProfilesData = await Promise.all(
depositorProfilesResponse.map((d) => d.json())
)
// const depositorProfilesData = await Promise.all(
// depositorProfilesResponse.map((d) => d.json())
// )
const borrowerProfilesResponse = await Promise.all(
borrowsData.map((r: TopDepositorBorrower) =>
fetch(
`${MANGO_DATA_API_URL}/user-data/profile-details?wallet-pk=${r.wallet_pk}`
)
)
)
// const borrowerProfilesResponse = await Promise.all(
// borrowsData.map((r: TopDepositorBorrower) =>
// fetch(
// `${MANGO_DATA_API_URL}/user-data/profile-details?wallet-pk=${r.wallet_pk}`
// )
// )
// )
const borrowerProfilesData = await Promise.all(
borrowerProfilesResponse.map((d) => d.json())
)
// const borrowerProfilesData = await Promise.all(
// borrowerProfilesResponse.map((d) => d.json())
// )
return [
depositsData
.map((data, i) => ({ ...data, ...depositorProfilesData[i] }))
// .map((data, i) => ({ ...data, ...depositorProfilesData[i] }))
.slice(0, 10)
.filter((d) => d.value > 0),
borrowsData
.map((data, i) => ({ ...data, ...borrowerProfilesData[i] }))
// .map((data, i) => ({ ...data, ...borrowerProfilesData[i] }))
.slice(0, 10)
.filter((d) => d.value < 0),
]

View File

@ -30,7 +30,7 @@ export const usePerpFundingRate = () => {
return Array.isArray(res?.data) ? res : { isSuccess: false, data: null }
}
const formatFunding = Intl.NumberFormat('en', {
export const formatFunding = Intl.NumberFormat('en', {
minimumSignificantDigits: 1,
maximumSignificantDigits: 2,
style: 'percent',

View File

@ -60,7 +60,7 @@ export const ConnectWalletButton: React.FC = () => {
</div>
</div>
</button>
<div className="absolute top-1/2 right-0 z-10 h-full -translate-y-1/2">
<div className="absolute top-1/2 right-0 z-20 h-full -translate-y-1/2">
<WalletSelect />
</div>
</div>

View File

@ -233,7 +233,15 @@ const Dashboard: NextPage = () => {
/>
<KeyValuePair
label="Collected fees native"
value={bank.collectedFeesNative.toNumber()}
value={`${toUiDecimals(
bank.collectedFeesNative.toNumber(),
bank.mintDecimals
).toFixed(2)} ($${(
toUiDecimals(
bank.collectedFeesNative.toNumber(),
bank.mintDecimals
) * bank.uiPrice
).toFixed(2)})`}
/>
<KeyValuePair
label="Dust"
@ -241,21 +249,35 @@ const Dashboard: NextPage = () => {
/>
<KeyValuePair
label="Deposits"
value={toUiDecimals(
value={`${toUiDecimals(
bank.indexedDeposits
.mul(bank.depositIndex)
.toNumber(),
bank.mintDecimals
)}
)} ($${(
toUiDecimals(
bank.indexedDeposits
.mul(bank.depositIndex)
.toNumber(),
bank.mintDecimals
) * bank.uiPrice
).toFixed(2)})`}
/>
<KeyValuePair
label="Borrows"
value={toUiDecimals(
value={`${toUiDecimals(
bank.indexedBorrows
.mul(bank.borrowIndex)
.toNumber(),
bank.mintDecimals
)}
)} ($${(
toUiDecimals(
bank.indexedBorrows
.mul(bank.borrowIndex)
.toNumber(),
bank.mintDecimals
) * bank.uiPrice
).toFixed(2)})`}
/>
<KeyValuePair
label="Avg Utilization"

26
pages/leaderboard.tsx Normal file
View File

@ -0,0 +1,26 @@
import LeaderboardPage from '@components/leaderboard/LeaderboardPage'
import type { NextPage } from 'next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, [
'common',
'leaderboard',
'profile',
'search',
])),
},
}
}
const Leaderboard: NextPage = () => {
return (
<div className="pb-16 md:pb-0">
<LeaderboardPage />
</div>
)
}
export default Leaderboard

View File

@ -6,16 +6,22 @@
"asset-liquidated": "Asset Liquidated",
"asset-returned": "Asset Returned",
"connect-activity": "Connect to view your account activity",
"counterparty": "Counterparty",
"credit": "Credit",
"debit": "Debit",
"deposit": "Deposit",
"deposits": "Deposits",
"filter-results": "Filter",
"liquidate_perp_base_position_or_positive_pnl": "Perp Liquidation",
"liquidate_token_with_token": "Spot Liquidation",
"liquidated": "Liquidated",
"liquidation": "Liquidation",
"liquidation-fee": "Liquidation Fee",
"liquidation-type": "Liquidation Type",
"liquidation-side": "Liquidation Side",
"liquidations": "Liquidations",
"liquidation-details": "Liquidation Details",
"liquidate_token_with_token": "Spot Liquidation",
"liquidator": "Liquidator",
"no-activity": "No account activity",
"openbook_trade": "Spot Trade",
"perps": "Perps",
@ -30,6 +36,7 @@
"update": "Update",
"value-from": "Value From",
"value-to": "Value To",
"view-account": "View Account",
"withdraw": "Withdraw",
"withdrawals": "Withdrawals"
}

View File

@ -16,6 +16,7 @@
"actions": "Actions",
"add-new-account": "Add New Account",
"agree-and-continue": "Agree and Continue",
"all": "All",
"amount": "Amount",
"amount-owed": "Amount Owed",
"asset-liability-weight": "Asset/Liability Weights",
@ -83,6 +84,7 @@
"insufficient-sol": "Solana requires 0.04454 SOL rent to create a Mango Account. This will be returned if you close your account.",
"interest-earned": "Interest Earned",
"interest-earned-paid": "Interest Earned",
"leaderboard": "Leaderboard",
"learn": "Learn",
"leverage": "Leverage",
"liability-weight": "Liability Weight",
@ -126,6 +128,7 @@
"select-withdraw-token": "Select Withdraw Token",
"sell": "Sell",
"settings": "Settings",
"show-more": "Show More",
"show-zero-balances": "Show Zero Balances",
"solana-tps": "Solana TPS",
"spot": "Spot",

View File

@ -0,0 +1,4 @@
{
"leaderboard-desc": "Top 100 Mango traders by total PnL (spot and perp)",
"leaderboard-unavailable": "Leaderboard unavailable"
}

View File

@ -6,16 +6,22 @@
"asset-liquidated": "Asset Liquidated",
"asset-returned": "Asset Returned",
"connect-activity": "Connect to view your account activity",
"counterparty": "Counterparty",
"credit": "Credit",
"debit": "Debit",
"deposit": "Deposit",
"deposits": "Deposits",
"filter-results": "Filter",
"liquidate_perp_base_position_or_positive_pnl": "Perp Liquidation",
"liquidate_token_with_token": "Spot Liquidation",
"liquidated": "Liquidated",
"liquidation": "Liquidation",
"liquidation-fee": "Liquidation Fee",
"liquidation-type": "Liquidation Type",
"liquidation-side": "Liquidation Side",
"liquidations": "Liquidations",
"liquidation-details": "Liquidation Details",
"liquidate_token_with_token": "Spot Liquidation",
"liquidator": "Liquidator",
"no-activity": "No account activity",
"openbook_trade": "Spot Trade",
"perps": "Perps",
@ -30,6 +36,7 @@
"update": "Update",
"value-from": "Value From",
"value-to": "Value To",
"view-account": "View Account",
"withdraw": "Withdraw",
"withdrawals": "Withdrawals"
}

View File

@ -16,6 +16,7 @@
"actions": "Actions",
"add-new-account": "Add New Account",
"agree-and-continue": "Agree and Continue",
"all": "All",
"amount": "Amount",
"amount-owed": "Amount Owed",
"asset-liability-weight": "Asset/Liability Weights",
@ -83,6 +84,7 @@
"insufficient-sol": "Solana requires 0.04454 SOL rent to create a Mango Account. This will be returned if you close your account.",
"interest-earned": "Interest Earned",
"interest-earned-paid": "Interest Earned",
"leaderboard": "Leaderboard",
"learn": "Learn",
"leverage": "Leverage",
"liability-weight": "Liability Weight",
@ -126,6 +128,7 @@
"select-withdraw-token": "Select Withdraw Token",
"sell": "Sell",
"settings": "Settings",
"show-more": "Show More",
"show-zero-balances": "Show Zero Balances",
"solana-tps": "Solana TPS",
"spot": "Spot",

View File

@ -0,0 +1,4 @@
{
"leaderboard-desc": "Top 100 Mango traders by total PnL (spot and perp)",
"leaderboard-unavailable": "Leaderboard unavailable"
}

View File

@ -6,16 +6,22 @@
"asset-liquidated": "Asset Liquidated",
"asset-returned": "Asset Returned",
"connect-activity": "Connect to view your account activity",
"counterparty": "Counterparty",
"credit": "Credit",
"debit": "Debit",
"deposit": "Deposit",
"deposits": "Deposits",
"filter-results": "Filter",
"liquidate_perp_base_position_or_positive_pnl": "Perp Liquidation",
"liquidate_token_with_token": "Spot Liquidation",
"liquidated": "Liquidated",
"liquidation": "Liquidation",
"liquidation-fee": "Liquidation Fee",
"liquidation-type": "Liquidation Type",
"liquidation-side": "Liquidation Side",
"liquidations": "Liquidations",
"liquidation-details": "Liquidation Details",
"liquidate_token_with_token": "Spot Liquidation",
"liquidator": "Liquidator",
"no-activity": "No account activity",
"openbook_trade": "Spot Trade",
"perps": "Perps",
@ -30,6 +36,7 @@
"update": "Update",
"value-from": "Value From",
"value-to": "Value To",
"view-account": "View Account",
"withdraw": "Withdraw",
"withdrawals": "Withdrawals"
}

View File

@ -16,6 +16,7 @@
"actions": "Actions",
"add-new-account": "Add New Account",
"agree-and-continue": "Agree and Continue",
"all": "All",
"amount": "Amount",
"amount-owed": "Amount Owed",
"asset-liability-weight": "Asset/Liability Weights",
@ -83,6 +84,7 @@
"insufficient-sol": "Solana requires 0.04454 SOL rent to create a Mango Account. This will be returned if you close your account.",
"interest-earned": "Interest Earned",
"interest-earned-paid": "Interest Earned",
"leaderboard": "Leaderboard",
"learn": "Learn",
"leverage": "Leverage",
"liability-weight": "Liability Weight",
@ -126,6 +128,7 @@
"select-withdraw-token": "Select Withdraw Token",
"sell": "Sell",
"settings": "Settings",
"show-more": "Show More",
"show-zero-balances": "Show Zero Balances",
"solana-tps": "Solana TPS",
"spot": "Spot",

View File

@ -0,0 +1,4 @@
{
"leaderboard-desc": "Top 100 Mango traders by total PnL (spot and perp)",
"leaderboard-unavailable": "Leaderboard unavailable"
}

View File

@ -6,16 +6,22 @@
"asset-liquidated": "Asset Liquidated",
"asset-returned": "Asset Returned",
"connect-activity": "Connect to view your account activity",
"counterparty": "Counterparty",
"credit": "Credit",
"debit": "Debit",
"deposit": "Deposit",
"deposits": "Deposits",
"filter-results": "Filter",
"liquidate_perp_base_position_or_positive_pnl": "Perp Liquidation",
"liquidate_token_with_token": "Spot Liquidation",
"liquidated": "Liquidated",
"liquidation": "Liquidation",
"liquidation-fee": "Liquidation Fee",
"liquidation-type": "Liquidation Type",
"liquidation-side": "Liquidation Side",
"liquidations": "Liquidations",
"liquidation-details": "Liquidation Details",
"liquidate_token_with_token": "Spot Liquidation",
"liquidator": "Liquidator",
"no-activity": "No account activity",
"openbook_trade": "Spot Trade",
"perps": "Perps",
@ -30,6 +36,7 @@
"update": "Update",
"value-from": "Value From",
"value-to": "Value To",
"view-account": "View Account",
"withdraw": "Withdraw",
"withdrawals": "Withdrawals"
}

View File

@ -16,6 +16,7 @@
"actions": "Actions",
"add-new-account": "Add New Account",
"agree-and-continue": "Agree and Continue",
"all": "All",
"amount": "Amount",
"amount-owed": "Amount Owed",
"asset-liability-weight": "Asset/Liability Weights",
@ -83,6 +84,7 @@
"insufficient-sol": "Solana requires 0.04454 SOL rent to create a Mango Account. This will be returned if you close your account.",
"interest-earned": "Interest Earned",
"interest-earned-paid": "Interest Earned",
"leaderboard": "Leaderboard",
"learn": "Learn",
"leverage": "Leverage",
"liability-weight": "Liability Weight",
@ -126,6 +128,7 @@
"select-withdraw-token": "Select Withdraw Token",
"sell": "Sell",
"settings": "Settings",
"show-more": "Show More",
"show-zero-balances": "Show Zero Balances",
"solana-tps": "Solana TPS",
"spot": "Spot",

View File

@ -0,0 +1,4 @@
{
"leaderboard-desc": "Top 100 Mango traders by total PnL (spot and perp)",
"leaderboard-unavailable": "Leaderboard unavailable"
}

View File

@ -6,16 +6,22 @@
"asset-liquidated": "Asset Liquidated",
"asset-returned": "Asset Returned",
"connect-activity": "Connect to view your account activity",
"counterparty": "Counterparty",
"credit": "Credit",
"debit": "Debit",
"deposit": "Deposit",
"deposits": "Deposits",
"filter-results": "Filter",
"liquidate_perp_base_position_or_positive_pnl": "Perp Liquidation",
"liquidate_token_with_token": "Spot Liquidation",
"liquidated": "Liquidated",
"liquidation": "Liquidation",
"liquidation-fee": "Liquidation Fee",
"liquidation-type": "Liquidation Type",
"liquidation-side": "Liquidation Side",
"liquidations": "Liquidations",
"liquidation-details": "Liquidation Details",
"liquidate_token_with_token": "Spot Liquidation",
"liquidator": "Liquidator",
"no-activity": "No account activity",
"openbook_trade": "Spot Trade",
"perps": "Perps",
@ -30,6 +36,7 @@
"update": "Update",
"value-from": "Value From",
"value-to": "Value To",
"view-account": "View Account",
"withdraw": "Withdraw",
"withdrawals": "Withdrawals"
}

View File

@ -16,6 +16,7 @@
"actions": "Actions",
"add-new-account": "Add New Account",
"agree-and-continue": "Agree and Continue",
"all": "All",
"amount": "Amount",
"amount-owed": "Amount Owed",
"asset-liability-weight": "Asset/Liability Weights",
@ -83,6 +84,7 @@
"insufficient-sol": "Solana requires 0.04454 SOL rent to create a Mango Account. This will be returned if you close your account.",
"interest-earned": "Interest Earned",
"interest-earned-paid": "Interest Earned",
"leaderboard": "Leaderboard",
"learn": "Learn",
"leverage": "Leverage",
"liability-weight": "Liability Weight",
@ -126,6 +128,7 @@
"select-withdraw-token": "Select Withdraw Token",
"sell": "Sell",
"settings": "Settings",
"show-more": "Show More",
"show-zero-balances": "Show Zero Balances",
"solana-tps": "Solana TPS",
"spot": "Spot",

View File

@ -0,0 +1,4 @@
{
"leaderboard-desc": "Top 100 Mango traders by total PnL (spot and perp)",
"leaderboard-unavailable": "Leaderboard unavailable"
}

View File

@ -132,22 +132,42 @@ export interface DepositWithdrawFeedItem {
wallet_pk: string
}
export interface LiquidationFeedItem {
export interface SpotLiquidationFeedItem {
asset_amount: number
asset_price: number
asset_symbol: string
block_datetime: string
counterparty: string
liab_amount: number
liab_price: number
liab_symbol: string
mango_account: string
mango_group: string
side: string
side: 'liqor' | 'liqee'
signature: string
}
export interface PerpLiquidationFeedItem {
base_transfer: -0.5
block_datetime: string
counterparty: string
mango_account: string
mango_group: string
perp_market_name: string
pnl_settle_limit_transfer: number
pnl_transfer: number
price: number
quote_transfer: number
side: 'liqor' | 'liqee'
signature: string
}
export type SpotOrPerpLiquidationItem =
| SpotLiquidationFeedItem
| PerpLiquidationFeedItem
export interface LiquidationActivity {
activity_details: LiquidationFeedItem
activity_details: SpotOrPerpLiquidationItem
block_datetime: string
activity_type: string
symbol: string
@ -162,6 +182,15 @@ export function isLiquidationFeedItem(
return false
}
export function isPerpLiquidation(
activityDetails: SpotOrPerpLiquidationItem
): activityDetails is PerpLiquidationFeedItem {
if ((activityDetails as PerpLiquidationFeedItem).base_transfer) {
return true
}
return false
}
export interface SwapHistoryItem {
block_datetime: string
mango_account: string
@ -202,7 +231,8 @@ export type ActivityFeed = {
symbol: string
activity_details:
| DepositWithdrawFeedItem
| LiquidationFeedItem
| SpotLiquidationFeedItem
| PerpLiquidationFeedItem
| SwapHistoryItem
| PerpTradeHistory
| SpotTradeHistory

2898
yarn.lock

File diff suppressed because it is too large Load Diff