From 29ba630bf7d1a626f5829ff016e3fd859b8f5a38 Mon Sep 17 00:00:00 2001 From: MrX-SNX Date: Thu, 12 Sep 2024 15:48:04 +0100 Subject: [PATCH] feat: votepower (#452) * feat: votepower * fix: bugs * style * button state * test: fixing tests * fix: my vote row * ref:prepare for eval * wip * test: fix test * feat: fix voting period countdown * fix icons * fix: typecheck * fix: typecheck * wip * wip * done --- .../cypress/e2e/Councils - Nomination.e2e.js | 3 +- .../cypress/e2e/Councils - Voting.e2e.js | 4 +- .../CouncilNominees/CouncilNominees.tsx | 11 +- .../components/CouncilTabs/CouncilTabs.tsx | 28 +-- .../ui/src/components/Icons/Ethereum.tsx | 45 +++++ .../ui/src/components/Icons/Optimism.tsx | 24 +++ governance/ui/src/components/Icons/index.ts | 2 + .../ui/src/components/MyVoteRow/MyVoteRow.tsx | 131 ++++++++------ .../PeriodCountdown/PeriodCountdown.tsx | 23 ++- .../components/UserListItem/UserListItem.tsx | 16 +- .../UserProfileCard/UserProfileDetails.tsx | 167 ++++++++++++------ .../components/UserProfileCard/VotePower.tsx | 79 +++++++++ .../UserProfileEditPreview.tsx | 1 + .../UserTableView/UserTableView.tsx | 9 +- .../ui/src/mutations/useNominateSelf.ts | 6 +- governance/ui/src/pages/MyVotes.tsx | 152 ++++++++++------ .../ui/src/queries/useGetCurrentPeriod.ts | 7 +- .../ui/src/queries/useGetEpochSchedule.ts | 3 +- .../ui/src/queries/useGetUserVotingPower.ts | 124 ++++++++----- 19 files changed, 589 insertions(+), 246 deletions(-) create mode 100644 governance/ui/src/components/Icons/Ethereum.tsx create mode 100644 governance/ui/src/components/Icons/Optimism.tsx create mode 100644 governance/ui/src/components/UserProfileCard/VotePower.tsx diff --git a/governance/cypress/cypress/e2e/Councils - Nomination.e2e.js b/governance/cypress/cypress/e2e/Councils - Nomination.e2e.js index 10af57c8c..d758ae76b 100644 --- a/governance/cypress/cypress/e2e/Councils - Nomination.e2e.js +++ b/governance/cypress/cypress/e2e/Councils - Nomination.e2e.js @@ -13,9 +13,10 @@ it('Councils - Administration', () => { cy.get('[data-cy="period-countdown"]').should('exist'); cy.get('[data-cy="period-countdown"]').contains('Voting starts'); cy.get('[data-cy="name-table-header"]').click(); + cy.get('[data-cy="sort-arrow-down"]').should('exist'); + cy.get('[data-cy="sort-arrow-down"]').click(); cy.get('[data-cy="sort-arrow-up"]').should('exist'); cy.get('[data-cy="sort-arrow-up"]').click(); - cy.get('[data-cy="sort-arrow-down"]').should('exist'); cy.get('[data-cy="own-user-list-item"]').should( 'have.css', 'border-top', diff --git a/governance/cypress/cypress/e2e/Councils - Voting.e2e.js b/governance/cypress/cypress/e2e/Councils - Voting.e2e.js index 503d86acc..dee1a73a9 100644 --- a/governance/cypress/cypress/e2e/Councils - Voting.e2e.js +++ b/governance/cypress/cypress/e2e/Councils - Voting.e2e.js @@ -16,11 +16,13 @@ it('Councils - Administration', () => { cy.get('[data-cy="period-countdown"]').should('exist'); cy.get('[data-cy="period-countdown"]').contains('Voting ends'); cy.get('[data-cy="name-table-header"]').click(); + cy.get('[data-cy="sort-arrow-down"]').should('exist'); + cy.get('[data-cy="sort-arrow-down"]').click(); cy.get('[data-cy="sort-arrow-up"]').should('exist'); cy.get('[data-cy="sort-arrow-up"]').click(); - cy.get('[data-cy="sort-arrow-down"]').should('exist'); cy.get('[data-cy="own-user-list-item"]').click(); cy.get('[data-cy="nominate-self-button-user-profile-details-voting-period"]').click(); + cy.get('[data-cy="nominate-self-button-in-user-profile"]').click(); cy.get('[data-cy="council-nomination-select-spartan"]').click(); cy.get('[data-cy="nominate-self-cast-nomination-button"]').click(); cy.get('[data-cy="nominate-self-done-button"]').click(); diff --git a/governance/ui/src/components/CouncilNominees/CouncilNominees.tsx b/governance/ui/src/components/CouncilNominees/CouncilNominees.tsx index 7dfa69a29..e90e4008c 100644 --- a/governance/ui/src/components/CouncilNominees/CouncilNominees.tsx +++ b/governance/ui/src/components/CouncilNominees/CouncilNominees.tsx @@ -35,7 +35,7 @@ import { sortUsers } from '../../utils/sort-users'; export default function CouncilNominees({ activeCouncil }: { activeCouncil: CouncilSlugs }) { const [search, setSearch] = useState(''); - const [sortConfig, setSortConfig] = useState<[boolean, string]>([false, 'name']); + const [sortConfig, setSortConfig] = useState<[boolean, string]>([true, 'votingPower']); const { network } = useNetwork(); const { data: epochId } = useGetEpochIndex(activeCouncil); @@ -149,7 +149,7 @@ export default function CouncilNominees({ activeCouncil }: { activeCouncil: Coun - {councilPeriod === '2' && ( + {(councilPeriod === '2' || councilPeriod === '3') && ( - {councilPeriod === '2' && ( + {(councilPeriod === '2' || councilPeriod === '3') && ( )} - {councilPeriod === '2' && ( + {(councilPeriod === '2' || councilPeriod === '3') && ( )} - {councilPeriod === '2' && ( + {(councilPeriod === '2' || councilPeriod === '3') && ( )} diff --git a/governance/ui/src/components/CouncilTabs/CouncilTabs.tsx b/governance/ui/src/components/CouncilTabs/CouncilTabs.tsx index 4a7f6c95f..90549f974 100644 --- a/governance/ui/src/components/CouncilTabs/CouncilTabs.tsx +++ b/governance/ui/src/components/CouncilTabs/CouncilTabs.tsx @@ -175,7 +175,7 @@ export default function CouncilTabs({ activeCouncil }: { activeCouncil: CouncilS address={userInformation[index].userInformation?.address} size={9} newVoteCast={newVoteCast} - isCouncilTabs + isCouncilTabs={false} /> {userInformation[index]?.userInformation?.address ? newVoteCast?.toLowerCase() !== @@ -186,7 +186,7 @@ export default function CouncilTabs({ activeCouncil }: { activeCouncil: CouncilS address={newVoteCast} size={9} newVoteCast={newVoteCast} - isCouncilTabs + isCouncilTabs={true} /> ) @@ -200,23 +200,25 @@ export default function CouncilTabs({ activeCouncil }: { activeCouncil: CouncilS address={userInformation[index].userInformation?.address} size={9} newVoteCast={newVoteCast} - isCouncilTabs + isCouncilTabs={false} /> )} {newVoteCast && userInformation[index].userInformation?.address && ( )} - + {(newVoteCast === 'remove' || !newVoteCast) && ( + + )} ) )} diff --git a/governance/ui/src/components/Icons/Ethereum.tsx b/governance/ui/src/components/Icons/Ethereum.tsx new file mode 100644 index 000000000..09640c138 --- /dev/null +++ b/governance/ui/src/components/Icons/Ethereum.tsx @@ -0,0 +1,45 @@ +import { Icon } from '@chakra-ui/react'; + +export const EthereumIcon = ( + + + + + + + + + + + + + + + + +); diff --git a/governance/ui/src/components/Icons/Optimism.tsx b/governance/ui/src/components/Icons/Optimism.tsx new file mode 100644 index 000000000..5362949f3 --- /dev/null +++ b/governance/ui/src/components/Icons/Optimism.tsx @@ -0,0 +1,24 @@ +import { Icon } from '@chakra-ui/react'; + +export const OPIcon = ( + + + + + +); diff --git a/governance/ui/src/components/Icons/index.ts b/governance/ui/src/components/Icons/index.ts index 0781541db..f0e839f40 100644 --- a/governance/ui/src/components/Icons/index.ts +++ b/governance/ui/src/components/Icons/index.ts @@ -4,3 +4,5 @@ export * from './SNXHeaderIcon'; export * from './SNXHeaderIconSmall'; export * from './EditIcon'; export * from './ShareIcon'; +export * from './Optimism'; +export * from './Ethereum'; diff --git a/governance/ui/src/components/MyVoteRow/MyVoteRow.tsx b/governance/ui/src/components/MyVoteRow/MyVoteRow.tsx index 896aec577..bc5f805cd 100644 --- a/governance/ui/src/components/MyVoteRow/MyVoteRow.tsx +++ b/governance/ui/src/components/MyVoteRow/MyVoteRow.tsx @@ -7,6 +7,7 @@ import { useVoteContext } from '../../context/VoteContext'; import { useGetEpochIndex, useGetUserBallot, useNetwork, useWallet } from '../../queries'; import { getVoteSelectionState } from '../../utils/localstorage'; import { Badge } from '../Badge'; +import { utils } from 'ethers'; export default function MyVoteRow({ councilSlug, @@ -24,15 +25,62 @@ export default function MyVoteRow({ const { dispatch, state } = useVoteContext(); const { network } = useNetwork(); - const networkForState = getVoteSelectionState( + const votedCandidate = ballot?.votedCandidates[0] || ''; + const voteSelection = getVoteSelectionState( state, activeWallet?.address, epochId?.toString(), network?.id.toString(), councilSlug ); + const voteAddressState = typeof voteSelection === 'string' ? voteSelection : ''; + + const hasVoted = + utils.isAddress(votedCandidate) && + utils.isAddress(voteAddressState) && + votedCandidate.toLowerCase() === voteAddressState.toLowerCase(); + const isDisabled = period !== '2'; + + const handleAddVote = (e: React.MouseEvent) => { + e.stopPropagation(); + navigate(`/councils/${councilSlug}`); + }; + + const handleRemoveVote = (e: React.MouseEvent) => { + e.stopPropagation(); + if ( + utils.isAddress(votedCandidate) && + utils.isAddress(voteAddressState) && + votedCandidate.toLowerCase() !== voteAddressState.toLowerCase() + ) { + dispatch({ + type: councilSlug.toUpperCase(), + payload: { + action: votedCandidate, + network: network?.id.toString(), + epochId: epochId?.toString(), + wallet: activeWallet?.address, + }, + }); + } else { + dispatch({ + type: councilSlug.toUpperCase(), + payload: { + action: votedCandidate + ? voteAddressState === 'remove' + ? votedCandidate + : 'remove' + : voteAddressState === 'remove' + ? 'remove' + : undefined, + network: network?.id.toString(), + epochId: epochId?.toString(), + wallet: activeWallet?.address, + }, + }); + } + }; - const voteAddressState = typeof networkForState === 'string' ? networkForState : ''; return ( - - - {ballot?.votedCandidates[0] && - voteAddressState && - ballot.votedCandidates[0].toLowerCase() !== voteAddressState.toLowerCase() && ( - <> - - - - )} + + + {!hasVoted && votedCandidate && voteAddressState && ( + <> + + + + )} - {ballot?.votedCandidates.includes(voteAddressState) ? ( + + {hasVoted ? ( Your Vote ) : voteAddressState ? ( Selected ) : null} - {!networkForState && !ballot?.votedCandidates[0] ? ( + + {!voteAddressState && !votedCandidate ? ( } variant="outlined" mr="4" - isDisabled={period !== '2'} - onClick={(e) => { - e.stopPropagation(); - navigate(`/councils/${councilSlug}`); - }} + isDisabled={isDisabled} + onClick={handleAddVote} /> ) : ( { - e.stopPropagation(); - - if (network?.id) { - if (!!ballot?.votedCandidates[0]) { - dispatch({ - type: councilSlug.toUpperCase(), - payload: { - action: 'remove', - network: network.id.toString(), - epochId: epochId?.toString(), - wallet: activeWallet?.address, - }, - }); - } else { - dispatch({ - type: councilSlug.toUpperCase(), - payload: { - action: networkForState === 'remove' ? 'remove' : undefined, - network: network.id.toString(), - epochId: epochId?.toString(), - wallet: activeWallet?.address, - }, - }); - } - } - }} + isDisabled={isDisabled} + onClick={handleRemoveVote} /> )} diff --git a/governance/ui/src/components/PeriodCountdown/PeriodCountdown.tsx b/governance/ui/src/components/PeriodCountdown/PeriodCountdown.tsx index 7c9015c22..57dc058c6 100644 --- a/governance/ui/src/components/PeriodCountdown/PeriodCountdown.tsx +++ b/governance/ui/src/components/PeriodCountdown/PeriodCountdown.tsx @@ -8,7 +8,26 @@ export default function PeriodCountdown({ council }: { council: CouncilSlugs }) const { data: councilPeriod } = useGetCurrentPeriod(council); const { data: schedule, isLoading } = useGetEpochSchedule(council); - return councilPeriod !== '3' ? ( + if (councilPeriod === '3') { + return ( + + + CLOSED + + + ); + } + return ( - ) : null; + ); } diff --git a/governance/ui/src/components/UserListItem/UserListItem.tsx b/governance/ui/src/components/UserListItem/UserListItem.tsx index 72e3f3ef0..48654cede 100644 --- a/governance/ui/src/components/UserListItem/UserListItem.tsx +++ b/governance/ui/src/components/UserListItem/UserListItem.tsx @@ -88,19 +88,13 @@ export default function UserListItem({ colorScheme="gray" data-cy="nominate-self-button-user-profile-details-voting-period" _hover={{}} + _active={{}} onClick={(e) => { e.stopPropagation(); - if (nominationInformation?.isNominated) { - navigate({ - pathname: `/councils/${activeCouncil}`, - search: `view=${address}`, - }); - } else { - navigate({ - pathname: `/councils/${activeCouncil}`, - search: `view=${address}&nominate=true`, - }); - } + navigate({ + pathname: `/councils/${activeCouncil}`, + search: `view=${address}`, + }); }} color="white" > diff --git a/governance/ui/src/components/UserProfileCard/UserProfileDetails.tsx b/governance/ui/src/components/UserProfileCard/UserProfileDetails.tsx index 06d6786f1..f3bd9081f 100644 --- a/governance/ui/src/components/UserProfileCard/UserProfileDetails.tsx +++ b/governance/ui/src/components/UserProfileCard/UserProfileDetails.tsx @@ -7,12 +7,21 @@ import { CouncilSlugs } from '../../utils/councils'; import { useNavigate } from 'react-router-dom'; import { useVoteContext } from '../../context/VoteContext'; import { ProfilePicture } from './ProfilePicture'; -import { EditIcon, ShareIcon } from '../Icons'; -import { useGetEpochIndex, useGetUserBallot, useNetwork, useWallet } from '../../queries'; +import { EditIcon, EthereumIcon, OPIcon, ShareIcon } from '../Icons'; +import { + useGetEpochIndex, + useGetUserBallot, + useGetUserVotingPowerForAllChains, + useNetwork, + useWallet, +} from '../../queries'; import { useEffect, useRef, useState } from 'react'; import { useRecoilState } from 'recoil'; import { voteCardState } from '../../state/vote-card'; import { getVoteSelectionState } from '../../utils/localstorage'; +import { isMotherchain } from '../../utils/contracts'; +import VotePower from './VotePower'; +import { BigNumber } from 'ethers'; interface UserProfileDetailsProps { userData?: GetUserDetails; @@ -31,6 +40,7 @@ export const UserProfileDetails = ({ isNominated, councilPeriod, }: UserProfileDetailsProps) => { + const [showVotePower, setShowVoteBanner] = useState(false); const [_, setVoteCard] = useRecoilState(voteCardState); const { activeWallet } = useWallet(); const [tooltipLabel, setTooltipLabel] = useState('Copy Profile Link'); @@ -38,6 +48,7 @@ export const UserProfileDetails = ({ const [isTooltipOpen, setIsTooltipOpen] = useState(false); const [isWalletTooltipOpen, setWalletIsTooltipOpen] = useState(false); const { network } = useNetwork(); + const { data: votePowers } = useGetUserVotingPowerForAllChains(activeCouncil); const { data: epochId } = useGetEpochIndex(activeCouncil); const { dispatch, state } = useVoteContext(); const navigate = useNavigate(); @@ -99,6 +110,10 @@ export const UserProfileDetails = ({ }; }, []); + if (showVotePower) { + return ; + } + return ( <> @@ -235,6 +250,7 @@ export const UserProfileDetails = ({ fontSize="14px" lineHeight="20px" overflowY="scroll" + whiteSpace="pre-wrap" ref={elementRef} > {userData?.delegationPitch} @@ -254,7 +270,7 @@ export const UserProfileDetails = ({ {isOwn && ( <> - {councilPeriod === '2' ? ( + {councilPeriod === '2' && isNominated ? ( + )} + + colorScheme={ + !isNominated && isOwn ? 'gray' : !isAlreadyVoted && !isSelected ? 'cyan' : 'gray' + } + isDisabled={!isNominated && isOwn} + w="100%" + mt={!isOwn ? 4 : 0} + data-cy="select-user-to-vote-button" + onClick={async () => { + const parsedNetwork = network?.id ? network.id.toString() : '2192'; + if (isMotherchain(parsedNetwork) && !(process.env.CI === 'true')) { + setShowVoteBanner(true); + } else { + if (isAlreadyVoted) { + dispatch({ + type: activeCouncil.toUpperCase(), + payload: { + action: 'remove', + network: parsedNetwork, + epochId: epochId?.toString(), + wallet: activeWallet?.address, + }, + }); + } else if (isSelected) { + dispatch({ + type: activeCouncil.toUpperCase(), + payload: { + action: undefined, + network: parsedNetwork, + epochId: epochId?.toString(), + wallet: activeWallet?.address, + }, + }); + } else { + dispatch({ + type: activeCouncil.toUpperCase(), + payload: { + action: userData?.address.toLowerCase(), + network: parsedNetwork, + epochId: epochId?.toString(), + wallet: activeWallet?.address, + }, + }); + } + setVoteCard(true); + } + }} + > + + {isAlreadyVoted ? 'Withdraw Vote ' : isSelected ? 'Remove ' : 'Select '} + {userData?.ens || userData?.username + ? userData.username + : prettyString(userData!.address)} + + + )} {!isOwn && councilPeriod !== '2' && ( @@ -352,3 +397,23 @@ export const UserProfileDetails = ({ ); }; + +interface VotePowerToNetwork { + power: BigNumber; + isDeclared: boolean; +} + +function votePowerToNetwork( + power?: + | { + L1: VotePowerToNetwork | undefined; + Optimism: VotePowerToNetwork | undefined; + } + | undefined +) { + if (!power) return []; + const networks = []; + networks.push({ icon: EthereumIcon, chainId: 1, power: power.L1?.power }); + networks.push({ icon: OPIcon, chainId: 10, power: power.Optimism?.power }); + return networks; +} diff --git a/governance/ui/src/components/UserProfileCard/VotePower.tsx b/governance/ui/src/components/UserProfileCard/VotePower.tsx new file mode 100644 index 000000000..c7770816a --- /dev/null +++ b/governance/ui/src/components/UserProfileCard/VotePower.tsx @@ -0,0 +1,79 @@ +import { CloseIcon } from '@chakra-ui/icons'; +import { Button, Flex, Heading, IconButton, Text } from '@chakra-ui/react'; +import { ReactNode } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useNetwork } from '../../queries'; +import { BigNumber } from 'ethers'; +import { formatNumber } from '@snx-v3/formatters'; + +export default function VotePower({ + activeCouncil, + networks, +}: { + networks: { icon: ReactNode; chainId: number; power?: BigNumber }[]; + activeCouncil: string; +}) { + const navigate = useNavigate(); + const { setNetwork } = useNetwork(); + return ( + + navigate(`/councils/${activeCouncil}`)} + size="sm" + aria-label="close button" + icon={} + variant="ghost" + colorScheme="whiteAlpha" + color="white" + position="absolute" + top="16px" + right="0px" + _hover={{}} + /> + + Voting not available on Snaxchain + + + Voting is not available on Snaxchain. Switch to Ethereum or Optimism.{' '} + + + {networks.map((network) => ( + + + {network.icon} + + + Total Voting Power + + + {formatNumber(network.power?.toString() || '0') || '0'} + + + + + + ))} + + + ); +} diff --git a/governance/ui/src/components/UserProfileForm/UserProfileEditPreview.tsx b/governance/ui/src/components/UserProfileForm/UserProfileEditPreview.tsx index a099a9be3..3d9e7a447 100644 --- a/governance/ui/src/components/UserProfileForm/UserProfileEditPreview.tsx +++ b/governance/ui/src/components/UserProfileForm/UserProfileEditPreview.tsx @@ -143,6 +143,7 @@ export default function UserProfileEditPreview({ fontSize="14px" data-cy="governance-pitch-preview" lineHeight="20px" + whiteSpace="pre-wrap" overflowY="scroll" h="330px" mb="auto" diff --git a/governance/ui/src/components/UserTableView/UserTableView.tsx b/governance/ui/src/components/UserTableView/UserTableView.tsx index d41025f93..05ec18d8f 100644 --- a/governance/ui/src/components/UserTableView/UserTableView.tsx +++ b/governance/ui/src/components/UserTableView/UserTableView.tsx @@ -30,7 +30,8 @@ export default function UserTableView({ const { data: ballot } = useGetUserBallot(activeCouncil); const { data: councilPeriod } = useGetCurrentPeriod(activeCouncil); const isSelected = searchParams.get('view') === user.address; - const councilIsInAdminOrVoting = councilPeriod === '2' || councilPeriod === '0'; + const councilIsInAdminOrVotinOrEval = + councilPeriod === '2' || councilPeriod === '0' || councilPeriod === '3'; const totalVotingPowerPercentage = totalVotingPower && user.voteResult ? formatNumber(user.voteResult?.votePower.mul(100).div(totalVotingPower).toString()) @@ -43,7 +44,7 @@ export default function UserTableView({ onClick={() => navigate(`/councils/${activeCouncil}?view=${user.address}`)} _hover={{ background: 'rgba(255,255,255,0.12)' }} > - {councilIsInAdminOrVoting && ( + {councilIsInAdminOrVotinOrEval && ( - {councilIsInAdminOrVoting && ( + {councilIsInAdminOrVotinOrEval && (
setSortConfig([!sortConfig[0], 'name'])} > Name {sortConfig[1] === 'name' && } - {sortConfig[1] === 'start' && } } }
)} - {councilIsInAdminOrVoting && ( + {councilIsInAdminOrVotinOrEval && ( ); - const { mutateAsync, isPending } = useCastVotes(councilToCastVote, councilsToAddress); + const { mutateAsync, isPending, isSuccess } = useCastVotes(councilToCastVote, councilsToAddress); const navigate = useNavigate(); const formattedVotePower = formatNumber( votingPowerSpartan?.power && votingPowerAmbassador?.power && votingPowerTreassury?.power @@ -198,59 +201,100 @@ export default function MyVotes() { flexDir="column" h="fit-content" > - - {period === '2' ? 'Cast Your Votes' : 'Voting Power'} - - - Your voting power is determined by your staked SNX on Synthetix V2x and represents the - influence you hold in the governance process. - - - - Total Voting Power - - - {formattedVotePower === '0.00' - ? formatNumber( - votingPowerSpartan?.power - .add(votingPowerAmbassador?.power || 0) - .add(votingPowerTreassury?.power || 0) - .toString() || 0 - ) - : formattedVotePower} - - - + + ) : ( + <> + + {period === '2' ? 'Cast Your Votes' : 'Voting Power'} + + + Your voting power is determined by your staked SNX on Synthetix V2x and represents + the influence you hold in the governance process. + + + + Total Voting Power + + + {formattedVotePower === '0.00' + ? formatNumber( + votingPowerSpartan?.power + .add(votingPowerAmbassador?.power || 0) + .add(votingPowerTreassury?.power || 0) + .toString() || 0 + ) + : formattedVotePower} + + + + onClick={async () => { + if (!network?.id) { + connect(); + } else { + if (councilToCastVote.length !== 3) { + setShowConfirmation(true); + } else { + await mutateAsync(); + } + } + }} + > + {!network?.id ? 'Connect Wallet' : 'Cast Votes'} + + + )} + { @@ -294,7 +338,7 @@ export default function MyVotes() { {council.title} - {councilsToAddress[council.slug] ? ( + {utils.isAddress(councilsToAddress[council.slug]) ? ( ) : ( diff --git a/governance/ui/src/queries/useGetCurrentPeriod.ts b/governance/ui/src/queries/useGetCurrentPeriod.ts index 1fb9a4786..12e6d4c1f 100644 --- a/governance/ui/src/queries/useGetCurrentPeriod.ts +++ b/governance/ui/src/queries/useGetCurrentPeriod.ts @@ -3,12 +3,17 @@ import { CouncilSlugs } from '../utils/councils'; import { motherShipProvider } from '../utils/providers'; import { getCouncilContract } from '../utils/contracts'; import { useNetwork } from './useWallet'; +import { useGetEpochSchedule } from './useGetEpochSchedule'; export function useGetCurrentPeriod(council?: CouncilSlugs) { const { network } = useNetwork(); + const { data: schedule } = useGetEpochSchedule(council); return useQuery({ - queryKey: ['period', council, network?.id], + queryKey: ['period', council, network?.id, schedule?.endDate], queryFn: async () => { + if (schedule?.endDate && schedule.endDate < 0) { + return '3'; + } return ( await getCouncilContract(council!) .connect(motherShipProvider(network?.id || 2192)) diff --git a/governance/ui/src/queries/useGetEpochSchedule.ts b/governance/ui/src/queries/useGetEpochSchedule.ts index 636e7c539..b910695c8 100644 --- a/governance/ui/src/queries/useGetEpochSchedule.ts +++ b/governance/ui/src/queries/useGetEpochSchedule.ts @@ -17,7 +17,8 @@ export function useGetEpochSchedule(council?: CouncilSlugs) { startDate: Number(schedule.startDate.toString()), nominationPeriodStartDate: Number(schedule.nominationPeriodStartDate.toString()), votingPeriodStartDate: Number(schedule.votingPeriodStartDate.toString()), - endDate: Number(schedule.endDate.toString()), + // Todo @dev remove after "bug" is resolved + endDate: Number((schedule.endDate - 3600).toString()), } as | { startDate: number; diff --git a/governance/ui/src/queries/useGetUserVotingPower.ts b/governance/ui/src/queries/useGetUserVotingPower.ts index cccd5251d..5417de6ed 100644 --- a/governance/ui/src/queries/useGetUserVotingPower.ts +++ b/governance/ui/src/queries/useGetUserVotingPower.ts @@ -3,7 +3,7 @@ import { useNetwork } from '@snx-v3/useBlockchain'; import { CouncilSlugs } from '../utils/councils'; import { SnapshotRecordContract, getCouncilContract, isMotherchain } from '../utils/contracts'; import { useProvider, useWallet } from './'; -import { BigNumber, ethers } from 'ethers'; +import { BigNumber, ethers, providers } from 'ethers'; import { motherShipProvider } from '../utils/providers'; export function useGetUserVotingPower(council: CouncilSlugs) { @@ -13,56 +13,84 @@ export function useGetUserVotingPower(council: CouncilSlugs) { return useQuery({ queryFn: async () => { - if (!activeWallet || !provider || !network?.id) return; - const isCI = process.env.CI === 'true'; - try { - const electionModule = getCouncilContract(council).connect(motherShipProvider(network.id)); - const isMC = isMotherchain(network.id); - - const snapshotContract = SnapshotRecordContract(network.id, council); - const electionId = await electionModule.getEpochIndex(); - const ballot: { votingPower: BigNumber } | undefined = await electionModule.getBallot( - activeWallet.address, - network.id, - electionId - ); - - const periodId = await snapshotContract?.connect(provider).currentPeriodId(); - - const votingPowerFromMotherchain: BigNumber = isCI - ? BigNumber.from(0) - : await electionModule - .connect(provider) - .getVotingPowerForUser(snapshotContract?.address, activeWallet.address, periodId); - - if (ballot?.votingPower?.gt(0)) { - return { - power: isCI ? (ballot.votingPower as BigNumber) : votingPowerFromMotherchain, - isDeclared: true, - }; - } - const votingPower: BigNumber = isCI - ? isMC - ? await electionModule - .connect(provider) - .callStatic.prepareBallotWithSnapshot( - snapshotContract?.address, - activeWallet?.address - ) - : BigNumber.from(0) - : votingPowerFromMotherchain; - - return { - power: votingPower, - isDeclared: false, - }; - } catch (error) { - console.error('ERROR IS', { error }); - return { power: ethers.BigNumber.from(0), isDeclared: false }; - } + return await fetchVotingPower(council, activeWallet?.address, provider, network?.id); }, enabled: !!provider && !!activeWallet && !!network?.id, queryKey: ['votingPower', council.toString(), activeWallet?.address, network?.id], staleTime: 900000, }); } + +export function useGetUserVotingPowerForAllChains(council: CouncilSlugs) { + const L1Provider = new providers.JsonRpcProvider( + 'https://mainnet.infura.io/v3/23087ce9f88c44d1b1c54fd7c07c65fb' + ); + const OptimismProvider = new providers.JsonRpcProvider( + 'https://optimism-mainnet.infura.io/v3/23087ce9f88c44d1b1c54fd7c07c65fb' + ); + const { activeWallet } = useWallet(); + return useQuery({ + queryFn: async () => { + return { + L1: await fetchVotingPower(council, activeWallet?.address, L1Provider, 1), + Optimism: await fetchVotingPower(council, activeWallet?.address, OptimismProvider, 10), + }; + }, + enabled: !!activeWallet, + queryKey: ['votingPowerForAllChains', council.toString(), activeWallet?.address], + staleTime: 900000, + }); +} + +async function fetchVotingPower( + council: CouncilSlugs, + activeWallet?: string, + provider?: providers.JsonRpcProvider, + networkId?: number +) { + if (!activeWallet || !provider || !networkId) return; + const isCI = process.env.CI === 'true'; + try { + const electionModule = getCouncilContract(council).connect(motherShipProvider(networkId)); + const isMC = isMotherchain(networkId); + + const snapshotContract = SnapshotRecordContract(networkId, council); + const electionId = await electionModule.getEpochIndex(); + const ballot: { votingPower: BigNumber } | undefined = await electionModule.getBallot( + activeWallet, + networkId, + electionId + ); + + const periodId = await snapshotContract?.connect(provider).currentPeriodId(); + + const votingPowerFromMotherchain: BigNumber = isCI + ? BigNumber.from(0) + : await electionModule + .connect(provider) + .getVotingPowerForUser(snapshotContract?.address, activeWallet, periodId); + + if (ballot?.votingPower?.gt(0)) { + return { + power: isCI ? (ballot.votingPower as BigNumber) : votingPowerFromMotherchain, + isDeclared: true, + }; + } + + const votingPower: BigNumber = isCI + ? isMC + ? await electionModule + .connect(provider) + .callStatic.prepareBallotWithSnapshot(snapshotContract?.address, activeWallet) + : BigNumber.from(0) + : votingPowerFromMotherchain; + + return { + power: votingPower, + isDeclared: false, + }; + } catch (error) { + console.error('ERROR IS', { error }); + return { power: ethers.BigNumber.from(0), isDeclared: false }; + } +}