diff --git a/src/index.ts b/src/index.ts index 31f1a13..829ac81 100644 --- a/src/index.ts +++ b/src/index.ts @@ -150,6 +150,47 @@ export { ApprovalSpend, CrossChainOrder, WithdrawRequest, + UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA, + UNIVERSAL_EMAIL_RECOVERY_ADDRESS__BASE, + MAX_VALIDATORS, + MAX_NUMBER_OF_GUARDIANS, + MINIMUM_RECOVERY_WINDOW, + CANCEL_EXPIRED_RECOVERY_COOLDOWN, + getUniversalEmailRecoveryExecutor, + getRecoveryConfig, + getRecoveryRequest, + getPreviousRecoveryRequest, + isActivated, + canStartRecoveryRequest, + getAllowValidatorRecoveryAction, + getDisallowValidatorRecoveryAction, + getAllowedValidators, + getAllowedSelectors, + acceptanceCommandTemplates, + recoveryCommandTemplates, + extractRecoveredAccountFromAcceptanceCommand, + extractRecoveredAccountFromRecoveryCommand, + computeAcceptanceTemplateId, + computeRecoveryTemplateId, + getVerifier, + getDkim, + getEmailAuthImplementation, + getUpdateRecoveryConfigAction, + getHandleAcceptanceAction, + getHandleRecoveryAction, + getCompleteRecoveryAction, + getCancelRecoveryAction, + getCancelExpiredRecoveryAction, + computeEmailAuthAddress, + getGuardianConfig, + getGuardian, + getAllGuardians, + hasGuardianVoted, + getAddGuardianAction, + getRemoveGuardianAction, + getChangeThresholdAction, + EmailAuthMsg, + EmailProof, } from './module' export type { ModuleType, Module, SigHookInit } from './module' diff --git a/src/module/index.ts b/src/module/index.ts index 6c85d50..e779081 100644 --- a/src/module/index.ts +++ b/src/module/index.ts @@ -177,6 +177,50 @@ export { RHINESTONE_ATTESTER_ADDRESS, } from './registry' +export { + UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA, + UNIVERSAL_EMAIL_RECOVERY_ADDRESS__BASE, + MAX_VALIDATORS, + MAX_NUMBER_OF_GUARDIANS, + MINIMUM_RECOVERY_WINDOW, + CANCEL_EXPIRED_RECOVERY_COOLDOWN, + getUniversalEmailRecoveryExecutor, + getRecoveryConfig, + getRecoveryRequest, + getPreviousRecoveryRequest, + isActivated, + canStartRecoveryRequest, + getAllowValidatorRecoveryAction, + getDisallowValidatorRecoveryAction, + getAllowedValidators, + getAllowedSelectors, + acceptanceCommandTemplates, + recoveryCommandTemplates, + extractRecoveredAccountFromAcceptanceCommand, + extractRecoveredAccountFromRecoveryCommand, + computeAcceptanceTemplateId, + computeRecoveryTemplateId, + getVerifier, + getDkim, + getEmailAuthImplementation, + getUpdateRecoveryConfigAction, + getHandleAcceptanceAction, + getHandleRecoveryAction, + getCompleteRecoveryAction, + getCancelRecoveryAction, + getCancelExpiredRecoveryAction, + computeEmailAuthAddress, + getGuardianConfig, + getGuardian, + getAllGuardians, + hasGuardianVoted, + getAddGuardianAction, + getRemoveGuardianAction, + getChangeThresholdAction, + EmailAuthMsg, + EmailProof, +} from './zk-email-recovery/universal-email-recovery' + export type { ModuleType, Module, diff --git a/src/module/zk-email-recovery/universal-email-recovery/abi.ts b/src/module/zk-email-recovery/universal-email-recovery/abi.ts new file mode 100644 index 0000000..1cbb98a --- /dev/null +++ b/src/module/zk-email-recovery/universal-email-recovery/abi.ts @@ -0,0 +1,1941 @@ +export const abi = [ + { + type: 'constructor', + inputs: [ + { + name: 'verifier', + type: 'address', + internalType: 'address', + }, + { + name: 'dkimRegistry', + type: 'address', + internalType: 'address', + }, + { + name: 'emailAuthImpl', + type: 'address', + internalType: 'address', + }, + { + name: 'commandHandler', + type: 'address', + internalType: 'address', + }, + { + name: 'minimumDelay', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'killSwitchAuthorizer', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'CANCEL_EXPIRED_RECOVERY_COOLDOWN', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'MAX_VALIDATORS', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'MINIMUM_RECOVERY_WINDOW', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'acceptanceCommandTemplates', + inputs: [], + outputs: [ + { + name: '', + type: 'string[][]', + internalType: 'string[][]', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'addGuardian', + inputs: [ + { + name: 'guardian', + type: 'address', + internalType: 'address', + }, + { + name: 'weight', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'allowValidatorRecovery', + inputs: [ + { + name: 'validator', + type: 'address', + internalType: 'address', + }, + { + name: 'isInstalledContext', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'recoverySelector', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'canStartRecoveryRequest', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + { + name: 'validator', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + internalType: 'bool', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'cancelExpiredRecovery', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'cancelRecovery', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'changeThreshold', + inputs: [ + { + name: 'threshold', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'commandHandler', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'completeRecovery', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + { + name: 'recoveryData', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'computeAcceptanceTemplateId', + inputs: [ + { + name: 'templateIdx', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'pure', + }, + { + type: 'function', + name: 'computeEmailAuthAddress', + inputs: [ + { + name: 'recoveredAccount', + type: 'address', + internalType: 'address', + }, + { + name: 'accountSalt', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'computeRecoveryTemplateId', + inputs: [ + { + name: 'templateIdx', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'pure', + }, + { + type: 'function', + name: 'disallowValidatorRecovery', + inputs: [ + { + name: 'validator', + type: 'address', + internalType: 'address', + }, + { + name: 'prevValidator', + type: 'address', + internalType: 'address', + }, + { + name: 'recoverySelector', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'dkim', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'dkimAddr', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'emailAuthImplementation', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'emailAuthImplementationAddr', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'extractRecoveredAccountFromAcceptanceCommand', + inputs: [ + { + name: 'commandParams', + type: 'bytes[]', + internalType: 'bytes[]', + }, + { + name: 'templateIdx', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'extractRecoveredAccountFromRecoveryCommand', + inputs: [ + { + name: 'commandParams', + type: 'bytes[]', + internalType: 'bytes[]', + }, + { + name: 'templateIdx', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getAllGuardians', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'address[]', + internalType: 'address[]', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getAllowedSelectors', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'bytes4[]', + internalType: 'bytes4[]', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getAllowedValidators', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'address[]', + internalType: 'address[]', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getGuardian', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + { + name: 'guardian', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct GuardianStorage', + components: [ + { + name: 'status', + type: 'uint8', + internalType: 'enum GuardianStatus', + }, + { + name: 'weight', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getGuardianConfig', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct IGuardianManager.GuardianConfig', + components: [ + { + name: 'guardianCount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'totalWeight', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'acceptedWeight', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'threshold', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getPreviousRecoveryRequest', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct IEmailRecoveryManager.PreviousRecoveryRequest', + components: [ + { + name: 'previousGuardianInitiated', + type: 'address', + internalType: 'address', + }, + { + name: 'cancelRecoveryCooldown', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRecoveryConfig', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct IEmailRecoveryManager.RecoveryConfig', + components: [ + { + name: 'delay', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'expiry', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getRecoveryRequest', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: 'executeAfter', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'executeBefore', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'currentWeight', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'recoveryDataHash', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'handleAcceptance', + inputs: [ + { + name: 'emailAuthMsg', + type: 'tuple', + internalType: 'struct EmailAuthMsg', + components: [ + { + name: 'templateId', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'commandParams', + type: 'bytes[]', + internalType: 'bytes[]', + }, + { + name: 'skippedCommandPrefix', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'proof', + type: 'tuple', + internalType: 'struct EmailProof', + components: [ + { + name: 'domainName', + type: 'string', + internalType: 'string', + }, + { + name: 'publicKeyHash', + type: 'bytes32', + internalType: 'bytes32', + }, + { + name: 'timestamp', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'maskedCommand', + type: 'string', + internalType: 'string', + }, + { + name: 'emailNullifier', + type: 'bytes32', + internalType: 'bytes32', + }, + { + name: 'accountSalt', + type: 'bytes32', + internalType: 'bytes32', + }, + { + name: 'isCodeExist', + type: 'bool', + internalType: 'bool', + }, + { + name: 'proof', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + ], + }, + { + name: 'templateIdx', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'handleRecovery', + inputs: [ + { + name: 'emailAuthMsg', + type: 'tuple', + internalType: 'struct EmailAuthMsg', + components: [ + { + name: 'templateId', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'commandParams', + type: 'bytes[]', + internalType: 'bytes[]', + }, + { + name: 'skippedCommandPrefix', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'proof', + type: 'tuple', + internalType: 'struct EmailProof', + components: [ + { + name: 'domainName', + type: 'string', + internalType: 'string', + }, + { + name: 'publicKeyHash', + type: 'bytes32', + internalType: 'bytes32', + }, + { + name: 'timestamp', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'maskedCommand', + type: 'string', + internalType: 'string', + }, + { + name: 'emailNullifier', + type: 'bytes32', + internalType: 'bytes32', + }, + { + name: 'accountSalt', + type: 'bytes32', + internalType: 'bytes32', + }, + { + name: 'isCodeExist', + type: 'bool', + internalType: 'bool', + }, + { + name: 'proof', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + ], + }, + { + name: 'templateIdx', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'hasGuardianVoted', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + { + name: 'guardian', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + internalType: 'bool', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isActivated', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + internalType: 'bool', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isInitialized', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + internalType: 'bool', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'isModuleType', + inputs: [ + { + name: 'typeID', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + internalType: 'bool', + }, + ], + stateMutability: 'pure', + }, + { + type: 'function', + name: 'killSwitchEnabled', + inputs: [], + outputs: [ + { + name: '', + type: 'bool', + internalType: 'bool', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'minimumDelay', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'name', + inputs: [], + outputs: [ + { + name: '', + type: 'string', + internalType: 'string', + }, + ], + stateMutability: 'pure', + }, + { + type: 'function', + name: 'onInstall', + inputs: [ + { + name: 'data', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'onUninstall', + inputs: [ + { + name: '', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'owner', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'recoveryCommandTemplates', + inputs: [], + outputs: [ + { + name: '', + type: 'string[][]', + internalType: 'string[][]', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'removeGuardian', + inputs: [ + { + name: 'guardian', + type: 'address', + internalType: 'address', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'renounceOwnership', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'toggleKillSwitch', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'transferOwnership', + inputs: [ + { + name: 'newOwner', + type: 'address', + internalType: 'address', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'updateRecoveryConfig', + inputs: [ + { + name: 'recoveryConfig', + type: 'tuple', + internalType: 'struct IEmailRecoveryManager.RecoveryConfig', + components: [ + { + name: 'delay', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'expiry', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'validatorCount', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: 'count', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'verifier', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'verifierAddr', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'version', + inputs: [], + outputs: [ + { + name: '', + type: 'string', + internalType: 'string', + }, + ], + stateMutability: 'pure', + }, + { + type: 'event', + name: 'AddedGuardian', + inputs: [ + { + name: 'account', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'guardian', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'weight', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'ChangedThreshold', + inputs: [ + { + name: 'account', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'threshold', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'GuardianAccepted', + inputs: [ + { + name: 'account', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'guardian', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'GuardianStatusUpdated', + inputs: [ + { + name: 'account', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'guardian', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'newStatus', + type: 'uint8', + indexed: false, + internalType: 'enum GuardianStatus', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'GuardianVoted', + inputs: [ + { + name: 'account', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'guardian', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'currentWeight', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + { + name: 'guardianWeight', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'NewValidatorRecovery', + inputs: [ + { + name: 'account', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'validator', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'recoverySelector', + type: 'bytes4', + indexed: false, + internalType: 'bytes4', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'OwnershipTransferred', + inputs: [ + { + name: 'previousOwner', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'newOwner', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RecoveryCancelled', + inputs: [ + { + name: 'account', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RecoveryCompleted', + inputs: [ + { + name: 'account', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RecoveryConfigUpdated', + inputs: [ + { + name: 'account', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'delay', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + { + name: 'expiry', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RecoveryConfigured', + inputs: [ + { + name: 'account', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'guardianCount', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + { + name: 'totalWeight', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + { + name: 'threshold', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RecoveryDeInitialized', + inputs: [ + { + name: 'account', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RecoveryExecuted', + inputs: [ + { + name: 'account', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'validator', + type: 'address', + indexed: true, + internalType: 'address', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RecoveryRequestComplete', + inputs: [ + { + name: 'account', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'guardian', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'executeAfter', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + { + name: 'executeBefore', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + { + name: 'recoveryDataHash', + type: 'bytes32', + indexed: false, + internalType: 'bytes32', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RecoveryRequestStarted', + inputs: [ + { + name: 'account', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'guardian', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'executeBefore', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + { + name: 'recoveryDataHash', + type: 'bytes32', + indexed: false, + internalType: 'bytes32', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RemovedGuardian', + inputs: [ + { + name: 'account', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'guardian', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'weight', + type: 'uint256', + indexed: false, + internalType: 'uint256', + }, + ], + anonymous: false, + }, + { + type: 'event', + name: 'RemovedValidatorRecovery', + inputs: [ + { + name: 'account', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'validator', + type: 'address', + indexed: true, + internalType: 'address', + }, + { + name: 'recoverySelector', + type: 'bytes4', + indexed: false, + internalType: 'bytes4', + }, + ], + anonymous: false, + }, + { + type: 'error', + name: 'AccountNotConfigured', + inputs: [], + }, + { + type: 'error', + name: 'AddressAlreadyGuardian', + inputs: [], + }, + { + type: 'error', + name: 'AddressNotGuardianForAccount', + inputs: [], + }, + { + type: 'error', + name: 'AlreadyInitialized', + inputs: [ + { + name: 'smartAccount', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'DelayLessThanMinimumDelay', + inputs: [ + { + name: 'delay', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'minimumDelay', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + { + type: 'error', + name: 'DelayMoreThanExpiry', + inputs: [ + { + name: 'delay', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'expiry', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + { + type: 'error', + name: 'DelayNotPassed', + inputs: [ + { + name: 'blockTimestamp', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'executeAfter', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + { + type: 'error', + name: 'GuardianAlreadyVoted', + inputs: [], + }, + { + type: 'error', + name: 'GuardianMustWaitForCooldown', + inputs: [ + { + name: 'guardian', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'IncorrectNumberOfWeights', + inputs: [ + { + name: 'guardianCount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'weightCount', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + { + type: 'error', + name: 'InvalidAccountAddress', + inputs: [], + }, + { + type: 'error', + name: 'InvalidCommandHandler', + inputs: [], + }, + { + type: 'error', + name: 'InvalidDkimRegistry', + inputs: [], + }, + { + type: 'error', + name: 'InvalidEmailAuthImpl', + inputs: [], + }, + { + type: 'error', + name: 'InvalidFactory', + inputs: [], + }, + { + type: 'error', + name: 'InvalidGuardianAddress', + inputs: [ + { + name: 'guardian', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'InvalidGuardianStatus', + inputs: [ + { + name: 'guardianStatus', + type: 'uint8', + internalType: 'enum GuardianStatus', + }, + { + name: 'expectedGuardianStatus', + type: 'uint8', + internalType: 'enum GuardianStatus', + }, + ], + }, + { + type: 'error', + name: 'InvalidGuardianWeight', + inputs: [], + }, + { + type: 'error', + name: 'InvalidKillSwitchAuthorizer', + inputs: [], + }, + { + type: 'error', + name: 'InvalidOnInstallData', + inputs: [], + }, + { + type: 'error', + name: 'InvalidProxyBytecodeHash', + inputs: [], + }, + { + type: 'error', + name: 'InvalidRecoveryDataHash', + inputs: [ + { + name: 'recoveryDataHash', + type: 'bytes32', + internalType: 'bytes32', + }, + { + name: 'expectedRecoveryDataHash', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + }, + { + type: 'error', + name: 'InvalidSelector', + inputs: [ + { + name: 'selector', + type: 'bytes4', + internalType: 'bytes4', + }, + ], + }, + { + type: 'error', + name: 'InvalidValidator', + inputs: [ + { + name: 'validator', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'InvalidVerifier', + inputs: [], + }, + { + type: 'error', + name: 'KillSwitchEnabled', + inputs: [], + }, + { + type: 'error', + name: 'LinkedList_AlreadyInitialized', + inputs: [], + }, + { + type: 'error', + name: 'LinkedList_EntryAlreadyInList', + inputs: [ + { + name: 'entry', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'LinkedList_InvalidEntry', + inputs: [ + { + name: 'entry', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'LinkedList_InvalidPage', + inputs: [], + }, + { + type: 'error', + name: 'MaxNumberOfGuardiansReached', + inputs: [], + }, + { + type: 'error', + name: 'MaxValidatorsReached', + inputs: [], + }, + { + type: 'error', + name: 'NoRecoveryConfigured', + inputs: [], + }, + { + type: 'error', + name: 'NoRecoveryInProcess', + inputs: [], + }, + { + type: 'error', + name: 'NotEnoughApprovals', + inputs: [ + { + name: 'currentWeight', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'threshold', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + { + type: 'error', + name: 'NotInitialized', + inputs: [ + { + name: 'smartAccount', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'OwnableInvalidOwner', + inputs: [ + { + name: 'owner', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'OwnableUnauthorizedAccount', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + ], + }, + { + type: 'error', + name: 'RecoveryHasNotExpired', + inputs: [ + { + name: 'account', + type: 'address', + internalType: 'address', + }, + { + name: 'blockTimestamp', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'executeBefore', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + { + type: 'error', + name: 'RecoveryInProcess', + inputs: [], + }, + { + type: 'error', + name: 'RecoveryIsNotActivated', + inputs: [], + }, + { + type: 'error', + name: 'RecoveryModuleNotInitialized', + inputs: [], + }, + { + type: 'error', + name: 'RecoveryRequestExpired', + inputs: [ + { + name: 'blockTimestamp', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'executeBefore', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + { + type: 'error', + name: 'RecoveryWindowTooShort', + inputs: [ + { + name: 'recoveryWindow', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + { + type: 'error', + name: 'SetupAlreadyCalled', + inputs: [], + }, + { + type: 'error', + name: 'SetupNotCalled', + inputs: [], + }, + { + type: 'error', + name: 'StatusCannotBeTheSame', + inputs: [ + { + name: 'newStatus', + type: 'uint8', + internalType: 'enum GuardianStatus', + }, + ], + }, + { + type: 'error', + name: 'ThresholdCannotBeZero', + inputs: [], + }, + { + type: 'error', + name: 'ThresholdExceedsAcceptedWeight', + inputs: [ + { + name: 'threshold', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'acceptedWeight', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + { + type: 'error', + name: 'ThresholdExceedsTotalWeight', + inputs: [ + { + name: 'threshold', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'totalWeight', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + { + type: 'error', + name: 'TooManyValuesToRemove', + inputs: [], + }, +] as const diff --git a/src/module/zk-email-recovery/universal-email-recovery/constants.ts b/src/module/zk-email-recovery/universal-email-recovery/constants.ts new file mode 100644 index 0000000..8270c26 --- /dev/null +++ b/src/module/zk-email-recovery/universal-email-recovery/constants.ts @@ -0,0 +1,23 @@ +import { Address } from 'viem' +import { base, sepolia } from 'viem/chains' + +export const UNIVERSAL_EMAIL_RECOVERY_ADDRESS__BASE: Address = + '0x36A470159F8170ad262B9518095a9FeD0824e7dD' +export const UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA: Address = + '0x8ECcb707C4770239D7e95743cd01aaA72d6D313E' + +export const MAX_VALIDATORS = 32 +export const MAX_NUMBER_OF_GUARDIANS = 32 +export const MINIMUM_RECOVERY_WINDOW = 2 * 24 * 60 * 60 // 2 days in seconds; +export const CANCEL_EXPIRED_RECOVERY_COOLDOWN = 24 * 60 * 60 // 1 day in seconds; + +/** Helper function get the module address based on chain id */ +export const getModuleAddress = (chainId: number): Address => { + if (chainId === sepolia.id) { + return UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA + } + if (chainId === base.id) { + return UNIVERSAL_EMAIL_RECOVERY_ADDRESS__BASE + } + throw new Error('Unsupported chain') +} diff --git a/src/module/zk-email-recovery/universal-email-recovery/index.ts b/src/module/zk-email-recovery/universal-email-recovery/index.ts new file mode 100644 index 0000000..799be5b --- /dev/null +++ b/src/module/zk-email-recovery/universal-email-recovery/index.ts @@ -0,0 +1,3 @@ +export * from './installation' +export * from './usage' +export * from './constants' diff --git a/src/module/zk-email-recovery/universal-email-recovery/installation.ts b/src/module/zk-email-recovery/universal-email-recovery/installation.ts new file mode 100644 index 0000000..93257d3 --- /dev/null +++ b/src/module/zk-email-recovery/universal-email-recovery/installation.ts @@ -0,0 +1,58 @@ +import { Address, encodeAbiParameters, Hex } from 'viem' +import { Module } from '../../types' +import { getModuleAddress } from './constants' + +export const getUniversalEmailRecoveryExecutor = ({ + validator, + isInstalledContext, + initialSelector, + guardians, + weights, + threshold, + delay, + expiry, + chainId, + hook, +}: { + validator: Address + isInstalledContext: Hex + initialSelector: Hex + guardians: Array
+ weights: Array + threshold: bigint + delay: bigint + expiry: bigint + chainId: number + hook?: Address +}): Module => { + return { + address: getModuleAddress(chainId), + module: getModuleAddress(chainId), + initData: encodeAbiParameters( + [ + { name: 'validator', type: 'address' }, + { name: 'isInstalledContext', type: 'bytes' }, + { name: 'initialSelector', type: 'bytes4' }, + { name: 'guardians', type: 'address[]' }, + { name: 'weights', type: 'uint256[]' }, + { name: 'delay', type: 'uint256' }, + { name: 'expiry', type: 'uint256' }, + { name: 'threshold', type: 'uint256' }, + ], + [ + validator, + isInstalledContext, + initialSelector, + guardians, + weights.map((weight) => BigInt(weight)), + BigInt(threshold), + BigInt(delay), + BigInt(expiry), + ], + ), + deInitData: '0x', + additionalContext: '0x', + type: 'executor', + hook, + } +} diff --git a/src/module/zk-email-recovery/universal-email-recovery/usage.ts b/src/module/zk-email-recovery/universal-email-recovery/usage.ts new file mode 100644 index 0000000..d84f723 --- /dev/null +++ b/src/module/zk-email-recovery/universal-email-recovery/usage.ts @@ -0,0 +1,731 @@ +import { getModuleAddress } from './constants' +import { Execution } from '../../../account/types' +import { + Address, + encodeFunctionData, + Hex, + PublicClient, + toHex, + zeroAddress, +} from 'viem' +import { Account } from '../../../account/types' +import { abi } from './abi' + +export type EmailAuthMsg = { + templateId: bigint + commandParams: Hex[] + skippedCommandPrefix: bigint + proof: EmailProof +} + +export type EmailProof = { + domainName: string + publicKeyHash: Hex + timestamp: bigint + maskedCommand: string + emailNullifier: Hex + accountSalt: Hex + isCodeExist: boolean + proof: Hex +} + +export const getRecoveryConfig = async ({ + account, + client, +}: { + account: Account + client: PublicClient +}): Promise<{ delay: bigint; expiry: bigint }> => { + const address = getModuleAddress(await client.getChainId()) + try { + return await client.readContract({ + address, + abi, + functionName: 'getRecoveryConfig', + args: [account.address], + }) + } catch (err) { + return { delay: 0n, expiry: 0n } + } +} + +export const getRecoveryRequest = async ({ + account, + client, +}: { + account: Account + client: PublicClient +}): Promise => { + const address = getModuleAddress(await client.getChainId()) + try { + return await client.readContract({ + address, + abi, + functionName: 'getRecoveryRequest', + args: [account.address], + }) + } catch (err) { + return [0n, 0n, 0n, toHex(0, { size: 32 })] + } +} + +export const getPreviousRecoveryRequest = async ({ + account, + client, +}: { + account: Account + client: PublicClient +}): Promise<{ + previousGuardianInitiated: Address + cancelRecoveryCooldown: bigint +}> => { + const address = getModuleAddress(await client.getChainId()) + try { + return await client.readContract({ + address, + abi, + functionName: 'getPreviousRecoveryRequest', + args: [account.address], + }) + } catch (err) { + return { + previousGuardianInitiated: zeroAddress, + cancelRecoveryCooldown: 0n, + } + } +} + +export const isActivated = async ({ + account, + client, +}: { + account: Account + client: PublicClient +}): Promise => { + const address = getModuleAddress(await client.getChainId()) + try { + return await client.readContract({ + address, + abi, + functionName: 'isActivated', + args: [account.address], + }) + } catch (err) { + return false + } +} + +export const canStartRecoveryRequest = async ({ + account, + client, + validator, +}: { + account: Account + client: PublicClient + validator: Address +}): Promise => { + const address = getModuleAddress(await client.getChainId()) + try { + return await client.readContract({ + address, + abi, + functionName: 'canStartRecoveryRequest', + args: [account.address, validator], + }) + } catch (err) { + return false + } +} + +export const getAllowValidatorRecoveryAction = async ({ + client, + validator, + isInstalledContext, + recoverySelector, +}: { + client: PublicClient + validator: Address + isInstalledContext: Hex + recoverySelector: Hex +}): Promise => { + const address = getModuleAddress(await client.getChainId()) + const data = encodeFunctionData({ + functionName: 'allowValidatorRecovery', + abi, + args: [validator, isInstalledContext, recoverySelector], + }) + + return { + to: address, + target: address, + value: 0n, + callData: data, + data, + } +} + +export const getDisallowValidatorRecoveryAction = async ({ + client, + validator, + prevValidator, + recoverySelector, +}: { + client: PublicClient + validator: Address + prevValidator: Address + recoverySelector: Hex +}): Promise => { + const address = getModuleAddress(await client.getChainId()) + const data = encodeFunctionData({ + functionName: 'disallowValidatorRecovery', + abi, + args: [validator, prevValidator, recoverySelector], + }) + + return { + to: address, + target: address, + value: 0n, + callData: data, + data, + } +} + +export const getAllowedValidators = async ({ + account, + client, +}: { + account: Account + client: PublicClient +}): Promise => { + const address = getModuleAddress(await client.getChainId()) + try { + return await client.readContract({ + address, + abi, + functionName: 'getAllowedValidators', + args: [account.address], + }) + } catch (err) { + return [] + } +} + +export const getAllowedSelectors = async ({ + account, + client, +}: { + account: Account + client: PublicClient +}): Promise => { + const address = getModuleAddress(await client.getChainId()) + try { + return await client.readContract({ + address, + abi, + functionName: 'getAllowedSelectors', + args: [account.address], + }) + } catch (err) { + return [] + } +} + +export const acceptanceCommandTemplates = async ({ + client, +}: { + client: PublicClient +}): Promise => { + const address = getModuleAddress(await client.getChainId()) + try { + return await client.readContract({ + address, + abi, + functionName: 'acceptanceCommandTemplates', + }) + } catch (err) { + return [] + } +} + +export const recoveryCommandTemplates = async ({ + client, +}: { + client: PublicClient +}): Promise => { + const address = getModuleAddress(await client.getChainId()) + try { + return await client.readContract({ + address, + abi, + functionName: 'recoveryCommandTemplates', + }) + } catch (err) { + return [] + } +} + +export const extractRecoveredAccountFromAcceptanceCommand = async ({ + client, + commandParams, + templateIdx, +}: { + client: PublicClient + commandParams: Hex[] + templateIdx: bigint +}): Promise
=> { + const address = getModuleAddress(await client.getChainId()) + try { + return await client.readContract({ + address, + abi, + functionName: 'extractRecoveredAccountFromAcceptanceCommand', + args: [commandParams, templateIdx], + }) + } catch (err) { + return zeroAddress + } +} + +export const extractRecoveredAccountFromRecoveryCommand = async ({ + client, + commandParams, + templateIdx, +}: { + client: PublicClient + commandParams: Hex[] + templateIdx: bigint +}): Promise
=> { + const address = getModuleAddress(await client.getChainId()) + try { + return await client.readContract({ + address, + abi, + functionName: 'extractRecoveredAccountFromRecoveryCommand', + args: [commandParams, templateIdx], + }) + } catch (err) { + return zeroAddress + } +} + +export const computeAcceptanceTemplateId = async ({ + client, + templateIdx, +}: { + client: PublicClient + templateIdx: bigint +}): Promise => { + const address = getModuleAddress(await client.getChainId()) + try { + return await client.readContract({ + address, + abi, + functionName: 'computeAcceptanceTemplateId', + args: [templateIdx], + }) + } catch (err) { + return 0n + } +} + +export const computeRecoveryTemplateId = async ({ + client, + templateIdx, +}: { + client: PublicClient + templateIdx: bigint +}): Promise => { + const address = getModuleAddress(await client.getChainId()) + try { + return await client.readContract({ + address, + abi, + functionName: 'computeRecoveryTemplateId', + args: [templateIdx], + }) + } catch (err) { + return 0n + } +} + +export const getVerifier = async ({ + client, +}: { + client: PublicClient +}): Promise
=> { + const address = getModuleAddress(await client.getChainId()) + try { + return await client.readContract({ + address, + abi, + functionName: 'verifier', + }) + } catch (err) { + return zeroAddress + } +} + +export const getDkim = async ({ + client, +}: { + client: PublicClient +}): Promise
=> { + const address = getModuleAddress(await client.getChainId()) + try { + return await client.readContract({ + address, + abi, + functionName: 'dkim', + }) + } catch (err) { + return zeroAddress + } +} + +export const getEmailAuthImplementation = async ({ + client, +}: { + client: PublicClient +}): Promise
=> { + const address = getModuleAddress(await client.getChainId()) + try { + return await client.readContract({ + address, + abi, + functionName: 'emailAuthImplementation', + }) + } catch (err) { + return zeroAddress + } +} + +export const getUpdateRecoveryConfigAction = async ({ + client, + delay, + expiry, +}: { + client: PublicClient + delay: bigint + expiry: bigint +}): Promise => { + const address = getModuleAddress(await client.getChainId()) + const data = encodeFunctionData({ + functionName: 'updateRecoveryConfig', + abi, + args: [{ delay, expiry }], + }) + + return { + to: address, + target: address, + value: 0n, + callData: data, + data, + } +} + +export const getHandleAcceptanceAction = async ({ + client, + emailAuthMsg, + templateIdx, +}: { + client: PublicClient + emailAuthMsg: EmailAuthMsg + templateIdx: bigint +}): Promise => { + const address = getModuleAddress(await client.getChainId()) + const data = encodeFunctionData({ + functionName: 'handleAcceptance', + abi, + args: [emailAuthMsg, templateIdx], + }) + + return { + to: address, + target: address, + value: 0n, + callData: data, + data, + } +} + +export const getHandleRecoveryAction = async ({ + client, + emailAuthMsg, + templateIdx, +}: { + client: PublicClient + emailAuthMsg: EmailAuthMsg + templateIdx: bigint +}): Promise => { + const address = getModuleAddress(await client.getChainId()) + const data = encodeFunctionData({ + functionName: 'handleRecovery', + abi, + args: [emailAuthMsg, templateIdx], + }) + + return { + to: address, + target: address, + value: 0n, + callData: data, + data, + } +} + +export const getCompleteRecoveryAction = async ({ + client, + account, + recoveryData, +}: { + client: PublicClient + account: Address + recoveryData: Hex +}): Promise => { + const address = getModuleAddress(await client.getChainId()) + const data = encodeFunctionData({ + functionName: 'completeRecovery', + abi, + args: [account, recoveryData], + }) + + return { + to: address, + target: address, + value: 0n, + callData: data, + data, + } +} + +export const getCancelRecoveryAction = async ({ + client, +}: { + client: PublicClient +}): Promise => { + const address = getModuleAddress(await client.getChainId()) + const data = encodeFunctionData({ + functionName: 'cancelRecovery', + abi, + }) + + return { + to: address, + target: address, + value: 0n, + callData: data, + data, + } +} + +export const getCancelExpiredRecoveryAction = async ({ + client, + account, +}: { + client: PublicClient + account: Address +}): Promise => { + const address = getModuleAddress(await client.getChainId()) + const data = encodeFunctionData({ + functionName: 'cancelExpiredRecovery', + abi, + args: [account], + }) + + return { + to: address, + target: address, + value: 0n, + callData: data, + data, + } +} + +export const computeEmailAuthAddress = async ({ + client, + recoveredAccount, + accountSalt, +}: { + client: PublicClient + recoveredAccount: Address + accountSalt: Hex +}): Promise
=> { + const address = getModuleAddress(await client.getChainId()) + try { + return await client.readContract({ + address, + abi, + functionName: 'computeEmailAuthAddress', + args: [recoveredAccount, accountSalt], + }) + } catch (err) { + return zeroAddress + } +} + +export const getGuardianConfig = async ({ + account, + client, +}: { + account: Account + client: PublicClient +}): Promise<{ + guardianCount: bigint + totalWeight: bigint + acceptedWeight: bigint + threshold: bigint +}> => { + const address = getModuleAddress(await client.getChainId()) + try { + return await client.readContract({ + address, + abi, + functionName: 'getGuardianConfig', + args: [account.address], + }) + } catch (err) { + return { + guardianCount: 0n, + totalWeight: 0n, + acceptedWeight: 0n, + threshold: 0n, + } + } +} + +export const getGuardian = async ({ + account, + client, + guardian, +}: { + account: Account + client: PublicClient + guardian: Address +}): Promise<{ status: number; weight: bigint }> => { + const address = getModuleAddress(await client.getChainId()) + try { + return await client.readContract({ + address, + abi, + functionName: 'getGuardian', + args: [account.address, guardian], + }) + } catch (err) { + return { status: 0, weight: 0n } + } +} + +export const getAllGuardians = async ({ + account, + client, +}: { + account: Account + client: PublicClient +}): Promise => { + const address = getModuleAddress(await client.getChainId()) + try { + return await client.readContract({ + address, + abi, + functionName: 'getAllGuardians', + args: [account.address], + }) + } catch (err) { + return [] + } +} + +export const hasGuardianVoted = async ({ + account, + client, + guardian, +}: { + account: Account + client: PublicClient + guardian: Address +}): Promise => { + const address = getModuleAddress(await client.getChainId()) + try { + return await client.readContract({ + address, + abi, + functionName: 'hasGuardianVoted', + args: [account.address, guardian], + }) + } catch (err) { + return false + } +} + +export const getAddGuardianAction = async ({ + client, + guardian, + weight, +}: { + client: PublicClient + guardian: Address + weight: bigint +}): Promise => { + const address = getModuleAddress(await client.getChainId()) + const data = encodeFunctionData({ + functionName: 'addGuardian', + abi, + args: [guardian, weight], + }) + + return { + to: address, + target: address, + value: 0n, + callData: data, + data, + } +} + +export const getRemoveGuardianAction = async ({ + client, + guardian, +}: { + client: PublicClient + guardian: Address +}): Promise => { + const address = getModuleAddress(await client.getChainId()) + const data = encodeFunctionData({ + functionName: 'removeGuardian', + abi, + args: [guardian], + }) + + return { + to: address, + target: address, + value: 0n, + callData: data, + data, + } +} + +export const getChangeThresholdAction = async ({ + client, + threshold, +}: { + client: PublicClient + threshold: bigint +}): Promise => { + const address = getModuleAddress(await client.getChainId()) + const data = encodeFunctionData({ + functionName: 'changeThreshold', + abi, + args: [threshold], + }) + + return { + to: address, + target: address, + value: 0n, + callData: data, + data, + } +} diff --git a/test/e2e/accounts/7579-ref-implementation.e2e-test.ts b/test/e2e/accounts/7579-ref-implementation.e2e-test.ts index bd8b915..d3d7ecf 100644 --- a/test/e2e/accounts/7579-ref-implementation.e2e-test.ts +++ b/test/e2e/accounts/7579-ref-implementation.e2e-test.ts @@ -14,6 +14,7 @@ import { testScheduledTransfersExecutor, testHookMultiPlexer, testSocialRecoveryValidator, + testUniversalEmailRecoveryExecutor, testSmartSessionsValidator, } from 'test/e2e/modules' @@ -76,6 +77,12 @@ describe('Test erc7579 reference implementation', () => { testClient, }) + testUniversalEmailRecoveryExecutor({ + account, + publicClient, + testClient, + }) + testRegistryHook({ account, publicClient, diff --git a/test/e2e/infra/installModuleActions.ts b/test/e2e/infra/installModuleActions.ts index 86d357a..d197eeb 100644 --- a/test/e2e/infra/installModuleActions.ts +++ b/test/e2e/infra/installModuleActions.ts @@ -12,16 +12,17 @@ import { Address, encodeAbiParameters, encodePacked, + getAddress, Hex, PublicClient, toBytes, + toFunctionSelector, toHex, zeroAddress, } from 'viem' import { CallType } from 'src/module/types' import { REGISTRY_ADDRESS } from 'src/module/registry' import { SafeHookType } from 'src/account/safe/types' -import { encodeValidationData } from 'src/module/ownable-validator/usage' import { getSudoPolicy } from 'src/module/smart-sessions/policies/sudo-policy' import { privateKeyToAccount } from 'viem/accounts' import { getOwnableExecutor } from 'src/module/ownable-executor' @@ -34,6 +35,7 @@ import { import { getHookMultiPlexer } from 'src/module/hook-multi-plexer' import { getDeadmanSwitch } from 'src/module/deadman-switch' import { getMultiFactorValidator } from 'src/module/multi-factor-validator' +import { getUniversalEmailRecoveryExecutor } from 'src/module/zk-email-recovery/universal-email-recovery' import { sepolia } from 'viem/chains' type Params = { @@ -56,6 +58,7 @@ export const getInstallModuleActions = async ({ account, client }: Params) => { scheduledTransfersExecutor, hookMultiPlexer, smartSessions, + universalEmailRecoveryExecutor, } = getInstallModuleData({ account, }) @@ -159,6 +162,15 @@ export const getInstallModuleActions = async ({ account, client }: Params) => { module: getSmartSessionsValidator(smartSessions), }) + // install universal email recovery executor + const installUniversalEmailRecoveryAction = await installModule({ + client, + account, + module: await getUniversalEmailRecoveryExecutor( + universalEmailRecoveryExecutor, + ), + }) + return [ ...installSmartSessionsValidatorAction, ...installOwnableValidatorAction, @@ -173,6 +185,7 @@ export const getInstallModuleActions = async ({ account, client }: Params) => { ...installScheduledOrdersExecutorAction, ...installScheduledTransfersExecutorAction, ...installHookMultiplexerAction, + ...installUniversalEmailRecoveryAction, ] } @@ -314,4 +327,19 @@ export const getInstallModuleData = ({ account }: Pick) => ({ ], hook: zeroAddress, }, + universalEmailRecoveryExecutor: { + validator: OWNABLE_VALIDATOR_ADDRESS, + isInstalledContext: toHex(0), + initialSelector: toFunctionSelector('function addOwner(address)'), + guardians: [ + getAddress('0x0Cb7EAb54EB751579a82D80Fe2683687deb918f3'), + getAddress('0x9FF36a253C70b65122B47c70F2AfaF65F2957118'), + ], + weights: [1n, 2n], + threshold: 2n, + delay: 60n * 60n * 6n, // 6 hours + expiry: 2n * 7n * 24n * 60n * 60n, // 2 days + chainId: sepolia.id, + hook: zeroAddress, + }, }) diff --git a/test/e2e/infra/unInstallModuleActions.ts b/test/e2e/infra/unInstallModuleActions.ts index fd73653..98f100a 100644 --- a/test/e2e/infra/unInstallModuleActions.ts +++ b/test/e2e/infra/unInstallModuleActions.ts @@ -5,6 +5,7 @@ import { SCHEDULED_ORDERS_EXECUTOR_ADDRESS, SCHEDULED_TRANSFERS_EXECUTOR_ADDRESS, SMART_SESSIONS_ADDRESS, + UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA, } from 'src/module' import { Account } from 'src/account' import { Hex, PublicClient } from 'viem' @@ -160,7 +161,18 @@ export const getUnInstallModuleActions = async ({ }), }) + // unInstall universal email recovery executor + const unInstallUniversalEmailRecoveryExecutorAction = await uninstallModule({ + client, + account, + module: getModule({ + type: 'executor', + module: UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA, + }), + }) + return [ + ...unInstallUniversalEmailRecoveryExecutorAction, ...unInstallHookMultiPlexerHookAction, ...unInstallScheduledTransfersExecutorAction, ...unInstallScheduledOrdersExecutorAction, diff --git a/test/e2e/modules/index.ts b/test/e2e/modules/index.ts index b1ffa71..ace7b68 100644 --- a/test/e2e/modules/index.ts +++ b/test/e2e/modules/index.ts @@ -10,4 +10,5 @@ export * from './scheduledOrdersExecutor' export * from './scheduledTransfersExecutor' export * from './hookMultiPlexer' export * from './socialRecoveryValidator' +export * from './universalEmailRecoveryExecutor' export * from './smartSessionsValidator' diff --git a/test/e2e/modules/universalEmailRecoveryExecutor.ts b/test/e2e/modules/universalEmailRecoveryExecutor.ts new file mode 100644 index 0000000..b02c68e --- /dev/null +++ b/test/e2e/modules/universalEmailRecoveryExecutor.ts @@ -0,0 +1,229 @@ +import { Account, isModuleInstalled } from 'src/account' +import { + getAddGuardianAction, + getAllGuardians, + getAllowedSelectors, + getAllowedValidators, + getAllowValidatorRecoveryAction, + getChangeThresholdAction, + getDisallowValidatorRecoveryAction, + getGuardian, + getGuardianConfig, + getModule, + getRecoveryConfig, + getRemoveGuardianAction, + getUpdateRecoveryConfigAction, + MULTI_FACTOR_VALIDATOR_ADDRESS, + OWNABLE_VALIDATOR_ADDRESS, +} from 'src/module' +import { + getAddress, + PublicClient, + TestClient, + toFunctionSelector, + toHex, +} from 'viem' +import { UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA } from 'src/module/zk-email-recovery/universal-email-recovery/constants' +import { sendUserOp } from '../infra' +import { SENTINEL_ADDRESS } from 'src/common' + +type Params = { + account: Account + publicClient: PublicClient + testClient: TestClient +} + +export const testUniversalEmailRecoveryExecutor = async ({ + account, + publicClient, +}: Params) => { + it('should return true when checking universal email recovery executor isInstalled', async () => { + const isUniversalEmailRecoveryInstalled = await isModuleInstalled({ + account, + client: publicClient, + module: getModule({ + type: 'executor', + module: UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA, + }), + }) + + expect(isUniversalEmailRecoveryInstalled).toBe(true) + }, 20000) + + it('should add guardian', async () => { + const newGuardian = getAddress('0x206f270A1eBB6Dd3Bc97581376168014FD6eE57c') + const weight = 1n + + const execution = await getAddGuardianAction({ + client: publicClient, + guardian: newGuardian, + weight, + }) + + await sendUserOp({ + account, + actions: [execution], + }) + + const guardians = await getAllGuardians({ + account, + client: publicClient, + }) + const guardian = await getGuardian({ + account, + client: publicClient, + guardian: newGuardian, + }) + + expect(guardians.length).toBe(3) + expect(guardians.includes(newGuardian)).toBe(true) + expect(guardian.weight).toBe(weight) + expect(guardian.status).toBe(1) + }, 20000) + + it('should remove guardian', async () => { + const guardianToRemove = getAddress( + '0x206f270A1eBB6Dd3Bc97581376168014FD6eE57c', + ) + + const execution = await getRemoveGuardianAction({ + client: publicClient, + guardian: guardianToRemove, + }) + + await sendUserOp({ + account, + actions: [execution], + }) + + const guardians = await getAllGuardians({ + account, + client: publicClient, + }) + const guardian = await getGuardian({ + account, + client: publicClient, + guardian: guardianToRemove, + }) + + expect(guardians.length).toBe(2) + expect(guardians.includes(guardianToRemove)).toBe(false) + expect(guardian.weight).toBe(0n) + expect(guardian.status).toBe(0) + }, 20000) + + it('should change threshold', async () => { + const newThreshold = 1n + + const execution = await getChangeThresholdAction({ + client: publicClient, + threshold: newThreshold, + }) + + await sendUserOp({ + account, + actions: [execution], + }) + + const config = await getGuardianConfig({ + account, + client: publicClient, + }) + + expect(config.threshold).toBe(newThreshold) + }, 20000) + + it('should update recovery config', async () => { + const delay = 86400n // 1 day + const expiry = 604800n // 1 week + + const execution = await getUpdateRecoveryConfigAction({ + client: publicClient, + delay, + expiry, + }) + + await sendUserOp({ + account, + actions: [execution], + }) + + const config = await getRecoveryConfig({ + account, + client: publicClient, + }) + + expect(config.delay).toBe(delay) + expect(config.expiry).toBe(expiry) + }, 20000) + + it('should allow validator recovery', async () => { + const validator = MULTI_FACTOR_VALIDATOR_ADDRESS + const isInstalledContext = toHex(0) + const recoverySelector = toFunctionSelector( + 'function setValidator(address,ValidatorId,bytes)', + ) + + const execution = await getAllowValidatorRecoveryAction({ + client: publicClient, + validator, + isInstalledContext, + recoverySelector, + }) + + await sendUserOp({ + account, + actions: [execution], + }) + + const validators = await getAllowedValidators({ + account, + client: publicClient, + }) + const selectors = await getAllowedSelectors({ + account, + client: publicClient, + }) + + expect(validators.length).toBe(2) + expect(selectors.length).toBe(2) + expect(validators[0]).toBe(validator) + expect(selectors[0]).toBe(recoverySelector) + }, 20000) + + it('should disallow validator recovery', async () => { + const validator = MULTI_FACTOR_VALIDATOR_ADDRESS + const prevValidator = SENTINEL_ADDRESS + const recoverySelector = toFunctionSelector( + 'function setValidator(address,ValidatorId,bytes)', + ) + + const execution = await getDisallowValidatorRecoveryAction({ + client: publicClient, + validator, + prevValidator, + recoverySelector, + }) + + await sendUserOp({ + account, + actions: [execution], + }) + + const validators = await getAllowedValidators({ + account, + client: publicClient, + }) + const selectors = await getAllowedSelectors({ + account, + client: publicClient, + }) + + expect(validators.length).toBe(1) + expect(selectors.length).toBe(1) + expect(validators[0]).toBe(OWNABLE_VALIDATOR_ADDRESS) + expect(selectors[0]).toBe( + toFunctionSelector('function addOwner(address owner)'), + ) + }, 20000) +} diff --git a/test/unit/module/zkEmailRecovery/universalEmailRecovery/universalEmailRecovery.test.ts b/test/unit/module/zkEmailRecovery/universalEmailRecovery/universalEmailRecovery.test.ts new file mode 100644 index 0000000..fba0678 --- /dev/null +++ b/test/unit/module/zkEmailRecovery/universalEmailRecovery/universalEmailRecovery.test.ts @@ -0,0 +1,484 @@ +import { getAddress, toFunctionSelector, toHex, zeroAddress } from 'viem' +import { sepolia } from 'viem/chains' +import { getUniversalEmailRecoveryExecutor } from 'src/module/zk-email-recovery/universal-email-recovery/installation' +import { UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA } from 'src/module/zk-email-recovery/universal-email-recovery/constants' +import { + acceptanceCommandTemplates, + canStartRecoveryRequest, + computeAcceptanceTemplateId, + computeEmailAuthAddress, + computeRecoveryTemplateId, + EmailAuthMsg, + extractRecoveredAccountFromAcceptanceCommand, + extractRecoveredAccountFromRecoveryCommand, + getAddGuardianAction, + getAllGuardians, + getAllowedSelectors, + getAllowedValidators, + getAllowValidatorRecoveryAction, + getCancelExpiredRecoveryAction, + getCancelRecoveryAction, + getChangeThresholdAction, + getCompleteRecoveryAction, + getDisallowValidatorRecoveryAction, + getDkim, + getEmailAuthImplementation, + getGuardian, + getGuardianConfig, + getHandleAcceptanceAction, + getHandleRecoveryAction, + getPreviousRecoveryRequest, + getRecoveryConfig, + getRecoveryRequest, + getRemoveGuardianAction, + getUpdateRecoveryConfigAction, + getVerifier, + hasGuardianVoted, + isActivated, + recoveryCommandTemplates, +} from 'src/module/zk-email-recovery/universal-email-recovery/usage' +import { getClient } from 'src/common/getClient' +import { MockClient } from '../../../../utils/mocks/client' +import { getAccount } from 'src/account' +import { MockAccountDeployed } from '../../../../utils/mocks/account' +import { OWNABLE_VALIDATOR_ADDRESS } from 'src/module' + +describe('Universal Email Recovery Module', () => { + // Setup + const client = getClient(MockClient) + const account = getAccount(MockAccountDeployed) + + const validator = OWNABLE_VALIDATOR_ADDRESS + const isInstalledContext = toHex(0) + const initialSelector = toFunctionSelector('function addOwner(address)') + const guardians = [ + getAddress('0x0Cb7EAb54EB751579a82D80Fe2683687deb918f3'), + getAddress('0x9FF36a253C70b65122B47c70F2AfaF65F2957118'), + ] + const weights = [1n, 2n] + const threshold = 2n + const delay = 0n // 0 seconds + const expiry = 2n * 7n * 24n * 60n * 60n // 2 weeks in seconds + + it('should get install universal email recovery module', async () => { + const installUniversalEmailModule = getUniversalEmailRecoveryExecutor({ + validator, + isInstalledContext, + initialSelector, + guardians, + weights, + threshold, + delay, + expiry, + chainId: sepolia.id, + }) + + expect(installUniversalEmailModule.address).toEqual( + UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA, + ) + expect(installUniversalEmailModule.module).toEqual( + UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA, + ) + expect(installUniversalEmailModule.initData).toBeDefined() + expect(installUniversalEmailModule.deInitData).toEqual('0x') + expect(installUniversalEmailModule.additionalContext).toEqual('0x') + expect(installUniversalEmailModule.type).toEqual('executor') + expect(installUniversalEmailModule.hook).toBeUndefined() + }) + + it('Should get recovery config', async () => { + const config = await getRecoveryConfig({ + account, + client, + }) + expect(config.delay).toEqual(0n) + expect(config.expiry).toEqual(0n) + }) + + it('Should get recovery request', async () => { + const request = await getRecoveryRequest({ + account, + client, + }) + expect(request[0]).toEqual(0n) + expect(request[1]).toEqual(0n) + expect(request[2]).toEqual(0n) + expect(request[3]).toEqual( + '0x0000000000000000000000000000000000000000000000000000000000000000', + ) + }) + + it('Should get previous recovery request', async () => { + const prevRequest = await getPreviousRecoveryRequest({ + account, + client, + }) + expect(prevRequest.previousGuardianInitiated).toEqual(zeroAddress) + expect(prevRequest.cancelRecoveryCooldown).toEqual(0n) + }) + + it('Should check if activated', async () => { + const activated = await isActivated({ + account, + client, + }) + expect(activated).toEqual(false) + }) + + it('Should check if can start recovery request', async () => { + const canStart = await canStartRecoveryRequest({ + account, + client, + validator, + }) + expect(canStart).toEqual(false) + }) + + it('Should get allow validator recovery action', async () => { + const action = await getAllowValidatorRecoveryAction({ + client, + validator, + isInstalledContext, + recoverySelector: initialSelector, + }) + expect(action.to).toEqual(UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA) + expect(action.target).toEqual(UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA) + expect(action.value).toEqual(0n) + expect(action.callData).toBeDefined() + expect(action.data).toBeDefined() + }) + + it('Should get disallow validator recovery action', async () => { + const prevValidator = '0xD990393C670dCcE8b4d8F858FB98c9912dBFAa06' + const action = await getDisallowValidatorRecoveryAction({ + client, + validator, + prevValidator, + recoverySelector: initialSelector, + }) + expect(action.to).toEqual(UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA) + expect(action.target).toEqual(UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA) + expect(action.value).toEqual(0n) + expect(action.callData).toBeDefined() + expect(action.data).toBeDefined() + }) + + it('Should get allowed validators', async () => { + const validators = await getAllowedValidators({ + account, + client, + }) + expect(validators).toEqual([]) + }) + + it('Should get allowed selectors', async () => { + const selectors = await getAllowedSelectors({ + account, + client, + }) + expect(selectors).toEqual([]) + }) + + it('Should get acceptance command templates', async () => { + const templates = await acceptanceCommandTemplates({ + client, + }) + expect(templates).toEqual([ + ['Accept', 'guardian', 'request', 'for', '{ethAddr}'], + ]) + }) + + it('Should get recovery command templates', async () => { + const templates = await recoveryCommandTemplates({ + client, + }) + expect(templates).toEqual([ + [ + 'Recover', + 'account', + '{ethAddr}', + 'using', + 'recovery', + 'hash', + '{string}', + ], + ]) + }) + + it('Should extract recovered account from acceptance command', async () => { + const recoveredAccount = await extractRecoveredAccountFromAcceptanceCommand( + { + client, + commandParams: [account.address], + templateIdx: 0n, + }, + ) + expect(recoveredAccount).toEqual(zeroAddress) + }) + + it('Should extract recovered account from recovery command', async () => { + const recoveredAccount = await extractRecoveredAccountFromRecoveryCommand({ + client, + commandParams: [account.address], + templateIdx: 0n, + }) + expect(recoveredAccount).toEqual(zeroAddress) + }) + + it('Should compute acceptance template id', async () => { + const expectedId = + 78246708611299769969691317804450782728492512111741514614578044794817483451845n + const id = await computeAcceptanceTemplateId({ + client, + templateIdx: 0n, + }) + expect(id).toEqual(expectedId) + }) + + it('Should compute recovery template id', async () => { + const expectedId = + 41597252099594059824363833791590872545117890762070757419930713588231239964259n + const id = await computeRecoveryTemplateId({ + client, + templateIdx: 0n, + }) + expect(id).toEqual(expectedId) + }) + + it('Should get verifier', async () => { + const expectedVerifier = getAddress( + '0x0D5C8bcae3A3589F2CFbb04895933717aA5098e1', + ) + const verifier = await getVerifier({ + client, + }) + expect(verifier).toEqual(expectedVerifier) + }) + + it('Should get DKIM', async () => { + const expectedDkim = getAddress( + '0x1D2B1F8cF98382e53C7735F05ef84d51FEd8Eff6', + ) + const dkim = await getDkim({ + client, + }) + expect(dkim).toEqual(expectedDkim) + }) + + it('Should get email auth implementation', async () => { + const expectedEmailAuth = getAddress( + '0xCa4d16459b7AC7b348016244f1fA49d3f87b6F3F', + ) + const emailAuthImplementation = await getEmailAuthImplementation({ + client, + }) + expect(emailAuthImplementation).toEqual(expectedEmailAuth) + }) + + it('Should get update recovery config action', async () => { + const action = await getUpdateRecoveryConfigAction({ + client, + delay, + expiry, + }) + expect(action.to).toEqual(UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA) + expect(action.target).toEqual(UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA) + expect(action.value).toEqual(0n) + expect(action.callData).toBeDefined() + expect(action.data).toBeDefined() + }) + + it('Should get handle acceptance action', async () => { + const emailAuthMsg: EmailAuthMsg = { + templateId: 1n, + commandParams: [ + '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', + '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef', + ], + skippedCommandPrefix: 0n, + proof: { + domainName: 'gmail.com', + publicKeyHash: + '0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba', + timestamp: 1709251200n, // 1st March 2024 + maskedCommand: 'recover account', + emailNullifier: + '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + accountSalt: + '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + isCodeExist: true, + proof: + '0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + }, + } + + const action = await getHandleAcceptanceAction({ + client, + emailAuthMsg, + templateIdx: 0n, + }) + expect(action.to).toEqual(UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA) + expect(action.target).toEqual(UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA) + expect(action.value).toEqual(0n) + expect(action.callData).toBeDefined() + expect(action.data).toBeDefined() + }) + + it('Should get handle recovery action', async () => { + const emailAuthMsg: EmailAuthMsg = { + templateId: 1n, + commandParams: [ + '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', + '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef', + ], + skippedCommandPrefix: 0n, + proof: { + domainName: 'gmail.com', + publicKeyHash: + '0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba', + timestamp: 1709251200n, // 1st March 2024 + maskedCommand: 'recover account', + emailNullifier: + '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + accountSalt: + '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + isCodeExist: true, + proof: + '0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + }, + } + + const action = await getHandleRecoveryAction({ + client, + emailAuthMsg, + templateIdx: 0n, + }) + expect(action.to).toEqual(UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA) + expect(action.target).toEqual(UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA) + expect(action.value).toEqual(0n) + expect(action.callData).toBeDefined() + expect(action.data).toBeDefined() + }) + + it('Should get complete recovery action', async () => { + const action = await getCompleteRecoveryAction({ + client, + account: account.address, + recoveryData: '0x', + }) + expect(action.to).toEqual(UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA) + expect(action.target).toEqual(UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA) + expect(action.value).toEqual(0n) + expect(action.callData).toBeDefined() + expect(action.data).toBeDefined() + }) + + it('Should get cancel recovery action', async () => { + const action = await getCancelRecoveryAction({ client }) + expect(action.to).toEqual(UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA) + expect(action.target).toEqual(UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA) + expect(action.value).toEqual(0n) + expect(action.callData).toBeDefined() + expect(action.data).toBeDefined() + }) + + it('Should get cancel expired recovery action', async () => { + const action = await getCancelExpiredRecoveryAction({ + client, + account: account.address, + }) + expect(action.to).toEqual(UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA) + expect(action.target).toEqual(UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA) + expect(action.value).toEqual(0n) + expect(action.callData).toBeDefined() + expect(action.data).toBeDefined() + }) + + it('Should compute email auth address', async () => { + const expectedEmailAuthAddress = getAddress( + '0x81e375B511FA247B22D09519f37ddaa661cbd59a', + ) + const emailAuthAddress = await computeEmailAuthAddress({ + client, + recoveredAccount: account.address, + accountSalt: + '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + }) + expect(emailAuthAddress).toEqual(expectedEmailAuthAddress) + }) + + it('Should get guardian config', async () => { + const config = await getGuardianConfig({ + account, + client, + }) + expect(config.guardianCount).toEqual(0n) + expect(config.totalWeight).toEqual(0n) + expect(config.acceptedWeight).toEqual(0n) + expect(config.threshold).toEqual(0n) + }) + + it('Should get guardian', async () => { + const guardian = await getGuardian({ + account, + client, + guardian: guardians[0], + }) + expect(guardian.status).toEqual(0) + expect(guardian.weight).toEqual(0n) + }) + + it('Should get all guardians', async () => { + const allGuardians = await getAllGuardians({ + account, + client, + }) + expect(allGuardians).toEqual([]) + }) + + it('Should check if guardian has voted', async () => { + const hasVoted = await hasGuardianVoted({ + account, + client, + guardian: guardians[0], + }) + expect(hasVoted).toEqual(false) + }) + + it('Should get add guardian action', async () => { + const action = await getAddGuardianAction({ + client, + guardian: guardians[0], + weight: weights[0], + }) + expect(action.to).toEqual(UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA) + expect(action.target).toEqual(UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA) + expect(action.value).toEqual(0n) + expect(action.callData).toBeDefined() + expect(action.data).toBeDefined() + }) + + it('Should get remove guardian action', async () => { + const action = await getRemoveGuardianAction({ + client, + guardian: guardians[0], + }) + expect(action.to).toEqual(UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA) + expect(action.target).toEqual(UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA) + expect(action.value).toEqual(0n) + expect(action.callData).toBeDefined() + expect(action.data).toBeDefined() + }) + + it('Should get change threshold action', async () => { + const action = await getChangeThresholdAction({ + client, + threshold, + }) + expect(action.to).toEqual(UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA) + expect(action.target).toEqual(UNIVERSAL_EMAIL_RECOVERY_ADDRESS__ETH_SEPOLIA) + expect(action.value).toEqual(0n) + expect(action.callData).toBeDefined() + expect(action.data).toBeDefined() + }) +})