Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add integration to confirm recovery view #54

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions packages/auth-server/composables/useRecoveryGuardian.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,29 @@ export const useRecoveryGuardian = () => {
}
}

const getRecoveryInProgress = ref(false);
const getRecoveryError = ref<Error | null>(null);

async function getRecovery(account: Address) {
getRecoveryInProgress.value = true;
getRecoveryError.value = null;

try {
const client = getPublicClient({ chainId: defaultChain.id });
return await client.readContract({
address: contractsByChain[defaultChain.id].recovery,
abi: GuardianRecoveryModuleAbi,
functionName: "pendingRecoveryData",
args: [account],
});
} catch (err) {
getRecoveryError.value = err as Error;
return [];
} finally {
getRecoveryInProgress.value = false;
}
}

const { inProgress: proposeGuardianInProgress, error: proposeGuardianError, execute: proposeGuardian } = useAsync(async (address: Address) => {
const client = getClient({ chainId: defaultChain.id });
const tx = await client.proposeGuardian({
Expand Down Expand Up @@ -91,6 +114,20 @@ export const useRecoveryGuardian = () => {
return tx;
});

const { inProgress: initRecoveryInProgress, error: initRecoveryError, execute: initRecovery } = useAsync(async (account: Address, passKey: `0x${string}`, accountId: string) => {
const client = await getWalletClient({ chainId: defaultChain.id });
const [address] = await client.getAddresses();

const tx = await client.writeContract({
account: address,
address: contractsByChain[defaultChain.id].recovery,
abi: GuardianRecoveryModuleAbi,
functionName: "initRecovery",
args: [account, passKey, accountId],
});
return tx;
});

return {
confirmGuardianInProgress,
confirmGuardianError,
Expand All @@ -101,12 +138,18 @@ export const useRecoveryGuardian = () => {
removeGuardianInProgress,
removeGuardianError,
removeGuardian,
initRecoveryInProgress,
initRecoveryError,
initRecovery,
getGuardedAccountsInProgress,
getGuardedAccountsError,
getGuardedAccounts,
getGuardiansInProgress,
getGuardiansError,
getGuardiansData,
getGuardians,
getRecoveryInProgress,
getRecoveryError,
getRecovery,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@
v-if="accountData.isConnected && !isSuccess"
class="w-full lg:w-fit"
:disabled="!isAccountGuardian"
@click="handleSuccess"
:loading="initRecoveryInProgress"
@click="confirmRecoveryAction"
>
Sign Recovery Transaction
</ZkButton>
Expand Down Expand Up @@ -132,13 +133,16 @@
<script setup lang="ts">
import { CheckCircleIcon, ExclamationTriangleIcon } from "@heroicons/vue/24/solid";
import { useAppKitAccount } from "@reown/appkit/vue";
import { hexToBytes, isAddressEqual, zeroAddress } from "viem";
import { encodePasskeyModuleParameters, getPublicKeyBytesFromPasskeySignature } from "zksync-sso/utils";
import { z } from "zod";

import { uint8ArrayToHex } from "@/utils/formatters";
import { AddressSchema } from "@/utils/schemas";

const error = ref<string | null>(null);
const accountData = useAppKitAccount();
const { getRecovery, initRecovery, initRecoveryInProgress, getGuardians, getGuardiansData } = useRecoveryGuardian();

definePageMeta({
layout: "dashboard",
Expand Down Expand Up @@ -167,6 +171,8 @@ const RecoveryParamsSchema = z
);

const recoveryParams = ref<z.infer<typeof RecoveryParamsSchema> | null>(null);
const isAccountGuardian = ref(false);
const isSuccess = ref(false);

try {
recoveryParams.value = await RecoveryParamsSchema.parseAsync({
Expand All @@ -175,14 +181,37 @@ try {
credentialPublicKey: route.query.credentialPublicKey,
checksum: route.query.checksum,
});
await getGuardians(recoveryParams.value.accountAddress);

const recoveryStatus = await getRecovery(recoveryParams.value.accountAddress);
isSuccess.value = recoveryStatus[2] === route.query.credentialId;
} catch {
error.value = "Invalid recovery parameters. Please verify the URL and try again.";
}

const isAccountGuardian = ref(true);
const isSuccess = ref(false);
watchEffect(() => {
isAccountGuardian.value = !!(getGuardiansData.value?.find((x) => isAddressEqual(x.addr, (accountData.value.address as `0x${string}`) ?? zeroAddress))?.isReady);
});

const handleSuccess = () => {
const confirmRecoveryAction = async () => {
let origin: string | undefined = undefined;
if (!origin) {
try {
origin = window.location.origin;
} catch {
throw new Error("Can't identify expectedOrigin, please provide it manually");
}
}

// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
const passkeyPublicKey = getPublicKeyBytesFromPasskeySignature(hexToBytes(`0x${recoveryParams.value?.credentialPublicKey!}`));
const encodedPasskeyParameters = encodePasskeyModuleParameters({
passkeyPublicKey,
expectedOrigin: origin,
});
const accountId = recoveryParams.value?.credentialId || encodedPasskeyParameters;
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
await initRecovery(recoveryParams.value?.accountAddress!, encodedPasskeyParameters, accountId);
isSuccess.value = true;
};
</script>
124 changes: 101 additions & 23 deletions packages/sdk/src/abi/GuardianRecoveryModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@ export const GuardianRecoveryModuleAbi = [
{
inputs: [
{
internalType: "address",
internalType: "contract WebAuthValidator",
name: "_webAuthValidator",
type: "address",
},
{
internalType: "contract AAFactory",
name: "_aaFactory",
type: "address",
},
],
stateMutability: "nonpayable",
type: "constructor",
},
{
inputs: [],
name: "CooldownPerionNotPassed",
name: "CooldownPeriodNotPassed",
type: "error",
},
{
Expand Down Expand Up @@ -79,6 +84,19 @@ export const GuardianRecoveryModuleAbi = [
name: "RecoveryInitiated",
type: "event",
},
{
inputs: [],
name: "aaFactory",
outputs: [
{
internalType: "contract AAFactory",
name: "",
type: "address",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [
{
Expand All @@ -104,6 +122,11 @@ export const GuardianRecoveryModuleAbi = [
name: "isReady",
type: "bool",
},
{
internalType: "uint64",
name: "addedAt",
type: "uint64",
},
],
stateMutability: "view",
type: "function",
Expand All @@ -128,10 +151,32 @@ export const GuardianRecoveryModuleAbi = [
type: "function",
},
{
inputs: [],
name: "disable",
outputs: [],
stateMutability: "nonpayable",
inputs: [
{
internalType: "string",
name: "accountId",
type: "string",
},
],
name: "checkRecoveryRequest",
outputs: [
{
internalType: "address",
name: "account",
type: "address",
},
{
internalType: "bool",
name: "ready",
type: "bool",
},
{
internalType: "uint256",
name: "remainingTime",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
{
Expand Down Expand Up @@ -206,6 +251,11 @@ export const GuardianRecoveryModuleAbi = [
name: "isReady",
type: "bool",
},
{
internalType: "uint64",
name: "addedAt",
type: "uint64",
},
],
internalType: "struct GuardianRecoveryValidator.Guardian[]",
name: "",
Expand All @@ -217,44 +267,67 @@ export const GuardianRecoveryModuleAbi = [
},
{
inputs: [
{
internalType: "address",
name: "accountToRecover",
type: "address",
},
{
internalType: "bytes",
name: "initData",
name: "passkey",
type: "bytes",
},
{
internalType: "string",
name: "accountId",
type: "string",
},
],
name: "init",
name: "initRecovery",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "accountToRecover",
internalType: "contract WebAuthValidator",
name: "_webAuthValidator",
type: "address",
},
{
internalType: "contract AAFactory",
name: "_aaFactory",
type: "address",
},
],
name: "initialize",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [
{
internalType: "bytes",
name: "passkey",
name: "",
type: "bytes",
},
],
name: "initRecovery",
name: "onInstall",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "_webAuthValidator",
type: "address",
internalType: "bytes",
name: "",
type: "bytes",
},
],
name: "initialize",
name: "onUninstall",
outputs: [],
stateMutability: "nonpayable",
type: "function",
Expand All @@ -279,6 +352,11 @@ export const GuardianRecoveryModuleAbi = [
name: "timestamp",
type: "uint256",
},
{
internalType: "string",
name: "accountId",
type: "string",
},
],
stateMutability: "view",
type: "function",
Expand Down Expand Up @@ -325,19 +403,19 @@ export const GuardianRecoveryModuleAbi = [
type: "bool",
},
],
stateMutability: "view",
stateMutability: "pure",
type: "function",
},
{
inputs: [
{
internalType: "bytes32",
name: "signedHash",
name: "",
type: "bytes32",
},
{
internalType: "bytes",
name: "signature",
name: "",
type: "bytes",
},
],
Expand All @@ -349,19 +427,19 @@ export const GuardianRecoveryModuleAbi = [
type: "bool",
},
],
stateMutability: "view",
stateMutability: "pure",
type: "function",
},
{
inputs: [
{
internalType: "bytes32",
name: "signedHash",
name: "",
type: "bytes32",
},
{
internalType: "bytes",
name: "signature",
name: "",
type: "bytes",
},
{
Expand Down Expand Up @@ -468,7 +546,7 @@ export const GuardianRecoveryModuleAbi = [
name: "webAuthValidator",
outputs: [
{
internalType: "address",
internalType: "contract WebAuthValidator",
name: "",
type: "address",
},
Expand Down
Loading
Loading