From 8a574baae23ef767d6f73f2584e937caad117f97 Mon Sep 17 00:00:00 2001 From: Josh Date: Mon, 17 May 2021 12:43:18 -0700 Subject: [PATCH] Explorer: add token market prices from Coingecko (#17289) * feat: add coingecko prices to tokens with associated coingeckoId * feat: useCoingecko util --- .../account/TokenAccountSection.tsx | 27 +++++- explorer/src/pages/ClusterStatsPage.tsx | 85 +----------------- explorer/src/utils/coingecko.tsx | 89 +++++++++++++++++++ 3 files changed, 116 insertions(+), 85 deletions(-) create mode 100644 explorer/src/utils/coingecko.tsx diff --git a/explorer/src/components/account/TokenAccountSection.tsx b/explorer/src/components/account/TokenAccountSection.tsx index 551851678a..9313c644c1 100644 --- a/explorer/src/components/account/TokenAccountSection.tsx +++ b/explorer/src/components/account/TokenAccountSection.tsx @@ -17,6 +17,8 @@ import { reportError } from "utils/sentry"; import { useTokenRegistry } from "providers/mints/token-registry"; import { BigNumber } from "bignumber.js"; import { Copyable } from "components/common/Copyable"; +import { CoingeckoStatus, useCoinGecko } from "utils/coingecko"; +import { displayTimestampWithoutDate } from "utils/date"; const getEthAddress = (link?: string) => { let address = ""; @@ -76,7 +78,6 @@ function MintAccountCard({ const mintAddress = account.pubkey.toBase58(); const fetchInfo = useFetchAccountInfo(); const refresh = () => fetchInfo(account.pubkey); - const tokenInfo = tokenRegistry.get(mintAddress); const bridgeContractAddress = getEthAddress( @@ -86,6 +87,13 @@ function MintAccountCard({ tokenInfo?.extensions?.assetContract ); + const coinInfo = useCoinGecko(tokenInfo?.extensions?.coingeckoId); + + let tokenPriceInfo; + if (coinInfo?.status === CoingeckoStatus.Success) { + tokenPriceInfo = coinInfo.coinInfo; + } + return (
@@ -115,6 +123,17 @@ function MintAccountCard({ )} + {tokenPriceInfo?.price && ( + + Current Price + + $ + {tokenPriceInfo.price.toLocaleString("en-US", { + minimumFractionDigits: 2, + })} + + + )} {tokenInfo?.extensions?.website && ( Website @@ -189,6 +208,12 @@ function MintAccountCard({ )} + {tokenPriceInfo && ( +

+ Price updated at{" "} + {displayTimestampWithoutDate(tokenPriceInfo.last_updated.getTime())} +

+ )}
); } diff --git a/explorer/src/pages/ClusterStatsPage.tsx b/explorer/src/pages/ClusterStatsPage.tsx index c19744cd7e..fb823027b2 100644 --- a/explorer/src/pages/ClusterStatsPage.tsx +++ b/explorer/src/pages/ClusterStatsPage.tsx @@ -15,18 +15,9 @@ import { Status, useFetchSupply, useSupply } from "providers/supply"; import { ErrorCard } from "components/common/ErrorCard"; import { LoadingCard } from "components/common/LoadingCard"; import { useVoteAccounts } from "providers/accounts/vote-accounts"; -// @ts-ignore -import * as CoinGecko from "coingecko-api"; - -enum CoingeckoStatus { - Success, - FetchFailed, -} - -const CoinGeckoClient = new CoinGecko(); +import { CoingeckoStatus, useCoinGecko } from "utils/coingecko"; const CLUSTER_STATS_TIMEOUT = 5000; -const PRICE_REFRESH = 10000; export function ClusterStatsPage() { return ( @@ -332,77 +323,3 @@ export function StatsNotReady({ error }: { error: boolean }) {
); } - -interface CoinInfo { - price: number; - volume_24: number; - market_cap: number; - price_change_percentage_24h: number; - market_cap_rank: number; - last_updated: Date; -} - -interface CoinInfoResult { - data: { - market_data: { - current_price: { - usd: number; - }; - total_volume: { - usd: number; - }; - market_cap: { - usd: number; - }; - price_change_percentage_24h: number; - market_cap_rank: number; - }; - last_updated: string; - }; -} - -type CoinGeckoResult = { - coinInfo?: CoinInfo; - status: CoingeckoStatus; -}; - -function useCoinGecko(coinId: string): CoinGeckoResult | undefined { - const [coinInfo, setCoinInfo] = React.useState(); - - React.useEffect(() => { - const getCoinInfo = () => { - CoinGeckoClient.coins - .fetch("solana") - .then((info: CoinInfoResult) => { - setCoinInfo({ - coinInfo: { - price: info.data.market_data.current_price.usd, - volume_24: info.data.market_data.total_volume.usd, - market_cap: info.data.market_data.market_cap.usd, - market_cap_rank: info.data.market_data.market_cap_rank, - price_change_percentage_24h: - info.data.market_data.price_change_percentage_24h, - last_updated: new Date(info.data.last_updated), - }, - status: CoingeckoStatus.Success, - }); - }) - .catch((error: any) => { - setCoinInfo({ - status: CoingeckoStatus.FetchFailed, - }); - }); - }; - - getCoinInfo(); - const interval = setInterval(() => { - getCoinInfo(); - }, PRICE_REFRESH); - - return () => { - clearInterval(interval); - }; - }, [setCoinInfo]); - - return coinInfo; -} diff --git a/explorer/src/utils/coingecko.tsx b/explorer/src/utils/coingecko.tsx new file mode 100644 index 0000000000..cf86fbc5f9 --- /dev/null +++ b/explorer/src/utils/coingecko.tsx @@ -0,0 +1,89 @@ +import React from "react"; +// @ts-ignore +import * as CoinGecko from "coingecko-api"; + +const PRICE_REFRESH = 10000; + +const CoinGeckoClient = new CoinGecko(); + +export enum CoingeckoStatus { + Success, + FetchFailed, +} + +export interface CoinInfo { + price: number; + volume_24: number; + market_cap: number; + price_change_percentage_24h: number; + market_cap_rank: number; + last_updated: Date; +} + +export interface CoinInfoResult { + data: { + market_data: { + current_price: { + usd: number; + }; + total_volume: { + usd: number; + }; + market_cap: { + usd: number; + }; + price_change_percentage_24h: number; + market_cap_rank: number; + }; + last_updated: string; + }; +} + +export type CoinGeckoResult = { + coinInfo?: CoinInfo; + status: CoingeckoStatus; +}; + +export function useCoinGecko(coinId?: string): CoinGeckoResult | undefined { + const [coinInfo, setCoinInfo] = React.useState(); + React.useEffect(() => { + let interval: NodeJS.Timeout | undefined; + if (coinId) { + const getCoinInfo = () => { + CoinGeckoClient.coins + .fetch(coinId) + .then((info: CoinInfoResult) => { + setCoinInfo({ + coinInfo: { + price: info.data.market_data.current_price.usd, + volume_24: info.data.market_data.total_volume.usd, + market_cap: info.data.market_data.market_cap.usd, + market_cap_rank: info.data.market_data.market_cap_rank, + price_change_percentage_24h: + info.data.market_data.price_change_percentage_24h, + last_updated: new Date(info.data.last_updated), + }, + status: CoingeckoStatus.Success, + }); + }) + .catch((error: any) => { + setCoinInfo({ + status: CoingeckoStatus.FetchFailed, + }); + }); + }; + + getCoinInfo(); + interval = setInterval(() => { + getCoinInfo(); + }, PRICE_REFRESH); + } + return () => { + if (interval) { + clearInterval(interval); + } + }; + }, [setCoinInfo, coinId]); + + return coinInfo; +}