From fdfcb4fa8e1bfd9136d88899c4fa20039c7be916 Mon Sep 17 00:00:00 2001 From: mark hinschberger Date: Fri, 13 Feb 2026 17:56:36 +0000 Subject: [PATCH 1/3] fix: vote cache --- .../transactions/GovVote/GovVoteActions.tsx | 48 +++++++++++++++---- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/src/components/transactions/GovVote/GovVoteActions.tsx b/src/components/transactions/GovVote/GovVoteActions.tsx index e7cdb37402..489cfae62a 100644 --- a/src/components/transactions/GovVote/GovVoteActions.tsx +++ b/src/components/transactions/GovVote/GovVoteActions.tsx @@ -2,13 +2,13 @@ import { ChainId } from '@aave/contract-helpers'; import { GelatoRelay } from '@gelatonetwork/relay-sdk'; import { Trans } from '@lingui/macro'; import { useQueryClient } from '@tanstack/react-query'; -import { AbiCoder, keccak256, RLP } from 'ethers/lib/utils'; +import { AbiCoder, keccak256, parseUnits, RLP } from 'ethers/lib/utils'; import { useState } from 'react'; import { MOCK_SIGNED_HASH } from 'src/helpers/useTransactionHandler'; import { useGovernanceTokensAndPowers } from 'src/hooks/governance/useGovernanceTokensAndPowers'; import { useModalContext } from 'src/hooks/useModal'; import { useWeb3Context } from 'src/libs/hooks/useWeb3Context'; -import { VoteProposalData } from 'src/modules/governance/types'; +import { ProposalDetailDisplay, VoteProposalData } from 'src/modules/governance/types'; import { useRootStore } from 'src/store/root'; import { governanceV3Config } from 'src/ui-config/governanceConfig'; import { getProvider } from 'src/utils/marketsAndNetworksConfig'; @@ -194,6 +194,7 @@ export const GovVoteActions = ({ setApprovalTxState, approvalTxState, setTxError, + args, } = useModalContext(); const user = useRootStore((store) => store.account); @@ -266,10 +267,21 @@ export const GovVoteActions = ({ loading: false, success: true, }); - queryClient.invalidateQueries({ queryKey: ['governance_proposal', proposalId, user] }); - queryClient.invalidateQueries({ - queryKey: ['governance-detail-cache', proposalId, user], - }); + + const votingPowerWei = args?.power ? parseUnits(args.power, 18).toString() : '1'; + const updater = (old: ProposalDetailDisplay | null | undefined) => { + if (!old?.voteProposalData) return old; + return { + ...old, + voteProposalData: { + ...old.voteProposalData, + votedInfo: { support, votingPower: votingPowerWei }, + }, + }; + }; + queryClient.setQueryData(['governance-detail-cache', proposalId, user], updater); + queryClient.setQueryData(['governance-detail-graph', proposalId, user], updater); + queryClient.invalidateQueries({ queryKey: ['proposalVotes', proposalId] }); queryClient.invalidateQueries({ queryKey: ['governance-voters-cache-for', proposalId], @@ -277,6 +289,9 @@ export const GovVoteActions = ({ queryClient.invalidateQueries({ queryKey: ['governance-voters-cache-against', proposalId], }); + queryClient.invalidateQueries({ + queryKey: ['governance-voters-graph', proposalId], + }); return; } else { setTimeout(checkForStatus, 5000); @@ -301,8 +316,22 @@ export const GovVoteActions = ({ success: true, }); - queryClient.invalidateQueries({ queryKey: ['governance_proposal', proposalId, user] }); - queryClient.invalidateQueries({ queryKey: ['governance-detail-cache', proposalId, user] }); + // Optimistically update votedInfo so "You voted" shows immediately + // without waiting for the cache service to index the vote + const votingPowerWei = args?.power ? parseUnits(args.power, 18).toString() : '1'; + const updater = (old: ProposalDetailDisplay | null | undefined) => { + if (!old?.voteProposalData) return old; + return { + ...old, + voteProposalData: { + ...old.voteProposalData, + votedInfo: { support, votingPower: votingPowerWei }, + }, + }; + }; + queryClient.setQueryData(['governance-detail-cache', proposalId, user], updater); + queryClient.setQueryData(['governance-detail-graph', proposalId, user], updater); + queryClient.invalidateQueries({ queryKey: ['proposalVotes', proposalId] }); queryClient.invalidateQueries({ queryKey: ['governance-voters-cache-for', proposalId], @@ -310,6 +339,9 @@ export const GovVoteActions = ({ queryClient.invalidateQueries({ queryKey: ['governance-voters-cache-against', proposalId], }); + queryClient.invalidateQueries({ + queryKey: ['governance-voters-graph', proposalId], + }); } } catch (err) { setMainTxState({ From e44cbab686cb8ce5f4ec3bfd1f74307331c4cf69 Mon Sep 17 00:00:00 2001 From: mark hinschberger Date: Fri, 13 Feb 2026 18:14:04 +0000 Subject: [PATCH 2/3] fix: handle voter count optimistic --- .../transactions/GovVote/GovVoteActions.tsx | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/components/transactions/GovVote/GovVoteActions.tsx b/src/components/transactions/GovVote/GovVoteActions.tsx index 489cfae62a..baebd7e176 100644 --- a/src/components/transactions/GovVote/GovVoteActions.tsx +++ b/src/components/transactions/GovVote/GovVoteActions.tsx @@ -268,11 +268,26 @@ export const GovVoteActions = ({ success: true, }); + const power = parseFloat(args?.power || '0'); const votingPowerWei = args?.power ? parseUnits(args.power, 18).toString() : '1'; const updater = (old: ProposalDetailDisplay | null | undefined) => { if (!old?.voteProposalData) return old; + const forVotes = old.voteInfo.forVotes + (support ? power : 0); + const againstVotes = old.voteInfo.againstVotes + (support ? 0 : power); + const total = forVotes + againstVotes; + const currentDifferential = forVotes - againstVotes; return { ...old, + voteInfo: { + ...old.voteInfo, + forVotes, + againstVotes, + forPercent: total > 0 ? forVotes / total : 0, + againstPercent: total > 0 ? againstVotes / total : 0, + currentDifferential, + quorumReached: forVotes >= old.voteInfo.quorum, + differentialReached: currentDifferential >= old.voteInfo.requiredDifferential, + }, voteProposalData: { ...old.voteProposalData, votedInfo: { support, votingPower: votingPowerWei }, @@ -316,13 +331,28 @@ export const GovVoteActions = ({ success: true, }); - // Optimistically update votedInfo so "You voted" shows immediately - // without waiting for the cache service to index the vote + // Optimistically update votedInfo and vote counts so the UI reflects + // the vote immediately without waiting for the cache service to index + const power = parseFloat(args?.power || '0'); const votingPowerWei = args?.power ? parseUnits(args.power, 18).toString() : '1'; const updater = (old: ProposalDetailDisplay | null | undefined) => { if (!old?.voteProposalData) return old; + const forVotes = old.voteInfo.forVotes + (support ? power : 0); + const againstVotes = old.voteInfo.againstVotes + (support ? 0 : power); + const total = forVotes + againstVotes; + const currentDifferential = forVotes - againstVotes; return { ...old, + voteInfo: { + ...old.voteInfo, + forVotes, + againstVotes, + forPercent: total > 0 ? forVotes / total : 0, + againstPercent: total > 0 ? againstVotes / total : 0, + currentDifferential, + quorumReached: forVotes >= old.voteInfo.quorum, + differentialReached: currentDifferential >= old.voteInfo.requiredDifferential, + }, voteProposalData: { ...old.voteProposalData, votedInfo: { support, votingPower: votingPowerWei }, From 637c4e8de9a3be40722bae918df5aa27fb5061fd Mon Sep 17 00:00:00 2001 From: mark hinschberger Date: Mon, 16 Feb 2026 14:25:44 +0000 Subject: [PATCH 3/3] fix: invalidate --- .../transactions/GovVote/GovVoteActions.tsx | 136 +++++++----------- 1 file changed, 55 insertions(+), 81 deletions(-) diff --git a/src/components/transactions/GovVote/GovVoteActions.tsx b/src/components/transactions/GovVote/GovVoteActions.tsx index baebd7e176..dd880ed888 100644 --- a/src/components/transactions/GovVote/GovVoteActions.tsx +++ b/src/components/transactions/GovVote/GovVoteActions.tsx @@ -235,6 +235,58 @@ export const GovVoteActions = ({ }); } + const optimisticallyUpdateVote = () => { + const power = parseFloat(args?.power || '0'); + const votingPowerWei = parseUnits(args?.power || '0', 18).toString(); + const updater = (old: ProposalDetailDisplay | null | undefined) => { + if (!old?.voteProposalData) return old; + const forVotes = old.voteInfo.forVotes + (support ? power : 0); + const againstVotes = old.voteInfo.againstVotes + (support ? 0 : power); + const total = forVotes + againstVotes; + const currentDifferential = forVotes - againstVotes; + return { + ...old, + voteInfo: { + ...old.voteInfo, + forVotes, + againstVotes, + forPercent: total > 0 ? forVotes / total : 0, + againstPercent: total > 0 ? againstVotes / total : 0, + currentDifferential, + quorumReached: forVotes >= old.voteInfo.quorum, + differentialReached: currentDifferential >= old.voteInfo.requiredDifferential, + }, + voteProposalData: { + ...old.voteProposalData, + votedInfo: { support, votingPower: votingPowerWei }, + }, + }; + }; + queryClient.setQueryData(['governance-detail-cache', proposalId, user], updater); + queryClient.setQueryData(['governance-detail-graph', proposalId, user], updater); + + queryClient.invalidateQueries({ queryKey: ['proposalVotes', proposalId] }); + queryClient.invalidateQueries({ + queryKey: ['governance-voters-cache-for', proposalId], + }); + queryClient.invalidateQueries({ + queryKey: ['governance-voters-cache-against', proposalId], + }); + queryClient.invalidateQueries({ + queryKey: ['governance-voters-graph', proposalId], + }); + + // Invalidate the same detail queries we just wrote to. setQueryData above + // gives instant UI feedback, while this triggers a background refetch to + // replace the optimistic snapshot with real indexed data. + queryClient.invalidateQueries({ + queryKey: ['governance-detail-cache', proposalId, user], + }); + queryClient.invalidateQueries({ + queryKey: ['governance-detail-graph', proposalId, user], + }); + }; + const action = async () => { setMainTxState({ ...mainTxState, loading: true }); try { @@ -268,45 +320,7 @@ export const GovVoteActions = ({ success: true, }); - const power = parseFloat(args?.power || '0'); - const votingPowerWei = args?.power ? parseUnits(args.power, 18).toString() : '1'; - const updater = (old: ProposalDetailDisplay | null | undefined) => { - if (!old?.voteProposalData) return old; - const forVotes = old.voteInfo.forVotes + (support ? power : 0); - const againstVotes = old.voteInfo.againstVotes + (support ? 0 : power); - const total = forVotes + againstVotes; - const currentDifferential = forVotes - againstVotes; - return { - ...old, - voteInfo: { - ...old.voteInfo, - forVotes, - againstVotes, - forPercent: total > 0 ? forVotes / total : 0, - againstPercent: total > 0 ? againstVotes / total : 0, - currentDifferential, - quorumReached: forVotes >= old.voteInfo.quorum, - differentialReached: currentDifferential >= old.voteInfo.requiredDifferential, - }, - voteProposalData: { - ...old.voteProposalData, - votedInfo: { support, votingPower: votingPowerWei }, - }, - }; - }; - queryClient.setQueryData(['governance-detail-cache', proposalId, user], updater); - queryClient.setQueryData(['governance-detail-graph', proposalId, user], updater); - - queryClient.invalidateQueries({ queryKey: ['proposalVotes', proposalId] }); - queryClient.invalidateQueries({ - queryKey: ['governance-voters-cache-for', proposalId], - }); - queryClient.invalidateQueries({ - queryKey: ['governance-voters-cache-against', proposalId], - }); - queryClient.invalidateQueries({ - queryKey: ['governance-voters-graph', proposalId], - }); + optimisticallyUpdateVote(); return; } else { setTimeout(checkForStatus, 5000); @@ -324,54 +338,14 @@ export const GovVoteActions = ({ const txWithEstimatedGas = await estimateGasLimit(tx, votingChainId); const response = await sendTx(txWithEstimatedGas); - await response.wait(1); + await response.wait(3); setMainTxState({ txHash: response.hash, loading: false, success: true, }); - // Optimistically update votedInfo and vote counts so the UI reflects - // the vote immediately without waiting for the cache service to index - const power = parseFloat(args?.power || '0'); - const votingPowerWei = args?.power ? parseUnits(args.power, 18).toString() : '1'; - const updater = (old: ProposalDetailDisplay | null | undefined) => { - if (!old?.voteProposalData) return old; - const forVotes = old.voteInfo.forVotes + (support ? power : 0); - const againstVotes = old.voteInfo.againstVotes + (support ? 0 : power); - const total = forVotes + againstVotes; - const currentDifferential = forVotes - againstVotes; - return { - ...old, - voteInfo: { - ...old.voteInfo, - forVotes, - againstVotes, - forPercent: total > 0 ? forVotes / total : 0, - againstPercent: total > 0 ? againstVotes / total : 0, - currentDifferential, - quorumReached: forVotes >= old.voteInfo.quorum, - differentialReached: currentDifferential >= old.voteInfo.requiredDifferential, - }, - voteProposalData: { - ...old.voteProposalData, - votedInfo: { support, votingPower: votingPowerWei }, - }, - }; - }; - queryClient.setQueryData(['governance-detail-cache', proposalId, user], updater); - queryClient.setQueryData(['governance-detail-graph', proposalId, user], updater); - - queryClient.invalidateQueries({ queryKey: ['proposalVotes', proposalId] }); - queryClient.invalidateQueries({ - queryKey: ['governance-voters-cache-for', proposalId], - }); - queryClient.invalidateQueries({ - queryKey: ['governance-voters-cache-against', proposalId], - }); - queryClient.invalidateQueries({ - queryKey: ['governance-voters-graph', proposalId], - }); + optimisticallyUpdateVote(); } } catch (err) { setMainTxState({