Explorer: add token market prices from Coingecko (#17289)
* feat: add coingecko prices to tokens with associated coingeckoId * feat: useCoingecko util
This commit is contained in:
parent
9d6837c904
commit
8a574baae2
|
@ -17,6 +17,8 @@ import { reportError } from "utils/sentry";
|
||||||
import { useTokenRegistry } from "providers/mints/token-registry";
|
import { useTokenRegistry } from "providers/mints/token-registry";
|
||||||
import { BigNumber } from "bignumber.js";
|
import { BigNumber } from "bignumber.js";
|
||||||
import { Copyable } from "components/common/Copyable";
|
import { Copyable } from "components/common/Copyable";
|
||||||
|
import { CoingeckoStatus, useCoinGecko } from "utils/coingecko";
|
||||||
|
import { displayTimestampWithoutDate } from "utils/date";
|
||||||
|
|
||||||
const getEthAddress = (link?: string) => {
|
const getEthAddress = (link?: string) => {
|
||||||
let address = "";
|
let address = "";
|
||||||
|
@ -76,7 +78,6 @@ function MintAccountCard({
|
||||||
const mintAddress = account.pubkey.toBase58();
|
const mintAddress = account.pubkey.toBase58();
|
||||||
const fetchInfo = useFetchAccountInfo();
|
const fetchInfo = useFetchAccountInfo();
|
||||||
const refresh = () => fetchInfo(account.pubkey);
|
const refresh = () => fetchInfo(account.pubkey);
|
||||||
|
|
||||||
const tokenInfo = tokenRegistry.get(mintAddress);
|
const tokenInfo = tokenRegistry.get(mintAddress);
|
||||||
|
|
||||||
const bridgeContractAddress = getEthAddress(
|
const bridgeContractAddress = getEthAddress(
|
||||||
|
@ -86,6 +87,13 @@ function MintAccountCard({
|
||||||
tokenInfo?.extensions?.assetContract
|
tokenInfo?.extensions?.assetContract
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const coinInfo = useCoinGecko(tokenInfo?.extensions?.coingeckoId);
|
||||||
|
|
||||||
|
let tokenPriceInfo;
|
||||||
|
if (coinInfo?.status === CoingeckoStatus.Success) {
|
||||||
|
tokenPriceInfo = coinInfo.coinInfo;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="card-header">
|
<div className="card-header">
|
||||||
|
@ -115,6 +123,17 @@ function MintAccountCard({
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{tokenPriceInfo?.price && (
|
||||||
|
<tr>
|
||||||
|
<td>Current Price</td>
|
||||||
|
<td className="text-lg-right">
|
||||||
|
$
|
||||||
|
{tokenPriceInfo.price.toLocaleString("en-US", {
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
})}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
{tokenInfo?.extensions?.website && (
|
{tokenInfo?.extensions?.website && (
|
||||||
<tr>
|
<tr>
|
||||||
<td>Website</td>
|
<td>Website</td>
|
||||||
|
@ -189,6 +208,12 @@ function MintAccountCard({
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
</TableCardBody>
|
</TableCardBody>
|
||||||
|
{tokenPriceInfo && (
|
||||||
|
<p className="updated-time text-muted mr-4">
|
||||||
|
Price updated at{" "}
|
||||||
|
{displayTimestampWithoutDate(tokenPriceInfo.last_updated.getTime())}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,18 +15,9 @@ import { Status, useFetchSupply, useSupply } from "providers/supply";
|
||||||
import { ErrorCard } from "components/common/ErrorCard";
|
import { ErrorCard } from "components/common/ErrorCard";
|
||||||
import { LoadingCard } from "components/common/LoadingCard";
|
import { LoadingCard } from "components/common/LoadingCard";
|
||||||
import { useVoteAccounts } from "providers/accounts/vote-accounts";
|
import { useVoteAccounts } from "providers/accounts/vote-accounts";
|
||||||
// @ts-ignore
|
import { CoingeckoStatus, useCoinGecko } from "utils/coingecko";
|
||||||
import * as CoinGecko from "coingecko-api";
|
|
||||||
|
|
||||||
enum CoingeckoStatus {
|
|
||||||
Success,
|
|
||||||
FetchFailed,
|
|
||||||
}
|
|
||||||
|
|
||||||
const CoinGeckoClient = new CoinGecko();
|
|
||||||
|
|
||||||
const CLUSTER_STATS_TIMEOUT = 5000;
|
const CLUSTER_STATS_TIMEOUT = 5000;
|
||||||
const PRICE_REFRESH = 10000;
|
|
||||||
|
|
||||||
export function ClusterStatsPage() {
|
export function ClusterStatsPage() {
|
||||||
return (
|
return (
|
||||||
|
@ -332,77 +323,3 @@ export function StatsNotReady({ error }: { error: boolean }) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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<CoinGeckoResult>();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -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<CoinGeckoResult>();
|
||||||
|
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;
|
||||||
|
}
|
Loading…
Reference in New Issue