Skip to content

Commit

Permalink
Merge pull request #266 from berachain/fix/update-loading-states-of-t…
Browse files Browse the repository at this point in the history
…oken-avatars

feat(hub): update loading times of tokens for swaps and pools
  • Loading branch information
BrownBrownBear authored Dec 23, 2024
2 parents d67be32 + 0d08d1f commit 0aaa0cd
Show file tree
Hide file tree
Showing 12 changed files with 329 additions and 126 deletions.
3 changes: 3 additions & 0 deletions apps/hub/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ const config = {
"res.cloudinary.com",
"raw.githubusercontent.com",
"s3.amazonaws.com",
"assets.coingecko.com",
"artio-static-asset-public.s3.ap-southeast-1.amazonaws.com", // clean this up with new links
// Add google cdn here as well
],
},
};
Expand Down
30 changes: 21 additions & 9 deletions apps/hub/src/app/Providers.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
"use client";

import React, { useEffect, type PropsWithChildren } from "react";
import { BeraJsProvider, BlockTimeProvider } from "@bera/berajs";
import { BeraJsProvider, BlockTimeProvider, SWRFallback } from "@bera/berajs";
import { tokenListUrl } from "@bera/config";
import { BeraWagmi } from "@bera/wagmi";
import posthog from "posthog-js";
import { PostHogProvider } from "posthog-js/react";
import { unstable_serialize } from "swr";

import { ThemeProvider } from "~/components/theme-provider";

export default function Providers({ children }: PropsWithChildren<any>) {
export default function Providers({
children,
content,
}: PropsWithChildren<any>) {
useEffect(() => {
if (!process.env.NEXT_PUBLIC_POSTHOG_KEY) {
return;
Expand All @@ -24,14 +29,21 @@ export default function Providers({ children }: PropsWithChildren<any>) {
<BeraWagmi>
<BeraJsProvider configOverride={undefined}>
<BlockTimeProvider>
<ThemeProvider
attribute="class"
defaultTheme="dark"
forcedTheme="dark"
themes={["dark"]}
<SWRFallback
fallback={{
[unstable_serialize(["useTokens", tokenListUrl])]:
content?.tokens,
}}
>
{children}
</ThemeProvider>
<ThemeProvider
attribute="class"
defaultTheme="dark"
forcedTheme="dark"
themes={["dark"]}
>
{children}
</ThemeProvider>
</SWRFallback>
</BlockTimeProvider>
</BeraJsProvider>
</BeraWagmi>
Expand Down
17 changes: 14 additions & 3 deletions apps/hub/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import "../styles/globals.css";
import { readFileSync } from "fs";
import path from "path";
import { Metadata } from "next";
import dynamic from "next/dynamic";
import { IBM_Plex_Sans } from "next/font/google";
import Script from "next/script";
import { hubName, hubUrl } from "@bera/config";
import { hubName, hubUrl, tokenListUrl } from "@bera/config";
import {
Footer,
Header,
Expand Down Expand Up @@ -35,7 +37,16 @@ const PostHogPageView = dynamic(() => import("./PostHogPageView"), {
ssr: false,
});

export default function RootLayout(props: { children: React.ReactNode }) {
export default async function RootLayout(props: { children: React.ReactNode }) {
const fetchedTokenList = await (tokenListUrl.startsWith("http")
? fetch(tokenListUrl).then((res) => res.json())
: JSON.parse(
readFileSync(
path.join(process.cwd(), `public/${tokenListUrl}`),
"utf8",
),
));

return (
<html lang="en" className="bg-background">
<Script
Expand All @@ -55,7 +66,7 @@ export default function RootLayout(props: { children: React.ReactNode }) {
<body
className={cn("min-h-screen font-sans antialiased", fontSans.variable)}
>
<Providers>
<Providers content={fetchedTokenList}>
<TermOfUseModal />

<PostHogPageView />
Expand Down
92 changes: 6 additions & 86 deletions packages/berajs/src/actions/dex/getTokens.ts
Original file line number Diff line number Diff line change
@@ -1,94 +1,14 @@
import { chainId } from "@bera/config";
import { tokenListUrl } from "@bera/config";

import { BeraConfig } from "../../types";
import { Token } from "../../types/dex";

export interface GetTokensRequest {
externalList?: Token[];
config: BeraConfig;
}

export interface GetTokens {
tokenList?: Token[] | undefined;
customTokenList?: Token[] | undefined;
tokenDictionary?: { [key: string]: Token } | undefined;
featuredTokenList?: Token[] | undefined;
}

/**
* fetch and format the token list
*/
function tokenListToDict(list: Token[]): { [key: string]: Token } {
return list.reduce((acc, item) => {
// @ts-ignore
acc[item.address] = item;
return acc;
}, {});
}

export const getTokens = async ({
externalList,
config,
}: GetTokensRequest): Promise<GetTokens> => {
if (!config.endpoints?.tokenList) {
return {
tokenList: [],
customTokenList: [...(externalList ?? [])],
tokenDictionary: tokenListToDict(externalList ?? []),
featuredTokenList: [],
};
}
export const getTokens = async (): Promise<Token[]> => {
try {
const tokenList = await fetch(config.endpoints?.tokenList);
const temp = await tokenList.json();
if (!temp.tokens)
return {
tokenList: externalList ?? [],
customTokenList: externalList ?? [],
featuredTokenList: [],
tokenDictionary: {},
};
const defaultList = temp.tokens
.filter(
(token: any) =>
!token.chainId || Number(token.chainId) === Number(chainId),
)
.map((token: any) => {
return { ...token, default: true };
});

const isFeatured = (tag: string) => tag === "featured";

const defaultFeaturedList = defaultList
.filter((token: any) => {
return token.tags.some(isFeatured);
})
.map((token: any) => {
return { ...token, default: true };
});

const list = [...defaultList, ...(externalList ?? [])];

const uniqueList = list.filter(
(item, index) =>
list.findIndex((i) => i.address === item.address) === index,
);

console.log({ uniqueList, temp, defaultList, defaultFeaturedList });

return {
tokenList: uniqueList,
customTokenList: [...(externalList ?? [])],
tokenDictionary: tokenListToDict(list),
featuredTokenList: defaultFeaturedList ?? [],
};
const tokenList = await fetch(tokenListUrl);
const tokenData = await tokenList.json();
return tokenData.tokens ?? [];
} catch (error) {
console.error("Error fetching token list", error);
return {
tokenList: [...(externalList ?? [])],
customTokenList: [...(externalList ?? [])],
featuredTokenList: [],
tokenDictionary: tokenListToDict(externalList ?? []),
};
return [];
}
};
37 changes: 26 additions & 11 deletions packages/berajs/src/hooks/useTokens.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import { useMemo } from "react";
import { chainId, tokenListUrl } from "@bera/config";
import useSWRImmutable from "swr/immutable";
import { useLocalStorage } from "usehooks-ts";

import {
DefaultHookOptions,
DefaultHookReturnType,
useBeraJs,
formatTokenList,
type Token,
} from "..";
import { GetTokens, getTokens } from "../actions/dex";
import { getTokens } from "../actions/dex";

export interface GetTokens {
tokenList?: Token[] | undefined;
customTokenList?: Token[] | undefined;
tokenDictionary?: { [key: string]: Token } | undefined;
featuredTokenList?: Token[] | undefined;
}

export interface IUseTokens extends DefaultHookReturnType<GetTokens> {
addNewToken: (token: Token | undefined) => void;
Expand All @@ -17,22 +26,25 @@ export interface IUseTokens extends DefaultHookReturnType<GetTokens> {
export const useTokens = (options?: DefaultHookOptions): IUseTokens => {
const TOKEN_KEY = "tokens";

const { config: beraConfig } = useBeraJs();
const config = options?.beraConfigOverride ?? beraConfig;

const [localStorageTokenList, setLocalStorageTokenList] = useLocalStorage<
Token[]
>(TOKEN_KEY, []);
const swrResponse = useSWRImmutable<GetTokens>(
["defaultTokenList", localStorageTokenList, config],

const { data: fetchedTokens, ...swrRest } = useSWRImmutable<Token[]>(
["useTokens", tokenListUrl],
async () => {
return getTokens({ externalList: localStorageTokenList, config });
return await getTokens();
},
{
...options?.opts,
},
);

const formattedTokenList = useMemo(
() => formatTokenList(fetchedTokens ?? [], localStorageTokenList, chainId),
[fetchedTokens, localStorageTokenList, chainId],
);

const addNewToken = (token: Token | undefined) => {
// Indicate that this token is now accepted into the default list of tokens
const acceptedToken = {
Expand All @@ -42,7 +54,7 @@ export const useTokens = (options?: DefaultHookOptions): IUseTokens => {

// Check if the token already exists in tokenList
if (
swrResponse.data?.tokenList?.some(
formattedTokenList?.tokenList?.some(
(t: { address: string }) =>
t.address.toLowerCase() === acceptedToken?.address?.toLowerCase(),
)
Expand All @@ -54,6 +66,7 @@ export const useTokens = (options?: DefaultHookOptions): IUseTokens => {
? [...localStorageTokenList]
: [...localStorageTokenList, acceptedToken as Token];
setLocalStorageTokenList(updatedData);
swrRest?.mutate();
// Update config data and store it in localStorage
};

Expand All @@ -64,11 +77,13 @@ export const useTokens = (options?: DefaultHookOptions): IUseTokens => {

const updatedData = [...filteredList];
setLocalStorageTokenList(updatedData);
swrRest?.mutate();
};

return {
...swrResponse,
refresh: () => swrResponse?.mutate?.(),
...swrRest,
data: formattedTokenList,
refresh: () => swrRest?.mutate?.(),
addNewToken,
removeToken,
};
Expand Down
1 change: 1 addition & 0 deletions packages/berajs/src/types/dex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type Token = {
usdValue?: string;
beraValue?: string;
weight?: number;
base64?: string;
};

export interface TokenWithAmount extends Token {
Expand Down
60 changes: 60 additions & 0 deletions packages/berajs/src/utils/formatTokenList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Token } from "../types";

/**
* fetch and format the token list
*/
function tokenListToDict(list: Token[]): { [key: string]: Token } {
return list.reduce((acc, item) => {
// @ts-ignore
acc[item.address] = item;
return acc;
}, {});
}

const formatTokenList = (
tokenList: Token[],
externalList: Token[],
chainId: number,
) => {
if (!tokenList || tokenList.length === 0)
return {
tokenList: externalList ?? [],
customTokenList: externalList ?? [],
featuredTokenList: [],
tokenDictionary: {},
};
const defaultList = tokenList
.filter(
(token: any) =>
!token.chainId || Number(token.chainId) === Number(chainId),
)
.map((token: any) => {
return { ...token, default: true };
});

const isFeatured = (tag: string) => tag === "featured";

const defaultFeaturedList = defaultList
.filter((token: any) => {
return token.tags.some(isFeatured);
})
.map((token: any) => {
return { ...token, default: true };
});

const list = [...defaultList, ...(externalList ?? [])];

const uniqueList = list.filter(
(item, index) =>
list.findIndex((i) => i.address === item.address) === index,
);

return {
tokenList: uniqueList,
customTokenList: [...(externalList ?? [])],
tokenDictionary: tokenListToDict(list),
featuredTokenList: defaultFeaturedList ?? [],
};
};

export { formatTokenList, tokenListToDict };
1 change: 1 addition & 0 deletions packages/berajs/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ export * from "./tokenWrapping";
export * from "./errorMessages";
export * from "./isSubgraphStale";
export * from "./const";
export * from "./formatTokenList";
Loading

0 comments on commit 0aaa0cd

Please sign in to comment.