28. Install the recovery module and manage guardians
Installing the recovery module gives an identity a way back when its MANAGEMENT keys are lost. The Identity Owner installs the RecoveryModule with the guardians it chose, a threshold, a delay and an expiration, and gives the module the authority to change keys. It then keeps the guardian set current. All of this takes a MANAGEMENT key, so it is done while the owner still has its keys, ideally in the same session as the creation of the identity.
At a glance
Triggered by
Identity Owner
Actors
Identity Owner, Guardian, Platform
Contracts
ONCHAINID, RecoveryModule, ERC734Validator
Preconditions
Result
The module is installed on the identity with its guardians, weights, threshold, delay and expiration, and holds the MANAGEMENT purpose
Actors and roles
Identity Owner
Chooses the guardians and the settings, installs the module and maintains it.
A MANAGEMENT key of the identity
Guardian
Agrees to act for the owner and provides the signer it signs with. Does not transact in this flow.
A signer of its own. No key of the identity
Platform
Builds the installation data, prepares the calls and shows the settings back to the owner.
Whatever keys the owner has authorized
Sequence
Step by step
Step 1. Choose the guardians and the settings
Who: Identity Owner, with the Platform
Call: Off-chain
Before the call: The owner has decided who its guardians are, and each guardian has given the signer it signs with.
What happens: The owner fixes five settings.
Guardians
The signers that can authorize a recovery together. Each is given as signer data: for an address, its 20 bytes.
Weights
The weight of each guardian's signature, one for each guardian and at least 1. One each, unless the owner wants a guardian to count for more.
Threshold
The total weight a recovery or a cancellation by guardians must reach. For example, 2 with three guardians of weight 1.
Delay
The time between the scheduling of a recovery and the moment it can execute. The owner can cancel during this time.
Expiration
The time a recovery stays executable after its delay. Past it, the recovery has to be scheduled again.
The Platform encodes them as the installation data, initData: the two-byte length of the executor arguments, the executor arguments abi.encodePacked(uint32 delay, uint32 expiration), the two-byte length of the guardian arguments, and the guardian arguments abi.encode(bytes[] guardians, uint64 threshold, uint64[] weights).
What changes: Nothing on-chain.
Step 2. Install the module
Who: Identity Owner (MANAGEMENT key)
Call: Identity.installModule(2, recoveryModule, initData), where 2 is the ERC-7579 module type of an executor
Before the call: Step 1 is done. The Platform shows the owner the guardians and the settings it is about to install.
What the contract checks:
The caller holds
MANAGEMENTon the identity, or is the identity itself. Otherwise the call reverts withSenderDoesNotHaveManagementKey.The module declares itself an executor. Otherwise
ERC7579MismatchedModuleTypeId.The module is not already installed on the identity. Otherwise
ERC7579AlreadyInstalledModule.In the module, the identity has no settings yet. Otherwise
ERC7579DelayedExecutorAlreadyInstalledorERC7579MultisigAlreadyInstalled.The guardian arguments decode and list at least one guardian. Otherwise
ERC7579MultisigInvalidInitDataorERC7579MultisigEmptySigners.Every guardian is at least 20 bytes long and is listed once. Otherwise
ERC7579MultisigInvalidSignerorERC7579MultisigAlreadyExists.There is one weight for each guardian, and no weight is zero. Otherwise
ERC7579MultisigMismatchedLengthorERC7579MultisigInvalidWeight.The threshold is above zero and within the total weight of the guardians. Otherwise
ERC7579MultisigZeroThresholdorERC7579MultisigUnreachableThreshold.
What changes: The identity records the module as one of its executors and emits ModuleInstalled, then ModuleInstallData(moduleTypeId, module, initData, caller). The module stores the guardians, the weights, the threshold, the delay and the expiration for this identity, and emits ERC7913SignerAdded for each guardian, ERC7579MultisigWeightChanged for each weight above 1, ERC7913ThresholdSet, ERC7579ExecutorDelayUpdated and ERC7579ExecutorExpirationUpdated. The module cannot change a key yet.
Step 3. Give the module the authority to change keys
Who: Identity Owner (MANAGEMENT key)
Call: Identity.addKeyWithData(keyHash, 1, 4, signerData, clientData), with signerData the 20 bytes of the module's address, keyHash = keccak256(signerData), purpose 1 for MANAGEMENT, key type 4 for MODULE and an empty clientData
Before the call: Step 2 is confirmed.
What the contract checks: As for any key, in 22: the caller holds MANAGEMENT, the key hash matches the signer data, and the module does not already hold the purpose. Otherwise SenderDoesNotHaveManagementKey, InvalidSignerData or KeyAlreadyRegistered.
What changes: The key registry records the module as a key of type MODULE with the purpose MANAGEMENT, and emits KeyAdded(identity, keyHash, purpose, keyType, signerData, clientData, caller). A recovery executed by the module is now allowed to add a MANAGEMENT key to the identity. A key of type MODULE cannot sign, and it does not count as a manager when the identity checks that a MANAGEMENT signer remains.
Step 4. Confirm the settings
Who: Platform
Call: Off-chain, using read calls
Before the call: Steps 2 and 3 are confirmed.
What happens: The Platform reads Identity.isModuleInstalled(2, recoveryModule, "") and Identity.keyHasPurpose(keyHash, 1), which must both be true. On the module, it reads getGuardians(identity, start, end), getGuardianCount(identity), signerWeight(identity, guardian) for each guardian, totalWeight(identity), threshold(identity), getDelay(identity) and getExpiration(identity), and shows them to the owner. The owner tells each guardian that it is now a guardian of this identity, and how a request for recovery reaches it.
What changes: Nothing on-chain.
Step 5. Change the guardians or the settings
Later, each time a guardian has to be replaced or a setting adjusted.
Who: Identity Owner (MANAGEMENT key)
Call: Identity.execute(identity, 0, data). data is a call of the identity to its own ERC-7579 execute(mode, executionCalldata), and executionCalldata targets the module with one of: addGuardians(guardians), removeGuardians(guardians), setSignerWeights(guardians, weights), setThreshold(threshold), setDelay(delay), setExpiration(expiration)
Before the call: The owner has decided the change. For a new guardian, the guardian has agreed and given its signer data. The Platform reads the current settings, as in step 4.
What the contract checks:
The request comes from a
MANAGEMENTkey. The identity's execution queue then runs it at once: the identity calls itself, and the module sees the identity as its caller. A request queued by a key that does not holdMANAGEMENTis recorded and not run.The module changes the settings of its caller, and of no one else. Only a call that comes from the identity changes the identity's settings. The identity refuses to dispatch a call into one of its own modules unless it is calling itself:
OwnModuleTargetBlocked.In the module: a guardian to add is at least 20 bytes long and not already listed (
ERC7579MultisigInvalidSigner,ERC7579MultisigAlreadyExists). A guardian to remove or to reweight is listed (ERC7579MultisigNonexistentSigner). A weight is not zero (ERC7579MultisigInvalidWeight). After the change, the threshold is above zero and within the total weight (ERC7579MultisigZeroThreshold,ERC7579MultisigUnreachableThreshold).
A failure of check 2 or 3 does not revert the owner's transaction. The queue catches it: the transaction succeeds, the identity's queue emits ExecutionFailed, and nothing changes. The error of the module is not returned to the caller.
What changes: The identity's queue emits Executed, and the module updates the settings of this identity. A change of guardians, of weights, of threshold or of expiration applies at once. A change of delay does not. A longer delay takes effect after the new delay itself, capped at 5 days. A shorter delay takes effect after the difference between the current delay and the new one, so a recovery can never execute sooner than the delay that applied when the change was made. Until then, getDelay(identity) returns the current delay, the pending one and the time it takes effect.
State after the flow
Identity.isModuleInstalled(2, recoveryModule, "")is true.Identity.keyHasPurpose(keyHash, 1)is true for the module's key.RecoveryModule.getGuardianCount(identity),totalWeight(identity),threshold(identity),getDelay(identity)andgetExpiration(identity)return the settings the owner chose.RecoveryModule.isGuardian(identity, guardian)is true for each guardian.The identity's own keys, claims, linked wallets and positions are unchanged.
No recovery is scheduled. The guardians have no authority until they sign one together.
Failure modes
installModule reverts with SenderDoesNotHaveManagementKey
The caller does not hold MANAGEMENT on the identity (step 2, check 1)
Submit with a MANAGEMENT key
installModule reverts with ERC7579MismatchedModuleTypeId
The module does not declare the module type that was passed (step 2, check 2)
Use the address of a RecoveryModule and module type 2
installModule reverts with ERC7579AlreadyInstalledModule
The module is already installed on this identity (step 2, check 3)
Change its settings with step 5
installModule reverts with ERC7579DelayedExecutorAlreadyInstalled or ERC7579MultisigAlreadyInstalled
The module already holds settings for this identity (step 2, check 4)
Change them with step 5
installModule reverts with ERC7579MultisigInvalidInitData or ERC7579MultisigEmptySigners
The guardian arguments are malformed, or list no guardian (step 2, check 5)
Rebuild initData as in step 1
installModule reverts with ERC7579MultisigInvalidSigner or ERC7579MultisigAlreadyExists
A guardian is shorter than 20 bytes, or listed twice (step 2, check 6)
Pass each guardian once, as signer data
installModule reverts with ERC7579MultisigMismatchedLength or ERC7579MultisigInvalidWeight
The weights do not match the guardians one for one, or a weight is zero (step 2, check 7)
Give each guardian a weight of at least 1
installModule reverts with ERC7579MultisigZeroThreshold or ERC7579MultisigUnreachableThreshold
The threshold is zero, or above the total weight (step 2, check 8)
Choose a threshold between 1 and the total weight
addKeyWithData reverts with InvalidSignerData
keyHash is not the hash of the module's 20-byte address (step 3)
Compute keyHash = keccak256(abi.encodePacked(recoveryModule))
addKeyWithData reverts with KeyAlreadyRegistered
The module already holds MANAGEMENT (step 3)
Nothing to do
A recovery executes and reverts with ExecutorPurposeNotAuthorized
The module was installed and step 3 was never done
Do step 3 while a MANAGEMENT key exists. Check it in step 4
A change of settings has no effect and no Executed event appears
The request was queued by a key that does not hold MANAGEMENT (step 5, check 1)
Submit with a MANAGEMENT key
The transaction succeeds, ExecutionFailed is emitted and the settings are unchanged
The call targeted the module directly and not through the identity calling itself (step 5, check 2), or the module refused the change, for example a removal after which the threshold cannot be reached (step 5, check 3)
Wrap the call as in step 5. Lower the threshold first, or add the replacement before removing
A user operation that targets the module reverts with OwnModuleTargetBlocked
The identity does not dispatch a call into its own modules (step 5, check 2)
Make the identity call itself, as in step 5
A new delay does not apply
A change of delay waits for its transition period (step 5)
Read getDelay(identity) for the pending value and its effect time
Common misunderstandings
Wrong: "Installing the module is enough."
Right: Installation stores the guardians and the settings. The module can only add a key to the identity once it holds the
MANAGEMENTpurpose itself, as a key of typeMODULE. Without step 3, a recovery is scheduled, waits for its delay, and fails when it executes.
Wrong: "Guardians can replace one another."
Right: The guardian set and the settings change only by a call from the identity itself, which takes a
MANAGEMENTkey. Guardians can do two things together: authorize an operation that executes after the delay, and cancel a scheduled one.
Wrong: "The module is one more manager of the identity."
Right: A key of type
MODULEcannot sign anything. It lets the module execute what the guardians authorized, after the delay. It does not count when the identity checks that aMANAGEMENTsigner remains.
Wrong: "A guardian has to be an ONCHAINID."
Right: A guardian is a signer: an address, or any other signer type the identity layer verifies. It needs no identity, no key on the protected identity and no role anywhere.
Integration notes
Offer this flow in the same session as the creation of the identity (02). Steps 2 and 3 can be sent together. An identity that loses its keys before step 3 has no on-chain recovery.
Always run step 4, and show the owner the result. A module installed without the
MANAGEMENTpurpose looks configured and cannot recover anything.One
RecoveryModuleserves every identity, like every module of the identity layer. It keeps its settings per identity, and a guardian's signature is bound to the identity it was made for.When the executor arguments are left out of the installation data, the module applies its defaults: a delay of 5 days and an expiration of 60 days.
Choose the delay with the owner. It is the time the owner has to cancel a recovery it did not ask for. Monitor scheduled recoveries on every identity your application serves.
Guardians are stored as signer data. For an address, pass
abi.encodePacked(address), in the installation data and in every later call.Replace a guardian by adding the new one before removing the old one, so that the threshold stays reachable at every step.
After each change, read the settings again as in step 4. A change that the module refused does not revert the transaction: it shows as
ExecutionFailedon the identity's queue.After a recovery, review the guardians: the event that cost the keys may have exposed them too.
Uninstalling the module with
Identity.uninstallModule(2, recoveryModule, "")removes the purposes the module holds and its guardian settings for the identity. The identity then has no on-chain recovery until the module is installed again. The delay is not forgotten at once: it winds down over its own length, and an installation made in the meantime follows the transition rules of step 5, so uninstalling and reinstalling cannot shorten the delay at once.Index the module's events, filtered by identity, to follow a configuration:
ERC7913SignerAdded,ERC7913SignerRemoved,ERC7579MultisigWeightChanged,ERC7913ThresholdSet,ERC7579ExecutorDelayUpdated,ERC7579ExecutorExpirationUpdated.
Related flows
21 Recover access to an ONCHAINID : what the guardians do when the keys are lost
02 Deploy an ONCHAINID : the moment to install the module
22 Add and remove keys on an ONCHAINID : the key operations used in step 3