diff --git a/package.json b/package.json
index 9882a430e1..d58949d6b9 100644
--- a/package.json
+++ b/package.json
@@ -42,6 +42,7 @@
"@emotion/react": "^11.10.0",
"@emotion/server": "^11.10.0",
"@emotion/styled": "^11.10.0",
+ "@gnosis.pm/zodiac": "^3.4.2",
"@mui/icons-material": "^5.14.3",
"@mui/material": "^5.14.3",
"@mui/x-date-pickers": "^5.0.12",
diff --git a/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowReview.tsx b/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowReview.tsx
index 7772e437c5..97bb575dcb 100644
--- a/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowReview.tsx
+++ b/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowReview.tsx
@@ -59,7 +59,6 @@ export function EnableRecoveryFlowReview({ params }: { params: EnableRecoveryFlo
- {/* TODO: Info */}
@@ -83,7 +82,7 @@ export function EnableRecoveryFlowReview({ params }: { params: EnableRecoveryFlo
>
{delay}
- {/* TODO: Info */}
+
diff --git a/src/pages/settings/recovery.tsx b/src/pages/settings/recovery.tsx
index a5aa677b9f..b483293687 100644
--- a/src/pages/settings/recovery.tsx
+++ b/src/pages/settings/recovery.tsx
@@ -4,7 +4,6 @@ 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 (
<>
diff --git a/src/services/recovery/__tests__/setup.test.ts b/src/services/recovery/__tests__/setup.test.ts
new file mode 100644
index 0000000000..258d9a1ab9
--- /dev/null
+++ b/src/services/recovery/__tests__/setup.test.ts
@@ -0,0 +1,100 @@
+import { getModuleInstance, KnownContracts, deployAndSetUpModule } from '@gnosis.pm/zodiac'
+import { faker } from '@faker-js/faker'
+import { BigNumber } from 'ethers'
+import type { Web3Provider } from '@ethersproject/providers'
+import type { SafeInfo } from '@safe-global/safe-gateway-typescript-sdk'
+
+import { getRecoverySetup } from '@/services/recovery/setup'
+
+jest.mock('@gnosis.pm/zodiac', () => ({
+ ...jest.requireActual('@gnosis.pm/zodiac'),
+ getModuleInstance: jest.fn(),
+ deployAndSetUpModule: jest.fn(),
+}))
+
+const mockGetModuleInstance = getModuleInstance as jest.MockedFunction
+const mockDeployAndSetUpModule = deployAndSetUpModule as jest.MockedFunction
+
+describe('getRecoverySetup', () => {
+ beforeEach(() => {
+ jest.clearAllMocks()
+ })
+
+ it('should deploy Delay Modifier, enable it on Safe and add a Guardian', () => {
+ const txCooldown = faker.string.numeric()
+ const txExpiration = faker.string.numeric()
+ const guardians = [faker.finance.ethereumAddress()]
+ const safeAddress = faker.finance.ethereumAddress()
+ const chainId = faker.string.numeric()
+ const safe = {
+ address: {
+ value: safeAddress,
+ },
+ chainId,
+ } as SafeInfo
+ const provider = {} as Web3Provider
+
+ const expectedModuleAddress = faker.finance.ethereumAddress()
+ const deployDelayModifierTx = {
+ to: faker.finance.ethereumAddress(),
+ data: faker.string.hexadecimal(),
+ value: BigNumber.from(0),
+ }
+ mockGetModuleInstance.mockReturnValue({
+ interface: {
+ encodeFunctionData: jest.fn().mockReturnValue(deployDelayModifierTx.data),
+ },
+ } as any)
+ mockDeployAndSetUpModule.mockReturnValue({
+ expectedModuleAddress,
+ transaction: deployDelayModifierTx,
+ })
+
+ const result = getRecoverySetup({
+ txCooldown,
+ txExpiration,
+ guardians,
+ safe,
+ provider,
+ })
+
+ expect(mockDeployAndSetUpModule).toHaveBeenCalledTimes(1)
+ expect(mockDeployAndSetUpModule).toHaveBeenCalledWith(
+ KnownContracts.DELAY,
+ {
+ types: ['address', 'address', 'address', 'uint256', 'uint256'],
+ values: [
+ safeAddress, // address _owner
+ safeAddress, // address _avatar
+ safeAddress, // address _target
+ txCooldown, // uint256 _cooldown
+ txExpiration, // uint256 _expiration
+ ],
+ },
+ provider,
+ Number(safe.chainId),
+ expect.any(String),
+ )
+
+ expect(result.expectedModuleAddress).toEqual(expectedModuleAddress)
+ expect(result.transactions).toHaveLength(3)
+
+ // Deploy Delay Modifier
+ expect(result.transactions[0]).toEqual({
+ ...deployDelayModifierTx,
+ value: '0',
+ })
+ // Enable Delay Modifier on Safe
+ expect(result.transactions[1]).toEqual({
+ to: safeAddress,
+ data: expect.any(String),
+ value: '0',
+ })
+ // Add guardian to Delay Modifier
+ expect(result.transactions[2]).toEqual({
+ to: expectedModuleAddress,
+ data: expect.any(String),
+ value: '0',
+ })
+ })
+})
diff --git a/yarn.lock b/yarn.lock
index 978b89eee0..0f23fdb5d2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2706,6 +2706,27 @@
dependencies:
tslib "^2.1.0"
+"@gnosis.pm/mock-contract@^4.0.0":
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/@gnosis.pm/mock-contract/-/mock-contract-4.0.0.tgz#eaf500fddcab81b5f6c22280a7b22ff891dd6f87"
+ integrity sha512-SkRq2KwPx6vo0LAjSc8JhgQstrQFXRyn2yqquIfub7r2WHi5nUbF8beeSSXsd36hvBcQxQfmOIYNYRpj9JOhrQ==
+
+"@gnosis.pm/safe-contracts@1.3.0":
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-contracts/-/safe-contracts-1.3.0.tgz#316741a7690d8751a1f701538cfc9ec80866eedc"
+ integrity sha512-1p+1HwGvxGUVzVkFjNzglwHrLNA67U/axP0Ct85FzzH8yhGJb4t9jDjPYocVMzLorDoWAfKicGy1akPY9jXRVw==
+
+"@gnosis.pm/zodiac@^3.4.2":
+ version "3.4.2"
+ resolved "https://registry.yarnpkg.com/@gnosis.pm/zodiac/-/zodiac-3.4.2.tgz#ce3e7498e39ccc3324eabc6f163bd173bf4d9aad"
+ integrity sha512-u7BPXsoo1ZdbmsElMbuejKNTWA3NPvFdzs3vjUSIcFfHTb9B/UE+gDQ3vMYL6bt+YLVw0F/IT5ytbiruKYQpEQ==
+ dependencies:
+ "@gnosis.pm/mock-contract" "^4.0.0"
+ "@gnosis.pm/safe-contracts" "1.3.0"
+ "@openzeppelin/contracts" "^5.0.0"
+ "@openzeppelin/contracts-upgradeable" "^5.0.0"
+ ethers "^5.7.1"
+
"@grpc/grpc-js@~1.9.0":
version "1.9.5"
resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.9.5.tgz#22e283754b7b10d1ad26c3fb21849028dcaabc53"
@@ -3604,11 +3625,21 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
+"@openzeppelin/contracts-upgradeable@^5.0.0":
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-5.0.0.tgz#859c00c55f04b6dda85b3c88bce507d65019888f"
+ integrity sha512-D54RHzkOKHQ8xUssPgQe2d/U92mwaiBDY7qCCVGq6VqwQjsT3KekEQ3bonev+BLP30oZ0R1U6YC8/oLpizgC5Q==
+
"@openzeppelin/contracts@^4.9.2":
version "4.9.3"
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.3.tgz#00d7a8cf35a475b160b3f0293a6403c511099364"
integrity sha512-He3LieZ1pP2TNt5JbkPA4PNT9WC3gOTOlDcFGJW4Le4QKqwmiNJCRt44APfxMxvq7OugU/cqYuPcSBzOw38DAg==
+"@openzeppelin/contracts@^5.0.0":
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.0.0.tgz#ee0e4b4564f101a5c4ee398cd4d73c0bd92b289c"
+ integrity sha512-bv2sdS6LKqVVMLI5+zqnNrNU/CA+6z6CmwFXm/MzmOPBRSO5reEJN7z0Gbzvs0/bv/MZZXNklubpwy3v2+azsw==
+
"@polka/url@^1.0.0-next.20":
version "1.0.0-next.23"
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.23.tgz#498e41218ab3b6a1419c735e5c6ae2c5ed609b6c"
@@ -8798,7 +8829,7 @@ ethers@5.5.4:
"@ethersproject/web" "5.5.1"
"@ethersproject/wordlists" "5.5.0"
-ethers@5.7.2:
+ethers@5.7.2, ethers@^5.7.1:
version "5.7.2"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e"
integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==