diff --git a/apps/web/src/app/(evm)/[chainId]/explore/hero.tsx b/apps/web/src/app/(evm)/[chainId]/explore/hero.tsx new file mode 100644 index 0000000000..a1e0ecfda6 --- /dev/null +++ b/apps/web/src/app/(evm)/[chainId]/explore/hero.tsx @@ -0,0 +1,154 @@ +'use client' + +import { GiftIcon } from '@heroicons/react-v1/outline' +import { LinkExternal, LinkInternal, typographyVariants } from '@sushiswap/ui' +import { Button } from '@sushiswap/ui' +import { Chip } from '@sushiswap/ui' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@sushiswap/ui' +import { SelectIcon } from '@sushiswap/ui' +import { DiscordIcon } from '@sushiswap/ui/icons/DiscordIcon' +import { FC } from 'react' +import { ChainId } from 'sushi/chain' +import { + SushiSwapV3ChainId, + isSushiSwapV2ChainId, + isSushiSwapV3ChainId, +} from 'sushi/config' +import { useChainId } from 'wagmi' + +export const Hero: FC = () => { + const chainId = useChainId() + + return ( +
+
+
+

+ Put your funds to work
+ by providing liquidity. +

+

+ When you add liquidity to a pool, you can receive a share of its + trading volume and potentially snag extra rewards when there are + incentives involved! +

+
+
+
+ + + + + + + + + +
+ V3 Position + + {isSushiSwapV3ChainId(chainId as SushiSwapV3ChainId) + ? 'New 🔥' + : 'Unavailable'} + +
+

+ Provide liquidity to a V3 liquidity pool. +

+
+
+ {isSushiSwapV2ChainId(chainId as ChainId) ? ( + + +
+ V2 Position +
+

+ Provide liquidity to a V2 liquidity pool. +

+
+
+ ) : null} +
+
+
+
+ +
+
+
+
+ + Looking for a partnership with Sushi? + + +
+
+ Need Help? + +
+
+
+ ) +} diff --git a/apps/web/src/app/(evm)/[chainId]/explore/layout.tsx b/apps/web/src/app/(evm)/[chainId]/explore/layout.tsx new file mode 100644 index 0000000000..cb725bb649 --- /dev/null +++ b/apps/web/src/app/(evm)/[chainId]/explore/layout.tsx @@ -0,0 +1,29 @@ +import { Container } from '@sushiswap/ui' +import { GlobalStatsCharts } from 'src/ui/explore/global-stats-charts' +import { ChainId } from 'sushi/chain' +import { Hero } from './hero' + +export const metadata = { + title: 'Pools 💦', +} + +export default async function ExploreLayout({ + children, + params: { chainId }, +}: { children: React.ReactNode; params: { chainId: string } }) { + return ( + <> + + +
+ +
+
+
+
+ {children} +
+
+ + ) +} diff --git a/apps/web/src/app/(evm)/[chainId]/explore/page.tsx b/apps/web/src/app/(evm)/[chainId]/explore/page.tsx new file mode 100644 index 0000000000..031e191ca0 --- /dev/null +++ b/apps/web/src/app/(evm)/[chainId]/explore/page.tsx @@ -0,0 +1,43 @@ +import { getTopPools } from '@sushiswap/graph-client/data-api' +import { Container } from '@sushiswap/ui' +import { unstable_cache } from 'next/cache' +import React from 'react' +import { PoolsFiltersProvider } from 'src/ui/pool' +import { PoolsTable } from 'src/ui/pool/PoolsTable' +import { TableFiltersSmartPoolsOnly } from 'src/ui/pool/TableFilterSmartPoolsOnly' +import { TableFiltersFarmsOnly } from 'src/ui/pool/TableFiltersFarmsOnly' +import { TableFiltersNetwork } from 'src/ui/pool/TableFiltersNetwork' +import { TableFiltersPoolType } from 'src/ui/pool/TableFiltersPoolType' +import { TableFiltersResetButton } from 'src/ui/pool/TableFiltersResetButton' +import { TableFiltersSearchToken } from 'src/ui/pool/TableFiltersSearchToken' +import { ChainId } from 'sushi/chain' + +export default async function PoolPage({ + params: { chainId }, +}: { + params: { chainId: string } +}) { + const pools = await unstable_cache( + async () => getTopPools({ chainId: +chainId }), + ['pools', chainId.toString()], + { + revalidate: 60 * 3, + }, + )() + + return ( + + +
+ + + + + + +
+ +
+
+ ) +} diff --git a/apps/web/src/app/(evm)/[chainId]/layout.tsx b/apps/web/src/app/(evm)/[chainId]/layout.tsx index bd46e1474f..293a1baf59 100644 --- a/apps/web/src/app/(evm)/[chainId]/layout.tsx +++ b/apps/web/src/app/(evm)/[chainId]/layout.tsx @@ -1,15 +1,18 @@ import { HotJar } from '@sushiswap/ui' +import { isSupportedChainId } from 'src/config' import { Header } from './header' +import notFound from './not-found' import { Providers } from './providers' -export const metadata = { - title: 'Pool 💦', -} - export default function PoolLayout({ children, -}: { children: React.ReactNode }) { + params: { chainId }, +}: { children: React.ReactNode; params: { chainId: string } }) { + if (!isSupportedChainId(+chainId)) { + return notFound() + } + return ( <> diff --git a/apps/web/src/app/(evm)/[chainId]/pool/layout.tsx b/apps/web/src/app/(evm)/[chainId]/pool/layout.tsx deleted file mode 100644 index a98cc77a03..0000000000 --- a/apps/web/src/app/(evm)/[chainId]/pool/layout.tsx +++ /dev/null @@ -1,20 +0,0 @@ -// import { Container } from '@sushiswap/ui' - -// import { Hero } from '../../pool/hero' - -export default async function PoolsLayout({ - children, -}: { children: React.ReactNode }) { - return ( - <> - {/* - - */} -
-
- {children} -
-
- - ) -} diff --git a/apps/web/src/app/(evm)/[chainId]/pool/page.tsx b/apps/web/src/app/(evm)/[chainId]/pool/page.tsx deleted file mode 100644 index 35226a4ed6..0000000000 --- a/apps/web/src/app/(evm)/[chainId]/pool/page.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { getTopPools } from '@sushiswap/graph-client/data-api' -import { Container } from '@sushiswap/ui' -import { unstable_cache } from 'next/cache' -import React from 'react' - -import { NewPoolsTable } from 'src/ui/pool/NewPoolsTable' - -export default async function PoolPage({ - params, -}: { - params: { chainId: string } -}) { - const pools = await unstable_cache( - async () => getTopPools({ chainId: Number(params.chainId) }), - ['pools', params.chainId.toString()], - { - revalidate: 60 * 3, - }, - )() - - return ( - - - - ) -} diff --git a/apps/web/src/app/(evm)/[chainId]/pool/v2/[address]/(landing)/page.tsx b/apps/web/src/app/(evm)/[chainId]/pool/v2/[address]/(landing)/page.tsx index 25e0cd1bed..d5643b5db4 100644 --- a/apps/web/src/app/(evm)/[chainId]/pool/v2/[address]/(landing)/page.tsx +++ b/apps/web/src/app/(evm)/[chainId]/pool/v2/[address]/(landing)/page.tsx @@ -1,6 +1,5 @@ -import { getV2Pool } from '@sushiswap/graph-client/data-api' +import { V2Pool, getV2Pool } from '@sushiswap/graph-client/data-api' import { unstable_cache } from 'next/cache' -import { notFound } from 'next/navigation' import { PoolPageV2 } from 'src/ui/pool/PoolPageV2' @@ -10,18 +9,13 @@ export default async function PoolPage({ params: { chainId: string; address: string } }) { const { chainId, address } = params - const pool = await unstable_cache( + const pool = (await unstable_cache( async () => await getV2Pool({ chainId: Number(chainId), address }), ['pool', `${chainId}:${address}`], { revalidate: 60 * 3, }, - )() - - // Rockstar C&D - if (!pool || pool.id === '42161:0x0a4f9962e24893a4a7567e52c1ce37d5482365de') { - return notFound() - } + )()) as NonNullable return } diff --git a/apps/web/src/app/(evm)/[chainId]/pool/v2/[address]/(landing)/layout.tsx b/apps/web/src/app/(evm)/[chainId]/pool/v2/[address]/layout.tsx similarity index 86% rename from apps/web/src/app/(evm)/[chainId]/pool/v2/[address]/(landing)/layout.tsx rename to apps/web/src/app/(evm)/[chainId]/pool/v2/[address]/layout.tsx index e296cbc259..4ea0e33e55 100644 --- a/apps/web/src/app/(evm)/[chainId]/pool/v2/[address]/(landing)/layout.tsx +++ b/apps/web/src/app/(evm)/[chainId]/pool/v2/[address]/layout.tsx @@ -3,7 +3,7 @@ import { Breadcrumb, Container } from '@sushiswap/ui' import { unstable_cache } from 'next/cache' import { headers } from 'next/headers' import { PoolHeader } from 'src/ui/pool/PoolHeader' -import notFound from '../../../../not-found' +import notFound from '../../../not-found' export const metadata = { title: 'Pool 💦', @@ -18,18 +18,17 @@ export default async function Layout({ }) { const { chainId, address } = params const pool = await unstable_cache( - async () => - getV2Pool({ chainId: Number(chainId), address }), + async () => getV2Pool({ chainId: Number(chainId), address }), ['pool', `${chainId}:${address}`], { revalidate: 60 * 15, }, )() - if (!pool) { + // Rockstar C&D + if (!pool || pool.id === '42161:0x0a4f9962e24893a4a7567e52c1ce37d5482365de') { return notFound() } - const headersList = headers() const referer = headersList.get('referer') return ( diff --git a/apps/web/src/app/(evm)/[chainId]/pool/v2/[address]/manage/page.tsx b/apps/web/src/app/(evm)/[chainId]/pool/v2/[address]/manage/page.tsx new file mode 100644 index 0000000000..03ebb5b40f --- /dev/null +++ b/apps/web/src/app/(evm)/[chainId]/pool/v2/[address]/manage/page.tsx @@ -0,0 +1,49 @@ +import { V2Pool, getV2Pool } from '@sushiswap/graph-client/data-api' +import { Container } from '@sushiswap/ui' +import { unstable_cache } from 'next/cache' +import { + PoolPositionProvider, + PoolPositionRewardsProvider, + PoolPositionStakedProvider, +} from 'src/ui/pool' +import { ManageV2LiquidityCard } from 'src/ui/pool/ManageV2LiquidityCard' +import { PoolMyRewards } from 'src/ui/pool/PoolMyRewards' +import { PoolPosition } from 'src/ui/pool/PoolPosition' + +export default async function ManageV2PoolPage({ + params: { chainId, address, tab }, +}: { + params: { + chainId: string + address: string + tab: 'add' | 'remove' | 'unstake' | 'stake' + } +}) { + const pool = (await unstable_cache( + async () => getV2Pool({ chainId: Number(chainId), address }), + ['pool', `${chainId}:${address}`], + { + revalidate: 60 * 15, + }, + )()) as NonNullable + + return ( + +
+
+ +
+
+ + + + + + + + +
+
+
+ ) +} diff --git a/apps/web/src/app/(evm)/[chainId]/pool/v2/add/layout.tsx b/apps/web/src/app/(evm)/[chainId]/pool/v2/add/layout.tsx new file mode 100644 index 0000000000..7a929e95e8 --- /dev/null +++ b/apps/web/src/app/(evm)/[chainId]/pool/v2/add/layout.tsx @@ -0,0 +1,39 @@ +import { Container, typographyVariants } from '@sushiswap/ui' + +import { BackButton } from 'src/ui/pool/BackButton' + +export const metadata = { + title: 'Pool 💦', +} + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + <> + +
+
+ +

+ Add Liquidity +

+
+

+ Create a new pool or create a liquidity position on an existing + pool. +

+
+
+
+
+ + {children} + +
+
+ + ) +} diff --git a/apps/web/src/app/(evm)/[chainId]/pool/v2/add/page.tsx b/apps/web/src/app/(evm)/[chainId]/pool/v2/add/page.tsx new file mode 100644 index 0000000000..346523cfe3 --- /dev/null +++ b/apps/web/src/app/(evm)/[chainId]/pool/v2/add/page.tsx @@ -0,0 +1,394 @@ +'use client' + +import { PlusIcon } from '@heroicons/react-v1/solid' +import { FormSection } from '@sushiswap/ui' +import { Button } from '@sushiswap/ui' +import { Loader } from '@sushiswap/ui' +import { useRouter } from 'next/navigation' +import React, { + Dispatch, + FC, + ReactNode, + SetStateAction, + useCallback, + useEffect, + useMemo, + useState, +} from 'react' +import { DISABLED_CHAIN_IDS } from 'src/config' +import { APPROVE_TAG_ADD_LEGACY } from 'src/lib/constants' +import { isSushiSwapV2Pool } from 'src/lib/functions' +import { ChainId, TESTNET_CHAIN_IDS } from 'sushi/chain' +import { + SUSHISWAP_V2_ROUTER_ADDRESS, + SUSHISWAP_V2_SUPPORTED_CHAIN_IDS, + defaultCurrency, + defaultQuoteCurrency, + isSushiSwapV2ChainId, + isWNativeSupported, +} from 'sushi/config' +import { Amount, Type, tryParseAmount } from 'sushi/currency' +import { ZERO } from 'sushi/math' +import { SushiSwapV2Pool } from 'sushi/pool/sushiswap-v2' +import { SWRConfig } from 'swr' + +import { Web3Input } from 'src/lib/wagmi/components/web3-input' +import { SushiSwapV2PoolState } from 'src/lib/wagmi/hooks/pools/hooks/useSushiSwapV2Pools' +import { Checker } from 'src/lib/wagmi/systems/Checker' +import { CheckerProvider } from 'src/lib/wagmi/systems/Checker/Provider' +import { PoolFinder } from 'src/lib/wagmi/systems/PoolFinder/PoolFinder' +import { AddSectionPoolShareCardV2 } from 'src/ui/pool/AddSectionPoolShareCardV2' +import { AddSectionReviewModalLegacy } from 'src/ui/pool/AddSectionReviewModalLegacy' +import { SelectNetworkWidget } from 'src/ui/pool/SelectNetworkWidget' +import { SelectTokensWidget } from 'src/ui/pool/SelectTokensWidget' +import notFound from '../../../not-found' + +export default function Page({ params }: { params: { chainId: string } }) { + const chainId = +params.chainId as ChainId + if (!isSushiSwapV2ChainId(chainId)) { + return notFound() + } + + const router = useRouter() + const [token0, setToken0] = useState( + defaultCurrency[chainId as keyof typeof defaultCurrency], + ) + const [token1, setToken1] = useState( + defaultQuoteCurrency[chainId as keyof typeof defaultQuoteCurrency], + ) + + useEffect(() => { + setToken0(defaultCurrency[chainId as keyof typeof defaultCurrency]) + setToken1( + defaultQuoteCurrency[chainId as keyof typeof defaultQuoteCurrency], + ) + }, [chainId]) + + return ( + + + + + } + > + {({ pool: [poolState, pool] }) => { + const title = + !token0 || !token1 ? ( + 'Select Tokens' + ) : [SushiSwapV2PoolState.LOADING].includes( + poolState as SushiSwapV2PoolState, + ) ? ( +
+ +
+ ) : [SushiSwapV2PoolState.EXISTS].includes( + poolState as SushiSwapV2PoolState, + ) ? ( + 'Add Liquidity' + ) : ( + 'Create Pool' + ) + + return ( + <_Add + chainId={chainId} + setChainId={(chainId) => { + if (!isSushiSwapV2ChainId(chainId)) return + router.push(`/${chainId}/pool/v2/add`) + }} + pool={pool as SushiSwapV2Pool | null} + poolState={poolState as SushiSwapV2PoolState} + title={title} + token0={token0} + token1={token1} + setToken0={setToken0} + setToken1={setToken1} + /> + ) + }} +
+
+ ) +} + +interface AddProps { + chainId: ChainId + setChainId(chainId: ChainId): void + pool: SushiSwapV2Pool | null + poolState: SushiSwapV2PoolState + title: ReactNode + token0: Type | undefined + token1: Type | undefined + setToken0: Dispatch> + setToken1: Dispatch> +} + +const _Add: FC = ({ + chainId, + setChainId, + pool, + poolState, + title, + token0, + token1, + setToken0, + setToken1, +}) => { + const [independendField, setIndependendField] = useState(0) + + const [{ input0, input1 }, setTypedAmounts] = useState<{ + input0: string + input1: string + }>({ input0: '', input1: '' }) + + const [parsedInput0, parsedInput1] = useMemo(() => { + if (!token0 || !token1) return [undefined, undefined] + + return [ + tryParseAmount(input0, token0) || Amount.fromRawAmount(token0, 0), + tryParseAmount(input1, token1) || Amount.fromRawAmount(token1, 0), + ] + }, [input0, input1, token0, token1]) + + const noLiquidity = useMemo(() => { + return pool?.reserve0.equalTo(ZERO) && pool.reserve1.equalTo(ZERO) + }, [pool]) + + const onChangeToken0TypedAmount = useCallback( + (value: string) => { + setIndependendField(0) + if (poolState === SushiSwapV2PoolState.NOT_EXISTS || noLiquidity) { + setTypedAmounts((prev) => ({ + ...prev, + input0: value, + })) + } else if (token0 && pool) { + setTypedAmounts({ + input0: value, + input1: '', + }) + } + }, + [noLiquidity, pool, poolState, token0], + ) + + const onChangeToken1TypedAmount = useCallback( + (value: string) => { + setIndependendField(1) + if (poolState === SushiSwapV2PoolState.NOT_EXISTS || noLiquidity) { + setTypedAmounts((prev) => ({ + ...prev, + input1: value, + })) + } else if (token1 && pool) { + setTypedAmounts({ + input0: '', + input1: value, + }) + } + }, + [noLiquidity, pool, poolState, token1], + ) + + const networks = useMemo( + () => + SUSHISWAP_V2_SUPPORTED_CHAIN_IDS.filter( + (chainId) => + !TESTNET_CHAIN_IDS.includes( + chainId as (typeof TESTNET_CHAIN_IDS)[number], + ) && + !DISABLED_CHAIN_IDS.includes( + chainId as (typeof DISABLED_CHAIN_IDS)[number], + ), + ), + [], + ) + + const _setToken0 = useCallback( + (token: Type | undefined) => { + if (token?.id === token1?.id) return + setIndependendField(1) + setTypedAmounts((prev) => ({ ...prev, input0: '' })) + setToken0(token) + }, + [setToken0, token1], + ) + + const _setToken1 = useCallback( + (token: Type | undefined) => { + if (token?.id === token0?.id) return + setIndependendField(0) + setTypedAmounts((prev) => ({ ...prev, input1: '' })) + setToken1(token) + }, + [setToken1, token0], + ) + + useEffect(() => { + // Includes !!pool + if ( + pool?.reserve0.greaterThan(0) && + pool.reserve1.greaterThan(0) && + token0 && + token1 + ) { + if (independendField === 0) { + const parsedAmount = tryParseAmount(input0, token0) + setTypedAmounts({ + input0, + input1: parsedAmount + ? pool.priceOf(token0.wrapped).quote(parsedAmount.wrapped).toExact() + : '', + }) + } + + if (independendField === 1) { + const parsedAmount = tryParseAmount(input1, token1) + setTypedAmounts({ + input0: parsedAmount + ? pool.priceOf(token1.wrapped).quote(parsedAmount.wrapped).toExact() + : '', + input1, + }) + } + } + }, [independendField, pool, input0, input1, token0, token1]) + + return ( + <> + + + +
+ +
+ +
+ + + + + + + {(!pool || isSushiSwapV2Pool(pool)) && + isSushiSwapV2ChainId(chainId) && ( + <> + + + + { + setTypedAmounts({ input0: '', input1: '' }) + }} + > + + + + + + + )} + + + + +
+
+ + ) +} diff --git a/apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/(landing)/manage/[positionId]/page.tsx b/apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/(landing)/manage/[positionId]/page.tsx new file mode 100644 index 0000000000..38d41d697b --- /dev/null +++ b/apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/(landing)/manage/[positionId]/page.tsx @@ -0,0 +1,20 @@ +import { Container, LinkInternal } from '@sushiswap/ui' +import { V3PositionView } from 'src/ui/pool/V3PositionView' + +export default async function V3PositionsPage({ + params, +}: { params: { chainId: string; address: string; position: string } }) { + return ( + +
+ + ← View all positions + + +
+
+ ) +} diff --git a/apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/(landing)/manage/page.tsx b/apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/(landing)/manage/page.tsx new file mode 100644 index 0000000000..acc3ad404c --- /dev/null +++ b/apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/(landing)/manage/page.tsx @@ -0,0 +1,34 @@ +import { V3Pool, getV3Pool } from '@sushiswap/graph-client/data-api' +import { Container } from '@sushiswap/ui' +import { unstable_cache } from 'next/cache' +import { PoolsFiltersProvider } from 'src/ui/pool' +import { ConcentratedPositionsTable } from 'src/ui/pool/ConcentratedPositionsTable' + +export default async function ManageV3PoolPage({ + params, +}: { + params: { chainId: string; address: string } +}) { + const { chainId, address } = params + const pool = (await unstable_cache( + async () => await getV3Pool({ chainId: Number(chainId), address }), + ['pool', `${chainId}:${address}`], + { + revalidate: 60 * 3, + }, + )()) as NonNullable + + return ( + +
+ + + +
+
+ ) +} diff --git a/apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/(landing)/page.tsx b/apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/(landing)/page.tsx index 6d65e4e4c1..a7a52dcbbd 100644 --- a/apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/(landing)/page.tsx +++ b/apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/(landing)/page.tsx @@ -1,6 +1,5 @@ -import { getV3Pool } from '@sushiswap/graph-client/data-api' +import { V3Pool, getV3Pool } from '@sushiswap/graph-client/data-api' import { unstable_cache } from 'next/cache' -import { notFound } from 'next/navigation' import { PoolPageV3 } from 'src/ui/pool/PoolPageV3' @@ -10,18 +9,13 @@ export default async function PoolPage({ params: { chainId: string; address: string } }) { const { chainId, address } = params - const pool = await unstable_cache( + const pool = (await unstable_cache( async () => await getV3Pool({ chainId: Number(chainId), address }), ['pool', `${chainId}:${address}`], { revalidate: 60 * 3, }, - )() - - // Rockstar C&D - if (!pool || pool.id === '42161:0x0a4f9962e24893a4a7567e52c1ce37d5482365de') { - return notFound() - } + )()) as NonNullable return } diff --git a/apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/smart/[vaultId]/layout.tsx b/apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/smart/[vault]/layout.tsx similarity index 88% rename from apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/smart/[vaultId]/layout.tsx rename to apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/smart/[vault]/layout.tsx index 8ddaabc0d1..6e49646bae 100644 --- a/apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/smart/[vaultId]/layout.tsx +++ b/apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/smart/[vault]/layout.tsx @@ -11,16 +11,15 @@ export default async function Layout({ params, }: { children: React.ReactNode - params: { chainId: string; vaultId: string, address: string } + params: { chainId: string; vault: string; address: string } }) { - const poolAddress = params.address.toLowerCase() const pool = await unstable_cache( async () => await getV3Pool({ chainId: Number(params.chainId), - address: poolAddress, + address: params.address, }), - ['v3-pool', `${params.chainId}:${poolAddress}`], + ['pool', `${params.chainId}:${params.address}`], { revalidate: 60 * 15 }, )() @@ -28,14 +27,13 @@ export default async function Layout({ async () => await getVault({ chainId: Number(params.chainId), - vaultAddress: params.vaultId, + vaultAddress: params.vault, }), - ['vault', `${params.chainId}:${params.vaultId}`], + ['vault', `${params.chainId}:${params.vault}`], { revalidate: 60 * 15 }, )() if (!vault || !pool) { - console.log({pool, vault}) notFound() } diff --git a/apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/smart/[vaultId]/loading.tsx b/apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/smart/[vault]/loading.tsx similarity index 100% rename from apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/smart/[vaultId]/loading.tsx rename to apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/smart/[vault]/loading.tsx diff --git a/apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/smart/[vaultId]/page.tsx b/apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/smart/[vault]/page.tsx similarity index 76% rename from apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/smart/[vaultId]/page.tsx rename to apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/smart/[vault]/page.tsx index 6ef333bd90..ddca05b2a2 100644 --- a/apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/smart/[vaultId]/page.tsx +++ b/apps/web/src/app/(evm)/[chainId]/pool/v3/[address]/smart/[vault]/page.tsx @@ -1,8 +1,10 @@ -import { getV3Pool, getVault, VaultV1 } from '@sushiswap/graph-client/data-api' import { - getTokenRatios, - getVaultPositions, -} from '@sushiswap/steer-sdk' + V3Pool, + VaultV1, + getV3Pool, + getVault, +} from '@sushiswap/graph-client/data-api' +import { getTokenRatios, getVaultPositions } from '@sushiswap/steer-sdk' import { Container } from '@sushiswap/ui' import formatDistanceStrict from 'date-fns/formatDistanceStrict' import formatDistanceToNow from 'date-fns/formatDistanceToNow' @@ -13,9 +15,8 @@ import { } from 'src/ui/pool/Steer/SteerStrategies' import { publicClientConfig } from 'sushi/config' import { Token } from 'sushi/currency' -import { formatNumber, unsanitize } from 'sushi/format' +import { formatNumber } from 'sushi/format' import { tickToPrice } from 'sushi/pool/sushiswap-v3' -import notFound from '../../../../../not-found' import { PublicClient, createPublicClient } from 'viem' function getPriceExtremes( @@ -76,40 +77,30 @@ async function getGenerics(vault: VaultV1): Promise { export default async function SteerVaultPage({ params, -}: { params: { chainId: string, vaultId: string, address: string } }) { - - const poolAddress = params.address.toLowerCase() - const pool = await unstable_cache( +}: { params: { chainId: string; vault: string; address: string } }) { + const pool = (await unstable_cache( async () => await getV3Pool({ chainId: Number(params.chainId), - address: poolAddress, + address: params.address, }), - ['pool', `${params.chainId}:${poolAddress}`], + ['pool', `${params.chainId}:${params.address}`], { revalidate: 60 * 15 }, - )() - + )()) as NonNullable - const vaultId = unsanitize(params.vaultId) - - const vault = await unstable_cache( + const vault = (await unstable_cache( async () => - await getVault({ + await getVault({ chainId: Number(params.chainId), - vaultAddress: params.vaultId, + vaultAddress: params.vault, }), - ['vault', `${params.chainId}:${params.vaultId}`], + ['vault', `${params.chainId}:${params.vault}`], { revalidate: 60 * 15 }, - )() - - if (!pool || !vault) { - console.log({pool, vault}) - return notFound() - } + )()) as NonNullable const generics = await unstable_cache( async () => await getGenerics(vault), - ['steer-vault-generics', vaultId], + ['steer-vault-generics', `${params.chainId}:${params.vault}`], { revalidate: 60 * 15, }, diff --git a/apps/web/src/app/(evm)/[chainId]/pool/v3/add/layout.tsx b/apps/web/src/app/(evm)/[chainId]/pool/v3/add/layout.tsx new file mode 100644 index 0000000000..7a929e95e8 --- /dev/null +++ b/apps/web/src/app/(evm)/[chainId]/pool/v3/add/layout.tsx @@ -0,0 +1,39 @@ +import { Container, typographyVariants } from '@sushiswap/ui' + +import { BackButton } from 'src/ui/pool/BackButton' + +export const metadata = { + title: 'Pool 💦', +} + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + <> + +
+
+ +

+ Add Liquidity +

+
+

+ Create a new pool or create a liquidity position on an existing + pool. +

+
+
+
+
+ + {children} + +
+
+ + ) +} diff --git a/apps/web/src/app/(evm)/[chainId]/pool/v3/add/page.tsx b/apps/web/src/app/(evm)/[chainId]/pool/v3/add/page.tsx new file mode 100644 index 0000000000..018dc7a660 --- /dev/null +++ b/apps/web/src/app/(evm)/[chainId]/pool/v3/add/page.tsx @@ -0,0 +1,210 @@ +'use client' + +import React, { FC, useMemo, useState } from 'react' +import { SUPPORTED_CHAIN_IDS } from 'src/config' +import { useTokenAmountDollarValues } from 'src/lib/hooks' +import { useConcentratedPositionInfo } from 'src/lib/wagmi/hooks/positions/hooks/useConcentratedPositionInfo' +import { ConcentratedLiquidityProvider } from 'src/ui/pool/ConcentratedLiquidityProvider' +import { + ConcentratedLiquidityURLStateProvider, + useConcentratedLiquidityURLState, +} from 'src/ui/pool/ConcentratedLiquidityURLStateProvider' +import { ConcentratedLiquidityWidget } from 'src/ui/pool/ConcentratedLiquidityWidget' +import { SelectFeeConcentratedWidget } from 'src/ui/pool/SelectFeeConcentratedWidget' +import { SelectNetworkWidget } from 'src/ui/pool/SelectNetworkWidget' +import { SelectPricesWidget } from 'src/ui/pool/SelectPricesWidget' +import { SelectTokensWidget } from 'src/ui/pool/SelectTokensWidget' +import { computeSushiSwapV3PoolAddress } from 'sushi' +import { SUSHISWAP_V3_FACTORY_ADDRESS, isWNativeSupported } from 'sushi/config' +import { tryParseAmount } from 'sushi/currency' +import { SWRConfig } from 'swr' +import { useAccount } from 'wagmi' + +export default function Page() { + return ( + + + + <_Add /> + + + + ) +} + +const _Add: FC = () => { + const { address } = useAccount() + const { + chainId, + token0, + token1, + setToken1, + setToken0, + setNetwork, + feeAmount, + setFeeAmount, + tokensLoading, + tokenId, + switchTokens, + } = useConcentratedLiquidityURLState() + + const [_invert, _setInvert] = useState(false) + const { data: position } = useConcentratedPositionInfo({ + chainId, + token0, + tokenId, + token1, + }) + + const poolAddress = useMemo( + () => + token0 && token1 && feeAmount && chainId + ? computeSushiSwapV3PoolAddress({ + factoryAddress: SUSHISWAP_V3_FACTORY_ADDRESS[chainId], + tokenA: token0.wrapped, + tokenB: token1.wrapped, + fee: feeAmount, + }) + : undefined, + [chainId, feeAmount, token0, token1], + ) + + const fiatAmounts = useMemo( + () => [tryParseAmount('1', token0), tryParseAmount('1', token1)], + [token0, token1], + ) + const _fiatAmountsAsNumber = useTokenAmountDollarValues({ + chainId, + amounts: fiatAmounts, + }) + + return ( + <> + {/*
*/} + {/*
*/} + {/*
*/} + {/*
*/} + {/* */} + {/* ) : (*/} + {/*
*/} + {/* )*/} + {/* }*/} + {/* >*/} + {/* */} + {/* {token0 && !tokensLoading ? (*/} + {/* */} + {/* ) : (*/} + {/*
*/} + {/* )}*/} + {/* {token1 && !tokensLoading ? (*/} + {/* */} + {/* ) : (*/} + {/*
*/} + {/* )}*/} + {/* */} + {/* */} + {/*
*/} + {/*
*/} + {/* {token0 && token1 ? (*/} + {/* <>*/} + {/*

*/} + {/* {token0.symbol}/{token1.symbol}*/} + {/*

*/} + {/*

*/} + {/* SushiSwap V3 • {feeAmount / 10000}%*/} + {/*

*/} + {/* */} + {/* ) : tokensLoading ? (*/} + {/* <>*/} + {/* */} + {/* */} + {/* */} + {/* ) : (*/} + {/* <>*/} + {/* )}*/} + {/*
*/} + {/*
*/} + {/*
*/} + {/* Network*/} + {/*
*/} + {/* {Chain.from(chainId)?.name}*/} + {/*
*/} + {/*
*/} + {/*
*/} + {/* Fee Tier*/} + {/*
{feeAmount / 10000}% Fee
*/} + {/*
*/} + {/*
*/} + {/* Pool Type*/} + {/*
Concentrated Liquidity
*/} + {/*
*/} + {/*
*/} + {/* Current Price*/} + {/* {!isInitialLoading && !pool ? (*/} + {/* N/A*/} + {/* ) : isInitialLoading ? (*/} + {/* */} + {/* ) : token0 && token1 && pool ? (*/} + {/*
*/} + {/* */} + {/*
*/} + {/* ) : null}*/} + {/*
*/} + {/*
*/} + {/*
*/} + + + + + + + ) +} diff --git a/apps/web/src/app/(evm)/[chainId]/positions/(landing)/hero.tsx b/apps/web/src/app/(evm)/[chainId]/positions/(landing)/hero.tsx new file mode 100644 index 0000000000..8d26e88433 --- /dev/null +++ b/apps/web/src/app/(evm)/[chainId]/positions/(landing)/hero.tsx @@ -0,0 +1,14 @@ +import { FC } from 'react' + +export const Hero: FC = () => { + return ( +
+

Manage Liquidity Positions

+

+ You can adjust and claim rewards for your liquidity positions on the + connected network. For V2 pools, you can migrate to increase capital + efficiency. +

+
+ ) +} diff --git a/apps/web/src/app/(evm)/[chainId]/positions/(landing)/layout.tsx b/apps/web/src/app/(evm)/[chainId]/positions/(landing)/layout.tsx new file mode 100644 index 0000000000..cabc2d8082 --- /dev/null +++ b/apps/web/src/app/(evm)/[chainId]/positions/(landing)/layout.tsx @@ -0,0 +1,76 @@ +'use client' + +import { Container, LinkInternal } from '@sushiswap/ui' +import { useSearchParams } from 'next/navigation' +import { PathnameButton } from 'src/ui/pathname-button' +import { PoolsFiltersProvider } from 'src/ui/pool' +import { Hero } from './hero' + +export default function TabsLayout({ + children, + params: { chainId }, +}: { + children: React.ReactNode + params: { chainId: string } +}) { + const searchParams = useSearchParams() + + return ( + <> + + + + +
+ + + My Positions + + + + + My Rewards + + + + + Migrate + + +
+
+
+
+ {children} +
+
+ + ) +} diff --git a/apps/web/src/app/(evm)/[chainId]/positions/(landing)/migrate/page.tsx b/apps/web/src/app/(evm)/[chainId]/positions/(landing)/migrate/page.tsx new file mode 100644 index 0000000000..02a4ad4dc9 --- /dev/null +++ b/apps/web/src/app/(evm)/[chainId]/positions/(landing)/migrate/page.tsx @@ -0,0 +1,7 @@ +import React from 'react' + +import { MigrateTabContent } from 'src/ui/pool/MigrateTabContent' + +export default function MigratePage() { + return +} diff --git a/apps/web/src/app/(evm)/pool/(landing)/my-positions/page.tsx b/apps/web/src/app/(evm)/[chainId]/positions/(landing)/page.tsx similarity index 69% rename from apps/web/src/app/(evm)/pool/(landing)/my-positions/page.tsx rename to apps/web/src/app/(evm)/[chainId]/positions/(landing)/page.tsx index ce75c7bbcc..c5475622af 100644 --- a/apps/web/src/app/(evm)/pool/(landing)/my-positions/page.tsx +++ b/apps/web/src/app/(evm)/[chainId]/positions/(landing)/page.tsx @@ -2,21 +2,26 @@ import { Container } from '@sushiswap/ui' import React from 'react' +import { ChainId } from 'sushi/chain' import { PositionsTab } from 'src/ui/pool/PositionsTab' import { TableFiltersNetwork } from 'src/ui/pool/TableFiltersNetwork' import { TableFiltersResetButton } from 'src/ui/pool/TableFiltersResetButton' import { TableFiltersSearchToken } from 'src/ui/pool/TableFiltersSearchToken' -export default function MyPositionsPage() { +export default function MyPositionsPage({ + params: { chainId }, +}: { + params: { chainId: string } +}) { return (
- +
- +
) } diff --git a/apps/web/src/app/(evm)/pool/(landing)/my-rewards/page.tsx b/apps/web/src/app/(evm)/[chainId]/positions/(landing)/rewards/page.tsx similarity index 100% rename from apps/web/src/app/(evm)/pool/(landing)/my-rewards/page.tsx rename to apps/web/src/app/(evm)/[chainId]/positions/(landing)/rewards/page.tsx diff --git a/apps/web/src/app/(evm)/[chainId]/positions/layout.tsx b/apps/web/src/app/(evm)/[chainId]/positions/layout.tsx new file mode 100644 index 0000000000..bae85f2f59 --- /dev/null +++ b/apps/web/src/app/(evm)/[chainId]/positions/layout.tsx @@ -0,0 +1,9 @@ +export const metadata = { + title: 'Positions 💦', +} + +export default function PoolLayout({ + children, +}: { children: React.ReactNode }) { + return
{children}
+} diff --git a/apps/web/src/app/(evm)/analytics/api/bentobox/route.ts b/apps/web/src/app/(evm)/analytics/api/bentobox/route.ts deleted file mode 100644 index 3159171c2d..0000000000 --- a/apps/web/src/app/(evm)/analytics/api/bentobox/route.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { NextResponse } from 'next/server' -import { bentoBoxTokensSchema } from 'src/lib/schema' - -import { getBentoBoxTokens } from 'src/lib/graph' - -export const revalidate = 3600 - -export async function GET(request: Request) { - const { searchParams } = new URL(request.url) - const tokenSymbols = searchParams.get('tokenSymbols') - const chainIds = searchParams.get('chainIds') - const result = bentoBoxTokensSchema.safeParse({ tokenSymbols, chainIds }) - if (!result.success) { - return new Response(result.error.message, { status: 422 }) - } - const tokens = await getBentoBoxTokens(result.data) - return NextResponse.json(tokens, { - headers: { - 'Cache-Control': 'public, max-age=60, stale-while-revalidate=600', - }, - }) -} diff --git a/apps/web/src/app/(evm)/analytics/api/furoTokens/route.ts b/apps/web/src/app/(evm)/analytics/api/furoTokens/route.ts deleted file mode 100644 index f28b61b78c..0000000000 --- a/apps/web/src/app/(evm)/analytics/api/furoTokens/route.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { NextResponse } from 'next/server' -import { getFuroTokens } from 'src/lib/graph' -import { furoTokensSchema } from 'src/lib/schema' - -export const revalidate = 3600 - -export async function GET(request: Request) { - const { searchParams } = new URL(request.url) - const tokenSymbols = searchParams.get('tokenSymbols') - const chainIds = searchParams.get('chainIds') - const result = furoTokensSchema.safeParse({ tokenSymbols, chainIds }) - if (!result.success) { - return new Response(result.error.message, { status: 422 }) - } - const tokens = await getFuroTokens(result.data) - return NextResponse.json(tokens, { - headers: { - 'Cache-Control': 'public, max-age=60, stale-while-revalidate=600', - }, - }) -} diff --git a/apps/web/src/app/(evm)/analytics/api/pools/count/route.ts b/apps/web/src/app/(evm)/analytics/api/pools/count/route.ts deleted file mode 100644 index 261db7e93f..0000000000 --- a/apps/web/src/app/(evm)/analytics/api/pools/count/route.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { getPoolCount } from '@sushiswap/client' -import { PoolCountApiSchema } from '@sushiswap/client/api' -import { NextResponse } from 'next/server' - -export const revalidate = 3600 - -export async function GET(request: Request) { - const { searchParams } = new URL(request.url) - const chainIds = searchParams.get('chainIds') - const isIncentivized = searchParams.get('isIncentivized') - const isWhitelisted = searchParams.get('isWhitelisted') - const tokenSymbols = searchParams.get('tokenSymbols') - const protocols = searchParams.get('protocols') - const result = PoolCountApiSchema.safeParse({ - chainIds, - isIncentivized, - isWhitelisted, - tokenSymbols, - protocols, - }) - if (!result.success) { - return new Response(result.error.message, { status: 422 }) - } - const count = await getPoolCount(result.data) - return NextResponse.json(count, { - headers: { - 'Cache-Control': 'public, max-age=60, stale-while-revalidate=600', - }, - }) -} diff --git a/apps/web/src/app/(evm)/analytics/api/pools/route.ts b/apps/web/src/app/(evm)/analytics/api/pools/route.ts deleted file mode 100644 index 67e71cd4ab..0000000000 --- a/apps/web/src/app/(evm)/analytics/api/pools/route.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { getPools } from '@sushiswap/client' -import { PoolsApiSchema } from '@sushiswap/client/api' -import { NextResponse } from 'next/server' - -export const revalidate = 3600 - -export async function GET(request: Request) { - const { searchParams } = new URL(request.url) - const chainIds = searchParams.get('chainIds') - const isIncentivized = searchParams.get('isIncentivized') - const isWhitelisted = searchParams.get('isWhitelisted') - const tokenSymbols = searchParams.get('tokenSymbols') - const protocols = searchParams.get('protocols') - const cursor = searchParams.get('cursor') - const orderBy = searchParams.get('orderBy') - const orderDir = searchParams.get('orderDir') - const result = PoolsApiSchema.safeParse({ - chainIds, - isIncentivized, - isWhitelisted, - tokenSymbols, - protocols, - cursor, - orderBy, - orderDir, - }) - if (!result.success) { - return new Response(result.error.message, { status: 422 }) - } - const pools = await getPools(result.data) - return NextResponse.json(pools, { - headers: { - 'Cache-Control': 'public, max-age=60, stale-while-revalidate=600', - }, - }) -} diff --git a/apps/web/src/app/(evm)/analytics/header.tsx b/apps/web/src/app/(evm)/analytics/header.tsx deleted file mode 100644 index f6bcd3ca85..0000000000 --- a/apps/web/src/app/(evm)/analytics/header.tsx +++ /dev/null @@ -1,8 +0,0 @@ -'use client' - -import { Navigation } from '@sushiswap/ui' -import React, { FC } from 'react' - -export const Header: FC = () => { - return -} diff --git a/apps/web/src/app/(evm)/analytics/layout.tsx b/apps/web/src/app/(evm)/analytics/layout.tsx deleted file mode 100644 index f0a66bc1b9..0000000000 --- a/apps/web/src/app/(evm)/analytics/layout.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { Container, LinkInternal, typographyVariants } from '@sushiswap/ui' -import { HotJar } from '@sushiswap/ui' - -import { Header } from './header' - -import { PathnameButton } from 'src/ui/pool' - -export const metadata = { - title: 'Analytics', -} - -export default function AnalyticsLayout({ - children, -}: { children: React.ReactNode }) { - return ( - <> -
- -
- -
-

- Analytics. -

-

- Providing liquidity to a pool allows you to earn a percentage of - the pools traded volume as well as any extra rewards if the pool - is incentivized. -

-
-
-
-
- -
- - - Sushi Pay - - - - - Sushi Vault - - -
-
-
-
- {children} -
-
-
-
- - - ) -} diff --git a/apps/web/src/app/(evm)/analytics/page.tsx b/apps/web/src/app/(evm)/analytics/page.tsx deleted file mode 100644 index 596437925f..0000000000 --- a/apps/web/src/app/(evm)/analytics/page.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Container } from '@sushiswap/ui' -import { Suspense } from 'react' - -import { TableFiltersFarmsOnly } from 'src/ui/pool/TableFiltersFarmsOnly' -import { TableFiltersNetwork } from 'src/ui/pool/TableFiltersNetwork' -import { TableFiltersPoolType } from 'src/ui/pool/TableFiltersPoolType' -import { TableFiltersResetButton } from 'src/ui/pool/TableFiltersResetButton' -import { TableFiltersSearchToken } from 'src/ui/pool/TableFiltersSearchToken' - -export default function AnalyticsPage() { - return ( - - -
- - - - - -
-
-
- ) -} diff --git a/apps/web/src/app/(evm)/analytics/pay/page.tsx b/apps/web/src/app/(evm)/analytics/pay/page.tsx deleted file mode 100644 index 17ea446a52..0000000000 --- a/apps/web/src/app/(evm)/analytics/pay/page.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Container } from '@sushiswap/ui' -import React, { Suspense } from 'react' - -import { FuroTokenTable } from 'src/ui/analytics/furo-token-table' -import { PoolsFiltersProvider } from 'src/ui/pool' -import { TableFiltersNetwork } from 'src/ui/pool/TableFiltersNetwork' -import { TableFiltersResetButton } from 'src/ui/pool/TableFiltersResetButton' -import { TableFiltersSearchToken } from 'src/ui/pool/TableFiltersSearchToken' - -export default function PayPage() { - return ( - - - -
- - - -
- -
-
-
- ) -} diff --git a/apps/web/src/app/(evm)/analytics/vault/page.tsx b/apps/web/src/app/(evm)/analytics/vault/page.tsx deleted file mode 100644 index ee7f2e0695..0000000000 --- a/apps/web/src/app/(evm)/analytics/vault/page.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Container } from '@sushiswap/ui' -import React, { Suspense } from 'react' - -import { BentoBoxTokenTable } from './table' - -import { PoolsFiltersProvider } from 'src/ui/pool' -import { TableFiltersNetwork } from 'src/ui/pool/TableFiltersNetwork' -import { TableFiltersResetButton } from 'src/ui/pool/TableFiltersResetButton' -import { TableFiltersSearchToken } from 'src/ui/pool/TableFiltersSearchToken' - -export default function VaultPage() { - return ( - - - -
- - - -
- -
-
-
- ) -} diff --git a/apps/web/src/app/(evm)/analytics/vault/table.tsx b/apps/web/src/app/(evm)/analytics/vault/table.tsx deleted file mode 100644 index a86ff3e386..0000000000 --- a/apps/web/src/app/(evm)/analytics/vault/table.tsx +++ /dev/null @@ -1,146 +0,0 @@ -'use client' - -import { - Badge, - Card, - CardContent, - CardHeader, - CardTitle, - Currency, - DataTable, - SkeletonText, -} from '@sushiswap/ui' -import { NetworkIcon } from '@sushiswap/ui/icons/NetworkIcon' -import { - ColumnDef, - PaginationState, - SortingState, - TableState, -} from '@tanstack/react-table' -import React, { FC, useMemo, useState } from 'react' -import { formatNumber, formatUSD } from 'sushi/format' - -import { - BentoBoxToken, - GetBentoBoxTokenArgs, - useBentoBoxTokens, -} from './use-bentobox-tokens' - -import { usePoolFilters } from 'src/ui/pool' -import { BentoBoxChainId, isBentoBoxChainId } from 'sushi/config' - -const COLUMNS: ColumnDef[] = [ - { - id: 'tokenName', - header: 'Name', - cell: ({ - row: { - original: { token }, - }, - }) => ( -
-
- - } - > - - -
-
-

{token.symbol}

-

{token.name}

-
-
- ), - meta: { - skeleton: , - }, - }, - { - id: 'liquidity', - header: 'Liquidity', - cell: (props) => { - return formatNumber(props.row.original.liquidity) - }, - meta: { - skeleton: , - }, - }, - { - id: 'liquidityUSD', - header: 'Liquidity (USD)', - accessorFn: (row) => row.liquidityUSD, - cell: (props) => { - return formatUSD(props.row.original.liquidityUSD) - }, - meta: { - skeleton: , - }, - }, -] - -export const BentoBoxTokenTable: FC = () => { - const { chainIds, tokenSymbols } = usePoolFilters() - - const [sorting, setSorting] = useState([ - { id: 'liquidityUSD', desc: true }, - ]) - const [pagination, setPagination] = useState({ - pageIndex: 0, - pageSize: 10, - }) - - const args = useMemo( - () => ({ - chainIds: chainIds.filter(isBentoBoxChainId) as BentoBoxChainId[], - tokenSymbols, - }), - [chainIds, tokenSymbols], - ) - - const { data: bentoBoxTokens, isLoading } = useBentoBoxTokens(args) - - const data = useMemo(() => bentoBoxTokens || [], [bentoBoxTokens]) - - const state: Partial = useMemo(() => { - return { - sorting, - pagination, - } - }, [pagination, sorting]) - - return ( - - - - Tokens{' '} - {bentoBoxTokens?.length ? ( - - ({bentoBoxTokens?.length}) - - ) : null} - - - - - - - ) -} diff --git a/apps/web/src/app/(evm)/analytics/vault/use-bentobox-tokens.ts b/apps/web/src/app/(evm)/analytics/vault/use-bentobox-tokens.ts deleted file mode 100644 index 1ca5f728db..0000000000 --- a/apps/web/src/app/(evm)/analytics/vault/use-bentobox-tokens.ts +++ /dev/null @@ -1,71 +0,0 @@ -'use client' - -import { GetApiInputFromOutput, parseArgs } from '@sushiswap/client' -import type { BentoBoxRebases } from '@sushiswap/graph-client/bentobox' -import { useAllPrices } from '@sushiswap/react-query' -import { useMemo } from 'react' -import { Amount, Token } from 'sushi/currency' - -import { useQuery } from '@tanstack/react-query' -import { bentoBoxTokensSchema } from 'src/lib/schema' - -export type GetBentoBoxTokenArgs = GetApiInputFromOutput< - (typeof bentoBoxTokensSchema)['_input'], - (typeof bentoBoxTokensSchema)['_output'] -> - -export type BentoBoxToken = NonNullable< - ReturnType['data'] ->[number] - -export const getBentoBoxTokensUrl = (args: GetBentoBoxTokenArgs) => - `/analytics/api/bentobox${parseArgs(args)}` - -function useBentoBoxTokens(args: GetBentoBoxTokenArgs) { - const { data: rebases, isInitialLoading } = useQuery({ - queryKey: [getBentoBoxTokensUrl(args)], - queryFn: () => - fetch(getBentoBoxTokensUrl(args)).then((data) => data.json()), - }) - - const { data: prices, isLoading } = useAllPrices() - - return { - data: useMemo( - () => - prices && - rebases?.map((rebase) => { - const token = new Token({ - chainId: rebase.chainId, - decimals: rebase.token.decimals, - address: rebase.id.split(':')[1], - symbol: rebase.token.symbol, - name: rebase.token.name, - }) - - const liquidity = Amount.fromRawAmount(token, rebase.elastic) - - const price = prices?.[token.chainId]?.[token.address] - - return { - id: token.id, - token, - liquidity: parseInt(liquidity.toSignificant(6)), - liquidityUSD: price - ? parseInt( - liquidity - .multiply( - prices?.[token.chainId]?.[token.address].asFraction, - ) - .toSignificant(4), - ) - : 0, - } - }), - [rebases, prices], - ), - isLoading: isLoading || isInitialLoading, - } -} - -export { useBentoBoxTokens } diff --git a/apps/web/src/app/(evm)/pool/error.tsx b/apps/web/src/app/(evm)/pool/error.tsx deleted file mode 100644 index 3d442b8007..0000000000 --- a/apps/web/src/app/(evm)/pool/error.tsx +++ /dev/null @@ -1,49 +0,0 @@ -'use client' - -import { ChevronRightIcon } from '@heroicons/react/20/solid' -import * as Sentry from '@sentry/nextjs' -import { Button, LinkInternal, typographyVariants } from '@sushiswap/ui' -import { useEffect } from 'react' - -export default function ErrorPage({ - error, -}: { error: Error & { digest?: string }; reset: () => void }) { - useEffect(() => { - // Log the error to sentry - Sentry.captureException(error) - }, [error]) - return ( -
-
-

- Something went wrong! -

-
- - -
-
-
- ) -} diff --git a/apps/web/src/lib/hooks/api/useSushiV2UserPositions.ts b/apps/web/src/lib/hooks/api/useSushiV2UserPositions.ts index 6986e836fa..67d0dd93bc 100644 --- a/apps/web/src/lib/hooks/api/useSushiV2UserPositions.ts +++ b/apps/web/src/lib/hooks/api/useSushiV2UserPositions.ts @@ -8,12 +8,12 @@ import { import { useQuery } from '@tanstack/react-query' export function useSushiV2UserPositions( - args: GetV2Positions, + args: Partial, shouldFetch = true, ) { return useQuery({ queryKey: ['v2-positions', args], - queryFn: async () => await getV2Positions(args), + queryFn: async () => await getV2Positions(args as GetV2Positions), enabled: Boolean(shouldFetch && args.chainId && args.user), }) } diff --git a/apps/web/src/lib/hooks/api/useVault.ts b/apps/web/src/lib/hooks/api/useVault.ts index c13245c0ee..0bf7caad5b 100644 --- a/apps/web/src/lib/hooks/api/useVault.ts +++ b/apps/web/src/lib/hooks/api/useVault.ts @@ -1,16 +1,12 @@ 'use client' -import { - getVault, - GetVault, - VaultV1, -} from '@sushiswap/graph-client/data-api' +import { GetVault, VaultV1, getVault } from '@sushiswap/graph-client/data-api' import { useQuery } from '@tanstack/react-query' export function useVault(args: GetVault, shouldFetch = true) { - return useQuery({ - queryKey: ['vault', { ...args }], - queryFn: async () => await getVault(args), + return useQuery({ + queryKey: ['vault', args], + queryFn: async () => await getVault(args!), enabled: Boolean(shouldFetch && args.chainId), }) } diff --git a/apps/web/src/lib/wagmi/hooks/pools/actions/getConcentratedLiquidityPoolReserves.ts b/apps/web/src/lib/wagmi/hooks/pools/actions/getConcentratedLiquidityPoolReserves.ts index d5a0e4662a..c00d4738a2 100644 --- a/apps/web/src/lib/wagmi/hooks/pools/actions/getConcentratedLiquidityPoolReserves.ts +++ b/apps/web/src/lib/wagmi/hooks/pools/actions/getConcentratedLiquidityPoolReserves.ts @@ -1,43 +1,30 @@ -import { SUSHISWAP_V3_FACTORY_ADDRESS, SushiSwapV3ChainId } from 'sushi/config' -import { Amount } from 'sushi/currency' -import { - SushiSwapV3Pool, - computeSushiSwapV3PoolAddress, -} from 'sushi/pool/sushiswap-v3' - +import { V3Pool } from '@sushiswap/graph-client/data-api' import { PublicWagmiConfig } from '@sushiswap/wagmi-config' import { getBalance } from '@wagmi/core' +import { Amount, Token } from 'sushi/currency' + export const getConcentratedLiquidityPoolReserves = async ({ pool, - chainId, config, }: { - pool: SushiSwapV3Pool - chainId: SushiSwapV3ChainId + pool: V3Pool config: PublicWagmiConfig }) => { - const address = computeSushiSwapV3PoolAddress({ - factoryAddress: SUSHISWAP_V3_FACTORY_ADDRESS[chainId], - tokenA: pool.token0, - tokenB: pool.token1, - fee: pool.fee, - }) - const [balance1, balance2] = await Promise.all([ getBalance(config, { - address, + address: pool.address, chainId: pool.chainId, token: pool.token0.address, }), getBalance(config, { - address, + address: pool.address, chainId: pool.chainId, token: pool.token1.address, }), ]) return [ - Amount.fromRawAmount(pool.token0, balance1.value), - Amount.fromRawAmount(pool.token1, balance2.value), + Amount.fromRawAmount(new Token(pool.token0), balance1.value), + Amount.fromRawAmount(new Token(pool.token1), balance2.value), ] } diff --git a/apps/web/src/lib/wagmi/hooks/pools/hooks/useConcentratedLiquidityPoolReserves.ts b/apps/web/src/lib/wagmi/hooks/pools/hooks/useConcentratedLiquidityPoolReserves.ts index e1316d8fc0..4b648c159f 100644 --- a/apps/web/src/lib/wagmi/hooks/pools/hooks/useConcentratedLiquidityPoolReserves.ts +++ b/apps/web/src/lib/wagmi/hooks/pools/hooks/useConcentratedLiquidityPoolReserves.ts @@ -1,11 +1,11 @@ +import { V3Pool } from '@sushiswap/graph-client/data-api' import { useQuery } from '@tanstack/react-query' import { SushiSwapV3ChainId } from 'sushi/config' -import { SushiSwapV3Pool } from 'sushi/pool/sushiswap-v3' import { useConfig } from 'wagmi' import { getConcentratedLiquidityPoolReserves } from '../actions/getConcentratedLiquidityPoolReserves' interface UseConcentratedLiquidityPoolReserves { - pool: SushiSwapV3Pool | null | undefined + pool: V3Pool | null | undefined chainId: SushiSwapV3ChainId enabled?: boolean } @@ -23,7 +23,7 @@ export const useConcentratedLiquidityPoolReserves = ({ { token0: pool?.token0, token1: pool?.token1, - feeAmount: pool?.fee, + feeAmount: pool?.swapFee, chainId, }, ], @@ -31,7 +31,6 @@ export const useConcentratedLiquidityPoolReserves = ({ if (pool) { return getConcentratedLiquidityPoolReserves({ pool, - chainId, config, }) } diff --git a/apps/web/src/ui/analytics/furo-token-table.tsx b/apps/web/src/ui/analytics/furo-token-table.tsx deleted file mode 100644 index f980fd5ec6..0000000000 --- a/apps/web/src/ui/analytics/furo-token-table.tsx +++ /dev/null @@ -1,136 +0,0 @@ -'use client' - -import { - Badge, - Card, - CardContent, - CardHeader, - CardTitle, - Currency, - DataTable, - SkeletonText, -} from '@sushiswap/ui' -import { NetworkIcon } from '@sushiswap/ui/icons/NetworkIcon' -import { ColumnDef, PaginationState, SortingState } from '@tanstack/react-table' -import React, { FC, useMemo, useState } from 'react' -import { formatNumber, formatUSD } from 'sushi/format' - -import { isFuroChainId } from 'sushi/config' -import { - FuroToken, - GetFuroTokenArgs, - useFuroTokens, -} from '../../lib/analytics/use-furo-tokens' -import { usePoolFilters } from '../pool' - -const COLUMNS: ColumnDef[] = [ - { - id: 'tokenName', - header: 'Name', - cell: ({ - row: { - original: { token }, - }, - }) => ( -
-
- - } - > - - -
-
-

{token.symbol}

-

{token.name}

-
-
- ), - meta: { - skeleton: , - }, - }, - { - id: 'liquidity', - header: 'Liquidity', - cell: (props) => { - const liquidity = formatNumber(props.row.original.liquidity) - return liquidity === 'NaN' ? '0' : liquidity - }, - meta: { - skeleton: , - }, - }, - { - id: 'liquidityUSD', - header: 'Liquidity (USD)', - accessorFn: (row) => row.liquidityUSD, - cell: (props) => { - return formatUSD(props.row.original.liquidityUSD) - }, - meta: { - skeleton: , - }, - }, -] -export const FuroTokenTable: FC = () => { - const { chainIds, tokenSymbols } = usePoolFilters() - - const [sorting, setSorting] = useState([ - { id: 'liquidityUSD', desc: true }, - ]) - const [pagination, setPagination] = useState({ - pageIndex: 0, - pageSize: 10, - }) - - const args = useMemo( - () => ({ - chainIds: chainIds.filter(isFuroChainId), - tokenSymbols, - }), - [chainIds, tokenSymbols], - ) - - const { data: furoTokens, isLoading } = useFuroTokens(args) - - const data = useMemo(() => furoTokens || [], [furoTokens]) - - return ( - - - - Tokens{' '} - {furoTokens?.length ? ( - - ({furoTokens?.length}) - - ) : null} - - - - - - - ) -} diff --git a/apps/web/src/ui/analytics/token-page-header.tsx b/apps/web/src/ui/analytics/token-page-header.tsx deleted file mode 100644 index 23f67ee4c6..0000000000 --- a/apps/web/src/ui/analytics/token-page-header.tsx +++ /dev/null @@ -1,59 +0,0 @@ -// 'use client' - -// import { Bundle, Token as GraphToken } from '@sushiswap/graph-client' -// import { Button } from '@sushiswap/ui' -// import { Chip } from '@sushiswap/ui' -// import { Currency } from '@sushiswap/ui' -// import { useRouter } from 'next/router' -// import { FC } from 'react' -// import { formatUSD } from 'sushi/format' -// import useSWR from 'swr' -// import { getAddress } from 'viem' - -// import { useTokenFromToken } from '../../lib/hooks/useTokenFromToken' - -// interface TokenHeader { -// token: GraphToken -// } -// export const TokenHeader: FC = ({ token }) => { -// const router = useRouter() -// const _token = useTokenFromToken(token) -// const { data: bundles } = useSWR('/analytics/api/bundles', (url) => -// fetch(url).then((response) => response.json()), -// ) - -// const price = formatUSD( -// token.derivedETH * bundles?.[token.chainId]?.ethPrice, -// ) - -// return ( -//
-//
-//
-//
-// -//
-// -// {_token.name} -// -// {_token.symbol} -//
-// -// {price.includes('NaN') ? '$0.00' : price} -// -//
-// -//
-// ) -// } diff --git a/apps/web/src/ui/analytics/token-page-information.tsx b/apps/web/src/ui/analytics/token-page-information.tsx deleted file mode 100644 index b092859fd6..0000000000 --- a/apps/web/src/ui/analytics/token-page-information.tsx +++ /dev/null @@ -1,89 +0,0 @@ -// 'use client' - -// import { ExternalLinkIcon } from '@heroicons/react-v1/solid' -// import { Token as GraphToken } from '@sushiswap/graph-client' -// import { ClipboardController, LinkExternal } from '@sushiswap/ui' -// import { Currency } from '@sushiswap/ui' -// import { -// Table, -// TableBody, -// TableCell, -// TableHead, -// TableHeader, -// TableRow, -// } from '@sushiswap/ui' -// import { FC } from 'react' -// import { Chain } from 'sushi/chain' -// import { shortenAddress } from 'sushi/format' - -// import { useTokenFromToken } from '../../lib/hooks/useTokenFromToken' - -// interface TokenInformation { -// token: GraphToken -// } - -// export const TokenInformation: FC = ({ token }) => { -// const _token = useTokenFromToken(token) -// const chain = Chain.from(_token.chainId) as Chain - -// return ( -//
-//

Token Information

-// -// -// -//
Symbol
-//
-// -//
Name
-//
-// -//
Address
-//
-// -//
Action
-//
-//
-// -// -// -//

-// {_token.symbol} -//

-//
-// -//
-// -//

-// {_token.name} -//

-//
-//
-// -// -// {({ setCopied }) => ( -// setCopied(_token.wrapped.address)} -// onKeyDown={() => setCopied(_token.wrapped.address)} -// className="text-sm font-medium" -// > -// {shortenAddress(_token.wrapped.address)} -// -// )} -// -// -// -// -//

View

-// -//
-//
-//
-//
-//
-//
-// ) -// } diff --git a/apps/web/src/ui/analytics/token-page-pairs.tsx b/apps/web/src/ui/analytics/token-page-pairs.tsx deleted file mode 100644 index 1852a9db27..0000000000 --- a/apps/web/src/ui/analytics/token-page-pairs.tsx +++ /dev/null @@ -1,140 +0,0 @@ -// 'use client' - -// import { usePools } from '@sushiswap/client/hooks' -// import { Token as GraphToken } from '@sushiswap/graph-client' -// import { LinkExternal, LinkInternal } from '@sushiswap/ui' -// import { Currency } from '@sushiswap/ui' -// import { -// Table, -// TableBody, -// TableCell, -// TableHead, -// TableHeader, -// TableRow, -// } from '@sushiswap/ui' -// import React, { FC } from 'react' -// import chains from 'sushi/chain' -// import { Native, Token } from 'sushi/currency' -// import { formatPercent, formatUSD } from 'sushi/format' -// import { useSWRConfig } from 'swr' - -// interface TokenPairs { -// token: GraphToken -// } - -// export const TokenPairs: FC = ({ token }) => { -// const { data: pools } = usePools({ -// args: { ids: [...token.pairBase.map(({ pair }) => pair.id), ...token.pairQuote.map(({ pair }) => pair.id)] }, -// swrConfig: useSWRConfig(), -// }) - -// return ( -//
-//

Trending Pairs

-// -// -// -//
Name
-//
-// -//
TVL
-//
-// -//
Volume (7d)
-//
-// -//
APY
-//
-//
-// -// {pools && -// token.pairs.map(({ pair }, i) => { -// const _pool = pools.find((pool) => pool.id === pair.id) - -// const [token0, token1] = [ -// pair.token0.id === Native.onChain(token.chainId).wrapped.address -// ? Native.onChain(token.chainId) -// : new Token({ -// address: pair.token0.id, -// chainId: pair.chainId, -// decimals: Number(pair.token0.decimals), -// symbol: pair.token0.symbol, -// }), -// pair.token1.id === Native.onChain(token.chainId).wrapped.address -// ? Native.onChain(token.chainId) -// : new Token({ -// address: pair.token1.id, -// chainId: pair.chainId, -// decimals: Number(pair.token1.decimals), -// symbol: pair.token1.symbol, -// }), -// ] - -// const liquidityUSD = formatUSD(pair.liquidityUSD) -// const volume1w = formatUSD(pair.volume1w) - -// return ( -// -// -// -//
-// -// -// -// -// -//

-// {token0.symbol}{' '} -// /{' '} -// {token1.symbol} -//

-//
-//
-//
-//
-// -// -//

-// {liquidityUSD.includes('NaN') ? '$0.00' : liquidityUSD} -//

-//
-//
-// -// -//

-// {volume1w.includes('NaN') ? '$0.00' : volume1w} -//

-//
-//
-// -// -//

-// {formatPercent(pair.feeApr)}{' '} -// {/*{pool && pool.incentives.length > 0 && pool.incentiveApr > 0 && (*/} -// {/* */} -// {/*)}*/} -//

-//
-//
-//
-// ) -// })} -//
-//
-//
-// ) -// } diff --git a/apps/web/src/ui/analytics/token-page-stats.tsx b/apps/web/src/ui/analytics/token-page-stats.tsx deleted file mode 100644 index 38228a739a..0000000000 --- a/apps/web/src/ui/analytics/token-page-stats.tsx +++ /dev/null @@ -1,42 +0,0 @@ -// import { Token as GraphToken } from '@sushiswap/graph-client' -// import { FC } from 'react' -// import { formatUSD } from 'sushi/format' - -// interface TokenStatsProps { -// token: GraphToken -// } - -// export const TokenStats: FC = ({ token }) => { -// const tvl = formatUSD(token.liquidityUSD) -// const volume = formatUSD(token.volumeUSD) -// const fees = formatUSD(token.feesUSD) - -// return ( -//
-//
-//

Liquidity

-//

-// {tvl.includes('NaN') ? '$0.00' : tvl} -//

-//
-//
-//

Volume

-//

-// {volume.includes('NaN') ? '$0.00' : volume} -//

-//
-//
-//

Fees

-//

-// {fees.includes('NaN') ? '$0.00' : fees} -//

-//
-//
-//

Market Cap

-//

-// {fees.includes('NaN') ? '$0.00' : fees} -//

-//
-//
-// ) -// } diff --git a/apps/web/src/ui/analytics/token-table.tsx b/apps/web/src/ui/analytics/token-table.tsx deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/apps/web/src/ui/explore/global-stats-charts.tsx b/apps/web/src/ui/explore/global-stats-charts.tsx new file mode 100644 index 0000000000..bfc84cec7b --- /dev/null +++ b/apps/web/src/ui/explore/global-stats-charts.tsx @@ -0,0 +1,33 @@ +import { getAnalyticsDayBuckets } from '@sushiswap/graph-client/data-api' +import { Card } from '@sushiswap/ui' +import { unstable_cache } from 'next/cache' +import { FC } from 'react' +import { ChainId } from 'sushi/chain' +import { TVLChart } from './tvl-chart' +import { VolumeChart } from './volume-chart' + +export const GlobalStatsCharts: FC<{ chainId: ChainId }> = async ({ + chainId, +}) => { + const dayBuckets = await unstable_cache( + async () => + getAnalyticsDayBuckets({ + chainId, + }), + ['dayBuckets', `${chainId}`], + { + revalidate: 60 * 3, + }, + )() + + return ( +
+ +
+ + +
+
+
+ ) +} diff --git a/apps/web/src/ui/analytics/tvl-chart.tsx b/apps/web/src/ui/explore/tvl-chart.tsx similarity index 78% rename from apps/web/src/ui/analytics/tvl-chart.tsx rename to apps/web/src/ui/explore/tvl-chart.tsx index c48ca756b4..63868150f2 100644 --- a/apps/web/src/ui/analytics/tvl-chart.tsx +++ b/apps/web/src/ui/explore/tvl-chart.tsx @@ -16,6 +16,7 @@ import { formatUSD } from 'sushi/format' import tailwindConfig from 'tailwind.config.js' import resolveConfig from 'tailwindcss/resolveConfig' +import { AnalyticsDayBuckets } from '@sushiswap/graph-client/data-api' import ReactEchartsCore from 'echarts-for-react' import { EChartsOption } from 'echarts-for-react/lib/types' import 'echarts/lib/chart/bar' @@ -43,21 +44,39 @@ const chartTimespans: Record = { [TvlChartPeriod.All]: Infinity, } -export const TVLChart: FC<{ x: number[]; y: number[] }> = ({ x, y }) => { +export const TVLChart: FC<{ data: AnalyticsDayBuckets }> = ({ data }) => { const [chartPeriod, setChartPeriod] = useState( TvlChartPeriod.Month, ) const [xData, yData] = useMemo(() => { + const v2Data = data.v2.reverse() + const v3Data = data.v3.reverse() + + const _xData = (v2Data.length > v3Data.length ? v2Data : v3Data).map( + (data) => data.date, + ) + const currentDate = Math.round(Date.now()) - const predicates = x?.map( + const predicates = _xData.map( (x) => x * 1000 >= currentDate - chartTimespans[chartPeriod], ) - return [ - x?.filter((_x, i) => predicates[i]).reverse(), - y?.filter((_y, i) => predicates[i]).reverse(), - ] - }, [chartPeriod, x, y]) + + const yData = { + v2: Array(_xData.length - v2Data.length) + .fill(0) + .concat(v2Data.map((data) => data.liquidityUSD)) + .filter((_, i) => predicates[i]), + v3: Array(_xData.length - v3Data.length) + .fill(0) + .concat(v3Data.map((data) => data.liquidityUSD)) + .filter((_, i) => predicates[i]), + } + + const xData = _xData.filter((_, i) => predicates[i]) + + return [xData, yData] + }, [data, chartPeriod]) // Transient update for performance const onMouseOver = useCallback( @@ -94,13 +113,19 @@ export const TVLChart: FC<{ x: number[]; y: number[] }> = ({ x, y }) => { }, }, formatter: (params: any) => { - onMouseOver({ name: params[0].name, value: params[0].value }) + onMouseOver({ + name: params[0].name, + value: params[0].value + params[1].value, + }) const date = new Date(Number(params[0].name * 1000)) return `
${formatUSD( params[0].value, )} + ${formatUSD( + params[1].value, + )} ${ date instanceof Date && !Number.isNaN(date?.getTime()) ? format(date, 'dd MMM yyyy HH:mm') @@ -146,6 +171,14 @@ export const TVLChart: FC<{ x: number[]; y: number[] }> = ({ x, y }) => { max: 'dataMax', min: 'dataMin', }, + { + show: false, + type: 'value', + scale: true, + name: 'Volume', + max: 'dataMax', + min: 'dataMin', + }, ], series: [ { @@ -165,7 +198,26 @@ export const TVLChart: FC<{ x: number[]; y: number[] }> = ({ x, y }) => { }, animationEasing: 'elasticOut', animationDelayUpdate: (idx: number) => idx * 2, - data: yData, + data: yData.v2, + }, + { + name: 'Volume', + type: 'line', + xAxisIndex: 0, + yAxisIndex: 1, + itemStyle: { + color: 'red', + normal: { + barBorderRadius: 2, + }, + }, + areaStyle: { + // @ts-ignore + color: tailwind.theme.colors.red['500'], + }, + animationEasing: 'elasticOut', + animationDelayUpdate: (idx: number) => idx * 2, + data: yData.v3, }, ], }), @@ -235,13 +287,11 @@ export const TVLChart: FC<{ x: number[]; y: number[] }> = ({ x, y }) => { - {yData?.length ? ( - - {formatUSD(yData[yData.length - 1])} - - ) : ( - - )} + + {formatUSD( + yData.v2[yData.v2.length - 1] + yData.v3[yData.v3.length - 1], + )} + {xData?.length ? ( diff --git a/apps/web/src/ui/analytics/volume-chart.tsx b/apps/web/src/ui/explore/volume-chart.tsx similarity index 76% rename from apps/web/src/ui/analytics/volume-chart.tsx rename to apps/web/src/ui/explore/volume-chart.tsx index aafa38856c..2e60f81f85 100644 --- a/apps/web/src/ui/analytics/volume-chart.tsx +++ b/apps/web/src/ui/explore/volume-chart.tsx @@ -1,5 +1,6 @@ 'use client' +import { AnalyticsDayBuckets } from '@sushiswap/graph-client/data-api' import { CardContent, CardDescription, @@ -36,21 +37,39 @@ const chartTimespans: Record = { [TvlChartPeriod.All]: Infinity, } -export const VolumeChart: FC<{ x: number[]; y: number[] }> = ({ x, y }) => { +export const VolumeChart: FC<{ data: AnalyticsDayBuckets }> = ({ data }) => { const [chartPeriod, setChartPeriod] = useState( TvlChartPeriod.Month, ) const [xData, yData] = useMemo(() => { + const v2Data = data.v2.reverse() + const v3Data = data.v3.reverse() + + const _xData = (v2Data.length > v3Data.length ? v2Data : v3Data).map( + (data) => data.date, + ) + const currentDate = Math.round(Date.now()) - const predicates = x?.map( + const predicates = _xData.map( (x) => x * 1000 >= currentDate - chartTimespans[chartPeriod], ) - return [ - x?.filter((_x, i) => predicates[i]).reverse(), - y?.filter((_y, i) => predicates[i]).reverse(), - ] - }, [chartPeriod, x, y]) + + const yData = { + v2: Array(_xData.length - v2Data.length) + .fill(0) + .concat(v2Data.map((data) => data.volumeUSD)) + .filter((_, i) => predicates[i]), + v3: Array(_xData.length - v3Data.length) + .fill(0) + .concat(v3Data.map((data) => data.volumeUSD)) + .filter((_, i) => predicates[i]), + } + + const xData = _xData.filter((_, i) => predicates[i]) + + return [xData, yData] + }, [data, chartPeriod]) // Transient update for performance const onMouseOver = useCallback( @@ -89,13 +108,19 @@ export const VolumeChart: FC<{ x: number[]; y: number[] }> = ({ x, y }) => { }, }, formatter: (params: any) => { - onMouseOver({ name: params[0].name, value: params[0].value }) + onMouseOver({ + name: params[0].name, + value: params[0].value + params[1].value, + }) const date = new Date(Number(params[0].name * 1000)) return `
${formatUSD( params[0].value, )} + ${formatUSD( + params[1].value, + )} ${ date instanceof Date && !Number.isNaN(date?.getTime()) ? format(date, 'dd MMM yyyy HH:mm') @@ -122,7 +147,7 @@ export const VolumeChart: FC<{ x: number[]; y: number[] }> = ({ x, y }) => { visualMap: { show: false, // @ts-ignore - color: [tailwind.theme.colors.blue['500']], + // color: [tailwind.theme.colors.blue['500']], }, xAxis: [ { @@ -141,6 +166,14 @@ export const VolumeChart: FC<{ x: number[]; y: number[] }> = ({ x, y }) => { max: 'dataMax', min: 'dataMin', }, + { + show: false, + type: 'value', + scale: true, + name: 'Volume', + max: 'dataMax', + min: 'dataMin', + }, ], series: [ { @@ -161,7 +194,27 @@ export const VolumeChart: FC<{ x: number[]; y: number[] }> = ({ x, y }) => { }, animationEasing: 'elasticOut', animationDelayUpdate: (idx: number) => idx * 2, - data: yData, + data: yData.v2, + }, + { + name: 'Volume', + type: 'bar', + xAxisIndex: 0, + yAxisIndex: 1, + barWidth: '70%', + itemStyle: { + color: 'red', + normal: { + barBorderRadius: 2, + }, + }, + areaStyle: { + // @ts-ignore + color: tailwind.theme.colors.red['500'], + }, + animationEasing: 'elasticOut', + animationDelayUpdate: (idx: number) => idx * 2, + data: yData.v3, }, ], }), @@ -231,13 +284,11 @@ export const VolumeChart: FC<{ x: number[]; y: number[] }> = ({ x, y }) => { - {yData?.length ? ( - - {formatUSD(yData[yData.length - 1])} - - ) : ( - - )} + + {formatUSD( + yData.v2[yData.v2.length - 1] + yData.v3[yData.v3.length - 1], + )} + {xData?.length ? ( diff --git a/apps/web/src/ui/pool/ConcentratedPositionsTable/ConcentratedPositionsTable.tsx b/apps/web/src/ui/pool/ConcentratedPositionsTable/ConcentratedPositionsTable.tsx index 4b1b316273..40159ad1a2 100644 --- a/apps/web/src/ui/pool/ConcentratedPositionsTable/ConcentratedPositionsTable.tsx +++ b/apps/web/src/ui/pool/ConcentratedPositionsTable/ConcentratedPositionsTable.tsx @@ -15,9 +15,10 @@ import { ColumnDef, PaginationState, Row } from '@tanstack/react-table' import React, { FC, ReactNode, useCallback, useMemo, useState } from 'react' import { useConcentratedLiquidityPositions } from 'src/lib/wagmi/hooks/positions/hooks/useConcentratedLiquidityPositions' import { ConcentratedLiquidityPositionWithV3Pool } from 'src/lib/wagmi/hooks/positions/types' +import { ChainId } from 'sushi' import { SUSHISWAP_V3_SUPPORTED_CHAIN_IDS, - SushiSwapV3ChainId, + isSushiSwapV3ChainId, } from 'sushi/config' import { useAccount } from 'wagmi' import { usePoolFilters } from '../PoolsFiltersProvider' @@ -38,7 +39,7 @@ const COLUMNS = [ const tableState = { sorting: [{ id: 'positionSize', desc: true }] } interface ConcentratedPositionsTableProps { - chainId?: SushiSwapV3ChainId + chainId?: ChainId poolId?: string onRowClick?(row: ConcentratedLiquidityPositionWithV3Pool): void hideNewSmartPositionButton?: boolean @@ -54,11 +55,13 @@ export const ConcentratedPositionsTable: FC = hideNewPositionButton = false, }) => { const { address } = useAccount() - const { chainIds: filterChainIds, tokenSymbols } = usePoolFilters() + const { tokenSymbols } = usePoolFilters() const [hide, setHide] = useState(true) const chainIds = useMemo(() => { - if (chainId) return [chainId] as SushiSwapV3ChainId[] + if (chainId) { + return isSushiSwapV3ChainId(chainId) ? [chainId] : [] + } return [...SUSHISWAP_V3_SUPPORTED_CHAIN_IDS] }, [chainId]) @@ -76,11 +79,6 @@ export const ConcentratedPositionsTable: FC = const _positions = useMemo(() => { const _tokenSymbols = tokenSymbols?.filter((el) => el !== '') || [] return (positions || []) - ?.filter((el) => - filterChainIds.includes( - el.chainId as (typeof filterChainIds)[number], - ), - ) .filter((el) => _tokenSymbols.length > 0 ? _tokenSymbols.some((symbol) => { @@ -97,7 +95,7 @@ export const ConcentratedPositionsTable: FC = (poolId ? el.address.toLowerCase() === poolId.toLowerCase() : true) ) }) - }, [tokenSymbols, positions, filterChainIds, hide, poolId]) + }, [tokenSymbols, positions, hide, poolId]) const rowRenderer = useCallback( ( diff --git a/apps/web/src/ui/pool/ManagePosition.tsx b/apps/web/src/ui/pool/ManagePosition.tsx deleted file mode 100644 index 3763b975b4..0000000000 --- a/apps/web/src/ui/pool/ManagePosition.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React, { FC } from 'react' -import { PoolsFiltersProvider } from 'src/ui/pool/PoolsFiltersProvider' - -import { ConcentratedPositionsTable } from './ConcentratedPositionsTable/ConcentratedPositionsTable' - -interface ManagePositionProps { - address: string -} - -export const ManagePosition: FC = () => { - return ( - - - - ) -} diff --git a/apps/web/src/ui/pool/NewPoolsTable.tsx b/apps/web/src/ui/pool/NewPoolsTable.tsx deleted file mode 100644 index 23479116ad..0000000000 --- a/apps/web/src/ui/pool/NewPoolsTable.tsx +++ /dev/null @@ -1,250 +0,0 @@ -'use client' - -import { Slot } from '@radix-ui/react-slot' -import { TopPools } from '@sushiswap/graph-client/data-api' - -import { - Badge, - Card, - CardHeader, - CardTitle, - Currency, - DataTable, - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from '@sushiswap/ui' -import { NetworkIcon } from '@sushiswap/ui/icons/NetworkIcon' -import { ColumnDef, Row, SortingState, TableState } from '@tanstack/react-table' -import React, { FC, ReactNode, useCallback, useMemo, useState } from 'react' -import { ChainId } from 'sushi/chain' -import { Token } from 'sushi/currency' -import { formatNumber, formatUSD } from 'sushi/format' -import { SushiSwapProtocol } from 'sushi/types' -import { ProtocolBadge } from './PoolNameCell' -import { APR_COLUMN, TVL_COLUMN, VOLUME_1D_COLUMN } from './columns' - -const COLUMNS = [ - { - id: 'name', - header: 'Name', - - cell: (props) => { - const [token0, token1] = useMemo( - () => [ - new Token({ - chainId: props.row.original.chainId, - address: props.row.original.token0Address, - decimals: 0, - }), - new Token({ - chainId: props.row.original.chainId, - address: props.row.original.token1Address, - decimals: 0, - }), - ], - [props.row.original], - ) - - return ( -
-
- {token0 && token1 ? ( - - } - > - - - - - - ) : null} -
-
- - {props.row.original.name} - {/* {token0?.symbol}{' '} - - / - {' '} - {token1?.symbol}{' '} */} -
- -
- - - - { - ProtocolBadge[ - props.row.original.protocol as SushiSwapProtocol - ] - } - - -

Protocol version

-
-
-
- - - -
- {formatNumber(props.row.original.swapFee * 100)}% -
-
- -

Swap fee

-
-
-
- {props.row.original.isIncentivized && ( - - - -
- 🧑‍🌾{' '} -
-
- -

Farm rewards available

-
-
-
- )} - {props.row.original.isSmartPool && ( - - - -
- 💡 -
-
- -

Smart Pool available

-
-
-
- )} -
-
-
- ) - }, - size: 300, - }, - TVL_COLUMN, - { - id: 'volumeUSD1h', - header: 'Volume (60min)', - accessorFn: (row) => row.volumeUSD1h, - sortingFn: ({ original: rowA }, { original: rowB }) => - rowA.volumeUSD1h - rowB.volumeUSD1h, - cell: (props) => - formatUSD(props.row.original.volumeUSD1h).includes('NaN') - ? '$0.00' - : formatUSD(props.row.original.volumeUSD1h), - }, - VOLUME_1D_COLUMN, - { - id: 'feeUSD1h', - header: 'Fees (60min)', - accessorFn: (row) => row.feeUSD1h, - sortingFn: ({ original: rowA }, { original: rowB }) => - rowA.feeUSD1h - rowB.feeUSD1h, - cell: (props) => - formatUSD(props.row.original.feeUSD1h).includes('NaN') - ? '$0.00' - : formatUSD(props.row.original.feeUSD1h), - }, - { - id: 'feeUSD1d', - header: 'Fees (24h)', - accessorFn: (row) => row.feeUSD1d, - sortingFn: ({ original: rowA }, { original: rowB }) => - rowA.feeUSD1d - rowB.feeUSD1d, - cell: (props) => - formatUSD(props.row.original.feeUSD1d).includes('NaN') - ? '$0.00' - : formatUSD(props.row.original.feeUSD1d), - }, - APR_COLUMN, -] as ColumnDef[] - -interface PositionsTableProps { - pools: TopPools - onRowClick?(row: TopPools[number]): void -} - -export const NewPoolsTable: FC = ({ - pools, - onRowClick, -}) => { - const [sorting, setSorting] = useState([ - { id: 'liquidityUSD', desc: true }, - ]) - - const data = useMemo(() => pools?.flat() || [], [pools]) - - const state: Partial = useMemo(() => { - return { - sorting, - pagination: { - pageIndex: 0, - pageSize: data?.length, - }, - } - }, [data?.length, sorting]) - - const rowRenderer = useCallback( - (row: Row, rowNode: ReactNode) => { - if (onRowClick) - return ( - onRowClick?.(row.original)} - > - {rowNode} - - ) - return rowNode - }, - [onRowClick], - ) - - return ( - - - - Pools{' '} - {pools.length ? ( - - ({pools.length}) - - ) : null} - - - `pool/${row.protocol === SushiSwapProtocol.SUSHISWAP_V2 ? 'v2' : 'v3'}/${row.address}`} - rowRenderer={rowRenderer} - columns={COLUMNS} - data={data} - /> - - ) -} diff --git a/apps/web/src/ui/pool/PoolPageV2.tsx b/apps/web/src/ui/pool/PoolPageV2.tsx index 4e97921a13..09310082fc 100644 --- a/apps/web/src/ui/pool/PoolPageV2.tsx +++ b/apps/web/src/ui/pool/PoolPageV2.tsx @@ -1,57 +1,36 @@ import { Container, Separator } from '@sushiswap/ui' import { PoolTransactionsV2 } from 'src/ui/pool/PoolTransactionsV2' +import { V2Pool } from '@sushiswap/graph-client/data-api' import { FC } from 'react' import { PoolChartV2 } from './PoolChartV2' import { PoolComposition } from './PoolComposition' import { PoolRewards } from './PoolRewards' import { PoolStats } from './PoolStats' import { UnknownTokenAlert } from './UnknownTokenAlert' -import { V2Pool } from '@sushiswap/graph-client/data-api' interface PoolPageV2 { pool: Awaited - } export const PoolPageV2: FC = ({ pool }) => { return ( -
- {/*
-
- -
-
- - - - - - - - -
-
*/} -
- +
+
+
-
-
- -
-
- - - {pool.isIncentivized ? : null} -
+
+ + + {pool.isIncentivized ? : null}
-
- -
-
+
+ +
+ ) } diff --git a/apps/web/src/ui/pool/PoolPageV3.tsx b/apps/web/src/ui/pool/PoolPageV3.tsx index af0d875f04..b353b67247 100644 --- a/apps/web/src/ui/pool/PoolPageV3.tsx +++ b/apps/web/src/ui/pool/PoolPageV3.tsx @@ -19,16 +19,14 @@ import { } from '@sushiswap/ui' import { FC } from 'react' import { useTokenAmountDollarValues } from 'src/lib/hooks' -import { useConcentratedLiquidityPool } from 'src/lib/wagmi/hooks/pools/hooks/useConcentratedLiquidityPool' import { useConcentratedLiquidityPoolReserves } from 'src/lib/wagmi/hooks/pools/hooks/useConcentratedLiquidityPoolReserves' -import { SushiSwapV3ChainId } from 'sushi/config' import { formatUSD } from 'sushi/format' import { ConcentratedLiquidityProvider } from './ConcentratedLiquidityProvider' import { PoolRewardDistributionsCard } from './PoolRewardDistributionsCard' import { PoolTransactionsV3 } from './PoolTransactionsV3' import { StatisticsChartsV3 } from './StatisticsChartV3' -const PoolPageV3: FC<{ pool: Awaited }> = ({ pool }) => { +const PoolPageV3: FC<{ pool: V3Pool }> = ({ pool }) => { return ( @@ -36,25 +34,17 @@ const PoolPageV3: FC<{ pool: Awaited }> = ({ pool }) => { ) } -const Pool: FC<{ pool: Awaited }> = ({ pool }) => { - const { id } = pool - const [_chainId, address] = id.split(':') - const chainId = +_chainId as SushiSwapV3ChainId +const Pool: FC<{ pool: V3Pool }> = ({ pool }) => { + const { chainId, address } = pool const { data: poolStats } = useConcentratedLiquidityPoolStats({ chainId, address, }) - const { data: cPool } = useConcentratedLiquidityPool({ - chainId, - token0: poolStats?.token0, - token1: poolStats?.token1, - feeAmount: poolStats?.feeAmount, - }) const { data: reserves, isLoading: isReservesLoading } = useConcentratedLiquidityPoolReserves({ - pool: cPool, + pool, chainId, }) const fiatValues = useTokenAmountDollarValues({ chainId, amounts: reserves }) @@ -62,7 +52,7 @@ const Pool: FC<{ pool: Awaited }> = ({ pool }) => { return (
- {pool?.hasEnabledSteerVault && ( + {pool.hasEnabledSteerVault && ( {`This pool has been activated to leverage our smart pool feature. Smart pools are designed to optimize the allocation of liquidity within customized price ranges, thereby improving trading efficiency. They achieve @@ -79,13 +69,6 @@ const Pool: FC<{ pool: Awaited }> = ({ pool }) => { )} - {/* - - */}
diff --git a/apps/web/src/ui/pool/PoolPositionProvider.tsx b/apps/web/src/ui/pool/PoolPositionProvider.tsx index 054b495175..1341d5fe7b 100644 --- a/apps/web/src/ui/pool/PoolPositionProvider.tsx +++ b/apps/web/src/ui/pool/PoolPositionProvider.tsx @@ -1,15 +1,15 @@ 'use client' +import { V2Pool, V3Pool } from '@sushiswap/graph-client/data-api' import { FC, ReactNode, createContext, useContext, useMemo } from 'react' import { + getTokensFromPool, useTokenAmountDollarValues, useUnderlyingTokenBalanceFromPool, - useV2Pool, } from 'src/lib/hooks' import { useBalanceWeb3 } from 'src/lib/wagmi/hooks/balances/useBalanceWeb3' import { ChainId } from 'sushi/chain' import { Amount, Type } from 'sushi/currency' -import type { PoolId } from 'sushi/types' import { useAccount } from 'wagmi' interface PoolPositionContext { @@ -25,15 +25,34 @@ interface PoolPositionContext { const Context = createContext(undefined) export const PoolPositionProvider: FC<{ - pool: PoolId + pool: NonNullable children: ReactNode watch?: boolean }> = ({ pool, children }) => { const { address: account } = useAccount() - const { - data: { reserve0, reserve1, totalSupply, liquidityToken }, - } = useV2Pool(pool) + const { liquidityToken, reserve0, reserve1, totalSupply } = useMemo(() => { + if (!pool) + return { + token0: undefined, + token1: undefined, + liquidityToken: undefined, + } + + const { token0, token1, liquidityToken } = getTokensFromPool(pool) + + return { + liquidityToken, + reserve0: + token0 && pool ? Amount.fromRawAmount(token0, pool.reserve0) : null, + reserve1: + token1 && pool ? Amount.fromRawAmount(token1, pool.reserve1) : null, + totalSupply: + liquidityToken && pool + ? Amount.fromRawAmount(liquidityToken, pool.liquidity) + : null, + } + }, [pool]) const { data: balance, diff --git a/apps/web/src/ui/pool/PoolPositionStakedProvider.tsx b/apps/web/src/ui/pool/PoolPositionStakedProvider.tsx index 9f05d0127a..63e9a6faa4 100644 --- a/apps/web/src/ui/pool/PoolPositionStakedProvider.tsx +++ b/apps/web/src/ui/pool/PoolPositionStakedProvider.tsx @@ -1,13 +1,14 @@ 'use client' +import { V2Pool, V3Pool } from '@sushiswap/graph-client/data-api' import { FC, ReactNode, createContext, useContext, useMemo } from 'react' import { + getTokensFromPool, useTokenAmountDollarValues, useUnderlyingTokenBalanceFromPool, - useV2Pool, } from 'src/lib/hooks' import { useMasterChef } from 'src/lib/wagmi/hooks/master-chef/use-master-chef' -import type { ChefType, PoolId, PoolWithIncentives } from 'sushi' +import type { ChefType } from 'sushi' import { ChainId } from 'sushi/chain' import { Amount, Currency, Token } from 'sushi/currency' @@ -26,7 +27,7 @@ interface PoolPositionStakedContext { const Context = createContext(undefined) interface PoolPositionStakedProviderProps { - pool: PoolWithIncentives + pool: V2Pool | V3Pool children: ReactNode watch?: boolean } @@ -65,7 +66,7 @@ export const PoolPositionStakedProvider: FC = } interface _PoolPositionStakedProviderProps { - pool: PoolId + pool: V2Pool | V3Pool children: ReactNode farmId: number chefType: ChefType @@ -79,9 +80,28 @@ const _PoolPositionStakedProvider: FC<_PoolPositionStakedProviderProps> = ({ chefType, children, }) => { - const { - data: { reserve0, reserve1, totalSupply, liquidityToken }, - } = useV2Pool(pool) + const { liquidityToken, reserve0, reserve1, totalSupply } = useMemo(() => { + if (!pool) + return { + token0: undefined, + token1: undefined, + liquidityToken: undefined, + } + + const { token0, token1, liquidityToken } = getTokensFromPool(pool) + + return { + liquidityToken, + reserve0: + token0 && pool ? Amount.fromRawAmount(token0, pool.reserve0) : null, + reserve1: + token1 && pool ? Amount.fromRawAmount(token1, pool.reserve1) : null, + totalSupply: + liquidityToken && pool + ? Amount.fromRawAmount(liquidityToken, pool.liquidity) + : null, + } + }, [pool]) const { balance, isLoading, isError, isWritePending, isWriteError } = useMasterChef({ diff --git a/apps/web/src/ui/pool/PoolsFiltersProvider.tsx b/apps/web/src/ui/pool/PoolsFiltersProvider.tsx index c5e9359afe..665bd6c499 100644 --- a/apps/web/src/ui/pool/PoolsFiltersProvider.tsx +++ b/apps/web/src/ui/pool/PoolsFiltersProvider.tsx @@ -11,12 +11,11 @@ import { useContext, useMemo, } from 'react' -import { SUPPORTED_CHAIN_IDS, isSupportedChainId } from 'src/config' +import { SushiSwapProtocol } from 'sushi' import { z } from 'zod' import { useTypedSearchParams } from '../../lib/hooks' import { POOL_TYPES } from './TableFiltersPoolType' -import { SushiSwapProtocol } from 'sushi' type FilterContext = z.TypeOf @@ -33,15 +32,6 @@ export const poolFiltersSchema = z.object({ tokenSymbols: z.coerce.string().transform((symbols) => { return symbols.split(',').filter((symbol) => symbol !== '') }), - chainIds: z.coerce - .string() - .default(SUPPORTED_CHAIN_IDS.join(',')) - .transform((chainIds) => - chainIds !== null && chainIds !== ',' - ? chainIds.split(',').map((chainId) => Number(chainId)) - : SUPPORTED_CHAIN_IDS, - ) - .transform((chainIds) => chainIds.filter(isSupportedChainId)), protocols: z .string() .transform((protocols) => @@ -61,20 +51,18 @@ export const PoolsFiltersProvider: FC = ({ children, }) => { const urlFilters = useTypedSearchParams(poolFiltersSchema.partial()) - const { tokenSymbols, chainIds, protocols, farmsOnly, smartPoolsOnly } = - urlFilters + const { tokenSymbols, protocols, farmsOnly, smartPoolsOnly } = urlFilters return ( ({ tokenSymbols: tokenSymbols ? tokenSymbols : [], - chainIds: chainIds ? chainIds : SUPPORTED_CHAIN_IDS, protocols: protocols ? protocols : POOL_TYPES, farmsOnly: farmsOnly ? farmsOnly : false, smartPoolsOnly: smartPoolsOnly ? smartPoolsOnly : false, }), - [chainIds, farmsOnly, protocols, tokenSymbols, smartPoolsOnly], + [farmsOnly, protocols, tokenSymbols, smartPoolsOnly], )} > {children} diff --git a/apps/web/src/ui/pool/PoolsTable.tsx b/apps/web/src/ui/pool/PoolsTable.tsx index 4f0be23ba9..86c6c97b8c 100644 --- a/apps/web/src/ui/pool/PoolsTable.tsx +++ b/apps/web/src/ui/pool/PoolsTable.tsx @@ -1,23 +1,26 @@ 'use client' +import { Slot } from '@radix-ui/react-slot' +import { TopPools } from '@sushiswap/graph-client/data-api' + import { UploadIcon } from '@heroicons/react-v1/outline' import { DownloadIcon } from '@heroicons/react-v1/solid' -import { ArrowDownRightIcon } from '@heroicons/react/20/solid' import { + ArrowDownRightIcon, EllipsisHorizontalIcon, GiftIcon, LightBulbIcon, MinusIcon, PlusIcon, } from '@heroicons/react/24/outline' -import { Slot } from '@radix-ui/react-slot' -import { GetPoolsArgs } from '@sushiswap/client' import { + Badge, Button, Card, CardHeader, CardTitle, Chip, + Currency, DataTable, DropdownMenu, DropdownMenuContent, @@ -27,51 +30,180 @@ import { DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, - Loader, SkeletonText, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '@sushiswap/ui' +import { NetworkIcon } from '@sushiswap/ui/icons/NetworkIcon' import { ColumnDef, Row, SortingState, TableState } from '@tanstack/react-table' import Link from 'next/link' import React, { FC, ReactNode, useCallback, useMemo, useState } from 'react' -import InfiniteScroll from 'react-infinite-scroll-component' -import { Native } from 'sushi/currency' -import { useSWRConfig } from 'swr' - -import { usePoolCount, usePoolsInfinite } from '@sushiswap/client/hooks' -import { PoolHasSteerVaults } from '@sushiswap/steer-sdk' +import { ChainId } from 'sushi/chain' import { isAngleEnabledChainId } from 'sushi/config' -import { - PoolBase, - PoolHistory, - PoolIfIncentivized, - unnestPool, -} from 'sushi/types' +import { Native, Token } from 'sushi/currency' +import { formatNumber, formatUSD } from 'sushi/format' +import { SushiSwapProtocol } from 'sushi/types' +import { ProtocolBadge } from './PoolNameCell' import { usePoolFilters } from './PoolsFiltersProvider' -import { - APR_COLUMN, - FEES_COLUMN, - NAME_COLUMN_POOL, - TVL_COLUMN, - VOLUME_1D_COLUMN, - VOLUME_1M_COLUMN, - VOLUME_1W_COLUMN, -} from './columns' - -type RequiredPool = PoolHasSteerVaults< - PoolIfIncentivized> -> +import { APR_COLUMN, TVL_COLUMN, VOLUME_1D_COLUMN } from './columns' const COLUMNS = [ - NAME_COLUMN_POOL, + { + id: 'name', + header: 'Name', + + cell: (props) => { + const [token0, token1] = useMemo( + () => [ + new Token({ + chainId: props.row.original.chainId, + address: props.row.original.token0Address, + decimals: 0, + }), + new Token({ + chainId: props.row.original.chainId, + address: props.row.original.token1Address, + decimals: 0, + }), + ], + [props.row.original], + ) + + return ( +
+
+ {token0 && token1 ? ( + + } + > + + + + + + ) : null} +
+
+ + {props.row.original.name} + {/* {token0?.symbol}{' '} + + / + {' '} + {token1?.symbol}{' '} */} +
+ +
+ + + + { + ProtocolBadge[ + props.row.original.protocol as SushiSwapProtocol + ] + } + + +

Protocol version

+
+
+
+ + + +
+ {formatNumber(props.row.original.swapFee * 100)}% +
+
+ +

Swap fee

+
+
+
+ {props.row.original.isIncentivized && ( + + + +
+ 🧑‍🌾{' '} +
+
+ +

Farm rewards available

+
+
+
+ )} + {props.row.original.isSmartPool && ( + + + +
+ 💡 +
+
+ +

Smart Pool available

+
+
+
+ )} +
+
+
+ ) + }, + size: 300, + }, TVL_COLUMN, + { + id: 'volumeUSD1h', + header: 'Volume (60min)', + accessorFn: (row) => row.volumeUSD1h, + sortingFn: ({ original: rowA }, { original: rowB }) => + rowA.volumeUSD1h - rowB.volumeUSD1h, + cell: (props) => + formatUSD(props.row.original.volumeUSD1h).includes('NaN') + ? '$0.00' + : formatUSD(props.row.original.volumeUSD1h), + }, VOLUME_1D_COLUMN, - VOLUME_1W_COLUMN, - VOLUME_1M_COLUMN, - FEES_COLUMN, + { + id: 'feeUSD1h', + header: 'Fees (60min)', + accessorFn: (row) => row.feeUSD1h, + sortingFn: ({ original: rowA }, { original: rowB }) => + rowA.feeUSD1h - rowB.feeUSD1h, + cell: (props) => + formatUSD(props.row.original.feeUSD1h).includes('NaN') + ? '$0.00' + : formatUSD(props.row.original.feeUSD1h), + }, + { + id: 'feeUSD1d', + header: 'Fees (24h)', + accessorFn: (row) => row.feeUSD1d, + sortingFn: ({ original: rowA }, { original: rowB }) => + rowA.feeUSD1d - rowB.feeUSD1d, + cell: (props) => + formatUSD(props.row.original.feeUSD1d).includes('NaN') + ? '$0.00' + : formatUSD(props.row.original.feeUSD1d), + }, APR_COLUMN, { id: 'actions', @@ -85,7 +217,7 @@ const COLUMNS = [ - {row.original.token0.symbol} / {row.original.token1.symbol} + {row.original.name} SushiSwap V3 @@ -116,10 +248,10 @@ const COLUMNS = [ - + e.stopPropagation()} @@ -143,7 +275,7 @@ const COLUMNS = [

- {!row.original.hasEnabledSteerVault + {!row.original.isSmartPool ? 'No Steer vaults available for this pool' : `Smart pools optimize liquidity allocation within custom price ranges, enhancing trading efficiency by providing deeper liquidity around the current price, increasing Liquidity Providers (LP) fee earnings.`} @@ -170,15 +302,15 @@ const COLUMNS = [ href={`/pool/incentivize?chainId=${ row.original.chainId }&fromCurrency=${ - row.original.token0.address === + row.original.token0Address === Native.onChain(row.original.chainId).wrapped.address ? 'NATIVE' - : row.original.token0.address + : row.original.token0Address }&toCurrency=${ - row.original.token1.address === + row.original.token1Address === Native.onChain(row.original.chainId).wrapped.address ? 'NATIVE' - : row.original.token1.address + : row.original.token1Address }&feeAmount=${row.original.swapFee * 10_000 * 100}`} > @@ -207,7 +339,7 @@ const COLUMNS = [ - {row.original.token0.symbol} / {row.original.token1.symbol} + {row.original.name} {row.original.protocol === 'SUSHISWAP_V2' && ( SushiSwap V2 @@ -288,67 +420,47 @@ const COLUMNS = [ disableLink: true, skeleton: , }, - } satisfies ColumnDef, -] as ColumnDef[] + } satisfies ColumnDef, +] as ColumnDef[] interface PositionsTableProps { - onRowClick?(row: RequiredPool): void + pools: TopPools + onRowClick?(row: TopPools[number]): void } -export const PoolsTable: FC = ({ onRowClick }) => { - const { chainIds, tokenSymbols, protocols, farmsOnly, smartPoolsOnly } = +export const PoolsTable: FC = ({ pools, onRowClick }) => { + const { tokenSymbols, protocols, farmsOnly, smartPoolsOnly } = usePoolFilters() + const [sorting, setSorting] = useState([ { id: 'liquidityUSD', desc: true }, ]) - const sortingId = useMemo(() => { - switch (sorting[0]?.id) { - case 'volumeUSD1d': - return 'volume1d' - case 'volumeUSD1w': - return 'volume1w' - case 'volumeUSD1m': - return 'volume1m' - default: - return sorting[0]?.id - } - }, [sorting]) + const data = useMemo( + () => + pools?.flat()?.filter((pool) => { + if ( + tokenSymbols.length && + !tokenSymbols.some((tokenSymbol) => + pool.name.toLowerCase().includes(tokenSymbol.toLowerCase()), + ) + ) + return false - const args = useMemo(() => { - return { - chainIds: chainIds, - tokenSymbols, - isIncentivized: farmsOnly || undefined, // will filter farms out if set to false, undefined will be filtered out by the parser - hasEnabledSteerVault: smartPoolsOnly || undefined, // will filter smart pools out if set to false, undefined will be filtered out by the parser - isWhitelisted: true, // can be added to filters later, need to put it here so fallback works - orderBy: sortingId, - orderDir: sorting[0] ? (sorting[0].desc ? 'desc' : 'asc') : 'desc', - protocols, - } - }, [ - chainIds, - tokenSymbols, - farmsOnly, - smartPoolsOnly, - sorting, - sortingId, - protocols, - ]) + if ( + protocols.length && + !protocols.some((protocol) => pool.protocol === protocol) + ) + return false - const { - data: pools, - isValidating, - setSize, - } = usePoolsInfinite({ args, shouldFetch: true, swrConfig: useSWRConfig() }) + if (smartPoolsOnly && !pool.isSmartPool) return false - const { data: poolCount } = usePoolCount({ - args, - shouldFetch: true, - swrConfig: useSWRConfig(), - }) + if (farmsOnly && !pool.isIncentivized) return false - const data = useMemo(() => pools?.flat() || [], [pools]) + return true + }) || [], + [pools, tokenSymbols, protocols, farmsOnly, smartPoolsOnly], + ) const state: Partial = useMemo(() => { return { @@ -361,7 +473,7 @@ export const PoolsTable: FC = ({ onRowClick }) => { }, [data?.length, sorting]) const rowRenderer = useCallback( - (row: Row, rowNode: ReactNode) => { + (row: Row, rowNode: ReactNode) => { if (onRowClick) return ( = ({ onRowClick }) => { ) return ( - setSize((prev) => prev + 1)} - hasMore={data.length < (poolCount?.count || 0)} - loader={ -

- -
- } - > - - - - Pools{' '} - {poolCount?.count ? ( - - ({poolCount.count}) - - ) : null} - - - - `/pool/${unnestPool(row).chainId}%3A${unnestPool(row).address}` - } - rowRenderer={rowRenderer} - columns={COLUMNS} - data={data} - /> - - + + + + Pools{' '} + {data.length ? ( + + ({data.length}) + + ) : null} + + + + `pool/${ + row.protocol === SushiSwapProtocol.SUSHISWAP_V2 ? 'v2' : 'v3' + }/${row.address}` + } + rowRenderer={rowRenderer} + columns={COLUMNS} + data={data} + /> + ) } diff --git a/apps/web/src/ui/pool/PositionQuickHoverTooltip.tsx b/apps/web/src/ui/pool/PositionQuickHoverTooltip.tsx index dc258dd58d..a81d7c1387 100644 --- a/apps/web/src/ui/pool/PositionQuickHoverTooltip.tsx +++ b/apps/web/src/ui/pool/PositionQuickHoverTooltip.tsx @@ -1,21 +1,15 @@ import { ArrowDownIcon, MinusIcon, PlusIcon } from '@heroicons/react-v1/solid' +import { V2Pool, V3Pool } from '@sushiswap/graph-client/data-api' import { Button } from '@sushiswap/ui' import { Currency } from '@sushiswap/ui' import { LinkInternal } from '@sushiswap/ui' import { List } from '@sushiswap/ui' import React, { FC, useCallback } from 'react' +import { ChainId } from 'sushi/chain' import { formatPercent, formatUSD } from 'sushi/format' import { ZERO } from 'sushi/math' - -import type { - PoolBase, - PoolWithAprs, - PoolWithIncentives, - SushiPositionStaked, - SushiPositionWithPool, -} from 'sushi' -import { ChainId } from 'sushi/chain' import { useAccount, useSwitchChain } from 'wagmi' + import { PoolPositionProvider, usePoolPosition } from './PoolPositionProvider' import { PoolPositionRewardsProvider, @@ -27,19 +21,17 @@ import { } from './PoolPositionStakedProvider' interface PositionQuickHoverTooltipProps { - row: SushiPositionStaked< - SushiPositionWithPool>> - > + pool: NonNullable } export const PositionQuickHoverTooltip: FC = ({ - row, + pool, }) => { return ( - - - - <_PositionQuickHoverTooltip row={row} /> + + + + <_PositionQuickHoverTooltip pool={pool} /> @@ -47,7 +39,7 @@ export const PositionQuickHoverTooltip: FC = ({ } const _PositionQuickHoverTooltip: FC = ({ - row, + pool, }) => { const { switchChain } = useSwitchChain() const { chain } = useAccount() @@ -63,12 +55,12 @@ const _PositionQuickHoverTooltip: FC = ({ usePoolPositionRewards() const _harvest = useCallback(() => { - if (row.pool.chainId !== chain?.id) { - switchChain?.({ chainId: row.pool.chainId as ChainId }) + if (pool.chainId !== chain?.id) { + switchChain?.({ chainId: pool.chainId as ChainId }) } else if (harvest) { harvest() } - }, [chain?.id, harvest, row.pool.chainId, switchChain]) + }, [chain?.id, harvest, pool.chainId, switchChain]) return (
@@ -80,21 +72,18 @@ const _PositionQuickHoverTooltip: FC = ({ • Rewards + Fees - {formatPercent(row.pool.totalApr1d)}{' '} + {formatPercent(pool.totalApr1d)}{' '} - {formatPercent(row.pool.incentiveApr)} +{' '} - {formatPercent(row.pool.feeApr1d)} + {formatPercent(pool.incentiveApr)} + {formatPercent(pool.feeApr1d)}
@@ -108,7 +97,7 @@ const _PositionQuickHoverTooltip: FC = ({
- {row.pool.incentives && pendingRewards.length > 0 && ( + {pool.incentives && pendingRewards.length > 0 && ( <> Pending rewards @@ -184,7 +173,7 @@ const _PositionQuickHoverTooltip: FC = ({ - {row.pool.incentives && + {pool.incentives && stakedUnderlying0?.greaterThan(ZERO) && stakedUnderlying1?.greaterThan(ZERO) && ( diff --git a/apps/web/src/ui/pool/PositionsTab.tsx b/apps/web/src/ui/pool/PositionsTab.tsx index 3c466694f6..aed26fd7e6 100644 --- a/apps/web/src/ui/pool/PositionsTab.tsx +++ b/apps/web/src/ui/pool/PositionsTab.tsx @@ -9,8 +9,9 @@ import { TabsList, TabsTrigger, } from '@sushiswap/ui' -import React, { useState } from 'react' +import React, { FC, useState } from 'react' +import { ChainId } from 'sushi/chain' import { ConcentratedPositionsTable } from './ConcentratedPositionsTable/ConcentratedPositionsTable' import { PositionsTable } from './PositionsTable' import { SmartPositionsTable } from './SmartPositionsTable' @@ -52,7 +53,7 @@ const ITEMS: { id: string; value: string; children: React.ReactNode }[] = [ }, ] -export const PositionsTab = () => { +export const PositionsTab: FC<{ chainId: ChainId }> = ({ chainId }) => { const [tab, setTab] = useState('v3') return ( @@ -86,13 +87,19 @@ export const PositionsTab = () => {
- + - `/pool/${row.pool.id}`} /> + `/pool/${row.pool.id}`} + /> - +
diff --git a/apps/web/src/ui/pool/PositionsTable.tsx b/apps/web/src/ui/pool/PositionsTable.tsx index 56d8c84681..dd1c67b332 100644 --- a/apps/web/src/ui/pool/PositionsTable.tsx +++ b/apps/web/src/ui/pool/PositionsTable.tsx @@ -16,6 +16,7 @@ const COLUMNS = [ ] as DisplayColumnDef[] interface PositionsTableProps { + chainId: ChainId onRowClick?(row: V2Position): void rowLink?(row: V2Position): string } @@ -23,23 +24,21 @@ interface PositionsTableProps { const tableState = { sorting: [{ id: 'value', desc: true }] } export const PositionsTable: FC = ({ + chainId, onRowClick, rowLink, }) => { const { address } = useAccount() - // const { chainIds, tokenSymbols } = usePoolFilters() const [paginationState, setPaginationState] = useState({ pageIndex: 0, pageSize: 10, }) - const { data: positions, isLoading } = useSushiV2UserPositions( - { - user: address!, - chainId: ChainId.ARBITRUM, // TODO: fix - }, - !!address, - ) + const { data: positions, isLoading } = useSushiV2UserPositions({ + user: address, + chainId, + }) + const _positions = useMemo(() => { if (!positions) return [] return positions diff --git a/apps/web/src/ui/pool/RewardsSection.tsx b/apps/web/src/ui/pool/RewardsSection.tsx index bda147dd27..b4f56f93d2 100644 --- a/apps/web/src/ui/pool/RewardsSection.tsx +++ b/apps/web/src/ui/pool/RewardsSection.tsx @@ -35,7 +35,7 @@ const COLUMNS = [ export const RewardsSection: FC = () => { const { address } = useAccount() - const { chainIds, tokenSymbols } = usePoolFilters() + const { tokenSymbols } = usePoolFilters() const { data, isInitialLoading } = useAngleRewardsMultipleChains({ chainIds: ANGLE_SUPPORTED_CHAIN_IDS, account: address, @@ -58,28 +58,24 @@ export const RewardsSection: FC = () => { const positions = useMemo(() => { const _tokenSymbols = tokenSymbols?.filter((el) => el !== '') || [] - return (data ?? []) - .filter((el) => - chainIds.includes(el.chainId as (typeof chainIds)[number]), - ) - .flatMap((el) => { - return Object.values(el.pools ?? {}) - .filter( - (el) => - (el?.userBalanceToken0 ?? 0) + (el?.userBalanceToken1 ?? 0) > 0 || - Object.keys(el.rewardsPerToken).length > 0, - ) - .filter((el) => - _tokenSymbols.length > 0 - ? _tokenSymbols.some((symbol) => { - return [el.token0.symbol, el.token1.symbol].includes( - symbol.toUpperCase(), - ) - }) - : true, - ) - }) - }, [chainIds, tokenSymbols, data]) + return (data ?? []).flatMap((el) => { + return Object.values(el.pools ?? {}) + .filter( + (el) => + (el?.userBalanceToken0 ?? 0) + (el?.userBalanceToken1 ?? 0) > 0 || + Object.keys(el.rewardsPerToken).length > 0, + ) + .filter((el) => + _tokenSymbols.length > 0 + ? _tokenSymbols.some((symbol) => { + return [el.token0.symbol, el.token1.symbol].includes( + symbol.toUpperCase(), + ) + }) + : true, + ) + }) + }, [tokenSymbols, data]) const rowLink = useCallback((row: AngleRewardsPool) => { return `/pool/${row.id}` diff --git a/apps/web/src/ui/pool/SmartPoolsTable.tsx b/apps/web/src/ui/pool/SmartPoolsTable.tsx deleted file mode 100644 index 2d5d670c29..0000000000 --- a/apps/web/src/ui/pool/SmartPoolsTable.tsx +++ /dev/null @@ -1,600 +0,0 @@ -'use client' - -import { UploadIcon } from '@heroicons/react-v1/outline' -import { DownloadIcon } from '@heroicons/react-v1/solid' -import { ArrowDownRightIcon } from '@heroicons/react/20/solid' -import { - EllipsisHorizontalIcon, - GiftIcon, - LightBulbIcon, - MinusIcon, - PlusIcon, -} from '@heroicons/react/24/outline' -import { - Badge, - Button, - Card, - CardDescription, - CardHeader, - CardTitle, - Chip, - Currency, - DataTable, - DropdownMenu, - DropdownMenuContent, - DropdownMenuGroup, - DropdownMenuGroupLabel, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, - LinkExternal, - SkeletonCircle, - SkeletonText, - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, - classNames, -} from '@sushiswap/ui' -import { NetworkIcon } from '@sushiswap/ui/icons/NetworkIcon' -import { - ColumnDef, - PaginationState, - SortingState, - TableState, -} from '@tanstack/react-table' -import { useMemo, useState } from 'react' -import { Native, Token, unwrapToken } from 'sushi/currency' -import { formatPercent, formatUSD } from 'sushi/format' - -import { SmartPoolsV1, getSmartPools } from '@sushiswap/graph-client/data-api' -import { useQuery } from '@tanstack/react-query' -import Link from 'next/link' -import { ChainId, SushiSwapProtocol } from 'sushi' -import { isAngleEnabledChainId } from 'sushi/config' -import { Address } from 'viem' -import { APRHoverCard } from './APRHoverCard' -import { ProtocolBadge } from './PoolNameCell' -import { usePoolFilters } from './PoolsFiltersProvider' -import { SteerStrategyConfig } from './Steer/constants' -import { SteerStrategy } from '@sushiswap/steer-sdk' - -const COLUMNS = [ - { - id: 'poolName', - header: 'Pool name', - cell: ({ row: { original } }) => { - const token0 = new Token({ - chainId: original.chainId, - address: original.token0.address, - decimals: original.token0.decimals, - symbol: original.token0.symbol, - }) - const token1 = new Token({ - chainId: original.chainId, - address: original.token1.address, - decimals: original.token1.decimals, - symbol: original.token1.symbol, - }) - - return ( -
-
- - } - > - - - - - -
-
- - {unwrapToken(token0).symbol}{' '} - - / - {' '} - {unwrapToken(token1).symbol}{' '} -
- -
- - - - {ProtocolBadge[original.protocol as SushiSwapProtocol]} - - -

Protocol version

-
-
-
- - - -
- {formatPercent(original.swapFee)} -
-
- -

Swap fee

-
-
-
- {original.isIncentivized && ( - - - -
- 🧑🌾{' '} -
-
- -

Farm rewards available

-
-
-
- )} - - - -
- 💡 -
-
- -

Smart Pool available

-
-
-
- - {/* {original.isDeprecated && ( -
- Deprecated -
- )} */} -
-
-
- ) - }, - meta: { - skeleton: ( -
-
- - -
-
- -
-
- ), - }, - size: 300, - }, - { - id: 'name', - header: 'Strategy', - cell: ({ row: { original } }) => ( - - - - - {original.strategy.replace(/([a-z0-9])([A-Z])/g, '$1 $2')} - - - -

- {' '} - {SteerStrategyConfig[original.strategy as SteerStrategy] - .description ?? ''} -

-
-
-
- ), - meta: { - skeleton: , - }, - }, - { - id: 'liquidityUSD', - header: 'TVL', - accessorFn: (row) => row.vaultLiquidityUSD, - cell: ({ row: { original } }) => ( - - - {formatUSD(original.liquidityUSD).includes('NaN') - ? '$0.00' - : formatUSD(original.liquidityUSD)} - - - - - - {formatUSD(original.vaultLiquidityUSD).includes('NaN') - ? '$0.00' - : formatUSD(original.vaultLiquidityUSD)} - - - -

Amount of liquidity deposited in the smart pool.

-
-
-
-
- ), - meta: { - skeleton: , - }, - }, - { - id: 'fees1d', - header: 'Fees (24h)', - accessorFn: (row) => row.feeUSD1d, - cell: (props) => - formatUSD(props.row.original.feeUSD1d).includes('NaN') - ? '$0.00' - : formatUSD(props.row.original.feeUSD1d), - meta: { - skeleton: , - }, - }, - { - id: 'totalApr1d', - header: 'APR (24h)', - accessorFn: (row) => row.stakedAndIncentiveApr1d, - cell: (props) => { - return ( -
- - - - - {formatPercent(props.row.original.feeAndIncentiveApr1d)} - - - -

APR when not staked within the vault.

-
-
-
- - - {formatPercent(props.row.original.stakedAndIncentiveApr1d)} - - -
- ) - }, - meta: { - skeleton: , - }, - }, - { - id: 'actions', - cell: ({ row }) => - row.original.protocol === 'SUSHISWAP_V3' ? ( - - - - - - - {row.original.token0.symbol} / {row.original.token1.symbol} - - SushiSwap V3 - - - - - - e.stopPropagation()} - shallow={true} - className="flex items-center" - href={`/pool/${row.original.id}`} - > - - Pool details - - - - e.stopPropagation()} - shallow={true} - className="flex items-center" - href={`/pool/${row.original.id}/positions/create`} - > - - Create position - - - - - - - e.stopPropagation()} - shallow={true} - className="flex items-center" - href={`/pool/${row.original.id}/smart/${row.original.id}`} - > - - - - - - - Create smart position - - - - -

- {`Smart pools optimize liquidity allocation within custom price ranges, enhancing trading efficiency by - providing deeper liquidity around the current price, increasing Liquidity Providers (LP) fee earnings.`} -

-
-
-
-
- - - - - - - e.stopPropagation()} - shallow={true} - className="flex items-center" - href={`/pool/incentivize?chainId=${ - row.original.chainId - }&fromCurrency=${ - row.original.token0.address === - Native.onChain(row.original.chainId).wrapped - .address - ? 'NATIVE' - : row.original.token0.address - }&toCurrency=${ - row.original.token1.address === - Native.onChain(row.original.chainId).wrapped - .address - ? 'NATIVE' - : row.original.token1.address - }&feeAmount=${ - row.original.swapFee * 10_000 * 100 - }`} - > - - Add incentive - - - - -

- {!isAngleEnabledChainId(row.original.chainId) - ? 'Not available on this network' - : 'Add rewards to a pool to incentivize liquidity providers joining in.'} -

-
-
-
-
-
-
- ) : ( - - - - - - - {row.original.token0.symbol} / {row.original.token1.symbol} - {row.original.protocol === 'SUSHISWAP_V2' && ( - - SushiSwap V2 - - )} - - - - - e.stopPropagation()} - shallow={true} - className="flex items-center" - href={`/pool/${row.original.id}/add`} - > - - Add liquidity - - - - e.stopPropagation()} - shallow={true} - className="flex items-center" - href={`/pool/${row.original.id}/remove`} - > - - Remove liquidity - - - - - - Farm rewards - - - - - e.stopPropagation()} - shallow={true} - className="flex items-center" - href={`/pool/${row.original.id}/stake`} - > - - Stake - - - - -

- {!row.original.isIncentivized - ? 'No rewards available on this pool' - : 'After adding liquidity, stake your liquidity tokens to benefit from extra rewards'} -

-
-
-
- - e.stopPropagation()} - shallow={true} - className="flex items-center" - href={`/pool/${row.original.id}/unstake`} - > - - Unstake - - -
-
-
- ), - meta: { - disableLink: true, - skeleton: , - }, - }, -] satisfies ColumnDef[] - -export const SmartPoolsTable = () => { - const { chainIds, protocols, farmsOnly } = usePoolFilters() - const [sorting, setSorting] = useState([ - { id: 'liquidityUSD', desc: true }, - ]) - const [pagination, setPagination] = useState({ - pageIndex: 0, - pageSize: 10, - }) - - const { data: vaults, isLoading: isValidatingVaults } = useQuery({ - queryKey: ['useSmartPools', { chainId: chainIds[0] }], - queryFn: async () => await getSmartPools({ chainId: chainIds[0] }), - keepPreviousData: true, - staleTime: 0, - cacheTime: 86400000, // 24hs - // enabled, - }) - - const state: Partial = useMemo(() => { - return { - sorting, - pagination, - } - }, [sorting, pagination]) - - const _vaults = useMemo( - () => - vaults - ? vaults - .filter((el) => (farmsOnly ? el.isIncentivized : true)) - .filter((el) => - protocols.length > 0 - ? protocols.includes(el.protocol as SushiSwapProtocol) // wont need this when table is refactored - : true, - ) - : [], - [protocols, farmsOnly, vaults], - ) - - return ( - - - - Smart Pools{' '} - {_vaults?.length ? ( - - ({_vaults.length}) - - ) : null} - - - Smart pools optimize liquidity allocation within custom price ranges, - enhancing trading efficiency by providing deeper liquidity around the - current price, increasing Liquidity Providers (LP) fee earnings. To - learn more about Smart Pools, click{' '} - - here - - . - - - `smart-pools-table-${row.id.replace(':', '-')}`} - onPaginationChange={setPagination} - pagination={true} - state={state} - linkFormatter={(row) => `/pool/${row.id}/smart/${row.id}`} - onSortingChange={setSorting} - loading={!vaults && isValidatingVaults} - columns={COLUMNS} - data={_vaults} - /> - - ) -} diff --git a/apps/web/src/ui/pool/SmartPositionsTable.tsx b/apps/web/src/ui/pool/SmartPositionsTable.tsx index 5afde066fd..da894f30f1 100644 --- a/apps/web/src/ui/pool/SmartPositionsTable.tsx +++ b/apps/web/src/ui/pool/SmartPositionsTable.tsx @@ -1,10 +1,7 @@ 'use client' +import { isSteerChainId } from '@sushiswap/steer-sdk' import { Card, CardHeader, CardTitle, DataTable } from '@sushiswap/ui' -import { ColumnDef, PaginationState } from '@tanstack/react-table' -import React, { useMemo, useState } from 'react' - -import { STEER_SUPPORTED_CHAIN_IDS } from '@sushiswap/steer-sdk' import { SkeletonText, Tooltip, @@ -12,11 +9,14 @@ import { TooltipProvider, TooltipTrigger, } from '@sushiswap/ui' +import { ColumnDef, PaginationState } from '@tanstack/react-table' +import React, { FC, useMemo, useState } from 'react' import { SteerAccountPositionExtended, useSteerAccountPositionsExtended, } from 'src/lib/wagmi/hooks/steer/useSteerAccountPositionsExtended' -import { ChainId, formatPercent, SushiSwapProtocol } from 'sushi' +import { ChainId, SushiSwapProtocol, formatPercent } from 'sushi' +import { Address } from 'viem' import { useAccount } from 'wagmi' import { APRHoverCard } from './APRHoverCard' import { @@ -24,8 +24,6 @@ import { STEER_POSITION_SIZE_COLUMN, STEER_STRATEGY_COLUMN, } from './ConcentratedPositionsTable/Tables/Smart/columns' -import { usePoolFilters } from './PoolsFiltersProvider' -import { Address } from 'viem' const COLUMNS = [ STEER_NAME_COLUMN, @@ -81,9 +79,8 @@ const COLUMNS = [ const tableState = { sorting: [{ id: 'positionSize', desc: true }] } -export const SmartPositionsTable = () => { +export const SmartPositionsTable: FC<{ chainId: ChainId }> = ({ chainId }) => { const { address } = useAccount() - const { chainIds } = usePoolFilters() const [paginationState, setPaginationState] = useState({ pageIndex: 0, pageSize: 10, @@ -91,7 +88,7 @@ export const SmartPositionsTable = () => { const { data: positions, isLoading } = useSteerAccountPositionsExtended({ account: address, - chainIds: chainIds ? chainIds : [...STEER_SUPPORTED_CHAIN_IDS], + chainIds: isSteerChainId(chainId) ? [chainId] : [], }) const _positions = useMemo(() => (positions ? positions : []), [positions]) diff --git a/apps/web/src/ui/pool/TableFiltersNetwork.tsx b/apps/web/src/ui/pool/TableFiltersNetwork.tsx index 07b4a120e5..55f1f79c17 100644 --- a/apps/web/src/ui/pool/TableFiltersNetwork.tsx +++ b/apps/web/src/ui/pool/TableFiltersNetwork.tsx @@ -1,13 +1,7 @@ 'use client' import { PlusCircleIcon } from '@heroicons/react/24/outline' -import { - Chip, - Popover, - PopoverContent, - PopoverTrigger, - Separator, -} from '@sushiswap/ui' +import { Chip, Popover, PopoverContent, PopoverTrigger } from '@sushiswap/ui' import { Button } from '@sushiswap/ui' import { Command, @@ -18,46 +12,24 @@ import { } from '@sushiswap/ui' import { CheckIcon } from '@sushiswap/ui/icons/CheckIcon' import { NetworkIcon } from '@sushiswap/ui/icons/NetworkIcon' -import React, { FC, useCallback, useState, useTransition } from 'react' +import { usePathname, useRouter } from 'next/navigation' +import React, { FC, useCallback, useState } from 'react' import { SUPPORTED_CHAIN_IDS } from 'src/config' -import { Chain } from 'sushi/chain' - -import { usePoolFilters, useSetPoolFilters } from './PoolsFiltersProvider' +import { Chain, ChainId } from 'sushi/chain' -const isAllThenNone = (chainIds: number[]) => - SUPPORTED_CHAIN_IDS.length === chainIds.length ? [] : chainIds - -export const TableFiltersNetwork: FC = () => { - const [pending, startTransition] = useTransition() +export const TableFiltersNetwork: FC<{ chainId: ChainId }> = ({ chainId }) => { const [open, setOpen] = useState(false) - const { chainIds } = usePoolFilters() - const setFilters = useSetPoolFilters() - const [localValue, setValues] = useState(isAllThenNone(chainIds)) - const values = pending ? localValue : isAllThenNone(chainIds) + const router = useRouter() + const pathname = usePathname() const onClick = useCallback( - (chainId: (typeof chainIds)[number]) => { - let _newValues: number[] - if (localValue.includes(chainId)) { - _newValues = localValue.filter((el) => el !== chainId) - } else { - _newValues = [...(localValue ?? []), chainId] - } - setValues(_newValues) - - startTransition(() => { - setFilters((prev) => { - if (prev.chainIds?.includes(chainId)) { - const chains = prev.chainIds!.filter((el) => el !== chainId) - return { ...prev, chainIds: chains } - } else { - return { ...prev, chainIds: [...(prev.chainIds ?? []), chainId] } - } - }) - }) + (chainId: string) => { + const pathSegments = pathname.split('/') + pathSegments[1] = chainId + router.push(pathSegments.join('/')) }, - [setFilters, localValue], + [pathname, router], ) return ( @@ -72,27 +44,7 @@ export const TableFiltersNetwork: FC = () => { className="!border-dashed" > Networks - {values?.length > 0 && ( - <> - - - {values.length} - -
- {values.length > 2 ? ( - {values.length} selected - ) : ( - SUPPORTED_CHAIN_IDS.filter((option) => - values.includes(option), - ).map((option) => ( - - {Chain.from(option)?.name} - - )) - )} -
- - )} + {Chain.from(chainId)?.name} { /> No network found. - {SUPPORTED_CHAIN_IDS.map((chainId) => ( + {SUPPORTED_CHAIN_IDS.map((_chainId) => ( - onClick(+value.split('__')[1] as (typeof chainIds)[number]) - } + key={_chainId} + value={_chainId.toString()} + onSelect={onClick} className="py-2 pl-8 pr-2" > - {values.includes(chainId) ? ( + {chainId === _chainId ? ( { ) : null} - {Chain.from(chainId)?.name} + {Chain.from(_chainId)?.name} ))} diff --git a/apps/web/src/ui/pool/TableFiltersResetButton.tsx b/apps/web/src/ui/pool/TableFiltersResetButton.tsx index 8fc1ec2fe2..52282c2432 100644 --- a/apps/web/src/ui/pool/TableFiltersResetButton.tsx +++ b/apps/web/src/ui/pool/TableFiltersResetButton.tsx @@ -4,29 +4,21 @@ import { XMarkIcon } from '@heroicons/react/24/solid' import { Button } from '@sushiswap/ui' import React, { FC, useCallback, useMemo, useState, useTransition } from 'react' -import { SUPPORTED_CHAIN_IDS } from '../../config' import { usePoolFilters, useSetPoolFilters } from './PoolsFiltersProvider' import { POOL_TYPES } from './TableFiltersPoolType' export const TableFiltersResetButton: FC = () => { const [isPending, startTransition] = useTransition() - const { protocols, chainIds, tokenSymbols, farmsOnly, smartPoolsOnly } = + const { protocols, tokenSymbols, farmsOnly, smartPoolsOnly } = usePoolFilters() const setFilters = useSetPoolFilters() - const networks = useMemo( - () => (SUPPORTED_CHAIN_IDS.length === chainIds.length ? [] : chainIds), - [chainIds], - ) const types = useMemo( () => (protocols.length === POOL_TYPES.length ? [] : protocols), [protocols], ) const [show, setShow] = useState( - (types?.length ?? 0) + - (networks?.length ?? 0) + - (tokenSymbols?.length ?? 0) > - 0 || + (types?.length ?? 0) + (tokenSymbols?.length ?? 0) > 0 || farmsOnly || smartPoolsOnly, ) @@ -36,7 +28,6 @@ export const TableFiltersResetButton: FC = () => { startTransition(() => { setFilters({ protocols: undefined, - chainIds: undefined, tokenSymbols: undefined, farmsOnly: undefined, smartPoolsOnly: undefined, @@ -47,10 +38,7 @@ export const TableFiltersResetButton: FC = () => { if ( isPending ? show - : (types?.length ?? 0) + - (networks?.length ?? 0) + - (tokenSymbols?.length ?? 0) > - 0 || + : (types?.length ?? 0) + (tokenSymbols?.length ?? 0) > 0 || farmsOnly || smartPoolsOnly ) { diff --git a/apps/web/src/ui/pool/PositionView.tsx b/apps/web/src/ui/pool/V3PositionView.tsx similarity index 98% rename from apps/web/src/ui/pool/PositionView.tsx rename to apps/web/src/ui/pool/V3PositionView.tsx index efd6463927..dbb151604d 100644 --- a/apps/web/src/ui/pool/PositionView.tsx +++ b/apps/web/src/ui/pool/V3PositionView.tsx @@ -63,13 +63,12 @@ import { ConcentratedLiquidityRemoveWidget } from './ConcentratedLiquidityRemove import { ConcentratedLiquidityWidget } from './ConcentratedLiquidityWidget' import { DistributionDataTable } from './DistributionDataTable' -const Component: FC<{ id: string }> = ({ id }) => { +const Component: FC<{ chainId: string; address: string; position: string }> = ({ + chainId: _chainId, + address: poolId, + position: tokenId, +}) => { const { address } = useAccount() - const [_chainId, poolId, tokenId] = id.split('%3A') as [ - SushiSwapV3ChainId, - string, - string, - ] const chainId = Number(_chainId) as SushiSwapV3ChainId const [invert, setInvert] = useState(false) @@ -737,10 +736,12 @@ const Component: FC<{ id: string }> = ({ id }) => { ) } -export const PositionView = ({ params }: { params: { id: string } }) => { +export const V3PositionView = ({ + params: { chainId, address, position }, +}: { params: { chainId: string; address: string; position: string } }) => { return ( - + ) }