From 4482e2dd37c66a1d2767713ee97c62c7135db8ca Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:59:48 -0400 Subject: [PATCH 01/23] initiate freeze from heirarchy; - move freezeOption to own memo - update logic to use prop guardcontract instead of global state --- .../ui/menus/ManageDAO/ManageDAOMenu.tsx | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/components/ui/menus/ManageDAO/ManageDAOMenu.tsx b/src/components/ui/menus/ManageDAO/ManageDAOMenu.tsx index e8c5177dd1..ec03a63fe9 100644 --- a/src/components/ui/menus/ManageDAO/ManageDAOMenu.tsx +++ b/src/components/ui/menus/ManageDAO/ManageDAOMenu.tsx @@ -55,7 +55,6 @@ export function ManageDAOMenu({ const { node: { safe }, governance: { type }, - guardContracts: { freezeVotingContractAddress }, } = useFractal(); const baseContracts = useSafeContracts(); const currentTime = BigNumber.from(useBlockTimestamp()); @@ -131,18 +130,13 @@ export function ManageDAOMenu({ const handleModifyGovernance = useFractalModal(ModalType.CONFIRM_MODIFY_GOVERNANCE); - const options = useMemo(() => { - const createSubDAOOption = { - optionKey: 'optionCreateSubDAO', - onClick: () => navigate(DAO_ROUTES.newSubDao.relative(safeAddress), { replace: true }), - }; - - const freezeOption = { + const freezeOption = useMemo( + () => ({ optionKey: 'optionInitiateFreeze', onClick: () => { const freezeVotingContract = baseContracts!.freezeMultisigVotingMasterCopyContract.asSigner.attach( - freezeVotingContractAddress!, + guardContracts!.freezeVotingContractAddress!, ); const freezeVotingType = guardContracts!.freezeVotingType; if (freezeVotingContract) { @@ -150,12 +144,14 @@ export function ManageDAOMenu({ freezeVotingType === FreezeVotingType.MULTISIG || freezeVotingType === FreezeVotingType.ERC20 ) { - (freezeVotingContract as ERC20FreezeVoting | MultisigFreezeVoting).castFreezeVote(); + return ( + freezeVotingContract as ERC20FreezeVoting | MultisigFreezeVoting + ).castFreezeVote(); } else if (freezeVotingType === FreezeVotingType.ERC721) { getUserERC721VotingTokens(undefined, parentAddress).then(tokensInfo => { const freezeERC721VotingContract = baseContracts!.freezeERC721VotingMasterCopyContract.asSigner.attach( - freezeVotingContractAddress!, + guardContracts!.freezeVotingContractAddress!, ); return freezeERC721VotingContract!['castFreezeVote(address[],uint256[])']( tokensInfo.totalVotingTokenAddresses, @@ -165,8 +161,20 @@ export function ManageDAOMenu({ } } }, - }; + }), + [ + baseContracts, + guardContracts, + getUserERC721VotingTokens, + parentAddress, + ], + ); + const options = useMemo(() => { + const createSubDAOOption = { + optionKey: 'optionCreateSubDAO', + onClick: () => navigate(DAO_ROUTES.newSubDao.relative(safeAddress), { replace: true }), + }; const clawBackOption = { optionKey: 'optionInitiateClawback', onClick: handleClawBack, @@ -233,16 +241,12 @@ export function ManageDAOMenu({ currentTime, navigate, safeAddress, - parentAddress, governanceType, - guardContracts, handleClawBack, canUserCreateProposal, handleModifyGovernance, handleNavigateToSettings, - getUserERC721VotingTokens, - baseContracts, - freezeVotingContractAddress, + freezeOption, ]); return ( From 90a8798b059d7a6a63c9c0ca8dd253001ef8c7e2 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:00:51 -0400 Subject: [PATCH 02/23] run pretty/lint --- src/components/ui/menus/ManageDAO/ManageDAOMenu.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/components/ui/menus/ManageDAO/ManageDAOMenu.tsx b/src/components/ui/menus/ManageDAO/ManageDAOMenu.tsx index ec03a63fe9..7dfd7e6186 100644 --- a/src/components/ui/menus/ManageDAO/ManageDAOMenu.tsx +++ b/src/components/ui/menus/ManageDAO/ManageDAOMenu.tsx @@ -162,12 +162,7 @@ export function ManageDAOMenu({ } }, }), - [ - baseContracts, - guardContracts, - getUserERC721VotingTokens, - parentAddress, - ], + [baseContracts, guardContracts, getUserERC721VotingTokens, parentAddress], ); const options = useMemo(() => { From 45bfda34484355c13d35120f5b588e5959bb5ec6 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Wed, 27 Mar 2024 14:15:11 -0400 Subject: [PATCH 03/23] Make displayed avatars consistent for same addresses --- src/components/Activity/ActivityDescriptionGovernance.tsx | 6 +++++- .../ui/menus/AccountDisplay/MenuButtonDisplay.tsx | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/Activity/ActivityDescriptionGovernance.tsx b/src/components/Activity/ActivityDescriptionGovernance.tsx index dcfa243ce4..2af1420272 100644 --- a/src/components/Activity/ActivityDescriptionGovernance.tsx +++ b/src/components/Activity/ActivityDescriptionGovernance.tsx @@ -1,6 +1,7 @@ import { Box, Flex, Text } from '@chakra-ui/react'; import { useTranslation } from 'react-i18next'; import { useGetMetadata } from '../../hooks/DAO/proposal/useGetMetadata'; +import useAvatar from '../../hooks/utils/useAvatar'; import useDisplayName from '../../hooks/utils/useDisplayName'; import { Activity, @@ -52,6 +53,8 @@ function ProposalAuthor({ activity }: { activity: Activity }) { : multisigProposal.confirmations[0].owner; const { displayName: author } = useDisplayName(proposer); + const avatarURL = useAvatar(author); + return ( {author} diff --git a/src/components/ui/menus/AccountDisplay/MenuButtonDisplay.tsx b/src/components/ui/menus/AccountDisplay/MenuButtonDisplay.tsx index b13609567c..91030b445a 100644 --- a/src/components/ui/menus/AccountDisplay/MenuButtonDisplay.tsx +++ b/src/components/ui/menus/AccountDisplay/MenuButtonDisplay.tsx @@ -25,7 +25,7 @@ export function Connected() { } = useFractal(); const account = user.address; const { displayName: accountDisplayName } = useDisplayName(account); - const avatarURL = useAvatar(account); + const avatarURL = useAvatar(accountDisplayName); if (!account) { return null; From 678d48d9da1cdee4fbdbd1fd10cce14217b7e1ef Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Wed, 27 Mar 2024 22:25:09 -0400 Subject: [PATCH 04/23] Fix DAO state not reseting when switching DAOs --- src/hooks/DAO/useDAOController.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hooks/DAO/useDAOController.ts b/src/hooks/DAO/useDAOController.ts index 48161072a6..649bb2c6a1 100644 --- a/src/hooks/DAO/useDAOController.ts +++ b/src/hooks/DAO/useDAOController.ts @@ -22,10 +22,11 @@ export default function useDAOController() { } = useFractal(); useEffect(() => { if (daoAddress && !currentDAOAddress.current) { - currentDAOAddress.current = daoAddress; + action.resetDAO().then(() => { + currentDAOAddress.current = daoAddress; + }) } if (!daoAddress || daoAddress !== currentDAOAddress.current) { - action.resetDAO(); currentDAOAddress.current = undefined; } }, [action, daoAddress]); From c3ce72444b51c0fd1c68540e378a22b4eaa4e394 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Wed, 27 Mar 2024 22:28:29 -0400 Subject: [PATCH 05/23] fix freeze showing up when switching DAOs. --- src/components/pages/DaoDashboard/Activities/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/pages/DaoDashboard/Activities/index.tsx b/src/components/pages/DaoDashboard/Activities/index.tsx index aecd7dd7f8..009e63add1 100644 --- a/src/components/pages/DaoDashboard/Activities/index.tsx +++ b/src/components/pages/DaoDashboard/Activities/index.tsx @@ -14,6 +14,7 @@ import { useActivities } from './hooks/useActivities'; export function Activities() { const { + guardContracts: { freezeVotingContractAddress }, guard, governance: { type }, } = useFractal(); @@ -37,7 +38,7 @@ export function Activities() { flexDirection="column" gap="1rem" > - {guard.freezeProposalVoteCount?.gt(0) && } + {freezeVotingContractAddress && guard.freezeProposalVoteCount?.gt(0) && } {!type ? ( ) : sortedActivities.length ? ( From a48370eb4bf9df2b0580042d778e703e20804a87 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Wed, 27 Mar 2024 22:28:44 -0400 Subject: [PATCH 06/23] run pretty/lint --- src/hooks/DAO/useDAOController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/DAO/useDAOController.ts b/src/hooks/DAO/useDAOController.ts index 649bb2c6a1..f769dc91c1 100644 --- a/src/hooks/DAO/useDAOController.ts +++ b/src/hooks/DAO/useDAOController.ts @@ -24,7 +24,7 @@ export default function useDAOController() { if (daoAddress && !currentDAOAddress.current) { action.resetDAO().then(() => { currentDAOAddress.current = daoAddress; - }) + }); } if (!daoAddress || daoAddress !== currentDAOAddress.current) { currentDAOAddress.current = undefined; From 19690dd2524b8506f0d77e208f5e6db63cecedd2 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Thu, 28 Mar 2024 22:18:26 -0400 Subject: [PATCH 07/23] Add better typing to `useAsyncRetry` --- src/components/pages/DaoHierarchy/useFetchNodes.tsx | 2 +- .../DAO/loaders/governance/useAzoriusProposals.ts | 10 ++++++---- src/hooks/utils/useAsyncRetry.ts | 10 ++-------- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/components/pages/DaoHierarchy/useFetchNodes.tsx b/src/components/pages/DaoHierarchy/useFetchNodes.tsx index f3f5790016..7b5be8631b 100644 --- a/src/components/pages/DaoHierarchy/useFetchNodes.tsx +++ b/src/components/pages/DaoHierarchy/useFetchNodes.tsx @@ -96,7 +96,7 @@ export function useFetchNodes(address?: string) { for await (const subDAO of nodes) { try { const safeInfo = await requestWithRetries(() => fetchDAOInfo(subDAO.address), 5, 5000); - if (safeInfo.guard) { + if (safeInfo && safeInfo.guard) { if (safeInfo.guard === ethers.constants.AddressZero) { subDAOs.push(safeInfo); } else { diff --git a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts index 6928af6f11..127fa75646 100644 --- a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts +++ b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts @@ -167,10 +167,12 @@ export const useAzoriusProposals = () => { ); }; const proposal = await requestWithRetries(func, 5, 7000); - action.dispatch({ - type: FractalGovernanceAction.UPDATE_PROPOSALS_NEW, - payload: proposal, - }); + if (proposal) { + action.dispatch({ + type: FractalGovernanceAction.UPDATE_PROPOSALS_NEW, + payload: proposal, + }); + } }, [ baseContracts, diff --git a/src/hooks/utils/useAsyncRetry.ts b/src/hooks/utils/useAsyncRetry.ts index 9ed3f83345..9b7258333e 100644 --- a/src/hooks/utils/useAsyncRetry.ts +++ b/src/hooks/utils/useAsyncRetry.ts @@ -5,15 +5,9 @@ function sleep(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } -export type RequestWithRetries = ( - func: () => Promise, - retries: number, - secondsToWait?: number, -) => Promise; - export function useAsyncRetry() { - const requestWithRetries: RequestWithRetries = useCallback( - async (func: () => Promise, retries: number, secondsToWait: number = 2000) => { + const requestWithRetries = useCallback( + async (func: () => Promise, retries: number, secondsToWait: number = 2000) => { let currentRetries = 0; let result = null; From 48d1a2ddca3a8326e44e1acfa25e68c437f4a205 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Thu, 28 Mar 2024 22:23:50 -0400 Subject: [PATCH 08/23] The search bar DAO selection click area is now just button --- .../ui/menus/DAOSearch/SearchDisplay.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/components/ui/menus/DAOSearch/SearchDisplay.tsx b/src/components/ui/menus/DAOSearch/SearchDisplay.tsx index 3e5c67ed51..2157e81dc5 100644 --- a/src/components/ui/menus/DAOSearch/SearchDisplay.tsx +++ b/src/components/ui/menus/DAOSearch/SearchDisplay.tsx @@ -52,14 +52,6 @@ export function SearchDisplay({ return ( { - if (!isCurrentSafe) { - onClickView(); - if (closeDrawer) closeDrawer(); - action.resetDAO(); - navigate(DAO_ROUTES.dao.relative(address)); - } - }} cursor={isCurrentSafe ? 'not-allowed' : 'default'} justifyContent="space-between" > @@ -81,6 +73,12 @@ export function SearchDisplay({ From 2facb7d000b0c02be72cb776f16f7acf3cd4e8f2 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Thu, 28 Mar 2024 22:23:50 -0400 Subject: [PATCH 09/23] Add an "addressPrefix" to the network config objects --- src/providers/NetworkConfig/networks/mainnet.ts | 1 + src/providers/NetworkConfig/networks/polygon.ts | 1 + src/providers/NetworkConfig/networks/sepolia.ts | 1 + src/types/network.ts | 1 + 4 files changed, 4 insertions(+) diff --git a/src/providers/NetworkConfig/networks/mainnet.ts b/src/providers/NetworkConfig/networks/mainnet.ts index 4a841affd4..3cb72d6d5d 100644 --- a/src/providers/NetworkConfig/networks/mainnet.ts +++ b/src/providers/NetworkConfig/networks/mainnet.ts @@ -31,6 +31,7 @@ export const mainnetConfig: NetworkConfig = { etherscanAPIBaseUrl: 'https://api.etherscan.io', chainId: CHAIN_ID, name: mainnet.name, + addressPrefix: 'eth', color: 'blue.400', nativeTokenSymbol: mainnet.nativeCurrency.symbol, nativeTokenIcon: '/images/coin-icon-eth.svg', diff --git a/src/providers/NetworkConfig/networks/polygon.ts b/src/providers/NetworkConfig/networks/polygon.ts index 792e80a1f7..c20df4eead 100644 --- a/src/providers/NetworkConfig/networks/polygon.ts +++ b/src/providers/NetworkConfig/networks/polygon.ts @@ -31,6 +31,7 @@ export const polygonConfig: NetworkConfig = { etherscanAPIBaseUrl: 'https://api.polygonscan.com/api', // TODO test ABISelector on template builder chainId: CHAIN_ID, name: polygon.name, + addressPrefix: 'matic', color: '#753cd8cc', nativeTokenSymbol: polygon.nativeCurrency.symbol, nativeTokenIcon: '/images/coin-icon-eth.svg', diff --git a/src/providers/NetworkConfig/networks/sepolia.ts b/src/providers/NetworkConfig/networks/sepolia.ts index 1f909d17f7..9c4e82562a 100644 --- a/src/providers/NetworkConfig/networks/sepolia.ts +++ b/src/providers/NetworkConfig/networks/sepolia.ts @@ -32,6 +32,7 @@ export const sepoliaConfig: NetworkConfig = { etherscanAPIBaseUrl: 'https://api-sepolia.etherscan.io', chainId: CHAIN_ID, name: sepolia.name, + addressPrefix: 'sep', color: 'gold.300', nativeTokenSymbol: sepolia.nativeCurrency.symbol, nativeTokenIcon: '/images/coin-icon-eth.svg', diff --git a/src/types/network.ts b/src/types/network.ts index 5176b89261..d6c3e39f67 100644 --- a/src/types/network.ts +++ b/src/types/network.ts @@ -13,6 +13,7 @@ export type NetworkConfig = { etherscanAPIBaseUrl: string; chainId: number; name: string; + addressPrefix: string; // copy whatever Safe uses color: string; nativeTokenSymbol: string; nativeTokenIcon: string; From 2d03379aed49714efe6f85c08a21de47318aba3e Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Thu, 28 Mar 2024 22:23:50 -0400 Subject: [PATCH 10/23] When redirecting at initial load, best-guess append the network prefix from the currently connected network --- src/hooks/DAO/useDAOController.ts | 15 ++- src/main.tsx | 9 +- src/router.tsx | 188 +++++++++++++++--------------- 3 files changed, 116 insertions(+), 96 deletions(-) diff --git a/src/hooks/DAO/useDAOController.ts b/src/hooks/DAO/useDAOController.ts index 6c9593f561..027532bb7e 100644 --- a/src/hooks/DAO/useDAOController.ts +++ b/src/hooks/DAO/useDAOController.ts @@ -12,7 +12,19 @@ import { useGovernanceContracts } from './loaders/useGovernanceContracts'; export default function useDAOController() { const [searchParams] = useSearchParams(); - const daoAddress = searchParams.get('dao'); + const addressWithPrefix = searchParams.get('dao'); + + if (addressWithPrefix === null) { + throw new Error('address is null'); + } + + if (!addressWithPrefix.includes(':')) { + throw new Error("address doesn't include prefix"); + } + + const prefixAndAddress = addressWithPrefix.split(':'); + const daoNetwork = prefixAndAddress[0]; + const daoAddress = prefixAndAddress[1]; const { node: { @@ -20,6 +32,7 @@ export default function useDAOController() { }, action, } = useFractal(); + useEffect(() => { if (!daoAddress) { action.resetDAO(); diff --git a/src/main.tsx b/src/main.tsx index ccdabe63d7..3bcd4fc12d 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,7 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import { RouterProvider } from 'react-router-dom'; - +import { useNetworkConfig } from './providers/NetworkConfig/NetworkConfigProvider'; import Providers from './providers/Providers'; import { router } from './router'; @@ -10,12 +10,17 @@ import '@fontsource/ibm-plex-sans'; import 'react-toastify/dist/ReactToastify.min.css'; import './assets/css/Markdown.css'; +function FractalRouterProvider() { + const { addressPrefix } = useNetworkConfig(); + return ; +} + const root = document.getElementById('root'); if (root !== null) { ReactDOM.createRoot(root).render( - + , ); diff --git a/src/router.tsx b/src/router.tsx index 79c0f4dd29..45ed45b973 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -17,98 +17,100 @@ import ProposalCreatePage from './pages/daos/[daoAddress]/proposals/new'; import SettingsPage from './pages/daos/[daoAddress]/settings'; import Treasury from './pages/daos/[daoAddress]/treasury'; -export const router = createBrowserRouter([ - { - path: '/', - element: ( - // We're placing ModalProvider here instead of src/providers/Providers.tsx due to the need of having router context - // within underlying modals. Otherwise - trying to invoke routing-related hooks would lead to crash. - // Not the best place to have this provider here but also more reasonalbe than putting that into - - - - ), - children: [ - { - index: true, - element: , - }, - { - path: 'create', - element: , - }, - { - path: '/', - element: , - children: [ - { - path: 'home', - element: , - }, - { - path: 'edit/governance', - element: , - }, - { - path: 'hierarchy', - element: , - }, - { - path: 'new', - element: , - }, - { - path: 'proposal-templates', - children: [ - { - index: true, - element: , - }, - { - path: 'new', - element: , - }, - ], - }, - { - path: 'proposals', - children: [ - { - index: true, - element: , - }, - { - path: ':proposalId', - element: , - }, - { - path: 'new', - element: , - }, - ], - }, - { - path: 'settings', - element: , - }, - { - path: 'treasury', - element: , - }, - ], - }, - { - // this exists to keep old links working - // /daos/0x0123/* will redirect to /home?dao=0x0123 - path: 'daos/:daoAddress/*', - loader: ({ params: { daoAddress } }) => redirect(`/home?dao=${daoAddress}`), - }, - { - path: '*', // 404 - element: , - }, - ], - }, -]); +export const router = (addressPrefix: string) => + createBrowserRouter([ + { + path: '/', + element: ( + // We're placing ModalProvider here instead of src/providers/Providers.tsx due to the need of having router context + // within underlying modals. Otherwise - trying to invoke routing-related hooks would lead to crash. + // Not the best place to have this provider here but also more reasonalbe than putting that into + + + + ), + children: [ + { + index: true, + element: , + }, + { + path: 'create', + element: , + }, + { + path: '/', + element: , + children: [ + { + path: 'home', + element: , + }, + { + path: 'edit/governance', + element: , + }, + { + path: 'hierarchy', + element: , + }, + { + path: 'new', + element: , + }, + { + path: 'proposal-templates', + children: [ + { + index: true, + element: , + }, + { + path: 'new', + element: , + }, + ], + }, + { + path: 'proposals', + children: [ + { + index: true, + element: , + }, + { + path: ':proposalId', + element: , + }, + { + path: 'new', + element: , + }, + ], + }, + { + path: 'settings', + element: , + }, + { + path: 'treasury', + element: , + }, + ], + }, + { + // this exists to keep old links working + // /daos/0x0123/* will redirect to /home?dao=0x0123 + path: 'daos/:daoAddress/*', + loader: ({ params: { daoAddress } }) => + redirect(`/home?dao=${addressPrefix}:${daoAddress}`), + }, + { + path: '*', // 404 + element: , + }, + ], + }, + ]); export default router; From aeb3f8e8aa3ec6cfb95dd1fe8a75759c9308646c Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Thu, 28 Mar 2024 22:23:50 -0400 Subject: [PATCH 11/23] When using search input, and navigating to found address, make sure to prefix the network to navigation --- src/components/ui/menus/DAOSearch/SearchDisplay.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/ui/menus/DAOSearch/SearchDisplay.tsx b/src/components/ui/menus/DAOSearch/SearchDisplay.tsx index 2157e81dc5..d0d81c64a4 100644 --- a/src/components/ui/menus/DAOSearch/SearchDisplay.tsx +++ b/src/components/ui/menus/DAOSearch/SearchDisplay.tsx @@ -5,6 +5,7 @@ import { useNavigate } from 'react-router-dom'; import { DAO_ROUTES } from '../../../../constants/routes'; import useDisplayName from '../../../../hooks/utils/useDisplayName'; import { useFractal } from '../../../../providers/App/AppProvider'; +import { useNetworkConfig } from '../../../../providers/NetworkConfig/NetworkConfigProvider'; interface ISearchDisplay { loading?: boolean; @@ -30,6 +31,7 @@ export function SearchDisplay({ }: ISearchDisplay) { const { t } = useTranslation(['common', 'dashboard']); const { action, node } = useFractal(); + const { addressPrefix } = useNetworkConfig(); const navigate = useNavigate(); const isCurrentSafe = useMemo(() => { return !!node && !!node.daoAddress && node.daoAddress === address; @@ -77,7 +79,7 @@ export function SearchDisplay({ onClickView(); if (closeDrawer) closeDrawer(); action.resetDAO(); - navigate(DAO_ROUTES.dao.relative(address)); + navigate(DAO_ROUTES.dao.relative(`${addressPrefix}:${address}`)); }} > {t('labelViewDAO')} From 35766a4cdb7059370200c1a3c8029b5e19934899 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Thu, 28 Mar 2024 22:23:50 -0400 Subject: [PATCH 12/23] Add daoNetwork to fractal state object, use it where appropriate --- src/components/pages/DaoHierarchy/DaoNode.tsx | 12 ++- src/hooks/DAO/loaders/useFractalNode.ts | 87 +++++++++++-------- src/hooks/DAO/loaders/useLoadDAONode.ts | 49 ++++++----- src/hooks/DAO/useDAOController.ts | 2 +- .../daos/[daoAddress]/hierarchy/index.tsx | 5 +- src/providers/App/node/reducer.ts | 1 + src/types/fractal.ts | 1 + 7 files changed, 93 insertions(+), 64 deletions(-) diff --git a/src/components/pages/DaoHierarchy/DaoNode.tsx b/src/components/pages/DaoHierarchy/DaoNode.tsx index 0712fa8899..6c01e748cd 100644 --- a/src/components/pages/DaoHierarchy/DaoNode.tsx +++ b/src/components/pages/DaoHierarchy/DaoNode.tsx @@ -20,10 +20,12 @@ import { NodeLineVertical } from './NodeLines'; export function DaoNode({ parentAddress, daoAddress, + daoNetwork, depth, }: { parentAddress?: string; daoAddress?: string; + daoNetwork?: string; depth: number; }) { const [fractalNode, setNode] = useState(); @@ -38,8 +40,8 @@ export function DaoNode({ const isCurrentDAO = daoAddress === currentDAOAddress; useEffect(() => { - if (daoAddress && !fractalNode) { - loadDao(utils.getAddress(daoAddress)).then(_node => { + if (daoAddress && daoNetwork && !fractalNode) { + loadDao(utils.getAddress(daoAddress), daoNetwork).then(_node => { const errorNode = _node as WithError; if (!errorNode.error) { // calculates the total number of descendants below the given node @@ -69,7 +71,8 @@ export function DaoNode({ } else if (errorNode.error === 'errorFailedSearch') { setNode({ daoName: null, - daoAddress: daoAddress, + daoAddress, + daoNetwork, safe: null, fractalModules: [], nodeHierarchy: { @@ -80,7 +83,7 @@ export function DaoNode({ } }); } - }, [loadDao, daoAddress, fractalNode, depth]); + }, [loadDao, daoAddress, fractalNode, depth, daoNetwork]); return ( @@ -106,6 +109,7 @@ export function DaoNode({ diff --git a/src/hooks/DAO/loaders/useFractalNode.ts b/src/hooks/DAO/loaders/useFractalNode.ts index 984df02370..9036ad2686 100644 --- a/src/hooks/DAO/loaders/useFractalNode.ts +++ b/src/hooks/DAO/loaders/useFractalNode.ts @@ -14,7 +14,13 @@ import { useFractalModules } from './useFractalModules'; const ONE_MINUTE = 60 * 1000; -export const useFractalNode = ({ daoAddress }: { daoAddress?: string }) => { +export const useFractalNode = ({ + daoAddress, + daoNetwork, +}: { + daoAddress: string; + daoNetwork: string; +}) => { // tracks the current valid Safe address and chain id; helps prevent unnecessary calls const currentValidSafe = useRef(); const [nodeLoading, setNodeLoading] = useState(true); @@ -27,37 +33,41 @@ export const useFractalNode = ({ daoAddress }: { daoAddress?: string }) => { const lookupModules = useFractalModules(); const { requestWithRetries } = useAsyncRetry(); - const formatDAOQuery = useCallback((result: { data?: DAOQueryQuery }, _daoAddress: string) => { - if (!result.data) { - return; - } - const { daos } = result.data; - const dao = daos[0]; - if (dao) { - const { parentAddress, name, hierarchy, snapshotURL, proposalTemplatesHash } = dao; + const formatDAOQuery = useCallback( + (result: { data?: DAOQueryQuery }, _daoAddress: string, _daoNetwork: string) => { + if (!result.data) { + return; + } + const { daos } = result.data; + const dao = daos[0]; + if (dao) { + const { parentAddress, name, hierarchy, snapshotURL, proposalTemplatesHash } = dao; - const currentNode: Node = { - nodeHierarchy: { - parentAddress: parentAddress as string, - childNodes: mapChildNodes(hierarchy), - }, - daoName: name as string, - daoAddress: utils.getAddress(_daoAddress as string), - daoSnapshotURL: snapshotURL as string, - proposalTemplatesHash: proposalTemplatesHash as string, - }; - return currentNode; - } - return; - }, []); + const currentNode: Node = { + nodeHierarchy: { + parentAddress: parentAddress as string, + childNodes: mapChildNodes(hierarchy), + }, + daoName: name as string, + daoAddress: utils.getAddress(_daoAddress as string), + daoNetwork: _daoNetwork, + daoSnapshotURL: snapshotURL as string, + proposalTemplatesHash: proposalTemplatesHash as string, + }; + return currentNode; + } + return; + }, + [], + ); - const { subgraphChainName } = useNetworkConfig(); + const { subgraphChainName, addressPrefix } = useNetworkConfig(); useQuery(DAOQueryDocument, { variables: { daoAddress }, onCompleted: async data => { - if (!daoAddress) return; - const graphNodeInfo = formatDAOQuery({ data }, daoAddress); + if (!daoAddress || !daoNetwork) return; + const graphNodeInfo = formatDAOQuery({ data }, daoAddress, daoNetwork); const daoName = await getDaoName(utils.getAddress(daoAddress), graphNodeInfo?.daoName); if (!!graphNodeInfo) { @@ -84,10 +94,15 @@ export const useFractalNode = ({ daoAddress }: { daoAddress?: string }) => { }, [safeAPI, daoAddress]); const setDAO = useCallback( - async (_chainId: number, _daoAddress: string) => { + async (_daoNetwork: string, _daoAddress: string, _connectedNetwork: string) => { setNodeLoading(true); setErrorLoading(false); - if (utils.isAddress(_daoAddress) && safeAPI) { + + if (_connectedNetwork !== _daoNetwork) { + currentValidSafe.current = undefined; + action.resetDAO(); + setErrorLoading(true); + } else if (utils.isAddress(_daoAddress) && safeAPI) { try { const safeInfo = await requestWithRetries(fetchSafeInfo, 5); if (!safeInfo) { @@ -95,7 +110,7 @@ export const useFractalNode = ({ daoAddress }: { daoAddress?: string }) => { action.resetDAO(); setErrorLoading(true); } else { - currentValidSafe.current = _chainId + _daoAddress; + currentValidSafe.current = _daoNetwork + _daoAddress; action.dispatch({ type: NodeAction.SET_FRACTAL_MODULES, payload: await lookupModules(safeInfo.modules), @@ -120,20 +135,22 @@ export const useFractalNode = ({ daoAddress }: { daoAddress?: string }) => { } setNodeLoading(false); }, - [action, safeAPI, lookupModules, fetchSafeInfo, requestWithRetries], + [safeAPI, action, requestWithRetries, fetchSafeInfo, lookupModules], ); - const { chainId } = useNetworkConfig(); useEffect(() => { - if (daoAddress && chainId + daoAddress !== currentValidSafe.current) { + if ( + (daoAddress && daoNetwork && daoNetwork + daoAddress !== currentValidSafe.current) || + daoNetwork !== addressPrefix + ) { setNodeLoading(true); - setDAO(chainId, daoAddress); + setDAO(daoNetwork, daoAddress, addressPrefix); } - if (!daoAddress) { + if (!daoAddress || !daoNetwork) { currentValidSafe.current = undefined; setNodeLoading(false); } - }, [daoAddress, setDAO, currentValidSafe, chainId]); + }, [daoAddress, daoNetwork, setDAO, currentValidSafe, addressPrefix]); return { nodeLoading, errorLoading }; }; diff --git a/src/hooks/DAO/loaders/useLoadDAONode.ts b/src/hooks/DAO/loaders/useLoadDAONode.ts index 7be3374867..ab6565f206 100644 --- a/src/hooks/DAO/loaders/useLoadDAONode.ts +++ b/src/hooks/DAO/loaders/useLoadDAONode.ts @@ -19,36 +19,41 @@ export const useLoadDAONode = () => { context: { chainName: subgraphChainName }, }); - const formatDAOQuery = useCallback((result: { data?: DAOQueryQuery }, _daoAddress: string) => { - if (!result.data) { - return; - } - const { daos } = result.data; - const dao = daos[0]; - if (dao) { - const { parentAddress, name, hierarchy, snapshotURL } = dao; + const formatDAOQuery = useCallback( + (result: { data?: DAOQueryQuery }, _daoAddress: string, _daoNetwork: string) => { + if (!result.data) { + return; + } + const { daos } = result.data; + const dao = daos[0]; + if (dao) { + const { parentAddress, name, hierarchy, snapshotURL } = dao; - const currentNode: Node = { - nodeHierarchy: { - parentAddress: parentAddress as string, - childNodes: mapChildNodes(hierarchy), - }, - daoName: name as string, - daoAddress: utils.getAddress(_daoAddress as string), - daoSnapshotURL: snapshotURL as string, - }; - return currentNode; - } - return; - }, []); + const currentNode: Node = { + nodeHierarchy: { + parentAddress: parentAddress as string, + childNodes: mapChildNodes(hierarchy), + }, + daoName: name as string, + daoAddress: utils.getAddress(_daoAddress as string), + daoNetwork: _daoNetwork, + daoSnapshotURL: snapshotURL as string, + }; + return currentNode; + } + return; + }, + [], + ); const loadDao = useCallback( - async (_daoAddress: string): Promise => { + async (_daoAddress: string, _daoNetwork: string): Promise => { if (utils.isAddress(_daoAddress) && safeAPI) { try { const graphNodeInfo = formatDAOQuery( await getDAOInfo({ variables: { daoAddress: _daoAddress } }), _daoAddress, + _daoNetwork, ); if (!graphNodeInfo) { logError('graphQL query failed'); diff --git a/src/hooks/DAO/useDAOController.ts b/src/hooks/DAO/useDAOController.ts index 027532bb7e..ae687e1f5b 100644 --- a/src/hooks/DAO/useDAOController.ts +++ b/src/hooks/DAO/useDAOController.ts @@ -39,7 +39,7 @@ export default function useDAOController() { } }, [action, daoAddress]); - const { nodeLoading, errorLoading } = useFractalNode({ daoAddress: daoAddress || undefined }); + const { nodeLoading, errorLoading } = useFractalNode({ daoAddress, daoNetwork }); useGovernanceContracts(); useFractalGuardContracts({}); useFractalFreeze({ parentSafeAddress: parentAddress }); diff --git a/src/pages/daos/[daoAddress]/hierarchy/index.tsx b/src/pages/daos/[daoAddress]/hierarchy/index.tsx index b24bc10323..f9595b2ff3 100644 --- a/src/pages/daos/[daoAddress]/hierarchy/index.tsx +++ b/src/pages/daos/[daoAddress]/hierarchy/index.tsx @@ -8,11 +8,11 @@ import { useFractal } from '../../../../providers/App/AppProvider'; export default function HierarchyPage() { const { - node: { daoAddress, daoName, nodeHierarchy }, + node: { daoAddress, daoNetwork, daoName, nodeHierarchy }, } = useFractal(); const { t } = useTranslation('breadcrumbs'); - if (!daoAddress) { + if (!daoAddress || !daoNetwork) { return (
@@ -36,6 +36,7 @@ export default function HierarchyPage() { /> diff --git a/src/providers/App/node/reducer.ts b/src/providers/App/node/reducer.ts index e10a7a0489..43789b5914 100644 --- a/src/providers/App/node/reducer.ts +++ b/src/providers/App/node/reducer.ts @@ -10,6 +10,7 @@ export const initialNodeHierarchyState: NodeHierarchy = { export const initialNodeState: FractalNode = { daoName: null, daoAddress: null, + daoNetwork: null, safe: null, fractalModules: [], nodeHierarchy: initialNodeHierarchyState, diff --git a/src/types/fractal.ts b/src/types/fractal.ts index 9d8d5899fa..f5a927a328 100644 --- a/src/types/fractal.ts +++ b/src/types/fractal.ts @@ -234,6 +234,7 @@ export interface FractalGovernanceContracts { export interface FractalNode { daoName: string | null; daoAddress: string | null; + daoNetwork: string | null; safe: SafeInfoResponseWithGuard | null; fractalModules: FractalModuleData[]; nodeHierarchy: NodeHierarchy; From 6e4a12519003c987032721cf9e851174c6446d5f Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Thu, 28 Mar 2024 22:23:50 -0400 Subject: [PATCH 13/23] Differentiate error pages between invalid DAO address, and wrong network --- src/hooks/DAO/loaders/useFractalNode.ts | 5 +++- src/hooks/DAO/useDAOController.ts | 4 +-- src/i18n/locales/en/common.json | 4 ++- src/pages/DAOController.tsx | 35 +++++-------------------- src/pages/LoadingProblem.tsx | 28 ++++++++++++++++++++ 5 files changed, 43 insertions(+), 33 deletions(-) create mode 100644 src/pages/LoadingProblem.tsx diff --git a/src/hooks/DAO/loaders/useFractalNode.ts b/src/hooks/DAO/loaders/useFractalNode.ts index 9036ad2686..35d8815152 100644 --- a/src/hooks/DAO/loaders/useFractalNode.ts +++ b/src/hooks/DAO/loaders/useFractalNode.ts @@ -25,6 +25,7 @@ export const useFractalNode = ({ const currentValidSafe = useRef(); const [nodeLoading, setNodeLoading] = useState(true); const [errorLoading, setErrorLoading] = useState(false); + const [wrongNetwork, setWrongNetwork] = useState(false); const { action } = useFractal(); const safeAPI = useSafeAPI(); @@ -97,11 +98,13 @@ export const useFractalNode = ({ async (_daoNetwork: string, _daoAddress: string, _connectedNetwork: string) => { setNodeLoading(true); setErrorLoading(false); + setWrongNetwork(false); if (_connectedNetwork !== _daoNetwork) { currentValidSafe.current = undefined; action.resetDAO(); setErrorLoading(true); + setWrongNetwork(true); } else if (utils.isAddress(_daoAddress) && safeAPI) { try { const safeInfo = await requestWithRetries(fetchSafeInfo, 5); @@ -152,5 +155,5 @@ export const useFractalNode = ({ } }, [daoAddress, daoNetwork, setDAO, currentValidSafe, addressPrefix]); - return { nodeLoading, errorLoading }; + return { nodeLoading, errorLoading, wrongNetwork }; }; diff --git a/src/hooks/DAO/useDAOController.ts b/src/hooks/DAO/useDAOController.ts index ae687e1f5b..975f91bd1f 100644 --- a/src/hooks/DAO/useDAOController.ts +++ b/src/hooks/DAO/useDAOController.ts @@ -39,7 +39,7 @@ export default function useDAOController() { } }, [action, daoAddress]); - const { nodeLoading, errorLoading } = useFractalNode({ daoAddress, daoNetwork }); + const { nodeLoading, errorLoading, wrongNetwork } = useFractalNode({ daoAddress, daoNetwork }); useGovernanceContracts(); useFractalGuardContracts({}); useFractalFreeze({ parentSafeAddress: parentAddress }); @@ -47,5 +47,5 @@ export default function useDAOController() { useFractalTreasury(); useERC20Claim(); useSnapshotProposals(); - return { nodeLoading, errorLoading }; + return { nodeLoading, errorLoading, wrongNetwork }; } diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index 10da04a503..2e61c550cb 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -98,7 +98,9 @@ "isMeSuffix": " (you)", "refresh": "Refresh", "invalidSafe1": "We couldn't find a Safe at this address on {{chain}}.", - "invalidSafe2": "Try refreshing the page or changing networks in your wallet.", + "invalidSafe2": "Please double check the address then try refreshing.", + "wrongNetwork1": "Fractal is currently connected to {{chain}}.", + "wrongNetwork2": "Try switching the app to the correct chain for your Safe.", "showMore": "Show More", "showLess": "Show Less", "poweredBy": "Powered by", diff --git a/src/pages/DAOController.tsx b/src/pages/DAOController.tsx index 50e41d5421..fd710306b3 100644 --- a/src/pages/DAOController.tsx +++ b/src/pages/DAOController.tsx @@ -1,40 +1,15 @@ -import { Button, Center, Text, VStack, ChakraProvider, extendTheme } from '@chakra-ui/react'; +import { ChakraProvider, extendTheme } from '@chakra-ui/react'; import { theme } from '@decent-org/fractal-ui'; import { useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; import { Outlet } from 'react-router-dom'; import useDAOController from '../hooks/DAO/useDAOController'; import useDAOMetadata from '../hooks/DAO/useDAOMetadata'; import { useFractal } from '../providers/App/AppProvider'; -import { useNetworkConfig } from '../providers/NetworkConfig/NetworkConfigProvider'; - -function InvalidSafe() { - const { name } = useNetworkConfig(); - const { t } = useTranslation('common'); - - return ( -
- - - {t('errorSentryFallbackTitle')} - - {t('invalidSafe1', { chain: name })} - {t('invalidSafe2')} - - -
- ); -} +import LoadingProblem from './LoadingProblem'; export default function DAOController() { const { node } = useFractal(); - const { nodeLoading, errorLoading } = useDAOController(); + const { nodeLoading, errorLoading, wrongNetwork } = useDAOController(); const daoMetadata = useDAOMetadata(); const activeTheme = useMemo(() => { if (daoMetadata && daoMetadata.bodyBackground) { @@ -68,10 +43,12 @@ export default function DAOController() { ); + } else if (wrongNetwork) { + display = ; } else if (nodeLoading || validSafe || !errorLoading) { display = ; } else { - display = ; + display = ; } return display; diff --git a/src/pages/LoadingProblem.tsx b/src/pages/LoadingProblem.tsx new file mode 100644 index 0000000000..552d1d6966 --- /dev/null +++ b/src/pages/LoadingProblem.tsx @@ -0,0 +1,28 @@ +import { Center, Text, VStack } from '@chakra-ui/react'; +import { useTranslation } from 'react-i18next'; +import { useNetworkConfig } from '../providers/NetworkConfig/NetworkConfigProvider'; + +function LoadingProblem({ type }: { type: 'invalidSafe' | 'wrongNetwork' }) { + const { name } = useNetworkConfig(); + const { t } = useTranslation('common'); + + return ( +
+ + + {t('errorSentryFallbackTitle')} + + {t(`${type}1`, { chain: name })} + {t(`${type}2`)} + +
+ ); +} + +export default LoadingProblem; From d1f3759168c2d5e5dc75ae19ed2f47c41adf13e4 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Thu, 28 Mar 2024 22:23:50 -0400 Subject: [PATCH 14/23] Update DAO_ROUTES.relative to take network prefix, fix all errors --- src/components/DaoCreator/StepWrapper.tsx | 6 ++- .../ProposalTemplateCard.tsx | 8 ++-- src/components/ProposalTemplates/index.tsx | 6 +-- .../Proposals/ProposalCard/ProposalCard.tsx | 8 +++- src/components/Proposals/ProposalsList.tsx | 6 +-- .../pages/AppHome/FeaturedDAOCard.tsx | 4 +- .../pages/DaoDashboard/Info/ParentLink.tsx | 6 +-- .../pages/DaoDashboard/Info/index.tsx | 18 ++++---- .../DaoSettings/components/Metadata/index.tsx | 8 +++- src/components/ui/cards/DAOInfoCard.tsx | 6 +-- .../ui/menus/DAOSearch/SearchDisplay.tsx | 2 +- .../ui/menus/FavoritesMenu/Favorite.tsx | 5 ++- .../ui/menus/FavoritesMenu/FavoritesList.tsx | 3 ++ .../ui/menus/ManageDAO/ManageDAOMenu.tsx | 33 ++++++++------- .../modals/ConfirmModifyGovernanceModal.tsx | 8 +++- .../ui/modals/ForkProposalTemplateModal.tsx | 17 ++++---- .../ui/modals/ProposalTemplateModal.tsx | 6 +-- src/components/ui/modals/Stake.tsx | 6 +-- src/components/ui/page/Header/PageHeader.tsx | 8 ++-- .../ui/page/Navigation/NavigationLinks.tsx | 14 ++++--- src/components/ui/page/Navigation/index.tsx | 4 +- src/constants/routes.ts | 41 ++++++++++++------- src/hooks/DAO/proposal/useSubmitProposal.ts | 20 ++++++--- src/hooks/DAO/useCreateSubDAOProposal.ts | 2 +- src/hooks/DAO/useDeployAzorius.ts | 7 ++-- src/hooks/DAO/useDeployDAO.ts | 9 ++-- src/pages/HomePage.tsx | 6 +++ src/pages/create/index.tsx | 4 +- .../[daoAddress]/edit/governance/index.tsx | 8 +++- src/pages/daos/[daoAddress]/new/index.tsx | 4 +- .../[daoAddress]/proposal-templates/index.tsx | 6 +-- .../proposal-templates/new/index.tsx | 16 +++++--- .../proposals/[proposalId]/index.tsx | 8 +++- .../daos/[daoAddress]/proposals/index.tsx | 6 +-- .../daos/[daoAddress]/proposals/new/index.tsx | 12 +++--- 35 files changed, 204 insertions(+), 127 deletions(-) diff --git a/src/components/DaoCreator/StepWrapper.tsx b/src/components/DaoCreator/StepWrapper.tsx index 8e46d0a6c3..82faffb0c9 100644 --- a/src/components/DaoCreator/StepWrapper.tsx +++ b/src/components/DaoCreator/StepWrapper.tsx @@ -27,7 +27,7 @@ export function StepWrapper({ shouldWrapChildren = true, }: IStepWrapper) { const { - node: { daoAddress }, + node: { daoAddress, daoNetwork }, } = useFractal(); const { t } = useTranslation(['daoCreate']); const navigate = useNavigate(); @@ -64,7 +64,9 @@ export function StepWrapper({ isButtonDisabled={isFormSubmitting} buttonClick={() => navigate( - !isSubDAO || !daoAddress ? BASE_ROUTES.landing : DAO_ROUTES.dao.relative(daoAddress), + !isSubDAO || !daoAddress || !daoNetwork + ? BASE_ROUTES.landing + : DAO_ROUTES.dao.relative(daoNetwork, daoAddress), ) } /> diff --git a/src/components/ProposalTemplates/ProposalTemplateCard.tsx b/src/components/ProposalTemplates/ProposalTemplateCard.tsx index d6c4d3a100..493fb09943 100644 --- a/src/components/ProposalTemplates/ProposalTemplateCard.tsx +++ b/src/components/ProposalTemplates/ProposalTemplateCard.tsx @@ -26,7 +26,7 @@ export default function ProposalTemplateCard({ const navigate = useNavigate(); const { t } = useTranslation('proposalTemplate'); const { - node: { safe, daoAddress }, + node: { safe, daoAddress, daoNetwork }, } = useFractal(); const { prepareRemoveProposalTemplateProposal } = useRemoveProposalTemplate(); @@ -42,11 +42,11 @@ export default function ProposalTemplateCard({ }); const successCallback = useCallback(() => { - if (daoAddress) { + if (daoAddress && daoNetwork) { // Redirecting to proposals page so that user will see Proposal for Proposal Template creation - navigate(DAO_ROUTES.proposals.relative(daoAddress)); + navigate(DAO_ROUTES.proposals.relative(daoNetwork, daoAddress)); } - }, [navigate, daoAddress]); + }, [navigate, daoAddress, daoNetwork]); const nonce = safe?.nonce; const handleRemoveTemplate = useCallback(async () => { diff --git a/src/components/ProposalTemplates/index.tsx b/src/components/ProposalTemplates/index.tsx index c20e69f5d8..7cf24fa072 100644 --- a/src/components/ProposalTemplates/index.tsx +++ b/src/components/ProposalTemplates/index.tsx @@ -11,7 +11,7 @@ import ProposalTemplateCard from './ProposalTemplateCard'; export default function ProposalTemplates() { const { t } = useTranslation('proposalTemplate'); const { - node: { daoAddress }, + node: { daoAddress, daoNetwork }, governance: { proposalTemplates }, } = useFractal(); const { canUserCreateProposal } = useSubmitProposal(); @@ -36,8 +36,8 @@ export default function ProposalTemplates() { )) ) : ( - {canUserCreateProposal && ( - + {canUserCreateProposal && daoNetwork && daoAddress && ( + )} - {canUserCreateProposal && ( - + {canUserCreateProposal && daoNetwork && daoAddress && ( + )} - {canUserCreateProposal && daoNetwork && daoAddress && ( - + {canUserCreateProposal && daoAddress && ( +