From 66be84876bbf9af02fca8e13da3b693141f5ad1e Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Fri, 15 Dec 2023 12:47:52 +0100 Subject: [PATCH 1/5] Fix proposal creation error toast while actual proposal creation was successful, change Snapshot link from rendering button to rendering , change proposal Details link from rendering button to rendering --- .../ProposalActions/ProposalAction.tsx | 50 ++++++++----------- src/components/Proposals/ProposalInfo.tsx | 10 ++-- src/components/ui/badges/Snapshot.tsx | 19 +++---- .../loaders/snapshot/useSnapshotProposal.ts | 16 ++++-- src/hooks/DAO/proposal/useCastVote.ts | 3 +- src/hooks/DAO/proposal/useSubmitProposal.ts | 15 ++++-- src/hooks/utils/useAsyncRetry.ts | 3 +- 7 files changed, 59 insertions(+), 57 deletions(-) diff --git a/src/components/Proposals/ProposalActions/ProposalAction.tsx b/src/components/Proposals/ProposalActions/ProposalAction.tsx index bad0ed455a..60cb224a18 100644 --- a/src/components/Proposals/ProposalActions/ProposalAction.tsx +++ b/src/components/Proposals/ProposalActions/ProposalAction.tsx @@ -1,17 +1,12 @@ import { Button, Flex, Text } from '@chakra-ui/react'; -import { useRouter } from 'next/navigation'; +import Link from 'next/link'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { BACKGROUND_SEMI_TRANSPARENT } from '../../../constants/common'; import { DAO_ROUTES } from '../../../constants/routes'; import useSnapshotProposal from '../../../hooks/DAO/loaders/snapshot/useSnapshotProposal'; import { useFractal } from '../../../providers/App/AppProvider'; -import { - ExtendedSnapshotProposal, - FractalProposal, - FractalProposalState, - SnapshotProposal, -} from '../../../types'; +import { ExtendedSnapshotProposal, FractalProposal, FractalProposalState } from '../../../types'; import ContentBox from '../../ui/containers/ContentBox'; import { ProposalCountdown } from '../../ui/proposal/ProposalCountdown'; import { useVoteContext } from '../ProposalVotes/context/VoteContext'; @@ -60,7 +55,6 @@ export function ProposalAction({ node: { daoAddress }, readOnly: { user, dao }, } = useFractal(); - const { push } = useRouter(); const { t } = useTranslation(); const { isSnapshotProposal } = useSnapshotProposal(proposal); const { canVote } = useVoteContext(); @@ -78,16 +72,6 @@ export function ProposalAction({ proposal.state === FractalProposalState.TIMELOCKABLE || proposal.state === FractalProposalState.TIMELOCKED)); - const handleClick = () => { - if (isSnapshotProposal) { - push( - DAO_ROUTES.proposal.relative(daoAddress, (proposal as SnapshotProposal).snapshotProposalId) - ); - } else { - push(DAO_ROUTES.proposal.relative(daoAddress, proposal.proposalId)); - } - }; - const labelKey = useMemo(() => { switch (proposal.state) { case FractalProposalState.ACTIVE: @@ -119,12 +103,17 @@ export function ProposalAction({ if (!showActionButton) { if (!expandedView) { return ( - + + ); } // This means that Proposal in state where there's no action to perform @@ -155,11 +144,16 @@ export function ProposalAction({ } return ( - + + ); } diff --git a/src/components/Proposals/ProposalInfo.tsx b/src/components/Proposals/ProposalInfo.tsx index 123481654a..e0b8ab4cd8 100644 --- a/src/components/Proposals/ProposalInfo.tsx +++ b/src/components/Proposals/ProposalInfo.tsx @@ -1,4 +1,4 @@ -import { Box, Flex, Text, Image, Button } from '@chakra-ui/react'; +import { Box, Flex, Text, Image, Button, Link } from '@chakra-ui/react'; import { Shield } from '@decent-org/fractal-ui'; import { useTranslation } from 'react-i18next'; import useSnapshotProposal from '../../hooks/DAO/loaders/snapshot/useSnapshotProposal'; @@ -41,14 +41,12 @@ export function ProposalInfo({ /> {(proposal as ExtendedSnapshotProposal).privacy === 'shutter' && ( ); diff --git a/src/hooks/DAO/loaders/snapshot/useSnapshotProposal.ts b/src/hooks/DAO/loaders/snapshot/useSnapshotProposal.ts index e162641749..de70cd7397 100644 --- a/src/hooks/DAO/loaders/snapshot/useSnapshotProposal.ts +++ b/src/hooks/DAO/loaders/snapshot/useSnapshotProposal.ts @@ -1,5 +1,6 @@ import { gql } from '@apollo/client'; import { useCallback, useMemo, useState } from 'react'; +import { logError } from '../../../../helpers/errorLogging'; import { useFractal } from '../../../../providers/App/AppProvider'; import { ExtendedSnapshotProposal, @@ -197,6 +198,11 @@ export default function useSnapshotProposal(proposal: FractalProposal | null | u }, [snapshotProposal?.snapshotProposalId, proposal, snapshotProposal?.state, getVoteWeight]); const loadVotingWeight = useCallback(async () => { + const emptyVotingWeight = { + votingWeight: 0, + votingWeightByStrategy: [0], + votingState: '', + }; if (snapshotProposal?.snapshotProposalId) { const queryResult = await client .query({ @@ -214,6 +220,10 @@ export default function useSnapshotProposal(proposal: FractalProposal | null | u }`, }) .then(({ data: { vp } }) => { + if (!vp) { + logError('Error while retrieving Snapshot voting weight', vp); + return emptyVotingWeight; + } return { votingWeight: vp.vp, votingWeightByStrategy: vp.vp_by_strategy, @@ -224,11 +234,7 @@ export default function useSnapshotProposal(proposal: FractalProposal | null | u return queryResult; } - return { - votingWeight: 0, - votingWeightByStrategy: [0], - votingState: '', - }; + return emptyVotingWeight; }, [address, daoSnapshotURL, snapshotProposal?.snapshotProposalId]); return { diff --git a/src/hooks/DAO/proposal/useCastVote.ts b/src/hooks/DAO/proposal/useCastVote.ts index ff5498b382..d077a7e714 100644 --- a/src/hooks/DAO/proposal/useCastVote.ts +++ b/src/hooks/DAO/proposal/useCastVote.ts @@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'; import { toast } from 'react-toastify'; import { useSigner } from 'wagmi'; import { useVoteContext } from '../../../components/Proposals/ProposalVotes/context/VoteContext'; +import { logError } from '../../../helpers/errorLogging'; import { useFractal } from '../../../providers/App/AppProvider'; import { AzoriusGovernance, @@ -167,7 +168,7 @@ const useCastVote = ({ } catch (e) { toast.dismiss(toastId); toast.error('failedCastVote'); - console.error('Error while casting Snapshot vote', e); + logError('Error while casting Snapshot vote', e); } } }, diff --git a/src/hooks/DAO/proposal/useSubmitProposal.ts b/src/hooks/DAO/proposal/useSubmitProposal.ts index 9dc80667ec..21b36939a2 100644 --- a/src/hooks/DAO/proposal/useSubmitProposal.ts +++ b/src/hooks/DAO/proposal/useSubmitProposal.ts @@ -295,6 +295,7 @@ export default function useSubmitProposal() { }); setPendingCreateTx(true); + let success = false; try { const transactions = proposalData.targets.map((target, index) => ({ to: target, @@ -316,12 +317,12 @@ export default function useSubmitProposal() { }) ) ).wait(); - await loadDAOProposals(); + success = true; + toast.dismiss(toastId); + toast(successToastMessage); if (successCallback) { successCallback(safeAddress!); } - toast.dismiss(toastId); - toast(successToastMessage); } catch (e) { toast.dismiss(toastId); toast(failedToastMessage); @@ -329,6 +330,14 @@ export default function useSubmitProposal() { } finally { setPendingCreateTx(false); } + + if (success) { + // Frequently there's an error in loadDAOProposals if we're loading the proposal immediately after proposal creation + // The error occurs because block of proposal creation not yet mined and trying to fetch underlying data of voting weight for new proposal fails with that error + // The code that throws an error: https://github.com/decent-dao/fractal-contracts/blob/develop/contracts/azorius/LinearERC20Voting.sol#L205-L211 + // So to avoid showing error toast - we're marking proposal creation as success and only then re-fetching proposals + await loadDAOProposals(); + } }, [loadDAOProposals] ); diff --git a/src/hooks/utils/useAsyncRetry.ts b/src/hooks/utils/useAsyncRetry.ts index dc8f629169..2781cbffdd 100644 --- a/src/hooks/utils/useAsyncRetry.ts +++ b/src/hooks/utils/useAsyncRetry.ts @@ -1,4 +1,5 @@ import { useCallback } from 'react'; +import { logError } from '../../helpers/errorLogging'; function sleep(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); @@ -23,7 +24,7 @@ export function useAsyncRetry() { return result; } } catch (error) { - console.error('Error in requestWithRetries:', error); + logError('Error in requestWithRetries:', error); } currentRetries += 1; From 16ae3a3558d4c79c6750eb692d7f95e2f7ad341d Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Fri, 15 Dec 2023 12:48:55 +0100 Subject: [PATCH 2/5] Set loadDAOProposals loading via timeout instead of immediatelly calling that --- src/hooks/DAO/proposal/useSubmitProposal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/DAO/proposal/useSubmitProposal.ts b/src/hooks/DAO/proposal/useSubmitProposal.ts index 21b36939a2..7185f3835e 100644 --- a/src/hooks/DAO/proposal/useSubmitProposal.ts +++ b/src/hooks/DAO/proposal/useSubmitProposal.ts @@ -336,7 +336,7 @@ export default function useSubmitProposal() { // The error occurs because block of proposal creation not yet mined and trying to fetch underlying data of voting weight for new proposal fails with that error // The code that throws an error: https://github.com/decent-dao/fractal-contracts/blob/develop/contracts/azorius/LinearERC20Voting.sol#L205-L211 // So to avoid showing error toast - we're marking proposal creation as success and only then re-fetching proposals - await loadDAOProposals(); + setTimeout(loadDAOProposals, 5000); } }, [loadDAOProposals] From b2b91b07b4d92886ca4c2785b515b3aca716301d Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Fri, 15 Dec 2023 12:54:52 +0100 Subject: [PATCH 3/5] Increase timeout --- src/hooks/DAO/proposal/useSubmitProposal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/DAO/proposal/useSubmitProposal.ts b/src/hooks/DAO/proposal/useSubmitProposal.ts index 7185f3835e..eb98e3d868 100644 --- a/src/hooks/DAO/proposal/useSubmitProposal.ts +++ b/src/hooks/DAO/proposal/useSubmitProposal.ts @@ -336,7 +336,7 @@ export default function useSubmitProposal() { // The error occurs because block of proposal creation not yet mined and trying to fetch underlying data of voting weight for new proposal fails with that error // The code that throws an error: https://github.com/decent-dao/fractal-contracts/blob/develop/contracts/azorius/LinearERC20Voting.sol#L205-L211 // So to avoid showing error toast - we're marking proposal creation as success and only then re-fetching proposals - setTimeout(loadDAOProposals, 5000); + setTimeout(loadDAOProposals, 10000); } }, [loadDAOProposals] From 4327f48b9d17e5103a1654e288ebd520690b3b17 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Fri, 15 Dec 2023 13:00:40 +0100 Subject: [PATCH 4/5] Proper timeout based on averageBlockTime --- src/hooks/DAO/proposal/useSubmitProposal.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/hooks/DAO/proposal/useSubmitProposal.ts b/src/hooks/DAO/proposal/useSubmitProposal.ts index eb98e3d868..073f4b03e9 100644 --- a/src/hooks/DAO/proposal/useSubmitProposal.ts +++ b/src/hooks/DAO/proposal/useSubmitProposal.ts @@ -9,7 +9,7 @@ import { BigNumber, Signer, utils } from 'ethers'; import { getAddress, isAddress } from 'ethers/lib/utils'; import { useCallback, useMemo, useState, useEffect } from 'react'; import { toast } from 'react-toastify'; -import { useSigner } from 'wagmi'; +import { useProvider, useSigner } from 'wagmi'; import { ADDRESS_MULTISIG_METADATA } from '../../../constants/common'; import { buildSafeAPIPost, encodeMultiSend } from '../../../helpers'; import { logError } from '../../../helpers/errorLogging'; @@ -24,6 +24,7 @@ import { ProposalMetadata, } from '../../../types'; import { buildSafeApiUrl, getAzoriusModuleFromModules } from '../../../utils'; +import { getAverageBlockTime } from '../../../utils/contract'; import useSignerOrProvider from '../../utils/useSignerOrProvider'; import { useFractalModules } from '../loaders/useFractalModules'; import { useDAOProposals } from '../loaders/useProposals'; @@ -61,6 +62,7 @@ export default function useSubmitProposal() { const [canUserCreateProposal, setCanUserCreateProposal] = useState(false); const loadDAOProposals = useDAOProposals(); const { data: signer } = useSigner(); + const provider = useProvider(); const { node: { safe, fractalModules }, @@ -332,14 +334,15 @@ export default function useSubmitProposal() { } if (success) { + const averageBlockTime = await getAverageBlockTime(provider); // Frequently there's an error in loadDAOProposals if we're loading the proposal immediately after proposal creation // The error occurs because block of proposal creation not yet mined and trying to fetch underlying data of voting weight for new proposal fails with that error // The code that throws an error: https://github.com/decent-dao/fractal-contracts/blob/develop/contracts/azorius/LinearERC20Voting.sol#L205-L211 // So to avoid showing error toast - we're marking proposal creation as success and only then re-fetching proposals - setTimeout(loadDAOProposals, 10000); + setTimeout(loadDAOProposals, averageBlockTime * 1.5 * 1000); } }, - [loadDAOProposals] + [loadDAOProposals, provider] ); const submitProposal = useCallback( From e0a70fe6ba9b4c16c97aab03745c4b841ece25b6 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Tue, 19 Dec 2023 19:03:20 +0100 Subject: [PATCH 5/5] Handle full Snapshot URL --- src/components/ui/badges/Snapshot.tsx | 2 +- src/hooks/DAO/loaders/snapshot/index.ts | 13 ++- .../loaders/snapshot/useSnapshotProposal.ts | 25 ++++-- .../loaders/snapshot/useSnapshotProposals.ts | 79 +++++++++++-------- .../loaders/snapshot/useSnapshotSpaceName.ts | 9 +++ 5 files changed, 79 insertions(+), 49 deletions(-) create mode 100644 src/hooks/DAO/loaders/snapshot/useSnapshotSpaceName.ts diff --git a/src/components/ui/badges/Snapshot.tsx b/src/components/ui/badges/Snapshot.tsx index cb442e0c23..25c2d5f08d 100644 --- a/src/components/ui/badges/Snapshot.tsx +++ b/src/components/ui/badges/Snapshot.tsx @@ -8,7 +8,7 @@ interface Props extends ButtonProps { export default function Snapshot({ snapshotURL, ...rest }: Props) { return (