diff --git a/src/components/new-safe/create/AdvancedCreateSafe.tsx b/src/components/new-safe/create/AdvancedCreateSafe.tsx index 1cd1a12ff6..5dd08899bb 100644 --- a/src/components/new-safe/create/AdvancedCreateSafe.tsx +++ b/src/components/new-safe/create/AdvancedCreateSafe.tsx @@ -1,3 +1,4 @@ +import { ECOSYSTEM_ID_ADDRESS } from '@/config/constants' import { Container, Typography, Grid } from '@mui/material' import { useRouter } from 'next/router' @@ -98,6 +99,7 @@ const AdvancedCreateSafe = () => { threshold: 1, saltNonce: 0, safeVersion: getLatestSafeVersion(chain), + paymentReceiver: ECOSYSTEM_ID_ADDRESS, } const onClose = () => { diff --git a/src/components/new-safe/create/index.tsx b/src/components/new-safe/create/index.tsx index 52a29f56ec..788abf45b7 100644 --- a/src/components/new-safe/create/index.tsx +++ b/src/components/new-safe/create/index.tsx @@ -28,10 +28,11 @@ export type NewSafeFormData = { networks: ChainInfo[] threshold: number owners: NamedAddress[] - saltNonce: number + saltNonce?: number safeVersion: SafeVersion safeAddress?: string willRelay?: boolean + paymentReceiver?: string } const staticHints: Record< @@ -173,7 +174,6 @@ const CreateSafe = () => { networks: [], owners: [], threshold: 1, - saltNonce: 0, safeVersion: getLatestSafeVersion(chain) as SafeVersion, } diff --git a/src/components/new-safe/create/logic/index.ts b/src/components/new-safe/create/logic/index.ts index af85829355..0361d80caf 100644 --- a/src/components/new-safe/create/logic/index.ts +++ b/src/components/new-safe/create/logic/index.ts @@ -202,7 +202,9 @@ export type UndeployedSafeWithoutSalt = Omit */ export const createNewUndeployedSafeWithoutSalt = ( safeVersion: SafeVersion, - safeAccountConfig: Pick, + safeAccountConfig: Pick & { + paymentReceiver?: string + }, chain: ChainInfo, ): UndeployedSafeWithoutSalt => { // Create universal deployment Data across chains: @@ -243,7 +245,7 @@ export const createNewUndeployedSafeWithoutSalt = ( fallbackHandler: fallbackHandlerAddress, to: includeMigration && safeToL2SetupAddress ? safeToL2SetupAddress : ZERO_ADDRESS, data: includeMigration ? safeToL2SetupInterface.encodeFunctionData('setupToL2', [safeL2Address]) : EMPTY_DATA, - paymentReceiver: ECOSYSTEM_ID_ADDRESS, + paymentReceiver: safeAccountConfig.paymentReceiver ?? ECOSYSTEM_ID_ADDRESS, }, safeVersion, } diff --git a/src/components/new-safe/create/steps/AdvancedOptionsStep/index.tsx b/src/components/new-safe/create/steps/AdvancedOptionsStep/index.tsx index 71314ad37b..829b386199 100644 --- a/src/components/new-safe/create/steps/AdvancedOptionsStep/index.tsx +++ b/src/components/new-safe/create/steps/AdvancedOptionsStep/index.tsx @@ -1,6 +1,8 @@ +import { predictAddressBasedOnReplayData } from '@/features/multichain/utils/utils' +import { useWeb3ReadOnly } from '@/hooks/wallets/web3' import { Button, MenuItem, Divider, Box, TextField, Stack, Skeleton, SvgIcon, Tooltip, Typography } from '@mui/material' import { Controller, FormProvider, useForm } from 'react-hook-form' -import type { ReactElement } from 'react' +import { type ReactElement, useMemo } from 'react' import type { StepRenderProps } from '@/components/new-safe/CardStepper/useCardStepper' import type { NewSafeFormData } from '@/components/new-safe/create' @@ -11,29 +13,29 @@ import { type SafeVersion } from '@safe-global/safe-core-sdk-types' import NumberField from '@/components/common/NumberField' import { useCurrentChain } from '@/hooks/useChains' import useAsync from '@/hooks/useAsync' -import { computeNewSafeAddress } from '../../logic' -import { getReadOnlyFallbackHandlerContract } from '@/services/contracts/safeContracts' +import { createNewUndeployedSafeWithoutSalt } from '../../logic' import EthHashInfo from '@/components/common/EthHashInfo' import InfoIcon from '@/public/images/notifications/info.svg' -import useWallet from '@/hooks/wallets/useWallet' import { isSmartContract } from '@/utils/wallets' enum AdvancedOptionsFields { safeVersion = 'safeVersion', saltNonce = 'saltNonce', + paymentReceiver = 'paymentReceiver', } export type AdvancedOptionsStepForm = { [AdvancedOptionsFields.safeVersion]: SafeVersion [AdvancedOptionsFields.saltNonce]: number + [AdvancedOptionsFields.paymentReceiver]: string } const ADVANCED_OPTIONS_STEP_FORM_ID = 'create-safe-advanced-options-step-form' const AdvancedOptionsStep = ({ onSubmit, onBack, data, setStep }: StepRenderProps): ReactElement => { - const wallet = useWallet() useSyncSafeCreationStep(setStep, data.networks) const chain = useCurrentChain() + const provider = useWeb3ReadOnly() const formMethods = useForm({ mode: 'onChange', @@ -44,39 +46,31 @@ const AdvancedOptionsStep = ({ onSubmit, onBack, data, setStep }: StepRenderProp const selectedSafeVersion = watch(AdvancedOptionsFields.safeVersion) const selectedSaltNonce = watch(AdvancedOptionsFields.saltNonce) + const selectedPaymentReceiver = watch(AdvancedOptionsFields.paymentReceiver) - const [readOnlyFallbackHandlerContract] = useAsync( - () => (chain ? getReadOnlyFallbackHandlerContract(selectedSafeVersion) : undefined), - [chain, selectedSafeVersion], + const newSafeProps = useMemo( + () => + chain + ? createNewUndeployedSafeWithoutSalt( + selectedSafeVersion, + { + owners: data.owners.map((owner) => owner.address), + threshold: data.threshold, + paymentReceiver: selectedPaymentReceiver, + }, + chain, + ) + : undefined, + [chain, data.owners, data.threshold, selectedSafeVersion, selectedPaymentReceiver], ) const [predictedSafeAddress] = useAsync(async () => { - if (!chain || !readOnlyFallbackHandlerContract || !wallet) { - return undefined - } - - return computeNewSafeAddress( - wallet.provider, - { - safeAccountConfig: { - owners: data.owners.map((owner) => owner.address), - threshold: data.threshold, - fallbackHandler: await readOnlyFallbackHandlerContract.getAddress(), - }, - saltNonce: selectedSaltNonce.toString(), - }, - chain, - selectedSafeVersion, - ) - }, [ - chain, - data.owners, - data.threshold, - wallet, - readOnlyFallbackHandlerContract, - selectedSafeVersion, - selectedSaltNonce, - ]) + if (!provider || !newSafeProps) return + + const replayedSafeWithNonce = { ...newSafeProps, saltNonce: selectedSaltNonce.toString() } + + return predictAddressBasedOnReplayData(replayedSafeWithNonce, provider) + }, [provider, newSafeProps, selectedSaltNonce]) const [isDeployed] = useAsync( async () => (predictedSafeAddress ? await isSmartContract(predictedSafeAddress) : false), @@ -102,7 +96,7 @@ const AdvancedOptionsStep = ({ onSubmit, onBack, data, setStep }: StepRenderProp )} /> - - - Salt nonce @@ -183,15 +176,57 @@ const AdvancedOptionsStep = ({ onSubmit, onBack, data, setStep }: StepRenderProp }, required: true, })} + fullWidth label="Salt nonce" error={Boolean(formState.errors[AdvancedOptionsFields.saltNonce]) || Boolean(isDeployed)} helperText={ - (formState.errors[AdvancedOptionsFields.saltNonce]?.message ?? Boolean(isDeployed)) - ? 'The Safe is already deployed. Use a different salt nonce.' - : undefined + formState.errors[AdvancedOptionsFields.saltNonce]?.message ?? + (Boolean(isDeployed) ? 'The Safe is already deployed. Use a different salt nonce.' : undefined) } /> + + + Payment receiver + + + + + + + + Impacts the derived Safe address + + + + + )} + + owner.address), threshold: data.threshold, + paymentReceiver: data.paymentReceiver, }, chain, ) : undefined, - [chain, data.owners, data.safeVersion, data.threshold], + [chain, data.owners, data.safeVersion, data.threshold, data.paymentReceiver], ) const safePropsForGasEstimation = useMemo(() => { @@ -226,12 +227,10 @@ const ReviewStep = ({ data, onSubmit, onBack, setStep }: StepRenderProps