diff --git a/.env b/.env
index 9bcb865..0ace994 100644
--- a/.env
+++ b/.env
@@ -1,3 +1,3 @@
-NEXT_PUBLIC_API_URL = http://localhost:8000
+NEXT_PUBLIC_DAT_URL = https://d2v1fiwobg9w6.cloudfront.net
NEXT_PUBLIC_GOOGLE_ANALYTICS_CONFIG = G-8GN39Z428L
diff --git a/.vscode/settings.json b/.vscode/settings.json
index b7f2744..3492f91 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,6 +1,6 @@
{
- "typescript.enablePromptUseWorkspaceTsdk": true,
- "editor.wordWrap": "wordWrapColumn",
- "editor.wordWrapColumn": 140,
- "typescript.tsdk": "node_modules/typescript/lib"
-}
\ No newline at end of file
+ "typescript.enablePromptUseWorkspaceTsdk": true,
+ "editor.wordWrap": "wordWrapColumn",
+ "editor.wordWrapColumn": 140,
+ "typescript.tsdk": "node_modules/typescript/lib"
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index f7b3ea7..e8a7c74 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,31 +1,24 @@
import { ChakraProvider } from '@chakra-ui/react';
-import theme from "../styles/theme";
-import { Providers } from "./providers";
+import theme from '../styles/theme';
+import { Providers } from './providers';
export const metadata = {
title: 'Hyperliquid Stats',
description: 'Explore the Hyperliquid protocol’s statistics',
-}
-
-export default function RootLayout({
- children,
-}: {
- children: React.ReactNode
-}) {
+};
+export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
-
+
-
-
-
-
+
+
+
+
-
- {children}
-
+ {children}
- )
+ );
}
diff --git a/app/page.tsx b/app/page.tsx
index 0676fcd..704b9ec 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -8,7 +8,6 @@ const Home: NextPage = () => {
<>
-
>
);
};
diff --git a/app/providers.tsx b/app/providers.tsx
index 4f6c9b4..4d765e1 100644
--- a/app/providers.tsx
+++ b/app/providers.tsx
@@ -1,33 +1,28 @@
-'use client'
+'use client';
import { useEffect } from 'react';
-import { CacheProvider } from '@chakra-ui/next-js'
-import { ChakraProvider } from '@chakra-ui/react'
-import TagManager from 'react-gtm-module'
+import { CacheProvider } from '@chakra-ui/next-js';
+import { ChakraProvider } from '@chakra-ui/react';
+import TagManager from 'react-gtm-module';
import { Global } from '@emotion/react';
-import theme from "../styles/theme";
+import theme from '../styles/theme';
import { GlobalStyles } from '@/styles/global';
-import { DataContextProvider } from "../contexts/data";
+import { DataContextProvider } from '../contexts/data';
const tagManagerArgs = { gtmId: process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_CONFIG as string };
-export function Providers({
- children
-}: {
- children: React.ReactNode
-}) {
+export function Providers({ children }: { children: React.ReactNode }) {
+ useEffect(() => {
+ TagManager.initialize(tagManagerArgs);
+ }, []);
- useEffect(() => {
- TagManager.initialize(tagManagerArgs)
- }, []);
-
- return (
-
-
-
-
- {children}
-
-
-
- )
-}
\ No newline at end of file
+ return (
+
+
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/components/common/chartWrapper/index.tsx b/components/common/chartWrapper/index.tsx
index 0c042a7..c357876 100644
--- a/components/common/chartWrapper/index.tsx
+++ b/components/common/chartWrapper/index.tsx
@@ -1,67 +1,159 @@
-import { RiLoader5Fill } from 'react-icons/ri';
-import { Box, Button, ButtonGroup, Text, Spinner } from '@chakra-ui/react';
+import { useIsMobile } from '@/hooks/useIsMobile';
+import { ChevronDownIcon } from '@chakra-ui/icons';
+import {
+ Box,
+ Button,
+ ButtonGroup,
+ Text,
+ Spinner,
+ MenuButton,
+ Menu,
+ MenuList,
+ MenuItemOption,
+ MenuOptionGroup,
+ Grid,
+} from '@chakra-ui/react';
interface Toggle {
- text: string;
- event: () => void;
- active: boolean;
+ text: string;
+ event: () => void;
+ active: boolean;
}
-const Loader = () =>
+export interface CoinSelector {
+ name: string;
+ event: () => void;
+ isChecked: boolean;
+}
+
+const Loader = () => (
+
+
+
+);
+
+type Props = {
+ title: string;
+ loading: boolean;
+ controls?: {
+ toggles: Toggle[];
+ };
+ zIndex?: number;
+ coinSelectors?: CoinSelector[];
+ children?: React.ReactNode;
+};
+
+function ChartWrapper({ title, loading, controls, zIndex, coinSelectors, children }: Props) {
+ const isMobile = useIsMobile();
+ const controlButtons =
+ controls &&
+ controls.toggles &&
+ controls.toggles.length > 0 &&
+ controls.toggles.map((toggle: Toggle, index: number) => {
+ return (
+
+ );
+ });
+
+ const coinSelectorsMenu = coinSelectors && (
+
+
+
+ );
-function ChartWrapper(props: any) {
- const {
- title,
- loading,
- csvFields,
- data,
- controls,
- zIndex,
- } = props;
+ const menu = (
+
+
+ {isMobile && controls ? (
+ controlButtons
+ ) : (
+ {controlButtons}
+ )}
+ {coinSelectorsMenu}
+
+
+ );
- return (
-
-
-
-
-
- {title}
-
-
-
- {controls && controls.toggles && controls.toggles.length > 0 && controls.toggles.map((toggle: Toggle, index: number) => {
- return (
-
- )
- })}
-
-
-
-
- {loading && }
- {props.children}
-
-
- );
+ return (
+
+
+
+
+ {title}
+
+ {menu}
+
+ {loading && }
+ {children}
+
+
+ );
}
-export default ChartWrapper;
\ No newline at end of file
+export default ChartWrapper;
diff --git a/components/common/footer/index.tsx b/components/common/footer/index.tsx
index d87fcb2..3e855b5 100644
--- a/components/common/footer/index.tsx
+++ b/components/common/footer/index.tsx
@@ -1,32 +1,69 @@
-'use client'
+'use client';
import React from 'react';
import { Box, Text, Flex, Image, Container } from '@chakra-ui/react';
import { FaGithub, FaTelegram } from 'react-icons/fa';
const Footer = () => {
return (
-
-
-
-
-
-
+
+
+
+
+
+
Built By
-
-
+
+
-
+
-
-
+
+
diff --git a/components/common/header/index.tsx b/components/common/header/index.tsx
index 1247588..cc4540c 100644
--- a/components/common/header/index.tsx
+++ b/components/common/header/index.tsx
@@ -1,22 +1,22 @@
-'use client'
+'use client';
import React from 'react';
-import NextImg from "next/image"
-import { Container, Box, Text, Image, Flex, useMediaQuery } from '@chakra-ui/react';
+import NextImg from 'next/image';
+import { Container, Box, Image, Flex, useMediaQuery } from '@chakra-ui/react';
import * as S from './styles';
const Header = () => {
const [isMobile] = useMediaQuery('(max-width: 700px)');
return (
-
+
{
paddingY='0'
>
-
+
-
+
{!isMobile && (
-
-
+
+
Built By
-
-
+
+
)}
{isMobile && (
-
-
-
-
+
+
+
+
Built By
-
-
+
+
-
)}
-
);
};
diff --git a/components/home/charts/cumulative-inflow.tsx b/components/home/charts/cumulative-inflow.tsx
index a2c0e73..4416476 100644
--- a/components/home/charts/cumulative-inflow.tsx
+++ b/components/home/charts/cumulative-inflow.tsx
@@ -1,164 +1,169 @@
import {
- Bar,
- Label,
- XAxis,
- YAxis,
- CartesianGrid,
- Tooltip,
- Legend,
- ResponsiveContainer,
- ComposedChart,
- Line,
- Cell,
+ Bar,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+ ResponsiveContainer,
+ ComposedChart,
+ Line,
+ Cell,
} from 'recharts';
import { useEffect, useState } from 'react';
import { useRequest } from '@/hooks/useRequest';
-import { Box, Text, useMediaQuery } from "@chakra-ui/react"
import ChartWrapper from '../../common/chartWrapper';
+import { CHART_HEIGHT, YAXIS_WIDTH, BRIGHT_GREEN, GREEN, RED } from '../../../constants';
import {
- CHART_HEIGHT,
- YAXIS_WIDTH,
- BRIGHT_GREEN,
- GREEN,
- RED,
-} from "../../../constants";
-import {
- yaxisFormatter,
- xAxisFormatter,
- formatNumberWithOptions,
- tooltipFormatterCurrency,
-} from '../../../helpers'
-import {
- daily_inflow,
- cumulative_inflow,
-} from "../../../constants/api"
+ yaxisFormatter,
+ xAxisFormatter,
+ tooltipFormatterCurrency,
+ tooltipFormatterDate,
+} from '../../../helpers';
+import { daily_inflow, cumulative_inflow } from '../../../constants/api';
-const REQUESTS = [
- daily_inflow,
- cumulative_inflow,
-];
+const REQUESTS = [daily_inflow, cumulative_inflow];
export default function CumulativeInflow() {
- const [isMobile] = useMediaQuery('(max-width: 700px)');
-
- const [formattedData, setFormattedData] = useState([])
- const [dataDailyInflow, loadingDailyInflow, errorDailyInflow] = useRequest(REQUESTS[0], [], 'chart_data');
- const [dataCumulativeInflow, loadingCumulativeInflow, errorCumulativeInflow] = useRequest(REQUESTS[1], [], 'chart_data');
-
- const loading = loadingDailyInflow || loadingCumulativeInflow;
- const error = errorCumulativeInflow || errorCumulativeInflow;
-
- interface DailyInflowData {
- time: string;
- inflow: number;
- }
-
- interface CumulativeInflowData {
- time: string;
- cumulative_inflow: number;
- }
-
- interface MergedData {
- time: Date;
- inflow?: number;
- cumulative_inflow?: number;
- unit: string;
+ const [formattedData, setFormattedData] = useState([]);
+ const [dataDailyInflow, loadingDailyInflow, errorDailyInflow] = useRequest(
+ REQUESTS[0],
+ [],
+ 'chart_data'
+ );
+ const [dataCumulativeInflow, loadingCumulativeInflow, errorCumulativeInflow] = useRequest(
+ REQUESTS[1],
+ [],
+ 'chart_data'
+ );
+
+ const loading = loadingDailyInflow || loadingCumulativeInflow;
+ const error = errorCumulativeInflow || errorCumulativeInflow;
+
+ interface DailyInflowData {
+ time: string;
+ inflow: number;
+ }
+
+ interface CumulativeInflowData {
+ time: string;
+ cumulative_inflow: number;
+ }
+
+ interface MergedData {
+ time: Date;
+ inflow?: number;
+ cumulative_inflow?: number;
+ unit: string;
+ }
+
+ const mergeInflows = (
+ dailyInflows: DailyInflowData[],
+ cumulativeInflows: CumulativeInflowData[]
+ ): MergedData[] => {
+ const map = new Map();
+
+ dailyInflows.forEach((item) => {
+ map.set(item.time, {
+ ...map.get(item.time),
+ time: new Date(item.time),
+ inflow: item.inflow,
+ unit: 'single',
+ });
+ });
+
+ cumulativeInflows.forEach((item) => {
+ map.set(item.time, {
+ ...map.get(item.time),
+ time: new Date(item.time),
+ cumulative_inflow: item.cumulative_inflow,
+ unit: 'single',
+ });
+ });
+
+ return Array.from(map.values());
+ };
+
+ const formatData = () => {
+ const formattedData = mergeInflows(dataDailyInflow, dataCumulativeInflow);
+ setFormattedData(formattedData);
+ };
+
+ useEffect(() => {
+ if (!loading && !errorDailyInflow) {
+ formatData();
}
-
- const mergeInflows = (dailyInflows: DailyInflowData[], cumulativeInflows: CumulativeInflowData[]): MergedData[] => {
- const map = new Map();
-
- dailyInflows.forEach(item => {
- map.set(item.time, { ...map.get(item.time), time: new Date(item.time), inflow: item.inflow, unit: "single" });
- });
-
- cumulativeInflows.forEach(item => {
- map.set(item.time, { ...map.get(item.time), time: new Date(item.time), cumulative_inflow: item.cumulative_inflow, unit: "single" });
- });
-
- return Array.from(map.values());
- };
-
-
- const formatData = () => {
- const formattedData = mergeInflows(dataDailyInflow, dataCumulativeInflow);
- setFormattedData(formattedData);
- };
-
- useEffect(() => {
- if (!loading && !errorDailyInflow) {
- formatData();
- }
- }, [loading])
-
- return (
-
-
-
-
-
-
-
- ''}
- contentStyle={{
- textAlign: 'left',
- background: "#0A1F1B",
- borderColor: "#061412",
- color: "#fff",
- boxShadow: "0px 0px 7px rgb(0 0 0 / 20%)",
- borderRadius: "26px",
- maxHeight: "500px"
- }}
- itemSorter={(item) => {
- return Number(item.value) * -1;
- }}
-
- />
-
-
- {(formattedData || []).map((item: any, i: number) => {
- return 0 ? GREEN : RED} />
- })}
- |
-
-
-
-
- )
-}
\ No newline at end of file
+ }, [loading, errorDailyInflow]);
+
+ return (
+
+
+
+
+
+
+
+ {
+ return Number(item.value) * -1;
+ }}
+ />
+
+
+ {(formattedData || []).map((item: any, i: number) => {
+ return 0 ? GREEN : RED} />;
+ })}
+ |
+
+
+
+
+ );
+}
diff --git a/components/home/charts/cumulative-notional-liquidated.tsx b/components/home/charts/cumulative-notional-liquidated.tsx
deleted file mode 100644
index 19e3701..0000000
--- a/components/home/charts/cumulative-notional-liquidated.tsx
+++ /dev/null
@@ -1,314 +0,0 @@
-import {
- Bar,
- Label,
- XAxis,
- YAxis,
- CartesianGrid,
- Tooltip,
- Legend,
- ResponsiveContainer,
- ComposedChart,
- Line
-} from 'recharts';
-import { useEffect, useState } from 'react';
-import { useRequest } from '@/hooks/useRequest';
-import { Box, Text, useMediaQuery } from "@chakra-ui/react"
-import ChartWrapper from '../../common/chartWrapper';
-import {
- CHART_HEIGHT,
- YAXIS_WIDTH,
- BRIGHT_GREEN,
- BRAND_GREEN_2,
- BRAND_GREEN_3
-} from "../../../constants";
-import {
- tooltipLabelFormatter,
- yaxisFormatter,
- xAxisFormatter,
- tooltipFormatterCurrency,
-} from '../../../helpers'
-import { getTokenHex } from "../../../constants/tokens";
-import {
- cumulative_liquidated_notional,
- daily_notional_liquidated_total,
- daily_notional_liquidated_by_leverage_type,
- daily_notional_liquidated_by_coin,
-} from "../../../constants/api"
-
-const REQUESTS = [
- cumulative_liquidated_notional,
- daily_notional_liquidated_total,
- daily_notional_liquidated_by_leverage_type,
- daily_notional_liquidated_by_coin,
-];
-
-export default function CumulativeNotionalLiquidated() {
- const [isMobile] = useMediaQuery('(max-width: 700px)');
-
- const [dataMode, setDataMode] = useState<"COINS" | "MARGIN">('COINS');
- const [formattedDataCoins, setFormattedDataCoins] = useState([])
- const [formattedDataMargin, setFormattedDataMargin] = useState([])
- const [formattedCumulativeVolumeData, setFormattedCumulativeVolumeData] = useState([]);
-
- const [coinKeys, setCoinKeys] = useState([])
-
- const [dataCumulativeLiquidated, loadingCumulativeLiquidated, errorCumulativeLiquidated] = useRequest(REQUESTS[0], [], 'chart_data');
- const [dataDailyLiquidatedTotal, loadingDailyLiquidatedTotal, errorDailyUsdVolumeTotal] = useRequest(REQUESTS[1], [], 'chart_data');
- const [dataDailyLiquidatedByMargin, loadingDailyLiquidatedByMargin, errorDailyLiquidatedByMargin] = useRequest(REQUESTS[2], [], 'chart_data');
- const [dataDailyLiquidatedByCoins, loadingDailyLiquidatedByCoins, errorDailyLiquidatedByCoins] = useRequest(REQUESTS[3], [], 'chart_data');
-
- console.log('test coinKeys', coinKeys);
-
-
-
- const loading = loadingCumulativeLiquidated || loadingDailyLiquidatedTotal || loadingDailyLiquidatedByMargin || loadingDailyLiquidatedByCoins;
- const error = errorCumulativeLiquidated || errorDailyUsdVolumeTotal || errorDailyLiquidatedByMargin || errorDailyLiquidatedByCoins;
-
- type CumulativeLiquidationData = { cumulative: number, time: string };
-
- const formatCumulativeLiquidatedByTime = (dataCumulativeLiquidated: CumulativeLiquidationData[]): { [key: string]: number } => {
- const result: { [key: string]: number } = {};
- for (const data of dataCumulativeLiquidated) {
- result[data.time] = data.cumulative;
- }
- return result;
- };
-
- type VolumeData = { coin: string, daily_usd_volume: number, time: string };
-
- type LiquidationData = {
- time: string;
- leverage_type: 'Cross' | 'Isolated';
- daily_notional_liquidated: number;
- };
-
- const formatLiquidatedByMargin = (
- dataDailyLiquidatedByMargin: LiquidationData[],
- formattedCumulativeLiquidatedByTime: { [key: string]: number }
- ): any[] => {
-
- const temp: { [key: string]: any } = {};
- for (const data of dataDailyLiquidatedByMargin) {
- if (!temp[data.time]) {
- temp[data.time] = { all: 0 };
- }
- temp[data.time][data.leverage_type] = data.daily_notional_liquidated;
- temp[data.time].all += data.daily_notional_liquidated;
- }
- const result: any[] = Object.entries(temp).map((item: any) => {
- return {
- time: new Date(item[0]),
- crossed: item[1].hasOwnProperty('Cross') ? item[1].Cross : 0,
- isolated: item[1].hasOwnProperty('Isolated') ? item[1].Isolated : 0,
- all: item[1].all,
- cumulative: formattedCumulativeLiquidatedByTime[item[0]]
- };
- });
- return result;
- };
-
- type FormattedCoinTradesData = any[];
-
- const formatDailyTradesByCoins = (
- dataDailyTradesByCoin: { time: string, coin: string, daily_notional_liquidated: number }[],
- formattedCumulativeByTime: { [key: string]: number },
- ): FormattedCoinTradesData[] => {
- const temp: { [key: string]: { all: number, [coin: string]: number } } = {};
- for (const data of dataDailyTradesByCoin) {
- if (!temp[data.time]) {
- temp[data.time] = { all: 0 };
- }
- temp[data.time][data.coin] = data.daily_notional_liquidated;
- temp[data.time].all += data.daily_notional_liquidated;
- }
-
- const sortAndSliceTop10 = (obj: { [coin: string]: number }) => {
- const sortedEntries = Object.entries(obj)
- .sort(([, aVolume], [, bVolume]) => bVolume - aVolume);
- const top10Entries = sortedEntries.slice(0, 10);
- const otherEntries = sortedEntries.slice(10);
-
- const otherVolume = otherEntries.reduce((total, [, volume]) => total + volume, 0);
- return {
- ...Object.fromEntries(top10Entries),
- Other: otherVolume
- };
- };
-
- const result: any[] = Object.entries(temp).map(([time, volumes]) => {
- const top10Volumes = sortAndSliceTop10(volumes);
- return {
- time: new Date(time),
- ...top10Volumes,
- cumulative: formattedCumulativeByTime[time as any],
- unit: '',
- };
- });
- return result;
- };
-
- const extractUniqueCoins = (formattedData: any[]): string[] => {
- const coinSet = new Set();
- for (const data of formattedData) {
- Object.keys(data).forEach(coin => {
- if (coin !== 'time' &&
- coin !== 'unit' && coin !== 'cumulative' && coin !== 'all'
- ) {
- coinSet.add(coin);
- }
- });
- }
- const coinsArray = Array.from(coinSet);
- if (coinsArray.includes('Other')) {
- const index = coinsArray.indexOf('Other');
- coinsArray.splice(index, 1);
- coinsArray.push('Other');
- }
- return coinsArray;
- };
-
- const formatData = () => {
- const formattedCumulativeLiquidatedByTime = formatCumulativeLiquidatedByTime(dataCumulativeLiquidated)
- const formattedVolumeByMargin = formatLiquidatedByMargin(dataDailyLiquidatedByMargin, formattedCumulativeLiquidatedByTime);
- const formattedDailyTradesByCoins = formatDailyTradesByCoins(dataDailyLiquidatedByCoins, formattedCumulativeLiquidatedByTime);
- setCoinKeys(extractUniqueCoins(formattedDailyTradesByCoins));
- setFormattedDataMargin(formattedVolumeByMargin);
- setFormattedDataCoins(formattedDailyTradesByCoins);
- console.log('dev formattedDailyTradesByCoins', formattedDailyTradesByCoins)
- };
-
- const controls = {
- toggles: [
- {
- text: "Coins",
- event: () => setDataMode('COINS'),
- active: dataMode === 'COINS',
- },
- {
- text: "Cross / Isolated Margin",
- event: () => setDataMode('MARGIN'),
- active: dataMode === 'MARGIN',
- },
- ]
- }
-
- useEffect(() => {
- if (!loading) {
- formatData();
- }
- }, [loading])
-
- return (
-
-
-
-
-
-
-
- {
- return Number(item.value) * -1;
- }}
- />
-
- {
- dataMode === 'COINS' && (
- <>
- {
- coinKeys && coinKeys.map(((coinName, i) => {
- return (
-
- )
- }))
- }
- >
- )
- }
- {
- dataMode === 'MARGIN' && (
- <>
-
-
- >
- )
- }
-
-
-
-
-
- Top 10 Coins grouped daily and remaining coins grouped by Other
-
-
-
- )
-}
diff --git a/components/home/charts/cumulative-users.tsx b/components/home/charts/cumulative-users.tsx
index 0e766c6..0e65a97 100644
--- a/components/home/charts/cumulative-users.tsx
+++ b/components/home/charts/cumulative-users.tsx
@@ -1,141 +1,141 @@
import {
- Bar,
- Label,
- XAxis,
- YAxis,
- CartesianGrid,
- Tooltip,
- Legend,
- ResponsiveContainer,
- ComposedChart,
- Line
+ Bar,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+ ResponsiveContainer,
+ ComposedChart,
+ Line,
} from 'recharts';
import { useEffect, useState } from 'react';
import { useRequest } from '@/hooks/useRequest';
-import { useMediaQuery } from "@chakra-ui/react"
import ChartWrapper from '../../common/chartWrapper';
+import { CHART_HEIGHT, YAXIS_WIDTH, BRIGHT_GREEN, GREEN } from '../../../constants';
import {
- CHART_HEIGHT,
- YAXIS_WIDTH,
- BRIGHT_GREEN,
- GREEN,
-} from "../../../constants";
+ xAxisFormatter,
+ yaxisFormatterNumber,
+ tooltipFormatter,
+ tooltipFormatterDate,
+} from '../../../helpers';
import {
- xAxisFormatter,
- yaxisFormatterNumber,
- tooltipFormatter
-} from "../../../helpers"
-import {
- cumulative_new_users,
- daily_unique_users,
- daily_unique_users_by_coin,
-} from "../../../constants/api"
+ cumulative_new_users,
+ daily_unique_users,
+ daily_unique_users_by_coin,
+} from '../../../constants/api';
-const REQUESTS = [
- cumulative_new_users,
- daily_unique_users,
- daily_unique_users_by_coin,
-];
+const REQUESTS = [cumulative_new_users, daily_unique_users, daily_unique_users_by_coin];
export default function CumulativeUsers() {
- const [isMobile] = useMediaQuery('(max-width: 700px)');
-
- const [formattedData, setFormattedData] = useState([])
- const [unquieKeys, setUnquieKeys] = useState([])
+ const [formattedData, setFormattedData] = useState([]);
- const [dataCumulativeNewUsers, loadingCumulativeNewUsers, errorCumulativeNewUsers] = useRequest(REQUESTS[0], [], 'chart_data');
- const [dataDailyUniqueUsers, loadingDailyUniqueUsers, errorDailyUniqueUsers] = useRequest(REQUESTS[1], [], 'chart_data');
- const [dataDailyUniqueUsersByCoin, loadingDailyUniqueUsersByCoin, errorDailyUniqueUsersByCoin] = useRequest(REQUESTS[2], [], 'chart_data');
+ const [dataCumulativeNewUsers, loadingCumulativeNewUsers, errorCumulativeNewUsers] = useRequest(
+ REQUESTS[0],
+ [],
+ 'chart_data'
+ );
+ const [dataDailyUniqueUsers, loadingDailyUniqueUsers, errorDailyUniqueUsers] = useRequest(
+ REQUESTS[1],
+ [],
+ 'chart_data'
+ );
- const loading = loadingCumulativeNewUsers || loadingDailyUniqueUsers;
- const error = errorCumulativeNewUsers || errorDailyUniqueUsers;
+ const loading = loadingCumulativeNewUsers || loadingDailyUniqueUsers;
+ const error = errorCumulativeNewUsers || errorDailyUniqueUsers;
- type CumulativeUniqueUsers = { time: string, cumulative_new_users: number, daily_new_users: number };
+ type CumulativeUniqueUsers = {
+ time: string;
+ cumulative_new_users: number;
+ daily_new_users: number;
+ };
- const formatdataCumulativeUniqueUsers = (dataCumulativeUsdVolume: CumulativeUniqueUsers[]): any[] => {
- return dataCumulativeUsdVolume.map((item: CumulativeUniqueUsers) => ({
- ...item,
- time: new Date(item.time),
- }));
- };
+ const formatdataCumulativeUniqueUsers = (
+ dataCumulativeUsdVolume: CumulativeUniqueUsers[]
+ ): any[] => {
+ return dataCumulativeUsdVolume.map((item: CumulativeUniqueUsers) => ({
+ ...item,
+ time: new Date(item.time),
+ }));
+ };
- const formatData = () => {
- const formattedData = formatdataCumulativeUniqueUsers(dataCumulativeNewUsers);
- setFormattedData(formattedData);
- };
+ const formatData = () => {
+ const formattedData = formatdataCumulativeUniqueUsers(dataCumulativeNewUsers);
+ setFormattedData(formattedData);
+ };
- useEffect(() => {
- if (!loading) {
- formatData();
- }
- }, [loading])
+ useEffect(() => {
+ if (!loading || !error) {
+ formatData();
+ }
+ }, [loading, error]);
- return (
-
-
-
-
-
-
-
- ''}
- contentStyle={{
- textAlign: 'left',
- background: "#0A1F1B",
- borderColor: "#061412",
- boxShadow: "0px 0px 7px rgb(0 0 0 / 20%)",
- borderRadius: "26px",
- maxHeight: "500px"
- }}
- itemSorter={(item) => {
- return Number(item.value) * -1;
- }}
- filterNull={true}
- />
-
-
-
-
-
-
- )
-}
\ No newline at end of file
+ return (
+
+
+
+
+
+
+
+ {
+ return Number(item.value) * -1;
+ }}
+ filterNull={true}
+ />
+
+
+
+
+
+
+ );
+}
diff --git a/components/home/charts/date-range.tsx b/components/home/charts/date-range.tsx
index cf64013..9c45ed6 100644
--- a/components/home/charts/date-range.tsx
+++ b/components/home/charts/date-range.tsx
@@ -1,178 +1,163 @@
-import { useState, useEffect, useContext } from 'react'
-import Select from 'react-dropdown-select'
-import { Box, Text } from "@chakra-ui/react"
-import moment from 'moment'
+import { useState, useEffect, useContext } from 'react';
+import Select from 'react-dropdown-select';
+import { Box } from '@chakra-ui/react';
+import moment from 'moment';
import { DateRange } from 'react-date-range';
-import strftime from 'strftime';
-import { DataContext } from "../../../contexts/data";
+import strftime, { timezone } from 'strftime';
+import { DataContext } from '../../../contexts/data';
import 'react-date-range/dist/styles.css';
import 'react-date-range/dist/theme/default.css';
const ALL_TIME_ID = 4;
-const DATA_START_DATE = new Date('2023-05-10');
+// const DATA_START_DATE = new Date('2023-06-14T20:00:00.000');
+const DATA_START_DATE = new Date('2024-03-11T20:00:00.000');
const DATE_NOW = new Date();
export const DateRangeSelect = () => {
- const { dates, setDates } = useContext(DataContext);
-
- const [selectedDateRangeOption, setSelectedDateRangeOption] = useState(null)
- const [rangeState, setRangeState] = useState<{ startDate: Date | undefined, endDate: Date | undefined, key: string }[]>([
- {
- startDate: DATA_START_DATE,
- endDate: DATE_NOW,
- key: 'selection'
- }
+ const { setDates } = useContext(DataContext);
+
+ const [selectedDateRangeOption, setSelectedDateRangeOption] = useState(null);
+ const [rangeState, setRangeState] = useState<
+ { startDate: Date | undefined; endDate: Date | undefined; key: string }[]
+ >([
+ {
+ startDate: DATA_START_DATE,
+ endDate: DATE_NOW,
+ key: 'selection',
+ },
+ ]);
+
+ console.log({
+ startDate: DATA_START_DATE,
+ endDate: new Date(),
+ key: 'selection',
+ });
+
+ const [dataRange, setDataRange] = useState({ fromValue: DATA_START_DATE, toValue: DATE_NOW });
+
+ const onChange = (selectedDates: any) => {
+ const [start, end] = selectedDates;
+ const from = start ? strftime('%Y-%m-%d', new Date(start)) : undefined;
+ const to = end ? strftime('%Y-%m-%d', end) : undefined;
+ if (from === to) return;
+ setDataRange({ fromValue: start, toValue: end });
+ setDates({ from, to });
+ };
+
+ const dateRangeOptions = [
+ {
+ label: 'Last Month',
+ id: 1,
+ },
+ {
+ label: 'All Time',
+ id: 4,
+ isDefault: true,
+ },
+ ];
+
+ useEffect(() => {
+ setRangeState([
+ {
+ startDate: dataRange.fromValue,
+ endDate: dataRange.toValue,
+ key: 'selection',
+ },
]);
+ }, [dataRange.fromValue, dataRange.toValue]);
- console.log({
- startDate: DATA_START_DATE,
- endDate: new Date(),
- key: 'selection'
- })
-
- const [dataRange, setDataRange] = useState(
- { fromValue: DATA_START_DATE, toValue: DATE_NOW }
- )
-
- const onChange = (selectedDates: any) => {
- const [start, end] = selectedDates;
- const from = start ? strftime('%Y-%m-%d', new Date(start)) : undefined
- const to = end ? strftime('%Y-%m-%d', end) : undefined
- if (from === to) return;
- setDataRange({ fromValue: start, toValue: end })
- setDates({ from, to });
- };
-
- const dateRangeOptions = [{
- label: "Last Month",
- id: 1
- }, {
- label: "All Time",
- id: 4,
- isDefault: true
- }]
-
- useEffect(() => {
- setRangeState([
- {
- startDate: dataRange.fromValue,
- endDate: dataRange.toValue,
- key: 'selection'
- }
- ])
- }, [dataRange.fromValue, dataRange.toValue])
-
- const onSelectItem = (option: { id: number, label: string, isDefault?: boolean }) => {
-
- if (option.id == ALL_TIME_ID) {
- onChange([null, null])
- return;
- }
- const end = new Date()
- const start = moment().subtract(option.id, 'month').toDate();
- setSelectedDateRangeOption(option.id)
- if (option.id == ALL_TIME_ID) {
- onChange([null, null])
- } else {
- onChange([start, end])
- }
+ const onSelectItem = (option: { id: number; label: string; isDefault?: boolean }) => {
+ if (option.id == ALL_TIME_ID) {
+ onChange([null, null]);
+ return;
}
-
- useEffect(() => {
- let selected = false
- for (const option of dateRangeOptions) {
- if (option.isDefault) {
- selected = true
- onSelectItem(option)
- break
- }
- }
- if (!selected) {
- onSelectItem(dateRangeOptions[0])
- }
- }, [])
-
- const onDateRangeChange = (item: any) => {
- setRangeState([item.selection])
- if (item.selection.startDate == item.selection.endDate) {
- return
- }
- onChange([item.selection.startDate, item.selection.endDate])
+ const end = new Date();
+ const start = moment().subtract(option.id, 'month').toDate();
+ setSelectedDateRangeOption(option.id);
+ if (option.id == ALL_TIME_ID) {
+ onChange([null, null]);
+ } else {
+ onChange([start, end]);
+ }
+ };
+
+ useEffect(() => {
+ let selected = false;
+ for (const option of dateRangeOptions) {
+ if (option.isDefault) {
+ selected = true;
+ onSelectItem(option);
+ break;
+ }
}
+ if (!selected) {
+ onSelectItem(dateRangeOptions[0]);
+ }
+ }, []);
- const customContentRenderer = ({ props, state }: any) => {
- const start = dataRange.fromValue && dataRange.fromValue.toISOString().slice(0, 10)
- const end = dataRange.toValue && (dataRange.toValue as Date).toISOString().slice(0, 10)
-
- return (
- {dataRange.fromValue && dataRange.toValue && `${strftime('%m-%d-%y', dataRange.fromValue)} to ${strftime('%m-%d-%y', dataRange.toValue)}`}
- {(!dataRange.fromValue || !dataRange.toValue) && 'All time'}
-
)
- };
-
- const customDropdownRenderer = ({ props, state, methods }: any) => {
- const regexp = new RegExp(state.search, 'i');
-
- return (
-
- {/*
- {props.options
- .filter((item: any) => regexp.test(item[props.searchBy] || item[props.labelField]))
- .map((option: any, index: any) => {
- if (!props.keepSelectedInList && methods.isSelected(option)) {
- return null;
- }
-
- return (
- null : () => onSelectItem(option)}
- className={option.id === selectedDateRangeOption ? 'date-range-item selected' : 'date-range-item'}
- >
- {option[props.labelField]}
-
- );
- })}
- */}
-
-
-
-
- );
- };
-
- const selectedOption = dateRangeOptions.find(option => option.id === selectedDateRangeOption);
- const values = selectedOption ? [selectedOption] : [];
-
- const handleSelectChange = () => {
- // console.log('handleSelectChange')
+ const onDateRangeChange = (item: any) => {
+ setRangeState([item.selection]);
+ if (item.selection.startDate == item.selection.endDate) {
+ return;
}
+ onChange([item.selection.startDate, item.selection.endDate]);
+ };
+ const customContentRenderer = () => {
+ return (
+
+ {dataRange.fromValue &&
+ dataRange.toValue &&
+ `${strftime('%m-%d-%y', dataRange.fromValue)} to ${strftime(
+ '%m-%d-%y',
+ dataRange.toValue
+ )}`}
+ {(!dataRange.fromValue || !dataRange.toValue) && 'All time'}
+
+ );
+ };
+
+ const customDropdownRenderer = ({ props, state }: any) => {
return (
-
-
+
+
+
- )
-}
\ No newline at end of file
+
+ );
+ };
+
+ const selectedOption = dateRangeOptions.find((option) => option.id === selectedDateRangeOption);
+ const values = selectedOption ? [selectedOption] : [];
+
+ const handleSelectChange = () => {
+ // console.log('handleSelectChange')
+ };
+
+ return (
+
+
+
+ );
+};
diff --git a/components/home/charts/fees.tsx b/components/home/charts/fees.tsx
new file mode 100644
index 0000000..eb21a7c
--- /dev/null
+++ b/components/home/charts/fees.tsx
@@ -0,0 +1,136 @@
+import {
+ Bar,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+ ResponsiveContainer,
+ ComposedChart,
+ Line,
+} from 'recharts';
+import { useEffect, useState } from 'react';
+import { useRequest } from '@/hooks/useRequest';
+import ChartWrapper from '../../common/chartWrapper';
+import { CHART_HEIGHT, YAXIS_WIDTH, BRIGHT_GREEN, GREEN } from '../../../constants';
+import {
+ yaxisFormatter,
+ xAxisFormatter,
+ tooltipFormatterCurrency,
+ tooltipFormatterDate,
+} from '../../../helpers';
+import { total_accrued_fees } from '../../../constants/api';
+
+const REQUESTS = [total_accrued_fees];
+
+export default function Fees() {
+ const [formattedData, setFormattedData] = useState([]);
+ const [dailyFeesAccrued, loading, error] = useRequest(REQUESTS[0], [], 'chart_data');
+
+ interface DailyFeesAccrued {
+ time: Date;
+ daily_accrued_fees: number;
+ }
+
+ interface MergedData {
+ time: Date;
+ daily_accrued_fees: number;
+ cumulative_accrued_fees: number;
+ }
+
+ const makeFormattedData = (dailyFeesAccrued: DailyFeesAccrued[]): MergedData[] => {
+ const result = [];
+ let cumulativeFees = 0;
+ for (let i = 0; i < dailyFeesAccrued.length; i++) {
+ const dailyFeeAccrued = dailyFeesAccrued[i].daily_accrued_fees ?? 0;
+ cumulativeFees += dailyFeeAccrued;
+ result.push({
+ time: new Date(dailyFeesAccrued[i].time),
+ daily_accrued_fees: dailyFeeAccrued,
+ cumulative_accrued_fees: cumulativeFees,
+ });
+ }
+
+ return result;
+ };
+
+ const formatData = () => {
+ const newFormattedData = makeFormattedData(dailyFeesAccrued);
+ setFormattedData(newFormattedData);
+ };
+
+ useEffect(() => {
+ if (!loading && !error) {
+ formatData();
+ }
+ }, [loading, error]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {
+ return Number(item.value) * -1;
+ }}
+ />
+
+
+
+ );
+}
diff --git a/components/home/charts/funding-rate.tsx b/components/home/charts/funding-rate.tsx
index 1619aa0..88e1d0c 100644
--- a/components/home/charts/funding-rate.tsx
+++ b/components/home/charts/funding-rate.tsx
@@ -1,198 +1,163 @@
import {
- XAxis,
- YAxis,
- CartesianGrid,
- Tooltip,
- Legend,
- ResponsiveContainer,
- LineChart,
- Line
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+ ResponsiveContainer,
+ LineChart,
+ Line,
} from 'recharts';
-import { Box, Text, useMediaQuery } from "@chakra-ui/react"
import { useEffect, useState } from 'react';
import { useRequest } from '@/hooks/useRequest';
import ChartWrapper from '../../common/chartWrapper';
+import { CHART_HEIGHT } from '../../../constants';
import {
- CHART_HEIGHT,
- YAXIS_WIDTH,
- BRIGHT_GREEN,
- GREEN,
- RED,
-} from "../../../constants";
-import {
- yaxisFormatterNumber,
- tooltipFormatter,
- tooltipLabelFormatter,
- xAxisFormatter,
- formatterPercent,
- yaxisFormatter,
-} from '../../../helpers'
-import { getTokenHex } from "../../../constants/tokens";
-import {
- funding_rate,
-} from "../../../constants/api"
-
-const REQUESTS = [
- funding_rate
-];
-
-export default function FundingRate() {
- const [isMobile] = useMediaQuery('(max-width: 700px)');
-
- const [coinKeys, setCoinKeys] = useState([])
- const [formattedData, setFormattedData] = useState([])
- const [dataFundingRate, loadingFundingRate, errorFundingRate] = useRequest(REQUESTS[0], [], 'chart_data');
-
- const loading = loadingFundingRate;
- const error = errorFundingRate;
+ tooltipFormatter,
+ xAxisFormatter,
+ formatterPercent,
+ tooltipFormatterDate,
+} from '../../../helpers';
+import { createCoinSelectors } from '../../../helpers/utils';
- type FundingData = {
- coin: string,
- sum_funding: number,
- time: string,
- };
+import { getTokenColor, initialTokensSelected } from '../../../constants/tokens';
+import { funding_rate } from '../../../constants/api';
- type GroupedFundingData = {
- time: Date;
- [coin: string]: number | Date;
+const REQUESTS = [funding_rate];
- };
+export default function FundingRate() {
+ const [coinKeys, setCoinKeys] = useState([]);
+ const [formattedData, setFormattedData] = useState([]);
+ const [dataFundingRate, loadingFundingRate, errorFundingRate] = useRequest(
+ REQUESTS[0],
+ [],
+ 'chart_data'
+ );
+ const [coinsSelected, setCoinsSelected] = useState(initialTokensSelected);
+
+ const loading = loadingFundingRate;
+ const error = errorFundingRate;
+
+ type FundingData = {
+ coin: string;
+ sum_funding: number;
+ time: string;
+ };
+
+ type GroupedFundingData = {
+ time: Date;
+ [coin: string]: number | Date;
+ };
+
+ const groupByTimeAndFilterUnselected = (data: FundingData[]): GroupedFundingData[] => {
+ const map = new Map();
+ const coinFundingTotals = new Map();
+
+ data.forEach((item) => {
+ if (!coinsSelected.includes(item.coin)) {
+ return;
+ }
+ const key = item.time;
+ if (!map.has(key)) {
+ map.set(key, {
+ time: new Date(key),
+ unit: '%',
+ });
+ }
- const groupByTime = (data: FundingData[]): GroupedFundingData[] => {
- const map = new Map();
- const coinFundingTotals = new Map();
+ const existingEntry = map.get(key);
- data.forEach((item) => {
- const key = item.time;
- if (!map.has(key)) {
- map.set(key, {
- time: new Date(key),
- unit: "%"
- });
- }
+ const value = (existingEntry[item.coin] || 0) + item.sum_funding;
- const existingEntry = map.get(key);
+ existingEntry[item.coin] = value * 100;
- const value = (existingEntry[item.coin] || 0) + item.sum_funding;
+ // Update total funding for the coin
+ coinFundingTotals.set(item.coin, value * 100);
- existingEntry[item.coin] = value * 100;
+ map.set(key, existingEntry);
+ });
- // Update total funding for the coin
- coinFundingTotals.set(item.coin, value * 100);
+ const result = Array.from(map.values());
+ return result;
+ };
- map.set(key, existingEntry);
- });
+ const extractUniqueCoins = (fundingData: FundingData[]): string[] => {
+ const coinSet = new Set();
+ for (const data of fundingData) {
+ const { coin } = data;
+ if (coin !== 'time' && coin !== 'unit') {
+ coinSet.add(coin);
+ }
+ }
+ return Array.from(coinSet);
+ };
- // Get the top 10 coins by total funding over the whole time period
- const topCoins = Array.from(coinFundingTotals.entries())
- .sort((a, b) => b[1] - a[1])
- .slice(0, 10)
- .map(([coin]) => coin);
-
- // Filter out the coins not in the top 10 for each time period
- const result = Array.from(map.values()).map((record: any) => {
- Object.keys(record).forEach((coin) => {
- if (coin !== 'time' && !topCoins.includes(coin) && coin !== 'unit') {
- delete record[coin];
- }
- });
- return record;
- });
+ useEffect(() => {
+ if (!loading && !error) {
+ const uniqueCoins = extractUniqueCoins(dataFundingRate);
+ setCoinKeys(uniqueCoins);
+ }
+ }, [loading, dataFundingRate]);
- return result;
- };
-
-
- const extractUniqueCoins = (formattedData: GroupedFundingData[]): string[] => {
- const coinSet = new Set();
- for (const data of formattedData) {
- Object.keys(data).forEach(coin => {
- if (coin !== 'time' &&
- coin !== 'unit'
- ) {
- coinSet.add(coin);
- }
- });
- }
- const coinsArray = Array.from(coinSet);
- if (coinsArray.includes('Other')) {
- const index = coinsArray.indexOf('Other');
- coinsArray.splice(index, 1);
- coinsArray.push('Other');
- }
- return coinsArray;
- };
-
- const formatData = () => {
- if (dataFundingRate) {
- const groupedData = groupByTime(dataFundingRate);
- const uniquieCoins = extractUniqueCoins(groupedData);
- setFormattedData(groupedData);
- setCoinKeys(uniquieCoins);
- }
+ const formatData = () => {
+ if (dataFundingRate) {
+ const groupedAndFilteredData = groupByTimeAndFilterUnselected(dataFundingRate);
+ setFormattedData(groupedAndFilteredData);
}
+ };
- useEffect(() => {
- if (!loading && !error) {
- formatData();
- }
- }, [loading])
-
- return (
-
-
-
-
-
-
- ''}
- contentStyle={{
- textAlign: 'left',
- background: "#0A1F1B",
- borderColor: "#061412",
- color: "#fff",
- boxShadow: "0px 0px 7px rgb(0 0 0 / 20%)",
- borderRadius: "26px",
- maxHeight: "500px"
- }}
- itemSorter={(item) => {
- return Number(item.value) * -1;
- }}
- />
-
- {
- coinKeys.map(((coinName, i) => {
- return (
-
- )
- }))
- }
-
-
-
- Top 10 Coins over time
-
-
- )
-}
\ No newline at end of file
+ useEffect(() => {
+ if (!loading && !error) {
+ formatData();
+ }
+ }, [loading, coinsSelected]);
+
+ const coinSelectors = createCoinSelectors(coinKeys, coinsSelected, setCoinsSelected, formatData);
+
+ return (
+
+
+
+
+
+
+ {
+ return Number(item.value) * -1;
+ }}
+ />
+
+ {coinsSelected.map((coinName, i) => {
+ return (
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/components/home/charts/hlp-liquidator-profit.tsx b/components/home/charts/hlp-liquidator-profit.tsx
deleted file mode 100644
index a73605e..0000000
--- a/components/home/charts/hlp-liquidator-profit.tsx
+++ /dev/null
@@ -1,197 +0,0 @@
-import React, { useEffect, useState } from 'react'
-import {
- Bar,
- Cell,
- CartesianGrid,
- ComposedChart,
- Legend,
- Line,
- ResponsiveContainer,
- Tooltip,
- XAxis,
- YAxis
-} from 'recharts';
-import { sortBy, maxBy, minBy } from 'lodash'
-import { useMediaQuery } from "@chakra-ui/react"
-import { useRequest } from '@/hooks/useRequest';
-import { hlp_liquidator_pnl, cumulative_hlp_liquidator_pnl, hlp_liquidator_pnl_false, cumulative_hlp_liquidator_pnl_false } from "../../../constants/api"
-import ChartWrapper from '../../common/chartWrapper';
-import {
- CHART_HEIGHT,
- YAXIS_WIDTH,
- BRIGHT_GREEN,
- GREEN,
- RED,
-} from "../../../constants";
-import {
- yaxisFormatter,
- xAxisFormatter,
- tooltipFormatterCurrency,
-} from '../../../helpers'
-
-const REQUESTS = [
- hlp_liquidator_pnl,
- cumulative_hlp_liquidator_pnl,
- hlp_liquidator_pnl_false,
- cumulative_hlp_liquidator_pnl_false,
-];
-
-export default function HLPProfitLossChart() {
- const [isMobile] = useMediaQuery('(max-width: 700px)');
-
- const [chartDataLiquidator, setChartDataLiquidator] = useState(null);
- const [chartDataNonLiquidator, setChartDataNonLiquidator] = useState(null);
- const [dataMode, setDataMode] = useState<"HLP" | "NON_HLP">('HLP');
- const [dataHLPLiquidatorPNL, loadingHLPLiquidatorPNL, errorHLPLiquidatorPNL] = useRequest(REQUESTS[0], [], 'chart_data');
- const [dataCumulativeHLPLiquidatorPNL, loadingCumulativeHLPLiquidatorPNL, errorCumulative_HLPLiquidatorPNL] = useRequest(REQUESTS[1], [], 'chart_data');
- const [dataNonLiquidator, loadingNonLiquidator, errorNonLiquidator] = useRequest(REQUESTS[2], [], 'chart_data');
- const [dataCumulativeNonLiquidatorPNL, loadingCumulativeNonLiquidatorPNL, errorCumulative_NonLiquidatorPNL] = useRequest(REQUESTS[3], [], 'chart_data');
-
-
- const formatTradingData = (dataHLPLiquidatorPNL: any, dataCumulativeHLPLiquidatorPNL: any) => {
- let currentProfitCumulative = 0;
- let currentLossCumulative = 0;
-
- let data = sortBy(dataHLPLiquidatorPNL, (i) => Date.parse(i.time)).map((dataItem, index) => {
- const cumulative_pnl = dataCumulativeHLPLiquidatorPNL[index]?.cumulative_pnl || 0;
- return {
- cumulative_pnl: cumulative_pnl,
- total_pnl: dataItem.total_pnl,
- timestamp: new Date(dataItem.time),
- profit_cumulative: cumulative_pnl > 0 ? cumulative_pnl : 0,
- loss_cumulative: cumulative_pnl < 0 ? cumulative_pnl : 0,
- unit: "single",
- };
- });
-
- let result;
-
- if (data && data.length > 0) {
- const maxProfit = maxBy(data, item => item.profit_cumulative)?.profit_cumulative || 0;
- const maxLoss = minBy(data, item => item.loss_cumulative)?.loss_cumulative || 0;
- const maxPnl = maxBy(data, item => item.total_pnl)?.total_pnl || 0;
- const minPnl = minBy(data, item => item.total_pnl)?.total_pnl || 0;
- const maxCurrentCumulativePnl = maxBy(data, item => item.cumulative_pnl)?.cumulative_pnl || 0;
- const minCurrentCumulativePnl = minBy(data, item => item.cumulative_pnl)?.cumulative_pnl || 0;
-
- const stats = {
- maxProfit,
- maxLoss,
- currentProfitCumulative,
- currentLossCumulative,
- maxCurrentCumulativeProfitLoss: Math.max(currentProfitCumulative, -currentLossCumulative),
- minAbsPnl: minPnl,
- maxAbsPnl: Math.max(
- Math.abs(maxPnl),
- Math.abs(minPnl),
- ),
- maxAbsCumulativePnl: Math.max(
- Math.abs(maxCurrentCumulativePnl),
- Math.abs(minCurrentCumulativePnl)
- ),
- }
- result = {
- data,
- stats,
- };
- }
- return result;
- };
-
-
- useEffect(() => {
- if (dataHLPLiquidatorPNL.length > 0 && dataCumulativeHLPLiquidatorPNL.length > 0 && dataNonLiquidator.length > 0 && dataCumulativeNonLiquidatorPNL.length > 0) {
- const formattedTradingData = formatTradingData(dataHLPLiquidatorPNL, dataCumulativeHLPLiquidatorPNL);
- const formattedDataNonLiquidator = formatTradingData(dataNonLiquidator, dataCumulativeNonLiquidatorPNL);
- setChartDataLiquidator(formattedTradingData);
- setChartDataNonLiquidator(formattedDataNonLiquidator);
- }
- }, [dataHLPLiquidatorPNL, dataCumulativeHLPLiquidatorPNL, dataNonLiquidator, dataCumulativeNonLiquidatorPNL]);
-
-
- const controls = {
- toggles: [
- {
- text: "HLP",
- event: () => setDataMode('HLP'),
- active: dataMode === 'HLP',
- },
- {
- text: "Liquidator",
- event: () => setDataMode('NON_HLP'),
- active: dataMode === 'NON_HLP',
- }
- ]
- }
-
- const chartInfo = dataMode === 'HLP' ? chartDataLiquidator : chartDataNonLiquidator;
- const chartData = chartInfo ? chartInfo.data : [];
- const domainYRight = [-chartInfo?.stats.maxAbsCumulativePnl * 1.10, chartInfo?.stats.maxAbsCumulativePnl * 1.10]
- const domainYLeft = [-chartInfo?.stats.maxAbsPnl * 1.10, chartInfo?.stats.maxAbsPnl * 1.10]
-
- return (
-
-
-
-
-
-
-
- ''}
- contentStyle={{
- textAlign: 'left',
- background: "#0A1F1B",
- borderColor: "#061412",
- boxShadow: "0px 0px 7px rgb(0 0 0 / 20%)",
- borderRadius: "26px",
- maxHeight: "500px"
- }}
- itemSorter={(item) => {
- return Number(item.value) * -1;
- }}
- />
-
-
- {(chartData || []).map((item: any, i: number) => {
- return 0 ? GREEN : RED} />
- })}
- maxBarSize={20}
- |
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/components/home/charts/hlp.tsx b/components/home/charts/hlp.tsx
new file mode 100644
index 0000000..e0b338c
--- /dev/null
+++ b/components/home/charts/hlp.tsx
@@ -0,0 +1,362 @@
+import {
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+ ResponsiveContainer,
+ Bar,
+ Cell,
+ Line,
+ ComposedChart,
+} from 'recharts';
+import { Box, Text } from '@chakra-ui/react';
+import { useEffect, useState } from 'react';
+import { useRequest } from '@/hooks/useRequest';
+
+import ChartWrapper from '../../common/chartWrapper';
+import { BLUE, BRIGHT_GREEN, CHART_HEIGHT, GREEN, ORANGE, RED } from '../../../constants';
+import {
+ tooltipFormatterDate,
+ tooltipFormatterCurrency,
+ xAxisFormatter,
+ yaxisFormatter,
+ tooltipFormatterLongShort,
+} from '../../../helpers';
+
+import { getTokenColor } from '@/constants/tokens';
+import { asset_ctxs, hlp_liquidator_pnl, hlp_positions } from '@/constants/api';
+const REQUESTS = [hlp_positions, asset_ctxs, hlp_liquidator_pnl];
+
+export default function Hlp() {
+ const [dataHlpPositions, loadingDataHlpPositions, errorDataHlpPositions] = useRequest(
+ REQUESTS[0],
+ [],
+ 'chart_data'
+ );
+ const [assetCtxs, loadingAssetCtxs, errorAssetCtxs] = useRequest(REQUESTS[1], [], 'chart_data');
+ const [dataHlpPnL, loadingHlpLiquidatorPNL, errorHlpLiquidatorPNL] = useRequest(
+ REQUESTS[2],
+ [],
+ 'chart_data'
+ );
+ const [dataMode, setDataMode] = useState<'COINS' | 'NET' | 'PNL' | 'HEDGED'>('PNL');
+ const [coins, setCoins] = useState([]);
+
+ const [formattedHlpPnL, setFormattedHlpPnL] = useState([]);
+ const [formattedData, setFormattedData] = useState([]);
+
+ const loading = loadingAssetCtxs | loadingDataHlpPositions | loadingHlpLiquidatorPNL;
+ const error = errorAssetCtxs | errorDataHlpPositions | errorHlpLiquidatorPNL;
+
+ type HlpPosition = {
+ time: string;
+ coin: string;
+ daily_ntl: number;
+ daily_ntl_abs: number;
+ };
+
+ type AssetCtx = {
+ time: string;
+ coin: string;
+ avg_oracle_px: number;
+ first_oracle_px: number;
+ last_oracle_px: number;
+ avg_open_interest: number;
+ };
+
+ type HlpPnl = {
+ time: Date;
+ pnl: number;
+ cumulativePnl: number;
+ };
+
+ type GroupedData = {
+ time: Date;
+ daily_ntl: number;
+ [coin: string]: any;
+ hedged_pnl: number;
+ };
+
+ const getOraclePxs = (assetCtxs: AssetCtx[]): {firstOraclePxs: Map, lastOraclePxs: Map} => {
+ const firstOraclePxs = new Map();
+ const lastOraclePxs = new Map();
+
+ assetCtxs.forEach((item) => {
+ firstOraclePxs.set(item.coin + item.time, item.first_oracle_px);
+ lastOraclePxs.set(item.coin + item.time, item.last_oracle_px)
+ });
+ return {firstOraclePxs, lastOraclePxs};
+ };
+
+ const makeHlpPnl = (
+ dataHlpPnL: {
+ time: string;
+ total_pnl: number;
+ }[]
+ ): Map => {
+ const map = new Map();
+ let cumulativePnl = 0;
+ dataHlpPnL.forEach((item) => {
+ cumulativePnl += item.total_pnl;
+ map.set(item.time, {
+ time: new Date(item.time),
+ pnl: item.total_pnl,
+ cumulativePnl,
+ });
+ });
+ return map;
+ };
+
+ function getNextTime(time: string) {
+ const date = new Date(time);
+ date.setDate(date.getDate() + 1);
+
+ var dd = String(date.getDate()).padStart(2, '0');
+ var mm = String(date.getMonth() + 1).padStart(2, '0'); //January is 0!
+ var yyyy = date.getFullYear();
+
+ return yyyy + '-' + mm + '-' + dd + 'T00:00:00';
+ }
+
+ const makeFormattedData = (
+ hlpPositions: HlpPosition[],
+ hlpPnL: Map
+ ): [GroupedData[], string[]] => {
+ const map = new Map();
+ const uniqueTopCoins = new Set();
+
+ let hedgedCumulativePnl = 0;
+ const {firstOraclePxs, lastOraclePxs} = getOraclePxs(assetCtxs);
+
+ hlpPositions.forEach((item: HlpPosition) => {
+ let { time, coin, daily_ntl } = item;
+ if (!map.has(time)) {
+ const pnl = hlpPnL.get(time)?.pnl || 0;
+ hedgedCumulativePnl += pnl;
+ map.set(time, {
+ time: new Date(time),
+ daily_ntl: 0,
+ hedged_pnl: pnl,
+ hedged_cumulative_pnl: hedgedCumulativePnl,
+ Other: 0,
+ });
+ }
+
+ const existingEntry = map.get(time)!;
+ existingEntry[`${coin}`] = (existingEntry[`${coin}`] || 0) + daily_ntl;
+ existingEntry.daily_ntl += daily_ntl;
+
+ const firstOraclePx = firstOraclePxs.get(coin + time);
+ let hedgedPnl = 0;
+ let lastOraclePx = lastOraclePxs.get(coin + time);
+ let curDayData = map.get(time);
+ let coinNtlPosition = curDayData ? curDayData[`${coin}`] : null;
+ if (lastOraclePx && firstOraclePx && coinNtlPosition) {
+ const pxChange = 1 - firstOraclePx / lastOraclePx;
+ const pnl = -1 * coinNtlPosition * pxChange;
+ hedgedPnl += pnl;
+ }
+
+ existingEntry.hedged_pnl += hedgedPnl;
+ hedgedCumulativePnl += hedgedPnl;
+ existingEntry.hedged_cumulative_pnl = hedgedCumulativePnl;
+ });
+
+ map.forEach((entry) => {
+ const coinEntries = Object.entries(entry).filter(
+ ([key]) =>
+ key !== 'time' &&
+ key !== 'daily_ntl' &&
+ key !== 'hedged_pnl' &&
+ key !== 'other' &&
+ key !== 'hedged_cumulative_pnl'
+ );
+ const sortedCoinEntries = coinEntries.sort(
+ (a, b) => Math.abs(Number(b[1])) - Math.abs(Number(a[1]))
+ );
+ const topCoins = sortedCoinEntries.slice(0, 10).map(([coin]) => coin);
+ const otherCoins = sortedCoinEntries.slice(10);
+
+ topCoins.forEach((coin) => uniqueTopCoins.add(coin));
+
+ let otherTotal = 0;
+ otherCoins.forEach(([coin, value]) => {
+ otherTotal += value;
+ delete entry[coin];
+ });
+ entry.Other = otherTotal;
+ });
+
+ const result = Array.from(map.values());
+ uniqueTopCoins.add('Other');
+ return [result, Array.from(uniqueTopCoins)];
+ };
+
+ const controls = {
+ toggles: [
+ {
+ text: 'PnL',
+ event: () => setDataMode('PNL'),
+ active: dataMode === 'PNL',
+ },
+ {
+ text: 'Hedged PnL',
+ event: () => setDataMode('HEDGED'),
+ active: dataMode === 'HEDGED',
+ },
+ {
+ text: 'Net position',
+ event: () => setDataMode('NET'),
+ active: dataMode === 'NET',
+ },
+ {
+ text: 'Coin positions',
+ event: () => setDataMode('COINS'),
+ active: dataMode === 'COINS',
+ },
+ ],
+ };
+
+ const formatData = () => {
+ if (dataHlpPositions && assetCtxs && dataHlpPnL) {
+ const newHlpPnL = makeHlpPnl(dataHlpPnL);
+ setFormattedHlpPnL(Array.from(newHlpPnL.values()));
+ const [groupedData, coins] = makeFormattedData(dataHlpPositions, newHlpPnL);
+ setCoins(coins);
+ setFormattedData(groupedData);
+ }
+ };
+
+ useEffect(() => {
+ if (!loading && !error) {
+ formatData();
+ }
+ }, [loading, error]);
+
+ return (
+
+
+
+
+
+
+ {
+ return Math.abs(Number(item.value)) * -1;
+ }}
+ />
+ {dataMode === 'NET' && (
+
+ {(formattedData || []).map((item: GroupedData, i: number) => {
+ return 0 ? BLUE : ORANGE} />;
+ })}
+ |
+ )}
+ {dataMode === 'COINS' &&
+ coins.map((coin, i) => {
+ return (
+
+ );
+ })}
+ {dataMode === 'HEDGED' && (
+ <>
+
+
+ {(formattedData || []).map((item: GroupedData, i: number) => {
+ return 0 ? GREEN : RED} />;
+ })}
+ |
+ >
+ )}
+ {dataMode === 'PNL' && (
+ <>
+
+
+ {formattedHlpPnL.map((item: HlpPnl, i: number) => {
+ return 0 ? GREEN : RED} />;
+ })}
+ |
+ >
+ )}
+
+
+
+
+ {dataMode === 'HEDGED' && (
+
+ Hedge each day's returns by holding the previous day's ending positions for
+ each asset.
+
+ )}
+
+
+
+ {dataMode === 'NET' && Net notional position over time}
+
+
+ );
+}
diff --git a/components/home/charts/liquidator.tsx b/components/home/charts/liquidator.tsx
new file mode 100644
index 0000000..2933bc9
--- /dev/null
+++ b/components/home/charts/liquidator.tsx
@@ -0,0 +1,370 @@
+import {
+ Bar,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+ ResponsiveContainer,
+ ComposedChart,
+ Line,
+} from 'recharts';
+import { useEffect, useState } from 'react';
+import { useRequest } from '@/hooks/useRequest';
+import ChartWrapper from '../../common/chartWrapper';
+import {
+ CHART_HEIGHT,
+ YAXIS_WIDTH,
+ BRIGHT_GREEN,
+ BRAND_GREEN_2,
+ BRAND_GREEN_3,
+} from '../../../constants';
+import {
+ tooltipLabelFormatter,
+ yaxisFormatter,
+ xAxisFormatter,
+ tooltipFormatterCurrency,
+ tooltipFormatterDate,
+} from '../../../helpers';
+import { createCoinSelectors } from '../../../helpers/utils';
+
+import { getTokenColor, initialTokensSelectedWithOther } from '../../../constants/tokens';
+import {
+ cumulative_liquidated_notional,
+ daily_notional_liquidated_total,
+ daily_notional_liquidated_by_leverage_type,
+ daily_notional_liquidated_by_coin,
+ hlp_liquidator_pnl_false,
+ cumulative_hlp_liquidator_pnl_false,
+} from '../../../constants/api';
+
+const REQUESTS = [
+ cumulative_liquidated_notional,
+ daily_notional_liquidated_total,
+ daily_notional_liquidated_by_leverage_type,
+ daily_notional_liquidated_by_coin,
+ hlp_liquidator_pnl_false,
+ cumulative_hlp_liquidator_pnl_false,
+];
+
+export default function LiquidatorChart(props: any) {
+ const isMobile = props.isMobile;
+
+ const [dataMode, setDataMode] = useState<'COINS' | 'MARGIN'>('COINS');
+ const [coinsSelected, setCoinsSelected] = useState(initialTokensSelectedWithOther);
+ const [formattedDataCoins, setFormattedDataCoins] = useState([]);
+ const [formattedDataMargin, setFormattedDataMargin] = useState([]);
+
+ const [coinKeys, setCoinKeys] = useState([]);
+
+ const [dataCumulativeLiquidated, loadingCumulativeLiquidated, errorCumulativeLiquidated] =
+ useRequest(REQUESTS[0], [], 'chart_data');
+ const [dataDailyLiquidatedTotal, loadingDailyLiquidatedTotal, errorDailyUsdVolumeTotal] =
+ useRequest(REQUESTS[1], [], 'chart_data');
+ const [
+ dataDailyLiquidatedByMargin,
+ loadingDailyLiquidatedByMargin,
+ errorDailyLiquidatedByMargin,
+ ] = useRequest(REQUESTS[2], [], 'chart_data');
+ const [dataDailyLiquidatedByCoins, loadingDailyLiquidatedByCoins, errorDailyLiquidatedByCoins] =
+ useRequest(REQUESTS[3], [], 'chart_data');
+ const [
+ dataLiquidatorCumulativePnl,
+ loadingLiquidatorCumulativePnl,
+ errorLiquidatorCumulativePnl,
+ ] = useRequest(REQUESTS[5], [], 'chart_data');
+ const [formattedLiquidatorPnl, setFormattedLiquidatorPnl] = useState([]);
+
+ type LiquidatorPnl = {
+ time: Date;
+ pnl: number;
+ cumulativePnl: number;
+ };
+
+ const loading =
+ loadingCumulativeLiquidated ||
+ loadingDailyLiquidatedTotal ||
+ loadingDailyLiquidatedByMargin ||
+ loadingDailyLiquidatedByCoins ||
+ loadingLiquidatorCumulativePnl;
+ const error =
+ errorCumulativeLiquidated ||
+ errorDailyUsdVolumeTotal ||
+ errorDailyLiquidatedByMargin ||
+ errorDailyLiquidatedByCoins ||
+ errorLiquidatorCumulativePnl;
+ type CumulativeLiquidationData = { cumulative: number; time: string };
+
+ const formatCumulativeLiquidatedByTime = (
+ dataCumulativeLiquidated: CumulativeLiquidationData[]
+ ): { [key: string]: number } => {
+ const result: { [key: string]: number } = {};
+ for (const data of dataCumulativeLiquidated) {
+ result[data.time] = data.cumulative;
+ }
+ return result;
+ };
+
+ type LiquidationData = {
+ time: string;
+ leverage_type: 'Cross' | 'Isolated';
+ daily_notional_liquidated: number;
+ };
+
+ const formatLiquidatedByMargin = (
+ dataDailyLiquidatedByMargin: LiquidationData[],
+ formattedCumulativeLiquidatedByTime: { [key: string]: number }
+ ): any[] => {
+ const temp: { [key: string]: any } = {};
+ for (const data of dataDailyLiquidatedByMargin) {
+ if (!temp[data.time]) {
+ temp[data.time] = { all: 0 };
+ }
+ temp[data.time][data.leverage_type] = data.daily_notional_liquidated;
+ temp[data.time].all += data.daily_notional_liquidated;
+ }
+ const result: any[] = Object.entries(temp).map((item: any) => {
+ return {
+ time: new Date(item[0]),
+ crossed: item[1].hasOwnProperty('Cross') ? item[1].Cross : 0,
+ isolated: item[1].hasOwnProperty('Isolated') ? item[1].Isolated : 0,
+ all: item[1].all,
+ cumulative: formattedCumulativeLiquidatedByTime[item[0]],
+ };
+ });
+ return result;
+ };
+
+ type FormattedCoinTradesData = any[];
+
+ const formatDailyTradesByCoins = (
+ CoinsSelected: string[],
+ dataDailyTradesByCoin: { time: string; coin: string; daily_notional_liquidated: number }[],
+ formattedCumulativeByTime: { [key: string]: number }
+ ): FormattedCoinTradesData[] => {
+ const temp: { [key: string]: { all: number; [coin: string]: number } } = {};
+ for (const data of dataDailyTradesByCoin) {
+ if (!temp[data.time]) {
+ temp[data.time] = { all: 0 };
+ }
+ temp[data.time][data.coin] = data.daily_notional_liquidated;
+ temp[data.time].all += data.daily_notional_liquidated;
+ }
+
+ const selectedCoinData = (obj: { [coin: string]: number }) => {
+ const selectedEntries = Object.entries(obj).filter(
+ ([coin]) => CoinsSelected.includes(coin) || coin === 'all'
+ );
+ const otherEntries = Object.entries(obj).filter(
+ ([coin]) => !CoinsSelected.includes(coin) && coin !== 'all'
+ );
+ const otherVolume = otherEntries.reduce((total, [, volume]) => total + volume, 0);
+ return {
+ ...Object.fromEntries(selectedEntries),
+ Other: otherVolume,
+ };
+ };
+
+ const result: any[] = Object.entries(temp).map(([time, volumes]) => {
+ const selectedVolumes = selectedCoinData(volumes);
+ return {
+ time: new Date(time),
+ ...selectedVolumes,
+ cumulative: formattedCumulativeByTime[time as any],
+ unit: '',
+ };
+ });
+ return result;
+ };
+
+ const extractUniqueCoins = (coinData: any[]): string[] => {
+ const coinSet = new Set();
+ for (const data of coinData) {
+ if (
+ data.coin !== 'time' &&
+ data.coin !== 'unit' &&
+ data.coin !== 'cumulative' &&
+ data.coin !== 'all'
+ ) {
+ coinSet.add(data.coin);
+ }
+ }
+ return Array.from(coinSet);
+ };
+
+ const formatData = (CoinsSelected: string[]) => {
+ const formattedCumulativeLiquidatedByTime =
+ formatCumulativeLiquidatedByTime(dataCumulativeLiquidated);
+ const formattedVolumeByMargin = formatLiquidatedByMargin(
+ dataDailyLiquidatedByMargin,
+ formattedCumulativeLiquidatedByTime
+ );
+ const formattedDailyTradesByCoins = formatDailyTradesByCoins(
+ CoinsSelected,
+ dataDailyLiquidatedByCoins,
+ formattedCumulativeLiquidatedByTime
+ );
+ setCoinKeys(extractUniqueCoins(dataDailyLiquidatedByCoins));
+ setFormattedDataMargin(formattedVolumeByMargin);
+ setFormattedDataCoins(formattedDailyTradesByCoins);
+ console.log('dev formattedDailyTradesByCoins', formattedDailyTradesByCoins);
+ };
+
+ const controls = {
+ toggles: [
+ {
+ text: 'By coin',
+ event: () => setDataMode('COINS'),
+ active: dataMode === 'COINS',
+ },
+ {
+ text: 'By margin type',
+ event: () => setDataMode('MARGIN'),
+ active: dataMode === 'MARGIN',
+ },
+ ],
+ };
+
+ useEffect(() => {
+ if (!loading && !error) {
+ formatData(coinsSelected);
+ }
+ }, [loading, error]);
+
+ const dataModeToData = (dataMode: string) => {
+ switch (dataMode) {
+ case 'COINS':
+ return formattedDataCoins;
+ case 'MARGIN':
+ return formattedDataMargin;
+ case 'PNL':
+ return formattedLiquidatorPnl;
+ }
+ };
+
+ const pnlYDomain = () => {
+ let maxPnl = formattedLiquidatorPnl.reduce((max, curr) => {
+ return Math.abs(max.pnl) > Math.abs(curr.pnl) ? max : curr;
+ }).pnl;
+ return [-1 * Math.abs(maxPnl) * 1.1, Math.abs(maxPnl) * 1.1];
+ };
+
+ const cumulativePnlYDomain = () => {
+ let maxCumulativePnl = formattedLiquidatorPnl.reduce((max, curr) => {
+ return Math.abs(max.cumulativePnl) > Math.abs(curr.cumulativePnl) ? max : curr;
+ }).cumulativePnl;
+
+ return [-1 * Math.abs(maxCumulativePnl) * 1.1, Math.abs(maxCumulativePnl) * 1.1];
+ };
+
+ const coinSelectors = createCoinSelectors(coinKeys, coinsSelected, setCoinsSelected, formatData);
+
+ return (
+
+
+
+
+
+ {
+ return Math.abs(Number(item.value));
+ }}
+ />
+
+ {dataMode === 'COINS' && (
+ <>
+ {coinsSelected.map((coinName, i) => {
+ return (
+
+ );
+ })}
+ >
+ )}
+ {dataMode === 'MARGIN' && (
+ <>
+
+
+ >
+ )}
+
+
+
+
+
+
+ );
+}
diff --git a/components/home/charts/liquidity.tsx b/components/home/charts/liquidity.tsx
index 4ab0b4a..60a7abd 100644
--- a/components/home/charts/liquidity.tsx
+++ b/components/home/charts/liquidity.tsx
@@ -1,323 +1,326 @@
import {
- Bar,
- XAxis,
- YAxis,
- CartesianGrid,
- Tooltip,
- Legend,
- ResponsiveContainer,
- ComposedChart,
- Line,
- LineChart
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+ ResponsiveContainer,
+ Line,
+ LineChart,
} from 'recharts';
import { useEffect, useState } from 'react';
-import { Box, Text, useMediaQuery } from "@chakra-ui/react"
import { useRequest } from '@/hooks/useRequest';
import ChartWrapper from '../../common/chartWrapper';
+import { CHART_HEIGHT } from '../../../constants';
import {
- CHART_HEIGHT,
-} from "../../../constants";
-import {
- tooltipFormatter,
- tooltipLabelFormatter,
- xAxisFormatter,
- formatterPercent,
-
-} from '../../../helpers'
-import { getTokenHex } from "../../../constants/tokens";
-import {
- liquidity_by_coin,
-} from "../../../constants/api"
-
-type DailyUniqueUsersByCoin = {
- time: string;
- coin: string;
- daily_unique_users: number;
- percentage_of_total_users: number;
- all: number;
-};
-
-type UniqueUserTradeData = {
- time: string;
- daily_unique_users: number;
-};
-
-type CumulativeNewUsersData = {
- time: string;
- daily_new_users: number;
- cumulative_new_users: number;
-};
-
-type GroupedTradeData = {
- time: Date;
- all: number;
- daily_unique_users: number;
- cumulative_unique_users: number;
- unit: string;
- [key: string]: number | Date | { [key: string]: number } | string | undefined;
-}
-
-type TempGroupedTradeData = {
- time: Date;
- coins: { [key: string]: number };
- all: number;
- daily_unique_users: number;
- cumulative_unique_users: number;
- unit: string;
- [key: string]: number | Date | { [key: string]: number } | string | undefined;
-}
-
-const REQUESTS = [
- liquidity_by_coin
-];
-
-export default function CumulativeUsers() {
- const [isMobile] = useMediaQuery('(max-width: 700px)');
-
- const [formattedData0, setFormattedData0] = useState([])
- const [formattedData1000, setFormattedData1000] = useState([])
- const [formattedData3000, setFormattedData3000] = useState([])
- const [formattedData10000, setFormattedData10000] = useState([])
- const [minMax, setMinMax] = useState()
-
- const [coinKeys0, setCoinKeys0] = useState([])
- const [coinKeys1000, setCoinKeys1000] = useState([])
- const [coinKeys3000, setCoinKeys3000] = useState([])
- const [coinKeys10000, setCoinKeys10000] = useState([])
-
-
- const [dataMode, setDataMode] = useState<'0' | '1000' | '3000' | '10000'>('0')
-
- const [dataLiqudity, loadingLiqudity, errorLiqudity] = useRequest(REQUESTS[0], [], 'chart_data');
- const loading = loadingLiqudity;
- const error = errorLiqudity;
-
- const controls = {
- toggles: [
- {
- text: "Half Spread",
- event: () => setDataMode('0'),
- active: dataMode === '0',
- },
- {
- text: "$1k",
- event: () => setDataMode('1000'),
- active: dataMode === '1000',
- },
- {
- text: "$3k",
- event: () => setDataMode('3000'),
- active: dataMode === '3000',
- },
- {
- text: "$10k",
- event: () => setDataMode('10000'),
- active: dataMode === '10000',
- }
- ]
+ tooltipFormatter,
+ xAxisFormatter,
+ formatterPercent,
+ tooltipFormatterDate,
+} from '../../../helpers';
+import { createCoinSelectors } from '../../../helpers/utils';
+
+import { getTokenColor, initialTokensSelected } from '../../../constants/tokens';
+import { liquidity_by_coin } from '../../../constants/api';
+import { Box, Text } from '@chakra-ui/react';
+
+const REQUESTS = [liquidity_by_coin];
+
+export default function Liquidity() {
+ const [formattedData0, setFormattedData0] = useState([]);
+ const [formattedData1000, setFormattedData1000] = useState([]);
+ const [formattedData10000, setFormattedData10000] = useState([]);
+ const [formattedData30000, setFormattedData30000] = useState([]);
+ const [formattedData100000, setFormattedData100000] = useState([]);
+
+ const [coinKeys, setCoinKeys] = useState([]);
+ const [coinKeys0, setCoinKeys0] = useState([]);
+ const [coinKeys1000, setCoinKeys1000] = useState([]);
+ const [coinKeys10000, setCoinKeys10000] = useState([]);
+ const [coinKeys30000, setCoinKeys30000] = useState([]);
+ const [coinKeys100000, setCoinKeys100000] = useState([]);
+
+ const [dataMode, setDataMode] = useState<'0' | '1000' | '10000' | '30000' | '100000'>('0');
+ const [coinsSelected, setCoinsSelected] = useState(initialTokensSelected);
+
+ const [dataLiqudity, loadingLiqudity, errorLiqudity] = useRequest(REQUESTS[0], [], 'chart_data');
+ const loading = loadingLiqudity;
+ const error = errorLiqudity;
+
+ const controls = {
+ toggles: [
+ {
+ text: 'Half Spread',
+ event: () => setDataMode('0'),
+ active: dataMode === '0',
+ },
+ {
+ text: '$1k',
+ event: () => setDataMode('1000'),
+ active: dataMode === '1000',
+ },
+ {
+ text: '$10k',
+ event: () => setDataMode('10000'),
+ active: dataMode === '10000',
+ },
+ {
+ text: '$30k',
+ event: () => setDataMode('30000'),
+ active: dataMode === '30000',
+ },
+ {
+ text: '$100k',
+ event: () => setDataMode('100000'),
+ active: dataMode === '100000',
+ },
+ ],
+ };
+
+ type InputData = {
+ [key: string]: {
+ median_slippage_0: number;
+ median_slippage_1000: number;
+ median_slippage_10000: number;
+ median_slippage_30000: number;
+ median_slippage_100000: number;
+ time: string;
+ }[];
+ };
+
+ type OutputData = {
+ median_slippage_0: { time: Date; [key: string]: number | Date | string }[];
+ median_slippage_1000: { time: Date; [key: string]: number | Date | string }[];
+ median_slippage_10000: { time: Date; [key: string]: number | Date | string }[];
+ median_slippage_30000: { time: Date; [key: string]: number | Date | string }[];
+ median_slippage_100000: { time: Date; [key: string]: number | Date | string }[];
+ };
+
+ const extractCoins = (data: InputData): string[] => {
+ let coins = [];
+ for (let coin of Object.keys(data)) {
+ coins.push(coin);
+ }
+ return coins;
+ };
+
+ const transformData = (data: InputData): OutputData => {
+ // Filter data for each category by top 10 coins
+ const filteredData: InputData = {};
+ for (let coin of coinsSelected) {
+ if (coinsSelected.includes(coin)) {
+ filteredData[coin] = data[coin];
+ }
}
- type InputData = {
- [key: string]: {
- median_slippage_0: number,
- median_slippage_1000: number,
- median_slippage_3000: number,
- median_slippage_10000: number,
- time: string
- }[]
- };
-
- type OutputData = {
- median_slippage_0: { time: Date, [key: string]: number | Date | string }[],
- median_slippage_1000: { time: Date, [key: string]: number | Date | string }[],
- median_slippage_3000: { time: Date, [key: string]: number | Date | string }[],
- median_slippage_10000: { time: Date, [key: string]: number | Date | string }[],
- };
-
- const transformData = (data: InputData): OutputData => {
- const coinTotals = new Map();
-
- const minMax: { min: number; max: number } = { min: Infinity, max: -Infinity };
-
- // Compute overall totals for each coin
- for (let key in data) {
- data[key].forEach((record) => {
- coinTotals.set(key, (coinTotals.get(key) || 0) + record.median_slippage_1000 + record.median_slippage_3000 + record.median_slippage_10000);
- });
- }
-
- // Get the top 10 coins by total over the whole time period
- const topCoins = Array.from(coinTotals.entries())
- .sort((a, b) => b[1] - a[1])
- .slice(0, 10)
- .map(([coin]) => coin);
-
- // Filter data for each category by top 10 coins
- const filteredData: InputData = {};
- for (let coin of topCoins) {
- filteredData[coin] = data[coin];
- }
-
- const median_slippage_0 = new Map();
- const median_slippage_1000 = new Map();
- const median_slippage_3000 = new Map();
- const median_slippage_10000 = new Map();
-
- for (let key in filteredData) {
- filteredData[key].forEach((record) => {
- const { time, median_slippage_0: val_0, median_slippage_1000: val_1000, median_slippage_3000: val_3000, median_slippage_10000: val_10000 } = record;
-
- const map0 = median_slippage_0.get(time) || { time: new Date(time), unit: "%" };
- const map1000 = median_slippage_1000.get(time) || { time: new Date(time), unit: "%" };
- const map3000 = median_slippage_3000.get(time) || { time: new Date(time), unit: "%" };
- const map10000 = median_slippage_10000.get(time) || { time: new Date(time), unit: "%" };
-
- map0[key] = val_0 * 100;
- map1000[key] = val_1000 * 100;
- map3000[key] = val_3000 * 100;
- map10000[key] = val_10000 * 100;
-
- median_slippage_0.set(time, map0);
- median_slippage_1000.set(time, map1000);
- median_slippage_3000.set(time, map3000);
- median_slippage_10000.set(time, map10000);
- });
- }
-
- return {
- median_slippage_0: Array.from(median_slippage_0.values()),
- median_slippage_1000: Array.from(median_slippage_1000.values()),
- median_slippage_3000: Array.from(median_slippage_3000.values()),
- median_slippage_10000: Array.from(median_slippage_10000.values())
- };
- };
-
- type MinMaxValues = {
- min: number;
- max: number;
- };
-
- const getMinMaxValues = (data: any): MinMaxValues => {
- let min = Infinity;
- let max = -Infinity;
- for (let prop in data) {
- if (Object.prototype.hasOwnProperty.call(data, prop)) {
- const propData = data[prop];
- propData.forEach((item: any) => {
- for (let key in item) {
- if (key !== 'time' && typeof item[key] === 'number') {
- const value = item[key] as number;
- min = Math.min(min, value);
- max = Math.max(max, value);
- }
- }
- });
- }
- }
- return { min, max };
- };
-
+ const median_slippage_0 = new Map<
+ string,
+ { time: Date; [key: string]: number | Date | string }
+ >();
+ const median_slippage_1000 = new Map<
+ string,
+ { time: Date; [key: string]: number | Date | string }
+ >();
+ const median_slippage_10000 = new Map<
+ string,
+ { time: Date; [key: string]: number | Date | string }
+ >();
+ const median_slippage_30000 = new Map<
+ string,
+ { time: Date; [key: string]: number | Date | string }
+ >();
+ const median_slippage_100000 = new Map<
+ string,
+ { time: Date; [key: string]: number | Date | string }
+ >();
+
+ for (let key in filteredData) {
+ if (!filteredData[key]) {
+ continue;
+ }
+ filteredData[key].forEach((record) => {
+ const {
+ time,
+ median_slippage_0: val_0,
+ median_slippage_1000: val_1000,
+ median_slippage_10000: val_10000,
+ median_slippage_30000: val_30000,
+ median_slippage_100000: val_100000,
+ } = record;
+
+ const map0 = median_slippage_0.get(time) || { time: new Date(time), unit: '%' };
+ const map1000 = median_slippage_1000.get(time) || { time: new Date(time), unit: '%' };
+ const map10000 = median_slippage_10000.get(time) || { time: new Date(time), unit: '%' };
+ const map30000 = median_slippage_30000.get(time) || { time: new Date(time), unit: '%' };
+ const map100000 = median_slippage_100000.get(time) || { time: new Date(time), unit: '%' };
+
+ map0[key] = val_0 * 100;
+ map1000[key] = val_1000 * 100;
+ map10000[key] = val_10000 * 100;
+ map30000[key] = val_30000 * 100;
+ map100000[key] = val_100000 * 100;
+
+ median_slippage_0.set(time, map0);
+ median_slippage_1000.set(time, map1000);
+ median_slippage_10000.set(time, map10000);
+ median_slippage_30000.set(time, map30000);
+ median_slippage_100000.set(time, map100000);
+ });
+ }
- const extractUniqueCoins = (data: OutputData['median_slippage_1000'] | OutputData['median_slippage_10000'] | OutputData['median_slippage_1000']): string[] => {
- const coinSet = new Set();
- data.forEach(record => {
- Object.keys(record).forEach(key => {
- if (key !== 'time' && key !== 'unit') {
- coinSet.add(key);
- }
- });
- });
- return Array.from(coinSet);
+ const sortByDate = (a: Date, b: Date) => {
+ return a.valueOf() - b.valueOf();
};
- const formatData = () => {
- const formattedData = transformData(dataLiqudity);
- setFormattedData0(formattedData.median_slippage_0)
- setFormattedData1000(formattedData.median_slippage_1000)
- setFormattedData3000(formattedData.median_slippage_3000)
- setFormattedData10000(formattedData.median_slippage_10000)
- const formattedUniqueCoinKeys0 = extractUniqueCoins(formattedData.median_slippage_0)
- const formattedUniqueCoinKeys1000 = extractUniqueCoins(formattedData.median_slippage_1000)
- const formattedUniqueCoinKeys3000 = extractUniqueCoins(formattedData.median_slippage_3000)
- const formattedUniqueCoinKeys10000 = extractUniqueCoins(formattedData.median_slippage_10000)
- const minMaxValues = getMinMaxValues(formattedData);
- setMinMax(minMaxValues);
- setCoinKeys0(formattedUniqueCoinKeys0)
- setCoinKeys1000(formattedUniqueCoinKeys1000)
- setCoinKeys3000(formattedUniqueCoinKeys3000)
- setCoinKeys10000(formattedUniqueCoinKeys10000)
+ return {
+ median_slippage_0: Array.from(median_slippage_0.values()).sort((a, b) =>
+ sortByDate(a.time, b.time)
+ ),
+ median_slippage_1000: Array.from(median_slippage_1000.values()).sort((a, b) =>
+ sortByDate(a.time, b.time)
+ ),
+ median_slippage_10000: Array.from(median_slippage_10000.values()).sort((a, b) =>
+ sortByDate(a.time, b.time)
+ ),
+ median_slippage_30000: Array.from(median_slippage_30000.values()).sort((a, b) =>
+ sortByDate(a.time, b.time)
+ ),
+ median_slippage_100000: Array.from(median_slippage_100000.values()).sort((a, b) =>
+ sortByDate(a.time, b.time)
+ ),
};
-
- useEffect(() => {
- if (!loading && !error) {
- formatData();
+ };
+
+ const extractUniqueCoins = (
+ data: OutputData['median_slippage_1000'] | OutputData['median_slippage_10000']
+ ): string[] => {
+ const coinSet = new Set();
+ data.forEach((record) => {
+ Object.keys(record).forEach((key) => {
+ if (key !== 'time' && key !== 'unit') {
+ coinSet.add(key);
}
- }, [loading])
-
- const chartData = dataMode === '0' ? formattedData0 : dataMode === '1000' ? formattedData1000 : dataMode === '3000' ? formattedData3000 : formattedData10000;
-
- const chartDataCoinKeys = dataMode === '0' ? coinKeys0 : dataMode === '1000' ? coinKeys1000 : dataMode === '3000' ? coinKeys3000 : coinKeys10000;
-
- return (
-
-
-
-
-
-
- {
- return Number(item.value) * -1;
- }}
- />
-
- {
- chartDataCoinKeys.map(((coinName, i) => {
- return (
-
- )
- }))
- }
-
-
-
- Top 10 Coins over time
-
-
- )
-}
\ No newline at end of file
+ });
+ });
+ return Array.from(coinSet);
+ };
+
+ const formatData = () => {
+ const extractedCoinKeys = extractCoins(dataLiqudity);
+ setCoinKeys(extractedCoinKeys);
+ const formattedData = transformData(dataLiqudity);
+ setFormattedData0(formattedData.median_slippage_0);
+ setFormattedData1000(formattedData.median_slippage_1000);
+ setFormattedData10000(formattedData.median_slippage_10000);
+ setFormattedData30000(formattedData.median_slippage_30000);
+ setFormattedData100000(formattedData.median_slippage_100000);
+
+ const formattedUniqueCoinKeys0 = extractUniqueCoins(formattedData.median_slippage_0);
+ const formattedUniqueCoinKeys1000 = extractUniqueCoins(formattedData.median_slippage_1000);
+ const formattedUniqueCoinKeys10000 = extractUniqueCoins(formattedData.median_slippage_10000);
+ const formattedUniqueCoinKeys30000 = extractUniqueCoins(formattedData.median_slippage_30000);
+ const formattedUniqueCoinKeys100000 = extractUniqueCoins(formattedData.median_slippage_100000);
+
+ setCoinKeys0(formattedUniqueCoinKeys0);
+ setCoinKeys1000(formattedUniqueCoinKeys1000);
+ setCoinKeys10000(formattedUniqueCoinKeys10000);
+ setCoinKeys30000(formattedUniqueCoinKeys30000);
+ setCoinKeys100000(formattedUniqueCoinKeys100000);
+ };
+
+ useEffect(() => {
+ if (!loading && !error) {
+ formatData();
+ }
+ }, [loading, coinKeys]);
+
+ let chartData;
+ let chartDataCoinKeys;
+ switch (dataMode) {
+ case '0':
+ chartData = formattedData0;
+ chartDataCoinKeys = coinKeys0;
+ break;
+ case '1000':
+ chartData = formattedData1000;
+ chartDataCoinKeys = coinKeys1000;
+ break;
+ case '10000':
+ chartData = formattedData10000;
+ chartDataCoinKeys = coinKeys10000;
+ break;
+ case '30000':
+ chartData = formattedData30000;
+ chartDataCoinKeys = coinKeys30000;
+ break;
+ case '100000':
+ chartData = formattedData100000;
+ chartDataCoinKeys = coinKeys100000;
+ break;
+ }
+
+ const coinSelectors = createCoinSelectors(
+ coinKeys,
+ coinsSelected,
+ setCoinsSelected,
+ formatData,
+ true
+ );
+
+ return (
+
+
+
+
+
+
+ {
+ return Number(item.value);
+ }}
+ />
+
+ {coinsSelected.map((coinName, i) => {
+ return (
+
+ );
+ })}
+
+
+
+ Slippage percentage by trade size.
+
+
+ );
+}
diff --git a/components/home/charts/open-interest.tsx b/components/home/charts/open-interest.tsx
index 9a1a021..9c12323 100644
--- a/components/home/charts/open-interest.tsx
+++ b/components/home/charts/open-interest.tsx
@@ -1,193 +1,171 @@
import {
- Bar,
- Label,
- XAxis,
- YAxis,
- CartesianGrid,
- Tooltip,
- Legend,
- LineChart,
- ResponsiveContainer,
- ComposedChart,
- Line
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+ LineChart,
+ ResponsiveContainer,
+ Line,
} from 'recharts';
import { useEffect, useState } from 'react';
-import { Box, Text, useMediaQuery } from "@chakra-ui/react"
+import { Box, Text } from '@chakra-ui/react';
import { useRequest } from '@/hooks/useRequest';
import ChartWrapper from '../../common/chartWrapper';
+import { BRIGHT_GREEN, CHART_HEIGHT, YAXIS_WIDTH } from '../../../constants';
import {
- CHART_HEIGHT,
- YAXIS_WIDTH,
-} from "../../../constants";
-import {
- xAxisFormatter,
- tooltipLabelFormatter,
- yaxisFormatter,
- tooltipFormatterCurrency,
-} from '../../../helpers'
-import { getTokenHex } from "../../../constants/tokens";
-import {
- open_interest,
-
-} from "../../../constants/api"
-
-type OpenInterestData = { time: string; coin: string; open_interest: number };
-
-type GroupedOpenInterestData = { time: string;[key: string]: number | string };
-
-const REQUESTS = [
- open_interest
-];
-
-export default function VolumeChart() {
- const [isMobile] = useMediaQuery('(max-width: 700px)');
-
- const [coinKeys, setCoinKeys] = useState([])
- const [formattedData, setFormattedData] = useState([])
- const [dataOpenInterest, loadingOpenInterest, errorOpenInterest] = useRequest(REQUESTS[0], [], 'chart_data');
-
- const loading = loadingOpenInterest;
- const error = errorOpenInterest;
-
- type OpenInterestData = { time: string; coin: string; open_interest: number };
- type GroupedOpenInterestData = { time: Date; unit: string; all: number;[key: string]: number | Date | string };
-
- const groupByTime = (data: OpenInterestData[]): GroupedOpenInterestData[] => {
- const map = new Map();
- const totalOpenInterestMap = new Map();
-
- data.forEach((item) => {
- const key = item.time;
- if (!map.has(key)) {
- map.set(key, {
- time: new Date(key),
- Other: 0, // Initialize the 'Other' property
- all: 0, // Initialize the 'all' property
- unit: "$", // Initialize the 'unit' property
- });
- }
- const existingEntry = map.get(key);
- existingEntry[item.coin] = (existingEntry[item.coin] || 0) + item.open_interest;
- existingEntry.all += item.open_interest; // Aggregate total open interest for 'all' property
-
- // Aggregate total open interest for each coin
- totalOpenInterestMap.set(item.coin, (totalOpenInterestMap.get(item.coin) || 0) + item.open_interest);
- });
-
- // Get top 10 coins by total open interest
- const top10Coins = Array.from(totalOpenInterestMap.entries())
- .sort(([, a], [, b]) => b - a)
- .slice(0, 10)
- .map(([coin]) => coin);
-
- // Filter out the coins that are not in the top 10 and calculate 'Other'
- return Array.from(map.values()).map((record: any) => {
- let otherSum = 0;
- Object.keys(record).forEach((coin) => {
- if (coin !== 'time' && coin !== 'unit' && coin !== 'all' && !top10Coins.includes(coin)) { // Exclude 'unit' and 'all' from calculations
- otherSum += record[coin];
- delete record[coin];
- }
- });
- record.Other = otherSum; // Update 'Other' property with sum of other coins
- return record as GroupedOpenInterestData;
+ xAxisFormatter,
+ yaxisFormatter,
+ tooltipFormatterCurrency,
+ tooltipFormatterDate,
+} from '../../../helpers';
+
+import { getTokenColor, initialTokensSelectedWithOther } from '../../../constants/tokens';
+import { open_interest } from '../../../constants/api';
+import { createCoinSelectors } from '@/helpers/utils';
+
+const REQUESTS = [open_interest];
+
+export default function OpenInterestChart() {
+ const [coins, setCoins] = useState([]);
+ const initialTokensSelected = [...initialTokensSelectedWithOther, 'All'];
+ const [coinsSelected, setCoinsSelected] = useState(initialTokensSelected);
+
+ const [formattedData, setFormattedData] = useState([]);
+ const [dataOpenInterest, loadingOpenInterest, errorOpenInterest] = useRequest(
+ REQUESTS[0],
+ [],
+ 'chart_data'
+ );
+
+ const loading = loadingOpenInterest;
+ const error = errorOpenInterest;
+
+ type OpenInterestData = { time: string; coin: string; open_interest: number };
+ type GroupedOpenInterestData = {
+ time: Date;
+ unit: string;
+ All: number;
+ [key: string]: number | Date | string;
+ };
+
+ const groupByTime = (data: OpenInterestData[]): [GroupedOpenInterestData[], string[]] => {
+ const map = new Map();
+ const totalOpenInterestMap = new Map();
+ const uniqueCoins = new Set();
+
+ data.forEach((item) => {
+ const key = item.time;
+ if (!map.has(key)) {
+ map.set(key, {
+ time: new Date(key),
+ All: 0,
+ unit: '$',
});
- };
-
- const extractUniqueCoins = (formattedVolumeData: GroupedOpenInterestData[]): string[] => {
- const coinSet = new Set();
- for (const data of formattedVolumeData) {
- Object.keys(data).forEach(coin => {
- if (coin !== 'all' &&
- coin !== 'time' &&
- coin !== 'unit'
- ) {
- coinSet.add(coin);
- }
- });
- }
- const coinsArray = Array.from(coinSet);
- if (coinsArray.includes('Other')) {
- const index = coinsArray.indexOf('Other');
- coinsArray.splice(index, 1);
- coinsArray.push('Other');
- }
- return coinsArray;
- };
-
- const formatData = () => {
- const groupedData = groupByTime(dataOpenInterest);
- const uniquieCoins = extractUniqueCoins(groupedData);
- setFormattedData(groupedData);
- setCoinKeys(uniquieCoins);
+ }
+ const existingEntry = map.get(key);
+ existingEntry[item.coin] = (existingEntry[item.coin] || 0) + item.open_interest;
+ existingEntry.All += item.open_interest;
+
+ totalOpenInterestMap.set(
+ item.coin,
+ (totalOpenInterestMap.get(item.coin) || 0) + item.open_interest
+ );
+ });
+
+ map.forEach((entry) => {
+ const coinEntries = Object.entries(entry).filter(
+ ([key]) =>
+ key !== 'time' &&
+ key !== 'total' &&
+ key !== 'cumulative' &&
+ key !== 'other' &&
+ key !== 'unit' &&
+ key !== 'Other'
+ );
+ coinEntries.forEach(([coin]) => uniqueCoins.add(coin));
+ });
+ const result = Array.from(map.values());
+ return [result, Array.from(uniqueCoins)];
+ };
+
+ const formatData = () => {
+ const [groupedData, coins] = groupByTime(dataOpenInterest);
+ setCoins(coins);
+ setFormattedData(groupedData);
+ };
+
+ useEffect(() => {
+ if (!loading && !error) {
+ formatData();
}
-
- useEffect(() => {
- if (!loading && !error) {
- formatData();
- }
- }, [loading])
-
- return (
-
-
-
-
-
-
- {
- return Number(item.value) * -1;
- }}
- />
-
- {
- coinKeys.map(((coinName, i) => {
- return (
-
- )
- }))
- }
-
-
-
- Top 10 Coins grouped by total volume over time and remaining coins grouped by Other
-
-
- )
-}
\ No newline at end of file
+ }, [loading]);
+
+ const coinSelectors = createCoinSelectors(
+ coins,
+ coinsSelected,
+ setCoinsSelected,
+ formatData,
+ true,
+ 'All'
+ );
+
+ return (
+
+
+
+
+
+
+ {
+ return Number(item.value) * -1;
+ }}
+ />
+
+ {coinsSelected.map((coinName, i) => {
+ return (
+
+ );
+ })}
+
+
+
+
+ Before March 22, 2024, this showed one-sided open interest. It now shows two-sided open interest to address user confusion.
+
+
+
+ );
+}
diff --git a/components/home/charts/retail-volume.tsx b/components/home/charts/retail-volume.tsx
new file mode 100644
index 0000000..8f5f8ed
--- /dev/null
+++ b/components/home/charts/retail-volume.tsx
@@ -0,0 +1,370 @@
+import {
+ Bar,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+ ResponsiveContainer,
+ ComposedChart,
+ Line,
+} from 'recharts';
+import { useEffect, useState } from 'react';
+import { Box, Text } from '@chakra-ui/react';
+import { useRequest } from '@/hooks/useRequest';
+import ChartWrapper from '../../common/chartWrapper';
+import {
+ CHART_HEIGHT,
+ YAXIS_WIDTH,
+ BRIGHT_GREEN,
+ BRAND_GREEN_2,
+ BRAND_GREEN_3,
+} from '../../../constants';
+import {
+ tooltipFormatterCurrency,
+ tooltipLabelFormatter,
+ yaxisFormatter,
+ xAxisFormatter,
+} from '../../../helpers';
+import { createCoinSelectors } from '../../../helpers/utils';
+
+import { getTokenColor, initialTokensSelectedWithOther } from '../../../constants/tokens';
+import {
+ cumulative_usd_volume,
+ daily_usd_volume,
+ daily_usd_volume_by_coin,
+ daily_usd_volume_by_crossed,
+ daily_usd_volume_by_user,
+} from '../../../constants/api';
+
+const REQUESTS = [
+ cumulative_usd_volume,
+ daily_usd_volume,
+ daily_usd_volume_by_coin,
+ daily_usd_volume_by_crossed,
+ daily_usd_volume_by_user,
+];
+
+export default function RetailVolumeChart() {
+ const [dataMode, setDataMode] = useState<'COINS' | 'MARGIN'>('COINS');
+ const [formattedDataCoins, setFormattedDataCoins] = useState([]);
+ const [formattedDataMargin, setFormattedDataMargin] = useState([]);
+ const [coinsSelected, setCoinsSelected] = useState(initialTokensSelectedWithOther);
+ const [coinKeys, setCoinKeys] = useState([]);
+ const [dataCumulativeUsdVolume, loadingCumulativeUsdVolume, errorCumulativeUsdVolume] =
+ useRequest(REQUESTS[0], [], 'chart_data');
+ const [dataDailyUsdVolume, loadingDailyUsdVolume, errorDailyUsdVolume] = useRequest(
+ REQUESTS[1],
+ [],
+ 'chart_data'
+ );
+ const [dataDailyUsdVolumeByCoin, loadingDailyUsdVolumeByCoin, errorDailyUsdVolumeByCoin] =
+ useRequest(REQUESTS[2], [], 'chart_data');
+ const [
+ dataDailyUsdVolumeByCrossed,
+ loadingDailyUsdVolumeByCrossed,
+ errorDailyUsdVolumeByCrossed,
+ ] = useRequest(REQUESTS[3], [], 'chart_data');
+ const [dataDailyUsdVolumeByUser, loadingDailyUsdVolumeByUser, errorDailyUsdVolumeByUser] =
+ useRequest(REQUESTS[4], [], 'chart_data');
+
+ const loading =
+ loadingCumulativeUsdVolume ||
+ loadingDailyUsdVolume ||
+ loadingDailyUsdVolumeByCoin ||
+ loadingDailyUsdVolumeByCrossed ||
+ loadingDailyUsdVolumeByUser;
+ const error =
+ errorCumulativeUsdVolume ||
+ errorDailyUsdVolume ||
+ errorDailyUsdVolumeByCoin ||
+ errorDailyUsdVolumeByCrossed ||
+ errorDailyUsdVolumeByUser;
+
+ type CumulativeVolumeData = { cumulative: number; time: string };
+
+ const formatCumulativeVolumeByTime = (
+ dataCumulativeUsdVolume: CumulativeVolumeData[]
+ ): { [key: string]: number } => {
+ const result: { [key: string]: number } = {};
+ for (const data of dataCumulativeUsdVolume) {
+ result[data.time] = data.cumulative;
+ }
+ return result;
+ };
+
+ type FormattedDailyVolumeData = { time: string; daily_usd_volume: number };
+
+ const formatDailyVolumeByTime = (
+ dataCumulativeUsdVolume: FormattedDailyVolumeData[]
+ ): { [key: string]: number } => {
+ const result: { [key: string]: number } = {};
+ for (const data of dataCumulativeUsdVolume) {
+ result[data.time] = data.daily_usd_volume;
+ }
+ return result;
+ };
+
+ type VolumeData = { coin: string; daily_usd_volume: number; time: string };
+ type FormattedVolumeData = any[]; //{ time: string, all: number, [coin: string]: number };
+
+ const formatVolumeByCoins = (
+ CoinsSelected: string[],
+ dataDailyUsdVolumeByCoin: VolumeData[]
+ ): FormattedVolumeData[] => {
+ const temp: { [key: string]: { all: number; [coin: string]: number } } = {};
+ const cumulativeByTime: { [key: string]: number } = {};
+ let runningSelectedCumulative = 0;
+ let runningTotalCumulative = 0;
+ for (const data of dataDailyUsdVolumeByCoin) {
+ if (!temp[data.time]) {
+ temp[data.time] = { all: 0, cumulative: 0 };
+ }
+ if (CoinsSelected.includes(data.coin) || CoinsSelected.includes('Other')) {
+ temp[data.time].all += data.daily_usd_volume;
+ runningSelectedCumulative += data.daily_usd_volume;
+ }
+ temp[data.time][data.coin] = data.daily_usd_volume;
+ runningTotalCumulative += data.daily_usd_volume;
+ cumulativeByTime[data.time] = runningSelectedCumulative;
+ }
+
+ const selectedCoinData = (obj: { [coin: string]: number }) => {
+ const selectedEntries = Object.entries(obj).filter(
+ ([coin]) => CoinsSelected.includes(coin) && coin !== 'all'
+ );
+ const otherEntries = Object.entries(obj).filter(
+ ([coin]) => !CoinsSelected.includes(coin) && coin !== 'all'
+ );
+ const otherVolume = otherEntries.reduce((total, [_, volume]) => total + volume, 0);
+ return {
+ ...Object.fromEntries(selectedEntries),
+ Other: otherVolume,
+ all: obj.all,
+ };
+ };
+
+ const result: any[] = Object.entries(temp).map(([time, volumes]) => {
+ const selectedVolumes = selectedCoinData(volumes);
+ const all =
+ CoinsSelected.length === 1 && CoinsSelected.includes('Other')
+ ? selectedVolumes.Other
+ : selectedVolumes.all;
+ return {
+ time: new Date(time),
+ ...selectedVolumes,
+ cumulative: cumulativeByTime[time],
+ all,
+ unit: '$',
+ };
+ });
+
+ return result;
+ };
+
+ const extractUniqueCoins = (formattedVolumeData: VolumeData[]): string[] => {
+ const coinSet = new Set();
+ for (const data of formattedVolumeData) {
+ coinSet.add(data.coin);
+ }
+ const coinsArray = ['Other', ...Array.from(coinSet)];
+ return coinsArray;
+ };
+
+ type VolumeCrossedData = { crossed: boolean; daily_usd_volume: number; time: string };
+
+ const formatVolumeByCrossed = (
+ dataDailyUsdVolumeByCrossed: VolumeCrossedData[],
+ formattedCumulativeUsdVolume: { [key: string]: number },
+ formattedDailyVolumeByTime: { [key: string]: number }
+ ): any[] => {
+ // Create a temporary object to collect the data
+ const temp: { [key: string]: any } = {};
+
+ for (const data of dataDailyUsdVolumeByCrossed) {
+ if (!temp[data.time]) {
+ temp[data.time] = { all: 0, maker: 0, taker: 0 };
+ }
+ // Assigning daily_usd_volume to 'maker' if crossed is true, else assign to 'taker'
+ if (data.crossed) {
+ temp[data.time].taker = data.daily_usd_volume;
+ } else {
+ temp[data.time].maker = data.daily_usd_volume;
+ }
+ temp[data.time].all += data.daily_usd_volume;
+ }
+ // Convert the collected data into an array
+ const result: any[] = Object.entries(temp).map((item: any) => {
+ return {
+ time: new Date(item[0]),
+ maker: item[1].maker || 0,
+ taker: item[1].taker || 0,
+ Cumulative: formattedCumulativeUsdVolume[item[0]],
+ all: formattedDailyVolumeByTime[item[0]],
+ unit: '$',
+ };
+ });
+ return result;
+ };
+
+ const formatData = (CoinsSelected: string[]) => {
+ const formattedCumulativeVolumeByTime = formatCumulativeVolumeByTime(dataCumulativeUsdVolume);
+ const formattedDailyVolumeByTime = formatDailyVolumeByTime(dataDailyUsdVolume);
+ const formattedVolumeByCoins = formatVolumeByCoins(CoinsSelected, dataDailyUsdVolumeByCoin);
+ const formattedVolumeByCrossed = formatVolumeByCrossed(
+ dataDailyUsdVolumeByCrossed,
+ formattedCumulativeVolumeByTime,
+ formattedDailyVolumeByTime
+ );
+ setCoinKeys(extractUniqueCoins(dataDailyUsdVolumeByCoin));
+ setFormattedDataCoins(formattedVolumeByCoins);
+ setFormattedDataMargin(formattedVolumeByCrossed);
+ };
+
+ const controls = {
+ toggles: [
+ {
+ text: 'Coins',
+ event: () => setDataMode('COINS'),
+ active: dataMode === 'COINS',
+ },
+ {
+ text: 'Maker / Taker',
+ event: () => setDataMode('MARGIN'),
+ active: dataMode === 'MARGIN',
+ },
+ ],
+ };
+
+ useEffect(() => {
+ if (!loading || error) {
+ formatData(coinsSelected);
+ }
+ }, [loading, error]);
+
+ const coinSelectors = createCoinSelectors(
+ coinKeys,
+ coinsSelected,
+ setCoinsSelected,
+ formatData,
+ false,
+ 'Other'
+ );
+ return (
+
+
+
+
+
+
+ {
+ return Number(item.value) * -1;
+ }}
+ />
+
+ {dataMode === 'COINS' && (
+ <>
+ {coinsSelected.map((coinName, i) => {
+ return (
+
+ );
+ })}
+ >
+ )}
+ {dataMode === 'MARGIN' && (
+ <>
+
+
+ >
+ )}
+
+
+
+
+
+
+ This measures two-sided volume, i.e. each side of a trade is counted once if that side is retail.
+ This previously tracked retail volume, but was changed in February 2024 to non-HLP volume.
+
+
+
+ );
+}
diff --git a/components/home/charts/top-stats.tsx b/components/home/charts/top-stats.tsx
index 14fdbf9..280e52e 100644
--- a/components/home/charts/top-stats.tsx
+++ b/components/home/charts/top-stats.tsx
@@ -1,97 +1,151 @@
-'use strict'
-import React from 'react';
+'use strict';
+import React, { useEffect, useState } from 'react';
import { Box, Grid, Text, Spinner } from '@chakra-ui/react';
-import { total_users, total_usd_volume, total_deposits, total_withdrawals, total_notional_liquidated } from "../../../constants/api"
+import {
+ total_users,
+ total_deposits,
+ total_withdrawals,
+ total_notional_liquidated,
+ total_volume,
+} from '../../../constants/api';
import { useRequest } from '@/hooks/useRequest';
import { formatNumber } from '@/utils/formatting';
const REQUESTS = [
- total_users,
- total_usd_volume,
- total_deposits,
- total_withdrawals,
- total_notional_liquidated,
+ total_users,
+ total_volume,
+ total_deposits,
+ total_withdrawals,
+ total_notional_liquidated,
];
-const Card = (props: any) => (
-
-);
+const Card = (props: any) => ;
-const Loader = () =>
+const Loader = () => (
+
+
+
+);
const TopStats = () => {
+ const [dataTotalUsers, loadingTotalUsers, errorTotalUsers] = useRequest(
+ REQUESTS[0],
+ 0,
+ 'total_users',
+ true
+ );
+ const [dataTotalVolume, loadingVol, errorVol] = useRequest(REQUESTS[1], 0, 'chart_data', true);
+ const [totalVolume, setTotalVolume] = useState(0);
+ const [dataTotalDeposits, loadingTotalDeposits, errorTotalDeposits] = useRequest(
+ REQUESTS[2],
+ 0,
+ 'total_deposits',
+ true
+ );
+ const [dataTotalWithdrawals, loadingTotalWithdrawals, errorTotalWithdrawals] = useRequest(
+ REQUESTS[3],
+ 0,
+ 'total_withdrawals',
+ true
+ );
+ const [
+ dataTotalNotionalLiquidated,
+ loadingTotalNotionalLiquidated,
+ errorTotalNotionalLiquidated,
+ ] = useRequest(REQUESTS[4], 0, 'total_notional_liquidated', true);
+
+ interface TotalVolume {
+ time: string;
+ total_volume: number;
+ coin: string;
+ }
+
+ const computeTotalVolume = (dataTotalVolume: TotalVolume[]) => {
+ let totalVolume = 0;
+ dataTotalVolume.forEach((volume: TotalVolume) => {
+ totalVolume += volume.total_volume;
+ });
+
+ setTotalVolume(totalVolume);
+ };
- const [dataTotalUsers, loadingTotalUsers, errorTotalUsers] = useRequest(REQUESTS[0], 0, "total_users", true);
- const [dataTotalUsdVol, loadingUsdVol, errorUsdVol] = useRequest(REQUESTS[1], 0, "total_usd_volume", true);
- const [dataTotalDeposits, loadingTotalDeposits, errorTotalDeposits] = useRequest(REQUESTS[2], 0, "total_deposits", true);
- const [dataTotalWithdrawals, loadingTotalWithdrawals, errorTotalWithdrawals] = useRequest(REQUESTS[3], 0, "total_withdrawals", true);
- const [dataTotalNotionalLiquidated, loadingTotalNotionalLiquidated, errorTotalNotionalLiquidated] = useRequest(REQUESTS[4], 0, "total_notional_liquidated", true);
+ useEffect(() => {
+ if (!loadingVol && !errorVol && dataTotalVolume) {
+ computeTotalVolume(dataTotalVolume);
+ }
+ }, [loadingVol, errorVol]);
- return (
-
-
-
- {dataTotalUsers ? formatNumber(dataTotalUsers) : errorTotalUsers ? "Error" : null}
-
-
- Total Users
-
- {loadingTotalUsers && }
-
-
-
- {dataTotalUsdVol ? `$${formatNumber(dataTotalUsdVol, 0)}` : errorUsdVol ? "Error" : null}
-
-
- Total non-HLP Volume
-
- {loadingUsdVol && }
-
-
-
- {dataTotalDeposits ? `$${formatNumber(dataTotalDeposits, 0)}` : errorTotalDeposits ? "Error" : null}
-
-
- Total Deposits
-
- {loadingTotalDeposits && }
-
-
-
- {dataTotalWithdrawals ? `$${formatNumber(Math.abs(dataTotalWithdrawals), 0)}` : errorTotalWithdrawals ? "Error" : null}
-
-
- Total Withdrawals
-
- {loadingTotalWithdrawals && }
-
-
-
- {dataTotalNotionalLiquidated ? `$${formatNumber(dataTotalNotionalLiquidated, 0)}` : errorTotalNotionalLiquidated ? "Error" : null}
-
-
- Total Notional Liquidated
-
- {loadingTotalNotionalLiquidated && }
-
-
- );
+ return (
+
+
+
+ {dataTotalUsers ? formatNumber(dataTotalUsers) : errorTotalUsers ? 'Error' : null}
+
+
+ Total Users
+
+ {loadingTotalUsers && }
+
+
+
+ {totalVolume ? `$${formatNumber(totalVolume, 0)}` : errorVol ? 'Error' : null}
+
+
+ Total Volume
+
+ {loadingVol && }
+
+
+
+ {dataTotalDeposits
+ ? `$${formatNumber(dataTotalDeposits + 245601, 0)}`
+ : errorTotalDeposits
+ ? 'Error'
+ : null}
+
+
+ Total Deposits (All Time)
+
+ {loadingTotalDeposits && }
+
+
+
+ {dataTotalWithdrawals
+ ? `$${formatNumber(Math.abs(dataTotalWithdrawals), 0)}`
+ : errorTotalWithdrawals
+ ? 'Error'
+ : null}
+
+
+ Total Withdrawals (All Time)
+
+ {loadingTotalWithdrawals && }
+
+
+
+ {dataTotalNotionalLiquidated
+ ? `$${formatNumber(dataTotalNotionalLiquidated, 0)}`
+ : errorTotalNotionalLiquidated
+ ? 'Error'
+ : null}
+
+
+ Total Notional Liquidated
+
+ {loadingTotalNotionalLiquidated && }
+
+
+ );
};
export default TopStats;
diff --git a/components/home/charts/trader-profit.tsx b/components/home/charts/trader-profit.tsx
index 9d89b12..740a085 100644
--- a/components/home/charts/trader-profit.tsx
+++ b/components/home/charts/trader-profit.tsx
@@ -1,173 +1,167 @@
-import React, { useEffect, useState } from 'react'
+import React, { useEffect, useState } from 'react';
import {
- Area,
- Bar,
- Cell,
- CartesianGrid,
- ComposedChart,
- Legend,
- Line,
- ResponsiveContainer,
- Tooltip,
- XAxis,
- YAxis
+ Bar,
+ Cell,
+ CartesianGrid,
+ ComposedChart,
+ Legend,
+ Line,
+ ResponsiveContainer,
+ Tooltip,
+ XAxis,
+ YAxis,
} from 'recharts';
-import { chain, sumBy, sortBy, maxBy, minBy } from 'lodash'
+import { sortBy, maxBy, minBy } from 'lodash';
import { useRequest } from '@/hooks/useRequest';
-import { Box, Text, useMediaQuery } from "@chakra-ui/react"
-import { cumulative_user_pnl, user_pnl } from "../../../constants/api"
+import { Box, Text } from '@chakra-ui/react';
+import { cumulative_user_pnl, user_pnl } from '../../../constants/api';
import ChartWrapper from '../../common/chartWrapper';
+import { CHART_HEIGHT, YAXIS_WIDTH, BRIGHT_GREEN, GREEN, RED } from '../../../constants';
import {
- CHART_HEIGHT,
- YAXIS_WIDTH,
- BRIGHT_GREEN,
- GREEN,
- RED,
-} from "../../../constants";
-import {
- yaxisFormatter,
- xAxisFormatter,
- formatNumberWithOptions,
- tooltipFormatterCurrency,
-} from '../../../helpers'
+ yaxisFormatter,
+ xAxisFormatter,
+ tooltipFormatterCurrency,
+ tooltipFormatterDate,
+} from '../../../helpers';
-const REQUESTS = [
- cumulative_user_pnl,
- user_pnl,
-];
+const REQUESTS = [cumulative_user_pnl, user_pnl];
-export default function TradersProfitLossChart() {
- const [isMobile] = useMediaQuery('(max-width: 700px)');
+export default function TradersProfitLossChart(props: any) {
+ const isMobile = props.isMobile;
- const [data, setData] = useState(null);
- const [dataCumulativeUserPNL, loadingCumulativeUserPNL, errorCumulativeUserPNL] = useRequest(REQUESTS[0], [], 'chart_data');
- const [dataUserPNL, loadingUserPNL, errorUserPNL] = useRequest(REQUESTS[1], [], 'chart_data');
+ const [data, setData] = useState(null);
+ const [dataCumulativeUserPNL, loadingCumulativeUserPNL, errorCumulativeUserPNL] = useRequest(
+ REQUESTS[0],
+ [],
+ 'chart_data'
+ );
+ const [dataUserPNL, loadingUserPNL, errorUserPNL] = useRequest(REQUESTS[1], [], 'chart_data');
- const formatTradingData = () => {
- let currentPnlCumulative = 0;
- let currentProfitCumulative = 0;
- let currentLossCumulative = 0;
+ const loading = loadingUserPNL || loadingCumulativeUserPNL;
+ const error = errorUserPNL || errorCumulativeUserPNL;
+ const formatTradingData = () => {
+ let currentProfitCumulative = 0;
+ let currentLossCumulative = 0;
- dataUserPNL.shift();
- dataCumulativeUserPNL.shift();
+ dataUserPNL.shift();
+ dataCumulativeUserPNL.shift();
- if (!dataCumulativeUserPNL || !dataCumulativeUserPNL.length || !dataUserPNL || !dataUserPNL.length) return
+ if (
+ !dataCumulativeUserPNL ||
+ !dataCumulativeUserPNL.length ||
+ !dataUserPNL ||
+ !dataUserPNL.length
+ )
+ return;
- let data: any[] = sortBy(dataCumulativeUserPNL, (i: any) => Date.parse(i.time)).map((dataItem: any, index: number) => ({
- cumulative_pnl: dataItem.cumulative_pnl,
- user_pnl: dataUserPNL && dataUserPNL[index] ? dataUserPNL[index].total_pnl : 0,
- timestamp: new Date(dataItem.time),
- profit_cumulative: dataItem.cumulative_pnl > 0 ? dataItem.cumulative_pnl : 0,
- loss_cumulative: dataItem.cumulative_pnl < 0 ? dataItem.cumulative_pnl : 0,
- unit: "single",
- }));
+ let data: any[] = sortBy(dataCumulativeUserPNL, (i: any) => Date.parse(i.time)).map(
+ (dataItem: any, index: number) => ({
+ cumulative_pnl: dataItem.cumulative_pnl,
+ user_pnl: dataUserPNL && dataUserPNL[index] ? dataUserPNL[index].total_pnl : 0,
+ timestamp: new Date(dataItem.time),
+ profit_cumulative: dataItem.cumulative_pnl > 0 ? dataItem.cumulative_pnl : 0,
+ loss_cumulative: dataItem.cumulative_pnl < 0 ? dataItem.cumulative_pnl : 0,
+ unit: 'single',
+ })
+ );
- const maxProfit = maxBy(data, item => item.profit_cumulative).profit_cumulative
- const maxLoss = minBy(data, item => item.loss_cumulative).loss_cumulative
+ const maxProfit = maxBy(data, (item) => item.profit_cumulative).profit_cumulative;
+ const maxLoss = minBy(data, (item) => item.loss_cumulative).loss_cumulative;
- const maxPnl = maxBy(data, item => item.user_pnl).user_pnl;
- const minPnl = minBy(data, item => item.user_pnl).user_pnl;
+ const maxPnl = maxBy(data, (item) => item.user_pnl).user_pnl;
+ const minPnl = minBy(data, (item) => item.user_pnl).user_pnl;
- const maxCurrentCumulativePnl = maxBy(data, item => item.cumulative_pnl).cumulative_pnl;
- const minCurrentCumulativePnl = minBy(data, item => item.cumulative_pnl).cumulative_pnl;
+ const maxCurrentCumulativePnl = maxBy(data, (item) => item.cumulative_pnl).cumulative_pnl;
+ const minCurrentCumulativePnl = minBy(data, (item) => item.cumulative_pnl).cumulative_pnl;
- const stats = {
- maxProfit,
- maxLoss,
- currentProfitCumulative,
- currentLossCumulative,
- maxCurrentCumulativeProfitLoss: Math.max(currentProfitCumulative, -currentLossCumulative),
+ const stats = {
+ maxProfit,
+ maxLoss,
+ currentProfitCumulative,
+ currentLossCumulative,
+ maxCurrentCumulativeProfitLoss: Math.max(currentProfitCumulative, -currentLossCumulative),
- maxAbsPnl: Math.max(
- Math.abs(maxPnl),
- Math.abs(minPnl),
- ),
- maxAbsCumulativePnl: Math.max(
- Math.abs(maxCurrentCumulativePnl),
- Math.abs(minCurrentCumulativePnl)
- ),
- }
+ maxAbsPnl: Math.max(Math.abs(maxPnl), Math.abs(minPnl)),
+ maxAbsCumulativePnl: Math.max(
+ Math.abs(maxCurrentCumulativePnl),
+ Math.abs(minCurrentCumulativePnl)
+ ),
+ };
- setData({
- data,
- stats,
- })
- }
+ setData({
+ data,
+ stats,
+ });
+ };
- useEffect(() => {
- if (dataCumulativeUserPNL.length > 0 && dataUserPNL.length > 0) {
- formatTradingData();
- }
- }, [dataCumulativeUserPNL, dataUserPNL]);
+ useEffect(() => {
+ if (!loading && !error) {
+ formatTradingData();
+ }
+ }, [loading, error]);
- return (
-
-
-
-
-
-
-
- ''}
- contentStyle={{
- textAlign: 'left',
- background: "#0A1F1B",
- borderColor: "#061412",
- color: "#fff",
- boxShadow: "0px 0px 7px rgb(0 0 0 / 20%)",
- borderRadius: "26px",
- maxHeight: "500px"
- }}
- itemSorter={(item) => {
- return Number(item.value) * -1;
- }}
- />
-
-
- {(data && data.data || []).map((item: any, i: number) => {
- return 0 ? GREEN : RED} />
- })}
- maxBarSize={20}
- |
-
-
-
-
-
- This is computed as negative of (HLP + liquidator)
-
-
-
- )
-}
\ No newline at end of file
+ return (
+
+
+
+
+
+
+
+ {
+ return Number(item.value) * -1;
+ }}
+ />
+
+
+ {((data && data.data) || []).map((item: any, i: number) => {
+ return 0 ? GREEN : RED} />;
+ })}
+ |
+
+
+
+
+ This is computed as negative of (HLP + liquidator)
+
+
+ );
+}
diff --git a/components/home/charts/unique-users-coin.tsx b/components/home/charts/unique-users-coin.tsx
new file mode 100644
index 0000000..b32bffa
--- /dev/null
+++ b/components/home/charts/unique-users-coin.tsx
@@ -0,0 +1,268 @@
+import {
+ Bar,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+ ResponsiveContainer,
+ ComposedChart,
+ Line,
+} from 'recharts';
+import { useEffect, useState } from 'react';
+import { Box, Text, useMediaQuery } from '@chakra-ui/react';
+import { useRequest } from '@/hooks/useRequest';
+
+import ChartWrapper, { CoinSelector } from '../../common/chartWrapper';
+import { CHART_HEIGHT, YAXIS_WIDTH, BRIGHT_GREEN } from '../../../constants';
+import {
+ tooltipFormatter,
+ tooltipFormatterDate,
+ xAxisFormatter,
+ yaxisFormatterNumber,
+ yaxisFormatterPercent,
+} from '../../../helpers';
+import { createCoinSelectors } from '../../../helpers/utils';
+
+import { getTokenColor, initialTokensSelectedWithOther } from '../../../constants/tokens';
+import {
+ cumulative_new_users,
+ daily_unique_users,
+ daily_unique_users_by_coin,
+} from '../../../constants/api';
+
+type DailyUniqueUsersByCoin = {
+ time: string;
+ coin: string;
+ daily_unique_users: number;
+ percentage_of_total_users: number;
+ all: number;
+};
+
+type UniqueUserTradeData = {
+ time: string;
+ daily_unique_users: number;
+};
+
+type CumulativeNewUsersData = {
+ time: string;
+ daily_new_users: number;
+ cumulative_new_users: number;
+};
+
+type GroupedTradeData = {
+ time: Date;
+ daily_unique_users: number;
+ cumulative_unique_users: number;
+ unit: string;
+ [key: string]: number | Date | { [key: string]: number } | string | undefined;
+};
+
+type TempGroupedTradeData = {
+ time: Date;
+ coins: { [key: string]: number };
+ //all: number;
+ daily_unique_users: number;
+ cumulative_unique_users: number;
+ unit: string;
+ [key: string]: number | Date | { [key: string]: number } | string | undefined;
+};
+
+const REQUESTS = [cumulative_new_users, daily_unique_users, daily_unique_users_by_coin];
+
+export default function UniqueUsers() {
+ const [coinsSelected, setCoinsSelected] = useState(initialTokensSelectedWithOther);
+
+ const [formattedData, setFormattedData] = useState([]);
+ const [coinKeys, setCoinKeys] = useState([]);
+
+ const [dataCumulativeNewUsers, loadingCumulativeNewUsers, errorCumulativeNewUsers] = useRequest(
+ REQUESTS[0],
+ [],
+ 'chart_data'
+ );
+ const [dataDailyUniqueUsers, loadingDailyUniqueUsers, errorDailyUniqueUsers] = useRequest(
+ REQUESTS[1],
+ [],
+ 'chart_data'
+ );
+ const [dataDailyUniqueUsersByCoin, loadingDailyUniqueUsersByCoin, errorDailyUniqueUsersByCoin] =
+ useRequest(REQUESTS[2], [], 'chart_data');
+
+ const loading =
+ loadingCumulativeNewUsers || loadingDailyUniqueUsers || loadingDailyUniqueUsersByCoin;
+ const error = errorCumulativeNewUsers || errorDailyUniqueUsers || errorDailyUniqueUsersByCoin;
+
+ const formatTradesByCoinAndTime = (
+ CoinsSelected: string[],
+ dataDailyUniqueUsersByCoin: DailyUniqueUsersByCoin[],
+ uniqueUserTradeData: UniqueUserTradeData[],
+ dataCumulativeNewUsers: CumulativeNewUsersData[]
+ ): GroupedTradeData[] => {
+ const temp: { [key: string]: TempGroupedTradeData } = {};
+ const uniqueUserTradeDataMap: { [key: string]: number } = {};
+ const cumulativeUniqueUserTradeDataMap: { [key: string]: number } = {};
+
+ uniqueUserTradeData.forEach((item) => {
+ uniqueUserTradeDataMap[item.time] = item.daily_unique_users;
+ });
+
+ dataCumulativeNewUsers.forEach((item) => {
+ cumulativeUniqueUserTradeDataMap[item.time] = item.cumulative_new_users;
+ });
+
+ dataDailyUniqueUsersByCoin.forEach(
+ ({ time, coin, daily_unique_users, percentage_of_total_users }) => {
+ if (!temp[time]) {
+ temp[time] = {
+ time: new Date(time),
+ coins: {},
+ daily_unique_users: uniqueUserTradeDataMap[time] || 0,
+ cumulative_unique_users: cumulativeUniqueUserTradeDataMap[time] || 0,
+ unit: '%',
+ };
+ }
+ temp[time].coins[coin] = (temp[time].coins[coin] || 0) + percentage_of_total_users * 100;
+ temp[time][`${coin}_daily_unique_users`] = daily_unique_users;
+ }
+ );
+
+ const selectedCoinData = (obj: { [coin: string]: number }) => {
+ const selectedEntries = Object.entries(obj).filter(
+ ([coin]) => CoinsSelected.includes(coin) || coin === 'all'
+ );
+ const otherEntries = Object.entries(obj).filter(
+ ([coin]) => !CoinsSelected.includes(coin) && coin !== 'all'
+ );
+ const otherVolume = otherEntries.reduce((total, [, volume]) => total + volume, 0);
+ return {
+ ...Object.fromEntries(selectedEntries),
+ Other: otherVolume,
+ };
+ };
+
+ return Object.values(temp).map(({ time, coins, ...rest }) => {
+ return {
+ time: new Date(time),
+ ...selectedCoinData(coins),
+ ...rest,
+ unit: '%',
+ };
+ });
+ };
+
+ const extractUniqueCoins = (CoinData: any): string[] => {
+ const coinSet = new Set();
+ for (const data of CoinData) {
+ coinSet.add(data.coin);
+ }
+ const coinsArray = Array.from(coinSet);
+ return coinsArray;
+ };
+
+ const formatData = (CoinsSelector: string[]) => {
+ const formattedData = formatTradesByCoinAndTime(
+ CoinsSelector,
+ dataDailyUniqueUsersByCoin,
+ dataDailyUniqueUsers,
+ dataCumulativeNewUsers
+ );
+ const formattedUniqueCoinKeys = extractUniqueCoins(dataDailyUniqueUsersByCoin);
+ setFormattedData(formattedData);
+ setCoinKeys(formattedUniqueCoinKeys);
+ };
+
+ useEffect(() => {
+ if (!loading && !error) {
+ formatData(coinsSelected);
+ }
+ }, [loading]);
+
+ const coinSelectors = createCoinSelectors(coinKeys, coinsSelected, setCoinsSelected, formatData);
+
+ return (
+
+
+
+
+
+
+
+ {
+ return Number(item.value) * -1;
+ }}
+ />
+
+ {coinsSelected.map((coinName, i) => {
+ return (
+
+ );
+ })}
+
+
+
+
+
+ The line is the number of unique addresses who traded on Hyperliquid each day, bars represent
+ proportion of users who traded specific coins. Total exceeds 100% as users can trade
+ multiple coins.
+
+
+
+ );
+}
diff --git a/components/home/charts/uniquie-users-coin.tsx b/components/home/charts/uniquie-users-coin.tsx
deleted file mode 100644
index 3c192b3..0000000
--- a/components/home/charts/uniquie-users-coin.tsx
+++ /dev/null
@@ -1,265 +0,0 @@
-import {
- Bar,
- XAxis,
- YAxis,
- CartesianGrid,
- Tooltip,
- Legend,
- ResponsiveContainer,
- ComposedChart,
- Line
-} from 'recharts';
-import { useEffect, useState } from 'react';
-import { Box, Text, useMediaQuery } from "@chakra-ui/react"
-import { useRequest } from '@/hooks/useRequest';
-import ChartWrapper from '../../common/chartWrapper';
-import {
- CHART_HEIGHT,
- YAXIS_WIDTH,
- BRIGHT_GREEN,
-} from "../../../constants";
-import {
- tooltipFormatter,
- tooltipLabelFormatter,
- xAxisFormatter,
- yaxisFormatterNumber,
- yaxisFormatterPercent,
-} from '../../../helpers'
-import { getTokenHex } from "../../../constants/tokens";
-import {
- cumulative_new_users,
- daily_unique_users,
- daily_unique_users_by_coin,
-} from "../../../constants/api"
-
-type DailyUniqueUsersByCoin = {
- time: string;
- coin: string;
- daily_unique_users: number;
- percentage_of_total_users: number;
- all: number;
-};
-
-type UniqueUserTradeData = {
- time: string;
- daily_unique_users: number;
-};
-
-type CumulativeNewUsersData = {
- time: string;
- daily_new_users: number;
- cumulative_new_users: number;
-};
-
-type GroupedTradeData = {
- time: Date;
- daily_unique_users: number;
- cumulative_unique_users: number;
- unit: string;
- [key: string]: number | Date | { [key: string]: number } | string | undefined;
-}
-
-type TempGroupedTradeData = {
- time: Date;
- coins: { [key: string]: number };
- //all: number;
- daily_unique_users: number;
- cumulative_unique_users: number;
- unit: string;
- [key: string]: number | Date | { [key: string]: number } | string | undefined;
-}
-
-const REQUESTS = [
- cumulative_new_users,
- daily_unique_users,
- daily_unique_users_by_coin,
-];
-
-export default function CumulativeUsers() {
- const [isMobile] = useMediaQuery('(max-width: 700px)');
-
- const [formattedData, setFormattedData] = useState([])
- const [coinKeys, setCoinKeys] = useState([])
-
- const [dataCumulativeNewUsers, loadingCumulativeNewUsers, errorCumulativeNewUsers] = useRequest(REQUESTS[0], [], 'chart_data');
- const [dataDailyUniqueUsers, loadingDailyUniqueUsers, errorDailyUniqueUsers] = useRequest(REQUESTS[1], [], 'chart_data');
- const [dataDailyUniqueUsersByCoin, loadingDailyUniqueUsersByCoin, errorDailyUniqueUsersByCoin] = useRequest(REQUESTS[2], [], 'chart_data');
-
- const loading = loadingCumulativeNewUsers || loadingDailyUniqueUsers || loadingDailyUniqueUsersByCoin;
- const error = errorCumulativeNewUsers || errorDailyUniqueUsers || errorDailyUniqueUsersByCoin;
-
- const formatTradesByCoinAndTime = (
- dataDailyUniqueUsersByCoin: DailyUniqueUsersByCoin[],
- uniqueUserTradeData: UniqueUserTradeData[],
- dataCumulativeNewUsers: CumulativeNewUsersData[]
- ): GroupedTradeData[] => {
- const temp: { [key: string]: TempGroupedTradeData } = {};
- const uniqueUserTradeDataMap: { [key: string]: number } = {};
- const cumulativeUniqueUserTradeDataMap: { [key: string]: number } = {};
-
- uniqueUserTradeData.forEach(item => {
- uniqueUserTradeDataMap[item.time] = item.daily_unique_users;
- });
-
- dataCumulativeNewUsers.forEach(item => {
- cumulativeUniqueUserTradeDataMap[item.time] = item.cumulative_new_users;
- });
-
- dataDailyUniqueUsersByCoin.forEach(({ time, coin, daily_unique_users, percentage_of_total_users }) => {
- if (!temp[time]) {
- temp[time] = {
- time: new Date(time),
- coins: {},
- daily_unique_users: uniqueUserTradeDataMap[time] || 0,
- cumulative_unique_users: cumulativeUniqueUserTradeDataMap[time] || 0,
- unit: '%'
- };
- }
- temp[time].coins[coin] = (temp[time].coins[coin] || 0) + (percentage_of_total_users * 100);
- temp[time][`${coin}_daily_unique_users`] = daily_unique_users;
- });
-
- const sortAndSliceTop10 = (obj: { [coin: string]: number }) => {
- const sortedEntries = Object.entries(obj)
- .sort(([, aVolume], [, bVolume]) => bVolume - aVolume);
- const top10Entries = sortedEntries.slice(0, 10);
- const otherEntries = sortedEntries.slice(10);
- const otherVolume = otherEntries.reduce((total, [, volume]) => total + volume, 0);
- return {
- ...Object.fromEntries(top10Entries),
- Other: otherVolume
- };
- };
-
- return Object.values(temp).map(({ time, coins, ...rest }) => {
- return {
- time: new Date(time),
- ...sortAndSliceTop10(coins),
- ...rest,
- unit: '%',
- };
- });
- }
-
- const extractUniqueCoins = (formattedVolumeData: GroupedTradeData[]): string[] => {
- const coinSet = new Set();
- for (const data of formattedVolumeData) {
- Object.keys(data).forEach(coin => {
- if (coin !== 'all' &&
- coin !== 'cumulative' &&
- coin !== 'time' &&
- coin !== 'other' &&
- coin !== 'unit' &&
- coin !== 'daily_unique_users' &&
- coin !== 'cumulative_unique_users' &&
- !coin.includes('daily_unique_users')
- ) {
- coinSet.add(coin);
- }
- });
- }
- const coinsArray = Array.from(coinSet);
- if (coinsArray.includes('Other')) {
- const index = coinsArray.indexOf('Other');
- coinsArray.splice(index, 1);
- coinsArray.push('Other');
- }
- return coinsArray;
- };
-
- const formatData = () => {
- const formattedData = formatTradesByCoinAndTime(dataDailyUniqueUsersByCoin, dataDailyUniqueUsers, dataCumulativeNewUsers);
- const formattedUniqueCoinKeys = extractUniqueCoins(formattedData)
- setFormattedData(formattedData);
- setCoinKeys(formattedUniqueCoinKeys)
- };
-
- useEffect(() => {
- if (!loading && !error) {
- formatData();
- }
- }, [loading])
-
- return (
-
-
-
-
-
-
-
- {
- return Number(item.value) * -1;
- }}
- />
-
- {
- coinKeys.map(((coinName, i) => {
- return (
-
- )
- }))
- }
-
-
-
-
-
- The line is the number of unique addresses who used Hyperliquid each day, bars represent proportion of users who traded specific coins. Total exceeds 100% as users can trade multiple coins. Top 10 coins are shown separately and the rest are grouped as Other.
-
-
-
- )
-}
\ No newline at end of file
diff --git a/components/home/charts/volume-non-hlp.tsx b/components/home/charts/volume-non-hlp.tsx
deleted file mode 100644
index a9f8936..0000000
--- a/components/home/charts/volume-non-hlp.tsx
+++ /dev/null
@@ -1,336 +0,0 @@
-import {
- Bar,
- XAxis,
- YAxis,
- CartesianGrid,
- Tooltip,
- Legend,
- ResponsiveContainer,
- ComposedChart,
- Line
-} from 'recharts';
-import { useEffect, useState } from 'react';
-import { Box, Text, useMediaQuery } from "@chakra-ui/react"
-import { useRequest } from '@/hooks/useRequest';
-import ChartWrapper from '../../common/chartWrapper';
-import {
- CHART_HEIGHT,
- YAXIS_WIDTH,
- BRIGHT_GREEN,
- BRAND_GREEN_2,
- BRAND_GREEN_3,
-} from "../../../constants";
-import {
- tooltipFormatterCurrency,
- tooltipLabelFormatter,
- yaxisFormatter,
- xAxisFormatter,
-} from '../../../helpers'
-import { getTokenHex } from "../../../constants/tokens";
-import {
- cumulative_usd_volume,
- daily_usd_volume,
- daily_usd_volume_by_coin,
- daily_usd_volume_by_crossed,
- daily_usd_volume_by_user,
-} from "../../../constants/api"
-
-const REQUESTS = [
- cumulative_usd_volume,
- daily_usd_volume,
- daily_usd_volume_by_coin,
- daily_usd_volume_by_crossed,
- daily_usd_volume_by_user,
-];
-
-export default function VolumeChart() {
- const [isMobile] = useMediaQuery('(max-width: 700px)');
- const [dataMode, setDataMode] = useState<"COINS" | "MARGIN">('COINS');
- const [formattedDataCoins, setFormattedDataCoins] = useState([])
- const [formattedDataMarin, setFormattedDataMarin] = useState([])
- const [formattedCumulativeVolumeData, setFormattedCumulativeVolumeData] = useState([]);
- const [coinKeys, setCoinKeys] = useState([])
- const [dataCumulativeUsdVolume, loadingCumulativeUsdVolume, errorCumulativeUsdVolume] = useRequest(REQUESTS[0], [], 'chart_data');
- const [dataDailyUsdVolume, loadingDailyUsdVolume, errorDailyUsdVolume] = useRequest(REQUESTS[1], [], 'chart_data');
- const [dataDailyUsdVolumeByCoin, loadingDailyUsdVolumeByCoin, errorDailyUsdVolumeByCoin] = useRequest(REQUESTS[2], [], 'chart_data');
- const [dataDailyUsdVolumeByCrossed, loadingDailyUsdVolumeByCrossed, errorDailyUsdVolumeByCrossed] = useRequest(REQUESTS[3], [], 'chart_data');
- const [dataDailyUsdVolumeByUser, loadingDailyUsdVolumeByUser, errorDailyUsdVolumeByUser] = useRequest(REQUESTS[4], [], 'chart_data');
-
- const loading = loadingCumulativeUsdVolume || loadingDailyUsdVolume || loadingDailyUsdVolumeByCoin || loadingDailyUsdVolumeByCrossed || loadingDailyUsdVolumeByUser;
-
- const error = errorCumulativeUsdVolume || errorDailyUsdVolume || errorDailyUsdVolumeByCoin || errorDailyUsdVolumeByCrossed || errorDailyUsdVolumeByUser;
-
- type CumulativeVolumeData = { cumulative: number, time: string };
-
- const formatCumulativeVolumeByTime = (dataCumulativeUsdVolume: CumulativeVolumeData[]): { [key: string]: number } => {
- const result: { [key: string]: number } = {};
- for (const data of dataCumulativeUsdVolume) {
- result[data.time] = data.cumulative;
- }
- return result;
- };
-
- type FormattedDailyVolumeData = { time: string, daily_usd_volume: number };
-
- const formatDailyVolumeByTime = (dataCumulativeUsdVolume: FormattedDailyVolumeData[]): { [key: string]: number } => {
- const result: { [key: string]: number } = {};
- for (const data of dataCumulativeUsdVolume) {
- result[data.time] = data.daily_usd_volume;
- }
- return result;
- };
-
- type VolumeData = { coin: string, daily_usd_volume: number, time: string };
- type FormattedVolumeData = any[] //{ time: string, all: number, [coin: string]: number };
-
- const formatVolumeByCoins = (
- dataDailyUsdVolumeByCoin: VolumeData[],
- formattedCumulativeUsdVolume: { [key: string]: number },
- formattedDailyVolumeByTime: { [key: string]: number }
- ): FormattedVolumeData[] => {
- const temp: { [key: string]: { all: number, [coin: string]: number } } = {};
- for (const data of dataDailyUsdVolumeByCoin) {
- if (!temp[data.time]) {
- temp[data.time] = { all: 0 };
- }
- temp[data.time][data.coin] = data.daily_usd_volume;
- temp[data.time].all += data.daily_usd_volume;
- }
-
- const sortAndSliceTop10 = (obj: { [coin: string]: number }) => {
- const sortedEntries = Object.entries(obj)
- .sort(([, aVolume], [, bVolume]) => bVolume - aVolume);
- const top10Entries = sortedEntries.slice(0, 10);
- const otherEntries = sortedEntries.slice(10);
-
- const otherVolume = otherEntries.reduce((total, [, volume]) => total + volume, 0);
- return {
- ...Object.fromEntries(top10Entries),
- Other: otherVolume
- };
- };
-
- const result: any[] = Object.entries(temp).map(([time, volumes]) => {
- const top10Volumes = sortAndSliceTop10(volumes);
- return {
- time: new Date(time),
- ...top10Volumes,
- cumulative: formattedCumulativeUsdVolume[time as any],
- all: formattedDailyVolumeByTime[time as any],
- unit: "$",
- };
- });
-
- return result;
- };
-
-
- const extractUniqueCoins = (formattedVolumeData: FormattedVolumeData[]): string[] => {
- const coinSet = new Set();
- for (const data of formattedVolumeData) {
- Object.keys(data).forEach(coin => {
- if (coin !== 'all' &&
- coin !== 'cumulative' &&
- coin !== 'time' &&
- coin !== 'other' &&
- coin !== 'unit'
- ) {
- coinSet.add(coin);
- }
- });
- }
- const coinsArray = Array.from(coinSet);
- if (coinsArray.includes('Other')) {
- const index = coinsArray.indexOf('Other');
- coinsArray.splice(index, 1);
- coinsArray.push('Other');
- }
-
- return coinsArray;
- };
-
- type VolumeCrossedData = { crossed: boolean, daily_usd_volume: number, time: string }
-
- const formatVolumeByCrossed = (
- dataDailyUsdVolumeByCrossed: VolumeCrossedData[],
- formattedCumulativeUsdVolume: { [key: string]: number },
- formattedDailyVolumeByTime: { [key: string]: number }
- ): any[] => {
- // Create a temporary object to collect the data
- const temp: { [key: string]: any } = {};
-
- for (const data of dataDailyUsdVolumeByCrossed) {
- if (!temp[data.time]) {
- temp[data.time] = { all: 0, maker: 0, taker: 0 };
- }
- // Assigning daily_usd_volume to 'maker' if crossed is true, else assign to 'taker'
- if (data.crossed) {
- temp[data.time].taker = data.daily_usd_volume;
- } else {
- temp[data.time].maker = data.daily_usd_volume;
- }
- temp[data.time].all += data.daily_usd_volume;
- }
- // Convert the collected data into an array
- const result: any[] = Object.entries(temp).map((item: any) => {
- return {
- time: new Date(item[0]),
- maker: item[1].maker || 0,
- taker: item[1].taker || 0,
- cumulative: formattedCumulativeUsdVolume[item[0]],
- all: formattedDailyVolumeByTime[item[0]],
- unit: "$",
- };
- });
- return result;
- };
-
- const formatData = () => {
- const formattedCumulativeVolumeByTime = formatCumulativeVolumeByTime(dataCumulativeUsdVolume)
- const formattedDailyVolumeByTime = formatDailyVolumeByTime(dataDailyUsdVolume)
- const formattedVolumeByCoins = formatVolumeByCoins(dataDailyUsdVolumeByCoin, formattedCumulativeVolumeByTime, formattedDailyVolumeByTime);
- const formattedVolumeByCrossed = formatVolumeByCrossed(dataDailyUsdVolumeByCrossed, formattedCumulativeVolumeByTime, formattedDailyVolumeByTime);
- setCoinKeys(extractUniqueCoins(formattedVolumeByCoins))
- setFormattedDataCoins(formattedVolumeByCoins);
- setFormattedDataMarin(formattedVolumeByCrossed)
- };
-
- const controls = {
- toggles: [
- {
- text: "Coins",
- event: () => setDataMode('COINS'),
- active: dataMode === 'COINS',
- },
- {
- text: "Maker / Taker",
- event: () => setDataMode('MARGIN'),
- active: dataMode === 'MARGIN',
- }
- ]
- }
-
- useEffect(() => {
- if (!loading) {
- formatData();
- }
- }, [loading])
-
- return (
-
-
-
-
-
-
-
- {
- return Number(item.value) * -1;
- }}
- />
-
- {
- dataMode === 'COINS' && (
- <>
- {
- coinKeys.map(((coinName, i) => {
- return (
-
- )
- }))
- }
- >
- )
- }
- {
- dataMode === 'MARGIN' && (
- <>
-
-
-
- >
- )
- }
-
-
-
-
- {dataMode === 'COINS' && (
- Top 10 Coins grouped daily and remaining coins grouped by Other
- )}
-
-
- )
-}
\ No newline at end of file
diff --git a/components/home/charts/volume-num-trades.tsx b/components/home/charts/volume-num-trades.tsx
index 874858d..e699631 100644
--- a/components/home/charts/volume-num-trades.tsx
+++ b/components/home/charts/volume-num-trades.tsx
@@ -1,353 +1,380 @@
import {
- Bar,
- Label,
- XAxis,
- YAxis,
- CartesianGrid,
- Tooltip,
- Legend,
- ResponsiveContainer,
- ComposedChart,
- Line
+ Bar,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+ ResponsiveContainer,
+ ComposedChart,
+ Line,
} from 'recharts';
-import { Box, Text, useMediaQuery } from "@chakra-ui/react"
import { useEffect, useState } from 'react';
import { useRequest } from '@/hooks/useRequest';
+
import ChartWrapper from '../../common/chartWrapper';
import {
- CHART_HEIGHT,
- YAXIS_WIDTH,
- BRIGHT_GREEN,
- BRAND_GREEN_2,
- BRAND_GREEN_3,
-} from "../../../constants";
+ CHART_HEIGHT,
+ YAXIS_WIDTH,
+ BRIGHT_GREEN,
+ BRAND_GREEN_2,
+ BRAND_GREEN_3,
+} from '../../../constants';
import {
- tooltipFormatter,
- tooltipLabelFormatter,
- yaxisFormatterNumber,
- xAxisFormatter,
-} from '../../../helpers'
-import { getTokenHex } from "../../../constants/tokens";
+ tooltipFormatter,
+ tooltipLabelFormatter,
+ yaxisFormatterNumber,
+ xAxisFormatter,
+} from '../../../helpers';
+import { createCoinSelectors } from '../../../helpers/utils';
+
+import { getTokenColor, initialTokensSelectedWithOther } from '../../../constants/tokens';
import {
- cumulative_trades,
- daily_trades,
- daily_trades_by_coin,
- daily_trades_by_crossed,
- daily_trades_by_user,
-} from "../../../constants/api"
+ cumulative_trades,
+ daily_trades,
+ daily_trades_by_coin,
+ daily_trades_by_crossed,
+ daily_trades_by_user,
+} from '../../../constants/api';
const REQUESTS = [
- daily_trades,
- daily_trades_by_coin,
- daily_trades_by_crossed,
- daily_trades_by_user,
- cumulative_trades,
+ daily_trades,
+ daily_trades_by_coin,
+ daily_trades_by_crossed,
+ daily_trades_by_user,
+ cumulative_trades,
];
export default function VolumeChart() {
- const [isMobile] = useMediaQuery('(max-width: 700px)');
-
- const [dataMode, setDataMode] = useState<"COINS" | "MARGIN" | "USER">('COINS');
- const [formattedDataCoins, setFormattedDataCoins] = useState([])
- const [formattedDataMarin, setFormattedDataMarin] = useState([])
- const [formattedDataUsers, setFormattedDataUsers] = useState([])
- const [maxAllValueUser, setMaxAllValueUser] = useState('auto');
- const [uniqueUsers, setUniqueUsers] = useState([])
- const [coinKeys, setCoinKeys] = useState([])
+ const [coinsSelected, setCoinsSelected] = useState(initialTokensSelectedWithOther);
- const [dataDailyTrades, loadingDailyTrades, errorDailyTrades] = useRequest(REQUESTS[0], [], 'chart_data');
- const [dataDailyTradesByCoin, loadingDailyTradesByCoin, errorDailyTradesByCoin] = useRequest(REQUESTS[1], [], 'chart_data');
- const [dataDailyTradesByMargin, loadingDailyTradesByMargin, errorDailyTradesByMargin] = useRequest(REQUESTS[2], [], 'chart_data');
- const [dataDailyTradesByUser, loadingDailyTradesByUser, errorDailyTradesByUser] = useRequest(REQUESTS[3], [], 'chart_data');
- const [dataCumulativeTrades, loadingCumulativeTrades, errorCumulativeTrades] = useRequest(REQUESTS[4], [], 'chart_data');
+ const [dataMode, setDataMode] = useState<'COINS' | 'MARGIN' | 'USER'>('COINS');
+ const [formattedDataCoins, setFormattedDataCoins] = useState([]);
+ const [formattedDataMarin, setFormattedDataMarin] = useState([]);
+ const [formattedDataUsers, setFormattedDataUsers] = useState([]);
+ const [maxAllValueUser, setMaxAllValueUser] = useState('auto');
+ const [uniqueUsers, setUniqueUsers] = useState([]);
+ const [coinKeys, setCoinKeys] = useState([]);
- const loading = loadingDailyTrades || loadingDailyTradesByCoin || loadingDailyTradesByMargin || loadingDailyTradesByUser || loadingCumulativeTrades;
+ const [dataDailyTrades, loadingDailyTrades, errorDailyTrades] = useRequest(
+ REQUESTS[0],
+ [],
+ 'chart_data'
+ );
+ const [dataDailyTradesByCoin, loadingDailyTradesByCoin, errorDailyTradesByCoin] = useRequest(
+ REQUESTS[1],
+ [],
+ 'chart_data'
+ );
+ const [dataDailyTradesByMargin, loadingDailyTradesByMargin, errorDailyTradesByMargin] =
+ useRequest(REQUESTS[2], [], 'chart_data');
+ const [dataDailyTradesByUser, loadingDailyTradesByUser, errorDailyTradesByUser] = useRequest(
+ REQUESTS[3],
+ [],
+ 'chart_data'
+ );
+ const [dataCumulativeTrades, loadingCumulativeTrades, errorCumulativeTrades] = useRequest(
+ REQUESTS[4],
+ [],
+ 'chart_data'
+ );
- const error = errorDailyTrades || errorDailyTradesByCoin || errorDailyTradesByMargin || errorDailyTradesByUser || errorCumulativeTrades;
- ``
+ const loading =
+ loadingDailyTrades ||
+ loadingDailyTradesByCoin ||
+ loadingDailyTradesByMargin ||
+ loadingDailyTradesByUser ||
+ loadingCumulativeTrades;
- type CumulativeTradeData = { cumulative: number, time: string };
-
- const formatTradesByTime = (dataCumulativeUsdVolume: CumulativeTradeData[]): { [key: string]: number } => {
- const result: { [key: string]: number } = {};
- for (const data of dataCumulativeUsdVolume) {
- result[data.time] = data.cumulative;
- }
- return result;
- };
+ const error =
+ errorDailyTrades ||
+ errorDailyTradesByCoin ||
+ errorDailyTradesByMargin ||
+ errorDailyTradesByUser ||
+ errorCumulativeTrades;
+ ``;
- type DailyTradesData = { time: string, daily_trades: number };
+ type CumulativeTradeData = { cumulative: number; time: string };
- const formatDailyTradesByTime = (dataDailyTrades: DailyTradesData[]): { [key: string]: number } => {
- const result: { [key: string]: number } = {};
- for (const data of dataDailyTrades) {
- result[data.time] = data.daily_trades;
- }
- return result;
- };
-
- type CoinTradesData = { coin: string, daily_usd_volume: number, time: string };
- type FormattedCoinTradesData = any[] //{ time: string, all: number, [coin: string]: number };
-
- const formatDailyTradesByCoins = (
- dataDailyTradesByCoin: { coin: string, daily_trades: number, time: string }[],
- formattedCumulativeTradesByTime: { [key: string]: number },
- ): FormattedCoinTradesData[] => {
- const temp: { [key: string]: { all: number, [coin: string]: number } } = {};
- for (const data of dataDailyTradesByCoin) {
- if (!temp[data.time]) {
- temp[data.time] = { all: 0 };
- }
- temp[data.time][data.coin] = data.daily_trades;
- temp[data.time].all += data.daily_trades;
- }
+ const formatTradesByTime = (
+ dataCumulativeUsdVolume: CumulativeTradeData[]
+ ): { [key: string]: number } => {
+ const result: { [key: string]: number } = {};
+ for (const data of dataCumulativeUsdVolume) {
+ result[data.time] = data.cumulative;
+ }
+ return result;
+ };
- const sortAndSliceTop10 = (obj: { [coin: string]: number }) => {
- const sortedEntries = Object.entries(obj)
- .sort(([, aVolume], [, bVolume]) => bVolume - aVolume);
- const top10Entries = sortedEntries.slice(0, 10);
- const otherEntries = sortedEntries.slice(10);
+ type FormattedCoinTradesData = any[]; //{ time: string, all: number, [coin: string]: number };
- const otherVolume = otherEntries.reduce((total, [, volume]) => total + volume, 0);
- return {
- ...Object.fromEntries(top10Entries),
- Other: otherVolume
- };
- };
+ const formatDailyTradesByCoins = (
+ CoinsSelected: string[],
+ dataDailyTradesByCoin: { coin: string; daily_trades: number; time: string }[],
+ formattedCumulativeTradesByTime: { [key: string]: number }
+ ): FormattedCoinTradesData[] => {
+ const temp: { [key: string]: { all: number; [coin: string]: number } } = {};
+ for (const data of dataDailyTradesByCoin) {
+ if (!temp[data.time]) {
+ temp[data.time] = { all: 0 };
+ }
+ temp[data.time][data.coin] = data.daily_trades;
+ temp[data.time].all += data.daily_trades;
+ }
- const result: any[] = Object.entries(temp).map(([time, volumes]) => {
- const top10Volumes = sortAndSliceTop10(volumes);
- return {
- time: new Date(time),
- ...top10Volumes,
- cumulative: formattedCumulativeTradesByTime[time as any],
- unit: '',
- };
- });
- return result;
+ const selectedCoinData = (obj: { [coin: string]: number }) => {
+ const selectedEntries = Object.entries(obj).filter(
+ ([coin]) => CoinsSelected.includes(coin) || coin === 'all'
+ );
+ const otherEntries = Object.entries(obj).filter(
+ ([coin]) => !CoinsSelected.includes(coin) && coin !== 'all'
+ );
+ const otherVolume = otherEntries.reduce((total, [, volume]) => total + volume, 0);
+ return {
+ ...Object.fromEntries(selectedEntries),
+ Other: otherVolume,
+ };
};
- const extractUniqueCoins = (formattedCoinTradesData: FormattedCoinTradesData[]): string[] => {
- const coinSet = new Set();
- for (const data of formattedCoinTradesData) {
- Object.keys(data).forEach(coin => {
- if (coin !== 'all' && coin !== 'cumulative' && coin !== 'time' && coin !== 'unit') {
- coinSet.add(coin);
- }
- });
- }
- const coinsArray = Array.from(coinSet);
- if (coinsArray.includes('Other')) {
- const index = coinsArray.indexOf('Other');
- coinsArray.splice(index, 1);
- coinsArray.push('Other');
- }
- return coinsArray;
- };
+ const result: any[] = Object.entries(temp).map(([time, volumes]) => {
+ const top10Volumes = selectedCoinData(volumes);
+ return {
+ time: new Date(time),
+ ...top10Volumes,
+ cumulative: formattedCumulativeTradesByTime[time as any],
+ unit: '',
+ };
+ });
+ return result;
+ };
- type MarginData = { crossed: boolean, daily_trades: number, time: string };
- type FormattedMarginData = { time: Date, maker: number, taker: number, all: number, cumulative: number };
+ const extractUniqueCoins = (CoinData: any): string[] => {
+ const coinSet = new Set();
+ for (const data of CoinData) {
+ coinSet.add(data.coin);
+ }
+ const coinsArray = Array.from(coinSet);
+ return coinsArray;
+ };
- const formatTradesByMargin = (
- dataDailyTradesByMargin: MarginData[],
- formattedCumulativeTradesByTime: { [key: string]: number },
- ): FormattedMarginData[] => {
- const groupedByTime: { [key: string]: FormattedMarginData } = {};
+ type MarginData = { crossed: boolean; daily_trades: number; time: string };
+ type FormattedMarginData = {
+ time: Date;
+ maker: number;
+ taker: number;
+ all: number;
+ cumulative: number;
+ };
- for (const data of dataDailyTradesByMargin) {
- const dateKey = new Date(data.time).toISOString();
+ const formatTradesByMargin = (
+ dataDailyTradesByMargin: MarginData[],
+ formattedCumulativeTradesByTime: { [key: string]: number }
+ ): FormattedMarginData[] => {
+ const groupedByTime: { [key: string]: FormattedMarginData } = {};
- if (!groupedByTime[dateKey]) {
- groupedByTime[dateKey] = {
- time: new Date(data.time),
- maker: 0,
- taker: 0,
- all: 0,
- cumulative: 0,
- }
- }
+ for (const data of dataDailyTradesByMargin) {
+ const dateKey = new Date(data.time).toISOString();
- if (data.crossed) {
- groupedByTime[dateKey].taker += data.daily_trades;
- } else {
- groupedByTime[dateKey].maker += data.daily_trades;
- }
- groupedByTime[dateKey].all += data.daily_trades;
- groupedByTime[dateKey].cumulative = formattedCumulativeTradesByTime[data.time as any];
- }
+ if (!groupedByTime[dateKey]) {
+ groupedByTime[dateKey] = {
+ time: new Date(data.time),
+ maker: 0,
+ taker: 0,
+ all: 0,
+ cumulative: 0,
+ };
+ }
- return Object.values(groupedByTime);
+ if (data.crossed) {
+ groupedByTime[dateKey].taker += data.daily_trades;
+ } else {
+ groupedByTime[dateKey].maker += data.daily_trades;
+ }
+ groupedByTime[dateKey].all += data.daily_trades;
+ groupedByTime[dateKey].cumulative = formattedCumulativeTradesByTime[data.time as any];
}
- const formatData = () => {
- const formattedCumulativeTradesByTime = formatTradesByTime(dataCumulativeTrades)
- const formattedTradesByCoins = formatDailyTradesByCoins(dataDailyTradesByCoin, formattedCumulativeTradesByTime);
- const formattedTradesByMargin = formatTradesByMargin(dataDailyTradesByMargin, formattedCumulativeTradesByTime);
- setMaxAllValueUser(maxAllValueUser);
- setCoinKeys(extractUniqueCoins(formattedTradesByCoins))
- setFormattedDataCoins(formattedTradesByCoins);
- setFormattedDataMarin(formattedTradesByMargin);
- };
+ return Object.values(groupedByTime);
+ };
+
+ const formatData = (CoinsSelected: string[]) => {
+ const formattedCumulativeTradesByTime = formatTradesByTime(dataCumulativeTrades);
+ const formattedTradesByCoins = formatDailyTradesByCoins(
+ CoinsSelected,
+ dataDailyTradesByCoin,
+ formattedCumulativeTradesByTime
+ );
+ const formattedTradesByMargin = formatTradesByMargin(
+ dataDailyTradesByMargin,
+ formattedCumulativeTradesByTime
+ );
+ setMaxAllValueUser(maxAllValueUser);
+ setCoinKeys(extractUniqueCoins(dataDailyTradesByCoin));
+ setFormattedDataCoins(formattedTradesByCoins);
+ setFormattedDataMarin(formattedTradesByMargin);
+ };
- const controls = {
- toggles: [
- {
- text: "Coins",
- event: () => setDataMode('COINS'),
- active: dataMode === 'COINS',
- },
- {
- text: "Maker / Taker",
- event: () => setDataMode('MARGIN'),
- active: dataMode === 'MARGIN',
- }
- ]
+ const controls = {
+ toggles: [
+ {
+ text: 'Coins',
+ event: () => setDataMode('COINS'),
+ active: dataMode === 'COINS',
+ },
+ {
+ text: 'Maker / Taker',
+ event: () => setDataMode('MARGIN'),
+ active: dataMode === 'MARGIN',
+ },
+ ],
+ };
+
+ useEffect(() => {
+ if (!loading && !error) {
+ formatData(coinsSelected);
}
+ }, [loading, dataMode]);
- useEffect(() => {
- if (!loading && !error) {
- formatData();
- }
- }, [loading, dataMode])
+ const coinSelectors = createCoinSelectors(coinKeys, coinsSelected, setCoinsSelected, formatData);
- return (
-
+
+
-
-
-
-
-
-
- {
- return Number(item.value) * -1;
- }}
- />
- {(dataMode === 'COINS' || dataMode === 'MARGIN') && }
- {
- dataMode === 'COINS' && (
- <>
- {
- coinKeys.map(((coinName, i) => {
- return (
-
- )
- }))
- }
- >
- )
- }
- {
- dataMode === 'MARGIN' && (
- <>
-
-
- >
- )
- }
- {
- dataMode === 'USER' && (
- <>
- {
- uniqueUsers.map(((user, i) => {
- return (
-
- )
- }))
- }
- >
- )
- }
-
-
-
-
- {dataMode === 'COINS' && (
- Top 10 Coins grouped daily and remaining coins grouped by Other
- )}
-
-
- )
-}
\ No newline at end of file
+
+
+
+
+ {
+ return Number(item.value) * -1;
+ }}
+ />
+ {(dataMode === 'COINS' || dataMode === 'MARGIN') && (
+
+ )}
+ {dataMode === 'COINS' && (
+ <>
+ {coinsSelected.map((coinName, i) => {
+ return (
+
+ );
+ })}
+ >
+ )}
+ {dataMode === 'MARGIN' && (
+ <>
+
+
+ >
+ )}
+ {dataMode === 'USER' && (
+ <>
+ {uniqueUsers.map((user, i) => {
+ return (
+
+ );
+ })}
+ >
+ )}
+
+
+
+
+ );
+}
diff --git a/components/home/charts/volume-total.tsx b/components/home/charts/volume-total.tsx
new file mode 100644
index 0000000..2427602
--- /dev/null
+++ b/components/home/charts/volume-total.tsx
@@ -0,0 +1,202 @@
+import {
+ Bar,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+ ResponsiveContainer,
+ ComposedChart,
+ Line,
+} from 'recharts';
+import { useEffect, useState } from 'react';
+import { useRequest } from '@/hooks/useRequest';
+
+import ChartWrapper from '../../common/chartWrapper';
+import { BRIGHT_GREEN, CHART_HEIGHT, YAXIS_WIDTH } from '../../../constants';
+import {
+ yaxisFormatter,
+ xAxisFormatter,
+ tooltipFormatterCurrency,
+ tooltipLabelFormatter,
+} from '../../../helpers';
+import { createCoinSelectors } from '../../../helpers/utils';
+
+import { total_volume } from '../../../constants/api';
+import { getTokenColor, initialTokensSelectedWithOther } from '@/constants/tokens';
+
+const REQUESTS = [total_volume];
+
+export default function TotalVolumeChart() {
+ const [formattedData, setFormattedData] = useState([]);
+ const [coinsSelected, setCoinsSelected] = useState(initialTokensSelectedWithOther);
+ const [coins, setCoins] = useState([]);
+ const [dataTotalVolume, loading, error] = useRequest(REQUESTS[0], [], 'chart_data');
+
+ interface TotalVolume {
+ time: string;
+ total_volume: number;
+ coin: string;
+ }
+
+ interface MergedData {
+ time: Date;
+ total: number;
+ [coin: string]: any;
+ cumulative: number;
+ unit: string;
+ Other: number;
+ }
+
+ const makeFormattedData = (
+ CoinsSelected: string[],
+ dataTotalVolume: TotalVolume[]
+ ): [MergedData[], string[]] => {
+ const map = new Map();
+ const uniqueCoins = new Set();
+
+ let cumulative = 0;
+ dataTotalVolume.forEach((item: TotalVolume) => {
+ let { time, coin, total_volume } = item;
+ if (CoinsSelected.includes(coin) || CoinsSelected.includes('Other')) {
+ cumulative += total_volume;
+ }
+ if (!map.has(time)) {
+ map.set(time, {
+ time: new Date(time),
+ total: total_volume,
+ [`${coin}`]: total_volume,
+ cumulative,
+ Other: 0,
+ unit: '$',
+ });
+ } else {
+ const existingEntry = map.get(time)!;
+ existingEntry[`${coin}`] = (existingEntry[`${coin}`] || 0) + total_volume;
+ if (CoinsSelected.includes(coin) || CoinsSelected.includes('Other')) {
+ existingEntry.total += total_volume;
+ }
+ existingEntry.cumulative = cumulative;
+ }
+ });
+
+ map.forEach((entry) => {
+ const coinEntries = Object.entries(entry).filter(
+ ([key]) => key !== 'time' && key !== 'total' && key !== 'other' && key !== 'unit'
+ );
+ const otherCoins = coinEntries.filter(
+ ([coin]) => !CoinsSelected.includes(coin) && coin !== 'all'
+ );
+
+ coinEntries.forEach(([coin]) => uniqueCoins.add(coin));
+
+ let otherTotal = 0;
+ otherCoins.forEach(([key, value]) => {
+ if (key !== 'cumulative') {
+ otherTotal += value;
+ }
+ });
+ entry.Other = otherTotal;
+ });
+
+ const result = Array.from(map.values());
+ return [result, Array.from(uniqueCoins)];
+ };
+
+ const formatData = (CoinsSelected: string[]) => {
+ const [newFormattedData, coins] = makeFormattedData(CoinsSelected, dataTotalVolume);
+ setCoins(coins);
+ setFormattedData(newFormattedData);
+ };
+
+ useEffect(() => {
+ if (!loading && !error) {
+ formatData(coinsSelected);
+ }
+ }, [loading, error]);
+
+ const coinSelectors = createCoinSelectors(
+ coins,
+ coinsSelected,
+ setCoinsSelected,
+ formatData,
+ false,
+ 'Other'
+ );
+
+ return (
+
+
+
+
+
+
+
+ {coinsSelected.map((coin, i) => {
+ return (
+
+ );
+ })}
+
+
+ tooltipLabelFormatter(label, args, 'total')}
+ contentStyle={{
+ textAlign: 'left',
+ background: '#0A1F1B',
+ borderColor: '#061412',
+ color: '#fff',
+ boxShadow: '0px 0px 7px rgb(0 0 0 / 20%)',
+ borderRadius: '26px',
+ maxHeight: '500px',
+ }}
+ position={{ y: -100 }}
+ itemSorter={(item) => {
+ return Number(item.value) * -1;
+ }}
+ />
+
+
+
+ );
+}
diff --git a/components/home/main/index.tsx b/components/home/main/index.tsx
index dc35f6f..04f7611 100644
--- a/components/home/main/index.tsx
+++ b/components/home/main/index.tsx
@@ -1,95 +1,124 @@
-'use client'
-import React, { useState } from 'react';
-import moment from "moment";
+'use client';
+import React, { useEffect } from 'react';
import { Container, Box, Text, Grid, Flex } from '@chakra-ui/react';
-import * as S from './styles';
import TopStats from '../charts/top-stats';
-import VolumeNonMMChart from '../charts/volume-non-hlp';
+import RetailVolumeChart from '../charts/retail-volume';
import VolumeNumTrades from '../charts/volume-num-trades';
import OpenInterestChart from '../charts/open-interest';
import TradersProfitLossChart from '../charts/trader-profit';
-import HLPProfitLossChart from '../charts/hlp-liquidator-profit';
import { DateRangeSelect } from '../charts/date-range';
import FundingRateChart from '../charts/funding-rate';
import CumulativeUsersChart from '../charts/cumulative-users';
-import CoinTradesByUsers from "../charts/uniquie-users-coin";
import CumulativeInflowChart from '../charts/cumulative-inflow';
-import CumulativeNotionalLiquidatedChart from '../charts/cumulative-notional-liquidated';
-import TableLargestUsers from "../tables/largest-users"
-import TableUserDesposits from "../tables/user-deposits"
-import TableLiquidatedNotional from "../tables/liquidated-notional-user"
-import TableTradeCount from "../tables/user-trade-count"
-import Liquidity from "../charts/liquidity"
-
+import CumulativeNotionalLiquidatedChart from '../charts/liquidator';
+import TableLargestUsers from '../tables/largest-users';
+import TableUserDesposits from '../tables/user-deposits';
+import TableLiquidatedNotional from '../tables/liquidated-notional-user';
+import TableTradeCount from '../tables/user-trade-count';
+import Liquidity from '../charts/liquidity';
+import HlpExposure from '../charts/hlp';
+import TotalVolumeChart from '../charts/volume-total';
+import UniqueUsers from '../charts/unique-users-coin';
+import mixpanel from 'mixpanel-browser';
const Main = () => {
-
- return (
- {
+ if (process.env.NEXT_PUBLIC_MIXPANEL_TOKEN) {
+ mixpanel.init(process.env.NEXT_PUBLIC_MIXPANEL_TOKEN, {
+ api_host: 'https://metrics.hyperliquid.xyz',
+ });
+ mixpanel.track('Stats Loaded');
+ }
+ }, []);
+ return (
+
+
+
+
+ Hyperliquid Stats
+
+
+
+
+
-
-
-
- Hyperliquid Stats
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
};
export default Main;
diff --git a/components/home/main/styles.ts b/components/home/main/styles.ts
index fb9d11f..9b58e95 100644
--- a/components/home/main/styles.ts
+++ b/components/home/main/styles.ts
@@ -101,7 +101,7 @@ export const BgImg2 = styled(Box)`
z-index: 0.5;
animation: ${fade} 20s infinite;
transition: visibility 20s ease-in-out;
-
+
img {
width: 100%;
}
@@ -129,4 +129,4 @@ export const ImgPlanet2 = styled(Box)`
img {
width: 100%;
}
-`;
\ No newline at end of file
+`;
diff --git a/components/home/tables/largest-users.tsx b/components/home/tables/largest-users.tsx
index 19e7321..093e448 100644
--- a/components/home/tables/largest-users.tsx
+++ b/components/home/tables/largest-users.tsx
@@ -1,116 +1,154 @@
-import React from "react";
-import { useTable, usePagination } from "react-table";
-import { Box, Button, ButtonGroup, Flex, Table, Tbody, Td, Th, Thead, Tr, Text, useMediaQuery } from "@chakra-ui/react";
+import React from 'react';
+import { useTable, usePagination } from 'react-table';
import {
- largest_users_by_usd_volume,
-} from "../../../constants/api"
-import {
- formatNumberWithOptions,
-} from "../../../helpers/index"
+ Box,
+ Button,
+ ButtonGroup,
+ Flex,
+ Table,
+ Tbody,
+ Td,
+ Th,
+ Thead,
+ Tr,
+ Text,
+ useMediaQuery,
+} from '@chakra-ui/react';
+import { largest_users_by_usd_volume } from '../../../constants/api';
+import { formatNumberWithOptions } from '../../../helpers/index';
import { formatAddress } from '../../../utils/formatting';
import { useRequest } from '@/hooks/useRequest';
import ChartWrapper from '../../common/chartWrapper';
-const REQUESTS = [
- largest_users_by_usd_volume
-]
+const REQUESTS = [largest_users_by_usd_volume];
export default function TableComponent() {
+ const [dataLargestUsersByUsdVolume] = useRequest(REQUESTS[0], [], 'table_data');
+ const [isMobile] = useMediaQuery('(max-width: 700px)');
- const [dataLargestUsersByUsdVolume, loadingLargestUsersByUsdVolume, errorLargestUsersByUsdVolume] = useRequest(REQUESTS[0], [], 'table_data');
- const [isMobile] = useMediaQuery('(max-width: 700px)');
-
- const columns = React.useMemo(() => [
- {
- Header: "Address",
- accessor: "name" as const,
- Cell: ({ value }: { value: string }) =>
-
-
- {formatAddress(value, isMobile ? 6 : 6)}
-
- ,
- },
- {
- Header: "Volume USD",
- accessor: "value" as const,
- Cell: ({ value }: { value: number }) => ${formatNumberWithOptions(value)},
- },
- ], []);
+ const columns = React.useMemo(
+ () => [
+ {
+ Header: 'Address',
+ accessor: 'name' as const,
+ Cell: ({ value }: { value: string }) => (
+
+
+ {formatAddress(value, isMobile ? 6 : 6)}
+
+
+ ),
+ },
+ {
+ Header: 'Volume USD',
+ accessor: 'value' as const,
+ Cell: ({ value }: { value: number }) => ${formatNumberWithOptions(value)},
+ },
+ ],
+ []
+ );
- const {
- getTableProps,
- getTableBodyProps,
- headerGroups,
- prepareRow,
- page,
- canPreviousPage,
- canNextPage,
- pageOptions,
- pageCount,
- gotoPage,
- nextPage,
- previousPage,
- state: { pageIndex, pageSize },
- } = useTable(
- {
- columns,
- data: dataLargestUsersByUsdVolume ? dataLargestUsersByUsdVolume : [],
- initialState: { pageIndex: 0, pageSize: 5 },
- },
- usePagination
- );
+ const {
+ getTableProps,
+ getTableBodyProps,
+ headerGroups,
+ prepareRow,
+ page,
+ canPreviousPage,
+ canNextPage,
+ pageCount,
+ gotoPage,
+ nextPage,
+ previousPage,
+ state: { pageIndex },
+ } = useTable(
+ {
+ columns,
+ data: dataLargestUsersByUsdVolume ? dataLargestUsersByUsdVolume : [],
+ initialState: { pageIndex: 0, pageSize: 5 },
+ },
+ usePagination
+ );
- return (
-
-
- Largest Users By USD Volume
-
-
-
- {headerGroups.map((headerGroup, i) => (
-
- {headerGroup.headers.map((column, j) => (
- {column.render('Header')} |
- ))}
-
- ))}
-
-
- {page.map((row: any, i: number) => {
- prepareRow(row);
- return (
-
- {row.cells.map((cell: any, j: any) => {
- return {cell.render('Cell')} | ;
- })}
-
- );
- })}
-
-
-
+
+
+ Largest Users By USD Volume
+
+
+
+
+ {headerGroups.map((headerGroup, i) => (
+
+ {headerGroup.headers.map((column, j) => (
+
+ {column.render('Header')}
+ |
+ ))}
+
+ ))}
+
+
+ {page.map((row: any, i: number) => {
+ prepareRow(row);
+ return (
+
+ {row.cells.map((cell: any, j: any) => {
+ return (
+
+ {cell.render('Cell')}
+ |
+ );
+ })}
+
+ );
+ })}
+
+
+
+
+
+
+
+
+
-
- );
-}
\ No newline at end of file
+ {'Last'}
+
+
+
+
+ Page: {pageIndex + 1} / {pageCount} | Results: {dataLargestUsersByUsdVolume.length}
+
+
+
+ );
+}
diff --git a/components/home/tables/liquidated-notional-user.tsx b/components/home/tables/liquidated-notional-user.tsx
index 7edd694..d4718cf 100644
--- a/components/home/tables/liquidated-notional-user.tsx
+++ b/components/home/tables/liquidated-notional-user.tsx
@@ -1,116 +1,153 @@
-import React from "react";
-import { useTable, usePagination } from "react-table";
-import { Box, Button, ButtonGroup, Flex, Table, Tbody, Td, Th, Thead, Tr, Text, useMediaQuery } from "@chakra-ui/react";
+import React from 'react';
+import { useTable, usePagination } from 'react-table';
import {
- largest_liquidated_notional_by_user,
-} from "../../../constants/api"
-import {
- formatNumberWithOptions
-} from "../../../helpers/index"
+ Box,
+ Button,
+ ButtonGroup,
+ Flex,
+ Table,
+ Tbody,
+ Td,
+ Th,
+ Thead,
+ Tr,
+ Text,
+ useMediaQuery,
+} from '@chakra-ui/react';
+import { largest_liquidated_notional_by_user } from '../../../constants/api';
+import { formatNumberWithOptions } from '../../../helpers/index';
import { useRequest } from '@/hooks/useRequest';
-import ChartWrapper from '../../common/chartWrapper';
import { formatAddress } from '../../../utils/formatting';
-const REQUESTS = [
- largest_liquidated_notional_by_user
-]
+const REQUESTS = [largest_liquidated_notional_by_user];
export default function TableComponent() {
+ const [dataLargestLiquidatedNotional] = useRequest(REQUESTS[0], [], 'table_data');
+ const [isMobile] = useMediaQuery('(max-width: 700px)');
- const [dataLargestLiquidatedNotional, loadingLargestLiquidatedNotional, errorLargestLiquidatedNotional] = useRequest(REQUESTS[0], [], 'table_data');
- const [isMobile] = useMediaQuery('(max-width: 700px)');
-
- const columns = React.useMemo(() => [
- {
- Header: "Address",
- accessor: "name" as const,
- Cell: ({ value }: { value: string }) =>
-
-
- {formatAddress(value, isMobile ? 6 : 6)}
-
- ,
- },
- {
- Header: "Liquidated Position USD",
- accessor: "value" as const,
- Cell: ({ value }: { value: number }) => ${formatNumberWithOptions(value)},
- },
- ], []);
+ const columns = React.useMemo(
+ () => [
+ {
+ Header: 'Address',
+ accessor: 'name' as const,
+ Cell: ({ value }: { value: string }) => (
+
+
+ {formatAddress(value, isMobile ? 6 : 6)}
+
+
+ ),
+ },
+ {
+ Header: 'Liquidated Position USD',
+ accessor: 'value' as const,
+ Cell: ({ value }: { value: number }) => ${formatNumberWithOptions(value)},
+ },
+ ],
+ []
+ );
- const {
- getTableProps,
- getTableBodyProps,
- headerGroups,
- prepareRow,
- page,
- canPreviousPage,
- canNextPage,
- pageOptions,
- pageCount,
- gotoPage,
- nextPage,
- previousPage,
- state: { pageIndex, pageSize },
- } = useTable(
- {
- columns,
- data: dataLargestLiquidatedNotional ? dataLargestLiquidatedNotional : [],
- initialState: { pageIndex: 0, pageSize: 5 },
- },
- usePagination
- );
+ const {
+ getTableProps,
+ getTableBodyProps,
+ headerGroups,
+ prepareRow,
+ page,
+ canPreviousPage,
+ canNextPage,
+ pageCount,
+ gotoPage,
+ nextPage,
+ previousPage,
+ state: { pageIndex },
+ } = useTable(
+ {
+ columns,
+ data: dataLargestLiquidatedNotional ? dataLargestLiquidatedNotional : [],
+ initialState: { pageIndex: 0, pageSize: 5 },
+ },
+ usePagination
+ );
- return (
-
-
- Largest Liquidated Users By USD
-
-
-
- {headerGroups.map((headerGroup, i) => (
-
- {headerGroup.headers.map((column, j) => (
- {column.render('Header')} |
- ))}
-
- ))}
-
-
- {page.map((row: any, i: number) => {
- prepareRow(row);
- return (
-
- {row.cells.map((cell: any, j: any) => {
- return {cell.render('Cell')} | ;
- })}
-
- );
- })}
-
-
-
+
+
+ Largest Liquidated Users By USD
+
+
+
+
+ {headerGroups.map((headerGroup, i) => (
+
+ {headerGroup.headers.map((column, j) => (
+
+ {column.render('Header')}
+ |
+ ))}
+
+ ))}
+
+
+ {page.map((row: any, i: number) => {
+ prepareRow(row);
+ return (
+
+ {row.cells.map((cell: any, j: any) => {
+ return (
+
+ {cell.render('Cell')}
+ |
+ );
+ })}
+
+ );
+ })}
+
+
+
+
+
+ gotoPage(0)}
+ disabled={!canPreviousPage}
+ >
+ {'First'}
+
+ previousPage()}
+ disabled={!canPreviousPage}
+ >
+ {'<'}
+
+ nextPage()} disabled={!canNextPage}>
+ {'>'}
+
+ gotoPage(pageCount - 1)}
+ disabled={!canNextPage}
>
-
-
- gotoPage(0)} disabled={!canPreviousPage}>
- {"First"}
-
- previousPage()} disabled={!canPreviousPage}>
- {"<"}
-
- nextPage()} disabled={!canNextPage}>
- {">"}
-
- gotoPage(pageCount - 1)} disabled={!canNextPage}>
- {"Last"}
-
-
-
-
- Page: {pageIndex + 1} / {pageCount} | Results: {dataLargestLiquidatedNotional.length}
-
-
-
- );
-}
\ No newline at end of file
+ {'Last'}
+
+
+
+
+ Page: {pageIndex + 1} / {pageCount} | Results: {dataLargestLiquidatedNotional.length}
+
+
+
+ );
+}
diff --git a/components/home/tables/user-deposits.tsx b/components/home/tables/user-deposits.tsx
index ad319ed..c268eef 100644
--- a/components/home/tables/user-deposits.tsx
+++ b/components/home/tables/user-deposits.tsx
@@ -1,118 +1,155 @@
-import React from "react";
-import { useTable, usePagination } from "react-table";
-import { Box, Button, ButtonGroup, Flex, Table, Tbody, Td, Th, Thead, Tr, Text, useMediaQuery } from "@chakra-ui/react";
+import React from 'react';
+import { useTable, usePagination } from 'react-table';
import {
- largest_user_depositors,
-} from "../../../constants/api"
-import {
- formatNumberWithOptions
-} from "../../../helpers/index"
+ Box,
+ Button,
+ ButtonGroup,
+ Flex,
+ Table,
+ Tbody,
+ Td,
+ Th,
+ Thead,
+ Tr,
+ Text,
+ useMediaQuery,
+} from '@chakra-ui/react';
+import { largest_user_depositors } from '../../../constants/api';
+import { formatNumberWithOptions } from '../../../helpers/index';
import { useRequest } from '@/hooks/useRequest';
import { formatAddress } from '../../../utils/formatting';
import ChartWrapper from '../../common/chartWrapper';
-
-const REQUESTS = [
- largest_user_depositors
-]
+const REQUESTS = [largest_user_depositors];
export default function TableComponent() {
+ const [dataLargestUsersByDeposits] = useRequest(REQUESTS[0], [], 'table_data');
+ const [isMobile] = useMediaQuery('(max-width: 700px)');
- const [dataLargestUsersByDeposits, loadingLargestUsersByDeposits, errorLargestUsersByDeposits] = useRequest(REQUESTS[0], [], 'table_data');
- const [isMobile] = useMediaQuery('(max-width: 700px)');
-
- const columns = React.useMemo(() => [
- {
- Header: "Address",
- accessor: "name" as const,
- Cell: ({ value }: { value: string }) =>
-
-
- {formatAddress(value, isMobile ? 6 : 6)}
-
- ,
-
- },
- {
- Header: "Deposit USD",
- accessor: "value" as const,
- Cell: ({ value }: { value: number }) => ${formatNumberWithOptions(value)},
- },
- ], []);
+ const columns = React.useMemo(
+ () => [
+ {
+ Header: 'Address',
+ accessor: 'name' as const,
+ Cell: ({ value }: { value: string }) => (
+
+
+ {formatAddress(value, isMobile ? 6 : 6)}
+
+
+ ),
+ },
+ {
+ Header: 'Deposit USD',
+ accessor: 'value' as const,
+ Cell: ({ value }: { value: number }) => ${formatNumberWithOptions(value)},
+ },
+ ],
+ []
+ );
- const {
- getTableProps,
- getTableBodyProps,
- headerGroups,
- prepareRow,
- page,
- canPreviousPage,
- canNextPage,
- pageOptions,
- pageCount,
- gotoPage,
- nextPage,
- previousPage,
- state: { pageIndex, pageSize },
- } = useTable(
- {
- columns,
- data: dataLargestUsersByDeposits ? dataLargestUsersByDeposits : [],
- initialState: { pageIndex: 0, pageSize: 5 },
- },
- usePagination
- );
+ const {
+ getTableProps,
+ getTableBodyProps,
+ headerGroups,
+ prepareRow,
+ page,
+ canPreviousPage,
+ canNextPage,
+ pageOptions,
+ pageCount,
+ gotoPage,
+ nextPage,
+ previousPage,
+ state: { pageIndex, pageSize },
+ } = useTable(
+ {
+ columns,
+ data: dataLargestUsersByDeposits ? dataLargestUsersByDeposits : [],
+ initialState: { pageIndex: 0, pageSize: 5 },
+ },
+ usePagination
+ );
- return (
-
-
- Largest User Deposits By USD Value
-
-
-
- {headerGroups.map((headerGroup, i) => (
-
- {headerGroup.headers.map((column, j) => (
- {column.render('Header')} |
- ))}
-
- ))}
-
-
- {page.map((row: any, i: number) => {
- prepareRow(row);
- return (
-
- {row.cells.map((cell: any, j: any) => {
- return {cell.render('Cell')} | ;
- })}
-
- );
- })}
-
-
-
+
+
+ Largest User Deposits By USD Value
+
+
+
+
+ {headerGroups.map((headerGroup, i) => (
+
+ {headerGroup.headers.map((column, j) => (
+
+ {column.render('Header')}
+ |
+ ))}
+
+ ))}
+
+
+ {page.map((row: any, i: number) => {
+ prepareRow(row);
+ return (
+
+ {row.cells.map((cell: any, j: any) => {
+ return (
+
+ {cell.render('Cell')}
+ |
+ );
+ })}
+
+ );
+ })}
+
+
+
+
+
+ gotoPage(0)}
+ disabled={!canPreviousPage}
+ >
+ {'First'}
+
+ previousPage()}
+ disabled={!canPreviousPage}
+ >
+ {'<'}
+
+ nextPage()} disabled={!canNextPage}>
+ {'>'}
+
+ gotoPage(pageCount - 1)}
+ disabled={!canNextPage}
>
-
-
- gotoPage(0)} disabled={!canPreviousPage}>
- {"First"}
-
- previousPage()} disabled={!canPreviousPage}>
- {"<"}
-
- nextPage()} disabled={!canNextPage}>
- {">"}
-
- gotoPage(pageCount - 1)} disabled={!canNextPage}>
- {"Last"}
-
-
-
-
- Page: {pageIndex + 1} / {pageCount} | Results: {dataLargestUsersByDeposits.length}
-
-
-
- );
-}
\ No newline at end of file
+ {'Last'}
+
+
+
+
+ Page: {pageIndex + 1} / {pageCount} | Results: {dataLargestUsersByDeposits.length}
+
+
+
+ );
+}
diff --git a/components/home/tables/user-trade-count.tsx b/components/home/tables/user-trade-count.tsx
index 9487056..6217a8e 100644
--- a/components/home/tables/user-trade-count.tsx
+++ b/components/home/tables/user-trade-count.tsx
@@ -1,118 +1,153 @@
-import React from "react";
-import { useTable, usePagination } from "react-table";
-import { Box, Button, ButtonGroup, Flex, Table, Tbody, Td, Th, Thead, Tr, Text, useMediaQuery } from "@chakra-ui/react";
+import React from 'react';
+import { useTable, usePagination } from 'react-table';
import {
- largest_user_trade_count,
-} from "../../../constants/api"
-import {
- formatNumberWithOptions
-} from "../../../helpers/index"
+ Box,
+ Button,
+ ButtonGroup,
+ Flex,
+ Table,
+ Tbody,
+ Td,
+ Th,
+ Thead,
+ Tr,
+ Text,
+ useMediaQuery,
+} from '@chakra-ui/react';
+import { largest_user_trade_count } from '../../../constants/api';
+import { formatNumberWithOptions } from '../../../helpers/index';
import { useRequest } from '@/hooks/useRequest';
-import ChartWrapper from '../../common/chartWrapper';
import { formatAddress } from '../../../utils/formatting';
-
-const REQUESTS = [
- largest_user_trade_count
-]
+const REQUESTS = [largest_user_trade_count];
export default function TableComponent() {
+ const [dataLargestUsersByTradeCount] = useRequest(REQUESTS[0], [], 'table_data');
+ const [isMobile] = useMediaQuery('(max-width: 700px)');
- const [dataLargestUsersByTradeCount, loadingLargestUsersByTradeCount, errorLargestUsersByTradeCount] = useRequest(REQUESTS[0], [], 'table_data');
- const [isMobile] = useMediaQuery('(max-width: 700px)');
-
- const columns = React.useMemo(() => [
- {
- Header: "Address",
- accessor: "name" as const,
- Cell: ({ value }: { value: string }) =>
-
-
- {formatAddress(value, isMobile ? 6 : 6)}
-
- ,
-
- },
- {
- Header: "Number of Trades",
- accessor: "value" as const,
- Cell: ({ value }: { value: number }) => {formatNumberWithOptions(value)},
- },
- ], []);
+ const columns = React.useMemo(
+ () => [
+ {
+ Header: 'Address',
+ accessor: 'name' as const,
+ Cell: ({ value }: { value: string }) => (
+
+
+ {formatAddress(value, isMobile ? 6 : 6)}
+
+
+ ),
+ },
+ {
+ Header: 'Number of Trades',
+ accessor: 'value' as const,
+ Cell: ({ value }: { value: number }) => {formatNumberWithOptions(value)},
+ },
+ ],
+ []
+ );
- const {
- getTableProps,
- getTableBodyProps,
- headerGroups,
- prepareRow,
- page,
- canPreviousPage,
- canNextPage,
- pageOptions,
- pageCount,
- gotoPage,
- nextPage,
- previousPage,
- state: { pageIndex, pageSize },
- } = useTable(
- {
- columns,
- data: dataLargestUsersByTradeCount ? dataLargestUsersByTradeCount : [],
- initialState: { pageIndex: 0, pageSize: 5 },
- },
- usePagination
- );
+ const {
+ getTableProps,
+ getTableBodyProps,
+ headerGroups,
+ prepareRow,
+ page,
+ canPreviousPage,
+ canNextPage,
+ pageCount,
+ gotoPage,
+ nextPage,
+ previousPage,
+ state: { pageIndex },
+ } = useTable(
+ {
+ columns,
+ data: dataLargestUsersByTradeCount ? dataLargestUsersByTradeCount : [],
+ initialState: { pageIndex: 0, pageSize: 5 },
+ },
+ usePagination
+ );
- return (
-
-
- Largest Trade Count by Users
-
-
-
- {headerGroups.map((headerGroup, i) => (
-
- {headerGroup.headers.map((column, j) => (
- {column.render('Header')} |
- ))}
-
- ))}
-
-
- {page.map((row: any, i: number) => {
- prepareRow(row);
- return (
-
- {row.cells.map((cell: any, j: any) => {
- return {cell.render('Cell')} | ;
- })}
-
- );
- })}
-
-
-
+
+
+ Largest Trade Count By Users
+
+
+
+
+ {headerGroups.map((headerGroup, i) => (
+
+ {headerGroup.headers.map((column, j) => (
+
+ {column.render('Header')}
+ |
+ ))}
+
+ ))}
+
+
+ {page.map((row: any, i: number) => {
+ prepareRow(row);
+ return (
+
+ {row.cells.map((cell: any, j: any) => {
+ return (
+
+ {cell.render('Cell')}
+ |
+ );
+ })}
+
+ );
+ })}
+
+
+
+
+
+ gotoPage(0)}
+ disabled={!canPreviousPage}
+ >
+ {'First'}
+
+ previousPage()}
+ disabled={!canPreviousPage}
+ >
+ {'<'}
+
+ nextPage()} disabled={!canNextPage}>
+ {'>'}
+
+ gotoPage(pageCount - 1)}
+ disabled={!canNextPage}
>
-
-
- gotoPage(0)} disabled={!canPreviousPage}>
- {"First"}
-
- previousPage()} disabled={!canPreviousPage}>
- {"<"}
-
- nextPage()} disabled={!canNextPage}>
- {">"}
-
- gotoPage(pageCount - 1)} disabled={!canNextPage}>
- {"Last"}
-
-
-
-
- Page: {pageIndex + 1} / {pageCount} | Results: {dataLargestUsersByTradeCount.length}
-
-
-
- );
-}
\ No newline at end of file
+ {'Last'}
+
+
+
+
+ Page: {pageIndex + 1} / {pageCount} | Results: {dataLargestUsersByTradeCount.length}
+
+
+
+ );
+}
diff --git a/constants/api.ts b/constants/api.ts
index 1a35682..3fefe63 100644
--- a/constants/api.ts
+++ b/constants/api.ts
@@ -1,48 +1,54 @@
-export const total_users = "/hyperliquid/total_users"; // Retrieves the total number of users.
-export const total_usd_volume = "/hyperliquid/total_usd_volume"; // Retrieves the total USD trading volume.
-export const total_deposits = "/hyperliquid/total_deposits"; // Retrieves the total amount of deposits.
-export const total_withdrawals = "/hyperliquid/total_withdrawals"; // Retrieves the total amount of withdrawals.
-export const total_notional_liquidated = "/hyperliquid/total_notional_liquidated"; // Retrieves the total notional value liquidated.
-export const cumulative_usd_volume = "/hyperliquid/cumulative_usd_volume"; // Retrieves the cumulative USD trading volume over time.
-export const daily_usd_volume = "/hyperliquid/daily_usd_volume"; // Retrieves the daily USD trading volume over time.
-export const daily_usd_volume_by_coin = "/hyperliquid/daily_usd_volume_by_coin"; // Retrieves the daily USD trading volume by coin over time.
-export const daily_usd_volume_by_crossed = "/hyperliquid/daily_usd_volume_by_crossed"; // Retrieves the daily USD trading volume by crossed over time.
-export const daily_usd_volume_by_user = "/hyperliquid/daily_usd_volume_by_user"; // Retrieves the daily USD trading volume by top 10 user and the rest are summed and marked as other.
-
-export const cumulative_trades = "/hyperliquid/cumulative_trades"; // Retrieves the cumulative number of trades over time. The line chart of Cumulative total trades chart.
-
-export const daily_trades = "/hyperliquid/daily_trades"; // Retrieves the daily number of trades.
-export const daily_trades_by_coin = "/hyperliquid/daily_trades_by_coin"; // Retrieves the daily number of trades by coin.
-export const daily_trades_by_crossed = "/hyperliquid/daily_trades_by_crossed"; // Retrieves the daily number of trades by crossed.
-export const daily_trades_by_user = "/hyperliquid/daily_trades_by_user"; // Retrieves the daily number of trades by top 10 user and the rest are summed and marked as other.
-export const user_pnl = "/hyperliquid/user_pnl"; // Retrieves the profit and loss (PnL) for all users daily.
-export const cumulative_user_pnl = "/hyperliquid/cumulative_user_pnl"; // Retrieves the cumulative PnL for all users over time.
-export const hlp_liquidator_pnl = "/hyperliquid/hlp_liquidator_pnl"; // Retrieves the PnL for liquidators daily.
-export const hlp_liquidator_pnl_false = "/hyperliquid/hlp_liquidator_pnl?is_hlp=false"; // Retrieves the PnL for liquidators daily.
-
-export const cumulative_hlp_liquidator_pnl = "/hyperliquid/cumulative_hlp_liquidator_pnl"; // Retrieves the cumulative PnL for liquidators over time.
-
-export const cumulative_hlp_liquidator_pnl_false = "/hyperliquid/cumulative_hlp_liquidator_pnl?is_hlp=false"; // Retrieves the cumulative PnL for liquidators over time.
-
-export const cumulative_liquidated_notional = "/hyperliquid/cumulative_liquidated_notional"; // Retrieves the cumulative liquidated notional value over time.
-export const daily_unique_users = "/hyperliquid/daily_unique_users"; // Retrieves the daily number of unique users.
-export const cumulative_users = "/hyperliquid/cumulative_users"; // Retrieves the cumulative number of users over time
-
-export const daily_unique_users_by_coin = "/hyperliquid/daily_unique_users_by_coin";
-
-export const cumulative_inflow = "/hyperliquid/cumulative_inflow"; // Retrieves the cumulative inflow of funds over time.
-export const open_interest = "/hyperliquid/open_interest"; // Retrieves the open interest data.
-export const funding_rate = "/hyperliquid/funding_rate"; // Retrieves the funding rate data.
-export const cumulative_new_users = "/hyperliquid/cumulative_new_users"; // Retrieves the cumulative number of unique users over time.
-export const daily_inflow = "/hyperliquid/daily_inflow"; // Retrieves the daily inflow of funds.
-export const liquidity_per_symbol = "/hyperliquid/liquidity_per_symbol"; // Retrieves the liquidity data per symbol.
-export const largest_users_by_usd_volume = "/hyperliquid/largest_users_by_usd_volume"; // Retrieves the largest users by USD trading volume.
-export const largest_user_depositors = "/hyperliquid/largest_user_depositors"; // Retrieves the largest user depositors.
-export const largest_liquidated_notional_by_user = "/hyperliquid/largest_liquidated_notional_by_user"; // Retrieves the largest liquidated notional by user.
-export const largest_user_trade_count = "/hyperliquid/largest_user_trade_count"; // Retrieves the users with the highest trade counts.
-
-export const daily_notional_liquidated_total = "/hyperliquid/daily_notional_liquidated_total";
-export const daily_notional_liquidated_by_leverage_type = "/hyperliquid/daily_notional_liquidated_by_leverage_type";
-
-export const liquidity_by_coin = "/hyperliquid/liquidity_by_coin";
-export const daily_notional_liquidated_by_coin = "/hyperliquid/daily_notional_liquidated_by_coin";
\ No newline at end of file
+export const total_users = 'total_users'; // Retrieves the total number of users.
+export const total_usd_volume = 'total_usd_volume'; // Retrieves the total USD trading volume.
+export const total_deposits = 'total_deposits'; // Retrieves the total amount of deposits.
+export const total_withdrawals = 'total_withdrawals'; // Retrieves the total amount of withdrawals.
+export const total_notional_liquidated = 'total_notional_liquidated'; // Retrieves the total notional value liquidated.
+export const cumulative_usd_volume = 'cumulative_usd_volume'; // Retrieves the cumulative USD trading volume over time.
+export const daily_usd_volume = 'daily_usd_volume'; // Retrieves the daily USD trading volume over time.
+export const daily_usd_volume_by_coin = 'daily_usd_volume_by_coin'; // Retrieves the daily USD trading volume by coin over time.
+export const daily_usd_volume_by_crossed = 'daily_usd_volume_by_crossed'; // Retrieves the daily USD trading volume by crossed over time.
+export const daily_usd_volume_by_user = 'daily_usd_volume_by_user'; // Retrieves the daily USD trading volume by top 10 user and the rest are summed and marked as other.
+
+export const cumulative_trades = 'cumulative_trades'; // Retrieves the cumulative number of trades over time. The line chart of Cumulative total trades chart.
+
+export const daily_trades = 'daily_trades'; // Retrieves the daily number of trades.
+export const daily_trades_by_coin = 'daily_trades_by_coin'; // Retrieves the daily number of trades by coin.
+export const daily_trades_by_crossed = 'daily_trades_by_crossed'; // Retrieves the daily number of trades by crossed.
+export const daily_trades_by_user = 'daily_trades_by_user'; // Retrieves the daily number of trades by top 10 user and the rest are summed and marked as other.
+export const user_pnl = 'user_pnl'; // Retrieves the profit and loss (PnL) for all users daily.
+export const cumulative_user_pnl = 'cumulative_user_pnl'; // Retrieves the cumulative PnL for all users over time.
+export const hlp_liquidator_pnl = 'hlp_liquidator_pnl'; // Retrieves the PnL for liquidators daily.
+export const hlp_liquidator_pnl_false = 'hlp_liquidator_pnl?is_hlp=false'; // Retrieves the PnL for liquidators daily.
+
+export const cumulative_hlp_liquidator_pnl = 'cumulative_hlp_liquidator_pnl'; // Retrieves the cumulative PnL for liquidators over time.
+
+export const cumulative_hlp_liquidator_pnl_false = 'cumulative_hlp_liquidator_pnl?is_hlp=false'; // Retrieves the cumulative PnL for liquidators over time.
+
+export const cumulative_liquidated_notional = 'cumulative_liquidated_notional'; // Retrieves the cumulative liquidated notional value over time.
+export const daily_unique_users = 'daily_unique_users'; // Retrieves the daily number of unique users.
+export const cumulative_users = 'cumulative_users'; // Retrieves the cumulative number of users over time
+
+export const daily_unique_users_by_coin = 'daily_unique_users_by_coin';
+
+export const cumulative_inflow = 'cumulative_inflow'; // Retrieves the cumulative inflow of funds over time.
+export const open_interest = 'open_interest'; // Retrieves the open interest data.
+export const funding_rate = 'funding_rate'; // Retrieves the funding rate data.
+export const cumulative_new_users = 'cumulative_new_users'; // Retrieves the cumulative number of unique users over time.
+export const daily_inflow = 'daily_inflow'; // Retrieves the daily inflow of funds.
+export const liquidity_per_symbol = 'liquidity_per_symbol'; // Retrieves the liquidity data per symbol.
+export const largest_users_by_usd_volume = 'largest_users_by_usd_volume'; // Retrieves the largest users by USD trading volume.
+export const largest_user_depositors = 'largest_user_depositors'; // Retrieves the largest user depositors.
+export const largest_liquidated_notional_by_user = 'largest_liquidated_notional_by_user'; // Retrieves the largest liquidated notional by user.
+export const largest_user_trade_count = 'largest_user_trade_count'; // Retrieves the users with the highest trade counts.
+
+export const daily_notional_liquidated_total = 'daily_notional_liquidated_total';
+export const daily_notional_liquidated_by_leverage_type =
+ 'daily_notional_liquidated_by_leverage_type';
+
+export const liquidity_by_coin = 'liquidity_by_coin';
+export const daily_notional_liquidated_by_coin = 'daily_notional_liquidated_by_coin';
+
+export const total_accrued_fees = 'total_accrued_fees';
+export const total_volume = 'total_volume';
+export const hlp_positions = 'hlp_positions';
+export const asset_ctxs = 'asset_ctxs';
diff --git a/constants/index.ts b/constants/index.ts
index 5631e4f..0822ee4 100644
--- a/constants/index.ts
+++ b/constants/index.ts
@@ -1,10 +1,12 @@
-export const CHART_HEIGHT = 400
-export const YAXIS_WIDTH = 60
+export const CHART_HEIGHT = 400;
+export const YAXIS_WIDTH = 60;
-export const GREEN = '#62B143'
-export const RED = '#DC0428'
+export const GREEN = '#62B143';
+export const RED = '#DC0428';
+export const ORANGE = '#ffa500';
+export const BLUE = '#008ECC';
export const BRIGHT_GREEN = '#97ffe4';
export const BRAND_GREEN_2 = '#4E8174';
export const BRAND_GREEN_3 = '#72BDAB';
-export const excluded_percentage_tooltip = ['daily_unique_users'];
\ No newline at end of file
+export const excluded_percentage_tooltip = ['daily_unique_users'];
diff --git a/constants/tokens.ts b/constants/tokens.ts
index b37b8dd..d54f685 100644
--- a/constants/tokens.ts
+++ b/constants/tokens.ts
@@ -1,37 +1,20 @@
-const TOKEN_COLORS: any = {
- "BNB": "#ebc509",
- "BTC": "#F2A900",
- "DOGE": "#cb9800",
- "ETH": "#8A94B1",
- "INJ": "#0386FA",
- "KPEPE": "#509844",
- "MATIC": "#8C35D5",
- "Other": "#BBBAC6",
- "SOL": "#C867F0",
- "AVAX": "#e74242",
- "LTC": "#CCCCCC",
- "ARB": "#FCA100",
- "LINK": "#81D2FD",
- "APE": "#4087BE",
- "ATOM": "#FCA100",
- "CFX": "#F3654E",
- "CRV": "#850087",
- "DYDX": "#BE586C",
- "FTM": "#568EC0",
- "GMX": "#59C782",
- "LDO": "#DB6ED7",
- "OP": "#7F0182",
- "RNDR": "#FFA300",
- "SNX": "#498548",
- "STX": "#578374",
- "SUI": "#6A807A"
-}
+import * as CryptoJS from 'crypto-js';
+
+export const initialTokensSelected = ['BTC', 'ETH', 'SOL'];
+export const initialTokensSelectedWithOther = [...initialTokensSelected, 'Other'];
+
+export function getTokenColor(token: string): string {
+ if (token == 'Other') {
+ return 'pink';
+ }
+ if (token == 'SOL') {
+ return 'orange';
+ }
+ // Use the CryptoJS library to get the MD5 hash of the string
+ let hash = CryptoJS.MD5('col' + token);
-export const getTokenHex = (token: string) => {
- const symbol = token.toUpperCase()
- if (TOKEN_COLORS[symbol]) {
- return TOKEN_COLORS[symbol];
- } else {
- return "pink"
- }
-}
\ No newline at end of file
+ // Convert the hash into a hex string
+ let color = hash.toString(CryptoJS.enc.Hex).substr(0, 6);
+
+ return '#' + color;
+}
diff --git a/contexts/data.tsx b/contexts/data.tsx
index 2fbad6b..89e7636 100644
--- a/contexts/data.tsx
+++ b/contexts/data.tsx
@@ -1,46 +1,42 @@
-import React, { useState } from "react";
-import strftime from "strftime";
+import React, { useState } from 'react';
+import strftime from 'strftime';
type Dates = {
- to: string | undefined;
- from: string | undefined;
-}
+ to: string | undefined;
+ from: string | undefined;
+};
export interface State {
- dates: Dates;
- setDates: (dates: Dates) => void;
+ dates: Dates;
+ setDates: (dates: Dates) => void;
}
const DATE_NOW = new Date();
const DATE_TO = strftime('%Y-%m-%d', DATE_NOW);
export const DataContext = React.createContext({
+ dates: {
+ from: undefined,
+ to: undefined,
+ },
+ setDates: (dates: Dates) => {},
+});
+
+export const DataContextProvider = (props: any) => {
+ const setDates = (dates: Dates) => {
+ setState({ ...state, dates });
+ };
+
+ const initState: State = {
dates: {
- from: undefined,
- to: undefined,
+ // from: '2023-06-14',
+ from: '2024-03-11',
+ to: DATE_TO,
},
- setDates: (dates: Dates) => { },
-})
+ setDates: setDates,
+ };
-export const DataContextProvider = (props: any) => {
+ const [state, setState] = useState(initState);
- const setDates = (dates: Dates) => {
- setState({ ...state, dates });
- }
-
- const initState: State = {
- dates: {
- from: '2023-05-10',
- to: DATE_TO,
- },
- setDates: setDates,
- };
-
- const [state, setState] = useState(initState);
-
- return (
-
- {props.children}
-
- )
-}
\ No newline at end of file
+ return {props.children};
+};
diff --git a/helpers/index.ts b/helpers/index.ts
index faf294e..dd41f7c 100644
--- a/helpers/index.ts
+++ b/helpers/index.ts
@@ -1,16 +1,40 @@
-import strftime from 'strftime'
+import strftime from 'strftime';
-import { excluded_percentage_tooltip } from "../constants"
+import { excluded_percentage_tooltip } from '../constants';
-const numberFmt0: Intl.NumberFormat = new Intl.NumberFormat('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 0 });
-const numberFmt1: Intl.NumberFormat = new Intl.NumberFormat('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 1 });
-const numberFmt2: Intl.NumberFormat = new Intl.NumberFormat('en-US', { minimumFractionDigits: 3, maximumFractionDigits: 3 });
-const currencyFmt0: Intl.NumberFormat = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 0, maximumFractionDigits: 0 });
-const currencyFmt1: Intl.NumberFormat = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 1, maximumFractionDigits: 1 });
-const currencyFmt2: Intl.NumberFormat = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 });
+const numberFmt0: Intl.NumberFormat = new Intl.NumberFormat('en-US', {
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+});
+const numberFmt1: Intl.NumberFormat = new Intl.NumberFormat('en-US', {
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 1,
+});
+const numberFmt2: Intl.NumberFormat = new Intl.NumberFormat('en-US', {
+ minimumFractionDigits: 3,
+ maximumFractionDigits: 3,
+});
+const currencyFmt0: Intl.NumberFormat = new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: 'USD',
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+});
+const currencyFmt1: Intl.NumberFormat = new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: 'USD',
+ minimumFractionDigits: 1,
+ maximumFractionDigits: 1,
+});
+const currencyFmt2: Intl.NumberFormat = new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: 'USD',
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+});
function getNumberFormatBasedOnValue(value: number): Intl.NumberFormat {
- const absValue = Math.abs(value)
+ const absValue = Math.abs(value);
if (absValue < 10) {
return numberFmt2;
} else if (absValue < 1000) {
@@ -21,7 +45,7 @@ function getNumberFormatBasedOnValue(value: number): Intl.NumberFormat {
}
function getCurrencyFormatBasedOnValue(value: number): Intl.NumberFormat {
- const absValue = Math.abs(value)
+ const absValue = Math.abs(value);
if (absValue < 10) {
return currencyFmt2;
} else if (absValue < 1000) {
@@ -36,143 +60,177 @@ interface FormatNumberOpts {
compact?: boolean;
}
-export const formatNumberWithOptions = (value: number, opts: FormatNumberOpts = {}): string => {
- const currency = !!opts.currency
- const compact = !!opts.compact
+export const formatNumberWithOptions = (
+ value: number,
+ opts: FormatNumberOpts = {},
+ fixedDecimals?: number
+): string => {
+ const currency = !!opts.currency;
+ const compact = !!opts.compact;
+ const sign = value < 0 ? '-' : '';
+ const absoluteValue = Math.abs(value);
if (currency && !compact) {
- return getCurrencyFormatBasedOnValue(value).format(value);
+ return sign + getCurrencyFormatBasedOnValue(absoluteValue).format(absoluteValue);
}
- const display = compact ? formatNumberToCompactForm(value) : getNumberFormatBasedOnValue(value).format(value);
+ const display = compact
+ ? formatNumberToCompactForm(absoluteValue, fixedDecimals)
+ : getNumberFormatBasedOnValue(absoluteValue).format(absoluteValue);
+
if (currency) {
- return `$${display}`;
+ return `${sign}$${display}`;
}
return display;
-}
+};
-export const formatNumberToCompactForm = (value: number): string => {
- const abs = Math.abs(value)
+export const formatNumberToCompactForm = (value: number, fixedDecimals?: number): string => {
+ const abs = Math.abs(value);
if (abs >= 1e9) {
- return `${(value / 1e9).toFixed(abs < 1e10 ? 2 : 1)}B`;
+ return `${(value / 1e9).toFixed(fixedDecimals !== undefined ? 0 : abs < 1e10 ? 2 : 1)}B`;
}
if (abs >= 1e6) {
- return `${(value / 1e6).toFixed(abs < 1e7 ? 2 : 1)}M`;
+ return `${(value / 1e6).toFixed(fixedDecimals !== undefined ? 0 : abs < 1e7 ? 2 : 1)}M`;
}
if (abs >= 1e3) {
- return `${(value / 1e3).toFixed(abs < 1e4 ? 2 : 1)}K`;
+ return `${(value / 1e3).toFixed(fixedDecimals !== undefined ? 0 : abs < 1e4 ? 2 : 1)}K`;
}
return `${value.toFixed(1)}`;
-}
-
+};
export const tooltipLabelFormatterPercent = (label: any, args: any): any => {
- const hide = args && args[0] && args[0].payload && args[0].payload.unit && (args[0].payload.unit === '%' || args[0].payload.unit === 'single') ;
+ const hide =
+ args &&
+ args[0] &&
+ args[0].payload &&
+ args[0].payload.unit &&
+ (args[0].payload.unit === '%' || args[0].payload.unit === 'single');
if (hide) return '';
if (!label) return;
if (label.constructor !== Date) {
- label = new Date(label * 1000)
+ label = new Date(label * 1000);
}
- const item = args && args[0] && args[0].payload && args[0]
- const dateFmtString = 'Total %d-%m-%y : '
- const date = strftime(dateFmtString, label)
- const all = item && (item.payload.all)
+ const item = args && args[0] && args[0].payload && args[0];
+ const date = `Total ${label.toLocaleDateString()} : `;
+ const all = item && item.payload.all;
if (all) {
- return `${date} ${formatNumberWithOptions(all, {compact: true})}%`
+ return `${date} ${formatNumberWithOptions(all, { compact: true })}%`;
}
- return date
-}
+ return date;
+};
-export const tooltipLabelFormatter = (label: any, args: any): any => {
- const hide = args && args[0] && args[0].payload && args[0].payload.unit && (args[0].payload.unit === '%' || args[0].payload.unit === 'single') ;
+export const tooltipLabelFormatter = (label: any, args: any, key?: string): any => {
+ const hide =
+ args &&
+ args[0] &&
+ args[0].payload &&
+ args[0].payload.unit &&
+ (args[0].payload.unit === '%' || args[0].payload.unit === 'single');
if (hide) return '';
if (!label) return;
if (label.constructor !== Date) {
- label = new Date(label * 1000)
+ label = new Date(label * 1000);
}
- const item = args && args[0] && args[0].payload && args[0]
- const dateFmtString = 'Total %d-%m-%y :'
- const date = strftime(dateFmtString, label)
- const all = item && (item.payload.all)
- if (all) {
- if (item && item.unit === '$' || item.payload && item.payload.unit === '$') {
- return `${date} ${formatNumberWithOptions(all, {currency: true, compact: true})}`
+ const item = args && args[0] && args[0].payload && args[0];
+ const date = `Total ${label.toLocaleDateString()} : `;
+
+ let value;
+ if (key) {
+ value = item && item.payload[key];
+ } else {
+ value = item && item.payload.all;
+ }
+ if (value) {
+ if ((item && item.unit === '$') || (item.payload && item.payload.unit === '$')) {
+ return `${date} ${formatNumberWithOptions(value, { currency: true, compact: true })}`;
}
- return `${date} ${formatNumberWithOptions(all, {compact: true})}`
-
+ return `${date} ${formatNumberWithOptions(value, { compact: true })}`;
}
- return date
-}
+ return date;
+};
export const xAxisFormatter = (label: any, args: any): any => {
if (!label) return;
if (label.constructor !== Date) {
- label = new Date(label * 1000)
+ label = new Date(label * 1000);
}
- const item = args && args[0] && args[0].payload && args[0]
- const dateFmtString = '%d-%m'
- const date = strftime(dateFmtString, label)
- const all = item && (item.payload.all)
+ const item = args && args[0] && args[0].payload && args[0];
+ const dateFmtString = '%d-%m';
+ const date = strftime(dateFmtString, label);
+ const all = item && item.payload.all;
if (all) {
- if (item && item.unit === '%' || item.payload && item.payload.unit === '%') {
- return `${date} ${formatNumberWithOptions(all, {compact: true})}%`
+ if ((item && item.unit === '%') || (item.payload && item.payload.unit === '%')) {
+ return `${date} ${formatNumberWithOptions(all, { compact: true })}%`;
}
- if (item && item.unit === '$' || item.payload && item.payload.unit === '$') {
- return `${date} ${formatNumberWithOptions(all, {currency: true, compact: true})}`
+ if ((item && item.unit === '$') || (item.payload && item.payload.unit === '$')) {
+ return `${date} ${formatNumberWithOptions(all, { currency: true, compact: true })}`;
}
- return `${date} ${formatNumberWithOptions(all, {compact: true})}`
+ return `${date} ${formatNumberWithOptions(all, { compact: true })}`;
}
- return date
-}
+ return date;
+};
export const yaxisFormatterPercent = (value: number): string => {
return value.toFixed(0) + '%';
-}
-
+};
export const formatterPercent = (value: number): string => {
return value.toFixed(2) + '%';
-}
+};
export const yaxisFormatterNumber = (value: number): string => {
return formatNumberToCompactForm(value);
-}
+};
export const yaxisFormatter = (value: number): string => {
-
return formatNumberWithOptions(value, { currency: true, compact: true });
-}
+};
export const tooltipFormatterNumber = (value: number | string): string => {
return formatNumberWithOptions(Number(value), { compact: true });
-}
+};
export const tooltipFormatterCurrency = (value: number | string): string => {
return formatNumberWithOptions(Number(value), { currency: true, compact: true });
-}
+};
+
+export const tooltipFormatterLongShort = (value: number | string): string => {
+ let formattedNumber = formatNumberToCompactForm(Math.abs(+value));
+ if (+value < 0) {
+ return `Short $${formattedNumber}`;
+ } else {
+ return `Long $${formattedNumber}`;
+ }
+};
+
+export const tooltipFormatterDate = (label: any) => {
+ const date = new Date(label);
+ return `Date : ${date.toLocaleDateString()}`;
+};
export const tooltipFormatterPercent = (value: number): string => {
return value.toFixed(2) + '%';
-}
+};
export const tooltipFormatter = (value: any, name: any, item: any) => {
if (excluded_percentage_tooltip.indexOf(item.dataKey) > -1) {
return `${value}`;
}
- if (item && item.unit === '%' || item.payload && item.payload.unit === '%') {
+ if ((item && item.unit === '%') || (item.payload && item.payload.unit === '%')) {
return `${value.toFixed(2)}%`;
}
if (item && !item.unit) {
return formatNumberWithOptions(item.value, { currency: false });
}
- return formatNumberWithOptions(value, { currency: true })
-}
-
+ return formatNumberWithOptions(value, { currency: true });
+};
-export const tooltipLabelFormatterUnits = (label: number | Date, args?: any[]): string | undefined => {
-
+export const tooltipLabelFormatterUnits = (
+ label: number | Date,
+ args?: any[]
+): string | undefined => {
if (!label) {
return label as any | undefined;
}
@@ -190,10 +248,10 @@ export const tooltipLabelFormatterUnits = (label: number | Date, args?: any[]):
return date;
}
- const all = item && (item.payload.all);
+ const all = item && item.payload.all;
if (label.constructor !== Date) {
return all ? `${label}, total: ${all}` : label.toString();
}
return all ? `${date}, total: ${all}` : date;
-}
\ No newline at end of file
+};
diff --git a/helpers/utils.ts b/helpers/utils.ts
new file mode 100644
index 0000000..63976cb
--- /dev/null
+++ b/helpers/utils.ts
@@ -0,0 +1,62 @@
+import { CoinSelector } from '../components/common/chartWrapper';
+
+const coinSelectorsSort = (a: CoinSelector, b: CoinSelector, specialKey?: string) => {
+ if (a.name === specialKey) {
+ return -1;
+ }
+ if (b.name === specialKey) {
+ return 1;
+ }
+ if (a.isChecked !== b.isChecked) {
+ return a.isChecked ? -1 : 1;
+ }
+ return a.name.localeCompare(b.name);
+};
+
+export const createCoinSelectors = (
+ coinKeys: string[],
+ coinsSelected: string[],
+ setCoinsSelected: (arg: string[]) => any,
+ formatData: ((arg: string[]) => any) | (() => any),
+ noOtherOption?: boolean,
+ specialKey?: string
+) => {
+ const emptySelection = noOtherOption ? [] : ['Other'];
+ const deselectAll = {
+ name: 'Deselect All',
+ event: () => {
+ setCoinsSelected(emptySelection);
+ formatData(emptySelection);
+ },
+ isChecked: coinsSelected.length === 0,
+ };
+
+ const coinSelectors = coinKeys.map((coinKey: string) => {
+ return {
+ name: coinKey,
+ event: () => {
+ let newCoinsSelected = [...coinsSelected];
+ if (coinsSelected.includes(coinKey)) {
+ newCoinsSelected = newCoinsSelected.filter((e) => e !== coinKey);
+ } else {
+ newCoinsSelected.push(coinKey);
+ }
+
+ if (typeof formatData === 'function') {
+ if (formatData.length > 0) {
+ formatData(newCoinsSelected);
+ } else {
+ const noArgsFormatData = formatData as () => any;
+ noArgsFormatData();
+ }
+ }
+ setCoinsSelected(newCoinsSelected);
+ },
+ isChecked: coinsSelected.includes(coinKey),
+ };
+ });
+
+ const sortedCoinSelectors = coinSelectors.sort((a, b) => coinSelectorsSort(a, b, specialKey));
+
+ return [deselectAll, ...sortedCoinSelectors];
+};
diff --git a/hooks/useIsMobile.ts b/hooks/useIsMobile.ts
new file mode 100644
index 0000000..793f162
--- /dev/null
+++ b/hooks/useIsMobile.ts
@@ -0,0 +1,16 @@
+import { useEffect, useState } from 'react';
+
+export function useIsMobile() {
+ const [isMobile, setIsMobile] = useState(true);
+ const handleWindowResize = () => {
+ setIsMobile(window.innerWidth < 900);
+ };
+
+ useEffect(() => {
+ handleWindowResize();
+ window.addEventListener('resize', handleWindowResize);
+ return () => window.removeEventListener('resize', handleWindowResize);
+ }, []);
+
+ return isMobile;
+}
diff --git a/hooks/useRequest.ts b/hooks/useRequest.ts
index 656ff7a..5ec86a6 100644
--- a/hooks/useRequest.ts
+++ b/hooks/useRequest.ts
@@ -1,49 +1,69 @@
-import { useState, useEffect, useContext } from "react";
-import { DataContext } from "../contexts/data";
+import { useState, useEffect, useContext, useCallback } from 'react';
+import { DataContext } from '../contexts/data';
-const fetcher = (url: string) => fetch(url).then(res => res.json())
+let runningRequests: number = 0;
+const MAX_CONCURRENT_REQUESTS = 4;
+
+const fetcher = (url: string) => fetch(url).then((res) => res.json());
export function useRequest(url: string, defaultValue: any, key?: string, dontRefetch?: boolean) {
const [loading, setLoading] = useState(true);
- const [error, setError] = useState();
const [data, setData] = useState(defaultValue);
const dataContext = useContext(DataContext);
- const urlHasParams = url.indexOf("?") !== -1;
-
- let params = '';
- if (dataContext.dates.from) {
- if (!urlHasParams) { params += '?' }
- if (urlHasParams) { params += '&' }
- params += `start_date=${dataContext.dates.from}`;
- }
- if (dataContext.dates.to) {
- params += `&end_date=${dataContext.dates.to}`;
- }
-
- const init = async () => {
- try {
- setLoading(true)
- const data = await fetcher(`${process.env.NEXT_PUBLIC_API_URL}${url}${params}`);
- if (key && data[key]) {
- setData(data[key])
- } else {
- setData(data)
- }
- } catch (error) {
- console.error(error)
- setError(error)
+
+ const init = useCallback(async () => {
+ const request = async () => {
+ if (runningRequests > MAX_CONCURRENT_REQUESTS) {
+ setTimeout(() => {
+ request();
+ }, 500);
+ return;
+ }
+
+ runningRequests++;
+ setLoading(true);
+ try {
+ const data = await fetcher(`${process.env.NEXT_PUBLIC_DAT_URL}/${url}`);
+ runningRequests--;
+
+ const dataFromKey = key ? data[key] : data?.table_data || data?.chart_data || data;
+ setData(
+ dataFromKey.filter
+ ? dataFromKey.filter((line: any) => {
+ if (!line.time) {
+ return true;
+ }
+ if (
+ dataContext.dates.from &&
+ new Date(line.time) < new Date(dataContext.dates.from)
+ ) {
+ return false;
+ }
+ if (dataContext.dates.to && new Date(line.time) > new Date(dataContext.dates.to)) {
+ return false;
+ }
+ return true;
+ })
+ : dataFromKey
+ );
+ setLoading(false);
+ } catch (e) {
+ console.log(e);
+ runningRequests--;
}
- setLoading(false)
- }
+ };
+
+ request();
+ }, [dataContext.dates.from, dataContext.dates.to, key, url]);
useEffect(() => {
init();
- }, [url])
+ }, [init, url]);
useEffect(() => {
if (dontRefetch) return;
init();
- }, [dataContext.dates.from, dataContext.dates.to])
+ }, [dataContext.dates.from, dataContext.dates.to, dontRefetch, init]);
- return [data, loading, error]
-}
\ No newline at end of file
+ return [data, loading];
+}
diff --git a/next.config.js b/next.config.js
index f2525fe..4f2be75 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,15 +1,15 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
- reactStrictMode: false,
- webpack: (config, { isServer, defaultLoaders }) => {
- config.module.rules.push({
- test: /\.svg$/,
- use: ['@svgr/webpack'],
- });
- return config;
- },
- };
-
- module.exports = nextConfig;
-
\ No newline at end of file
+ output: 'export',
+ reactStrictMode: false,
+ webpack: (config, { isServer, defaultLoaders }) => {
+ config.module.rules.push({
+ test: /\.svg$/,
+ use: ['@svgr/webpack'],
+ });
+ return config;
+ },
+};
+
+module.exports = nextConfig;
diff --git a/package-lock.json b/package-lock.json
index 7e7ff36..a27081d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
- "name": "hyperliquid-web",
- "version": "0.1.0",
+ "name": "hyperliquid-stats-web",
+ "version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
- "name": "hyperliquid-web",
- "version": "0.1.0",
+ "name": "hyperliquid-stats-web",
+ "version": "1.0.0",
"dependencies": {
"@chakra-ui/icons": "^2.0.19",
"@chakra-ui/next-js": "^2.1.3",
@@ -14,19 +14,26 @@
"@emotion/react": "^11.11.0",
"@emotion/styled": "^11.11.0",
"@svgr/webpack": "^8.0.1",
+ "@types/crypto-js": "^4.1.1",
"@types/node": "20.2.5",
"@types/react": "18.2.7",
"@types/react-dom": "18.2.4",
"@types/strftime": "^0.9.4",
"axios": "^1.4.0",
+ "crypto-js": "^4.1.1",
"eslint": "8.41.0",
"eslint-config-next": "13.4.4",
"ethers": "^5.7.2",
+ "express": "^4.19.2",
+ "express-rate-limit": "^7.2.0",
"framer-motion": "^10.12.16",
"lodash": "^4.17.21",
+ "mixpanel-browser": "^2.47.0",
"moment": "^2.29.4",
- "next": "13.4.4",
+ "next": "^13.4.4",
"polished": "^4.2.2",
+ "prettier": "^3.0.0",
+ "rate-limiter-flexible": "^5.0.3",
"react": "18.2.0",
"react-date-range": "^1.4.0",
"react-dom": "18.2.0",
@@ -41,6 +48,7 @@
"devDependencies": {
"@types/console-log-level": "^1.4.2",
"@types/lodash": "^4.14.195",
+ "@types/mixpanel-browser": "^2.47.1",
"@types/moment": "^2.13.0",
"@types/react-date-range": "^1.4.4",
"@types/react-gtm-module": "^2.0.1",
@@ -4399,6 +4407,11 @@
"integrity": "sha512-TnhDAntcJthcCMrR3OAKAUjgHyQgoms1yaBJepGv+BtXi8PLf8aX2L/NMCfofRTpVqW0bLklpGTsuqmUSCR2Uw==",
"dev": true
},
+ "node_modules/@types/crypto-js": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz",
+ "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA=="
+ },
"node_modules/@types/d3-array": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.5.tgz",
@@ -4471,6 +4484,12 @@
"@types/lodash": "*"
}
},
+ "node_modules/@types/mixpanel-browser": {
+ "version": "2.47.1",
+ "resolved": "https://registry.npmjs.org/@types/mixpanel-browser/-/mixpanel-browser-2.47.1.tgz",
+ "integrity": "sha512-4Oif1hbjmStA6KI1OLer19rbKjlPd67ujp5Wipfa7rIskA/lOFZbp6kYsPjXD0f5AiiCQTbf0R/SnnzgOaKrbA==",
+ "dev": true
+ },
"node_modules/@types/moment": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/@types/moment/-/moment-2.13.0.tgz",
@@ -4685,6 +4704,18 @@
"resolved": "https://registry.npmjs.org/@zag-js/focus-visible/-/focus-visible-0.2.2.tgz",
"integrity": "sha512-0j2gZq8HiZ51z4zNnSkF1iSkqlwRDvdH+son3wHdoz+7IUdMN/5Exd4TxMJ+gq2Of1DiXReYLL9qqh2PdQ4wgA=="
},
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/acorn": {
"version": "8.8.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
@@ -4779,6 +4810,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
+ },
"node_modules/array-includes": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz",
@@ -4986,6 +5022,42 @@
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
"integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ=="
},
+ "node_modules/body-parser": {
+ "version": "1.20.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
+ "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "on-finished": "2.4.1",
+ "qs": "6.11.0",
+ "raw-body": "2.5.2",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/body-parser/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/body-parser/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -5083,6 +5155,14 @@
"node": ">=10.16.0"
}
},
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
@@ -5190,11 +5270,43 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/convert-source-map": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
},
+ "node_modules/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
+ },
"node_modules/copy-to-clipboard": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
@@ -5245,6 +5357,11 @@
"node": ">= 8"
}
},
+ "node_modules/crypto-js": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
+ "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw=="
+ },
"node_modules/css-box-model": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
@@ -5589,6 +5706,23 @@
"node": ">=0.4.0"
}
},
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
"node_modules/detect-node-es": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@@ -5684,6 +5818,11 @@
"tslib": "^2.0.3"
}
},
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+ },
"node_modules/electron-to-chromium": {
"version": "1.4.414",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.414.tgz",
@@ -5713,6 +5852,14 @@
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
},
+ "node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/enhanced-resolve": {
"version": "5.14.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.1.tgz",
@@ -5855,6 +6002,11 @@
"node": ">=6"
}
},
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+ },
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -6342,6 +6494,14 @@
"node": ">=0.10.0"
}
},
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/ethers": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz",
@@ -6416,6 +6576,74 @@
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
+ "node_modules/express": {
+ "version": "4.19.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
+ "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.20.2",
+ "content-disposition": "0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "0.6.0",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "1.2.0",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "merge-descriptors": "1.0.1",
+ "methods": "~1.1.2",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "0.1.7",
+ "proxy-addr": "~2.0.7",
+ "qs": "6.11.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "0.18.0",
+ "serve-static": "1.15.0",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ }
+ },
+ "node_modules/express-rate-limit": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.2.0.tgz",
+ "integrity": "sha512-T7nul1t4TNyfZMJ7pKRKkdeVJWa2CqB8NA1P8BwYaoDI5QSBZARv5oMS43J7b7I5P+4asjVXjb7ONuwDKucahg==",
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/express-rate-limit"
+ },
+ "peerDependencies": {
+ "express": "4 || 5 || ^5.0.0-beta.1"
+ }
+ },
+ "node_modules/express/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/express/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -6495,6 +6723,36 @@
"node": ">=8"
}
},
+ "node_modules/finalhandler": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+ "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "2.0.1",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/finalhandler/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/finalhandler/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
"node_modules/find-root": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
@@ -6583,6 +6841,14 @@
"node": ">= 6"
}
},
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/framer-motion": {
"version": "10.12.16",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.12.16.tgz",
@@ -6634,6 +6900,14 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
},
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -6929,6 +7203,21 @@
"react-is": "^16.7.0"
}
},
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/human-signals": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz",
@@ -6937,6 +7226,17 @@
"node": ">=14.18.0"
}
},
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/ignore": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@@ -7011,6 +7311,14 @@
"loose-envify": "^1.0.0"
}
},
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
@@ -7528,6 +7836,19 @@
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="
},
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+ "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
+ },
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -7541,6 +7862,14 @@
"node": ">= 8"
}
},
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
@@ -7553,6 +7882,17 @@
"node": ">=8.6"
}
},
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -7612,6 +7952,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/mixpanel-browser": {
+ "version": "2.47.0",
+ "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.47.0.tgz",
+ "integrity": "sha512-Ldrva0fRBEIFWmEibBQO1PulfpJVF3pf28Guk09lDirDaSQqqU/xs9zQLwN2rL5VwVtsP1aD3JaCgaa98EjojQ=="
+ },
"node_modules/moment": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
@@ -7647,6 +7992,14 @@
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="
},
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/next": {
"version": "13.4.4",
"resolved": "https://registry.npmjs.org/next/-/next-13.4.4.tgz",
@@ -7859,6 +8212,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -7970,6 +8334,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -7999,6 +8371,11 @@
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
+ "node_modules/path-to-regexp": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+ "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
+ },
"node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -8070,6 +8447,20 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/prettier": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz",
+ "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -8080,6 +8471,18 @@
"react-is": "^16.13.1"
}
},
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -8093,6 +8496,20 @@
"node": ">=6"
}
},
+ "node_modules/qs": {
+ "version": "6.11.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
+ "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+ "dependencies": {
+ "side-channel": "^1.0.4"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -8112,6 +8529,33 @@
}
]
},
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/rate-limiter-flexible": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-5.0.3.tgz",
+ "integrity": "sha512-lWx2y8NBVlTOLPyqs+6y7dxfEpT6YFqKy3MzWbCy95sTTOhOuxufP2QvRyOHpfXpB9OUJPbVLybw3z3AVAS5fA=="
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/react": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
@@ -8707,6 +9151,25 @@
"queue-microtask": "^1.2.2"
}
},
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
"node_modules/safe-regex-test": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
@@ -8720,6 +9183,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
"node_modules/scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
@@ -8741,6 +9209,66 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/send": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+ "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/send/node_modules/debug/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "node_modules/serve-static": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
+ "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+ "dependencies": {
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.18.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+ },
"node_modules/shallow-equal": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz",
@@ -8816,6 +9344,14 @@
"node": ">=0.10.0"
}
},
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/stop-iteration-iterator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz",
@@ -9089,6 +9625,14 @@
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
},
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
"node_modules/tsconfig-paths": {
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz",
@@ -9157,6 +9701,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/typed-array-length": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
@@ -9232,6 +9788,14 @@
"node": ">=4"
}
},
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/untildify": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
@@ -9318,6 +9882,22 @@
}
}
},
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/victory-vendor": {
"version": "36.6.10",
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.6.10.tgz",
@@ -12374,6 +12954,11 @@
"integrity": "sha512-TnhDAntcJthcCMrR3OAKAUjgHyQgoms1yaBJepGv+BtXi8PLf8aX2L/NMCfofRTpVqW0bLklpGTsuqmUSCR2Uw==",
"dev": true
},
+ "@types/crypto-js": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz",
+ "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA=="
+ },
"@types/d3-array": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.5.tgz",
@@ -12446,6 +13031,12 @@
"@types/lodash": "*"
}
},
+ "@types/mixpanel-browser": {
+ "version": "2.47.1",
+ "resolved": "https://registry.npmjs.org/@types/mixpanel-browser/-/mixpanel-browser-2.47.1.tgz",
+ "integrity": "sha512-4Oif1hbjmStA6KI1OLer19rbKjlPd67ujp5Wipfa7rIskA/lOFZbp6kYsPjXD0f5AiiCQTbf0R/SnnzgOaKrbA==",
+ "dev": true
+ },
"@types/moment": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/@types/moment/-/moment-2.13.0.tgz",
@@ -12604,6 +13195,15 @@
"resolved": "https://registry.npmjs.org/@zag-js/focus-visible/-/focus-visible-0.2.2.tgz",
"integrity": "sha512-0j2gZq8HiZ51z4zNnSkF1iSkqlwRDvdH+son3wHdoz+7IUdMN/5Exd4TxMJ+gq2Of1DiXReYLL9qqh2PdQ4wgA=="
},
+ "accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "requires": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ }
+ },
"acorn": {
"version": "8.8.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
@@ -12674,6 +13274,11 @@
"is-array-buffer": "^3.0.1"
}
},
+ "array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
+ },
"array-includes": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz",
@@ -12834,6 +13439,40 @@
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
"integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ=="
},
+ "body-parser": {
+ "version": "1.20.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
+ "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
+ "requires": {
+ "bytes": "3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "on-finished": "2.4.1",
+ "qs": "6.11.0",
+ "raw-body": "2.5.2",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ }
+ }
+ },
"boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -12896,6 +13535,11 @@
"streamsearch": "^1.1.0"
}
},
+ "bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
+ },
"call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
@@ -12971,11 +13615,34 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
+ "content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "requires": {
+ "safe-buffer": "5.2.1"
+ }
+ },
+ "content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="
+ },
"convert-source-map": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
},
+ "cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="
+ },
+ "cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
+ },
"copy-to-clipboard": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
@@ -13013,6 +13680,11 @@
"which": "^2.0.1"
}
},
+ "crypto-js": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
+ "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw=="
+ },
"css-box-model": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
@@ -13258,6 +13930,16 @@
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
},
+ "depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
+ },
+ "destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="
+ },
"detect-node-es": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@@ -13329,6 +14011,11 @@
"tslib": "^2.0.3"
}
},
+ "ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+ },
"electron-to-chromium": {
"version": "1.4.414",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.414.tgz",
@@ -13360,6 +14047,11 @@
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
},
+ "encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
+ },
"enhanced-resolve": {
"version": "5.14.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.1.tgz",
@@ -13472,6 +14164,11 @@
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
},
+ "escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+ },
"escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -13825,6 +14522,11 @@
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
},
+ "etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
+ },
"ethers": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz",
@@ -13883,6 +14585,65 @@
"strip-final-newline": "^3.0.0"
}
},
+ "express": {
+ "version": "4.19.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
+ "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
+ "requires": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.20.2",
+ "content-disposition": "0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "0.6.0",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "1.2.0",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "merge-descriptors": "1.0.1",
+ "methods": "~1.1.2",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "0.1.7",
+ "proxy-addr": "~2.0.7",
+ "qs": "6.11.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "0.18.0",
+ "serve-static": "1.15.0",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ }
+ }
+ },
+ "express-rate-limit": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.2.0.tgz",
+ "integrity": "sha512-T7nul1t4TNyfZMJ7pKRKkdeVJWa2CqB8NA1P8BwYaoDI5QSBZARv5oMS43J7b7I5P+4asjVXjb7ONuwDKucahg==",
+ "requires": {}
+ },
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -13949,6 +14710,35 @@
"to-regex-range": "^5.0.1"
}
},
+ "finalhandler": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+ "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+ "requires": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "2.0.1",
+ "unpipe": "~1.0.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ }
+ }
+ },
"find-root": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
@@ -14008,6 +14798,11 @@
"mime-types": "^2.1.12"
}
},
+ "forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
+ },
"framer-motion": {
"version": "10.12.16",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.12.16.tgz",
@@ -14049,6 +14844,11 @@
}
}
},
+ "fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="
+ },
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -14254,11 +15054,31 @@
"react-is": "^16.7.0"
}
},
+ "http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "requires": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ }
+ },
"human-signals": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz",
"integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ=="
},
+ "iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ },
"ignore": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@@ -14315,6 +15135,11 @@
"loose-envify": "^1.0.0"
}
},
+ "ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
+ },
"is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
@@ -14669,6 +15494,16 @@
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="
},
+ "media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="
+ },
+ "merge-descriptors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+ "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
+ },
"merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -14679,6 +15514,11 @@
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
},
+ "methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="
+ },
"micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
@@ -14688,6 +15528,11 @@
"picomatch": "^2.3.1"
}
},
+ "mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
+ },
"mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -14729,6 +15574,11 @@
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="
},
+ "mixpanel-browser": {
+ "version": "2.47.0",
+ "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.47.0.tgz",
+ "integrity": "sha512-Ldrva0fRBEIFWmEibBQO1PulfpJVF3pf28Guk09lDirDaSQqqU/xs9zQLwN2rL5VwVtsP1aD3JaCgaa98EjojQ=="
+ },
"moment": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
@@ -14749,6 +15599,11 @@
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="
},
+ "negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
+ },
"next": {
"version": "13.4.4",
"resolved": "https://registry.npmjs.org/next/-/next-13.4.4.tgz",
@@ -14883,6 +15738,14 @@
"es-abstract": "^1.20.4"
}
},
+ "on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "requires": {
+ "ee-first": "1.1.1"
+ }
+ },
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -14958,6 +15821,11 @@
"lines-and-columns": "^1.1.6"
}
},
+ "parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
+ },
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -14978,6 +15846,11 @@
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
+ "path-to-regexp": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+ "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
+ },
"path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -15021,6 +15894,11 @@
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="
},
+ "prettier": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz",
+ "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g=="
+ },
"prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -15031,6 +15909,15 @@
"react-is": "^16.13.1"
}
},
+ "proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "requires": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ }
+ },
"proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -15041,11 +15928,40 @@
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
"integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA=="
},
+ "qs": {
+ "version": "6.11.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
+ "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+ "requires": {
+ "side-channel": "^1.0.4"
+ }
+ },
"queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
},
+ "range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
+ },
+ "rate-limiter-flexible": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-5.0.3.tgz",
+ "integrity": "sha512-lWx2y8NBVlTOLPyqs+6y7dxfEpT6YFqKy3MzWbCy95sTTOhOuxufP2QvRyOHpfXpB9OUJPbVLybw3z3AVAS5fA=="
+ },
+ "raw-body": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "requires": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ }
+ },
"react": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
@@ -15437,6 +16353,11 @@
"queue-microtask": "^1.2.2"
}
},
+ "safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+ },
"safe-regex-test": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
@@ -15447,6 +16368,11 @@
"is-regex": "^1.1.4"
}
},
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
"scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
@@ -15465,6 +16391,64 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
},
+ "send": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+ "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+ "requires": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ },
+ "dependencies": {
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ }
+ }
+ },
+ "ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ }
+ }
+ },
+ "serve-static": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
+ "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+ "requires": {
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.18.0"
+ }
+ },
+ "setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+ },
"shallow-equal": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz",
@@ -15522,6 +16506,11 @@
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
},
+ "statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
+ },
"stop-iteration-iterator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz",
@@ -15699,6 +16688,11 @@
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
},
+ "toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
+ },
"tsconfig-paths": {
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz",
@@ -15753,6 +16747,15 @@
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="
},
+ "type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "requires": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ }
+ },
"typed-array-length": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
@@ -15803,6 +16806,11 @@
"resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz",
"integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w=="
},
+ "unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
+ },
"untildify": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
@@ -15842,6 +16850,16 @@
"tslib": "^2.0.0"
}
},
+ "utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="
+ },
+ "vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
+ },
"victory-vendor": {
"version": "36.6.10",
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.6.10.tgz",
diff --git a/package.json b/package.json
index f177a08..50c229e 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,56 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
- "lint": "next lint"
+ "lint": "npx eslint . --fix",
+ "prettify": "npx prettier --write ."
+ },
+ "eslintConfig": {
+ "plugins": [
+ "react",
+ "@typescript-eslint"
+ ],
+ "extends": [
+ "react-app",
+ "react-app/jest",
+ "eslint:recommended",
+ "plugin:react/recommended",
+ "plugin:@typescript-eslint/recommended"
+ ],
+ "rules": {
+ "no-var": "warn",
+ "@typescript-eslint/no-unused-vars": [
+ "error",
+ {
+ "vars": "all",
+ "args": "all",
+ "ignoreRestSiblings": false,
+ "argsIgnorePattern": "^_"
+ }
+ ],
+ "@typescript-eslint/naming-convention": [
+ "error",
+ {
+ "selector": "parameter",
+ "format": [
+ "camelCase"
+ ],
+ "leadingUnderscore": "allow"
+ }
+ ]
+ },
+ "ignorePatterns": [
+ "build/**"
+ ],
+ "overrides": [
+ {
+ "files": [
+ "**/*.stories.*"
+ ],
+ "rules": {
+ "import/no-anonymous-default-export": "off"
+ }
+ }
+ ]
},
"dependencies": {
"@chakra-ui/icons": "^2.0.19",
@@ -14,19 +63,26 @@
"@emotion/react": "^11.11.0",
"@emotion/styled": "^11.11.0",
"@svgr/webpack": "^8.0.1",
+ "@types/crypto-js": "^4.1.1",
"@types/node": "20.2.5",
"@types/react": "18.2.7",
"@types/react-dom": "18.2.4",
"@types/strftime": "^0.9.4",
"axios": "^1.4.0",
+ "crypto-js": "^4.1.1",
"eslint": "8.41.0",
"eslint-config-next": "13.4.4",
"ethers": "^5.7.2",
+ "express": "^4.19.2",
+ "express-rate-limit": "^7.2.0",
"framer-motion": "^10.12.16",
"lodash": "^4.17.21",
+ "mixpanel-browser": "^2.47.0",
"moment": "^2.29.4",
- "next": "13.4.4",
+ "next": "^13.4.4",
"polished": "^4.2.2",
+ "prettier": "^3.0.0",
+ "rate-limiter-flexible": "^5.0.3",
"react": "18.2.0",
"react-date-range": "^1.4.0",
"react-dom": "18.2.0",
@@ -41,9 +97,10 @@
"devDependencies": {
"@types/console-log-level": "^1.4.2",
"@types/lodash": "^4.14.195",
+ "@types/mixpanel-browser": "^2.47.1",
"@types/moment": "^2.13.0",
"@types/react-date-range": "^1.4.4",
"@types/react-gtm-module": "^2.0.1",
"@types/react-table": "^7.7.14"
}
-}
\ No newline at end of file
+}
diff --git a/pre_push_and_pre_commit.sh b/pre_push_and_pre_commit.sh
new file mode 100755
index 0000000..31784ad
--- /dev/null
+++ b/pre_push_and_pre_commit.sh
@@ -0,0 +1 @@
+npx prettier --check . && npx eslint . --max-warnings=0
diff --git a/react-table-config.d.ts b/react-table-config.d.ts
index 744d657..9d6f924 100644
--- a/react-table-config.d.ts
+++ b/react-table-config.d.ts
@@ -1,120 +1,120 @@
import {
- UseColumnOrderInstanceProps,
- UseColumnOrderState,
- UseExpandedHooks,
- UseExpandedInstanceProps,
- UseExpandedOptions,
- UseExpandedRowProps,
- UseExpandedState,
- UseFiltersColumnOptions,
- UseFiltersColumnProps,
- UseFiltersInstanceProps,
- UseFiltersOptions,
- UseFiltersState,
- UseGlobalFiltersColumnOptions,
- UseGlobalFiltersInstanceProps,
- UseGlobalFiltersOptions,
- UseGlobalFiltersState,
- UseGroupByCellProps,
- UseGroupByColumnOptions,
- UseGroupByColumnProps,
- UseGroupByHooks,
- UseGroupByInstanceProps,
- UseGroupByOptions,
- UseGroupByRowProps,
- UseGroupByState,
- UsePaginationInstanceProps,
- UsePaginationOptions,
- UsePaginationState,
- UseResizeColumnsColumnOptions,
- UseResizeColumnsColumnProps,
- UseResizeColumnsOptions,
- UseResizeColumnsState,
- UseRowSelectHooks,
- UseRowSelectInstanceProps,
- UseRowSelectOptions,
- UseRowSelectRowProps,
- UseRowSelectState,
- UseRowStateCellProps,
- UseRowStateInstanceProps,
- UseRowStateOptions,
- UseRowStateRowProps,
- UseRowStateState,
- UseSortByColumnOptions,
- UseSortByColumnProps,
- UseSortByHooks,
- UseSortByInstanceProps,
- UseSortByOptions,
- UseSortByState
- } from 'react-table'
-
- declare module 'react-table' {
- // take this file as-is, or comment out the sections that don't apply to your plugin configuration
-
- export interface TableOptions>
- extends UseExpandedOptions,
- UseFiltersOptions,
- UseGlobalFiltersOptions,
- UseGroupByOptions,
- UsePaginationOptions,
- UseResizeColumnsOptions,
- UseRowSelectOptions,
- UseRowStateOptions,
- UseSortByOptions,
- // note that having Record here allows you to add anything to the options, this matches the spirit of the
- // underlying js library, but might be cleaner if it's replaced by a more specific type that matches your
- // feature set, this is a safe default.
- Record {}
-
- export interface Hooks = Record>
- extends UseExpandedHooks,
- UseGroupByHooks,
- UseRowSelectHooks,
- UseSortByHooks {}
-
- export interface TableInstance = Record>
- extends UseColumnOrderInstanceProps,
- UseExpandedInstanceProps,
- UseFiltersInstanceProps,
- UseGlobalFiltersInstanceProps,
- UseGroupByInstanceProps,
- UsePaginationInstanceProps,
- UseRowSelectInstanceProps,
- UseRowStateInstanceProps,
- UseSortByInstanceProps {}
-
- export interface TableState = Record>
- extends UseColumnOrderState,
- UseExpandedState,
- UseFiltersState,
- UseGlobalFiltersState,
- UseGroupByState,
- UsePaginationState,
- UseResizeColumnsState,
- UseRowSelectState,
- UseRowStateState,
- UseSortByState {}
-
- export interface ColumnInterface = Record>
- extends UseFiltersColumnOptions,
- UseGlobalFiltersColumnOptions,
- UseGroupByColumnOptions,
- UseResizeColumnsColumnOptions,
- UseSortByColumnOptions {}
-
- export interface ColumnInstance = Record>
- extends UseFiltersColumnProps,
- UseGroupByColumnProps,
- UseResizeColumnsColumnProps,
- UseSortByColumnProps {}
-
- export interface Cell = Record, V = any>
- extends UseGroupByCellProps,
- UseRowStateCellProps {}
-
- export interface Row = Record>
- extends UseExpandedRowProps,
- UseGroupByRowProps,
- UseRowSelectRowProps,
- UseRowStateRowProps {}
- }
\ No newline at end of file
+ UseColumnOrderInstanceProps,
+ UseColumnOrderState,
+ UseExpandedHooks,
+ UseExpandedInstanceProps,
+ UseExpandedOptions,
+ UseExpandedRowProps,
+ UseExpandedState,
+ UseFiltersColumnOptions,
+ UseFiltersColumnProps,
+ UseFiltersInstanceProps,
+ UseFiltersOptions,
+ UseFiltersState,
+ UseGlobalFiltersColumnOptions,
+ UseGlobalFiltersInstanceProps,
+ UseGlobalFiltersOptions,
+ UseGlobalFiltersState,
+ UseGroupByCellProps,
+ UseGroupByColumnOptions,
+ UseGroupByColumnProps,
+ UseGroupByHooks,
+ UseGroupByInstanceProps,
+ UseGroupByOptions,
+ UseGroupByRowProps,
+ UseGroupByState,
+ UsePaginationInstanceProps,
+ UsePaginationOptions,
+ UsePaginationState,
+ UseResizeColumnsColumnOptions,
+ UseResizeColumnsColumnProps,
+ UseResizeColumnsOptions,
+ UseResizeColumnsState,
+ UseRowSelectHooks,
+ UseRowSelectInstanceProps,
+ UseRowSelectOptions,
+ UseRowSelectRowProps,
+ UseRowSelectState,
+ UseRowStateCellProps,
+ UseRowStateInstanceProps,
+ UseRowStateOptions,
+ UseRowStateRowProps,
+ UseRowStateState,
+ UseSortByColumnOptions,
+ UseSortByColumnProps,
+ UseSortByHooks,
+ UseSortByInstanceProps,
+ UseSortByOptions,
+ UseSortByState,
+} from 'react-table';
+
+declare module 'react-table' {
+ // take this file as-is, or comment out the sections that don't apply to your plugin configuration
+
+ export interface TableOptions>
+ extends UseExpandedOptions,
+ UseFiltersOptions,
+ UseGlobalFiltersOptions,
+ UseGroupByOptions,
+ UsePaginationOptions,
+ UseResizeColumnsOptions,
+ UseRowSelectOptions,
+ UseRowStateOptions,
+ UseSortByOptions,
+ // note that having Record here allows you to add anything to the options, this matches the spirit of the
+ // underlying js library, but might be cleaner if it's replaced by a more specific type that matches your
+ // feature set, this is a safe default.
+ Record {}
+
+ export interface Hooks = Record>
+ extends UseExpandedHooks,
+ UseGroupByHooks,
+ UseRowSelectHooks,
+ UseSortByHooks {}
+
+ export interface TableInstance = Record>
+ extends UseColumnOrderInstanceProps,
+ UseExpandedInstanceProps,
+ UseFiltersInstanceProps,
+ UseGlobalFiltersInstanceProps,
+ UseGroupByInstanceProps,
+ UsePaginationInstanceProps,
+ UseRowSelectInstanceProps,
+ UseRowStateInstanceProps,
+ UseSortByInstanceProps {}
+
+ export interface TableState = Record>
+ extends UseColumnOrderState,
+ UseExpandedState,
+ UseFiltersState,
+ UseGlobalFiltersState,
+ UseGroupByState,
+ UsePaginationState,
+ UseResizeColumnsState,
+ UseRowSelectState,
+ UseRowStateState,
+ UseSortByState {}
+
+ export interface ColumnInterface = Record>
+ extends UseFiltersColumnOptions,
+ UseGlobalFiltersColumnOptions,
+ UseGroupByColumnOptions,
+ UseResizeColumnsColumnOptions,
+ UseSortByColumnOptions {}
+
+ export interface ColumnInstance = Record>
+ extends UseFiltersColumnProps,
+ UseGroupByColumnProps,
+ UseResizeColumnsColumnProps,
+ UseSortByColumnProps {}
+
+ export interface Cell = Record, V = any>
+ extends UseGroupByCellProps,
+ UseRowStateCellProps {}
+
+ export interface Row = Record>
+ extends UseExpandedRowProps,
+ UseGroupByRowProps,
+ UseRowSelectRowProps,
+ UseRowStateRowProps {}
+}
diff --git a/styles/components/alert.ts b/styles/components/alert.ts
index 930f567..b31689c 100644
--- a/styles/components/alert.ts
+++ b/styles/components/alert.ts
@@ -2,10 +2,9 @@ const Alert = {
baseStyle: {
container: {
borderRadius: 'xl',
- }
- },
- variants: {
+ },
},
+ variants: {},
};
export default Alert;
diff --git a/styles/components/button.ts b/styles/components/button.ts
index eeeeeda..562f24a 100644
--- a/styles/components/button.ts
+++ b/styles/components/button.ts
@@ -1,8 +1,8 @@
-import {lighten} from "polished"
+import { lighten } from 'polished';
const Button = {
baseStyle: {
- fontWeight: '600',
+ fontWeight: '400',
borderRadius: '24px',
letterSpacing: '1px',
padding: '1rem 1rem',
diff --git a/styles/components/input.ts b/styles/components/input.ts
index 214233a..b576087 100644
--- a/styles/components/input.ts
+++ b/styles/components/input.ts
@@ -3,7 +3,7 @@ const Input = {
rounded: {
borderRadius: '12rem',
background: 'red',
- }
+ },
},
};
diff --git a/styles/components/modal.ts b/styles/components/modal.ts
index 24f77ed..9018929 100644
--- a/styles/components/modal.ts
+++ b/styles/components/modal.ts
@@ -7,9 +7,7 @@ export const ModalHeader = {
const Modal = {
variants: {
default: {
- dialogContainer: {
-
- },
+ dialogContainer: {},
},
full: {
dialogContainer: {
diff --git a/styles/components/tabs.ts b/styles/components/tabs.ts
index 737ff7d..93259a1 100644
--- a/styles/components/tabs.ts
+++ b/styles/components/tabs.ts
@@ -1,30 +1,31 @@
-import { tabsAnatomy } from '@chakra-ui/anatomy'
-import { createMultiStyleConfigHelpers } from '@chakra-ui/react'
-import { mode } from '@chakra-ui/theme-tools' // import utility to set light and dark mode props
+import { tabsAnatomy } from '@chakra-ui/anatomy';
+import { createMultiStyleConfigHelpers } from '@chakra-ui/react';
+import { mode } from '@chakra-ui/theme-tools'; // import utility to set light and dark mode props
-const { definePartsStyle, defineMultiStyleConfig } =
- createMultiStyleConfigHelpers(tabsAnatomy.keys)
+const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers(
+ tabsAnatomy.keys
+);
// define a custom variant
const colorfulVariant = definePartsStyle((props) => {
- const { colorScheme: c } = props // extract colorScheme from component props
+ const { colorScheme: c } = props; // extract colorScheme from component props
return {
tab: {
border: '2px solid',
borderColor: 'transparent',
// use colorScheme to change background color with dark and light mode options
- bg: "transparent",
+ bg: 'transparent',
//bg: mode(`${c}.300`, `${c}.600`)(props),
borderTopRadius: 'lg',
borderBottom: 'none',
- color: "#444",
+ color: '#444',
fontWeight: 600,
//borderBottom: 'none',
_selected: {
//bg: mode('#fff', 'gray.800')(props),
- bg: "#ddd",
- color: "#0277ba",
+ bg: '#ddd',
+ color: '#0277ba',
//borderColor: 'inherit',
borderBottom: 'none',
mb: '-2px',
@@ -40,12 +41,12 @@ const colorfulVariant = definePartsStyle((props) => {
borderBottomRadius: 'lg',
borderTopRightRadius: 'lg',
},
- }
-})
+ };
+});
const variants = {
colorful: colorfulVariant,
-}
+};
// export the component theme
-export const tabsTheme = defineMultiStyleConfig({ variants })
\ No newline at end of file
+export const tabsTheme = defineMultiStyleConfig({ variants });
diff --git a/styles/custom.ts b/styles/custom.ts
index 0526fef..269c74f 100644
--- a/styles/custom.ts
+++ b/styles/custom.ts
@@ -1,141 +1,149 @@
import { css } from '@emotion/react';
-export default css`
-
- .react-datepicker {
- font-family: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
- overflow: hidden;
- }
-
- .react-datepicker__navigation--next--with-time:not(.react-datepicker__navigation--next--with-today-button) {
- right: 90px;
- }
-
- .react-datepicker__navigation--previous,
- .react-datepicker__navigation--next {
- height: 8px;
- }
-
- .react-datepicker__navigation--previous {
- border-right-color: #cbd5e0;
-
- &:hover {
- border-right-color: #a0aec0;
- }
- }
-
- .react-datepicker__navigation--next {
- border-left-color: #cbd5e0;
-
- &:hover {
- border-left-color: #a0aec0;
- }
- }
-
- .react-datepicker-wrapper,
- .react-datepicker__input-container {
- display: block;
- }
-
- .react-datepicker__header {
- border-radius: 0;
- background: #f7fafc;
- }
-
- .react-datepicker,
- .react-datepicker__header,
- .react-datepicker__time-container {
- border-color: #e2e8f0;
- }
-
- .react-datepicker__current-month,
- .react-datepicker-time__header,
- .react-datepicker-year-header {
- font-size: inherit;
- font-weight: 600;
- }
-
- .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item {
- margin: 0 1px 0 0;
- height: auto;
- padding: 7px 10px;
-
- &:hover {
- background: #edf2f7;
- }
- }
-
- .react-datepicker__day:hover {
- background: #edf2f7;
- }
-
- .react-datepicker__day--selected,
- .react-datepicker__day--in-selecting-range,
- .react-datepicker__day--in-range,
- .react-datepicker__month-text--selected,
- .react-datepicker__month-text--in-selecting-range,
- .react-datepicker__month-text--in-range,
- .react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list li.react-datepicker__time-list-item--selected {
- background: #3182ce;
- font-weight: normal;
-
- &:hover {
- background: #2a69ac;
- }
- }
-
- .react-dropdown-select {
- border-radius: 12px !important;
- }
-
- .react-dropdown-select:hover,
- .react-dropdown-select:focus-within {
- border-color: #97ffe4 !important;
- }
-
- .react-dropdown-select-dropdown {
- max-height: 300px !important;
- height: 300px !important;
- background: #0A1F1B !important;
- border-color: #061412 !important;
- box-shadow: 0px 0px 7px rgb(0 0 0 / 20%) !important;
- padding: 6px !important;
- }
-
- .date-range-item {
- padding: 6px !important;
- }
-
- .date-range-item:hover {
- background: #0f2e29 !important;
- }
-
- .rdrCalendarWrapper {
- box-sizing: border-box;
- background: transparent !important;
- color: #fff !important;
- }
- .rdrDayDisabled {
- background-color: rgb(9 32 27) !important;
- }
- .rdrMonthAndYearPickers select {
- color: #fff !important;
- }
- .rdrNextPrevButton {
- background-color: #194D44 !important;
- }
- .rdrNextPrevButton i {
- border-color: transparent transparent transparent #fff !important;
- }
- .rdrNextPrevButton.rdrPprevButton i {
- border-color: transparent #fff transparent transparent !important;
- }
- .rdrDayNumber span {
- color: #fff !important;
- }
- .rdrDayToday .rdrDayNumber span:after {
- background: #97ffe4 !important;
- }
+export default css`
+ .react-datepicker {
+ font-family: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
+ sans-serif;
+ overflow: hidden;
+ }
+
+ .react-datepicker__navigation--next--with-time:not(
+ .react-datepicker__navigation--next--with-today-button
+ ) {
+ right: 90px;
+ }
+
+ .react-datepicker__navigation--previous,
+ .react-datepicker__navigation--next {
+ height: 8px;
+ }
+
+ .react-datepicker__navigation--previous {
+ border-right-color: #cbd5e0;
+
+ &:hover {
+ border-right-color: #a0aec0;
+ }
+ }
+
+ .react-datepicker__navigation--next {
+ border-left-color: #cbd5e0;
+
+ &:hover {
+ border-left-color: #a0aec0;
+ }
+ }
+
+ .react-datepicker-wrapper,
+ .react-datepicker__input-container {
+ display: block;
+ }
+
+ .react-datepicker__header {
+ border-radius: 0;
+ background: #f7fafc;
+ }
+
+ .react-datepicker,
+ .react-datepicker__header,
+ .react-datepicker__time-container {
+ border-color: #e2e8f0;
+ }
+
+ .react-datepicker__current-month,
+ .react-datepicker-time__header,
+ .react-datepicker-year-header {
+ font-size: inherit;
+ font-weight: 600;
+ }
+
+ .react-datepicker__time-container
+ .react-datepicker__time
+ .react-datepicker__time-box
+ ul.react-datepicker__time-list
+ li.react-datepicker__time-list-item {
+ margin: 0 1px 0 0;
+ height: auto;
+ padding: 7px 10px;
+
+ &:hover {
+ background: #edf2f7;
+ }
+ }
+
+ .react-datepicker__day:hover {
+ background: #edf2f7;
+ }
+
+ .react-datepicker__day--selected,
+ .react-datepicker__day--in-selecting-range,
+ .react-datepicker__day--in-range,
+ .react-datepicker__month-text--selected,
+ .react-datepicker__month-text--in-selecting-range,
+ .react-datepicker__month-text--in-range,
+ .react-datepicker__time-container
+ .react-datepicker__time
+ .react-datepicker__time-box
+ ul.react-datepicker__time-list
+ li.react-datepicker__time-list-item--selected {
+ background: #3182ce;
+ font-weight: normal;
+
+ &:hover {
+ background: #2a69ac;
+ }
+ }
+
+ .react-dropdown-select {
+ border-radius: 12px !important;
+ }
+
+ .react-dropdown-select:hover,
+ .react-dropdown-select:focus-within {
+ border-color: #97ffe4 !important;
+ }
+
+ .react-dropdown-select-dropdown {
+ max-height: 300px !important;
+ height: 300px !important;
+ background: #0a1f1b !important;
+ border-color: #061412 !important;
+ box-shadow: 0px 0px 7px rgb(0 0 0 / 20%) !important;
+ padding: 6px !important;
+ }
+
+ .date-range-item {
+ padding: 6px !important;
+ }
+
+ .date-range-item:hover {
+ background: #0f2e29 !important;
+ }
+
+ .rdrCalendarWrapper {
+ box-sizing: border-box;
+ background: transparent !important;
+ color: #fff !important;
+ }
+ .rdrDayDisabled {
+ background-color: rgb(9 32 27) !important;
+ }
+ .rdrMonthAndYearPickers select {
+ color: #fff !important;
+ }
+ .rdrNextPrevButton {
+ background-color: #194d44 !important;
+ }
+ .rdrNextPrevButton i {
+ border-color: transparent transparent transparent #fff !important;
+ }
+ .rdrNextPrevButton.rdrPprevButton i {
+ border-color: transparent #fff transparent transparent !important;
+ }
+ .rdrDayNumber span {
+ color: #fff !important;
+ }
+ .rdrDayToday .rdrDayNumber span:after {
+ background: #97ffe4 !important;
+ }
`;
-
-
diff --git a/styles/fonts.ts b/styles/fonts.ts
index d0d5cde..fe53d8a 100644
--- a/styles/fonts.ts
+++ b/styles/fonts.ts
@@ -5,45 +5,53 @@ import customTheme from './theme';
import onboard from './custom';
export default css`
-
- @font-face {
- font-display: swap;
- font-family: Inter;
- font-style: normal;
- font-weight: 300;
- src: url(/fonts/Inter-Light.woff2) format("woff2"), url(/fonts/Inter-Light.woff?v=3.19) format("woff");
- }
+ @font-face {
+ font-display: swap;
+ font-family: Inter;
+ font-style: normal;
+ font-weight: 300;
+ src:
+ url(/fonts/Inter-Light.woff2) format('woff2'),
+ url(/fonts/Inter-Light.woff?v=3.19) format('woff');
+ }
- @font-face {
- font-display: swap;
- font-family: Inter;
- font-style: normal;
- font-weight: 400;
- src: url(/fonts/Inter-Regular.woff2) format("woff2"), url(/fonts/Inter-Regular.woff?v=3.19) format("woff");
- }
+ @font-face {
+ font-display: swap;
+ font-family: Inter;
+ font-style: normal;
+ font-weight: 400;
+ src:
+ url(/fonts/Inter-Regular.woff2) format('woff2'),
+ url(/fonts/Inter-Regular.woff?v=3.19) format('woff');
+ }
- @font-face {
- font-display: swap;
- font-family: Inter;
- font-style: normal;
- font-weight: 700;
- src: url(/fonts/Inter-Bold.woff2) format("woff2"), url(/fonts/Inter-Bold.woff?v=3.19) format("woff");
- }
+ @font-face {
+ font-display: swap;
+ font-family: Inter;
+ font-style: normal;
+ font-weight: 700;
+ src:
+ url(/fonts/Inter-Bold.woff2) format('woff2'),
+ url(/fonts/Inter-Bold.woff?v=3.19) format('woff');
+ }
- @font-face {
- font-display: swap;
- font-family: Teodor;
- font-style: normal;
- font-weight: 400;
- src: url(/fonts/Teodor-Light.woff) format("woff"), url(/fonts/Teodor-Light.woff2) format("woff2");
- }
+ @font-face {
+ font-display: swap;
+ font-family: Teodor;
+ font-style: normal;
+ font-weight: 400;
+ src:
+ url(/fonts/Teodor-Light.woff) format('woff'),
+ url(/fonts/Teodor-Light.woff2) format('woff2');
+ }
- @font-face {
- font-display: swap;
- font-family: Teodor;
- font-style: italic;
- font-weight: 400;
- src: url(/fonts/Teodor-LightItalic.woff) format("woff"), url(/fonts/Teodor-LightItalic.woff2) format("woff2");
- }
-
-`;
\ No newline at end of file
+ @font-face {
+ font-display: swap;
+ font-family: Teodor;
+ font-style: italic;
+ font-weight: 400;
+ src:
+ url(/fonts/Teodor-LightItalic.woff) format('woff'),
+ url(/fonts/Teodor-LightItalic.woff2) format('woff2');
+ }
+`;
diff --git a/styles/global.ts b/styles/global.ts
index 80c83b8..646bef5 100644
--- a/styles/global.ts
+++ b/styles/global.ts
@@ -37,5 +37,4 @@ export const GlobalStyles = css`
}
${custom}
-
`;
diff --git a/styles/theme.ts b/styles/theme.ts
index 974a616..d7229e8 100644
--- a/styles/theme.ts
+++ b/styles/theme.ts
@@ -1,5 +1,4 @@
import { extendTheme, ThemeConfig } from '@chakra-ui/react';
-import { createBreakpoints } from '@chakra-ui/theme-tools';
import Button from './components/button';
import Progress from './components/progress';
import Text from './components/text';
@@ -8,8 +7,7 @@ import Drawer from './components/drawer';
import Input from './components/input';
import Tooltip from './components/tooltip';
import Alert from './components/alert';
-import { tabsTheme } from "./components/tabs";
-
+import { tabsTheme } from './components/tabs';
const config: ThemeConfig = {
initialColorMode: 'dark',
@@ -21,7 +19,7 @@ const breakpoints = {
sm: '576px',
md: '768px',
lg: '992px',
- xl: '1500px',
+ xl: '1400px',
xxl: '2400px',
};
@@ -68,6 +66,6 @@ const baseTheme = extendTheme({
},
});
-export const theme = extendTheme(baseTheme)
+export const theme = extendTheme(baseTheme);
export default baseTheme;
diff --git a/tsconfig.json b/tsconfig.json
index a914549..e06a445 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "es5",
- "lib": [
- "dom",
- "dom.iterable",
- "esnext"
- ],
+ "lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -24,18 +20,9 @@
}
],
"paths": {
- "@/*": [
- "./*"
- ]
+ "@/*": ["./*"]
}
},
- "include": [
- "next-env.d.ts",
- "**/*.ts",
- "**/*.tsx",
- ".next/types/**/*.ts"
- ],
- "exclude": [
- "node_modules"
- ]
-}
\ No newline at end of file
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/utils/formatting.ts b/utils/formatting.ts
index 7e5162e..e398d3e 100644
--- a/utils/formatting.ts
+++ b/utils/formatting.ts
@@ -7,5 +7,8 @@ export const formatNumber = (num: number | string | boolean, maximumFractionDigi
export const formatAddress = (account: string, length?: number): string => {
if (!length) return account;
- return `${account.substring(0, length)}...${account.substring(account.length - length, account.length)}`;
+ return `${account.substring(0, length)}...${account.substring(
+ account.length - length,
+ account.length
+ )}`;
};