Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 63 additions & 14 deletions packages/lib/modules/pool/PoolList/PoolListFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
} from '../pool.types'
import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider'
import { useEffect, useState } from 'react'
import { Filter, Plus } from 'react-feather'
import { Filter, Info, Plus } from 'react-feather'
import { useBreakpoints } from '@repo/lib/shared/hooks/useBreakpoints'
import { useCurrency } from '@repo/lib/shared/hooks/useCurrency'
import { motion, AnimatePresence } from 'framer-motion'
Expand All @@ -54,10 +54,19 @@ import { AnimatedTag } from '@repo/lib/shared/components/other/AnimatedTag'
import { PoolMinTvlFilter } from './PoolMinTvlFilter'
import { AnalyticsEvent, trackEvent } from '@repo/lib/shared/services/fathom/Fathom'
import NextLink from 'next/link'
import { TooltipWithTouch } from '@repo/lib/shared/components/tooltips/TooltipWithTouch'

export function useFilterTagsVisible() {
const {
queryState: { networks, poolTypes, minTvl, poolTags, poolHookTags, protocolVersion },
queryState: {
networks,
poolTypes,
minTvl,
poolTags,
poolHookTags,
protocolVersion,
joinablePools,
},
} = usePoolList()

return (
Expand All @@ -66,25 +75,52 @@ export function useFilterTagsVisible() {
minTvl > 0 ||
poolTags.length > 0 ||
poolHookTags.length > 0 ||
joinablePools ||
!!protocolVersion
)
}

function UserPoolFilter() {
function UserLiquidityFilters() {
const {
queryState: { userAddress, toggleUserAddress },
queryState: { userAddress, toggleUserAddress, joinablePools, toggleJoinablePools },
} = usePoolList()
const { userAddress: connectedUserAddress } = useUserAccount()
const isChecked = connectedUserAddress ? userAddress === connectedUserAddress : false
const isMyPositionsChecked = connectedUserAddress ? userAddress === connectedUserAddress : false

return (
<Checkbox
isChecked={isChecked}
mb="xxs"
onChange={e => toggleUserAddress(e.target.checked, connectedUserAddress as string)}
>
<Text fontSize="sm">My positions</Text>
</Checkbox>
<VStack align="start" spacing="xs">
<Checkbox
isChecked={isMyPositionsChecked}
mb="xxs"
onChange={e => toggleUserAddress(e.target.checked, connectedUserAddress as string)}
>
<Text fontSize="sm">My positions</Text>
</Checkbox>

<Checkbox
isChecked={joinablePools}
mb="xxs"
onChange={e => toggleJoinablePools(e.target.checked)}
>
<HStack gap="xs">
<Text fontSize="sm">Joinable pools</Text>
<TooltipWithTouch
label="This shows pools across networks where you have at least one token in your wallet. For performance reasons, this will only filter from the top 100 pools for your current search criteria."
placement="top"
>
<Icon
_hover={{ opacity: 1 }}
as={Info}
boxSize={3}
color="font.secondary"
opacity={0.6}
position="relative"
top="1px"
/>
</TooltipWithTouch>
</HStack>
</Checkbox>
</VStack>
)
}

Expand Down Expand Up @@ -262,6 +298,8 @@ export interface FilterTagsPops {
poolHookTags?: PoolHookTagType[]
togglePoolHookTag?: (checked: boolean, value: PoolHookTagType) => void
poolHookTagLabel?: (poolHookTag: PoolHookTagType) => string
joinablePools?: boolean
toggleJoinablePools?: (checked: boolean) => void
}

export function FilterTags({
Expand All @@ -280,6 +318,8 @@ export function FilterTags({
poolHookTags,
togglePoolHookTag,
poolHookTagLabel,
joinablePools,
toggleJoinablePools,
protocolVersion,
setProtocolVersion,
}: FilterTagsPops) {
Expand All @@ -293,6 +333,7 @@ export function FilterTags({
(poolTags ? poolTags.length === 0 : true) &&
!includeExpiredPools &&
(poolHookTags ? poolHookTags.length === 0 : true) &&
!joinablePools &&
!protocolVersion
) {
return <Box display={{ base: 'flex', md: 'none' }} minHeight="32px" />
Expand Down Expand Up @@ -360,6 +401,14 @@ export function FilterTags({
onClose={() => togglePoolHookTag && togglePoolHookTag(false, tag)}
/>
))}

{joinablePools && (
<AnimatedTag
key="joinablePools"
label="Joinable pools"
onClose={() => toggleJoinablePools && toggleJoinablePools(false)}
/>
)}
</AnimatePresence>
</HStack>
)
Expand Down Expand Up @@ -552,9 +601,9 @@ export function PoolListFilters() {
{isConnected ? (
<Box as={motion.div} variants={staggeredFadeInUp}>
<Heading as="h3" my="sm" size="sm">
My liquidity
Based on my wallet
</Heading>
<UserPoolFilter />
<UserLiquidityFilters />
</Box>
) : null}
{/* TODO: filter for cow networks when 'isCowPath' is true */}
Expand Down
4 changes: 4 additions & 0 deletions packages/lib/modules/pool/PoolList/PoolListLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export function PoolListLayout() {
poolHookTags,
togglePoolHookTag,
poolHookTagLabel,
joinablePools,
toggleJoinablePools,
protocolVersion,
setProtocolVersion,
},
Expand Down Expand Up @@ -78,6 +80,7 @@ export function PoolListLayout() {
</Box>
</HStack>
<FilterTags
joinablePools={joinablePools}
minTvl={minTvl}
networks={networks}
poolHookTagLabel={poolHookTagLabel}
Expand All @@ -89,6 +92,7 @@ export function PoolListLayout() {
protocolVersion={protocolVersion}
setMinTvl={setMinTvl}
setProtocolVersion={setProtocolVersion}
toggleJoinablePools={toggleJoinablePools}
toggleNetwork={toggleNetwork}
togglePoolHookTag={togglePoolHookTag}
togglePoolTag={togglePoolTag}
Expand Down
3 changes: 3 additions & 0 deletions packages/lib/modules/pool/PoolList/PoolListPoolDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ export function PoolListPoolDisplay({
pool,
name,
poolDisplayType,
isTokenInWallet,
}: {
pool: PoolListItem | ExpandedPoolInfo
name: string | undefined
poolDisplayType: PoolDisplayType
isTokenInWallet?: (tokenAddress: string) => boolean
}) {
let component

Expand All @@ -24,6 +26,7 @@ export function PoolListPoolDisplay({
<PoolListTokenPills
h={['32px', '36px']}
iconSize={name ? 24 : 20}
isTokenInWallet={isTokenInWallet}
nameSize="sm"
p={['xxs', 'sm']}
pool={pool}
Expand Down
129 changes: 122 additions & 7 deletions packages/lib/modules/pool/PoolList/PoolListProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
'use client'

import { createContext, PropsWithChildren, useEffect, useState } from 'react'
import { createContext, PropsWithChildren, useEffect, useMemo, useState } from 'react'
import {
GetPoolsDocument,
GqlChain,
GqlPoolType,
} from '@repo/lib/shared/services/api/generated/graphql'
import { useQuery } from '@apollo/client/react'
import { useApolloClient, useQuery } from '@apollo/client/react'
import { usePoolListQueryState } from './usePoolListQueryState'
import { useMandatoryContext } from '@repo/lib/shared/utils/contexts'
import { useUserAccount } from '../../web3/UserAccountProvider'
Expand All @@ -15,6 +15,10 @@ import { PoolDisplayType } from '../pool.types'
import { PROJECT_CONFIG } from '@repo/lib/config/getProjectConfig'
import { removeHookDataFromPoolIfNecessary } from '../pool.utils'
import { PoolListItem } from '../pool.types'
import { useQuery as useReactQuery } from '@tanstack/react-query'
import { useTokens } from '../../tokens/TokensProvider'
import { bn } from '@repo/lib/shared/utils/numbers'
import { useWalletTokenBalances } from '../../tokens/useWalletTokenBalances'

export function usePoolListLogic({
fixedPoolTypes,
Expand All @@ -24,12 +28,14 @@ export function usePoolListLogic({
fixedChains?: GqlChain[]
} = {}) {
const queryState = usePoolListQueryState()
const { userAddress } = useUserAccount()
const { userAddress, isConnected } = useUserAccount()
const apolloClient = useApolloClient()
const { isLoadingTokens, isLoadingTokenPrices } = useTokens()
const [poolDisplayType, setPoolDisplayType] = useState<PoolDisplayType>(
PROJECT_CONFIG.options.poolDisplayType
)

const { queryVariables, toggleUserAddress } = queryState
const { queryVariables, toggleUserAddress, joinablePools } = queryState

const variables = {
...queryVariables,
Expand All @@ -51,6 +57,114 @@ export function usePoolListLogic({

const poolsData = pools.map(pool => removeHookDataFromPoolIfNecessary(pool)) as PoolListItem[]

const selectedChains = variables.where.chainIn || []
const joinableChains = selectedChains.filter(chain => chain !== GqlChain.Sepolia)

const {
tokenBalancesByChain: walletTokenAddressesByChain,
isLoading: isWalletBalancesLoading,
errors: walletBalanceErrors,
hasBalance: hasWalletTokenBalance,
} = useWalletTokenBalances(joinableChains, joinablePools)

const joinablePoolsQuery = useReactQuery({
queryKey: [
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we really want to cache each of these queries?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's caching several apollo queries so why don't do it?

'pool-list-joinable-pools',
joinableChains.join(','),
joinableChains
.map(chain => `${chain}:${(walletTokenAddressesByChain.get(chain) || []).join(',')}`)
.join('|'),
queryVariables.first,
queryVariables.skip,
queryVariables.orderBy,
queryVariables.orderDirection,
queryVariables.textSearch || '',
queryVariables.where.protocolVersionIn?.join(',') || '',
queryVariables.where.poolTypeIn?.join(',') || '',
queryVariables.where.poolTypeNotIn?.join(',') || '',
queryVariables.where.tagIn?.join(',') || '',
queryVariables.where.tagNotIn?.join(',') || '',
],
queryFn: async () => {
const chainsWithTokens = joinableChains
.map(chain => ({
chain,
tokensIn: (walletTokenAddressesByChain.get(chain) || []).map(address =>
address.toLowerCase()
),
}))
.filter(({ tokensIn }) => tokensIn.length > 0)

const results = await Promise.allSettled(
chainsWithTokens.map(async ({ chain, tokensIn }) => {
const response = await apolloClient.query({
query: GetPoolsDocument,
variables: {
...queryVariables,
where: {
...queryVariables.where,
chainIn: [chain],
tokensIn,
minTvl: 50_000,
},
},
})

return response.data?.pools || []
})
)

return results.flatMap(result => (result.status === 'fulfilled' ? result.value : []))
},
enabled:
joinablePools &&
isConnected &&
isAddress(userAddress) &&
!isLoadingTokens &&
!isLoadingTokenPrices &&
joinableChains.some(chain => (walletTokenAddressesByChain.get(chain) || []).length > 0),
staleTime: 30_000,
})

const joinablePoolsData = useMemo(() => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure we need the useMemo here.

Copy link
Copy Markdown
Collaborator

@groninge01 groninge01 Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is some deduping and sorting done so i think it's ok to keep it

if (!joinablePools || !isConnected || !isAddress(userAddress)) return poolsData
if (walletBalanceErrors.length > 0) return poolsData

const allJoinablePools = joinablePoolsQuery.data || []
const uniquePools = new Map<string, PoolListItem>()

allJoinablePools.forEach(pool => {
if (!uniquePools.has(pool.id)) {
uniquePools.set(pool.id, removeHookDataFromPoolIfNecessary(pool) as PoolListItem)
}
})

return Array.from(uniquePools.values()).sort((a, b) => {
return bn(b.dynamicData.totalLiquidity).comparedTo(bn(a.dynamicData.totalLiquidity)) ?? 0
})
}, [
joinablePools,
poolsData,
isConnected,
userAddress,
joinablePoolsQuery.data,
walletBalanceErrors,
])

const isJoinableBalanceLoading =
joinablePools &&
(isLoadingTokens ||
isLoadingTokenPrices ||
isWalletBalancesLoading ||
joinablePoolsQuery.isLoading ||
joinablePoolsQuery.isFetching)

const filteredPools = joinablePools
? isJoinableBalanceLoading
? poolsData
: joinablePoolsData
: poolsData

const isFixedPoolType = !!fixedPoolTypes && fixedPoolTypes.length > 0

// If the user has previously selected to filter by their liquidity and then
Expand All @@ -62,13 +176,14 @@ export function usePoolListLogic({
}, [userAddress])

return {
pools: poolsData,
count: data?.count || previousData?.count,
pools: filteredPools,
count: joinablePools ? filteredPools.length : data?.count || previousData?.count,
queryState,
loading,
loading: loading || isJoinableBalanceLoading,
error,
networkStatus,
isFixedPoolType,
hasWalletTokenBalance,
refetch,
poolDisplayType,
setPoolDisplayType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export function PoolListTable({ pools, count, loading }: Props) {
getRowId={item => item.id}
items={pools}
loading={loading}
loadingSpinnerPosition="top"
noItemsFoundLabel="No pools found"
paginationProps={paginationProps}
renderTableHeader={renderTableHeader}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const setIsDesc = (id: GqlPoolOrderBy, currentSortingObj: PoolsColumnSort) =>

export function PoolListTableHeader({ ...rest }) {
const {
queryState: { sorting, setSorting },
queryState: { sorting, setSorting, joinablePools },
} = usePoolList()
const { orderBy } = usePoolOrderByState()
const sortingObj = sorting[0]
Expand Down Expand Up @@ -57,6 +57,7 @@ export function PoolListTableHeader({ ...rest }) {
right="-6px"
>
<SortableHeader
isDisabled={joinablePools}
isSorted={sortingObj.id === orderByItem}
label={orderByHash[orderByItem]}
onSort={() => handleSort(orderByItem)}
Expand Down
Loading
Loading