diff --git a/src/components/settings/Recovery/index.tsx b/src/components/settings/Recovery/index.tsx
new file mode 100644
index 0000000000..7181add121
--- /dev/null
+++ b/src/components/settings/Recovery/index.tsx
@@ -0,0 +1,38 @@
+import { Box, Button, Chip, Grid, Paper, Typography } from '@mui/material'
+import { useContext } from 'react'
+import type { ReactElement } from 'react'
+
+import { EnableRecoveryFlow } from '@/components/tx-flow/flows/EnableRecovery'
+import { TxModalContext } from '@/components/tx-flow'
+
+export function Recovery(): ReactElement {
+ const { setTxFlow } = useContext(TxModalContext)
+
+ return (
+
+
+
+
+
+ Account recovery
+
+
+ {/* TODO: Extract when widget is merged https://github.com/safe-global/safe-wallet-web/pull/2768 */}
+
+
+
+
+
+
+ Choose a trusted guardian to recover your Safe Account, in case you should ever lose access to your Account.
+ Enabling the Account recovery module will require a transactions.
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/sidebar/SidebarNavigation/config.tsx b/src/components/sidebar/SidebarNavigation/config.tsx
index 9f8dbde18f..67f870101f 100644
--- a/src/components/sidebar/SidebarNavigation/config.tsx
+++ b/src/components/sidebar/SidebarNavigation/config.tsx
@@ -84,6 +84,10 @@ export const settingsNavItems = [
label: 'Appearance',
href: AppRoutes.settings.appearance,
},
+ {
+ label: 'Recovery',
+ href: AppRoutes.settings.recovery,
+ },
{
label: 'Notifications',
href: AppRoutes.settings.notifications,
diff --git a/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowIntro.tsx b/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowIntro.tsx
new file mode 100644
index 0000000000..6d8296c2c8
--- /dev/null
+++ b/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowIntro.tsx
@@ -0,0 +1,7 @@
+import type { ReactElement } from 'react'
+
+import TxCard from '../../common/TxCard'
+
+export function EnableRecoveryFlowIntro(): ReactElement {
+ return EnableRecoveryFlowIntro
+}
diff --git a/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowReview.tsx b/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowReview.tsx
new file mode 100644
index 0000000000..4b2beab26a
--- /dev/null
+++ b/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowReview.tsx
@@ -0,0 +1,43 @@
+import { useContext, useEffect, useMemo } from 'react'
+import type { ReactElement } from 'react'
+
+import SignOrExecuteForm from '@/components/tx/SignOrExecuteForm'
+import { Errors, logError } from '@/services/exceptions'
+import { createMultiSendCallOnlyTx } from '@/services/tx/tx-sender'
+import { SafeTxContext } from '@/components/tx-flow/SafeTxProvider'
+import { getRecoverySetup } from '@/services/recovery/setup'
+import { useWeb3 } from '@/hooks/wallets/web3'
+import useSafeInfo from '@/hooks/useSafeInfo'
+import type { EnableRecoveryFlowProps } from '.'
+
+export function EnableRecoveryFlowReview({ params }: { params: EnableRecoveryFlowProps }): ReactElement {
+ const web3 = useWeb3()
+ const { safe } = useSafeInfo()
+ const { setSafeTx, safeTxError, setSafeTxError } = useContext(SafeTxContext)
+
+ const recoverySetup = useMemo(() => {
+ if (!web3) {
+ return
+ }
+
+ return getRecoverySetup({
+ ...params,
+ safe,
+ provider: web3,
+ })
+ }, [params, safe, web3])
+
+ useEffect(() => {
+ if (recoverySetup) {
+ createMultiSendCallOnlyTx(recoverySetup.transactions).then(setSafeTx).catch(setSafeTxError)
+ }
+ }, [recoverySetup, setSafeTx, setSafeTxError])
+
+ useEffect(() => {
+ if (safeTxError) {
+ logError(Errors._809, safeTxError.message)
+ }
+ }, [safeTxError])
+
+ return null} />
+}
diff --git a/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowSettings.tsx b/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowSettings.tsx
new file mode 100644
index 0000000000..79709c965d
--- /dev/null
+++ b/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowSettings.tsx
@@ -0,0 +1,14 @@
+import type { ReactElement } from 'react'
+
+import TxCard from '../../common/TxCard'
+import type { EnableRecoveryFlowProps } from '.'
+
+export function EnableRecoveryFlowSettings({
+ params,
+ onSubmit,
+}: {
+ params: EnableRecoveryFlowProps
+ onSubmit: (formData: EnableRecoveryFlowProps) => void
+}): ReactElement {
+ return EnableRecoveryFlowSettings
+}
diff --git a/src/components/tx-flow/flows/EnableRecovery/index.tsx b/src/components/tx-flow/flows/EnableRecovery/index.tsx
new file mode 100644
index 0000000000..2f092732c7
--- /dev/null
+++ b/src/components/tx-flow/flows/EnableRecovery/index.tsx
@@ -0,0 +1,58 @@
+import type { ReactElement } from 'react'
+
+import TxLayout from '@/components/tx-flow/common/TxLayout'
+import CodeIcon from '@/public/images/apps/code-icon.svg'
+import useTxStepper from '../../useTxStepper'
+import { EnableRecoveryFlowReview } from './EnableRecoveryFlowReview'
+import { EnableRecoveryFlowSettings } from './EnableRecoveryFlowSettings'
+import { EnableRecoveryFlowIntro } from './EnableRecoveryFlowIntro'
+
+export enum EnableRecoveryFlowFields {
+ guardians = 'guardians',
+ txCooldown = 'txCooldown',
+ txExpiration = 'txExpiration',
+}
+
+export type EnableRecoveryFlowProps = {
+ [EnableRecoveryFlowFields.guardians]: Array
+ [EnableRecoveryFlowFields.txCooldown]: string
+ [EnableRecoveryFlowFields.txExpiration]: string
+}
+
+export function EnableRecoveryFlow(): ReactElement {
+ const { data, step, nextStep, prevStep } = useTxStepper({
+ [EnableRecoveryFlowFields.guardians]: [],
+ [EnableRecoveryFlowFields.txCooldown]: '0',
+ [EnableRecoveryFlowFields.txExpiration]: '0',
+ })
+
+ const steps = [
+ ,
+ nextStep({ ...data, ...formData })} />,
+ ,
+ ]
+
+ const isIntro = step === 0
+ const isSettings = step === 1
+
+ const subtitle = isIntro
+ ? 'How does recovery work?'
+ : isSettings
+ ? 'Set up account settings'
+ : 'Set up account recovery'
+
+ const icon = isIntro ? undefined : CodeIcon
+
+ return (
+
+ {steps}
+
+ )
+}
diff --git a/src/config/routes.ts b/src/config/routes.ts
index 6090dbb7ad..a3f245cd3b 100644
--- a/src/config/routes.ts
+++ b/src/config/routes.ts
@@ -28,6 +28,7 @@ export const AppRoutes = {
settings: {
spendingLimits: '/settings/spending-limits',
setup: '/settings/setup',
+ recovery: '/settings/recovery',
notifications: '/settings/notifications',
modules: '/settings/modules',
index: '/settings',
diff --git a/src/pages/settings/recovery.tsx b/src/pages/settings/recovery.tsx
new file mode 100644
index 0000000000..a5aa677b9f
--- /dev/null
+++ b/src/pages/settings/recovery.tsx
@@ -0,0 +1,24 @@
+import Head from 'next/head'
+import type { NextPage } from 'next'
+
+import SettingsHeader from '@/components/settings/SettingsHeader'
+import { Recovery } from '@/components/settings/Recovery'
+
+// TODO: Condense to other setting section once confirmed
+const RecoveryPage: NextPage = () => {
+ return (
+ <>
+
+ {'Safe{Wallet} – Settings – Recovery'}
+
+
+
+
+
+
+
+ >
+ )
+}
+
+export default RecoveryPage
diff --git a/src/services/recovery/setup.ts b/src/services/recovery/setup.ts
new file mode 100644
index 0000000000..6e8291468a
--- /dev/null
+++ b/src/services/recovery/setup.ts
@@ -0,0 +1,85 @@
+import { getModuleInstance, KnownContracts, deployAndSetUpModule } from '@gnosis.pm/zodiac'
+import { Interface } from 'ethers/lib/utils'
+import type { Web3Provider } from '@ethersproject/providers'
+import type { SafeInfo } from '@safe-global/safe-gateway-typescript-sdk'
+import type { MetaTransactionData } from '@safe-global/safe-core-sdk-types'
+
+export function getRecoverySetup({
+ txCooldown,
+ txExpiration,
+ guardians,
+ safe,
+ provider,
+}: {
+ txCooldown: string
+ txExpiration: string
+ guardians: Array
+ safe: SafeInfo
+ provider: Web3Provider
+}): {
+ expectedModuleAddress: string
+ transactions: Array
+} {
+ const safeAddress = safe.address.value
+
+ const setupArgs: Parameters[1] = {
+ types: ['address', 'address', 'address', 'uint256', 'uint256'],
+ values: [
+ safeAddress, // address _owner
+ safeAddress, // address _avatar
+ safeAddress, // address _target
+ txCooldown, // uint256 _cooldown
+ txExpiration, // uint256 _expiration
+ ],
+ }
+
+ const saltNonce: Parameters[4] = Date.now().toString()
+
+ const { transaction, expectedModuleAddress } = deployAndSetUpModule(
+ KnownContracts.DELAY,
+ setupArgs,
+ provider,
+ Number(safe.chainId),
+ saltNonce,
+ )
+
+ const transactions: Array = []
+
+ // Deploy Delay Modifier
+ const deployDeplayModifier: MetaTransactionData = {
+ ...transaction,
+ value: transaction.value.toString(),
+ }
+
+ transactions.push(deployDeplayModifier)
+
+ const safeAbi = ['function enableModule(address module)']
+ const safeInterface = new Interface(safeAbi)
+
+ // Enable Delay Modifier on Safe
+ const enableDelayModifier: MetaTransactionData = {
+ to: safeAddress,
+ value: '0',
+ data: safeInterface.encodeFunctionData('enableModule', [expectedModuleAddress]),
+ }
+
+ transactions.push(enableDelayModifier)
+
+ const delayModifierContract = getModuleInstance(KnownContracts.DELAY, expectedModuleAddress, provider)
+
+ // Add guardians to Delay Modifier
+ const enableDelayModifierModules: Array = guardians.map((guardian) => {
+ return {
+ to: expectedModuleAddress,
+ data: delayModifierContract.interface.encodeFunctionData('enableModule', [guardian]),
+ value: '0',
+ }
+ })
+
+ transactions.push(...enableDelayModifierModules)
+
+ return {
+ expectedModuleAddress,
+ transactions,
+ }
+}