From 5707413d9010f73c10da1b9ee60733f3c9bebf81 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 26 Nov 2024 03:04:59 -0500 Subject: [PATCH 01/26] add new DAOInfo typing --- src/types/fractal.ts | 47 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/types/fractal.ts b/src/types/fractal.ts index ad96d0d7a..59069d4fc 100644 --- a/src/types/fractal.ts +++ b/src/types/fractal.ts @@ -124,6 +124,50 @@ export enum FractalProposalState { CLOSED = 'stateClosed', } +export type NullUnionUndefined = null | undefined; + +export type GnosisSafe = { + // replaces SafeInfoResponseWithGuard and SafeWithNextNonce + address: Address | NullUnionUndefined; + owners: Address[]; + nonce: number; + nextNonce: number; + threshold: number; + modules: Address[]; + guard: Address | NullUnionUndefined; +}; + +export interface DAOSubgraph { + // replaces Part of DaoInfo + daoName: string | NullUnionUndefined; + parentAddress: Address | NullUnionUndefined; + childNodes: Address[]; + daoSnapshotENS: string | NullUnionUndefined; + proposalTemplatesHash: string | NullUnionUndefined; +} + +// @todo should we add other Decent Module types here? +export enum DecentModuleType { + // replaces FractalModuleType + AZORIUS, // Token Module + FRACTAL, // CHILD GOVERNANCE MODULE + UNKNOWN, // NON-DECENT MODULE +} + +export interface DecentModule { + // replaces FractalModuleData + address: Address; + type: FractalModuleType; +} + +// @todo better typing here, SUBGRAPH has DAO type name, +export interface IDAO { + // replaces DaoInfo + safe: GnosisSafe | null; + subgraphInfo: DAOSubgraph | null; + daoModules: DecentModule[] | null; +} + export interface GovernanceActivity extends ActivityBase { state: FractalProposalState | null; proposalId: string; @@ -184,7 +228,7 @@ export interface FractalGovernanceContracts { isLoaded: boolean; } -export type SafeWithNextNonce = SafeInfoResponseWithGuard & { address: Address; nextNonce: number }; +export type SafeWithNextNonce = SafeInfoResponseWithGuard & { nextNonce: number }; // @dev Information retreived from subgraph interface SubgraphDAOInfo { @@ -224,6 +268,7 @@ export enum FractalModuleType { FRACTAL, UNKNOWN, } + export interface FractalGuardContracts { freezeGuardContractAddress?: Address; freezeVotingContractAddress?: Address; From 638640e2411cb97f79b54fbbd0e9b68e3e25adbc Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 26 Nov 2024 03:05:05 -0500 Subject: [PATCH 02/26] refactor: update DAOInfo structure and improve type handling in SafeController --- src/pages/dao/SafeController.tsx | 7 ++-- src/providers/App/hooks/useSafeAPI.ts | 2 - src/store/daoInfo/useDaoInfoStore.ts | 55 +++++++++++++++++---------- src/types/safeGlobal.ts | 4 +- 4 files changed, 39 insertions(+), 29 deletions(-) diff --git a/src/pages/dao/SafeController.tsx b/src/pages/dao/SafeController.tsx index 0d7dc3285..0d45a6550 100644 --- a/src/pages/dao/SafeController.tsx +++ b/src/pages/dao/SafeController.tsx @@ -26,9 +26,7 @@ export function SafeController() { useTemporaryProposals(); useAutomaticSwitchChain({ addressPrefix }); - const { - nodeHierarchy: { parentAddress }, - } = useDaoInfoStore(); + const { subgraphInfo } = useDaoInfoStore(); const { errorLoading } = useFractalNode({ addressPrefix, @@ -37,7 +35,8 @@ export function SafeController() { useGovernanceContracts(); useFractalGuardContracts({}); - useFractalFreeze({ parentSafeAddress: parentAddress }); + // @todo - Maybe update typing to be parentSafeAddress: Address | null | undefined + useFractalFreeze({ parentSafeAddress: subgraphInfo?.parentAddress ?? null }); useFractalGovernance(); useDecentTreasury(); useERC20Claim(); diff --git a/src/providers/App/hooks/useSafeAPI.ts b/src/providers/App/hooks/useSafeAPI.ts index 16f4636ae..014e08dca 100644 --- a/src/providers/App/hooks/useSafeAPI.ts +++ b/src/providers/App/hooks/useSafeAPI.ts @@ -90,8 +90,6 @@ class EnhancedSafeApiKit extends SafeApiKit { const safeInfo = { ...safeInfoResponse, nextNonce, - address: getAddress(safeInfoResponse.address), - guard: getAddress(safeInfoResponse.guard), }; return safeInfo; } diff --git a/src/store/daoInfo/useDaoInfoStore.ts b/src/store/daoInfo/useDaoInfoStore.ts index 5a46f0988..1c5b786e6 100644 --- a/src/store/daoInfo/useDaoInfoStore.ts +++ b/src/store/daoInfo/useDaoInfoStore.ts @@ -1,39 +1,54 @@ +import { getAddress } from 'viem'; import { create } from 'zustand'; -import { DaoInfo, SafeWithNextNonce } from '../../types'; +import { DAOSubgraph, DecentModule, IDAO, SafeWithNextNonce } from '../../types'; -type DAOInfoWithoutLoaders = Omit; - -export const initialDaoInfoStore: DaoInfo = { - daoName: null, +export const initialDaoInfoStore: IDAO = { safe: null, - fractalModules: [], - nodeHierarchy: { - parentAddress: null, - childNodes: [], - }, - isHierarchyLoaded: false, - isModulesLoaded: false, + subgraphInfo: null, + daoModules: null, }; -export interface DaoInfoStore extends DaoInfo { - setSafeInfo: (safeWithNonce: SafeWithNextNonce) => void; - setDaoInfo: (daoInfo: DAOInfoWithoutLoaders) => void; +export interface DaoInfoStore extends IDAO { + setSafeInfo: (safe: SafeWithNextNonce) => void; + setDaoInfo: (daoInfo: DAOSubgraph) => void; + setDecentModules: (modules: DecentModule[]) => void; updateDaoName: (newDaoName: string) => void; resetDaoInfoStore: () => void; } export const useDaoInfoStore = create()(set => ({ ...initialDaoInfoStore, - setSafeInfo: (safeWithNonce: SafeWithNextNonce) => { - set({ safe: safeWithNonce }); + setSafeInfo: (safe: SafeWithNextNonce) => { + const { address, owners, nonce, nextNonce, threshold, modules, guard } = safe; + set({ + safe: { + owners: owners.map(getAddress), + modules: modules.map(getAddress), + guard: getAddress(guard), + address: getAddress(address), + nextNonce, + threshold, + nonce, + }, + }); }, // called by subgraph data flow - setDaoInfo: (daoInfo: DAOInfoWithoutLoaders) => { - set({ ...daoInfo, isHierarchyLoaded: true, isModulesLoaded: true }); + setDaoInfo: (subgraphInfo: DAOSubgraph) => { + set({ subgraphInfo }); }, + setDecentModules: (daoModules: DecentModule[]) => { + set({ daoModules }); + }, updateDaoName: (newDaoName: string) => { - set({ daoName: newDaoName }); + set(state => { + if (!state.subgraphInfo) { + throw new Error('Subgraph info is not set'); + } + return { + subgraphInfo: { ...state.subgraphInfo, daoName: newDaoName }, + }; + }); }, resetDaoInfoStore: () => set(initialDaoInfoStore), })); diff --git a/src/types/safeGlobal.ts b/src/types/safeGlobal.ts index d4e7cea45..aa9115450 100644 --- a/src/types/safeGlobal.ts +++ b/src/types/safeGlobal.ts @@ -1,5 +1,4 @@ import { SafeInfoResponse } from '@safe-global/api-kit'; -import { Address } from 'viem'; export declare enum Operation { CALL = 0, @@ -30,6 +29,5 @@ export declare type DataDecoded = { export type SafeInfoResponseWithGuard = SafeInfoResponse & { nextNonce: number; - guard?: Address; - address: Address; + guard: string; }; From 7fa5c292273bfb04dfa763d26fc43328c7f840f4 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 26 Nov 2024 03:08:28 -0500 Subject: [PATCH 03/26] refactor: replace nodeHierarchy with subgraphInfo in DAO components and hooks --- .../formComponents/AzoriusGovernance.tsx | 8 ++------ .../DaoDashboard/Info/ParentLink.tsx | 6 +++--- .../ui/menus/ManageDAO/ManageDAOMenu.tsx | 18 ++++++++++++------ .../DAO/proposal/useUserERC721VotingTokens.ts | 4 ++-- src/hooks/DAO/useCastFreezeVote.ts | 8 +++----- src/hooks/DAO/useClawBack.ts | 2 +- src/hooks/DAO/useDeployAzorius.ts | 15 ++++++--------- src/models/AzoriusTxBuilder.ts | 2 +- src/models/BaseTxBuilder.ts | 4 ++-- src/models/DaoTxBuilder.ts | 2 +- src/models/TxBuilderFactory.ts | 2 +- src/models/helpers/fractalModuleData.ts | 2 +- 12 files changed, 35 insertions(+), 38 deletions(-) diff --git a/src/components/DaoCreator/formComponents/AzoriusGovernance.tsx b/src/components/DaoCreator/formComponents/AzoriusGovernance.tsx index c7acda7f7..e5a7ea4a2 100644 --- a/src/components/DaoCreator/formComponents/AzoriusGovernance.tsx +++ b/src/components/DaoCreator/formComponents/AzoriusGovernance.tsx @@ -25,11 +25,7 @@ import { DAOCreateMode } from './EstablishEssentials'; export function AzoriusGovernance(props: ICreationStepProps) { const { values, setFieldValue, isSubmitting, transactionPending, isSubDAO, mode } = props; - const { - safe, - nodeHierarchy: { parentAddress }, - fractalModules, - } = useDaoInfoStore(); + const { safe, subgraphInfo, fractalModules } = useDaoInfoStore(); const fractalModule = useMemo( () => fractalModules.find(_module => _module.moduleType === FractalModuleType.FRACTAL), @@ -189,7 +185,7 @@ export function AzoriusGovernance(props: ICreationStepProps) { - {!!parentAddress && ( + {!!subgraphInfo?.parentAddress && ( { + getUserERC721VotingTokens(dao.subgraphInfo?.parentAddress, null).then(tokensInfo => { if (!guardContracts.freezeVotingContractAddress) { throw new Error('freeze voting contract address not set'); } @@ -105,7 +105,13 @@ export function ManageDAOMenu() { } }, }), - [getUserERC721VotingTokens, guardContracts, node.nodeHierarchy.parentAddress, walletClient], + [ + dao.subgraphInfo?.parentAddress, + getUserERC721VotingTokens, + guardContracts.freezeVotingContractAddress, + guardContracts.freezeVotingType, + walletClient, + ], ); const options = useMemo(() => { diff --git a/src/hooks/DAO/proposal/useUserERC721VotingTokens.ts b/src/hooks/DAO/proposal/useUserERC721VotingTokens.ts index a0b3b298e..0957b5ea6 100644 --- a/src/hooks/DAO/proposal/useUserERC721VotingTokens.ts +++ b/src/hooks/DAO/proposal/useUserERC721VotingTokens.ts @@ -19,7 +19,7 @@ import useVotingStrategiesAddresses from '../../utils/useVotingStrategiesAddress * @returns {string[]} `remainingTokenAddresses` - same as `totalVotingTokenAddresses` - repeats contract address of NFT for each token ID in `remainingTokenIds` array. */ export default function useUserERC721VotingTokens( - safeAddress: Address | null, + safeAddress: Address | null | undefined, proposalId: string | null, loadOnMount: boolean = true, ) { @@ -45,7 +45,7 @@ export default function useUserERC721VotingTokens( const globalContextSafeAddress = safe?.address; const getUserERC721VotingTokens = useCallback( - async (_safeAddress: Address | null, _proposalId: number | null) => { + async (_safeAddress: Address | null | undefined, _proposalId: number | null) => { const totalTokenAddresses: Address[] = []; const totalTokenIds: string[] = []; const tokenAddresses: Address[] = []; diff --git a/src/hooks/DAO/useCastFreezeVote.ts b/src/hooks/DAO/useCastFreezeVote.ts index c43874376..0cdbec385 100644 --- a/src/hooks/DAO/useCastFreezeVote.ts +++ b/src/hooks/DAO/useCastFreezeVote.ts @@ -14,9 +14,7 @@ export const useCastFreezeVote = () => { const { guardContracts: { freezeVotingContractAddress, freezeVotingType }, } = useFractal(); - const { - nodeHierarchy: { parentAddress }, - } = useDaoInfoStore(); + const { subgraphInfo } = useDaoInfoStore(); const { getUserERC721VotingTokens } = useUserERC721VotingTokens(null, null, false); const { t } = useTranslation('transaction'); @@ -35,7 +33,7 @@ export const useCastFreezeVote = () => { address: freezeVotingContractAddress, client: walletClient, }); - return getUserERC721VotingTokens(parentAddress, null).then(tokensInfo => { + return getUserERC721VotingTokens(subgraphInfo?.parentAddress, null).then(tokensInfo => { return freezeERC721VotingContract.write.castFreezeVote([ tokensInfo.totalVotingTokenAddresses, tokensInfo.totalVotingTokenIds.map(i => BigInt(i)), @@ -86,7 +84,7 @@ export const useCastFreezeVote = () => { freezeVotingContractAddress, freezeVotingType, getUserERC721VotingTokens, - parentAddress, + subgraphInfo?.parentAddress, t, walletClient, ]); diff --git a/src/hooks/DAO/useClawBack.ts b/src/hooks/DAO/useClawBack.ts index 203cccf2c..6d2030bc1 100644 --- a/src/hooks/DAO/useClawBack.ts +++ b/src/hooks/DAO/useClawBack.ts @@ -20,7 +20,7 @@ import useSubmitProposal from './proposal/useSubmitProposal'; interface IUseClawBack { childSafeInfo: DaoInfo; - parentAddress: Address | null; + parentAddress: Address | null | undefined; } export default function useClawBack({ childSafeInfo, parentAddress }: IUseClawBack) { diff --git a/src/hooks/DAO/useDeployAzorius.ts b/src/hooks/DAO/useDeployAzorius.ts index 6b74ab05c..ba112d4ef 100644 --- a/src/hooks/DAO/useDeployAzorius.ts +++ b/src/hooks/DAO/useDeployAzorius.ts @@ -53,10 +53,7 @@ const useDeployAzorius = () => { }, addressPrefix, } = useNetworkConfig(); - const { - safe, - nodeHierarchy: { parentAddress }, - } = useDaoInfoStore(); + const { safe, subgraphInfo } = useDaoInfoStore(); const { t } = useTranslation(['transaction', 'proposalMetadata']); const { submitProposal } = useSubmitProposal(); @@ -86,8 +83,8 @@ const useDeployAzorius = () => { let attachFractalModule = false; let parentNode: DaoInfo | undefined; - if (parentAddress) { - const loadedParentNode = await loadDao(parentAddress); + if (subgraphInfo?.parentAddress) { + const loadedParentNode = await loadDao(subgraphInfo.parentAddress); const loadingParentNodeError = (loadedParentNode as WithError).error; if (loadingParentNodeError) { toast.error(t(loadingParentNodeError)); @@ -154,7 +151,7 @@ const useDeployAzorius = () => { linearVotingErc20MasterCopy, linearVotingErc721MasterCopy, moduleAzoriusMasterCopy, - parentAddress || undefined, + subgraphInfo?.parentAddress ?? undefined, parentTokenAddress, ); @@ -216,7 +213,7 @@ const useDeployAzorius = () => { canUserCreateProposal, safe, publicClient, - parentAddress, + subgraphInfo?.parentAddress, compatibilityFallbackHandler, votesErc20WrapperMasterCopy, votesErc20MasterCopy, @@ -238,9 +235,9 @@ const useDeployAzorius = () => { submitProposal, t, loadDao, + getAddressContractType, navigate, addressPrefix, - getAddressContractType, ], ); diff --git a/src/models/AzoriusTxBuilder.ts b/src/models/AzoriusTxBuilder.ts index f2aa85d58..9eaa140ca 100644 --- a/src/models/AzoriusTxBuilder.ts +++ b/src/models/AzoriusTxBuilder.ts @@ -73,7 +73,7 @@ export class AzoriusTxBuilder extends BaseTxBuilder { linearVotingErc721MasterCopy: Address, moduleAzoriusMasterCopy: Address, - parentAddress?: Address, + parentAddress?: Address | null, parentTokenAddress?: Address, ) { super(publicClient, true, daoData, parentAddress, parentTokenAddress); diff --git a/src/models/BaseTxBuilder.ts b/src/models/BaseTxBuilder.ts index df8f3f5cc..d6c143c00 100644 --- a/src/models/BaseTxBuilder.ts +++ b/src/models/BaseTxBuilder.ts @@ -5,14 +5,14 @@ export class BaseTxBuilder { protected readonly publicClient: PublicClient; protected readonly isAzorius: boolean; protected readonly daoData: SafeMultisigDAO | AzoriusERC20DAO | AzoriusERC721DAO | SubDAO; - protected readonly parentAddress?: Address; + protected readonly parentAddress?: Address | null; protected readonly parentTokenAddress?: Address; constructor( publicClient: PublicClient, isAzorius: boolean, daoData: SafeMultisigDAO | AzoriusERC20DAO | AzoriusERC721DAO | SubDAO, - parentAddress?: Address, + parentAddress?: Address | null, parentTokenAddress?: Address, ) { this.publicClient = publicClient; diff --git a/src/models/DaoTxBuilder.ts b/src/models/DaoTxBuilder.ts index 9d69c1fe8..32d0319c7 100644 --- a/src/models/DaoTxBuilder.ts +++ b/src/models/DaoTxBuilder.ts @@ -53,7 +53,7 @@ export class DaoTxBuilder extends BaseTxBuilder { attachFractalModule?: boolean, - parentAddress?: Address, + parentAddress?: Address | null, parentTokenAddress?: Address, parentStrategyType?: VotingStrategyType, diff --git a/src/models/TxBuilderFactory.ts b/src/models/TxBuilderFactory.ts index 38856448d..02e89fce9 100644 --- a/src/models/TxBuilderFactory.ts +++ b/src/models/TxBuilderFactory.ts @@ -65,7 +65,7 @@ export class TxBuilderFactory extends BaseTxBuilder { linearVotingErc20MasterCopy: Address, linearVotingErc721MasterCopy: Address, moduleAzoriusMasterCopy: Address, - parentAddress?: Address, + parentAddress?: Address | null, parentTokenAddress?: Address, ) { super(publicClient, isAzorius, daoData, parentAddress, parentTokenAddress); diff --git a/src/models/helpers/fractalModuleData.ts b/src/models/helpers/fractalModuleData.ts index 54c6898c7..2dd24a55b 100644 --- a/src/models/helpers/fractalModuleData.ts +++ b/src/models/helpers/fractalModuleData.ts @@ -25,7 +25,7 @@ export const fractalModuleData = ( moduleProxyFactoryAddress: Address, safeAddress: Address, saltNum: bigint, - parentAddress?: Address, + parentAddress?: Address | null, ): FractalModuleData => { const fractalModuleCalldata = encodeFunctionData({ abi: abis.FractalModule, From ebe87c19cc461441c26263b654f2e97a04703e15 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 26 Nov 2024 07:06:34 -0500 Subject: [PATCH 04/26] refactor: remove unused hooks related to Fractal modules and DAO loading --- ...eFractalModules.ts => useDecentModules.ts} | 2 +- src/hooks/DAO/loaders/useLoadDAONode.ts | 86 ------------------- src/hooks/DAO/useDAOData.ts | 60 ------------- src/types/daoGeneral.ts | 35 -------- 4 files changed, 1 insertion(+), 182 deletions(-) rename src/hooks/DAO/loaders/{useFractalModules.ts => useDecentModules.ts} (97%) delete mode 100644 src/hooks/DAO/loaders/useLoadDAONode.ts delete mode 100644 src/hooks/DAO/useDAOData.ts diff --git a/src/hooks/DAO/loaders/useFractalModules.ts b/src/hooks/DAO/loaders/useDecentModules.ts similarity index 97% rename from src/hooks/DAO/loaders/useFractalModules.ts rename to src/hooks/DAO/loaders/useDecentModules.ts index 8d6185b56..19ad9fe43 100644 --- a/src/hooks/DAO/loaders/useFractalModules.ts +++ b/src/hooks/DAO/loaders/useDecentModules.ts @@ -4,7 +4,7 @@ import { usePublicClient } from 'wagmi'; import { FractalModuleData, FractalModuleType } from '../../../types'; import { useAddressContractType } from '../../utils/useAddressContractType'; -export const useFractalModules = () => { +export const useDecentModules = () => { const { getAddressContractType } = useAddressContractType(); const publicClient = usePublicClient(); const lookupModules = useCallback( diff --git a/src/hooks/DAO/loaders/useLoadDAONode.ts b/src/hooks/DAO/loaders/useLoadDAONode.ts deleted file mode 100644 index 4d18dc961..000000000 --- a/src/hooks/DAO/loaders/useLoadDAONode.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { useLazyQuery } from '@apollo/client'; -import { useCallback } from 'react'; -import { Address, getAddress, isAddress } from 'viem'; -import { DAO, DAOQueryDocument } from '../../../../.graphclient'; -import { useSafeAPI } from '../../../providers/App/hooks/useSafeAPI'; -import { useNetworkConfig } from '../../../providers/NetworkConfig/NetworkConfigProvider'; -import { DaoInfo } from '../../../types'; -import { useGetSafeName } from '../../utils/useGetSafeName'; -import { useFractalModules } from './useFractalModules'; - -type DAOInfoWithoutLoaders = Omit; - -export const useLoadDAONode = () => { - const safeAPI = useSafeAPI(); - const { getSafeName } = useGetSafeName(); - const lookupModules = useFractalModules(); - const { subgraph } = useNetworkConfig(); - const [getDAOInfo] = useLazyQuery(DAOQueryDocument, { - context: { - subgraphSpace: subgraph.space, - subgraphSlug: subgraph.slug, - subgraphVersion: subgraph.version, - }, - }); - - const getDAOWithHierarchyData = useCallback( - async (dao: DAO) => { - const childData: DAOInfoWithoutLoaders[] = []; - for await (const child of dao.hierarchy) { - const safe = await safeAPI.getSafeData(getAddress(child.address)); - childData.push({ - fractalModules: [], - safe: safe, - nodeHierarchy: { - parentAddress: isAddress(child.parentAddress) ? getAddress(child.parentAddress) : null, - childNodes: await getDAOWithHierarchyData(child), - }, - daoName: child.name ?? null, - daoSnapshotENS: child.snapshotENS ?? undefined, - }); - } - return childData; - }, - [safeAPI], - ); - - const loadDao = useCallback( - async (safeAddress: Address): Promise => { - if (safeAPI) { - try { - const graphRawNodeData = await getDAOInfo({ variables: { safeAddress } }); - const graphDAOData = graphRawNodeData.data?.daos[0]; - if (!graphRawNodeData || !graphDAOData) { - throw new Error('No data found'); - } - const currentSafe = await safeAPI.getSafeData(safeAddress); - const daoWithHierarchyData: DAOInfoWithoutLoaders = { - fractalModules: await lookupModules(currentSafe.modules), - safe: currentSafe, - nodeHierarchy: { - parentAddress: isAddress(graphDAOData.parentAddress) - ? getAddress(graphDAOData.parentAddress) - : null, - childNodes: await getDAOWithHierarchyData(graphDAOData), - }, - daoName: graphDAOData.name ?? (await getSafeName(currentSafe.address)), - daoSnapshotENS: - graphDAOData.snapshotENS === null || graphDAOData.snapshotENS === undefined - ? undefined - : graphDAOData.snapshotENS, - }; - return daoWithHierarchyData; - } catch (e) { - // @note There is a recurring error (AbortError: signal is aborted without reason) - // related to the getDAOInfo query. This error does not seem to affect the app's functionality. - throw new Error('Error loading DAO'); - } - } else { - throw new Error('SafeAPI not set'); - } - }, - [getDAOInfo, getDAOWithHierarchyData, getSafeName, lookupModules, safeAPI], - ); - - return { loadDao }; -}; diff --git a/src/hooks/DAO/useDAOData.ts b/src/hooks/DAO/useDAOData.ts deleted file mode 100644 index fa53c0f83..000000000 --- a/src/hooks/DAO/useDAOData.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { useEffect, useState } from 'react'; -import { Address } from 'viem'; -import { initialGuardState } from '../../providers/App/guard/reducer'; -import { initialGuardContractsState } from '../../providers/App/guardContracts/reducer'; -import { DaoInfo } from '../../types'; -import { DAOData } from '../../types/daoGeneral'; -import { FractalGuardContracts } from '../../types/fractal'; -import { useFractalFreeze } from './loaders/useFractalFreeze'; -import { useFractalGuardContracts } from './loaders/useFractalGuardContracts'; - -/** - * A hook for loading guard and freeze guard contract data for the provided - * FractalNode. - */ -export function useLoadDAOData(parentSafeAddress: Address | null, fractalNode?: DaoInfo) { - const [daoData, setDAOData] = useState(); - const loadFractalGuardContracts = useFractalGuardContracts({ loadOnMount: false }); - const loadFractalFreezeGuard = useFractalFreeze({ - loadOnMount: false, - parentSafeAddress, - }); - - useEffect(() => { - const loadDAOData = async () => { - if (!fractalNode) { - return; - } - const { safe, fractalModules } = fractalNode; - - if (!safe?.address) { - return; - } - - let freezeGuardContracts: FractalGuardContracts | undefined = await loadFractalGuardContracts( - safe, - fractalModules, - ); - - if (!freezeGuardContracts) { - freezeGuardContracts = initialGuardContractsState; - } - let freezeGuard = await loadFractalFreezeGuard(freezeGuardContracts); - - if (!freezeGuard) { - freezeGuard = initialGuardState; - } - - setDAOData({ - safe, - fractalModules, - freezeGuardContracts: freezeGuardContracts, - freezeGuard, - }); - }; - loadDAOData(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return { daoData }; -} diff --git a/src/types/daoGeneral.ts b/src/types/daoGeneral.ts index 140970ac0..b81d287a2 100644 --- a/src/types/daoGeneral.ts +++ b/src/types/daoGeneral.ts @@ -1,38 +1,3 @@ -import { - FractalModuleData, - FractalGuardContracts, - FreezeGuard, - SafeWithNextNonce, -} from './fractal'; - -export type DAOData = { - safe: SafeWithNextNonce; - fractalModules?: FractalModuleData[]; - freezeGuardContracts: FractalGuardContracts; - freezeGuard: FreezeGuard; -}; - -export type DAOMetadata = { - address: string; - logo: string; - headerBackground: string; - bodyBackground?: string; - links: { - title: string; - url: string; - }[]; - sections: { - title: string; - content: string; - background?: string; - link?: { - url: string; - text: string; - position: 'start' | 'end'; - }; - }[]; -}; - export enum DAOState { freezeInit = 'stateFreezeInit', frozen = 'stateFrozen', From 3582df25c8a75c5624132b9cb50a614d80eaeb57 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 26 Nov 2024 07:06:56 -0500 Subject: [PATCH 05/26] refactor: update useFractalNode to use subgraph queries and improve DAO data handling --- src/hooks/DAO/loaders/useFractalNode.ts | 37 ++++++++++++++++++------- src/store/daoInfo/useDaoInfoStore.ts | 6 ++-- src/types/fractal.ts | 12 ++------ 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/hooks/DAO/loaders/useFractalNode.ts b/src/hooks/DAO/loaders/useFractalNode.ts index 160faa1bd..4ec43432a 100644 --- a/src/hooks/DAO/loaders/useFractalNode.ts +++ b/src/hooks/DAO/loaders/useFractalNode.ts @@ -1,8 +1,10 @@ +import { useLazyQuery } from '@apollo/client'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { Address } from 'viem'; +import { Address, getAddress, isAddress } from 'viem'; +import { DAOQueryDocument } from '../../../../.graphclient'; import { useFractal } from '../../../providers/App/AppProvider'; +import { useNetworkConfig } from '../../../providers/NetworkConfig/NetworkConfigProvider'; import { useDaoInfoStore } from '../../../store/daoInfo/useDaoInfoStore'; -import { useLoadDAONode } from './useLoadDAONode'; export const useFractalNode = ({ addressPrefix, @@ -14,11 +16,18 @@ export const useFractalNode = ({ // tracks the current valid Safe address and chain id; helps prevent unnecessary calls const currentValidSafe = useRef(); const [errorLoading, setErrorLoading] = useState(false); + const { subgraph } = useNetworkConfig(); + const [getDAOInfo] = useLazyQuery(DAOQueryDocument, { + context: { + subgraphSpace: subgraph.space, + subgraphSlug: subgraph.slug, + subgraphVersion: subgraph.version, + }, + }); const { action } = useFractal(); const { setDaoInfo } = useDaoInfoStore(); - const { loadDao } = useLoadDAONode(); const reset = useCallback( ({ error }: { error: boolean }) => { @@ -35,19 +44,27 @@ export const useFractalNode = ({ setErrorLoading(false); try { - const daoInfo = await loadDao(safeAddress); - if (!daoInfo.safe) { - throw new Error('Invalid Safe'); + const graphRawNodeData = await getDAOInfo({ variables: { safeAddress } }); + const graphDAOData = graphRawNodeData.data?.daos[0]; + if (!graphRawNodeData || !graphDAOData) { + throw new Error('No data found'); } - setDaoInfo(daoInfo); + + setDaoInfo({ + parentAddress: isAddress(graphDAOData.parentAddress) + ? getAddress(graphDAOData.parentAddress) + : null, + childAddresses: graphDAOData.hierarchy.map(child => getAddress(child.address)), + daoName: graphDAOData.name, + daoSnapshotENS: graphDAOData.snapshotENS, + proposalTemplatesHash: graphDAOData.proposalTemplatesHash, + }); } catch (e) { - // TODO: this is the thing causing an error when - // trying to load a DAO with a valid address which is not a Safe reset({ error: true }); return; } } - }, [addressPrefix, safeAddress, loadDao, setDaoInfo, reset]); + }, [addressPrefix, safeAddress, getDAOInfo, setDaoInfo, reset]); useEffect(() => { if (`${addressPrefix}${safeAddress}` !== currentValidSafe.current) { diff --git a/src/store/daoInfo/useDaoInfoStore.ts b/src/store/daoInfo/useDaoInfoStore.ts index 1c5b786e6..513a59303 100644 --- a/src/store/daoInfo/useDaoInfoStore.ts +++ b/src/store/daoInfo/useDaoInfoStore.ts @@ -1,6 +1,6 @@ import { getAddress } from 'viem'; import { create } from 'zustand'; -import { DAOSubgraph, DecentModule, IDAO, SafeWithNextNonce } from '../../types'; +import { DAOSubgraph, FractalModuleData, IDAO, SafeWithNextNonce } from '../../types'; export const initialDaoInfoStore: IDAO = { safe: null, @@ -10,7 +10,7 @@ export const initialDaoInfoStore: IDAO = { export interface DaoInfoStore extends IDAO { setSafeInfo: (safe: SafeWithNextNonce) => void; setDaoInfo: (daoInfo: DAOSubgraph) => void; - setDecentModules: (modules: DecentModule[]) => void; + setDecentModules: (modules: FractalModuleData[]) => void; updateDaoName: (newDaoName: string) => void; resetDaoInfoStore: () => void; } @@ -37,7 +37,7 @@ export const useDaoInfoStore = create()(set => ({ set({ subgraphInfo }); }, - setDecentModules: (daoModules: DecentModule[]) => { + setDecentModules: (daoModules: FractalModuleData[]) => { set({ daoModules }); }, updateDaoName: (newDaoName: string) => { diff --git a/src/types/fractal.ts b/src/types/fractal.ts index 59069d4fc..59fd733bb 100644 --- a/src/types/fractal.ts +++ b/src/types/fractal.ts @@ -128,7 +128,7 @@ export type NullUnionUndefined = null | undefined; export type GnosisSafe = { // replaces SafeInfoResponseWithGuard and SafeWithNextNonce - address: Address | NullUnionUndefined; + address: Address; owners: Address[]; nonce: number; nextNonce: number; @@ -141,7 +141,7 @@ export interface DAOSubgraph { // replaces Part of DaoInfo daoName: string | NullUnionUndefined; parentAddress: Address | NullUnionUndefined; - childNodes: Address[]; + childAddresses: Address[]; daoSnapshotENS: string | NullUnionUndefined; proposalTemplatesHash: string | NullUnionUndefined; } @@ -154,18 +154,12 @@ export enum DecentModuleType { UNKNOWN, // NON-DECENT MODULE } -export interface DecentModule { - // replaces FractalModuleData - address: Address; - type: FractalModuleType; -} - // @todo better typing here, SUBGRAPH has DAO type name, export interface IDAO { // replaces DaoInfo safe: GnosisSafe | null; subgraphInfo: DAOSubgraph | null; - daoModules: DecentModule[] | null; + daoModules: FractalModuleData[] | null; } export interface GovernanceActivity extends ActivityBase { From 0ca063ceb5d9b89adc7c406fb99988e2723958cd Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 26 Nov 2024 07:07:01 -0500 Subject: [PATCH 06/26] refactor: rename fractalModules to daoModules and update related references across components and hooks --- .../formComponents/AzoriusGovernance.tsx | 10 ++--- .../SafeSettings/SettingsNavigation.tsx | 4 +- .../ui/menus/ManageDAO/ManageDAOMenu.tsx | 25 +++++++---- .../DAO/loaders/useFractalGuardContracts.ts | 20 ++++----- .../DAO/loaders/useGovernanceContracts.ts | 10 ++--- src/hooks/DAO/proposal/useSubmitProposal.ts | 17 ++++---- src/hooks/DAO/useClawBack.ts | 15 ++++--- src/hooks/DAO/useDeployAzorius.ts | 42 +++++++++++++------ src/hooks/utils/useUpdateSafeData.ts | 8 ++-- .../utils/useVotingStrategiesAddresses.ts | 11 +++-- src/i18n/locales/en/transaction.json | 5 ++- .../SafeModulesSettingsPage.tsx | 8 ++-- 12 files changed, 105 insertions(+), 70 deletions(-) diff --git a/src/components/DaoCreator/formComponents/AzoriusGovernance.tsx b/src/components/DaoCreator/formComponents/AzoriusGovernance.tsx index e5a7ea4a2..4e0b84e16 100644 --- a/src/components/DaoCreator/formComponents/AzoriusGovernance.tsx +++ b/src/components/DaoCreator/formComponents/AzoriusGovernance.tsx @@ -25,12 +25,12 @@ import { DAOCreateMode } from './EstablishEssentials'; export function AzoriusGovernance(props: ICreationStepProps) { const { values, setFieldValue, isSubmitting, transactionPending, isSubDAO, mode } = props; - const { safe, subgraphInfo, fractalModules } = useDaoInfoStore(); + const { safe, subgraphInfo, daoModules } = useDaoInfoStore(); - const fractalModule = useMemo( - () => fractalModules.find(_module => _module.moduleType === FractalModuleType.FRACTAL), - [fractalModules], - ); + const fractalModule = useMemo(() => { + if (!daoModules) return null; + return daoModules.find(_module => _module.moduleType === FractalModuleType.FRACTAL); + }, [daoModules]); const [showCustomNonce, setShowCustomNonce] = useState(); const { t } = useTranslation(['daoCreate', 'common']); diff --git a/src/components/SafeSettings/SettingsNavigation.tsx b/src/components/SafeSettings/SettingsNavigation.tsx index 5c942b14a..39b8e1c11 100644 --- a/src/components/SafeSettings/SettingsNavigation.tsx +++ b/src/components/SafeSettings/SettingsNavigation.tsx @@ -88,7 +88,7 @@ export default function SettingsNavigation() { const { t } = useTranslation('settings'); const { addressPrefix } = useNetworkConfig(); const { governance } = useFractal(); - const { safe, fractalModules } = useDaoInfoStore(); + const { safe, daoModules } = useDaoInfoStore(); const azoriusGovernance = governance as AzoriusGovernance; return ( @@ -143,7 +143,7 @@ export default function SettingsNavigation() { textStyle="body-base" color="neutral-7" > - {fractalModules.length + safe.guard ? 1 : 0} + {(daoModules ?? []).length + (safe?.guard ? 1 : 0)} module.moduleType === FractalModuleType.FRACTAL, ); if (fractalModule) { @@ -183,18 +186,22 @@ export function ManageDAOMenu() { return optionsArr; } }, [ - guard, - currentTime, - navigate, - safeAddress, - type, handleClawBack, - canUserCreateProposal, handleModifyGovernance, handleNavigateToSettings, + guard.freezeProposalCreatedTime, + guard.freezeProposalPeriod, + guard.freezePeriod, + guard.userHasVotes, + guard.isFrozen, + currentTime, + safeAddress, + navigate, addressPrefix, + type, freezeOption, - node.fractalModules, + dao.daoModules, + canUserCreateProposal, ]); return ( diff --git a/src/hooks/DAO/loaders/useFractalGuardContracts.ts b/src/hooks/DAO/loaders/useFractalGuardContracts.ts index 1b7637131..2e86772d9 100644 --- a/src/hooks/DAO/loaders/useFractalGuardContracts.ts +++ b/src/hooks/DAO/loaders/useFractalGuardContracts.ts @@ -6,16 +6,16 @@ import { useFractal } from '../../../providers/App/AppProvider'; import { GuardContractAction } from '../../../providers/App/guardContracts/action'; import { useNetworkConfig } from '../../../providers/NetworkConfig/NetworkConfigProvider'; import { useDaoInfoStore } from '../../../store/daoInfo/useDaoInfoStore'; -import { FreezeGuardType, FreezeVotingType, SafeInfoResponseWithGuard } from '../../../types'; +import { FreezeGuardType, FreezeVotingType } from '../../../types'; import { useAddressContractType } from '../../utils/useAddressContractType'; -import { FractalModuleData, FractalModuleType } from './../../../types/fractal'; +import { FractalModuleData, FractalModuleType, GnosisSafe } from './../../../types/fractal'; export const useFractalGuardContracts = ({ loadOnMount = true }: { loadOnMount?: boolean }) => { // load key for component; helps prevent unnecessary calls const loadKey = useRef(); const { action } = useFractal(); - const { fractalModules, safe, isHierarchyLoaded } = useDaoInfoStore(); + const { daoModules, safe, subgraphInfo } = useDaoInfoStore(); const safeAddress = safe?.address; @@ -26,7 +26,7 @@ export const useFractalGuardContracts = ({ loadOnMount = true }: { loadOnMount?: const publicClient = usePublicClient(); const loadFractalGuardContracts = useCallback( - async (_safe: SafeInfoResponseWithGuard, _fractalModules: FractalModuleData[]) => { + async (_safe: GnosisSafe, _fractalModules: FractalModuleData[]) => { if (!publicClient) { return; } @@ -124,19 +124,19 @@ export const useFractalGuardContracts = ({ loadOnMount = true }: { loadOnMount?: ); const setGuardContracts = useCallback(async () => { - if (!safe) return; - const contracts = await loadFractalGuardContracts(safe, fractalModules); + if (!safe || daoModules === null) return; + const contracts = await loadFractalGuardContracts(safe, daoModules); if (!contracts) return; action.dispatch({ type: GuardContractAction.SET_GUARD_CONTRACT, payload: contracts }); - }, [action, safe, fractalModules, loadFractalGuardContracts]); + }, [action, safe, daoModules, loadFractalGuardContracts]); useEffect(() => { if ( loadOnMount && safeAddress && safeAddress + chain.id !== loadKey.current && - isHierarchyLoaded && - safe + subgraphInfo !== null && + safe !== null ) { loadKey.current = safeAddress + chain.id; setGuardContracts(); @@ -145,6 +145,6 @@ export const useFractalGuardContracts = ({ loadOnMount = true }: { loadOnMount?: if (!safeAddress) { loadKey.current = undefined; } - }, [setGuardContracts, isHierarchyLoaded, loadOnMount, chain, safeAddress, safe]); + }, [setGuardContracts, subgraphInfo, loadOnMount, chain, safeAddress, safe]); return loadFractalGuardContracts; }; diff --git a/src/hooks/DAO/loaders/useGovernanceContracts.ts b/src/hooks/DAO/loaders/useGovernanceContracts.ts index bec6c2849..06b4768c3 100644 --- a/src/hooks/DAO/loaders/useGovernanceContracts.ts +++ b/src/hooks/DAO/loaders/useGovernanceContracts.ts @@ -19,12 +19,12 @@ export const useGovernanceContracts = () => { const { getAddressContractType } = useAddressContractType(); const { getVotingStrategies } = useVotingStrategyAddress(); - const { fractalModules, isModulesLoaded, safe } = node; + const { daoModules, safe } = node; const safeAddress = safe?.address; const loadGovernanceContracts = useCallback(async () => { - const azoriusModule = getAzoriusModuleFromModules(fractalModules); + const azoriusModule = getAzoriusModuleFromModules(daoModules ?? []); const votingStrategies = await getVotingStrategies(); @@ -137,15 +137,15 @@ export const useGovernanceContracts = () => { }, }); } - }, [action, fractalModules, getVotingStrategies, publicClient, getAddressContractType]); + }, [action, daoModules, getVotingStrategies, publicClient, getAddressContractType]); useEffect(() => { - if (currentValidAddress.current !== safeAddress && isModulesLoaded) { + if (currentValidAddress.current !== safeAddress && daoModules !== null) { loadGovernanceContracts(); currentValidAddress.current = safeAddress; } if (!safeAddress) { currentValidAddress.current = null; } - }, [isModulesLoaded, loadGovernanceContracts, safeAddress]); + }, [daoModules, loadGovernanceContracts, safeAddress]); }; diff --git a/src/hooks/DAO/proposal/useSubmitProposal.ts b/src/hooks/DAO/proposal/useSubmitProposal.ts index a274265de..7a2206b7e 100644 --- a/src/hooks/DAO/proposal/useSubmitProposal.ts +++ b/src/hooks/DAO/proposal/useSubmitProposal.ts @@ -26,7 +26,7 @@ import { useDaoInfoStore } from '../../../store/daoInfo/useDaoInfoStore'; import { CreateProposalMetadata, MetaTransaction, ProposalExecuteData } from '../../../types'; import { buildSafeApiUrl, getAzoriusModuleFromModules } from '../../../utils'; import useVotingStrategiesAddresses from '../../utils/useVotingStrategiesAddresses'; -import { useFractalModules } from '../loaders/useFractalModules'; +import { useDecentModules } from '../loaders/useDecentModules'; import { useLoadDAOProposals } from '../loaders/useLoadDAOProposals'; export type SubmitProposalFunction = ({ @@ -49,7 +49,7 @@ interface ISubmitProposal { /** * @param safeAddress - provided address of DAO to which proposal will be submitted */ - safeAddress?: Address; + safeAddress?: Address | null; } interface ISubmitAzoriusProposal extends ISubmitProposal { @@ -77,12 +77,15 @@ export default function useSubmitProposal() { }, action, } = useFractal(); - const { safe, fractalModules } = useDaoInfoStore(); + const { safe, daoModules } = useDaoInfoStore(); const safeAPI = useSafeAPI(); const globalAzoriusContract = useMemo(() => { - const azoriusModule = getAzoriusModuleFromModules(fractalModules); - if (!azoriusModule || !walletClient) { + if (!daoModules || !walletClient) { + return; + } + const azoriusModule = getAzoriusModuleFromModules(daoModules); + if (!azoriusModule) { return; } @@ -91,9 +94,9 @@ export default function useSubmitProposal() { address: azoriusModule.moduleAddress, client: walletClient, }); - }, [fractalModules, walletClient]); + }, [daoModules, walletClient]); - const lookupModules = useFractalModules(); + const lookupModules = useDecentModules(); const { chain, safeBaseURL, diff --git a/src/hooks/DAO/useClawBack.ts b/src/hooks/DAO/useClawBack.ts index 6d2030bc1..868cf71f4 100644 --- a/src/hooks/DAO/useClawBack.ts +++ b/src/hooks/DAO/useClawBack.ts @@ -13,13 +13,16 @@ import { import { logError } from '../../helpers/errorLogging'; import useBalancesAPI from '../../providers/App/hooks/useBalancesAPI'; import { useSafeAPI } from '../../providers/App/hooks/useSafeAPI'; -import { FractalModuleType, DaoInfo } from '../../types'; +import { FractalModuleType, FractalModuleData } from '../../types'; import { MOCK_MORALIS_ETH_ADDRESS } from '../../utils/address'; import { useCanUserCreateProposal } from '../utils/useCanUserSubmitProposal'; import useSubmitProposal from './proposal/useSubmitProposal'; interface IUseClawBack { - childSafeInfo: DaoInfo; + childSafeInfo: { + daoAddress?: Address; + daoModules: FractalModuleData[] | null; + }; parentAddress: Address | null | undefined; } @@ -31,9 +34,9 @@ export default function useClawBack({ childSafeInfo, parentAddress }: IUseClawBa const { getTokenBalances } = useBalancesAPI(); const handleClawBack = useCallback(async () => { - if (childSafeInfo.safe?.address && parentAddress && safeAPI) { + if (childSafeInfo.daoAddress && parentAddress && safeAPI) { try { - const childSafeTokenBalance = await getTokenBalances(childSafeInfo.safe.address); + const childSafeTokenBalance = await getTokenBalances(childSafeInfo.daoAddress); if (childSafeTokenBalance.error || !childSafeTokenBalance.data) { toast.error(t('clawBackBalancesError', { duration: Infinity })); @@ -48,8 +51,8 @@ export default function useClawBack({ childSafeInfo, parentAddress }: IUseClawBa const parentSafeInfo = await safeAPI.getSafeData(parentAddress); const canUserCreateProposal = await getCanUserCreateProposal(parentAddress); - if (canUserCreateProposal && parentAddress && parentSafeInfo) { - const fractalModule = childSafeInfo.fractalModules!.find( + if (canUserCreateProposal && parentAddress && parentSafeInfo && childSafeInfo.daoModules) { + const fractalModule = childSafeInfo.daoModules!.find( module => module.moduleType === FractalModuleType.FRACTAL, ); diff --git a/src/hooks/DAO/useDeployAzorius.ts b/src/hooks/DAO/useDeployAzorius.ts index ba112d4ef..d310dabef 100644 --- a/src/hooks/DAO/useDeployAzorius.ts +++ b/src/hooks/DAO/useDeployAzorius.ts @@ -10,21 +10,21 @@ import MultiSendCallOnlyAbi from '../../assets/abi/MultiSendCallOnly'; import { SENTINEL_ADDRESS } from '../../constants/common'; import { DAO_ROUTES } from '../../constants/routes'; import { TxBuilderFactory } from '../../models/TxBuilderFactory'; +import { useSafeAPI } from '../../providers/App/hooks/useSafeAPI'; import { useNetworkConfig } from '../../providers/NetworkConfig/NetworkConfigProvider'; import { useDaoInfoStore } from '../../store/daoInfo/useDaoInfoStore'; import { AzoriusERC20DAO, AzoriusERC721DAO, FractalModuleType, - DaoInfo, ProposalExecuteData, SubDAO, VotingStrategyType, - WithError, } from '../../types'; import { useAddressContractType } from '../utils/useAddressContractType'; import { useCanUserCreateProposal } from '../utils/useCanUserSubmitProposal'; -import { useLoadDAONode } from './loaders/useLoadDAONode'; +import { FractalModuleData } from './../../types/fractal'; +import { useDecentModules } from './loaders/useDecentModules'; import useSubmitProposal from './proposal/useSubmitProposal'; const useDeployAzorius = () => { @@ -60,7 +60,24 @@ const useDeployAzorius = () => { const { canUserCreateProposal } = useCanUserCreateProposal(); const publicClient = usePublicClient(); - const { loadDao } = useLoadDAONode(); + const safeApi = useSafeAPI(); + const lookupModules = useDecentModules(); + + const getParentDAOModules = useCallback( + async (address: Address) => { + try { + if (!safeApi) { + throw new Error('Safe API not ready'); + } + const safeInfo = await safeApi.getSafeData(address); + const modules = await lookupModules(safeInfo.modules); + return modules; + } catch { + return; + } + }, + [lookupModules, safeApi], + ); const safeAddress = safe?.address; const deployAzorius = useCallback( @@ -81,17 +98,16 @@ const useDeployAzorius = () => { let parentStrategyAddress: Address | undefined; let parentStrategyType: VotingStrategyType | undefined; let attachFractalModule = false; - let parentNode: DaoInfo | undefined; + let parentModules: FractalModuleData[]; if (subgraphInfo?.parentAddress) { - const loadedParentNode = await loadDao(subgraphInfo.parentAddress); - const loadingParentNodeError = (loadedParentNode as WithError).error; - if (loadingParentNodeError) { - toast.error(t(loadingParentNodeError)); + const loadedParentModule = await getParentDAOModules(subgraphInfo.parentAddress); + if (!loadedParentModule) { + toast.error(t('errorLoadingParentNode')); return; } else { - parentNode = loadedParentNode as DaoInfo; - const parentAzoriusModule = parentNode.fractalModules.find( + parentModules = loadedParentModule; + const parentAzoriusModule = parentModules.find( fractalModule => fractalModule.moduleType === FractalModuleType.AZORIUS, ); if (parentAzoriusModule) { @@ -119,7 +135,7 @@ const useDeployAzorius = () => { } } - const parentFractalModule = parentNode.fractalModules.find( + const parentFractalModule = parentModules.find( fractalModule => fractalModule.moduleType === FractalModuleType.FRACTAL, ); if (!parentFractalModule) { @@ -234,7 +250,7 @@ const useDeployAzorius = () => { moduleAzoriusMasterCopy, submitProposal, t, - loadDao, + getParentDAOModules, getAddressContractType, navigate, addressPrefix, diff --git a/src/hooks/utils/useUpdateSafeData.ts b/src/hooks/utils/useUpdateSafeData.ts index 9f8722235..5a8ee3225 100644 --- a/src/hooks/utils/useUpdateSafeData.ts +++ b/src/hooks/utils/useUpdateSafeData.ts @@ -3,13 +3,14 @@ import { useLocation } from 'react-router-dom'; import { Address } from 'viem'; import { useSafeAPI } from '../../providers/App/hooks/useSafeAPI'; import { useDaoInfoStore } from '../../store/daoInfo/useDaoInfoStore'; +import { useDecentModules } from '../DAO/loaders/useDecentModules'; export const useUpdateSafeData = (safeAddress?: Address) => { const safeAPI = useSafeAPI(); const location = useLocation(); const prevPathname = useRef(location.pathname); - - const { setSafeInfo } = useDaoInfoStore(); + const lookupModules = useDecentModules(); + const { setSafeInfo, setDecentModules } = useDaoInfoStore(); useEffect(() => { if (!safeAPI || !safeAddress) { @@ -23,8 +24,9 @@ export const useUpdateSafeData = (safeAddress?: Address) => { const safeInfo = await safeAPI.getSafeData(safeAddress); setSafeInfo(safeInfo); + setDecentModules(await lookupModules(safeInfo.modules)); })(); prevPathname.current = location.pathname; } - }, [safeAddress, safeAPI, location, setSafeInfo]); + }, [safeAddress, safeAPI, location, setSafeInfo, setDecentModules, lookupModules]); }; diff --git a/src/hooks/utils/useVotingStrategiesAddresses.ts b/src/hooks/utils/useVotingStrategiesAddresses.ts index 638c94b51..5dd4bf052 100644 --- a/src/hooks/utils/useVotingStrategiesAddresses.ts +++ b/src/hooks/utils/useVotingStrategiesAddresses.ts @@ -7,7 +7,7 @@ import { useSafeAPI } from '../../providers/App/hooks/useSafeAPI'; import { useDaoInfoStore } from '../../store/daoInfo/useDaoInfoStore'; import { FractalModuleData } from '../../types'; import { getAzoriusModuleFromModules } from '../../utils'; -import { useFractalModules } from '../DAO/loaders/useFractalModules'; +import { useDecentModules } from '../DAO/loaders/useDecentModules'; import { useAddressContractType } from './useAddressContractType'; const useVotingStrategiesAddresses = () => { @@ -15,7 +15,7 @@ const useVotingStrategiesAddresses = () => { const publicClient = usePublicClient(); const safeAPI = useSafeAPI(); const { getAddressContractType } = useAddressContractType(); - const lookupModules = useFractalModules(); + const lookupModules = useDecentModules(); const getVotingStrategies = useCallback( async (safeAddress?: Address) => { @@ -29,7 +29,10 @@ const useVotingStrategiesAddresses = () => { const safeModules = await lookupModules(safeInfo.modules); azoriusModule = getAzoriusModuleFromModules(safeModules); } else { - azoriusModule = getAzoriusModuleFromModules(node.fractalModules); + if (!node.daoModules) { + throw new Error('DAO modules not ready'); + } + azoriusModule = getAzoriusModuleFromModules(node.daoModules); } if (!azoriusModule || !publicClient) { @@ -59,7 +62,7 @@ const useVotingStrategiesAddresses = () => { ); return result; }, - [lookupModules, getAddressContractType, node.fractalModules, publicClient, safeAPI], + [lookupModules, getAddressContractType, node.daoModules, publicClient, safeAPI], ); return { getVotingStrategies }; diff --git a/src/i18n/locales/en/transaction.json b/src/i18n/locales/en/transaction.json index 648d92cb6..56bf2d94e 100644 --- a/src/i18n/locales/en/transaction.json +++ b/src/i18n/locales/en/transaction.json @@ -19,5 +19,6 @@ "failedTokenClaim": "Failed to claim {{ symbol }}", "successTokenClaim": "You have successfully claimed {{ amount }} {{ symbol }}", "snapshotRecastVoteHelper": "You can still change your vote until the voting period ends.", - "modifyGovernanceSetAzoriusProposalPendingMessage": "Submitting proposal to modify governance..." -} + "modifyGovernanceSetAzoriusProposalPendingMessage": "Submitting proposal to modify governance...", + "errorLoadingParentNode": "Error loading parent" +} \ No newline at end of file diff --git a/src/pages/dao/settings/modules-and-guard/SafeModulesSettingsPage.tsx b/src/pages/dao/settings/modules-and-guard/SafeModulesSettingsPage.tsx index 87301d679..e8838598a 100644 --- a/src/pages/dao/settings/modules-and-guard/SafeModulesSettingsPage.tsx +++ b/src/pages/dao/settings/modules-and-guard/SafeModulesSettingsPage.tsx @@ -19,7 +19,7 @@ export function SafeModulesSettingsPage() { const { guardContracts: { freezeGuardContractAddress, freezeVotingContractAddress }, } = useFractal(); - const { fractalModules, isModulesLoaded, safe } = useDaoInfoStore(); + const { daoModules, safe } = useDaoInfoStore(); return ( <> @@ -42,9 +42,9 @@ export function SafeModulesSettingsPage() { w={{ base: 'calc(100% + 1.5rem)', md: 'calc(100% + 3rem)' }} mx={{ base: '-0.75rem', md: '-1.5rem' }} /> - {isModulesLoaded ? ( - fractalModules.length > 0 ? ( - fractalModules.map(({ moduleAddress, moduleType }) => { + {daoModules !== null ? ( + daoModules.length > 0 ? ( + daoModules.map(({ moduleAddress, moduleType }) => { const moduleHelper = moduleType === FractalModuleType.AZORIUS ? ' (Azorius Module)' From 1f540b694bcb3a66a674d51218d7a0793b18814d Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Wed, 27 Nov 2024 07:29:36 -0500 Subject: [PATCH 07/26] refactor: update useDaoInfoStore to utilize subgraphInfo and adjust related components for improved data handling --- .../formComponents/EstablishEssentials.tsx | 27 ++++++++++++----- .../MultisigProposalDetails/TxActions.tsx | 4 +-- src/components/Proposals/ProposalInfo.tsx | 6 ++-- src/components/Proposals/index.tsx | 6 ++-- src/components/ui/cards/DAOInfoCard.tsx | 8 +++-- .../ui/modals/ForkProposalTemplateModal.tsx | 7 +++-- src/components/ui/page/Header/PageHeader.tsx | 7 +++-- .../loaders/snapshot/useSnapshotProposal.ts | 11 +++++-- .../loaders/snapshot/useSnapshotProposals.ts | 18 +++++++----- src/hooks/DAO/proposal/useCastSnapshotVote.ts | 10 +++---- src/hooks/utils/useCreateRoles.ts | 6 ++-- src/hooks/utils/usePageTitle.ts | 8 ++--- src/i18n/locales/en/transaction.json | 2 +- .../governance/SafeEditGovernancePage.tsx | 8 ++--- src/pages/dao/hierarchy/SafeHierarchyPage.tsx | 10 ++----- src/pages/dao/settings/SafeSettingsPage.tsx | 4 +-- .../general/SafeGeneralSettingsPage.tsx | 29 ++++++++++++------- src/pages/dao/treasury/SafeTreasuryPage.tsx | 4 +-- 18 files changed, 103 insertions(+), 72 deletions(-) diff --git a/src/components/DaoCreator/formComponents/EstablishEssentials.tsx b/src/components/DaoCreator/formComponents/EstablishEssentials.tsx index 042d356b4..d06ad2612 100644 --- a/src/components/DaoCreator/formComponents/EstablishEssentials.tsx +++ b/src/components/DaoCreator/formComponents/EstablishEssentials.tsx @@ -24,27 +24,38 @@ export function EstablishEssentials(props: ICreationStepProps) { const { t } = useTranslation(['daoCreate', 'common']); const { values, setFieldValue, isSubmitting, transactionPending, isSubDAO, errors, mode } = props; - const { daoName, daoSnapshotENS, safe } = useDaoInfoStore(); + const { subgraphInfo, safe } = useDaoInfoStore(); const isEdit = mode === DAOCreateMode.EDIT; const safeAddress = safe?.address; useEffect(() => { - if (isEdit) { - setFieldValue('essentials.daoName', daoName, false); - if (safeAddress && createAccountSubstring(safeAddress) !== daoName) { + if (isEdit && subgraphInfo) { + setFieldValue('essentials.daoName', subgraphInfo.daoName, false); + if (safeAddress && createAccountSubstring(safeAddress) !== subgraphInfo.daoName) { // Pre-fill the snapshot URL form field when editing - setFieldValue('essentials.snapshotENS', daoSnapshotENS || '', false); + setFieldValue('essentials.snapshotENS', subgraphInfo.daoSnapshotENS || '', false); } } - }, [setFieldValue, mode, daoName, daoSnapshotENS, isEdit, safeAddress]); + }, [ + setFieldValue, + mode, + subgraphInfo?.daoName, + subgraphInfo?.daoSnapshotENS, + isEdit, + safeAddress, + subgraphInfo, + ]); const daoNameDisabled = - isEdit && !!daoName && !!safeAddress && createAccountSubstring(safeAddress) !== daoName; + isEdit && + !!subgraphInfo?.daoName && + !!safeAddress && + createAccountSubstring(safeAddress) !== subgraphInfo?.daoName; // If in governance edit mode and snapshot URL is already set, disable the field - const snapshotENSDisabled = isEdit && !!daoSnapshotENS; + const snapshotENSDisabled = isEdit && !!subgraphInfo?.daoSnapshotENS; const handleGovernanceChange = (value: string) => { if (value === GovernanceType.AZORIUS_ERC20) { diff --git a/src/components/Proposals/MultisigProposalDetails/TxActions.tsx b/src/components/Proposals/MultisigProposalDetails/TxActions.tsx index 7eef7242d..216c3146d 100644 --- a/src/components/Proposals/MultisigProposalDetails/TxActions.tsx +++ b/src/components/Proposals/MultisigProposalDetails/TxActions.tsx @@ -2,7 +2,7 @@ import { Box, Button, Text, Flex } from '@chakra-ui/react'; import { abis } from '@fractal-framework/fractal-contracts'; import { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { getAddress, getContract, isHex } from 'viem'; +import { getAddress, getContract, isHex, zeroAddress } from 'viem'; import { useAccount, useWalletClient } from 'wagmi'; import GnosisSafeL2Abi from '../../../assets/abi/GnosisSafeL2'; import { Check } from '../../../assets/theme/custom/icons/Check'; @@ -50,7 +50,7 @@ export function TxActions({ proposal }: { proposal: MultisigProposal }) { const { loadSafeMultisigProposals } = useSafeMultisigProposals(); const { data: walletClient } = useWalletClient(); - const isOwner = safe?.owners?.includes(userAccount.address || ''); + const isOwner = safe?.owners?.includes(userAccount.address ?? zeroAddress); if (!isOwner) return null; diff --git a/src/components/Proposals/ProposalInfo.tsx b/src/components/Proposals/ProposalInfo.tsx index 3abfeda4d..dbe0237e4 100644 --- a/src/components/Proposals/ProposalInfo.tsx +++ b/src/components/Proposals/ProposalInfo.tsx @@ -23,7 +23,7 @@ export function ProposalInfo({ }) { const metaData = useGetMetadata(proposal); const { t } = useTranslation('proposal'); - const { daoSnapshotENS } = useDaoInfoStore(); + const { subgraphInfo } = useDaoInfoStore(); const { snapshotProposal } = useSnapshotProposal(proposal); const [modalType, props] = useMemo(() => { @@ -62,10 +62,10 @@ export function ProposalInfo({ showIcon={false} textColor="neutral-7" /> - {snapshotProposal && ( + {snapshotProposal && subgraphInfo && ( <> {(proposal as ExtendedSnapshotProposal).privacy === 'shutter' && (