From 8a443acd73ef2152052d44141ca7f85bcf88f260 Mon Sep 17 00:00:00 2001 From: Oleksandr Date: Tue, 9 Jan 2024 14:35:47 +0200 Subject: [PATCH] fix: ui bugs (#50) * fix: rpc logic * fix: tutorial tx list styles for mobile * fix: voting modal and timeline * fix: against required voting power * fix: voters list styles * fix: will fail for list * fix: balances logic --- app/proposal-create-overview-v2/page.page.tsx | 19 --- app/proposal/page.page.tsx | 7 +- package.json | 2 +- .../ProposalCreateOverviewV2Page.tsx | 110 ---------------- .../store/proposalCreateOverviewV2Slice.ts | 113 ---------------- .../components/ProposalEstimatedStatus.tsx | 23 +--- src/proposals/components/VoteBar.tsx | 4 +- .../components/actionModals/VoteModal.tsx | 35 ++++- .../components/proposal/PayloadActions.tsx | 7 +- .../components/proposal/ProposalPage.tsx | 27 ++-- .../components/proposal/ProposalPayloads.tsx | 7 +- .../components/proposal/ProposalTimeline.tsx | 65 ++------- .../components/proposal/VotersList.tsx | 32 ++++- .../components/proposal/VotersListLoading.tsx | 12 +- .../components/proposal/VotersModal.tsx | 28 +++- .../proposalHistory/ProposalHistoryItem.tsx | 2 +- src/proposals/store/proposalsHistorySlice.ts | 2 +- src/proposals/store/proposalsSelectors.ts | 7 +- src/proposals/store/proposalsSlice.ts | 20 +-- src/proposals/utils/formatVoterAddress.ts | 8 +- src/rpcSwitcher/store/rpcSwitcherSlice.ts | 78 +++++------ src/store/index.ts | 8 +- .../components/TransactionsModalContent.tsx | 2 + src/transactions/store/transactionsSlice.ts | 4 +- src/ui/components/Timer.tsx | 7 +- src/ui/helpModals/HelpStatusesModal.tsx | 1 - src/ui/helpModals/HelpVoteTx.tsx | 39 +++++- src/ui/helpModals/getProposalData.ts | 2 +- src/ui/utils/routes.ts | 1 - src/utils/chains.ts | 37 ++++-- src/utils/initialClients.ts | 17 +-- .../components/wallet/AccountInfoModal.tsx | 20 ++- src/web3/providers/Web3HelperProvider.tsx | 10 -- src/web3/services/delegationService.ts | 123 ++++++++++-------- src/web3/services/govDataService.ts | 7 +- src/web3/store/web3Slice.ts | 17 ++- yarn.lock | 8 +- 37 files changed, 372 insertions(+), 539 deletions(-) delete mode 100644 app/proposal-create-overview-v2/page.page.tsx delete mode 100644 src/proposalCreateOverviewV2/components/ProposalCreateOverviewV2Page.tsx delete mode 100644 src/proposalCreateOverviewV2/store/proposalCreateOverviewV2Slice.ts diff --git a/app/proposal-create-overview-v2/page.page.tsx b/app/proposal-create-overview-v2/page.page.tsx deleted file mode 100644 index d711cdcb..00000000 --- a/app/proposal-create-overview-v2/page.page.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Metadata } from 'next'; -import React from 'react'; - -import { ProposalCreateOverviewV2Page } from '../../src/proposalCreateOverviewV2/components/ProposalCreateOverviewV2Page'; -import { metaTexts } from '../../src/ui/utils/metaTexts'; - -export const metadata: Metadata = { - title: metaTexts.ipfsTitle, - description: metaTexts.ipfsDescription, - openGraph: { - images: ['/metaLogo.jpg'], - title: metaTexts.ipfsTitle, - description: metaTexts.ipfsDescription, - }, -}; - -export default function ProposalCreateOverviewV2() { - return ; -} diff --git a/app/proposal/page.page.tsx b/app/proposal/page.page.tsx index eab07f06..cb8edfd0 100644 --- a/app/proposal/page.page.tsx +++ b/app/proposal/page.page.tsx @@ -121,7 +121,7 @@ export default async function ProposalPage({ (config) => config.accessLevel === cachedDetailsData?.proposal.accessLevel, )[0]; - const executionPayloadTime = Math.max.apply( + const executionDelay = Math.max.apply( null, cachedDetailsData?.payloads.map((payload) => payload.delay) || [0], ); @@ -146,7 +146,7 @@ export default async function ProposalPage({ timings: { cooldownPeriod: contractsConstants.cooldownPeriod, expirationTime: contractsConstants.expirationTime, - executionPayloadTime, + executionDelay, }, }; @@ -156,8 +156,7 @@ export default async function ProposalPage({ differential: proposalDataWithoutState.config.differential, precisionDivider: proposalDataWithoutState.precisionDivider, cooldownPeriod: proposalDataWithoutState.timings.cooldownPeriod, - executionPayloadTime: - proposalDataWithoutState.timings.executionPayloadTime, + executionDelay: proposalDataWithoutState.timings.executionDelay, }); proposalDataSSR = { diff --git a/package.json b/package.json index 807e038b..5d06f25a 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@bgd-labs/aave-address-book": "^2.13.3", - "@bgd-labs/aave-governance-ui-helpers": "^1.0.12", + "@bgd-labs/aave-governance-ui-helpers": "^1.0.15-3a0b4f08fbd9e1650f3a570b83b76f5a90872d88.0", "@bgd-labs/frontend-web3-utils": "^1.0.4-19dbd66bffa6f9723784771409aaa219bbe57b98.0", "@emotion/cache": "^11.11.0", "@emotion/react": "^11.11.3", diff --git a/src/proposalCreateOverviewV2/components/ProposalCreateOverviewV2Page.tsx b/src/proposalCreateOverviewV2/components/ProposalCreateOverviewV2Page.tsx deleted file mode 100644 index 345ce84f..00000000 --- a/src/proposalCreateOverviewV2/components/ProposalCreateOverviewV2Page.tsx +++ /dev/null @@ -1,110 +0,0 @@ -'use client'; - -import { Box } from '@mui/system'; -import { useRouter } from 'next/navigation'; -import React, { useEffect } from 'react'; -import { zeroAddress } from 'viem'; - -import { ProposalListItemWrapper } from '../../proposals/components/proposalList/ProposalListItemWrapper'; -import { useStore } from '../../store'; -import { BackButton3D, Container, Link, SmallButton } from '../../ui'; -import { CopyAndExternalIconsSet } from '../../ui/components/CopyAndExternalIconsSet'; -import { TopPanelContainer } from '../../ui/components/TopPanelContainer'; -import { ROUTES } from '../../ui/utils/routes'; -import { appConfig } from '../../utils/appConfig'; -import { chainInfoHelper } from '../../utils/configs'; - -export function ProposalCreateOverviewV2Page() { - const router = useRouter(); - - const { getProposalCreatedEventsData, proposalCreatedEventsData } = - useStore(); - - useEffect(() => { - getProposalCreatedEventsData(); - }, []); - - return ( - <> - - - - - - - {!proposalCreatedEventsData.length ? ( -

Data requested, please wait a moment for the result

- ) : ( - proposalCreatedEventsData.map((item) => { - const payloadsLinks = Object.values(item.payloads).map( - (payload, index) => { - return `&proposalId=${item.proposalId}&payload[${index}].chainId=${payload.chainId}&payload[${index}].accessLevel=${payload.accessLevel}&payload[${index}].payloadsController=${payload.payloadsController}&payload[${index}].payloadId=${payload.payloadId}&`; - }, - ); - - return ( - - - - Proposal Id: {item.proposalId} - - - - - Creator: {item.creator} - - - - - - - - View details - - - - - ); - }) - )} -
-
- - ); -} diff --git a/src/proposalCreateOverviewV2/store/proposalCreateOverviewV2Slice.ts b/src/proposalCreateOverviewV2/store/proposalCreateOverviewV2Slice.ts deleted file mode 100644 index da19ee5b..00000000 --- a/src/proposalCreateOverviewV2/store/proposalCreateOverviewV2Slice.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { - AaveGovernanceV2, - IAaveGovernanceV2_ABI, -} from '@bgd-labs/aave-address-book'; -import { getEventsBySteps } from '@bgd-labs/aave-governance-ui-helpers'; -import { StoreSlice } from '@bgd-labs/frontend-web3-utils'; -import { decodeAbiParameters, Hex } from 'viem'; - -import { IProposalsSlice } from '../../proposals/store/proposalsSlice'; -import { IRpcSwitcherSlice } from '../../rpcSwitcher/store/rpcSwitcherSlice'; -import { TransactionsSlice } from '../../transactions/store/transactionsSlice'; -import { IUISlice } from '../../ui/store/uiSlice'; -import { appConfig } from '../../utils/appConfig'; -import { IEnsSlice } from '../../web3/store/ensSlice'; -import { IWeb3Slice } from '../../web3/store/web3Slice'; - -type CreatedEventCallDataPayload = { - chainId: number; - accessLevel: number; - payloadsController: string; - payloadId: number; -}; - -export type CreatedEventData = { - proposalId: number; - ipfsHash: Hex; - creator: Hex; - payloads: Record; -}; -export interface IProposalCreateOverviewV2Slice { - proposalCreatedEventsData: CreatedEventData[]; - getProposalCreatedEventsData: () => Promise; -} - -export const createProposalCreateOverviewV2Slice: StoreSlice< - IProposalCreateOverviewV2Slice, - IWeb3Slice & - TransactionsSlice & - IProposalsSlice & - IUISlice & - IEnsSlice & - IRpcSwitcherSlice -> = (set, get) => ({ - proposalCreatedEventsData: [], - - getProposalCreatedEventsData: async () => { - const govCore = get().govDataService.govCore; - const client = get().clients[appConfig.govCoreChainId]; - - const currentBlock = await client.getBlock(); - - const events = async (startBlock: number, endBlock: number) => - await client.getContractEvents({ - address: AaveGovernanceV2.GOV, - abi: IAaveGovernanceV2_ABI, - eventName: 'ProposalCreated', - fromBlock: BigInt(startBlock), - toBlock: BigInt(endBlock), - }); - - const data = await getEventsBySteps( - Number(currentBlock.number) - Math.ceil(2000000 / 15), - Number(currentBlock.number), - 799, - events, - ); - - const formattedData = data - .map((data) => data.args) - .sort((a, b) => Number(b.id) - Number(a.id)) - .filter( - (data) => - data.targets?.every( - (target) => target.toLowerCase() === govCore.address.toLowerCase(), - ), - ) - .map((data) => { - const formattedCallDatas = - data.calldatas?.map((callData) => { - return decodeAbiParameters( - [ - { name: 'chainId', type: 'uint256' }, - { name: 'accessLevel', type: 'uint8' }, - { name: 'payloadsController', type: 'address' }, - { name: 'payloadId', type: 'uint40' }, - ], - callData, - ); - }) || []; - - const payloadsData: Record = {}; - formattedCallDatas.forEach((data) => { - const itemData: CreatedEventCallDataPayload = { - chainId: Number(data[0]), - accessLevel: data[1], - payloadsController: data[2], - payloadId: data[3], - }; - payloadsData[`${itemData.payloadsController}_${itemData.payloadId}`] = - itemData; - }); - - return { - proposalId: Number(data.id), - ipfsHash: data.ipfsHash, - creator: data.creator, - payloads: payloadsData, - } as CreatedEventData; - }); - - set({ proposalCreatedEventsData: formattedData }); - }, -}); diff --git a/src/proposals/components/ProposalEstimatedStatus.tsx b/src/proposals/components/ProposalEstimatedStatus.tsx index 78a45119..a0d2a45f 100644 --- a/src/proposals/components/ProposalEstimatedStatus.tsx +++ b/src/proposals/components/ProposalEstimatedStatus.tsx @@ -1,6 +1,5 @@ import { ProposalEstimatedState } from '@bgd-labs/aave-governance-ui-helpers'; import { Box, SxProps } from '@mui/system'; -import { useEffect } from 'react'; import { useStore } from '../../store'; import { Timer } from '../../ui'; @@ -11,7 +10,7 @@ interface ProposalEstimatedStatusProps { timestamp: number; isSecondary?: boolean; css?: SxProps; - + isForModal?: boolean; isForHelpModal?: boolean; } @@ -21,20 +20,10 @@ export function ProposalEstimatedStatus({ timestamp, isSecondary, css, + isForModal, isForHelpModal, }: ProposalEstimatedStatusProps) { - const { activeWallet, getDetailedProposalsData, getL1Balances } = useStore(); - - useEffect(() => { - if ( - activeWallet?.isActive && - estimatedStatus && - estimatedStatus < ProposalEstimatedState.ProposalExecuted && - !isForHelpModal - ) { - getL1Balances([proposalId]); - } - }, [estimatedStatus]); + const { getDetailedProposalsData } = useStore(); const statusTextStringArray = estimatedStatus.split(' '); statusTextStringArray.splice(-1); @@ -54,7 +43,9 @@ export function ProposalEstimatedStatus({ mr: 4, color: estimatedStatus === ProposalEstimatedState.Defeated - ? '$mainAgainst' + ? isForModal + ? '$mainAgainst' + : '$text' : estimatedStatus === ProposalEstimatedState.Succeed ? '$mainFor' : estimatedStatus === ProposalEstimatedState.Expired @@ -66,7 +57,7 @@ export function ProposalEstimatedStatus({ {statusText} {' '} - {status} + {isForModal ? status : status === 'fail' ? 'end' : status} diff --git a/src/proposals/components/VoteBar.tsx b/src/proposals/components/VoteBar.tsx index e397ef8f..2a379ab0 100644 --- a/src/proposals/components/VoteBar.tsx +++ b/src/proposals/components/VoteBar.tsx @@ -15,6 +15,7 @@ interface VoteBarProps { withAnim?: boolean; startValueForCountUp?: number | string; startRequiredValueForCountUp?: number | string; + isRequiredAgainstVisible?: boolean; } export function VoteBar({ @@ -28,6 +29,7 @@ export function VoteBar({ withAnim, startValueForCountUp, startRequiredValueForCountUp, + isRequiredAgainstVisible, }: VoteBarProps) { const theme = useTheme(); @@ -116,7 +118,7 @@ export function VoteBar({ startNumber={startValueForCountUp} /> - {!isFinished && ( + {!isFinished && (type !== 'against' || isRequiredAgainstVisible) && ( ([]); const [isEditVotingTokensOpen, setEditVotingTokens] = useState(false); const [isVotingModesInfoOpen, setIsVotingModesInfoOpen] = useState(false); + const [isSwitching, setIsSwitching] = useState(false); const proposalData = useStore((store) => getProposalDataById(store, proposalId), @@ -106,10 +107,16 @@ export function VoteModal({ setEditVotingTokens(false); setIsVotingModesInfoOpen(false); setLocalVotingTokens([]); + setIsSwitching(true); + setTimeout(() => setIsSwitching(false), 500); }, [isOpen]); const support = supportObject[proposalId]; - const setSupport = (value: boolean) => setSupportObject(proposalId, value); + const setSupport = (value: boolean) => { + setSupportObject(proposalId, value); + setIsSwitching(true); + setTimeout(() => setIsSwitching(false), 500); + }; const { error, @@ -191,7 +198,11 @@ export function VoteModal({ : againstVotesWithVotingPower + requiredDiff; const requiredAgainstVotesAfterVote = - forVotesWithVotingPower === 0 ? 0 : forVotesWithVotingPower; + forVotesWithVotingPower === 0 || + forVotesWithVotingPower - requiredDiff <= 0 || + forVotesWithVotingPower < minQuorumVotes + ? 0 + : forVotesWithVotingPower - requiredDiff; const forPercentAfterVote = new BigNumber(forVotesWithVotingPower) .dividedBy(requiredForVotesAfterVote) @@ -301,6 +312,7 @@ export function VoteModal({ proposalId={proposalId} estimatedStatus={estimatedState} timestamp={timestampForEstimatedState} + isForModal /> )} @@ -326,8 +338,12 @@ export function VoteModal({ isValueBig={!support} isRequiredValueBig={support} withAnim={!loading} - startValueForCountUp={forVotes} - startRequiredValueForCountUp={requiredForVotes} + startValueForCountUp={ + isSwitching ? forVotes : forVotesWithVotingPower + } + startRequiredValueForCountUp={ + isSwitching ? requiredForVotes : requiredForVotesAfterVote + } /> diff --git a/src/proposals/components/proposal/PayloadActions.tsx b/src/proposals/components/proposal/PayloadActions.tsx index 233598cc..a01a531f 100644 --- a/src/proposals/components/proposal/PayloadActions.tsx +++ b/src/proposals/components/proposal/PayloadActions.tsx @@ -125,7 +125,12 @@ export function PayloadActions({ + css={{ + display: 'flex', + alignItems: 'center', + mt: 4, + outline: 'none !important', + }}> { e.stopPropagation(); diff --git a/src/proposals/components/proposal/ProposalPage.tsx b/src/proposals/components/proposal/ProposalPage.tsx index 0004d8f5..ed09381c 100644 --- a/src/proposals/components/proposal/ProposalPage.tsx +++ b/src/proposals/components/proposal/ProposalPage.tsx @@ -92,7 +92,7 @@ export function ProposalPage({ differential: proposal.config.differential, precisionDivider: proposal.precisionDivider, cooldownPeriod: proposal.timings.cooldownPeriod, - executionPayloadTime: proposal.timings.executionPayloadTime, + executionDelay: proposal.timings.executionDelay, }); const startBlock = @@ -177,7 +177,7 @@ export function ProposalPage({ differential: proposal.config.differential, precisionDivider: proposal.precisionDivider, cooldownPeriod: proposal.timings.cooldownPeriod, - executionPayloadTime: proposal.timings.executionPayloadTime, + executionDelay: proposal.timings.executionDelay, }); const { @@ -229,36 +229,36 @@ export function ProposalPage({ ? lastPayloadExecutedAt : lastPayloadQueuedAt > 0 && lastPayloadExecutedAt === 0 && - lastPayloadQueuedAt + proposal.timings.executionPayloadTime < now + lastPayloadQueuedAt + proposal.timings.executionDelay < now ? now + 60 : lastPayloadQueuedAt > 0 && lastPayloadExecutedAt === 0 && - lastPayloadQueuedAt + proposal.timings.executionPayloadTime > now - ? lastPayloadQueuedAt + proposal.timings.executionPayloadTime + lastPayloadQueuedAt + proposal.timings.executionDelay > now + ? lastPayloadQueuedAt + proposal.timings.executionDelay : proposal.data.queuingTime > 0 && lastPayloadQueuedAt === 0 - ? proposal.data.queuingTime + proposal.timings.executionPayloadTime + ? proposal.data.queuingTime + proposal.timings.executionDelay : proposal.data.votingMachineData.votingClosedAndSentTimestamp > 0 && lastPayloadExecutedAt <= 0 && proposal.data.votingMachineData.votingClosedAndSentTimestamp + - proposal.timings.executionPayloadTime < + proposal.timings.executionDelay < now ? now + 60 : proposal.data.votingMachineData.votingClosedAndSentTimestamp > 0 && lastPayloadExecutedAt <= 0 && proposal.data.votingMachineData.votingClosedAndSentTimestamp + - proposal.timings.executionPayloadTime > + proposal.timings.executionDelay > now ? proposal.data.votingMachineData.votingClosedAndSentTimestamp + - proposal.timings.executionPayloadTime + proposal.timings.executionDelay : proposal.data.votingMachineData.endTime > 0 && lastPayloadExecutedAt <= 0 ? proposal.data.votingMachineData.endTime + - proposal.timings.executionPayloadTime + proposal.timings.executionDelay : openToVoteTimestamp + proposal.data.votingDuration + - proposal.timings.executionPayloadTime; + proposal.timings.executionDelay; const Timeline = () => { if (!store.isRendered) { @@ -281,7 +281,6 @@ export function ProposalPage({ createdTimestamp={proposal.data.creationTime} openToVoteTimestamp={openToVoteTimestamp} votingClosedTimestamp={votingClosedTimestamp} - payloadsExecutedTimestamp={payloadsExecutedTimestamp} finishedTimestamp={payloadsExecutedTimestamp} failedTimestamp={ proposal.state === ProposalState.Defeated @@ -351,8 +350,8 @@ export function ProposalPage({ color: '$light', textAlign: 'center', }}> - Payload id {payload.id} on{' '} - {getChainName(payload.chainId)} broken + Cannot get data for Payload id{payload.id} on{' '} + {getChainName(payload.chainId)} ))} diff --git a/src/proposals/components/proposal/ProposalPayloads.tsx b/src/proposals/components/proposal/ProposalPayloads.tsx index 9c0e8322..0799ff08 100644 --- a/src/proposals/components/proposal/ProposalPayloads.tsx +++ b/src/proposals/components/proposal/ProposalPayloads.tsx @@ -10,7 +10,7 @@ import { import { Box, useTheme } from '@mui/system'; import dayjs from 'dayjs'; import React, { ReactNode, useEffect, useState } from 'react'; -import { Hex, toHex } from 'viem'; +import { Hex } from 'viem'; import ArrowToBottom from '/public/images/icons/arrowToBottom.svg'; import ArrowToTop from '/public/images/icons/arrowToTop.svg'; @@ -235,9 +235,8 @@ function PayloadItem({ diff --git a/src/proposals/components/proposal/ProposalTimeline.tsx b/src/proposals/components/proposal/ProposalTimeline.tsx index 3891f38e..f6ad62e2 100644 --- a/src/proposals/components/proposal/ProposalTimeline.tsx +++ b/src/proposals/components/proposal/ProposalTimeline.tsx @@ -36,7 +36,6 @@ enum TimelineItemTypeType { created = 'created', openToVote = 'openToVote', votingClosed = 'votingClosed', - payloadsExecuted = 'payloadsExecuted', finished = 'Finished', } @@ -356,7 +355,6 @@ interface ProposalTimelineProps { createdTimestamp: number; openToVoteTimestamp: number; votingClosedTimestamp: number; - payloadsExecutedTimestamp: number; expiredTimestamp: number; finishedTimestamp: number; isFinished: boolean; @@ -372,7 +370,6 @@ export function ProposalTimeline({ createdTimestamp, openToVoteTimestamp, votingClosedTimestamp, - payloadsExecutedTimestamp, finishedTimestamp, isFinished, state, @@ -474,7 +471,7 @@ export function ProposalTimeline({ }, { title: texts.proposals.timelinePointVotingClosed, - position: getPosition(votingClosedTimestamp, payloadsExecutedTimestamp), + position: getPosition(votingClosedTimestamp, finishedTimestamp), finished: now >= votingClosedTimestamp || !!canceledTimestamp || isExpired, timestampForEstimatedState: @@ -493,37 +490,17 @@ export function ProposalTimeline({ rocketVisible: now >= votingClosedTimestamp && !isFinished, }, { - title: texts.proposals.timelinePointPayloadsExecuted, - position: - isFinished || !!canceledTimestamp - ? withoutDetails - ? 100 - : 90 - : getPosition(payloadsExecutedTimestamp, finishedTimestamp), + // title: texts.proposals.timelinePointPayloadsExecuted, + title: texts.proposals.timelinePointFinished, finished: isFinished || !!canceledTimestamp, timestampForEstimatedState: now >= openToVoteTimestamp && now >= votingClosedTimestamp && !(isFinished || !!canceledTimestamp) - ? payloadsExecutedTimestamp - now > 61 - ? payloadsExecutedTimestamp + ? finishedTimestamp - now > 61 + ? finishedTimestamp : undefined : undefined, - timestamp: canceledTimestamp - ? canceledTimestamp - : payloadsExecutedTimestamp, - type: TimelineItemTypeType.payloadsExecuted, - visibility: isExpired - ? false - : canceledTimestamp - ? false - : failedTimestamp - ? !failedTimestamp - : true, - }, - { - title: texts.proposals.timelinePointFinished, - finished: isFinished || !!canceledTimestamp, timestamp: isExpired ? expiredTimestamp : canceledTimestamp @@ -558,7 +535,7 @@ export function ProposalTimeline({ ); } else if ( - canceledTimestamp <= payloadsExecutedTimestamp && + canceledTimestamp <= finishedTimestamp && canceledTimestamp > votingClosedTimestamp ) { return ( - {children} - - - ); - } else if (canceledTimestamp > payloadsExecutedTimestamp) { - return ( - - @@ -666,6 +625,10 @@ export function ProposalTimeline({ ? -12 : 0, }, + '.Timer__value': { + background: 'none', + border: 'none', + }, }, }} key={timeline.type}> @@ -816,7 +779,7 @@ export function ProposalTimeline({ transition: 'all 0.3s ease', left: (timeline.position || 0) < 10 - ? 1 + ? 0 : (timeline.position || 0) < 20 && (timeline.position || 0) > 10 ? 15 @@ -836,7 +799,7 @@ export function ProposalTimeline({ height: 29, left: (timeline.position || 0) < 10 - ? 8 + ? 2 : (timeline.position || 0) < 20 && (timeline.position || 0) > 10 ? 15 diff --git a/src/proposals/components/proposal/VotersList.tsx b/src/proposals/components/proposal/VotersList.tsx index 3e7a84d3..97b80114 100644 --- a/src/proposals/components/proposal/VotersList.tsx +++ b/src/proposals/components/proposal/VotersList.tsx @@ -1,7 +1,9 @@ import { VotersData } from '@bgd-labs/aave-governance-ui-helpers'; import { Box, useTheme } from '@mui/system'; import React, { ReactNode } from 'react'; +import { Hex, zeroAddress } from 'viem'; +import { useStore } from '../../../store'; import { Link } from '../../../ui'; import { FormattedNumber } from '../../../ui/components/FormattedNumber'; import { texts } from '../../../ui/utils/texts'; @@ -100,7 +102,13 @@ export function VotersListItemsWrapper({ ); } -export function VotersListItem({ vote }: { vote: VotersData }) { +export function VotersListItem({ + vote, + activeAddress, +}: { + vote: VotersData; + activeAddress: Hex; +}) { return ( - + {formatVoterAddress(vote)} @@ -189,6 +205,8 @@ export function VotersList({ isVotingFinished, isStarted, }: VotersListProps) { + const { activeWallet, representative } = useStore(); + if (!isStarted) return null; return ( @@ -215,7 +233,15 @@ export function VotersList({ .sort((a, b) => b.votingPower - a.votingPower) .slice(0, votersVisibleCount) .map((vote) => ( - + ))} diff --git a/src/proposals/components/proposal/VotersListLoading.tsx b/src/proposals/components/proposal/VotersListLoading.tsx index 8848aa9f..fdc3be01 100644 --- a/src/proposals/components/proposal/VotersListLoading.tsx +++ b/src/proposals/components/proposal/VotersListLoading.tsx @@ -1,6 +1,8 @@ import { Box } from '@mui/system'; import React from 'react'; +import { zeroAddress } from 'viem'; +import { useStore } from '../../../store'; import { CustomSkeleton } from '../../../ui/components/CustomSkeleton'; import { votersCountForViewAll, @@ -24,6 +26,8 @@ export function VotersListLoading({ isStarted, setIsVotersModalOpen, }: VotersLoadingProps) { + const { activeWallet, representative } = useStore(); + if (!isStarted) return null; if (!voters?.length) return null; @@ -36,7 +40,13 @@ export function VotersListLoading({ .sort((a, b) => b.votingPower - a.votingPower) .slice(0, votersVisibleCount) .map((vote) => ( - + ))} vote.support); const votersAgainst = voters.filter((vote) => !vote.support); const ListItemAddress = ({ vote }: { vote: VotersData }) => { + const sm = useMediaQuery(media.sm); + return ( - - {formatVoterAddress(vote)} + href={`${chainInfoHelper.getChainParameters(appConfig.govCoreChainId) + .blockExplorers?.default.url}/address/${vote.address}`}> + + {formatVoterAddress(vote, sm)} ); diff --git a/src/proposals/components/proposalHistory/ProposalHistoryItem.tsx b/src/proposals/components/proposalHistory/ProposalHistoryItem.tsx index 68f74c39..02b6aafe 100644 --- a/src/proposals/components/proposalHistory/ProposalHistoryItem.tsx +++ b/src/proposals/components/proposalHistory/ProposalHistoryItem.tsx @@ -36,7 +36,7 @@ export function ProposalHistoryItem({ differential: proposalData.proposal.config.differential, precisionDivider: proposalData.proposal.precisionDivider, cooldownPeriod: proposalData.proposal.timings.cooldownPeriod, - executionPayloadTime: proposalData.proposal.timings.executionPayloadTime, + executionDelay: proposalData.proposal.timings.executionDelay, }, ); diff --git a/src/proposals/store/proposalsHistorySlice.ts b/src/proposals/store/proposalsHistorySlice.ts index 7710644c..c632dee9 100644 --- a/src/proposals/store/proposalsHistorySlice.ts +++ b/src/proposals/store/proposalsHistorySlice.ts @@ -162,7 +162,7 @@ export const createProposalsHistorySlice: StoreSlice< differential: proposal.config.differential, precisionDivider: proposal.precisionDivider, cooldownPeriod: proposal.timings.cooldownPeriod, - executionPayloadTime: proposal.timings.executionPayloadTime, + executionDelay: proposal.timings.executionDelay, }); // PAYLOADS_CREATED diff --git a/src/proposals/store/proposalsSelectors.ts b/src/proposals/store/proposalsSelectors.ts index d96badad..b0b5fcff 100644 --- a/src/proposals/store/proposalsSelectors.ts +++ b/src/proposals/store/proposalsSelectors.ts @@ -111,7 +111,7 @@ export const getProposalDataById = (store: RootState, id: number) => { ); // minimal delay from all payloads in proposal for payloads execution estimated status timestamp - const executionPayloadTime = Math.min.apply( + const executionDelay = Math.min.apply( null, proposalData.payloads.map((payload) => payload?.delay || 0), ); @@ -124,7 +124,7 @@ export const getProposalDataById = (store: RootState, id: number) => { timings: { cooldownPeriod: store.contractsConstants.cooldownPeriod, expirationTime: store.contractsConstants.expirationTime, - executionPayloadTime, + executionDelay, }, }; @@ -134,8 +134,7 @@ export const getProposalDataById = (store: RootState, id: number) => { differential: proposalDataWithoutState.config.differential, precisionDivider: proposalDataWithoutState.precisionDivider, cooldownPeriod: proposalDataWithoutState.timings.cooldownPeriod, - executionPayloadTime: - proposalDataWithoutState.timings.executionPayloadTime, + executionDelay: proposalDataWithoutState.timings.executionDelay, }); return { diff --git a/src/proposals/store/proposalsSlice.ts b/src/proposals/store/proposalsSlice.ts index d6428306..6e20ebd6 100644 --- a/src/proposals/store/proposalsSlice.ts +++ b/src/proposals/store/proposalsSlice.ts @@ -24,7 +24,6 @@ import { Hex } from 'viem'; import { IDelegationSlice } from '../../delegate/store/delegationSlice'; import { IPayloadsExplorerSlice } from '../../payloadsExplorer/store/payloadsExplorerSlice'; import { IProposalCreateOverviewSlice } from '../../proposalCreateOverview/store/proposalCreateOverviewSlice'; -import { IProposalCreateOverviewV2Slice } from '../../proposalCreateOverviewV2/store/proposalCreateOverviewV2Slice'; import { IRepresentationsSlice } from '../../representations/store/representationsSlice'; import { IRpcSwitcherSlice } from '../../rpcSwitcher/store/rpcSwitcherSlice'; import { @@ -219,8 +218,7 @@ export const createProposalsSlice: StoreSlice< IEnsSlice & IRpcSwitcherSlice & IProposalCreateOverviewSlice & - IPayloadsExplorerSlice & - IProposalCreateOverviewV2Slice + IPayloadsExplorerSlice > = (set, get) => ({ isInitialLoading: true, @@ -321,7 +319,7 @@ export const createProposalsSlice: StoreSlice< const { activeIds } = selectProposalIds(get(), paginatedIds); await get().getDetailedProposalsData(activeIds); if (!!activeIds.length) { - await Promise.all([ + await Promise.allSettled([ await get().getIpfsData(activeIds), await get().getL1Balances(activeIds), ]); @@ -332,7 +330,7 @@ export const createProposalsSlice: StoreSlice< const paginatedIds = selectPaginatedIds(get()); const { activeIds } = selectProposalIds(get(), paginatedIds); await get().getDetailedProposalsData(activeIds); - await Promise.all([ + await Promise.allSettled([ await get().getIpfsData(activeIds), await get().getL1Balances(activeIds), ]); @@ -376,7 +374,7 @@ export const createProposalsSlice: StoreSlice< }, getProposalDataWithIpfsById: async (id) => { await get().getDetailedProposalsData([id]); - await Promise.all([ + await Promise.allSettled([ await get().getIpfsData([id]), await get().getL1Balances([id]), ]); @@ -556,7 +554,11 @@ export const createProposalsSlice: StoreSlice< }), ); - if (!get().representativeLoading && !!get().configs[0].votingDuration) { + if ( + !get().representativeLoading && + !!get().configs[0] && + !!get().configs[0].votingDuration + ) { const isProposalNotInCache = !ids.filter( (proposalId) => proposalId === @@ -807,7 +809,7 @@ export const createProposalsSlice: StoreSlice< proposalData.accessLevel, ); - const executionPayloadTime = Math.max.apply( + const executionDelay = Math.max.apply( null, proposalData.payloads.map((payload) => payload.delay), ); @@ -818,7 +820,7 @@ export const createProposalsSlice: StoreSlice< differential: proposalConfig.differential, precisionDivider: get().contractsConstants.precisionDivider, cooldownPeriod: get().contractsConstants.cooldownPeriod, - executionPayloadTime, + executionDelay, }); if ( diff --git a/src/proposals/utils/formatVoterAddress.ts b/src/proposals/utils/formatVoterAddress.ts index b6461cf1..43104f76 100644 --- a/src/proposals/utils/formatVoterAddress.ts +++ b/src/proposals/utils/formatVoterAddress.ts @@ -2,10 +2,10 @@ import { VotersData } from '@bgd-labs/aave-governance-ui-helpers'; import { textCenterEllipsis } from '../../ui/utils/text-center-ellipsis'; -export function formatVoterAddress(vote: VotersData) { +export function formatVoterAddress(vote: VotersData, isBig?: boolean) { return vote.ensName - ? vote.ensName.length > 10 - ? textCenterEllipsis(vote.ensName, 5, 5) + ? vote.ensName.length > (isBig ? 18 : 10) + ? textCenterEllipsis(vote.ensName, isBig ? 12 : 5, isBig ? 6 : 5) : vote.ensName - : textCenterEllipsis(vote.address, 5, 4); + : textCenterEllipsis(vote.address, isBig ? 8 : 5, isBig ? 8 : 5); } diff --git a/src/rpcSwitcher/store/rpcSwitcherSlice.ts b/src/rpcSwitcher/store/rpcSwitcherSlice.ts index f71c58ac..97598179 100644 --- a/src/rpcSwitcher/store/rpcSwitcherSlice.ts +++ b/src/rpcSwitcher/store/rpcSwitcherSlice.ts @@ -6,17 +6,19 @@ import { import { StoreSlice } from '@bgd-labs/frontend-web3-utils'; import { PublicClient } from '@wagmi/core'; import { Draft, produce } from 'immer'; -import { Chain, createPublicClient, fallback, http } from 'viem'; +import { Chain, zeroAddress, zeroHash } from 'viem'; +import { mainnet } from 'viem/chains'; import { TransactionsSlice } from '../../transactions/store/transactionsSlice'; import { appConfig } from '../../utils/appConfig'; -import { fallBackConfig, initialRpcUrls, setChain } from '../../utils/chains'; +import { createViemClient } from '../../utils/chains'; import { chainInfoHelper } from '../../utils/configs'; import { getLocalStorageRpcUrls, setLocalStorageRpcUrls, } from '../../utils/localStorage'; import { IWeb3Slice } from '../../web3/store/web3Slice'; +import { getProof } from '../../web3/utils/helperToGetProofs'; import { selectAppClients } from './rpcSwitcherSelectors'; export type AppClient = { @@ -97,24 +99,10 @@ export const createRpcSwitcherSlice: StoreSlice< if (chain) { draft.appClients[chainIdNumber] = { rpcUrl: parsedRpcUrlsFromStorage[chainIdNumber].rpcUrl, - instance: createPublicClient({ - batch: { - multicall: true, - }, - chain: setChain( - chain, - parsedRpcUrlsFromStorage[chainIdNumber].rpcUrl, - ) as Draft, - transport: fallback( - [ - http(parsedRpcUrlsFromStorage[chainIdNumber].rpcUrl), - ...initialRpcUrls[chainIdNumber].map((url) => - http(url), - ), - ], - fallBackConfig, - ), - }), + instance: createViemClient( + chain, + parsedRpcUrlsFromStorage[chainIdNumber].rpcUrl, + ), }; } }); @@ -158,20 +146,10 @@ export const createRpcSwitcherSlice: StoreSlice< set((state) => produce(state, (draft) => { draft.appClients[chainId].rpcUrl = rpcUrl; - // @ts-ignore - draft.appClients[chainId].instance = createPublicClient({ - batch: { - multicall: true, - }, - chain: chainInfoHelper.getChainParameters(chainId), - transport: fallback( - [ - http(rpcUrl), - ...initialRpcUrls[chainId].map((url) => http(url)), - ], - fallBackConfig, - ), - }); + draft.appClients[chainId].instance = createViemClient( + chainInfoHelper.getChainParameters(chainId), + rpcUrl, + ); }), ); }); @@ -266,16 +244,11 @@ export const createRpcSwitcherSlice: StoreSlice< ) { return; } - const client = createPublicClient({ - batch: { - multicall: true, - }, - chain: chainInfoHelper.getChainParameters(chainId), - transport: fallback( - [http(rpcUrl), ...initialRpcUrls[chainId].map((url) => http(url))], - fallBackConfig, - ), - }); + const client = createViemClient( + chainInfoHelper.getChainParameters(chainId), + rpcUrl, + true, + ); const contractAddresses = appConfig.payloadsControllerConfig[chainId].contractAddresses; @@ -300,8 +273,23 @@ export const createRpcSwitcherSlice: StoreSlice< endBlock: Number(currentBlock.number), chainId, }); - get().setRpcFormError({ isError: false, rpcUrl, chainId }); + + // check get proofs if initial request success and chainId it's mainnet + if (mainnet.id === chainId) { + try { + await getProof( + client, + zeroAddress, + [zeroHash], + Number(currentBlock.number), + ); + + get().setRpcFormError({ isError: false, rpcUrl, chainId }); + } catch { + get().setRpcFormError({ isError: true, rpcUrl, chainId }); + } + } } catch { get().setRpcFormError({ isError: true, rpcUrl, chainId }); } diff --git a/src/store/index.ts b/src/store/index.ts index 48a9156f..05b8bf8c 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -16,10 +16,6 @@ import { createProposalCreateOverviewSlice, IProposalCreateOverviewSlice, } from '../proposalCreateOverview/store/proposalCreateOverviewSlice'; -import { - createProposalCreateOverviewV2Slice, - IProposalCreateOverviewV2Slice, -} from '../proposalCreateOverviewV2/store/proposalCreateOverviewV2Slice'; import { createProposalsHistorySlice, IProposalsHistorySlice, @@ -60,8 +56,7 @@ export type RootState = IProposalsSlice & IEnsSlice & IRpcSwitcherSlice & IProposalCreateOverviewSlice & - IPayloadsExplorerSlice & - IProposalCreateOverviewV2Slice; + IPayloadsExplorerSlice; const createRootSlice = ( set: StoreApi['setState'], @@ -79,7 +74,6 @@ const createRootSlice = ( ...createRpcSwitcherSlice(set, get), ...createProposalCreateOverviewSlice(set, get), ...createPayloadsExplorerSlice(set, get), - ...createProposalCreateOverviewV2Slice(set, get), }); export const useStore = create(devtools(createRootSlice, { serialize: true })); diff --git a/src/transactions/components/TransactionsModalContent.tsx b/src/transactions/components/TransactionsModalContent.tsx index 7d6ebc01..5fd8a33e 100644 --- a/src/transactions/components/TransactionsModalContent.tsx +++ b/src/transactions/components/TransactionsModalContent.tsx @@ -33,6 +33,8 @@ export function TransactionsModalContent({ ({ + overflowY: forTest ? 'scroll' : 'unset', + pr: forTest ? 20 : 0, height: forTest ? 191 : '100%', [theme.breakpoints.up('sm')]: { overflowY: 'scroll', diff --git a/src/transactions/store/transactionsSlice.ts b/src/transactions/store/transactionsSlice.ts index 6718d78f..aacf8a81 100644 --- a/src/transactions/store/transactionsSlice.ts +++ b/src/transactions/store/transactionsSlice.ts @@ -14,7 +14,6 @@ import { IDelegationSlice } from '../../delegate/store/delegationSlice'; import { DelegateData, DelegateItem } from '../../delegate/types'; import { IPayloadsExplorerSlice } from '../../payloadsExplorer/store/payloadsExplorerSlice'; import { IProposalCreateOverviewSlice } from '../../proposalCreateOverview/store/proposalCreateOverviewSlice'; -import { IProposalCreateOverviewV2Slice } from '../../proposalCreateOverviewV2/store/proposalCreateOverviewV2Slice'; import { IProposalsHistorySlice } from '../../proposals/store/proposalsHistorySlice'; import { IProposalsListCacheSlice } from '../../proposals/store/proposalsListCacheSlice'; import { getProposalDataById } from '../../proposals/store/proposalsSelectors'; @@ -194,8 +193,7 @@ export const createTransactionsSlice: StoreSlice< IEnsSlice & IRpcSwitcherSlice & IProposalCreateOverviewSlice & - IPayloadsExplorerSlice & - IProposalCreateOverviewV2Slice + IPayloadsExplorerSlice > = (set, get) => ({ ...createBaseTransactionsSlice({ txStatusChangedCallback: async (data) => { diff --git a/src/ui/components/Timer.tsx b/src/ui/components/Timer.tsx index db0c0b98..34d4fd2d 100644 --- a/src/ui/components/Timer.tsx +++ b/src/ui/components/Timer.tsx @@ -54,19 +54,19 @@ export function Timer({ return ( <> {d !== 0 && ( - + {d} {texts.other.day}{' '} )} {h !== 0 && ( - + {h} {texts.other.hours}{' '} )} {m !== 0 && ( - + {m} {texts.other.minutes}{' '} @@ -74,6 +74,7 @@ export function Timer({ {h < 1 && d < 1 && ( ({ display: 'inline-flex', alignItems: 'flex-start', diff --git a/src/ui/helpModals/HelpStatusesModal.tsx b/src/ui/helpModals/HelpStatusesModal.tsx index 3c7156cb..61fe8efd 100644 --- a/src/ui/helpModals/HelpStatusesModal.tsx +++ b/src/ui/helpModals/HelpStatusesModal.tsx @@ -304,7 +304,6 @@ export function HelpStatusesModal({ infoType }: HelpStatusesModalProps) { openToVoteTimestamp={statusInfo.openToVoteTimestamp} votingStartTime={statusInfo.openToVoteTimestamp} votingClosedTimestamp={statusInfo.votingClosedTimestamp} - payloadsExecutedTimestamp={statusInfo.finishedTimestamp} finishedTimestamp={statusInfo.finishedTimestamp} failedTimestamp={ statusInfo.state === ProposalStateWithName.Defeated diff --git a/src/ui/helpModals/HelpVoteTx.tsx b/src/ui/helpModals/HelpVoteTx.tsx index 5727d4d3..4ad2fb01 100644 --- a/src/ui/helpModals/HelpVoteTx.tsx +++ b/src/ui/helpModals/HelpVoteTx.tsx @@ -56,6 +56,7 @@ export function HelpVoteTx({ const [isGaslessVote, setIsGaslessVote] = useState(true); const [isEditVotingTokensOpen, setEditVotingTokens] = useState(false); const [isVotingModesInfoOpen, setIsVotingModesInfoOpen] = useState(false); + const [isSwitching, setIsSwitching] = useState(false); const [error, setError] = useState(''); const [isTxStart, setIsTxStart] = useState(false); @@ -71,6 +72,8 @@ export function HelpVoteTx({ setIsTxStart(false); setEditVotingTokens(false); setIsTxStart(false); + setIsSwitching(true); + setTimeout(() => setIsSwitching(false), 500); }, []); const { @@ -124,9 +127,12 @@ export function HelpVoteTx({ againstVotesWithVotingPower + requiredDiff < minQuorumVotes ? minQuorumVotes : againstVotesWithVotingPower + requiredDiff; - const requiredAgainstVotesAfterVote = - forVotesWithVotingPower === 0 ? 0 : forVotesWithVotingPower; + forVotesWithVotingPower === 0 || + forVotesWithVotingPower - requiredDiff <= 0 || + forVotesWithVotingPower < minQuorumVotes + ? 0 + : forVotesWithVotingPower - requiredDiff; const forPercentAfterVote = new BigNumber(forVotesWithVotingPower) .dividedBy(requiredForVotesAfterVote) @@ -173,7 +179,14 @@ export function HelpVoteTx({ position: 'relative', zIndex: isTxStart ? -1 : 1, }}> - + { + setSupport(value); + setIsSwitching(true); + setTimeout(() => setIsSwitching(false), 500); + }} + /> )} @@ -233,8 +247,12 @@ export function HelpVoteTx({ isValueBig={!support} isRequiredValueBig={support} withAnim={!txPending} - startValueForCountUp={forVotes} - startRequiredValueForCountUp={requiredForVotes} + startValueForCountUp={ + isSwitching ? forVotes : forVotesWithVotingPower + } + startRequiredValueForCountUp={ + isSwitching ? requiredForVotes : requiredForVotesAfterVote + } /> diff --git a/src/ui/helpModals/getProposalData.ts b/src/ui/helpModals/getProposalData.ts index a557f92b..ab3e4542 100644 --- a/src/ui/helpModals/getProposalData.ts +++ b/src/ui/helpModals/getProposalData.ts @@ -125,7 +125,7 @@ export function getProposalData() { timings: { cooldownPeriod: 600, expirationTime: 2592000, - executionPayloadTime: 600, + executionDelay: 600, }, state: 1, }, diff --git a/src/ui/utils/routes.ts b/src/ui/utils/routes.ts index af1212d2..bf3553ab 100644 --- a/src/ui/utils/routes.ts +++ b/src/ui/utils/routes.ts @@ -6,6 +6,5 @@ export const ROUTES = { `/proposal?proposalId=${proposalId}&ipfsHash=${ipfsHash}`, rpcSwitcher: '/rpc-switcher', proposalCreateOverview: '/proposal-create-overview', - proposalCreateOverviewV2: '/proposal-create-overview-v2', payloadsExplorer: '/payloads-explorer', }; diff --git a/src/utils/chains.ts b/src/utils/chains.ts index c96b9eca..39e0bbf5 100644 --- a/src/utils/chains.ts +++ b/src/utils/chains.ts @@ -1,4 +1,5 @@ -import { Chain } from 'viem'; +import { Draft } from 'immer'; +import { Chain, createPublicClient, fallback, http } from 'viem'; import { arbitrum, avalanche, @@ -16,18 +17,12 @@ import { sepolia, } from 'viem/chains'; -export const fallBackConfig = { - rank: false, - retryDelay: 100, - retryCount: 5, -}; - // chains RPC urls export const initialRpcUrls: Record = { [mainnet.id]: [ 'https://blissful-purple-sky.quiknode.pro', - 'https://rpc.mevblocker.io', 'https://rpc.ankr.com/eth', + 'https://eth.nodeconnect.org', ], [polygon.id]: [ 'https://polygon.blockpi.network/v1/rpc/public', @@ -86,6 +81,30 @@ export const initialRpcUrls: Record = { [bscTestnet.id]: ['https://data-seed-prebsc-1-s1.bnbchain.org:8545'], }; +export const fallBackConfig = { + rank: false, + retryDelay: 100, + retryCount: 5, +}; + +export const createViemClient = ( + chain: Chain, + rpcUrl: string, + withoutFallback?: boolean, +) => + createPublicClient({ + batch: { + multicall: true, + }, + chain: setChain(chain, rpcUrl) as Draft, + transport: withoutFallback + ? http(rpcUrl) + : fallback( + [http(rpcUrl), ...initialRpcUrls[chain.id].map((url) => http(url))], + fallBackConfig, + ), + }); + export function setChain(chain: Chain, url?: string) { return { ...chain, @@ -93,7 +112,7 @@ export function setChain(chain: Chain, url?: string) { ...chain.rpcUrls, default: { ...chain.rpcUrls.default, - http: [url || initialRpcUrls[chain.id][0]], + http: [url || initialRpcUrls[chain.id][0], ...initialRpcUrls[chain.id]], }, }, }; diff --git a/src/utils/initialClients.ts b/src/utils/initialClients.ts index a3c4490f..373db618 100644 --- a/src/utils/initialClients.ts +++ b/src/utils/initialClients.ts @@ -1,19 +1,12 @@ import { PublicClient } from '@wagmi/core'; -import { createPublicClient, fallback, http } from 'viem'; import { appUsedNetworks } from './appConfig'; -import { CHAINS, fallBackConfig, initialRpcUrls } from './chains'; +import { CHAINS, createViemClient } from './chains'; export const initialClients: Record = {}; appUsedNetworks.forEach((chain) => { - initialClients[chain] = createPublicClient({ - batch: { - multicall: true, - }, - chain: CHAINS[chain], - transport: fallback( - initialRpcUrls[chain].map((url) => http(url)), - fallBackConfig, - ), - }); + initialClients[chain] = createViemClient( + CHAINS[chain], + CHAINS[chain].rpcUrls.public.http[0], + ); }); diff --git a/src/web3/components/wallet/AccountInfoModal.tsx b/src/web3/components/wallet/AccountInfoModal.tsx index a9104f34..6d52744e 100644 --- a/src/web3/components/wallet/AccountInfoModal.tsx +++ b/src/web3/components/wallet/AccountInfoModal.tsx @@ -1,5 +1,5 @@ import { selectAllTransactionsByWallet } from '@bgd-labs/frontend-web3-utils'; -import React from 'react'; +import React, { useEffect } from 'react'; import { RepresentedAddress } from '../../../representations/store/representationsSlice'; import { useStore } from '../../../store'; @@ -28,7 +28,13 @@ export function AccountInfoModal({ representedAddresses, }: AccountInfoModalProps) { const store = useStore(); - const { activeWallet, disconnectActiveWallet, setModalOpen } = store; + const { + activeWallet, + disconnectActiveWallet, + setModalOpen, + representative, + getCurrentPowers, + } = store; const allTransactions = activeWallet ? selectAllTransactionsByWallet( @@ -43,6 +49,16 @@ export function AccountInfoModal({ setModalOpen(false); }; + useEffect(() => { + if (isOpen) { + if (!!representative.address) { + getCurrentPowers(representative.address); + } else if (activeWallet?.address) { + getCurrentPowers(activeWallet?.address); + } + } + }, [activeWallet?.address, representative.address, isOpen]); + return ( { @@ -33,14 +31,6 @@ function Child() { getRepresentingAddress(); }, [activeWallet?.address, representationData]); - useEffect(() => { - if (!!representative.address) { - getCurrentPowers(representative.address); - } else if (activeWallet?.address) { - getCurrentPowers(activeWallet?.address); - } - }, [activeWallet?.address, representative.address]); - return null; } diff --git a/src/web3/services/delegationService.ts b/src/web3/services/delegationService.ts index 5cdba851..ef907e56 100644 --- a/src/web3/services/delegationService.ts +++ b/src/web3/services/delegationService.ts @@ -6,8 +6,10 @@ import { metaDelegateHelperContract, normalizeBN, } from '@bgd-labs/aave-governance-ui-helpers'; +import { IAaveTokenV3_ABI } from '@bgd-labs/aave-governance-ui-helpers/dist/abis/IAaveTokenV3'; import { ClientsRecord } from '@bgd-labs/frontend-web3-utils'; import { WalletClient } from '@wagmi/core'; +import dayjs from 'dayjs'; import { encodeFunctionData, Hex, hexToSignature, zeroAddress } from 'viem'; import { appConfig } from '../../utils/appConfig'; @@ -56,10 +58,6 @@ export class DelegationService { } async getUserPowers(userAddress: Hex, underlyingAssets: Hex[]) { - const blockNumber = await this.clients[appConfig.govCoreChainId].getBlock({ - blockTag: 'safe', - }); - const contracts = underlyingAssets.map((asset) => { return { contract: aaveTokenV3Contract({ @@ -72,26 +70,25 @@ export class DelegationService { return await Promise.all( contracts.map(async (contract) => { - const userBalance = await contract.contract.read.balanceOf([ - userAddress, - ]); - const delegatee = await contract.contract.read.getDelegates([ - userAddress, + const data = await Promise.all([ + await contract.contract.read.balanceOf([userAddress]), + await contract.contract.read.getPowersCurrent([userAddress]), + await contract.contract.read.getDelegates([userAddress]), ]); const isPropositionPowerDelegated = - delegatee[GovernancePowerType.PROPOSITION] === userAddress || - delegatee[GovernancePowerType.PROPOSITION] === zeroAddress + data[2][GovernancePowerType.PROPOSITION] === userAddress || + data[2][GovernancePowerType.PROPOSITION] === zeroAddress ? false - : !!delegatee[GovernancePowerType.PROPOSITION]; + : !!data[2][GovernancePowerType.PROPOSITION]; const isVotingPowerDelegated = - delegatee[GovernancePowerType.VOTING] === userAddress || - delegatee[GovernancePowerType.VOTING] === zeroAddress + data[2][GovernancePowerType.VOTING] === userAddress || + data[2][GovernancePowerType.VOTING] === zeroAddress ? false - : !!delegatee[GovernancePowerType.VOTING]; + : !!data[2][GovernancePowerType.VOTING]; const getPower = (totalPower: bigint, type: GovernancePowerType) => { - let formattedUserBalance = userBalance; + let formattedUserBalance = data[0]; if ( type === GovernancePowerType.PROPOSITION && isPropositionPowerDelegated @@ -118,24 +115,18 @@ export class DelegationService { String(totalPower - formattedUserBalance), 18, ).toString(), - isWithDelegatedPower: formattedUserBalance !== totalPower, }; }; - const totalPowers = await contract.contract.read.getPowersCurrent( - [userAddress], - { blockNumber: blockNumber.number }, - ); - const proposition = getPower( - totalPowers[1], + data[1][1], GovernancePowerType.PROPOSITION, ); - const voting = getPower(totalPowers[0], GovernancePowerType.VOTING); + const voting = getPower(data[1][0], GovernancePowerType.VOTING); return { - timestamp: blockNumber.timestamp, + timestamp: dayjs().unix(), tokenName: getTokenName(contract.underlyingAsset), underlyingAsset: contract.underlyingAsset, proposition, @@ -194,43 +185,61 @@ export class DelegationService { userAddress: Hex, underlyingAssets: Hex[], ) { - const blockNumber = ( - await this.clients[appConfig.govCoreChainId].getBlock({ blockHash }) - ).number; + const client = this.clients[appConfig.govCoreChainId]; + const blockNumber = await client.getBlock({ + blockHash, + }); - const contracts = underlyingAssets.map((asset) => { - return { - contract: aaveTokenV3Contract({ - contractAddress: asset, - client: this.clients[appConfig.govCoreChainId], + const userBalances = await client.multicall({ + contracts: [ + ...underlyingAssets.map((asset) => { + const wagmiContract = { + address: asset, + abi: IAaveTokenV3_ABI, + } as const; + + return { + ...wagmiContract, + functionName: 'balanceOf', + args: [userAddress], + }; }), - underlyingAsset: asset, - }; + ], + blockNumber: blockNumber.number, }); - return Promise.all( - contracts.map(async (contract) => { - const userBalance = await contract.contract.read.balanceOf([ - userAddress, - ]); - const totalPower = await contract.contract.read.getPowerCurrent( - [userAddress, GovernancePowerType.VOTING], - { - blockNumber: blockNumber, - }, - ); + const votingPowers = await client.multicall({ + contracts: [ + ...underlyingAssets.map((asset) => { + const wagmiContract = { + address: asset, + abi: IAaveTokenV3_ABI, + } as const; - return { - blockHash, - tokenName: getTokenName(contract.underlyingAsset), - underlyingAsset: contract.underlyingAsset, - basicValue: totalPower.toString(), - value: normalizeBN(totalPower.toString(), 18).toString(), - userBalance: userBalance.toString(), - isWithDelegatedPower: userBalance < totalPower, - }; - }), - ); + return { + ...wagmiContract, + functionName: 'getPowerCurrent', + args: [userAddress, GovernancePowerType.VOTING], + }; + }), + ], + blockNumber: blockNumber.number, + }); + + return underlyingAssets.map((asset, index) => { + const userBalance = userBalances[index].result; + const votingPower = votingPowers[index].result; + + return { + blockHash, + tokenName: getTokenName(asset), + underlyingAsset: asset, + basicValue: String(votingPower), + value: normalizeBN(String(votingPower), 18).toString(), + userBalance: normalizeBN(String(userBalance), 18).toString(), + isWithDelegatedPower: userBalance !== votingPower, + }; + }); } async delegateMetaSig( diff --git a/src/web3/services/govDataService.ts b/src/web3/services/govDataService.ts index 82fd5293..6e75d12a 100644 --- a/src/web3/services/govDataService.ts +++ b/src/web3/services/govDataService.ts @@ -273,7 +273,7 @@ export class GovDataService { chainId: number, setRpcError?: ({ isError, rpcUrl, chainId }: SetRpcErrorParams) => void, ): Promise { - const rpcUrl = this.clients[chainId].chain.rpcUrls.default.http[0]; + const rpcUrl = this.clients[chainId]?.chain.rpcUrls.default.http[0]; try { const payloadsControllerContract = initPayloadControllerContract({ @@ -283,7 +283,8 @@ export class GovDataService { const payloadsCount = await payloadsControllerContract.read.getPayloadsCount(); - if (!!setRpcError) { + + if (!!setRpcError && rpcUrl) { setRpcError({ isError: false, rpcUrl, @@ -292,7 +293,7 @@ export class GovDataService { } return Number(payloadsCount); } catch { - if (!!setRpcError) { + if (!!setRpcError && rpcUrl) { setRpcError({ isError: true, rpcUrl, diff --git a/src/web3/store/web3Slice.ts b/src/web3/store/web3Slice.ts index 49e438d5..a412a597 100644 --- a/src/web3/store/web3Slice.ts +++ b/src/web3/store/web3Slice.ts @@ -95,14 +95,14 @@ export const createWeb3Slice: StoreSlice = ( const now = dayjs().unix(); const activeAddress = get().activeWallet?.address; - const requestAndSetData = async () => { + const requestAndSetData = async (adr: Hex) => { const votingStrategy = await get().govDataService.getVotingStrategyContract(); const underlyingAssets = (await votingStrategy.read.getVotingAssetList()) as Draft; const powers = await get().delegationService.getUserPowers( - address, + adr, underlyingAssets, ); @@ -147,7 +147,7 @@ export const createWeb3Slice: StoreSlice = ( set((state) => produce(state, (draft) => { - draft.currentPowers[address] = { + draft.currentPowers[adr] = { timestamp: Number(powers[0].timestamp), totalPropositionPower, totalVotingPower, @@ -168,13 +168,16 @@ export const createWeb3Slice: StoreSlice = ( get().currentPowers[address].timestamp + 3600000 < now || request ) { - await requestAndSetData(); + await requestAndSetData(address); } } else { - await requestAndSetData(); + await requestAndSetData(address); } - } else { - await requestAndSetData(); + } else if (activeAddress) { + await Promise.allSettled([ + await requestAndSetData(activeAddress), + await requestAndSetData(address), + ]); } } }, diff --git a/yarn.lock b/yarn.lock index 7d87b214..7d671ba3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1281,10 +1281,10 @@ resolved "https://registry.yarnpkg.com/@bgd-labs/aave-address-book/-/aave-address-book-2.13.3.tgz#a115c2677aeadd0398caa096436395fe07f4cf48" integrity sha512-9WQekYTCjeAuqjRouLHvYdE9R86Hp4m5v9QJhr9zGpsl8OE/YxpmI6QEz6o0gHMB3D6DhqwAZ3nakCouz7Cb+w== -"@bgd-labs/aave-governance-ui-helpers@^1.0.12": - version "1.0.12" - resolved "https://registry.yarnpkg.com/@bgd-labs/aave-governance-ui-helpers/-/aave-governance-ui-helpers-1.0.12.tgz#98f8d40e2ea9ba1e153816f7abd0d67363946b4d" - integrity sha512-53u4Pw5SK2NJn/2RyYNR1DY1Q4rHfZAaQ+EwWCGcnqE9zJJzOn2RakZtPltg02Mhk4aZFawnSCqyRUktHTyacw== +"@bgd-labs/aave-governance-ui-helpers@^1.0.15-3a0b4f08fbd9e1650f3a570b83b76f5a90872d88.0": + version "1.0.15-3a0b4f08fbd9e1650f3a570b83b76f5a90872d88.0" + resolved "https://registry.yarnpkg.com/@bgd-labs/aave-governance-ui-helpers/-/aave-governance-ui-helpers-1.0.15-3a0b4f08fbd9e1650f3a570b83b76f5a90872d88.0.tgz#a04882429b2e5d38b5dcb3af54bd6dc7202780c3" + integrity sha512-+P/aWVXVT5Q3QKsD5fv0lSD2XzDd+X0idKi+hdallEAakTbW599ui/x2WIYBdAzQuIRkfT7x93cgPBQhHOy+CA== dependencies: bs58 "^5.0.0" dayjs "^1.11.10"