From 43e3903d8bae04ab2757d23986b01c33b95c972d Mon Sep 17 00:00:00 2001 From: schmanu Date: Thu, 14 Nov 2024 14:12:39 +0700 Subject: [PATCH 1/2] feat: offer modal to synchronize Safe setups --- src/components/tx-flow/SafeTxProvider.tsx | 1 - .../SignOrExecuteForm/SignOrExecuteForm.tsx | 2 - .../InconsistentSignerSetupWarning.tsx | 43 ++++-- .../ReviewSynchronizeSignersStep.tsx | 42 ++++++ .../SelectNetworkStep.tsx | 124 ++++++++++++++++++ .../SynchronizeSignersFlow/index.tsx | 33 +++++ .../multichain/hooks/useSafeCreationData.ts | 1 - src/features/multichain/utils/utils.ts | 2 +- 8 files changed, 232 insertions(+), 16 deletions(-) create mode 100644 src/features/multichain/components/SynchronizeSignersFlow/ReviewSynchronizeSignersStep.tsx create mode 100644 src/features/multichain/components/SynchronizeSignersFlow/SelectNetworkStep.tsx create mode 100644 src/features/multichain/components/SynchronizeSignersFlow/index.tsx diff --git a/src/components/tx-flow/SafeTxProvider.tsx b/src/components/tx-flow/SafeTxProvider.tsx index f83c86663a..d197dc195f 100644 --- a/src/components/tx-flow/SafeTxProvider.tsx +++ b/src/components/tx-flow/SafeTxProvider.tsx @@ -84,7 +84,6 @@ const SafeTxProvider = ({ children }: { children: ReactNode }): ReactElement => createTx({ ...safeTx.data, safeTxGas: String(finalSafeTxGas) }, finalNonce) .then((tx) => { - console.log('SafeTxProvider: Updated tx with nonce and safeTxGas', tx) setSafeTx(tx) }) .catch(setSafeTxError) diff --git a/src/components/tx/SignOrExecuteForm/SignOrExecuteForm.tsx b/src/components/tx/SignOrExecuteForm/SignOrExecuteForm.tsx index 98bf41fada..65a1d3e37d 100644 --- a/src/components/tx/SignOrExecuteForm/SignOrExecuteForm.tsx +++ b/src/components/tx/SignOrExecuteForm/SignOrExecuteForm.tsx @@ -97,8 +97,6 @@ export const SignOrExecuteForm = ({ const isNewExecutableTx = useImmediatelyExecutable() && isCreation const isCorrectNonce = useValidateNonce(safeTx) - console.log(props.txDetails) - // TODO: move it to the confirmation view // const showTxDetails = // !isAnyStakingTxInfo(txDetails.txInfo) && diff --git a/src/features/multichain/components/SignerSetupWarning/InconsistentSignerSetupWarning.tsx b/src/features/multichain/components/SignerSetupWarning/InconsistentSignerSetupWarning.tsx index 33a5417525..95cc32a5b8 100644 --- a/src/features/multichain/components/SignerSetupWarning/InconsistentSignerSetupWarning.tsx +++ b/src/features/multichain/components/SignerSetupWarning/InconsistentSignerSetupWarning.tsx @@ -6,10 +6,12 @@ import { useAppSelector } from '@/store' import { selectCurrency, selectUndeployedSafes, useGetMultipleSafeOverviewsQuery } from '@/store/slices' import { useAllSafesGrouped } from '@/components/welcome/MyAccounts/useAllSafesGrouped' import { sameAddress } from '@/utils/addresses' -import { useMemo } from 'react' +import { useContext, useMemo } from 'react' import { getDeviatingSetups, getSafeSetups } from '@/features/multichain/utils/utils' -import { Box, Typography } from '@mui/material' +import { Box, Button, Stack, Typography } from '@mui/material' import ChainIndicator from '@/components/common/ChainIndicator' +import { TxModalContext } from '@/components/tx-flow' +import { SynchronizeSetupsFlow } from '../SynchronizeSignersFlow' const ChainIndicatorList = ({ chainIds }: { chainIds: string[] }) => { const { configs } = useChains() @@ -54,20 +56,39 @@ export const InconsistentSignerSetupWarning = () => { () => getSafeSetups(multiChainGroupSafes, safeOverviews ?? [], undeployedSafes), [multiChainGroupSafes, safeOverviews, undeployedSafes], ) - const deviatingSetups = getDeviatingSetups(safeSetups, currentChain?.chainId) - const deviatingChainIds = deviatingSetups.map((setup) => setup?.chainId) + + const deviatingSetups = useMemo( + () => getDeviatingSetups(safeSetups, currentChain?.chainId), + [currentChain?.chainId, safeSetups], + ) + const deviatingChainIds = useMemo(() => { + return deviatingSetups.map((setup) => setup?.chainId) + }, [deviatingSetups]) + + const { setTxFlow } = useContext(TxModalContext) + + const onClick = () => { + setTxFlow() + } if (!isMultichainSafe || !deviatingChainIds.length) return return ( - - Signers are different on these networks of this account: - - - - To manage your account easier and to prevent lose of funds, we recommend keeping the same signers. - + + + + Signers are different on these networks of this account: + + + + To manage your account easier and to prevent lose of funds, we recommend keeping the same signers. + + + + ) } diff --git a/src/features/multichain/components/SynchronizeSignersFlow/ReviewSynchronizeSignersStep.tsx b/src/features/multichain/components/SynchronizeSignersFlow/ReviewSynchronizeSignersStep.tsx new file mode 100644 index 0000000000..8cd21bc8d5 --- /dev/null +++ b/src/features/multichain/components/SynchronizeSignersFlow/ReviewSynchronizeSignersStep.tsx @@ -0,0 +1,42 @@ +import { SafeTxContext } from '@/components/tx-flow/SafeTxProvider' +import SignOrExecuteForm from '@/components/tx/SignOrExecuteForm' +import useSafeInfo from '@/hooks/useSafeInfo' +import { useContext, useEffect } from 'react' +import { type SynchronizeSetupsData } from '.' +import { type SafeSetup } from '../../utils/utils' +import { getRecoveryProposalTransactions } from '@/features/recovery/services/transaction' +import { createMultiSendCallOnlyTx, createTx } from '@/services/tx/tx-sender' + +export const ReviewSynchronizeSignersStep = ({ + data, + setups, +}: { + data: SynchronizeSetupsData + setups: SafeSetup[] +}) => { + const { setSafeTx, setSafeTxError } = useContext(SafeTxContext) + + const { safe } = useSafeInfo() + + useEffect(() => { + const selectedSetup = setups.find((setup) => setup.chainId === data.selectedChain) + if (!selectedSetup) { + // TODO: handle error + return + } + + const transactions = getRecoveryProposalTransactions({ + safe, + newThreshold: selectedSetup.threshold, + newOwners: selectedSetup.owners.map((owner) => ({ + value: owner, + })), + }) + + const promisedSafeTx = transactions.length > 1 ? createMultiSendCallOnlyTx(transactions) : createTx(transactions[0]) + + promisedSafeTx.then(setSafeTx).catch(setSafeTxError) + }, [safe, setSafeTx, setSafeTxError, setups, data.selectedChain]) + + return +} diff --git a/src/features/multichain/components/SynchronizeSignersFlow/SelectNetworkStep.tsx b/src/features/multichain/components/SynchronizeSignersFlow/SelectNetworkStep.tsx new file mode 100644 index 0000000000..7738df84f3 --- /dev/null +++ b/src/features/multichain/components/SynchronizeSignersFlow/SelectNetworkStep.tsx @@ -0,0 +1,124 @@ +import TxCard from '@/components/tx-flow/common/TxCard' +import { type SynchronizeSetupsData } from '.' +import { FormProvider, useForm, useFormContext } from 'react-hook-form' +import { Box, Button, ButtonBase, CardActions, Divider, Radio, Stack, Typography } from '@mui/material' +import { type SafeSetup } from '../../utils/utils' +import SafeIcon from '@/components/common/SafeIcon' +import useSafeAddress from '@/hooks/useSafeAddress' +import useAllAddressBooks from '@/hooks/useAllAddressBooks' +import { useChain } from '@/hooks/useChains' +import ChainIndicator from '@/components/common/ChainIndicator' +import { shortenAddress } from '@/utils/formatters' +import commonCss from '@/components/tx-flow/common/styles.module.css' + +const SingleSafeSetup = ({ + setup, + selected, + onSelect, +}: { + setup: SafeSetup + selected: boolean + onSelect: () => void +}) => { + const safeAddress = useSafeAddress() + const addressBooks = useAllAddressBooks() + const safeName = addressBooks[setup.chainId]?.[safeAddress] + const chain = useChain(setup.chainId) + return ( + + + + + + + + + {safeName && ( + + {safeName} + + )} + {chain?.shortName}: + + {shortenAddress(safeAddress)} + + + + + + ) +} + +const SetupSelector = ({ setups, selectedChain }: { setups: SafeSetup[]; selectedChain: string | null }) => { + const { setValue } = useFormContext() + return ( + + {setups.map((setup) => ( + setValue('selectedChain', setup.chainId)} + /> + ))} + + ) +} + +export const SelectNetworkStep = ({ + onSubmit, + data, + deviatingSetups, +}: { + onSubmit: (data: SynchronizeSetupsData) => void + data: SynchronizeSetupsData + deviatingSetups: SafeSetup[] +}) => { + const formMethods = useForm({ + defaultValues: data, + mode: 'all', + }) + + const { handleSubmit, watch } = formMethods + + const onFormSubmit = handleSubmit((formData: SynchronizeSetupsData) => { + onSubmit(formData) + }) + + const selectedChain = watch('selectedChain') + + return ( + + +
+ + Select network + + + + + + + + +
+
+ ) +} diff --git a/src/features/multichain/components/SynchronizeSignersFlow/index.tsx b/src/features/multichain/components/SynchronizeSignersFlow/index.tsx new file mode 100644 index 0000000000..03f767e65d --- /dev/null +++ b/src/features/multichain/components/SynchronizeSignersFlow/index.tsx @@ -0,0 +1,33 @@ +import TxLayout from '@/components/tx-flow/common/TxLayout' +import useTxStepper from '@/components/tx-flow/useTxStepper' +import { SelectNetworkStep } from './SelectNetworkStep' +import { type SafeSetup } from '../../utils/utils' +import { ReviewSynchronizeSignersStep } from './ReviewSynchronizeSignersStep' + +export type SynchronizeSetupsData = { + selectedChain: string | null +} + +export const SynchronizeSetupsFlow = ({ deviatingSetups }: { deviatingSetups: SafeSetup[] }) => { + const { data, step, nextStep, prevStep } = useTxStepper({ selectedChain: null }) + + const steps = [ + nextStep({ ...data, ...formData })} + data={data} + deviatingSetups={deviatingSetups} + />, + , + ] + return ( + + {steps} + + ) +} diff --git a/src/features/multichain/hooks/useSafeCreationData.ts b/src/features/multichain/hooks/useSafeCreationData.ts index d4c60013f6..c590dee19d 100644 --- a/src/features/multichain/hooks/useSafeCreationData.ts +++ b/src/features/multichain/hooks/useSafeCreationData.ts @@ -174,7 +174,6 @@ export const useSafeCreationData = (safeAddress: string, chains: ChainInfo[]): A try { const creationData = await getCreationDataForChain(chain, undeployedSafe, safeAddress, customRpc) - console.log({ creationData }) return creationData } catch (err) { lastError = asError(err) diff --git a/src/features/multichain/utils/utils.ts b/src/features/multichain/utils/utils.ts index 1f60254d53..02e06b4c61 100644 --- a/src/features/multichain/utils/utils.ts +++ b/src/features/multichain/utils/utils.ts @@ -12,7 +12,7 @@ import { FEATURES, hasFeature } from '@/utils/chains' import { type SafeItem } from '@/components/welcome/MyAccounts/useAllSafes' import { type MultiChainSafeItem } from '@/components/welcome/MyAccounts/useAllSafesGrouped' -type SafeSetup = { +export type SafeSetup = { owners: string[] threshold: number chainId: string From 8a76b0212e86c16f677f7602058f1a529614d6d2 Mon Sep 17 00:00:00 2001 From: schmanu Date: Thu, 14 Nov 2024 15:24:09 +0700 Subject: [PATCH 2/2] fix: adjust modal design slightly --- .../SynchronizeSignersFlow/SelectNetworkStep.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/features/multichain/components/SynchronizeSignersFlow/SelectNetworkStep.tsx b/src/features/multichain/components/SynchronizeSignersFlow/SelectNetworkStep.tsx index 7738df84f3..ee56e31bcd 100644 --- a/src/features/multichain/components/SynchronizeSignersFlow/SelectNetworkStep.tsx +++ b/src/features/multichain/components/SynchronizeSignersFlow/SelectNetworkStep.tsx @@ -33,6 +33,7 @@ const SingleSafeSetup = ({ justifyContent: 'start', flexDirection: 'row', gap: '1', + border: ({ palette }) => `1px solid ${palette.border.light}`, }} onClick={onSelect} > @@ -44,6 +45,7 @@ const SingleSafeSetup = ({ owners={setup.owners.length} threshold={setup.threshold} chainId={setup.chainId} + size={32} /> @@ -59,7 +61,7 @@ const SingleSafeSetup = ({ - + ) } @@ -67,7 +69,7 @@ const SingleSafeSetup = ({ const SetupSelector = ({ setups, selectedChain }: { setups: SafeSetup[]; selectedChain: string | null }) => { const { setValue } = useFormContext() return ( - + {setups.map((setup) => (
- - Select network + + + This action copies the setup from another Safe account with the same address. + + Select Setup to copy