diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index af8594e2..00000000
--- a/.gitmodules
+++ /dev/null
@@ -1,6 +0,0 @@
-[submodule "lib/ether-email-auth"]
- path = lib/ether-email-auth
- url = https://github.com/zkemail/ether-email-auth
-[submodule "lib/email-wallet-sdk"]
- path = lib/email-wallet-sdk
- url = https://github.com/zkemail/email-wallet-sdk
diff --git a/README.md b/README.md
index 8389f9b2..604e0a71 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,11 @@
## ZK Email Recovery
## Overview
-Account recovery has traditionally been one of the most complex UX hurdles that account holders have to contend with. The ZK Email Recovery contracts provide a robust and simple mechanism for account holders to recover accounts via email guardians.
+Account recovery has traditionally been one of the most complex UX hurdles that account holders have to contend with. The ZK Email Recovery contracts provide a robust and simple mechanism for account holders to recover modular accounts via email guardians.
+
+Modular accounts get account recovery for 'free' by using our pre-deployed universal email recovery module. Since Safe's can be made 7579 compatible, the following contracts also support Safe account recovery out of the box too.
+
+Modular account developers can easily integrate email recovery with richer and more specific email subejcts by writing their own subject handler contracts, which are designed to be simple and contain the modular account-specific logic to recover an account.
## Usage
@@ -9,7 +13,6 @@ Account recovery has traditionally been one of the most complex UX hurdles that
```shell
pnpm install
-forge install
```
### Build
@@ -26,40 +29,36 @@ forge test
# ZK Email Recovery
-## High level contracts diagram (WIP).
-
+## High level contracts diagram
+
+
+Note: `EmailAccountRecovery.sol` & `EmailAuth.sol` can be found in the [ether-email-auth](https://github.com/zkemail/ether-email-auth) repo
-## Core contracts
-The core contracts contain the bulk of the recovery logic.
### EmailRecoveryManager.sol
-`EmailRecoveryManager.sol` defines a default implementation for email-based recovery. It is designed to provide the core logic for email based account recovery that can be used across different modular account implementations. For the end user, the core `EmailRecoveryManager` contract aims to provide a robust and simple mechanism to recover accounts via email guardians.
+`EmailRecoveryManager.sol` defines the main logic for email-based recovery. It is designed to provide the core logic for email based account recovery that can be used across different modular account implementations. For the end user, the core `EmailRecoveryManager` contract aims to provide a robust and simple mechanism to recover accounts via email guardians.
-It inherits from a zk email contract called `EmailAccountRecovery.sol` which defines some basic recovery logic that interacts with lower level zk email contracts. `EmailAccountRecovery.sol` holds the logic that interacts with the lower level zk email contracts EmailAuth.sol, verifier, dkim registry etc. More info on the underlying EmailAccountRecovery.sol contract: https://github.com/zkemail/ether-email-auth/tree/main/packages/contracts#emailaccountrecovery-contract.
+It inherits from a zk email contract called `EmailAccountRecovery.sol` which defines some basic recovery logic that interacts with lower level zk email contracts. `EmailAccountRecovery.sol` holds the logic that interacts with the lower level zk email contracts `EmailAuth.sol`, verifier, dkim registry etc. More info on the underlying `EmailAccountRecovery.sol` contract can be found [here](https://github.com/zkemail/ether-email-auth/tree/main/packages/contracts#emailaccountrecovery-contract).
-The guardians are represented onchain by EmailAuth.sol instances. EmailAuth.sol is a lower level zk email contract. it is designed to authenticate that a user is a correct holder of the specific email address and authorize anything described in the email. The guardians privacy is protected onchain, for more info on zk email privacy and EmailAuth - see the [zk email docs](https://zkemail.gitbook.io/zk-email).
+The guardians are represented onchain by EmailAuth.sol instances. `EmailAuth.sol` is designed to authenticate that a user is a correct holder of the specific email address and authorize anything described in the email. The guardians privacy is protected onchain, for more info on zk email privacy and EmailAuth - see the [zk email docs](https://zkemail.gitbook.io/zk-email).
-EmailRecoveryManager relies on a dedicated recovery module to execute a recovery attempt. This (EmailRecoveryManager) contract defines "what a valid recovery attempt is for an account", and the recovery module defines “how that recovery attempt is executed on the account”. One motivation for having the 7579 recovery module and the core ZkEMailRecovery contract being seperated is to allow the core recovery logic to be used across different account implementations and module standards. The core `EmailRecoveryManager.sol` contract is designed to be account implementation agnostic. It's functionality can be extended by creating new subject handler contracts such as `EmailRecoverySubjectHandler.sol`. TODO add more info on this
+`EmailRecoveryManager` relies on a dedicated recovery module to execute a recovery attempt. The `EmailRecoveryManager` contract defines "what a valid recovery attempt is for an account", and the recovery module defines “how that recovery attempt is executed on the account”. One motivation for having the 7579 recovery module and the core `EmailRecoveryManager` contract being seperated is to allow the core recovery logic to be used across different account implementations and module standards. The core `EmailRecoveryManager.sol` contract is designed to be account implementation agnostic. It's functionality can be extended by creating new subject handler contracts such as `EmailRecoverySubjectHandler.sol`.
## EmailRecoveryManager flow walkthrough
The core functions that must be called in the end-to-end flow for recovery are
1. configureRecovery (does not need to be called again for subsequent recovery attempts)
-2. handleAcceptance - called for each guardian. Defined on EmailAccountRecovery.sol, calls acceptGuardian in this contract
-3. handleRecovery - called for each guardian. Defined on EmailAccountRecovery.sol, calls processRecovery in this contract
+2. handleAcceptance - called for each guardian. Defined on `EmailAccountRecovery.sol`, calls acceptGuardian in this contract
+3. handleRecovery - called for each guardian. Defined on `EmailAccountRecovery.sol`, calls processRecovery in this contract
4. completeRecovery
-Before proceeding, ensure that the following components are correctly set up before deploying a EmailRecoveryManager instance:
-- verifierAddr: Address of the ZKP verifier.
-- dkimAddr: Address of the DKIM registry for email authentication.
-- emailAuthImplementationAddr: Address of the email authentication implementation.
-- subjectHandler: Address of the email subject handler.
+Before proceeding, ensure that you deploy the email recovery contracts via one of the email recovery factories.
### Configure Recovery
After deployment, this is the first core function in the recovery flow, setting up the recovery module, guardians, guardian weights, threshold, and delay/expiry. It only needs to be called once. The threshold must be set appropriately to balance security and usability. The same goes for the delay and expiry - there is a minimum recovery window time that protects against an account giving itself a prohibitively small window in which to complete a recovery attempt.
-It is strongly recommended that configureRecovery is called during the installation phase of the recovery module. This ensures that a user cannot forget to install the recovery module and end up with a broken recovery config.
+`configureRecovery` is called during the installation phase of the recovery module. This ensures that a user cannot forget to install the recovery module, go to configure recovery, and end up with a broken recovery config.
```ts
function configureRecovery(
@@ -72,7 +71,7 @@ function configureRecovery(
```
### Handle Acceptance
-This function handles the acceptance of each guardian. Each guardian must accept their role to be a part of the recovery process. This is an important step as it ensures that the guardian consents to the responsibility of being a guardian for a specific account, and protects against typos from entering a guardian email address. Such a typo would render the guardian unusable. handleAcceptance must be called for each guardian until the threshold is reached.
+This function handles the acceptance of each guardian. Each guardian must accept their role to be a part of the recovery process. This is an important step as it ensures that the guardian consents to the responsibility of being a guardian for a specific account, is in control of the specific email address, and protects against typos from entering the wrong email. Such a typo would render the guardian unusable. `handleAcceptance` must be called for each guardian until the threshold is reached.
```ts
function handleAcceptance(
@@ -84,7 +83,7 @@ function handleAcceptance(
```
### Handle Recovery
-This function processes each guardian's recovery request. A Guardian can initiate a recovery request by replying to an email. The contract verifies the guardian's status and checks if the threshold is met. Once the threshold is met and the delay has passed, anyone can complete the recovery process. The recovery delay is a security feature that gives the wallet owner time to react to a recovery attempt in case of a malicious guardian or guardians. This is possible from guardians who act maliciously, but also from attackers who have gotten access to a guardians email address. Although since guardian email privacy is preserved on chain, this reduces the attack surface further since someone with access to a someone elses email account would not know if the email address is used in a recovery setup, or if they did, which account to target. There is also an expiry time, which once expires, invalidates the recovery attempt. This encourages timely execution of recovery attempts and reduces the attack surface that could result from recovery attempts that have been stagnent and uncompleted for long periods of time.
+This function processes each guardian's recovery request. A guardian can initiate a recovery request by replying to an email. The contract verifies the guardian's status and checks if the threshold is met. Once the threshold is met and the delay has passed, anyone can complete the recovery process. The recovery delay is a security feature that gives the wallet owner time to react to a recovery attempt in case of a malicious guardian or guardians. This is possible from guardians who act maliciously, but also from attackers who have access to a guardians email address. Although since guardian email privacy is preserved on chain, this reduces the attack surface further since someone with access to a someone elses email account would not know if the email address is used in a recovery setup, or if they did, which account to target. There is also an expiry time, which once expires, invalidates the recovery attempt. This encourages timely execution of recovery attempts and reduces the attack surface that could result from recovery attempts that have been stagnent and uncompleted for long periods of time.
```ts
function handleRecovery(
@@ -99,33 +98,64 @@ function handleRecovery(
The final function to complete the recovery process. This function completes the recovery process by validating the recovery request and triggering the recovery module to perform the recovery on the account itself.
```ts
-function completeRecovery(address account) public;
+function completeRecovery(address account, bytes memory recoveryCalldata) public;
```
+## Subject Handlers
+
+Subject handlers define the subjects for recovery emails and how they should be validated. They are designed to be simple and self-contained contracts that hold the modular account-specific logic needed for adding email recovery. We define a universal subject handler which essentially gives 7579 module developers recovery for free that is generic to any validator (so long as the validator has functionality to recover itself). We also provide a Safe account subject handler which provides email account recovery for 7579-compatible Safe's.
+
+Subject handlers contain functions for defining the email subjects, of which there are two subject types - acceptance and recovery subjects. The acceptance subject is for the email that is displayed to the guardian when they have to accept becoming a guardian for an account. The recovery subject is for the email displayed to the guardian when an account is actually being recovered. Handlers also contain helper functions to extract the account address from both subject types. The acceptance and recovery templates can be written to contain any functional info, but they must contain the account address. The subject is an important part of the functional info that is used to generate and verify the zkp.
+
+Once a new subject has been written and audited, the deployment bytecode of the subject handler can be passed into one of the provided factories, which ensures that the deployment of a module, manager and subject handler are tightly coupled. The deployment of these contracts can be attested to via the use of an [ERC-7484 resistry](https://eips.ethereum.org/EIPS/eip-7484).
+
+### Why write your own subject handler?
+The generic subject handler supported out of the box is sufficiently generic for recovering any modular account via the use of a recovery hash in the email subject, which is validated against when executing recovery. A modular account developer may want to provide a more specifc and human readable subject handler for their users. It's also possible to write a subject template in a non-english language to support non-english speakers.
+
+It is important to re-iterate that modular accounts already get account recovery out of the box with these contract, via the use of the unviversal email recovery module.
+
### EmailRecoverySubjectHandler.sol
-TODO add section
+`EmailRecoverySubjectHandler` is a generic subject handler that can be used to recovery any validator.
+
+The acceptance subject template is:
+`Accept guardian request for {ethAddr}`
+
+The recovery subject template is:
+`Recover account {ethAddr} via recovery module {ethAddr} using recovery hash {string}`
### SafeRecoverySubjectHandler.sol
-TODO update section
-~~`SafeZkEmailRecovery.sol` is an extension of `EmailRecoveryManager.sol` that implements recovery for Safe accounts. It provides a good example of how to extend `EmailRecoveryManager.sol` for different account implementations. The contract follows the same multi-step process that EmailRecoveryManager.sol does, where guardians must first be accepted before they can initiate recovery. The Safe example does not override the acceptance related functions, only the recovery ones. This is because the acceptance subject is broad enough that it can stay the same whereas the recovery attempt subject needs additional info in order to complete the recovery request properly. This will be a common scenario where only the recovery subject-related functions will need overriding. A scenario in which you would definitely need to update both is if you wanted to provide email recovery functionality to users who didn't speak English. In which case you could translate the required subjects into the chosen language~~
-## How you can extend EmailRecoveryManager by adding a custom template
+`SafeRecoverySubjectHandler` is a specific subject handler that can be used to recover a Safe. It provides a good example of how to write a custom subject handler for a different account implementation. In contrast to the `EmailRecoverySubjectHandler`, the Safe subject requires additional info in order to complete the recovery request.
+
+The acceptance subject remains the same as the acceptance subject is already quite generic. This will be a common scenario where only the recovery subject-related functions will need changing. A scenario in which you would definitely need to update both is if you wanted to provide email recovery functionality to users who didn't speak English. In which case you could translate the required subjects into the chosen language.
+
+The acceptance subject template is:
+`Accept guardian request for {ethAddr}`
+
+The recovery subject template is:
+`Recover account {ethAddr} from old owner {ethAddr} to new owner {ethAddr} using recovery module {ethAddr}`
+
+## How you can write a custom subject template
+
+When you know what recovery specific information you need, you can create a new handler contract. The following functions must be implemented:
-~~When you know what recovery specific information you need, you can create a new contract, inherit from `EmailRecoveryManager.sol` and extend the relevant functions - note you only have to extend ones that are relevant:~~
* `acceptanceSubjectTemplates()`
-* `validateAcceptanceSubjectTemplates()`
+* `extractRecoveredAccountFromAcceptanceSubject(bytes[],uint256)`
+* `validateAcceptanceSubject(uint256,bytes[])`
* `recoverySubjectTemplates()`
-* `validateRecoverySubjectTemplates()`
+* `extractRecoveredAccountFromRecoverySubject(bytes[],uint256)`
+* `validateRecoverySubject(uint256,bytes[],address)`
+
-~~These functions, and the functions to validate the subject params are virtual so they can be overridden if a developer wants to change the subjects for a different implementation. A good example of this would be this code to add Safe compatibility (Safe recovery requires slightly different args in the subject). The account developer can choose any subject that they want if they override the default implementation. Here is some more info on subject params:~~
+### How a subject is interpreted
With an email subject of:
```bash
Recover account 0x50Bc6f1F08ff752F7F5d687F35a0fA25Ab20EF52 to new owner 0x7240b687730BE024bcfD084621f794C2e4F8408f using recovery module 0x344433E549E3F84B68D1aAC5b416Ac5cE2Be1063
```
-Where the first address in the subject is accountAddress, second the oldOwner, third newOwner and forth the recoveryModule address
+Where the first address in the subject is the account address recovery is being executed for, the second is the new owner address, and the third is the email recovery module address. This subject would lead to the the following subject params
The subject params would be:
@@ -137,23 +167,50 @@ subjectParamsForRecovery[2] = abi.encode(recoveryModule);
```
### What can I add to a subject template?
-A subject template defines the expected format of the message in the Subject for each recovery implementation. It allows developers to constrain that message to be in the application-specific format without writing new ZKP circuits. The use of different subject templates in this case allows for a flexible and extensible mechanism to define recovery messages, making it adaptable to different modular account implemtations.
+A subject template defines the expected format of the message in the subject for each recovery implementation. The underlying zkemail contracts are generic for any subject, negating some type and size constraints, so developers can write application-specific messages without writing new zk circuits. The use of different subject templates in this case allows for a flexible and extensible mechanism to define recovery messages, making it adaptable to different modular account implemtations. For recovery subjects using these contracts, the email subjects can be completely generic, but they must return the account address for which recovery is for.
-The subject template is an array of strings, each of which has some fixed strings without space and the following variable parts:
-- `"{string}"`: a string. Its Solidity type is `string`.
+The subject template is an array of strings, each of which has some fixed strings without space and the following variable parts. Subject variables must meet the following type constraints:
+- `"{string}"`: a string. Its Solidity type is `string`. The subject string type can be used to add the solidity `bytes` type to email subjects.
- `"{uint}"`: a decimal string of the unsigned integer. Its Solidity type is `uint256`.
- `"{int}"`: a decimal string of the signed integer. Its Solidity type is `int256`.
- `"{decimals}"`: a decimal string of the decimals. Its Solidity type is `uint256`. Its decimal size is fixed to 18. E.g., “2.7” ⇒ `abi.encode(2.7 * (10**18))`.
- `"{ethAddr}"`: a hex string of the Ethereum address. Its Solidity type is `address`. Its value MUST satisfy the checksum of the Ethereum address.
-If you are recovering an account that needs to rotate a public key which is of type `bytes` in solidity, you can use the string type for that for the subject template.
+If you are recovering an account that needs to rotate a public key which is of type `bytes` in solidity, you can use the string type for that for the subject template. To read more about the underlying zk email contracts that this repo uses, take a look at the [ether-email-auth](https://github.com/zkemail/ether-email-auth) repo.
### EmailRecoveryModule.sol
-An recovery module that recovers any validator.
+An recovery module that recovers a specific validator.
+
+The target validator and target selector are passed into the module when it is deployed. This means that the module is less generic, but the module is simpler and provides less room for error when confiuring recovery. This is because the module does not have to handle permissioning multiple validators and there is less room for configuration error when installing the module, as the target validator and selector are passed in at deployment instead.
+
+The `recover()` function on the module is the key entry point where recovery is executed. This function must be called from the trusted recovery contract. The function that calls `recover()` from `EmailRecoveryManager.sol` is `completeRecovery()` which can be called by anyone, but normally the relayer. It is the final function that is called once a recovery request becomes valid.
+
+`completeRecovery()` calls into the account specific recovery module and can call executeFromExecutor to execute the account specific recovery logic. The call from the executor retains the context of the account so the `msg.sender` of the next call is the account itself. This simplifies access control in the validator being recovered as it can just do a `msg.sender` check.
+
+When writing a custom subject handler, an account developer would likely chose to deploy a `EmailRecoveryModule` instance rather than a `UniversalEmailRecoveryModule` instance. This is because a custom subject handler would likely be specific to an validator implementation, so using the recovery module for specific validators is more appropriate than the generic recovery module.
+
+### UniversalEmailRecoveryModule.sol
+A recovery module that recovers any validator.
+
+The target validator and target selector are passed into the module when it is installed. This means that the module is generic and can be used to recover any 7579 validator. The module is slightly more complex as it has to handle permissioning multiple validators. Additionally there is a slightly higher chance of configuration error when installing the module as the target validator and selector are passed in at this stage instead of when the module is deployed.
+
+The `recover()` function on the module is the key entry point where recovery is executed. This function must be called from the trusted recovery contract. The function that calls `recover()` from `EmailRecoveryManager.sol` is `completeRecovery()` which can be called by anyone, but normally the relayer. It is the final function that is called once a recovery request becomes valid.
+
+`completeRecovery()` calls into the account specific recovery module and can call executeFromExecutor to execute the account specific recovery logic. The call from the executor retains the context of the account so the `msg.sender` of the next call is the account itself. This simplifies access control in the validator being recovered as it can just do a `msg.sender` check.
+
+### EmailRecoveryFactory.sol
+The factory for deploying new instances of `EmailRecoveryModule.sol` and associated managers and subject handlers. Because the relationship between the recovery module and manager is security critical, The factory ensures there is a tight coupling between a deployed module, and associated manager and subject handler.
+
+The deployment function for this factory deploys an `EmailRecoveryModule`, which takes a target emailRecoveryManager, validator and function selector. The other values passed into the deployment function are the same as the `EmailRecoveryUniversalFactory`, which include deployment salts, subject handler bytecode, and a dkim registry.
+
+When deploying a new recovery module for a specific validator with a more human readable subject, modular account developers can write their own subject handler and pass the deployment bytecode of that handler into the factory. The security of each module deployment and associated contracts can then be attested to via a ERC 7484 registry.
+
+### EmailRecoveryUniversalFactory.sol
+The factory for deploying new instances of `UniversalEmailRecoveryModule.sol` and associated managers and subject handlers.
-The `recover()` function on the module holds the core logic for the module. It defines “how a recovery attempt is executed on the account”. This function must be called from the trusted recovery contract. The function that calls `recover()` from `EmailRecoveryManager.sol` is `completeRecovery()` which can be called by anyone, but normally the relayer. It is the final function that is called once a recovery attempt has been successful.
+The deployment function for this factory deploys an `UniversalEmailRecoveryModule`, which only takes the target emailRecoveryManager. The other values passed into the deployment function are the same as the `EmailRecoveryFactory`, which include deployment salts, subject handler bytecode, and a dkim registry. The target validator and target function selector are set when the universal module is installed.
-`completeRecovery()` calls into the account specific recovery module and can call executeFromExecutor to execute the account specific recovery logic.
+While the subject handler for `EmailRecoveryUniversalFactory` will be more stable in comparison to a subject handlers used for `EmailRecoveryModule`, developers may want to write a generic subject handler in a slightly different way, or even in a non-english lanaguage, so the bytecode is still passed in here directly. The security of each module deployment and associated contracts can then be attested to via a ERC 7484 registry.
## Threat model
-Importantly this contract offers the functonality to recover an account via email in a scenario where a private key has been lost. This contract does NOT provide an adequate mechanism to protect an account from a stolen private key by a malicious actor. This attack vector requires a holistic approach to security that takes specific implementation details of an account into consideration. For example, adding additional access control when cancelling recovery to prevent a malicious actor stopping recovery attempts, and adding spending limits to prevent account draining. This contract is designed to be extended to take these additional considerations into account, but does not provide them by default.
\ No newline at end of file
+Importantly this contract offers the functonality to recover an account via email in a scenario where a private key has been lost. This contract does NOT provide an adequate mechanism to protect an account from a stolen private key by a malicious actor. This attack vector requires a holistic approach to security that takes specific implementation details of an account into consideration. For example, adding additional access control when cancelling recovery to prevent a malicious actor stopping recovery attempts, and adding spending limits to prevent account draining. Additionally, the current 7579 spec allows accounts to forcefully uninstall modules in the case of a malicious module, this means an attacker could forcefully uninstall a recovery module anyway. This is expected to be addressed in the future. This contract is designed to recover modular accounts in the case of a lost device/authentication method (private key), but does not provide adequate security for a scenario in which a malicious actor has control of the lost device/authentication method (private key).
\ No newline at end of file
diff --git a/docs/email-recovery-diagram.drawio.png b/docs/email-recovery-diagram.drawio.png
new file mode 100644
index 00000000..579a481c
Binary files /dev/null and b/docs/email-recovery-diagram.drawio.png differ
diff --git a/docs/recovery-module-contracts.drawio.png b/docs/recovery-module-contracts.drawio.png
deleted file mode 100644
index 13b164fa..00000000
Binary files a/docs/recovery-module-contracts.drawio.png and /dev/null differ
diff --git a/foundry.toml b/foundry.toml
index 91fcb045..beec336f 100644
--- a/foundry.toml
+++ b/foundry.toml
@@ -37,5 +37,5 @@ quote_style = "double"
tab_width = 4
wrap_comments = true
ignore = [
- "./src/zksyncDeps/contracts/L2ContractHelper.sol",
+ "./src/libraries/L2ContractHelper.sol",
]
diff --git a/lib/email-wallet-sdk b/lib/email-wallet-sdk
deleted file mode 160000
index a8c200e4..00000000
--- a/lib/email-wallet-sdk
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit a8c200e4855a81adc7b9493afa92a898ffcb8c56
diff --git a/lib/ether-email-auth b/lib/ether-email-auth
deleted file mode 160000
index 6eebd924..00000000
--- a/lib/ether-email-auth
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 6eebd924122503cf74742db6031a7a11c3079851
diff --git a/package.json b/package.json
index 033ec244..d98329fd 100644
--- a/package.json
+++ b/package.json
@@ -1,11 +1,11 @@
{
- "name": "@rhinestone/module-template",
- "version": "0.4.1",
- "description": "A Foundry template for building modules using the ModuleKit",
- "license": "GPL-3.0",
+ "name": "email-recovery",
+ "version": "0.0.1",
+ "description": "Smart account module and related contracts to enable email recovery for validators",
+ "license": "MIT",
"author": {
- "name": "Rhinestone",
- "url": "https://rhinestone.wtf"
+ "name": "email-recovery",
+ "url": "https://prove.email/"
},
"scripts": {
"build": "forge build",
@@ -21,18 +21,18 @@
"prettier:check": "prettier --check \"**/*.{json,md,svg,yml}\"",
"prettier:write": "prettier --write \"**/*.{json,md,svg,yml}\"",
"test": "forge test",
- "test:safe": "ACCOUNT_TYPE=SAFE forge test",
- "test:kernel": "ACCOUNT_TYPE=KERNEL forge test",
"test:lite": "FOUNDRY_PROFILE=lite forge test",
"test:optimized": "pnpm run build:optimized && FOUNDRY_PROFILE=test-optimized forge test"
},
"dependencies": {
- "@openzeppelin/contracts-upgradeable": "v5.0.1",
- "@zk-email/contracts": "v6.0.3",
"@matterlabs": "github:matter-labs/era-contracts",
+ "@openzeppelin/contracts-upgradeable": "v5.0.1",
"@rhinestone/modulekit": "github:rhinestonewtf/modulekit#test/safe-latest",
- "erc7579-implementation": "https://github.com/erc7579/erc7579-implementation.git",
- "solidity-stringutils": "https://github.com/Arachnid/solidity-stringutils.git"
+ "@zk-email/contracts": "v6.0.3",
+ "email-wallet-sdk": "github:zkemail/email-wallet-sdk",
+ "erc7579-implementation": "github:erc7579/erc7579-implementation",
+ "ether-email-auth": "github:zkemail/ether-email-auth",
+ "solidity-stringutils": "github:Arachnid/solidity-stringutils"
},
"files": [
"src",
@@ -42,18 +42,18 @@
"foundry.toml",
"remappings.txt"
],
- "homepage": "https://docs.rhinestone.wtf/module-template",
"repository": {
"type": "git",
- "url": "git+https://github.com/rhinestonewtf/module-template.git"
+ "url": "git+https://github.com/zkemail/email-recovery.git"
},
"bugs": {
- "url": "https://github.com/rhinestonewtf/module-template/issues"
+ "url": "https://github.com/zkemail/email-recovery/issues"
},
"keywords": [
+ "zk email",
+ "recovery",
"account abstraction",
- "smart account modules",
- "module template"
+ "smart account modules"
],
"publishConfig": {
"access": "public"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e328bc1c..8ddd0bc5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -20,12 +20,18 @@ importers:
'@zk-email/contracts':
specifier: v6.0.3
version: 6.0.3
+ email-wallet-sdk:
+ specifier: github:zkemail/email-wallet-sdk
+ version: https://codeload.github.com/zkemail/email-wallet-sdk/tar.gz/a8c200e4855a81adc7b9493afa92a898ffcb8c56(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2))
erc7579-implementation:
- specifier: https://github.com/erc7579/erc7579-implementation.git
- version: https://codeload.github.com/erc7579/erc7579-implementation/tar.gz/4f27201bfd949348ae3a390ae5aa8e85ea19cc11(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2))
+ specifier: github:erc7579/erc7579-implementation
+ version: https://codeload.github.com/erc7579/erc7579-implementation/tar.gz/8ab162604cb4878381bb8a15064fec42f775440b(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2))
+ ether-email-auth:
+ specifier: github:zkemail/ether-email-auth
+ version: https://codeload.github.com/zkemail/ether-email-auth/tar.gz/10a50a599feed9df9fd8db3e151787e3020f52d6
solidity-stringutils:
- specifier: https://github.com/Arachnid/solidity-stringutils.git
- version: solidity-stringutils.git@https://codeload.github.com/Arachnid/solidity-stringutils/tar.gz/4b2fcc43fa0426e19ce88b1f1ec16f5903a2e461
+ specifier: github:Arachnid/solidity-stringutils
+ version: https://codeload.github.com/Arachnid/solidity-stringutils/tar.gz/4b2fcc43fa0426e19ce88b1f1ec16f5903a2e461
packages:
@@ -41,6 +47,10 @@ packages:
resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==}
engines: {node: '>=6.9.0'}
+ '@email-wallet/contracts@https://gitpkg.now.sh/zkemail/email-wallet/packages/contracts?8ae807dfcfe617a1a3e4b5342a667f0f595b8c82':
+ resolution: {tarball: https://gitpkg.now.sh/zkemail/email-wallet/packages/contracts?8ae807dfcfe617a1a3e4b5342a667f0f595b8c82}
+ version: 1.0.0
+
'@ethereumjs/rlp@4.0.1':
resolution: {integrity: sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==}
engines: {node: '>=14'}
@@ -278,11 +288,17 @@ packages:
peerDependencies:
hardhat: ^2.0.4
+ '@openzeppelin/contracts-upgradeable@4.9.6':
+ resolution: {integrity: sha512-m4iHazOsOCv1DgM7eD7GupTJ+NFVujRZt1wzddDPSVGpWdKq1SKkla5htKG7+IS4d2XOCtzkUNwRZ7Vq5aEUMA==}
+
'@openzeppelin/contracts-upgradeable@5.0.1':
resolution: {integrity: sha512-MvaLoPnVcoZr/qqZP+4cl9piuR4gg0iIGgxVSZ/AL1iId3M6IdEHzz9Naw5Lirl4KKBI6ciTVnX07yL4dOMIJg==}
peerDependencies:
'@openzeppelin/contracts': 5.0.1
+ '@openzeppelin/contracts@3.4.2-solc-0.7':
+ resolution: {integrity: sha512-W6QmqgkADuFcTLzHL8vVoNBtkwjvQRpYIAom7KiUNoLKghyx3FgH0GBjt8NRvigV1ZmMOBllvE1By1C+bi8WpA==}
+
'@openzeppelin/contracts@4.9.6':
resolution: {integrity: sha512-xSmezSupL+y9VkHZJGDoCBpmnB2ogM13ccaYDWqJTfS3dbuHkgjuwDFUmaFauBCboQMGB/S5UqUl2y54X99BmA==}
@@ -314,8 +330,8 @@ packages:
'@rhinestone/erc4337-validation@0.0.1-alpha.2':
resolution: {integrity: sha512-sxBSHoR0hV0rN2bv5HfINHR3RyBChfd0OWH0TP8nlA9FolJ1EezLByxcyrvAgi2QLQ2Zf2zVcNky1qYdfF4NjQ==}
- '@rhinestone/module-bases@https://codeload.github.com/rhinestonewtf/module-bases/tar.gz/a0ad2843be643fffa11ecaf51f7146e301497370':
- resolution: {tarball: https://codeload.github.com/rhinestonewtf/module-bases/tar.gz/a0ad2843be643fffa11ecaf51f7146e301497370}
+ '@rhinestone/module-bases@https://codeload.github.com/rhinestonewtf/module-bases/tar.gz/99c4540e6f7f8b0b4a34accca97914c213671274':
+ resolution: {tarball: https://codeload.github.com/rhinestonewtf/module-bases/tar.gz/99c4540e6f7f8b0b4a34accca97914c213671274}
version: 0.0.1
'@rhinestone/modulekit@https://codeload.github.com/rhinestonewtf/modulekit/tar.gz/924f6aba8a4baab2f98b1a2160aca912bb6546f1':
@@ -441,6 +457,31 @@ packages:
'@types/secp256k1@4.0.6':
resolution: {integrity: sha512-hHxJU6PAEUn0TP4S/ZOzuTUvJWuZ6eIKeNKb5RBpODvSl6hp1Wrw4s7ATY50rklRCScUDpHzVA/DQdSjJ3UoYQ==}
+ '@uniswap/lib@4.0.1-alpha':
+ resolution: {integrity: sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA==}
+ engines: {node: '>=10'}
+
+ '@uniswap/v2-core@1.0.1':
+ resolution: {integrity: sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q==}
+ engines: {node: '>=10'}
+
+ '@uniswap/v3-core@1.0.1':
+ resolution: {integrity: sha512-7pVk4hEm00j9tc71Y9+ssYpO6ytkeI0y7WE9P6UcmNzhxPePwyAxImuhVsTqWK9YFvzgtvzJHi64pBl4jUzKMQ==}
+ engines: {node: '>=10'}
+
+ '@uniswap/v3-core@https://codeload.github.com/Uniswap/v3-core/tar.gz/6562c52e8f75f0c10f9deaf44861847585fc8129':
+ resolution: {tarball: https://codeload.github.com/Uniswap/v3-core/tar.gz/6562c52e8f75f0c10f9deaf44861847585fc8129}
+ version: 1.0.1-solc-0.8
+ engines: {node: '>=10'}
+
+ '@uniswap/v3-periphery@https://codeload.github.com/Uniswap/v3-periphery/tar.gz/697c2474757ea89fec12a4e6db16a574fe259610':
+ resolution: {tarball: https://codeload.github.com/Uniswap/v3-periphery/tar.gz/697c2474757ea89fec12a4e6db16a574fe259610}
+ version: 1.4.4
+ engines: {node: '>=10'}
+
+ '@zk-email/contracts@4.1.0':
+ resolution: {integrity: sha512-5cFgx1kpAPL0Pl5wUb2SA3qaIgYpbp5jjonuYH268BaspvyqSoFS77/UVX+4yjbOQtZmrTntaYXNaseghLLlXw==}
+
'@zk-email/contracts@6.0.3':
resolution: {integrity: sha512-nPSG27431Cz5bzPlR/ltn7qa9k+Joc/6LDHUz+JeFWEP/ff9VnzK11P0ay5qdX14qpQ647varPdxGtulosUtxw==}
@@ -566,6 +607,9 @@ packages:
base-x@3.0.9:
resolution: {integrity: sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==}
+ base64-sol@1.0.1:
+ resolution: {integrity: sha512-ld3cCNMeXt4uJXmLZBHFGMvVpK9KsLVEhPpFRXnvSVAqABKbuNZg/+dsq3NuM+wxFLb/UrVkz7m1ciWmkMfTbg==}
+
bech32@1.1.4:
resolution: {integrity: sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==}
@@ -817,6 +861,10 @@ packages:
elliptic@6.5.5:
resolution: {integrity: sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==}
+ email-wallet-sdk@https://codeload.github.com/zkemail/email-wallet-sdk/tar.gz/a8c200e4855a81adc7b9493afa92a898ffcb8c56:
+ resolution: {tarball: https://codeload.github.com/zkemail/email-wallet-sdk/tar.gz/a8c200e4855a81adc7b9493afa92a898ffcb8c56}
+ version: 0.0.1
+
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -835,10 +883,6 @@ packages:
resolution: {tarball: https://codeload.github.com/matter-labs/era-contracts/tar.gz/db9387690502937de081a959b164db5a5262ce0a}
version: 0.1.0
- erc7579-implementation@https://codeload.github.com/erc7579/erc7579-implementation/tar.gz/4f27201bfd949348ae3a390ae5aa8e85ea19cc11:
- resolution: {tarball: https://codeload.github.com/erc7579/erc7579-implementation/tar.gz/4f27201bfd949348ae3a390ae5aa8e85ea19cc11}
- version: 0.3.1
-
erc7579-implementation@https://codeload.github.com/erc7579/erc7579-implementation/tar.gz/8ab162604cb4878381bb8a15064fec42f775440b:
resolution: {tarball: https://codeload.github.com/erc7579/erc7579-implementation/tar.gz/8ab162604cb4878381bb8a15064fec42f775440b}
version: 0.3.1
@@ -889,6 +933,11 @@ packages:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
+ ether-email-auth@https://codeload.github.com/zkemail/ether-email-auth/tar.gz/10a50a599feed9df9fd8db3e151787e3020f52d6:
+ resolution: {tarball: https://codeload.github.com/zkemail/ether-email-auth/tar.gz/10a50a599feed9df9fd8db3e151787e3020f52d6}
+ version: 1.0.0
+ engines: {node: '18'}
+
ethereum-bloom-filters@1.1.0:
resolution: {integrity: sha512-J1gDRkLpuGNvWYzWslBQR9cDV4nd4kfvVTE/Wy4Kkm4yb3EYRSlyi0eB/inTsSTTVyA0+HyzHgbr95Fn/Z1fSw==}
@@ -1744,6 +1793,9 @@ packages:
resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==}
engines: {node: '>=10'}
+ solady@0.0.123:
+ resolution: {integrity: sha512-F/B8OMCplGsS4FrdPrnEG0xdg8HKede5PwC+Rum8soj/LWxfKckA0p+Uwnlbgey2iI82IHvmSOCNhsdbA+lUrw==}
+
solady@https://codeload.github.com/vectorized/solady/tar.gz/fcb4426857d2464a316544924b20c5d43f2b0088:
resolution: {tarball: https://codeload.github.com/vectorized/solady/tar.gz/fcb4426857d2464a316544924b20c5d43f2b0088}
version: 0.0.217
@@ -1767,7 +1819,7 @@ packages:
peerDependencies:
hardhat: ^2.11.0
- solidity-stringutils.git@https://codeload.github.com/Arachnid/solidity-stringutils/tar.gz/4b2fcc43fa0426e19ce88b1f1ec16f5903a2e461:
+ solidity-stringutils@https://codeload.github.com/Arachnid/solidity-stringutils/tar.gz/4b2fcc43fa0426e19ce88b1f1ec16f5903a2e461:
resolution: {tarball: https://codeload.github.com/Arachnid/solidity-stringutils/tar.gz/4b2fcc43fa0426e19ce88b1f1ec16f5903a2e461}
version: 0.0.0
@@ -2052,6 +2104,25 @@ snapshots:
js-tokens: 4.0.0
picocolors: 1.0.1
+ '@email-wallet/contracts@https://gitpkg.now.sh/zkemail/email-wallet/packages/contracts?8ae807dfcfe617a1a3e4b5342a667f0f595b8c82(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2))':
+ dependencies:
+ '@openzeppelin/contracts': 4.9.6
+ '@openzeppelin/contracts-upgradeable': 4.9.6
+ '@uniswap/v3-core': https://codeload.github.com/Uniswap/v3-core/tar.gz/6562c52e8f75f0c10f9deaf44861847585fc8129
+ '@uniswap/v3-periphery': https://codeload.github.com/Uniswap/v3-periphery/tar.gz/697c2474757ea89fec12a4e6db16a574fe259610
+ '@zk-email/contracts': 4.1.0
+ accountabstraction: https://codeload.github.com/eth-infinitism/account-abstraction/tar.gz/7174d6d845618dbd11cee68eefa715f5263690b6(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2))
+ solady: 0.0.123
+ transitivePeerDependencies:
+ - bufferutil
+ - encoding
+ - ethers
+ - hardhat
+ - lodash
+ - supports-color
+ - typechain
+ - utf-8-validate
+
'@ethereumjs/rlp@4.0.1': {}
'@ethereumjs/util@8.1.0':
@@ -2442,10 +2513,14 @@ snapshots:
- encoding
- supports-color
+ '@openzeppelin/contracts-upgradeable@4.9.6': {}
+
'@openzeppelin/contracts-upgradeable@5.0.1(@openzeppelin/contracts@5.0.2)':
dependencies:
'@openzeppelin/contracts': 5.0.2
+ '@openzeppelin/contracts@3.4.2-solc-0.7': {}
+
'@openzeppelin/contracts@4.9.6': {}
'@openzeppelin/contracts@5.0.1': {}
@@ -2492,7 +2567,7 @@ snapshots:
- typescript
- utf-8-validate
- '@rhinestone/module-bases@https://codeload.github.com/rhinestonewtf/module-bases/tar.gz/a0ad2843be643fffa11ecaf51f7146e301497370(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2))':
+ '@rhinestone/module-bases@https://codeload.github.com/rhinestonewtf/module-bases/tar.gz/99c4540e6f7f8b0b4a34accca97914c213671274(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2))':
dependencies:
'@ERC4337/account-abstraction': accountabstraction@https://codeload.github.com/kopy-kat/account-abstraction/tar.gz/c5887153fbfe3ed09b2637cac39873f96d676f38(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2))
erc7579: erc7579-implementation@https://codeload.github.com/erc7579/erc7579-implementation/tar.gz/8ab162604cb4878381bb8a15064fec42f775440b(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2))
@@ -2513,7 +2588,7 @@ snapshots:
'@ERC4337/account-abstraction-v0.6': accountabstraction@https://codeload.github.com/eth-infinitism/account-abstraction/tar.gz/7174d6d845618dbd11cee68eefa715f5263690b6(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2))
'@prb/math': 4.0.3
'@rhinestone/erc4337-validation': 0.0.1-alpha.2(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2))(typescript@5.5.2)
- '@rhinestone/module-bases': https://codeload.github.com/rhinestonewtf/module-bases/tar.gz/a0ad2843be643fffa11ecaf51f7146e301497370(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2))
+ '@rhinestone/module-bases': https://codeload.github.com/rhinestonewtf/module-bases/tar.gz/99c4540e6f7f8b0b4a34accca97914c213671274(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2))
'@rhinestone/safe7579': https://codeload.github.com/rhinestonewtf/safe7579/tar.gz/d961421641b826f6c970e13b0af18111ec10ebad(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2))(typescript@5.5.2)
'@rhinestone/sentinellist': https://codeload.github.com/rhinestonewtf/sentinellist/tar.gz/446b16c3ec5ba1d0acd730ecf2bebd2ac48f915f
'@safe-global/safe-contracts': 1.4.1(ethers@5.7.2)
@@ -2541,7 +2616,7 @@ snapshots:
'@ERC4337/account-abstraction-v0.6': accountabstraction@https://codeload.github.com/eth-infinitism/account-abstraction/tar.gz/7174d6d845618dbd11cee68eefa715f5263690b6(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2))
'@rhinestone/checknsignatures': https://codeload.github.com/rhinestonewtf/checknsignatures/tar.gz/a4b89298e28e62a984d62aad14a94dff255abdf0
'@rhinestone/erc4337-validation': 0.0.1-alpha.2(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2))(typescript@5.5.2)
- '@rhinestone/module-bases': https://codeload.github.com/rhinestonewtf/module-bases/tar.gz/a0ad2843be643fffa11ecaf51f7146e301497370(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2))
+ '@rhinestone/module-bases': https://codeload.github.com/rhinestonewtf/module-bases/tar.gz/99c4540e6f7f8b0b4a34accca97914c213671274(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2))
'@rhinestone/sentinellist': https://codeload.github.com/rhinestonewtf/sentinellist/tar.gz/446b16c3ec5ba1d0acd730ecf2bebd2ac48f915f
'@safe-global/safe-contracts': 1.4.1(ethers@5.7.2)
ds-test: https://codeload.github.com/dapphub/ds-test/tar.gz/e282159d5170298eb2455a6c05280ab5a73a4ef0
@@ -2707,6 +2782,27 @@ snapshots:
dependencies:
'@types/node': 20.14.8
+ '@uniswap/lib@4.0.1-alpha': {}
+
+ '@uniswap/v2-core@1.0.1': {}
+
+ '@uniswap/v3-core@1.0.1': {}
+
+ '@uniswap/v3-core@https://codeload.github.com/Uniswap/v3-core/tar.gz/6562c52e8f75f0c10f9deaf44861847585fc8129': {}
+
+ '@uniswap/v3-periphery@https://codeload.github.com/Uniswap/v3-periphery/tar.gz/697c2474757ea89fec12a4e6db16a574fe259610':
+ dependencies:
+ '@openzeppelin/contracts': 3.4.2-solc-0.7
+ '@uniswap/lib': 4.0.1-alpha
+ '@uniswap/v2-core': 1.0.1
+ '@uniswap/v3-core': 1.0.1
+ base64-sol: 1.0.1
+
+ '@zk-email/contracts@4.1.0':
+ dependencies:
+ '@openzeppelin/contracts': 4.9.6
+ dotenv: 16.4.5
+
'@zk-email/contracts@6.0.3':
dependencies:
'@openzeppelin/contracts': 5.0.2
@@ -2895,6 +2991,8 @@ snapshots:
dependencies:
safe-buffer: 5.2.1
+ base64-sol@1.0.1: {}
+
bech32@1.1.4: {}
bignumber.js@9.1.2: {}
@@ -3174,6 +3272,26 @@ snapshots:
minimalistic-assert: 1.0.1
minimalistic-crypto-utils: 1.0.1
+ email-wallet-sdk@https://codeload.github.com/zkemail/email-wallet-sdk/tar.gz/a8c200e4855a81adc7b9493afa92a898ffcb8c56(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2)):
+ dependencies:
+ '@email-wallet/contracts': https://gitpkg.now.sh/zkemail/email-wallet/packages/contracts?8ae807dfcfe617a1a3e4b5342a667f0f595b8c82(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2))
+ '@openzeppelin/contracts': 4.9.6
+ '@openzeppelin/contracts-upgradeable': 4.9.6
+ '@uniswap/v3-core': https://codeload.github.com/Uniswap/v3-core/tar.gz/6562c52e8f75f0c10f9deaf44861847585fc8129
+ '@uniswap/v3-periphery': https://codeload.github.com/Uniswap/v3-periphery/tar.gz/697c2474757ea89fec12a4e6db16a574fe259610
+ '@zk-email/contracts': 4.1.0
+ solady: 0.0.123
+ solidity-stringutils: https://codeload.github.com/Arachnid/solidity-stringutils/tar.gz/4b2fcc43fa0426e19ce88b1f1ec16f5903a2e461
+ transitivePeerDependencies:
+ - bufferutil
+ - encoding
+ - ethers
+ - hardhat
+ - lodash
+ - supports-color
+ - typechain
+ - utf-8-validate
+
emoji-regex@8.0.0: {}
encode-utf8@1.0.3: {}
@@ -3187,23 +3305,6 @@ snapshots:
era-contracts@https://codeload.github.com/matter-labs/era-contracts/tar.gz/db9387690502937de081a959b164db5a5262ce0a: {}
- erc7579-implementation@https://codeload.github.com/erc7579/erc7579-implementation/tar.gz/4f27201bfd949348ae3a390ae5aa8e85ea19cc11(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2)):
- dependencies:
- '@rhinestone/sentinellist': https://codeload.github.com/rhinestonewtf/sentinellist/tar.gz/446b16c3ec5ba1d0acd730ecf2bebd2ac48f915f
- account-abstraction: accountabstraction@https://codeload.github.com/eth-infinitism/account-abstraction/tar.gz/f1c5c11b273b7ddae26bb20809419b33ccb8f043(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2))
- ds-test: https://codeload.github.com/dapphub/ds-test/tar.gz/e282159d5170298eb2455a6c05280ab5a73a4ef0
- forge-std: https://codeload.github.com/foundry-rs/forge-std/tar.gz/07263d193d621c4b2b0ce8b4d54af58f6957d97d
- solady: https://codeload.github.com/vectorized/solady/tar.gz/fcb4426857d2464a316544924b20c5d43f2b0088
- transitivePeerDependencies:
- - bufferutil
- - encoding
- - ethers
- - hardhat
- - lodash
- - supports-color
- - typechain
- - utf-8-validate
-
erc7579-implementation@https://codeload.github.com/erc7579/erc7579-implementation/tar.gz/8ab162604cb4878381bb8a15064fec42f775440b(ethers@5.7.2)(hardhat@2.22.5(typescript@5.5.2))(lodash@4.17.21)(typechain@5.2.0(typescript@5.5.2)):
dependencies:
'@rhinestone/sentinellist': https://codeload.github.com/rhinestonewtf/sentinellist/tar.gz/446b16c3ec5ba1d0acd730ecf2bebd2ac48f915f
@@ -3254,6 +3355,8 @@ snapshots:
esutils@2.0.3: {}
+ ether-email-auth@https://codeload.github.com/zkemail/ether-email-auth/tar.gz/10a50a599feed9df9fd8db3e151787e3020f52d6: {}
+
ethereum-bloom-filters@1.1.0:
dependencies:
'@noble/hashes': 1.4.0
@@ -4271,6 +4374,8 @@ snapshots:
astral-regex: 2.0.0
is-fullwidth-code-point: 3.0.0
+ solady@0.0.123: {}
+
solady@https://codeload.github.com/vectorized/solady/tar.gz/fcb4426857d2464a316544924b20c5d43f2b0088: {}
solarray@https://codeload.github.com/sablier-labs/solarray/tar.gz/6bf10cb34cdace52a3ba5fe437e78cc82df92684: {}
@@ -4337,7 +4442,7 @@ snapshots:
shelljs: 0.8.5
web3-utils: 1.10.4
- solidity-stringutils.git@https://codeload.github.com/Arachnid/solidity-stringutils/tar.gz/4b2fcc43fa0426e19ce88b1f1ec16f5903a2e461: {}
+ solidity-stringutils@https://codeload.github.com/Arachnid/solidity-stringutils/tar.gz/4b2fcc43fa0426e19ce88b1f1ec16f5903a2e461: {}
source-map-support@0.5.21:
dependencies:
diff --git a/remappings.txt b/remappings.txt
index 51a7b27f..1b8a6496 100644
--- a/remappings.txt
+++ b/remappings.txt
@@ -18,10 +18,10 @@ solady/=node_modules/solady/src/
solarray/=node_modules/solarray/src/
@prb/math/=node_modules/@prb/math/src/
-ether-email-auth/=lib/ether-email-auth/
+ether-email-auth/=node_modules/ether-email-auth/
@zk-email/contracts/=node_modules/@zk-email/contracts/
solidity-stringutils/=node_modules/solidity-stringutils/
-@matterlabs/zksync-contracts/l2/=src/zksyncDeps/
+@matterlabs/zksync-contracts/l2/contracts/=src/libraries/
erc7579-implementation/=node_modules/erc7579-implementation/
kernel/=node_modules/@zerodev/kernel/src/
ExcessivelySafeCall/=node_modules/excessively-safe-call/src/
diff --git a/script/DeployEmailRecoveryModule.s.sol b/script/DeployEmailRecoveryModule.s.sol
index 259d5fd0..b3d3ae9e 100644
--- a/script/DeployEmailRecoveryModule.s.sol
+++ b/script/DeployEmailRecoveryModule.s.sol
@@ -8,7 +8,7 @@ import { Verifier } from "ether-email-auth/packages/contracts/src/utils/Verifier
import { ECDSAOwnedDKIMRegistry } from
"ether-email-auth/packages/contracts/src/utils/ECDSAOwnedDKIMRegistry.sol";
import { EmailAuth } from "ether-email-auth/packages/contracts/src/EmailAuth.sol";
-import { EmailRecoveryFactory } from "src/EmailRecoveryFactory.sol";
+import { EmailRecoveryFactory } from "src/factories/EmailRecoveryFactory.sol";
import { OwnableValidator } from "src/test/OwnableValidator.sol";
contract DeployEmailRecoveryModuleScript is Script {
diff --git a/script/DeploySafeRecovery.s.sol b/script/DeploySafeRecovery.s.sol
index 3e220fd2..358a4e83 100644
--- a/script/DeploySafeRecovery.s.sol
+++ b/script/DeploySafeRecovery.s.sol
@@ -4,8 +4,8 @@ pragma solidity ^0.8.25;
import { Script } from "forge-std/Script.sol";
import { console } from "forge-std/console.sol";
import { SafeRecoverySubjectHandler } from "src/handlers/SafeRecoverySubjectHandler.sol";
-import { EmailRecoveryFactory } from "src/EmailRecoveryFactory.sol";
-import { EmailRecoveryUniversalFactory } from "src/EmailRecoveryUniversalFactory.sol";
+import { EmailRecoveryFactory } from "src/factories/EmailRecoveryFactory.sol";
+import { EmailRecoveryUniversalFactory } from "src/factories/EmailRecoveryUniversalFactory.sol";
import { Verifier } from "ether-email-auth/packages/contracts/src/utils/Verifier.sol";
import { ECDSAOwnedDKIMRegistry } from
"ether-email-auth/packages/contracts/src/utils/ECDSAOwnedDKIMRegistry.sol";
diff --git a/script/DeployUniversalEmailRecoveryModule.s.sol b/script/DeployUniversalEmailRecoveryModule.s.sol
index 8198d5d9..342d61e8 100644
--- a/script/DeployUniversalEmailRecoveryModule.s.sol
+++ b/script/DeployUniversalEmailRecoveryModule.s.sol
@@ -8,7 +8,7 @@ import { Verifier } from "ether-email-auth/packages/contracts/src/utils/Verifier
import { ECDSAOwnedDKIMRegistry } from
"ether-email-auth/packages/contracts/src/utils/ECDSAOwnedDKIMRegistry.sol";
import { EmailAuth } from "ether-email-auth/packages/contracts/src/EmailAuth.sol";
-import { EmailRecoveryUniversalFactory } from "src/EmailRecoveryUniversalFactory.sol";
+import { EmailRecoveryUniversalFactory } from "src/factories/EmailRecoveryUniversalFactory.sol";
contract DeployUniversalEmailRecoveryModuleScript is Script {
function run() public {
diff --git a/src/EmailRecoveryFactory.sol b/src/EmailRecoveryFactory.sol
deleted file mode 100644
index 3d90e457..00000000
--- a/src/EmailRecoveryFactory.sol
+++ /dev/null
@@ -1,57 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.25;
-
-import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol";
-import { EmailRecoveryManager } from "./EmailRecoveryManager.sol";
-import { UniversalEmailRecoveryModule } from "./modules/UniversalEmailRecoveryModule.sol";
-import { EmailRecoveryModule } from "./modules/EmailRecoveryModule.sol";
-
-contract EmailRecoveryFactory {
- address public immutable verifier;
- address public immutable emailAuthImpl;
-
- event EmailRecoveryModuleDeployed(
- address emailRecoveryModule, address emailRecoveryManager, address subjectHandler
- );
-
- constructor(address _verifier, address _emailAuthImpl) {
- verifier = _verifier;
- emailAuthImpl = _emailAuthImpl;
- }
-
- function deployEmailRecoveryModule(
- bytes32 subjectHandlerSalt,
- bytes32 recoveryManagerSalt,
- bytes32 recoveryModuleSalt,
- bytes memory subjectHandlerBytecode,
- address dkimRegistry,
- address validator,
- bytes4 functionSelector
- )
- external
- returns (address, address, address)
- {
- // Deploy subject handler
- address subjectHandler = Create2.deploy(0, subjectHandlerSalt, subjectHandlerBytecode);
-
- // Deploy recovery manager
- address emailRecoveryManager = address(
- new EmailRecoveryManager{ salt: recoveryManagerSalt }(
- verifier, dkimRegistry, emailAuthImpl, subjectHandler
- )
- );
-
- // Deploy recovery module
- address emailRecoveryModule = address(
- new EmailRecoveryModule{ salt: recoveryModuleSalt }(
- emailRecoveryManager, validator, functionSelector
- )
- );
-
- // Initialize recovery manager with module address
- EmailRecoveryManager(emailRecoveryManager).initialize(emailRecoveryModule);
- emit EmailRecoveryModuleDeployed(emailRecoveryModule, emailRecoveryManager, subjectHandler);
-
- return (emailRecoveryModule, emailRecoveryManager, subjectHandler);
- }
-}
diff --git a/src/EmailRecoveryManager.sol b/src/EmailRecoveryManager.sol
index fe435f92..dd606fee 100644
--- a/src/EmailRecoveryManager.sol
+++ b/src/EmailRecoveryManager.sol
@@ -27,7 +27,10 @@ import { GuardianUtils } from "./libraries/GuardianUtils.sol";
*
* EmailRecoveryManager relies on a dedicated recovery module to execute a recovery attempt. This
* (EmailRecoveryManager) contract defines "what a valid recovery attempt is for an account", and
- * the recovery module defines “how that recovery attempt is executed on the account”.
+ * the recovery module defines “how that recovery attempt is executed on the account”. A
+ * specific email subject handler is also accociated with a recovery manager. A subject handler
+ * defines and validates the recovery email subjects. Developers can write their own subject
+ * handlers to make specifc subjects for recovering modules
*/
contract EmailRecoveryManager is EmailAccountRecovery, Initializable, IEmailRecoveryManager {
using GuardianUtils for mapping(address => GuardianConfig);
@@ -142,11 +145,9 @@ contract EmailRecoveryManager is EmailAccountRecovery, Initializable, IEmailReco
/**
* @notice Returns a two-dimensional array of strings representing the subject templates for an
* acceptance by a new guardian.
- * @dev This function is overridden from EmailAccountRecovery. It is also virtual so can be
- * re-implemented by inheriting contracts
- * to define different acceptance subject templates. This is useful for account implementations
- * which require different data
- * in the subject or if the email should be in a language that is not English.
+ * @dev This is retrieved from the associated subject handler. Developers can write their own
+ * subject handlers, this is useful for account implementations which require different data in
+ * the subject or if the email should be in a language that is not English.
* @return string[][] A two-dimensional array of strings, where each inner array represents a
* set of fixed strings and matchers for a subject template.
*/
@@ -157,11 +158,9 @@ contract EmailRecoveryManager is EmailAccountRecovery, Initializable, IEmailReco
/**
* @notice Returns a two-dimensional array of strings representing the subject templates for
* email recovery.
- * @dev This function is overridden from EmailAccountRecovery. It is also virtual so can be
- * re-implemented by inheriting contracts
- * to define different recovery subject templates. This is useful for account implementations
- * which require different data
- * in the subject or if the email should be in a language that is not English.
+ * @dev This is retrieved from the associated subject handler. Developers can write their own
+ * subject handlers, this is useful for account implementations which require different data in
+ * the subject or if the email should be in a language that is not English.
* @return string[][] A two-dimensional array of strings, where each inner array represents a
* set of fixed strings and matchers for a subject template.
*/
@@ -169,6 +168,13 @@ contract EmailRecoveryManager is EmailAccountRecovery, Initializable, IEmailReco
return IEmailRecoverySubjectHandler(subjectHandler).recoverySubjectTemplates();
}
+ /**
+ * @notice Extracts the account address to be recovered from the subject parameters of an
+ * acceptance email.
+ * @dev This is retrieved from the associated subject handler.
+ * @param subjectParams The subject parameters of the acceptance email.
+ * @param templateIdx The index of the acceptance subject template.
+ */
function extractRecoveredAccountFromAcceptanceSubject(
bytes[] memory subjectParams,
uint256 templateIdx
@@ -182,6 +188,13 @@ contract EmailRecoveryManager is EmailAccountRecovery, Initializable, IEmailReco
.extractRecoveredAccountFromAcceptanceSubject(subjectParams, templateIdx);
}
+ /**
+ * @notice Extracts the account address to be recovered from the subject parameters of a
+ * recovery email.
+ * @dev This is retrieved from the associated subject handler.
+ * @param subjectParams The subject parameters of the recovery email.
+ * @param templateIdx The index of the recovery subject template.
+ */
function extractRecoveredAccountFromRecoverySubject(
bytes[] memory subjectParams,
uint256 templateIdx
@@ -202,8 +215,9 @@ contract EmailRecoveryManager is EmailAccountRecovery, Initializable, IEmailReco
/**
* @notice Configures recovery for the caller's account. This is the first core function
* that must be called during the end-to-end recovery flow
- * @dev Can only be called once for configuration. Sets up the guardians, deploys a router
- * contract, and validates config parameters, ensuring that no recovery is in process
+ * @dev Can only be called once for configuration. Sets up the guardians, and validates config
+ * parameters, ensuring that no recovery is in process. It is possible to configure guardians at
+ * a later stage if neccessary
* @param guardians An array of guardian addresses
* @param weights An array of weights corresponding to each guardian
* @param threshold The threshold weight required for recovery
@@ -247,9 +261,7 @@ contract EmailRecoveryManager is EmailAccountRecovery, Initializable, IEmailReco
/**
* @notice Updates and validates the recovery configuration for the caller's account
* @dev Validates and sets the new recovery configuration for the caller's account, ensuring
- * that no
- * recovery is in process. Reverts if the recovery module address is invalid, if the
- * delay is greater than the expiry, or if the recovery window is too short
+ * that no recovery is in process.
* @param recoveryConfig The new recovery configuration to be set for the caller's account
*/
function updateRecoveryConfig(RecoveryConfig memory recoveryConfig)
@@ -281,10 +293,9 @@ contract EmailRecoveryManager is EmailAccountRecovery, Initializable, IEmailReco
* @notice Accepts a guardian for the specified account. This is the second core function
* that must be called during the end-to-end recovery flow
* @dev Called once per guardian added. Although this adds an extra step to recovery, this
- * acceptance
- * flow is an important security feature to ensure that no typos are made when adding a guardian
- * and that the guardian explicitly consents to the role. Called as part of handleAcceptance
- * in EmailAccountRecovery
+ * acceptance flow is an important security feature to ensure that no typos are made when adding
+ * a guardian, and that the guardian is in control of the specified email address. Called as
+ * part of handleAcceptance in EmailAccountRecovery
* @param guardian The address of the guardian to be accepted
* @param templateIdx The index of the template used for acceptance
* @param subjectParams An array of bytes containing the subject parameters
@@ -333,8 +344,7 @@ contract EmailRecoveryManager is EmailAccountRecovery, Initializable, IEmailReco
/**
* @notice Processes a recovery request for a given account. This is the third core function
* that must be called during the end-to-end recovery flow
- * @dev Reverts if the guardian address is invalid, if the template index is not zero, or if the
- * guardian status is not accepted
+ * @dev Called once per guardian until the threshold is reached
* @param guardian The address of the guardian initiating the recovery
* @param templateIdx The index of the template used for the recovery request
* @param subjectParams An array of bytes containing the subject parameters
@@ -393,11 +403,11 @@ contract EmailRecoveryManager is EmailAccountRecovery, Initializable, IEmailReco
* anyone.
* @dev Validates the recovery request by checking the total weight, that the delay has passed,
* and the request has not expired. Triggers the recovery module to perform the recovery. The
- * recovery module trusts that this contract has validated the recovery attempt. Deletes the
- * recovery
- * request but recovery config state is maintained so future recovery requests can be made
- * without having to reconfigure everything
+ * recovery module trusts that this contract has validated the recovery attempt. This function
+ * deletes the recovery request but recovery config state is maintained so future recovery
+ * requests can be made without having to reconfigure everything
* @param account The address of the account for which the recovery is being completed
+ * @param recoveryCalldata The calldata that is passed to recover the validator
*/
function completeRecovery(address account, bytes memory recoveryCalldata) public override {
if (account == address(0)) {
@@ -476,10 +486,22 @@ contract EmailRecoveryManager is EmailAccountRecovery, Initializable, IEmailReco
/* GUARDIAN LOGIC */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
+ /**
+ * @notice Retrieves the guardian configuration for a given account
+ * @param account The address of the account for which the guardian configuration is being
+ * retrieved
+ * @return GuardianConfig The guardian configuration for the specified account
+ */
function getGuardianConfig(address account) external view returns (GuardianConfig memory) {
return guardianConfigs[account];
}
+ /**
+ * @notice Retrieves the guardian storage details for a given guardian and account
+ * @param account The address of the account associated with the guardian
+ * @param guardian The address of the guardian
+ * @return GuardianStorage The guardian storage details for the specified guardian and account
+ */
function getGuardian(
address account,
address guardian
@@ -491,6 +513,15 @@ contract EmailRecoveryManager is EmailAccountRecovery, Initializable, IEmailReco
return guardiansStorage.getGuardianStorage(account, guardian);
}
+ /**
+ * @notice Sets up guardians for a given account with specified weights and threshold
+ * @dev This function can only be called once and ensures the guardians, weights, and threshold
+ * are correctly configured
+ * @param account The address of the account for which guardians are being set up
+ * @param guardians An array of guardian addresses
+ * @param weights An array of weights corresponding to each guardian
+ * @param threshold The threshold weight required for guardians to approve recovery attempts
+ */
function setupGuardians(
address account,
address[] memory guardians,
@@ -502,14 +533,33 @@ contract EmailRecoveryManager is EmailAccountRecovery, Initializable, IEmailReco
guardianConfigs.setupGuardians(guardiansStorage, account, guardians, weights, threshold);
}
+ /**
+ * @notice Adds a guardian for the caller's account with a specified weight
+ * @dev This function can only be called by the account associated with the guardian and only if
+ * no recovery is in process
+ * @param guardian The address of the guardian to be added
+ * @param weight The weight assigned to the guardian
+ */
function addGuardian(address guardian, uint256 weight) external onlyWhenNotRecovering {
guardiansStorage.addGuardian(guardianConfigs, msg.sender, guardian, weight);
}
+ /**
+ * @notice Removes a guardian for the caller's account
+ * @dev This function can only be called by the account associated with the guardian and only if
+ * no recovery is in process
+ * @param guardian The address of the guardian to be removed
+ */
function removeGuardian(address guardian) external onlyWhenNotRecovering {
guardiansStorage.removeGuardian(guardianConfigs, msg.sender, guardian);
}
+ /**
+ * @notice Changes the threshold for guardian approvals for the caller's account
+ * @dev This function can only be called by the account associated with the guardian config and
+ * only if no recovery is in process
+ * @param threshold The new threshold for guardian approvals
+ */
function changeThreshold(uint256 threshold) external onlyWhenNotRecovering {
guardianConfigs.changeThreshold(msg.sender, threshold);
}
diff --git a/src/EmailRecoveryUniversalFactory.sol b/src/EmailRecoveryUniversalFactory.sol
deleted file mode 100644
index 210c8940..00000000
--- a/src/EmailRecoveryUniversalFactory.sol
+++ /dev/null
@@ -1,52 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.25;
-
-import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol";
-import { EmailRecoveryManager } from "./EmailRecoveryManager.sol";
-import { UniversalEmailRecoveryModule } from "./modules/UniversalEmailRecoveryModule.sol";
-
-contract EmailRecoveryUniversalFactory {
- address public immutable verifier;
- address public immutable emailAuthImpl;
-
- event EmailRecoveryModuleDeployed(
- address emailRecoveryModule, address emailRecoveryManager, address subjectHandler
- );
-
- constructor(address _verifier, address _emailAuthImpl) {
- verifier = _verifier;
- emailAuthImpl = _emailAuthImpl;
- }
-
- function deployUniversalEmailRecoveryModule(
- bytes32 subjectHandlerSalt,
- bytes32 recoveryManagerSalt,
- bytes32 recoveryModuleSalt,
- bytes memory subjectHandlerBytecode,
- address dkimRegistry
- )
- external
- returns (address, address, address)
- {
- // Deploy subject handler
- address subjectHandler = Create2.deploy(0, subjectHandlerSalt, subjectHandlerBytecode);
-
- // Deploy recovery manager
- address emailRecoveryManager = address(
- new EmailRecoveryManager{ salt: recoveryManagerSalt }(
- verifier, dkimRegistry, emailAuthImpl, subjectHandler
- )
- );
-
- // Deploy recovery module
- address emailRecoveryModule = address(
- new UniversalEmailRecoveryModule{ salt: recoveryModuleSalt }(emailRecoveryManager)
- );
-
- // Initialize recovery manager with module address
- EmailRecoveryManager(emailRecoveryManager).initialize(emailRecoveryModule);
- emit EmailRecoveryModuleDeployed(emailRecoveryModule, emailRecoveryManager, subjectHandler);
-
- return (emailRecoveryModule, emailRecoveryManager, subjectHandler);
- }
-}
diff --git a/src/factories/EmailRecoveryFactory.sol b/src/factories/EmailRecoveryFactory.sol
new file mode 100644
index 00000000..8a7989a1
--- /dev/null
+++ b/src/factories/EmailRecoveryFactory.sol
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.25;
+
+import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol";
+import { EmailRecoveryManager } from "../EmailRecoveryManager.sol";
+import { EmailRecoveryModule } from "../modules/EmailRecoveryModule.sol";
+
+/**
+ * @title EmailRecoveryFactory
+ * @notice This contract facilitates the deployment of email recovery modules and their associated
+ * recovery managers and subject handlers.
+ * Create2 is leveraged to ensure deterministic addresses, which assists with module
+ * attestations
+ */
+contract EmailRecoveryFactory {
+ /**
+ * @notice Address of the verifier used by the recovery manager.
+ */
+ address public immutable verifier;
+
+ /**
+ * @notice Address of the EmailAuth.sol implementation.
+ */
+ address public immutable emailAuthImpl;
+
+ event EmailRecoveryModuleDeployed(
+ address emailRecoveryModule, address emailRecoveryManager, address subjectHandler
+ );
+
+ constructor(address _verifier, address _emailAuthImpl) {
+ verifier = _verifier;
+ emailAuthImpl = _emailAuthImpl;
+ }
+
+ /**
+ * @notice Deploys an email recovery module along with its recovery manager and subject handler
+ * @dev The subject handler bytecode cannot be determined ahead of time, unlike the recovery
+ * manager and recovery module, which is why it is passed in directly. In practice, this means a
+ * developer will write their own subject handler, and then pass the bytecode into this factory
+ * function.
+ *
+ * This deployment function deploys an `EmailRecoveryModule`, which takes a target validator and
+ * target function selector
+ * @param subjectHandlerSalt Salt for the subject handler deployment
+ * @param recoveryManagerSalt Salt for the recovery manager deployment
+ * @param recoveryModuleSalt Salt for the recovery module deployment
+ * @param subjectHandlerBytecode Bytecode of the subject handler contract
+ * @param dkimRegistry Address of the DKIM registry
+ * @param validator Address of the validator to be recovered
+ * @param functionSelector Function selector for the recovery function to be called on the
+ * target validator
+ * @return emailRecoveryModule The deployed email recovery module
+ * @return emailRecoveryManager The deployed email recovery manager
+ * @return subjectHandler The deployed subject handler
+ */
+ function deployEmailRecoveryModule(
+ bytes32 subjectHandlerSalt,
+ bytes32 recoveryManagerSalt,
+ bytes32 recoveryModuleSalt,
+ bytes memory subjectHandlerBytecode,
+ address dkimRegistry,
+ address validator,
+ bytes4 functionSelector
+ )
+ external
+ returns (address, address, address)
+ {
+ // Deploy subject handler
+ address subjectHandler = Create2.deploy(0, subjectHandlerSalt, subjectHandlerBytecode);
+
+ // Deploy recovery manager
+ address emailRecoveryManager = address(
+ new EmailRecoveryManager{ salt: recoveryManagerSalt }(
+ verifier, dkimRegistry, emailAuthImpl, subjectHandler
+ )
+ );
+
+ // Deploy recovery module
+ address emailRecoveryModule = address(
+ new EmailRecoveryModule{ salt: recoveryModuleSalt }(
+ emailRecoveryManager, validator, functionSelector
+ )
+ );
+
+ // Initialize recovery manager with module address
+ EmailRecoveryManager(emailRecoveryManager).initialize(emailRecoveryModule);
+ emit EmailRecoveryModuleDeployed(emailRecoveryModule, emailRecoveryManager, subjectHandler);
+
+ return (emailRecoveryModule, emailRecoveryManager, subjectHandler);
+ }
+}
diff --git a/src/factories/EmailRecoveryUniversalFactory.sol b/src/factories/EmailRecoveryUniversalFactory.sol
new file mode 100644
index 00000000..2f704c01
--- /dev/null
+++ b/src/factories/EmailRecoveryUniversalFactory.sol
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.25;
+
+import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol";
+import { EmailRecoveryManager } from "../EmailRecoveryManager.sol";
+import { UniversalEmailRecoveryModule } from "../modules/UniversalEmailRecoveryModule.sol";
+
+/**
+ * @title EmailRecoveryFactory
+ * @notice This contract facilitates the deployment of universal email recovery modules and their
+ * associated recovery managers and subject handlers.
+ * Create2 is leveraged to ensure deterministic addresses, which assists with module
+ * attestations
+ */
+contract EmailRecoveryUniversalFactory {
+ address public immutable verifier;
+ address public immutable emailAuthImpl;
+
+ event EmailRecoveryModuleDeployed(
+ address emailRecoveryModule, address emailRecoveryManager, address subjectHandler
+ );
+
+ constructor(address _verifier, address _emailAuthImpl) {
+ verifier = _verifier;
+ emailAuthImpl = _emailAuthImpl;
+ }
+
+ /**
+ * @notice Deploys a universal email recovery module along with its recovery manager and subject
+ * handler
+ * @dev The subject handler bytecode cannot be determined ahead of time, unlike the recovery
+ * manager and recovery module, which is why it is passed in directly. In practice, this means a
+ * developer will write their own subject handler, and then pass the bytecode into this factory
+ * function. The universal recovery module should have a relatively stable subject handler,
+ * however, developers may want to write a generic subject handler in a slightly different way,
+ * or even in a non-english lanaguage, so the bytecode is still passed in here directly.
+ *
+ * This deployment function deploys an `UniversalEmailRecoveryModule`, which only takes the
+ * target emailRecoveryManager. The target validator and target function selector are set when a
+ * module is installed. This is part of what makes the module generic for recovering any
+ * validator
+ * @param subjectHandlerSalt Salt for the subject handler deployment
+ * @param recoveryManagerSalt Salt for the recovery manager deployment
+ * @param recoveryModuleSalt Salt for the recovery module deployment
+ * @param subjectHandlerBytecode Bytecode of the subject handler contract
+ * @param dkimRegistry Address of the DKIM registry.
+ * @return emailRecoveryModule The deployed email recovery module
+ * @return emailRecoveryManager The deployed email recovery manager
+ * @return subjectHandler The deployed subject handler
+ */
+ function deployUniversalEmailRecoveryModule(
+ bytes32 subjectHandlerSalt,
+ bytes32 recoveryManagerSalt,
+ bytes32 recoveryModuleSalt,
+ bytes memory subjectHandlerBytecode,
+ address dkimRegistry
+ )
+ external
+ returns (address, address, address)
+ {
+ // Deploy subject handler
+ address subjectHandler = Create2.deploy(0, subjectHandlerSalt, subjectHandlerBytecode);
+
+ // Deploy recovery manager
+ address emailRecoveryManager = address(
+ new EmailRecoveryManager{ salt: recoveryManagerSalt }(
+ verifier, dkimRegistry, emailAuthImpl, subjectHandler
+ )
+ );
+
+ // Deploy recovery module
+ address emailRecoveryModule = address(
+ new UniversalEmailRecoveryModule{ salt: recoveryModuleSalt }(emailRecoveryManager)
+ );
+
+ // Initialize recovery manager with module address
+ EmailRecoveryManager(emailRecoveryManager).initialize(emailRecoveryModule);
+ emit EmailRecoveryModuleDeployed(emailRecoveryModule, emailRecoveryManager, subjectHandler);
+
+ return (emailRecoveryModule, emailRecoveryManager, subjectHandler);
+ }
+}
diff --git a/src/handlers/EmailRecoverySubjectHandler.sol b/src/handlers/EmailRecoverySubjectHandler.sol
index dd39eefc..c7e0c026 100644
--- a/src/handlers/EmailRecoverySubjectHandler.sol
+++ b/src/handlers/EmailRecoverySubjectHandler.sol
@@ -14,6 +14,12 @@ contract EmailRecoverySubjectHandler is IEmailRecoverySubjectHandler {
error InvalidAccount();
error InvalidRecoveryModule();
+ /**
+ * @notice Returns a hard-coded two-dimensional array of strings representing the subject
+ * templates for an acceptance by a new guardian.
+ * @return string[][] A two-dimensional array of strings, where each inner array represents a
+ * set of fixed strings and matchers for a subject template.
+ */
function acceptanceSubjectTemplates() public pure returns (string[][] memory) {
string[][] memory templates = new string[][](1);
templates[0] = new string[](5);
@@ -25,6 +31,12 @@ contract EmailRecoverySubjectHandler is IEmailRecoverySubjectHandler {
return templates;
}
+ /**
+ * @notice Returns a hard-coded two-dimensional array of strings representing the subject
+ * templates for email recovery.
+ * @return string[][] A two-dimensional array of strings, where each inner array represents a
+ * set of fixed strings and matchers for a subject template.
+ */
function recoverySubjectTemplates() public pure returns (string[][] memory) {
string[][] memory templates = new string[][](1);
templates[0] = new string[](11);
@@ -42,6 +54,11 @@ contract EmailRecoverySubjectHandler is IEmailRecoverySubjectHandler {
return templates;
}
+ /**
+ * @notice Extracts the account address to be recovered from the subject parameters of an
+ * acceptance email.
+ * @param subjectParams The subject parameters of the acceptance email.
+ */
function extractRecoveredAccountFromAcceptanceSubject(
bytes[] memory subjectParams,
uint256 /* templateIdx */
@@ -53,6 +70,11 @@ contract EmailRecoverySubjectHandler is IEmailRecoverySubjectHandler {
return abi.decode(subjectParams[0], (address));
}
+ /**
+ * @notice Extracts the account address to be recovered from the subject parameters of a
+ * recovery email.
+ * @param subjectParams The subject parameters of the recovery email.
+ */
function extractRecoveredAccountFromRecoverySubject(
bytes[] memory subjectParams,
uint256 /* templateIdx */
@@ -64,6 +86,11 @@ contract EmailRecoverySubjectHandler is IEmailRecoverySubjectHandler {
return abi.decode(subjectParams[0], (address));
}
+ /**
+ * @notice Validates the subject params for an acceptance email
+ * @param subjectParams The subject parameters of the recovery email.
+ * @return accountInEmail The account address in the acceptance email
+ */
function validateAcceptanceSubject(
uint256, /* templateIdx */
bytes[] calldata subjectParams
@@ -81,6 +108,14 @@ contract EmailRecoverySubjectHandler is IEmailRecoverySubjectHandler {
return accountInEmail;
}
+ /**
+ * @notice Validates the subject params for an acceptance email
+ * @param subjectParams The subject parameters of the recovery email.
+ * @param recoveryManager The recovery manager address. Used to help with validation
+ * @return accountInEmail The account address in the acceptance email
+ * @return calldataHash The keccak256 hash of the recovery calldata. Verified against later when
+ * recovery is executed
+ */
function validateRecoverySubject(
uint256, /* templateIdx */
bytes[] calldata subjectParams,
diff --git a/src/handlers/SafeRecoverySubjectHandler.sol b/src/handlers/SafeRecoverySubjectHandler.sol
index d8056967..9eada9ef 100644
--- a/src/handlers/SafeRecoverySubjectHandler.sol
+++ b/src/handlers/SafeRecoverySubjectHandler.sol
@@ -18,6 +18,12 @@ contract SafeRecoverySubjectHandler is IEmailRecoverySubjectHandler {
error InvalidNewOwner();
error InvalidRecoveryModule();
+ /**
+ * @notice Returns a hard-coded two-dimensional array of strings representing the subject
+ * templates for an acceptance by a new guardian.
+ * @return string[][] A two-dimensional array of strings, where each inner array represents a
+ * set of fixed strings and matchers for a subject template.
+ */
function acceptanceSubjectTemplates() public pure returns (string[][] memory) {
string[][] memory templates = new string[][](1);
templates[0] = new string[](5);
@@ -29,6 +35,12 @@ contract SafeRecoverySubjectHandler is IEmailRecoverySubjectHandler {
return templates;
}
+ /**
+ * @notice Returns a hard-coded two-dimensional array of strings representing the subject
+ * templates for email recovery.
+ * @return string[][] A two-dimensional array of strings, where each inner array represents a
+ * set of fixed strings and matchers for a subject template.
+ */
function recoverySubjectTemplates() public pure returns (string[][] memory) {
string[][] memory templates = new string[][](1);
templates[0] = new string[](15);
@@ -50,6 +62,11 @@ contract SafeRecoverySubjectHandler is IEmailRecoverySubjectHandler {
return templates;
}
+ /**
+ * @notice Extracts the account address to be recovered from the subject parameters of an
+ * acceptance email.
+ * @param subjectParams The subject parameters of the acceptance email.
+ */
function extractRecoveredAccountFromAcceptanceSubject(
bytes[] memory subjectParams,
uint256 /* templateIdx */
@@ -61,6 +78,11 @@ contract SafeRecoverySubjectHandler is IEmailRecoverySubjectHandler {
return abi.decode(subjectParams[0], (address));
}
+ /**
+ * @notice Extracts the account address to be recovered from the subject parameters of a
+ * recovery email.
+ * @param subjectParams The subject parameters of the recovery email.
+ */
function extractRecoveredAccountFromRecoverySubject(
bytes[] memory subjectParams,
uint256 /* templateIdx */
@@ -72,6 +94,11 @@ contract SafeRecoverySubjectHandler is IEmailRecoverySubjectHandler {
return abi.decode(subjectParams[0], (address));
}
+ /**
+ * @notice Validates the subject params for an acceptance email
+ * @param subjectParams The subject parameters of the recovery email.
+ * @return accountInEmail The account address in the acceptance email
+ */
function validateAcceptanceSubject(
uint256, /* templateIdx */
bytes[] calldata subjectParams
@@ -89,6 +116,14 @@ contract SafeRecoverySubjectHandler is IEmailRecoverySubjectHandler {
return accountInEmail;
}
+ /**
+ * @notice Validates the subject params for an acceptance email
+ * @param subjectParams The subject parameters of the recovery email.
+ * @param recoveryManager The recovery manager address. Used to help with validation
+ * @return accountInEmail The account address in the acceptance email
+ * @return calldataHash The keccak256 hash of the recovery calldata. Verified against later when
+ * recovery is executed
+ */
function validateRecoverySubject(
uint256, /* templateIdx */
bytes[] calldata subjectParams,
@@ -137,6 +172,14 @@ contract SafeRecoverySubjectHandler is IEmailRecoverySubjectHandler {
return (accountInEmail, calldataHash);
}
+ /**
+ * @notice Gets the previous owner in the Safe owners linked list that points to the
+ * owner passed into the function
+ * @param safe The Safe account to query
+ * @param oldOwner The owner address to get the previous owner for
+ * @return previousOwner The previous owner in the Safe owners linked list pointing to the owner
+ * passed in
+ */
function getPreviousOwnerInLinkedList(
address safe,
address oldOwner
diff --git a/src/libraries/EnumerableGuardianMap.sol b/src/libraries/EnumerableGuardianMap.sol
index 87967f8e..5f78685d 100644
--- a/src/libraries/EnumerableGuardianMap.sol
+++ b/src/libraries/EnumerableGuardianMap.sol
@@ -88,8 +88,6 @@ library EnumerableGuardianMap {
/**
* @dev Removes all key-value pairs from a map. O(n) where n <= 32
*
- * Returns true if the key was removed from the map, that is if it was present.
- *
* @custom:modification This is a new function that did not exist on the
* original Open Zeppelin library.
*/
diff --git a/src/libraries/GuardianUtils.sol b/src/libraries/GuardianUtils.sol
index e09e6c7a..3b5c4344 100644
--- a/src/libraries/GuardianUtils.sol
+++ b/src/libraries/GuardianUtils.sol
@@ -4,6 +4,9 @@ pragma solidity ^0.8.25;
import { EnumerableGuardianMap, GuardianStorage, GuardianStatus } from "./EnumerableGuardianMap.sol";
import { IEmailRecoveryManager } from "../interfaces/IEmailRecoveryManager.sol";
+/**
+ * A helper library to manage guardians
+ */
library GuardianUtils {
using EnumerableGuardianMap for EnumerableGuardianMap.AddressToGuardianMap;
@@ -21,6 +24,12 @@ library GuardianUtils {
error SetupNotCalled();
error UnauthorizedAccountForGuardian();
+ /**
+ * @notice Retrieves the guardian storage details for a given guardian and account
+ * @param account The address of the account associated with the guardian
+ * @param guardian The address of the guardian
+ * @return GuardianStorage The guardian storage details for the specified guardian and account
+ */
function getGuardianStorage(
mapping(address => EnumerableGuardianMap.AddressToGuardianMap) storage guardiansStorage,
address account,
@@ -33,6 +42,16 @@ library GuardianUtils {
return guardiansStorage[account].get(guardian);
}
+ /**
+ * @notice Sets up guardians for a given account with specified weights and threshold
+ * @dev This function can only be called once and ensures the guardians, weights, and threshold
+ * are correctly configured
+ * @param guardiansStorage The guardian storage associated with an account
+ * @param account The address of the account for which guardians are being set up
+ * @param guardians An array of guardian addresses
+ * @param weights An array of weights corresponding to each guardian
+ * @param threshold The threshold weight required for guardians to approve recovery attempts
+ */
function setupGuardians(
mapping(address => IEmailRecoveryManager.GuardianConfig) storage guardianConfigs,
mapping(address => EnumerableGuardianMap.AddressToGuardianMap) storage guardiansStorage,
@@ -93,6 +112,12 @@ library GuardianUtils {
});
}
+ /**
+ * @notice Updates the status for a guardian
+ * @param account The address of the account associated with the guardian
+ * @param guardian The address of the guardian
+ * @param newStatus The new status for the guardian
+ */
function updateGuardianStatus(
mapping(address => EnumerableGuardianMap.AddressToGuardianMap) storage guardiansStorage,
address account,
@@ -112,6 +137,13 @@ library GuardianUtils {
});
}
+ /**
+ * @notice Adds a guardian for the caller's account with a specified weight
+ * @param guardianConfigs The guardian config storage associated with an account
+ * @param account The address of the account associated with the guardian
+ * @param guardian The address of the guardian to be added
+ * @param weight The weight assigned to the guardian
+ */
function addGuardian(
mapping(address => EnumerableGuardianMap.AddressToGuardianMap) storage guardiansStorage,
mapping(address => IEmailRecoveryManager.GuardianConfig) storage guardianConfigs,
@@ -151,6 +183,12 @@ library GuardianUtils {
emit AddedGuardian(account, guardian);
}
+ /**
+ * @notice Removes a guardian for the caller's account
+ * @param guardianConfigs The guardian config storage associated with an account
+ * @param account The address of the account associated with the guardian
+ * @param guardian The address of the guardian to be removed
+ */
function removeGuardian(
mapping(address => EnumerableGuardianMap.AddressToGuardianMap) storage guardiansStorage,
mapping(address => IEmailRecoveryManager.GuardianConfig) storage guardianConfigs,
@@ -179,6 +217,10 @@ library GuardianUtils {
emit RemovedGuardian(account, guardian);
}
+ /**
+ * @notice Removes all guardians associated with an account
+ * @param account The address of the account associated with the guardians
+ */
function removeAllGuardians(
mapping(address => EnumerableGuardianMap.AddressToGuardianMap) storage guardiansStorage,
address account
@@ -188,6 +230,11 @@ library GuardianUtils {
guardiansStorage[account].removeAll(guardiansStorage[account].keys());
}
+ /**
+ * @notice Changes the threshold for guardian approvals for the caller's account
+ * @param account The address of the account associated with the guardians
+ * @param threshold The new threshold for guardian approvals
+ */
function changeThreshold(
mapping(address => IEmailRecoveryManager.GuardianConfig) storage guardianConfigs,
address account,
diff --git a/src/zksyncDeps/contracts/L2ContractHelper.sol b/src/libraries/L2ContractHelper.sol
similarity index 100%
rename from src/zksyncDeps/contracts/L2ContractHelper.sol
rename to src/libraries/L2ContractHelper.sol
diff --git a/src/modules/EmailRecoveryModule.sol b/src/modules/EmailRecoveryModule.sol
index 2c1d4b9b..b8553550 100644
--- a/src/modules/EmailRecoveryModule.sol
+++ b/src/modules/EmailRecoveryModule.sol
@@ -13,7 +13,10 @@ import { IEmailRecoveryManager } from "../interfaces/IEmailRecoveryManager.sol";
* permissioning certain functions to be called on validators. It facilitates recovery by
* integration with a trusted email recovery manager. The module defines how a recovery request is
* executed on a validator, while the trusted recovery manager defines what a valid
- * recovery request is
+ * recovery request is.
+ *
+ * This recovery module targets a specific validator, so this contract should be deployed per
+ * validator
*/
contract EmailRecoveryModule is ERC7579ExecutorBase, IEmailRecoveryModule {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
@@ -25,8 +28,14 @@ contract EmailRecoveryModule is ERC7579ExecutorBase, IEmailRecoveryModule {
*/
address public immutable emailRecoveryManager;
+ /**
+ * Validator being recovered
+ */
address public immutable validator;
+ /**
+ * function selector that is called when recovering validator
+ */
bytes4 public immutable selector;
/**
@@ -58,7 +67,7 @@ contract EmailRecoveryModule is ERC7579ExecutorBase, IEmailRecoveryModule {
/**
* Initializes the module with the threshold and guardians
- * @dev data is encoded as follows: abi.encode(validator, isInstalledContext, initialSelector,
+ * @dev data is encoded as follows: abi.encode(isInstalledContext,
* guardians, weights, threshold, delay, expiry)
*
* @param data encoded data for recovery configuration
@@ -111,6 +120,11 @@ contract EmailRecoveryModule is ERC7579ExecutorBase, IEmailRecoveryModule {
!= 0;
}
+ /**
+ * Check if the recovery module is authorized to recover the account
+ * @param smartAccount The smart account to check
+ * @return true if the module is authorized, false otherwise
+ */
function isAuthorizedToRecover(address smartAccount) external view returns (bool) {
return authorized[smartAccount];
}
diff --git a/src/modules/UniversalEmailRecoveryModule.sol b/src/modules/UniversalEmailRecoveryModule.sol
index 2dff414e..947c8a6c 100644
--- a/src/modules/UniversalEmailRecoveryModule.sol
+++ b/src/modules/UniversalEmailRecoveryModule.sol
@@ -15,6 +15,9 @@ import { IEmailRecoveryManager } from "../interfaces/IEmailRecoveryManager.sol";
* integration with a trusted email recovery manager. The module defines how a recovery request is
* executed on a validator, while the trusted recovery manager defines what a valid
* recovery request is
+ *
+ * This recovery module is generic and does not target a specific validator. An account may add
+ * multiple validators to this recovery module
*/
contract UniversalEmailRecoveryModule is ERC7579ExecutorBase, IUniversalEmailRecoveryModule {
using SentinelListLib for SentinelListLib.SentinelList;
@@ -226,6 +229,11 @@ contract UniversalEmailRecoveryModule is ERC7579ExecutorBase, IUniversalEmailRec
!= 0;
}
+ /**
+ * Check if the recovery module is authorized to recover the account
+ * @param smartAccount The smart account to check
+ * @return true if the module is authorized, false otherwise
+ */
function isAuthorizedToRecover(address smartAccount) external view returns (bool) {
return getAllowedValidators(smartAccount).length > 0;
}
diff --git a/src/test/MockRegistry.sol b/src/test/MockRegistry.sol
deleted file mode 100644
index 93d6ef6b..00000000
--- a/src/test/MockRegistry.sol
+++ /dev/null
@@ -1,45 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.25;
-
-import { IERC7484 } from "safe7579/interfaces/IERC7484.sol";
-
-/**
- * Used to setup the Safe in SafeIntegrationBase.sol. Taken from
- * safe7579/test/mocks/MockRegistry.sol
- */
-contract MockRegistry is IERC7484 {
- event Log(address sender);
-
- function check(address module) external view { }
-
- function checkForAccount(address smartAccount, address module) external view { }
-
- function check(address module, uint256 moduleType) external view { }
-
- function checkForAccount(
- address smartAccount,
- address module,
- uint256 moduleType
- )
- external
- view
- override
- { }
-
- function check(address module, address[] calldata attesters, uint256 threshold) external view { }
-
- function check(
- address module,
- uint256 moduleType,
- address[] calldata attesters,
- uint256 threshold
- )
- external
- view
- { }
-
- function trustAttesters(uint8, /* threshold */ address[] calldata /* attesters */ ) external {
- // emit Log(msg.sender);
- // emit NewTrustedAttesters();
- }
-}
diff --git a/test/integration/OwnableValidatorRecovery/EmailRecoveryModule/EmailRecoveryModuleBase.t.sol b/test/integration/OwnableValidatorRecovery/EmailRecoveryModule/EmailRecoveryModuleBase.t.sol
index 7a1d716c..51aa3bef 100644
--- a/test/integration/OwnableValidatorRecovery/EmailRecoveryModule/EmailRecoveryModuleBase.t.sol
+++ b/test/integration/OwnableValidatorRecovery/EmailRecoveryModule/EmailRecoveryModuleBase.t.sol
@@ -12,7 +12,7 @@ import {
} from "ether-email-auth/packages/contracts/src/EmailAuth.sol";
import { SubjectUtils } from "ether-email-auth/packages/contracts/src/libraries/SubjectUtils.sol";
import { EmailRecoverySubjectHandler } from "src/handlers/EmailRecoverySubjectHandler.sol";
-import { EmailRecoveryFactory } from "src/EmailRecoveryFactory.sol";
+import { EmailRecoveryFactory } from "src/factories/EmailRecoveryFactory.sol";
import { EmailRecoveryManager } from "src/EmailRecoveryManager.sol";
import { OwnableValidator } from "src/test/OwnableValidator.sol";
import { IntegrationBase } from "../../IntegrationBase.t.sol";
diff --git a/test/integration/OwnableValidatorRecovery/UniversalEmailRecoveryModule/UniversalEmailRecoveryModuleBase.t.sol b/test/integration/OwnableValidatorRecovery/UniversalEmailRecoveryModule/UniversalEmailRecoveryModuleBase.t.sol
index 272c9e04..bfb9ec68 100644
--- a/test/integration/OwnableValidatorRecovery/UniversalEmailRecoveryModule/UniversalEmailRecoveryModuleBase.t.sol
+++ b/test/integration/OwnableValidatorRecovery/UniversalEmailRecoveryModule/UniversalEmailRecoveryModuleBase.t.sol
@@ -12,8 +12,8 @@ import {
} from "ether-email-auth/packages/contracts/src/EmailAuth.sol";
import { SubjectUtils } from "ether-email-auth/packages/contracts/src/libraries/SubjectUtils.sol";
import { EmailRecoverySubjectHandler } from "src/handlers/EmailRecoverySubjectHandler.sol";
-import { EmailRecoveryFactory } from "src/EmailRecoveryFactory.sol";
-import { EmailRecoveryUniversalFactory } from "src/EmailRecoveryUniversalFactory.sol";
+import { EmailRecoveryFactory } from "src/factories/EmailRecoveryFactory.sol";
+import { EmailRecoveryUniversalFactory } from "src/factories/EmailRecoveryUniversalFactory.sol";
import { EmailRecoveryManager } from "src/EmailRecoveryManager.sol";
import { OwnableValidator } from "src/test/OwnableValidator.sol";
import { IntegrationBase } from "../../IntegrationBase.t.sol";
diff --git a/test/unit/EmailRecoveryFactory/deployEmailRecoveryModule.t.sol b/test/unit/EmailRecoveryFactory/deployEmailRecoveryModule.t.sol
index 080f17e3..3f3594ac 100644
--- a/test/unit/EmailRecoveryFactory/deployEmailRecoveryModule.t.sol
+++ b/test/unit/EmailRecoveryFactory/deployEmailRecoveryModule.t.sol
@@ -4,8 +4,8 @@ pragma solidity ^0.8.25;
import { console2 } from "forge-std/console2.sol";
import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol";
import { UnitBase } from "../UnitBase.t.sol";
-import { EmailRecoveryFactory } from "src/EmailRecoveryFactory.sol";
-import { EmailRecoveryUniversalFactory } from "src/EmailRecoveryUniversalFactory.sol";
+import { EmailRecoveryFactory } from "src/factories/EmailRecoveryFactory.sol";
+import { EmailRecoveryUniversalFactory } from "src/factories/EmailRecoveryUniversalFactory.sol";
import { EmailRecoverySubjectHandler } from "src/handlers/EmailRecoverySubjectHandler.sol";
import { EmailRecoveryManager } from "src/EmailRecoveryManager.sol";
import { EmailRecoveryModule } from "src/modules/EmailRecoveryModule.sol";
diff --git a/test/unit/EmailRecoveryUniversalFactory/deployUniversalEmailRecoveryModule.t.sol b/test/unit/EmailRecoveryUniversalFactory/deployUniversalEmailRecoveryModule.t.sol
index 79591fc0..686f43ab 100644
--- a/test/unit/EmailRecoveryUniversalFactory/deployUniversalEmailRecoveryModule.t.sol
+++ b/test/unit/EmailRecoveryUniversalFactory/deployUniversalEmailRecoveryModule.t.sol
@@ -4,8 +4,8 @@ pragma solidity ^0.8.25;
import { console2 } from "forge-std/console2.sol";
import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol";
import { UnitBase } from "../UnitBase.t.sol";
-import { EmailRecoveryFactory } from "src/EmailRecoveryFactory.sol";
-import { EmailRecoveryUniversalFactory } from "src/EmailRecoveryUniversalFactory.sol";
+import { EmailRecoveryFactory } from "src/factories/EmailRecoveryFactory.sol";
+import { EmailRecoveryUniversalFactory } from "src/factories/EmailRecoveryUniversalFactory.sol";
import { EmailRecoverySubjectHandler } from "src/handlers/EmailRecoverySubjectHandler.sol";
import { EmailRecoveryManager } from "src/EmailRecoveryManager.sol";
import { UniversalEmailRecoveryModule } from "src/modules/UniversalEmailRecoveryModule.sol";
diff --git a/test/unit/SafeUnitBase.t.sol b/test/unit/SafeUnitBase.t.sol
index 76764502..bd381261 100644
--- a/test/unit/SafeUnitBase.t.sol
+++ b/test/unit/SafeUnitBase.t.sol
@@ -12,7 +12,7 @@ import { EmailRecoveryManagerHarness } from "./EmailRecoveryManagerHarness.sol";
import { EmailRecoveryManager } from "src/EmailRecoveryManager.sol";
import { UniversalEmailRecoveryModule } from "src/modules/UniversalEmailRecoveryModule.sol";
import { SafeRecoverySubjectHandlerHarness } from "./SafeRecoverySubjectHandlerHarness.sol";
-import { EmailRecoveryFactory } from "src/EmailRecoveryFactory.sol";
+import { EmailRecoveryFactory } from "src/factories/EmailRecoveryFactory.sol";
import { IntegrationBase } from "../integration/IntegrationBase.t.sol";
abstract contract SafeUnitBase is IntegrationBase {
diff --git a/test/unit/UnitBase.t.sol b/test/unit/UnitBase.t.sol
index 76fa540d..ec58a2e3 100644
--- a/test/unit/UnitBase.t.sol
+++ b/test/unit/UnitBase.t.sol
@@ -25,8 +25,8 @@ import { EmailRecoveryManagerHarness } from "./EmailRecoveryManagerHarness.sol";
import { EmailRecoverySubjectHandler } from "src/handlers/EmailRecoverySubjectHandler.sol";
import { UniversalEmailRecoveryModuleHarness } from "./UniversalEmailRecoveryModuleHarness.sol";
import { EmailRecoveryManager } from "src/EmailRecoveryManager.sol";
-import { EmailRecoveryFactory } from "src/EmailRecoveryFactory.sol";
-import { EmailRecoveryUniversalFactory } from "src/EmailRecoveryUniversalFactory.sol";
+import { EmailRecoveryFactory } from "src/factories/EmailRecoveryFactory.sol";
+import { EmailRecoveryUniversalFactory } from "src/factories/EmailRecoveryUniversalFactory.sol";
import { OwnableValidator } from "src/test/OwnableValidator.sol";
import { MockGroth16Verifier } from "src/test/MockGroth16Verifier.sol";
diff --git a/test/unit/modules/EmailRecoveryModule/EmailRecoveryModuleBase.t.sol b/test/unit/modules/EmailRecoveryModule/EmailRecoveryModuleBase.t.sol
index 841f1d9d..996eb8f7 100644
--- a/test/unit/modules/EmailRecoveryModule/EmailRecoveryModuleBase.t.sol
+++ b/test/unit/modules/EmailRecoveryModule/EmailRecoveryModuleBase.t.sol
@@ -25,7 +25,7 @@ import { EmailRecoveryManagerHarness } from "../../EmailRecoveryManagerHarness.s
import { EmailRecoverySubjectHandler } from "src/handlers/EmailRecoverySubjectHandler.sol";
import { EmailRecoveryModule } from "src/modules/EmailRecoveryModule.sol";
import { EmailRecoveryManager } from "src/EmailRecoveryManager.sol";
-import { EmailRecoveryFactory } from "src/EmailRecoveryFactory.sol";
+import { EmailRecoveryFactory } from "src/factories/EmailRecoveryFactory.sol";
import { OwnableValidator } from "src/test/OwnableValidator.sol";
import { MockGroth16Verifier } from "src/test/MockGroth16Verifier.sol";