Axir Wallet Social Recovery Contract
The AxirSocialRecovery
contract provides a mechanism for social recovery of wallet ownership. Below is an explanation of its important functions.
Contract Structure
State Variables
guardians
: Maps a wallet address to its array of guardian addresses.thresholds
: Maps a wallet address to its guardian threshold.recoveries
: Maps a wallet address to its current recovery struct.nonces
: Maps a wallet address to its current nonce for replay protection.RECOVERY_DELAY
: A constant specifying the delay time for recovery execution (3 days).
Events
RecoveryInitiated
: Emitted when a recovery process is initiated.RecoveryExecuted
: Emitted when a recovery process is successfully executed.RecoveryCancelled
: Emitted when a recovery process is cancelled.
Functions
onInstall(bytes calldata data)
Installs the module in a wallet. This function decodes the provided data to initialize guardians and the threshold for a wallet.
function onInstall(bytes calldata data) external override {
if (isInitialized(msg.sender)) {
revert AlreadyInitialized(msg.sender);
}
(uint256 _threshold, address[] memory _guardians) = abi.decode(
data,
(uint256, address[])
);
require(
_threshold > 0 && _threshold <= _guardians.length,
"Invalid threshold"
);
guardians[msg.sender] = _guardians;
thresholds[msg.sender] = _threshold;
}
onUninstall(bytes calldata)
Uninstalls the module from a wallet. This function deletes all associated data for the wallet.
function onUninstall(bytes calldata) external override {
if (!isInitialized(msg.sender)) {
revert NotInitialized(msg.sender);
}
delete guardians[msg.sender];
delete thresholds[msg.sender];
delete recoveries[msg.sender];
delete nonces[msg.sender];
}
isModuleType(uint256 moduleTypeId)
Checks if the module type matches the executor type.
function isModuleType(uint256 moduleTypeId) external pure override returns (bool) {
return moduleTypeId == MODULE_TYPE_EXECUTOR;
}
isInitialized(address smartAccount)
Checks if the module is initialized for a wallet.
function isInitialized(address smartAccount) public view override returns (bool) {
return guardians[smartAccount].length > 0;
}
getHash(address _wallet, address _newOwner)
Generates a hash for the recovery process based on the wallet, new owner, and nonce.
function getHash(address _wallet, address _newOwner) public view returns (bytes32) {
return keccak256(abi.encodePacked(_wallet, _newOwner, nonces[_wallet]));
}
initiateRecovery(address _wallet, address _newOwner, bytes[] memory _signatures)
Initiates a recovery process. Verifies the signatures of guardians and sets the recovery details.
function initiateRecovery(
address _wallet,
address _newOwner,
bytes[] memory _signatures
) external {
require(
recoveries[_wallet].executionTime == 0,
"Recovery already in progress"
);
bytes32 recoveryHash = getHash(_wallet, _newOwner);
uint256 validSignatures = 0;
for (uint i = 0; i < _signatures.length; i++) {
address signer = recoveryHash.toEthSignedMessageHash().recover(
_signatures[i]
);
if (isGuardian(_wallet, signer)) {
validSignatures++;
}
}
require(
validSignatures >= thresholds[_wallet],
"Not enough valid signatures"
);
recoveries[_wallet] = Recovery({
newOwner: _newOwner,
guardianThreshold: validSignatures,
executionTime: block.timestamp + RECOVERY_DELAY,
executed: false
});
nonces[_wallet]++;
emit RecoveryInitiated(
_wallet,
_newOwner,
recoveries[_wallet].executionTime
);
}
executeRecovery(address _wallet)
Executes a recovery process after the recovery delay has passed.
function executeRecovery(address _wallet) external {
Recovery storage recovery = recoveries[_wallet];
require(recovery.executionTime > 0, "No recovery in progress");
require(
block.timestamp >= recovery.executionTime,
"Recovery delay not passed"
);
require(!recovery.executed, "Recovery already executed");
recovery.executed = true;
OwnerManager(_wallet).resetOwners(recovery.newOwner);
delete recoveries[_wallet];
emit RecoveryExecuted(_wallet, recovery.newOwner);
}
cancelRecovery()
Cancels a recovery process.
function cancelRecovery() external {
require(
recoveries[msg.sender].executionTime > 0,
"No recovery in progress"
);
delete recoveries[msg.sender];
emit RecoveryCancelled(msg.sender);
}
isGuardian(address _wallet, address _guardian)
Checks if an address is a guardian for a wallet.
function isGuardian(address _wallet, address _guardian) public view returns (bool) {
address[] storage walletGuardians = guardians[_wallet];
for (uint i = 0; i < walletGuardians.length; i++) {
if (walletGuardians[i] == _guardian) {
return true;
}
}
return false;
}
Summary
The AxirSocialRecovery
contract enables secure recovery of wallet ownership through a consensus of guardians. It provides mechanisms to install and uninstall the module, initiate and execute recoveries, and validate guardianship.