From 4e589c7c036954c3a174e3217bcfeef3c6cee339 Mon Sep 17 00:00:00 2001 From: Anastasios Date: Wed, 13 Nov 2024 20:24:11 +0300 Subject: [PATCH 1/6] feat: vote list - filtering/sorting --- .../PoolActivity/poolActivity.types.ts | 5 - .../PoolActivity/usePoolActivity.tsx | 2 +- .../PoolActivityTableHeader.tsx | 4 +- .../modules/pool/PoolList/PoolListFilters.tsx | 215 ++-- .../modules/pool/PoolList/PoolListLayout.tsx | 31 +- .../PoolListTable/PoolListTableHeader.tsx | 6 +- .../pool/PoolList/usePoolListQueryState.tsx | 38 +- .../PortfolioTable/PortfolioTableHeader.tsx | 6 +- .../modules/vebal/vote/VoteExpiredTooltip.tsx | 44 + .../modules/vebal/vote/VoteList/VoteList.tsx | 6 +- .../vebal/vote/VoteList/VoteListFilters.tsx | 166 +++ .../vebal/vote/VoteList/VoteListLayout.tsx | 37 +- .../vebal/vote/VoteList/VoteListProvider.tsx | 103 +- .../vebal/vote/VoteList/VoteListSearch.tsx | 26 + .../VoteList/VoteListTable/VoteListTable.tsx | 4 +- .../VoteListTable/VoteListTableHeader.tsx | 48 +- .../VoteListTable/VoteListTableRow.tsx | 2 + .../vote/VoteList/useVoteListFiltersState.tsx | 105 ++ .../vebal/vote/gauge/useGaugeVotes.tsx | 24 +- packages/lib/modules/vebal/vote/vote.types.ts | 14 + packages/lib/modules/web3/contracts/AbiMap.ts | 2 + .../web3/contracts/abi/LiquidityGaugeV5Abi.ts | 1016 +++++++++++++++++ .../shared/components/icons/VoteCapIcon.tsx | 26 +- .../components/tables/SortableHeader.tsx | 7 +- 24 files changed, 1767 insertions(+), 170 deletions(-) create mode 100644 packages/lib/modules/vebal/vote/VoteExpiredTooltip.tsx create mode 100644 packages/lib/modules/vebal/vote/VoteList/VoteListFilters.tsx create mode 100644 packages/lib/modules/vebal/vote/VoteList/VoteListSearch.tsx create mode 100644 packages/lib/modules/vebal/vote/VoteList/useVoteListFiltersState.tsx create mode 100644 packages/lib/modules/web3/contracts/abi/LiquidityGaugeV5Abi.ts diff --git a/packages/lib/modules/pool/PoolDetail/PoolActivity/poolActivity.types.ts b/packages/lib/modules/pool/PoolDetail/PoolActivity/poolActivity.types.ts index d751e22c0..5d959baa7 100644 --- a/packages/lib/modules/pool/PoolDetail/PoolActivity/poolActivity.types.ts +++ b/packages/lib/modules/pool/PoolDetail/PoolActivity/poolActivity.types.ts @@ -29,11 +29,6 @@ export interface PoolActivityTypeTab { label: string } -export enum Sorting { - asc = 'asc', - desc = 'desc', -} - export function getPoolActivityTabsList({ variant, poolType, diff --git a/packages/lib/modules/pool/PoolDetail/PoolActivity/usePoolActivity.tsx b/packages/lib/modules/pool/PoolDetail/PoolActivity/usePoolActivity.tsx index 943b2bedf..e088416ae 100644 --- a/packages/lib/modules/pool/PoolDetail/PoolActivity/usePoolActivity.tsx +++ b/packages/lib/modules/pool/PoolDetail/PoolActivity/usePoolActivity.tsx @@ -19,11 +19,11 @@ import { PoolActivityTokens, SortingBy, getPoolActivityTabsList, - Sorting, } from './poolActivity.types' import { PaginationState } from '@repo/lib/shared/components/pagination/pagination.types' import { usePoolActivityViewType } from '../PoolActivityViewType/usePoolActivityViewType' import { sortAlphabetically } from '@repo/lib/shared/utils/sorting' +import { Sorting } from '@repo/lib/shared/components/tables/SortableHeader' export type PoolActivityResponse = ReturnType export const PoolActivityContext = createContext(null) diff --git a/packages/lib/modules/pool/PoolDetail/PoolActivityTable/PoolActivityTableHeader.tsx b/packages/lib/modules/pool/PoolDetail/PoolActivityTable/PoolActivityTableHeader.tsx index c8e621b73..001ddd929 100644 --- a/packages/lib/modules/pool/PoolDetail/PoolActivityTable/PoolActivityTableHeader.tsx +++ b/packages/lib/modules/pool/PoolDetail/PoolActivityTable/PoolActivityTableHeader.tsx @@ -2,8 +2,8 @@ import { Divider, Grid, GridItem, Text } from '@chakra-ui/react' import { usePoolActivity } from '../PoolActivity/usePoolActivity' -import { Sorting, SortingBy } from '../PoolActivity/poolActivity.types' -import { SortableHeader } from '@repo/lib/shared/components/tables/SortableHeader' +import { SortingBy } from '../PoolActivity/poolActivity.types' +import { SortableHeader, Sorting } from '@repo/lib/shared/components/tables/SortableHeader' export function PoolActivityTableHeader({ ...rest }) { const { diff --git a/packages/lib/modules/pool/PoolList/PoolListFilters.tsx b/packages/lib/modules/pool/PoolList/PoolListFilters.tsx index 3513d4d86..0355acd3b 100644 --- a/packages/lib/modules/pool/PoolList/PoolListFilters.tsx +++ b/packages/lib/modules/pool/PoolList/PoolListFilters.tsx @@ -125,11 +125,19 @@ function PoolCategoryFilters() { ) } -function PoolTypeFilters() { - const { - queryState: { togglePoolType, poolTypes, poolTypeLabel, setPoolTypes }, - } = usePoolList() +export interface PoolTypeFiltersArgs { + poolTypes: PoolFilterType[] + poolTypeLabel: (poolType: PoolFilterType) => string + setPoolTypes: (value: PoolFilterType[] | null) => void + togglePoolType: (checked: boolean, value: PoolFilterType) => void +} +export function PoolTypeFilters({ + togglePoolType, + poolTypes, + poolTypeLabel, + setPoolTypes, +}: PoolTypeFiltersArgs) { // remove query param when empty useEffect(() => { if (!poolTypes.length) { @@ -157,11 +165,18 @@ function PoolTypeFilters() { ) } -function PoolNetworkFilters() { +export interface PoolNetworkFiltersArgs { + toggledNetworks: GqlChain[] + toggleNetwork: (checked: boolean, value: GqlChain) => void + setNetworks: (value: GqlChain[] | null) => void +} + +export function PoolNetworkFilters({ + toggledNetworks, + toggleNetwork, + setNetworks, +}: PoolNetworkFiltersArgs) { const { supportedNetworks } = getProjectConfig() - const { - queryState: { networks: toggledNetworks, toggleNetwork, setNetworks }, - } = usePoolList() // Sort networks alphabetically after mainnet const sortedNetworks = [supportedNetworks[0], ...supportedNetworks.slice(1).sort()] @@ -239,24 +254,44 @@ function PoolMinTvlFilter() { ) } -export function FilterTags() { - const { - queryState: { - networks, - toggleNetwork, - poolTypes, - togglePoolType, - poolTypeLabel, - minTvl, - setMinTvl, - poolTags, - togglePoolTag, - poolTagLabel, - }, - } = usePoolList() +export interface FilterTagsPops { + networks: GqlChain[] + toggleNetwork: (checked: boolean, value: GqlChain) => void + poolTypes: PoolFilterType[] + togglePoolType: (checked: boolean, value: PoolFilterType) => void + poolTypeLabel: (poolType: PoolFilterType) => string + minTvl?: number + setMinTvl?: (value: number | null) => void + poolTags?: PoolTagType[] + togglePoolTag?: (checked: boolean, value: PoolTagType) => void + poolTagLabel?: (poolTag: PoolTagType) => string + includeExpiredPools?: boolean + toggleIncludeExpiredPools?: (checked: boolean) => void +} + +export function FilterTags({ + networks, + toggleNetwork, + poolTypes, + togglePoolType, + poolTypeLabel, + minTvl, + setMinTvl, + poolTags, + togglePoolTag, + poolTagLabel, + includeExpiredPools, + toggleIncludeExpiredPools, +}: FilterTagsPops) { const { toCurrency } = useCurrency() - if (networks.length === 0 && poolTypes.length === 0 && minTvl === 0 && poolTags.length === 0) { + if ( + networks.length === 0 && + poolTypes.length === 0 && + minTvl === 0 && + (poolTags ? poolTags.length === 0 : true) && + !includeExpiredPools + ) { return null } @@ -308,7 +343,7 @@ export function FilterTags() { ))} - {minTvl > 0 && ( + {minTvl && minTvl > 0 && ( ${toCurrency(minTvl)}`} - setMinTvl(0)} /> + setMinTvl && setMinTvl(0)} /> )} - - {poolTags.map(tag => ( + {poolTags && poolTagLabel && ( + + {poolTags.map(tag => ( + + + + + {poolTagLabel(tag)} + + + togglePoolTag && togglePoolTag(false, tag)} /> + + + ))} + + )} + {includeExpiredPools && ( + - {poolTagLabel(tag)} + {`Expired`} - togglePoolTag(false, tag)} /> + toggleIncludeExpiredPools && toggleIncludeExpiredPools(false)} + /> - ))} - + + )} ) } -const FilterButton = forwardRef((props, ref) => { - const { - queryState: { totalFilterCount }, - } = usePoolList() - const { isMobile } = useBreakpoints() - const textColor = useColorModeValue('#fff', 'font.dark') - - return ( - - ) -}) +export const FilterButton = forwardRef( + ({ totalFilterCount, ...props }, ref) => { + const { isMobile } = useBreakpoints() + const textColor = useColorModeValue('#fff', 'font.dark') + + return ( + + ) + } +) function ProtocolVersionFilter() { const { @@ -454,7 +515,18 @@ export function PoolListFilters() { const [isPopoverOpen, setIsPopoverOpen] = useState(false) const { isFixedPoolType, - queryState: { resetFilters, totalFilterCount, setActiveProtocolVersionTab }, + queryState: { + resetFilters, + totalFilterCount, + setActiveProtocolVersionTab, + networks: toggledNetworks, + toggleNetwork, + setNetworks, + togglePoolType, + poolTypes, + poolTypeLabel, + setPoolTypes, + }, } = usePoolList() const { isCowPath } = useCow() const { isMobile } = useBreakpoints() @@ -475,7 +547,7 @@ export function PoolListFilters() { placement="bottom-end" > - + @@ -524,7 +596,11 @@ export function PoolListFilters() { Networks - + {!isCowPath && ( @@ -539,7 +615,12 @@ export function PoolListFilters() { Pool types - + )} diff --git a/packages/lib/modules/pool/PoolList/PoolListLayout.tsx b/packages/lib/modules/pool/PoolList/PoolListLayout.tsx index f2c50a828..6536a717a 100644 --- a/packages/lib/modules/pool/PoolList/PoolListLayout.tsx +++ b/packages/lib/modules/pool/PoolList/PoolListLayout.tsx @@ -10,7 +10,23 @@ import { ErrorBoundary } from 'react-error-boundary' import { BoundaryError } from '@repo/lib/shared/components/errors/ErrorBoundary' export function PoolListLayout() { - const { pools, loading, count } = usePoolList() + const { + pools, + loading, + count, + queryState: { + networks, + toggleNetwork, + poolTypes, + togglePoolType, + poolTypeLabel, + minTvl, + setMinTvl, + poolTags, + togglePoolTag, + poolTagLabel, + }, + } = usePoolList() const isFilterVisible = useFilterTagsVisible() const isMd = useBreakpointValue({ base: false, md: true }) @@ -56,7 +72,18 @@ export function PoolListLayout() { - + @@ -48,7 +48,7 @@ export function PoolListTableHeader({ ...rest }) { isSorted={sortingObj.id === orderByItem} label={orderByHash[orderByItem]} onSort={() => handleSort(orderByItem)} - sorting={sortingObj.desc ? 'desc' : 'asc'} + sorting={sortingObj.desc ? Sorting.desc : Sorting.asc} /> ))} diff --git a/packages/lib/modules/pool/PoolList/usePoolListQueryState.tsx b/packages/lib/modules/pool/PoolList/usePoolListQueryState.tsx index 681bf6f78..33d37eff5 100644 --- a/packages/lib/modules/pool/PoolList/usePoolListQueryState.tsx +++ b/packages/lib/modules/pool/PoolList/usePoolListQueryState.tsx @@ -40,6 +40,25 @@ export const PROTOCOL_VERSION_TABS: ButtonGroupOption[] = [ }, ] as const +export function poolTypeLabel(poolType: GqlPoolType) { + switch (poolType) { + case GqlPoolType.Weighted: + return 'Weighted' + case GqlPoolType.Stable: + return 'Stable' + case GqlPoolType.LiquidityBootstrapping: + return 'Liquidity Bootstrapping (LBP)' + case GqlPoolType.Gyro: + return 'Gyro CLP' + case GqlPoolType.CowAmm: + return 'CoW AMM' + case GqlPoolType.Fx: + return 'FX' + default: + return poolType.toLowerCase() + } +} + export function usePoolListQueryState() { const [first, setFirst] = useQueryState('first', poolListQueryStateParsers.first) const [skip, setSkip] = useQueryState('skip', poolListQueryStateParsers.skip) @@ -134,25 +153,6 @@ export function usePoolListQueryState() { setTextSearch(text) } - function poolTypeLabel(poolType: GqlPoolType) { - switch (poolType) { - case GqlPoolType.Weighted: - return 'Weighted' - case GqlPoolType.Stable: - return 'Stable' - case GqlPoolType.LiquidityBootstrapping: - return 'Liquidity Bootstrapping (LBP)' - case GqlPoolType.Gyro: - return 'Gyro CLP' - case GqlPoolType.CowAmm: - return 'CoW AMM' - case GqlPoolType.Fx: - return 'FX' - default: - return poolType.toLowerCase() - } - } - function poolTagLabel(poolTag: PoolTagType) { switch (poolTag) { case 'INCENTIVIZED': diff --git a/packages/lib/modules/portfolio/PortfolioTable/PortfolioTableHeader.tsx b/packages/lib/modules/portfolio/PortfolioTable/PortfolioTableHeader.tsx index 622ce665b..d62e68414 100644 --- a/packages/lib/modules/portfolio/PortfolioTable/PortfolioTableHeader.tsx +++ b/packages/lib/modules/portfolio/PortfolioTable/PortfolioTableHeader.tsx @@ -1,8 +1,8 @@ import { Grid, GridItem, Icon, Text, VStack } from '@chakra-ui/react' import { Globe } from 'react-feather' -import { SortableHeader } from '@repo/lib/shared/components/tables/SortableHeader' -import { PortfolioTableSortingId, PortfolioSortingData, portfolioOrderBy } from './PortfolioTable' +import { SortableHeader, Sorting } from '@repo/lib/shared/components/tables/SortableHeader' +import { portfolioOrderBy, PortfolioSortingData, PortfolioTableSortingId } from './PortfolioTable' const setIsDesc = (id: PortfolioTableSortingId, currentSortingObj: PortfolioSortingData) => currentSortingObj.id === id ? !currentSortingObj.desc : true @@ -50,7 +50,7 @@ export function PortfolioTableHeader({ currentSortingObj, setCurrentSortingObj, setCurrentSortingObj({ id: orderByItem.id, desc: false }) } }} - sorting={currentSortingObj.desc ? 'desc' : 'asc'} + sorting={currentSortingObj.desc ? Sorting.desc : Sorting.asc} /> ))} diff --git a/packages/lib/modules/vebal/vote/VoteExpiredTooltip.tsx b/packages/lib/modules/vebal/vote/VoteExpiredTooltip.tsx new file mode 100644 index 000000000..1fc1f5b26 --- /dev/null +++ b/packages/lib/modules/vebal/vote/VoteExpiredTooltip.tsx @@ -0,0 +1,44 @@ +import { + Badge, + Popover, + PopoverContent, + PopoverTrigger, + Portal, + Text, + VStack, +} from '@chakra-ui/react' + +interface Props { + usePortal?: boolean +} + +export function VoteExpiredTooltip({ usePortal }: Props) { + const popoverContent = ( + + + + Expired pool gauge + + + This pool gauge that collects votes from veBAL holders to distribute BAL liquidity + incentives is no longer active. If you have active votes to this pool, reallocate them to + active pool gauges to avoid wasting your vote. + + + + ) + + return ( + + <> + + + Expired + + + + {usePortal ? {popoverContent} : popoverContent} + + + ) +} diff --git a/packages/lib/modules/vebal/vote/VoteList/VoteList.tsx b/packages/lib/modules/vebal/vote/VoteList/VoteList.tsx index 1c81d07ba..75e9b2900 100644 --- a/packages/lib/modules/vebal/vote/VoteList/VoteList.tsx +++ b/packages/lib/modules/vebal/vote/VoteList/VoteList.tsx @@ -1,4 +1,3 @@ -import { PoolListProvider } from '@repo/lib/modules/pool/PoolList/PoolListProvider' import { VoteListLayout } from './VoteListLayout' import { VoteListProvider } from '@repo/lib/modules/vebal/vote/VoteList/VoteListProvider' import { getHiddenHandVotingIncentives } from '@repo/lib/modules/vebal/vote/hidden-hand/getHiddenHandVotingIncentives' @@ -33,10 +32,7 @@ export async function VoteList() { votingIncentives={votingIncentives} votingIncentivesError={errorToJson(votingIncentivesError)} > - {/* fix: remove PoolListProvider when voteFilters implemented */} - - - + ) } diff --git a/packages/lib/modules/vebal/vote/VoteList/VoteListFilters.tsx b/packages/lib/modules/vebal/vote/VoteList/VoteListFilters.tsx new file mode 100644 index 000000000..9e2659e00 --- /dev/null +++ b/packages/lib/modules/vebal/vote/VoteList/VoteListFilters.tsx @@ -0,0 +1,166 @@ +import { useEffect, useState } from 'react' +import { poolTypeLabel } from '@repo/lib/modules/pool/PoolList/usePoolListQueryState' +import { + Box, + Button, + Checkbox, + Flex, + Heading, + HStack, + Popover, + PopoverArrow, + PopoverBody, + PopoverCloseButton, + PopoverContent, + PopoverTrigger, + Text, + VStack, +} from '@chakra-ui/react' +import { AnimatePresence, motion } from 'framer-motion' +import { staggeredFadeInUp } from '@repo/lib/shared/utils/animations' +import { + FilterButton, + PoolNetworkFilters, + PoolTypeFilters, +} from '@repo/lib/modules/pool/PoolList/PoolListFilters' +import { useVoteList } from '@repo/lib/modules/vebal/vote/VoteList/VoteListProvider' +import { VoteListSearch } from '@repo/lib/modules/vebal/vote/VoteList/VoteListSearch' +import { PoolFilterType } from '@repo/lib/modules/pool/pool.types' + +export function useFilterTagsVisible() { + const { + filtersState: { networks, poolTypes, includeExpiredPools }, + } = useVoteList() + const [isVisible, setIsVisible] = useState(false) + + useEffect(() => { + setIsVisible(networks.length > 0 || poolTypes.length > 0 || includeExpiredPools) + }, [networks, poolTypes, includeExpiredPools]) + + return isVisible +} + +export function VoteListFilters() { + const [isPopoverOpen, setIsPopoverOpen] = useState(false) + const { + filtersState: { + resetFilters, + totalFilterCount, + networks: toggledNetworks, + toggleNetwork, + setNetworks, + togglePoolType, + poolTypes, + setPoolTypes, + includeExpiredPools, + toggleIncludeExpiredPools, + }, + } = useVoteList() + + function _resetFilters() { + resetFilters() + } + + return ( + + + + setIsPopoverOpen(false)} + onOpen={() => setIsPopoverOpen(true)} + placement="bottom-end" + > + + + + + + + + + + {isPopoverOpen ? ( + + + + + Filters + + {totalFilterCount > 0 && ( + + )} + + + + + + Networks + + + + + + Pool types + + + + + + Pool gauge display + + + + toggleIncludeExpiredPools(e.target.checked)} + > + + Show expired pools gauges + + + + + + + ) : null} + + + + + + + + ) +} diff --git a/packages/lib/modules/vebal/vote/VoteList/VoteListLayout.tsx b/packages/lib/modules/vebal/vote/VoteList/VoteListLayout.tsx index 76a20c610..c459e546b 100644 --- a/packages/lib/modules/vebal/vote/VoteList/VoteListLayout.tsx +++ b/packages/lib/modules/vebal/vote/VoteList/VoteListLayout.tsx @@ -5,16 +5,29 @@ import { motion } from 'framer-motion' import { fNum } from '@repo/lib/shared/utils/numbers' import { ErrorBoundary } from 'react-error-boundary' import { BoundaryError } from '@repo/lib/shared/components/errors/ErrorBoundary' -import { - FilterTags, - PoolListFilters, - useFilterTagsVisible, -} from '@repo/lib/modules/pool/PoolList/PoolListFilters' +import { FilterTags } from '@repo/lib/modules/pool/PoolList/PoolListFilters' import { VoteListTable } from '@repo/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTable' import { useVoteList } from '@repo/lib/modules/vebal/vote/VoteList/VoteListProvider' +import { + useFilterTagsVisible, + VoteListFilters, +} from '@repo/lib/modules/vebal/vote/VoteList/VoteListFilters' +import { poolTypeLabel } from '@repo/lib/modules/pool/PoolList/usePoolListQueryState' export function VoteListLayout() { - const { sortedVoteList, loading, count } = useVoteList() + const { + sortedVoteList, + loading, + count, + filtersState: { + networks, + toggleNetwork, + poolTypes, + togglePoolType, + includeExpiredPools, + toggleIncludeExpiredPools, + }, + } = useVoteList() const isFilterVisible = useFilterTagsVisible() const isMd = useBreakpointValue({ base: false, md: true }) @@ -60,7 +73,15 @@ export function VoteListLayout() { - + - + diff --git a/packages/lib/modules/vebal/vote/VoteList/VoteListProvider.tsx b/packages/lib/modules/vebal/vote/VoteList/VoteListProvider.tsx index 981f97455..c1306ca5c 100644 --- a/packages/lib/modules/vebal/vote/VoteList/VoteListProvider.tsx +++ b/packages/lib/modules/vebal/vote/VoteList/VoteListProvider.tsx @@ -1,13 +1,75 @@ 'use client' import { createContext, PropsWithChildren, useMemo } from 'react' -import { GetVeBalVotingListQuery } from '@repo/lib/shared/services/api/generated/graphql' +import { GetVeBalVotingListQuery, GqlChain } from '@repo/lib/shared/services/api/generated/graphql' import { useMandatoryContext } from '@repo/lib/shared/utils/contexts' -import { usePoolListQueryState } from '@repo/lib/modules/pool/PoolList/usePoolListQueryState' import { useGaugeVotes } from '@repo/lib/modules/vebal/vote/gauge/useGaugeVotes' -import { VotingPoolWithData } from '@repo/lib/modules/vebal/vote/vote.types' +import { SortingBy, VotingPoolWithData } from '@repo/lib/modules/vebal/vote/vote.types' import { orderBy } from 'lodash' import { HiddenHandData } from '@repo/lib/modules/vebal/vote/hidden-hand/hidden-hand.types' +import { useVoteListFiltersState } from '@repo/lib/modules/vebal/vote/VoteList/useVoteListFiltersState' +import { Sorting } from '@repo/lib/shared/components/tables/SortableHeader' +import { PoolFilterType } from '@repo/lib/modules/pool/pool.types' + +function sortVoteList(voteList: VotingPoolWithData[], sortBy: SortingBy, order: Sorting) { + return orderBy( + voteList, + value => { + switch (sortBy) { + case SortingBy.votes: + return value.gaugeVotes ? Number(value.gaugeVotes.votesNextPeriod) : -1 + case SortingBy.bribes: + return value.votingIncentive ? Number(value.votingIncentive.totalValue) : -1 + case SortingBy.bribesPerVebal: + return value.votingIncentive ? Number(value.votingIncentive.valuePerVote) : -1 + case SortingBy.type: + return value.type + default: + throw new Error(`Unsupported SortingBy value (${sortBy})`) + } + }, + order === Sorting.asc ? 'asc' : 'desc' + ) +} + +function filterVoteList( + voteList: VotingPoolWithData[], + textSearch: string, + networks: GqlChain[], + poolTypes: PoolFilterType[], + includeExpiredPools: boolean +) { + let result = voteList + + const _textSearch = textSearch.toLowerCase().trim() + if (_textSearch) { + result = result.filter(value => { + return ( + value.id.toLowerCase().includes(_textSearch) || + value.tokens.some(token => { + return ( + token.symbol.toLowerCase().includes(_textSearch) || + token.address.toLowerCase().includes(_textSearch) + ) + }) + ) + }) + } + + if (networks.length > 0) { + result = result.filter(value => networks.includes(value.chain)) + } + + if (poolTypes.length > 0) { + result = result.filter(value => poolTypes.includes(value.type as PoolFilterType)) + } + + if (!includeExpiredPools) { + result = result.filter(value => !value.gaugeVotes?.isKilled) + } + + return result +} export interface UseVoteListArgs { data: GetVeBalVotingListQuery | undefined @@ -26,13 +88,10 @@ export function _useVoteList({ votingIncentivesError, votingIncentivesLoading = false, }: UseVoteListArgs) { - // todo: implement vote's sorting/filtering - const queryState = usePoolListQueryState() + const filtersState = useVoteListFiltersState() const voteListData = data?.veBalGetVotingList || [] - const pagination = queryState.pagination - const gaugeAddresses = useMemo(() => voteListData.map(vote => vote.gauge.address), [voteListData]) const { gaugeVotes, isLoading: gaugeVotesIsLoading } = useGaugeVotes({ gaugeAddresses }) @@ -47,25 +106,37 @@ export function _useVoteList({ })) }, [voteListData, gaugeVotes, votingIncentives]) - const sortedVoteList = useMemo(() => { - const sortedList = orderBy( + const filteredVoteList = useMemo(() => { + return filterVoteList( votingPoolsList, - v => (v.gaugeVotes ? Number(v.gaugeVotes.votesNextPeriod) : 0), - 'desc' + filtersState.searchText, + filtersState.networks, + filtersState.poolTypes, + filtersState.includeExpiredPools ) + }, [ + votingPoolsList, + filtersState.searchText, + filtersState.networks, + filtersState.poolTypes, + filtersState.includeExpiredPools, + ]) + + const sortedVoteList = useMemo(() => { + const sortedList = sortVoteList(filteredVoteList, filtersState.sortingBy, filtersState.sorting) return sortedList.slice( - pagination.pageIndex * pagination.pageSize, - pagination.pageSize * (pagination.pageIndex + 1) + filtersState.pagination.pageIndex * filtersState.pagination.pageSize, + filtersState.pagination.pageSize * (filtersState.pagination.pageIndex + 1) ) - }, [votingPoolsList, pagination]) + }, [filteredVoteList, filtersState.pagination, filtersState.sorting, filtersState.sortingBy]) return { - queryState, + filtersState, sortedVoteList, voteListLoading, loading: voteListLoading || votingIncentivesLoading || gaugeVotesIsLoading, - count: data?.veBalGetVotingList.length, + count: filteredVoteList.length, error, votingIncentivesLoading, votingIncentivesError, diff --git a/packages/lib/modules/vebal/vote/VoteList/VoteListSearch.tsx b/packages/lib/modules/vebal/vote/VoteList/VoteListSearch.tsx new file mode 100644 index 000000000..c974d316f --- /dev/null +++ b/packages/lib/modules/vebal/vote/VoteList/VoteListSearch.tsx @@ -0,0 +1,26 @@ +import { FormControl, Box } from '@chakra-ui/react' +import { SearchInput } from '@repo/lib/shared/components/inputs/SearchInput' +import { useVoteList } from '@repo/lib/modules/vebal/vote/VoteList/VoteListProvider' + +export function VoteListSearch() { + const { + loading, + filtersState: { searchText, setSearch }, + } = useVoteList() + + return ( + +
+ + + +
+
+ ) +} diff --git a/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTable.tsx b/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTable.tsx index e57ed3073..82b779161 100644 --- a/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTable.tsx +++ b/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTable.tsx @@ -6,7 +6,7 @@ import { VoteListTableRow } from './VoteListTableRow' import { getPaginationProps } from '@repo/lib/shared/components/pagination/getPaginationProps' import { Card, Skeleton } from '@chakra-ui/react' import { useIsMounted } from '@repo/lib/shared/hooks/useIsMounted' -import { VoteListItem, VotingPoolWithData } from '@repo/lib/modules/vebal/vote/vote.types' +import { VotingPoolWithData } from '@repo/lib/modules/vebal/vote/vote.types' import { useVoteList } from '@repo/lib/modules/vebal/vote/VoteList/VoteListProvider' interface Props { @@ -18,7 +18,7 @@ interface Props { export function VoteListTable({ voteList, count, loading }: Props) { const isMounted = useIsMounted() const { - queryState: { pagination, setPagination }, + filtersState: { pagination, setPagination }, } = useVoteList() const paginationProps = getPaginationProps(count || 0, pagination, setPagination) const showPagination = !!voteList.length && !!count && count > pagination.pageSize diff --git a/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTableHeader.tsx b/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTableHeader.tsx index e3a6d6239..3dcea8bb8 100644 --- a/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTableHeader.tsx +++ b/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTableHeader.tsx @@ -2,8 +2,26 @@ import { Grid, GridItem, Icon, Text, VStack } from '@chakra-ui/react' import { Globe } from 'react-feather' +import { SortableHeader, Sorting } from '@repo/lib/shared/components/tables/SortableHeader' +import { useVoteList } from '@repo/lib/modules/vebal/vote/VoteList/VoteListProvider' +import { orderByHash, SortingBy } from '@repo/lib/modules/vebal/vote/vote.types' + +const orderBy = Object.values(SortingBy) export function VoteListTableHeader({ ...rest }) { + const { + filtersState: { sorting, setSorting, sortingBy, setSortingBy, toggleSorting }, + } = useVoteList() + + const handleSort = (newSortingBy: SortingBy) => { + if (sortingBy === newSortingBy) { + toggleSorting() + } else { + setSortingBy(newSortingBy) + setSorting(Sorting.desc) + } + } + return ( @@ -14,26 +32,16 @@ export function VoteListTableHeader({ ...rest }) { Pool name - - - Type - - - - - Bribes - - - - - Bribes/veBAL - - - - - veBAL votes - - + {orderBy.map(orderByItem => ( + + handleSort(orderByItem)} + sorting={sorting} + /> + + ))} Action diff --git a/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTableRow.tsx b/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTableRow.tsx index 1e82badd7..249333de3 100644 --- a/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTableRow.tsx +++ b/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTableRow.tsx @@ -10,6 +10,7 @@ import { ArrowUpIcon } from '@repo/lib/shared/components/icons/ArrowUpIcon' import React, { useState } from 'react' import { useVoteList } from '@repo/lib/modules/vebal/vote/VoteList/VoteListProvider' import { VoteListVotesCell } from '@repo/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListVotesCell' +import { VoteExpiredTooltip } from '@repo/lib/modules/vebal/vote/VoteExpiredTooltip' interface Props extends GridProps { vote: VotingPoolWithData @@ -64,6 +65,7 @@ export function VoteListTableRow({ vote, keyValue, ...rest }: Props) { pr={[1.5, 'ms']} type={vote.type} /> + {vote.gaugeVotes?.isKilled && } diff --git a/packages/lib/modules/vebal/vote/VoteList/useVoteListFiltersState.tsx b/packages/lib/modules/vebal/vote/VoteList/useVoteListFiltersState.tsx new file mode 100644 index 000000000..294be2bc4 --- /dev/null +++ b/packages/lib/modules/vebal/vote/VoteList/useVoteListFiltersState.tsx @@ -0,0 +1,105 @@ +import { useMemo, useState } from 'react' +import { PaginationState } from '@repo/lib/shared/components/pagination/pagination.types' +import { SortingBy } from '@repo/lib/modules/vebal/vote/vote.types' +import { Sorting } from '@repo/lib/shared/components/tables/SortableHeader' +import { GqlChain } from '@repo/lib/shared/services/api/generated/graphql' +import { uniq } from 'lodash' +import { PoolFilterType } from '@repo/lib/modules/pool/pool.types' + +const EMPTY_ARRAY: never[] = [] + +export function useVoteListFiltersState() { + const [sorting, setSorting] = useState(Sorting.desc) + const [sortingBy, setSortingBy] = useState(SortingBy.votes) + + const [first, setFirst] = useState(20) + const [skip, setSkip] = useState(0) + + const pagination: PaginationState = useMemo( + () => ({ + pageIndex: skip / first, + pageSize: first, + }), + [skip, first] + ) + + function setPagination(pagination: PaginationState) { + setFirst(pagination.pageSize) + setSkip(pagination.pageIndex * pagination.pageSize) + } + + function toggleSorting() { + setSorting(sorting === Sorting.asc ? Sorting.desc : Sorting.asc) + } + + const [networks, setNetworks] = useState(null) + const [poolTypes, setPoolTypes] = useState(null) + const [includeExpiredPools, setIncludeExpiredPools] = useState(false) + + const [textSearch, setTextSearch] = useState('') + + function resetFilters() { + setNetworks(null) + setPoolTypes(null) + setIncludeExpiredPools(false) + + setPagination({ pageSize: 20, pageIndex: 0 }) + setSorting(Sorting.desc) + setSortingBy(SortingBy.votes) + } + + // Set internal checked state + function toggleNetwork(checked: boolean, network: GqlChain) { + if (checked) { + setNetworks(current => uniq([...(current ?? []), network])) + } else { + setNetworks(current => (current ?? []).filter(chain => chain !== network)) + } + } + + // Set internal checked state + function togglePoolType(checked: boolean, poolType: PoolFilterType) { + if (checked) { + setPoolTypes(current => uniq([...(current ?? []), poolType])) + } else { + setPoolTypes(current => (current ?? []).filter(type => type !== poolType)) + } + } + + // Set internal checked state + function toggleIncludeExpiredPools(checked: boolean) { + setIncludeExpiredPools(checked) + } + + const totalFilterCount = + (networks?.length ?? 0) + (poolTypes?.length ?? 0) + (includeExpiredPools ? 1 : 0) + + function setSearch(text: string) { + if (text.length > 0) { + setSkip(0) + } + setTextSearch(text) + } + + return { + sorting, + setSorting, + sortingBy, + setSortingBy, + pagination, + setPagination, + toggleSorting, + totalFilterCount, + resetFilters, + searchText: textSearch, + setSearch, + networks: networks ?? EMPTY_ARRAY, + poolTypes: poolTypes ?? EMPTY_ARRAY, + includeExpiredPools, + toggleNetwork, + togglePoolType, + toggleIncludeExpiredPools, + setNetworks, + setPoolTypes, + } +} diff --git a/packages/lib/modules/vebal/vote/gauge/useGaugeVotes.tsx b/packages/lib/modules/vebal/vote/gauge/useGaugeVotes.tsx index 96118d328..11be02387 100644 --- a/packages/lib/modules/vebal/vote/gauge/useGaugeVotes.tsx +++ b/packages/lib/modules/vebal/vote/gauge/useGaugeVotes.tsx @@ -22,6 +22,7 @@ export interface RawVotesData { gaugeWeightNextPeriod: { result?: bigint; status: string } userVotes?: { result?: UserVotesData; status: string } lastUserVoteTime?: { result?: bigint; status: string } + isKilled?: { result?: boolean; status: string } } export interface VotesData { @@ -29,6 +30,7 @@ export interface VotesData { votesNextPeriod: string userVotes: string lastUserVoteTime: number + isKilled: boolean } export type RawVotesDataMap = Record @@ -42,6 +44,7 @@ function formatVotes(votesData: RawVotesData): VotesData { votesNextPeriod, userVotes: votesData?.userVotes?.result?.power.toString() || '0', lastUserVoteTime: Number(votesData?.lastUserVoteTime?.result) || 0, + isKilled: votesData?.isKilled?.result ?? false, } } @@ -65,29 +68,44 @@ export function useGaugeVotes({ gaugeAddresses }: UseGaugeVotesParams) { path: 'gaugeWeightThisPeriod', fn: 'gauge_relative_weight_write', args: [gaugeAddress, thisWeekTimestamp], + abi: AbiMap['balancer.gaugeControllerAbi'] as any, + address: mainnetNetworkConfig.contracts.gaugeController as Hex, }, { path: 'gaugeWeightNextPeriod', fn: 'gauge_relative_weight_write', args: [gaugeAddress, nextWeekTimestamp], + abi: AbiMap['balancer.gaugeControllerAbi'] as any, + address: mainnetNetworkConfig.contracts.gaugeController as Hex, }, isConnected && { path: 'userVotes', fn: 'vote_user_slopes', args: [userAddress, gaugeAddress], + abi: AbiMap['balancer.gaugeControllerAbi'] as any, + address: mainnetNetworkConfig.contracts.gaugeController as Hex, }, isConnected && { path: 'lastUserVoteTime', fn: 'last_user_vote', args: [userAddress, gaugeAddress], + abi: AbiMap['balancer.gaugeControllerAbi'] as any, + address: mainnetNetworkConfig.contracts.gaugeController as Hex, }, - ]).filter(Boolean) + { + path: 'isKilled', + fn: 'is_killed', + args: [], + abi: AbiMap['balancer.liquidityGaugeV5Abi'] as any, + address: gaugeAddress as Hex, + }, + ]) return requests.map(v => ({ chainId: mainnet.id, id: `${gaugeAddress}.${v.path}`, - abi: AbiMap['balancer.gaugeControllerAbi'] as any, - address: mainnetNetworkConfig.contracts.gaugeController as Hex, + abi: v.abi, + address: v.address, functionName: v.fn, args: v.args, })) diff --git a/packages/lib/modules/vebal/vote/vote.types.ts b/packages/lib/modules/vebal/vote/vote.types.ts index 7819b11cd..c186d8f0c 100644 --- a/packages/lib/modules/vebal/vote/vote.types.ts +++ b/packages/lib/modules/vebal/vote/vote.types.ts @@ -27,3 +27,17 @@ export function getVotesState(relativeWeightCap: number, votesNextPeriod: number } return VotesState.Normal } + +export enum SortingBy { + type = 'type', + bribes = 'bribes', + bribesPerVebal = 'bribesPerVebal', + votes = 'votes', +} + +export const orderByHash: Record = { + type: 'Type', + bribes: 'Bribes', + bribesPerVebal: 'Bribes/veBAL', + votes: 'veBAL votes', +} diff --git a/packages/lib/modules/web3/contracts/AbiMap.ts b/packages/lib/modules/web3/contracts/AbiMap.ts index 9a2283d6a..f4e282096 100644 --- a/packages/lib/modules/web3/contracts/AbiMap.ts +++ b/packages/lib/modules/web3/contracts/AbiMap.ts @@ -13,6 +13,7 @@ import { veDelegationProxyAbi, } from './abi/generated' import { VeDelegationProxyL2Abi } from './abi/veDelegationProxyL2' +import { LiquidityGaugeV5Abi } from './abi/LiquidityGaugeV5Abi' export const AbiMap = { 'balancer.vaultV2': balancerV2VaultAbi, @@ -28,6 +29,7 @@ export const AbiMap = { 'balancer.LiquidityGauge': LiquidityGaugeAbi, 'balancer.omniVotingEscrowAbi': OmniVotingEscrowAbi, 'balancer.gaugeControllerAbi': GaugeControllerAbi, + 'balancer.liquidityGaugeV5Abi': LiquidityGaugeV5Abi, } export type AbiMapType = keyof typeof AbiMap | undefined diff --git a/packages/lib/modules/web3/contracts/abi/LiquidityGaugeV5Abi.ts b/packages/lib/modules/web3/contracts/abi/LiquidityGaugeV5Abi.ts new file mode 100644 index 000000000..706a3264b --- /dev/null +++ b/packages/lib/modules/web3/contracts/abi/LiquidityGaugeV5Abi.ts @@ -0,0 +1,1016 @@ +export const LiquidityGaugeV5Abi = [ + { + name: 'Deposit', + inputs: [ + { + name: 'provider', + type: 'address', + indexed: true, + }, + { + name: 'value', + type: 'uint256', + indexed: false, + }, + ], + anonymous: false, + type: 'event', + }, + { + name: 'Withdraw', + inputs: [ + { + name: 'provider', + type: 'address', + indexed: true, + }, + { + name: 'value', + type: 'uint256', + indexed: false, + }, + ], + anonymous: false, + type: 'event', + }, + { + name: 'UpdateLiquidityLimit', + inputs: [ + { + name: 'user', + type: 'address', + indexed: true, + }, + { + name: 'original_balance', + type: 'uint256', + indexed: false, + }, + { + name: 'original_supply', + type: 'uint256', + indexed: false, + }, + { + name: 'working_balance', + type: 'uint256', + indexed: false, + }, + { + name: 'working_supply', + type: 'uint256', + indexed: false, + }, + ], + anonymous: false, + type: 'event', + }, + { + name: 'Transfer', + inputs: [ + { + name: '_from', + type: 'address', + indexed: true, + }, + { + name: '_to', + type: 'address', + indexed: true, + }, + { + name: '_value', + type: 'uint256', + indexed: false, + }, + ], + anonymous: false, + type: 'event', + }, + { + name: 'Approval', + inputs: [ + { + name: '_owner', + type: 'address', + indexed: true, + }, + { + name: '_spender', + type: 'address', + indexed: true, + }, + { + name: '_value', + type: 'uint256', + indexed: false, + }, + ], + anonymous: false, + type: 'event', + }, + { + stateMutability: 'nonpayable', + type: 'constructor', + inputs: [ + { + name: 'minter', + type: 'address', + }, + { + name: 'veBoostProxy', + type: 'address', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'deposit', + inputs: [ + { + name: '_value', + type: 'uint256', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'deposit', + inputs: [ + { + name: '_value', + type: 'uint256', + }, + { + name: '_addr', + type: 'address', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'deposit', + inputs: [ + { + name: '_value', + type: 'uint256', + }, + { + name: '_addr', + type: 'address', + }, + { + name: '_claim_rewards', + type: 'bool', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'withdraw', + inputs: [ + { + name: '_value', + type: 'uint256', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'withdraw', + inputs: [ + { + name: '_value', + type: 'uint256', + }, + { + name: '_claim_rewards', + type: 'bool', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'claim_rewards', + inputs: [], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'claim_rewards', + inputs: [ + { + name: '_addr', + type: 'address', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'claim_rewards', + inputs: [ + { + name: '_addr', + type: 'address', + }, + { + name: '_receiver', + type: 'address', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'transferFrom', + inputs: [ + { + name: '_from', + type: 'address', + }, + { + name: '_to', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + }, + ], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'transfer', + inputs: [ + { + name: '_to', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + }, + ], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'approve', + inputs: [ + { + name: '_spender', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + }, + ], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'permit', + inputs: [ + { + name: '_owner', + type: 'address', + }, + { + name: '_spender', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, + { + name: '_deadline', + type: 'uint256', + }, + { + name: '_v', + type: 'uint8', + }, + { + name: '_r', + type: 'bytes32', + }, + { + name: '_s', + type: 'bytes32', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + }, + ], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'increaseAllowance', + inputs: [ + { + name: '_spender', + type: 'address', + }, + { + name: '_added_value', + type: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + }, + ], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'decreaseAllowance', + inputs: [ + { + name: '_spender', + type: 'address', + }, + { + name: '_subtracted_value', + type: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + }, + ], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'user_checkpoint', + inputs: [ + { + name: 'addr', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + }, + ], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'set_rewards_receiver', + inputs: [ + { + name: '_receiver', + type: 'address', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'kick', + inputs: [ + { + name: 'addr', + type: 'address', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'deposit_reward_token', + inputs: [ + { + name: '_reward_token', + type: 'address', + }, + { + name: '_amount', + type: 'uint256', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'add_reward', + inputs: [ + { + name: '_reward_token', + type: 'address', + }, + { + name: '_distributor', + type: 'address', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'set_reward_distributor', + inputs: [ + { + name: '_reward_token', + type: 'address', + }, + { + name: '_distributor', + type: 'address', + }, + ], + outputs: [], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'set_killed', + inputs: [ + { + name: '_is_killed', + type: 'bool', + }, + ], + outputs: [], + }, + { + stateMutability: 'view', + type: 'function', + name: 'claimed_reward', + inputs: [ + { + name: '_addr', + type: 'address', + }, + { + name: '_token', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'claimable_reward', + inputs: [ + { + name: '_user', + type: 'address', + }, + { + name: '_reward_token', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'claimable_reward_write', + inputs: [ + { + name: '_addr', + type: 'address', + }, + { + name: '_token', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'claimable_tokens', + inputs: [ + { + name: 'addr', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'integrate_checkpoint', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'future_epoch_time', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'inflation_rate', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'decimals', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'version', + inputs: [], + outputs: [ + { + name: '', + type: 'string', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'allowance', + inputs: [ + { + name: 'owner', + type: 'address', + }, + { + name: 'spender', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'nonpayable', + type: 'function', + name: 'initialize', + inputs: [ + { + name: '_lp_token', + type: 'address', + }, + ], + outputs: [], + }, + { + stateMutability: 'view', + type: 'function', + name: 'balanceOf', + inputs: [ + { + name: 'arg0', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'totalSupply', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'name', + inputs: [], + outputs: [ + { + name: '', + type: 'string', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'symbol', + inputs: [], + outputs: [ + { + name: '', + type: 'string', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'DOMAIN_SEPARATOR', + inputs: [], + outputs: [ + { + name: '', + type: 'bytes32', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'nonces', + inputs: [ + { + name: 'arg0', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'factory', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'lp_token', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'is_killed', + inputs: [], + outputs: [ + { + name: '', + type: 'bool', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'reward_count', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'reward_data', + inputs: [ + { + name: 'arg0', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + components: [ + { + name: 'token', + type: 'address', + }, + { + name: 'distributor', + type: 'address', + }, + { + name: 'period_finish', + type: 'uint256', + }, + { + name: 'rate', + type: 'uint256', + }, + { + name: 'last_update', + type: 'uint256', + }, + { + name: 'integral', + type: 'uint256', + }, + ], + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'rewards_receiver', + inputs: [ + { + name: 'arg0', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'address', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'reward_integral_for', + inputs: [ + { + name: 'arg0', + type: 'address', + }, + { + name: 'arg1', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'working_balances', + inputs: [ + { + name: 'arg0', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'working_supply', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'integrate_inv_supply_of', + inputs: [ + { + name: 'arg0', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'integrate_checkpoint_of', + inputs: [ + { + name: 'arg0', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'integrate_fraction', + inputs: [ + { + name: 'arg0', + type: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'period', + inputs: [], + outputs: [ + { + name: '', + type: 'int128', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'reward_tokens', + inputs: [ + { + name: 'arg0', + type: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'address', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'period_timestamp', + inputs: [ + { + name: 'arg0', + type: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, + { + stateMutability: 'view', + type: 'function', + name: 'integrate_inv_supply', + inputs: [ + { + name: 'arg0', + type: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + }, +] diff --git a/packages/lib/shared/components/icons/VoteCapIcon.tsx b/packages/lib/shared/components/icons/VoteCapIcon.tsx index 03e19ac79..fcb7e11c4 100644 --- a/packages/lib/shared/components/icons/VoteCapIcon.tsx +++ b/packages/lib/shared/components/icons/VoteCapIcon.tsx @@ -10,34 +10,34 @@ export function VoteCapIcon(props: SVGProps) { xmlns="http://www.w3.org/2000/svg" {...props} > - + diff --git a/packages/lib/shared/components/tables/SortableHeader.tsx b/packages/lib/shared/components/tables/SortableHeader.tsx index b7519f4e4..bcbc3cbb3 100644 --- a/packages/lib/shared/components/tables/SortableHeader.tsx +++ b/packages/lib/shared/components/tables/SortableHeader.tsx @@ -3,10 +3,15 @@ import { SortableIcon } from '../icons/SortableIcon' import { ArrowDownIcon } from '../icons/ArrowDownIcon' import { ArrowUpIcon } from '../icons/ArrowUpIcon' +export enum Sorting { + asc = 'asc', + desc = 'desc', +} + type SortableHeaderProps = { label: string isSorted: boolean - sorting: 'asc' | 'desc' + sorting: Sorting onSort: (newSortingBy: any) => void align?: 'left' | 'right' } From 9e77314276c5d240994b2885c87127522940ba19 Mon Sep 17 00:00:00 2001 From: Anastasios Date: Fri, 15 Nov 2024 16:34:41 +0400 Subject: [PATCH 2/6] fix: lint --- .../lib/modules/pool/PoolList/PoolListFilters.tsx | 10 +++++----- .../lib/modules/pool/PoolList/PoolListLayout.tsx | 12 ++++++------ .../lib/modules/vebal/vote/VoteExpiredTooltip.tsx | 2 +- .../vebal/vote/VoteList/VoteListFilters.tsx | 8 ++++---- .../modules/vebal/vote/VoteList/VoteListLayout.tsx | 8 ++++---- .../lib/shared/components/icons/VoteCapIcon.tsx | 14 +++++++------- 6 files changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/lib/modules/pool/PoolList/PoolListFilters.tsx b/packages/lib/modules/pool/PoolList/PoolListFilters.tsx index 0355acd3b..16fd0b81c 100644 --- a/packages/lib/modules/pool/PoolList/PoolListFilters.tsx +++ b/packages/lib/modules/pool/PoolList/PoolListFilters.tsx @@ -406,7 +406,7 @@ export function FilterTags({ - {`Expired`} + Expired - + @@ -597,9 +597,9 @@ export function PoolListFilters() { Networks {!isCowPath && ( @@ -616,10 +616,10 @@ export function PoolListFilters() { Pool types )} diff --git a/packages/lib/modules/pool/PoolList/PoolListLayout.tsx b/packages/lib/modules/pool/PoolList/PoolListLayout.tsx index 6536a717a..848ab40f0 100644 --- a/packages/lib/modules/pool/PoolList/PoolListLayout.tsx +++ b/packages/lib/modules/pool/PoolList/PoolListLayout.tsx @@ -73,16 +73,16 @@ export function PoolListLayout() { diff --git a/packages/lib/modules/vebal/vote/VoteExpiredTooltip.tsx b/packages/lib/modules/vebal/vote/VoteExpiredTooltip.tsx index 1fc1f5b26..08c21e4ad 100644 --- a/packages/lib/modules/vebal/vote/VoteExpiredTooltip.tsx +++ b/packages/lib/modules/vebal/vote/VoteExpiredTooltip.tsx @@ -16,7 +16,7 @@ export function VoteExpiredTooltip({ usePortal }: Props) { const popoverContent = ( - + Expired pool gauge diff --git a/packages/lib/modules/vebal/vote/VoteList/VoteListFilters.tsx b/packages/lib/modules/vebal/vote/VoteList/VoteListFilters.tsx index 9e2659e00..23ad50ccb 100644 --- a/packages/lib/modules/vebal/vote/VoteList/VoteListFilters.tsx +++ b/packages/lib/modules/vebal/vote/VoteList/VoteListFilters.tsx @@ -72,7 +72,7 @@ export function VoteListFilters() { placement="bottom-end" > - + @@ -114,9 +114,9 @@ export function VoteListFilters() { Networks @@ -124,10 +124,10 @@ export function VoteListFilters() { Pool types diff --git a/packages/lib/modules/vebal/vote/VoteList/VoteListLayout.tsx b/packages/lib/modules/vebal/vote/VoteList/VoteListLayout.tsx index c459e546b..e113aa3e7 100644 --- a/packages/lib/modules/vebal/vote/VoteList/VoteListLayout.tsx +++ b/packages/lib/modules/vebal/vote/VoteList/VoteListLayout.tsx @@ -74,13 +74,13 @@ export function VoteListLayout() { diff --git a/packages/lib/shared/components/icons/VoteCapIcon.tsx b/packages/lib/shared/components/icons/VoteCapIcon.tsx index fcb7e11c4..49fa22880 100644 --- a/packages/lib/shared/components/icons/VoteCapIcon.tsx +++ b/packages/lib/shared/components/icons/VoteCapIcon.tsx @@ -3,10 +3,10 @@ import { SVGProps } from 'react' export function VoteCapIcon(props: SVGProps) { return ( @@ -14,35 +14,35 @@ export function VoteCapIcon(props: SVGProps) { - + From 7425d3633763a3321e9f383b2c2f524be6bf8262 Mon Sep 17 00:00:00 2001 From: Anastasios Date: Wed, 25 Dec 2024 17:20:33 +0300 Subject: [PATCH 3/6] feat: vote list - hoverable headers --- .../VoteListTable/VoteListTableHeader.tsx | 13 +++- packages/lib/modules/vebal/vote/vote.types.ts | 18 ++++-- .../components/tables/SortableHeader.tsx | 59 ++++++++++++++----- 3 files changed, 69 insertions(+), 21 deletions(-) diff --git a/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTableHeader.tsx b/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTableHeader.tsx index 3dcea8bb8..60dcbdf8e 100644 --- a/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTableHeader.tsx +++ b/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTableHeader.tsx @@ -1,6 +1,6 @@ 'use client' -import { Grid, GridItem, Icon, Text, VStack } from '@chakra-ui/react' +import { Grid, GridItem, Icon, PopoverContent, Text, VStack } from '@chakra-ui/react' import { Globe } from 'react-feather' import { SortableHeader, Sorting } from '@repo/lib/shared/components/tables/SortableHeader' import { useVoteList } from '@repo/lib/modules/vebal/vote/VoteList/VoteListProvider' @@ -36,8 +36,17 @@ export function VoteListTableHeader({ ...rest }) { handleSort(orderByItem)} + popoverContent={ + orderByHash[orderByItem].title ? ( + + + {orderByHash[orderByItem].title} + + + ) : undefined + } sorting={sorting} /> diff --git a/packages/lib/modules/vebal/vote/vote.types.ts b/packages/lib/modules/vebal/vote/vote.types.ts index c186d8f0c..85ca49ccb 100644 --- a/packages/lib/modules/vebal/vote/vote.types.ts +++ b/packages/lib/modules/vebal/vote/vote.types.ts @@ -35,9 +35,17 @@ export enum SortingBy { votes = 'votes', } -export const orderByHash: Record = { - type: 'Type', - bribes: 'Bribes', - bribesPerVebal: 'Bribes/veBAL', - votes: 'veBAL votes', +export const orderByHash: Record = { + type: { label: 'Type' }, + bribes: { + label: 'Bribes', + title: + 'Voting incentives (referred to as ‘Bribes’ in DeFi) are provided by unaffiliated 3rd parties through the Hidden Hand platform to incentivize liquidity to certain pools.', + }, + bribesPerVebal: { + label: 'Bribes/veBAL', + title: + 'This shows the ratio of 3rd party voting incentives (known as Bribes) to veBAL. The higher this ratio, the more profitable it is to currently vote on this pool. Note this ratio could change up till the voting deadline.', + }, + votes: { label: 'veBAL votes' }, } diff --git a/packages/lib/shared/components/tables/SortableHeader.tsx b/packages/lib/shared/components/tables/SortableHeader.tsx index bcbc3cbb3..e3d36ab99 100644 --- a/packages/lib/shared/components/tables/SortableHeader.tsx +++ b/packages/lib/shared/components/tables/SortableHeader.tsx @@ -1,7 +1,17 @@ -import { Box, Button, GridItem, HStack, Text } from '@chakra-ui/react' +import { + Box, + Button, + GridItem, + HStack, + Popover, + PopoverTrigger, + Portal, + Text, +} from '@chakra-ui/react' import { SortableIcon } from '../icons/SortableIcon' import { ArrowDownIcon } from '../icons/ArrowDownIcon' import { ArrowUpIcon } from '../icons/ArrowUpIcon' +import { ReactNode } from 'react' export enum Sorting { asc = 'asc', @@ -14,6 +24,8 @@ type SortableHeaderProps = { sorting: Sorting onSort: (newSortingBy: any) => void align?: 'left' | 'right' + popoverContent?: ReactNode + usePortal?: string } export function SortableHeader({ @@ -22,25 +34,44 @@ export function SortableHeader({ onSort, sorting, align = 'left', + popoverContent, + usePortal, }: SortableHeaderProps) { const renderSortIcon = () => { return !isSorted ? : sorting === 'asc' ? : } const color = isSorted ? 'font.highlight' : 'font.primary' + const justifySelf = align === 'left' ? 'start' : 'end' - return ( - - - + const HeaderContent = ( + ) + + if (popoverContent) { + return ( + + + {() => ( + <> + {HeaderContent} + + {usePortal ? {popoverContent} : popoverContent} + + )} + + + ) + } + + return {HeaderContent} } From 5b21d679a1fee31a15f624e0373928a08e1703e0 Mon Sep 17 00:00:00 2001 From: Anastasios Date: Wed, 25 Dec 2024 17:35:49 +0300 Subject: [PATCH 4/6] feat: vote list - disable Select for expired rows --- .../VoteListTable/VoteListTableRow.tsx | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTableRow.tsx b/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTableRow.tsx index d3b1445e0..e4fbd4b2f 100644 --- a/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTableRow.tsx +++ b/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTableRow.tsx @@ -44,6 +44,8 @@ export function VoteListTableRow({ vote, keyValue, ...rest }: Props) { [vote] ) + const isKilled = vote.gaugeVotes?.isKilled + return ( - {vote.gaugeVotes?.isKilled && } + {isKilled && } @@ -116,16 +118,28 @@ export function VoteListTableRow({ vote, keyValue, ...rest }: Props) { )} - + {isKilled ? ( + + ) : ( + + )} From 79d7d6d5e57dd1d5902fb4b1edc25798ed8a29ec Mon Sep 17 00:00:00 2001 From: Anastasios Date: Wed, 25 Dec 2024 18:04:06 +0300 Subject: [PATCH 5/6] feat: vote list - ui for protocolVersion filtering --- .../modules/pool/PoolList/PoolListFilters.tsx | 44 +++++++++++++------ .../vebal/vote/VoteList/VoteListFilters.tsx | 17 +++++++ .../vebal/vote/VoteList/VoteListProvider.tsx | 11 ++++- .../vote/VoteList/useVoteListFiltersState.tsx | 8 ++++ 4 files changed, 64 insertions(+), 16 deletions(-) diff --git a/packages/lib/modules/pool/PoolList/PoolListFilters.tsx b/packages/lib/modules/pool/PoolList/PoolListFilters.tsx index bd1044dc0..5718f8c05 100644 --- a/packages/lib/modules/pool/PoolList/PoolListFilters.tsx +++ b/packages/lib/modules/pool/PoolList/PoolListFilters.tsx @@ -458,18 +458,23 @@ export const FilterButton = forwardRef> + protocolVersion: number | null + poolTypes: PoolFilterType[] + activeProtocolVersionTab: ButtonGroupOption + setActiveProtocolVersionTab: React.Dispatch> + hideProtocolVersion?: string[] +} +export function ProtocolVersionFilter({ + setProtocolVersion, + protocolVersion, + poolTypes, + activeProtocolVersionTab, + setActiveProtocolVersionTab, + hideProtocolVersion, +}: ProtocolVersionFilterProps) { const tabs = PROTOCOL_VERSION_TABS function toggleTab(option: ButtonGroupOption) { @@ -505,7 +510,7 @@ function ProtocolVersionFilter() { currentOption={activeProtocolVersionTab} groupId="protocol-version" onChange={toggleTab} - options={tabs.filter(tab => !hideProtocolVersion.includes(tab.value))} + options={tabs.filter(tab => !(hideProtocolVersion ?? []).includes(tab.value))} size="xxs" /> ) @@ -527,8 +532,12 @@ export function PoolListFilters() { poolTypes, poolTypeLabel, setPoolTypes, + setProtocolVersion, + protocolVersion, + activeProtocolVersionTab, }, hidePoolTypes, + hideProtocolVersion, } = usePoolList() const { isCowPath } = useCow() const { isMobile } = useBreakpoints() @@ -614,7 +623,14 @@ export function PoolListFilters() { Protocol version - + )} {!isFixedPoolType && ( @@ -623,11 +639,11 @@ export function PoolListFilters() { Pool types )} diff --git a/packages/lib/modules/vebal/vote/VoteList/VoteListFilters.tsx b/packages/lib/modules/vebal/vote/VoteList/VoteListFilters.tsx index 73ab49bc9..5f330695b 100644 --- a/packages/lib/modules/vebal/vote/VoteList/VoteListFilters.tsx +++ b/packages/lib/modules/vebal/vote/VoteList/VoteListFilters.tsx @@ -22,6 +22,7 @@ import { FilterButton, PoolNetworkFilters, PoolTypeFilters, + ProtocolVersionFilter, } from '@repo/lib/modules/pool/PoolList/PoolListFilters' import { useVoteList } from '@repo/lib/modules/vebal/vote/VoteList/VoteListProvider' import { VoteListSearch } from '@repo/lib/modules/vebal/vote/VoteList/VoteListSearch' @@ -53,6 +54,10 @@ export function VoteListFilters() { setPoolTypes, includeExpiredPools, toggleIncludeExpiredPools, + activeProtocolVersionTab, + setActiveProtocolVersionTab, + protocolVersion, + setProtocolVersion, }, } = useVoteList() @@ -118,6 +123,18 @@ export function VoteListFilters() { toggledNetworks={toggledNetworks} /> + + + Protocol version + + + Pool types diff --git a/packages/lib/modules/vebal/vote/VoteList/VoteListProvider.tsx b/packages/lib/modules/vebal/vote/VoteList/VoteListProvider.tsx index 425e85734..113d39d0b 100644 --- a/packages/lib/modules/vebal/vote/VoteList/VoteListProvider.tsx +++ b/packages/lib/modules/vebal/vote/VoteList/VoteListProvider.tsx @@ -37,7 +37,8 @@ function filterVoteList( textSearch: string, networks: GqlChain[], poolTypes: PoolFilterType[], - includeExpiredPools: boolean + includeExpiredPools: boolean, + protocolVersion: number | null ) { let result = voteList @@ -68,6 +69,10 @@ function filterVoteList( result = result.filter(value => !value.gaugeVotes?.isKilled) } + if (protocolVersion !== null) { + console.warn('todo: implement protocolVersion filtering when api is ready') + } + return result } @@ -112,7 +117,8 @@ export function _useVoteList({ filtersState.searchText, filtersState.networks, filtersState.poolTypes, - filtersState.includeExpiredPools + filtersState.includeExpiredPools, + filtersState.protocolVersion ) }, [ votingPoolsList, @@ -120,6 +126,7 @@ export function _useVoteList({ filtersState.networks, filtersState.poolTypes, filtersState.includeExpiredPools, + filtersState.protocolVersion, ]) const sortedVoteList = useMemo(() => { diff --git a/packages/lib/modules/vebal/vote/VoteList/useVoteListFiltersState.tsx b/packages/lib/modules/vebal/vote/VoteList/useVoteListFiltersState.tsx index 294be2bc4..79e100f52 100644 --- a/packages/lib/modules/vebal/vote/VoteList/useVoteListFiltersState.tsx +++ b/packages/lib/modules/vebal/vote/VoteList/useVoteListFiltersState.tsx @@ -5,6 +5,7 @@ import { Sorting } from '@repo/lib/shared/components/tables/SortableHeader' import { GqlChain } from '@repo/lib/shared/services/api/generated/graphql' import { uniq } from 'lodash' import { PoolFilterType } from '@repo/lib/modules/pool/pool.types' +import { PROTOCOL_VERSION_TABS } from '@repo/lib/modules/pool/PoolList/usePoolListQueryState' const EMPTY_ARRAY: never[] = [] @@ -35,6 +36,8 @@ export function useVoteListFiltersState() { const [networks, setNetworks] = useState(null) const [poolTypes, setPoolTypes] = useState(null) const [includeExpiredPools, setIncludeExpiredPools] = useState(false) + const [activeProtocolVersionTab, setActiveProtocolVersionTab] = useState(PROTOCOL_VERSION_TABS[0]) + const [protocolVersion, setProtocolVersion] = useState(null) const [textSearch, setTextSearch] = useState('') @@ -42,6 +45,7 @@ export function useVoteListFiltersState() { setNetworks(null) setPoolTypes(null) setIncludeExpiredPools(false) + setProtocolVersion(null) setPagination({ pageSize: 20, pageIndex: 0 }) setSorting(Sorting.desc) @@ -101,5 +105,9 @@ export function useVoteListFiltersState() { toggleIncludeExpiredPools, setNetworks, setPoolTypes, + activeProtocolVersionTab, + setActiveProtocolVersionTab, + protocolVersion, + setProtocolVersion, } } From 6441bb9ba1bb3b14305bcbee7e1af5b7fa03f98a Mon Sep 17 00:00:00 2001 From: Anastasios Date: Fri, 27 Dec 2024 01:07:35 +0300 Subject: [PATCH 6/6] fix(mobile): table list - fix rows overlapping --- .../lib/modules/pool/PoolList/PoolListTable/PoolListTable.tsx | 1 + .../lib/modules/portfolio/PortfolioTable/PortfolioTable.tsx | 2 ++ .../modules/vebal/vote/VoteList/VoteListTable/VoteListTable.tsx | 1 + 3 files changed, 4 insertions(+) diff --git a/packages/lib/modules/pool/PoolList/PoolListTable/PoolListTable.tsx b/packages/lib/modules/pool/PoolList/PoolListTable/PoolListTable.tsx index e3984655b..60c6fd309 100644 --- a/packages/lib/modules/pool/PoolList/PoolListTable/PoolListTable.tsx +++ b/packages/lib/modules/pool/PoolList/PoolListTable/PoolListTable.tsx @@ -43,6 +43,7 @@ export function PoolListTable({ pools, count, loading }: Props) { left={{ base: '-4px', sm: '0' }} p={{ base: '0', sm: '0' }} position="relative" + pr={{ base: 'md' }} w={{ base: '100vw', lg: 'full' }} > ) }} + pr={{ base: 'md' }} showPagination={false} w={{ base: '100vw', lg: 'full' }} /> diff --git a/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTable.tsx b/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTable.tsx index 87a90eac6..de0eaf9c9 100644 --- a/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTable.tsx +++ b/packages/lib/modules/vebal/vote/VoteList/VoteListTable/VoteListTable.tsx @@ -38,6 +38,7 @@ export function VoteListTable({ voteList, count, loading }: Props) { left={{ base: '-4px', sm: '0' }} p={{ base: '0', sm: '0' }} position="relative" + pr={{ base: 'md' }} w={{ base: '100vw', lg: 'full' }} >