From 07e111486ca0fdab107fb691fd5e620482fedf41 Mon Sep 17 00:00:00 2001 From: Juanjo Molinero Date: Fri, 10 Apr 2026 15:11:30 +0200 Subject: [PATCH 1/5] [#2316] remove everything related to veBAL voting --- .../app/(app)/vebal/ImageBorderCard.tsx | 73 ---- .../vebal/components/VebalBenefitsSection.tsx | 175 -------- .../(app)/vebal/components/VebalEcosystem.tsx | 261 ------------ .../vebal/components/VebalFooterSection.tsx | 101 ----- .../vebal/components/VebalHeroSection.tsx | 167 -------- .../vebal/components/VebalHowSection.tsx | 109 ----- .../(app)/vebal/components/VebalStepCard.tsx | 63 --- apps/frontend-v3/app/(app)/vebal/layout.tsx | 6 - .../app/(app)/vebal/manage/extend/page.tsx | 15 - .../app/(app)/vebal/manage/layout.tsx | 3 - .../app/(app)/vebal/manage/lock/page.tsx | 15 - .../app/(app)/vebal/manage/page.tsx | 2 - apps/frontend-v3/app/(app)/vebal/page.tsx | 26 -- .../app/(app)/vebal/vote/layout.tsx | 14 - .../frontend-v3/app/(app)/vebal/vote/page.tsx | 10 - .../lib/vebal/VeBalLockButtons.tsx | 9 +- .../useVeBalUserStats.integration.spec.tsx | 31 -- .../lib/vebal/cross-chain/CrossChainBoost.tsx | 143 ------- .../vebal/cross-chain/CrossChainSyncModal.tsx | 188 --------- .../CrossChainSyncModalContent.tsx | 19 - .../cross-chain/CrossChainSyncProvider.tsx | 265 ------------ .../vebal/cross-chain/CrossChainSyncRow.tsx | 38 -- .../cross-chain/useCrossChainNetworks.ts | 130 ------ .../cross-chain/useCrossChainSyncSteps.tsx | 149 ------- .../cross-chain/useEstimateSendUserBalance.ts | 28 -- .../vebal/cross-chain/useLayerZeroTxLinks.ts | 72 ---- .../cross-chain/useOmniEscrowLocksQuery.ts | 37 -- .../useVotingEscrowLocksQueries.ts | 40 -- .../lib/vebal/lock/VebalExtend.tsx | 20 - apps/frontend-v3/lib/vebal/lock/VebalLock.tsx | 20 - .../vebal/lock/VebalLockDetailsAccordion.tsx | 47 --- .../lib/vebal/lock/VebalUnlock.tsx | 7 - .../lock/duration/LockDurationSlider.tsx | 60 --- .../lib/vebal/lock/form/VebalLockForm.tsx | 303 -------------- .../lib/vebal/lock/form/VebalUnlockForm.tsx | 8 +- .../vebal/lock/form/WeeklyYieldTooltip.tsx | 100 ----- .../lib/vebal/lock/modal/VebalLockModal.tsx | 26 +- .../lib/vebal/lock/steps/lock-steps.utils.ts | 70 +--- .../vebal/lock/steps/useBuildLockSteps.tsx | 37 +- .../steps/useLockStep.integration.spec.tsx | 74 ---- .../lib/vebal/lock/steps/useLockStep.tsx | 18 +- .../lib/vebal/lock/steps/useLockSteps.tsx | 49 +-- .../lib/vebal/useGaugeWorkingBalanceQuery.ts | 24 -- .../useMaxAmountOfVeBal.integration.spec.tsx | 35 -- .../lib/vebal/useMaxAmountOfVeBal.ts | 59 --- .../useVeBALBalance.integration.spec.ts | 0 .../lib/vebal/{vote => }/useVeBALBalance.ts | 16 - .../frontend-v3/lib/vebal/useVebalUserData.ts | 2 +- .../frontend-v3/lib/vebal/vebal-navigation.ts | 24 +- .../lib/vebal/vote/VoteCapTooltip.tsx | 57 --- .../lib/vebal/vote/VoteExpiredTooltip.tsx | 53 --- .../vebal/vote/VoteList/SelectedPoolsMenu.tsx | 124 ------ .../lib/vebal/vote/VoteList/VoteList.tsx | 10 - .../vebal/vote/VoteList/VoteListFilters.tsx | 186 --------- .../vebal/vote/VoteList/VoteListLayout.tsx | 125 ------ .../vebal/vote/VoteList/VoteListProvider.tsx | 151 ------- .../vebal/vote/VoteList/VoteListSearch.tsx | 27 -- .../VoteList/VoteListTable/VoteListTable.tsx | 73 ---- .../VoteListTable/VoteListTableHeader.tsx | 81 ---- .../VoteListTable/VoteListTableRow.tsx | 185 --------- .../VoteListTable/VoteListVotesCell.tsx | 36 -- .../vote/VoteList/useVoteListFiltersState.tsx | 116 ------ .../lib/vebal/vote/VoteRateTooltip.tsx | 184 --------- .../Votes/MyVotes/CalendarReminderModal.tsx | 94 ----- .../lib/vebal/vote/Votes/MyVotes/MyVotes.tsx | 206 ---------- .../vote/Votes/MyVotes/MyVotesHintModal.tsx | 93 ----- .../vote/Votes/MyVotes/MyVotesLayout.tsx | 5 - .../vote/Votes/MyVotes/MyVotesProvider.tsx | 379 ------------------ .../MyVotesStatsAverageReward.tsx | 63 --- .../MyVotesStats/MyVotesStatsMyIncentives.tsx | 60 --- .../MyVotesStats/MyVotesStatsMyVebal.tsx | 95 ----- .../IncentivesOptimizedTooltip.tsx | 107 ----- .../MyVotesStatsMyIncentivesOptimized.tsx | 208 ---------- .../incentives-optimization/TooltipItem.tsx | 104 ----- .../incentives-optimization/heap.spec.ts | 111 ----- .../incentives-optimization/heap.ts | 99 ----- .../useIncentivesOptimized.spec.ts | 337 ---------------- .../useIncentivesOptimized.ts | 336 ---------------- .../MyVotes/MyVotesStats/shared/GainBadge.tsx | 35 -- .../shared/MyIncentivesTooltip.tsx | 36 -- .../shared/MyVebalChargeTooltip.tsx | 118 ------ .../MyVotesStats/shared/MyVotesStatsCard.tsx | 75 ---- .../useVeBALIncentives.integration.spec.ts | 14 - .../MyVotesStats/useVeBALIncentives.ts | 25 -- .../MyVotes/MyVotesStats/useVeBALPool.ts | 18 - .../MyVotes/MyVotesTable/MyVotesTable.tsx | 127 ------ .../MyVotesTable/MyVotesTableHeader.tsx | 90 ----- .../MyVotes/MyVotesTable/MyVotesTableRow.tsx | 222 ---------- .../MyVotesTable/MyVotesTableSubmitRow.tsx | 128 ------ .../MyVotesTable/MyVotesTableTotalRow.tsx | 119 ------ .../MyVotes/MyVotesTable/VoteWeightInput.tsx | 120 ------ .../Votes/MyVotes/VoteExceededTooltip.tsx | 47 --- .../Votes/MyVotes/VoteTimeLockedTooltip.tsx | 48 --- .../Votes/MyVotes/VoteUnallocatedTooltip.tsx | 46 --- .../Votes/MyVotes/VoteUnderpoweredTooltip.tsx | 48 --- .../vebal/vote/Votes/MyVotes/VoteWeight.tsx | 104 ----- .../submit/modal/PoolSelectionModal.tsx | 96 ----- .../actions/submit/modal/SubmitVotesModal.tsx | 86 ---- .../submit/modal/SubmitVotesPreview.tsx | 246 ------------ .../actions/submit/useSubmitVotesAllSteps.tsx | 16 - .../actions/submit/useSubmitVotesSteps.tsx | 123 ------ .../actions/submit/useSubmittingVotes.tsx | 66 --- .../vote/Votes/MyVotes/incentivesBlacklist.ts | 94 ----- .../vote/Votes/MyVotes/myVotes.helpers.ts | 138 ------- .../vebal/vote/Votes/MyVotes/myVotes.types.ts | 17 - .../Votes/MyVotes/useMyVotesFiltersState.tsx | 20 - .../lib/vebal/vote/Votes/VotesContainer.tsx | 49 --- .../vote/Votes/VotesIntroduction/HomeIcon.tsx | 26 -- .../VotesIntroduction/VotesIntroduction.tsx | 140 ------- .../VotesIntroductionLayout.tsx | 5 - .../VotingDeadline/DeadlineDayTooltip.tsx | 91 ----- .../VotingDeadline/ReminderButton.tsx | 24 -- .../VotingDeadline/StaticCalendar.tsx | 200 --------- .../VotingDeadline/VotingDeadline.tsx | 47 --- .../VotingDeadline/VotingDeadlineCalendar.tsx | 16 - .../VotingDeadlineContainer.tsx | 45 --- .../VotingDeadline/VotingDeadlineCounter.tsx | 101 ----- .../VotingDeadline/deadline.spec.ts | 28 -- .../VotingDeadline/deadline.ts | 5 - .../lib/vebal/vote/Votes/VotesProvider.tsx | 185 --------- .../lib/vebal/vote/useExpiredGaugesQuery.tsx | 55 --- .../lib/vebal/vote/useTotalVotes.ts | 20 - .../vote/useVeBALTotal.integration.spec.ts | 17 - .../lib/vebal/vote/useVeBALTotal.ts | 22 - packages/lib/config/config.types.ts | 1 - packages/lib/config/projects/balancer.ts | 3 +- packages/lib/config/projects/beets.ts | 1 - .../PoolSnapshot/PoolSnapshotValues.tsx | 2 +- .../PoolSnapshot/UserSnapshotValues.tsx | 17 +- .../PoolDetail/PoolUserEvents/BoostText.tsx | 34 -- .../PoolUserEvents/PoolUserEvents.tsx | 22 +- .../pool/PoolDetail/StakeUnstakeButton.tsx | 5 +- .../pool/PoolList/PoolListTokenPills.tsx | 25 -- .../add-liquidity/modal/StakingOptions.tsx | 62 ++- packages/lib/modules/pool/pool.utils.ts | 26 +- .../PortfolioTable/PortfolioTable.tsx | 15 +- .../PortfolioTable/PortfolioTableHeader.tsx | 3 +- .../PortfolioTable/PortfolioTableRow.tsx | 19 +- .../PortfolioTable/usePortfolioSorting.tsx | 32 +- .../modules/vebal/VebalLockDataProvider.tsx | 10 +- .../useGaugeTotalSupplyAndUserBalance.ts | 62 --- .../vebal/useGaugesSupplyAndBalance.ts | 88 ---- packages/lib/modules/vebal/useVebalBoost.tsx | 130 ------ .../lib/modules/vebal/vote/useGaugeVotes.tsx | 200 --------- .../lib/modules/vebal/vote/vote.helpers.ts | 80 ---- packages/lib/modules/vebal/vote/vote.types.ts | 35 -- .../components/modals/ActionModalFooter.tsx | 7 +- .../modals/VebalPartnerRedirectModal.tsx | 127 ------ .../tooltips/apr-tooltip/BaseAprTooltip.tsx | 70 +--- .../tooltips/apr-tooltip/MainAprTooltip.tsx | 8 +- .../tooltips/apr-tooltip/StakeAprTooltip.tsx | 5 - .../hooks/_mocks_/aprTooltipDataMock.ts | 10 - .../lib/shared/hooks/useAprTooltip.spec.tsx | 72 +--- packages/lib/shared/hooks/useAprTooltip.ts | 38 +- packages/lib/test/data/all-gql-tokens.fake.ts | 16 - 155 files changed, 103 insertions(+), 11413 deletions(-) delete mode 100644 apps/frontend-v3/app/(app)/vebal/ImageBorderCard.tsx delete mode 100644 apps/frontend-v3/app/(app)/vebal/components/VebalBenefitsSection.tsx delete mode 100644 apps/frontend-v3/app/(app)/vebal/components/VebalEcosystem.tsx delete mode 100644 apps/frontend-v3/app/(app)/vebal/components/VebalFooterSection.tsx delete mode 100644 apps/frontend-v3/app/(app)/vebal/components/VebalHeroSection.tsx delete mode 100644 apps/frontend-v3/app/(app)/vebal/components/VebalHowSection.tsx delete mode 100644 apps/frontend-v3/app/(app)/vebal/components/VebalStepCard.tsx delete mode 100644 apps/frontend-v3/app/(app)/vebal/layout.tsx delete mode 100644 apps/frontend-v3/app/(app)/vebal/manage/extend/page.tsx delete mode 100644 apps/frontend-v3/app/(app)/vebal/manage/lock/page.tsx delete mode 100644 apps/frontend-v3/app/(app)/vebal/page.tsx delete mode 100644 apps/frontend-v3/app/(app)/vebal/vote/layout.tsx delete mode 100644 apps/frontend-v3/app/(app)/vebal/vote/page.tsx delete mode 100644 apps/frontend-v3/lib/vebal/VebalStats/useVeBalUserStats.integration.spec.tsx delete mode 100644 apps/frontend-v3/lib/vebal/cross-chain/CrossChainBoost.tsx delete mode 100644 apps/frontend-v3/lib/vebal/cross-chain/CrossChainSyncModal.tsx delete mode 100644 apps/frontend-v3/lib/vebal/cross-chain/CrossChainSyncModalContent.tsx delete mode 100644 apps/frontend-v3/lib/vebal/cross-chain/CrossChainSyncProvider.tsx delete mode 100644 apps/frontend-v3/lib/vebal/cross-chain/CrossChainSyncRow.tsx delete mode 100644 apps/frontend-v3/lib/vebal/cross-chain/useCrossChainNetworks.ts delete mode 100644 apps/frontend-v3/lib/vebal/cross-chain/useCrossChainSyncSteps.tsx delete mode 100644 apps/frontend-v3/lib/vebal/cross-chain/useEstimateSendUserBalance.ts delete mode 100644 apps/frontend-v3/lib/vebal/cross-chain/useLayerZeroTxLinks.ts delete mode 100644 apps/frontend-v3/lib/vebal/cross-chain/useOmniEscrowLocksQuery.ts delete mode 100644 apps/frontend-v3/lib/vebal/cross-chain/useVotingEscrowLocksQueries.ts delete mode 100644 apps/frontend-v3/lib/vebal/lock/VebalExtend.tsx delete mode 100644 apps/frontend-v3/lib/vebal/lock/VebalLock.tsx delete mode 100644 apps/frontend-v3/lib/vebal/lock/VebalLockDetailsAccordion.tsx delete mode 100644 apps/frontend-v3/lib/vebal/lock/duration/LockDurationSlider.tsx delete mode 100644 apps/frontend-v3/lib/vebal/lock/form/VebalLockForm.tsx delete mode 100644 apps/frontend-v3/lib/vebal/lock/form/WeeklyYieldTooltip.tsx delete mode 100644 apps/frontend-v3/lib/vebal/lock/steps/useLockStep.integration.spec.tsx delete mode 100644 apps/frontend-v3/lib/vebal/useGaugeWorkingBalanceQuery.ts delete mode 100644 apps/frontend-v3/lib/vebal/useMaxAmountOfVeBal.integration.spec.tsx delete mode 100644 apps/frontend-v3/lib/vebal/useMaxAmountOfVeBal.ts rename apps/frontend-v3/lib/vebal/{vote => }/useVeBALBalance.integration.spec.ts (100%) rename apps/frontend-v3/lib/vebal/{vote => }/useVeBALBalance.ts (67%) delete mode 100644 apps/frontend-v3/lib/vebal/vote/VoteCapTooltip.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/VoteExpiredTooltip.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/VoteList/SelectedPoolsMenu.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/VoteList/VoteList.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/VoteList/VoteListFilters.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/VoteList/VoteListLayout.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/VoteList/VoteListProvider.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/VoteList/VoteListSearch.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/VoteList/VoteListTable/VoteListTable.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/VoteList/VoteListTable/VoteListTableHeader.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/VoteList/VoteListTable/VoteListTableRow.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/VoteList/VoteListTable/VoteListVotesCell.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/VoteList/useVoteListFiltersState.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/VoteRateTooltip.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/CalendarReminderModal.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotes.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesHintModal.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesLayout.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesProvider.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/MyVotesStatsAverageReward.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/MyVotesStatsMyIncentives.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/MyVotesStatsMyVebal.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/IncentivesOptimizedTooltip.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/MyVotesStatsMyIncentivesOptimized.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/TooltipItem.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/heap.spec.ts delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/heap.ts delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/useIncentivesOptimized.spec.ts delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/useIncentivesOptimized.ts delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/GainBadge.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/MyIncentivesTooltip.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/MyVebalChargeTooltip.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/MyVotesStatsCard.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/useVeBALIncentives.integration.spec.ts delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/useVeBALIncentives.ts delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/useVeBALPool.ts delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTable.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTableHeader.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTableRow.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTableSubmitRow.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTableTotalRow.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/VoteWeightInput.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/VoteExceededTooltip.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/VoteTimeLockedTooltip.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/VoteUnallocatedTooltip.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/VoteUnderpoweredTooltip.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/VoteWeight.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/modal/PoolSelectionModal.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/modal/SubmitVotesModal.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/modal/SubmitVotesPreview.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/useSubmitVotesAllSteps.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/useSubmitVotesSteps.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/useSubmittingVotes.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/incentivesBlacklist.ts delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/myVotes.helpers.ts delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/myVotes.types.ts delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/useMyVotesFiltersState.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/VotesContainer.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/HomeIcon.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotesIntroduction.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotesIntroductionLayout.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/DeadlineDayTooltip.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/ReminderButton.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/StaticCalendar.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/VotingDeadline.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/VotingDeadlineCalendar.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/VotingDeadlineContainer.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/VotingDeadlineCounter.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/deadline.spec.ts delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/deadline.ts delete mode 100644 apps/frontend-v3/lib/vebal/vote/Votes/VotesProvider.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/useExpiredGaugesQuery.tsx delete mode 100644 apps/frontend-v3/lib/vebal/vote/useTotalVotes.ts delete mode 100644 apps/frontend-v3/lib/vebal/vote/useVeBALTotal.integration.spec.ts delete mode 100644 apps/frontend-v3/lib/vebal/vote/useVeBALTotal.ts delete mode 100644 packages/lib/modules/pool/PoolDetail/PoolUserEvents/BoostText.tsx delete mode 100644 packages/lib/modules/vebal/useGaugeTotalSupplyAndUserBalance.ts delete mode 100644 packages/lib/modules/vebal/useGaugesSupplyAndBalance.ts delete mode 100644 packages/lib/modules/vebal/useVebalBoost.tsx delete mode 100644 packages/lib/modules/vebal/vote/useGaugeVotes.tsx delete mode 100644 packages/lib/modules/vebal/vote/vote.helpers.ts delete mode 100644 packages/lib/shared/components/modals/VebalPartnerRedirectModal.tsx diff --git a/apps/frontend-v3/app/(app)/vebal/ImageBorderCard.tsx b/apps/frontend-v3/app/(app)/vebal/ImageBorderCard.tsx deleted file mode 100644 index 3fdc626955..0000000000 --- a/apps/frontend-v3/app/(app)/vebal/ImageBorderCard.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Box, BoxProps, useColorMode } from '@chakra-ui/react' -import Noise from '@repo/lib/shared/components/layout/Noise' -import { RadialPattern } from '@repo/lib/shared/components/zen/RadialPattern' - -export function ImageBorderCard({ children, ...props }: BoxProps) { - const { colorMode } = useColorMode() - // Use image-set for AVIF (preferred) and JPEG fallback - const borderImage = - colorMode === 'dark' - ? `image-set(url('/images/textures/rock-slate-square-dark.avif') type('image/avif'), url('/images/textures/rock-slate-square-dark.png') type('image/png'))` - : `image-set(url('/images/textures/rock-slate-square.avif') type('image/avif'), url('/images/textures/rock-slate-square.jpg') type('image/jpg'))` - - return ( - - - - - {children} - - - - ) -} diff --git a/apps/frontend-v3/app/(app)/vebal/components/VebalBenefitsSection.tsx b/apps/frontend-v3/app/(app)/vebal/components/VebalBenefitsSection.tsx deleted file mode 100644 index a04db51c66..0000000000 --- a/apps/frontend-v3/app/(app)/vebal/components/VebalBenefitsSection.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import { SimpleGrid, Flex, Box, Heading, Stack, Text } from '@chakra-ui/react' -import { Picture } from '@repo/lib/shared/components/other/Picture' -import { VebalBenefitsVoteIcon } from '@repo/lib/shared/components/icons/vebal/VebalBenefitsVoteIcon' -import { VebalBenefitsShareIcon } from '@repo/lib/shared/components/icons/vebal/VebalBenefitsShareIcon' -import { VebalBenefitsBribesIcon } from '@repo/lib/shared/components/icons/vebal/VebalBenefitsBribesIcon' -import { VebalBenefitsSparklesIcon } from '@repo/lib/shared/components/icons/vebal/VebalBenefitsSparklesIcon' -import FadeInOnView from '@repo/lib/shared/components/containers/FadeInOnView' -import { SparkleIconWrapper } from '@repo/lib/shared/components/animations/SparkleIconWrapper' -import { StoneIcon } from '@repo/lib/shared/components/StoneIcon' - -export function VebalBenefitsSection() { - const benefits = [ - { - icon: ( - - - - ), - title: "Vote on Balancer's future", - description: - 'veBAL holders govern the direction of the protocol including liquidity incentives.', - }, - { - icon: ( - - - - ), - title: 'Share protocol revenue', - description: - 'veBAL holders earn a share of protocol revenue in proportion to their holdings.', - transformBackground: 'rotate(90deg)', - }, - { - icon: ( - - - - ), - title: 'Earn weekly voting incentives', - description: - "veBAL holders can earn lucrative 'bribes' from 3rd parties for voting for their pools.", - transformBackground: 'rotate(180deg)', - }, - { - icon: ( - - - - ), - title: 'Boost liquidity mining yield', - description: - 'Liquidity Providers with veBAL can get up to a 2.5x boost on BAL liquidity incentives.', - transformBackground: 'rotate(-90deg)', - }, - ] - - return ( - - - - - Why get veBAL? - - - Turn your BAL tokens into voting power and rewards. - - - - - {benefits.map(benefit => ( - - - - - - - - - - - - {benefit.title} - - - {benefit.description} - - - - - - ))} - - - ) -} diff --git a/apps/frontend-v3/app/(app)/vebal/components/VebalEcosystem.tsx b/apps/frontend-v3/app/(app)/vebal/components/VebalEcosystem.tsx deleted file mode 100644 index 4d53c5eb3d..0000000000 --- a/apps/frontend-v3/app/(app)/vebal/components/VebalEcosystem.tsx +++ /dev/null @@ -1,261 +0,0 @@ -import { MotionValue, motion, useMotionValue, useSpring, useTransform } from 'framer-motion' -import { ReactNode, useRef, useState } from 'react' -import { Box, BoxProps, Flex, Text, useDisclosure, VStack, Stack, Heading } from '@chakra-ui/react' -import FadeInOnView from '@repo/lib/shared/components/containers/FadeInOnView' -import { StakedaoIcon } from '@repo/lib/shared/components/icons/logos/StakedaoIcon' -import { AuraIcon } from '@repo/lib/shared/components/icons/logos/AuraIcon' -import { PaladinIcon } from '@repo/lib/shared/components/icons/logos/PaladinIcon' -import { Picture } from '@repo/lib/shared/components/other/Picture' - -import { - VebalPartnerRedirectModal, - VebalRedirectPartner, -} from '@repo/lib/shared/components/modals/VebalPartnerRedirectModal' - -export function VebalEcosystem() { - const [redirectPartner, setRedirectPartner] = useState( - VebalRedirectPartner.Aura - ) - const mouseX = useMotionValue(Infinity) - - const partnerRedirectDisclosure = useDisclosure() - - const logos = [ - { icon: AuraIcon, name: 'Aura', partner: VebalRedirectPartner.Aura, size: 48 }, - { icon: StakedaoIcon, name: 'Stakedao', partner: VebalRedirectPartner.StakeDAO, size: 48 }, - { icon: PaladinIcon, name: 'Paladin', partner: VebalRedirectPartner.Palladin, size: 48 }, - ] - - function openRedirectModal(partner: VebalRedirectPartner) { - setRedirectPartner(partner) - partnerRedirectDisclosure.onOpen() - } - - return ( - - - - - - veBAL ecosystem - - - DeFi platforms have built advanced tools and incentive systems on top of veBAL. Some - also acquire veBAL to get a share of protocol revenue and to direct BAL emissions. - - - - - mouseX.set(Infinity)} - onMouseMove={e => mouseX.set(e.pageX)} - w="full" - > - {logos.map(logo => ( - } - key={logo.name.toLowerCase()} - mouseX={mouseX} - name={logo.name} - onClick={() => openRedirectModal(logo.partner)} - /> - ))} - - - - {logos.map(logo => ( - } - key={logo.name.toLowerCase()} - name={logo.name} - onClick={() => openRedirectModal(logo.partner)} - /> - ))} - - - - - ) -} - -function AppIcon({ - mouseX, - Icon, - name, - ...rest -}: { - mouseX: MotionValue - Icon: ReactNode - name: string -} & BoxProps) { - const ref = useRef(null) - - const distance = useTransform(mouseX, val => { - const bounds = ref.current?.getBoundingClientRect() ?? { x: 0, width: 0 } - return val - bounds.x - bounds.width / 2 - }) - - const widthSync = useTransform(distance, [-100, -50, 0, 50, 100], [120, 150, 150, 150, 120]) - const width = useSpring(widthSync, { mass: 2, stiffness: 100, damping: 30 }) - - return ( - - - - - - - {Icon} - - - - ) -} - -function SmallIcon({ Icon, name, ...rest }: { Icon: ReactNode; name: string } & BoxProps) { - return ( - - - - - - {Icon} - - - ) -} diff --git a/apps/frontend-v3/app/(app)/vebal/components/VebalFooterSection.tsx b/apps/frontend-v3/app/(app)/vebal/components/VebalFooterSection.tsx deleted file mode 100644 index ce816a8154..0000000000 --- a/apps/frontend-v3/app/(app)/vebal/components/VebalFooterSection.tsx +++ /dev/null @@ -1,101 +0,0 @@ -'use client' - -import { Box, Button, Center, Flex, Heading, Link, Text, HStack, Stack } from '@chakra-ui/react' -import { RadialPattern } from '@bal/app/(marketing)/_lib/landing-v3/shared/RadialPattern' -import FadeInOnView from '@repo/lib/shared/components/containers/FadeInOnView' -import NextLink from 'next/link' -import { ArrowUpRight } from 'react-feather' - -export function VebalFooterSection() { - return ( - - - - -
- - - - - Calling all BAL holders - - - Lock your BAL for veBAL to join Balancer governance, be eligible for incentives, and - share in the success of the protocol. - - - - - - - - - - View veBAL docs - - - - - - - - - -
-
- ) -} diff --git a/apps/frontend-v3/app/(app)/vebal/components/VebalHeroSection.tsx b/apps/frontend-v3/app/(app)/vebal/components/VebalHeroSection.tsx deleted file mode 100644 index d34c21e2da..0000000000 --- a/apps/frontend-v3/app/(app)/vebal/components/VebalHeroSection.tsx +++ /dev/null @@ -1,167 +0,0 @@ -'use client' -import { RadialPattern } from '@bal/app/(marketing)/_lib/landing-v3/shared/RadialPattern' -import { - Box, - Button, - Link, - Flex, - Grid, - GridItem, - Heading, - Stack, - Text, - useBreakpointValue, -} from '@chakra-ui/react' -import { DefaultPageContainer } from '@repo/lib/shared/components/containers/DefaultPageContainer' -import FadeInOnView from '@repo/lib/shared/components/containers/FadeInOnView' -import Noise from '@repo/lib/shared/components/layout/Noise' -import { Picture } from '@repo/lib/shared/components/other/Picture' -import NextLink from 'next/link' - -export function VebalHeroSection() { - const radialPatternProps = useBreakpointValue({ - base: { circleCount: 10, height: 1000, width: 1000 }, - md: { circleCount: 15, height: 1500, width: 1500 }, - xl: { circleCount: 20, height: 2000, width: 2000 }, - }) - - return ( - - - - - {/* Content Column */} - - - - - - Lock-in for veBAL boosts - - - veBAL is the Balancer protocol governance system that rewards long-term - commitment. Get veBAL by locking the LP tokens of the BAL/WETH 80/20 pool. - - - - - - - - - - - - Claim veBAL incentives - - - - - - - - - - - - - - - - - - ) -} diff --git a/apps/frontend-v3/app/(app)/vebal/components/VebalHowSection.tsx b/apps/frontend-v3/app/(app)/vebal/components/VebalHowSection.tsx deleted file mode 100644 index a78d9c60a5..0000000000 --- a/apps/frontend-v3/app/(app)/vebal/components/VebalHowSection.tsx +++ /dev/null @@ -1,109 +0,0 @@ -'use client' -import { Grid, GridItem, Heading, Stack, Text, chakra, Link } from '@chakra-ui/react' -import FadeInOnView from '@repo/lib/shared/components/containers/FadeInOnView' -import { VebalStepCard } from './VebalStepCard' -import NextLink from 'next/link' - -export function VebalHowSection() { - return ( - - - - - Here’s how it works - - - Add liquidity to the ve8020 BAL/WETH pool and lock it up. The longer you lock, the more - veBAL you get. - - - - - - - - - Join - -  80/20 BAL/WETH—aka the ve8020 Balancer protocol liquidity pool—to get the{' '} - B-80BAL-20WETH LP - token. - - } - heading="Add liquidity" - imgName="tokens" - step="1." - /> - - - - - - - Lock - {' '} - your B-80BAL-20WETH LP - tokens for a period to receive veBAL. The longer you lock, the more veBAL you get. - - } - heading="Lock your LP tokens" - imgName="lptoken" - step="2." - /> - - - - - - Earn protocol revenue + weekly voting incentives, boost liquidity mining, gain - governance power. - - } - heading="Get power + rewards" - imgName="vebal" - step="3." - /> - - - - - ) -} diff --git a/apps/frontend-v3/app/(app)/vebal/components/VebalStepCard.tsx b/apps/frontend-v3/app/(app)/vebal/components/VebalStepCard.tsx deleted file mode 100644 index f65b84645c..0000000000 --- a/apps/frontend-v3/app/(app)/vebal/components/VebalStepCard.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { Box, Heading, Stack, Text } from '@chakra-ui/react' -import FadeInOnView from '@repo/lib/shared/components/containers/FadeInOnView' -import { ParallaxImage } from '@repo/lib/shared/components/marketing/ParallaxImage' -import { Picture } from '@repo/lib/shared/components/other/Picture' -import { ImageBorderCard } from '../ImageBorderCard' -import { ReactNode } from 'react' - -interface VebalStepCardProps { - imgName: string - altText: string - heading: string - step?: string - description: ReactNode -} - -export function VebalStepCard({ - imgName, - altText, - heading, - step, - description, -}: VebalStepCardProps) { - return ( - - - - - - - - - - - {step} {heading} - - - {description} - - - - ) -} diff --git a/apps/frontend-v3/app/(app)/vebal/layout.tsx b/apps/frontend-v3/app/(app)/vebal/layout.tsx deleted file mode 100644 index eaf9f93bc1..0000000000 --- a/apps/frontend-v3/app/(app)/vebal/layout.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { Box } from '@chakra-ui/react' -import { PropsWithChildren } from 'react' - -export default async function VeBALLayout({ children }: PropsWithChildren) { - return {children} -} diff --git a/apps/frontend-v3/app/(app)/vebal/manage/extend/page.tsx b/apps/frontend-v3/app/(app)/vebal/manage/extend/page.tsx deleted file mode 100644 index 478bd8f4a2..0000000000 --- a/apps/frontend-v3/app/(app)/vebal/manage/extend/page.tsx +++ /dev/null @@ -1,15 +0,0 @@ -'use client' - -import { TransactionStateProvider } from '@repo/lib/modules/transactions/transaction-steps/TransactionStateProvider' -import { VebalExtend } from '@bal/lib/vebal/lock/VebalExtend' -import { VebalLockActionsLayout } from '@bal/lib/vebal/lock/VebalLockActionsLayout' - -export default function VebalExtendPage() { - return ( - - - - - - ) -} diff --git a/apps/frontend-v3/app/(app)/vebal/manage/layout.tsx b/apps/frontend-v3/app/(app)/vebal/manage/layout.tsx index a430461efd..f0714d4e5d 100644 --- a/apps/frontend-v3/app/(app)/vebal/manage/layout.tsx +++ b/apps/frontend-v3/app/(app)/vebal/manage/layout.tsx @@ -4,7 +4,6 @@ import { TokenBalancesProvider } from '@repo/lib/modules/tokens/TokenBalancesPro import { DefaultPageContainer } from '@repo/lib/shared/components/containers/DefaultPageContainer' import { PropsWithChildren } from 'react' import { useTokens } from '@repo/lib/modules/tokens/TokensProvider' -// import { CrossChainSyncProvider } from '@bal/lib/vebal/cross-chain/CrossChainSyncProvider' import { TransactionStateProvider } from '@repo/lib/modules/transactions/transaction-steps/TransactionStateProvider' export default function VeBALManageLayout({ children }: PropsWithChildren) { @@ -16,11 +15,9 @@ export default function VeBALManageLayout({ children }: PropsWithChildren) { return ( - {/* */} {children} - {/* */} ) } diff --git a/apps/frontend-v3/app/(app)/vebal/manage/lock/page.tsx b/apps/frontend-v3/app/(app)/vebal/manage/lock/page.tsx deleted file mode 100644 index 9be86519e8..0000000000 --- a/apps/frontend-v3/app/(app)/vebal/manage/lock/page.tsx +++ /dev/null @@ -1,15 +0,0 @@ -'use client' - -import { TransactionStateProvider } from '@repo/lib/modules/transactions/transaction-steps/TransactionStateProvider' -import { VebalLock } from '@bal/lib/vebal/lock/VebalLock' -import { VebalLockActionsLayout } from '@bal/lib/vebal/lock/VebalLockActionsLayout' - -export default function VebalLockPage() { - return ( - - - - - - ) -} diff --git a/apps/frontend-v3/app/(app)/vebal/manage/page.tsx b/apps/frontend-v3/app/(app)/vebal/manage/page.tsx index 797f85af30..f4ce4a4293 100644 --- a/apps/frontend-v3/app/(app)/vebal/manage/page.tsx +++ b/apps/frontend-v3/app/(app)/vebal/manage/page.tsx @@ -1,14 +1,12 @@ 'use client' import { VebalManage } from '@bal/lib/vebal/VebalManage' -// import { CrossChainBoost } from '@/bal/lib/vebal/cross-chain/CrossChainBoost' import { Stack } from '@chakra-ui/react' export default function VebalManagePage() { return ( - {/* */} ) } diff --git a/apps/frontend-v3/app/(app)/vebal/page.tsx b/apps/frontend-v3/app/(app)/vebal/page.tsx deleted file mode 100644 index e0f0423014..0000000000 --- a/apps/frontend-v3/app/(app)/vebal/page.tsx +++ /dev/null @@ -1,26 +0,0 @@ -'use client' -import Section from '@repo/lib/shared/components/layout/Section' - -import { VebalBenefitsSection } from './components/VebalBenefitsSection' -import { VebalFooterSection } from './components/VebalFooterSection' -import { VebalHowSection } from './components/VebalHowSection' -import { VebalHeroSection } from './components/VebalHeroSection' -import { VebalEcosystem } from './components/VebalEcosystem' - -export default function VeBALPage() { - return ( - <> - -
- -
-
- -
-
- -
- - - ) -} diff --git a/apps/frontend-v3/app/(app)/vebal/vote/layout.tsx b/apps/frontend-v3/app/(app)/vebal/vote/layout.tsx deleted file mode 100644 index 1d8e2aebe9..0000000000 --- a/apps/frontend-v3/app/(app)/vebal/vote/layout.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Metadata } from 'next' -import { PropsWithChildren } from 'react' - -export const metadata: Metadata = { - title: 'Vote and earn external incentives', - description: ` - Voting on pool gauges helps to direct weekly BAL liquidity mining incentives. - Voters are also eligible to earn additional 3rd party voting incentives. - `, -} - -export default async function Pools({ children }: PropsWithChildren) { - return
{children}
-} diff --git a/apps/frontend-v3/app/(app)/vebal/vote/page.tsx b/apps/frontend-v3/app/(app)/vebal/vote/page.tsx deleted file mode 100644 index 138c1360ef..0000000000 --- a/apps/frontend-v3/app/(app)/vebal/vote/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import FadeInOnView from '@repo/lib/shared/components/containers/FadeInOnView' -import { VotesContainer } from '@bal/lib/vebal/vote/Votes/VotesContainer' - -export default function VotePage() { - return ( - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/VeBalLockButtons.tsx b/apps/frontend-v3/lib/vebal/VeBalLockButtons.tsx index 15be2f0161..820c3dc33a 100644 --- a/apps/frontend-v3/lib/vebal/VeBalLockButtons.tsx +++ b/apps/frontend-v3/lib/vebal/VeBalLockButtons.tsx @@ -1,5 +1,5 @@ import { Flex, Button, ButtonProps } from '@chakra-ui/react' -import { getVeBalManagePath, VeBalAction, VeBalSourcePage } from './vebal-navigation' +import { getVeBalManagePath, VeBalAction } from './vebal-navigation' import NextLink from 'next/link' import { useVebalLockData } from '@repo/lib/modules/vebal/VebalLockDataProvider' @@ -7,24 +7,23 @@ type Props = { variant?: 'primary' | 'tertiary' size?: ButtonProps['size'] action?: VeBalAction - sourcePage?: VeBalSourcePage } export function VeBalLockButtons(props: Props) { return ( - + ) } -function VeBalGetOrUnLockButton({ size = 'lg', action = 'lock', sourcePage = 'manage' }: Props) { +function VeBalGetOrUnLockButton({ size = 'lg', action = 'unlock' }: Props) { const lockData = useVebalLockData() return ( - - - )} - - - {isLoading ? ( - - ) : ( - - Synced networks - - {networksBySyncState.synced.length ? ( - - {networksBySyncState.synced.map(chain => ( - {`Chain - ))} - - ) : ( - - Sync veBAL across networks for a boosted APR on your staked positions. - - )} - - - )} - - - )} - - ) : ( - Once you have some veBAL, sync your balance here to other networks. - )} - - ) -} diff --git a/apps/frontend-v3/lib/vebal/cross-chain/CrossChainSyncModal.tsx b/apps/frontend-v3/lib/vebal/cross-chain/CrossChainSyncModal.tsx deleted file mode 100644 index 06ee3ed620..0000000000 --- a/apps/frontend-v3/lib/vebal/cross-chain/CrossChainSyncModal.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import { - Modal, - ModalBody, - ModalCloseButton, - ModalContent, - Card, - Text, - ModalFooter, - Button, - VStack, - Checkbox, - ModalHeader, - HStack, - Heading, -} from '@chakra-ui/react' -import { GqlChain } from '@repo/lib/shared/services/api/generated/graphql' -import { useBreakpoints } from '@repo/lib/shared/hooks/useBreakpoints' -import { TransactionModalHeader } from '@repo/lib/shared/components/modals/TransactionModalHeader' -import { ActionModalFooter } from '@repo/lib/shared/components/modals/ActionModalFooter' -import { SuccessOverlay } from '@repo/lib/shared/components/modals/SuccessOverlay' -import { useState } from 'react' -import { AnimateHeightChange } from '@repo/lib/shared/components/animations/AnimateHeightChange' -import { useCrossChainSyncSteps } from '@bal/lib/vebal/cross-chain/useCrossChainSyncSteps' -import { getStylesForModalContentWithStepTracker } from '@repo/lib/modules/transactions/transaction-steps/step-tracker/step-tracker.utils' -import { DesktopStepTracker } from '@repo/lib/modules/transactions/transaction-steps/step-tracker/DesktopStepTracker' -import { MobileStepTracker } from '@repo/lib/modules/transactions/transaction-steps/step-tracker/MobileStepTracker' -import { uniq } from 'lodash' -import { getChainShortName } from '@repo/lib/config/app.config' -import { useVebalUserData } from '@bal/lib/vebal/useVebalUserData' -import { useCrossChainSync } from './CrossChainSyncProvider' -import { formatUnits } from 'viem' -import { useTransactionSteps } from '@repo/lib/modules/transactions/transaction-steps/useTransactionSteps' - -type Props = { - isOpen: boolean - onClose(): void - networks: GqlChain[] -} - -function NetworksSelectionStep({ networks, selectedNetworks, toggleNetwork }: NetworkOptionsProps) { - const { veBALBalance } = useVebalUserData() - - return ( - - - Layer 2 networks don’t know your veBAL balance from Ethereum, unless you sync it. Each - network costs additional gas to sync, so it’s best to only sync networks where you plan to - stake - - - - - Ethereum - {formatUnits(veBALBalance, 18)} veBAL - - - - - - ) -} - -interface NetworkOptionsProps { - networks: GqlChain[] - selectedNetworks: GqlChain[] - toggleNetwork: (checked: boolean, network: GqlChain) => void -} - -function NetworkOptions({ networks, selectedNetworks, toggleNetwork }: NetworkOptionsProps) { - const { l2VeBalBalances } = useCrossChainSync() - return ( - - {networks.map(network => ( - toggleNetwork(e.target.checked, network)} - > - - {getChainShortName(network)} - {l2VeBalBalances[network]} veBAL - - - ))} - - ) -} - -export function CrossChainSyncModal({ isOpen, onClose, networks }: Props) { - const { refetch } = useCrossChainSync() - const [selectedNetworks, setSelectedNetworks] = useState([]) - const [showTransactionSteps, setShowTransactionSteps] = useState(false) - const { isDesktop, isMobile } = useBreakpoints() - - const steps = useCrossChainSyncSteps({ - networks: selectedNetworks, - }) - const transactionSteps = useTransactionSteps(steps) - - const transactionHash = transactionSteps.lastTransaction?.result?.data?.transactionHash - - function onModalClose() { - onClose() - setSelectedNetworks([]) - setShowTransactionSteps(false) - transactionSteps.resetTransactionSteps() - refetch() - } - - function toggleNetwork(checked: boolean, network: GqlChain) { - if (checked) { - setSelectedNetworks(current => uniq([...current, network])) - } else { - setSelectedNetworks(current => current.filter(chain => chain !== network)) - } - } - - const isSuccess = !!transactionHash - - return ( - - - - - {showTransactionSteps && isDesktop && ( - - )} - {showTransactionSteps ? ( - - ) : ( - Sync veBAL: Select networks - )} - - - - {showTransactionSteps ? ( - <> - {isMobile && ( - - )} - - ) : ( - - )} - - - - {transactionSteps.currentStep && showTransactionSteps ? ( - - ) : ( - - - - )} - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/cross-chain/CrossChainSyncModalContent.tsx b/apps/frontend-v3/lib/vebal/cross-chain/CrossChainSyncModalContent.tsx deleted file mode 100644 index 4efebfe451..0000000000 --- a/apps/frontend-v3/lib/vebal/cross-chain/CrossChainSyncModalContent.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Card } from '@chakra-ui/react' -import { GqlChain } from '@repo/lib/shared/services/api/generated/graphql' -import { CrossChainSyncRow } from './CrossChainSyncRow' - -export function CrossChainSyncModalContent({ - currentNetwork, - networks, -}: { - currentNetwork: GqlChain - networks: GqlChain[] -}) { - return ( - - {networks.map(network => ( - - ))} - - ) -} diff --git a/apps/frontend-v3/lib/vebal/cross-chain/CrossChainSyncProvider.tsx b/apps/frontend-v3/lib/vebal/cross-chain/CrossChainSyncProvider.tsx deleted file mode 100644 index 4081964cc5..0000000000 --- a/apps/frontend-v3/lib/vebal/cross-chain/CrossChainSyncProvider.tsx +++ /dev/null @@ -1,265 +0,0 @@ -import networkConfigs, { GqlChainValues } from '@repo/lib/config/networks' -import { useEffect, useMemo, useCallback, createContext, PropsWithChildren } from 'react' -import { OmniEscrowLock, useOmniEscrowLocksQuery } from './useOmniEscrowLocksQuery' -import { - NetworkSyncState, - calculateVeBAlBalance, - getNetworkSyncState, - useCrossChainNetworks, -} from './useCrossChainNetworks' -import { GqlChain } from '@repo/lib/shared/services/api/generated/graphql' -import { Address, Hash } from 'viem' -import { useMandatoryContext } from '@repo/lib/shared/utils/contexts' -import { keyBy } from 'lodash' -import { useLocalStorage } from 'usehooks-ts' -import { LS_KEYS } from '@repo/lib/modules/local-storage/local-storage.constants' -import { useLayerZeroTxLinks } from './useLayerZeroTxLinks' -import { secs } from '@repo/lib/shared/utils/time' -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' - -const veBalSyncSupportedNetworks: GqlChain[] = Object.keys(networkConfigs) - .filter(key => networkConfigs[key as GqlChainValues]?.supportsVeBalSync) - .map(key => key) as GqlChain[] - -const REFETCH_INTERVAL = secs(30).toMs() - -export type CrossChainSyncResult = ReturnType -export const CrossChainSyncContext = createContext(null) - -interface CheckIfNetworkSyncingArgs { - networksSyncState: Record - tempSyncingNetworks: Record - userAddress: Address - network: GqlChain -} - -export function checkIfNetworkSyncing({ - networksSyncState, - tempSyncingNetworks, - userAddress, - network, -}: CheckIfNetworkSyncingArgs) { - return ( - networksSyncState?.[network] === NetworkSyncState.Syncing || - tempSyncingNetworks[userAddress]?.networks.includes(network) - ) -} - -export interface TempSyncingNetworks { - networks: GqlChain[] - syncTimestamp?: number -} - -export type SyncTxHashes = Record - -const initialTempSyncingNetworks: Record = {} -const initialSyncTxHashes: Record = {} - -export const useCrossChainSyncLogic = () => { - const { userAddress } = useUserAccount() - - const { - data: omniEscrowResponse, - isLoading: isLoadingOmniEscrow, - refetch: refetchOmniEscrow, - isError: isOmniEscrowError, - } = useOmniEscrowLocksQuery() - - const [tempSyncingNetworks, setTempSyncingNetworks] = useLocalStorage( - LS_KEYS.CrossChainSync.TempSyncingNetworks, - initialTempSyncingNetworks - ) - const [syncTxHashes, _setSyncTxHashes] = useLocalStorage( - LS_KEYS.CrossChainSync.SyncTxHashes, - initialSyncTxHashes - ) - const { syncLayerZeroTxLinks } = useLayerZeroTxLinks(syncTxHashes) - - const allNetworksUnsynced = omniEscrowResponse?.omniVotingEscrowLocks.length === 0 - - const omniEscrowLocksMap = useMemo(() => { - if (allNetworksUnsynced || !omniEscrowResponse) return null - - return omniEscrowResponse.omniVotingEscrowLocks.reduce>( - (acc, item) => { - acc[item.dstChainId] = item - return acc - }, - {} - ) - }, [allNetworksUnsynced, omniEscrowResponse]) - - const [mainnetCrossChainNetwork] = useCrossChainNetworks([GqlChain.Mainnet], omniEscrowLocksMap) - - const mainnetEscrowLock = mainnetCrossChainNetwork?.votingEscrowLocks - - const crossChainNetworksResponses = useCrossChainNetworks( - veBalSyncSupportedNetworks, - omniEscrowLocksMap - ) - const crossChainNetworks = keyBy(crossChainNetworksResponses, 'chainId') - - const isLoading = isLoadingOmniEscrow || mainnetCrossChainNetwork?.isLoading - - const networksSyncState = useMemo(() => { - return veBalSyncSupportedNetworks.reduce>>( - (acc, network) => { - acc[network] = getNetworkSyncState({ - omniEscrowLock: omniEscrowLocksMap?.[networkConfigs[network]?.layerZeroChainId || ''], - mainnetEscrowLock, - }) - return acc - }, - {} - ) - }, [mainnetEscrowLock, omniEscrowLocksMap]) - - const networksBySyncState = useMemo(() => { - const networksWithValidSyncState = Object.keys(networksSyncState) as GqlChain[] - - return { - synced: networksWithValidSyncState.filter( - network => networksSyncState[network] === NetworkSyncState.Synced - ), - unsynced: networksWithValidSyncState.filter( - network => networksSyncState[network] === NetworkSyncState.Unsynced - ), - syncing: networksWithValidSyncState.filter( - network => - networksSyncState[network] === NetworkSyncState.Syncing || - tempSyncingNetworks[userAddress]?.networks.includes(network) - ), - } - }, [networksSyncState, tempSyncingNetworks, userAddress]) - - const showingUnsyncedNetworks = [...networksBySyncState.unsynced, ...networksBySyncState.syncing] - - const hasError = - isOmniEscrowError || - veBalSyncSupportedNetworks.some(network => crossChainNetworks[network]?.isError) - - const l2VeBalBalances = useMemo(() => { - return veBalSyncSupportedNetworks.reduce>>((acc, network) => { - const crossChainNetwork = crossChainNetworks[network] - const votingEscrowLocks = crossChainNetwork?.votingEscrowLocks - - if (!votingEscrowLocks) { - return acc - } - - acc[network] = calculateVeBAlBalance(votingEscrowLocks) - return acc - }, {}) - }, [crossChainNetworks]) - - const warningMessage = useMemo(() => { - if (networksBySyncState.syncing.length > 0) { - return { - title: 'Wait until sync finalizes before restaking / triggering a gauge update on L2', - text: `Your sync has been initiated but it may take up to 30 mins to update across L2s. - Once your veBAL is synced, you will need to interact with each gauge to register your new max boost. - You can either claim, restake, or click the Update button, which will appear - on each individual pool page staking section.`, - } - } - return null - }, [networksBySyncState]) - - const infoMessage = useMemo(() => { - if (!warningMessage && networksBySyncState.synced.length > 0) { - return { - title: 'Trigger pool gauge updates to get your boosts sooner', - text: `Pool gauges don’t automatically recognize changes in veBAL until triggered. - Updates are triggered when any user interacts with a gauge, such as by claiming BAL, staking or unstaking. - Trigger individual gauges yourself for your boosts to apply immediately.`, - } - } - return null - }, [warningMessage, networksBySyncState]) - - const refetch = useCallback(async () => { - if (!mainnetCrossChainNetwork) return - await Promise.all([refetchOmniEscrow(), mainnetCrossChainNetwork.refetch()]) - - if (omniEscrowLocksMap) { - const promises = networksBySyncState.syncing.map(networkId => { - if (crossChainNetworks[networkId]) crossChainNetworks[networkId].refetch() - }) - await Promise.all(promises) - } - }, [ - refetchOmniEscrow, - mainnetCrossChainNetwork, - omniEscrowLocksMap, - networksBySyncState, - crossChainNetworks, - ]) - - useEffect(() => { - let intervalId: NodeJS.Timeout - if (networksBySyncState.syncing.length > 0) { - intervalId = setInterval(() => { - void refetch() - }, REFETCH_INTERVAL) - } - return () => clearInterval(intervalId as unknown as number) - }, [networksBySyncState, refetch]) - - function setSyncTxHashes(network: GqlChain, txHash: Hash) { - _setSyncTxHashes(prev => ({ - ...prev, - [userAddress]: { - ...prev[userAddress], - [network]: txHash, - }, - })) - } - - const clearTempSyncingNetworksFromSynced = useCallback(() => { - if (!tempSyncingNetworks[userAddress]) return - - setTempSyncingNetworks(prev => { - const updatedNetworks = prev[userAddress]?.networks.filter( - network => !networksBySyncState.synced.includes(network) - ) - return { ...prev, [userAddress]: { ...prev[userAddress], networks: updatedNetworks } } - }) - }, [userAddress, networksBySyncState, tempSyncingNetworks, setTempSyncingNetworks]) - - useEffect(() => { - if (networksBySyncState.synced.length > 0) { - const hasSyncingMismatch = networksBySyncState.syncing.some(network => - networksBySyncState.synced.includes(network) - ) - if (hasSyncingMismatch) { - clearTempSyncingNetworksFromSynced() - } - } - }, [networksBySyncState, clearTempSyncingNetworksFromSynced]) - - return { - showingUnsyncedNetworks, - hasError, - omniEscrowLocksMap, - networksSyncState, - isLoading, - networksBySyncState, - l2VeBalBalances, - refetch, - tempSyncingNetworks, - setTempSyncingNetworks, - warningMessage, - infoMessage, - syncTxHashes, - setSyncTxHashes, - syncLayerZeroTxLinks, - } -} - -export function CrossChainSyncProvider({ children }: PropsWithChildren) { - const hook = useCrossChainSyncLogic() - return {children} -} - -export const useCrossChainSync = (): CrossChainSyncResult => - useMandatoryContext(CrossChainSyncContext, 'CrossChainSyncContext') diff --git a/apps/frontend-v3/lib/vebal/cross-chain/CrossChainSyncRow.tsx b/apps/frontend-v3/lib/vebal/cross-chain/CrossChainSyncRow.tsx deleted file mode 100644 index 555aaaaf8a..0000000000 --- a/apps/frontend-v3/lib/vebal/cross-chain/CrossChainSyncRow.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { GqlChain } from '@repo/lib/shared/services/api/generated/graphql' - -import { useVebalUserData } from '@bal/lib/vebal/useVebalUserData' -import { HStack, Text, VStack } from '@chakra-ui/react' -import Image from 'next/image' -import { getChainShortName } from '@repo/lib/config/app.config' -import { useCrossChainSync } from './CrossChainSyncProvider' -import { formatUnits } from 'viem' - -export function CrossChainSyncRow({ network, current }: { network: GqlChain; current: boolean }) { - const { l2VeBalBalances } = useCrossChainSync() - const { veBALBalance } = useVebalUserData() - - return ( - - - {`Chain - {getChainShortName(network)} - - - - Current balance - {l2VeBalBalances[network]} - - - Post-sync balance - {formatUnits(veBALBalance, 18)} - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/cross-chain/useCrossChainNetworks.ts b/apps/frontend-v3/lib/vebal/cross-chain/useCrossChainNetworks.ts deleted file mode 100644 index 97c3224eba..0000000000 --- a/apps/frontend-v3/lib/vebal/cross-chain/useCrossChainNetworks.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { useMemo } from 'react' -import { GqlChain } from '@repo/lib/shared/services/api/generated/graphql' -import networkConfigs from '@repo/lib/config/networks' -import { OmniEscrowLock } from './useOmniEscrowLocksQuery' -import { - useVotingEscrowLocksQueries, - VotingEscrowLock, - VotingEscrowLockResponse, -} from './useVotingEscrowLocksQueries' -import { bn } from '@repo/lib/shared/utils/numbers' -import { allEqual } from '@repo/lib/shared/utils/array' -import { UseQueryResult } from '@tanstack/react-query' -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' - -export enum NetworkSyncState { - Unsynced = 'Unsynced', - Syncing = 'Syncing', - Synced = 'Synced', - Unknown = 'Unknown', -} - -interface GetNetworkSyncStateArgs { - votingEscrowLocks?: VotingEscrowLock - omniEscrowLock?: OmniEscrowLock | null - mainnetEscrowLock?: VotingEscrowLock -} - -export function getNetworkSyncState({ - votingEscrowLocks, - omniEscrowLock, - mainnetEscrowLock, -}: GetNetworkSyncStateArgs) { - if (!omniEscrowLock || !mainnetEscrowLock || !votingEscrowLocks) { - return NetworkSyncState.Unsynced - } - - const { bias: biasOmni, slope: slopeOmni } = omniEscrowLock - const { bias: biasMainnet, slope: slopeMainnet } = mainnetEscrowLock - const { bias: biasNetwork, slope: slopeNetwork } = votingEscrowLocks - - if (!slopeOmni || !slopeMainnet || !slopeNetwork) { - return NetworkSyncState.Unsynced - } - - const isSynced = - allEqual([biasOmni, biasMainnet, biasNetwork]) && - allEqual([slopeOmni, slopeMainnet, slopeNetwork]) - - const isSyncing = - allEqual([biasOmni, biasMainnet]) && - allEqual([slopeOmni, slopeMainnet]) && - slopeOmni !== slopeNetwork && - biasOmni !== biasNetwork - - if (isSynced) { - return NetworkSyncState.Synced - } - - if (isSyncing) { - return NetworkSyncState.Syncing - } - - return NetworkSyncState.Unsynced -} - -function formatVotingEscrowData( - votingEscrowResponses: UseQueryResult[], - chainIds: GqlChain[] -) { - return votingEscrowResponses.map( - ({ data: votingEscrowResponse, refetch, isError, isLoading: isInitialLoading }, index) => { - const chainId = chainIds[index] - - if (!chainId) { - throw new Error(`formatVotingEscrowData - ${chainId} not found in ${chainIds}`) - } - - const votingEscrowLocks = votingEscrowResponse?.votingEscrowLocks[0] - - return { - chainId, - votingEscrowLocks, - refetch, - isLoading: isInitialLoading, - isError, - } - } - ) -} - -// Calculate veBAL balance using bias, slope, and timestamp values -export function calculateVeBAlBalance(votingEscrowLocks: VotingEscrowLock | null) { - const { bias, slope, timestamp } = votingEscrowLocks || {} - - if (!bias || !slope || !timestamp) return bn(0).toFixed(4).toString() - - const x = bn(slope).multipliedBy(Math.floor(Date.now() / 1000) - timestamp) - - if (x.isLessThan(0)) return bn(bias).toFixed(4).toString() - - const balance = bn(bias).minus(x) - if (balance.isLessThan(0)) return bn(0).toFixed(4).toString() - - return balance.toFixed(4).toString() -} - -export function useCrossChainNetworks( - chainIds: GqlChain[], - omniEscrowMap: Record | null -) { - const { userAddress } = useUserAccount() - - // Determine the user address to use for each chain, considering omniEscrowMap if the chain is not Mainnet - const remoteUsers = useMemo(() => { - return chainIds.map(chainId => { - if (chainId === GqlChain.Mainnet) { - return userAddress - } - - const layerZeroChainId = networkConfigs[chainId]?.layerZeroChainId - return layerZeroChainId - ? omniEscrowMap?.[layerZeroChainId]?.remoteUser || userAddress - : userAddress - }) - }, [chainIds, userAddress, omniEscrowMap]) - - const votingEscrowResponses = useVotingEscrowLocksQueries(remoteUsers) - - return formatVotingEscrowData(votingEscrowResponses, chainIds) -} diff --git a/apps/frontend-v3/lib/vebal/cross-chain/useCrossChainSyncSteps.tsx b/apps/frontend-v3/lib/vebal/cross-chain/useCrossChainSyncSteps.tsx deleted file mode 100644 index fc1a235276..0000000000 --- a/apps/frontend-v3/lib/vebal/cross-chain/useCrossChainSyncSteps.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import { ManagedTransactionButton } from '@repo/lib/modules/transactions/transaction-steps/TransactionButton' -import { - ManagedResult, - TransactionLabels, - TransactionStep, -} from '@repo/lib/modules/transactions/transaction-steps/lib' -import { ManagedTransactionInput } from '@repo/lib/modules/web3/contracts/useManagedTransaction' -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' -import { sentryMetaForWagmiSimulation } from '@repo/lib/shared/utils/query-errors' -import { Address } from 'viem' -import { GqlChain } from '@repo/lib/shared/services/api/generated/graphql' -import { useNetworkConfig } from '@repo/lib/config/useNetworkConfig' -import { useEstimateSendUserBalance } from '@bal/lib/vebal/cross-chain/useEstimateSendUserBalance' -import { Button } from '@chakra-ui/react' -import { getChainShortName, getNetworkConfig } from '@repo/lib/config/app.config' -import { useStepsTransactionState } from '@repo/lib/modules/transactions/transaction-steps/useStepsTransactionState' -import { isTransactionSuccess } from '@repo/lib/modules/transactions/transaction-steps/transaction.helper' - -export const crossChainSyncStepPrefix = 'cross-chain-sync' - -export interface CrossChainSyncStepsProps { - networks: GqlChain[] -} - -function ChainSyncButton({ - omniVotingEscrow, - layerZeroChainId, - stepId, - network, - onTransactionChange, -}: { - omniVotingEscrow: Address - layerZeroChainId: number - stepId: string - network: GqlChain - onTransactionChange: (transaction: ManagedResult) => void -}) { - const { chainId } = useNetworkConfig() - const { data, error, isLoading } = useEstimateSendUserBalance(omniVotingEscrow, layerZeroChainId) - const { userAddress } = useUserAccount() - - // FIXME: (votes) handle error - if (error) { - return - } - - if (!data) { - return - } - - const nativeFee = BigInt(data.nativeFee.toString()) - - const txSimulationMeta = sentryMetaForWagmiSimulation( - 'Error in wagmi tx simulation (Cross Chain Sync transaction)', - { - userAddress, - layerZeroChainId, - stepId, - chainId, - } - ) - - // FIX: actual labels - const labels: TransactionLabels = { - tooltip: `Sync veBAL to ${getChainShortName(network)}`, - init: `Sync veBAL to ${getChainShortName(network)}`, - title: `Sync veBAL to ${getChainShortName(network)}`, - description: `Sync veBAL to ${getChainShortName(network)}`, - confirming: `Syncing veBAL to ${getChainShortName(network)}`, - confirmed: `Synced to ${getChainShortName(network)}`, - } - - const props: ManagedTransactionInput = { - contractId: 'balancer.omniVotingEscrowAbi', - chainId, - functionName: 'sendUserBalance', - contractAddress: omniVotingEscrow, - enabled: Boolean(nativeFee), - txSimulationMeta, - args: [userAddress, layerZeroChainId, userAddress], - labels, - onTransactionChange, - } - - return -} - -export function useCrossChainSyncSteps({ networks }: CrossChainSyncStepsProps): TransactionStep[] { - const { userAddress } = useUserAccount() - const { getTransaction, setTransactionFn } = useStepsTransactionState() - - const { contracts } = getNetworkConfig(GqlChain.Mainnet) - - return networks - .filter(network => { - const networkConfig = getNetworkConfig(network) - return Boolean(networkConfig.layerZeroChainId) - }) - .map(network => { - const stepId = `${crossChainSyncStepPrefix}-${network}` - - const transaction = getTransaction(stepId) - - const isComplete = () => userAddress && isTransactionSuccess(transaction) - - const networkConfig = getNetworkConfig(network) - - // FIX: actual labels - const labels: TransactionLabels = { - init: 'Sync', - title: `Sync veBAL to ${getChainShortName(network)}`, - description: 'description - Cross Chain Sync.', - confirming: 'confirming - Cross Chain Sync...', - confirmed: 'confirmed - Cross Chain Sync!', - tooltip: 'tooltip - Cross Chain Sync', - } - const layerZeroChainId = networkConfig.layerZeroChainId - - if (!layerZeroChainId) { - throw new Error('layerZeroChainId is not defined') - } - - const omniVotingEscrow = contracts.omniVotingEscrow - - if (!omniVotingEscrow) { - throw new Error('omniVotingEscrow contract address is not defined') - } - - const renderAction = () => ( - - ) - - return { - id: stepId, - stepType: 'crossChainSync', - labels, - transaction, - isComplete, - renderAction, - } - }) -} diff --git a/apps/frontend-v3/lib/vebal/cross-chain/useEstimateSendUserBalance.ts b/apps/frontend-v3/lib/vebal/cross-chain/useEstimateSendUserBalance.ts deleted file mode 100644 index 2dddbc0b2e..0000000000 --- a/apps/frontend-v3/lib/vebal/cross-chain/useEstimateSendUserBalance.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' -import { useReadContract } from '@repo/lib/shared/utils/wagmi' -import { OmniVotingEscrowAbi } from '@repo/lib/modules/web3/contracts/abi/OmniVotingEscrowAbi' -import { Address } from 'viem' -import { useNetworkConfig } from '@repo/lib/config/useNetworkConfig' -import { bn } from '@repo/lib/shared/utils/numbers' - -export function useEstimateSendUserBalance(tokenAddress: Address, layerZeroChainId: number) { - const { userAddress } = useUserAccount() - - const { chainId } = useNetworkConfig() - - return useReadContract({ - chainId, - abi: OmniVotingEscrowAbi, - functionName: 'estimateSendUserBalance', - address: tokenAddress, - args: [layerZeroChainId], - account: userAddress, - query: { - enabled: !!userAddress, - select: _data => { - const [nativeFee, zroFee] = _data - return { nativeFee: bn(nativeFee), zroFee: bn(zroFee) } - }, - }, - }) -} diff --git a/apps/frontend-v3/lib/vebal/cross-chain/useLayerZeroTxLinks.ts b/apps/frontend-v3/lib/vebal/cross-chain/useLayerZeroTxLinks.ts deleted file mode 100644 index b31ab2c892..0000000000 --- a/apps/frontend-v3/lib/vebal/cross-chain/useLayerZeroTxLinks.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { useState, useEffect, useCallback } from 'react' -import { getMessagesBySrcTxHash } from '@layerzerolabs/scan-client' -import { GqlChain } from '@repo/lib/shared/services/api/generated/graphql' - -import { Address } from 'viem' -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' -import { SyncTxHashes } from './CrossChainSyncProvider' -import { secs } from '@repo/lib/shared/utils/time' - -const REFETCH_GET_LAYER_ZERO_TX_LINKS_INTERVAL = secs(5).toMs() - -export async function getLayerZeroTxLink(txHash: Address) { - const { messages } = await getMessagesBySrcTxHash(101, txHash) - const message = messages[0] - if (!message) { - console.error('No message found in Layer Zero') - return '' - } - return `https://layerzeroscan.com/tx/${message.srcTxHash}` -} - -export function useLayerZeroTxLinks(syncTxHashes: Record) { - const [syncLayerZeroTxLinks, setSyncLayerZeroTxLinks] = useState({} as Record) - const { userAddress } = useUserAccount() - - const getLayerZeroTxLinkOnInterval = useCallback( - (networks: GqlChain[]) => { - let retryCount = 0 - - async function updateLinks() { - for (const network of networks) { - const hash = syncTxHashes[userAddress]?.[network] - if (hash) { - const link = await getLayerZeroTxLink(hash) - setSyncLayerZeroTxLinks(prev => ({ ...prev, [network]: link })) - } - } - } - - function shouldStopRetrying() { - const allLinksFetched = networks.every(network => syncLayerZeroTxLinks[network]) - return allLinksFetched || retryCount > 10 - } - - const intervalId = setInterval(async () => { - await updateLinks() - retryCount++ - if (shouldStopRetrying()) { - clearInterval(intervalId) - } - }, REFETCH_GET_LAYER_ZERO_TX_LINKS_INTERVAL) - - return intervalId - }, - [userAddress, syncTxHashes, syncLayerZeroTxLinks] - ) - - useEffect(() => { - const networks = Object.keys(syncTxHashes[userAddress] || {}) as GqlChain[] - let intervalId: NodeJS.Timeout - if (networks.length > 0) { - intervalId = getLayerZeroTxLinkOnInterval(networks) - } - return () => { - if (intervalId) { - clearInterval(intervalId as unknown as number) - } - } - }, [syncTxHashes, userAddress, getLayerZeroTxLinkOnInterval]) - - return { syncLayerZeroTxLinks } -} diff --git a/apps/frontend-v3/lib/vebal/cross-chain/useOmniEscrowLocksQuery.ts b/apps/frontend-v3/lib/vebal/cross-chain/useOmniEscrowLocksQuery.ts deleted file mode 100644 index 697354d328..0000000000 --- a/apps/frontend-v3/lib/vebal/cross-chain/useOmniEscrowLocksQuery.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' -import { useQuery } from '@tanstack/react-query' - -export interface OmniEscrowLock { - id: string - localUser: { - id: string - } - remoteUser: string - bias: string - slope: string - dstChainId: string -} - -export interface OmniEscrowLockResponse { - omniVotingEscrowLocks: OmniEscrowLock[] -} - -export function useOmniEscrowLocksQuery() { - const { userAddress } = useUserAccount() - const queryKey = ['OmniEscrowLocks', userAddress] - - async function fetchOmniEscrowLocks(): Promise { - return { - omniVotingEscrowLocks: [], - } - } - - const isEnabled = !!userAddress - - return useQuery({ - queryKey, - queryFn: fetchOmniEscrowLocks, - enabled: isEnabled, - refetchOnWindowFocus: false, - }) -} diff --git a/apps/frontend-v3/lib/vebal/cross-chain/useVotingEscrowLocksQueries.ts b/apps/frontend-v3/lib/vebal/cross-chain/useVotingEscrowLocksQueries.ts deleted file mode 100644 index f53838f37c..0000000000 --- a/apps/frontend-v3/lib/vebal/cross-chain/useVotingEscrowLocksQueries.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { useQueries } from '@tanstack/react-query' -import { useMemo } from 'react' - -export interface VotingEscrowLock { - id: string - slope: string - bias: string - timestamp: number - votingEscrowID: { - id: string - } - updatedAt: number -} - -export interface VotingEscrowLockResponse { - votingEscrowLocks: VotingEscrowLock[] -} - -export function useVotingEscrowLocksQueries(addresses: string[]) { - const votingEscrowLocksQueryEnabled = useMemo(() => { - return addresses.some(Boolean) - }, [addresses]) - - const fetchVotingEscrowLocks = async (): Promise => { - return { - votingEscrowLocks: [], - } - } - - return useQueries({ - queries: addresses.map(address => { - return { - queryKey: ['VotingEscrowLocksByNetworkId', address], - queryFn: fetchVotingEscrowLocks, - enabled: votingEscrowLocksQueryEnabled, - refetchOnWindowFocus: false, - } - }), - }) -} diff --git a/apps/frontend-v3/lib/vebal/lock/VebalExtend.tsx b/apps/frontend-v3/lib/vebal/lock/VebalExtend.tsx deleted file mode 100644 index 1783ba0252..0000000000 --- a/apps/frontend-v3/lib/vebal/lock/VebalExtend.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { VebalLockForm } from '@bal/lib/vebal/lock/form/VebalLockForm' -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' -import { ConnectWallet } from '@repo/lib/modules/web3/ConnectWallet' -import { Box, Center } from '@chakra-ui/react' - -export function VebalExtend() { - const { isConnected } = useUserAccount() - - if (!isConnected) { - return ( -
- - - -
- ) - } - - return -} diff --git a/apps/frontend-v3/lib/vebal/lock/VebalLock.tsx b/apps/frontend-v3/lib/vebal/lock/VebalLock.tsx deleted file mode 100644 index 679037d56a..0000000000 --- a/apps/frontend-v3/lib/vebal/lock/VebalLock.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { VebalLockForm } from '@bal/lib/vebal/lock/form/VebalLockForm' -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' -import { ConnectWallet } from '@repo/lib/modules/web3/ConnectWallet' -import { Box, Center } from '@chakra-ui/react' - -export function VebalLock() { - const { isConnected } = useUserAccount() - - if (!isConnected) { - return ( -
- - - -
- ) - } - - return -} diff --git a/apps/frontend-v3/lib/vebal/lock/VebalLockDetailsAccordion.tsx b/apps/frontend-v3/lib/vebal/lock/VebalLockDetailsAccordion.tsx deleted file mode 100644 index 28bf3ba349..0000000000 --- a/apps/frontend-v3/lib/vebal/lock/VebalLockDetailsAccordion.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { - Accordion, - AccordionItem, - AccordionButton, - AccordionPanel, - Box, - HStack, - Text, - AccordionIcon, -} from '@chakra-ui/react' -import { ReactNode } from 'react' - -interface VebalLockDetailsAccordionProps { - accordionPanelComponent: ReactNode - isDisabled?: boolean -} -export function VebalLockDetailsAccordion({ - accordionPanelComponent, - isDisabled, -}: VebalLockDetailsAccordionProps) { - return ( - - - -

- - - - - veBAL details - - - - - -

- {accordionPanelComponent} -
-
-
- ) -} diff --git a/apps/frontend-v3/lib/vebal/lock/VebalUnlock.tsx b/apps/frontend-v3/lib/vebal/lock/VebalUnlock.tsx index 7428d3df38..a5412eb002 100644 --- a/apps/frontend-v3/lib/vebal/lock/VebalUnlock.tsx +++ b/apps/frontend-v3/lib/vebal/lock/VebalUnlock.tsx @@ -2,12 +2,9 @@ import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' import { ConnectWallet } from '@repo/lib/modules/web3/ConnectWallet' import { Center } from '@chakra-ui/react' import { VebalUnlockForm } from '@bal/lib/vebal/lock/form/VebalUnlockForm' -import { VebalLockForm } from '@bal/lib/vebal/lock/form/VebalLockForm' -import { useVebalLockData } from '@repo/lib/modules/vebal/VebalLockDataProvider' export function VebalUnlock() { const { isConnected } = useUserAccount() - const { mainnetLockedInfo } = useVebalLockData() if (!isConnected) { return ( @@ -17,9 +14,5 @@ export function VebalUnlock() { ) } - if (mainnetLockedInfo.hasExistingLock && !mainnetLockedInfo.isExpired) { - return - } - return } diff --git a/apps/frontend-v3/lib/vebal/lock/duration/LockDurationSlider.tsx b/apps/frontend-v3/lib/vebal/lock/duration/LockDurationSlider.tsx deleted file mode 100644 index 502f449094..0000000000 --- a/apps/frontend-v3/lib/vebal/lock/duration/LockDurationSlider.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { Box, BoxProps, Button, HStack } from '@chakra-ui/react' -import { - SliderWithSteps, - SliderWithStepsProps, -} from '@repo/lib/shared/components/inputs/SliderWithSteps' - -export interface LockDurationSliderProps { - value: number - onChange: SliderWithStepsProps['onChange'] - min: SliderWithStepsProps['min'] - max: SliderWithStepsProps['max'] - step: SliderWithStepsProps['step'] - boxProps?: BoxProps - steps: number[] - minValue?: number - onSliderChange: (value: number) => void -} - -export function LockDurationSlider({ - value, - onChange, - min, - max, - step, - boxProps, - steps, - minValue, - onSliderChange, -}: LockDurationSliderProps) { - const sliderMaxed = value === max - - return ( - - - - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/lock/form/VebalLockForm.tsx b/apps/frontend-v3/lib/vebal/lock/form/VebalLockForm.tsx deleted file mode 100644 index baf99c49a5..0000000000 --- a/apps/frontend-v3/lib/vebal/lock/form/VebalLockForm.tsx +++ /dev/null @@ -1,303 +0,0 @@ -'use client' -import { TooltipWithTouch } from '@repo/lib/shared/components/tooltips/TooltipWithTouch' -import { - Box, - Button, - Card, - CardHeader, - Grid, - GridItem, - HStack, - Skeleton, - Text, - Tooltip, - VStack, -} from '@chakra-ui/react' -import { TokenInput } from '@repo/lib/modules/tokens/TokenInput/TokenInput' -import { HumanAmount } from '@balancer/sdk' -import { LockDurationSlider } from '@bal/lib/vebal/lock/duration/LockDurationSlider' -import { VebalLockDetailsAccordion } from '@bal/lib/vebal/lock/VebalLockDetailsAccordion' -import { VebalLockDetails } from '@bal/lib/vebal/lock/VebalLockDetails' -import { expectedTotalVeBal, LockMode, useVebalLock } from '@bal/lib/vebal/lock/VebalLockProvider' -import { fNum, isZero } from '@repo/lib/shared/utils/numbers' -import { VebalLockModal } from '@bal/lib/vebal/lock/modal/VebalLockModal' -import { useRouter } from 'next/navigation' -import { getModalLabel } from '@bal/lib/vebal/lock/steps/lock-steps.utils' -import { useState } from 'react' -import { ClickableText } from '@repo/lib/shared/components/typography/ClickableText' -import { Address } from 'viem' -import { TokenRowWithDetails } from '@repo/lib/modules/tokens/TokenRow/TokenRowWithDetails' -import { BalAlert } from '@repo/lib/shared/components/alerts/BalAlert' -import { BalAlertLink } from '@repo/lib/shared/components/alerts/BalAlertLink' -import { useTokenBalances } from '@repo/lib/modules/tokens/TokenBalancesProvider' -import { WeeklyYieldTooltip } from './WeeklyYieldTooltip' -import { useGetPoolRewards } from '@repo/lib/modules/pool/useGetPoolRewards' -import { useVeBALPool } from '../../vote/Votes/MyVotes/MyVotesStats/useVeBALPool' -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' -import { GqlPoolAprItem } from '@repo/lib/shared/services/api/generated/graphql' -import { useTokens } from '@repo/lib/modules/tokens/TokensProvider' -import { Pool } from '@repo/lib/modules/pool/pool.types' -import { bn } from '@repo/lib/shared/utils/numbers' -import { useVebalLockData } from '@repo/lib/modules/vebal/VebalLockDataProvider' -import { ArrowRight } from 'react-feather' - -type Props = { - editAlwaysOn?: boolean -} - -export function VebalLockForm({ editAlwaysOn = false }: Props) { - const { refetchAll } = useVebalLockData() - const { - vebalBptToken, - lpToken, - setLpToken, - resetLpToken, - previousLockEnd, - lockedAmount, - totalAmount, - isDisabled, - disabledReason, - previewModalDisclosure, - lockDuration, - isLoading, - lockMode, - expectedVeBalAmount, - } = useVebalLock() - - const router = useRouter() - - const { balanceFor } = useTokenBalances() - const bptBalance = balanceFor(vebalBptToken.address) - - function onModalClose(isSuccess: boolean, redirectPath: string) { - previewModalDisclosure.onClose() - refetchAll() - if (isSuccess) { - router.push(redirectPath) - } - } - - const [isEditingAmount, setIsEditingAmount] = useState(editAlwaysOn) - - const onEditAmountToggle = (value: boolean) => { - setIsEditingAmount(value) - if (!value) { - resetLpToken() - } - } - - const unlockingMode = LockMode.Unlock === lockMode - - const amountMode: 'edit' | 'show' = unlockingMode ? 'show' : isEditingAmount ? 'edit' : 'show' - - const { userAddress } = useUserAccount() - const { pool, poolIsLoading } = useVeBALPool(userAddress) - const { calculatePotentialYield } = useGetPoolRewards(pool || ({} as Pool)) - const { usdValueForToken } = useTokens() - const totalUsdValue = usdValueForToken(vebalBptToken, expectedVeBalAmount.totalExpectedVeBal) - const weeklyYield = !poolIsLoading ? calculatePotentialYield(totalUsdValue) : '0' - - const hasLockedAmount = lockedAmount && Number(lockedAmount) > 0 - const currentVeBALAmount = previousLockEnd - ? expectedTotalVeBal({ bpt: lockedAmount || '0', lockEndDate: previousLockEnd }) - : bn(0) - const currentTotalUsdValue = usdValueForToken(vebalBptToken, currentVeBALAmount || 0) - const currentWeeklyYield = !poolIsLoading ? calculatePotentialYield(currentTotalUsdValue) : '0' - - return ( - - - - - - {getModalLabel(lockMode, editAlwaysOn, true)} - - - - - {bptBalance !== undefined && isZero(bptBalance.amount) && isZero(totalAmount) && ( - - {`You need to lock the LP token of the `} - - 80% BAL / 20% WETH pool - - {` to get veBAL. This is the special Balancer protocol liquidity pool. Come back to this step once you have acquired some of these LP tokens by adding liquidity to the `} - - pool - - . - - } - status="info" - /> - )} - - - - {isLoading ? ( - - ) : ( - - {amountMode === 'show' - ? 'Locked amount' - : hasLockedAmount - ? 'Amount to add to your existing lock' - : 'Amount to lock'} - - )} - - {!isLoading && !unlockingMode && !editAlwaysOn && ( - onEditAmountToggle(!isEditingAmount)} - > - {amountMode === 'show' ? 'Edit amount' : 'Cancel'} - - )} - - - {amountMode === 'edit' && ( - setLpToken(e.currentTarget.value as HumanAmount)} - value={lpToken || ''} - /> - )} - - {amountMode === 'show' && ( - - - Lock-up period ended - , - - {lockDuration.lockedUntilDateFormatted} - , - ], - ] - : undefined - } - value={lockedAmount ?? ''} - /> - - )} - - - - - - - Lock duration - - - - - - {lockDuration.lockUntilDateDuration} - - - - - - - - } - /> - - - - - - - Total veBAL - - - - {`${fNum('token', currentVeBALAmount)}`} - - - - - - {fNum('token', expectedVeBalAmount.totalExpectedVeBal)} - - - - - - - {poolIsLoading ? ( - - ) : ( - - )} - - - - - - - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/lock/form/VebalUnlockForm.tsx b/apps/frontend-v3/lib/vebal/lock/form/VebalUnlockForm.tsx index fd64988627..93a9fa038a 100644 --- a/apps/frontend-v3/lib/vebal/lock/form/VebalUnlockForm.tsx +++ b/apps/frontend-v3/lib/vebal/lock/form/VebalUnlockForm.tsx @@ -60,7 +60,7 @@ export function VebalUnlockForm() { - {getModalLabel(LockMode.Unlock, false, false)} + {getModalLabel(LockMode.Unlock)} @@ -149,11 +149,7 @@ export function VebalUnlockForm() { - + ) } diff --git a/apps/frontend-v3/lib/vebal/lock/form/WeeklyYieldTooltip.tsx b/apps/frontend-v3/lib/vebal/lock/form/WeeklyYieldTooltip.tsx deleted file mode 100644 index 77f157aa01..0000000000 --- a/apps/frontend-v3/lib/vebal/lock/form/WeeklyYieldTooltip.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { Card, HStack, PopoverArrow, PopoverContent, Text, VStack } from '@chakra-ui/react' -import { useCurrency } from '@repo/lib/shared/hooks/useCurrency' -import { useCallback } from 'react' -import { bn } from '@repo/lib/shared/utils/numbers' -import BigNumber from 'bignumber.js' -import { Pool } from '@repo/lib/modules/pool/pool.types' -import BaseAprTooltip, { - BaseAprTooltipProps, -} from '@repo/lib/shared/components/tooltips/apr-tooltip/BaseAprTooltip' -import { SparklesIcon } from '@repo/lib/shared/components/tooltips/apr-tooltip/MainAprTooltip' -import { ArrowRight } from 'react-feather' - -interface Props extends Omit< - BaseAprTooltipProps, - | 'children' - | 'totalBaseText' - | 'totalBaseVeBalText' - | 'maxVeBalText' - | 'poolId' - | 'poolType' - | 'chain' -> { - totalUsdValue: string - weeklyYield: string - currentWeeklyYield: string - pool: Pool -} - -export function WeeklyYieldTooltip({ - weeklyYield, - currentWeeklyYield, - totalUsdValue, - pool, - ...props -}: Props) { - const { toCurrency } = useCurrency() - - const numberFormatter = useCallback( - (value: string) => bn(value).times(totalUsdValue).dividedBy(52), - [totalUsdValue] - ) - - const displayValueFormatter = useCallback( - (value: BigNumber) => toCurrency(value.toString(), { abbreviated: false }), - [toCurrency] - ) - - const customPopoverContent = - totalUsdValue === '0' ? ( - - - Enter some amounts of liquidity to add to simulate your potential weekly yield. - - - - ) : undefined - - return ( - - - - - - Potential weekly yield - - - - {toCurrency(currentWeeklyYield, { abbreviated: false })} - - - - - - {toCurrency(weeklyYield, { abbreviated: false })} - - - - - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/lock/modal/VebalLockModal.tsx b/apps/frontend-v3/lib/vebal/lock/modal/VebalLockModal.tsx index c8210c7764..eba1507048 100644 --- a/apps/frontend-v3/lib/vebal/lock/modal/VebalLockModal.tsx +++ b/apps/frontend-v3/lib/vebal/lock/modal/VebalLockModal.tsx @@ -16,9 +16,8 @@ import { TransactionModalHeader } from '@repo/lib/shared/components/modals/Trans import { ActionModalFooter } from '@repo/lib/shared/components/modals/ActionModalFooter' import { SuccessOverlay } from '@repo/lib/shared/components/modals/SuccessOverlay' import { GqlChain } from '@repo/lib/shared/services/api/generated/graphql' -import { LockMode, useVebalLock } from '@bal/lib/vebal/lock/VebalLockProvider' +import { useVebalLock } from '@bal/lib/vebal/lock/VebalLockProvider' import { Address } from 'viem' -import { VebalLockDetails } from '@bal/lib/vebal/lock/VebalLockDetails' import { AnimateHeightChange } from '@repo/lib/shared/components/animations/AnimateHeightChange' import { useRouter } from 'next/navigation' import { @@ -36,13 +35,11 @@ import { useVebalLockData } from '@repo/lib/modules/vebal/VebalLockDataProvider' type Props = { isOpen: boolean onClose(isSuccess: boolean, redirectPath: string): void - extendExpired: boolean } export function VebalLockModal({ isOpen, onClose, - extendExpired, ...rest }: Props & Omit) { const router = useRouter() @@ -56,7 +53,6 @@ export function VebalLockModal({ lpToken, lockDuration, lockMode, - isIncreasedLockAmount, isLoading: vebalLockIsLoading, } = useVebalLock() const { mainnetLockedInfo, isLoading: vebalLockDataIsLoading } = useVebalLockData() @@ -69,21 +65,17 @@ export function VebalLockModal({ const buildLockStepsArgs = useMemo( () => ({ - extendExpired, totalAmount: addedAmount, lockDuration, - isIncreasedLockAmount, mainnetLockedInfo, }), - [addedAmount, extendExpired, isIncreasedLockAmount, lockDuration, mainnetLockedInfo] + [addedAmount, lockDuration, mainnetLockedInfo] ) const { transactionSteps, lockTxHash } = useBuildLockSteps(buildLockStepsArgs) const isLoading = vebalLockIsLoading || vebalLockDataIsLoading || userAccountIsLoading - const isUnlocking = lockMode === LockMode.Unlock && !extendExpired - const isSuccess = !!lockTxHash return ( @@ -105,7 +97,7 @@ export function VebalLockModal({ ) : null} @@ -119,13 +111,13 @@ export function VebalLockModal({ ) : ( <> - {isUnlocking ? 'Locked amount' : 'Lock amount'} + {'Locked amount'} @@ -142,14 +134,6 @@ export function VebalLockModal({ /> - {!isUnlocking && ( - - Summary - - - - - )} )} diff --git a/apps/frontend-v3/lib/vebal/lock/steps/lock-steps.utils.ts b/apps/frontend-v3/lib/vebal/lock/steps/lock-steps.utils.ts index 5fa842fdea..5f17e72113 100644 --- a/apps/frontend-v3/lib/vebal/lock/steps/lock-steps.utils.ts +++ b/apps/frontend-v3/lib/vebal/lock/steps/lock-steps.utils.ts @@ -1,21 +1,10 @@ import { toUtcTime } from '@repo/lib/shared/utils/time' import { LockMode } from '@bal/lib/vebal/lock/VebalLockProvider' -import { fNum } from '@repo/lib/shared/utils/numbers' -import { format } from 'date-fns' -import { PRETTY_DATE_FORMAT } from '@bal/lib/vebal/lock/duration/lock-duration.constants' -import { formatUnits } from 'viem' import { LockActionType } from '@repo/lib/modules/vebal/vote/vote.types' -type LockContractFunctionName = - | 'create_lock' - | 'increase_amount' - | 'increase_unlock_time' - | 'withdraw' +type LockContractFunctionName = 'withdraw' const contractFunctionNames: Record = { - [LockActionType.CreateLock]: 'create_lock', - [LockActionType.IncreaseLock]: 'increase_amount', - [LockActionType.ExtendLock]: 'increase_unlock_time', [LockActionType.Unlock]: 'withdraw', } @@ -27,33 +16,18 @@ export function parseDate(date: string) { return (toUtcTime(new Date(date)) / 1000).toString() } -export function getModalLabel(mode: LockMode, editAlwaysOn: boolean, extendExpired: boolean) { +export function getModalLabel(mode: LockMode) { switch (mode) { - case LockMode.Lock: - return 'Lock to get veBAL' - case LockMode.Extend: - if (editAlwaysOn) return 'Lock to get more veBAL' - return 'Extend lock' case LockMode.Unlock: - if (extendExpired) { - return 'Extend expired lock' - } return 'veBAL expiry' default: return '' } } -export function getPreviewLabel(mode: LockMode, extendExpired: boolean) { +export function getPreviewLabel(mode: LockMode) { switch (mode) { - case LockMode.Lock: - return 'Lock' - case LockMode.Extend: - return 'Extend lock' case LockMode.Unlock: - if (extendExpired) { - return 'Extend expired lock' - } return 'Unlock' default: return '' @@ -62,12 +36,6 @@ export function getPreviewLabel(mode: LockMode, extendExpired: boolean) { export function getInitLabel(lockActionType: LockActionType) { switch (lockActionType) { - case LockActionType.CreateLock: - return 'Create Lock' - case LockActionType.ExtendLock: - return 'Extend Lock' - case LockActionType.IncreaseLock: - return 'Increase Lock' case LockActionType.Unlock: return 'Unlock' default: @@ -77,12 +45,6 @@ export function getInitLabel(lockActionType: LockActionType) { export function getDescription(lockActionType: LockActionType) { switch (lockActionType) { - case LockActionType.CreateLock: - return 'Create a new lock for the specified amount and end date.' - case LockActionType.ExtendLock: - return 'Extend an existing lock.' - case LockActionType.IncreaseLock: - return 'Increase the amount of an existing lock.' case LockActionType.Unlock: return 'Unlock' default: @@ -92,12 +54,6 @@ export function getDescription(lockActionType: LockActionType) { export function getConfirmingLabel(lockActionType: LockActionType) { switch (lockActionType) { - case LockActionType.CreateLock: - return 'Confirming lock creation...' - case LockActionType.ExtendLock: - return 'Confirming lock extension...' - case LockActionType.IncreaseLock: - return 'Confirming lock increase...' case LockActionType.Unlock: return 'Confirming unlock...' default: @@ -105,20 +61,8 @@ export function getConfirmingLabel(lockActionType: LockActionType) { } } -export function getConfirmedLabel( - lockActionType: LockActionType, - lockAmount: bigint, - lockEndDate: string -) { - const formattedAmount = formatUnits(lockAmount, 18) - +export function getConfirmedLabel(lockActionType: LockActionType) { switch (lockActionType) { - case LockActionType.CreateLock: - return `Lock created for ${fNum('token', formattedAmount)} tokens until ${format(new Date(lockEndDate), PRETTY_DATE_FORMAT)}` - case LockActionType.ExtendLock: - return `Lock extended until ${format(new Date(lockEndDate), PRETTY_DATE_FORMAT)}` - case LockActionType.IncreaseLock: - return `Lock amount increased by ${fNum('token', formattedAmount)}` case LockActionType.Unlock: return 'Lock unlocked' default: @@ -128,12 +72,6 @@ export function getConfirmedLabel( export function getTooltip(lockActionType: LockActionType) { switch (lockActionType) { - case LockActionType.CreateLock: - return 'Create a new lock for the specified amount and end date.' - case LockActionType.ExtendLock: - return 'Extend the end date of an existing lock.' - case LockActionType.IncreaseLock: - return 'Increase the amount of an existing lock.' case LockActionType.Unlock: return 'Unlock the specified lock.' default: diff --git a/apps/frontend-v3/lib/vebal/lock/steps/useBuildLockSteps.tsx b/apps/frontend-v3/lib/vebal/lock/steps/useBuildLockSteps.tsx index b4533d7c76..2b93c81eea 100644 --- a/apps/frontend-v3/lib/vebal/lock/steps/useBuildLockSteps.tsx +++ b/apps/frontend-v3/lib/vebal/lock/steps/useBuildLockSteps.tsx @@ -1,4 +1,3 @@ -import { useMemo } from 'react' import { useLockSteps } from '@bal/lib/vebal/lock/steps/useLockSteps' import { Address, parseUnits } from 'viem' import { useTransactionSteps } from '@repo/lib/modules/transactions/transaction-steps/useTransactionSteps' @@ -7,45 +6,17 @@ import { UseVebalLockDataResult } from '@repo/lib/modules/vebal/VebalLockDataPro import { LockActionType } from '@repo/lib/modules/vebal/vote/vote.types' export interface UseBuildLockStepsArgs { - extendExpired: boolean totalAmount: UseVebalLockResult['totalAmount'] lockDuration: UseVebalLockResult['lockDuration'] - isIncreasedLockAmount: UseVebalLockResult['isIncreasedLockAmount'] mainnetLockedInfo: UseVebalLockDataResult['mainnetLockedInfo'] } -export function useBuildLockSteps({ - extendExpired, - lockDuration, - isIncreasedLockAmount, - totalAmount, - mainnetLockedInfo, -}: UseBuildLockStepsArgs) { +export function useBuildLockSteps({ lockDuration, totalAmount }: UseBuildLockStepsArgs) { const { vebalBptToken } = useVebalLock() - const lockActionTypes = useMemo(() => { - if (mainnetLockedInfo.isExpired) { - if (extendExpired) { - return [LockActionType.Unlock, LockActionType.CreateLock] - } else { - return [LockActionType.Unlock] - } - } - if (mainnetLockedInfo.hasExistingLock && !mainnetLockedInfo.isExpired) { - if (isIncreasedLockAmount && lockDuration.isExtendedLockEndDate) { - return [LockActionType.IncreaseLock, LockActionType.ExtendLock] - } - if (lockDuration.isExtendedLockEndDate) { - return [LockActionType.ExtendLock] - } - if (isIncreasedLockAmount) { - return [LockActionType.IncreaseLock] - } - } - return [LockActionType.CreateLock] - }, [mainnetLockedInfo, isIncreasedLockAmount, lockDuration.isExtendedLockEndDate, extendExpired]) + const lockActionTypes = [LockActionType.Unlock] - const { steps, isLoadingSteps } = useLockSteps({ + const { steps } = useLockSteps({ lockAmount: { rawAmount: parseUnits(totalAmount.toString(), vebalBptToken.decimals), address: vebalBptToken.address as Address, @@ -54,7 +25,7 @@ export function useBuildLockSteps({ lockEndDate: lockDuration.lockEndDate.toString(), }) - const transactionSteps = useTransactionSteps(steps, isLoadingSteps) + const transactionSteps = useTransactionSteps(steps, false) const lockTxHash = transactionSteps.lastTransaction?.result?.data?.transactionHash diff --git a/apps/frontend-v3/lib/vebal/lock/steps/useLockStep.integration.spec.tsx b/apps/frontend-v3/lib/vebal/lock/steps/useLockStep.integration.spec.tsx deleted file mode 100644 index 8db70e56dc..0000000000 --- a/apps/frontend-v3/lib/vebal/lock/steps/useLockStep.integration.spec.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { ChainId } from '@balancer/sdk' -import mainnetNetworkConfig from '@repo/lib/config/networks/mainnet' -import { TransactionStateProvider } from '@repo/lib/modules/transactions/transaction-steps/TransactionStateProvider' -import { - ManagedTransactionInput, - useManagedTransaction, -} from '@repo/lib/modules/web3/contracts/useManagedTransaction' -import { defaultTestUserAccount } from '@repo/test/anvil/anvil-setup' -import { approveToken, resetBlock, setVeBalBptBalance } from '@repo/lib/test/integration/sdk-utils' -import { testHook, waitForSimulationSuccess } from '@repo/lib/test/utils/custom-renderers' -import { connectAndImpersonate } from '@repo/test/utils/wagmi/wagmi-connections' -import { mainnetTestPublicClient } from '@repo/test/utils/wagmi/wagmi-test-clients' -import { Address, parseUnits } from 'viem' -import { useLockStep } from './useLockStep' -import { getMaxLockEndDate } from '../lock-time.utils' -import { PropsWithChildren } from 'react' -import { TokenBalancesProvider } from '@repo/lib/modules/tokens/TokenBalancesProvider' -import { LockActionType } from '@repo/lib/modules/vebal/vote/vote.types' - -const lockAmount: bigint = parseUnits('3000', 18) -const veBalContractAddress = mainnetNetworkConfig.contracts.veBAL as Address -const veBalBpt = mainnetNetworkConfig.tokens.addresses.veBalBpt as Address - -function Providers({ children }: PropsWithChildren) { - return ( - - {children} - - ) -} - -function testUseLockStep() { - const { result } = testHook( - () => { - const { _txInput } = useLockStep({ - lockActionType: LockActionType.CreateLock, - lockAmount, - lockEndDate: getMaxLockEndDate(new Date()).toString(), - }) - - return useManagedTransaction(_txInput as ManagedTransactionInput) - }, - { wrapper: Providers } - ) - - return result -} - -test('Lock big veBal amount', async () => { - const veBalHolder = defaultTestUserAccount - - await resetBlock({ - client: mainnetTestPublicClient, - chain: ChainId.MAINNET, - }) - - await connectAndImpersonate(veBalHolder, ChainId.MAINNET) - - await setVeBalBptBalance({ - account: veBalHolder, - balance: lockAmount, - }) - - await approveToken({ - client: mainnetTestPublicClient, - account: veBalHolder, - token: veBalBpt, - spender: veBalContractAddress, - amount: lockAmount, - }) - - const result = testUseLockStep() - await waitForSimulationSuccess(result) -}) diff --git a/apps/frontend-v3/lib/vebal/lock/steps/useLockStep.tsx b/apps/frontend-v3/lib/vebal/lock/steps/useLockStep.tsx index c2597aa53d..ec9e36e651 100644 --- a/apps/frontend-v3/lib/vebal/lock/steps/useLockStep.tsx +++ b/apps/frontend-v3/lib/vebal/lock/steps/useLockStep.tsx @@ -14,7 +14,6 @@ import { getConfirmedLabel, getInitLabel, getTooltip, - parseDate, getLockContractFunctionName, } from './lock-steps.utils' import { useTokenBalances } from '@repo/lib/modules/tokens/TokenBalancesProvider' @@ -39,7 +38,7 @@ export function useLockStep({ lockAmount, lockEndDate, lockActionType }: UseLock title: getInitLabel(lockActionType), description: getDescription(lockActionType), confirming: getConfirmingLabel(lockActionType), - confirmed: getConfirmedLabel(lockActionType, lockAmount, lockEndDate), + confirmed: getConfirmedLabel(lockActionType), tooltip: getTooltip(lockActionType), }), [lockActionType, lockAmount, lockEndDate] @@ -56,19 +55,6 @@ export function useLockStep({ lockAmount, lockEndDate, lockActionType }: UseLock ) const props: ManagedTransactionInput = useMemo(() => { - function getArgs() { - switch (lockActionType) { - case LockActionType.CreateLock: - return [lockAmount, parseDate(lockEndDate)] - case LockActionType.ExtendLock: - return [parseDate(lockEndDate)] - case LockActionType.IncreaseLock: - return [lockAmount] - default: - return [] - } - } - return { enabled: !!lockEndDate, labels, @@ -76,7 +62,7 @@ export function useLockStep({ lockAmount, lockEndDate, lockActionType }: UseLock contractAddress: mainnetNetworkConfig.contracts.veBAL as Address, contractId: 'balancer.veBAL', functionName: getLockContractFunctionName(lockActionType), - args: getArgs() as any, + args: [], txSimulationMeta, onTransactionChange: setTransaction, } diff --git a/apps/frontend-v3/lib/vebal/lock/steps/useLockSteps.tsx b/apps/frontend-v3/lib/vebal/lock/steps/useLockSteps.tsx index 68cac124e0..3c8decd7d1 100644 --- a/apps/frontend-v3/lib/vebal/lock/steps/useLockSteps.tsx +++ b/apps/frontend-v3/lib/vebal/lock/steps/useLockSteps.tsx @@ -1,9 +1,6 @@ import { RawAmount } from '@repo/lib/modules/tokens/approvals/approval-rules' -import { useTokenApprovalSteps } from '@repo/lib/modules/tokens/approvals/useTokenApprovalSteps' -import mainnetNetworkConfig from '@repo/lib/config/networks/mainnet' import { useLockStep } from '@bal/lib/vebal/lock/steps/useLockStep' import { useMemo } from 'react' -import { Hex } from 'viem' import { LockActionType } from '@repo/lib/modules/vebal/vote/vote.types' import { TransactionStep } from '@repo/lib/modules/transactions/transaction-steps/lib' @@ -18,15 +15,6 @@ export function useLockSteps({ lockEndDate, lockActionTypes, }: UseLockStepsArgs) { - const { isLoading: isLoadingTokenApprovalSteps, steps: tokenApprovalSteps } = - useTokenApprovalSteps({ - spenderAddress: mainnetNetworkConfig.contracts.veBAL as Hex, - chain: mainnetNetworkConfig.chain, - approvalAmounts: [lockRawAmount], - actionType: 'Locking', - lpToken: 'token', - }) - const lockAmount: bigint = lockRawAmount.rawAmount const unlockStep = useLockStep({ @@ -34,48 +22,17 @@ export function useLockSteps({ lockEndDate, lockActionType: LockActionType.Unlock, }) - const createLockStep = useLockStep({ - lockAmount, - lockEndDate, - lockActionType: LockActionType.CreateLock, - }) - const increaseLockStep = useLockStep({ - lockAmount, - lockEndDate, - lockActionType: LockActionType.IncreaseLock, - }) - const extendLockStep = useLockStep({ - lockAmount, - lockEndDate, - lockActionType: LockActionType.ExtendLock, - }) const steps = useMemo(() => { - const lockSteps = [unlockStep, createLockStep, extendLockStep, increaseLockStep] + const lockSteps = [unlockStep] const selectedLockSteps = lockActionTypes .map(lockActionType => lockSteps.find(lockStep => lockStep.stepType === lockActionType)) .filter(Boolean) as TransactionStep[] - if ( - !lockActionTypes.includes(LockActionType.CreateLock) && - !lockActionTypes.includes(LockActionType.IncreaseLock) - ) { - // Avoid token approvals when extending lock date without increasing amount - return [...selectedLockSteps] - } - - return [...tokenApprovalSteps, ...selectedLockSteps] - }, [ - tokenApprovalSteps, - unlockStep, - createLockStep, - extendLockStep, - increaseLockStep, - lockActionTypes, - ]) + return [...selectedLockSteps] + }, [unlockStep, lockActionTypes]) return { - isLoadingSteps: isLoadingTokenApprovalSteps, steps, } } diff --git a/apps/frontend-v3/lib/vebal/useGaugeWorkingBalanceQuery.ts b/apps/frontend-v3/lib/vebal/useGaugeWorkingBalanceQuery.ts deleted file mode 100644 index 33b903a751..0000000000 --- a/apps/frontend-v3/lib/vebal/useGaugeWorkingBalanceQuery.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { useReadContract } from '@repo/lib/shared/utils/wagmi' -import { Address } from '@balancer/sdk' -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' -import { GaugeWorkingBalanceHelperAbi } from '@repo/lib/modules/web3/contracts/abi/GaugeWorkingBalanceHelperAbi' - -export function useGaugeWorkingBalanceQuery( - chainId: number, - contractAddress: Address, - gauge: string, - userAddress: string -) { - const { isConnected } = useUserAccount() - - return useReadContract({ - chainId: chainId, - address: contractAddress, - abi: GaugeWorkingBalanceHelperAbi, - functionName: 'getWorkingBalanceToSupplyRatios', - args: [gauge, userAddress], - query: { - enabled: isConnected, - }, - }) -} diff --git a/apps/frontend-v3/lib/vebal/useMaxAmountOfVeBal.integration.spec.tsx b/apps/frontend-v3/lib/vebal/useMaxAmountOfVeBal.integration.spec.tsx deleted file mode 100644 index 3445edc7c0..0000000000 --- a/apps/frontend-v3/lib/vebal/useMaxAmountOfVeBal.integration.spec.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { ChainId } from '@balancer/sdk' -import { alternativeTestUserAccount } from '@repo/test/anvil/anvil-setup' -import { setVeBalBptBalance } from '@repo/lib/test/integration/sdk-utils' -import { testHook } from '@repo/lib/test/utils/custom-renderers' -import { connectAndImpersonate } from '@repo/test/utils/wagmi/wagmi-connections' -import { waitFor } from '@testing-library/react' -import { parseUnits } from 'viem' -import { TokenBalancesProviderMock } from './__mocks__/provider-mocks' -import { useMaxAmountOfVeBAL } from './useMaxAmountOfVeBal' - -describe('useMaxAmountOfVeBAL hook', () => { - it('should calculate max veBAL amount and percentage', async () => { - const veBalHolder = alternativeTestUserAccount - await connectAndImpersonate(veBalHolder, ChainId.MAINNET) - - const veBalBptBalance: bigint = parseUnits('3000', 18) - - await setVeBalBptBalance({ - account: veBalHolder, - balance: veBalBptBalance, - }) - - const { result } = testHook(() => useMaxAmountOfVeBAL(), { wrapper: TokenBalancesProviderMock }) - await waitFor(() => expect(result.current.isMaxAmountLoading).toBeFalsy()) - - const maxAmount = result.current.maxAmount - expect(maxAmount.isGreaterThan(0)).toBe(true) - expect(maxAmount.isLessThan(5000)).toBe(true) - - const currentVeBalBalance = parseUnits('1000', 18) - const veBalPercentage = result.current.calculateCurrentVeBalPercentage(currentVeBalBalance) - expect(veBalPercentage).toBeGreaterThan(10) - expect(veBalPercentage).toBeLessThan(100) - }) -}) diff --git a/apps/frontend-v3/lib/vebal/useMaxAmountOfVeBal.ts b/apps/frontend-v3/lib/vebal/useMaxAmountOfVeBal.ts deleted file mode 100644 index 80c0da4291..0000000000 --- a/apps/frontend-v3/lib/vebal/useMaxAmountOfVeBal.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { bn } from '@repo/lib/shared/utils/numbers' -import { expectedTotalVeBal } from './lock/VebalLockProvider' -import { useLockEndDate } from './lock/duration/useLockEndDate' -import { formatUnits } from 'viem' -import { useTokens } from '@repo/lib/modules/tokens/TokensProvider' -import { useTokenBalances } from '@repo/lib/modules/tokens/TokenBalancesProvider' -import { useVebalLockData } from '@repo/lib/modules/vebal/VebalLockDataProvider' - -export function useMaxAmountOfVeBAL() { - const { vebalBptToken, isLoadingTokens } = useTokens() - const { balanceFor, isBalancesLoading } = useTokenBalances() - const { mainnetLockedInfo: lockedInfo, isLoading: isLockInfoLoading } = useVebalLockData() - - const lockedBPTAmount = lockedInfo.hasExistingLock ? lockedInfo.lockedAmount : 0 - const bptAmount = vebalBptToken - ? formatUnits(balanceFor(vebalBptToken.address)?.amount || 0n, 18) - : '0' - - const bptTotalAmount = bn(lockedBPTAmount).plus(bn(bptAmount || 0n)) - - const { maxLockEndDate } = useLockEndDate({ - lockedEndDate: lockedInfo.lockedEndDate ? new Date(lockedInfo.lockedEndDate) : undefined, - }) - - const maxAmount = expectedTotalVeBal({ - bpt: bptTotalAmount.toFixed(2), - lockEndDate: maxLockEndDate, - }) - - function calculateCurrentVeBalPercentage(currentBalance: bigint): number { - if (!maxAmount || maxAmount.isZero()) { - return 0 - } - - const currentBalanceBN = bn(formatUnits(currentBalance, 18)) - - const percentage = currentBalanceBN.div(maxAmount).toNumber() * 100 - - return Math.min(percentage, 100) - } - - function isSmallerThanCurrentBalance(currentBalance: bigint): boolean { - const currentBalanceBN = bn(formatUnits(currentBalance, 18)) - - return maxAmount.isLessThan(currentBalanceBN) - } - - const canExtendLock = - bptAmount !== '0' || - (lockedInfo.lockedEndDate && lockedInfo.lockedEndDate < maxLockEndDate.getTime()) - - return { - isMaxAmountLoading: isLoadingTokens || isBalancesLoading || isLockInfoLoading, - maxAmount, - calculateCurrentVeBalPercentage, - isSmallerThanCurrentBalance, - canExtendLock, - } -} diff --git a/apps/frontend-v3/lib/vebal/vote/useVeBALBalance.integration.spec.ts b/apps/frontend-v3/lib/vebal/useVeBALBalance.integration.spec.ts similarity index 100% rename from apps/frontend-v3/lib/vebal/vote/useVeBALBalance.integration.spec.ts rename to apps/frontend-v3/lib/vebal/useVeBALBalance.integration.spec.ts diff --git a/apps/frontend-v3/lib/vebal/vote/useVeBALBalance.ts b/apps/frontend-v3/lib/vebal/useVeBALBalance.ts similarity index 67% rename from apps/frontend-v3/lib/vebal/vote/useVeBALBalance.ts rename to apps/frontend-v3/lib/vebal/useVeBALBalance.ts index b3961fac7a..b162ed063c 100644 --- a/apps/frontend-v3/lib/vebal/vote/useVeBALBalance.ts +++ b/apps/frontend-v3/lib/vebal/useVeBALBalance.ts @@ -22,19 +22,3 @@ export function useVeBALBalance(accountAddress: Address) { queryKey: queryKey as InvalidateQueryFilters, } } - -export function useLastUserSlope(accountAddress: Address) { - const { data, isLoading } = useReadContract({ - chainId: mainnet.id, - abi: veBalAbi, - address: mainnetNetworkConfig.contracts.veBAL, - functionName: 'get_last_user_slope', - args: [accountAddress], - query: { enabled: !!accountAddress }, - }) - - return { - slope: data || 0n, - isLoading, - } -} diff --git a/apps/frontend-v3/lib/vebal/useVebalUserData.ts b/apps/frontend-v3/lib/vebal/useVebalUserData.ts index 2a16cc30c7..d137c9a362 100644 --- a/apps/frontend-v3/lib/vebal/useVebalUserData.ts +++ b/apps/frontend-v3/lib/vebal/useVebalUserData.ts @@ -4,7 +4,7 @@ import { GqlVeBalLockSnapshot, } from '@repo/lib/shared/services/api/generated/graphql' import { useQuery } from '@apollo/client/react' -import { useVeBALBalance } from './vote/useVeBALBalance' +import { useVeBALBalance } from './useVeBALBalance' import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' import { secondsToMilliseconds } from 'framer-motion' diff --git a/apps/frontend-v3/lib/vebal/vebal-navigation.ts b/apps/frontend-v3/lib/vebal/vebal-navigation.ts index 12511371bf..5cdf078691 100644 --- a/apps/frontend-v3/lib/vebal/vebal-navigation.ts +++ b/apps/frontend-v3/lib/vebal/vebal-navigation.ts @@ -1,29 +1,17 @@ -import { useSearchParams } from 'next/navigation' - export const fromManage = 'manage' export const fromVote = 'vote' // Used to determine the redirect path after a vebal lock action -export type VeBalSourcePage = 'manage' | 'vote' -export type VeBalAction = 'lock' | 'unlock' | 'extend' +export type VeBalSourcePage = 'manage' +export type VeBalAction = 'unlock' -export function getVeBalManagePath(veBalAction: VeBalAction, sourcePage: VeBalSourcePage) { - return `/vebal/manage/${veBalAction}?from=${sourcePage}` +export function getVeBalManagePath(veBalAction: VeBalAction) { + return `/vebal/manage/${veBalAction}` } export function useVeBalRedirectPath() { - const searchParams = useSearchParams() - const from = searchParams.get('from') - - let redirectPath = '/vebal/' - let returnLabel = 'Return to veBAL page' - if (from === 'manage') { - redirectPath = '/vebal/manage' - returnLabel = 'Return to veBAL manage' - } else if (from === 'vote') { - redirectPath = '/vebal/vote' - returnLabel = 'Return to veBal voting' - } + const redirectPath = '/vebal/manage' + const returnLabel = 'Return to veBAL manage' return { redirectPath, returnLabel } } diff --git a/apps/frontend-v3/lib/vebal/vote/VoteCapTooltip.tsx b/apps/frontend-v3/lib/vebal/vote/VoteCapTooltip.tsx deleted file mode 100644 index 8d2f55526e..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/VoteCapTooltip.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { - HStack, - Text, - Popover, - PopoverTrigger, - Portal, - PopoverContent, - VStack, -} from '@chakra-ui/react' -import { VoteCapIcon } from '@repo/lib/shared/components/icons/VoteCapIcon' -import { fNum } from '@repo/lib/shared/utils/numbers' -import { VotesState } from '@repo/lib/modules/vebal/vote/vote.types' - -interface Props { - relativeWeightCap: number - votesState?: VotesState - usePortal?: boolean -} - -export function VoteCapTooltip({ relativeWeightCap, votesState, usePortal = true }: Props) { - const votesColor = - votesState === 'normal' ? undefined : votesState === 'close' ? 'font.warning' : 'red.400' - - const voteCapText = - votesState === 'normal' - ? 'vote cap' - : votesState === 'close' - ? 'vote cap is close' - : 'vote cap exceeded' - - const popoverContent = ( - - - - {fNum('apr', relativeWeightCap)} {voteCapText} - - - Governance by veBAL holders have set a cap on this gauge. Any votes that push the vote - above this cap will be disregarded. - - - - ) - return ( - - <> - - - - - - - {usePortal ? {popoverContent} : popoverContent} - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/VoteExpiredTooltip.tsx b/apps/frontend-v3/lib/vebal/vote/VoteExpiredTooltip.tsx deleted file mode 100644 index a2fac33e13..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/VoteExpiredTooltip.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { - Badge, - Popover, - PopoverContent, - PopoverTrigger, - Portal, - Text, - VStack, -} from '@chakra-ui/react' -import { PropsWithChildren } from 'react' - -interface Props { - usePortal?: boolean -} - -export function VoteExpiredTooltip({ usePortal, children }: PropsWithChildren) { - 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 ( - - <> - - {children ?? ( - - Expired - - )} - - - {usePortal ? {popoverContent} : popoverContent} - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/VoteList/SelectedPoolsMenu.tsx b/apps/frontend-v3/lib/vebal/vote/VoteList/SelectedPoolsMenu.tsx deleted file mode 100644 index ccca492222..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/VoteList/SelectedPoolsMenu.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import { useState } from 'react' -import { Box, Button, Divider, HStack, Text, VStack, useColorMode } from '@chakra-ui/react' -import { Maximize2, Minimize2 } from 'react-feather' -import FadeInOnView from '@repo/lib/shared/components/containers/FadeInOnView' -import { motion, AnimatePresence } from 'framer-motion' - -const MIN_VISIBLE_POOLS_COUNT = 0 -const MAX_VISIBLE_POOLS_COUNT = 5 - -interface SelectedPoolsToastProps { - onAddVotesClick?: () => void - votingPools: { title: string; description: string }[] -} - -export function SelectedPoolsMenu({ onAddVotesClick, votingPools }: SelectedPoolsToastProps) { - const [expanded, setExpanded] = useState(false) - const sortedPools = votingPools.toReversed() - const visiblePools = expanded - ? sortedPools.slice(0, MAX_VISIBLE_POOLS_COUNT) - : sortedPools.slice(0, MIN_VISIBLE_POOLS_COUNT) - - const moreCount = sortedPools.length - visiblePools.length - const { colorMode } = useColorMode() - const bgBase = colorMode === 'dark' ? '#000' : '#fff' - - const handleGoToVote = () => { - setExpanded(false) - setTimeout(() => { - const target = document.getElementById('my-votes') - if (target) { - const targetPosition = target.getBoundingClientRect().top + window.pageYOffset - window.scrollTo({ top: targetPosition - 100, behavior: 'smooth' }) - } - }) - onAddVotesClick?.() - } - - return ( - - - - - Pool selection ({sortedPools.length}) - - - {onAddVotesClick && ( - - )} - {sortedPools.length > 1 && ( - setExpanded(v => !v)} - transition="color 0.3s var(--ease-out-cubic)" - > - {expanded ? ( - - - - ) : ( - - - - )} - - )} - - - - {expanded && ( - - - - {visiblePools.map(pool => ( - - {pool.title} - - ))} - {moreCount > 0 && ( - - Plus {moreCount} more... - - )} - - - )} - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/VoteList/VoteList.tsx b/apps/frontend-v3/lib/vebal/vote/VoteList/VoteList.tsx deleted file mode 100644 index 7b15c7cd43..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/VoteList/VoteList.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { VoteListLayout } from './VoteListLayout' -import { VoteListProvider } from '@bal/lib/vebal/vote/VoteList/VoteListProvider' - -export async function VoteList() { - return ( - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListFilters.tsx b/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListFilters.tsx deleted file mode 100644 index 46895c9e9e..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListFilters.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import { useState } from 'react' -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, - ProtocolVersionFilter, -} from '@repo/lib/modules/pool/PoolList/PoolListFilters' -import { useVoteList } from '@bal/lib/vebal/vote/VoteList/VoteListProvider' -import { VoteListSearch } from '@bal/lib/vebal/vote/VoteList/VoteListSearch' -import { PROJECT_CONFIG } from '@repo/lib/config/getProjectConfig' -import { poolTypeLabel } from '@repo/lib/modules/pool/pool.helpers' - -export function useFilterTagsVisible() { - const { - filtersState: { networks, poolTypes, includeExpiredPools, protocolVersion }, - } = useVoteList() - - return networks.length > 0 || poolTypes.length > 0 || includeExpiredPools || !!protocolVersion -} - -export function VoteListFilters() { - const [isPopoverOpen, setIsPopoverOpen] = useState(false) - const { - filtersState: { - resetFilters, - totalFilterCount, - networks: toggledNetworks, - toggleNetwork, - setNetworks, - togglePoolType, - poolTypes, - setPoolTypes, - includeExpiredPools, - toggleIncludeExpiredPools, - protocolVersion, - setProtocolVersion, - activeProtocolVersionTab, - setActiveProtocolVersionTab, - }, - } = useVoteList() - - function _resetFilters() { - resetFilters() - } - - return ( - - - - setIsPopoverOpen(false)} - onOpen={() => setIsPopoverOpen(true)} - placement="bottom-end" - > - - - - - - - - - - {isPopoverOpen ? ( - - - - - Filters - - {totalFilterCount > 0 && ( - - )} - - - - - - Networks - - - - - - Protocol version - - - - - - Pool types - - - - - - Pool gauge display - - - - toggleIncludeExpiredPools(e.target.checked)} - > - - Show expired pools gauges - - - - - - - ) : null} - - - - - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListLayout.tsx b/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListLayout.tsx deleted file mode 100644 index ce386140f6..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListLayout.tsx +++ /dev/null @@ -1,125 +0,0 @@ -'use client' - -import { Box, Heading, Stack, HStack, VStack, useBreakpointValue } from '@chakra-ui/react' -import { motion } from 'framer-motion' -import { bn, fNum } from '@repo/lib/shared/utils/numbers' -import { ErrorBoundary } from 'react-error-boundary' -import { BoundaryError } from '@repo/lib/shared/components/errors/ErrorBoundary' -import { FilterTags } from '@repo/lib/modules/pool/PoolList/PoolListFilters' -import { VoteListTable } from '@bal/lib/vebal/vote/VoteList/VoteListTable/VoteListTable' -import { useVoteList } from '@bal/lib/vebal/vote/VoteList/VoteListProvider' -import { useFilterTagsVisible, VoteListFilters } from '@bal/lib/vebal/vote/VoteList/VoteListFilters' -import { SelectedPoolsMenu } from '@bal/lib/vebal/vote/VoteList/SelectedPoolsMenu' -import { useVotes } from '@bal/lib/vebal/vote/Votes/VotesProvider' -import { StaticToast } from '@repo/lib/shared/components/toasts/StaticToast' -import { useMemo } from 'react' -import { poolTypeLabel } from '@repo/lib/modules/pool/pool.helpers' - -export function VoteListLayout() { - const { - sortedVoteList, - loading, - count, - filtersState: { - networks, - toggleNetwork, - poolTypes, - togglePoolType, - includeExpiredPools, - protocolVersion, - toggleIncludeExpiredPools, - setProtocolVersion, - }, - } = useVoteList() - const { selectedVotingPools, scrollToMyVotes } = useVotes() - const isFilterVisible = useFilterTagsVisible() - const isMd = useBreakpointValue({ base: false, md: true }) - - const variants = { - visible: { - transform: isMd ? 'translateY(-40px)' : 'translateY(0)', - }, - hidden: { - transform: 'translateY(0)', - }, - } - - const SelectedPoolsMenuRender = useMemo(() => { - return function SelectedPoolsRender() { - return ( - ({ - title: selectedVotingPool.poolTokens - .map( - token => `${token.symbol} ${token.weight ? `${bn(token.weight).times(100)}%` : ''}` - ) - .join(' / '), - description: selectedVotingPool.symbol, - }))} - /> - ) - } - }, [scrollToMyVotes, selectedVotingPools]) - - return ( - - - - - - - - - Pool gauge vote list - - - ({fNum('integer', count || 0)}) - - - - - - - - - - - - - - - - 0}>{SelectedPoolsMenuRender} - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListProvider.tsx b/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListProvider.tsx deleted file mode 100644 index 523f04cfde..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListProvider.tsx +++ /dev/null @@ -1,151 +0,0 @@ -'use client' - -import { createContext, PropsWithChildren, useMemo } from 'react' -import { GqlChain } from '@repo/lib/shared/services/api/generated/graphql' -import { useMandatoryContext } from '@repo/lib/shared/utils/contexts' -import { SortVotesBy, VotingPoolWithData } from '@repo/lib/modules/vebal/vote/vote.types' -import { orderBy } from 'lodash' -import { useVoteListFiltersState } from '@bal/lib/vebal/vote/VoteList/useVoteListFiltersState' -import { Sorting } from '@repo/lib/shared/components/tables/SortableHeader' -import { PoolFilterType } from '@repo/lib/modules/pool/pool.types' -import { useVotes } from '@bal/lib/vebal/vote/Votes/VotesProvider' - -function sortVoteList(voteList: VotingPoolWithData[], sortBy: SortVotesBy, order: Sorting) { - return orderBy( - voteList, - [ - value => { - switch (sortBy) { - case SortVotesBy.votes: - return value.gaugeVotes ? Number(value.gaugeVotes.votesNextPeriod) : -1 - case SortVotesBy.bribes: - return value.votingIncentive ? Number(value.votingIncentive.totalValue) : -1 - case SortVotesBy.bribesPerVebal: - return value.votingIncentive ? Number(value.votingIncentive.valuePerVote) : -1 - case SortVotesBy.type: - return value.type - default: - throw new Error(`Unsupported SortVotesBy value (${sortBy})`) - } - }, - value => (value.gaugeVotes ? Number(value.gaugeVotes.votesNextPeriod) : -1), - ], - [order === Sorting.asc ? 'asc' : 'desc', 'desc'] - ) -} - -function filterVoteList( - voteList: VotingPoolWithData[], - textSearch: string, - networks: GqlChain[], - poolTypes: PoolFilterType[], - includeExpiredPools: boolean, - protocolVersion: number | null -) { - let result = voteList - - const _textSearch = textSearch.toLowerCase().trim() - if (_textSearch) { - result = result.filter(value => { - return ( - value.id.toLowerCase().includes(_textSearch) || - value.poolTokens.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(pool => !pool.gauge.isKilled) - } - - if (protocolVersion) { - result = result.filter(pool => pool.protocolVersion === protocolVersion) - } - - return result -} - -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface UseVoteListArgs {} - -// eslint-disable-next-line no-empty-pattern -export function useVoteListLogic({}: UseVoteListArgs) { - const { - votingPools, - incentivesError, - incentivesAreLoading, - votingListLoading, - gaugeVotesIsLoading, - isExpiredGaugesLoading, - } = useVotes() - - const filtersState = useVoteListFiltersState() - - const filteredVoteList = useMemo(() => { - return filterVoteList( - votingPools, - filtersState.searchText, - filtersState.networks, - filtersState.poolTypes, - filtersState.includeExpiredPools, - filtersState.protocolVersion - ) - }, [ - votingPools, - filtersState.searchText, - filtersState.networks, - filtersState.poolTypes, - filtersState.includeExpiredPools, - filtersState.protocolVersion, - ]) - - const sortedVoteList = useMemo(() => { - const sortedList = sortVoteList( - filteredVoteList, - filtersState.sortVotesBy, - filtersState.sorting - ) - - return sortedList.slice( - filtersState.pagination.pageIndex * filtersState.pagination.pageSize, - filtersState.pagination.pageSize * (filtersState.pagination.pageIndex + 1) - ) - }, [filteredVoteList, filtersState.pagination, filtersState.sorting, filtersState.sortVotesBy]) - - return { - filtersState, - sortedVoteList, - votingListLoading, - loading: - votingListLoading || incentivesAreLoading || gaugeVotesIsLoading || isExpiredGaugesLoading, - count: filteredVoteList.length, - incentivesAreLoading, - incentivesError, - gaugeVotesIsLoading, - } -} - -export const VoteListContext = createContext | null>(null) - -export function VoteListProvider({ children, ...props }: PropsWithChildren) { - const hook = useVoteListLogic(props) - - return {children} -} - -export function useVoteList() { - return useMandatoryContext(VoteListContext, 'VoteList') -} diff --git a/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListSearch.tsx b/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListSearch.tsx deleted file mode 100644 index e861c06a5f..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListSearch.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { FormControl, Box } from '@chakra-ui/react' -import { SearchInput } from '@repo/lib/shared/components/inputs/SearchInput' -import { useVoteList } from '@bal/lib/vebal/vote/VoteList/VoteListProvider' - -export function VoteListSearch() { - const { - loading, - filtersState: { searchText, setSearch }, - } = useVoteList() - - return ( - -
- - - -
-
- ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListTable/VoteListTable.tsx b/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListTable/VoteListTable.tsx deleted file mode 100644 index ff37c1aa61..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListTable/VoteListTable.tsx +++ /dev/null @@ -1,73 +0,0 @@ -'use client' - -import { PaginatedTable } from '@repo/lib/shared/components/tables/PaginatedTable' -import { VoteListTableHeader } from './VoteListTableHeader' -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 { VotingPoolWithData } from '@repo/lib/modules/vebal/vote/vote.types' -import { useVoteList } from '@bal/lib/vebal/vote/VoteList/VoteListProvider' -import { useMemo } from 'react' - -interface Props { - voteList: VotingPoolWithData[] - count: number - loading: boolean -} - -const rowProps = { - px: { base: 'sm', sm: '0' }, - gridTemplateColumns: `32px minmax(320px, 1fr) minmax(144px, max-content) minmax(100px, max-content) minmax(140px, max-content) minmax(120px, max-content) minmax(96px, max-content)`, - alignItems: 'center', - gap: { base: 'xxs', xl: 'lg' }, -} - -export function VoteListTable({ voteList, count, loading }: Props) { - const isMounted = useIsMounted() - const { - filtersState: { pagination, setPagination }, - } = useVoteList() - const paginationProps = getPaginationProps(count || 0, pagination, setPagination) - const showPagination = !!voteList.length && !!count && count > pagination.pageSize - - const TableHeader = useMemo(() => { - return function TableHeader() { - return - } - }, []) - - // Memoize component's link to skip recreation - const TableRow = useMemo(() => { - return function TableRow({ item, index }: { item: VotingPoolWithData; index: number }) { - return - } - }, []) - - if (!isMounted) return - - return ( - - `${item.id}#${index}`} - items={voteList} - loading={loading} - noItemsFoundLabel="No votes found" - paginationProps={paginationProps} - renderTableHeader={TableHeader} - renderTableRow={TableRow} - showPagination={showPagination} - /> - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListTable/VoteListTableHeader.tsx b/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListTable/VoteListTableHeader.tsx deleted file mode 100644 index d3a99f0712..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListTable/VoteListTableHeader.tsx +++ /dev/null @@ -1,81 +0,0 @@ -'use client' - -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 '@bal/lib/vebal/vote/VoteList/VoteListProvider' -import { SortVotesBy } from '@repo/lib/modules/vebal/vote/vote.types' -import { orderByHash } from '@repo/lib/modules/vebal/vote/vote.helpers' - -const orderBy = Object.values(SortVotesBy) - -export function VoteListTableHeader({ ...rest }) { - const { - filtersState: { sorting, setSorting, sortVotesBy, setSortVotesBy, toggleSorting }, - } = useVoteList() - - const handleSort = (newSortVotesBy: SortVotesBy) => { - if (sortVotesBy === newSortVotesBy) { - toggleSorting() - } else { - setSortVotesBy(newSortVotesBy) - setSorting(Sorting.desc) - } - } - - return ( - - - - - - - - - Pool name - - - {orderBy.map(orderByItem => ( - - handleSort(orderByItem)} - popoverContent={ - orderByHash[orderByItem].title ? ( - - - {orderByHash[orderByItem].title} - - - ) : undefined - } - sorting={sorting} - textProps={orderByItem === 'type' ? { textAlign: 'left' } : undefined} - /> - - ))} - - - Action - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListTable/VoteListTableRow.tsx b/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListTable/VoteListTableRow.tsx deleted file mode 100644 index ad9b8e8ca8..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListTable/VoteListTableRow.tsx +++ /dev/null @@ -1,185 +0,0 @@ -import { - Box, - Button, - Grid, - GridItem, - GridProps, - HStack, - Popover, - PopoverContent, - PopoverTrigger, - Portal, - Skeleton, - Text, - Tooltip, -} from '@chakra-ui/react' -import Link from 'next/link' -import { NetworkIcon } from '@repo/lib/shared/components/icons/NetworkIcon' -import { useCurrency } from '@repo/lib/shared/hooks/useCurrency' -import FadeInOnView from '@repo/lib/shared/components/containers/FadeInOnView' -import { VotingPoolWithData } from '@repo/lib/modules/vebal/vote/vote.types' -import { VotingListTokenPills } from '@repo/lib/modules/pool/PoolList/PoolListTokenPills' -import { getPoolPath } from '@repo/lib/modules/pool/pool.utils' -import { ArrowUpIcon } from '@repo/lib/shared/components/icons/ArrowUpIcon' -import { useVoteList } from '@bal/lib/vebal/vote/VoteList/VoteListProvider' -import { VoteListVotesCell } from '@bal/lib/vebal/vote/VoteList/VoteListTable/VoteListVotesCell' -import { VoteExpiredTooltip } from '@bal/lib/vebal/vote/VoteExpiredTooltip' -import { useVotes } from '@bal/lib/vebal/vote/Votes/VotesProvider' -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' -import { PoolListTableDetailsCell } from '@repo/lib/modules/pool/PoolList/PoolListTable/PoolListTableDetailsCell' -import { voteToPool } from '@repo/lib/modules/vebal/vote/vote.helpers' - -interface Props extends GridProps { - vote: VotingPoolWithData - keyValue: string | number -} - -export function VoteListTableRow({ vote, keyValue, ...rest }: Props) { - const { isConnected } = useUserAccount() - const { toCurrency } = useCurrency() - const { hasAllVotingPowerTimeLocked, allowSelectVotingPools } = useVotes() - const { toggleVotingPool, isSelectedPool, isVotedPool } = useVotes() - - const selected = isSelectedPool(vote) - const voted = isVotedPool(vote) - - const { incentivesAreLoading, gaugeVotesIsLoading } = useVoteList() - - const isDisabled = !isConnected || hasAllVotingPowerTimeLocked || !allowSelectVotingPools - - const disabledReason = isConnected - ? 'Get veBAL to select and vote on pool gauges' - : 'Connect your wallet to select and vote on pool gauges.' - - const missingDataReason = 'There is currently no bribe data for this pool' - - return ( - - - - - - - - - - - {vote.gauge.isKilled && } - - - - - - - - - - - {incentivesAreLoading ? ( - - ) : vote.votingIncentive ? ( - {toCurrency(vote.votingIncentive.totalValue, { abbreviated: false })} - ) : ( - - - - — - - - - - - {missingDataReason} - - - - - )} - - - {incentivesAreLoading ? ( - - ) : vote.votingIncentive ? ( - - {toCurrency(vote.votingIncentive.valuePerVote, { - abbreviated: false, - forceThreeDecimals: true, - })} - - ) : ( - - - - - - - - {missingDataReason} - - - - - )} - - - {gaugeVotesIsLoading ? ( - - ) : ( - - )} - - - {vote.gauge.isKilled || voted ? ( - - ) : ( - - - - )} - - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListTable/VoteListVotesCell.tsx b/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListTable/VoteListVotesCell.tsx deleted file mode 100644 index 329d500f6a..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/VoteList/VoteListTable/VoteListVotesCell.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { VotingPoolWithData } from '@repo/lib/modules/vebal/vote/vote.types' -import { getVotesState } from '@repo/lib/modules/vebal/vote/vote.helpers' -import { HStack } from '@chakra-ui/react' -import { VoteCapTooltip } from '@bal/lib/vebal/vote/VoteCapTooltip' -import { VoteRateTooltip } from '@bal/lib/vebal/vote/VoteRateTooltip' -import { bn } from '@repo/lib/shared/utils/numbers' -import { getRelativeWeightCap } from '@repo/lib/modules/vebal/vote/vote.helpers' - -interface Props { - vote: VotingPoolWithData -} - -export function VoteListVotesCell({ vote }: Props) { - const relativeWeightCap = getRelativeWeightCap(vote.gauge.relativeWeightCap, vote.id) - const votes = vote.gaugeVotes?.votes - ? bn(vote.gaugeVotes.votes).shiftedBy(-18).toNumber() - : undefined - const votesNextPeriod = vote.gaugeVotes?.votesNextPeriod - ? bn(vote.gaugeVotes.votesNextPeriod).shiftedBy(-18).toNumber() - : undefined - - const votesState = getVotesState(relativeWeightCap, votesNextPeriod ?? 0) - - return ( - - {relativeWeightCap !== 0 ? ( - - ) : undefined} - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/VoteList/useVoteListFiltersState.tsx b/apps/frontend-v3/lib/vebal/vote/VoteList/useVoteListFiltersState.tsx deleted file mode 100644 index f3f6a5bea9..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/VoteList/useVoteListFiltersState.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { useMemo, useState } from 'react' -import { PaginationState } from '@repo/lib/shared/components/pagination/pagination.types' -import { SortVotesBy } 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' -import { PROTOCOL_VERSION_TABS } from '@repo/lib/modules/pool/PoolList/usePoolListQueryState' - -const EMPTY_ARRAY: never[] = [] - -export function useVoteListFiltersState() { - const [sorting, setSorting] = useState(Sorting.desc) - const [sortVotesBy, setSortVotesBy] = useState(SortVotesBy.bribesPerVebal) - - 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 [activeProtocolVersionTab, setActiveProtocolVersionTab] = useState( - PROTOCOL_VERSION_TABS[0]! - ) - const [protocolVersion, setProtocolVersion] = useState(null) - - const [textSearch, setTextSearch] = useState('') - - function resetFilters() { - setNetworks(null) - setPoolTypes(null) - setIncludeExpiredPools(false) - setProtocolVersion(null) - - setPagination({ pageSize: 20, pageIndex: 0 }) - setSorting(Sorting.desc) - setSortVotesBy(SortVotesBy.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, - sortVotesBy, - setSortVotesBy, - pagination, - setPagination, - toggleSorting, - totalFilterCount, - resetFilters, - searchText: textSearch, - setSearch, - networks: networks ?? EMPTY_ARRAY, - poolTypes: poolTypes ?? EMPTY_ARRAY, - includeExpiredPools, - protocolVersion, - activeProtocolVersionTab, - toggleNetwork, - togglePoolType, - toggleIncludeExpiredPools, - setNetworks, - setPoolTypes, - setProtocolVersion, - setActiveProtocolVersionTab, - } -} diff --git a/apps/frontend-v3/lib/vebal/vote/VoteRateTooltip.tsx b/apps/frontend-v3/lib/vebal/vote/VoteRateTooltip.tsx deleted file mode 100644 index a197426cfc..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/VoteRateTooltip.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import { ArrowUpIcon } from '@repo/lib/shared/components/icons/ArrowUpIcon' -import { ArrowDownIcon } from '@repo/lib/shared/components/icons/ArrowDownIcon' -import { - HStack, - Text, - Popover, - PopoverTrigger, - Portal, - PopoverContent, - VStack, - StackProps, - Badge, - Box, - useToken, - Center, - Divider, -} from '@chakra-ui/react' -import { ReactNode, useState } from 'react' -import { fNum } from '@repo/lib/shared/utils/numbers' -import { VotesState } from '@repo/lib/modules/vebal/vote/vote.types' -import tinycolor from 'tinycolor2' -import { useVeBALTotal } from './useVeBALTotal' -import { oneWeekInMs } from '@repo/lib/shared/utils/time' - -interface TooltipItemProps extends StackProps { - label: ReactNode - value: ReactNode -} - -function TooltipItem({ label, value, color, ...props }: TooltipItemProps) { - return ( - - - {label} - - - {value} - - - ) -} - -function formatVotesAsPercent(votes: number): string { - const normalizedVotes = Math.abs(votes) - return fNum('apr', normalizedVotes) -} - -interface Props { - votesShare?: number - votesShareNextWeek?: number - votesState?: VotesState - usePortal?: boolean -} - -function TrendUpIcon() { - const [color, _bgColor] = useToken('colors', ['green.500', 'green.600']) - const bgColor = tinycolor(_bgColor).setAlpha(0.15).toString() - return ( -
- -
- ) -} - -function TrendDownIcon() { - const [color] = useToken('colors', ['red.400']) - const bgColor = tinycolor(color).setAlpha(0.15).toString() - return ( -
- -
- ) -} - -export function VoteRateTooltip({ votesState, votesShare, votesShareNextWeek }: Props) { - const votesShareText = votesShare ? formatVotesAsPercent(votesShare) : undefined - const votesShareNextWeekText = votesShareNextWeek - ? formatVotesAsPercent(votesShareNextWeek) - : undefined - - const voteDiff = votesShare && votesShareNextWeek ? votesShareNextWeek - votesShare : undefined - const voteDiffText = formatVotesAsPercent(voteDiff ? voteDiff : 0) - const diffIcon = !voteDiff ? ( - - ) : voteDiff > 0 ? ( - - ) : ( - - ) - const badgeColorScheme = - !voteDiff || voteDiff === 0 - ? { variant: 'outline', bg: 'font.secondary' } - : voteDiff > 0 - ? { bg: 'font.highlight', variant: 'solid', color: 'white' } - : { colorScheme: 'red', variant: 'solid' } - - const votesColor = - votesState === 'normal' ? undefined : votesState === 'close' ? 'font.warning' : 'red.400' - - const trendIcon = !voteDiff ? undefined : voteDiff > 0 ? : - - const [thisWeek] = useState(() => Math.floor(Date.now() / oneWeekInMs) * oneWeekInMs) - const { totalAmount: totalVeBAL } = useVeBALTotal(thisWeek) - const votesThisWeek = totalVeBAL && votesShare ? (votesShare * totalVeBAL).toFixed(2) : undefined - - const nextWeek = thisWeek + oneWeekInMs - const { totalAmount: totalVeBALNextWeek } = useVeBALTotal(nextWeek) - const votesNextWeek = - totalVeBALNextWeek && votesShareNextWeek - ? (votesShareNextWeek * totalVeBALNextWeek).toFixed(2) - : undefined - - const popoverContent = ( - - - - veBAL votes - - } - mb="xs" - value={ - - - - {diffIcon} - - - {voteDiffText} - - - - } - /> - - - - —} /> - —} - /> - - —} - /> - —} - /> - - - ) - - return ( - - <> - - - - {votesShareNextWeekText ?? <>—} - - {trendIcon} - - - - {popoverContent} - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/CalendarReminderModal.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/CalendarReminderModal.tsx deleted file mode 100644 index dd5a42f4a6..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/CalendarReminderModal.tsx +++ /dev/null @@ -1,94 +0,0 @@ -'use client' - -import { SuccessOverlay } from '@repo/lib/shared/components/modals/SuccessOverlay' -import { - Modal, - ModalContent, - ModalCloseButton, - ModalBody, - Box, - Button, - ModalHeader, - VStack, - Text, - List, - ListItem, - UseDisclosureProps, -} from '@chakra-ui/react' -import { Picture } from '@repo/lib/shared/components/other/Picture' -import { openIcalEvent } from '@repo/lib/shared/utils/calendar' - -function setCalendarEvent(deadline: Date) { - const event = { - title: 'veBAL voting deadline (Balancer)', - start: deadline, - url: 'https://balancer.fi/vebal/vote', - } - - openIcalEvent({ event, makeItWeekly: true }) -} - -export function CalendarReminderModal({ - isOpen = false, - onClose, - deadline, -}: UseDisclosureProps & { deadline: Date }) { - const closeModal = () => onClose && onClose() - - function setReminder() { - setCalendarEvent(deadline) - closeModal() - } - - return ( - - - - Get calendar reminders - - - - - - - - - - - For people deep into veBAL voting incentives, it can be advantageous to vote as - late as possible in order to see exactly which pool gauges offer the best - incentives per veBAL vote before the deadline. - - - - - You can download an “.ics” file which can be used to add a recurring weekly - Thursday calendar reminder to vote. - - - - - An “.ics” file is a calendar file saved in a universal calendar format used by - several email and calendar programs, including Microsoft Outlook, Google Calendar, - and Apple Calendar. - - - - - - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotes.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotes.tsx deleted file mode 100644 index 82092b0fba..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotes.tsx +++ /dev/null @@ -1,206 +0,0 @@ -'use client' - -import { - Alert, - AlertDescription, - AlertIcon, - AlertTitle, - Button, - Grid, - GridItem, - Heading, - HStack, - Stack, - Text, - VStack, -} from '@chakra-ui/react' -import { MyVotesTable } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTable' -import { useMyVotes } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesProvider' -import { MyVotesStatsMyVebal } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesStats/MyVotesStatsMyVebal' -import { MyVotesStatsAverageReward } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesStats/MyVotesStatsAverageReward' -import { MyVotesStatsMyIncentives } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesStats/MyVotesStatsMyIncentives' -import { MyVotesStatsMyIncentivesOptimized } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/MyVotesStatsMyIncentivesOptimized' -import { useDisclosure } from '@chakra-ui/hooks' -import { MyVotesHintModal } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesHintModal' -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' -import { WEIGHT_VOTE_DELAY } from '@bal/lib/vebal/vote/Votes/MyVotes/myVotes.helpers' -import { oneDayInMs } from '@repo/lib/shared/utils/time' -import { useVotes } from '@bal/lib/vebal/vote/Votes/VotesProvider' -import { useVebalUserData } from '@bal/lib/vebal/useVebalUserData' -import { AlertTriangle } from 'react-feather' -import NextLink from 'next/link' -import { getVeBalManagePath } from '../../../vebal-navigation' - -export function MyVotes() { - const { hasAllVotingPowerTimeLocked, vebalIsExpired, vebalLockTooShort } = useVotes() - const { - sortedMyVotes, - loading: myVotesLoading, - hasExpiredGauges, - hasNewVotes, - hasUsablePools, - } = useMyVotes() - const { isOpen, onOpen, onClose } = useDisclosure() - const { isConnected } = useUserAccount() - - const { isLoading: vebalUserDataLoading, veBALBalance, noVeBALBalance } = useVebalUserData() - - const loading = myVotesLoading || vebalUserDataLoading - - return ( - - - - My votes - - - - - - - {isConnected && ( - <> - {vebalIsExpired ? ( - - - - - {`You can't vote due to expired veBAL`} - - - - ) : null} - - )} - - - - - - - - - - - - - - - - {isConnected && ( - <> - {!vebalIsExpired && !noVeBALBalance && ( - <> - {hasAllVotingPowerTimeLocked && ( - - - - - All your votes are timelocked - - {`Once you vote on a pool, your vote is fixed for ${WEIGHT_VOTE_DELAY / oneDayInMs} days.`} - - - - - )} - - {vebalLockTooShort && ( - - - - - {`You can't vote since your veBAL expires before the next vote period`} - - Gauge voting requires your veBAL to be locked for 7+ days.{' '} - - Extend your lock - {' '} - to vote. - - - - - )} - - {hasExpiredGauges && ( - - - - - You have votes on an expired pool gauge - Reallocate these to avoid wasting votes - - - - )} - - {hasNewVotes && hasUsablePools && ( - - - - - Resubmit your votes to use your full voting power - - Votes are set at the time you cast them. Since you've added more veBAL - afterward, that extra voting power is not being fully utilized. - - - - - )} - - )} - - )} - - - - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesHintModal.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesHintModal.tsx deleted file mode 100644 index b97ba2a097..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesHintModal.tsx +++ /dev/null @@ -1,93 +0,0 @@ -'use client' - -import { SuccessOverlay } from '@repo/lib/shared/components/modals/SuccessOverlay' -import { - Modal, - ModalContent, - ModalCloseButton, - ModalBody, - Link, - Box, - ModalHeader, - VStack, - Text, - List, - ListItem, - UseDisclosureProps, -} from '@chakra-ui/react' -import { useMemo } from 'react' -import { Picture } from '@repo/lib/shared/components/other/Picture' -import { addDays, format } from 'date-fns' - -export function MyVotesHintModal({ isOpen = false, onClose = () => {} }: UseDisclosureProps) { - const formattedUnlockDate = useMemo(() => { - const today = new Date() - return format(addDays(today, 10), 'dd MMM yyyy') - }, []) - return ( - - - - How veBAL voting works - - - - - - - - - - - Your vote directs liquidity mining emissions for the future periods starting next - Thursday at 0:00 UTC. - - - - - There are vote incentives offered by 3rd parties (also known as bribes). If you - vote on pools with bribes, you can claim these bribes on third party platforms - like Votemarket and Paladin. - - - - - You can vote on multiple pools in a single transaction. Simply add multiple pools - to your vote list. - - - - - Votes are timelocked for 10 days. If you vote now, no edits can be made until{' '} - {formattedUnlockDate}. - - - - - Voting power is set at the time of a vote. If you get more veBAL later, resubmit - your vote to use your increased power. - - - - - After you get veBAL, it can be synced to supported L2 networks to boost BAL - liquidity incentives on eligible pools. Currently, this is only supported at{' '} - app.balancer.fi - - - - - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesLayout.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesLayout.tsx deleted file mode 100644 index 890c4118ff..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesLayout.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { MyVotes } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotes' - -export function MyVotesLayout() { - return -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesProvider.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesProvider.tsx deleted file mode 100644 index 0cf65bb2e6..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesProvider.tsx +++ /dev/null @@ -1,379 +0,0 @@ -'use client' - -import { useVotes } from '@bal/lib/vebal/vote/Votes/VotesProvider' -import { createContext, PropsWithChildren, useMemo, useState } from 'react' -import { useMyVotesFiltersState } from '@bal/lib/vebal/vote/Votes/MyVotes/useMyVotesFiltersState' -import { VotingPoolWithData } from '@repo/lib/modules/vebal/vote/vote.types' -import { MyVotesTotalInfo, SortingBy } from '@bal/lib/vebal/vote/Votes/MyVotes/myVotes.types' -import { Sorting } from '@repo/lib/shared/components/tables/SortableHeader' -import { orderBy, uniqBy } from 'lodash' -import { useMandatoryContext } from '@repo/lib/shared/utils/contexts' -import { bn, sum } from '@repo/lib/shared/utils/numbers' -import { useSubmitVotesAllSteps } from '@bal/lib/vebal/vote/Votes/MyVotes/actions/submit/useSubmitVotesAllSteps' -import { useTransactionSteps } from '@repo/lib/modules/transactions/transaction-steps/useTransactionSteps' -import { - bpsToPercentage, - calculateMyValuePerVote, - calculateMyVoteRewardsValue, - calculateVotingPower, - sharesToBps, -} from '@bal/lib/vebal/vote/Votes/MyVotes/myVotes.helpers' - -import { - getExceededWeight, - getUnallocatedWeight, - isVotingTimeLocked, -} from '@bal/lib/vebal/vote/Votes/MyVotes/myVotes.helpers' -import BigNumber from 'bignumber.js' -import { useTotalVotes } from '../../useTotalVotes' -import { useVebalLockInfo } from '@bal/lib/vebal/useVebalLockInfo' -import { useBlacklistedVotes } from './incentivesBlacklist' -import { useLastUserSlope } from '../../useVeBALBalance' -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' -import { Address } from 'viem' -import { useVebalUserData } from '@bal/lib/vebal/useVebalUserData' -import { secondsToMilliseconds } from 'date-fns' - -function sortMyVotesList(voteList: VotingPoolWithData[], sortBy: SortingBy, order: Sorting) { - return orderBy( - voteList, - value => { - switch (sortBy) { - case SortingBy.currentVotes: - return value.gaugeVotes ? Number(value.gaugeVotes.userVotes) : -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.editVotes: - return 0 - default: - throw new Error(`Unsupported SortingBy value (${sortBy})`) - } - }, - order === Sorting.asc ? 'asc' : 'desc' - ) -} - -export interface SubmittingVote { - vote: VotingPoolWithData - weight: string -} - -export function useMyVotesLogic() { - const { - loading: votesLoading, - votingPools, - votedPools, - selectedVotingPools, - clearSelectedVotingPools, - refetchAll, - isPoolGaugeExpired, - } = useVotes() - const filtersState = useMyVotesFiltersState() - const { lastLockTimestamp } = useVebalUserData() - const [selectedVotes, setSelectedVotes] = useState([]) - - const myVotes = useMemo(() => { - return uniqBy([...votedPools, ...selectedVotingPools], vote => vote.id) - }, [votedPools, selectedVotingPools]) - - const sortedMyVotes = useMemo(() => { - return sortMyVotesList(myVotes, filtersState.sortingBy, filtersState.sorting) - }, [myVotes, filtersState.sorting, filtersState.sortingBy]) - - const hasVotes = myVotes.length > 0 - - const votedVotesWeights = useMemo>( - () => Object.fromEntries(votedPools.map(vote => [vote.id, vote.gaugeVotes?.userVotes || '0'])), - [votedPools] - ) - - // Record - const [editVotesOverrides, setEditVotesOverrides] = useState>(() => ({})) - - const editVotesWeights = useMemo>(() => { - const result: Record = {} - - for (const vote of myVotes) { - if (isPoolGaugeExpired(vote)) { - result[vote.id] = '0' - continue - } - - const override = editVotesOverrides[vote.id] - - if (override !== undefined) { - result[vote.id] = override - } else { - result[vote.id] = vote.gaugeVotes?.userVotes || '0' - } - } - - return result - }, [editVotesOverrides, myVotes, isPoolGaugeExpired]) - - const onEditVotesChange = (id: string, value: string) => { - setEditVotesOverrides(current => ({ - ...current, - [id]: value, - })) - } - - const hasExpiredGauges = myVotes.some(votedPool => votedPool.gauge.isKilled) - - const availableMyVotes = useMemo(() => { - return myVotes.filter(myVote => !isPoolGaugeExpired(myVote)) - }, [myVotes, isPoolGaugeExpired]) - - const { totalVotes, totalVotesLoading } = useTotalVotes() - const { mainnetLockedInfo, isLoading: lockInfoLoading } = useVebalLockInfo() - const lockEnd = mainnetLockedInfo.lockedEndDate - const { blacklistedVotes, isLoading: blacklistedLoading } = useBlacklistedVotes(votingPools) - const { userAddress, isLoading: userAccountLoading } = useUserAccount() - const { slope, isLoading: slopeLoading } = useLastUserSlope(userAddress) - - const totalInfo: MyVotesTotalInfo = useMemo(() => { - const infos = availableMyVotes.map(myVote => { - const currentWeight = myVote.gaugeVotes?.userVotes || 0 - const votedWeight = votedVotesWeights[myVote.id] || 0 - const editWeight = editVotesWeights[myVote.id] || 0 - const totalValue = myVote.votingIncentive?.totalValue || 0 - const valuePerVote = calculateMyValuePerVote( - editWeight, - myVote, - slope, - lockEnd, - totalVotes, - blacklistedVotes[myVote.gauge.address as Address] - ) - - return { - currentWeight, - votedWeight, - editWeight, - totalValue, - valuePerVote, - vote: myVote, - } - }) - - const currentVotes = sum(infos, ({ currentWeight }) => bn(currentWeight)) - const editVotes = sum(infos, ({ editWeight }) => bn(editWeight)) - - const totalRewardValue = sum(infos, ({ editWeight, vote }) => - calculateMyVoteRewardsValue( - editWeight, - vote, - slope, - lockEnd, - totalVotes, - blacklistedVotes[vote.gauge.address as Address] - ) - ) - - const prevTotalRewardValue = sum(infos, ({ votedWeight, vote }) => - calculateMyVoteRewardsValue( - votedWeight, - vote, - slope, - lockEnd, - totalVotes, - blacklistedVotes[vote.gauge.address as Address] - ) - ) - - const averageRewardPerVote = sum(infos, ({ valuePerVote, editWeight }) => - bn(valuePerVote).multipliedBy(bpsToPercentage(editWeight)) - ) - const prevAverageRewardPerVote = sum(infos, ({ valuePerVote, votedWeight }) => - bn(valuePerVote).multipliedBy(bpsToPercentage(votedWeight)) - ) - - const unallocatedVotes = sharesToBps(100).minus(editVotes) - - return { - currentVotes, - editVotes, - totalRewardValue, - prevTotalRewardValue, - totalRewardValueGain: totalRewardValue.minus(prevTotalRewardValue), - averageRewardPerVote, - averageRewardPerVoteGain: averageRewardPerVote.minus(prevAverageRewardPerVote), - unallocatedVotes: BigNumber.max(unallocatedVotes, 0), - } - }, [ - availableMyVotes, - votedVotesWeights, - editVotesWeights, - totalVotes, - slope, - blacklistedVotes, - lockEnd, - ]) - - const hasVotedBefore = votedPools.length > 0 - - const hasChanges = - (selectedVotingPools.length > 0 && - selectedVotingPools.some(votingPool => bn(editVotesWeights[votingPool.id] ?? 0).gt(0))) || - votedPools.some( - votedPool => !bn(votedPool.gaugeVotes?.userVotes ?? 0).eq(editVotesWeights[votedPool.id] ?? 0) - ) - - const hasExceededWeight = getExceededWeight(totalInfo.editVotes || bn(0)).gt(0) - const hasUnallocatedWeight = getUnallocatedWeight(totalInfo.editVotes || bn(0)).gt(0) - const hasNewVotes = newVotesSinceLastVote(myVotes, slope, lockEnd) - const hasUsablePools = - myVotes.filter( - vote => - !!vote.gaugeVotes?.lastUserVoteTime && - !isVotingTimeLocked(vote.gaugeVotes?.lastUserVoteTime) && - !isPoolGaugeExpired(vote) - ).length > 0 - - const clearAll = () => { - setEditVotesOverrides({}) - clearSelectedVotingPools() - } - - const submittingVotes = useMemo(() => { - return myVotes - .filter(vote => !isVotingTimeLocked(vote.gaugeVotes?.lastUserVoteTime ?? 0)) - .filter(vote => { - const newVoteWeight = editVotesWeights[vote.id] || 0 - const persistedVoteWeight = vote.gaugeVotes?.userVotes || 0 - if (bn(newVoteWeight).isZero() && bn(persistedVoteWeight).isZero()) return false - return newVoteWeight !== persistedVoteWeight || hasNewVotes - }) - .map(vote => { - const newVote = editVotesWeights[vote.id] || '0' - return { - vote, - weight: newVote, - } - }) - }, [myVotes, editVotesWeights, hasNewVotes]) - - const timeLockedVotes: SubmittingVote[] = useMemo(() => { - return votedPools - .filter(vote => isVotingTimeLocked(vote.gaugeVotes?.lastUserVoteTime ?? 0)) - .map(vote => { - return { - vote, - weight: vote.gaugeVotes?.userVotes ?? '0', - } - }) - }, [votedPools]) - - const changedVotes = submittingVotes.filter(vote => isVoteChanged(vote, lastLockTimestamp)) - const unchangedVotes = submittingVotes.filter(vote => !isVoteChanged(vote, lastLockTimestamp)) - const selectableVotes = changedVotes.filter(vote => { - const previousWeight = vote.vote.gaugeVotes?.userVotes || '0' - return previousWeight === vote.weight - }) - const submittableVotes = changedVotes - .filter(vote => { - const previousWeight = vote.vote.gaugeVotes?.userVotes || '0' - return previousWeight !== vote.weight || selectedVotes.includes(vote.vote.id as Address) - }) - .sort((a, b) => { - // Sending vote increases before decreases can reject the tx as each vote - // is sequentially processed and there is a check so the percentage is not - // bigger than 100%. Sorting the votes this way solves the problem. - const previousWeightA = a.vote.gaugeVotes?.userVotes || '0' - const previousWeightB = b.vote.gaugeVotes?.userVotes || '0' - const diffA = Number(a.weight) - Number(previousWeightA) - const diffB = Number(b.weight) - Number(previousWeightB) - - if (diffA < 0 && diffB >= 0) return 1 - if (diffB < 0 && diffA >= 0) return -1 - - return Number(a.weight) - Number(b.weight) - }) - .reverse() - - const { steps, isLoadingSteps } = useSubmitVotesAllSteps({ votes: submittableVotes }) - const transactionSteps = useTransactionSteps(steps, isLoadingSteps) - const txHash = transactionSteps.lastTransaction?.result?.data?.transactionHash - - return { - myVotes, - sortedMyVotes, - loading: - votesLoading || - totalVotesLoading || - blacklistedLoading || - lockInfoLoading || - slopeLoading || - userAccountLoading, - filtersState, - hasVotes, - hasVotedBefore, - hasChanges, - totalInfo, - votedVotesWeights, - editVotesWeights, - onEditVotesChange, - clearAll, - transactionSteps, - txHash, - timeLockedVotes, - changedVotes, - unchangedVotes, - hasExceededWeight, - hasUnallocatedWeight, - refetchAll, - hasExpiredGauges, - hasNewVotes, - hasUsablePools, - selectableVotes, - selectedVotes, - setSelectedVotes, - submittableVotes, - } -} - -function newVotesSinceLastVote( - myVotes: VotingPoolWithData[], - currentSlope: bigint, - lockEnd: number | undefined -) { - if (!lockEnd) return false - - const votingPower = calculateVotingPower(currentSlope, lockEnd) - - const previousVotingPower = myVotes.reduce((acc, vote) => { - const votes = calculateVotingPower( - vote.gaugeVotes?.userSlope || 0n, - secondsToMilliseconds(Number(vote.gaugeVotes?.userEndLock)) - ) - return acc.plus(votes) - }, bn(0)) - - return votingPower.gt(previousVotingPower) -} - -export const MyVotesContext = createContext | null>(null) - -export function MyVotesProvider({ children }: PropsWithChildren) { - const hook = useMyVotesLogic() - - return {children} -} - -export function useMyVotes() { - return useMandatoryContext(MyVotesContext, 'MyVotes') -} - -function isVoteChanged(vote: SubmittingVote, lastLockTimestamp: number | undefined) { - const voteTimestamp = (vote.vote.gaugeVotes?.lastUserVoteTime || 0) * 1000 - const previousWeight = vote.vote.gaugeVotes?.userVotes || '0' - - return ( - previousWeight !== vote.weight || - !voteTimestamp || - !lastLockTimestamp || - voteTimestamp < lastLockTimestamp - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/MyVotesStatsAverageReward.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/MyVotesStatsAverageReward.tsx deleted file mode 100644 index 7fce36fb58..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/MyVotesStatsAverageReward.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { HStack, Skeleton, Text } from '@chakra-ui/react' -import { useCurrency } from '@repo/lib/shared/hooks/useCurrency' -import { MyVotesStatsCard } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/MyVotesStatsCard' -import { GainBadge } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/GainBadge' -import { useMyVotes } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesProvider' -import { isZero } from '@repo/lib/shared/utils/numbers' -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' -import { canReceiveIncentives } from '../incentivesBlacklist' -import { TooltipWithTouch } from '@repo/lib/shared/components/tooltips/TooltipWithTouch' - -export function MyVotesStatsAverageReward() { - const { userAddress } = useUserAccount() - const { toCurrency } = useCurrency() - const { totalInfo, loading } = useMyVotes() - - return ( - - - My average reward (Bribes/veBAL) - - - } - leftContent={ - loading ? ( - - ) : !isZero(totalInfo.averageRewardPerVote) && canReceiveIncentives(userAddress) ? ( - - - {toCurrency(totalInfo.averageRewardPerVote, { - abbreviated: false, - forceThreeDecimals: true, - })} - - {totalInfo.averageRewardPerVoteGain && ( - - )} - - ) : ( - <>— - ) - } - /> - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/MyVotesStatsMyIncentives.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/MyVotesStatsMyIncentives.tsx deleted file mode 100644 index 87cc34efd4..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/MyVotesStatsMyIncentives.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { HStack, Skeleton, Text } from '@chakra-ui/react' -import { useCurrency } from '@repo/lib/shared/hooks/useCurrency' -import { MyVotesStatsCard } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/MyVotesStatsCard' -import { GainBadge } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/GainBadge' -import { MyIncentivesTooltip } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/MyIncentivesTooltip' -import { useMyVotes } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesProvider' -import { isZero } from '@repo/lib/shared/utils/numbers' -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' -import { canReceiveIncentives } from '../incentivesBlacklist' -import { TooltipWithTouch } from '@repo/lib/shared/components/tooltips/TooltipWithTouch' - -export function MyVotesStatsMyIncentives() { - const { userAddress } = useUserAccount() - const { toCurrency } = useCurrency() - const { totalInfo, loading } = useMyVotes() - - return ( - - - My potential bribes (1w) - - - } - leftContent={ - loading ? ( - - ) : !isZero(totalInfo.totalRewardValue) && canReceiveIncentives(userAddress) ? ( - - - {toCurrency(totalInfo.totalRewardValue, { abbreviated: false })} - - - {totalInfo.totalRewardValueGain && } - - ) : ( - <>— - ) - } - /> - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/MyVotesStatsMyVebal.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/MyVotesStatsMyVebal.tsx deleted file mode 100644 index a45839929d..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/MyVotesStatsMyVebal.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { Badge, Button, HStack, Skeleton, Text, Tooltip, VStack } from '@chakra-ui/react' -import { fNum } from '@repo/lib/shared/utils/numbers' -import NextLink from 'next/link' -import { useVebalLockInfo } from '@bal/lib/vebal/useVebalLockInfo' -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' -import { ConnectWallet } from '@repo/lib/modules/web3/ConnectWallet' -import { useLockEndDate } from '@bal/lib/vebal/lock/duration/useLockEndDate' -import { expectedTotalVeBal } from '@bal/lib/vebal/lock/VebalLockProvider' -import { MyVotesStatsCard } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/MyVotesStatsCard' -import { MyVebalChargeTooltip } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/MyVebalChargeTooltip' -import { formatUnits } from 'viem' - -interface Props { - myVebalBalance: bigint - loading: boolean -} - -export function MyVotesStatsMyVebal({ myVebalBalance, loading }: Props) { - const { isConnected } = useUserAccount() - - const { mainnetLockedInfo } = useVebalLockInfo() - const lockedEndDate = mainnetLockedInfo.lockedEndDate - const isLockExpired = mainnetLockedInfo.isExpired - - const lockedAmount = mainnetLockedInfo.hasExistingLock - ? mainnetLockedInfo.lockedAmount - : undefined - - const { maxLockEndDate } = useLockEndDate({ - lockedEndDate: lockedEndDate ? new Date(lockedEndDate) : undefined, - }) - - const expectedVeBalAmount = expectedTotalVeBal({ - bpt: lockedAmount ?? '0', - lockEndDate: maxLockEndDate, - }) - - const balance = !isLockExpired ? fNum('token', formatUnits(myVebalBalance || 0n, 18)) : '0' - - return ( - - ) : myVebalBalance === 0n ? ( - - - {isLockExpired && ( - - - Expired - - - )} - - ) : ( - - - {balance} - - - {lockedEndDate && ( - - )} - - ) - } - rightContent={ - loading ? ( - - ) : !isConnected ? ( - - - - ) : ( - - ) - } - variant={isLockExpired ? 'expired' : 'default'} - /> - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/IncentivesOptimizedTooltip.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/IncentivesOptimizedTooltip.tsx deleted file mode 100644 index 6d98e39bcc..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/IncentivesOptimizedTooltip.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { - Divider, - Icon, - Popover, - PopoverContent, - PopoverTrigger, - Portal, - Stack, - Text, - useColorModeValue, -} from '@chakra-ui/react' -import StarsIcon from '@repo/lib/shared/components/icons/StarsIcon' -import { TooltipItem } from './TooltipItem' -import { useCurrency } from '@repo/lib/shared/hooks/useCurrency' - -type Props = { - totalIncentives: number - protocolRevenueShare: number -} - -const baseItemProps = { - pl: 4, - pr: 2, - pb: 3, - backgroundColor: 'background.level1', - fontWeight: 700, -} - -const rewardGradient = - 'linear-gradient(90deg, rgba(179, 174, 245, 0.5) 0%, rgba(215, 203, 231, 0.5) 25%, rgba(229, 200, 200, 0.5) 50%, rgba(234, 168, 121, 0.5) 100%)' - -export function IncentivesOptimizedTooltip({ totalIncentives, protocolRevenueShare }: Props) { - const { toCurrency } = useCurrency() - - const gradColorFrom = useColorModeValue( - '#F49A55', // light from - '#F49175' // dark from - ) - const gradColorTo = useColorModeValue( - '#FCD45B', // light to - '#FFCC33' // dark to - ) - - const currencyFormatter = (value: number) => toCurrency(value, { abbreviated: false }) - - return ( - - <> - - - - - - - - - - - - - - - - - Note: In addition, veBAL holders earn extra BAL incentives when they LP in - eligible pools (based on veBAL pools) - - - - - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/MyVotesStatsMyIncentivesOptimized.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/MyVotesStatsMyIncentivesOptimized.tsx deleted file mode 100644 index 9d022de49d..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/MyVotesStatsMyIncentivesOptimized.tsx +++ /dev/null @@ -1,208 +0,0 @@ -import { Button, HStack, Skeleton, Text, Stack } from '@chakra-ui/react' -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' -import { ConnectWallet } from '@repo/lib/modules/web3/ConnectWallet' -import { MagicStickIcon } from '@repo/lib/shared/components/icons/MagicStickIcon' -import { useCurrency } from '@repo/lib/shared/hooks/useCurrency' -import { MyVotesStatsCard } from '../shared/MyVotesStatsCard' -import { useVeBALIncentives } from '../useVeBALIncentives' -import { useVebalUserData } from '@bal/lib/vebal/useVebalUserData' -import { bn, fNumCustom } from '@repo/lib/shared/utils/numbers' -import { areAllVotesTimelocked, useIncentivesOptimized } from './useIncentivesOptimized' -import { canReceiveIncentives, useBlacklistedVotes } from '../../incentivesBlacklist' -import { useVotes } from '../../../VotesProvider' -import { useMyVotes } from '../../MyVotesProvider' -import { useTotalVotes } from '@bal/lib/vebal/vote/useTotalVotes' -import { useVebalLockInfo } from '@bal/lib/vebal/useVebalLockInfo' -import { useLastUserSlope } from '@bal/lib/vebal/vote/useVeBALBalance' -import { calculateVotingPower } from '../../myVotes.helpers' -import { IncentivesOptimizedTooltip } from './IncentivesOptimizedTooltip' -import { useVeBALPool } from '../useVeBALPool' -import { - GqlPoolAprItem, - GqlPoolAprItemType, - GqlPoolStakingType, -} from '@repo/lib/shared/services/api/generated/graphql' -import { getStakedBalance } from '@repo/lib/modules/pool/user-balance.helpers' -import { Pool } from '@repo/lib/modules/pool/pool.types' -import { TooltipWithTouch } from '@repo/lib/shared/components/tooltips/TooltipWithTouch' - -export function MyVotesStatsMyIncentivesOptimized() { - const { toCurrency } = useCurrency() - const { isConnected, userAddress } = useUserAccount() - const { isLoading: vebalUserDataLoading, noVeBALBalance } = useVebalUserData() - - const { pool, poolIsLoading } = useVeBALPool(userAddress) - const aprItems = pool?.dynamicData.aprItems as GqlPoolAprItem[] - const lockingApr = aprItems?.find(item => item.type === GqlPoolAprItemType.Locking)?.apr || 0 - const balance = pool ? getStakedBalance(pool as Pool, GqlPoolStakingType.Vebal) : undefined - const protocolRevenueShare = balance ? (balance.balanceUsd * lockingApr) / 52 : 0 - - const { incentives, incentivesAreLoading } = useVeBALIncentives(userAddress) - - const { totalVotes, totalVotesLoading } = useTotalVotes() - const { mainnetLockedInfo, isLoading: lockInfoLoading } = useVebalLockInfo() - const lockEnd = mainnetLockedInfo.lockedEndDate - const { slope, isLoading: slopeLoading } = useLastUserSlope(userAddress) - const { loading: votesLoading, votingPools, toggleVotingPool, isVotedPool } = useVotes() - const { loading: myVotesLoading, myVotes, onEditVotesChange, totalInfo } = useMyVotes() - const { isLoading: blacklistedVotesLoading, blacklistedVotes } = useBlacklistedVotes(votingPools) - const inputsLoading = - totalVotesLoading || - lockInfoLoading || - slopeLoading || - votesLoading || - myVotesLoading || - blacklistedVotesLoading - - const { - isLoading: optimizationLoading, - totalIncentives: optimizedRewardValue, - votes: optimizedVotes, - } = useIncentivesOptimized( - votingPools, - myVotes, - calculateVotingPower(slope, lockEnd).shiftedBy(18), - bn(totalVotes), - blacklistedVotes, - inputsLoading - ) - - const headerText = - !isConnected || noVeBALBalance || !canReceiveIncentives(userAddress) ? ( - - - Average APR of vote incentives - - - ) : ( - - - My optimized vote incentives (1w) - - - ) - - const isLoading = - incentivesAreLoading || vebalUserDataLoading || optimizationLoading || poolIsLoading - - const applyOptimizedVotes = () => { - optimizedVotes.forEach(optimizedVote => { - const vote = votingPools.find(vote => vote.gauge.address === optimizedVote.gaugeAddress) - if (vote) { - if (!isVotedPool(vote)) toggleVotingPool(vote) - onEditVotesChange(vote.id, bn(optimizedVote.votePrct).times(10000).toString()) - } - }) - } - - const allVotesTimelocked = areAllVotesTimelocked(myVotes) - const votesAlreadyOptimized = totalInfo.totalRewardValue.toNumber() === optimizedRewardValue - - const disabledButton = !canReceiveIncentives(userAddress) || votesAlreadyOptimized - - let tooltipLabelText: string - if (allVotesTimelocked) { - tooltipLabelText = "All your votes are timelocked, so you can't apply any new vote combinations" - } else if (votesAlreadyOptimized && !allVotesTimelocked) { - tooltipLabelText = 'Your optimized votes have already been applied' - } else { - tooltipLabelText = - 'This selects pool gauges and applies the optimal vote combinations to maximize your rewards from the Votemarket vote market.' - } - - return ( - - ) : !isConnected || noVeBALBalance || !canReceiveIncentives(userAddress) ? ( - - {incentives.voting ? fNumCustom(incentives.voting, '0.00%') : '—'} - - ) : ( - - - {optimizedRewardValue ? ( - toCurrency(optimizedRewardValue, { abbreviated: false }) - ) : ( - <>— - )} - - - - ) - } - rightContent={ - isLoading ? ( - - ) : !isConnected ? ( - - - - ) : !noVeBALBalance ? ( - - - - - - ) : null - } - variant="special" - /> - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/TooltipItem.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/TooltipItem.tsx deleted file mode 100644 index 1c29e80674..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/TooltipItem.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { - BoxProps, - Box, - HStack, - Text, - Portal, - Popover, - PopoverTrigger, - PopoverContent, -} from '@chakra-ui/react' -import { ReactNode } from 'react' - -interface Props extends BoxProps { - fontWeight?: number - fontColor?: string - valueFontColor?: string - - title: string - value: number - displayValueFormatter: (value: number) => string - - boxBackground?: string - textBackground?: string - textBackgroundClip?: string - tooltipText?: string - textVariant?: string - children?: ReactNode -} - -export function TooltipItem({ - title, - value, - displayValueFormatter, - boxBackground, - bg = 'background.level3', - textBackground, - textBackgroundClip, - children, - fontWeight, - fontColor, - textVariant, - tooltipText, - valueFontColor, - ...props -}: Props) { - return ( - - - - {title} - - {tooltipText ? ( - - - - {displayValueFormatter(value)} - - - - - - {tooltipText} - - - - - ) : ( - - {displayValueFormatter(value)} - - )} - - {children} - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/heap.spec.ts b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/heap.spec.ts deleted file mode 100644 index 8214819afe..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/heap.spec.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { Element, heap, peek, pop, push, size } from './heap' - -describe('Heap get size', () => { - it('should return 0 on an empty heap', () => { - const h = emptyHeap() - - expect(size(h)).toBe(0) - }) - - it('should return the number of elements on the heap', () => { - const h = emptyHeap() - push(h, 5) - - expect(size(h)).toBe(1) - }) -}) - -describe('Heap peek top element', () => { - it('should return undefined on an empty heap', () => { - const h = emptyHeap() - - const peekedElement = peek(h) - - expect(peekedElement).toBe(undefined) - }) - - it('should return the first element of the heap', () => { - const h = emptyHeap() - push(h, 5) - - const peekedElement = peek(h) - - expect(peekedElement).toBe(5) - }) - - it('should not remove that element from the heap', () => { - const h = emptyHeap() - push(h, 5) - - peek(h) - - expect(size(h)).toBe(1) - }) -}) - -describe('Adding elements to heap', () => { - it('should resize heap as more elements are added', () => { - const h = heap(1, (a: Element) => a || 0) - - push(h, 1) - push(h, 2) - - expect(size(h)).toBe(2) - }) - - it('should place the bigger element on top as they are added', () => { - const h = emptyHeap() - - push(h, 5) - expect(peek(h)).toBe(5) - - push(h, 1) - expect(peek(h)).toBe(5) - - push(h, 10) - expect(peek(h)).toBe(10) - }) -}) - -describe('Removing elements from the heap', () => { - it('should return undefined on an empty heap', () => { - const h = emptyHeap() - - const popedElement = pop(h) - - expect(popedElement).toBe(undefined) - }) - - it('should return the biggest element of the heap', () => { - const h = emptyHeap() - push(h, 1) - push(h, 5) - push(h, 10) - push(h, 7) - - let popedElement = pop(h) - expect(popedElement).toBe(10) - - popedElement = pop(h) - expect(popedElement).toBe(7) - - popedElement = pop(h) - expect(popedElement).toBe(5) - - popedElement = pop(h) - expect(popedElement).toBe(1) - }) - - it('should remove the returned element of the heap', () => { - const h = emptyHeap() - push(h, 1) - - pop(h) - - expect(size(h)).toBe(0) - }) -}) - -function emptyHeap() { - return heap(10, (a: Element) => a || 0) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/heap.ts b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/heap.ts deleted file mode 100644 index cea1860db8..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/heap.ts +++ /dev/null @@ -1,99 +0,0 @@ -export type Element = T | undefined - -export type Heap = { - elements: Element[] - valueOf: (a: Element) => number -} - -export function heap(size: number, valueOf: (a: Element) => number): Heap { - return { - elements: new Array(size), - valueOf, - } -} - -export function size(h: Heap>) { - return h.elements.reduce((acc, current) => { - return current !== undefined ? acc + 1 : acc - }, 0) -} - -export function peek(h: Heap>) { - return h.elements[0] -} - -export function push(h: Heap>, element: Element) { - const lastIndex = size(h) - h.elements[lastIndex] = element - - return orderUp(h, lastIndex) -} - -export function pop(h: Heap>) { - const item = h.elements[0] - const heapSize = size(h) - h.elements[0] = h.elements[heapSize - 1] - h.elements[heapSize - 1] = undefined - orderDown(h, 0) - - return item -} - -const leftChildIndex = (i: number) => 2 * i + 1 - -function hasLeftChild(h: Heap>, i: number) { - return leftChildIndex(i) < size(h) -} - -function leftChild(h: Heap>, i: number) { - return h.elements[leftChildIndex(i)] -} - -const rightChildIndex = (i: number) => 2 * i + 2 - -function hasRightChild(h: Heap>, i: number) { - return rightChildIndex(i) < size(h) -} - -function rightChild(h: Heap>, i: number) { - return h.elements[rightChildIndex(i)] -} - -const hasParent = (i: number) => i > 0 - -const parentIndex = (i: number) => Math.ceil((i - 2) / 2) - -function parent(h: Heap>, i: number) { - return h.elements[parentIndex(i)] -} - -function swap(h: Heap>, i1: number, i2: number) { - const temp = h.elements[i1] - h.elements[i1] = h.elements[i2] - h.elements[i2] = temp -} - -function orderUp(h: Heap>, i: number) { - if (hasParent(i) && h.valueOf(parent(h, i)) < h.valueOf(h.elements[i])) { - swap(h, parentIndex(i), i) - return orderUp(h, parentIndex(i)) - } else { - return h - } -} - -function orderDown(h: Heap>, i: number) { - if (hasLeftChild(h, i)) { - const biggerChildIndex = - hasRightChild(h, i) && h.valueOf(rightChild(h, i)) > h.valueOf(leftChild(h, i)) - ? rightChildIndex(i) - : leftChildIndex(i) - - if (h.valueOf(h.elements[i]) < h.valueOf(h.elements[biggerChildIndex])) { - swap(h, i, biggerChildIndex) - return orderDown(h, biggerChildIndex) - } - } - - return h -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/useIncentivesOptimized.spec.ts b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/useIncentivesOptimized.spec.ts deleted file mode 100644 index 65db1bdbb0..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/useIncentivesOptimized.spec.ts +++ /dev/null @@ -1,337 +0,0 @@ -import { VotingPoolWithData } from '@repo/lib/modules/vebal/vote/vote.types' -import { OptimizedVotes, useIncentivesOptimized } from './useIncentivesOptimized' -import { GqlChain, GqlPoolType } from '@repo/lib/shared/services/api/generated/graphql' -import { millisecondsToSeconds, subDays } from 'date-fns' -import { Address } from 'viem' -import { bn } from '@repo/lib/shared/utils/numbers' -import { testHook } from '@repo/lib/test/utils/custom-renderers' -import { waitFor } from '@testing-library/react' -import { mockTokenPricesList } from '@repo/lib/test/msw/handlers/Tokens.handlers' -import { aTokenPriceMock } from '@repo/lib/modules/tokens/__mocks__/token.builders' -import { PoolVotingIncentivesPerWeek } from '@repo/lib/shared/services/voting-incentives/incentives.types' - -const one_month_ago = subDays(new Date(), 30) -const BAL = '0xba100000625a3754423978a60c9317c58a424e3d' -const BAL_PRICE = 1 -const ADDRESSS_POOL_1 = '0xd75026f8723b94d9a360a282080492d905c6a558' -const SYMBOL_POOL_1 = 'pool1' -const ADDRESSS_POOL_2 = '0xd75026f8723b94d9a360a282080492d905c6a559' -const SYMBOL_POOL_2 = 'pool2' - -function renderHook( - votingPools: VotingPoolWithData[], - myVotes: VotingPoolWithData[], - userVotingPower: BigNumber, - totalVotes: BigNumber, - blacklistedVotes: Record, - inputsLoading: boolean -) { - const { result } = testHook(() => - useIncentivesOptimized( - votingPools, - myVotes, - userVotingPower, - totalVotes, - blacklistedVotes, - inputsLoading - ) - ) - - return result -} - -describe('Incentives optimization', () => { - beforeEach(() => { - mockTokenPricesList([aTokenPriceMock({ address: BAL, price: BAL_PRICE })]) - }) - - it('should return loading when input votes and pools still loading', () => { - const inputLoading = true - - const result = renderHook([], [], total(1), total(100), {}, inputLoading) - - expect(result.current.isLoading).toBe(true) - }) - - it('should filter out time locked votes', async () => { - const pool1 = votingPool(ADDRESSS_POOL_1, SYMBOL_POOL_1) - const pool2 = votingPool(ADDRESSS_POOL_2, SYMBOL_POOL_2) - pool2.votingIncentive = incentivesWithAmount(10, 0) - const timelockedVote = timelock(vote(pool1, 0.05, new Date())) - - const result = renderHook([pool1, pool2], [timelockedVote], total(1), total(100), {}, false) - await waitFor(() => expect(result.current.isLoading).toBeFalsy()) - const vote2 = getVote(result.current, pool2.gauge.address as Address) - - expect(result.current.votes.length).toBe(1) - expect(vote2?.votePrct).toBe(0.95) - }) - - it('should zero out killed pools', async () => { - const pool1 = votingPool(ADDRESSS_POOL_1, SYMBOL_POOL_1) - const killedPool = kill(pool1) - const pool2 = votingPool(ADDRESSS_POOL_2, SYMBOL_POOL_2) - pool2.votingIncentive = incentivesWithAmount(10, 0) - const vote1 = vote(pool1, 0.05, one_month_ago) - - const result = renderHook([killedPool, pool2], [vote1], total(1), total(100), {}, false) - await waitFor(() => expect(result.current.isLoading).toBeFalsy()) - const killedPoolVote = getVote(result.current, killedPool.gauge.address as Address) - const vote2 = getVote(result.current, pool2.gauge.address as Address) - - expect(result.current.votes.length).toBe(2) - expect(killedPoolVote?.votePrct).toBe(0.0) - expect(vote2?.votePrct).toBe(1.0) - }) - - it('should distribute votes to the pool with biggest reward / vote', async () => { - const pool1 = votingPool(ADDRESSS_POOL_1, SYMBOL_POOL_1) - setPoolVotes(pool1, 0.5) - pool1.votingIncentive = incentivesWithAmount(10, 0) - const pool2 = votingPool(ADDRESSS_POOL_2, SYMBOL_POOL_2) - setPoolVotes(pool2, 0.5) - pool2.votingIncentive = incentivesWithAmount(100, 0) - - const result = renderHook([pool1, pool2], [], total(5), total(100), {}, false) - await waitFor(() => expect(result.current.isLoading).toBeFalsy()) - const vote2 = getVote(result.current, pool2.gauge.address as Address) - - expect(result.current.votes.length).toBe(1) - expect(vote2?.votePrct).toBe(1.0) - }) - - it('should remove old votes from gauges total', async () => { - const pool1 = votingPool(ADDRESSS_POOL_1, SYMBOL_POOL_1) - setPoolVotes(pool1, 0.5) - pool1.votingIncentive = incentivesWithAmount(100, 0) - const pool2 = votingPool(ADDRESSS_POOL_2, SYMBOL_POOL_2) - setPoolVotes(pool2, 0.5) - pool2.votingIncentive = incentivesWithAmount(100, 0) - const vote1 = vote(pool1, 1.0, one_month_ago) - - const result = renderHook([pool1, pool2], [vote1], total(1), total(100), {}, false) - await waitFor(() => expect(result.current.isLoading).toBeFalsy()) - const vote1Updated = getVote(result.current, pool1.gauge.address as Address) - - expect(result.current.votes.length).toBe(1) - expect(vote1Updated?.votePrct).toBe(1.0) - }) - - it('should remove blacklisted votes from gauges total', async () => { - const pool1 = votingPool(ADDRESSS_POOL_1, SYMBOL_POOL_1) - setPoolVotes(pool1, 0.5) - pool1.votingIncentive = incentivesWithAmount(100, 0) - const pool2 = votingPool(ADDRESSS_POOL_2, SYMBOL_POOL_2) - setPoolVotes(pool2, 0.5) - pool2.votingIncentive = incentivesWithAmount(100, 0) - const blacklistedVotes = { [ADDRESSS_POOL_2]: total(1) } as Record - - const result = renderHook([pool1, pool2], [], total(1), total(100), blacklistedVotes, false) - await waitFor(() => expect(result.current.isLoading).toBeFalsy()) - const vote2 = getVote(result.current, pool2.gauge.address as Address) - - expect(result.current.votes.length).toBe(1) - expect(vote2?.votePrct).toBe(1.0) - }) - - it('should calculate the amount of incentives', async () => { - const pool1 = votingPool(ADDRESSS_POOL_1, SYMBOL_POOL_1) - setPoolVotes(pool1, 0.0) - pool1.votingIncentive = incentivesWithAmount(100, 0) - - const result = renderHook([pool1], [], total(1), total(0), {}, false) - await waitFor(() => expect(result.current.isLoading).toBeFalsy()) - const vote1 = getVote(result.current, pool1.gauge.address as Address) - - expect(vote1?.incentivesAmount).toBe(100) - }) - - it('should return the total number of incentives (timelocked + optimized)', async () => { - const pool1 = votingPool(ADDRESSS_POOL_1, SYMBOL_POOL_1) - pool1.votingIncentive = incentivesWithAmount(100, 0) - setPoolVotes(pool1, 0.0) - const pool2 = votingPool(ADDRESSS_POOL_2, SYMBOL_POOL_2) - pool2.votingIncentive = incentivesWithAmount(100, 0) - setPoolVotes(pool2, 0.05) - const timelockedVote = timelock(vote(pool2, 0.05, new Date())) - - const result = renderHook([pool1, pool2], [timelockedVote], total(100), total(100), {}, false) - await waitFor(() => expect(result.current.isLoading).toBeFalsy()) - - expect(result.current.totalIncentives).toBe(200) - }) - - it('should cap incentives on maxTokensPerVote', async () => { - const pool1 = votingPool(ADDRESSS_POOL_1, SYMBOL_POOL_1) - pool1.votingIncentive = incentivesWithAmount(100, 0.5, 0.5) - mockTokenPricesList([aTokenPriceMock({ address: BAL, price: 0.5 })]) - - const result = renderHook([pool1], [], total(100), total(100), {}, false) - await waitFor(() => expect(result.current.isLoading).toBeFalsy()) - - expect(result.current.totalIncentives).toBe(25) - }) - - it('should respect relativeWeightCap and distribute remaining votes to other pools', async () => { - const pool1 = votingPool(ADDRESSS_POOL_1, SYMBOL_POOL_1) - setPoolVotes(pool1, 0.05) - pool1.votingIncentive = incentivesWithAmount(100, 0) - pool1.gauge.relativeWeightCap = '0.1' - - const pool2 = votingPool(ADDRESSS_POOL_2, SYMBOL_POOL_2) - setPoolVotes(pool2, 0.05) - pool2.votingIncentive = incentivesWithAmount(50, 0) - - // User has 10% of total votes to distribute - const result = renderHook([pool1, pool2], [], total(10), total(100), {}, false) - await waitFor(() => expect(result.current.isLoading).toBeFalsy()) - - const vote1 = getVote(result.current, pool1.gauge.address as Address) - const vote2 = getVote(result.current, pool2.gauge.address as Address) - - // Pool1 can receive 5% more weight (to reach 10% cap) - // With user's 10% voting power, that requires 50% of user's votes (5/10 = 0.5) - // Pool2 receives the remaining 50% of user's votes despite the lower incentives - expect(vote1?.votePrct).toBe(0.5) - expect(vote2?.votePrct).toBe(0.5) - }) - - it('should skip pools that already exceed their relativeWeightCap', async () => { - // Pool 1 already has 15% of total votes with a 10% cap - const pool1 = votingPool(ADDRESSS_POOL_1, SYMBOL_POOL_1) - setPoolVotes(pool1, 0.15) - pool1.votingIncentive = incentivesWithAmount(200, 0) - pool1.gauge.relativeWeightCap = '0.1' // 10% cap, but already at 15% - - // Pool2 has no cap - const pool2 = votingPool(ADDRESSS_POOL_2, SYMBOL_POOL_2) - setPoolVotes(pool2, 0.05) - pool2.votingIncentive = incentivesWithAmount(50, 0) - - const result = renderHook([pool1, pool2], [], total(10), total(100), {}, false) - await waitFor(() => expect(result.current.isLoading).toBeFalsy()) - - const vote1 = getVote(result.current, pool1.gauge.address as Address) - const vote2 = getVote(result.current, pool2.gauge.address as Address) - - expect(vote1).toBeUndefined() // Pool1 should receive no votes since it already exceeds cap - expect(vote2?.votePrct).toBe(1.0) // Pool2 should receive all user votes - }) - - it('should not allocate votes if all pools have reached their cap', async () => { - const pool1 = votingPool(ADDRESSS_POOL_1, SYMBOL_POOL_1) - setPoolVotes(pool1, 0.15) - pool1.votingIncentive = incentivesWithAmount(200, 0) - pool1.gauge.relativeWeightCap = '0.1' // 10% cap, but already at 15% - - const pool2 = votingPool(ADDRESSS_POOL_2, SYMBOL_POOL_2) - setPoolVotes(pool2, 0.12) - pool2.votingIncentive = incentivesWithAmount(100, 0) - pool2.gauge.relativeWeightCap = '0.1' // 10% cap, but already at 12% - - const result = renderHook([pool1, pool2], [], total(10), total(100), {}, false) - await waitFor(() => expect(result.current.isLoading).toBeFalsy()) - - const vote1 = getVote(result.current, pool1.gauge.address as Address) - const vote2 = getVote(result.current, pool2.gauge.address as Address) - - // No votes should be distributed since all pools exceed their caps - expect(vote1).toBeUndefined() - expect(vote2).toBeUndefined() - expect(result.current.votes.length).toBe(0) - }) -}) - -function getVote(result: OptimizedVotes, gaugeAddress: Address) { - return result.votes.find(vote => vote.gaugeAddress === gaugeAddress) -} - -function votingPool(gaugeAddress: Address, symbol: string): VotingPoolWithData { - return { - __typename: 'GqlVotingPool', - id: '0xc771c1a5905420daec317b154eb13e4198ba97d0000000000000000000000023', - symbol, - address: '0xc771c1a5905420daec317b154eb13e4198ba97d0', - chain: GqlChain.Base, - type: GqlPoolType.ComposableStable, - protocolVersion: 2, - poolTokens: [], - gauge: { - __typename: 'GqlVotingGauge', - address: gaugeAddress, - isKilled: false, - relativeWeight: '0.009591975004609172', - }, - gaugeVotes: { - votes: '9591975004609171', // 0,96% - votesNextPeriod: '9591975004609171', // 0,96% - userVotes: '', // '500' = 5% - userSlope: 0n, - userEndLock: 0n, - lastUserVoteTime: 0, - }, - votingIncentive: incentivesWithAmount(0, 0), - } -} - -function vote(votingPool: VotingPoolWithData, votesPrct: number, voteTimestamp: Date) { - if (votingPool.gaugeVotes) { - votingPool.gaugeVotes.userVotes = (votesPrct * 10000).toString() - votingPool.gaugeVotes.lastUserVoteTime = millisecondsToSeconds(voteTimestamp.getTime()) - } - - return votingPool -} - -function setPoolVotes(votingPool: VotingPoolWithData, votesPrct: number) { - if (votingPool.gaugeVotes) { - votingPool.gaugeVotes.votesNextPeriod = bn(votesPrct).shiftedBy(18).toString() - } - - return votingPool -} - -function incentivesWithAmount( - amount: number, - maxTokensPerVote: number, - price: number = BAL_PRICE -): PoolVotingIncentivesPerWeek { - return { - totalValue: 0, - valuePerVote: -1, - incentives: [ - { - token: { - symbol: 'BAL', - address: '0xba100000625a3754423978a60c9317c58a424e3d', - amount, - chainId: 1, - price, - decimals: 18, - name: 'Balancer', - }, - maxTokensPerVote: maxTokensPerVote, - briber: '', - }, - ], - gauge: '', - } -} - -function kill(votingPool: VotingPoolWithData) { - votingPool.gauge.isKilled = true - return votingPool -} - -function timelock(vote: VotingPoolWithData) { - const nine_days_ago = subDays(new Date(), 9) - if (vote.gaugeVotes) { - vote.gaugeVotes.lastUserVoteTime = millisecondsToSeconds(nine_days_ago.getTime()) - } - - return vote -} - -function total(value: number) { - return bn(value).shiftedBy(18) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/useIncentivesOptimized.ts b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/useIncentivesOptimized.ts deleted file mode 100644 index fb0ec5952d..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/incentives-optimization/useIncentivesOptimized.ts +++ /dev/null @@ -1,336 +0,0 @@ -import { VotingPoolWithData, VotesState } from '@repo/lib/modules/vebal/vote/vote.types' -import { Address } from 'viem' -import { isVotingTimeLocked } from '../../myVotes.helpers' -import { bn, MAX_BIGNUMBER, sum } from '@repo/lib/shared/utils/numbers' -import { Element, heap, push, pop, Heap } from './heap' -import { useQuery } from '@tanstack/react-query' -import { useTokens } from '@repo/lib/modules/tokens/TokensProvider' -import BigNumber from 'bignumber.js' -import { isSameAddress } from '@repo/lib/shared/utils/addresses' -import { getRelativeWeightCap } from '@repo/lib/modules/vebal/vote/vote.helpers' -import { getVotesState } from '@repo/lib/modules/vebal/vote/vote.helpers' - -type OptimizedVote = { - gaugeAddress: Address - votePrct: number - incentivesAmount: number -} - -export type OptimizedVotes = { - votes: OptimizedVote[] - totalIncentives: number - isLoading: boolean -} - -type PoolInfo = { - gaugeAddress: Address - votes: BigNumber - incentives: { - tokenPrice: number - tokenAmount: number - maxTokenPerVote: number - }[] - userVotes: BigNumber - userPrct: BigNumber - relativeWeightCap: number -} - -// The smallest step that could be voted for is 0.01%, but in the past this has caused -// problems with the transactions. For now, we will set the step to 1% -const PERCENT_STEP = bn(0.01) - -export function useIncentivesOptimized( - votingPools: VotingPoolWithData[], - myVotes: VotingPoolWithData[], - userVotingPower: BigNumber, - totalVotes: BigNumber, - blacklistedVotes: Record, - inputsLoading: boolean -): OptimizedVotes { - const { isLoadingTokenPrices } = useTokens() - - const { data, isPending } = useQuery({ - queryKey: ['optimized-incentives'], - queryFn: () => - calculateIncentivesOptimized( - votingPools, - myVotes, - userVotingPower, - totalVotes, - blacklistedVotes - ), - enabled: !inputsLoading && !isLoadingTokenPrices, - }) - - const votes = data || [] - const [timelockedVotes] = filterTimelockedVotes(myVotes) - const totalIncentives = sumTotalIncentives( - votes, - timelockedVotes, - userVotingPower, - totalVotes, - blacklistedVotes - ) - - return { votes, totalIncentives, isLoading: isPending } -} - -export function calculateIncentivesOptimized( - votingPools: VotingPoolWithData[], - myVotes: VotingPoolWithData[], - userVotingPower: BigNumber, - totalVotes: BigNumber, - blacklistedVotes: Record -): OptimizedVote[] { - const [timelockedVotes, timelockedPrct] = filterTimelockedVotes(myVotes) - - let prctToDistribute = bn(1).minus(timelockedPrct) - - const availableVotingPools = votingPools - .filter(pool => !findByGaugeAddress(timelockedVotes, pool.gauge.address as Address)) - .filter(pool => !pool.gauge.isKilled) - - const prioritizedPools = buildPoolsWithPriorities( - availableVotingPools, - myVotes, - userVotingPower, - totalVotes, - blacklistedVotes - ) - - const stepVoteAmount = userVotingPower.times(PERCENT_STEP) - const cappedPools: PoolInfo[] = [] - - while (prctToDistribute.gt(0)) { - const bestPool = pop(prioritizedPools) - - if (!bestPool) break - - const newVotes = bestPool.votes.plus(stepVoteAmount) - const newVotesWeight = newVotes.dividedBy(totalVotes).toNumber() - - const votesState = getVotesState(bestPool.relativeWeightCap, newVotesWeight) - - if (votesState === VotesState.Exceeded) { - // Preserve capped pool only if it has accumulated votes - if (bestPool.userPrct.gt(0)) cappedPools.push(bestPool) - continue - } - - // Add votes to the best pool - bestPool.votes = newVotes - bestPool.userVotes = bestPool.userVotes.plus(stepVoteAmount) - bestPool.userPrct = bestPool.userPrct.plus(PERCENT_STEP) - - push(prioritizedPools, bestPool) - prctToDistribute = prctToDistribute.minus(PERCENT_STEP) - } - - const votesToReset = myVotes.filter( - pool => !findByGaugeAddress(timelockedVotes, pool.gauge.address as Address) - ) - - const votes = mergeOptimizedVotes(resetVotes(votesToReset), prioritizedPools, cappedPools) - - return votes -} - -function findByGaugeAddress(voteOrPools: VotingPoolWithData[], gaugeAddress: Address) { - return voteOrPools.find(item => isSameAddress(item.gauge.address, gaugeAddress)) -} - -function extractVoteAmountAndIncentives( - votingPools: VotingPoolWithData[], - totalVotes: BigNumber -): PoolInfo[] { - return votingPools - .map(pool => { - const voteAmount = totalVotes.shiftedBy(-18).times(bn(pool.gaugeVotes?.votesNextPeriod || 0n)) - const incentives = !pool.votingIncentive?.incentives - ? [] - : pool.votingIncentive.incentives.map(incentive => { - return { - tokenPrice: incentive.token.price, - tokenAmount: incentive.token.amount, - maxTokenPerVote: incentive.maxTokensPerVote, - } - }) - - return { - gaugeAddress: pool.gauge.address as Address, - votes: voteAmount, - incentives, - userVotes: bn(0), - userPrct: bn(0), - relativeWeightCap: getRelativeWeightCap(pool.gauge.relativeWeightCap, pool.id), - } - }) - .filter(poolInfo => poolInfo.incentives.length !== 0) -} - -function buildPoolsWithPriorities( - votingPools: VotingPoolWithData[], - myVotes: VotingPoolWithData[], - userVotingPower: BigNumber, - totalVotes: BigNumber, - blacklistedVotes: Record -) { - const votesAndIncentives = extractVoteAmountAndIncentives(votingPools, totalVotes) - removeCurrentVotesFromGauges(votesAndIncentives, myVotes, userVotingPower) - removeBlacklistedVotes(votesAndIncentives, blacklistedVotes) - - const priorityHeap = heap>(votesAndIncentives.length, incentivePerVote) - votesAndIncentives.forEach(pool => push(priorityHeap, pool)) - - return priorityHeap -} - -function incentivePerVote(pool: Element) { - if (!pool || pool.incentives.length === 0) return 0 - - const valuePerVote = pool.incentives.reduce((acc, incentive) => { - const incentiveTokenPrice = bn(incentive.tokenPrice) - const totalIncentives = incentiveTokenPrice.times(incentive.tokenAmount) - const maxValuePerVote = incentiveTokenPrice.times(incentive.maxTokenPerVote) - const expectedValuePerVote = bn(totalIncentives).div(pool.votes.shiftedBy(-18)) - const valuePerVote = BigNumber.min( - maxValuePerVote.isZero() ? MAX_BIGNUMBER : maxValuePerVote, - expectedValuePerVote - ) - - return acc.plus(valuePerVote) - }, bn(0)) - - return valuePerVote.toNumber() -} - -function removeCurrentVotesFromGauges( - votingPools: PoolInfo[], - myVotes: VotingPoolWithData[], - userVotingPower: BigNumber -) { - return votingPools.map(pool => { - const oldVote = findOldVote(myVotes, pool.gaugeAddress) - const oldVotePrct = bn(oldVote?.gaugeVotes?.userVotes || 0).div(10000) - const oldVotesAmount = userVotingPower.times(oldVotePrct) - pool.votes = pool.votes.minus(oldVotesAmount) - return pool - }) -} - -function removeBlacklistedVotes( - votingPools: PoolInfo[], - blacklistedVotes: Record -) { - return votingPools.map(pool => { - const blacklistedVotesAmount = blacklistedVotes[pool.gaugeAddress] || 0 - pool.votes = pool.votes.minus(bn(blacklistedVotesAmount)) - return pool - }) -} - -function filterTimelockedVotes(myVotes: VotingPoolWithData[]): [VotingPoolWithData[], BigNumber] { - const timelockedVotes = myVotes.filter(vote => - isVotingTimeLocked(vote.gaugeVotes?.lastUserVoteTime || 0) - ) - - const timelockedPrct = timelockedVotes.reduce((acc, vote) => { - const userVotes = bn(vote.gaugeVotes?.userVotes || 0).div(10000) - return acc.plus(userVotes) - }, bn(0)) - - return [timelockedVotes, timelockedPrct] -} - -export function areAllVotesTimelocked(myVotes: VotingPoolWithData[]) { - const [, prct] = filterTimelockedVotes(myVotes) - return prct.isEqualTo(1) -} - -function resetVotes(myVotes: VotingPoolWithData[]) { - return myVotes.reduce((acc, vote) => { - acc.push({ - gaugeAddress: vote.gauge.address as Address, - votePrct: 0, - incentivesAmount: 0, - }) - - return acc - }, [] as OptimizedVote[]) -} - -function mergeOptimizedVotes( - votes: OptimizedVote[], - poolVotes: Heap>, - cappedPools: PoolInfo[] = [] -) { - const allPools = [...poolVotes.elements, ...cappedPools] - - return allPools.reduce((acc, pool) => { - if (pool && bn(pool.userVotes).gt(0)) { - const incentives = pool.userVotes.shiftedBy(-18).times(incentivePerVote(pool)) - - const vote = findVote(votes, pool.gaugeAddress) - if (!vote) { - acc.push({ - gaugeAddress: pool.gaugeAddress, - votePrct: pool.userPrct.toNumber(), - incentivesAmount: incentives.toNumber(), - }) - } else { - vote.votePrct = pool.userPrct.toNumber() - vote.incentivesAmount = incentives.toNumber() - } - } - - return acc - }, votes) -} - -function findVote(votes: OptimizedVote[], gaugeAddress: Address) { - return votes.find(vote => vote.gaugeAddress === gaugeAddress) -} - -function findOldVote(votes: VotingPoolWithData[], gaugeAddress: Address) { - return votes.find(vote => vote.gauge.address === gaugeAddress) -} - -function sumTotalIncentives( - optimizedVotes: OptimizedVote[], - timelockedVotes: VotingPoolWithData[], - userVotingPower: BigNumber, - totalVotes: BigNumber, - blacklistedVotes: Record -) { - const optimizedVotesAmount = sum(optimizedVotes, vote => bn(vote.incentivesAmount)) - - const timelockedVotesAmount = timelockedVotes.reduce((acc, vote) => { - const poolBlacklistedVotes = blacklistedVotes[vote.gauge.address as `0x${string}`] || bn(0) - const poolVotes = totalVotes - .times(bn(vote.gaugeVotes?.votesNextPeriod || 0).shiftedBy(-18)) - .minus(poolBlacklistedVotes) - .shiftedBy(-18) - const userPoolVotes = userVotingPower - .times(bn(vote.gaugeVotes?.userVotes || 0).div(10000)) - .shiftedBy(-18) - - if (!vote.votingIncentive?.incentives) return acc - - const valuePerVote = vote?.votingIncentive?.incentives.reduce((acc, incentive) => { - const incentiveTokenPrice = bn(incentive.token.price) - const totalIncentives = incentiveTokenPrice.times(incentive.token.amount) - const maxValuePerVote = incentiveTokenPrice.times(incentive.maxTokensPerVote) - const expectedValuePerVote = bn(totalIncentives).div(poolVotes) - const valuePerVote = BigNumber.min( - maxValuePerVote.isZero() ? MAX_BIGNUMBER : maxValuePerVote, - expectedValuePerVote - ) - - return acc.plus(valuePerVote) - }, bn(0)) - - return acc.plus(userPoolVotes.times(valuePerVote)) - }, bn(0)) - - return optimizedVotesAmount.plus(timelockedVotesAmount).toNumber() -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/GainBadge.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/GainBadge.tsx deleted file mode 100644 index 6753cba586..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/GainBadge.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Badge, HStack } from '@chakra-ui/react' -import { ArrowDownIcon } from '@repo/lib/shared/components/icons/ArrowDownIcon' -import { ArrowUpIcon } from '@repo/lib/shared/components/icons/ArrowUpIcon' -import { useCurrency } from '@repo/lib/shared/hooks/useCurrency' - -interface Props { - gain: BigNumber -} - -export function GainBadge({ gain }: Props) { - const { toCurrency } = useCurrency() - - return ( - - - {gain.lt(0) ? ( - - ) : gain.gt(0) ? ( - - ) : ( - '' - )} - <>{toCurrency(gain.abs(), { abbreviated: false })} - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/MyIncentivesTooltip.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/MyIncentivesTooltip.tsx deleted file mode 100644 index d375c56e99..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/MyIncentivesTooltip.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import StarsIcon from '@repo/lib/shared/components/icons/StarsIcon' -import { Icon, Stack, useTheme } from '@chakra-ui/react' -import { Popover, PopoverContent, PopoverTrigger, Text } from '@chakra-ui/react' - -export function MyIncentivesTooltip() { - const theme = useTheme() - - const popoverContent = ( - - - - {`Weekly bribes are provided by unaffiliated 3rd parties through the Votemarket platform - to incentivize liquidity to certain pools. This does not include additional yield that veBAL - holders also can earn (e.g. protocol revenue, extra boosts on liquidity mining incentives, - and swap fees).`} - - - - ) - - return ( - - <> - - - - - {popoverContent} - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/MyVebalChargeTooltip.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/MyVebalChargeTooltip.tsx deleted file mode 100644 index 951889fcfc..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/MyVebalChargeTooltip.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { - Badge, - Box, - Button, - HStack, - Popover, - PopoverContent, - PopoverTrigger, - Portal, - Text, - VStack, -} from '@chakra-ui/react' -import { differenceInSeconds, format } from 'date-fns' -import { oneYearInSecs } from '@repo/lib/shared/utils/time' -import { BatteryChargeIcon } from '@repo/lib/shared/components/icons/BatteryChargeIcon' -import React from 'react' -import { fNum } from '@repo/lib/shared/utils/numbers' -import { PRETTY_DATE_FORMAT } from '@bal/lib/vebal/lock/duration/lock-duration.constants' -import NextLink from 'next/link' -import { getVeBalManagePath } from '@bal/lib/vebal/vebal-navigation' - -interface Props { - lockedEndDate: number - expectedVeBalAmount: number - isLockExpired?: boolean - usePortal?: boolean -} - -function getLockedEndDatePercentage(lockedEndDate: number) { - const difference = differenceInSeconds(lockedEndDate, Date.now()) - return (100 * Math.max(difference, 0)) / oneYearInSecs -} - -export function MyVebalChargeTooltip({ - lockedEndDate, - isLockExpired, - expectedVeBalAmount, - usePortal, -}: Props) { - const lockedEndDatePercentage = getLockedEndDatePercentage(lockedEndDate) - - const popoverContent = ( - - - - - veBAL charge - - - {fNum('apr', lockedEndDatePercentage / 100)} - - - - - Expiry date - - - {format(lockedEndDate, PRETTY_DATE_FORMAT)} - - - - - veBAL if locked for 1 year - - - {fNum('token', expectedVeBalAmount)} - - - - - {isLockExpired && ( - - )} - - - - ) - - return ( - - <> - - - {isLockExpired ? ( - - Expired - - ) : ( - - )} - - - - {usePortal ? {popoverContent} : popoverContent} - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/MyVotesStatsCard.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/MyVotesStatsCard.tsx deleted file mode 100644 index 422eb186be..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/MyVotesStatsCard.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { Card, HStack, Text, Box, VStack } from '@chakra-ui/react' -import { ReactNode } from 'react' - -interface HeaderCardProps { - variant?: 'default' | 'special' | 'expired' - headerText: ReactNode - leftContent?: ReactNode - rightContent?: ReactNode -} - -const cardSpecialStyles = { - position: 'relative', - background: - 'linear-gradient(89.81deg, rgba(179, 174, 245, 0.12) -1.06%, rgba(215, 203, 231, 0.12) 27.62%, rgba(229, 200, 200, 0.12) 49.42%, rgba(234, 168, 121, 0.12) 98.68%)', - '&::before': { - content: '""', - position: 'absolute', - inset: '-1px', - padding: '1px', - borderRadius: 'inherit', - background: 'background.special', - WebkitMask: 'linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)', - WebkitMaskComposite: 'xor', - maskComposite: 'exclude', - pointerEvents: 'none', - }, -} - -const cardExpiredStyles = { - background: - 'radial-gradient(123.13% 233.05% at 50% 50%, rgba(244, 137, 117, 0) 0%, rgba(244, 137, 117, 0.15) 100%)', -} - -function getCardProps(variant: HeaderCardProps['variant']) { - switch (variant) { - case 'special': - return { - sx: cardSpecialStyles, - } - case 'expired': - return { - sx: cardExpiredStyles, - } - default: - return {} - } -} - -export function MyVotesStatsCard({ - headerText, - variant = 'default', - leftContent, - rightContent, -}: HeaderCardProps) { - const cardProps = getCardProps(variant) - return ( - - - - {headerText} - - - {leftContent || } - {rightContent} - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/useVeBALIncentives.integration.spec.ts b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/useVeBALIncentives.integration.spec.ts deleted file mode 100644 index 0bc9642923..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/useVeBALIncentives.integration.spec.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { testHook } from '@repo/lib/test/utils/custom-renderers' -import { useVeBALIncentives } from './useVeBALIncentives' -import { waitFor } from '@testing-library/react' - -describe('VeBAL incentives hook', () => { - it('should return the voting incentives', async () => { - const { result } = testHook(() => useVeBALIncentives('')) - await waitFor(() => expect(result.current.incentivesAreLoading).toBeFalsy()) - - const incentives = result.current.incentives - expect(incentives.voting).toBeDefined() - if (incentives.voting) expect(incentives.voting > 0).toBe(true) - }) -}) diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/useVeBALIncentives.ts b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/useVeBALIncentives.ts deleted file mode 100644 index d73d0e2b3e..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/useVeBALIncentives.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { GqlPoolAprItemType } from '@repo/lib/shared/services/api/generated/graphql' -import { useVeBALPool } from './useVeBALPool' - -type Incentives = { type: GqlPoolAprItemType; apr: number } - -function findIncentive(incentives: Incentives[], type: GqlPoolAprItemType) { - if (!incentives) return undefined - - const incentive = incentives.find(incentive => incentive.type === type) - return incentive?.apr -} - -export function useVeBALIncentives(userAddress: string) { - const { pool, poolIsLoading } = useVeBALPool(userAddress) - - const results = pool?.dynamicData.aprItems as Incentives[] - - const incentives = { - swapFee: findIncentive(results, GqlPoolAprItemType.SwapFee_24H), - locking: findIncentive(results, GqlPoolAprItemType.Locking), - voting: findIncentive(results, GqlPoolAprItemType.Voting), - } - - return { incentives, incentivesAreLoading: poolIsLoading } -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/useVeBALPool.ts b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/useVeBALPool.ts deleted file mode 100644 index 628f3bd749..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesStats/useVeBALPool.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { useQuery } from '@apollo/client/react' -import { GetPoolDocument, GqlChain } from '@repo/lib/shared/services/api/generated/graphql' - -const VEBAL_UNDERLYING_POOL = '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014' - -export function useVeBALPool(userAddress: string) { - const { data, loading } = useQuery(GetPoolDocument, { - variables: { - id: VEBAL_UNDERLYING_POOL, - chain: GqlChain.Mainnet, - userAddress: userAddress.toLowerCase(), - }, - }) - - const pool = data?.pool - - return { pool, poolIsLoading: loading } -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTable.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTable.tsx deleted file mode 100644 index f1929bb9eb..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTable.tsx +++ /dev/null @@ -1,127 +0,0 @@ -'use client' - -import { PaginatedTable } from '@repo/lib/shared/components/tables/PaginatedTable' -import { MyVotesTableHeader } from './MyVotesTableHeader' -import { MyVotesTableRow } from './MyVotesTableRow' -import { Card, Skeleton } from '@chakra-ui/react' -import { useIsMounted } from '@repo/lib/shared/hooks/useIsMounted' -import { VotingPoolWithData } from '@repo/lib/modules/vebal/vote/vote.types' -import { useMemo } from 'react' -import { MyVotesTotalRow } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTableTotalRow' -import { MyVotesSubmitRow } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTableSubmitRow' -import { useTotalVotes } from '../../../useTotalVotes' -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' - -interface Props { - myVotes: VotingPoolWithData[] - loading: boolean - noVeBALBalance: boolean -} - -enum RowType { - Data = 'Data', - Total = 'Total', - Submit = 'Submit', -} - -const rowProps = { - px: { base: 'sm', sm: '0' }, - gridTemplateColumns: `32px minmax(320px, 1fr) minmax(100px, max-content) minmax(140px, max-content) minmax(130px, max-content) minmax(120px, max-content) 50px`, - alignItems: 'center', - gap: { base: 'xxs', xl: 'lg' }, -} - -export function MyVotesTable({ myVotes, loading, noVeBALBalance }: Props) { - const isMounted = useIsMounted() - const { totalVotes, totalVotesLoading } = useTotalVotes() - const { isConnected } = useUserAccount() - - const items = useMemo(() => { - if (myVotes.length === 0) { - return [] - } - - const rows = myVotes.map( - myVote => - ({ - id: myVote.id, - vote: myVote, - type: RowType.Data, - }) as const - ) - - return [ - ...rows, - { - id: RowType.Total, - type: RowType.Total, - } as const, - { - id: RowType.Submit, - type: RowType.Submit, - } as const, - ] - }, [myVotes]) - - const TableHeader = useMemo(() => { - return function TableHeader() { - return - } - }, []) - - // Memoize component's link to skip recreation - const TableRow = useMemo(() => { - return function TableHeader({ item }: { item: (typeof items)[number]; index: number }) { - if (item.type === RowType.Total) { - return - } - - if (item.type === RowType.Submit) { - return - } - - return ( - - ) - } - }, [totalVotes]) - - if (!isMounted) return - - return ( - - item.id} - items={items} - loading={loading || totalVotesLoading} - loadingLength={3} - noItemsFoundLabel={ - !isConnected - ? 'Connect your wallet to see and edit your votes' - : noVeBALBalance - ? 'You have no active votes. Get some veBAL to start voting.' - : 'You have no active votes. Start by selecting some pool gauges from the table below.' - } - paginationProps={undefined} - renderTableHeader={TableHeader} - renderTableRow={TableRow} - showPagination={false} - /> - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTableHeader.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTableHeader.tsx deleted file mode 100644 index e90c73253a..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTableHeader.tsx +++ /dev/null @@ -1,90 +0,0 @@ -'use client' - -import { - Grid, - GridItem, - GridItemProps, - GridProps, - Icon, - PopoverContent, - Text, - VStack, -} from '@chakra-ui/react' -import { Globe, Trash2 } from 'react-feather' -import { SortableHeader, Sorting } from '@repo/lib/shared/components/tables/SortableHeader' -import { SortingBy } from '@bal/lib/vebal/vote/Votes/MyVotes/myVotes.types' -import { useMyVotes } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesProvider' -import { orderByHash } from '../myVotes.helpers' -const orderBy = Object.values(SortingBy) - -interface Props extends GridProps { - cellProps: GridItemProps -} - -export function MyVotesTableHeader({ cellProps, ...rest }: Props) { - const { - filtersState: { sorting, setSorting, sortingBy, setSortingBy, toggleSorting }, - } = useMyVotes() - - const handleSort = (newSortingBy: SortingBy) => { - if (sortingBy === newSortingBy) { - toggleSorting() - } else { - setSortingBy(newSortingBy) - setSorting(Sorting.desc) - } - } - - return ( - - - - - - - - - Pool name - - - {orderBy.map(orderByItem => ( - handleSort(orderByItem)} - popoverContent={ - orderByHash[orderByItem].title ? ( - - - {orderByHash[orderByItem].title} - - - ) : undefined - } - sorting={sorting} - /> - ))} - - - - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTableRow.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTableRow.tsx deleted file mode 100644 index cf66c47829..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTableRow.tsx +++ /dev/null @@ -1,222 +0,0 @@ -import { - Box, - Grid, - GridItem, - GridItemProps, - GridProps, - HStack, - IconButton, - Text, - Tooltip, - useToken, - VStack, -} from '@chakra-ui/react' -import Link from 'next/link' -import { NetworkIcon } from '@repo/lib/shared/components/icons/NetworkIcon' -import { useCurrency } from '@repo/lib/shared/hooks/useCurrency' -import FadeInOnView from '@repo/lib/shared/components/containers/FadeInOnView' -import { VotingPoolWithData } from '@repo/lib/modules/vebal/vote/vote.types' -import { VotingListTokenPills } from '@repo/lib/modules/pool/PoolList/PoolListTokenPills' -import { getPoolPath } from '@repo/lib/modules/pool/pool.utils' -import { ArrowUpIcon } from '@repo/lib/shared/components/icons/ArrowUpIcon' -import { VoteExpiredTooltip } from '@bal/lib/vebal/vote/VoteExpiredTooltip' -import { Trash2 } from 'react-feather' -import { useMyVotes } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesProvider' -import { VoteWeightInput } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesTable/VoteWeightInput' -import { - bpsToPercentage, - calculateMyValuePerVote, - calculateMyVoteRewardsValue, - inputPercentageWeightToBps, - votingTimeLockedEndDate, -} from '@bal/lib/vebal/vote/Votes/MyVotes/myVotes.helpers' - -import { useVotes } from '@bal/lib/vebal/vote/Votes/VotesProvider' -import { VoteWeight } from '@bal/lib/vebal/vote/Votes/MyVotes/VoteWeight' -import { isVotingTimeLocked } from '@bal/lib/vebal/vote/Votes/MyVotes/myVotes.helpers' -import { useVebalUserData } from '@bal/lib/vebal/useVebalUserData' -import { bn } from '@repo/lib/shared/utils/numbers' -import { canReceiveIncentives, useBlacklistedVotes } from '../incentivesBlacklist' -import { useVebalLockInfo } from '@bal/lib/vebal/useVebalLockInfo' -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' -import { useLastUserSlope } from '../../../useVeBALBalance' -import { Address } from 'viem' - -interface Props extends GridProps { - vote: VotingPoolWithData - totalVotes: bigint - keyValue: string | number - cellProps: GridItemProps -} - -export function MyVotesTableRow({ vote, totalVotes, keyValue, cellProps, ...rest }: Props) { - const { userAddress } = useUserAccount() - const { lastLockTimestamp } = useVebalUserData() - const { editVotesWeights, onEditVotesChange } = useMyVotes() - const { - votingPools, - isSelectedPool, - toggleVotingPool, - allowChangeVotes, - vebalLockTooShort, - isPoolGaugeExpired, - vebalIsExpired, - } = useVotes() - const { toCurrency } = useCurrency() - - const isGaugeExpired = isPoolGaugeExpired(vote) - - const editVotes = bpsToPercentage(editVotesWeights[vote.id] ?? 0).multipliedBy(100) - - const removable = isSelectedPool(vote) - - const timeLocked = isVotingTimeLocked(vote.gaugeVotes?.lastUserVoteTime ?? 0) - - const onRemove = () => toggleVotingPool(vote) - - const [fontSecondary] = useToken('colors', ['font.secondary']) - - const { noVeBALBalance } = useVebalUserData() - const { slope } = useLastUserSlope(userAddress) - - const isDisabled = timeLocked || !allowChangeVotes || (vebalIsExpired ?? true) || isGaugeExpired - - const { mainnetLockedInfo } = useVebalLockInfo() - const lockEnd = mainnetLockedInfo.lockedEndDate - const { blacklistedVotes } = useBlacklistedVotes(votingPools) - const rewards = calculateMyVoteRewardsValue( - editVotesWeights[vote.id] ?? 0, - vote, - slope, - lockEnd, - totalVotes, - blacklistedVotes[vote.gauge.address as Address] - ) - - const averageRewards = calculateMyValuePerVote( - editVotesWeights[vote.id] ?? 0, - vote, - slope, - lockEnd, - totalVotes, - blacklistedVotes[vote.gauge.address as Address] - ) - - const voteTimestamp = (vote.gaugeVotes?.lastUserVoteTime || 0) * 1000 - const isGaugeUnderpowered = - !!voteTimestamp && !!lastLockTimestamp && voteTimestamp < lastLockTimestamp - - return ( - - - - - - - - - - - {isGaugeExpired && } - - - - - - - - {vote.votingIncentive && canReceiveIncentives(userAddress) ? ( - {toCurrency(rewards, { abbreviated: false })} - ) : ( - - )} - - - {vote.votingIncentive && canReceiveIncentives(userAddress) ? ( - - {toCurrency(averageRewards, { - abbreviated: false, - forceThreeDecimals: true, - })} - - ) : ( - - )} - - - - - - - onEditVotesChange(vote.id, inputPercentageWeightToBps(value).toString()) - } - step={0.01} - textAlign="right" - width="100px" - /> - - - - - } - isDisabled={!removable} - onClick={onRemove} - variant="ghost" - /> - - - - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTableSubmitRow.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTableSubmitRow.tsx deleted file mode 100644 index c353012ae6..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTableSubmitRow.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { - Box, - Button, - Grid, - GridItem, - GridItemProps, - GridProps, - useDisclosure, -} from '@chakra-ui/react' -import FadeInOnView from '@repo/lib/shared/components/containers/FadeInOnView' -import { useRef } from 'react' -import { useMyVotes } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesProvider' -import { SubmitVotesModal } from '@bal/lib/vebal/vote/Votes/MyVotes/actions/submit/modal/SubmitVotesModal' -import { useVotes } from '@bal/lib/vebal/vote/Votes/VotesProvider' -import { PoolSelectionModal } from '../actions/submit/modal/PoolSelectionModal' -import { Address } from 'viem' - -interface Props extends GridProps { - keyValue: string | number - cellProps: GridItemProps -} - -export function MyVotesSubmitRow({ keyValue, cellProps, ...rest }: Props) { - const { - hasChanges, - hasExceededWeight, - refetchAll, - transactionSteps, - hasExpiredGauges, - hasNewVotes, - selectableVotes, - setSelectedVotes, - } = useMyVotes() - const { allowChangeVotes } = useVotes() - const { changedVotes } = useMyVotes() - const numberOfSelectableVotes = changedVotes.filter(vote => { - const previousWeight = vote.vote.gaugeVotes?.userVotes || '0' - return previousWeight === vote.weight - }).length - - const submitBtn = useRef(null) - - const { - isOpen: submitModalIsOpen, - onOpen: submitModalOnOpen, - onClose: submitModalOnClose, - } = useDisclosure() - - const { - isOpen: poolSelectionIsOpen, - onOpen: poolSelectionOnOpen, - onClose: poolSelectionOnClose, - } = useDisclosure() - - // hasExpiredGauges - allow to submit votes if there are expired pool gauges, so user could "remove" these pools - const isDisabled = - (!hasChanges && !hasExpiredGauges && !hasNewVotes) || hasExceededWeight || !allowChangeVotes - - const submitVotes = () => { - if (numberOfSelectableVotes > 0) { - setSelectedVotes(selectableVotes.map(vote => vote.vote.id) as Address[]) - return poolSelectionOnOpen() - } - submitModalOnOpen() - } - - const continueAfterPoolSelection = () => { - poolSelectionOnClose() - submitModalOnOpen() - } - - const handleClose = (anySuccess: boolean) => { - if (anySuccess) { - transactionSteps.resetTransactionSteps() - refetchAll() - } - submitModalOnClose() - } - - return ( - <> - - - - - - - - - - - - - - - - - - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTableTotalRow.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTableTotalRow.tsx deleted file mode 100644 index 1d2608f288..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/MyVotesTableTotalRow.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { - Box, - Button, - Grid, - GridItem, - GridItemProps, - GridProps, - Skeleton, - Text, - Divider, - VStack, -} from '@chakra-ui/react' -import { useCurrency } from '@repo/lib/shared/hooks/useCurrency' -import FadeInOnView from '@repo/lib/shared/components/containers/FadeInOnView' -import { useMyVotes } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesProvider' -import { VoteWeight } from '@bal/lib/vebal/vote/Votes/MyVotes/VoteWeight' -import { useVoteList } from '../../../VoteList/VoteListProvider' -import { bn } from '@repo/lib/shared/utils/numbers' -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' -import { canReceiveIncentives } from '../incentivesBlacklist' - -interface Props extends GridProps { - keyValue: string | number - cellProps: GridItemProps -} - -export function MyVotesTotalRow({ keyValue, cellProps, ...rest }: Props) { - const { userAddress } = useUserAccount() - const { totalInfo, clearAll, hasChanges, hasVotedBefore } = useMyVotes() - const { toCurrency } = useCurrency() - - const { incentivesAreLoading, gaugeVotesIsLoading } = useVoteList() - - return ( - - - - - - Total - - - - {incentivesAreLoading ? ( - - ) : totalInfo.totalRewardValue && canReceiveIncentives(userAddress) ? ( - - {toCurrency(totalInfo.totalRewardValue, { abbreviated: false })} - - ) : ( - - )} - - - - {incentivesAreLoading ? ( - - ) : totalInfo.averageRewardPerVote && canReceiveIncentives(userAddress) ? ( - - {toCurrency(totalInfo.averageRewardPerVote, { - abbreviated: false, - forceThreeDecimals: true, - })} - - ) : ( - - )} - - - - {gaugeVotesIsLoading ? ( - - ) : ( - - )} - - - - - - - - - - - - - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/VoteWeightInput.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/VoteWeightInput.tsx deleted file mode 100644 index 471f249c1c..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/MyVotesTable/VoteWeightInput.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { - Input, - InputGroup, - InputProps, - Box, - InputRightElement, - Tooltip, - useBoolean, - VStack, -} from '@chakra-ui/react' -import { blockInvalidNumberInput, bn } from '@repo/lib/shared/utils/numbers' -import { Percent } from 'react-feather' -import { useState } from 'react' -import { clamp } from 'lodash' -import { votingTimeLockedEndDate } from '../myVotes.helpers' -import { dateTimeLabelFor } from '@repo/lib/shared/utils/time' - -interface PercentageInputProps extends InputProps { - percentage: string - setPercentage: (value: string) => void - isTimeLocked: boolean - isLockExpired: boolean | undefined - isGaugeExpired: boolean | undefined - noBalance: boolean | undefined - isTooShort: boolean | undefined - lastVoteTime: number | undefined -} - -function parseValue(value: string) { - return value.replace('%', '') -} - -export function VoteWeightInput({ - percentage, - setPercentage, - isTimeLocked, - isLockExpired, - isGaugeExpired, - noBalance, - isTooShort, - lastVoteTime, - min = 0, - max = 100, - ...inputProps -}: PercentageInputProps) { - const handleChange = (e: React.ChangeEvent) => { - const value = parseValue(e.currentTarget.value) - setEditingValue(value) - - const numberValue = parseFloat(value) - if (Number.isNaN(numberValue)) return - - const _min = min ? bn(min).toNumber() : -Number.MAX_SAFE_INTEGER - const _max = max ? bn(max).toNumber() : Number.MAX_SAFE_INTEGER - setPercentage(clamp(numberValue, _min, _max).toString()) - } - - const [isEditing, { toggle }] = useBoolean(false) - const [editingValue, setEditingValue] = useState(parseFloat(percentage).toFixed(2)) - - const endOfLocking = dateTimeLabelFor(votingTimeLockedEndDate(lastVoteTime || 0)) - - let tooltipLabel = '' - if (isTimeLocked) { - tooltipLabel = `Your vote is timelocked. - Once you vote on a pool, your vote is fixed for 10 days. - No edits can be made until ${endOfLocking}` - } else if (isLockExpired) { - tooltipLabel = - "You can't vote because your lock has expired. Get new veBAL to vote by extending your lock." - } else if (isGaugeExpired) { - tooltipLabel = 'This pool gauge is expired. You must vote with 0% to reallocate these votes.' - } else if (noBalance) { - tooltipLabel = `You can't vote because you have no veBAL. - Extend your lock / get new veBAL to edit your votes` - } else if (isTooShort) { - tooltipLabel = `You can't vote because you have no veBAL. - Extend your lock / get new veBAL to edit your votes` - } else { - tooltipLabel = 'Unknown reason' - } - - function getInputValue() { - return isEditing ? editingValue : parseFloat(percentage).toFixed(2) - } - - return ( - - - - { - setEditingValue(parseFloat(percentage).toFixed(2)) - toggle() - }} - onChange={handleChange} - onFocus={() => { - setEditingValue(parseFloat(percentage).toFixed(2)) - toggle() - }} - onKeyDown={blockInvalidNumberInput} - type="number" - value={getInputValue()} - {...inputProps} - /> - - - - - - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/VoteExceededTooltip.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/VoteExceededTooltip.tsx deleted file mode 100644 index f03faf47ce..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/VoteExceededTooltip.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { - HStack, - Popover, - PopoverContent, - PopoverTrigger, - Portal, - Text, - VStack, -} from '@chakra-ui/react' -import { fNum } from '@repo/lib/shared/utils/numbers' -import { AlertIcon } from '@repo/lib/shared/components/icons/AlertIcon' -import { - bpsToPercentage, - WEIGHT_MAX_VOTES, -} from '@bal/lib/vebal/vote/Votes/MyVotes/myVotes.helpers' - -interface Props { - exceededWeight: BigNumber - usePortal?: boolean -} - -export function VoteExceededTooltip({ usePortal, exceededWeight }: Props) { - const popoverContent = ( - - - - {fNum('apr', bpsToPercentage(exceededWeight))} votes exceeded - - - Your votes can’t exceed {WEIGHT_MAX_VOTES}% - - - - ) - - return ( - - - - - - - - {usePortal ? {popoverContent} : popoverContent} - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/VoteTimeLockedTooltip.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/VoteTimeLockedTooltip.tsx deleted file mode 100644 index f025b66e2c..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/VoteTimeLockedTooltip.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { - HStack, - Popover, - PopoverContent, - PopoverTrigger, - Portal, - Text, - VStack, -} from '@chakra-ui/react' -import { WEIGHT_VOTE_DELAY } from '@bal/lib/vebal/vote/Votes/MyVotes/myVotes.helpers' -import { TimeLockIcon } from '@repo/lib/shared/components/icons/TimeLockIcon' -import { oneDayInMs } from '@repo/lib/shared/utils/time' -import { PRETTY_DATE_FORMAT } from '@bal/lib/vebal/lock/duration/lock-duration.constants' -import { format } from 'date-fns' - -interface Props { - timeLockedEndDate?: Date - usePortal?: boolean -} - -export function VoteTimeLockedTooltip({ timeLockedEndDate, usePortal }: Props) { - const popoverContent = ( - - - - Your vote is timelocked - - - Once you vote on a pool, your vote is fixed for {WEIGHT_VOTE_DELAY / oneDayInMs} days.{' '} - {timeLockedEndDate && - `No edits can be made until ${format(timeLockedEndDate, PRETTY_DATE_FORMAT)}`} - - - - ) - - return ( - - - - - - - - {usePortal ? {popoverContent} : popoverContent} - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/VoteUnallocatedTooltip.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/VoteUnallocatedTooltip.tsx deleted file mode 100644 index 70fd54dcbf..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/VoteUnallocatedTooltip.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { - HStack, - Popover, - PopoverContent, - PopoverTrigger, - Portal, - Text, - VStack, -} from '@chakra-ui/react' -import { fNum } from '@repo/lib/shared/utils/numbers' -import { AlertIcon } from '@repo/lib/shared/components/icons/AlertIcon' - -import { bpsToPercentage } from '@bal/lib/vebal/vote/Votes/MyVotes/myVotes.helpers' - -interface Props { - unallocatedWeight: BigNumber - usePortal?: boolean -} - -export function VoteUnallocatedTooltip({ usePortal, unallocatedWeight }: Props) { - const popoverContent = ( - - - - {fNum('apr', bpsToPercentage(unallocatedWeight))} votes unallocated - - - Since you’re not utilizing all of your votes, you are likely missing out on vote - incentives. - - - - ) - - return ( - - - - - - - - {usePortal ? {popoverContent} : popoverContent} - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/VoteUnderpoweredTooltip.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/VoteUnderpoweredTooltip.tsx deleted file mode 100644 index f098853320..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/VoteUnderpoweredTooltip.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { - HStack, - Popover, - PopoverContent, - PopoverTrigger, - Portal, - Text, - VStack, -} from '@chakra-ui/react' -import { AlertIcon } from '@repo/lib/shared/components/icons/AlertIcon' - -type Props = { - usePortal?: boolean - isTimelocked: boolean -} - -export function VoteUnderpoweredTooltip({ usePortal, isTimelocked }: Props) { - const title = isTimelocked - ? 'Resubmit your votes to utilize your full voting power once your timelock is over' - : 'Resubmit your votes to utilize your full voting power' - - const popoverContent = ( - - - - {title} - - - Your pool gauge was set at the time of your last vote and decays linearly until your veBAL - expiry. You have acquired new veBAL since your last vote. Re-vote to update the gauge with - your full veBAL voting power. - - - - ) - - return ( - - - - - - - - {usePortal ? {popoverContent} : popoverContent} - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/VoteWeight.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/VoteWeight.tsx deleted file mode 100644 index 24eb9790aa..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/VoteWeight.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { HStack, Text } from '@chakra-ui/react' -import { fNum } from '@repo/lib/shared/utils/numbers' -import { bpsToPercentage } from '@bal/lib/vebal/vote/Votes/MyVotes/myVotes.helpers' -import { - getExceededWeight, - getUnallocatedWeight, -} from '@bal/lib/vebal/vote/Votes/MyVotes/myVotes.helpers' -import { VoteUnallocatedTooltip } from '@bal/lib/vebal/vote/Votes/MyVotes/VoteUnallocatedTooltip' -import { VoteExceededTooltip } from '@bal/lib/vebal/vote/Votes/MyVotes/VoteExceededTooltip' -import { VoteTimeLockedTooltip } from '@bal/lib/vebal/vote/Votes/MyVotes/VoteTimeLockedTooltip' -import { VoteExpiredTooltip } from '@bal/lib/vebal/vote/VoteExpiredTooltip' -import { AlertIcon } from '@repo/lib/shared/components/icons/AlertIcon' -import { VoteUnderpoweredTooltip } from './VoteUnderpoweredTooltip' -import { ArrowRight } from 'react-feather' -import { BigNumber } from 'bignumber.js' - -interface Props { - timeLocked?: boolean - timeLockedEndDate?: Date - total?: boolean - skipTotalWarnings?: boolean - weight: BigNumber - variant: 'primary' | 'secondary' - isGaugeExpired?: boolean - isGaugeUnderpowered?: boolean - previousWeight?: BigNumber -} - -export function VoteWeight({ - timeLocked, - weight, - variant, - total = false, - skipTotalWarnings = false, - timeLockedEndDate, - isGaugeExpired, - isGaugeUnderpowered = false, - previousWeight, -}: Props) { - const exceededWeight = getExceededWeight(weight) - const unallocatedWeight = getUnallocatedWeight(weight) - - const showExceededWarning = total && !skipTotalWarnings && exceededWeight.gt(0) - const showUnallocatedWarning = total && !skipTotalWarnings && unallocatedWeight.gt(0) - - function getFontColor() { - if (total) { - if (showExceededWarning) return 'red.400' - if (showUnallocatedWarning) return 'font.warning' - - return 'font.maxContrast' - } - - if (isGaugeExpired || isGaugeUnderpowered) return 'font.warning' - - return variant === 'secondary' ? 'font.secondary' : undefined - } - - const fontColor = getFontColor() - - const weightText = - previousWeight && !previousWeight.isEqualTo(weight) ? ( - - ) : ( - - {fNum('apr', bpsToPercentage(weight))} - - ) - - return ( - - {timeLocked && } - {weightText} - {showExceededWarning && } - {showUnallocatedWarning && ( - - )} - {isGaugeExpired && ( - - - - - - )} - {isGaugeUnderpowered && } - - ) -} - -function ModifiedWeight({ previous, current }: { previous: BigNumber; current: BigNumber }) { - return ( - - - {fNum('apr', bpsToPercentage(previous))} - - - - - - {fNum('apr', bpsToPercentage(current))} - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/modal/PoolSelectionModal.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/modal/PoolSelectionModal.tsx deleted file mode 100644 index c9c784fffa..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/modal/PoolSelectionModal.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { - Button, - Checkbox, - CheckboxGroup, - HStack, - Modal, - ModalBody, - ModalCloseButton, - ModalContent, - ModalFooter, - ModalHeader, - ModalOverlay, - Text, - VStack, -} from '@chakra-ui/react' -import { SubmittingVote, useMyVotes } from '../../../MyVotesProvider' -import { NetworkIcon } from '@repo/lib/shared/components/icons/NetworkIcon' -import { VotingPoolWithData } from '@repo/lib/modules/vebal/vote/vote.types' -import { Address } from 'viem' - -type Props = { - isOpen: boolean - onClose(): void - onContinue(): void -} - -export function PoolSelectionModal({ isOpen, onClose, onContinue }: Props) { - const { selectableVotes, setSelectedVotes, changedVotes, submittableVotes } = useMyVotes() - const selectableVotesIds = selectableVotes.map(vote => vote.vote.id) - const updatedWeightVotes = changedVotes.filter(vote => !selectableVotesIds.includes(vote.vote.id)) - - const continueOrClose = () => { - if (submittableVotes.length > 0) return onContinue() - onClose() - } - - return ( - - - - Confirm vote resubmission - - - - - - Since you are not utilizing your full voting power on the pools listed below, it is - assumed you will want to re-submit your vote now. However, there are some cases where - you may not want to re-submit (e.g. to avoid the 10-day time lock), so please confirm. - - - - Select pools to resubmit: - - - vote.vote.id)} - onChange={(votes: string[]) => setSelectedVotes(votes as Address[])} - > - {selectableVotes.map(vote => ( - - - - ))} - - - {updatedWeightVotes.map(vote => ( - - - - ))} - - - - - - - - - ) -} - -function poolName(vote: VotingPoolWithData) { - return vote.poolTokens.map(token => token.underlyingToken?.symbol || token.symbol).join(' / ') -} - -function VoteDescription({ vote }: { vote: SubmittingVote }) { - return ( - - - {poolName(vote.vote)} - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/modal/SubmitVotesModal.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/modal/SubmitVotesModal.tsx deleted file mode 100644 index 1f73e3ceff..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/modal/SubmitVotesModal.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { DesktopStepTracker } from '@repo/lib/modules/transactions/transaction-steps/step-tracker/DesktopStepTracker' -import { Modal, ModalBody, ModalCloseButton, ModalContent, ModalProps } from '@chakra-ui/react' -import { RefObject, useRef } from 'react' -import { getStylesForModalContentWithStepTracker } from '@repo/lib/modules/transactions/transaction-steps/step-tracker/step-tracker.utils' -import { useBreakpoints } from '@repo/lib/shared/hooks/useBreakpoints' -import { SuccessOverlay } from '@repo/lib/shared/components/modals/SuccessOverlay' -import { MobileStepTracker } from '@repo/lib/modules/transactions/transaction-steps/step-tracker/MobileStepTracker' -import { ActionModalFooter } from '@repo/lib/shared/components/modals/ActionModalFooter' -import { TransactionModalHeader } from '@repo/lib/shared/components/modals/TransactionModalHeader' -import { AnimateHeightChange } from '@repo/lib/shared/components/animations/AnimateHeightChange' -import { useMyVotes } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesProvider' -import mainnetNetworkConfig from '@repo/lib/config/networks/mainnet' -import { SubmitVotesPreview } from '@bal/lib/vebal/vote/Votes/MyVotes/actions/submit/modal/SubmitVotesPreview' -import { useSubmittingVotes } from '@bal/lib/vebal/vote/Votes/MyVotes/actions/submit/useSubmittingVotes' -import { useVotes } from '@bal/lib/vebal/vote/Votes/VotesProvider' - -type Props = { - isOpen: boolean - onClose(anySuccess: boolean): void - onOpen(): void - finalFocusRef?: RefObject -} - -export function SubmitVotesModal({ - isOpen, - onClose, - finalFocusRef, - ...rest -}: Props & Omit) { - const { isDesktop, isMobile } = useBreakpoints() - const initialFocusRef = useRef(null) - const { transactionSteps, txHash, totalInfo, timeLockedVotes, unchangedVotes } = useMyVotes() - const { isPoolGaugeExpired } = useVotes() - - const chain = mainnetNetworkConfig.chain - - const { submittingVotesChunk, previousChunksAllocation, nextChunksAllocation } = - useSubmittingVotes() - - const handleClose = () => { - onClose(transactionSteps.steps.some(step => step.isComplete())) - } - - return ( - - - - - {isDesktop && } - - - - - {isMobile && } - - - - {transactionSteps.currentStep && ( - - )} - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/modal/SubmitVotesPreview.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/modal/SubmitVotesPreview.tsx deleted file mode 100644 index 4e40bab855..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/modal/SubmitVotesPreview.tsx +++ /dev/null @@ -1,246 +0,0 @@ -import { VStack, Card, HStack, Text, Divider, Box, Badge } from '@chakra-ui/react' -import { VotingListTokenPills } from '@repo/lib/modules/pool/PoolList/PoolListTokenPills' -import { SubmittingVote } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesProvider' -import { fNum, bn } from '@repo/lib/shared/utils/numbers' -import { - bpsToPercentage, - votingTimeLockedEndDate, -} from '@bal/lib/vebal/vote/Votes/MyVotes/myVotes.helpers' -import { NetworkIcon } from '@repo/lib/shared/components/icons/NetworkIcon' -import { MyVotesTotalInfo } from '@bal/lib/vebal/vote/Votes/MyVotes/myVotes.types' -import { VoteWeight } from '@bal/lib/vebal/vote/Votes/MyVotes/VoteWeight' -import { useCurrency } from '@repo/lib/shared/hooks/useCurrency' -import { VotesChunksAllocation } from '@bal/lib/vebal/vote/Votes/MyVotes/actions/submit/useSubmittingVotes' -import { AlertTriangle } from 'react-feather' -import { CHUNK_SIZE } from '@bal/lib/vebal/vote/Votes/MyVotes/actions/submit/useSubmitVotesSteps' -import { VotingPoolWithData } from '@repo/lib/modules/vebal/vote/vote.types' -import { GainBadge } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesStats/shared/GainBadge' -import { MyIncentivesTooltip } from '../../../MyVotesStats/shared/MyIncentivesTooltip' - -interface Props { - changedVotes: SubmittingVote[] - unchangedVotes: SubmittingVote[] - timeLockedVotes: SubmittingVote[] - totalInfo: MyVotesTotalInfo - previousChunksAllocation: VotesChunksAllocation | undefined - nextChunksAllocation: VotesChunksAllocation | undefined - isPoolGaugeExpired?: (votingPool: VotingPoolWithData) => boolean -} - -export function SubmitVotesPreview({ - changedVotes, - unchangedVotes, - timeLockedVotes, - totalInfo, - previousChunksAllocation, - nextChunksAllocation, - isPoolGaugeExpired, -}: Props) { - const { toCurrency } = useCurrency() - - const unallocatedVotes = totalInfo.unallocatedVotes || bn(0) - const editVotes = totalInfo.editVotes || bn(0) - - return ( - - - - - - Pool gauge - - - Votes - - - - {changedVotes.length > 0 && ( - <> - - - - {changedVotes.map(({ vote, weight }) => { - const isExpired = isPoolGaugeExpired?.(vote) - - return ( - - - - - - - {isExpired && ( - - Expired - - )} - - - - ) - })} - - - )} - - {(timeLockedVotes.length > 0 || unchangedVotes.length > 0) && ( - <> - - - - Unchanged votes - - - {timeLockedVotes.map(({ vote, weight }) => ( - - ))} - - {unchangedVotes.map(({ vote, weight }) => ( - - ))} - - - )} - - {totalInfo.unallocatedVotes && ( - <> - - - - - Unallocated votes - - {fNum('apr', bpsToPercentage(unallocatedVotes))} - - - )} - - {previousChunksAllocation && ( - <> - - - - - - - - - Votes confirmed in previous steps - - - - {fNum('apr', bpsToPercentage(previousChunksAllocation.weight))} - - - - )} - - {nextChunksAllocation && ( - <> - - - - - - - - - {nextChunksAllocation.count === 1 - ? `Votes to allocate in the next transaction` - : `Votes to allocate in the next ${nextChunksAllocation.count} transactions`}{' '} -
- (Only {CHUNK_SIZE} votes can be processed at a time) -
-
- - {fNum('apr', bpsToPercentage(nextChunksAllocation.weight))} - -
- - )} - - - - - Total - {fNum('apr', bpsToPercentage(editVotes))} - -
-
- - - - Potential incentives (1w) - - - {totalInfo.totalRewardValue !== undefined ? ( - toCurrency(totalInfo.totalRewardValue, { abbreviated: false }) - ) : ( - <>— - )} - - {totalInfo.totalRewardValueGain && } - - - - - {totalInfo.averageRewardPerVote !== undefined && ( - - Avg. Reward (Bribes/veBAL) - - {toCurrency(totalInfo.averageRewardPerVote, { abbreviated: false })} - - - )} - -
- ) -} - -type UnchangedVoteProps = { - vote: VotingPoolWithData - weight: string - timelocked: boolean -} - -function UnchangedVote({ vote, weight, timelocked }: UnchangedVoteProps) { - return ( - - - - - - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/useSubmitVotesAllSteps.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/useSubmitVotesAllSteps.tsx deleted file mode 100644 index dc4519e1d9..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/useSubmitVotesAllSteps.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { useSubmitVotesSteps } from '@bal/lib/vebal/vote/Votes/MyVotes/actions/submit/useSubmitVotesSteps' -import mainnetNetworkConfig from '@repo/lib/config/networks/mainnet' -import { SubmittingVote } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesProvider' - -type Params = { - votes: SubmittingVote[] -} - -export function useSubmitVotesAllSteps({ votes }: Params) { - const { steps, isLoading } = useSubmitVotesSteps(mainnetNetworkConfig.chainId, votes) - - return { - isLoadingSteps: isLoading, - steps, - } -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/useSubmitVotesSteps.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/useSubmitVotesSteps.tsx deleted file mode 100644 index fbae7a81ed..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/useSubmitVotesSteps.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { SupportedChainId } from '@repo/lib/config/config.types' -import { sentryMetaForWagmiSimulation } from '@repo/lib/shared/utils/query-errors' -import mainnetNetworkConfig from '@repo/lib/config/networks/mainnet' -import { Hex } from 'viem' -import { - TransactionLabels, - TransactionStep, -} from '@repo/lib/modules/transactions/transaction-steps/lib' -import { useStepsTransactionState } from '@repo/lib/modules/transactions/transaction-steps/useStepsTransactionState' -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' -import { ManagedTransactionInput } from '@repo/lib/modules/web3/contracts/useManagedTransaction' -import { ManagedTransactionButton } from '@repo/lib/modules/transactions/transaction-steps/TransactionButton' -import { SubmittingVote } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesProvider' -import { chunk } from 'lodash' -import { isTransactionSuccess } from '@repo/lib/modules/transactions/transaction-steps/transaction.helper' - -const submitVotesStepId = 'submit-votes' - -function getStepId(idx: number) { - return [submitVotesStepId, idx].join('_') -} - -export function getVotesTransactionsStepIndex(stepId: string) { - if (stepId.startsWith(submitVotesStepId)) { - return Number(stepId.split('_')[1]) - } - return undefined -} - -export const CHUNK_SIZE = 8 - -export function chunkVotes(votes: SubmittingVote[]) { - return chunk(votes, CHUNK_SIZE) -} - -function getVotesForManyGauges(votes: SubmittingVote[]) { - // Gauge Controller requires a fixed 8 Gauge Addresses - // We take the first 8 Voting Gauges - // If there's less than 8, fill the remaining with Zero Addresses - const gaugeAddresses = votes.map(item => item.vote.gauge.address as Hex) - const weights = votes.map(item => item.weight) - - const zeroAddresses: Hex[] = new Array(CHUNK_SIZE - gaugeAddresses.length).fill( - '0x0000000000000000000000000000000000000000' // ZeroAddress - ) - const zeroWeights: string[] = new Array(CHUNK_SIZE - gaugeAddresses.length).fill('0') - - return { - gaugeAddresses: [...gaugeAddresses, ...zeroAddresses], - weights: [...weights, ...zeroWeights].map(weight => BigInt(weight)), - } -} - -export function useSubmitVotesSteps( - chainId: SupportedChainId, - votes: SubmittingVote[] -): { - isLoading: boolean - steps: TransactionStep[] -} { - const { userAddress, isConnected } = useUserAccount() - const { getTransaction, setTransactionFn } = useStepsTransactionState() - - const gaugeControllerAddress = mainnetNetworkConfig.contracts.gaugeController as Hex - - const isLoading = false - - const chunks = chunkVotes(votes) - - const steps: TransactionStep[] = chunks.map((votesChunk, idx) => { - const labels: TransactionLabels = { - title: `Vote for ${votesChunk.length} pool gauges`, - description: 'Confirming votes', - init: 'Confirm votes', - confirming: 'Confirming votes...', - confirmed: 'Votes confirmed', - tooltip: 'Confirm votes', - } - - const { gaugeAddresses, weights } = getVotesForManyGauges(votesChunk) - - const txSimulationMeta = sentryMetaForWagmiSimulation( - 'Error in wagmi tx simulation: Submit votes', - { - idx, - gaugeControllerAddress, - gaugeAddresses, - weights, - userAddress, - chainId, - } - ) - - const stepId = getStepId(idx) - const transaction = getTransaction(stepId) - - const props: ManagedTransactionInput = { - contractAddress: gaugeControllerAddress, - contractId: 'balancer.gaugeControllerAbi', - functionName: 'vote_for_many_gauge_weights', // test tx: 0xf57f05f6f75040faaf2fdbf783c314e84907fa82a3a36312a34ec90c7b5d9e95 - labels, - chainId, - args: [gaugeAddresses, weights], - enabled: !!userAddress && !isLoading, - txSimulationMeta, - onTransactionChange: setTransactionFn(stepId), - } - - return { - id: stepId, - stepType: 'voteForManyGaugeWeights', - labels, - transaction, - isComplete: () => isConnected && isTransactionSuccess(transaction), - renderAction: () => , - } - }) - - return { - isLoading, - steps, - } -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/useSubmittingVotes.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/useSubmittingVotes.tsx deleted file mode 100644 index 18dc6b5002..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/actions/submit/useSubmittingVotes.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { SubmittingVote, useMyVotes } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesProvider' -import { - chunkVotes, - getVotesTransactionsStepIndex, -} from '@bal/lib/vebal/vote/Votes/MyVotes/actions/submit/useSubmitVotesSteps' -import { useMemo } from 'react' -import { sumBy } from 'lodash' -import { bn } from '@repo/lib/shared/utils/numbers' - -export interface VotesChunksAllocation { - count: number - weight: number -} - -export function useSubmittingVotes() { - const { transactionSteps, submittableVotes } = useMyVotes() - - const submittingVotesChunks = chunkVotes(submittableVotes) - - const currentStepVotesTransactionsIndex = transactionSteps.currentStep?.id - ? getVotesTransactionsStepIndex(transactionSteps.currentStep.id) - : undefined - - const { previousChunksAllocation, nextChunksAllocation, submittingVotesChunk } = useMemo<{ - previousChunksAllocation: VotesChunksAllocation | undefined - nextChunksAllocation: VotesChunksAllocation | undefined - submittingVotesChunk: SubmittingVote[] | undefined - }>(() => { - if (typeof currentStepVotesTransactionsIndex !== 'number') { - return { - previousChunksAllocation: undefined, - nextChunksAllocation: undefined, - submittingVotesChunk: [], - } - } - - const [previousVotesChunks, nextVotesChunks] = [ - submittingVotesChunks.slice(0, currentStepVotesTransactionsIndex), - submittingVotesChunks.slice(currentStepVotesTransactionsIndex + 1), - ] - - return { - previousChunksAllocation: - previousVotesChunks.length > 0 - ? { - count: previousVotesChunks.length, - weight: sumBy(previousVotesChunks.flat(), vote => bn(vote.weight).toNumber()), - } - : undefined, - nextChunksAllocation: - nextVotesChunks.length > 0 - ? { - count: nextVotesChunks.length, - weight: sumBy(nextVotesChunks.flat(), vote => bn(vote.weight).toNumber()), - } - : undefined, - submittingVotesChunk: submittingVotesChunks[currentStepVotesTransactionsIndex], - } - }, [submittingVotesChunks, currentStepVotesTransactionsIndex]) - - return { - submittingVotesChunk, - previousChunksAllocation, - nextChunksAllocation, - } -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/incentivesBlacklist.ts b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/incentivesBlacklist.ts deleted file mode 100644 index 3be7b825a1..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/incentivesBlacklist.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { AbiMap } from '@repo/lib/modules/web3/contracts/AbiMap' -import { Address, Hex } from 'viem' -import { mainnet } from 'viem/chains' -import mainnetNetworkConfig from '@repo/lib/config/networks/mainnet' -import { useReadContracts } from '@repo/lib/shared/utils/wagmi' -import { onlyExplicitRefetch } from '@repo/lib/shared/utils/queries' -import { bn } from '@repo/lib/shared/utils/numbers' -import { VoteListItem } from '@repo/lib/modules/vebal/vote/vote.types' -import { millisecondsToSeconds, nextThursday } from 'date-fns' -import { startOfDayUtc } from '@repo/lib/shared/utils/time' - -type VoteInfo = { - accountAddress: Address - gaugeAddress: Address - slope: bigint - power: bigint - end: bigint -} - -const BLACKLISTED_ADDRESSES: Address[] = [ - '0xaF52695E1bB01A16D33D7194C28C42b10e0Dbec2', // Aura -] - -export function canReceiveIncentives(address: Address) { - return !BLACKLISTED_ADDRESSES.includes(address) -} - -export function useBlacklistedVotes(votingPools: VoteListItem[]) { - const activeVotingPools = votingPools.filter(pool => !pool.gauge.isKilled) - - const { blacklistedVotes, isLoading: votesAreLoading } = useVotes(activeVotingPools) - const blacklistedVotesGroupedByGauge = blacklistedVotes - .map(vote => { - const votes = calculateCurrentUserVotes(vote) - - return { gaugeAddress: vote.gaugeAddress, votes } - }) - .reduce( - (acc, current) => { - acc[current.gaugeAddress] = (acc[current.gaugeAddress] || bn(0)).plus(current.votes) - return acc - }, - {} as Record - ) - - return { - isLoading: votesAreLoading, - blacklistedVotes: blacklistedVotesGroupedByGauge as Record, - } -} - -function useVotes(votingPools: VoteListItem[]) { - const calls = BLACKLISTED_ADDRESSES.flatMap(blacklistedAddress => { - return votingPools.map( - votingPool => - ({ - chainId: mainnet.id, - abi: AbiMap['balancer.gaugeControllerAbi'], - address: mainnetNetworkConfig.contracts.gaugeController as Hex, - functionName: 'vote_user_slopes', - args: [blacklistedAddress, votingPool.gauge.address], - }) as const - ) - }) - - const { data, isLoading } = useReadContracts({ - batchSize: 10_000, // 10kb batch ~ 75kb payload - allowFailure: false, - query: { - ...onlyExplicitRefetch, - enabled: true, - }, - contracts: calls, - }) - - const blacklistedVotesResult = data || [] - - const blacklistedVotes = blacklistedVotesResult.map((vote, i) => { - const accountAddress = calls[i]!.args[0] - const gaugeAddress = calls[i]!.args[1] as Address - - return { ...vote, accountAddress, gaugeAddress } - }) - - return { blacklistedVotes, isLoading } -} - -function calculateCurrentUserVotes(vote: VoteInfo) { - const slope = bn(vote.slope) - const nextVoteTimestamp = millisecondsToSeconds(startOfDayUtc(nextThursday(new Date())).getTime()) - const lockEndInSeconds = Number(vote.end) - - return slope.times(lockEndInSeconds - nextVoteTimestamp) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/myVotes.helpers.ts b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/myVotes.helpers.ts deleted file mode 100644 index 92d376381a..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/myVotes.helpers.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { oneDayInMs, startOfDayUtc, toJsTimestamp } from '@repo/lib/shared/utils/time' -import { bn, MAX_BIGNUMBER, Numberish } from '@repo/lib/shared/utils/numbers' -import { millisecondsToSeconds, nextThursday } from 'date-fns' -import { SortingBy } from './myVotes.types' -import BigNumber from 'bignumber.js' -import { VotingPoolWithData } from '@repo/lib/modules/vebal/vote/vote.types' -import { formatUnits } from 'viem' - -export const WEIGHT_VOTE_DELAY = 10 * oneDayInMs - -export const WEIGHT_MAX_VOTES = 100 - -export function bpsToPercentage(weight: Numberish) { - // 1556n -> 15.56% - return bn(weight).shiftedBy(-4) -} - -export function inputPercentageWeightToBps(weight: string | number) { - // 15.557% -> 1556n - return sharesToBps(bn(weight).toFixed(2, BigNumber.ROUND_HALF_UP)) -} - -export function sharesToBps(weight: string | number) { - // 15.55% -> 1555n - return bn(weight).shiftedBy(2) -} - -export function isVotingTimeLocked(lastVoteTime: number) { - const lastUserVoteTime = toJsTimestamp(lastVoteTime) - return Date.now() < lastUserVoteTime + WEIGHT_VOTE_DELAY -} - -export function votingTimeLockedEndDate(lastVoteTime: number) { - const lastUserVoteTime = toJsTimestamp(lastVoteTime) - return new Date(lastUserVoteTime + WEIGHT_VOTE_DELAY) -} - -export function getExceededWeight(weight: Numberish) { - return BigNumber.max(bn(weight).minus(sharesToBps(WEIGHT_MAX_VOTES)), 0) -} - -export function getUnallocatedWeight(weight: Numberish) { - return BigNumber.max(sharesToBps(WEIGHT_MAX_VOTES).minus(weight), 0) -} - -// Bribes for voted Vote (based on defilytica - HH Rewards) -// see: https://github.com/defilytica/balancer-tools-v2/blob/63b035b09efbe452eb2bd4fd9e2e0fc920745765/src/pages/VeBALVoter/index.tsx#L394 -export function calculateMyVoteRewardsValue( - newWeight: string | number, - votingPool: VotingPoolWithData, - slope: bigint, - lockEnd: number | undefined, - totalVotes: bigint, - blacklistedVotes: BigNumber = bn(0) -) { - const votingPower = calculateVotingPower(slope, lockEnd) - const oldWeight = votingPool.gaugeVotes?.userVotes || '0' - const currentUserVotes = votingPower.times(bpsToPercentage(oldWeight)) - const newUserVotes = votingPower.times(bpsToPercentage(newWeight)) - - const poolVoteCount = bn(formatUnits(totalVotes, 18)) - .times(bn(votingPool.gaugeVotes?.votesNextPeriod || 0n).shiftedBy(-18)) - .minus(blacklistedVotes.shiftedBy(-18)) - const newPoolVoteCount = bn(poolVoteCount).minus(currentUserVotes).plus(newUserVotes) - - if (!votingPool?.votingIncentive?.incentives) return bn(0) - - const valuePerVote = votingPool?.votingIncentive?.incentives.reduce((acc, incentive) => { - const incentiveTokenPrice = bn(incentive.token.price) - const totalIncentives = incentiveTokenPrice.times(incentive.token.amount) - const maxValuePerVote = incentiveTokenPrice.times(incentive.maxTokensPerVote) - const expectedValuePerVote = bn(totalIncentives).div(newPoolVoteCount) - const valuePerVote = BigNumber.min( - maxValuePerVote.isZero() ? MAX_BIGNUMBER : maxValuePerVote, - expectedValuePerVote - ) - - return acc.plus(valuePerVote) - }, bn(0)) - - const rewardInUSD = valuePerVote.times(newUserVotes) - - return rewardInUSD -} - -export function calculateMyValuePerVote( - newWeight: string | number, - votingPool: VotingPoolWithData, - slope: bigint, - lockEnd: number | undefined, - totalVotes: bigint, - blacklistedVotes: BigNumber = bn(0) -) { - const votingPower = calculateVotingPower(slope, lockEnd) - const newUserVotes = votingPower.times(bpsToPercentage(newWeight)) - const myRewards = calculateMyVoteRewardsValue( - newWeight, - votingPool, - slope, - lockEnd, - totalVotes, - blacklistedVotes - ) - - return bn(myRewards).div(newUserVotes) -} - -export function calculateVotingPower(slope: bigint, lockEnd: number | undefined) { - if (!lockEnd) return bn(0) - - const nextVoteTimestamp = millisecondsToSeconds(startOfDayUtc(nextThursday(new Date())).getTime()) - const lockEndInSeconds = millisecondsToSeconds(lockEnd) - - return bn(slope) - .shiftedBy(-18) - .times(lockEndInSeconds - nextVoteTimestamp) -} - -export const orderByHash: Record = { - bribes: { - label: 'Bribes', - title: - 'Voting incentives (referred to as ‘Bribes’ in DeFi) are provided by unaffiliated 3rd parties through the Votemarket 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.', - }, - currentVotes: { - label: 'My votes', - title: - 'Your previously confirmed votes. Your confirmed votes are timelocked for 10 days and cannot be changed during this period.', - }, - editVotes: { - label: 'Edit votes', - }, -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/myVotes.types.ts b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/myVotes.types.ts deleted file mode 100644 index 857990152c..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/myVotes.types.ts +++ /dev/null @@ -1,17 +0,0 @@ -export interface MyVotesTotalInfo { - totalRewardValue: BigNumber - prevTotalRewardValue: BigNumber - totalRewardValueGain: BigNumber - averageRewardPerVote: BigNumber - averageRewardPerVoteGain: BigNumber - currentVotes: BigNumber - editVotes: BigNumber - unallocatedVotes: BigNumber -} - -export enum SortingBy { - bribes = 'bribes', - bribesPerVebal = 'bribesPerVebal', - currentVotes = 'currentVotes', - editVotes = 'editVotes', -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/useMyVotesFiltersState.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/useMyVotesFiltersState.tsx deleted file mode 100644 index ae70eadfb5..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/MyVotes/useMyVotesFiltersState.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { useState } from 'react' -import { SortingBy } from '@bal/lib/vebal/vote/Votes/MyVotes/myVotes.types' -import { Sorting } from '@repo/lib/shared/components/tables/SortableHeader' - -export function useMyVotesFiltersState() { - const [sorting, setSorting] = useState(Sorting.desc) - const [sortingBy, setSortingBy] = useState(SortingBy.currentVotes) - - function toggleSorting() { - setSorting(sorting === Sorting.asc ? Sorting.desc : Sorting.asc) - } - - return { - sorting, - setSorting, - sortingBy, - setSortingBy, - toggleSorting, - } -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/VotesContainer.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/VotesContainer.tsx deleted file mode 100644 index 4751527f66..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/VotesContainer.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { GetVeBalVotingListDocument } from '@repo/lib/shared/services/api/generated/graphql' -import { getApolloServerClient } from '@repo/lib/shared/services/api/apollo-server.client' -import { VotesProvider } from '@bal/lib/vebal/vote/Votes/VotesProvider' -import { VoteListLayout } from '@bal/lib/vebal/vote/VoteList/VoteListLayout' -import { MyVotesLayout } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesLayout' -import { VoteListProvider } from '@bal/lib/vebal/vote/VoteList/VoteListProvider' -import { MyVotesProvider } from '@bal/lib/vebal/vote/Votes/MyVotes/MyVotesProvider' -import { TransactionStateProvider } from '@repo/lib/modules/transactions/transaction-steps/TransactionStateProvider' -import { VotesIntroductionLayout } from '@bal/lib/vebal/vote/Votes/VotesIntroduction/VotesIntroductionLayout' -import { Box, VStack } from '@chakra-ui/react' -import { DefaultPageContainer } from '@repo/lib/shared/components/containers/DefaultPageContainer' - -export async function VotesContainer() { - const client = getApolloServerClient() - - function getVebalVotingList() { - return client.query({ - query: GetVeBalVotingListDocument, - variables: { includeKilled: true }, - context: { - fetchOptions: { - cache: 'no-store', // Disable Next.js cache for large responses (>2MB) - }, - }, - }) - } - - const { data: voteListQueryData } = await getVebalVotingList() - - return ( - - - - - - - - - - - - - - - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/HomeIcon.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/HomeIcon.tsx deleted file mode 100644 index 689770240e..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/HomeIcon.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { SVGProps } from 'react' - -export function HomeIcon(props: SVGProps) { - return ( - - - - - - - - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotesIntroduction.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotesIntroduction.tsx deleted file mode 100644 index ca027ad81d..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotesIntroduction.tsx +++ /dev/null @@ -1,140 +0,0 @@ -'use client' - -import { - Heading, - HStack, - VStack, - Text, - Breadcrumb, - Box, - BreadcrumbItem, - BreadcrumbLink, - useBreakpointValue, - Button, -} from '@chakra-ui/react' -import { VotingDeadline } from '@bal/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/VotingDeadline' -import { ChevronRight } from 'react-feather' -import { Home } from 'react-feather' -import { DefaultPageContainer } from '@repo/lib/shared/components/containers/DefaultPageContainer' -import Noise from '@repo/lib/shared/components/layout/Noise' -import { RadialPattern } from '@bal/app/(marketing)/_lib/landing-v3/shared/RadialPattern' - -const linkStyles = { - color: 'font.secondary', - fontSize: '14px', - lineHeight: '16px', - fontWeight: 500, -} - -export function VotesIntroduction() { - const radialPatternProps = useBreakpointValue({ - base: { circleCount: 10, height: 1000, width: 1000 }, - md: { circleCount: 15, height: 1500, width: 1500 }, - xl: { circleCount: 12, height: 900, width: 900 }, - }) - - return ( - - - - - - - - - } - spacing="xs" - w="full" - zIndex="1" - > - - - - - - - - veBAL - - - - - Vote - - - - - - - - - - Vote on pool gauges, earn extra incentives. - - - - Voting on pool gauges helps to direct weekly BAL emissions and earns you - additional voting incentives from third-party platforms like Votemarket. - - - - - - - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotesIntroductionLayout.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotesIntroductionLayout.tsx deleted file mode 100644 index b8f26eeb78..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotesIntroductionLayout.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { VotesIntroduction } from '@bal/lib/vebal/vote/Votes/VotesIntroduction/VotesIntroduction' - -export function VotesIntroductionLayout() { - return -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/DeadlineDayTooltip.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/DeadlineDayTooltip.tsx deleted file mode 100644 index 66f5b5c72f..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/DeadlineDayTooltip.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { - Button, - Popover, - PopoverBody, - PopoverContent, - PopoverTrigger, - VStack, - Text, - SystemStyleObject, -} from '@chakra-ui/react' -import { ReminderButton } from './ReminderButton' -import { format } from 'date-fns' -import { ReactNode, useState } from 'react' -import { useBreakpoints } from '@repo/lib/shared/hooks/useBreakpoints' -import { CalendarReminderModal } from '../../MyVotes/CalendarReminderModal' - -type Props = { - children: ReactNode - title: string - day: Date - deadline: Date - sharedStyles: any - getDayStyles: (day: Date) => SystemStyleObject -} - -const popoverBoxShadow = [ - '0px 0px 0px 1px #00000005', - '1px 1px 1px -0.5px #0000000F', - '3px 3px 3px -1.5px #0000000F', - '6px 6px 6px -3px #0000000F', - '12px 12px 12px -6px #0000000F', - '24px 24px 24px -12px #0000000F', - '42px 42px 42px -24px #0000000F', - '-0.5px -0.5px 0px 0px #FFFFFF26', -].join(', ') - -export function DeadlineDayTooltip({ - children, - title, - day, - deadline, - sharedStyles, - getDayStyles, -}: Props) { - const { isMobile } = useBreakpoints() - - const [isCalendarReminderOpen, setIsCalendarReminderOpen] = useState(false) - - return ( - - - - - - - - - {title} - - - {format(day.setHours(deadline.getHours()), 'haaa zzzz, d LLLL yyyy')} - - - setIsCalendarReminderOpen(true)}> - Set weekly reminder - - - setIsCalendarReminderOpen(false)} - /> - - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/ReminderButton.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/ReminderButton.tsx deleted file mode 100644 index 6c184f4540..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/ReminderButton.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Button, ButtonProps } from '@chakra-ui/react' -import { Calendar } from 'react-feather' - -export function ReminderButton({ children, ...buttonProps }: ButtonProps) { - return ( - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/StaticCalendar.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/StaticCalendar.tsx deleted file mode 100644 index 930e7118ae..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/StaticCalendar.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import { Box, Grid, GridItem, Text } from '@chakra-ui/react' -import { - endOfWeek, - format, - startOfWeek, - eachDayOfInterval, - isWithinInterval, - isSameDay, - addWeeks, - isBefore, - startOfToday, -} from 'date-fns' -import { SystemStyleObject } from '@chakra-ui/styled-system' -import { useCallback } from 'react' -import { DeadlineDayTooltip } from './DeadlineDayTooltip' -import { Picture } from '@repo/lib/shared/components/other/Picture' - -function getWeekDays() { - const weekDays = eachDayOfInterval({ start: startOfWeek(new Date()), end: endOfWeek(new Date()) }) - return weekDays -} - -function getVisibleDays(startDate: Date, endDate: Date) { - const firstDay = startOfWeek(startDate) - const lastDay = endOfWeek(endDate) - const days = eachDayOfInterval({ start: firstDay, end: lastDay }) - - return days.map(day => ({ - day, - visible: isWithinInterval(day, { start: firstDay, end: lastDay }), - })) -} - -export interface StaticCalendarProps { - startDate: Date - endDate: Date - deadline: Date -} - -const sharedStyles = { - rounded: 'full', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - w: { base: '36px', xl: '40px' }, - h: { base: '36px', xl: '40px' }, - fontSize: { base: '12px', xl: '14px' }, - lineHeight: { base: undefined, xl: '20px' }, - letterSpacing: '0', - fontWeight: 500, - shadow: '2xl', - color: 'font.secondary', -} - -export function StaticCalendar({ startDate, endDate, deadline }: StaticCalendarProps) { - const weekDays = getWeekDays() - - const isDayDisabled = useCallback((day: Date) => isBefore(day, startOfToday()), []) - - const isDayActive = useCallback((day: Date) => isSameDay(day, deadline), [deadline]) - - const isDaySelected = useCallback( - (day: Date) => isSameDay(day, addWeeks(deadline, 1)), - [deadline] - ) - - const visibleDays = getVisibleDays(startDate, endDate) - - function getDayStyles(day: Date): SystemStyleObject { - const isDisabled = isDayDisabled(day) - const isActive = isDayActive(day) - const isSelected = isDaySelected(day) - const isToday = isSameDay(day, new Date()) - - if (isDisabled) { - return { - bg: 'background.level1', - shadow: 'sm', - } - } - - const styles: SystemStyleObject = {} - - if (isActive) { - styles.backgroundColor = 'font.highlight' - styles.color = 'font.dark' - styles.minWidth = '0 !important' - - if (isToday) { - styles.borderStyle = 'dashed' - styles.borderColor = 'black' - styles.borderWidth = '1px' - } - } else if (isSelected) { - styles.borderColor = 'font.highlight' - styles.borderStyle = 'dashed' - styles.color = 'font.highlight' - styles.borderWidth = '1px' - styles.minWidth = '0 !important' - - // Today styles override selected styles if both are true - if (isToday) { - styles.borderColor = 'font.link' - styles.borderStyle = 'dotted' - styles.color = 'font.link' - } - } else if (isToday) { - styles.borderColor = 'font.link' - styles.borderWidth = '1px' - styles.borderStyle = 'dotted' - styles.color = 'font.link' - } - - return styles - } - - return ( - - {weekDays.map(weekDay => ( - - - {format(weekDay, 'E')} - - - ))} - - {visibleDays.map(({ day, visible }) => ( - - {visible && ( - <> - {!isDayActive(day) && !isDaySelected(day) ? ( - - - - {format(day, 'd')} - - - - - - - - ) : ( - - {format(day, 'd')} - - )} - - )} - - ))} - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/VotingDeadline.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/VotingDeadline.tsx deleted file mode 100644 index 1b0a09c411..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/VotingDeadline.tsx +++ /dev/null @@ -1,47 +0,0 @@ -'use client' - -import { Box, HStack, Text, VStack } from '@chakra-ui/react' -import { VotingDeadlineCalendar } from './VotingDeadlineCalendar' -import { VotingDeadlineCounter } from './VotingDeadlineCounter' -import { Picture } from '@repo/lib/shared/components/other/Picture' - -export function VotingDeadline() { - return ( - - - - - - - Weekly voting deadline - - - - - - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/VotingDeadlineCalendar.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/VotingDeadlineCalendar.tsx deleted file mode 100644 index e84b72e4d4..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/VotingDeadlineCalendar.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { addWeeks, endOfWeek, startOfWeek } from 'date-fns' -import { VotingDeadlineContainer } from './VotingDeadlineContainer' -import { StaticCalendar } from './StaticCalendar' -import { nextVotingDeadline } from './deadline' - -export function VotingDeadlineCalendar() { - const deadline = nextVotingDeadline() - const startDate = startOfWeek(deadline) - const endDate = endOfWeek(addWeeks(deadline, 1)) - - return ( - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/VotingDeadlineContainer.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/VotingDeadlineContainer.tsx deleted file mode 100644 index f928e180e8..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/VotingDeadlineContainer.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { PropsWithChildren } from 'react' -import { Card, CardProps, Box } from '@chakra-ui/react' -import { Picture } from '@repo/lib/shared/components/other/Picture' - -export function VotingDeadlineContainer({ - children, - ...stackProps -}: PropsWithChildren & CardProps) { - return ( - - - - - - - {children} - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/VotingDeadlineCounter.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/VotingDeadlineCounter.tsx deleted file mode 100644 index 1152b4e100..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/VotingDeadlineCounter.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { HStack, VStack, Text, Box } from '@chakra-ui/react' -import { useCurrentDate, useDateCountdown } from '@repo/lib/shared/hooks/date.hooks' -import { differenceInMinutes, format } from 'date-fns' -import { oneSecondInMs } from '@repo/lib/shared/utils/time' -import { VotingDeadlineContainer } from './VotingDeadlineContainer' -import { ReminderButton } from './ReminderButton' -import { Picture } from '@repo/lib/shared/components/other/Picture' -import { CalendarReminderModal } from '../../MyVotes/CalendarReminderModal' -import { useState } from 'react' -import { nextVotingDeadline } from './deadline' - -export function VotingDeadlineCounter() { - const now = useCurrentDate(oneSecondInMs) - const deadline = nextVotingDeadline() - - const { daysDiff, hoursDiff, minutesDiff, secondsDiff } = useDateCountdown(deadline) - - const counters = [ - { title: 'D', value: daysDiff }, - { title: 'H', value: hoursDiff }, - { title: 'M', value: minutesDiff }, - { title: 'S', value: secondsDiff }, - ] - - const closeToDeadline = differenceInMinutes(deadline, now) < 30 - - const [isCalendarReminderOpen, setIsCalendarReminderOpen] = useState(false) - - return ( - - - - - {format(deadline, 'EEEE, haaa zzzz')} - - setIsCalendarReminderOpen(true)}> - Get reminders - - setIsCalendarReminderOpen(false)} - /> - - - {counters.map(counter => ( - - - - - - - - {counter.title} - - - {String(counter.value).padStart(2, '0')} - - - - ))} - - - - ) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/deadline.spec.ts b/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/deadline.spec.ts deleted file mode 100644 index 4e53230ba8..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/deadline.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { nextVotingDeadline } from './deadline' - -afterEach(() => { - vi.unstubAllEnvs() -}) - -const TODAY = new Date('2025-04-04T10:00:00.000Z') // Today is Friday 4 April 2025 at 10 AM -const NEXT_THURSDAY = new Date('2025-04-10T00:00:00.000Z') - -describe('Deadline calculation', () => { - it('should work on Madrid', () => { - vi.stubEnv('TZ', 'Europe/Madrid') - vi.setSystemTime(TODAY) - - const result = nextVotingDeadline() - - expect(result).toEqual(NEXT_THURSDAY) - }) - - it('should work on Florianopolis', () => { - vi.stubEnv('TZ', 'America/Sao_Paulo') - vi.setSystemTime(new Date('2025-04-04T10:00:00.000Z')) - - const result = nextVotingDeadline() - - expect(result).toEqual(NEXT_THURSDAY) - }) -}) diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/deadline.ts b/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/deadline.ts deleted file mode 100644 index 00c1c6230e..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/VotesIntroduction/VotingDeadline/deadline.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { nextThursday } from 'date-fns' - -export function nextVotingDeadline() { - return new Date(nextThursday(new Date()).setUTCHours(0, 0, 0, 0)) -} diff --git a/apps/frontend-v3/lib/vebal/vote/Votes/VotesProvider.tsx b/apps/frontend-v3/lib/vebal/vote/Votes/VotesProvider.tsx deleted file mode 100644 index 98f6634f38..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/Votes/VotesProvider.tsx +++ /dev/null @@ -1,185 +0,0 @@ -'use client' - -import { createContext, PropsWithChildren, useCallback, useMemo, useState } from 'react' -import { GetVeBalVotingListQuery } from '@repo/lib/shared/services/api/generated/graphql' -import { useMandatoryContext } from '@repo/lib/shared/utils/contexts' -import { useGaugeVotes } from '@repo/lib/modules/vebal/vote/useGaugeVotes' -import { VotingPoolWithData } from '@repo/lib/modules/vebal/vote/vote.types' -import { bn } from '@repo/lib/shared/utils/numbers' -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' -import { isVotingTimeLocked, sharesToBps } from '@bal/lib/vebal/vote/Votes/MyVotes/myVotes.helpers' -import { sumBy } from 'lodash' -import { useVebalLockInfo } from '@bal/lib/vebal/useVebalLockInfo' -import { useVebalUserData } from '@bal/lib/vebal/useVebalUserData' -import { useExpiredGauges } from '@bal/lib/vebal/vote/useExpiredGaugesQuery' -import { useVotingIncentives } from '@repo/lib/shared/services/voting-incentives/useVotingIncentives' -import { isGaugeExpired } from '@repo/lib/modules/vebal/vote/vote.helpers' -import { filterVotingPoolsForAnvilFork } from '@repo/lib/test/utils/wagmi/fork.helpers' -import { shouldUseAnvilFork } from '@repo/lib/config/app.config' - -export interface UseVotesArgs { - data: GetVeBalVotingListQuery | undefined - votingListLoading?: boolean - error?: any -} - -export function useVotesLogic({ data, votingListLoading = false, error }: UseVotesArgs) { - const { userAddress } = useUserAccount() - - const votingList = useMemo(() => { - const votingPools = data?.veBalGetVotingList || [] - return shouldUseAnvilFork ? filterVotingPoolsForAnvilFork(votingPools) : votingPools - }, [data?.veBalGetVotingList]) - - const gaugeAddresses = useMemo(() => votingList.map(vote => vote.gauge.address), [votingList]) - const { expiredGauges, isLoading: isExpiredGaugesLoading } = useExpiredGauges({ gaugeAddresses }) - const { incentives, incentivesError, incentivesAreLoading } = useVotingIncentives() - - const { - gaugeVotes, - isLoading: gaugeVotesIsLoading, - refetchAll, - } = useGaugeVotes({ gaugeAddresses }) - - const votingPools = useMemo(() => { - return votingList.map(vote => ({ - ...vote, - gaugeVotes: gaugeVotes ? gaugeVotes[vote.gauge.address] : undefined, - votingIncentive: incentives?.find(incentive => incentive.gauge === vote.gauge.address), - })) - }, [votingList, gaugeVotes, incentives]) - - const votedPools = useMemo( - () => votingPools.filter(vote => bn(vote.gaugeVotes?.userVotes || '0').gt(0)), - [votingPools] - ) - - const [votingPoolsByUser, setVotingPoolsByUser] = useState>( - {} - ) - - const selectedVotingPools = useMemo(() => { - if (!userAddress) return [] - - const pools = votingPoolsByUser[userAddress] ?? [] - - if (!gaugeVotes) return pools - - return pools.filter(pool => { - const votesData = gaugeVotes[pool.gauge.address] - - if (!votesData) return true - - return bn(votesData.userVotes).lte(0) - }) - }, [userAddress, gaugeVotes, votingPoolsByUser]) - - const toggleVotingPool = (votingPool: VotingPoolWithData) => { - if (!userAddress) return - - setVotingPoolsByUser(current => { - const userPools = current[userAddress] ?? [] - const exists = userPools.find(pool => pool.id === votingPool.id) - - const updated = exists - ? userPools.filter(pool => pool.id !== votingPool.id) - : [...userPools, votingPool] - - return { ...current, [userAddress]: updated } - }) - } - - const clearSelectedVotingPools = () => { - if (!userAddress) return - - setVotingPoolsByUser(current => ({ ...current, [userAddress]: [] })) - } - - const isSelectedPool = useCallback( - (votingPool: VotingPoolWithData) => - Boolean( - selectedVotingPools.find(selectedVotingPool => selectedVotingPool.id === votingPool.id) - ), - [selectedVotingPools] - ) - - const isVotedPool = useCallback( - (vote: VotingPoolWithData) => Boolean(votedPools.find(votedPool => votedPool.id === vote.id)), - [votedPools] - ) - - const isPoolGaugeExpired = useCallback( - (votingPool: VotingPoolWithData) => isGaugeExpired(expiredGauges, votingPool.gauge.address), - [expiredGauges] - ) - - const currentVotes = sumBy(votedPools, vote => bn(vote.gaugeVotes?.userVotes || 0).toNumber()) - - const hasTimeLockedPools = votedPools.some(votedPool => - isVotingTimeLocked(votedPool.gaugeVotes?.lastUserVoteTime ?? 0) - ) - - const hasAllVotingPowerTimeLocked = - votedPools.every(votedPool => - isVotingTimeLocked(votedPool.gaugeVotes?.lastUserVoteTime ?? 0) - ) && sharesToBps(100).eq(currentVotes) - - const { mainnetLockedInfo } = useVebalLockInfo() - const vebalIsExpired = mainnetLockedInfo.isExpired - const vebalLockTooShort = mainnetLockedInfo.lockTooShort - - const { noVeBALBalance } = useVebalUserData() - - const votingIsDisabled = - vebalIsExpired || vebalLockTooShort || noVeBALBalance || hasAllVotingPowerTimeLocked - - const allowChangeVotes = !votingIsDisabled - const allowSelectVotingPools = !votingIsDisabled - - const scrollToMyVotes = () => { - document.body.scrollIntoView({ behavior: 'smooth' }) - } - - return { - votingPools, - votingListLoading, - incentives, - incentivesError, - incentivesAreLoading, - loading: - votingListLoading || incentivesAreLoading || gaugeVotesIsLoading || isExpiredGaugesLoading, - isExpiredGaugesLoading, - count: votingPools.length, - error, - gaugeVotesIsLoading, - gaugeVotes, - votedPools, - selectedVotingPools, - clearSelectedVotingPools, - toggleVotingPool, - isSelectedPool, - isVotedPool, - isPoolGaugeExpired, - refetchAll, - hasTimeLockedPools, - hasAllVotingPowerTimeLocked, - vebalIsExpired, - vebalLockTooShort, - allowSelectVotingPools, - allowChangeVotes, - expiredGauges, - scrollToMyVotes, - } -} - -export const VotesContext = createContext | null>(null) - -export function VotesProvider({ children, ...props }: PropsWithChildren) { - const hook = useVotesLogic(props) - - return {children} -} - -export function useVotes() { - return useMandatoryContext(VotesContext, 'Votes') -} diff --git a/apps/frontend-v3/lib/vebal/vote/useExpiredGaugesQuery.tsx b/apps/frontend-v3/lib/vebal/vote/useExpiredGaugesQuery.tsx deleted file mode 100644 index dea2a9e93e..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/useExpiredGaugesQuery.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useReadContracts } from '@repo/lib/shared/utils/wagmi' -import { mainnet } from 'viem/chains' -import { AbiMap } from '@repo/lib/modules/web3/contracts/AbiMap' -import { Hex } from 'viem' -import { onlyExplicitRefetch } from '@repo/lib/shared/utils/queries' -import { useMemo } from 'react' - -const readContractsParams = { - batchSize: 10_000, // 10kb batch ~ 75kb payload - allowFailure: true, - query: onlyExplicitRefetch, -} as const - -function useExpiredGaugesQuery(gaugeAddresses: UseExpiredGaugesParams['gaugeAddresses']) { - return useReadContracts({ - ...readContractsParams, - query: { - ...readContractsParams.query, - }, - contracts: gaugeAddresses.map(gaugeAddress => { - return { - chainId: mainnet.id, - abi: AbiMap['balancer.liquidityGaugeV5Abi'], - address: gaugeAddress as Hex, - functionName: 'is_killed', - args: [], - } as const - }), - }) -} - -export interface UseExpiredGaugesParams { - gaugeAddresses: string[] -} - -export function useExpiredGauges({ gaugeAddresses }: UseExpiredGaugesParams) { - const expiredGaugesQuery = useExpiredGaugesQuery(gaugeAddresses) - - const expiredGauges = useMemo(() => { - if (expiredGaugesQuery.isLoading) { - return undefined - } - - return gaugeAddresses.filter((_gaugeAddress, index) => { - const isKilled = (expiredGaugesQuery.data ?? [])[index] - return isKilled === undefined ? true : isKilled.result - }) - }, [expiredGaugesQuery.data, expiredGaugesQuery.isLoading, gaugeAddresses]) - - return { - expiredGauges, - isLoading: expiredGaugesQuery.isLoading, - refetch: expiredGaugesQuery.refetch, - } -} diff --git a/apps/frontend-v3/lib/vebal/vote/useTotalVotes.ts b/apps/frontend-v3/lib/vebal/vote/useTotalVotes.ts deleted file mode 100644 index c0c3a458df..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/useTotalVotes.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { AbiMap } from '@repo/lib/modules/web3/contracts/AbiMap' -import { Hex } from 'viem' -import { mainnet } from 'viem/chains' -import { useReadContract } from '@repo/lib/shared/utils/wagmi' -import mainnetNetworkConfig from '@repo/lib/config/networks/mainnet' - -export function useTotalVotes() { - const { data, isLoading } = useReadContract({ - chainId: mainnet.id, - abi: AbiMap['balancer.gaugeControllerAbi'], - address: mainnetNetworkConfig.contracts.gaugeController as Hex, - functionName: 'get_total_weight', - args: [], - query: { enabled: true }, - }) - - const totalVotes = data || 0n - - return { totalVotes, totalVotesLoading: isLoading } -} diff --git a/apps/frontend-v3/lib/vebal/vote/useVeBALTotal.integration.spec.ts b/apps/frontend-v3/lib/vebal/vote/useVeBALTotal.integration.spec.ts deleted file mode 100644 index 7749f83804..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/useVeBALTotal.integration.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { testHook } from '@repo/lib/test/utils/custom-renderers' -import { useVeBALTotal } from './useVeBALTotal' -import { oneWeekInMs } from '@repo/lib/shared/utils/time' -import { waitFor } from '@testing-library/react' - -describe('VeBAL total custom hook', () => { - it('should return the current amount of VeBAL', async () => { - const now = Math.floor(Date.now() / oneWeekInMs) * oneWeekInMs - - const { result } = testHook(() => useVeBALTotal(now)) - await waitFor(() => expect(result.current.isLoading).toBeFalsy()) - - const totalAmount = result.current.totalAmount - expect(totalAmount).toBeDefined() - if (totalAmount) expect(totalAmount > 0).toBe(true) - }) -}) diff --git a/apps/frontend-v3/lib/vebal/vote/useVeBALTotal.ts b/apps/frontend-v3/lib/vebal/vote/useVeBALTotal.ts deleted file mode 100644 index 06daffcd40..0000000000 --- a/apps/frontend-v3/lib/vebal/vote/useVeBALTotal.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { mainnet } from 'viem/chains' -import { useReadContract } from '@repo/lib/shared/utils/wagmi' -import { Hex } from 'viem' -import mainnetNetworkConfig from '@repo/lib/config/networks/mainnet' -import { toUnixTimestamp } from '@repo/lib/shared/utils/time' -import { bn } from '@repo/lib/shared/utils/numbers' -import { AbiMap } from '@repo/lib/modules/web3/contracts/AbiMap' - -export function useVeBALTotal(timestamp: number) { - const { data, isLoading } = useReadContract({ - chainId: mainnet.id, - abi: AbiMap['balancer.veBAL'], - address: mainnetNetworkConfig.contracts.veBAL as Hex, - functionName: 'totalSupply', - args: [BigInt(toUnixTimestamp(timestamp))], - query: { enabled: true }, - }) - - const totalAmount = data !== undefined ? Number(bn(data).div(bn(10).pow(18))) : undefined - - return { totalAmount, isLoading } -} diff --git a/packages/lib/config/config.types.ts b/packages/lib/config/config.types.ts index 43aaa714a8..c261f8041a 100644 --- a/packages/lib/config/config.types.ts +++ b/packages/lib/config/config.types.ts @@ -153,7 +153,6 @@ type OptionsConfig = { hidePoolTypes: GqlPoolType[] hideProtocolVersion: string[] showPoolName: boolean - showVeBal: boolean showMaBeets: boolean allowCreateWallet: boolean isOnSafeAppList: boolean diff --git a/packages/lib/config/projects/balancer.ts b/packages/lib/config/projects/balancer.ts index 08c1993a85..b52fa046a9 100644 --- a/packages/lib/config/projects/balancer.ts +++ b/packages/lib/config/projects/balancer.ts @@ -45,14 +45,13 @@ export const ProjectConfigBalancer: ProjectConfig = { hidePoolTypes: [GqlPoolType.Fx, ...(isProd ? [GqlPoolType.LiquidityBootstrapping] : [])], hideProtocolVersion: [], showPoolName: false, - showVeBal: true, showMaBeets: false, allowCreateWallet: true, isOnSafeAppList: true, }, links: { appLinks: [ - { href: '/vebal', label: 'veBAL' }, + { href: '/vebal/manage', label: 'veBAL' }, ...(isDev || isStaging ? [{ href: '/lbp/create', label: 'LBP' }] : []), ], ecosystemLinks: [ diff --git a/packages/lib/config/projects/beets.ts b/packages/lib/config/projects/beets.ts index 5f73323ae2..6089e3ea97 100644 --- a/packages/lib/config/projects/beets.ts +++ b/packages/lib/config/projects/beets.ts @@ -23,7 +23,6 @@ export const ProjectConfigBeets: ProjectConfig = { hidePoolTypes: [GqlPoolType.LiquidityBootstrapping, GqlPoolType.CowAmm, GqlPoolType.Fx], hideProtocolVersion: ['cow'], showPoolName: true, - showVeBal: false, showMaBeets: true, allowCreateWallet: false, isOnSafeAppList: false, diff --git a/packages/lib/modules/pool/PoolDetail/PoolStats/PoolSnapshot/PoolSnapshotValues.tsx b/packages/lib/modules/pool/PoolDetail/PoolStats/PoolSnapshot/PoolSnapshotValues.tsx index 8309179bae..de9e239326 100644 --- a/packages/lib/modules/pool/PoolDetail/PoolStats/PoolSnapshot/PoolSnapshotValues.tsx +++ b/packages/lib/modules/pool/PoolDetail/PoolStats/PoolSnapshot/PoolSnapshotValues.tsx @@ -80,7 +80,7 @@ export function PoolSnapshotValues() { { - if (isEmpty(veBalBoostMap)) return - return veBalBoostMap[pool.id] - }, [veBalBoostMap]) - // If user has staked on Aura, use Aura APR const auraApr = pool.staking?.aura?.apr !== undefined ? pool.staking?.aura?.apr : undefined const hasAuraStakedBalanceValue = hasAuraStakedBalance(pool) const myAprRaw = - hasAuraStakedBalanceValue && auraApr - ? auraApr - : getTotalAprRaw(pool.dynamicData.aprItems, boost) + hasAuraStakedBalanceValue && auraApr ? auraApr : getTotalAprRaw(pool.dynamicData.aprItems) const poolMyStatsValues: PoolMyStatsValues | undefined = useMemo(() => { if (pool && pool.userBalance && !isLoadingPool && !isLoadingClaiming) { @@ -143,7 +133,6 @@ export function UserSnapshotValues() { pool={pool} poolId={pool.id} textProps={{ fontWeight: 'bold', fontSize: '2xl', lineHeight: '28px' }} - vebalBoost={boost || '1'} /> ) ) : ( @@ -161,7 +150,7 @@ export function UserSnapshotValues() { ) : ( )} diff --git a/packages/lib/modules/pool/PoolDetail/PoolUserEvents/BoostText.tsx b/packages/lib/modules/pool/PoolDetail/PoolUserEvents/BoostText.tsx deleted file mode 100644 index aaf988c095..0000000000 --- a/packages/lib/modules/pool/PoolDetail/PoolUserEvents/BoostText.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { useVebalBoost } from '@repo/lib/modules/vebal/useVebalBoost' -import { bn, fNum } from '@repo/lib/shared/utils/numbers' -import { useMemo } from 'react' -import { Pool } from '../../pool.types' -import { Text } from '@chakra-ui/react' - -export interface BoostTextProps { - pool: Pool -} - -export function BoostText({ pool }: BoostTextProps) { - const { veBalBoostMap } = useVebalBoost([pool]) - - const boost = useMemo(() => { - const boost = veBalBoostMap[pool.id] - - if (!boost || boost === '1') { - return '1.00' - } - - return fNum('boost', bn(boost)) - }, [veBalBoostMap, pool]) - - return ( - <> - - · - - - {`${boost}x boost`} - - - ) -} diff --git a/packages/lib/modules/pool/PoolDetail/PoolUserEvents/PoolUserEvents.tsx b/packages/lib/modules/pool/PoolDetail/PoolUserEvents/PoolUserEvents.tsx index d275c82243..af6b100e74 100644 --- a/packages/lib/modules/pool/PoolDetail/PoolUserEvents/PoolUserEvents.tsx +++ b/packages/lib/modules/pool/PoolDetail/PoolUserEvents/PoolUserEvents.tsx @@ -5,7 +5,6 @@ import { useCurrency } from '@repo/lib/shared/hooks/useCurrency' import { GetPoolEventsQuery, GqlChain, - GqlPoolStakingType, GqlPoolAddRemoveEventV3, GqlPoolSwapEventV3, } from '@repo/lib/shared/services/api/generated/graphql' @@ -16,9 +15,7 @@ import { PoolEventItem } from '../../usePoolEvents' import { calcTotalStakedBalance, getUserTotalBalance } from '../../user-balance.helpers' import { fNum, bn } from '@repo/lib/shared/utils/numbers' import { isEmpty } from 'lodash' -import { BoostText } from './BoostText' import { getBlockExplorerTxUrl } from '@repo/lib/shared/utils/blockExplorer' -import { PROJECT_CONFIG } from '@repo/lib/config/getProjectConfig' import { formatDistanceToNowAbbr } from '@repo/lib/shared/utils/time' import { PoolTransactionsCard } from '../../PoolTransactionsCard' @@ -170,14 +167,6 @@ export default function PoolUserEvents({ const [height, setHeight] = useState(0) const { toCurrency } = useCurrency() - const { - options: { showVeBal }, - } = PROJECT_CONFIG - - const isVeBalPoolStaking = pool.staking?.type === GqlPoolStakingType.Vebal - const showBoostValue = - pool.staking?.type === GqlPoolStakingType.Gauge && !isVeBalPoolStaking && showVeBal - // keep this card the same height as the 'My liquidity' section useLayoutEffect(() => { if (myLiquiditySectionRef && myLiquiditySectionRef.current) { @@ -185,14 +174,6 @@ export default function PoolUserEvents({ } }, []) - function getShareTitle() { - if (showVeBal) { - return 'locked' - } - - return 'staked' - } - const stakedPercentage = useMemo(() => { const totalBalance = getUserTotalBalance(pool) const stakedBalance = calcTotalStakedBalance(pool) @@ -216,9 +197,8 @@ export default function PoolUserEvents({ - {`${stakedPercentage} ${getShareTitle()}`} + {`${stakedPercentage} staked`} - {showBoostValue && } } diff --git a/packages/lib/modules/pool/PoolDetail/StakeUnstakeButton.tsx b/packages/lib/modules/pool/PoolDetail/StakeUnstakeButton.tsx index 0bd00ea6ee..1b90595dc9 100644 --- a/packages/lib/modules/pool/PoolDetail/StakeUnstakeButton.tsx +++ b/packages/lib/modules/pool/PoolDetail/StakeUnstakeButton.tsx @@ -24,7 +24,6 @@ import { calcGaugeStakedBalanceUsd, } from '../user-balance.helpers' import { Pool } from '../pool.types' -import { useVebalBoost } from '../../vebal/useVebalBoost' import { getTotalApr } from '../pool.utils' import { getNetworkConfig, getChainId } from '@repo/lib/config/app.config' import { @@ -94,13 +93,11 @@ export function StakeUnstakeButton({ pool, action }: StakeUnstakeButtonProps) { const router = useRouter() const pathname = usePathname() const auraDisclosure = useDisclosure() - const { veBalBoostMap } = useVebalBoost([pool]) const { toCurrency } = useCurrency() const canStake = getCanStake(pool) const hasUnstakedBalance = bn(getUserWalletBalance(pool)).gt(0) - const vebalBoost = veBalBoostMap[pool.address] - const [, balancerMaxApr] = getTotalApr(pool.dynamicData.aprItems, vebalBoost) + const [, balancerMaxApr] = getTotalApr(pool.dynamicData.aprItems) const hasGaugeStakedBalance = bn(calcGaugeStakedBalance(pool)).gt(0) const hasAuraStakedBalance = bn(calcAuraStakedBalance(pool)).gt(0) diff --git a/packages/lib/modules/pool/PoolList/PoolListTokenPills.tsx b/packages/lib/modules/pool/PoolList/PoolListTokenPills.tsx index f5825c7521..1d5b8d62bf 100644 --- a/packages/lib/modules/pool/PoolList/PoolListTokenPills.tsx +++ b/packages/lib/modules/pool/PoolList/PoolListTokenPills.tsx @@ -11,9 +11,7 @@ import { usePoolMetadata } from '../metadata/usePoolMetadata' import { isLiquidityBootstrapping, isStableLike } from '../pool.helpers' import { getUserReferenceTokens } from '../pool-tokens.utils' import { PoolCore, PoolToken } from '../pool.types' -import { VotingPoolWithData } from '../../vebal/vote/vote.types' import { ApiToken } from '@repo/lib/modules/tokens/token.types' -import { voteToPool } from '@repo/lib/modules/vebal/vote/vote.helpers' type IsTokenInWallet = (tokenAddress: string) => boolean @@ -280,29 +278,6 @@ function StableTokenPills({ ) } -type VotingListTokenPillsProps = { - vote: VotingPoolWithData - iconSize?: number - nameSize?: string -} & BadgeProps - -export function VotingListTokenPills({ vote, ...props }: VotingListTokenPillsProps) { - const pool = voteToPool(vote) - const { name } = usePoolMetadata(pool) - const tokens = getUserReferenceTokens(pool) - - return ( - - ) -} - type PoolListTokenPillsProps = { pool: PoolCore iconSize?: number diff --git a/packages/lib/modules/pool/actions/add-liquidity/modal/StakingOptions.tsx b/packages/lib/modules/pool/actions/add-liquidity/modal/StakingOptions.tsx index a936e5769a..52e7d206f7 100644 --- a/packages/lib/modules/pool/actions/add-liquidity/modal/StakingOptions.tsx +++ b/packages/lib/modules/pool/actions/add-liquidity/modal/StakingOptions.tsx @@ -74,8 +74,7 @@ export function StakingOptions() { {/* SHOULD WE USE MAX APR instead of the range?? */} {/* {fNum('apr', totalApr)} */} - {/* skip vebal boost here */} - {getTotalAprLabel(pool.dynamicData.aprItems, undefined, canBeNegative)} + {getTotalAprLabel(pool.dynamicData.aprItems, canBeNegative)} @@ -99,37 +98,36 @@ export function StakingOptions() { - {(PROJECT_CONFIG.options.showVeBal || pool.chain === GqlChain.Optimism) && - pool.staking?.aura && ( - - - - Aura + {pool.chain === GqlChain.Optimism && pool.staking?.aura && ( + + + + Aura + + + + {pool.staking?.aura ? fNum('apr', pool.staking.aura.apr) : 'Not available'} - - - {pool.staking?.aura ? fNum('apr', pool.staking.aura.apr) : 'Not available'} - - - - balancer - - {pool.staking && pool.staking.aura && ( - <> - - - - )} - - - )} + + + balancer + + {pool.staking && pool.staking.aura && ( + <> + + + + )} + + + )} ) diff --git a/packages/lib/modules/pool/pool.utils.ts b/packages/lib/modules/pool/pool.utils.ts index 69dead6c67..202e27402f 100644 --- a/packages/lib/modules/pool/pool.utils.ts +++ b/packages/lib/modules/pool/pool.utils.ts @@ -128,23 +128,18 @@ export function getPoolActionPath({ * Calculates the total APR based on the array of APR items and an optional boost value. * * @param {GqlPoolAprItem[]} aprItems - The array of APR items to calculate the total APR from. - * @param {string} [vebalBoost] - An optional boost value for calculation. * @returns {[BigNumber, BigNumber]} The total APR range. */ -export function getTotalApr( - aprItems: GqlPoolAprItem[], - vebalBoost?: string -): [BigNumber, BigNumber] { +export function getTotalApr(aprItems: GqlPoolAprItem[]): [BigNumber, BigNumber] { let minTotal = bn(0) let maxTotal = bn(0) - const boost = vebalBoost || 1 aprItems // Filter known APR types to avoid including new unknown API types that are not yet displayed in the APR tooltip .filter(item => TOTAL_APR_TYPES.includes(item.type)) .forEach(item => { if (item.type === GqlPoolAprItemType.StakingBoost) { - maxTotal = bn(item.apr).times(boost).plus(maxTotal) + maxTotal = bn(item.apr).plus(maxTotal) return } @@ -171,25 +166,20 @@ export function getTotalApr( * Calculates the total APR label based on the array of APR items and an optional boost value. * * @param {GqlPoolAprItem[]} aprItems - The array of APR items to calculate the total APR label from. - * @param {string} [vebalBoost] - An optional boost value for calculation. * @returns {string} The formatted total APR label. */ -export function getTotalAprLabel( - aprItems: GqlPoolAprItem[], - vebalBoost?: string, - canBeNegative = false -): string { - const [minTotal, maxTotal] = getTotalApr(aprItems, vebalBoost) - - if (minTotal.eq(maxTotal) || vebalBoost) { +export function getTotalAprLabel(aprItems: GqlPoolAprItem[], canBeNegative = false): string { + const [minTotal, maxTotal] = getTotalApr(aprItems) + + if (minTotal.eq(maxTotal)) { return fNum('apr', minTotal.toString(), { canBeNegative }) // only a single apr could be negative? } else { return `${fNum('apr', minTotal.toString())} - ${fNum('apr', maxTotal.toString())}` } } -export function getTotalAprRaw(aprItems: GqlPoolAprItem[], vebalBoost?: string): string { - const [minTotal] = getTotalApr(aprItems, vebalBoost) +export function getTotalAprRaw(aprItems: GqlPoolAprItem[]): string { + const [minTotal] = getTotalApr(aprItems) return minTotal.toString() } diff --git a/packages/lib/modules/portfolio/PortfolioTable/PortfolioTable.tsx b/packages/lib/modules/portfolio/PortfolioTable/PortfolioTable.tsx index 31a199285e..04d04c3d02 100644 --- a/packages/lib/modules/portfolio/PortfolioTable/PortfolioTable.tsx +++ b/packages/lib/modules/portfolio/PortfolioTable/PortfolioTable.tsx @@ -33,9 +33,9 @@ import { MigrationAlert } from '../../pool/migrations/MigrationAlert' import { isChainDeprecated } from '../../chains/chain.utils' import { BalAlert } from '@repo/lib/shared/components/alerts/BalAlert' -const rowProps = (addExtraColumn: boolean, needsLastColumnWider: boolean) => ({ +const rowProps = (needsLastColumnWider: boolean) => ({ px: [0, 4], - gridTemplateColumns: `32px minmax(320px, 1fr) minmax(180px, max-content) minmax(100px, max-content) 126px ${addExtraColumn ? '120px' : ''} ${needsLastColumnWider ? '160px' : 'minmax(100px, max-content)'}`, + gridTemplateColumns: `32px minmax(320px, 1fr) minmax(180px, max-content) minmax(100px, max-content) 126px ${needsLastColumnWider ? '160px' : 'minmax(100px, max-content)'}`, alignItems: 'center', gap: { base: 'xxs', xl: 'lg' }, }) @@ -45,7 +45,7 @@ export function PortfolioTable() { const { isConnected } = useUserAccount() const isFilterVisible = usePortfolioFilterTagsVisible() const isMd = useBreakpointValue({ base: false, md: true }) - const { sortedPools, setSorting, currentSortingObj, veBalBoostMap } = usePortfolioSorting() + const { sortedPools, setSorting, currentSortingObj } = usePortfolioSorting() const hasStakingBoost = sortedPools.some(pool => pool.dynamicData?.aprItems?.some(item => item.type === 'STAKING_BOOST') @@ -63,7 +63,7 @@ export function PortfolioTable() { shouldFilterTinyBalances, } = usePortfolioFilters() - const { projectName, options } = PROJECT_CONFIG + const { projectName } = PROJECT_CONFIG const variants = { visible: { @@ -175,7 +175,7 @@ export function PortfolioTable() { )} renderTableRow={({ item }) => { @@ -183,8 +183,7 @@ export function PortfolioTable() { ) }} @@ -205,7 +204,7 @@ export function PortfolioTable() {
diff --git a/packages/lib/modules/portfolio/PortfolioTable/PortfolioTableHeader.tsx b/packages/lib/modules/portfolio/PortfolioTable/PortfolioTableHeader.tsx index aafb21e4ae..120d883b3f 100644 --- a/packages/lib/modules/portfolio/PortfolioTable/PortfolioTableHeader.tsx +++ b/packages/lib/modules/portfolio/PortfolioTable/PortfolioTableHeader.tsx @@ -2,7 +2,6 @@ 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 { PortfolioSortingData, portfolioOrderByFn } from './usePortfolioSorting' -import { PROJECT_CONFIG } from '@repo/lib/config/getProjectConfig' const setIsDesc = (id: PortfolioSortingData['id'], currentSortingObj: PortfolioSortingData) => currentSortingObj.id === id ? !currentSortingObj.desc : true @@ -12,7 +11,7 @@ type Props = { setCurrentSortingObj: (value: PortfolioSortingData) => void } export function PortfolioTableHeader({ currentSortingObj, setCurrentSortingObj, ...rest }: Props) { - const portfolioOrderBy = portfolioOrderByFn(PROJECT_CONFIG.options.showVeBal) + const portfolioOrderBy = portfolioOrderByFn() return ( diff --git a/packages/lib/modules/portfolio/PortfolioTable/PortfolioTableRow.tsx b/packages/lib/modules/portfolio/PortfolioTable/PortfolioTableRow.tsx index db4217bcb2..2a719d33ed 100644 --- a/packages/lib/modules/portfolio/PortfolioTable/PortfolioTableRow.tsx +++ b/packages/lib/modules/portfolio/PortfolioTable/PortfolioTableRow.tsx @@ -30,7 +30,6 @@ import { isChainDeprecated } from '../../chains/chain.utils' interface Props extends GridProps { pool: ExpandedPoolInfo keyValue: string - veBalBoostMap: Record } const MemoizedMainAprTooltip = memo(MainAprTooltip) @@ -53,13 +52,11 @@ const getStakingFilterKey = (poolType: ExpandedPoolType): StakingFilterKeyType = } } -export function PortfolioTableRow({ pool, keyValue, veBalBoostMap, ...rest }: Props) { +export function PortfolioTableRow({ pool, keyValue, ...rest }: Props) { const { toCurrency } = useCurrency() const { name } = usePoolMetadata(pool) const { needsMigration } = usePoolMigrations() - const { options } = PROJECT_CONFIG - const vebalBoostValue = veBalBoostMap?.[pool.id] === 'NaN' ? undefined : veBalBoostMap?.[pool.id] const filterKey = getStakingFilterKey(pool.poolType) const stakingText = STAKING_LABEL_MAP[filterKey] @@ -100,20 +97,9 @@ export function PortfolioTableRow({ pool, keyValue, veBalBoostMap, ...rest }: Pr {stakingText} - + - {options?.showVeBal && ( - - - {vebalBoostValue ? `${Number(vebalBoostValue).toFixed(2)}x` : '-'} - - - )} @@ -145,7 +131,6 @@ export function PortfolioTableRow({ pool, keyValue, veBalBoostMap, ...rest }: Pr pool={pool} poolId={pool.id} textProps={{ fontWeight: 'medium' }} - vebalBoost={vebalBoostValue} /> )} diff --git a/packages/lib/modules/portfolio/PortfolioTable/usePortfolioSorting.tsx b/packages/lib/modules/portfolio/PortfolioTable/usePortfolioSorting.tsx index 5796651280..7a46388090 100644 --- a/packages/lib/modules/portfolio/PortfolioTable/usePortfolioSorting.tsx +++ b/packages/lib/modules/portfolio/PortfolioTable/usePortfolioSorting.tsx @@ -5,7 +5,6 @@ import { getTotalApr } from '../../pool/pool.utils' import { ExpandedPoolInfo, ExpandedPoolType } from './useExpandedPools' import { usePortfolioFilters } from './PortfolioFiltersProvider' import { usePortfolio } from '../PortfolioProvider' -import { useVebalBoost } from '../../vebal/useVebalBoost' import { bn } from '@repo/lib/shared/utils/numbers' export type PortfolioTableSortingId = 'staking' | 'vebal' | 'liquidity' | 'apr' @@ -14,22 +13,14 @@ export interface PortfolioSortingData { desc: boolean } -export const portfolioOrderByFn: (addExtraColumn: boolean) => { +export const portfolioOrderByFn: () => { title: string id: PortfolioTableSortingId -}[] = (addExtraColumn: boolean) => [ +}[] = () => [ { title: 'Staking', id: 'staking', }, - ...(addExtraColumn - ? [ - { - title: 'veBAL boost', - id: 'vebal' as PortfolioTableSortingId, - }, - ] - : []), { title: 'My liquidity', id: 'liquidity', @@ -77,7 +68,6 @@ export function usePortfolioSorting() { usePortfolioFilters() const { portfolioData } = usePortfolio() - const { veBalBoostMap } = useVebalBoost(portfolioData.stakedPools) const [manualSortingObj, dispatch] = useReducer(sortingReducer, null) @@ -151,12 +141,6 @@ export function usePortfolioSorting() { return weightDiff } - if (currentSortingObj.id === 'vebal') { - const aVebalBoost = Number(veBalBoostMap?.[a.id] || 0) - const bVebalBoost = Number(veBalBoostMap?.[b.id] || 0) - return currentSortingObj.desc ? bVebalBoost - aVebalBoost : aVebalBoost - bVebalBoost - } - if (currentSortingObj.id === 'liquidity') { const aTotalBalance = a.poolPositionUsd const bTotalBalance = b.poolPositionUsd @@ -182,13 +166,7 @@ export function usePortfolioSorting() { return 0 }) - }, [ - portfolioData?.pools, - filteredExpandedPools, - currentSortingObj.id, - currentSortingObj.desc, - veBalBoostMap, - ]) - - return { sortedPools, setSorting, currentSortingObj, veBalBoostMap } + }, [portfolioData?.pools, filteredExpandedPools, currentSortingObj.id, currentSortingObj.desc]) + + return { sortedPools, setSorting, currentSortingObj } } diff --git a/packages/lib/modules/vebal/VebalLockDataProvider.tsx b/packages/lib/modules/vebal/VebalLockDataProvider.tsx index 129b6971db..d3a18d617c 100644 --- a/packages/lib/modules/vebal/VebalLockDataProvider.tsx +++ b/packages/lib/modules/vebal/VebalLockDataProvider.tsx @@ -11,8 +11,8 @@ import mainnetNetworkConfig from '@repo/lib/config/networks/mainnet' import { useMulticall } from '@repo/lib/modules/web3/contracts/useMulticall' import { useCurrentDate } from '@repo/lib/shared/hooks/date.hooks' import { toJsTimestamp } from '@repo/lib/shared/utils/time' -import { PROJECT_CONFIG } from '@repo/lib/config/getProjectConfig' import { LockActionType } from './vote/vote.types' +import { isBalancer } from '@repo/lib/config/getProjectConfig' export type UseVebalLockDataResult = ReturnType export const VebalLockDataContext = createContext(null) @@ -23,17 +23,11 @@ function getAvailableLockActions( ): Record { if (typeof hasLock === 'boolean' && typeof isExpired === 'boolean') { return { - [LockActionType.CreateLock]: !hasLock, [LockActionType.Unlock]: isExpired, - [LockActionType.IncreaseLock]: hasLock, - [LockActionType.ExtendLock]: hasLock, } } return { - [LockActionType.CreateLock]: false, [LockActionType.Unlock]: false, - [LockActionType.IncreaseLock]: false, - [LockActionType.ExtendLock]: false, } } @@ -93,7 +87,7 @@ export function useVebalLockDataLogic() { }) const { results, refetchAll, isLoading } = useMulticall(lockDataRequests, { - enabled: isConnected && PROJECT_CONFIG.options.showVeBal, + enabled: isConnected && isBalancer, }) const now = useCurrentDate() diff --git a/packages/lib/modules/vebal/useGaugeTotalSupplyAndUserBalance.ts b/packages/lib/modules/vebal/useGaugeTotalSupplyAndUserBalance.ts deleted file mode 100644 index f4e6287873..0000000000 --- a/packages/lib/modules/vebal/useGaugeTotalSupplyAndUserBalance.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { useMulticall } from '../web3/contracts/useMulticall' -import { AbiMap } from '../web3/contracts/AbiMap' -import { useUserAccount } from '../web3/UserAccountProvider' -import { getNetworkConfig } from '@repo/lib/config/networks' -import { GqlChain } from '@repo/lib/shared/services/api/generated/graphql' -import { GaugeArg } from './useVebalBoost' -import { getChainId } from '@repo/lib/config/app.config' - -export function useGaugeTotalSupplyAndUserBalance(gauges: GaugeArg[]) { - const { userAddress } = useUserAccount() - - const totalSupplyRequestChains = gauges - .map(gauge => gauge.chain) - .filter(v => v !== GqlChain.Mainnet && v !== GqlChain.Sepolia) - - const vebalTotalSupplyRequests = totalSupplyRequestChains.map(chain => { - const address = getNetworkConfig(chain).contracts.veDelegationProxy - if (!address) throw new Error('veDelegationProxy address not found on: ' + chain) - - return { - chainId: getChainId(chain), - id: chain, - abi: AbiMap['balancer.veDelegationProxyL2'] as any, - address, - functionName: 'totalSupply', - args: [], - } - }) - - const { results: veBalTotalSupplyL2, isLoading } = useMulticall(vebalTotalSupplyRequests) - - const userVebalBalanceRequestChains = gauges - .map(gauge => gauge.chain) - .filter(v => v !== GqlChain.Sepolia) - - const userVeBALBalanceRequests = userVebalBalanceRequestChains.map(chain => { - const address = getNetworkConfig(chain).contracts.veDelegationProxy - if (!address) throw new Error('veDelegationProxy address not found on: ' + chain) - const abi = - chain === GqlChain.Mainnet - ? AbiMap['balancer.veDelegationProxy'] - : AbiMap['balancer.veDelegationProxyL2'] - - return { - chainId: getChainId(chain), - id: chain, - abi: abi as any, - address, - functionName: 'adjustedBalanceOf', - args: [userAddress], - } - }) - - const { results: userVeBALBalances, isLoading: isLoadingUserVebalBalances } = - useMulticall(userVeBALBalanceRequests) - - return { - veBalTotalSupplyL2, - userVeBALBalances, - isLoading: isLoading || isLoadingUserVebalBalances, - } -} diff --git a/packages/lib/modules/vebal/useGaugesSupplyAndBalance.ts b/packages/lib/modules/vebal/useGaugesSupplyAndBalance.ts deleted file mode 100644 index 964ded410b..0000000000 --- a/packages/lib/modules/vebal/useGaugesSupplyAndBalance.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { useMemo } from 'react' -import { useUserAccount } from '../web3/UserAccountProvider' -import { GaugeArg } from './useVebalBoost' - -import { useMulticall } from '../web3/contracts/useMulticall' - -import { AbiMap } from '../web3/contracts/AbiMap' -import { Hex } from 'viem' -import { getChainId, getGqlChain } from '@repo/lib/config/app.config' - -type GaugeDataByPool = Record - -export function useGaugesSupplyAndBalance(gauges: GaugeArg[]) { - const { userAddress } = useUserAccount() - - const gaugesTotalSupplyDataRequests = gauges.map(gauge => { - return { - chainId: getChainId(gauge.chain), - id: `${gauge.chain}.${gauge.gaugeAddress}`, - abi: AbiMap['balancer.LiquidityGauge'] as any, - address: gauge.gaugeAddress as Hex, - functionName: 'totalSupply', - args: [], - } - }) - - const { results: gaugesTotalSupply, isLoading: isLoadingTotalSupply } = useMulticall( - gaugesTotalSupplyDataRequests - ) - const gaugesBalancesDataRequests = gauges.map(gauge => { - return { - chainId: getChainId(gauge.chain), - id: `${gauge.chain}.${gauge.gaugeAddress}`, - abi: AbiMap['balancer.LiquidityGauge'] as any, - address: gauge.gaugeAddress as Hex, - functionName: 'balanceOf', - args: [userAddress], - } - }) - - const { results: gaugesBalances, isLoading: isLoadingBalances } = useMulticall( - gaugesBalancesDataRequests - ) - - // get gauge total supply and user balance - const gaugeDataByPoolMap = useMemo(() => { - const totalSupplyData = Object.values(gaugesTotalSupply) as any[] - const balancesData = Object.values(gaugesBalances) as any[] - - const totalSupplyDataGaugeMap = totalSupplyData.reduce((acc, v) => { - if (v.status === 'error' || v.status === 'pending') { - return acc - } - const chain = getGqlChain(Number(v.chainId)) - - return { - ...acc, - ...v.data[chain], - } - }, {}) - - const balancesDataGaugeMap = balancesData.reduce((acc, v) => { - if (v.status === 'error' || v.status === 'pending') { - return acc - } - const chain = getGqlChain(Number(v.chainId)) - - return { - ...acc, - ...v.data[chain], - } - }, {}) - - return gauges.reduce((acc: GaugeDataByPool, gauge) => { - const totalSupply = totalSupplyDataGaugeMap[gauge.gaugeAddress] - const balance = balancesDataGaugeMap[gauge.gaugeAddress] - - acc[gauge.poolId] = { - totalSupply: totalSupply ? totalSupply.result : 0, - userBalance: balance ? balance.result : 0, - gauge, - } - return acc - }, {}) - }, [gauges, gaugesTotalSupply, gaugesBalances]) - - return { gaugeDataByPoolMap, isLoading: isLoadingTotalSupply || isLoadingBalances } -} diff --git a/packages/lib/modules/vebal/useVebalBoost.tsx b/packages/lib/modules/vebal/useVebalBoost.tsx deleted file mode 100644 index 1ba65c195b..0000000000 --- a/packages/lib/modules/vebal/useVebalBoost.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { GqlChain } from '@repo/lib/shared/services/api/generated/graphql' -import { useMemo } from 'react' -import { bn } from '@repo/lib/shared/utils/numbers' -import { isUndefined } from 'lodash' -import { useGaugesSupplyAndBalance } from './useGaugesSupplyAndBalance' -import { useGaugeTotalSupplyAndUserBalance } from './useGaugeTotalSupplyAndUserBalance' -import { getChainId } from '@repo/lib/config/app.config' -import { Pool } from '../pool/pool.types' -import { useVebalLockData } from './VebalLockDataProvider' - -export type VeBalLockData = { - lockedEndDate: number - lockedAmount: string - totalSupply: string - epoch: string - hasExistingLock: boolean - isExpired: boolean -} - -export interface GaugeArg { - chain: GqlChain - gaugeAddress: string - poolId: string -} - -type OnchainVebalBalance = { result: bigint; status: string } - -function calcUserBoost({ - userGaugeBalance, - gaugeTotalSupply, - userVeBALBalance, - veBALTotalSupply, -}: { - userGaugeBalance: string - gaugeTotalSupply: string - userVeBALBalance: string - veBALTotalSupply: string -}): string { - const _userGaugeBalance = bn(userGaugeBalance) - const _gaugeTotalSupply = bn(gaugeTotalSupply) - const _userVeBALBalance = bn(userVeBALBalance) - const _veBALTotalSupply = bn(veBALTotalSupply) - - const boost = bn(_userGaugeBalance).gt(0) - ? bn(1).plus( - bn(1.5) - .times(_userVeBALBalance) - .div(_veBALTotalSupply) - .times(_gaugeTotalSupply) - .div(_userGaugeBalance) - ) - : bn(1) - - const minBoost = bn(2.5).lt(boost) ? 2.5 : boost - - return minBoost.toString() -} - -export function useVebalBoost(pools: Pool[]) { - const { mainnetLockedInfo } = useVebalLockData() - - const gauges = useMemo(() => { - if (!pools) return [] - - const gauges = pools.map(p => { - if (p.staking?.gauge?.status === 'KILLED') return [] - - const gaugeAddress = p.staking?.gauge?.gaugeAddress - - if (!gaugeAddress) return [] - - return { - chain: p.chain, - gaugeAddress, - poolId: p.id, - } - }) - - return gauges.flat() - }, [pools]) - - const { veBalTotalSupplyL2, userVeBALBalances } = useGaugeTotalSupplyAndUserBalance(gauges) - - const { gaugeDataByPoolMap } = useGaugesSupplyAndBalance(gauges) - - const veBalBoostMap = useMemo(() => { - return Object.entries(gaugeDataByPoolMap).reduce( - (acc, [poolId, { totalSupply, userBalance, gauge }]) => { - const gaugeChainId = getChainId(gauge.chain) - - const userVeBALChainBalance = userVeBALBalances?.[gaugeChainId]?.data as - | Record - | undefined - const veBALChainTotalSupply = veBalTotalSupplyL2?.[gaugeChainId]?.data as - | Record - | undefined - - const userVeBALBalance = userVeBALChainBalance?.[gauge.chain]?.result - - const veBALTotalSupply = - gauge.chain === GqlChain.Mainnet - ? mainnetLockedInfo.totalSupply - : veBALChainTotalSupply?.[gauge.chain]?.result || 0 - - if ( - isUndefined(userVeBALBalance) || - isUndefined(veBALTotalSupply) || - isUndefined(totalSupply) || - isUndefined(userBalance) - ) { - return acc - } - - const userBoost = calcUserBoost({ - userGaugeBalance: userBalance, - gaugeTotalSupply: totalSupply, - userVeBALBalance: userVeBALBalance.toString(), - veBALTotalSupply: veBALTotalSupply.toString(), - }) - - acc[poolId] = userBoost - - return acc - }, - {} as Record - ) - }, [gaugeDataByPoolMap, veBalTotalSupplyL2, userVeBALBalances, mainnetLockedInfo]) - - return { veBalBoostMap } -} diff --git a/packages/lib/modules/vebal/vote/useGaugeVotes.tsx b/packages/lib/modules/vebal/vote/useGaugeVotes.tsx deleted file mode 100644 index 2c510bd2bb..0000000000 --- a/packages/lib/modules/vebal/vote/useGaugeVotes.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import { useMemo, useState } from 'react' -import { oneWeekInMs, toUnixTimestamp } from '@repo/lib/shared/utils/time' -import { mainnet } from 'viem/chains' -import { AbiMap } from '@repo/lib/modules/web3/contracts/AbiMap' -import mainnetNetworkConfig from '@repo/lib/config/networks/mainnet' -import { Hex } from 'viem' -import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider' -import { onlyExplicitRefetch } from '../../../shared/utils/queries' -import { useReadContracts } from 'wagmi' -import { UserVotesData } from '@repo/lib/modules/vebal/vote/vote.types' -import { shouldUseAnvilFork } from '@repo/lib/config/app.config' - -export interface RawVotesData { - gaugeWeightThisPeriod?: { result?: bigint; status: string } - gaugeWeightNextPeriod?: { result?: bigint; status: string } - userVotes?: { result?: UserVotesData; status: string } - lastUserVoteTime?: { result?: bigint; status: string } -} - -export interface VotesData { - votes: string - votesNextPeriod: string - userVotes: string - userSlope: bigint - userEndLock: bigint - lastUserVoteTime: number -} - -function formatVotes(votesData: RawVotesData): VotesData { - const votes = votesData.gaugeWeightThisPeriod?.result?.toString() || '0' - const votesNextPeriod = votesData.gaugeWeightNextPeriod?.result?.toString() || '0' - - return { - votes, - votesNextPeriod, - userVotes: votesData?.userVotes?.result?.power.toString() || '0', // 6000n = 60.00% - userSlope: votesData?.userVotes?.result?.slope || 0n, - userEndLock: votesData?.userVotes?.result?.end || 0n, - lastUserVoteTime: Number(votesData?.lastUserVoteTime?.result) || 0, - } -} - -const readContractsParams = { - batchSize: 10_000, // 10kb batch ~ 75kb payload - allowFailure: true, - query: onlyExplicitRefetch, -} as const - -function useGaugeRelativeWeightsWrite( - gaugeAddresses: UseGaugeVotesParams['gaugeAddresses'], - timestamp: number -) { - return useReadContracts({ - ...readContractsParams, - query: { - ...readContractsParams.query, - enabled: true, - }, - contracts: gaugeAddresses.map(gaugeAddress => { - return { - chainId: mainnet.id, - abi: AbiMap['balancer.gaugeControllerAbi'], - address: mainnetNetworkConfig.contracts.gaugeController as Hex, - // Write function is avoided in anvil fork mode cause it is very slow - functionName: shouldUseAnvilFork ? 'gauge_relative_weight' : 'gauge_relative_weight_write', - args: [gaugeAddress, timestamp], - } as const - }), - }) -} - -function useVoteUserSlopes( - gaugeAddresses: UseGaugeVotesParams['gaugeAddresses'], - userAddress: string -) { - const { isConnected } = useUserAccount() - return useReadContracts({ - ...readContractsParams, - query: { - ...readContractsParams.query, - enabled: isConnected, - }, - contracts: gaugeAddresses.map(gaugeAddress => { - return { - chainId: mainnet.id, - abi: AbiMap['balancer.gaugeControllerAbi'], - address: mainnetNetworkConfig.contracts.gaugeController as Hex, - functionName: 'vote_user_slopes', - args: [userAddress, gaugeAddress], - } as const - }), - }) -} - -function useLastUserVotes( - gaugeAddresses: UseGaugeVotesParams['gaugeAddresses'], - userAddress: string -) { - const { isConnected } = useUserAccount() - return useReadContracts({ - ...readContractsParams, - query: { - ...readContractsParams.query, - enabled: isConnected, - }, - contracts: gaugeAddresses.map(gaugeAddress => { - return { - chainId: mainnet.id, - abi: AbiMap['balancer.gaugeControllerAbi'], - address: mainnetNetworkConfig.contracts.gaugeController as Hex, - functionName: 'last_user_vote', - args: [userAddress, gaugeAddress], - } as const - }), - }) -} - -export interface UseGaugeVotesParams { - gaugeAddresses: string[] -} - -export function useGaugeVotes({ gaugeAddresses }: UseGaugeVotesParams) { - const { userAddress, isConnected } = useUserAccount() - const [now] = useState(() => Date.now()) - - const thisWeek = Math.floor(now / oneWeekInMs) * oneWeekInMs - const gaugeWeightThisPeriodQuery = useGaugeRelativeWeightsWrite( - gaugeAddresses, - toUnixTimestamp(thisWeek) - ) - - const nextWeek = thisWeek + oneWeekInMs - const gaugeWeightNextPeriodQuery = useGaugeRelativeWeightsWrite( - gaugeAddresses, - toUnixTimestamp(nextWeek) - ) - - const userVotesQuery = useVoteUserSlopes(isConnected ? gaugeAddresses : [], userAddress) - const lastUserVotesQuery = useLastUserVotes(isConnected ? gaugeAddresses : [], userAddress) - - const refetchAll = () => - Promise.all([ - gaugeWeightThisPeriodQuery.refetch(), - gaugeWeightNextPeriodQuery.refetch(), - userVotesQuery.refetch(), - lastUserVotesQuery.refetch(), - ]) - - const isLoading = - gaugeWeightThisPeriodQuery.isLoading || - gaugeWeightNextPeriodQuery.isLoading || - userVotesQuery.isLoading || - lastUserVotesQuery.isLoading - - const isRefetching = - gaugeWeightThisPeriodQuery.isRefetching || - gaugeWeightNextPeriodQuery.isRefetching || - userVotesQuery.isRefetching || - lastUserVotesQuery.isRefetching - - const gaugeVotes = useMemo(() => { - if (isLoading) { - return undefined - } - - const result: Record = Object.fromEntries( - gaugeAddresses.map((address, index) => { - const gaugeWeightThisPeriod = (gaugeWeightThisPeriodQuery.data ?? [])[index] - const gaugeWeightNextPeriod = (gaugeWeightNextPeriodQuery.data ?? [])[index] - const userVotes = (userVotesQuery.data ?? [])[index] - const lastUserVoteTime = (lastUserVotesQuery.data ?? [])[index] - - return [ - address, - formatVotes({ - gaugeWeightThisPeriod, - gaugeWeightNextPeriod, - userVotes, - lastUserVoteTime, - }), - ] - }) - ) - - return result - }, [ - gaugeWeightThisPeriodQuery.data, - gaugeWeightNextPeriodQuery.data, - userVotesQuery.data, - lastUserVotesQuery.data, - isLoading, - gaugeAddresses, - ]) - - return { - gaugeVotes, - refetchAll, - isLoading: isLoading || isRefetching, - } -} diff --git a/packages/lib/modules/vebal/vote/vote.helpers.ts b/packages/lib/modules/vebal/vote/vote.helpers.ts deleted file mode 100644 index e695f1715e..0000000000 --- a/packages/lib/modules/vebal/vote/vote.helpers.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { - VotingPoolWithData, - VotesState, - SortVotesBy, -} from '@repo/lib/modules/vebal/vote/vote.types' -import { isSameAddress } from '@repo/lib/shared/utils/addresses' -import { VotingPool } from '@repo/lib/modules/pool/pool.types' -import { ApiToken } from '@repo/lib/modules/tokens/token.types' -import { getChainId } from '@repo/lib/config/app.config' -import { isVebalPool } from '@repo/lib/modules/pool/pool.helpers' - -export function getVotesState(relativeWeightCap: number, votesNextPeriodWeight: number) { - if (relativeWeightCap === 0 || votesNextPeriodWeight === 0) return VotesState.Normal - if (votesNextPeriodWeight > relativeWeightCap) { - return VotesState.Exceeded - } - if (relativeWeightCap - votesNextPeriodWeight <= 0.01) { - return VotesState.Close - } - return VotesState.Normal -} - -export function voteToPool(vote: VotingPoolWithData): VotingPool { - return { - id: vote.id, - type: vote.type, - chain: vote.chain, - poolTokens: vote.poolTokens.map( - token => - ({ - ...token, - chain: token.chain, - chainId: token.chainId || getChainId(vote.chain), - }) as ApiToken - ), - address: vote.address, - protocolVersion: vote.protocolVersion, - symbol: vote.symbol, - tags: vote.tags, - // TODO: API is not returning the following fields in GqlVotingPool yet - hook: undefined, - hasErc4626: false, - } -} - -export function isGaugeExpired(expiredGauges: string[] | undefined, gaugeAddress: string): boolean { - if (!expiredGauges) return false - return expiredGauges.some(item => isSameAddress(gaugeAddress, item)) -} - -export function isPoolExpired(pool: VotingPoolWithData) { - return pool.gauge.isKilled -} - -export function getRelativeWeightCap(relativeWeightCap: string | undefined | null, poolId: string) { - if (isVebalPool(poolId)) { - return 0.1 - } - - return !relativeWeightCap || relativeWeightCap === '1' ? 0 : Number(relativeWeightCap) -} - -export const orderByHash: Record = { - type: { label: 'Details' }, - bribes: { - label: 'Bribes', - title: - 'Voting incentives (referred to as ‘Bribes’ in DeFi) are provided by unaffiliated 3rd parties through the Votemarket 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', - title: - 'The percentage of votes for each pool for the next period. This is based on voting by veBAL holders based on their collective voting power.', - }, -} diff --git a/packages/lib/modules/vebal/vote/vote.types.ts b/packages/lib/modules/vebal/vote/vote.types.ts index fd23a56bbc..b5f3daa885 100644 --- a/packages/lib/modules/vebal/vote/vote.types.ts +++ b/packages/lib/modules/vebal/vote/vote.types.ts @@ -1,38 +1,3 @@ -import { GetVeBalVotingListQuery } from '@repo/lib/shared/services/api/generated/graphql' -import { VotesData } from './useGaugeVotes' -import { PoolVotingIncentivesPerWeek } from '@repo/lib/shared/services/voting-incentives/incentives.types' - -export type VoteList = GetVeBalVotingListQuery['veBalGetVotingList'] - -export type VoteListItem = VoteList[0] - -export type VotingPoolWithData = VoteListItem & { - gaugeVotes: VotesData | undefined - votingIncentive: PoolVotingIncentivesPerWeek | undefined -} - -export enum VotesState { - Normal = 'normal', - Close = 'close', - Exceeded = 'exceeded', -} - -export enum SortVotesBy { - type = 'type', - bribes = 'bribes', - bribesPerVebal = 'bribesPerVebal', - votes = 'votes', -} - export enum LockActionType { - CreateLock = 'createLock', - IncreaseLock = 'increaseAmount', - ExtendLock = 'extendLock', Unlock = 'unlock', } - -export interface UserVotesData { - end: bigint - power: bigint - slope: bigint -} diff --git a/packages/lib/shared/components/modals/ActionModalFooter.tsx b/packages/lib/shared/components/modals/ActionModalFooter.tsx index aadd3912f0..211bdc467a 100644 --- a/packages/lib/shared/components/modals/ActionModalFooter.tsx +++ b/packages/lib/shared/components/modals/ActionModalFooter.tsx @@ -7,8 +7,8 @@ import { AnimatePresence, motion } from 'framer-motion' import { PropsWithChildren } from 'react' import { CornerDownLeft, MessageSquare, ThumbsUp } from 'react-feather' import { TransactionStep } from '../../../modules/transactions/transaction-steps/lib' -import { PROJECT_CONFIG } from '@repo/lib/config/getProjectConfig' import { getDiscordLink } from '../../utils/links' +import { isBalancer } from '@repo/lib/config/getProjectConfig' export function SuccessActions({ returnLabel, @@ -19,9 +19,6 @@ export function SuccessActions({ }) { const { openNpsModal } = useAppzi() const { colorMode } = useColorMode() - const { - options: { showVeBal }, - } = PROJECT_CONFIG return ( @@ -36,7 +33,7 @@ export function SuccessActions({ > {returnLabel} - {showVeBal && ( + {isBalancer && ( - - - - ) -} diff --git a/packages/lib/shared/components/tooltips/apr-tooltip/BaseAprTooltip.tsx b/packages/lib/shared/components/tooltips/apr-tooltip/BaseAprTooltip.tsx index f5aff949c4..9a757da467 100644 --- a/packages/lib/shared/components/tooltips/apr-tooltip/BaseAprTooltip.tsx +++ b/packages/lib/shared/components/tooltips/apr-tooltip/BaseAprTooltip.tsx @@ -18,7 +18,6 @@ import { swapFeesTooltipText, useAprTooltip, inherentTokenYieldTooltipText, - extraBalTooltipText, lockingIncentivesTooltipText, votingIncentivesTooltipText, merklIncentivesTooltipText, @@ -38,14 +37,9 @@ interface Props { placement?: PlacementWithLogical poolId: string poolType: GqlPoolType - vebalBoost?: string - totalBaseText: string | ((hasVeBalBoost?: boolean) => string) - totalBaseVeBalText: string - totalVeBalTitle?: string - maxVeBalText: string + totalBaseText: string customPopoverContent?: ReactNode shouldDisplayBaseTooltip?: boolean - shouldDisplayMaxVeBalTooltip?: boolean usePortal?: boolean children?: ReactNode | (({ isOpen }: { isOpen: boolean }) => ReactNode) chain: GqlChain @@ -86,14 +80,9 @@ function BaseAprTooltip({ numberFormatter, displayValueFormatter, placement, - vebalBoost, customPopoverContent, totalBaseText, - totalBaseVeBalText, - totalVeBalTitle, - maxVeBalText, shouldDisplayBaseTooltip, - shouldDisplayMaxVeBalTooltip, children, poolType, chain, @@ -111,7 +100,6 @@ function BaseAprTooltip({ const { totalBaseDisplayed, - extraBalApr, yieldBearingTokensAprDisplayed, stakingIncentivesAprDisplayed, merklIncentivesAprDisplayed, @@ -125,9 +113,7 @@ function BaseAprTooltip({ yieldBearingTokensDisplayed, stakingIncentivesDisplayed, subitemPopoverAprItemProps, - hasVeBalBoost, totalBase, - maxVeBal, lockingAprDisplayed, votingAprDisplayed, isVotingPresent, @@ -146,19 +132,10 @@ function BaseAprTooltip({ fuulTooltipText, } = useAprTooltip({ aprItems, - vebalBoost: Number(vebalBoost), numberFormatter: usedNumberFormatter, chain, }) - const totalBaseTitle = isVebal - ? totalBaseVeBalText - : typeof totalBaseText === 'function' - ? totalBaseText(hasVeBalBoost) - : totalBaseText - - const boostLabel = !vebalBoost ? '2.5x' : `${vebalBoost}x` - const popoverContent = customPopoverContent || ( - - - ) : null} - {hasVeBalBoost ? ( - <> - - - - - ) : null} + {isMaBeetsPresent && ( <> diff --git a/packages/lib/shared/components/tooltips/apr-tooltip/MainAprTooltip.tsx b/packages/lib/shared/components/tooltips/apr-tooltip/MainAprTooltip.tsx index 7dd04c69c3..20eccf2a14 100644 --- a/packages/lib/shared/components/tooltips/apr-tooltip/MainAprTooltip.tsx +++ b/packages/lib/shared/components/tooltips/apr-tooltip/MainAprTooltip.tsx @@ -130,14 +130,13 @@ function MainAprTooltip({ onlySparkles, textProps, apr, - vebalBoost, aprLabel, height = '16px', pool, id, ...props }: Props) { - const aprToShow = apr || getTotalAprLabel(pool.dynamicData.aprItems, vebalBoost, true) + const aprToShow = apr || getTotalAprLabel(pool.dynamicData.aprItems, true) const isAprNegative = bn(aprToShow.replace('%', '')).lt(0) // hoverColor here is used for the text and therefore can use a semantic token @@ -161,11 +160,8 @@ function MainAprTooltip({ chain={pool.chain} customPopoverContent={customPopoverContent} hookType={isPool(pool) ? pool.hook?.type : undefined} - maxVeBalText="Total APR with veBAL boost" poolType={pool.type} - totalBaseText={hasVeBalBoost => `Total ${hasVeBalBoost ? 'base' : ''} APR`} - totalBaseVeBalText="Total base APR" - vebalBoost={vebalBoost} + totalBaseText="Total APR" > {({ isOpen }) => ( diff --git a/packages/lib/shared/components/tooltips/apr-tooltip/StakeAprTooltip.tsx b/packages/lib/shared/components/tooltips/apr-tooltip/StakeAprTooltip.tsx index 2be7f9bc23..624cfef94e 100644 --- a/packages/lib/shared/components/tooltips/apr-tooltip/StakeAprTooltip.tsx +++ b/packages/lib/shared/components/tooltips/apr-tooltip/StakeAprTooltip.tsx @@ -36,18 +36,13 @@ function StakeAprTooltip({ pool, totalUsdValue, weeklyRewards }: Props) { aprItems={pool.dynamicData.aprItems} chain={pool.chain} displayValueFormatter={displayValueFormatter} - maxVeBalText="Total with max veBAL" numberFormatter={numberFormatter} placement="top-start" poolId={pool.id} poolType={pool.type} shouldDisplayBaseTooltip - shouldDisplayMaxVeBalTooltip totalBaseText="Total weekly base" - totalBaseVeBalText="Total weekly base" - totalVeBalTitle="Total weekly" usePortal={false} - vebalBoost="1" > diff --git a/packages/lib/shared/hooks/_mocks_/aprTooltipDataMock.ts b/packages/lib/shared/hooks/_mocks_/aprTooltipDataMock.ts index ace71da2b0..1c7d8e3c3b 100644 --- a/packages/lib/shared/hooks/_mocks_/aprTooltipDataMock.ts +++ b/packages/lib/shared/hooks/_mocks_/aprTooltipDataMock.ts @@ -16,13 +16,6 @@ const aprItems = [ apr: 0.0007062046857971015, type: 'SWAP_FEE_24H', }, - { - __typename: 'GqlPoolAprItem', - id: '0x79ef6103a513951a3b25743db509e267685726b7-BAL-apr', - title: 'BAL reward APR', - apr: 0.01914598038405333, - type: 'VEBAL_EMISSIONS', - }, { __typename: 'GqlPoolAprItem', id: '0x79ef6103a513951a3b25743db509e267685726b7-BAL-apr-boost', @@ -32,9 +25,6 @@ const aprItems = [ }, ] as GqlPoolAprItem[] -const vebalBoost = undefined - export const aprTooltipDataMock = { aprItems, - vebalBoost, } as Parameters[0] diff --git a/packages/lib/shared/hooks/useAprTooltip.spec.tsx b/packages/lib/shared/hooks/useAprTooltip.spec.tsx index 62dbf8f742..30823ef80a 100644 --- a/packages/lib/shared/hooks/useAprTooltip.spec.tsx +++ b/packages/lib/shared/hooks/useAprTooltip.spec.tsx @@ -3,10 +3,9 @@ import { bn, fNum } from '../utils/numbers' import { useAprTooltip } from './useAprTooltip' import { aprTooltipDataMock } from './_mocks_/aprTooltipDataMock' import BigNumber from 'bignumber.js' -import { GqlPoolAprItem, GqlPoolAprItemType, GqlChain } from '../services/api/generated/graphql' +import { GqlPoolAprItem, GqlChain } from '../services/api/generated/graphql' import { getApiPoolMock } from '@repo/lib/modules/pool/__mocks__/api-mocks/api-mocks' import { boostedCoinshiftUsdcUsdl } from '@repo/lib/modules/pool/__mocks__/pool-examples/boosted' -import { act } from '@testing-library/react' const defaultNumberFormatter = (value: string) => bn(bn(value).toFixed(4, BigNumber.ROUND_HALF_UP)) @@ -35,78 +34,9 @@ describe('useAprTooltip', () => { const totalBaseDisplayed = result.current.totalBaseDisplayed expect(totalBaseDisplayed.toFixed()).toBe('0.0266') expect(fNum('apr', totalBaseDisplayed)).toBe('2.66%') // 0.07 + 1.91 + 0.68 = 2.66 - - const extraBalApr = result.current.extraBalApr - expect(extraBalApr.toFixed(4)).toBe('0.0287') - expect(fNum('apr', extraBalApr)).toBe('2.87%') - - const maxVeBalApr = result.current.maxVeBal - expect(maxVeBalApr.toFixed(4)).toBe('0.0553') - expect(fNum('apr', maxVeBalApr)).toBe('5.53%') }) }) -it('When the pool has BAL staking incentives (outside the veBAL system)', () => { - const aprItems: GqlPoolAprItem[] = [ - { - __typename: 'GqlPoolAprItem', - id: '0xf08d4dea369c456d26a3168ff0024b904f2d8b91-swap-apr', - title: 'Swap fees APR', - type: GqlPoolAprItemType.SwapFee_24H, - apr: 0.01557208351741454, - }, - { - __typename: 'GqlPoolAprItem', - id: '0xf08d4dea369c456d26a3168ff0024b904f2d8b91-surplus', - title: 'Surplus APR', - type: GqlPoolAprItemType.Surplus_24H, - apr: 0.05520228189828037, - }, - // BAL incentives that are outside of the veBAL system - { - __typename: 'GqlPoolAprItem', - id: '0xf9423b78d784d610a00955e733dba0bf9bda7b06-BAL-apr', - title: 'BAL reward APR', - type: GqlPoolAprItemType.Staking, - apr: 6883.427340115957, - }, - ] - - const result = testUseAprTooltip({ aprItems }) - - expect(result.current.hasVeBalBoost).toBeFalsy() -}) - -it('When the pool has BAL staking incentives (inside the veBAL system)', async () => { - const aprItems: GqlPoolAprItem[] = [ - { - __typename: 'GqlPoolAprItem', - id: '0x1e19cf2d73a72ef1332c882f20534b6519be0276000200000000000000000112-rETH-yield-apr', - title: 'rETH APR', - type: GqlPoolAprItemType.IbYield, - apr: 0.007768926276078092, - }, - { - __typename: 'GqlPoolAprItem', - id: '0x1e19cf2d73a72ef1332c882f20534b6519be0276000200000000000000000112-swap-apr', - title: 'Swap fees APR', - type: GqlPoolAprItemType.SwapFee_24H, - apr: 0.001428043486003915, - }, - { - __typename: 'GqlPoolAprItem', - id: '0x79ef6103a513951a3b25743db509e267685726b7-BAL-apr', - title: 'BAL reward APR', - type: GqlPoolAprItemType.Staking, - apr: 0.01454219612063976, - }, - ] - - const result = await act(() => testUseAprTooltip({ aprItems })) - - expect(result.current.hasVeBalBoost).toBeFalsy() -}) - it('When the pool has multiple Yield bearing token APRs', () => { const pool = getApiPoolMock(boostedCoinshiftUsdcUsdl) diff --git a/packages/lib/shared/hooks/useAprTooltip.ts b/packages/lib/shared/hooks/useAprTooltip.ts index 12157f786a..0f48dd82fa 100644 --- a/packages/lib/shared/hooks/useAprTooltip.ts +++ b/packages/lib/shared/hooks/useAprTooltip.ts @@ -7,7 +7,6 @@ import { import { useThemeColorMode } from '../services/chakra/useThemeColorMode' import { bn } from '../utils/numbers' import BigNumber from 'bignumber.js' -import { PROJECT_CONFIG } from '@repo/lib/config/getProjectConfig' export const swapFeesTooltipText = `LPs earn fees when swaps are routed through this pool. The displayed APR is net earnings for LPs (with all protocol fees already deducted). Fees automatically compound into positions—no claiming needed.` @@ -19,9 +18,6 @@ export const merklIncentivesTooltipText = `Merkl is a platform that allows 3rd p export const surplusIncentivesTooltipText = `In a traditional AMM, LPs lose money to arbitrageurs. CoW AMM prevents this loss (also called LVR), thereby increasing LP returns.` -export const extraBalTooltipText = `veBAL holders can get an extra boost of up to 2.5x on their staking yield. -The more veBAL held, the higher the boost.` - export const lockingIncentivesTooltipText = `The protocol revenue share for Liquidity Providers with 1-year locked Balancer ve8020 tokens.` @@ -30,8 +26,6 @@ export const votingIncentivesTooltipText = `Vote incentives are offered to veBAL Your incentives are determined by your veBAL voting power compared to other voters. The listed APR represents an average rather than a guaranteed return for active participants.` -const stakingBalTooltipText = `LPs who stake earn extra ‘BAL’ liquidity mining incentives. The displayed APR is the base amount that all Stakers in this pool get (determined by weekly gauge voting). In addition, veBAL holders can get an extra boost of up to 2.5x.` - const maBeetsVotingRewardsTooltipText = 'To receive Voting APR you must vote for incentivized pools in the bi-weekly gauge vote. APR is dependent on your vote distribution.' @@ -60,7 +54,6 @@ const TOTAL_BASE_APR_TYPES = [ GqlPoolAprItemType.Staking, GqlPoolAprItemType.Merkl, GqlPoolAprItemType.Surplus_24H, - GqlPoolAprItemType.VebalEmissions, GqlPoolAprItemType.MabeetsEmissions, GqlPoolAprItemType.QuantAmmUplift, GqlPoolAprItemType.Fuul, @@ -77,25 +70,13 @@ export const TOTAL_APR_TYPES = [ export function useAprTooltip({ aprItems, numberFormatter, - vebalBoost, - chain, }: { aprItems: GqlPoolAprItem[] numberFormatter: (value: string) => BigNumber - vebalBoost?: number chain: GqlChain }) { const colorMode = useThemeColorMode() - const { - options: { showVeBal }, - } = PROJECT_CONFIG - - const hasVeBalBoost = - // optimism is the exception here as it's available in the balancer app and beets app - (showVeBal || chain === GqlChain.Optimism) && - !!aprItems.find(item => item.type === GqlPoolAprItemType.StakingBoost) - // Swap fees const swapFee = aprItems.find(item => item.type === GqlPoolAprItemType.SwapFee_24H) const swapFeesDisplayed = numberFormatter(swapFee ? swapFee.apr.toString() : '0') @@ -133,7 +114,7 @@ export function useAprTooltip({ const stakingIncentivesDisplayed = stakingIncentives.map(item => ({ title: item.rewardTokenSymbol || '', apr: numberFormatter(item.apr.toString()), - tooltipText: `3rd party incentives for LPs who stake. These token incentives are outside the ${showVeBal || chain === GqlChain.Optimism ? 'veBAL' : 'gauge bounty'} system.`, + tooltipText: `3rd party incentives for LPs who stake. These token incentives are outside the gauge bounty system.`, })) const votingApr = aprItems.find(item => item.type === GqlPoolAprItemType.Voting) @@ -207,20 +188,6 @@ export function useAprTooltip({ .reduce((acc, item) => acc.plus(item.apr), bn(0)) const totalCombinedDisplayed = numberFormatter(totalCombined.toString()) - // Bal Reward - const balReward = aprItems.find(item => item.type === GqlPoolAprItemType.VebalEmissions) - const extraVeBALBoost = bn(balReward?.apr || 0).times(vebalBoost ? vebalBoost : 2.5) - const extraBalApr = bn(extraVeBALBoost).minus(balReward?.apr || 0) - const maxVeBal = totalBase.plus(extraBalApr) - - if (balReward) { - stakingIncentivesDisplayed.push({ - title: balReward.rewardTokenSymbol || '', - apr: numberFormatter(balReward.apr.toString()), - tooltipText: stakingBalTooltipText, - }) - } - const stakingIncentivesAprDisplayed = stakingIncentivesDisplayed.reduce( (acc, item) => acc.plus(item.apr), bn(0) @@ -243,7 +210,6 @@ export function useAprTooltip({ return { totalBaseDisplayed, - extraBalApr, yieldBearingTokensAprDisplayed, stakingIncentivesAprDisplayed, swapFeesDisplayed, @@ -261,8 +227,6 @@ export function useAprTooltip({ isVotingPresent, isLockingAprPresent, subitemPopoverAprItemProps, - hasVeBalBoost, - maxVeBal, totalBase, totalCombined, totalCombinedDisplayed, diff --git a/packages/lib/test/data/all-gql-tokens.fake.ts b/packages/lib/test/data/all-gql-tokens.fake.ts index 65739656f2..aa657dc230 100644 --- a/packages/lib/test/data/all-gql-tokens.fake.ts +++ b/packages/lib/test/data/all-gql-tokens.fake.ts @@ -305,22 +305,6 @@ export const allFakeGqlTokens: GqlToken[] = [ isErc4626: false, isBufferAllowed: true, }, - { - // Used in VeBal voting related tests - __typename: 'GqlToken', - address: '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56', - name: 'B-80BAL-20WETH', - symbol: 'B-80BAL-20WETH', - decimals: 18, - chainId: 1, - chain: GqlChain.Mainnet, - logoURI: - 'https://assets-cdn.trustwallet.com/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png', - priority: 0, - tradable: true, - isErc4626: false, - isBufferAllowed: true, - }, ] export function fakeTokenBySymbol(symbol: FakeTokenSymbol) { From 9b900426e8154bfe96e05abf905788df6ab0f915 Mon Sep 17 00:00:00 2001 From: Juanjo Molinero Date: Fri, 10 Apr 2026 16:07:48 +0200 Subject: [PATCH 2/5] [#2316] remove the possibility to stake on Aura --- .../pool/PoolDetail/StakeUnstakeButton.tsx | 28 ++++++---- .../add-liquidity/modal/StakingOptions.tsx | 56 +------------------ packages/lib/modules/pool/pool.utils.ts | 2 +- 3 files changed, 20 insertions(+), 66 deletions(-) diff --git a/packages/lib/modules/pool/PoolDetail/StakeUnstakeButton.tsx b/packages/lib/modules/pool/PoolDetail/StakeUnstakeButton.tsx index 1b90595dc9..d472fa2797 100644 --- a/packages/lib/modules/pool/PoolDetail/StakeUnstakeButton.tsx +++ b/packages/lib/modules/pool/PoolDetail/StakeUnstakeButton.tsx @@ -189,18 +189,22 @@ export function StakeUnstakeButton({ pool, action }: StakeUnstakeButtonProps) { title="Balancer" usdValue={isStakeAction ? undefined : gaugeStakedBalanceUsdFormatted} /> - - diff --git a/packages/lib/modules/pool/actions/add-liquidity/modal/StakingOptions.tsx b/packages/lib/modules/pool/actions/add-liquidity/modal/StakingOptions.tsx index 52e7d206f7..814bb27fb4 100644 --- a/packages/lib/modules/pool/actions/add-liquidity/modal/StakingOptions.tsx +++ b/packages/lib/modules/pool/actions/add-liquidity/modal/StakingOptions.tsx @@ -1,35 +1,17 @@ 'use client' import StarsIcon from '@repo/lib/shared/components/icons/StarsIcon' -import { - Button, - Card, - Flex, - Box, - HStack, - Icon, - Text, - Tooltip, - useDisclosure, - VStack, -} from '@chakra-ui/react' +import { Button, Card, Flex, Box, HStack, Icon, Text, Tooltip, VStack } from '@chakra-ui/react' import Image from 'next/image' import Link from 'next/link' -import { getAuraPoolLink, getPoolActionPath, getTotalAprLabel } from '../../../pool.utils' +import { getPoolActionPath, getTotalAprLabel } from '../../../pool.utils' import { usePool } from '../../../PoolProvider' -import { fNum } from '@repo/lib/shared/utils/numbers' -import { getChainId } from '@repo/lib/config/app.config' -import { - PartnerRedirectModal, - RedirectPartner, -} from '@repo/lib/shared/components/modals/PartnerRedirectModal' -import { GqlChain } from '@repo/lib/shared/services/api/generated/graphql' import { PROJECT_CONFIG } from '@repo/lib/config/getProjectConfig' import { isQuantAmmPool } from '../../../pool.helpers' import { InfoIcon } from '@repo/lib/shared/components/icons/InfoIcon' export function StakingOptions() { - const { chain, pool } = usePool() + const { pool } = usePool() const { projectName, projectId } = PROJECT_CONFIG const canBeNegative = isQuantAmmPool(pool.type) @@ -41,8 +23,6 @@ export function StakingOptions() { action: 'stake', }) - const auraDisclosure = useDisclosure() - return ( <> @@ -98,36 +78,6 @@ export function StakingOptions() { - {pool.chain === GqlChain.Optimism && pool.staking?.aura && ( - - - - Aura - - - - {pool.staking?.aura ? fNum('apr', pool.staking.aura.apr) : 'Not available'} - - - - balancer - - {pool.staking && pool.staking.aura && ( - <> - - - - )} - - - )} ) diff --git a/packages/lib/modules/pool/pool.utils.ts b/packages/lib/modules/pool/pool.utils.ts index 202e27402f..16d2c0fc56 100644 --- a/packages/lib/modules/pool/pool.utils.ts +++ b/packages/lib/modules/pool/pool.utils.ts @@ -146,7 +146,7 @@ export function getTotalApr(aprItems: GqlPoolAprItem[]): [BigNumber, BigNumber] if (item.type === GqlPoolAprItemType.VebalEmissions) { // We don't add this to maxTotal as is already included on the staking boost minTotal = bn(item.apr).plus(minTotal) - return + return // Deprecated, should be 0 once emissions stop } if (item.type === GqlPoolAprItemType.MabeetsEmissions) { From 4ca158ce9cae211de3d4d4acb659412d65eb6f2d Mon Sep 17 00:00:00 2001 From: Juanjo Molinero Date: Fri, 10 Apr 2026 17:00:15 +0200 Subject: [PATCH 3/5] [#2316] fix unit tests --- apps/frontend-v3/lib/vebal/__mocks__/provider-mocks.tsx | 2 +- .../tokens/TokenBalancesProvider.integration.spec.ts | 2 +- packages/lib/shared/hooks/useAprTooltip.spec.tsx | 8 ++------ packages/lib/test/data/all-gql-tokens.fake.ts | 1 - 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/apps/frontend-v3/lib/vebal/__mocks__/provider-mocks.tsx b/apps/frontend-v3/lib/vebal/__mocks__/provider-mocks.tsx index 3a46f0feba..618f62db64 100644 --- a/apps/frontend-v3/lib/vebal/__mocks__/provider-mocks.tsx +++ b/apps/frontend-v3/lib/vebal/__mocks__/provider-mocks.tsx @@ -4,7 +4,7 @@ import { fakeTokenBySymbol } from '@repo/lib/test/data/all-gql-tokens.fake' import { PropsWithChildren } from 'react' export function TokenBalancesProviderMock({ children }: PropsWithChildren) { - const vebalBptToken = fakeTokenBySymbol('B-80BAL-20WETH') + const vebalBptToken = fakeTokenBySymbol('USDC-DAI-USDT') return ( diff --git a/packages/lib/modules/tokens/TokenBalancesProvider.integration.spec.ts b/packages/lib/modules/tokens/TokenBalancesProvider.integration.spec.ts index 79130b5b74..dc43a017ec 100644 --- a/packages/lib/modules/tokens/TokenBalancesProvider.integration.spec.ts +++ b/packages/lib/modules/tokens/TokenBalancesProvider.integration.spec.ts @@ -97,7 +97,7 @@ test('Should not return balances when user is not connected (account is empty) ' }) test('should return balances of 20 tokens', async () => { - const numberOfTokens = 20 + const numberOfTokens = 19 const { result } = testHook(() => useTokenBalancesLogic(allFakeGqlTokens.slice(0, numberOfTokens)) diff --git a/packages/lib/shared/hooks/useAprTooltip.spec.tsx b/packages/lib/shared/hooks/useAprTooltip.spec.tsx index 30823ef80a..63735d5f03 100644 --- a/packages/lib/shared/hooks/useAprTooltip.spec.tsx +++ b/packages/lib/shared/hooks/useAprTooltip.spec.tsx @@ -23,17 +23,13 @@ describe('useAprTooltip', () => { expect(result.current.swapFeesDisplayed.toFixed()).toBe('0.0007') expect(fNum('apr', result.current.swapFeesDisplayed)).toBe('0.07%') - const stakingIncentivesApr = result.current.stakingIncentivesDisplayed[0].apr - expect(stakingIncentivesApr.toFixed()).toBe('0.0191') - expect(fNum('apr', stakingIncentivesApr)).toBe('1.91%') - const yieldBearingTokensApr = result.current.yieldBearingTokensDisplayed[0].apr expect(yieldBearingTokensApr.toFixed()).toBe('0.0068') expect(fNum('apr', yieldBearingTokensApr)).toBe('0.68%') const totalBaseDisplayed = result.current.totalBaseDisplayed - expect(totalBaseDisplayed.toFixed()).toBe('0.0266') - expect(fNum('apr', totalBaseDisplayed)).toBe('2.66%') // 0.07 + 1.91 + 0.68 = 2.66 + expect(totalBaseDisplayed.toFixed()).toBe('0.0075') + expect(fNum('apr', totalBaseDisplayed)).toBe('0.75%') // 0.07 + 0.0068 + 0.68 = 0.75 }) }) diff --git a/packages/lib/test/data/all-gql-tokens.fake.ts b/packages/lib/test/data/all-gql-tokens.fake.ts index aa657dc230..c40e935826 100644 --- a/packages/lib/test/data/all-gql-tokens.fake.ts +++ b/packages/lib/test/data/all-gql-tokens.fake.ts @@ -13,7 +13,6 @@ export const fakeTokenSymbols = [ 'aUSDC', 'USDC', 'USDC-DAI-USDT', - 'B-80BAL-20WETH', ] as const export type FakeTokenSymbol = (typeof fakeTokenSymbols)[number] From 8c1c196f572c388b29652a90591af152e692c008 Mon Sep 17 00:00:00 2001 From: Juanjo Molinero Date: Mon, 13 Apr 2026 14:12:07 +0200 Subject: [PATCH 4/5] [#2316] redirect /vebal to /vebal/manage --- apps/frontend-v3/next.config.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/frontend-v3/next.config.ts b/apps/frontend-v3/next.config.ts index 4cb0c172e2..7cbf24c82b 100644 --- a/apps/frontend-v3/next.config.ts +++ b/apps/frontend-v3/next.config.ts @@ -32,6 +32,13 @@ const nextConfig: NextConfig = { // Safe App setup headers: manifestHeaders, reactCompiler: true, + redirects: async () => [ + { + source: '/vebal', + destination: '/vebal/manage', + permanent: true, + }, + ], } // Avoid sentry setup in CI From 55e26e375f1942d3a40f3c2686820813d5cd5a92 Mon Sep 17 00:00:00 2001 From: Juanjo Molinero Date: Wed, 15 Apr 2026 11:16:33 +0200 Subject: [PATCH 5/5] [#2316] remove unused graphql queries --- .../lib/shared/services/api/vote-list.graphql | 22 ------------------- packages/lib/test/utils/wagmi/fork.helpers.ts | 20 ----------------- 2 files changed, 42 deletions(-) delete mode 100644 packages/lib/shared/services/api/vote-list.graphql diff --git a/packages/lib/shared/services/api/vote-list.graphql b/packages/lib/shared/services/api/vote-list.graphql deleted file mode 100644 index 7d67bb00e3..0000000000 --- a/packages/lib/shared/services/api/vote-list.graphql +++ /dev/null @@ -1,22 +0,0 @@ -query GetVeBalVotingList($includeKilled: Boolean) { - veBalGetVotingList(includeKilled: $includeKilled) { - id - symbol - type - address - chain - protocolVersion - gauge { - addedTimestamp - address - childGaugeAddress - isKilled - relativeWeight - relativeWeightCap - } - poolTokens { - ...PoolTokens - } - tags - } -} diff --git a/packages/lib/test/utils/wagmi/fork.helpers.ts b/packages/lib/test/utils/wagmi/fork.helpers.ts index ae9890f3de..0d1f8a3abd 100644 --- a/packages/lib/test/utils/wagmi/fork.helpers.ts +++ b/packages/lib/test/utils/wagmi/fork.helpers.ts @@ -4,8 +4,6 @@ import { TokenBalance, TokenBalancesByChain } from './fork-options' import { createConfig } from 'wagmi' import { mainnet, sonic } from 'viem/chains' import { drpcUrlByChainId } from '@repo/lib/shared/utils/rpc' -import { orderBy } from 'lodash' -import { GetVeBalVotingListQuery } from '@repo/lib/shared/services/api/generated/graphql' import { isBeets } from '@repo/lib/config/getProjectConfig' /* @@ -104,21 +102,3 @@ export function clearImpersonatedAddressLS() { function isLocalStorageAvailable() { return typeof localStorage !== 'undefined' } - -type VotingPools = GetVeBalVotingListQuery['veBalGetVotingList'] -/* -The anvil fork will be too slow when used with > 700 gauges so we filter the list to allow faster tests -*/ -export function filterVotingPoolsForAnvilFork(votingPools: VotingPools) { - const killedGaugesToInclude = [ - '0xcf5938ca6d9f19c73010c7493e19c02acfa8d24d', // gauge of tetuBal pool - '0xc13a3315806f097cee00e39c4285f5bf250dd8a4', // gauge of vgUSDC / xUSDC pool - ] - // Order by isKilled first - return orderBy(votingPools, ['gauge.isKilled'], ['desc']).filter(vote => { - // Filter not killed gauges with only specific killed ones - return !vote.gauge.isKilled || killedGaugesToInclude.includes(vote.gauge.address) - }) - // Uncomment to enable faster anvil voting list (with a subset of gauges) - // .slice(0, 50) -}