diff --git a/pages/api/token-stats.ts b/pages/api/token-stats.ts
new file mode 100644
index 0000000..a8e2400
--- /dev/null
+++ b/pages/api/token-stats.ts
@@ -0,0 +1,34 @@
+import { NextApiRequest, NextApiResponse } from "next";
+import { fetchTokenList } from "services/tokens";
+import { Network } from "types/network";
+import { TokenType } from "types/tokens";
+import { buildPoolsInfo } from "utils/info/pools";
+import { buildTokenStats } from "utils/info/tokens";
+import { getMercuryPools } from "zephyr/helpers";
+
+async function handler(req: NextApiRequest, res: NextApiResponse) {
+ const queryParams = req.query;
+
+ let network = queryParams?.network as string;
+ network = network?.toUpperCase() as Network;
+
+ if (network !== "MAINNET" && network !== "TESTNET") {
+ return res.status(400).json({ error: "Invalid network" });
+ }
+
+ try {
+ const tokenList: TokenType[] = await fetchTokenList({ network });
+
+ const data = await getMercuryPools(network);
+
+ const result = await buildPoolsInfo(data, tokenList, network);
+
+ const tokensInfo = await buildTokenStats(tokenList, result);
+
+ return res.json(tokensInfo);
+ } catch (error) {
+ return res.status(500).json({ error: "Failed to fetch token list" });
+ }
+}
+
+export default handler;
diff --git a/pages/index.tsx b/pages/index.tsx
index 0cc1fb0..730e595 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -5,7 +5,7 @@ import PoolsTable from "../src/components/pools-table/pools-table";
import TokensTable from "../src/components/tokens-table/tokens-table";
import TVLChart from "../src/components/tvl-chart";
import { useQueryPools } from "../src/hooks/pools";
-import { useQueryTokens } from "../src/hooks/tokens";
+import { useQueryTokens, useQueryTokenStats } from "../src/hooks/tokens";
import TransactionsTable from "../src/components/transaction-table/transactions-table";
import { useQueryAllEvents } from "../src/hooks/events";
import useEventTopicFilter from "../src/hooks/use-event-topic-filter";
@@ -24,10 +24,12 @@ import { useRouter } from "next/router";
import { StyledCard } from "components/styled/card";
import LoadingSkeleton from "components/loading-skeleton";
import { formatNumberToMoney } from "utils/utils";
+import { Text } from "components/styled/text";
export default function Home() {
const pools = useQueryPools();
const tokens = useQueryTokens();
+ const tokenStats = useQueryTokenStats();
const router = useRouter();
@@ -82,6 +84,60 @@ export default function Home() {
Soroswap Info
+
+
+
+ Total Volume 24h
+
+
+ {formatNumberToMoney(tokenStats.data?.volume24h)}
+
+
+
+
+ Total Volume 7d
+
+
+ {formatNumberToMoney(tokenStats.data?.volume7d)}
+
+
+
+
+ Total Volume All Time
+
+
+ {formatNumberToMoney(tokenStats.data?.volumeAllTime)}
+
+
+
+
+
+
+ Total Fees Generated 24h
+
+
+ {formatNumberToMoney(tokenStats.data?.fees24h)}
+
+
+
+
+ Total Fees Generated 7d
+
+
+ {formatNumberToMoney(tokenStats.data?.fees7d)}
+
+
+
+
+ Total Fees Generated All Time
+
+
+ {formatNumberToMoney(tokenStats.data?.feesAllTime)}
+
+
+
+
+
diff --git a/pages/pools/[id]/index.tsx b/pages/pools/[id]/index.tsx
index 2ccaf39..8ef5c07 100644
--- a/pages/pools/[id]/index.tsx
+++ b/pages/pools/[id]/index.tsx
@@ -314,6 +314,35 @@ const PoolPage = () => {
+
+
+
+
+ Volume 7d
+
+
+ {formatNumberToMoney(pool.data?.volume7d)}
+
+
+
+
+ Fees 7d
+
+
+ {formatNumberToMoney(pool.data?.fees7d)}
+
+
+
+
+ APY
+
+
+ {formatNumberToToken(pool.data?.apy)}
+
+
+
+
+
{formatNumberToMoney(row.fees24h)}
+
+ {formatNumberToMoney(row.fees7d)}
+
{formatNumberToMoney(row.feesYearly)}
+
+ {formatNumberToMoney(row.apy)}
+
);
})}
diff --git a/src/hooks/tokens.ts b/src/hooks/tokens.ts
index d784117..986fa63 100644
--- a/src/hooks/tokens.ts
+++ b/src/hooks/tokens.ts
@@ -2,6 +2,7 @@ import { useQuery } from "@tanstack/react-query";
import {
fetchToken,
fetchTokenPriceChart,
+ fetchTokenStats,
fetchTokenTVLChart,
fetchTokenVolumeChart,
fetchTokens,
@@ -71,3 +72,13 @@ export const useQueryTokenVolumeChart = ({
enabled: !!tokenAddress && isValidQuery,
});
};
+
+export const useQueryTokenStats = () => {
+ const { network, isValidQuery } = useQueryNetwork();
+
+ return useQuery({
+ queryKey: [key, network, "token-stats"],
+ queryFn: () => fetchTokenStats({ network: network! }),
+ enabled: isValidQuery,
+ });
+};
diff --git a/src/services/tokens.ts b/src/services/tokens.ts
index 817974d..d734488 100644
--- a/src/services/tokens.ts
+++ b/src/services/tokens.ts
@@ -1,5 +1,5 @@
import { ApiNetwork, Network } from "types/network";
-import { Token } from "../types/tokens";
+import { Token, TokenStats } from "../types/tokens";
import { fillDatesAndSort } from "../utils/complete-chart";
import axiosInstance from "./axios";
import { xlmToken } from "constants/constants";
@@ -105,3 +105,11 @@ export const fetchTokenVolumeChart = async ({
return filledData;
};
+
+export const fetchTokenStats = async ({ network }: ApiNetwork) => {
+ const { data } = await axiosInstance.get("/api/token-stats", {
+ params: { network },
+ });
+
+ return data;
+};
diff --git a/src/types/pools.ts b/src/types/pools.ts
index 7dfd2e9..461db0d 100644
--- a/src/types/pools.ts
+++ b/src/types/pools.ts
@@ -44,6 +44,8 @@ export interface Pool {
volume24h?: number;
volume7d?: number;
fees24h?: number;
+ fees7d?: number;
+ apy?:number;
feesYearly?: number;
tvlChartData?: TvlChartData[];
volumeChartData?: VolumeChartData[];
diff --git a/src/types/tokens.ts b/src/types/tokens.ts
index 3504d04..d1c53f8 100644
--- a/src/types/tokens.ts
+++ b/src/types/tokens.ts
@@ -18,6 +18,21 @@ export interface Token {
issuer: string;
}
+export interface TokenFeesChartData {
+ date: string;
+ fees: number;
+ timestamp: number;
+}
+
+export interface TokenStats {
+ volume24h: number;
+ volume7d: number;
+ volumeAllTime: number;
+ fees24h: number;
+ fees7d: number;
+ feesAllTime: number;
+}
+
export interface TokenType {
code: string;
issuer?: string;
diff --git a/src/utils/info/pools/index.ts b/src/utils/info/pools/index.ts
index f6e4ed6..c9223a0 100644
--- a/src/utils/info/pools/index.ts
+++ b/src/utils/info/pools/index.ts
@@ -144,6 +144,9 @@ export const buildPoolsInfo = async (
const feesYearly = fees7d * 52;
+ const weeklyYield = fees7d / tvl;
+ const apy = weeklyYield * 52 * 100;
+
return {
...poolData,
tvlChartData,
@@ -152,6 +155,8 @@ export const buildPoolsInfo = async (
volume7d,
volume24h,
fees24h,
+ fees7d,
+ apy,
feesYearly,
};
})
diff --git a/src/utils/info/tokens/index.ts b/src/utils/info/tokens/index.ts
index a85e752..762ec3c 100644
--- a/src/utils/info/tokens/index.ts
+++ b/src/utils/info/tokens/index.ts
@@ -6,7 +6,7 @@ import {
TvlChartData,
VolumeChartData,
} from "types/pools";
-import { Token, TokenType } from "types/tokens";
+import { Token, TokenType, TokenFeesChartData, TokenStats } from "types/tokens";
import { MercuryRsvCh, getMercuryRsvCh } from "zephyr/helpers";
import { getDate } from "../pools";
import { getExpectedAmountOfOne } from "utils/utils";
@@ -216,3 +216,138 @@ export const getTokenPriceChart = (
return filledPriceChartData as PriceChartData[];
};
+
+export const buildTokenStats = async (
+ tokenList: TokenType[],
+ pools: Pool[]
+) => {
+ const tokens: Token[] = tokenList.map((t) => ({
+ asset: t,
+ fees24h: 0,
+ price: 0,
+ priceChange24h: 0,
+ tvl: 0,
+ tvlSlippage24h: 0,
+ tvlSlippage7d: 0,
+ volume24h: 0,
+ volume24hChange: 0,
+ volume7d: 0,
+ volume7dChange: 0,
+ issuer: "",
+ }));
+
+ const USDC = tokens.find((token) => token.asset.code === "USDC");
+
+ if (!USDC) return null;
+
+ const totalStats = {
+ volume24h: 0,
+ volume7d: 0,
+ volumeAllTime: 0,
+ fees24h: 0,
+ fees7d: 0,
+ feesAllTime: 0,
+ };
+
+ tokens.forEach((token) => {
+ const tokenPools = pools.filter(
+ (pool) =>
+ pool.tokenA.contract === token.asset.contract ||
+ pool.tokenB.contract === token.asset.contract
+ );
+
+ const volumeChartData = getTokenVolumeChartData(token, tokenPools);
+
+ const nowTimestamp = new Date().getTime() / 1000;
+
+ const volume24h = volumeChartData.reduce((acc, item) => {
+ const itemTimestamp = new Date(item.date).getTime() / 1000;
+
+ if (nowTimestamp - itemTimestamp <= 24 * 3600) {
+ return acc + item.volume;
+ }
+ return acc;
+ }, 0);
+
+ const volume7d = volumeChartData.reduce((acc, item) => {
+ const itemTimestamp = new Date(item.date).getTime() / 1000;
+
+ if (nowTimestamp - itemTimestamp <= 7 * 24 * 3600) {
+ return acc + item.volume;
+ }
+ return acc;
+ }, 0);
+
+ const volumeAllTime = volumeChartData.reduce((acc, item) => {
+ return acc + item.volume;
+ }, 0);
+
+ const feesChartData = getTokenFeesChartData(tokenPools);
+
+ const fees24h = feesChartData.reduce((acc, item) => {
+ const itemTimestamp = new Date(item.date).getTime() / 1000;
+
+ if (nowTimestamp - itemTimestamp <= 24 * 3600) {
+ return acc + item.fees;
+ }
+ return acc;
+ }, 0);
+
+ const fees7d = feesChartData.reduce((acc, item) => {
+ const itemTimestamp = new Date(item.date).getTime() / 1000;
+
+ if (nowTimestamp - itemTimestamp <= 7 * 24 * 3600) {
+ return acc + item.fees;
+ }
+ return acc;
+ }, 0);
+
+ const feesAllTime = feesChartData.reduce((acc, item) => {
+ return acc + item.fees;
+ }, 0);
+
+ // Accumulate totals
+ totalStats.volume24h += volume24h;
+ totalStats.volume7d += volume7d;
+ totalStats.volumeAllTime += volumeAllTime;
+ totalStats.fees24h += fees24h;
+ totalStats.fees7d += fees7d;
+ totalStats.feesAllTime += feesAllTime;
+ });
+
+ return totalStats as TokenStats;
+};
+
+export const getTokenFeesChartData = (
+ tokenPools: Pool[]
+): TokenFeesChartData[] => {
+ let feesChartData: { [x: string]: any } = {};
+
+ tokenPools.forEach((pool) => {
+ pool.feesChartData?.forEach((data) => {
+ const fees = data.fees || 0;
+
+ if (!feesChartData[data.date]) {
+ feesChartData[data.date] = {
+ date: data.date,
+ fees: fees,
+ timestamp: data.timestamp,
+ };
+ } else {
+ feesChartData[data.date] = {
+ ...feesChartData[data.date],
+ fees: feesChartData[data.date].fees + fees,
+ };
+ }
+ });
+ });
+
+ feesChartData = Object.values(feesChartData);
+
+ feesChartData.sort(
+ (a: TokenFeesChartData, b: TokenFeesChartData) =>
+ new Date(a.date).getTime() - new Date(b.date).getTime()
+ );
+
+ return feesChartData as TokenFeesChartData[];
+};