2022-12-06 16:22:32 -08:00
|
|
|
import { Bank } from '@blockworks-foundation/mango-v4'
|
|
|
|
import Change from '@components/shared/Change'
|
2023-01-24 16:54:24 -08:00
|
|
|
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
2022-12-11 15:21:40 -08:00
|
|
|
import { ArrowSmallUpIcon, NoSymbolIcon } from '@heroicons/react/20/solid'
|
2023-04-03 18:37:03 -07:00
|
|
|
import { useQuery } from '@tanstack/react-query'
|
|
|
|
import { makeApiRequest } from 'apis/birdeye/helpers'
|
2022-12-06 16:22:32 -08:00
|
|
|
import dayjs from 'dayjs'
|
|
|
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
2023-04-03 18:37:03 -07:00
|
|
|
import { BirdeyePriceResponse } from 'hooks/useBirdeyeMarketPrices'
|
2022-12-06 16:22:32 -08:00
|
|
|
import parse from 'html-react-parser'
|
|
|
|
import { useTranslation } from 'next-i18next'
|
2023-02-13 15:17:41 -08:00
|
|
|
import { useMemo, useState } from 'react'
|
2023-07-13 22:47:05 -07:00
|
|
|
import { DAILY_SECONDS } from 'utils/constants'
|
2023-10-09 18:59:56 -07:00
|
|
|
import DetailedAreaOrBarChart from '@components/shared/DetailedAreaOrBarChart'
|
2023-10-24 03:51:01 -07:00
|
|
|
import { countLeadingZeros, formatCurrencyValue } from 'utils/numbers'
|
2022-12-06 16:22:32 -08:00
|
|
|
dayjs.extend(relativeTime)
|
|
|
|
|
|
|
|
const DEFAULT_COINGECKO_VALUES = {
|
|
|
|
ath: 0,
|
|
|
|
atl: 0,
|
|
|
|
ath_change_percentage: 0,
|
|
|
|
atl_change_percentage: 0,
|
|
|
|
ath_date: 0,
|
|
|
|
atl_date: 0,
|
|
|
|
high_24h: 0,
|
|
|
|
circulating_supply: 0,
|
|
|
|
fully_diluted_valuation: 0,
|
|
|
|
low_24h: 0,
|
|
|
|
market_cap: 0,
|
|
|
|
max_supply: 0,
|
|
|
|
price_change_percentage_24h: 0,
|
|
|
|
total_supply: 0,
|
|
|
|
total_volume: 0,
|
|
|
|
}
|
|
|
|
|
2023-04-03 18:37:03 -07:00
|
|
|
interface BirdeyeResponse {
|
|
|
|
data: { items: BirdeyePriceResponse[] }
|
|
|
|
success: boolean
|
|
|
|
}
|
|
|
|
|
|
|
|
const fetchBirdeyePrices = async (
|
|
|
|
daysToShow: string,
|
2023-07-21 11:47:53 -07:00
|
|
|
mint: string,
|
2023-04-03 18:37:03 -07:00
|
|
|
): Promise<BirdeyePriceResponse[] | []> => {
|
|
|
|
const interval = daysToShow === '1' ? '30m' : daysToShow === '7' ? '1H' : '4H'
|
|
|
|
const queryEnd = Math.floor(Date.now() / 1000)
|
2023-07-13 22:47:05 -07:00
|
|
|
const queryStart = queryEnd - parseInt(daysToShow) * DAILY_SECONDS
|
2023-04-03 18:37:03 -07:00
|
|
|
const query = `defi/history_price?address=${mint}&address_type=token&type=${interval}&time_from=${queryStart}&time_to=${queryEnd}`
|
|
|
|
const response: BirdeyeResponse = await makeApiRequest(query)
|
|
|
|
|
|
|
|
if (response.success && response?.data?.items) {
|
|
|
|
return response.data.items
|
|
|
|
}
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
|
2022-12-06 16:22:32 -08:00
|
|
|
const CoingeckoStats = ({
|
|
|
|
bank,
|
|
|
|
coingeckoData,
|
|
|
|
}: {
|
|
|
|
bank: Bank
|
2023-02-27 23:20:11 -08:00
|
|
|
// TODO: Add Coingecko api types
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
2022-12-06 16:22:32 -08:00
|
|
|
coingeckoData: any
|
|
|
|
}) => {
|
|
|
|
const { t } = useTranslation(['common', 'token'])
|
|
|
|
const [showFullDesc, setShowFullDesc] = useState(false)
|
|
|
|
const [daysToShow, setDaysToShow] = useState<string>('1')
|
|
|
|
|
2023-10-09 18:59:56 -07:00
|
|
|
const { data: birdeyePrices, isLoading: loadingBirdeyePrices } = useQuery(
|
2023-04-12 17:31:22 -07:00
|
|
|
['birdeye-token-prices', daysToShow, bank.mint],
|
2023-04-03 18:37:03 -07:00
|
|
|
() => fetchBirdeyePrices(daysToShow, bank.mint.toString()),
|
|
|
|
{
|
|
|
|
cacheTime: 1000 * 60 * 15,
|
|
|
|
staleTime: 1000 * 60 * 10,
|
|
|
|
retry: 3,
|
|
|
|
enabled: !!bank,
|
|
|
|
refetchOnWindowFocus: false,
|
2023-07-21 11:47:53 -07:00
|
|
|
},
|
2023-04-03 18:37:03 -07:00
|
|
|
)
|
2022-12-06 16:22:32 -08:00
|
|
|
|
2023-10-09 18:59:56 -07:00
|
|
|
const chartData = useMemo(() => {
|
|
|
|
if (!birdeyePrices || !birdeyePrices.length) return []
|
2023-10-24 03:51:01 -07:00
|
|
|
return birdeyePrices.map((item) => {
|
|
|
|
const decimals = countLeadingZeros(item.value) + 3
|
|
|
|
const floatPrice = parseFloat(item.value.toString())
|
|
|
|
const roundedPrice = +floatPrice.toFixed(decimals)
|
|
|
|
return {
|
|
|
|
unixTime: item.unixTime * 1000,
|
|
|
|
value: roundedPrice,
|
|
|
|
}
|
|
|
|
})
|
2023-10-09 18:59:56 -07:00
|
|
|
}, [birdeyePrices])
|
|
|
|
|
2022-12-06 16:22:32 -08:00
|
|
|
const {
|
|
|
|
ath,
|
|
|
|
atl,
|
|
|
|
ath_change_percentage,
|
|
|
|
atl_change_percentage,
|
|
|
|
ath_date,
|
|
|
|
atl_date,
|
|
|
|
circulating_supply,
|
|
|
|
fully_diluted_valuation,
|
|
|
|
market_cap,
|
|
|
|
max_supply,
|
|
|
|
total_supply,
|
|
|
|
total_volume,
|
2023-04-03 18:37:03 -07:00
|
|
|
} = coingeckoData ? coingeckoData : DEFAULT_COINGECKO_VALUES
|
2022-12-06 16:22:32 -08:00
|
|
|
|
2023-02-13 15:17:41 -08:00
|
|
|
const truncateDescription = (desc: string) =>
|
|
|
|
desc.substring(0, (desc + ' ').lastIndexOf(' ', 144))
|
|
|
|
|
|
|
|
const description = useMemo(() => {
|
|
|
|
const desc = coingeckoData?.description?.en
|
|
|
|
if (!desc) return ''
|
|
|
|
return showFullDesc
|
|
|
|
? coingeckoData.description.en
|
|
|
|
: truncateDescription(coingeckoData.description.en)
|
|
|
|
}, [coingeckoData, showFullDesc])
|
|
|
|
|
2022-12-06 16:22:32 -08:00
|
|
|
return (
|
|
|
|
<>
|
2023-02-13 15:17:41 -08:00
|
|
|
{description ? (
|
2023-08-17 16:47:11 -07:00
|
|
|
<div className="border-b border-th-bkg-3 px-6 py-4">
|
2023-01-11 04:25:38 -08:00
|
|
|
<h2 className="mb-1 text-xl">About {bank.name}</h2>
|
|
|
|
<div className="flex items-end">
|
2023-02-13 15:17:41 -08:00
|
|
|
<p className="max-w-[720px]">{parse(description)}</p>
|
|
|
|
{coingeckoData.description.en.length > description.length ||
|
|
|
|
showFullDesc ? (
|
2023-01-11 04:25:38 -08:00
|
|
|
<span
|
2023-04-19 18:12:45 -07:00
|
|
|
className="ml-4 flex cursor-pointer items-end font-normal underline hover:text-th-fgd-2 md:hover:no-underline"
|
2023-01-11 04:25:38 -08:00
|
|
|
onClick={() => setShowFullDesc(!showFullDesc)}
|
|
|
|
>
|
|
|
|
{showFullDesc ? 'Less' : 'More'}
|
|
|
|
<ArrowSmallUpIcon
|
|
|
|
className={`h-5 w-5 ${
|
|
|
|
showFullDesc ? 'rotate-360' : 'rotate-180'
|
|
|
|
} default-transition`}
|
|
|
|
/>
|
|
|
|
</span>
|
|
|
|
) : null}
|
|
|
|
</div>
|
2022-12-06 16:22:32 -08:00
|
|
|
</div>
|
2023-01-11 04:25:38 -08:00
|
|
|
) : null}
|
2023-10-09 18:59:56 -07:00
|
|
|
{chartData?.length ? (
|
|
|
|
<div className="p-6 pb-4">
|
|
|
|
<DetailedAreaOrBarChart
|
|
|
|
data={chartData.concat([
|
|
|
|
{
|
|
|
|
unixTime: Date.now(),
|
2023-10-24 03:51:01 -07:00
|
|
|
value: parseFloat(
|
|
|
|
bank.uiPrice.toFixed(countLeadingZeros(bank.uiPrice) + 3),
|
|
|
|
),
|
2023-10-09 18:59:56 -07:00
|
|
|
},
|
|
|
|
])}
|
|
|
|
daysToShow={daysToShow}
|
|
|
|
setDaysToShow={setDaysToShow}
|
|
|
|
loading={loadingBirdeyePrices}
|
|
|
|
heightClass="h-64"
|
|
|
|
loaderHeightClass="h-[350px]"
|
|
|
|
prefix="$"
|
2023-10-24 03:51:01 -07:00
|
|
|
tickFormat={(x) =>
|
|
|
|
x < 0.00001 ? x.toExponential() : formatCurrencyValue(x)
|
|
|
|
}
|
2023-10-09 18:59:56 -07:00
|
|
|
title={`${bank.name} Price Chart`}
|
|
|
|
xKey="unixTime"
|
|
|
|
yKey="value"
|
2023-10-24 03:51:01 -07:00
|
|
|
yDecimals={countLeadingZeros(bank.uiPrice) + 3}
|
|
|
|
domain={['dataMin', 'dataMax']}
|
2023-10-09 18:59:56 -07:00
|
|
|
/>
|
2023-03-18 04:21:48 -07:00
|
|
|
</div>
|
2023-04-03 18:37:03 -07:00
|
|
|
) : (
|
|
|
|
<div className="m-6 flex h-72 items-center justify-center rounded-lg border border-th-bkg-3 md:h-80">
|
|
|
|
<div className="flex flex-col items-center">
|
|
|
|
<NoSymbolIcon className="mb-2 h-7 w-7 text-th-fgd-4" />
|
|
|
|
<p>{t('chart-unavailable')}</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
2022-12-06 16:22:32 -08:00
|
|
|
)}
|
|
|
|
<div className="grid grid-cols-1 border-b border-th-bkg-3 md:grid-cols-2">
|
|
|
|
<div className="col-span-1 border-y border-th-bkg-3 px-6 py-4 md:col-span-2">
|
|
|
|
<h2 className="text-base">{bank.name} Stats</h2>
|
|
|
|
</div>
|
2023-04-30 20:10:24 -07:00
|
|
|
<div className="col-span-1 px-6 py-4 md:border-r md:border-th-bkg-3">
|
2022-12-06 16:22:32 -08:00
|
|
|
<div className="flex justify-between pb-4">
|
|
|
|
<p>{t('token:market-cap')}</p>
|
|
|
|
<p className="font-mono text-th-fgd-2">
|
2022-12-11 15:03:14 -08:00
|
|
|
{market_cap?.usd ? (
|
2023-01-24 16:54:24 -08:00
|
|
|
<FormatNumericValue value={market_cap.usd} isUsd />
|
2022-12-11 15:03:14 -08:00
|
|
|
) : (
|
|
|
|
<span className="font-body text-th-fgd-4">
|
|
|
|
{t('unavailable')}
|
|
|
|
</span>
|
|
|
|
)}{' '}
|
2022-12-06 16:22:32 -08:00
|
|
|
<span className="text-th-fgd-4">
|
2022-12-11 15:03:14 -08:00
|
|
|
{coingeckoData.market_cap_rank
|
|
|
|
? `#${coingeckoData.market_cap_rank}`
|
|
|
|
: ''}
|
2022-12-06 16:22:32 -08:00
|
|
|
</span>
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
<div className="flex justify-between border-t border-th-bkg-3 py-4">
|
|
|
|
<p>{t('token:volume')}</p>
|
|
|
|
<p className="font-mono text-th-fgd-2">
|
2022-12-11 15:03:14 -08:00
|
|
|
{total_volume?.usd ? (
|
2023-01-24 16:54:24 -08:00
|
|
|
<FormatNumericValue value={total_volume.usd} isUsd />
|
2022-12-11 15:03:14 -08:00
|
|
|
) : (
|
|
|
|
<span className="font-body text-th-fgd-4">
|
|
|
|
{t('unavailable')}
|
|
|
|
</span>
|
|
|
|
)}
|
2022-12-06 16:22:32 -08:00
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
<div className="flex justify-between border-t border-th-bkg-3 py-4">
|
|
|
|
<p>{t('token:all-time-high')}</p>
|
|
|
|
<div className="flex flex-col items-end">
|
|
|
|
<div className="flex items-center font-mono text-th-fgd-2">
|
|
|
|
<span className="mr-2">
|
2022-12-11 15:03:14 -08:00
|
|
|
{ath?.usd ? (
|
2023-01-24 16:54:24 -08:00
|
|
|
<FormatNumericValue value={ath.usd} isUsd />
|
2022-12-11 15:03:14 -08:00
|
|
|
) : (
|
|
|
|
<span className="font-body text-th-fgd-4">
|
|
|
|
{t('unavailable')}
|
|
|
|
</span>
|
|
|
|
)}
|
2022-12-06 16:22:32 -08:00
|
|
|
</span>
|
2022-12-11 15:03:14 -08:00
|
|
|
{ath_change_percentage.usd ? (
|
|
|
|
<Change change={ath_change_percentage.usd} suffix="%" />
|
|
|
|
) : null}
|
2022-12-06 16:22:32 -08:00
|
|
|
</div>
|
|
|
|
<p className="text-xs text-th-fgd-4">
|
|
|
|
{dayjs(ath_date.usd).format('MMM, D, YYYY')} (
|
|
|
|
{dayjs(ath_date.usd).fromNow()})
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="flex justify-between border-b border-t border-th-bkg-3 py-4 md:border-b-0 md:pb-0">
|
|
|
|
<p>{t('token:all-time-low')}</p>
|
|
|
|
<div className="flex flex-col items-end">
|
|
|
|
<div className="flex items-center font-mono text-th-fgd-2">
|
|
|
|
<span className="mr-2">
|
2022-12-11 15:03:14 -08:00
|
|
|
{atl?.usd ? (
|
2023-01-24 16:54:24 -08:00
|
|
|
<FormatNumericValue value={atl.usd} isUsd />
|
2022-12-11 15:03:14 -08:00
|
|
|
) : (
|
|
|
|
<span className="font-body text-th-fgd-4">
|
|
|
|
{t('unavailable')}
|
|
|
|
</span>
|
|
|
|
)}
|
2022-12-06 16:22:32 -08:00
|
|
|
</span>
|
|
|
|
<Change change={atl_change_percentage.usd} suffix="%" />
|
|
|
|
</div>
|
|
|
|
<p className="text-xs text-th-fgd-4">
|
|
|
|
{dayjs(atl_date.usd).format('MMM, D, YYYY')} (
|
|
|
|
{dayjs(atl_date.usd).fromNow()})
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="col-span-1 px-6 pb-4 md:pt-4">
|
|
|
|
{fully_diluted_valuation.usd ? (
|
|
|
|
<div className="flex justify-between pb-4">
|
|
|
|
<p>{t('token:fdv')}</p>
|
|
|
|
<p className="font-mono text-th-fgd-2">
|
2022-12-11 15:03:14 -08:00
|
|
|
{fully_diluted_valuation?.usd ? (
|
2023-01-24 16:54:24 -08:00
|
|
|
<FormatNumericValue
|
|
|
|
value={fully_diluted_valuation.usd}
|
|
|
|
isUsd
|
|
|
|
/>
|
2022-12-11 15:03:14 -08:00
|
|
|
) : (
|
|
|
|
<span className="font-body text-th-fgd-4">
|
|
|
|
{t('unavailable')}
|
|
|
|
</span>
|
|
|
|
)}
|
2022-12-06 16:22:32 -08:00
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
) : null}
|
|
|
|
<div
|
|
|
|
className={`flex justify-between ${
|
|
|
|
fully_diluted_valuation.usd
|
|
|
|
? 'border-t border-th-bkg-3 py-4'
|
|
|
|
: 'pb-4'
|
|
|
|
}`}
|
|
|
|
>
|
|
|
|
<p>{t('token:circulating-supply')}</p>
|
|
|
|
<p className="font-mono text-th-fgd-2">
|
2022-12-11 15:03:14 -08:00
|
|
|
{circulating_supply ? (
|
2023-01-24 16:54:24 -08:00
|
|
|
<FormatNumericValue value={circulating_supply} />
|
2022-12-11 15:03:14 -08:00
|
|
|
) : (
|
|
|
|
<span className="font-body text-th-fgd-4">
|
|
|
|
{t('unavailable')}
|
|
|
|
</span>
|
|
|
|
)}
|
2022-12-06 16:22:32 -08:00
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
<div
|
|
|
|
className={`flex justify-between border-t border-th-bkg-3 ${
|
|
|
|
max_supply ? 'py-4' : 'border-b pt-4 md:pb-4'
|
|
|
|
}`}
|
|
|
|
>
|
|
|
|
<p>{t('token:total-supply')}</p>
|
|
|
|
<p className="font-mono text-th-fgd-2">
|
2022-12-11 15:03:14 -08:00
|
|
|
{total_supply ? (
|
2023-01-24 16:54:24 -08:00
|
|
|
<FormatNumericValue value={total_supply} />
|
2022-12-11 15:03:14 -08:00
|
|
|
) : (
|
|
|
|
<span className="font-body text-th-fgd-4">
|
|
|
|
{t('unavailable')}
|
|
|
|
</span>
|
|
|
|
)}
|
2022-12-06 16:22:32 -08:00
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
{max_supply ? (
|
|
|
|
<div className="flex justify-between border-t border-th-bkg-3 pt-4">
|
|
|
|
<p>{t('token:max-supply')}</p>
|
|
|
|
<p className="font-mono text-th-fgd-2">
|
2022-12-11 15:03:14 -08:00
|
|
|
{max_supply ? (
|
2023-01-24 16:54:24 -08:00
|
|
|
<FormatNumericValue value={max_supply} />
|
2022-12-11 15:03:14 -08:00
|
|
|
) : (
|
|
|
|
<span className="font-body text-th-fgd-4">
|
|
|
|
{t('unavailable')}
|
|
|
|
</span>
|
|
|
|
)}
|
2022-12-06 16:22:32 -08:00
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
) : null}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
export default CoingeckoStats
|