Skip to content

Commit

Permalink
Denominate market cap in usd
Browse files Browse the repository at this point in the history
  • Loading branch information
CRBl69 committed Dec 31, 2024
1 parent be24e2c commit 6e635ee
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 25 deletions.
8 changes: 8 additions & 0 deletions src/typescript/frontend/src/app/dev/verify_api_keys/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { fetchMarketsWithCount } from "@/queries/home";
import { AccountAddress } from "@aptos-labs/ts-sdk";
import { VERCEL } from "@sdk/const";
import { getAptosClient } from "@sdk/utils/aptos-client";
import { getAptPrice } from "lib/queries/get-apt-price";

export const dynamic = "force-static";
export const revalidate = 600;
Expand Down Expand Up @@ -52,6 +53,13 @@ const VerifyApiKeys = async () => {
throw new Error(`Couldn't fetch the price feed on the server. ${msg}`);
}

// Check that CoinMarketCap API key works
try {
await getAptPrice();
} catch (e) {
throw new Error(`Couldn't fetch APT price.\n\tInvalid CoinMarketCap API key.`);
}

return <div className="bg-black w-full h-full m-auto pixel-heading-2">LGTM</div>;
};

Expand Down
25 changes: 16 additions & 9 deletions src/typescript/frontend/src/app/home/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { type DatabaseModels, toPriceFeed } from "@sdk/indexer-v2/types";
import { type DatabaseJsonType } from "@sdk/indexer-v2/types/json-types";
import { SortMarketsBy } from "@sdk/indexer-v2/types/common";
import { ORDER_BY } from "@sdk/queries";
import { getAptPrice } from "lib/queries/get-apt-price";
import { AptPriceContextProvider } from "context/AptPrice";

export const revalidate = 2;

Expand Down Expand Up @@ -75,20 +77,25 @@ export default async function Home({ searchParams }: HomePageParams) {
numMarketsPromise = getCachedNumMarketsFromAptosNode();
}

const [priceFeedData, markets, numMarkets] = await Promise.all([
const aptPricePromise = getAptPrice();

const [priceFeedData, markets, numMarkets, aptPrice] = await Promise.all([
priceFeedPromise,
marketsPromise,
numMarketsPromise,
aptPricePromise,
]);

return (
<HomePageComponent
markets={markets}
numMarkets={numMarkets}
page={page}
sortBy={sortBy}
searchBytes={q}
priceFeed={priceFeedData}
/>
<AptPriceContextProvider aptPrice={aptPrice}>
<HomePageComponent
markets={markets}
numMarkets={numMarkets}
page={page}
sortBy={sortBy}
searchBytes={q}
priceFeed={priceFeedData}
/>
</AptPriceContextProvider>
);
}
27 changes: 16 additions & 11 deletions src/typescript/frontend/src/app/market/[market]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { pathToEmojiNames } from "utils/pathname-helpers";
import { fetchChatEvents, fetchMarketState, fetchSwapEvents } from "@/queries/market";
import { getMarketAddress } from "@sdk/emojicoin_dot_fun";
import { type Metadata } from "next";
import { getAptPrice } from "lib/queries/get-apt-price";
import { AptPriceContextProvider } from "context/AptPrice";

export const revalidate = 2;

Expand Down Expand Up @@ -75,23 +77,26 @@ const EmojicoinPage = async (params: EmojicoinPageProps) => {
const { marketID } = state.market;
const marketAddress = getMarketAddress(emojis);

const [chats, swaps, marketView] = await Promise.all([
const [chats, swaps, marketView, aptPrice] = await Promise.all([
fetchChatEvents({ marketID, pageSize: EVENTS_ON_PAGE_LOAD }),
fetchSwapEvents({ marketID, pageSize: EVENTS_ON_PAGE_LOAD }),
wrappedCachedContractMarketView(marketAddress.toString()),
getAptPrice(),
]);

return (
<ClientEmojicoinPage
data={{
symbol: state.market.symbolData.symbol,
swaps,
chats,
state,
marketView,
...state.market,
}}
/>
<AptPriceContextProvider aptPrice={aptPrice}>
<ClientEmojicoinPage
data={{
symbol: state.market.symbolData.symbol,
swaps,
chats,
state,
marketView,
...state.market,
}}
/>
</AptPriceContextProvider>
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { useAptos } from "context/wallet-context/AptosContextProvider";
import type { Colors } from "theme/types";
import Popup from "components/popup";
import { DISCORD_METADATA_REQUEST_CHANNEL, LINKS } from "lib/env";
import { useAptPrice } from "context/AptPrice";
import { toNominal } from "lib/utils/decimals";

const statsTextClasses = "uppercase ellipses font-forma text-[24px]";

Expand Down Expand Up @@ -96,6 +98,10 @@ const MainInfo = ({ data }: MainInfoProps) => {
}
}, [stateEvents]);

const aptPrice = useAptPrice();

const usdMarketCap = aptPrice ? toNominal(marketCap) * aptPrice : undefined;

const { isMobile, isTablet } = useMatchBreakpoints();

const explorerLink = toExplorerLink({
Expand Down Expand Up @@ -197,9 +203,17 @@ const MainInfo = ({ data }: MainInfoProps) => {
<div className={statsTextClasses + " text-light-gray"}>{t("Market Cap:")}</div>
<div className={statsTextClasses + " text-white"}>
<div className="flex flex-row justify-center items-center">
<FormattedNumber value={marketCap} nominalize scramble />
{usdMarketCap === undefined ? (
<FormattedNumber value={marketCap} nominalize scramble />
) : (
<FormattedNumber value={usdMarketCap} scramble />
)}
&nbsp;
<AptosIconBlack className="icon-inline mb-[0.3ch]" />
{usdMarketCap === undefined ? (
<AptosIconBlack className={"icon-inline mb-[0.3ch]"} />
) : (
"$"
)}
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { sortByValue } from "lib/utils/sort-events";
import { AnimatePresence, motion } from "framer-motion";
import { useInterval } from "react-use";
import { FormattedNumber } from "components/FormattedNumber";
import { useAptPrice } from "context/AptPrice";
import { toNominal } from "lib/utils/decimals";

export interface MainCardProps {
featuredMarkets: HomePageProps["priceFeed"];
Expand Down Expand Up @@ -60,6 +62,10 @@ const MainCard = (props: MainCardProps) => {
};
}, [featured]);

const aptPrice = useAptPrice();

const usdMarketCap = aptPrice ? toNominal(marketCap) * aptPrice : undefined;

/* eslint-disable react-hooks/exhaustive-deps */
useEffect(() => {
const timeout = setTimeout(() => {
Expand Down Expand Up @@ -146,9 +152,17 @@ const MainCard = (props: MainCardProps) => {
</div>
<div className="font-forma text-white market-data-text uppercase">
<div className="flex flex-row items-center justify-center">
<FormattedNumber value={marketCap} scramble nominalize />
{usdMarketCap === undefined ? (
<FormattedNumber value={marketCap} nominalize scramble />
) : (
<FormattedNumber value={usdMarketCap} scramble />
)}
&nbsp;
<AptosIconBlack className={"icon-inline mb-[0.3ch]"} />
{usdMarketCap === undefined ? (
<AptosIconBlack className={"icon-inline mb-[0.3ch]"} />
) : (
"$"
)}
</div>
</div>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import { Emoji } from "utils/emoji";
import { SortMarketsBy } from "@sdk/indexer-v2/types/common";
import "./module.css";
import { FormattedNumber } from "components/FormattedNumber";
import { useAptPrice } from "context/AptPrice";
import { toNominal } from "lib/utils/decimals";

const getFontSize = (emojis: SymbolEmojiData[]) =>
emojis.length <= 2 ? ("pixel-heading-1" as const) : ("pixel-heading-1b" as const);
Expand Down Expand Up @@ -81,6 +83,10 @@ const TableCard = ({
};
}, [sortBy, animationEvent, staticVolume24H, staticMarketCap]);

const aptPrice = useAptPrice();

const usdMarketCap = aptPrice ? toNominal(marketCap) * aptPrice : undefined;

// Keep track of whether or not the component is mounted to avoid animating an unmounted component.
useLayoutEffect(() => {
isMounted.current = true;
Expand Down Expand Up @@ -266,7 +272,11 @@ const TableCard = ({
className="body-sm uppercase font-forma"
style={{ color: "#FFFFFFFF", filter: "brightness(1) contrast(1)" }}
>
<FormattedNumber value={marketCap} scramble nominalize suffix=" APT" />
{usdMarketCap === undefined ? (
<FormattedNumber value={marketCap} scramble nominalize suffix=" APT" />
) : (
<FormattedNumber value={usdMarketCap} suffix=" $" />
)}
</motion.div>
</Column>
<Column width="50%">
Expand Down
20 changes: 20 additions & 0 deletions src/typescript/frontend/src/context/AptPrice.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"use client";

import { createContext, type PropsWithChildren, useContext } from "react";

export const AptPriceContext = createContext<number | undefined>(undefined);

export function AptPriceContextProvider({
aptPrice,
children,
}: PropsWithChildren<{ aptPrice: number }>) {
return <AptPriceContext.Provider value={aptPrice}>{children}</AptPriceContext.Provider>;
}

export const useAptPrice = (): number | undefined => {
const context = useContext(AptPriceContext);
if (context === null) {
throw new Error("useAptPrice must be used within a AptPriceContext.");
}
return context;
};
22 changes: 22 additions & 0 deletions src/typescript/frontend/src/lib/queries/get-apt-price.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"use server";

import { COINMARKETCAP_API_KEY } from "lib/server-env";
import { unstable_cache } from "next/cache";

const COINMARKETCAP_APT_ID = "21794";
const COINMARKETCAP_ROOT_URL = "https://pro-api.coinmarketcap.com";
const COINMARKETCAP_ENDPOINT = "v2/cryptocurrency/quotes/latest";

export const getAptPrice = unstable_cache(
async () =>
fetch(`${COINMARKETCAP_ROOT_URL}/${COINMARKETCAP_ENDPOINT}?id=${COINMARKETCAP_APT_ID}`, {
headers: {
Accept: "application/json",
"X-CMC_PRO_API_KEY": COINMARKETCAP_API_KEY,
},
})
.then((r) => r.json())
.then((r) => r.data[COINMARKETCAP_APT_ID].quote.USD.price as number),
["apt-price"],
{ revalidate: 60 * 10 } // Ten minutes
);
5 changes: 5 additions & 0 deletions src/typescript/frontend/src/lib/server-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ if (typeof process.env.REVALIDATION_TIME === "undefined") {
throw new Error("Environment variable REVALIDATION_TIME is undefined.");
}

if (typeof process.env.COINMARKETCAP_API_KEY === "undefined") {
throw new Error("Environment variable COINMARKETCAP_API_KEY is undefined.");
}

export const GEOBLOCKED: { countries: string[]; regions: string[] } = JSON.parse(
process.env.GEOBLOCKED ?? '{"countries":[],"regions":[]}'
);
Expand All @@ -28,6 +32,7 @@ export const GEOBLOCKING_ENABLED = GEOBLOCKED.countries.length > 0 || GEOBLOCKED
export const ALLOWLISTER3K_URL: string | undefined = process.env.ALLOWLISTER3K_URL;
export const REVALIDATION_TIME: number = Number(process.env.REVALIDATION_TIME);
export const PRE_LAUNCH_TEASER: boolean = process.env.PRE_LAUNCH_TEASER === "true";
export const COINMARKETCAP_API_KEY: string = process.env.COINMARKETCAP_API_KEY;

if (
APTOS_NETWORK.toString() === "local" &&
Expand Down

0 comments on commit 6e635ee

Please sign in to comment.