diff --git a/src/components/Header/useMenu.tsx b/src/components/Header/useMenu.tsx index 01d9e1de63..88d67a9257 100644 --- a/src/components/Header/useMenu.tsx +++ b/src/components/Header/useMenu.tsx @@ -295,7 +295,27 @@ const useMenu: UseMenu = () => { } if (featureEnabled(Feature.ANALYTICS, chainId)) { + if (featureEnabled(Feature.TRIDENT, chainId)) { + analyticsMenu.items.push({ + key: 'trident', + title: 'Trident', + link: `/analytics/trident`, + }) + } menu.push(analyticsMenu) + } else if (featureEnabled(Feature.TRIDENT, chainId)) { + menu.push({ + key: 'analytics', + title: i18n._(t`Analytics`), + icon: , + items: [ + { + key: 'trident', + title: 'Trident', + link: `/analytics/trident`, + }, + ], + }) } if (account) { diff --git a/src/features/analytics/trident/SearchResultTokens.tsx b/src/features/analytics/trident/SearchResultTokens.tsx new file mode 100644 index 0000000000..2918d0aeee --- /dev/null +++ b/src/features/analytics/trident/SearchResultTokens.tsx @@ -0,0 +1,124 @@ +import { ArrowDownIcon, ArrowUpIcon } from '@heroicons/react/solid' +import { TablePageToggler } from 'app/features/transactions/TablePageToggler' +import { + TABLE_TABLE_CLASSNAME, + TABLE_TBODY_TD_CLASSNAME, + TABLE_TBODY_TR_CLASSNAME, + TABLE_TR_TH_CLASSNAME, + TABLE_WRAPPER_DIV_CLASSNAME, +} from 'app/features/trident/constants' +import Link from 'next/link' +import React, { FC } from 'react' +// @ts-ignore TYPE NEEDS FIXING +import { useFilters, useFlexLayout, usePagination, useSortBy, useTable } from 'react-table' + +import { SearchCategoryLabel } from '../tokens/SearchCategoryLabel' +import { useInstantiateTableFeatures } from './useInstantiateTableFeatures' +import { useTokensTableConfig } from './useTokensTableConfig' + +const SearchResultTokens: FC<{ chainId: number }> = ({ chainId }) => { + const { config } = useTokensTableConfig(chainId) + + const { + getTableProps, + getTableBodyProps, + headerGroups, + rows, + // @ts-ignore TYPE NEEDS FIXING + page, + // @ts-ignore TYPE NEEDS FIXING + gotoPage, + // @ts-ignore TYPE NEEDS FIXING + canPreviousPage, + // @ts-ignore TYPE NEEDS FIXING + canNextPage, + // @ts-ignore TYPE NEEDS FIXING + prepareRow, + // @ts-ignore TYPE NEEDS FIXING + setFilter, + // @ts-ignore TYPE NEEDS FIXING + // toggleSortBy, + // @ts-ignore TYPE NEEDS FIXING + state: { pageIndex, pageSize }, + // @ts-ignore TYPE NEEDS FIXING + } = useTable(config, useFlexLayout, useFilters, useSortBy, useFlexLayout, usePagination) + useInstantiateTableFeatures(setFilter) + return ( +
+ +
+ + + {headerGroups.map((headerGroup, i) => ( + + {headerGroup.headers.map((column, i) => ( + + ))} + + ))} + + + {/*@ts-ignore TYPE NEEDS FIXING*/} + {page.map((row, i) => { + prepareRow(row) + return ( + + + {/*@ts-ignore TYPE NEEDS FIXING*/} + {row.cells.map((cell, i) => { + return ( + + ) + })} + + + ) + })} + +
+ {column.render('Header')} + + {/*@ts-ignore TYPE NEEDS FIXING*/} + {column.isSorted ? ( + // @ts-ignore TYPE NEEDS FIXING + column.isSortedDesc ? ( + + ) : ( + + ) + ) : ( + '' + )} + +
+ {cell.render('Cell')} +
+
+ +
+ ) +} + +export default SearchResultTokens diff --git a/src/features/analytics/trident/tokenTableFilters.ts b/src/features/analytics/trident/tokenTableFilters.ts new file mode 100644 index 0000000000..a29271513d --- /dev/null +++ b/src/features/analytics/trident/tokenTableFilters.ts @@ -0,0 +1,12 @@ +// import { Fee } from '@sushiswap/trident-sdk' + +type FilterSymbolsFunc = (arg0: { original: { name: string; symbol: string } }[], arg1: string[], arg2: T) => any[] + +export const filterForSearchQuery: FilterSymbolsFunc<{ searchQuery: string }> = (rows, id, filterValue) => { + return rows.filter(({ original }) => { + // Allow searching for symbol (LINK) or name (chainlink) + const searchableText = original?.name?.concat(original?.symbol)?.toLowerCase() + // return true + return !filterValue.searchQuery.length || searchableText.includes(filterValue.searchQuery.toLowerCase()) + }) +} diff --git a/src/features/analytics/trident/tokens/Header.tsx b/src/features/analytics/trident/tokens/Header.tsx new file mode 100644 index 0000000000..083eb7e396 --- /dev/null +++ b/src/features/analytics/trident/tokens/Header.tsx @@ -0,0 +1,81 @@ +import { getAddress } from '@ethersproject/address' +import { LinkIcon } from '@heroicons/react/outline' +import { useLingui } from '@lingui/react' +import { ChainId, Token } from '@sushiswap/core-sdk' +import CopyHelper from 'app/components/AccountDetails/Copy' +import { CurrencyLogo } from 'app/components/CurrencyLogo' +import Typography from 'app/components/Typography' +import { formatNumber, getExplorerLink, shortenAddress } from 'app/functions' +import { useAllTokens } from 'app/hooks/Tokens' +import useDesktopMediaQuery from 'app/hooks/useDesktopMediaQuery' +import Link from 'next/link' +import { FC, useMemo } from 'react' + +interface HeaderProps { + chainId: ChainId + token?: any +} + +export const Header: FC = ({ token, chainId }) => { + const { i18n } = useLingui() + const isDesktop = useDesktopMediaQuery() + const allTokens = useAllTokens() + const currency = useMemo(() => { + const address = getAddress(token.id) + return address in allTokens + ? allTokens[address] + : new Token(chainId, address, Number(token.decimals), token.symbol, token.name) + }, [token, allTokens, chainId]) + return ( +
+
+
+ +
+
+
+ {token && ( + + + + {token.symbol} + + + + )} + {token && ( + + {shortenAddress(token.id)} + + )} +
+
+
+
+
+ + {i18n._('Price')} + + + {formatNumber(token.price.derivedUSD, true)} + +
+
+ + {i18n._('Liquidity')} + + + {formatNumber(token.kpi.liquidityUSD, true)} + +
+
+
+ ) +} + +export default Header diff --git a/src/features/analytics/trident/tokens/TokenStats.tsx b/src/features/analytics/trident/tokens/TokenStats.tsx new file mode 100644 index 0000000000..c32b538cd8 --- /dev/null +++ b/src/features/analytics/trident/tokens/TokenStats.tsx @@ -0,0 +1,83 @@ +import { t } from '@lingui/macro' +import { useLingui } from '@lingui/react' +import { ChainId } from '@sushiswap/core-sdk' +import Typography from 'app/components/Typography' +import { classNames, formatPercent } from 'app/functions' +import useDesktopMediaQuery from 'app/hooks/useDesktopMediaQuery' +import { useTridentRollingTokenStats } from 'app/services/graph' +import { FC } from 'react' + +interface TokenStatsProps { + chainId: ChainId + token?: any +} + +const TokenStats: FC = ({ chainId, token }) => { + const { i18n } = useLingui() + const isDesktop = useDesktopMediaQuery() + + const { data: stats } = useTridentRollingTokenStats({ + chainId, + variables: { where: { id: token.id.toLowerCase() } }, + shouldFetch: !!chainId && !!token && !!token.id.toLowerCase(), + }) + + const items = [ + { + label: i18n._(t`Volume (24H)`), + value: 'volume', + change: 'volume24hChange', + }, + { + label: i18n._(t`Fees (24H)`), + value: 'fees', + change: 'fees24hChange', + }, + { + label: i18n._(t`Utilization (24H)`), + value: 'liquidity', + change: 'liquidity24hChange', + }, + { + label: i18n._(t`Transactions (24H)`), + value: 'transactions', + change: 'transactions24hChange', + }, + ] + + return ( +
+ {items.map(({ label, value, change }, index) => ( +
+ + {label} + +
+ + {/*@ts-ignore TYPE NEEDS FIXING*/} + {stats?.[0]?.[value]} + + 0 && 'text-green', + stats?.[0]?.[change] < 0 && 'text-red', + 'text-inherit' + )} + > + {/*@ts-ignore TYPE NEEDS FIXING*/} + {formatPercent(stats?.[0]?.[change])} + +
+
+ ))} +
+ ) +} + +export default TokenStats diff --git a/src/features/analytics/trident/tokens/TokenStatsChart.tsx b/src/features/analytics/trident/tokens/TokenStatsChart.tsx new file mode 100644 index 0000000000..4829c059b6 --- /dev/null +++ b/src/features/analytics/trident/tokens/TokenStatsChart.tsx @@ -0,0 +1,150 @@ +import { ChainId } from '@sushiswap/core-sdk' +import BarGraph from 'app/components/BarGraph' +import Button from 'app/components/Button' +import LineGraph from 'app/components/LineGraph' +import Tabs from 'app/components/Tabs' +import Typography from 'app/components/Typography' +import { formatDate, formatNumber } from 'app/functions' +import useDesktopMediaQuery from 'app/hooks/useDesktopMediaQuery' +import { useTridentTokenDayBuckets, useTridentTokenHourBuckets } from 'app/services/graph' +import { FC, useEffect, useMemo, useState } from 'react' + +interface TokenStatsChartProps { + chainId: ChainId + token?: any +} + +enum ChartType { + Volume = 'Volume', + TVL = 'TVL', + Price = 'Price', +} + +enum ChartRange { + '24H' = '24H', + '1W' = '1W', + '1M' = '1M', + '1Y' = '1Y', + 'ALL' = 'ALL', +} + +const chartTimespans: Record = { + [ChartRange['24H']]: 86400, + [ChartRange['1W']]: 604800, + [ChartRange['1M']]: 2629746, + [ChartRange['1Y']]: 31556952, + [ChartRange['ALL']]: Infinity, +} + +const TokenStatsChart: FC = ({ token, chainId }) => { + const isDesktop = useDesktopMediaQuery() + const [chartType, setChartType] = useState(ChartType.Volume) + const [chartRange, setChartRange] = useState(ChartRange['1W']) + + const hourBuckets = useTridentTokenHourBuckets({ + chainId, + variables: { + first: 168, + where: { token: token.id.toLowerCase() }, + }, + shouldFetch: !!token && chartTimespans[chartRange] < chartTimespans['1W'], + }) + + const dayBuckets = useTridentTokenDayBuckets({ + chainId, + variables: { + where: { token: token.id.toLowerCase() }, + }, + shouldFetch: !!token && chartTimespans[chartRange] >= chartTimespans['1W'], + }) + + const data = chartTimespans[chartRange] < chartTimespans['1W'] ? hourBuckets : dayBuckets + + const graphData = useMemo(() => { + const currentDate = Math.round(Date.now() / 1000) + return ( + data + ?.reduce((acc, cur) => { + const x = cur.date.getTime() + if (Math.round(x / 1000) >= currentDate - chartTimespans[chartRange]) { + acc.push({ + // @ts-ignore TYPE NEEDS FIXING + x, + // @ts-ignore TYPE NEEDS FIXING + y: Number( + chartType === ChartType.Volume + ? cur.volumeUSD + : chartType === ChartType.Price + ? cur.priceUSD + : cur.liquidityUSD + ), + }) + } + + return acc + }, []) + // @ts-ignore TYPE NEEDS FIXING + .sort((a, b) => a.x - b.x) + ) + }, [data, chartRange, chartType]) + + useEffect(() => setSelectedIndex(graphData?.length - 1), [graphData]) + + const [selectedIndex, setSelectedIndex] = useState(graphData?.length - 1) + + const chartButtons = ( +
+ {/*@ts-ignore TYPE NEEDS FIXING*/} + {Object.keys(chartTimespans).map((text: ChartRange) => ( + + ))} +
+ ) + + return ( +
+
+ +
{chartButtons}
+
+
+ {graphData && graphData.length > 0 && ( +
+
+ + {/*@ts-ignore TYPE NEEDS FIXING*/} + {formatNumber(graphData[selectedIndex]?.y, true, false, 2)} + + + {/*@ts-ignore TYPE NEEDS FIXING*/} + {formatDate(new Date(graphData[selectedIndex]?.x))} + +
+
+ {isDesktop ? ( + + ) : ( + + )} +
+
+ )} +
{chartButtons}
+
+ ) +} + +export default TokenStatsChart diff --git a/src/features/analytics/trident/useInstantiateTableFeatures.tsx b/src/features/analytics/trident/useInstantiateTableFeatures.tsx new file mode 100644 index 0000000000..d898f61c00 --- /dev/null +++ b/src/features/analytics/trident/useInstantiateTableFeatures.tsx @@ -0,0 +1,13 @@ +import { TableInstance } from 'app/features/transactions/types' +import { useAppSelector } from 'app/state/hooks' +import { selectTokens } from 'app/state/tokens/slice' +import { useMemo } from 'react' + +const useInstantiateFilters = (setFilter: TableInstance['setFilter']) => { + const { searchQuery } = useAppSelector(selectTokens) + useMemo(() => setFilter('name', { searchQuery }), [searchQuery, setFilter]) +} + +export const useInstantiateTableFeatures = (setFilter: TableInstance['setFilter']) => { + useInstantiateFilters(setFilter) +} diff --git a/src/features/analytics/trident/useTokensTableConfig.tsx b/src/features/analytics/trident/useTokensTableConfig.tsx new file mode 100644 index 0000000000..fee122cc87 --- /dev/null +++ b/src/features/analytics/trident/useTokensTableConfig.tsx @@ -0,0 +1,87 @@ +import { getAddress } from '@ethersproject/address' +import { Token } from '@sushiswap/core-sdk' +import { CurrencyLogo } from 'app/components/CurrencyLogo' +import { formatNumber } from 'app/functions' +import { useAllTokens } from 'app/hooks/Tokens' +import { useTridentTokens } from 'app/services/graph' +import { useMemo } from 'react' +import { UsePaginationOptions, UseSortByOptions } from 'react-table' + +import { filterForSearchQuery } from './tokenTableFilters' + +export const useTokensTableConfig = (chainId: number) => { + const { data } = useTridentTokens({ + chainId, + variables: {}, + }) + + const allTokens = useAllTokens() + const columns = useMemo( + () => [ + { + Header: 'Name', + accessor: 'name', + maxWidth: 100, + // @ts-ignore + Cell: (props) => { + const currency = useMemo(() => { + const token = props.row.original + const address = getAddress(token.id) + return address in allTokens + ? allTokens[address] + : new Token(chainId, address, Number(token.decimals), token.symbol, token.name) + }, [props]) + return ( +
+ + {props.row.original.symbol} +
+ ) + }, + filter: filterForSearchQuery, + }, + { + Header: 'Price', + accessor: 'price.derivedUSD', + minWidth: 150, + // @ts-ignore + Cell: (props) => formatNumber(props.value, true, undefined, 2), + align: 'right', + }, + { + Header: 'Liquidity', + accessor: 'kpi.liquidityUSD', + minWidth: 150, + // @ts-ignore + Cell: (props) => formatNumber(props.value, true, false), + align: 'right', + }, + { + Header: 'Volume', + accessor: 'volumeUSD', + minWidth: 150, + // @ts-ignore + Cell: (props) => formatNumber(props.value, true, false), + align: 'right', + }, + ], + [chainId] + ) + + return useMemo( + () => ({ + config: { + columns, + data: data ?? [], + initialState: { + sortBy: [{ id: 'kpi.liquidityUSD', desc: true }], + }, + autoResetFilters: false, + autoResetPage: false, + } as UseSortByOptions & UsePaginationOptions, + // loading: isValidating, + // error, + }), + [columns, data] + ) +} diff --git a/src/features/trident/pools/SearchCategoryLabel.tsx b/src/features/trident/pools/SearchCategoryLabel.tsx index c45d4f07d8..d18a5fac2a 100644 --- a/src/features/trident/pools/SearchCategoryLabel.tsx +++ b/src/features/trident/pools/SearchCategoryLabel.tsx @@ -10,7 +10,6 @@ export const SearchCategoryLabel: FC = () => { const { i18n } = useLingui() const { searchQuery } = useAppSelector(selectTridentPools) - console.log({ searchQuery }) return (
diff --git a/src/pages/analytics/trident/index.tsx b/src/pages/analytics/trident/index.tsx new file mode 100644 index 0000000000..73b63b0d1d --- /dev/null +++ b/src/pages/analytics/trident/index.tsx @@ -0,0 +1,72 @@ +import { t } from '@lingui/macro' +import { useLingui } from '@lingui/react' +import Typography from 'app/components/Typography' +import TokenSearch from 'app/features/analytics/tokens/TokenSearch' +import SearchResultTokens from 'app/features/analytics/trident/SearchResultTokens' +import { PoolSearch } from 'app/features/trident/pools/PoolSearch' +import { PoolSort } from 'app/features/trident/pools/PoolSort' +import SearchResultPools from 'app/features/trident/pools/SearchResultPools' +import { TridentBody, TridentHeader } from 'app/layouts/Trident' +import { useRouter } from 'next/router' +import { NextSeo } from 'next-seo' + +const chartTimespans = [ + { + text: '1W', + length: 604800, + }, + { + text: '1M', + length: 2629746, + }, + { + text: '1Y', + length: 31556952, + }, + { + text: 'ALL', + length: Infinity, + }, +] + +function Analytics() { + const { i18n } = useLingui() + const router = useRouter() + const chainId = Number(router.query.chainId) + + return ( + <> + + +
+ + {i18n._(t`Trident Analytics.`)} + + + {i18n._(t`Dive deeper in the analytics of Trident Pools and Tokens.`)} + +
+
+ + +
+
+
+
+ + +
+ +
+
+
+ + +
+
+
+ + ) +} + +export default Analytics diff --git a/src/pages/analytics/trident/tokens/[id].tsx b/src/pages/analytics/trident/tokens/[id].tsx new file mode 100644 index 0000000000..9d34cef176 --- /dev/null +++ b/src/pages/analytics/trident/tokens/[id].tsx @@ -0,0 +1,63 @@ +import { ChevronLeftIcon } from '@heroicons/react/solid' +import { t } from '@lingui/macro' +import { useLingui } from '@lingui/react' +import { ChainId } from '@sushiswap/core-sdk' +import Button from 'app/components/Button' +import { Feature } from 'app/enums' +import Header from 'app/features/analytics/trident/tokens/Header' +import TokenStats from 'app/features/analytics/trident/tokens/TokenStats' +import TokenStatsChart from 'app/features/analytics/trident/tokens/TokenStatsChart' +import NetworkGuard from 'app/guards/Network' +import TridentLayout, { TridentBody, TridentHeader } from 'app/layouts/Trident' +import { useTridentTokens } from 'app/services/graph' +import Link from 'next/link' +import { useRouter } from 'next/router' +import { NextSeo } from 'next-seo' + +const Token = () => { + const { i18n } = useLingui() + const router = useRouter() + const { query } = router + const chainId = Number(query.chainId) as ChainId + const id = query.id + const { data: tokens } = useTridentTokens({ chainId, variables: { where: { id } } }) + + if (!tokens || tokens.length === 0) return null + + const token = tokens[0] + return ( + <> + + +
+
+ +
+
+
+ + +
+
+
+
+
+ + +
+ + ) +} + +Token.Guard = NetworkGuard(Feature.TRIDENT) +Token.Layout = TridentLayout + +export default Token diff --git a/src/services/graph/fetchers/pager.ts b/src/services/graph/fetchers/pager.ts index 4560482ec0..59548458b7 100644 --- a/src/services/graph/fetchers/pager.ts +++ b/src/services/graph/fetchers/pager.ts @@ -10,21 +10,22 @@ export async function pager(endpoint, query, variables = {}) { while (flag) { flag = false - const req = await request(endpoint, query, variables) + try { + const req = await request(endpoint, query, variables) + Object.keys(req).forEach((key) => { + data[key] = data[key] ? [...data[key], ...req[key]] : req[key] + }) - Object.keys(req).forEach((key) => { - data[key] = data[key] ? [...data[key], ...req[key]] : req[key] - }) + Object.values(req).forEach((entry: any) => { + if (entry.length === 1000) flag = true + }) - Object.values(req).forEach((entry: any) => { - if (entry.length === 1000) flag = true - }) + // @ts-ignore TYPE NEEDS FIXING + if (Object.keys(variables).includes('first') && variables['first'] !== undefined) break - // @ts-ignore TYPE NEEDS FIXING - if (Object.keys(variables).includes('first') && variables['first'] !== undefined) break - - skip += 1000 - variables = { ...variables, skip } + skip += 1000 + variables = { ...variables, skip } + } catch (e) {} } return data } diff --git a/src/services/graph/fetchers/tokens.ts b/src/services/graph/fetchers/tokens.ts index 6e79d12575..f27e353c45 100644 --- a/src/services/graph/fetchers/tokens.ts +++ b/src/services/graph/fetchers/tokens.ts @@ -2,6 +2,8 @@ import { ChainId, Currency, CurrencyAmount } from '@sushiswap/core-sdk' import { STABLECOIN_AMOUNT_OUT } from 'app/hooks/useUSDCPrice' import { fetcher } from 'app/services/graph' import { + getTridentTokenDaySnapshotsQuery, + getTridentTokenHourSnapshotsQuery, getTridentTokenPriceQuery, getTridentTokenPricesQuery, getTridentTokensQuery, @@ -93,27 +95,133 @@ export const getTridentTokens = async ( return tokens.map((token) => ({ id: token.id, price: { - derivedNative: Number(token.price.derivedNative), - derivedUSD: Number(token.price.derivedUSD), + derivedNative: token.price ? Number(token.price.derivedNative) : 0, + derivedUSD: token.price ? Number(token.price.derivedUSD) : 0, }, kpi: { - liquidity: Number(token.kpi.liquidity), - liquidityNative: Number(token.kpi.liquidityNative), - liquidityUSD: Number(token.kpi.liquidityUSD), - volume: Number(token.kpi.volume), - volumeNative: Number(token.kpi.volumeNative), - volumeUSD: Number(token.kpi.volumeUSD), - fees: Number(token.kpi.fees), - feesNative: Number(token.kpi.feesNative), - feesUSD: Number(token.kpi.feesUSD), - transactionCount: Number(token.kpi.transactionCount), + liquidity: token.kpi ? Number(token.kpi.liquidity) : 0, + liquidityNative: token.kpi ? Number(token.kpi.liquidityNative) : 0, + liquidityUSD: token.kpi ? Number(token.kpi.liquidityUSD) : 0, + volume: token.kpi ? Number(token.kpi.volume) : 0, + volumeNative: token.kpi ? Number(token.kpi.volumeNative) : 0, + volumeUSD: token.kpi ? Number(token.kpi.volumeUSD) : 0, + fees: token.kpi ? Number(token.kpi.fees) : 0, + feesNative: token.kpi ? Number(token.kpi.feesNative) : 0, + feesUSD: token.kpi ? Number(token.kpi.feesUSD) : 0, + transactionCount: token.kpi ? Number(token.kpi.transactionCount) : 0, }, rebase: { - base: Number(token.rebase.base), - elastic: Number(token.rebase.elastic), + base: token.rebase ? Number(token.rebase.base) : 0, + elastic: token.rebase ? Number(token.rebase.elastic) : 0, }, symbol: token.symbol, name: token.name, decimals: Number(token.decimals), })) } + +interface TokenBucketQueryResult { + id: string + date: string + liquidityUSD: string + volumeUSD: string + feesUSD: string + priceUSD: string + transactionCount: string +} + +export interface TokenBucket { + date: Date + liquidityUSD: number + volumeUSD: number + feesUSD: number + priceUSD: number + transactionCount: number +} + +const formatBuckets = (buckets: TokenBucketQueryResult[]): TokenBucket[] => + buckets.map((bucket) => ({ + date: new Date(Number(bucket.date) * 1000), + liquidityUSD: Number(bucket.liquidityUSD), + volumeUSD: Number(bucket.volumeUSD), + feesUSD: Number(bucket.feesUSD), + priceUSD: Number(bucket.priceUSD), + transactionCount: Number(bucket.transactionCount), + })) + +export const getTridentTokenHourBuckets = async ( + chainId: ChainId = ChainId.ETHEREUM, + variables: any +): Promise => { + const result: TokenBucketQueryResult[] = Object.values( + await fetcher(chainId, getTridentTokenHourSnapshotsQuery, variables) + )?.[0] as TokenBucketQueryResult[] + return formatBuckets(result) +} + +export const getTridentTokenDayBuckets = async ( + chainId: ChainId = ChainId.ETHEREUM, + variables: any +): Promise => { + const result: TokenBucketQueryResult[] = Object.values( + await fetcher(chainId, getTridentTokenDaySnapshotsQuery, variables) + )?.[0] as TokenBucketQueryResult[] + return formatBuckets(result) +} + +export interface TokenKpiQueryResult { + id: string + fees: string + feesUSD: string + volume: string + volumeUSD: string + liquidity: string + liquidityUSD: string + transactionCount: string +} + +export interface TokenKpi { + id: string + fees: number + feesUSD: number + volume: number + volumeUSD: number + liquidity: number + liquidityUSD: number + transactionCount: number +} + +const formatKpi = ({ + id, + fees, + feesUSD, + volume, + volumeUSD, + liquidity, + liquidityUSD, + transactionCount, +}: TokenKpiQueryResult) => ({ + id, + fees: Number(fees), + feesUSD: Number(feesUSD), + volume: Number(volume), + volumeUSD: Number(volumeUSD), + liquidity: Number(liquidity), + liquidityUSD: Number(liquidityUSD), + transactionCount: Number(transactionCount), +}) + +// @ts-ignore TYPE NEEDS FIXING +export const getTridentTokenKpis = async (chainId: ChainId = ChainId.ETHEREUM, variables = {}): Promise => { + const result: TokenKpiQueryResult[] = Object.values( + await fetcher(chainId, getTridentTokenKpis, variables) + )?.[0] as TokenKpiQueryResult[] + return result.map(formatKpi) +} + +export const getTridentTokenKpi = async (chainId: ChainId = ChainId.ETHEREUM, variables = {}): Promise => { + const result: TokenKpiQueryResult = Object.values( + await fetcher(chainId, getTridentTokenKpi, variables) + )?.[0] as TokenKpiQueryResult + return formatKpi(result) +} diff --git a/src/services/graph/hooks/tokens.ts b/src/services/graph/hooks/tokens.ts index 62b8744a5a..d64dc7a25f 100644 --- a/src/services/graph/hooks/tokens.ts +++ b/src/services/graph/hooks/tokens.ts @@ -1,7 +1,18 @@ -import { getTridentTokenPrices } from 'app/services/graph' +import { formatNumber, formatPercent } from 'app/functions' +import { getTridentTokenPrices, useOneDayBlock, useTwoDayBlock } from 'app/services/graph' +import stringify from 'fast-json-stable-stringify' import useSWR from 'swr' -import { getTridentTokenPrice, getTridentTokens } from '../fetchers' +import { + getTridentTokenDayBuckets, + getTridentTokenHourBuckets, + getTridentTokenKpi, + getTridentTokenKpis, + getTridentTokenPrice, + getTridentTokens, + TokenBucket, +} from '../fetchers' +import { GraphProps } from '../interfaces' // @ts-ignore TYPE NEEDS FIXING export function useTridentTokens({ chainId, variables, shouldFetch = true, swrConfig = undefined }) { @@ -29,3 +40,185 @@ export function useTridentTokenPrice({ chainId, variables, shouldFetch = true, s swrConfig ) } + +export function useTridentTokenHourBuckets({ + chainId, + variables, + shouldFetch = true, + swrConfig = undefined, +}: GraphProps): TokenBucket[] { + const { data } = useSWR( + shouldFetch && !!chainId ? ['trident-token-hour-buckets', chainId, stringify(variables)] : null, + () => getTridentTokenHourBuckets(chainId, variables), + swrConfig + ) + return data +} + +export function useTridentTokenDayBuckets({ + chainId, + variables, + shouldFetch = true, + swrConfig = undefined, +}: GraphProps): TokenBucket[] { + const { data } = useSWR( + shouldFetch && !!chainId ? ['trident-token-day-buckets', chainId, stringify(variables)] : null, + () => getTridentTokenDayBuckets(chainId, variables), + swrConfig + ) + return data +} + +export function useTridentTokenKpi({ chainId, variables, shouldFetch = true, swrConfig = undefined }: GraphProps) { + return useSWR( + shouldFetch && !!chainId ? ['trident-token-kpis', chainId, stringify(variables)] : null, + () => getTridentTokenKpi(chainId, variables), + swrConfig + ) +} + +// @ts-ignore TYPE NEEDS FIXING +export function useTridentTokenKpis({ chainId, variables, shouldFetch = true, swrConfig = undefined }: GraphProps) { + return useSWR( + shouldFetch && !!chainId ? ['trident-token-kpis', chainId, stringify(variables)] : null, + () => getTridentTokenKpis(chainId, variables), + swrConfig + ) +} + +export function useTridentOneDayTokenKpis({ + chainId, + variables, + shouldFetch = true, + swrConfig = undefined, +}: GraphProps) { + return useSWR( + shouldFetch && !!chainId ? ['trident-token-kpis', chainId, stringify(variables)] : null, + () => getTridentTokenKpis(chainId, variables), + swrConfig + ) +} + +// @ts-ignore TYPE NEEDS FIXING +export function useTridentTwoDayTokenKpis({ + chainId, + variables, + shouldFetch = true, + swrConfig = undefined, +}: GraphProps) { + return useSWR( + shouldFetch && !!chainId ? ['trident-token-kpis', chainId, stringify(variables)] : null, + () => getTridentTokenKpis(chainId, variables), + swrConfig + ) +} + +export function useTridentRollingTokenStats({ + chainId, + variables, + shouldFetch = true, + swrConfig = undefined, +}: GraphProps) { + const { data: oneDayBlock } = useOneDayBlock({ chainId, shouldFetch: !!chainId }) + const { data: twoDayBlock } = useTwoDayBlock({ chainId, shouldFetch: !!chainId }) + + const { + data: tokenKpis, + isValidating: tokenKpisIsValidating, + error: tokenKpisError, + } = useTridentTokenKpis({ + chainId, + shouldFetch, + variables, + swrConfig, + }) + const { + data: oneDayTokenKpis, + isValidating: oneDayTokenKpisIsValidating, + error: oneDayTokenKpisError, + } = useTridentOneDayTokenKpis({ + chainId, + shouldFetch, + variables: { ...variables, block: oneDayBlock }, + swrConfig, + }) + const { + data: twoDayTokenKpis, + isValidating: twoDayTokenKpisIsValidating, + error: twoDayTokenKpisError, + } = useTridentTwoDayTokenKpis({ + chainId, + shouldFetch, + variables: { ...variables, block: twoDayBlock }, + swrConfig, + }) + + return { + isValidating: tokenKpisIsValidating || oneDayTokenKpisIsValidating || twoDayTokenKpisIsValidating, + error: tokenKpisError || oneDayTokenKpisError || twoDayTokenKpisError, + data: tokenKpis?.map((tokenKpi: any) => { + const oneDayTokenKpi = oneDayTokenKpis?.find((oneDayTokenKpi: any) => oneDayTokenKpi.id === tokenKpi.id) + const twoDayTokenKpi = twoDayTokenKpis?.find((twoDayTokenKpi: any) => twoDayTokenKpi.id === tokenKpi.id) + + const volume = formatNumber( + oneDayTokenKpi?.volumeUSD ? tokenKpi.volumeUSD - oneDayTokenKpi.volumeUSD : tokenKpi.volumeUSD, + true, + false + ) + + const volume24hChange = + ((tokenKpi?.volumeUSD - oneDayTokenKpi?.volumeUSD) / (oneDayTokenKpi?.volumeUSD - twoDayTokenKpi?.volumeUSD)) * + 100 - + 100 + + const fees = formatNumber( + oneDayTokenKpi ? tokenKpi?.feesUSD - oneDayTokenKpi?.feesUSD : tokenKpi?.feesUSD, + true, + false + ) + + const fees24hChange = + ((tokenKpi?.feesUSD - oneDayTokenKpi?.feesUSD) / (oneDayTokenKpi?.feesUSD - twoDayTokenKpi?.feesUSD)) * 100 - + 100 + + const liquidity = formatPercent( + ((oneDayTokenKpi ? tokenKpi?.volumeUSD - oneDayTokenKpi?.volumeUSD : tokenKpi?.volumeUSD) / + tokenKpi?.liquidityUSD) * + 100 + ) + + const transactions = oneDayTokenKpi + ? tokenKpi.transactionCount - oneDayTokenKpi.transactionCount + : tokenKpi.transactionCount + + const apy = + tokenKpi.liquidityUSD > 0 + ? (Math.max(0, oneDayTokenKpi ? tokenKpi?.feesUSD - oneDayTokenKpi?.feesUSD : tokenKpi?.feesUSD) * + 365 * + 100) / + tokenKpi?.liquidityUSD + : 0 + + return { + volume, + volume24hChange, + fees, + fees24hChange, + liquidity, + liquidity24hChange: + ((tokenKpi?.volumeUSD - oneDayTokenKpi?.volumeUSD) / + tokenKpi?.liquidityUSD / + ((oneDayTokenKpi?.volumeUSD - twoDayTokenKpi?.volumeUSD) / oneDayTokenKpi?.liquidityUSD)) * + 100 - + 100, + transactions, + transactions24hChange: + ((tokenKpi?.transactionCount - oneDayTokenKpi?.transactionCount) / + (oneDayTokenKpi?.transactionCount - twoDayTokenKpi?.transactionCount)) * + 100 - + 100, + apy, + } + }), + } +} diff --git a/src/services/graph/queries/tokens.ts b/src/services/graph/queries/tokens.ts index 099c373946..79ab06e737 100644 --- a/src/services/graph/queries/tokens.ts +++ b/src/services/graph/queries/tokens.ts @@ -51,3 +51,61 @@ export const getTridentTokensQuery = gql` } } ` + +export const getTridentTokenHourSnapshotsQuery = gql` + query tokenHourSnapshots($first: Int = 1000, $skip: Int = 0, $block: Block_height, $where: TokenDaySnapshot_filter) { + tokenHourSnapshots(first: $first, skip: $skip, block: $block, where: $where, orderBy: date, orderDirection: desc) { + id + date + liquidityUSD + volumeUSD + feesUSD + priceUSD + transactionCount + } + } +` + +export const getTridentTokenDaySnapshotsQuery = gql` + query tokenDaySnapshots($first: Int = 1000, $skip: Int = 0, $block: Block_height, $where: TokenDaySnapshot_filter) { + tokenDaySnapshots(first: $first, skip: $skip, block: $block, where: $where, orderBy: date, orderDirection: desc) { + id + date + liquidityUSD + volumeUSD + feesUSD + priceUSD + transactionCount + } + } +` + +export const getTridentTokenKpiQuery = gql` + query tridentTokenKpiQuery($id: String!, $block: Block_height, $where: PoolKpi_filter) { + poolKpi(id: $id, block: $block, where: $where) { + id + fees + feesUSD + volume + volumeUSD + liquidity + liquidityUSD + transactionCount + } + } +` + +export const getTridentTokenKpisQuery = gql` + query tokenKpisQuery($first: Int = 1000, $skip: Int = 0, $block: Block_height, $where: PoolKpi_filter) { + tokenKpis(first: $first, skip: $skip, block: $block, where: $where) { + id + fees + feesUSD + volume + volumeUSD + liquidity + liquidityUSD + transactionCount + } + } +`