import { FunctionComponent, useEffect, useMemo, useState } from 'react' import { EyeOffIcon } from '@heroicons/react/outline' import { ChevronDownIcon } from '@heroicons/react/solid' import { Disclosure } from '@headlessui/react' import dayjs from 'dayjs' import relativeTime from 'dayjs/plugin/relativeTime' import { AreaChart, Area, XAxis, YAxis, Tooltip } from 'recharts' import useDimensions from 'react-cool-dimensions' import { IconButton } from './Button' import { LineChartIcon } from './icons' import { useTranslation } from 'next-i18next' dayjs.extend(relativeTime) interface SwapTokenInfoProps { inputTokenId?: string outputTokenId?: string } export const numberFormatter = Intl.NumberFormat('en', { minimumFractionDigits: 1, maximumFractionDigits: 5, }) const SwapTokenInfo: FunctionComponent = ({ inputTokenId, outputTokenId, }) => { const [chartData, setChartData] = useState([]) const [hideChart, setHideChart] = useState(false) const [baseTokenId, setBaseTokenId] = useState('mango-markets') const [quoteTokenId, setQuoteTokenId] = useState('usd-coin') const [inputTokenInfo, setInputTokenInfo] = useState(null) const [outputTokenInfo, setOutputTokenInfo] = useState(null) const [mouseData, setMouseData] = useState(null) const [daysToShow, setDaysToShow] = useState(1) const { observe, width, height } = useDimensions() const { t } = useTranslation(['common', 'swap']) const handleMouseMove = (coords) => { if (coords.activePayload) { setMouseData(coords.activePayload[0].payload) } } const handleMouseLeave = () => { setMouseData(null) } useEffect(() => { if (['usd-coin', 'tether'].includes(inputTokenId)) { setBaseTokenId(outputTokenId) setQuoteTokenId(inputTokenId) } else { setBaseTokenId(inputTokenId) setQuoteTokenId(outputTokenId) } }, [inputTokenId, outputTokenId]) // Use ohlc data const getChartData = async () => { const inputResponse = await fetch( `https://api.coingecko.com/api/v3/coins/${baseTokenId}/ohlc?vs_currency=usd&days=${daysToShow}` ) const outputResponse = await fetch( `https://api.coingecko.com/api/v3/coins/${quoteTokenId}/ohlc?vs_currency=usd&days=${daysToShow}` ) const inputData = await inputResponse.json() const outputData = await outputResponse.json() const data = inputData.concat(outputData) const formattedData = data.reduce((a, c) => { const found = a.find((price) => price.time === c[0]) if (found) { found.price = found.inputPrice / c[4] } else { a.push({ time: c[0], inputPrice: c[4] }) } return a }, []) formattedData[formattedData.length - 1].time = Date.now() setChartData(formattedData.filter((d) => d.price)) } // Alternative chart data. Needs a timestamp tolerance to get data points for each asset // const getChartData = async () => { // const now = Date.now() / 1000 // const inputResponse = await fetch( // `https://api.coingecko.com/api/v3/coins/${inputTokenId}/market_chart/range?vs_currency=usd&from=${ // now - 1 * 86400 // }&to=${now}` // ) // const outputResponse = await fetch( // `https://api.coingecko.com/api/v3/coins/${outputTokenId}/market_chart/range?vs_currency=usd&from=${ // now - 1 * 86400 // }&to=${now}` // ) // const inputData = await inputResponse.json() // const outputData = await outputResponse.json() // const data = inputData?.prices.concat(outputData?.prices) // const formattedData = data.reduce((a, c) => { // const found = a.find( // (price) => c[0] >= price.time - 120000 && c[0] <= price.time + 120000 // ) // if (found) { // found.price = found.inputPrice / c[1] // } else { // a.push({ time: c[0], inputPrice: c[1] }) // } // return a // }, []) // setChartData(formattedData.filter((d) => d.price)) // } const getInputTokenInfo = async () => { const response = await fetch( `https://api.coingecko.com/api/v3/coins/${baseTokenId}?localization=false&tickers=false&developer_data=false&sparkline=false ` ) const data = await response.json() setInputTokenInfo(data) } const getOutputTokenInfo = async () => { const response = await fetch( `https://api.coingecko.com/api/v3/coins/${quoteTokenId}?localization=false&tickers=false&developer_data=false&sparkline=false ` ) const data = await response.json() setOutputTokenInfo(data) } useMemo(() => { if (baseTokenId && quoteTokenId) { getChartData() } }, [daysToShow, baseTokenId, quoteTokenId]) useMemo(() => { if (baseTokenId) { getInputTokenInfo() } if (quoteTokenId) { getOutputTokenInfo() } }, [baseTokenId, quoteTokenId]) const chartChange = chartData.length ? ((chartData[chartData.length - 1]['price'] - chartData[0]['price']) / chartData[0]['price']) * 100 : 0 return (
{chartData.length && baseTokenId && quoteTokenId ? (
{inputTokenInfo && outputTokenInfo ? (
{`${inputTokenInfo?.symbol?.toUpperCase()}/${outputTokenInfo?.symbol?.toUpperCase()}`}
) : null} {mouseData ? ( <>
{numberFormatter.format(mouseData['price'])} = 0 ? 'text-th-green' : 'text-th-red' }`} > {chartChange.toFixed(2)}%
{dayjs(mouseData['time']).format('DD MMM YY, h:mma')}
) : ( <>
{numberFormatter.format( chartData[chartData.length - 1]['price'] )} = 0 ? 'text-th-green' : 'text-th-red' }`} > {chartChange.toFixed(2)}%
{dayjs(chartData[chartData.length - 1]['time']).format( 'DD MMM YY, h:mma' )}
)}
setHideChart(!hideChart)}> {hideChart ? ( ) : ( )}
{!hideChart ? (
} />
) : null}
) : (
{t('swap:chart-not-available')}
)} {inputTokenInfo && inputTokenId ? (
{({ open }) => ( <>
{inputTokenInfo.image?.small ? ( {inputTokenInfo.name} ) : null}

{inputTokenInfo?.symbol?.toUpperCase()}

{inputTokenInfo.name}
{inputTokenInfo.market_data?.current_price?.usd ? (
$ {numberFormatter.format( inputTokenInfo.market_data.current_price.usd )}
) : null} {inputTokenInfo.market_data ?.price_change_percentage_24h ? (
= 0 ? 'text-th-green' : 'text-th-red' }`} > {inputTokenInfo.market_data.price_change_percentage_24h.toFixed( 2 )} %
) : null}
{inputTokenInfo.market_cap_rank ? (
{t('swap:market-cap-rank')}
#{inputTokenInfo.market_cap_rank}
) : null} {inputTokenInfo.market_data?.market_cap ? (
{t('swap:market-cap')}
$ {numberFormatter.format( inputTokenInfo.market_data?.market_cap?.usd )}
) : null} {inputTokenInfo?.market_data?.total_volume?.usd ? (
{t('daily-volume')}
$ {numberFormatter.format( inputTokenInfo?.market_data?.total_volume?.usd )}
) : null} {inputTokenInfo.market_data?.circulating_supply ? (
{t('swap:token-supply')}
{numberFormatter.format( inputTokenInfo.market_data.circulating_supply )}
{inputTokenInfo.market_data?.max_supply ? (
{t('swap:max-supply')}:{' '} {numberFormatter.format( inputTokenInfo.market_data.max_supply )}
) : null}
) : null} {inputTokenInfo.market_data?.ath?.usd ? (
{t('swap:ath')}
$ {numberFormatter.format( inputTokenInfo.market_data.ath.usd )}
{inputTokenInfo.market_data?.ath_change_percentage ?.usd ? (
= 0 ? 'text-th-green' : 'text-th-red' }`} > {(inputTokenInfo.market_data?.ath_change_percentage?.usd).toFixed( 2 )} %
) : null}
{inputTokenInfo.market_data?.ath_date?.usd ? (
{dayjs( inputTokenInfo.market_data.ath_date.usd ).fromNow()}
) : null}
) : null} {inputTokenInfo.market_data?.atl?.usd ? (
{t('swap:atl')}
$ {numberFormatter.format( inputTokenInfo.market_data.atl.usd )}
{inputTokenInfo.market_data?.atl_change_percentage ?.usd ? (
= 0 ? 'text-th-green' : 'text-th-red' }`} > {(inputTokenInfo.market_data?.atl_change_percentage?.usd).toLocaleString( undefined, { minimumFractionDigits: 0, maximumFractionDigits: 2, } )} %
) : null}
{inputTokenInfo.market_data?.atl_date?.usd ? (
{dayjs( inputTokenInfo.market_data.atl_date.usd ).fromNow()}
) : null}
) : null}
)}
) : (
{t('swap:input-info-unavailable')}
)} {outputTokenInfo && outputTokenId ? (
{({ open }) => ( <>
{outputTokenInfo.image?.small ? ( {outputTokenInfo.name} ) : null}

{outputTokenInfo?.symbol?.toUpperCase()}

{outputTokenInfo.name}
{outputTokenInfo.market_data?.current_price?.usd ? (
$ {numberFormatter.format( outputTokenInfo.market_data.current_price.usd )}
) : null} {outputTokenInfo.market_data ?.price_change_percentage_24h ? (
= 0 ? 'text-th-green' : 'text-th-red' }`} > {outputTokenInfo.market_data.price_change_percentage_24h.toFixed( 2 )} %
) : null}
{outputTokenInfo.market_cap_rank ? (
{t('swap:market-cap-rank')}
#{outputTokenInfo.market_cap_rank}
) : null} {outputTokenInfo.market_data?.market_cap ? (
{t('swap:market-cap')}
$ {numberFormatter.format( outputTokenInfo.market_data?.market_cap?.usd )}
) : null} {outputTokenInfo?.market_data?.total_volume?.usd ? (
{t('daily-volume')}
$ {numberFormatter.format( outputTokenInfo?.market_data?.total_volume?.usd )}
) : null} {outputTokenInfo.market_data?.circulating_supply ? (
{t('swap:token-supply')}
{numberFormatter.format( outputTokenInfo.market_data.circulating_supply )}
{outputTokenInfo.market_data?.max_supply ? (
{t('swap:max-supply')}:{' '} {numberFormatter.format( outputTokenInfo.market_data.max_supply )}
) : null}
) : null} {outputTokenInfo.market_data?.ath?.usd ? (
{t('swap:ath')}
$ {numberFormatter.format( outputTokenInfo.market_data.ath.usd )}
{outputTokenInfo.market_data?.ath_change_percentage ?.usd ? (
= 0 ? 'text-th-green' : 'text-th-red' }`} > {(outputTokenInfo.market_data?.ath_change_percentage?.usd).toFixed( 2 )} %
) : null}
{outputTokenInfo.market_data?.ath_date?.usd ? (
{dayjs( outputTokenInfo.market_data.ath_date.usd ).fromNow()}
) : null}
) : null} {outputTokenInfo.market_data?.atl?.usd ? (
{t('swap:atl')}
$ {numberFormatter.format( outputTokenInfo.market_data.atl.usd )}
{outputTokenInfo.market_data?.atl_change_percentage ?.usd ? (
= 0 ? 'text-th-green' : 'text-th-red' }`} > {(outputTokenInfo.market_data?.atl_change_percentage?.usd).toLocaleString( undefined, { minimumFractionDigits: 0, maximumFractionDigits: 2, } )} %
) : null}
{outputTokenInfo.market_data?.atl_date?.usd ? (
{dayjs( outputTokenInfo.market_data.atl_date.usd ).fromNow()}
) : null}
) : null}
)}
) : (
{t('swap:output-info-unavailable')}
)}
) } export default SwapTokenInfo