+ {liquidityWidget}
+
+
+
+ {t`Earning with Smart Liquidity Providing`}
+
+
+ {t`KyberSwap Zap: Instantly and easily add liquidity to high-APY pools using any token or a combination of tokens.`}
+
+
+
+
+ updateFilters('tag', '')}>
+ {t`All pools`}
+
+
+ updateFilters('tag', 'favorite')}>
+
+
+
+ {filterTags.map((item, index) =>
+ !upToMedium ? (
+
+ updateFilters('tag', item.value)}
+ >
+ {!upToExtraSmall && item.icon}
+ {item.label}
+
+
+ ) : (
+ updateFilters('tag', item.value)}
+ >
+ {!upToExtraSmall && item.icon}
+ {item.label}
+
+ ),
+ )}
+
+ {!upToLarge && (
+ navigate({ pathname: APP_PATHS.EARN_POSITIONS })}>
+
+ {t`My Positions`}
+
+ )}
+
+
+
+
+
+
+
+ setSearch(val)}
+ style={{ height: '36px' }}
+ />
+
+
+
+ {!upToMedium && (
+
+ Protocol
+ Pair
+ onSortChange(SortBy.APR)}
+ >
+ APR
+
+
+ onSortChange(SortBy.EARN_FEE)}
+ >
+ Earn Fees
+
+
+ onSortChange(SortBy.TVL)}
+ >
+ TVL
+
+
+ onSortChange(SortBy.VOLUME)}
+ >
+ Volume
+
+
+
+
+ )}
+
+
+ updateFilters('page', newPage.toString())}
+ totalCount={poolData?.data?.pagination?.totalItems || 0}
+ currentPage={filters.page || 1}
+ pageSize={filters.limit || 10}
+ />
+
+
+ {t`KyberSwap provides tools for tracking & adding liquidity to third-party Protocols. For any pool-related concerns, please contact the respective Liquidity Protocol directly.`}
+
+ )
+}
+
+export default Earn
diff --git a/src/pages/Earns/PoolExplorer/styles.tsx b/src/pages/Earns/PoolExplorer/styles.tsx
new file mode 100644
index 0000000000..3a5ac9745a
--- /dev/null
+++ b/src/pages/Earns/PoolExplorer/styles.tsx
@@ -0,0 +1,179 @@
+import { rgba } from 'polished'
+import styled from 'styled-components'
+
+import { Image } from 'components/Image'
+
+export const PoolPageWrapper = styled.div`
+ padding: 32px 24px 50px;
+ width: 100%;
+ max-width: 1500px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+
+ ${({ theme }) => theme.mediaWidth.upToSmall`
+ padding: 24px 16px 100px;
+ `}
+`
+
+export const LiquidityWidgetWrapper = styled.div`
+ width: 100%;
+ display: flex;
+ justify-content: center;
+`
+
+export const HeadSection = styled.div`
+ display: flex;
+ width: 100%;
+ align-items: center;
+ justify-content: space-between;
+`
+
+export const TagContainer = styled.div`
+ display: flex;
+ gap: 1rem;
+ width: 100%;
+ overflow-x: auto;
+
+ ${({ theme }) => theme.mediaWidth.upToSmall`
+ gap: 0.75rem;
+ `}
+`
+
+export const Tag = styled.div<{ active: boolean }>`
+ background: ${({ theme, active }) => (active ? rgba(theme.primary, 0.2) : theme.background)};
+ border: 1px solid ${({ theme, active }) => (active ? theme.primary : 'transparent')};
+ border-radius: 12px;
+ padding: 4px 16px;
+ font-size: 14px;
+ cursor: pointer;
+ color: ${({ theme, active }) => (active ? theme.text : theme.subText)};
+ font-weight: ${({ active }) => (active ? '500' : '400')};
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ flex: 0 0 auto;
+ line-height: 28px;
+ height: 42px;
+
+ ${({ theme }) => theme.mediaWidth.upToMedium`
+ height: 38px;
+ `}
+`
+
+export const UserPositionButton = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ background-color: ${({ theme }) => rgba(theme.primary, 0.1)};
+ color: ${({ theme }) => theme.subText};
+ border-radius: 12px;
+ padding: 8px 16px;
+ width: max-content;
+ font-size: 14px;
+ cursor: pointer;
+
+ :hover {
+ filter: brightness(1.1);
+ }
+`
+
+export const TableWrapper = styled.div`
+ background: ${({ theme }) => rgba(theme.background, 0.8)};
+ border-radius: 16px;
+ overflow: hidden;
+
+ ${({ theme }) => theme.mediaWidth.upToMedium`
+ margin: 0 -16px;
+ border-radius: 0;
+ `}
+`
+
+export const ContentWrapper = styled.div``
+
+export const TableHeader = styled.div`
+ display: grid;
+ grid-template-columns: 1fr 1fr 0.5fr 1fr 1fr 1fr 80px;
+ align-items: center;
+ color: ${({ theme }) => theme.subText};
+ border-bottom: 1px solid ${({ theme }) => theme.tableHeader};
+ padding-bottom: 24px;
+ margin: 24px;
+ margin-bottom: 0;
+
+ ${({ theme }) => theme.mediaWidth.upToLarge`
+ grid-template-columns: 1fr 1.2fr 0.5fr 1fr 1fr 1fr 80px;
+ `}
+`
+
+export const TableBody = styled.div`
+ max-height: 720px;
+ overflow-y: auto;
+`
+
+export const TableRow = styled.div`
+ display: grid;
+ grid-template-columns: 1fr 1fr 0.5fr 1fr 1fr 1fr 80px;
+ padding: 24px;
+ cursor: pointer;
+
+ :hover {
+ background: #31cb9e1a;
+ }
+
+ ${({ theme }) => theme.mediaWidth.upToLarge`
+ grid-template-columns: 1fr 1.2fr 0.5fr 1fr 1fr 1fr 80px;
+ `}
+`
+
+export const FeeTier = styled.div`
+ border-radius: 30px;
+ padding: 4px 8px;
+ font-size: 12px;
+ background: ${({ theme }) => rgba(theme.white, 0.04)};
+ color: ${({ theme }) => theme.subText};
+ width: fit-content;
+
+ ${({ theme }) => theme.mediaWidth.upToExtraSmall`
+ font-size: 14px;
+ `}
+`
+
+export const CurrencyRoundedImage = styled(Image)`
+ border-radius: 50%;
+ width: 24px;
+ height: 24px;
+`
+
+export const CurrencySecondImage = styled(CurrencyRoundedImage)`
+ position: relative;
+ left: -6px;
+`
+
+export const SymbolText = styled.div`
+ max-width: 115px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+`
+
+export const Apr = styled.div<{ positive: boolean }>`
+ display: flex;
+ justify-content: flex-end;
+ color: ${({ positive, theme }) => (positive ? theme.primary : theme.red)};
+`
+
+export const MobileTableRow = styled.div`
+ padding: 28px 24px 0;
+ cursor: pointer;
+
+ :hover {
+ background: ${({ theme }) => rgba(theme.primary, 0.2)};
+ }
+`
+export const MobileTableBottomRow = styled.div<{ withoutBorder: boolean }>`
+ display: grid;
+ grid-template-columns: 1.5fr 1fr 1fr;
+ padding: 16px 0;
+ border-bottom: ${({ withoutBorder, theme }) => (withoutBorder ? 'none' : `1px solid ${theme.tableHeader}`)};
+`
diff --git a/src/pages/Earns/PoolExplorer/useFilter.ts b/src/pages/Earns/PoolExplorer/useFilter.ts
new file mode 100644
index 0000000000..e635b70e0d
--- /dev/null
+++ b/src/pages/Earns/PoolExplorer/useFilter.ts
@@ -0,0 +1,65 @@
+import { ChainId } from '@kyberswap/ks-sdk-core'
+import { useCallback, useMemo } from 'react'
+import { useSearchParams } from 'react-router-dom'
+import { earnSupportedChains } from 'services/krystalEarn'
+import { QueryParams } from 'services/zapEarn'
+
+import { useActiveWeb3React } from 'hooks'
+import { Direction } from 'pages/MarketOverview/SortIcon'
+
+import { FilterTag, SortBy, timings } from '.'
+
+export default function useFilter(setSearch?: (search: string) => void) {
+ const [searchParams, setSearchParams] = useSearchParams()
+ const { account, chainId } = useActiveWeb3React()
+
+ const filters: QueryParams = useMemo(() => {
+ return {
+ chainId: +(
+ searchParams.get('chainId') || (chainId && earnSupportedChains.includes(chainId) ? chainId : ChainId.MAINNET)
+ ),
+ page: +(searchParams.get('page') || 1),
+ limit: 10,
+ interval: searchParams.get('interval') || (timings[0].value as string),
+ protocol: searchParams.get('protocol') || '',
+ userAddress: account,
+ tag: searchParams.get('tag') || '',
+ sortBy: searchParams.get('sortBy') || (!searchParams.get('tag') ? SortBy.TVL : ''),
+ orderBy: searchParams.get('orderBy') || (!searchParams.get('tag') ? Direction.DESC : ''),
+ q: searchParams.get('q')?.trim() || '',
+ }
+ }, [searchParams, account, chainId])
+
+ const updateFilters = useCallback(
+ (key: keyof QueryParams, value: string) => {
+ if (!value) {
+ searchParams.delete(key)
+ if (key === 'tag') {
+ searchParams.set('sortBy', SortBy.TVL)
+ searchParams.set('orderBy', Direction.DESC)
+ }
+ } else {
+ searchParams.set(key, value)
+ if (key === 'chainId') searchParams.delete('protocol')
+ if (key === 'tag') {
+ searchParams.delete('sortBy')
+ searchParams.delete('orderBy')
+ if (setSearch) setSearch('')
+ if (value === FilterTag.LOW_VOLATILITY) {
+ searchParams.set('sortBy', SortBy.APR)
+ searchParams.set('orderBy', Direction.DESC)
+ }
+ }
+ }
+ if (key !== 'sortBy' && key !== 'orderBy' && key !== 'page') searchParams.delete('page')
+
+ setSearchParams(searchParams)
+ },
+ [setSearchParams, searchParams, setSearch],
+ )
+
+ return {
+ filters,
+ updateFilters,
+ }
+}
diff --git a/src/pages/Earns/PoolExplorer/useSupportedDexesAndChains.ts b/src/pages/Earns/PoolExplorer/useSupportedDexesAndChains.ts
new file mode 100644
index 0000000000..c0aa06a4b2
--- /dev/null
+++ b/src/pages/Earns/PoolExplorer/useSupportedDexesAndChains.ts
@@ -0,0 +1,40 @@
+import { useMemo } from 'react'
+import { useGetDexListQuery } from 'services/ksSetting'
+import { QueryParams, useSupportedProtocolsQuery } from 'services/zapEarn'
+
+import { NETWORKS_INFO } from 'constants/networks'
+import useChainsConfig from 'hooks/useChainsConfig'
+
+const useSupportedDexesAndChains = (filters: QueryParams) => {
+ const { supportedChains } = useChainsConfig()
+ const dexList = useGetDexListQuery({
+ chainId: NETWORKS_INFO[filters.chainId].ksSettingRoute,
+ })
+ const { data: supportedProtocols } = useSupportedProtocolsQuery()
+
+ const supportedDexes = useMemo(() => {
+ if (!supportedProtocols?.data?.chains) return []
+ const parsedProtocols =
+ supportedProtocols.data.chains[filters.chainId]?.protocols?.map(item => ({
+ label: (dexList?.data?.find(dex => dex.dexId === item.id)?.name || item.name).replaceAll('-', ' '),
+ value: item.id,
+ })) || []
+ return [{ label: 'All Protocols', value: '' }].concat(parsedProtocols)
+ }, [filters.chainId, supportedProtocols, dexList])
+
+ const chains = useMemo(
+ () =>
+ supportedChains
+ .map(chain => ({
+ label: chain.name,
+ value: chain.chainId,
+ icon: chain.icon,
+ }))
+ .filter(chain => supportedProtocols?.data?.chains?.[chain.value]),
+ [supportedChains, supportedProtocols],
+ )
+
+ return { supportedDexes, supportedChains: chains }
+}
+
+export default useSupportedDexesAndChains
diff --git a/src/pages/Earns/PositionDetail/Header.tsx b/src/pages/Earns/PositionDetail/Header.tsx
new file mode 100644
index 0000000000..330b89765b
--- /dev/null
+++ b/src/pages/Earns/PositionDetail/Header.tsx
@@ -0,0 +1,81 @@
+import { ChainId } from '@kyberswap/ks-sdk-core'
+import { t } from '@lingui/macro'
+import { useNavigate } from 'react-router-dom'
+import { useMedia } from 'react-use'
+import { Flex, Text } from 'rebass'
+import { EarnSupportedProtocols, PositionStatus, earnSupportedProtocols } from 'services/krystalEarn'
+
+import CopyHelper from 'components/Copy'
+import { MouseoverTooltipDesktopOnly } from 'components/Tooltip'
+import useTheme from 'hooks/useTheme'
+import { MEDIA_WIDTHS } from 'theme'
+import { shortenAddress } from 'utils'
+
+import { ParsedPosition } from '.'
+import { CurrencyRoundedImage, CurrencySecondImage } from '../PoolExplorer/styles'
+import { Badge, BadgeType, ChainImage, DexImage, ImageContainer, PositionOverview } from '../UserPositions/styles'
+import { DexInfo, IconArrowLeft } from './styles'
+
+const PositionDetailHeader = ({ position }: { position: ParsedPosition }) => {
+ const theme = useTheme()
+ const navigate = useNavigate()
+ const upToSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToSmall}px)`)
+
+ const onOpenPositionInDexSite = () => {
+ if (!position || !earnSupportedProtocols.includes(position.dex)) return
+
+ if (position.dex === EarnSupportedProtocols.UNISWAP_V3)
+ window.open(`https://app.uniswap.org/positions/v3/${position.chainName}/${position.id}`)
+ else if (position.dex === EarnSupportedProtocols.SUSHISWAP_V3)
+ window.open(`https://www.sushi.com/${position.chainName}/pool/v3/${position.poolAddress}/${position.id}`)
+ else if (position.dex === EarnSupportedProtocols.PANCAKESWAP_V3)
+ window.open(`https://pancakeswap.finance/liquidity/${position.id}`)
+ }
+
+ return (
+